diff --git a/resources/shared/todomvc-utils.mjs b/resources/shared/todomvc-utils.mjs new file mode 100644 index 000000000..dca451575 --- /dev/null +++ b/resources/shared/todomvc-utils.mjs @@ -0,0 +1 @@ +export const numberOfItemsToAdd = 100; diff --git a/resources/translations.mjs b/resources/shared/translations.mjs similarity index 99% rename from resources/translations.mjs rename to resources/shared/translations.mjs index 3de1264e2..a8ce5ddbe 100644 --- a/resources/translations.mjs +++ b/resources/shared/translations.mjs @@ -724,3 +724,11 @@ export const defaultTodoText = { ru: "Кое-что сделать", emoji: "Something to do 😊", }; + +export const defaultLanguage = "en"; + +export function getTodoText(lang = "en", index) { + const todosSelection = todos[lang]; + const currentIndex = index % todosSelection.length; + return todosSelection[currentIndex]; +} diff --git a/resources/tests.mjs b/resources/tests.mjs index d7cf6794a..ba50acfc9 100644 --- a/resources/tests.mjs +++ b/resources/tests.mjs @@ -1,14 +1,6 @@ import { BenchmarkTestStep } from "./benchmark-runner.mjs"; -import { todos } from "./translations.mjs"; - -const numberOfItemsToAdd = 100; -const defaultLanguage = "en"; - -function getTodoText(lang, index) { - const todosSelection = todos[lang] ?? todos[defaultLanguage]; - const currentIndex = index % todosSelection.length; - return todosSelection[currentIndex]; -} +import { getTodoText, defaultLanguage } from "./shared/translations.mjs"; +import { numberOfItemsToAdd } from "./shared/todomvc-utils.mjs"; export const Suites = []; @@ -274,6 +266,18 @@ Suites.push({ ], }); +Suites.push({ + name: "TodoMVC-WebComponents-PostMessage", + url: "resources/todomvc/vanilla-examples/javascript-web-components/dist/index.html", + tags: ["experimental", "todomvc", "webcomponents"], + disabled: true, + async prepare() {}, + type: "remote", + /* config: { + name: "default", // optional param to target non-default tests locally + }, */ +}); + Suites.push({ name: "TodoMVC-WebComponents-Complex-DOM", url: "resources/todomvc/vanilla-examples/javascript-web-components-complex/dist/index.html", diff --git a/resources/todomvc/vanilla-examples/javascript-web-components/dist/index.html b/resources/todomvc/vanilla-examples/javascript-web-components/dist/index.html index a8fec9787..2a38dbaac 100644 --- a/resources/todomvc/vanilla-examples/javascript-web-components/dist/index.html +++ b/resources/todomvc/vanilla-examples/javascript-web-components/dist/index.html @@ -9,10 +9,10 @@ - - - - + + + + @@ -26,5 +26,6 @@ + diff --git a/resources/todomvc/vanilla-examples/javascript-web-components/dist/components/todo-app/todo-app.component.js b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/components/todo-app/todo-app.component.js similarity index 95% rename from resources/todomvc/vanilla-examples/javascript-web-components/dist/components/todo-app/todo-app.component.js rename to resources/todomvc/vanilla-examples/javascript-web-components/dist/src/components/todo-app/todo-app.component.js index 877d0c29d..52be978d1 100644 --- a/resources/todomvc/vanilla-examples/javascript-web-components/dist/components/todo-app/todo-app.component.js +++ b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/components/todo-app/todo-app.component.js @@ -1,9 +1,9 @@ import template from "./todo-app.template.js"; import { useRouter } from "../../hooks/useRouter.js"; -import globalStyles from "../../styles/global.constructable.js"; -import appStyles from "../../styles/app.constructable.js"; -import mainStyles from "../../styles/main.constructable.js"; +import globalStyles from "../../../styles/global.constructable.js"; +import appStyles from "../../../styles/app.constructable.js"; +import mainStyles from "../../../styles/main.constructable.js"; class TodoApp extends HTMLElement { #isReady = false; #data = []; diff --git a/resources/todomvc/vanilla-examples/javascript-web-components/dist/components/todo-app/todo-app.template.js b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/components/todo-app/todo-app.template.js similarity index 100% rename from resources/todomvc/vanilla-examples/javascript-web-components/dist/components/todo-app/todo-app.template.js rename to resources/todomvc/vanilla-examples/javascript-web-components/dist/src/components/todo-app/todo-app.template.js diff --git a/resources/todomvc/vanilla-examples/javascript-web-components/dist/components/todo-bottombar/todo-bottombar.component.js b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/components/todo-bottombar/todo-bottombar.component.js similarity index 94% rename from resources/todomvc/vanilla-examples/javascript-web-components/dist/components/todo-bottombar/todo-bottombar.component.js rename to resources/todomvc/vanilla-examples/javascript-web-components/dist/src/components/todo-bottombar/todo-bottombar.component.js index cc0203c48..b4ec1c368 100644 --- a/resources/todomvc/vanilla-examples/javascript-web-components/dist/components/todo-bottombar/todo-bottombar.component.js +++ b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/components/todo-bottombar/todo-bottombar.component.js @@ -1,7 +1,7 @@ import template from "./todo-bottombar.template.js"; -import globalStyles from "../../styles/global.constructable.js"; -import bottombarStyles from "../../styles/bottombar.constructable.js"; +import globalStyles from "../../../styles/global.constructable.js"; +import bottombarStyles from "../../../styles/bottombar.constructable.js"; class TodoBottombar extends HTMLElement { static get observedAttributes() { diff --git a/resources/todomvc/vanilla-examples/javascript-web-components/dist/components/todo-bottombar/todo-bottombar.template.js b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/components/todo-bottombar/todo-bottombar.template.js similarity index 100% rename from resources/todomvc/vanilla-examples/javascript-web-components/dist/components/todo-bottombar/todo-bottombar.template.js rename to resources/todomvc/vanilla-examples/javascript-web-components/dist/src/components/todo-bottombar/todo-bottombar.template.js diff --git a/resources/todomvc/vanilla-examples/javascript-web-components/dist/components/todo-item/todo-item.component.js b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/components/todo-item/todo-item.component.js similarity index 97% rename from resources/todomvc/vanilla-examples/javascript-web-components/dist/components/todo-item/todo-item.component.js rename to resources/todomvc/vanilla-examples/javascript-web-components/dist/src/components/todo-item/todo-item.component.js index a082628a8..aa0302d5d 100644 --- a/resources/todomvc/vanilla-examples/javascript-web-components/dist/components/todo-item/todo-item.component.js +++ b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/components/todo-item/todo-item.component.js @@ -2,8 +2,8 @@ import template from "./todo-item.template.js"; import { useDoubleClick } from "../../hooks/useDoubleClick.js"; import { useKeyListener } from "../../hooks/useKeyListener.js"; -import globalStyles from "../../styles/global.constructable.js"; -import itemStyles from "../../styles/todo-item.constructable.js"; +import globalStyles from "../../../styles/global.constructable.js"; +import itemStyles from "../../../styles/todo-item.constructable.js"; class TodoItem extends HTMLElement { static get observedAttributes() { diff --git a/resources/todomvc/vanilla-examples/javascript-web-components/dist/components/todo-item/todo-item.template.js b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/components/todo-item/todo-item.template.js similarity index 100% rename from resources/todomvc/vanilla-examples/javascript-web-components/dist/components/todo-item/todo-item.template.js rename to resources/todomvc/vanilla-examples/javascript-web-components/dist/src/components/todo-item/todo-item.template.js diff --git a/resources/todomvc/vanilla-examples/javascript-web-components/dist/components/todo-list/todo-list.component.js b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/components/todo-list/todo-list.component.js similarity index 96% rename from resources/todomvc/vanilla-examples/javascript-web-components/dist/components/todo-list/todo-list.component.js rename to resources/todomvc/vanilla-examples/javascript-web-components/dist/src/components/todo-list/todo-list.component.js index 8e90e291f..be2efa915 100644 --- a/resources/todomvc/vanilla-examples/javascript-web-components/dist/components/todo-list/todo-list.component.js +++ b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/components/todo-list/todo-list.component.js @@ -1,8 +1,8 @@ import template from "./todo-list.template.js"; import TodoItem from "../todo-item/todo-item.component.js"; -import globalStyles from "../../styles/global.constructable.js"; -import listStyles from "../../styles/todo-list.constructable.js"; +import globalStyles from "../../../styles/global.constructable.js"; +import listStyles from "../../../styles/todo-list.constructable.js"; class TodoList extends HTMLElement { static get observedAttributes() { diff --git a/resources/todomvc/vanilla-examples/javascript-web-components/dist/components/todo-list/todo-list.template.js b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/components/todo-list/todo-list.template.js similarity index 100% rename from resources/todomvc/vanilla-examples/javascript-web-components/dist/components/todo-list/todo-list.template.js rename to resources/todomvc/vanilla-examples/javascript-web-components/dist/src/components/todo-list/todo-list.template.js diff --git a/resources/todomvc/vanilla-examples/javascript-web-components/dist/components/todo-topbar/todo-topbar.component.js b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/components/todo-topbar/todo-topbar.component.js similarity index 96% rename from resources/todomvc/vanilla-examples/javascript-web-components/dist/components/todo-topbar/todo-topbar.component.js rename to resources/todomvc/vanilla-examples/javascript-web-components/dist/src/components/todo-topbar/todo-topbar.component.js index f024d7ca1..390d195b5 100644 --- a/resources/todomvc/vanilla-examples/javascript-web-components/dist/components/todo-topbar/todo-topbar.component.js +++ b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/components/todo-topbar/todo-topbar.component.js @@ -2,8 +2,8 @@ import template from "./todo-topbar.template.js"; import { useKeyListener } from "../../hooks/useKeyListener.js"; import { nanoid } from "../../utils/nanoid.js"; -import globalStyles from "../../styles/global.constructable.js"; -import topbarStyles from "../../styles/topbar.constructable.js"; +import globalStyles from "../../../styles/global.constructable.js"; +import topbarStyles from "../../../styles/topbar.constructable.js"; class TodoTopbar extends HTMLElement { static get observedAttributes() { diff --git a/resources/todomvc/vanilla-examples/javascript-web-components/dist/components/todo-topbar/todo-topbar.template.js b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/components/todo-topbar/todo-topbar.template.js similarity index 100% rename from resources/todomvc/vanilla-examples/javascript-web-components/dist/components/todo-topbar/todo-topbar.template.js rename to resources/todomvc/vanilla-examples/javascript-web-components/dist/src/components/todo-topbar/todo-topbar.template.js diff --git a/resources/todomvc/vanilla-examples/javascript-web-components/dist/hooks/useDoubleClick.js b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/hooks/useDoubleClick.js similarity index 100% rename from resources/todomvc/vanilla-examples/javascript-web-components/dist/hooks/useDoubleClick.js rename to resources/todomvc/vanilla-examples/javascript-web-components/dist/src/hooks/useDoubleClick.js diff --git a/resources/todomvc/vanilla-examples/javascript-web-components/dist/hooks/useKeyListener.js b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/hooks/useKeyListener.js similarity index 100% rename from resources/todomvc/vanilla-examples/javascript-web-components/dist/hooks/useKeyListener.js rename to resources/todomvc/vanilla-examples/javascript-web-components/dist/src/hooks/useKeyListener.js diff --git a/resources/todomvc/vanilla-examples/javascript-web-components/dist/hooks/useRouter.js b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/hooks/useRouter.js similarity index 100% rename from resources/todomvc/vanilla-examples/javascript-web-components/dist/hooks/useRouter.js rename to resources/todomvc/vanilla-examples/javascript-web-components/dist/src/hooks/useRouter.js diff --git a/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/index.mjs b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/index.mjs new file mode 100644 index 000000000..b57b46c22 --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/index.mjs @@ -0,0 +1,10 @@ +import { BenchmarkConnector } from "./speedometer-utils/benchmark.mjs"; +import suites, { appName, appVersion } from "./workload-test.mjs"; + +/* +Paste below into dev console for manual testing: +window.addEventListener("message", (event) => console.log(event.data)); +window.postMessage({ id: "todomvc-postmessage-1.0.0", key: "benchmark-connector", type: "benchmark-suite", name: "default" }, "*"); +*/ +const benchmarkConnector = new BenchmarkConnector(suites, appName, appVersion); +benchmarkConnector.connect(); diff --git a/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/speedometer-utils/benchmark.mjs b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/speedometer-utils/benchmark.mjs new file mode 100644 index 000000000..b691e86ac --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/speedometer-utils/benchmark.mjs @@ -0,0 +1,127 @@ +/* eslint-disable no-case-declarations */ +import { TestRunner } from "./test-runner.mjs"; +import { Params } from "./params.mjs"; + +/** + * BenchmarkStep + * + * A single test step, with a common interface to interact with. + */ +export class BenchmarkStep { + constructor(name, run) { + this.name = name; + this.run = run; + } + + async runAndRecord(params, suite, test, callback) { + const testRunner = new TestRunner(null, null, params, suite, test, callback); + const result = await testRunner.runTest(); + return result; + } +} + +/** + * BenchmarkSuite + * + * A single test suite that contains one or more test steps. + */ +export class BenchmarkSuite { + constructor(name, tests) { + this.name = name; + this.tests = tests; + } + + record(_test, syncTime, asyncTime) { + const total = syncTime + asyncTime; + const results = { + tests: { Sync: syncTime, Async: asyncTime }, + total: total, + }; + + return results; + } + + async runAndRecord(params, onProgress) { + const measuredValues = { + tests: {}, + total: 0, + }; + const suiteStartLabel = `suite-${this.name}-start`; + const suiteEndLabel = `suite-${this.name}-end`; + + performance.mark(suiteStartLabel); + + for (const test of this.tests) { + const result = await test.runAndRecord(params, this, test, this.record); + measuredValues.tests[test.name] = result; + measuredValues.total += result.total; + onProgress?.(test.name); + } + + performance.mark(suiteEndLabel); + performance.measure(`suite-${this.name}`, suiteStartLabel, suiteEndLabel); + + return { + type: "suite-tests-complete", + status: "success", + result: measuredValues, + suitename: this.name, + }; + } +} + +/** ********************************************************************** + * BenchmarkConnector + * + * postMessage is used to communicate between app and benchmark. + * When the app is ready, an 'app-ready' message is sent to signal that the app can receive instructions. + * + * A prepare script within the apps appends window.name and window.version from the package.json file. + * The appId is build by appending name-version + * It's used as an additional safe-guard to ensure the correct app responds to a message. + *************************************************************************/ +export class BenchmarkConnector { + constructor(suites, name, version) { + this.suites = suites; + this.name = name; + this.version = version; + + if (!name || !version) + console.warn("No name or version supplied, to create a unique appId"); + + this.appId = name && version ? `${name}-${version}` : -1; + this.onMessage = this.onMessage.bind(this); + } + + async onMessage(event) { + if (event.data.id !== this.appId || event.data.key !== "benchmark-connector") + return; + + switch (event.data.type) { + case "benchmark-suite": + const params = new Params(new URLSearchParams(window.location.search)); + const suite = this.suites[event.data.name]; + if (!suite) + console.error(`Suite with the name of "${event.data.name}" not found!`); + const { result } = await suite.runAndRecord(params, (test) => this.sendMessage({ type: "step-complete", status: "success", appId: this.appId, name: this.name, test })); + this.sendMessage({ type: "suite-complete", status: "success", appId: this.appId, result }); + this.disconnect(); + break; + default: + console.error(`Message data type not supported: ${event.data.type}`); + } + } + + sendMessage(message) { + window.top.postMessage(message, "*"); + } + + connect() { + window.addEventListener("message", this.onMessage); + this.sendMessage({ type: "app-ready", status: "success", appId: this.appId }); + } + + disconnect() { + window.removeEventListener("message", this.onMessage); + } +} diff --git a/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/speedometer-utils/helpers.mjs b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/speedometer-utils/helpers.mjs new file mode 100644 index 000000000..729d5f3a4 --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/speedometer-utils/helpers.mjs @@ -0,0 +1,30 @@ +/** + * Helper Methods + * + * Various methods that are extracted from the Page class. + */ +export function getParent(lookupStartNode, path) { + lookupStartNode = lookupStartNode.shadowRoot ?? lookupStartNode; + const parent = path.reduce((root, selector) => { + const node = root.querySelector(selector); + return node.shadowRoot ?? node; + }, lookupStartNode); + + return parent; +} + +export function getElement(selector, path = [], lookupStartNode = document) { + const element = getParent(lookupStartNode, path).querySelector(selector); + return element; +} + +export function getAllElements(selector, path = [], lookupStartNode = document) { + const elements = Array.from(getParent(lookupStartNode, path).querySelectorAll(selector)); + return elements; +} + +export function forceLayout() { + const rect = document.body.getBoundingClientRect(); + const e = document.elementFromPoint((rect.width / 2) | 0, (rect.height / 2) | 0); + return e; +} diff --git a/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/speedometer-utils/params.mjs b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/speedometer-utils/params.mjs new file mode 100644 index 000000000..701222d97 --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/speedometer-utils/params.mjs @@ -0,0 +1,195 @@ +export class Params { + viewport = { + width: 800, + height: 600, + }; + // Enable a detailed developer menu to change the current Params. + developerMode = false; + startAutomatically = false; + iterationCount = 10; + suites = []; + // A list of tags to filter suites + tags = []; + // Toggle running a dummy suite once before the normal test suites. + useWarmupSuite = false; + // toggle async type vs default raf type. + useAsyncSteps = false; + // Change how a test measurement is triggered and async time is measured: + // "timer": The classic (as in Speedometer 2.x) way using setTimeout + // "raf": Using rAF callbacks, both for triggering the sync part and for measuring async time. + measurementMethod = "raf"; + // Wait time before the sync step in ms. + waitBeforeSync = 0; + // Warmup time before the sync step in ms. + warmupBeforeSync = 0; + // Seed for shuffling the execution order of suites. + // "off": do not shuffle + // "generate": generate a random seed + // : use the provided integer as a seed + shuffleSeed = "off"; + + constructor(searchParams = undefined) { + if (searchParams) + this._copyFromSearchParams(searchParams); + if (!this.developerMode) { + Object.freeze(this.viewport); + Object.freeze(this); + } + } + + _parseInt(value, errorMessage) { + const number = Number(value); + if (!Number.isInteger(number) && errorMessage) + throw new Error(`Invalid ${errorMessage} param: '${value}', expected int.`); + return parseInt(number); + } + + _copyFromSearchParams(searchParams) { + this.viewport = this._parseViewport(searchParams); + this.startAutomatically = this._parseBooleanParam(searchParams, "startAutomatically"); + this.iterationCount = this._parseIntParam(searchParams, "iterationCount", 1); + this.suites = this._parseSuites(searchParams); + this.tags = this._parseTags(searchParams); + this.developerMode = this._parseBooleanParam(searchParams, "developerMode"); + this.useWarmupSuite = this._parseBooleanParam(searchParams, "useWarmupSuite"); + this.useAsyncSteps = this._parseBooleanParam(searchParams, "useAsyncSteps"); + this.waitBeforeSync = this._parseIntParam(searchParams, "waitBeforeSync", 0); + this.warmupBeforeSync = this._parseIntParam(searchParams, "warmupBeforeSync", 0); + this.measurementMethod = this._parseMeasurementMethod(searchParams); + this.shuffleSeed = this._parseShuffleSeed(searchParams); + + const unused = Array.from(searchParams.keys()); + if (unused.length > 0) + console.error("Got unused search params", unused); + } + + _parseBooleanParam(searchParams, paramKey) { + if (!searchParams.has(paramKey)) + return false; + searchParams.delete(paramKey); + return true; + } + + _parseIntParam(searchParams, paramKey, minValue) { + if (!searchParams.has(paramKey)) + return defaultParams[paramKey]; + + const parsedValue = this._parseInt(searchParams.get(paramKey), "waitBeforeSync"); + if (parsedValue < minValue) + throw new Error(`Invalid ${paramKey} param: '${parsedValue}', value must be >= ${minValue}.`); + searchParams.delete(paramKey); + return parsedValue; + } + + _parseViewport(searchParams) { + if (!searchParams.has("viewport")) + return defaultParams.viewport; + const viewportParam = searchParams.get("viewport"); + const [width, height] = viewportParam.split("x"); + const viewport = { + width: this._parseInt(width, "viewport.width"), + height: this._parseInt(height, "viewport.height"), + }; + if (this.viewport.width < 800 || this.viewport.height < 600) + throw new Error(`Invalid viewport param: ${viewportParam}`); + searchParams.delete("viewport"); + return viewport; + } + + _parseSuites(searchParams) { + if (searchParams.has("suite") || searchParams.has("suites")) { + if (searchParams.has("suite") && searchParams.has("suites")) + throw new Error("Params 'suite' and 'suites' can not be used together."); + const value = searchParams.get("suite") || searchParams.get("suites"); + const suites = value.split(","); + if (suites.length === 0) + throw new Error("No suites selected"); + searchParams.delete("suite"); + searchParams.delete("suites"); + return suites; + } + return defaultParams.suites; + } + + _parseTags(searchParams) { + if (!searchParams.has("tags")) + return defaultParams.tags; + if (this.suites.length) + throw new Error("'suites' and 'tags' cannot be used together."); + const tags = searchParams.get("tags").split(","); + searchParams.delete("tags"); + return tags; + } + + _parseMeasurementMethod(searchParams) { + if (!searchParams.has("measurementMethod")) + return defaultParams.measurementMethod; + const measurementMethod = searchParams.get("measurementMethod"); + if (measurementMethod !== "raf") + throw new Error(`Invalid measurement method: '${measurementMethod}', must be 'raf'.`); + searchParams.delete("measurementMethod"); + return measurementMethod; + } + + _parseShuffleSeed(searchParams) { + if (!searchParams.has("shuffleSeed")) + return defaultParams.shuffleSeed; + let shuffleSeed = searchParams.get("shuffleSeed"); + if (shuffleSeed !== "off") { + if (shuffleSeed === "generate") { + shuffleSeed = Math.floor((Math.random() * 1) << 16); + console.log(`Generated a random suite order seed: ${shuffleSeed}`); + } else { + shuffleSeed = parseInt(shuffleSeed); + } + if (!Number.isInteger(shuffleSeed)) + throw new Error(`Invalid shuffle seed: '${shuffleSeed}', must be either 'off', 'generate' or an integer.`); + } + searchParams.delete("shuffleSeed"); + return shuffleSeed; + } + + toCompleteSearchParamsObject() { + return this.toSearchParamsObject(false); + } + + toSearchParamsObject(filter = true) { + const rawUrlParams = { __proto__: null }; + for (const [key, value] of Object.entries(this)) { + // Handle composite values separately. + if (key === "viewport" || key === "suites" || key === "tags") + continue; + // Skip over default values. + if (filter && value === defaultParams[key]) + continue; + rawUrlParams[key] = value; + } + + if (this.viewport.width !== defaultParams.viewport.width || this.viewport.height !== defaultParams.viewport.height) + rawUrlParams.viewport = `${this.viewport.width}x${this.viewport.height}`; + + if (this.suites.length) + rawUrlParams.suites = this.suites.join(","); + else if (this.tags.length) + rawUrlParams.tags = this.tags.join(","); + + return new URLSearchParams(rawUrlParams); + } + + toSearchParams() { + return this.toSearchParamsObject().toString(); + } +} + +export const defaultParams = new Params(); + +let maybeCustomParams = defaultParams; +if (globalThis?.location?.search) { + const searchParams = new URLSearchParams(globalThis.location.search); + try { + maybeCustomParams = new Params(searchParams); + } catch (e) { + console.error("Invalid URL Param", e, "\nUsing defaults as fallback:", maybeCustomParams); + } +} +export const params = maybeCustomParams; diff --git a/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/speedometer-utils/test-invoker.mjs b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/speedometer-utils/test-invoker.mjs new file mode 100644 index 000000000..7bf105bdc --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/speedometer-utils/test-invoker.mjs @@ -0,0 +1,86 @@ +class TestInvoker { + constructor(syncCallback, asyncCallback, reportCallback, params) { + this._syncCallback = syncCallback; + this._asyncCallback = asyncCallback; + this._reportCallback = reportCallback; + this._params = params; + } +} + +class BaseRAFTestInvoker extends TestInvoker { + start() { + return new Promise((resolve) => { + if (this._params.waitBeforeSync) + setTimeout(() => this._scheduleCallbacks(resolve), this._params.waitBeforeSync); + else + this._scheduleCallbacks(resolve); + }); + } +} + +class RAFTestInvoker extends BaseRAFTestInvoker { + _scheduleCallbacks(resolve) { + requestAnimationFrame(() => this._syncCallback()); + requestAnimationFrame(() => { + setTimeout(() => { + this._asyncCallback(); + setTimeout(async () => { + const result = await this._reportCallback(); + resolve(result); + }, 0); + }, 0); + }); + } +} + +class AsyncRAFTestInvoker extends BaseRAFTestInvoker { + static mc = new MessageChannel(); + _scheduleCallbacks(resolve) { + let gotTimer = false; + let gotMessage = false; + let gotPromise = false; + + const tryTriggerAsyncCallback = () => { + if (!gotTimer || !gotMessage || !gotPromise) + return; + + this._asyncCallback(); + setTimeout(async () => { + await this._reportCallback(); + resolve(); + }, 0); + }; + + requestAnimationFrame(async () => { + await this._syncCallback(); + gotPromise = true; + tryTriggerAsyncCallback(); + }); + + requestAnimationFrame(() => { + setTimeout(async () => { + await Promise.resolve(); + gotTimer = true; + tryTriggerAsyncCallback(); + }); + + AsyncRAFTestInvoker.mc.port1.addEventListener( + "message", + async function () { + await Promise.resolve(); + gotMessage = true; + tryTriggerAsyncCallback(); + }, + { once: true } + ); + AsyncRAFTestInvoker.mc.port1.start(); + AsyncRAFTestInvoker.mc.port2.postMessage("speedometer"); + }); + } +} + +export const TEST_INVOKER_LOOKUP = { + __proto__: null, + raf: RAFTestInvoker, + async: AsyncRAFTestInvoker, +}; diff --git a/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/speedometer-utils/test-runner.mjs b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/speedometer-utils/test-runner.mjs new file mode 100644 index 000000000..52defcb36 --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/speedometer-utils/test-runner.mjs @@ -0,0 +1,112 @@ +import { TEST_INVOKER_LOOKUP } from "./test-invoker.mjs"; + +export class TestRunner { + #frame; + #page; + #params; + #suite; + #test; + #callback; + #type; + + constructor(frame, page, params, suite, test, callback, type) { + this.#suite = suite; + this.#test = test; + this.#params = params; + this.#callback = callback; + this.#page = page; + this.#frame = frame; + this.#type = type; + } + + get page() { + return this.#page; + } + + get test() { + return this.#test; + } + + _runSyncStep(test, page) { + test.run(page); + } + + async runTest() { + // Prepare all mark labels outside the measuring loop. + const suiteName = this.#suite.name; + const testName = this.#test.name; + const syncStartLabel = `${suiteName}.${testName}-start`; + const syncEndLabel = `${suiteName}.${testName}-sync-end`; + const asyncEndLabel = `${suiteName}.${testName}-async-end`; + + let syncTime; + let asyncStartTime; + let asyncTime; + + const runSync = async () => { + if (this.#params.warmupBeforeSync) { + performance.mark("warmup-start"); + const startTime = performance.now(); + // Infinite loop for the specified ms. + while (performance.now() - startTime < this.#params.warmupBeforeSync) + continue; + performance.mark("warmup-end"); + } + performance.mark(syncStartLabel); + const syncStartTime = performance.now(); + + if (this.#type === "async") + await this._runSyncStep(this.test, this.page); + else + this._runSyncStep(this.test, this.page); + + const mark = performance.mark(syncEndLabel); + const syncEndTime = mark.startTime; + + syncTime = syncEndTime - syncStartTime; + asyncStartTime = syncEndTime; + }; + const measureAsync = () => { + const bodyReference = this.#frame ? this.#frame.contentDocument.body : document.body; + const windowReference = this.#frame ? this.#frame.contentWindow : window; + // Some browsers don't immediately update the layout for paint. + // Force the layout here to ensure we're measuring the layout time. + const height = bodyReference.getBoundingClientRect().height; + windowReference._unusedHeightValue = height; // Prevent dead code elimination. + + const asyncEndTime = performance.now(); + performance.mark(asyncEndLabel); + + asyncTime = asyncEndTime - asyncStartTime; + + if (this.#params.warmupBeforeSync) + performance.measure("warmup", "warmup-start", "warmup-end"); + performance.measure(`${suiteName}.${testName}-sync`, syncStartLabel, syncEndLabel); + performance.measure(`${suiteName}.${testName}-async`, syncEndLabel, asyncEndLabel); + }; + + const report = () => this.#callback(this.#test, syncTime, asyncTime); + const invokerType = this.#suite.type === "async" || this.#params.useAsyncSteps ? "async" : this.#params.measurementMethod; + const invokerClass = TEST_INVOKER_LOOKUP[invokerType]; + const invoker = new invokerClass(runSync, measureAsync, report, this.#params); + + return invoker.start(); + } +} + +export class AsyncTestRunner extends TestRunner { + constructor(frame, page, params, suite, test, callback, type) { + super(frame, page, params, suite, test, callback, type); + } + + async _runSyncStep(test, page) { + await test.run(page); + } +} + +export const TEST_RUNNER_LOOKUP = { + __proto__: null, + default: TestRunner, + async: AsyncTestRunner, + remote: TestRunner, +}; diff --git a/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/speedometer-utils/todomvc-utils.mjs b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/speedometer-utils/todomvc-utils.mjs new file mode 100644 index 000000000..dca451575 --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/speedometer-utils/todomvc-utils.mjs @@ -0,0 +1 @@ +export const numberOfItemsToAdd = 100; diff --git a/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/speedometer-utils/translations.mjs b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/speedometer-utils/translations.mjs new file mode 100644 index 000000000..a8ce5ddbe --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/speedometer-utils/translations.mjs @@ -0,0 +1,734 @@ +export const todos = { + en: [ + "Electronic Granite Hat", + "Bespoke Soft Table", + "Ergonomic Fresh Bike", + "Luxurious Wooden Cheese", + "Gorgeous Fresh Pizza", + "Rustic Rubber Shirt", + "Modern Rubber Soap", + "Small Bronze Ball", + "Awesome Bronze Shoes", + "Bespoke Steel Chair", + "Practical Plastic Soap", + "Incredible Granite Bacon", + "Elegant Metal Keyboard", + "Electronic Wooden Sausages", + "Tasty Wooden Gloves", + "Luxurious Metal Cheese", + "Awesome Rubber Gloves", + "Sleek Soft Car", + "Licensed Fresh Salad", + "Ergonomic Frozen Towels", + "Modern Rubber Keyboard", + "Tasty Concrete Pizza", + "Handmade Plastic Chicken", + "Luxurious Rubber Chicken", + "Practical Soft Fish", + "Ergonomic Bronze Shirt", + "Handcrafted Plastic Bacon", + "Unbranded Plastic Pants", + "Modern Wooden Sausages", + "Handmade Steel Shoes", + "Rustic Steel Bike", + "Gorgeous Frozen Salad", + "Handmade Bronze Chicken", + "Sleek Granite Bike", + "Generic Concrete Sausages", + "Incredible Plastic Tuna", + "Bespoke Fresh Cheese", + "Electronic Fresh Bacon", + "Licensed Wooden Car", + "Recycled Fresh Fish", + "Incredible Fresh Shoes", + "Practical Soft Chips", + "Small Soft Chicken", + "Intelligent Fresh Mouse", + "Modern Metal Mouse", + "Tasty Granite Gloves", + "Awesome Rubber Bike", + "Small Steel Shirt", + "Refined Concrete Computer", + "Sleek Frozen Shirt", + "Intelligent Concrete Shoes", + "Handmade Rubber Car", + "Sleek Rubber Towels", + "Unbranded Concrete Hat", + "Incredible Plastic Fish", + "Practical Soft Gloves", + "Organic Stone Pizza", + "Generic Wooden Keyboard", + "Recycled Wooden Chips", + "Incredible Rubber Chips", + "Ergonomic Granite Shirt", + "Tasty Frozen Keyboard", + "Gorgeous Steel Soap", + "Luxurious Plastic Chair", + "Elegant Frozen Bike", + "Recycled Steel Chair", + "Modern Bronze Sausages", + "Elegant Wooden Cheese", + "Small Plastic Sausages", + "Luxurious Frozen Shoes", + "Sleek Plastic Sausages", + "Handcrafted Fresh Sausages", + "Incredible Soft Chair", + "Recycled Wooden Soap", + "Soft Rubber Duck", + "Licensed Concrete Tuna", + "Luxurious Granite Pants", + "Refined Rubber Keyboard", + "Handcrafted Plastic Computer", + "Practical Steel Salad", + "Incredible Soft Bacon", + "Practical Metal Fish", + "Elegant Rubber Shirt", + "Handcrafted Rubber Table", + "Gorgeous Wooden Table", + "Fantastic Steel Sausages", + "Small Soft Keyboard", + "Generic Steel Ball", + "Electronic Frozen Hat", + "Gorgeous Fresh Chair", + "Sleek Soft Sausages", + "Gorgeous Wooden Towels", + "Bespoke Granite Pizza", + "Generic Metal Salad", + "Handmade Rubber Cheese", + "Fantastic Steel Chair", + "Handcrafted Frozen Computer", + "Rustic Rubber Mouse", + "Sleek Granite Pizza", + "Gorgeous Plastic Keyboard", + ], + "zh-cn": [ + "电子花岗岩帽子", + "定制软桌", + "符合人体工程学新鲜自行车", + "豪华的木制奶酪", + "华丽的新鲜披萨", + "质朴的橡胶衬衫", + "现代橡胶肥皂", + "小青铜球", + "很棒的青铜鞋", + "定制钢椅", + "实用的塑料肥皂", + "令人难以置信的花岗岩培根", + "优雅的金属键盘", + "电子木香肠", + "美味的木制手套", + "豪华的金属奶酪", + "很棒的橡胶手套", + "光滑的柔软汽车", + "有执照的新鲜沙拉", + "人体工程学的冷冻毛巾", + "现代橡胶键盘", + "美味的混凝土披萨", + "手工塑料鸡", + "豪华橡胶鸡", + "实用的软鱼", + "人体工程学的青铜衬衫", + "手工制作的塑料培根", + "未品牌的塑料裤", + "现代木香肠", + "手工钢鞋", + "质朴的钢自行车", + "华丽的冷冻沙拉", + "手工铜鸡", + "光滑的花岗岩自行车", + "通用混凝土香肠", + "令人难以置信的塑料金枪鱼", + "定制新鲜奶酪", + "电子新鲜培根", + "有执照的木车", + "回收的新鲜鱼", + "令人难以置信的新鲜鞋", + "实用的软芯片", + "小鸡肉", + "智能新鲜鼠标", + "现代金属鼠标", + "美味的花岗岩手套", + "很棒的橡胶自行车", + "小钢衬衫", + "精制混凝土计算机", + "光滑的冷冻衬衫", + "智能混凝土鞋", + "手工橡胶车", + "光滑的橡胶毛巾", + "未品牌的混凝土帽子", + "令人难以置信的塑料鱼", + "实用的软手套", + "有机石比萨", + "通用木制键盘", + "再生木芯片", + "令人难以置信的橡胶芯片", + "人体工程学花岗岩衬衫", + "美味的冷冻键盘", + "华丽的钢肥皂", + "豪华的塑料椅", + "优雅的冷冻自行车", + "再生钢椅", + "现代铜香肠", + "优雅的木制奶酪", + "小塑料香肠", + "豪华的冷冻鞋", + "光滑的塑料香肠", + "手工制作的新鲜香肠", + "令人难以置信的柔软椅子", + "回收的木制肥皂", + "软橡皮鸭", + "许可的混凝土金枪鱼", + "豪华的花岗岩裤", + "精制橡胶键盘", + "手工制作的塑料计算机", + "实用的钢沙拉", + "令人难以置信的软培根", + "实用的金属鱼", + "优雅的橡胶衬衫", + "手工橡胶桌", + "华丽的木桌", + "奇妙的钢香肠", + "小软键盘", + "通用钢球", + "电子冷冻帽子", + "华丽的新鲜椅子", + "光滑的软香肠", + "华丽的木制毛巾", + "定制的花岗岩披萨", + "通用金属沙拉", + "手工橡胶奶酪", + "奇妙的钢椅", + "手工制作的冷冻计算机", + "质朴的橡胶老鼠", + "光滑的花岗岩披萨", + "华丽的塑料键盘", + ], + ja: [ + "電子花崗岩の帽子", + "オーダーメイドのソフトテーブル", + "人間工学に基づいた新鮮な自転車", + "豪華な木製チーズ", + "ゴージャスな新鮮なピザ", + "素朴なラバーシャツ", + "モダンゴム石鹸", + "小さなブロンズボール", + "素晴らしいブロンズシューズ", + "オーダーメイドのスチールチェア", + "実用的なプラスチック石鹸", + "信じられないほどの花崗岩のベーコン", + "エレガントなメタルキーボード", + "電子木製ソーセージ", + "おいしい木製手袋", + "豪華なメタルチーズ", + "素晴らしいラバーグローブ", + "洗練されたソフトカー", + "ライセンスされた新鮮なサラダ", + "人間工学に基づいた冷凍タオル", + "モダンなゴムキーボード", + "おいしいコンクリートピザ", + "手作りのプラスチックチキン", + "豪華なゴム鶏", + "実用的な柔らかい魚", + "人間工学に基づいたブロンズシャツ", + "手作りのプラスチックベーコン", + "ブランドのないプラスチックパンツ", + "モダンな木製ソーセージ", + "手作りのスチールシューズ", + "素朴なスチールバイク", + "ゴージャスな冷凍サラダ", + "手作りのブロンズチキン", + "洗練された花崗岩の自転車", + "一般的なコンクリートソーセージ", + "信じられないほどのプラスチックマグロ", + "オーダーメイドのフレッシュチーズ", + "電子新鮮なベーコン", + "ライセンスされた木製車", + "新鮮な魚をリサイクルしました", + "信じられないほどの新鮮な靴", + "実用的なソフトチップ", + "小さな柔らかい鶏", + "インテリジェントな新鮮なマウス", + "現代の金属マウス", + "おいしい花崗岩の手袋", + "素晴らしいラバーバイク", + "小さなスチールシャツ", + "洗練されたコンクリートコンピューター", + "洗練された冷凍シャツ", + "インテリジェントコンクリートシューズ", + "手作りのラバーカー", + "洗練されたゴムタオル", + "ブランドのないコンクリートの帽子", + "信じられないほどのプラスチックの魚", + "実用的なソフトグローブ", + "オーガニックストーンピザ", + "一般的な木製キーボード", + "リサイクルされた木製チップ", + "信じられないほどのゴムチップ", + "人間工学に基づいた花崗岩のシャツ", + "おいしい冷凍キーボード", + "ゴージャスなスチールソープ", + "豪華なプラスチック製の椅子", + "エレガントな冷凍自転車", + "リサイクルスチールチェア", + "モダンブロンズソーセージ", + "エレガントな木製チーズ", + "小さなプラスチックソーセージ", + "豪華な冷凍靴", + "なめらかなプラスチックソーセージ", + "手作りの新鮮なソーセージ", + "信じられないほどのソフトチェア", + "リサイクルされた木製石鹸", + "柔らかいラバーアヒル", + "ライセンスされたコンクリートマグロ", + "豪華な花崗岩のズボン", + "洗練されたゴムキーボード", + "手作りのプラスチックコンピューター", + "実用的なスチールサラダ", + "信じられないほどのソフトベーコン", + "実用的な金属魚", + "エレガントなラバーシャツ", + "手作りのゴムテーブル", + "ゴージャスな木製のテーブル", + "素晴らしいスチールソーセージ", + "小さなソフトキーボード", + "ジェネリックスチールボール", + "電子冷凍帽子", + "ゴージャスな新鮮な椅子", + "洗練された柔らかいソーセージ", + "ゴージャスな木製タオル", + "オーダーメイドの花崗岩のピザ", + "ジェネリックメタルサラダ", + "手作りのゴムチーズ", + "素晴らしいスチールチェア", + "手作りの冷凍コンピューター", + "素朴なゴムマウス", + "洗練された花崗岩のピザ", + "ゴージャスなプラスチックキーボード", + ], + es: [ + "Sombrero de granito electrónico", + "Mesa suave a medida", + "Bicicleta ergonómica fresca", + "Lujoso queso de madera", + "Hermosa pizza fresca", + "Camisa de goma rústica", + "Jabón de goma moderno", + "Bola de bronce pequeña", + "Zapatos de bronce impresionantes", + "Silla de acero a medida", + "Jabón de plástico práctico", + "Tocino de granito increíble", + "Teclado de metal elegante", + "Salchichas de madera electrónica", + "Sabrosos guantes de madera", + "Lujoso queso de metal", + "Guantes de goma impresionantes", + "Coche suave y elegante", + "Ensalada fresca con licencia", + "Toallas congeladas ergonómicas", + "Teclado de goma moderno", + "Pizza de hormigón sabrosa", + "Pollo de plástico hecho a mano", + "Pollo de goma lujoso", + "Pescado suave práctico", + "Camisa de bronce ergonómica", + "Tocino de plástico artesanal", + "Pantalones de plástico sin marca", + "Salchichas de madera modernas", + "Zapatos de acero hechos a mano", + "Bicicleta de acero rústica", + "Hermosa ensalada congelada", + "Pollo bronce hecho a mano", + "Bicicleta de granito elegante", + "Salchichas de concreto genérico", + "Atún de plástico increíble", + "Queso fresco a medida", + "Tocino fresco electrónico", + "Coche de madera con licencia", + "Pescado reciclado", + "Increíbles zapatos frescos", + "Chips suaves prácticos", + "Pollo pequeño y suave", + "Ratón fresco inteligente", + "Ratón de metal moderno", + "Sabrosos guantes de granito", + "Impresionante bicicleta de goma", + "Camisa de acero pequeña", + "Ordenador de concreto refinada", + "Camisa elegante congelada", + "Zapatos de concreto inteligentes", + "Coche de goma hecho a mano", + "Toallas de goma elegantes", + "Sombrero de hormigón sin marca", + "Pescado de plástico increíble", + "Guantes suaves prácticos", + "Pizza de piedra orgánica", + "Teclado de madera genérico", + "Chips de madera reciclados", + "Increíbles chips de goma", + "Camisa de granito ergonómico", + "Sabroso teclado congelado", + "Hermoso jabón de acero", + "Lujosa silla de plástico", + "Bicicleta congelada elegante", + "Silla de acero reciclada", + "Salchichas de bronce modernas", + "Elegante queso de madera", + "Pequeñas salchichas de plástico", + "Zapatos congelados lujosos", + "Salchichas de plástico elegantes", + "Salchichas frescas artesanales", + "Increíble silla suave", + "Jabón de madera reciclado", + "Pato de goma suave", + "Atún de hormigón con licencia", + "Pantalones de granito lujosos", + "Teclado de goma refinado", + "Computadora de plástico hecha a mano", + "Ensalada de acero práctica", + "Bocino suave increíble", + "Pescado de metal práctico", + "Elegante camisa de goma", + "Mesa de goma artesanal", + "Hermosa mesa de madera", + "Fantásticas salchichas de acero", + "Pequeño teclado suave", + "Bola de acero genérica", + "Sombrero de congelado", + "Hermosa silla fresca", + "Salchichas suaves y elegantes", + "Hermosas toallas de madera", + "Pizza de granito a medida", + "Ensalada de metal genérica", + "Queso de goma hecho a mano", + "Fantástica silla de acero", + "Computadora congelada hecha a mano", + "Ratón de goma rústica", + "Pizza de granito elegante", + "Hermoso teclado de plástico", + ], + de: [ + "Elektronischer Granithut", + "Maßgeschneiderter weicher Tisch", + "Ergonomisches frisches Fahrrad", + "Luxuriöser Holzkäse", + "Wunderschöne frische Pizza", + "Rustikales Gummihemd", + "Moderne Gummiseife", + "Kleine Bronzekugel", + "Tolle Bronzeschuhe", + "Maßgeschneiderter Stahlstuhl", + "Praktische Plastikseife", + "Unglaublicher Granitspeck", + "Elegante Metall -Tastatur", + "Elektronische Holzwurst", + "Leckere Holzhandschuhe", + "Luxuriöser Metallkäse", + "Super Gummihandschuhe", + "Schlankes weiches Auto", + "Lizenzierter frischer Salat", + "Ergonomische gefrorene Handtücher", + "Moderne Gummi Tastatur", + "Leckere Betonpizza", + "Handgefertigtes Plastikhuhn", + "Luxuriöses Gummihähnchen", + "Praktischer weicher Fisch", + "Ergonomisches Bronzehemd", + "Handgefertigter Plastikspeck", + "Plastikhosen ohne Marken", + "Moderne Holz Würste", + "Handgefertigte Stahlschuhe", + "Rustikales Stahlrad", + "Wunderschöner gefrorener Salat", + "Handgefertigtes Bronze Hühnchen", + "Schlankes Granitrad", + "Generische Beton Würste", + "Unglaublicher Plastik Thunfisch", + "Maßgeschneiderter frischer Käse", + "Elektronischer frischer Speck", + "Lizenziertes Holzauto", + "Recycelter frischer Fisch", + "Unglaubliche frische Schuhe", + "Praktische weiche Chips", + "Kleines weiches Huhn", + "Intelligente frische Maus", + "Moderne Metallmaus", + "Leckere Granithandschuhe", + "Super Gummi Fahrrad", + "Kleines Stahlhemd", + "Raffinierter Betoncomputer", + "Schlankes gefrorenes Hemd", + "Intelligente Betonschuhe", + "Handgefertigtes Gummiauto", + "Schlanke Gummi Handtücher", + "Betonhut ohne Markenzeichen", + "Unglaublicher Plastikfisch", + "Praktische weiche Handschuhe", + "Bio Steinpizza", + "Generische hölzerne Tastatur", + "Recycelte Holzchips", + "Unglaubliche Gummischchips", + "Ergonomisches Granithemd", + "Leckere gefrorene Tastatur", + "Wunderschöne Stahlseife", + "Luxuriöser Plastikstuhl", + "Elegantes gefrorenes Fahrrad", + "Recycelter Stahlstuhl", + "Moderne Bronze Würste", + "Eleganter Holzkäse", + "Kleine Plastikwurst", + "Luxuriöse gefrorene Schuhe", + "Schlanke Plastikwurst", + "Handgefertigte frische Würste", + "Unglaublicher weicher Stuhl", + "Recycelte Holzseife", + "Weiche Gummi Ente", + "Lizenzierter Beton Thunfisch", + "Luxuriöse Granithosen", + "Raffinierte Gummi Tastatur", + "Handgefertigter Kunststoffcomputer", + "Praktischer Stahlsalat", + "Unglaublicher weicher Speck", + "Praktische Metallfische", + "Elegantes Gummihemd", + "Handgefertigter Gummi Tisch", + "Wunderschöner Holztisch", + "Fantastische Stahlwurst", + "Kleine weiche Tastatur", + "Generischer Stahlkugel", + "Elektronischer Gefrorenhut", + "Wunderschöner frischer Stuhl", + "Schlanke weiche Würste", + "Wunderschöne Holztücher", + "Maßgeschneiderte Granitpizza", + "Generischer Metallsalat", + "Handgefertigter Gummi Käse", + "Fantastischer Stahlstuhl", + "Handgefertigter gefrorener Computer", + "Rustikale Gummi Maus", + "Schlanke Granitpizza", + "Wunderschöne Plastiktastatur", + ], + ru: [ + "Электронная гранитная шляпа", + "Беспокойный мягкий стол", + "Эргономичный свежий велосипед", + "Роскошный деревянный сыр", + "Великолепная свежая пицца", + "Деревенская резиновая рубашка", + "Современное резиновое мыло", + "Маленький бронзовый мяч", + "Потрясающие бронзовые туфли", + "Стальное кресло на заказ", + "Практическое пластиковое мыло", + "Невероятный гранитный бекон", + "Элегантная металлическая клавиатура", + "Электронные деревянные колбасы", + "Вкусные деревянные перчатки", + "Роскошный металлический сыр", + "Потрясающие резиновые перчатки", + "Гладкая мягкая машина", + "Лицензированный свежий салат", + "Эргономичные замороженные полотенца", + "Современная резиновая клавиатура", + "Вкусная бетонная пицца", + "Пластиковая курица ручной работы", + "Роскошная резиновая курица", + "Практическая мягкая рыба", + "Эргономичная бронзовая рубашка", + "Пластиковый бекон из ручной работы", + "Непревзойденные пластиковые брюки", + "Современные деревянные колбасы", + "Стальные туфли ручной работы", + "Деревенский стальный велосипед", + "Великолепный замороженный салат", + "Бронзовая курица ручной работы", + "Гладкий гранитный велосипед", + "Общие бетонные колбасы", + "Невероятный пластиковый тунец", + "Созданный свежий сыр", + "Электронный свежий бекон", + "Лицензированный деревянный автомобиль", + "Переработанная свежая рыба", + "Невероятные свежие туфли", + "Практические мягкие чипсы", + "Маленькая мягкая курица", + "Интеллектуальная свежая мышь", + "Современная металлическая мышь", + "Вкусные гранитные перчатки", + "Потрясающий резиновый велосипед", + "Маленькая стальная рубашка", + "Изысканный бетонный компьютер", + "Гладкая замороженная рубашка", + "Интеллектуальные бетонные туфли", + "Резиновый автомобиль ручной работы", + "Гладкие резиновые полотенца", + "Непревзойденная бетонная шляпа", + "Невероятная пластиковая рыба", + "Практические мягкие перчатки", + "Органическая камня пицца", + "Общая деревянная клавиатура", + "Переработанные деревянные чипсы", + "Невероятные резиновые чипсы", + "Эргономичная гранитная рубашка", + "Вкусная замороженная клавиатура", + "Великолепное стальное мыло", + "Роскошное пластиковое кресло", + "Элегантный замороженный велосипед", + "Переработанное стальное кресло", + "Современные бронзовые колбасы", + "Элегантный деревянный сыр", + "Маленькие пластиковые колбаски", + "Роскошные замороженные туфли", + "Гладкие пластиковые колбаски", + "Свежие колбаски ручной работы", + "Невероятный мягкий стул", + "Переработанное деревянное мыло", + "Мягкая резиновая утка", + "Лицензированный бетонный тунец", + "Роскошные гранитные штаны", + "Рафинированная резиновая клавиатура", + "Пластиковый компьютер ручной работы", + "Практический стальной салат", + "Невероятный мягкий бекон", + "Практическая металлическая рыба", + "Элегантная резиновая рубашка", + "Ручной резиновый стол", + "Великолепный деревянный стол", + "Фантастические стальные колбаски", + "Небольшая мягкая клавиатура", + "Общий стальной шар", + "Электронная замороженная шляпа", + "Великолепный свежий стул", + "Гладкие мягкие колбаски", + "Великолепные деревянные полотенца", + "Сделанная на заказ гранитная пицца", + "Общий металлический салат", + "Резиновый сыр ручной работы", + "Фантастическое стальное кресло", + "Замороженный компьютер ручной работы", + "Деревенская резиновая мышь", + "Главная гранитная пицца", + "Великолепная пластиковая клавиатура", + ], + emoji: [ + "Electronic Granite Hat 👆🏻", + "Bespoke Soft 🍷 Table", + "Ergonomic Fresh Bike 😚😚", + "Luxurious 🍉 Wooden Cheese 🍮", + "Gorgeous Fresh Pizza ⛔", + "Rustic 💪🏽 Rubber Shirt", + "Modern Rubber 🍀 Soap", + "👍 Small Bronze Ball 👍", + "Awesome Bronze Shoes 😎", + "Bespoke 👈🏽 Steel Chair", + "Practical Plastic 💋 💋 Soap", + "🙌🏻 Incredible Granite Bacon", + "🎃🎃🎃🎃🎃🎃🎃🎃🎃🎃🎃🎃🎃🎃", + "Electronic Wooden Sausages 🌷", + "Tasty 🍺 Wooden Gloves", + "🏖️ Luxurious Metal Cheese", + "Awesome Rubber 😉 Gloves", + "Sleek Soft Car 💁🏻‍♂️", + "Licensed 👏👏👏 Fresh Salad", + "Ergonomic Frozen Towels 🐇", + "🖐🏻 Modern Rubber Keyboard", + "Tasty Concrete Pizza ✨✨", + "Handmade 😘 Plastic 😘 Chicken 😘", + "🏁 Luxurious Rubber Chicken 🏁", + "Practical Soft Fish 🤍", + "Ergonomic Bronze Shirt 😍", + "😸😸 Handcrafted 🐻 Plastic Bacon", + "Unbranded 🐭 Plastic Pants", + "🤘 Modern 🤘 Wooden 🤘 Sausages", + "Handmade Steel Shoes 👍", + "Rustic 🧁🧁 Steel Bike", + "Gorgeous Frozen Salad 👩‍💻", + "Handmade Bronze Chicken 😮😸", + "Sleek 🍐 Granite Bike", + " ❌ ❌ Generic Concrete Sausages", + "Incredible 🍉 Plastic Tuna", + "Bespoke Fresh Cheese 😘", + "💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡", + "Licensed 🍐 Wooden 🍅 Car ", + "Recycled Fresh Fish 🤡", + "📞 Incredible Fresh Shoes", + "Practical 🐻🐻 Soft Chips", + "Small 💝 Soft Chicken", + " 💝 Intelligent Fresh Mouse 💝", + "Modern Metal 🧵 Mouse", + "🦈 Tasty Granite Gloves", + "Awesome Rubber Bike 😡😡😡😡", + "🚮 Small Steel Shirt 🚮", + "Refined 🌲 Concrete Computer", + "Sleek Frozen Shirt 👨‍🦰", + "Intelligent Concrete ➗➗➗ Shoes", + "🏅Handmade Rubber Car", + "Sleek 👨🏼‍🌾 Rubber 👨🏼‍🌾 Towels", + "Unbranded Concrete Hat🎇", + "🌀🌀Incredible Plastic Fish", + "Practical Soft Gloves🌶️ 🌶️", + "Organic 🍞 Stone 🔽 Pizza 🥴", + "Generic Wooden Keyboard 💙", + "Recycled 🔴 Wooden Chips", + "Incredible Rubber Chips 🍹", + "🌵 Ergonomic Granite Shirt", + "Tasty Frozen 🦄 Keyboard", + "🍣 Gorgeous Steel 🥯 Soap", + "Luxurious Plastic Chair 🧑‍🦰", + "Elegant Frozen 🧑‍🦰 Bike", + "Recycled 🟠🟠 Steel Chair", + "⭐⭐ Modern ⭐ Bronze ⭐ Sausages", + "Elegant Wooden Cheese🤘", + "Small 🎎 Plastic 🛩️ Sausages", + "*️⃣*️⃣*️⃣*️⃣ Luxurious Frozen Shoes", + "Sleek Plastic Sausages 🚩", + "Handcrafted Fresh 💮 Sausages", + "Incredible 🤢🤢 Soft Chair", + "🇬🇪 Recycled 🇲🇺 Wooden Soap", + "Soft 🦌 Rubber Duck 🐥", + "Licensed Concrete Tuna 👎👎", + "Luxurious Granite 💝 Pants", + "Refined Rubber Keyboard 💝", + "👌🏻👌🏻 Handcrafted Plastic Computer", + "Practical Steel 🐪 Salad", + "Incredible Soft Bacon 🌺", + "Practical Metal 🥊 Fish", + "Elegant 👩🏾‍❤️‍💋‍👨🏽👩🏾‍❤️‍💋‍👨🏽 Rubber Shirt", + "🛺 Handcrafted Rubber Table", + "Gorgeous 🦙 Wooden Table 🦙", + "🍉 Fantastic Steel Sausages", + "Small Soft Keyboard👟", + "Generic 🦙🦙 Steel Ball", + "Electronic Frozen Hat ✌🏾✌🏾✌🏾✌🏾✌🏾", + "Gorgeous 🍏 Fresh Chair", + "Sleek Soft 💧 Sausages", + "Gorgeous Wooden Towels 🍿", + "Bespoke 🌅 Granite Pizza", + "Generic Metal Salad 🎗️", + "✨ Handmade ✨ Rubber ✨ Cheese ✨", + "Fantastic 🐥 🌺 🤷🏾 Steel Chair", + "Handcrafted Frozen Computer 🛡️ 🧸 🐓", + "🐹 Rustic Rubber Mouse", + "💠 Sleek Granite Pizza 💠", + "Gorgeous 🧝🏻‍♂️ Plastic Keyboard", + ], +}; + +export const defaultTodoText = { + en: "Something to do", + "zh-cn": "做某事", + ja: "何かをする必要がある", + es: "Algo que hacer", + de: "Etwas zu tun", + ru: "Кое-что сделать", + emoji: "Something to do 😊", +}; + +export const defaultLanguage = "en"; + +export function getTodoText(lang = "en", index) { + const todosSelection = todos[lang]; + const currentIndex = index % todosSelection.length; + return todosSelection[currentIndex]; +} diff --git a/resources/todomvc/vanilla-examples/javascript-web-components/dist/utils/nanoid.js b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/utils/nanoid.js similarity index 100% rename from resources/todomvc/vanilla-examples/javascript-web-components/dist/utils/nanoid.js rename to resources/todomvc/vanilla-examples/javascript-web-components/dist/src/utils/nanoid.js diff --git a/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/workload-test.mjs b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/workload-test.mjs new file mode 100644 index 000000000..94fff2f75 --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-web-components/dist/src/workload-test.mjs @@ -0,0 +1,36 @@ +import { BenchmarkStep, BenchmarkSuite } from "./speedometer-utils/benchmark.mjs"; +import { getAllElements, getElement } from "./speedometer-utils/helpers.mjs"; +import { getTodoText, defaultLanguage } from "./speedometer-utils/translations.mjs"; +import { numberOfItemsToAdd } from "./speedometer-utils/todomvc-utils.mjs"; + +export const appName = "todomvc-postmessage"; +export const appVersion = "1.0.0"; + +const suites = { + default: new BenchmarkSuite("default", [ + new BenchmarkStep("Adding-items", () => { + const input = getElement(".new-todo-input", ["todo-app", "todo-topbar"]); + for (let i = 0; i < numberOfItemsToAdd; i++) { + input.value = getTodoText(defaultLanguage, i); + input.dispatchEvent(new Event("input")); + input.dispatchEvent(new KeyboardEvent("keyup", { keyCode: 13, key: "Enter" })); + } + }), + new BenchmarkStep("Completing-items", () => { + const items = getAllElements("todo-item", ["todo-app", "todo-list"]); + for (let i = 0; i < numberOfItemsToAdd; i++) { + const item = getElement(".toggle-todo-input", [], items[i]); + item.click(); + } + }), + new BenchmarkStep("Deleting-items", () => { + const items = getAllElements("todo-item", ["todo-app", "todo-list"]); + for (let i = numberOfItemsToAdd - 1; i >= 0; i--) { + const item = getElement(".remove-todo-button", [], items[i]); + item.click(); + } + }), + ]), +}; + +export default suites; diff --git a/resources/todomvc/vanilla-examples/javascript-web-components/index.html b/resources/todomvc/vanilla-examples/javascript-web-components/index.html index 6820a9346..8e0d54267 100644 --- a/resources/todomvc/vanilla-examples/javascript-web-components/index.html +++ b/resources/todomvc/vanilla-examples/javascript-web-components/index.html @@ -26,5 +26,6 @@ + diff --git a/resources/todomvc/vanilla-examples/javascript-web-components/package-lock.json b/resources/todomvc/vanilla-examples/javascript-web-components/package-lock.json index 34dfaedc3..da98ea232 100644 --- a/resources/todomvc/vanilla-examples/javascript-web-components/package-lock.json +++ b/resources/todomvc/vanilla-examples/javascript-web-components/package-lock.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "http-server": "^14.1.1", + "speedometer-utils": "../../../shared", "todomvc-css": "file:../../todomvc-css" }, "engines": { @@ -17,6 +18,10 @@ "npm": ">=8.19.3" } }, + "../../../shared": { + "name": "speedometer-utils", + "version": "1.0.0" + }, "../../todomvc-css": { "version": "1.0.0", "license": "ISC", @@ -404,6 +409,10 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/speedometer-utils": { + "resolved": "../../../shared", + "link": true + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -702,6 +711,9 @@ "object-inspect": "^1.9.0" } }, + "speedometer-utils": { + "version": "file:../../../shared" + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", diff --git a/resources/todomvc/vanilla-examples/javascript-web-components/package.json b/resources/todomvc/vanilla-examples/javascript-web-components/package.json index a04fdab85..d677c19c8 100644 --- a/resources/todomvc/vanilla-examples/javascript-web-components/package.json +++ b/resources/todomvc/vanilla-examples/javascript-web-components/package.json @@ -17,6 +17,7 @@ "license": "ISC", "dependencies": { "http-server": "^14.1.1", - "todomvc-css": "file:../../todomvc-css" + "todomvc-css": "file:../../todomvc-css", + "speedometer-utils": "../../../shared" } } diff --git a/resources/todomvc/vanilla-examples/javascript-web-components/scripts/build.js b/resources/todomvc/vanilla-examples/javascript-web-components/scripts/build.js index d5fa77557..be4499d96 100644 --- a/resources/todomvc/vanilla-examples/javascript-web-components/scripts/build.js +++ b/resources/todomvc/vanilla-examples/javascript-web-components/scripts/build.js @@ -1,94 +1,156 @@ const fs = require("fs").promises; -const getDirName = require("path").dirname; - -const rootDirectory = "./"; -const sourceDirectory = "./src"; -const targetDirectory = "./dist"; - -const htmlFile = "index.html"; - -const filesToMove = { - index: [ - { src: "node_modules/todomvc-css/dist/global.css", dest: "styles/global.css" }, - { src: "node_modules/todomvc-css/dist/header.css", dest: "styles/header.css" }, - { src: "node_modules/todomvc-css/dist/footer.css", dest: "styles/footer.css" }, - ], - app: [ - { src: "node_modules/todomvc-css/dist/global.constructable.js", dest: "styles/global.constructable.js" }, - { src: "node_modules/todomvc-css/dist/app.constructable.js", dest: "styles/app.constructable.js" }, - { src: "node_modules/todomvc-css/dist/topbar.constructable.js", dest: "styles/topbar.constructable.js" }, - { src: "node_modules/todomvc-css/dist/main.constructable.js", dest: "styles/main.constructable.js" }, - { src: "node_modules/todomvc-css/dist/bottombar.constructable.js", dest: "styles/bottombar.constructable.js" }, - { src: "node_modules/todomvc-css/dist/todo-list.constructable.js", dest: "styles/todo-list.constructable.js" }, - { src: "node_modules/todomvc-css/dist/todo-item.constructable.js", dest: "styles/todo-item.constructable.js" }, - ], -}; - -const importsToRename = { - src: "../../../node_modules/todomvc-css/dist/", - dest: "../../styles/", - files: [ - "components/todo-app/todo-app.component.js", - "components/todo-bottombar/todo-bottombar.component.js", - "components/todo-item/todo-item.component.js", - "components/todo-list/todo-list.component.js", - "components/todo-topbar/todo-topbar.component.js", - ], -}; - -const copy = async (src, dest) => { - await fs.mkdir(getDirName(dest), { recursive: true }); +const { dirname } = require("path"); + +/** + * createDirectory + * + * Removes and recreates a directory. + * + * @param {string} directory Directory name. + */ +async function createDirectory(directory) { + await fs.rm(directory, { recursive: true, force: true }); + await fs.mkdir(directory); +} + +/** + * copyDirectory + * + * Copies a source folder to a destination folder. + * + * @param {string} src Source directory. + * @param {string} dest Destination directory. + */ +async function copyDirectory(src, dest) { + await fs.cp(src, dest, { recursive: true }, (err) => { + if (err) + console.error(err); + }); +} + +/** + * copyFile + * + * Copies a file from a source to a destination. + * + * @param {string} src Source file. + * @param {string} dest Destination file. + */ +async function copyFile(src, dest) { + await fs.mkdir(dirname(dest), { recursive: true }); await fs.copyFile(src, dest); -}; - -const copyFilesToMove = async (files) => { - for (let i = 0; i < files.length; i++) - await copy(files[i].src, `${targetDirectory}/${files[i].dest}`); -}; - -const updateImports = async ({ file, src, dest }) => { - let contents = await fs.readFile(`${targetDirectory}/${file}`, "utf8"); +} + +/** + * copyFiles + * + * Copies multiple files from a source to a destination. + * + * @param {string[]} files Array of files to copy. + */ +async function copyFiles(files) { + for (const file of files) + await copyFile(file.src, file.dest); +} + +/** + * updateImportsInFile + * + * Reads a file and replaces a source path with a destination path. + * + * @param {Object} config - Config to update imports + * @param {string} config.src - The source path. + * @param {string} config.dest - The destination path. + * @param {string} config.file - File to read from. + */ +async function updateImportsInFile({ file, src, dest }) { + let contents = await fs.readFile(file, "utf8"); contents = contents.replaceAll(src, dest); - await fs.writeFile(`${targetDirectory}/${file}`, contents); -}; + await fs.writeFile(file, contents); +} + +/** + * updateImports + * + * Updates imports in multiple files. + * + * @param {Object} config - Config to update imports + * @param {string} config.src - The source path. + * @param {string} config.dest - The destination path. + * @param {string} config.file - Files to read from. + */ +async function updateImports({ files, src, dest }) { + for (const file of files) + await updateImportsInFile({ file, src, dest }); +} + +const filesToMove = [ + { src: "index.html", dest: "./dist/index.html" }, + { src: "node_modules/todomvc-css/dist/global.css", dest: "./dist/styles/global.css" }, + { src: "node_modules/todomvc-css/dist/header.css", dest: "./dist/styles/header.css" }, + { src: "node_modules/todomvc-css/dist/footer.css", dest: "./dist/styles/footer.css" }, + { src: "node_modules/todomvc-css/dist/global.constructable.js", dest: "./dist/styles/global.constructable.js" }, + { src: "node_modules/todomvc-css/dist/app.constructable.js", dest: "./dist/styles/app.constructable.js" }, + { src: "node_modules/todomvc-css/dist/topbar.constructable.js", dest: "./dist/styles/topbar.constructable.js" }, + { src: "node_modules/todomvc-css/dist/main.constructable.js", dest: "./dist/styles/main.constructable.js" }, + { src: "node_modules/todomvc-css/dist/bottombar.constructable.js", dest: "./dist/styles/bottombar.constructable.js" }, + { src: "node_modules/todomvc-css/dist/todo-list.constructable.js", dest: "./dist/styles/todo-list.constructable.js" }, + { src: "node_modules/todomvc-css/dist/todo-item.constructable.js", dest: "./dist/styles/todo-item.constructable.js" }, + { src: "node_modules/speedometer-utils/test-invoker.mjs", dest: "./dist/src/speedometer-utils/test-invoker.mjs" }, + { src: "node_modules/speedometer-utils/test-runner.mjs", dest: "./dist/src/speedometer-utils/test-runner.mjs" }, + { src: "node_modules/speedometer-utils/params.mjs", dest: "./dist/src/speedometer-utils/params.mjs" }, + { src: "node_modules/speedometer-utils/benchmark.mjs", dest: "./dist/src/speedometer-utils/benchmark.mjs" }, + { src: "node_modules/speedometer-utils/helpers.mjs", dest: "./dist/src/speedometer-utils//helpers.mjs" }, + { src: "node_modules/speedometer-utils/translations.mjs", dest: "./dist/src/speedometer-utils/translations.mjs" }, + { src: "node_modules/speedometer-utils/todomvc-utils.mjs", dest: "./dist/src/speedometer-utils/todomvc-utils.mjs" }, +]; + +const importsToRename = [ + { + src: "node_modules/todomvc-css/dist/", + dest: "styles/", + files: ["./dist/index.html"], + }, + { + src: "../../../node_modules/todomvc-css/dist/", + dest: "../../../styles/", + files: [ + "./dist/src/components/todo-app/todo-app.component.js", + "./dist/src/components/todo-bottombar/todo-bottombar.component.js", + "./dist/src/components/todo-item/todo-item.component.js", + "./dist/src/components/todo-list/todo-list.component.js", + "./dist/src/components/todo-topbar/todo-topbar.component.js", + ], + }, + { + src: "/src/", + dest: "./", + files: ["./dist/src/index.mjs"], + }, + { + src: "/node_modules/speedometer-utils/", + dest: "./speedometer-utils/", + files: ["./dist/src/index.mjs", "./dist/src/workload-test.mjs"], + }, +]; const build = async () => { - // remove dist directory if it exists - await fs.rm(targetDirectory, { recursive: true, force: true }); - - // re-create the directory. - await fs.mkdir(targetDirectory); + // create dist folder + await createDirectory("./dist"); // copy src folder - await fs.cp(sourceDirectory, targetDirectory, { recursive: true }, (err) => { - if (err) - console.error(err); - }); + await copyDirectory("./src", "./dist/src"); // copy files to Move - for (const key in filesToMove) - copyFilesToMove(filesToMove[key]); - - // read html file - let contents = await fs.readFile(`${rootDirectory}/${htmlFile}`, "utf8"); - - // remove base paths from files to move - const filesToMoveForIndex = filesToMove.index; - for (let i = 0; i < filesToMoveForIndex.length; i++) - contents = contents.replace(filesToMoveForIndex[i].src, filesToMoveForIndex[i].dest); - - // remove basePath from source directory - const basePath = `${sourceDirectory.split("/")[1]}/`; - const re = new RegExp(basePath, "g"); - contents = contents.replace(re, ""); - - // write html files - await fs.writeFile(`${targetDirectory}/${htmlFile}`, contents); + await copyFiles(filesToMove); - // rename imports in modules - importsToRename.files.forEach((file) => updateImports({ file, src: importsToRename.src, dest: importsToRename.dest })); + // rename imports files + for (const entry of importsToRename) { + const { files, src, dest } = entry; + await updateImports({ files, src, dest }); + } - console.log("done!!"); + console.log("Done with building!"); }; build(); diff --git a/resources/todomvc/vanilla-examples/javascript-web-components/src/index.mjs b/resources/todomvc/vanilla-examples/javascript-web-components/src/index.mjs new file mode 100644 index 000000000..fd4374edb --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-web-components/src/index.mjs @@ -0,0 +1,10 @@ +import { BenchmarkConnector } from "/node_modules/speedometer-utils/benchmark.mjs"; +import suites, { appName, appVersion } from "/src/workload-test.mjs"; + +/* +Paste below into dev console for manual testing: +window.addEventListener("message", (event) => console.log(event.data)); +window.postMessage({ id: "todomvc-postmessage-1.0.0", key: "benchmark-connector", type: "benchmark-suite", name: "default" }, "*"); +*/ +const benchmarkConnector = new BenchmarkConnector(suites, appName, appVersion); +benchmarkConnector.connect(); diff --git a/resources/todomvc/vanilla-examples/javascript-web-components/src/workload-test.mjs b/resources/todomvc/vanilla-examples/javascript-web-components/src/workload-test.mjs new file mode 100644 index 000000000..7d436dfa4 --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-web-components/src/workload-test.mjs @@ -0,0 +1,36 @@ +import { BenchmarkStep, BenchmarkSuite } from "/node_modules/speedometer-utils/benchmark.mjs"; +import { getAllElements, getElement } from "/node_modules/speedometer-utils/helpers.mjs"; +import { getTodoText, defaultLanguage } from "/node_modules/speedometer-utils/translations.mjs"; +import { numberOfItemsToAdd } from "/node_modules/speedometer-utils/todomvc-utils.mjs"; + +export const appName = "todomvc-postmessage"; +export const appVersion = "1.0.0"; + +const suites = { + default: new BenchmarkSuite("default", [ + new BenchmarkStep("Adding-items", () => { + const input = getElement(".new-todo-input", ["todo-app", "todo-topbar"]); + for (let i = 0; i < numberOfItemsToAdd; i++) { + input.value = getTodoText(defaultLanguage, i); + input.dispatchEvent(new Event("input")); + input.dispatchEvent(new KeyboardEvent("keyup", { keyCode: 13, key: "Enter" })); + } + }), + new BenchmarkStep("Completing-items", () => { + const items = getAllElements("todo-item", ["todo-app", "todo-list"]); + for (let i = 0; i < numberOfItemsToAdd; i++) { + const item = getElement(".toggle-todo-input", [], items[i]); + item.click(); + } + }), + new BenchmarkStep("Deleting-items", () => { + const items = getAllElements("todo-item", ["todo-app", "todo-list"]); + for (let i = numberOfItemsToAdd - 1; i >= 0; i--) { + const item = getElement(".remove-todo-button", [], items[i]); + item.click(); + } + }), + ]), +}; + +export default suites;