diff options
author | Sebastian <sebasjm@gmail.com> | 2021-08-23 16:46:06 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2021-08-23 16:48:30 -0300 |
commit | 38acabfa6089ab8ac469c12b5f55022fb96935e5 (patch) | |
tree | 453dbf70000cc5e338b06201af1eaca8343f8f73 /preact/benches/src | |
parent | f26125e039143b92dc0d84e7775f508ab0cdcaa8 (diff) | |
download | node-vendor-master.tar.gz node-vendor-master.tar.bz2 node-vendor-master.zip |
Diffstat (limited to 'preact/benches/src')
-rw-r--r-- | preact/benches/src/02_replace1k.html | 67 | ||||
-rw-r--r-- | preact/benches/src/03_update10th1k_x16.html | 78 | ||||
-rw-r--r-- | preact/benches/src/07_create10k.html | 49 | ||||
-rw-r--r-- | preact/benches/src/filter_list.html | 92 | ||||
-rw-r--r-- | preact/benches/src/hydrate1k.html | 145 | ||||
-rw-r--r-- | preact/benches/src/keyed-children/components.js | 152 | ||||
-rw-r--r-- | preact/benches/src/keyed-children/index.js | 29 | ||||
-rw-r--r-- | preact/benches/src/keyed-children/store.js | 119 | ||||
-rw-r--r-- | preact/benches/src/many_updates.html | 112 | ||||
-rw-r--r-- | preact/benches/src/text_update.html | 34 | ||||
-rw-r--r-- | preact/benches/src/util.js | 98 |
11 files changed, 975 insertions, 0 deletions
diff --git a/preact/benches/src/02_replace1k.html b/preact/benches/src/02_replace1k.html new file mode 100644 index 0000000..e6b6270 --- /dev/null +++ b/preact/benches/src/02_replace1k.html @@ -0,0 +1,67 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>replace all rows</title> + <meta name="description" content="updating all 1,000 rows" /> + <style> + .preloadicon { + display: none; + } + .glyphicon-remove:before { + content: '⨯'; + } + </style> + </head> + <body> + <div id="main"></div> + <script type="module"> + import { + measureName, + measureMemory, + testElementText, + afterFrame, + afterFrameAsync, + markRunStart, + markRunEnd + } from './util.js'; + import * as framework from 'framework'; + import { render } from '../src/keyed-children/index.js'; + + const { run } = render(framework, document.getElementById('main')); + + async function main() { + const elementSelector = 'tr:first-child > td:first-child'; + + // MUST BE KEPT IN SYNC WITH WARMUP COUNT IN benches/scripts/config.js + const WARMUP_COUNT = 5; + for (let i = 0; i < WARMUP_COUNT; i++) { + markRunStart(`warmup-${i}`); + run(); + await markRunEnd(`warmup-${i}`); + + await afterFrameAsync(); + testElementText(elementSelector, (i * 1000 + 1).toFixed()); + } + + await afterFrameAsync(); + + afterFrame(function () { + testElementText(elementSelector, WARMUP_COUNT + '001'); + performance.mark('stop'); + performance.measure(measureName, 'start', 'stop'); + + measureMemory(); + }); + + markRunStart('final'); + performance.mark('start'); + run(); + await markRunEnd('final'); + } + + afterFrame(main); + </script> + </body> +</html> diff --git a/preact/benches/src/03_update10th1k_x16.html b/preact/benches/src/03_update10th1k_x16.html new file mode 100644 index 0000000..9de8fdb --- /dev/null +++ b/preact/benches/src/03_update10th1k_x16.html @@ -0,0 +1,78 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>partial update</title> + <meta + name="description" + content="updating every 10th row for 1,000 rows (3 warmup runs). 16x CPU slowdown." + /> + <style> + .preloadicon { + display: none; + } + .glyphicon-remove:before { + content: "⨯"; + } + </style> + </head> + <body> + <div id="main"></div> + <script type="module"> + import { + measureName, + measureMemory, + afterFrame, + afterFrameAsync, + getRowLinkSel, + testElement, + testElementTextContains + } from './util.js'; + import * as framework from 'framework'; + import { render } from '../src/keyed-children/index.js'; + + const { run: mount, update } = render( + framework, + document.getElementById('main') + ); + + function repeat(pattern, repeats) { + let result = ''; + for (let i = 0; i < repeats; i++) { + result += pattern; + } + + return result; + } + + async function init() { + mount(); + + await afterFrameAsync(); + testElement(getRowLinkSel(1000)); + + for (let i = 0; i < 3; i++) { + update(); + + await afterFrameAsync(); + testElementTextContains(getRowLinkSel(991), repeat(' !!!', i + 1)); + } + } + + async function run() { + performance.mark('start'); + update(); + + await afterFrameAsync(); + testElementTextContains(getRowLinkSel(991), repeat(' !!!', 3 + 1)); + performance.mark('stop'); + performance.measure(measureName, 'start', 'stop'); + + measureMemory(); + } + + init().then(run); + </script> + </body> +</html> diff --git a/preact/benches/src/07_create10k.html b/preact/benches/src/07_create10k.html new file mode 100644 index 0000000..728d472 --- /dev/null +++ b/preact/benches/src/07_create10k.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>create many rows</title> + <meta name="description" content="creating 10,000 rows" /> + <style> + .preloadicon { + display: none; + } + .glyphicon-remove:before { + content: '⨯'; + } + </style> + </head> + <body> + <div id="main"></div> + <script type="module"> + import { + measureName, + measureMemory, + testElementText, + afterFrame + } from './util.js'; + import * as framework from 'framework'; + import { render } from '../src/keyed-children/index.js'; + + const { runLots } = render(framework, document.getElementById('main')); + + async function main() { + const elementSelector = 'tr:last-child > td:first-child'; + + performance.mark('start'); + runLots(); + + afterFrame(() => { + testElementText(elementSelector, '10000'); + performance.mark('stop'); + performance.measure(measureName, 'start', 'stop'); + + measureMemory(); + }); + } + + main(); + </script> + </body> +</html> diff --git a/preact/benches/src/filter_list.html b/preact/benches/src/filter_list.html new file mode 100644 index 0000000..d31e1af --- /dev/null +++ b/preact/benches/src/filter_list.html @@ -0,0 +1,92 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>Patching HTML</title> + <style> + .items { + margin: 1em 0; + padding: 0; + display: flex; + flex-wrap: wrap; + gap: 2px; + } + + .items > * { + display: flex; + align-items: center; + justify-content: center; + width: 3em; + height: 2em; + margin: 0; + padding: 0; + background: #eee; + } + </style> + </head> + <body> + <div id="app"></div> + <script type="module"> + import { measureName, measureMemory } from './util.js'; + import { createRoot, createElement as h, Component } from 'framework'; + + function Row(props) { + return h('article', null, props.children); + } + + function App(props) { + return h('div', {}, [ + h( + 'div', + { class: 'items' }, + props.items.map(id => h(Row, { key: id }, id)) + ) + ]); + } + + const count = 1000; + const start = 20; + const end = 600; + + const newItems = () => + Array(count) + .fill(0) + .map((item, i) => i); + let items = newItems(); + let currentItems = items; + + const root = createRoot(document.getElementById('app')); + root.render(h(App, { items })); + + function runPatch() { + items = newItems().filter(id => { + const isVisible = currentItems.includes(id); + return id >= start && id <= end ? !isVisible : isVisible; + }); + currentItems = items; + + root.render(h(App, { items })); + } + + async function warmup() { + const count = 25; + + for (let i = 0; i < count; i++) { + runPatch(); + await new Promise(r => requestAnimationFrame(r)); + } + } + + warmup().then(async () => { + performance.mark('start'); + runPatch(); + await new Promise(r => requestAnimationFrame(r)); + performance.mark('stop'); + performance.measure(measureName, 'start', 'stop'); + + measureMemory(); + }); + </script> + </body> +</html> diff --git a/preact/benches/src/hydrate1k.html b/preact/benches/src/hydrate1k.html new file mode 100644 index 0000000..bf43448 --- /dev/null +++ b/preact/benches/src/hydrate1k.html @@ -0,0 +1,145 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>hydrate 1k table rows</title> + <meta name="description" content="hydrating 1,000 rows" /> + <style> + .preloadicon { + display: none; + } + .glyphicon-remove:before { + content: '⨯'; + } + </style> + </head> + <body> + <template id="template"></template> + <script type="module"> + import { + measureName, + measureMemory, + testElementText, + afterFrame, + afterFrameAsync + } from './util.js'; + import * as framework from 'framework'; + import { getComponents } from '../src/keyed-children/components.js'; + import { Store } from '../src/keyed-children/store.js'; + + /** @type {HTMLTemplateElement} */ + const template = document.getElementById('template'); + const { Main } = getComponents(framework); + const { createRoot, createElement } = framework; + + const firstRowSel = 'tr:first-child > td:first-child'; + const lastRowSel = 'tr:last-child > td:first-child'; + + const baseStore = new Store(); + baseStore.run(); + + /** + * Delete the old hydrate root and create a new one with a clone of the + * template's content + */ + function setupHydrateRoot() { + const hydrateRootId = 'hydrate-root'; + let hydrateRoot = document.getElementById(hydrateRootId); + if (hydrateRoot) { + hydrateRoot.remove(); + } + + hydrateRoot = document.createElement('div'); + hydrateRoot.id = hydrateRootId; + hydrateRoot.appendChild(template.content.cloneNode(true)); + document.body.appendChild(hydrateRoot); + return hydrateRoot; + } + + /** Render the app inside the template tag */ + async function initializeTemplate() { + // Initialize template + createRoot(template.content).render( + createElement(Main, { store: baseStore }) + ); + await afterFrameAsync(); + } + + /** + * Click the second row's remove link and ensure the number of rows before + * and after the click are as expected + */ + async function clickRemove(root, label, expectedBefore, expectedAfter) { + let rowCount = root.querySelectorAll('tr').length; + if (rowCount !== expectedBefore) { + throw new Error( + `${label}: Incorrect number of rows before remove click. Expected ${expectedBefore} but got ${rowCount}` + ); + } + + const removeLink = root.querySelector( + 'tr:nth-child(2) td:nth-child(3) a' + ); + removeLink.click(); + await afterFrameAsync(); + + rowCount = root.querySelectorAll('tr').length; + if (rowCount !== expectedAfter) { + throw new Error( + `${label}: Incorrect number of rows after after remove click. Expected ${expectedAfter} but got ${rowCount}` + ); + } + } + + async function warmupRun(i) { + // Test out hydrate and ensure it works + const hydrateRoot = setupHydrateRoot(); + + // Verify initial hydrate root isn't already hydrated and is static + testElementText(firstRowSel, '1'); + testElementText(lastRowSel, '1000'); + await clickRemove(hydrateRoot, `WARMUP ${i} - prehydrate`, 1000, 1000); + + const store = new Store(); + store.data = baseStore.data.slice(); + createRoot(hydrateRoot).hydrate(createElement(Main, { store })); + + // Verify hydrate has correct markup and is properly hydrated + testElementText(firstRowSel, '1'); + testElementText(lastRowSel, '1000'); + await clickRemove(hydrateRoot, `WARMUP ${i} - posthydrate`, 1000, 999); + } + + function timedRun() { + afterFrame(function () { + performance.mark('stop'); + performance.measure(measureName, 'start', 'stop'); + + measureMemory(); + }); + + const hydrateRoot = setupHydrateRoot(); + const store = new Store(); + store.data = baseStore.data.slice(); + + performance.mark('start'); + createRoot(hydrateRoot).hydrate(createElement(Main, { store })); + } + + async function main() { + // const WARMUP_COUNT = 5; + const WARMUP_COUNT = 5; + for (let i = 0; i < WARMUP_COUNT; i++) { + await warmupRun(i); + } + + await afterFrameAsync(); + + timedRun(); + } + + initializeTemplate().then(main); + </script> + </body> +</html> diff --git a/preact/benches/src/keyed-children/components.js b/preact/benches/src/keyed-children/components.js new file mode 100644 index 0000000..0960b89 --- /dev/null +++ b/preact/benches/src/keyed-children/components.js @@ -0,0 +1,152 @@ +import { Store } from './store.js'; + +/** + * @param {import('./index').Framework} framework + */ +export function getComponents({ createElement, Component }) { + class Row extends Component { + constructor(props) { + super(props); + this.onDelete = this.onDelete.bind(this); + this.onClick = this.onClick.bind(this); + } + + shouldComponentUpdate(nextProps, nextState) { + return ( + nextProps.data !== this.props.data || + nextProps.styleClass !== this.props.styleClass + ); + } + + onDelete() { + this.props.onDelete(this.props.data.id); + } + + onClick() { + this.props.onClick(this.props.data.id); + } + + render() { + let { styleClass, onClick, onDelete, data } = this.props; + return createElement( + 'tr', + { + className: styleClass + }, + createElement( + 'td', + { + className: 'col-md-1' + }, + data.id + ), + createElement( + 'td', + { + className: 'col-md-4' + }, + createElement( + 'a', + { + onClick: this.onClick + }, + data.label + ) + ), + createElement( + 'td', + { + className: 'col-md-1' + }, + createElement( + 'a', + { + onClick: this.onDelete + }, + createElement('span', { + className: 'glyphicon glyphicon-remove', + 'aria-hidden': 'true' + }) + ) + ), + createElement('td', { + className: 'col-md-6' + }) + ); + } + } + + class Main extends Component { + constructor(props) { + super(props); + this.state = { store: props.store ?? new Store() }; + this.select = this.select.bind(this); + this.delete = this.delete.bind(this); + + // @ts-ignore + window.app = this; + } + run() { + this.state.store.run(); + this.setState({ store: this.state.store }); + } + add() { + this.state.store.add(); + this.setState({ store: this.state.store }); + } + update() { + this.state.store.update(); + this.setState({ store: this.state.store }); + } + select(id) { + this.state.store.select(id); + this.setState({ store: this.state.store }); + } + delete(id) { + this.state.store.delete(id); + this.setState({ store: this.state.store }); + } + runLots() { + this.state.store.runLots(); + this.setState({ store: this.state.store }); + } + clear() { + this.state.store.clear(); + this.setState({ store: this.state.store }); + } + swapRows() { + this.state.store.swapRows(); + this.setState({ store: this.state.store }); + } + render() { + let rows = this.state.store.data.map((d, i) => { + return createElement(Row, { + key: d.id, + data: d, + onClick: this.select, + onDelete: this.delete, + styleClass: d.id === this.state.store.selected ? 'danger' : '' + }); + }); + return createElement( + 'div', + { + className: 'container' + }, + createElement( + 'table', + { + className: 'table table-hover table-striped test-data' + }, + createElement('tbody', {}, rows) + ), + createElement('span', { + className: 'preloadicon glyphicon glyphicon-remove', + 'aria-hidden': 'true' + }) + ); + } + } + + return { Main, Row }; +} diff --git a/preact/benches/src/keyed-children/index.js b/preact/benches/src/keyed-children/index.js new file mode 100644 index 0000000..ece1265 --- /dev/null +++ b/preact/benches/src/keyed-children/index.js @@ -0,0 +1,29 @@ +import { getComponents } from './components.js'; + +/** + * @typedef Framework + * @property {(type: any, props?: any, ...children: any) => JSX.Element} createElement + * @property {(root: HTMLElement) => ({ render(vnode: JSX.Element): void; hydrate(vnode: JSX.Element): void; })} createRoot + * @property {any} Component + * + * @param {Framework} framework + * @param {HTMLElement} rootDom + */ +export function render(framework, rootDom) { + const { Main } = getComponents(framework); + framework.createRoot(rootDom).render(framework.createElement(Main)); + + /** @type {Main} */ + // @ts-ignore + const app = window.app; + return { + run: app.run.bind(app), + add: app.add.bind(app), + update: app.update.bind(app), + select: app.select.bind(app), + delete: app.delete.bind(app), + runLots: app.runLots.bind(app), + clear: app.clear.bind(app), + swapRows: app.swapRows.bind(app) + }; +} diff --git a/preact/benches/src/keyed-children/store.js b/preact/benches/src/keyed-children/store.js new file mode 100644 index 0000000..413208d --- /dev/null +++ b/preact/benches/src/keyed-children/store.js @@ -0,0 +1,119 @@ +function _random(max) { + return Math.round(Math.random() * 1000) % max; +} + +export class Store { + constructor() { + this.data = []; + this.selected = undefined; + this.id = 1; + } + buildData(count = 1000) { + var adjectives = [ + 'pretty', + 'large', + 'big', + 'small', + 'tall', + 'short', + 'long', + 'handsome', + 'plain', + 'quaint', + 'clean', + 'elegant', + 'easy', + 'angry', + 'crazy', + 'helpful', + 'mushy', + 'odd', + 'unsightly', + 'adorable', + 'important', + 'inexpensive', + 'cheap', + 'expensive', + 'fancy' + ]; + var colours = [ + 'red', + 'yellow', + 'blue', + 'green', + 'pink', + 'brown', + 'purple', + 'brown', + 'white', + 'black', + 'orange' + ]; + var nouns = [ + 'table', + 'chair', + 'house', + 'bbq', + 'desk', + 'car', + 'pony', + 'cookie', + 'sandwich', + 'burger', + 'pizza', + 'mouse', + 'keyboard' + ]; + var data = []; + for (var i = 0; i < count; i++) + data.push({ + id: this.id++, + label: + adjectives[_random(adjectives.length)] + + ' ' + + colours[_random(colours.length)] + + ' ' + + nouns[_random(nouns.length)] + }); + return data; + } + updateData(mod = 10) { + for (let i = 0; i < this.data.length; i += 10) { + this.data[i] = Object.assign({}, this.data[i], { + label: this.data[i].label + ' !!!' + }); + } + } + delete(id) { + var idx = this.data.findIndex(d => d.id === id); + this.data.splice(idx, 1); + } + run() { + this.data = this.buildData(); + this.selected = undefined; + } + add() { + this.data = this.data.concat(this.buildData(1000)); + } + update() { + this.updateData(); + } + select(id) { + this.selected = id; + } + runLots() { + this.data = this.buildData(10000); + this.selected = undefined; + } + clear() { + this.data = []; + this.selected = undefined; + } + swapRows() { + if (this.data.length > 998) { + var a = this.data[1]; + this.data[1] = this.data[998]; + this.data[998] = a; + } + } +} diff --git a/preact/benches/src/many_updates.html b/preact/benches/src/many_updates.html new file mode 100644 index 0000000..e59b304 --- /dev/null +++ b/preact/benches/src/many_updates.html @@ -0,0 +1,112 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>Patching HTML</title> + <style> + .hello { + color: red; + } + + .bye { + color: blue; + } + </style> + </head> + <body> + <div id="root"></div> + <script type="module"> + import { measureName, measureMemory } from './util.js'; + import { createRoot, createElement as h } from 'framework'; + + const state = { + msg: 'hello', + list: new Array(1000).fill(0).map((_, i) => ({ + i, + text: 'foobar' + i + })) + }; + + let counter = 0; + function App() { + return h( + 'div', + { id: 'app' }, + h('p', null, '> ', ++counter, ' <'), + h('p', null, state.msg), + ...state.list.map((obj, i) => + h( + 'div', + { key: i, title: state.msg + i }, + h('span', { className: state.msg }, obj.text), + h('span', { className: 'baz' }, 'one'), + h('span', { className: 'qux' }, 'two'), + h( + 'div', + null, + h('span', { className: 'qux' }, 'three'), + h('span', { className: 'qux' }, 'four'), + h('span', { className: 'baz' }, 'five'), + h( + 'div', + null, + h('span', { className: 'qux' }, 'six'), + h('span', { className: 'baz' }, 'seven'), + h('span', { className: state.msg }, 'eight') + ) + ) + ) + ) + ); + } + + const root = createRoot(document.getElementById('root')); + + // const p = performance.now(); + root.render(h(App)); + // console.log(`mount: ${(performance.now() - p).toFixed(2)}ms`); + + // const patchResults = []; + + function runPatch() { + // const s = performance.now(); + state.msg = state.msg === 'hello' ? 'bye' : 'hello'; + state.list[0].text = state.msg; + root.render(h(App)); + // patchResults.push(performance.now() - s); + } + + async function warmup() { + // const count = 100; + const count = 25; + + for (let i = 0; i < count; i++) { + runPatch(); + await new Promise(r => requestAnimationFrame(r)); + } + + // let fastest = Infinity; + // const total = patchResults.reduce((all, cur) => { + // if (cur < fastest) { + // fastest = cur; + // } + // return all + cur; + // }, 0); + + // console.log(`${count} runs average: ${(total / count).toFixed(2)}ms`); + // console.log(`fastest run: ${fastest.toFixed(2)}ms`); + } + + warmup().then(async () => { + performance.mark('start'); + runPatch(); + await new Promise(r => requestAnimationFrame(r)); + performance.mark('stop'); + performance.measure(measureName, 'start', 'stop'); + + measureMemory(); + }); + </script> + </body> +</html> diff --git a/preact/benches/src/text_update.html b/preact/benches/src/text_update.html new file mode 100644 index 0000000..6874558 --- /dev/null +++ b/preact/benches/src/text_update.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>Text Updates</title> + </head> + <body> + <div id="root"></div> + <script type="module"> + import { measureName, measureMemory } from './util.js'; + import { createRoot, createElement } from 'framework'; + + const root = createRoot(document.getElementById('root')); + + function component({ randomValue }) { + return createElement('div', {}, [ + createElement('h2', {}, 'Test ' + randomValue), + createElement('h1', {}, `===${randomValue}===`) + ]); + } + + let result; + + performance.mark('start'); + for (let i = 0; i < 100; i++) { + root.render(createElement(component, { randomValue: i })); + } + performance.mark('stop'); + performance.measure(measureName, 'start', 'stop'); + measureMemory(); + </script> + </body> +</html> diff --git a/preact/benches/src/util.js b/preact/benches/src/util.js new file mode 100644 index 0000000..64c4765 --- /dev/null +++ b/preact/benches/src/util.js @@ -0,0 +1,98 @@ +// import afterFrame from "../node_modules/afterframe/dist/afterframe.module.js"; +import afterFrame from 'afterframe'; + +export { afterFrame }; + +export const measureName = 'duration'; + +let promise = null; +export function afterFrameAsync() { + if (promise === null) { + promise = new Promise(resolve => + afterFrame(time => { + promise = null; + resolve(time); + }) + ); + } + + return promise; +} + +export function measureMemory() { + if ('gc' in window && 'memory' in performance) { + // Report results in MBs + window.gc(); + window.usedJSHeapSize = performance.memory.usedJSHeapSize / 1e6; + } else { + window.usedJSHeapSize = 0; + } +} + +export function markRunStart(runId) { + performance.mark(`run-${runId}-start`); +} + +let staticPromise = Promise.resolve(); +export function markRunEnd(runId) { + return staticPromise.then(() => { + performance.mark(`run-${runId}-end`); + performance.measure( + `run-${runId}`, + `run-${runId}-start`, + `run-${runId}-end` + ); + }); +} + +export function getRowIdSel(index) { + return `tbody > tr:nth-child(${index}) > td:first-child`; +} + +export function getRowLinkSel(index) { + return `tbody > tr:nth-child(${index}) > td:nth-child(2) > a`; +} + +/** + * @param {string} selector + * @returns {Element} + */ +export function getBySelector(selector) { + const element = document.querySelector(selector); + if (element == null) { + throw new Error(`Could not find element matching selector: ${selector}`); + } + + return element; +} + +export function testElement(selector) { + const testElement = document.querySelector(selector); + if (testElement == null) { + throw new Error( + 'Test failed. Rendering after one paint was not successful' + ); + } +} + +export function testElementText(selector, expectedText) { + const elm = document.querySelector(selector); + if (elm == null) { + throw new Error('Could not find element matching selector: ' + selector); + } + + if (elm.textContent != expectedText) { + throw new Error( + `Element did not have expected text. Expected: '${expectedText}' Actual: '${elm.textContent}'` + ); + } +} + +export function testElementTextContains(selector, expectedText) { + const elm = getBySelector(selector); + if (!elm.textContent.includes(expectedText)) { + throw new Error( + `Element did not include expected text. Expected to include: '${expectedText}' Actual: '${elm.textContent}'` + ); + } +} |