Update Speedometer2.1 content to latest runner (#348)

Merge the speedometer2.1-content branch with the latest main branch to get the new developer-menu.
This commit is contained in:
Camillo Bruni 2024-01-30 22:44:39 +01:00 committed by GitHub
parent 9365aa2e52
commit 491acc2d64
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
69 changed files with 2947 additions and 86754 deletions

View file

@ -1,18 +1,50 @@
/node_modules
**/dist
package-lock.json
**/.angular
/resources/tentative
/resources/tentative/*
/resources/todomvc/architecture-examples/*
!/resources/todomvc/architecture-examples/angular
!/resources/todomvc/architecture-examples/angular-complex
!/resources/todomvc/architecture-examples/backbone
!/resources/todomvc/architecture-examples/backbone-complex
!/resources/todomvc/architecture-examples/lit
/resources/todomvc/architecture-examples/lit/.wireit
/resources/todomvc/architecture-examples/lit/dist/index.js
!/resources/todomvc/architecture-examples/lit-complex
!/resources/todomvc/architecture-examples/preact
!/resources/todomvc/architecture-examples/preact-complex
!/resources/todomvc/architecture-examples/react
!/resources/todomvc/architecture-examples/react-complex
!/resources/todomvc/architecture-examples/react-redux
!/resources/todomvc/architecture-examples/react-redux-complex
!/resources/todomvc/architecture-examples/svelte
!/resources/todomvc/architecture-examples/svelte-complex
!/resources/todomvc/architecture-examples/vue
!/resources/todomvc/architecture-examples/vue-complex
/resources/todomvc/vanilla-examples/*
!/resources/todomvc/vanilla-examples/javascript-es5
!/resources/todomvc/vanilla-examples/javascript-es5-complex
!/resources/todomvc/vanilla-examples/javascript-es6
!/resources/todomvc/vanilla-examples/javascript-es6-complex
!/resources/todomvc/vanilla-examples/javascript-es6-webpack
!/resources/todomvc/vanilla-examples/javascript-es6-webpack-complex
!/resources/todomvc/vanilla-examples/javascript-web-components
!/resources/todomvc/vanilla-examples/javascript-web-components-complex/
!/resources/todomvc/big-dom-generator
!/resources/editors
/resources/editors/assets
!/resources/newssite
!/resources/newssite/news-next
!/resources/newssite/news-nuxt
!/resources/newssite/news-site-css
/resources/perf.webkit.org
/resources/react-stockcharts

View file

@ -24,7 +24,7 @@
*/
{
"extends": ["eslint:recommended"],
"extends": ["eslint:recommended", "plugin:@typescript-eslint/base"],
"env": {
"browser": true,
"node": true,

16
.gitignore vendored
View file

@ -8,14 +8,30 @@
.angular
/resources/todomvc/architecture-examples/angular/node_modules/
/resources/todomvc/architecture-examples/angular-complex/node_modules/
/resources/todomvc/architecture-examples/backbone/node_modules/
/resources/todomvc/architecture-examples/backbone-complex/node_modules/
/resources/todomvc/architecture-examples/jquery/node_modules/
/resources/todomvc/architecture-examples/jquery-complex/node_modules/
/resources/todomvc/architecture-examples/preact/node_modules/
/resources/todomvc/architecture-examples/preact-complex/node_modules/
/resources/todomvc/architecture-examples/react/node_modules/
/resources/todomvc/architecture-examples/react-complex/node_modules/
/resources/todomvc/architecture-examples/react-redux/node_modules/
/resources/todomvc/architecture-examples/react-redux-complex/node_modules/
/resources/todomvc/architecture-examples/svelte/node_modules/
/resources/todomvc/architecture-examples/svelte-complex/node_modules/
/resources/todomvc/architecture-examples/vue/node_modules/
/resources/todomvc/architecture-examples/vue-complex/node_modules/
/resources/todomvc/architecture-examples/lit-complex/node_modules/
/resources/todomvc/vanilla-examples/javascript-es5/node_modules/
/resources/todomvc/vanilla-examples/javascript-es5-complex/node_modules/
/resources/todomvc/vanilla-examples/javascript-es6/node_modules/
/resources/todomvc/vanilla-examples/javascript-es6-complex/node_modules/
/resources/todomvc/vanilla-examples/javascript-es6-webpack/node_modules/
/resources/todomvc/vanilla-examples/javascript-es6-webpack-complex/node_modules/
/resources/todomvc/vanilla-examples/javascript-web-components-complex/node_modules/
/resources/charts/node_modules/
/resources/todomvc/big-dom-generator/node_modules/

View file

@ -1,19 +1,50 @@
/node_modules
**/dist
package-lock.json
**/.angular
/resources/tentative/*
!/resources/tentative/editors
/resources/todomvc/architecture-examples/*
!/resources/todomvc/architecture-examples/angular
!/resources/todomvc/architecture-examples/angular-complex
!/resources/todomvc/architecture-examples/backbone
!/resources/todomvc/architecture-examples/backbone-complex
!/resources/todomvc/architecture-examples/lit
/resources/todomvc/architecture-examples/lit/dist/index.js
/resources/todomvc/architecture-examples/lit/.wireit
!/resources/todomvc/architecture-examples/lit-complex
!/resources/todomvc/architecture-examples/preact
!/resources/todomvc/architecture-examples/preact-complex
!/resources/todomvc/architecture-examples/react
!/resources/todomvc/architecture-examples/react-complex
!/resources/todomvc/architecture-examples/react-redux
!/resources/todomvc/architecture-examples/react-redux-complex
!/resources/todomvc/architecture-examples/svelte
!/resources/todomvc/architecture-examples/svelte-complex
!/resources/todomvc/architecture-examples/vue
!/resources/todomvc/architecture-examples/vue-complex
/resources/todomvc/vanilla-examples/*
!/resources/todomvc/vanilla-examples/javascript-es5
!/resources/todomvc/vanilla-examples/javascript-es5-complex
!/resources/todomvc/vanilla-examples/javascript-es6
!/resources/todomvc/vanilla-examples/javascript-es6-complex
!/resources/todomvc/vanilla-examples/javascript-es6-webpack
!/resources/todomvc/vanilla-examples/javascript-es6-webpack-complex
!/resources/todomvc/vanilla-examples/javascript-web-components
!/resources/todomvc/vanilla-examples/javascript-web-components-complex/
!/resources/todomvc/big-dom-generator
!/resources/editors
/resources/editors/assets
!/resources/newssite
!/resources/newssite/news-next
!/resources/newssite/news-nuxt
!/resources/newssite/news-site-css
/resources/perf.webkit.org
/resources/react-stockcharts

View file

@ -1,6 +1,8 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=850" />
<title>Speedometer 3.0 Interactive Runner</title>
<script src="resources/interactive.mjs" type="module"></script>
<link rel="icon" href="resources/favicon.png" />

View file

@ -2,6 +2,7 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=850" />
<title>Speedometer 3.0</title>
<link rel="stylesheet" href="resources/main.css" />
<link rel="icon" href="resources/favicon.png" />
@ -12,6 +13,7 @@
<section id="home">
<a href="#home" class="logo">
<img srcset="resources/logo@2x.png 2x" src="resources/logo.png" alt="Speedometer" />
<div class="version">3.0</div>
</a>
<div class="content">
<p>Speedometer is a browser benchmark that measures the responsiveness of Web applications. It uses demo web applications to simulate user actions such as adding to-do items.</p>
@ -35,10 +37,11 @@
<section id="running">
<a href="#home" class="logo">
<img srcset="resources/logo@2x.png 2x" src="resources/logo.png" alt="Speedometer" />
<div class="version">2.1 3.0</div>
</a>
<div id="testContainer"></div>
<div id="progress">
<div id="progress-completed"></div>
<progress aria-label="Progress" id="progress-completed"></progress>
</div>
<div id="info">
<div id="info-label"></div>
@ -49,6 +52,7 @@
<section id="summary">
<a href="#home" class="logo">
<img srcset="resources/logo@2x.png 2x" src="resources/logo.png" alt="Speedometer" />
<div class="version">3.0</div>
</a>
<h1>Score</h1>
<div class="gauge">
@ -68,13 +72,14 @@
<section id="details">
<a href="#home" class="logo">
<img srcset="resources/logo@2x.png 2x" src="resources/logo.png" alt="Speedometer" />
<div class="version">3.0</div>
</a>
<div class="section-grid">
<h1 class="section-header">Detailed Results</h1>
<div class="section-content all-metric-results">
<div class="aggregated-metric-result">
<h2>Total</h2>
<div id="total-chart"></div>
<h2>Geomean</h2>
<div id="geomean-chart"></div>
<h2>Tests</h2>
<div id="tests-chart"></div>
</div>
@ -88,8 +93,9 @@
<a class="button" href="#summary" title="Go back to the simplified summary view.">Summary</a>
</div>
<div class="button-row export-buttons">
<a class="button" id="download-json" title="Download all result metrics as json string.">Download JSON</a>
<button id="copy-json" title="Copy all result metrics as json string.">Copy JSON</button>
<button id="copy-full-json" title="Copy full result metrics as json string.">Copy JSON</button>
<a class="button" id="download-full-json" title="Download full result metrics as json string.">Download JSON</a>
<a class="button" id="download-classic-json" title="Download backwards-compatible result metrics as json string.">Download Classic JSON</a>
<a class="button" id="download-csv" title="Download all result metrics as CSV string.">Download CSV</a>
<button id="copy-csv" title="Copy all result metrics as CSV string.">Copy CSV</button>
</div>
@ -100,34 +106,68 @@
<section id="about">
<a href="#home" class="logo">
<img srcset="resources/logo@2x.png 2x" src="resources/logo.png" alt="Speedometer" />
<div class="version">3.0</div>
</a>
<div class="section-grid">
<h1 class="section-header">About Speedometer 2.1</h1>
<h1 class="section-header">About Speedometer 3</h1>
<div class="section-content">
<p>Speedometer tests a browser's Web app responsiveness by timing simulated user interactions.</p>
<p>Speedometer 3 is a benchmark for web browsers that measures Web application responsiveness by timing simulated user interactions on various workloads.</p>
<p>
This benchmark simulates user actions for adding, completing, and removing to-do items using multiple examples in TodoMVC. Each example in TodoMVC implements the same todo application using DOM APIs in different ways. Some
call DOM APIs directly from ECMAScript 5 (ES5), ECMASCript 2015 (ES6), ES6 transpiled to ES5, and Elm transpiled to ES5. Others use one of eleven popular JavaScript frameworks: React, React with Redux, Ember.js,
Backbone.js, AngularJS, (new) Angular, Vue.js, jQuery, Preact, Inferno, and Flight. Many of these frameworks are used on the most popular websites in the world, such as Facebook and Twitter. The performance of these types
of operations depends on the speed of the DOM APIs, the JavaScript engine, CSS style resolution, layout, and other technologies.
The following high level user journeys are implemented in the current version. Each of these journeys has one or more workloads which test important aspects of it - for example commonly used patterns, frameworks, or
technologies.
</p>
<ul>
<li>Working with a todo list</li>
<ul>
<li>Measures the time to add, complete, and remove 100 todo items in a basic list.</li>
<li>Each example implements the same todo application (TodoMVC) using different techniques and frameworks.</li>
<li>
Workloads: <a href="resources/todomvc/vanilla-examples/javascript-es5/readme.md" target="_blank">TodoMVC-JavaScript-ES5</a>,
<a href="resources/todomvc/vanilla-examples/javascript-web-components/readme.md" target="_blank">TodoMVC-WebComponents</a>,
<a href="resources/todomvc/architecture-examples/react-complex/readme.md" target="_blank">TodoMVC-React-Complex-DOM</a>,
<a href="resources/todomvc/architecture-examples/backbone/readme.md" target="_blank">TodoMVC-Backbone</a>, <a href="resources/todomvc/architecture-examples/angular/readme.md" target="_blank">TodoMVC-Angular</a>,
<a href="resources/todomvc/architecture-examples/vue/readme.md" target="_blank">TodoMVC-Vue</a>, <a href="resources/todomvc/architecture-examples/jquery/readme.md" target="_blank">TodoMVC-jQuery</a>,
<a href="resources/todomvc/architecture-examples/preact/readme.md" target="_blank">TodoMVC-Preact</a>, <a href="resources/todomvc/architecture-examples/svelte/readme.md" target="_blank">TodoMVC-Svelte</a>,
<a href="resources/todomvc/architecture-examples/lit/readme.md" target="_blank">TodoMVC-Lit</a>
</li>
</ul>
<li>Editing rich text</li>
<ul>
<li>Loading and styling text inside WYSIWYG and code editors.</li>
<li>Workloads: <a href="resources/editors/readme.md" target="_blank">Editor-CodeMirror</a>, <a href="resources/editors/readme.md" target="_blank">Editor-TipTap</a></li>
</ul>
<li>Rendering charts</li>
<ul>
<li>Loading and interacting with SVG and canvas charts.</li>
<li>
Workloads: <a href="resources/charts/readme.md" target="_blank">Charts-observable-plot</a>, <a href="resources/charts/readme.md" target="_blank">Charts-chartjs</a>,
<a href="resources/react-stockcharts/readme.md" target="_blank">React-Stockcharts-SVG</a>, <a href="resources/perf.webkit.org/readme.md" target="_blank">Perf-Dashboard</a>
</li>
</ul>
<li>Reading a news site</li>
<ul>
<li>Navigating across pages and interacting with a typical looking news site.</li>
<li>Workloads: <a href="resources/newssite/news-next/readme.md" target="_blank">NewsSite-Next</a>, <a href="resources/newssite/news-nuxt/readme.md" target="_blank">NewsSite-Nuxt</a></li>
</ul>
</ul>
<p>
Although user-driven actions like mouse movements and keyboard input cannot be accurately emulated in JavaScript, Speedometer does its best to faithfully replay a typical workload within the demo applications. To make the
run time long enough to measure with the limited precision, we synchronously execute a large number of the operations, such as adding one hundred to-do items.
</p>
<p>
Modern browser engines execute some work asynchronously as an optimization strategy to reduce the run time of synchronous operations. While returning control back to JavaScript execution as soon as possible is worth
pursuing, the run time cost of such an asynchronous work should still be taken into a holistic measurement of web application performance. In addition, some modern JavaScript frameworks such as Vue.js and Preact call into
DOM APIs asynchronously as an optimization technique. Speedometer approximates the run time of this asynchronous work in the UI thread with a zero-second timer that is scheduled immediately after each execution of
synchronous operations.
</p>
<p>Speedometer does not attempt to measure concurrent asynchronous work that does not directly impact the UI thread, which tends not to affect app responsiveness.</p>
<p class="note"><strong>Note:</strong> Speedometer should not be used as a way to compare the performance of different JavaScript frameworks as work load differs greatly in each framework.</p>
<p class="note"><strong>Notes about methodology</strong></p>
<ul>
<li>Although user-driven actions like mouse movements and keyboard input cannot be fully emulated in JavaScript, Speedometer does its best to faithfully replay a typical workload within the demo applications.</li>
<li>To make the run time long enough to measure with the limited precision, we synchronously execute a large number of the operations, such as adding one hundred to-do items.</li>
<li>
Modern browser engines execute some work asynchronously as an optimization strategy to reduce the run time of synchronous operations. While returning control back to JavaScript execution as soon as possible is worth
pursuing, the run time cost of such an asynchronous work should still be taken into a holistic measurement of web application performance. In addition, some JavaScript frameworks call into DOM APIs asynchronously as an
optimization technique. Speedometer approximates the run time of this asynchronous work in the UI thread with a zero-second timer that is scheduled immediately after each execution of synchronous operations.
</li>
<li>Speedometer does not attempt to measure concurrent asynchronous work (e.g. in Web Workers).</li>
<li>Speedometer should not be used as a way to compare the performance of different JavaScript frameworks.</li>
<li>
The goal of all workloads is to represent a scenario that could be found on the Web. Although all workloads strive to use patterns that are commonly used, some implementation details are Speedometer specific and should
not be used as a guideline on how to implement and deploy a standalone app. For example, due to constraints within the test harness, workloads must not depend on a server infrastructure to function properly and are
built as static files ahead of time.
</li>
</ul>
</div>
<div class="buttons section-footer">
<div class="button-row">

1089
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -16,8 +16,9 @@
"license": "SEE LICENSE IN LICENSE",
"scripts": {
"dev": "http-server ./ -p 7000 -c-1 --cors",
"server": "http-server ./ -p 7000 --cors",
"lint:check": "eslint **/*.mjs",
"lint:fix": "eslint \"**/*.{js,mjs,jsx}\" --fix",
"lint:fix": "eslint \"**/*.{js,mjs,jsx,ts}\" --fix",
"pretty:check": "prettier --check ./",
"pretty:fix": "prettier --write ./",
"format": "npm run pretty:fix ; npm run lint:fix",
@ -29,9 +30,13 @@
"@babel/core": "^7.21.3",
"@babel/eslint-parser": "^7.21.3",
"@babel/plugin-proposal-decorators": "^7.21.0",
"@nuxt/eslint-config": "^0.1.1",
"@next/eslint-plugin-next": "^13.4.5",
"@typescript-eslint/eslint-plugin": "^5.59.0",
"@typescript-eslint/parser": "^5.59.0",
"command-line-args": "^5.2.1",
"command-line-usage": "^6.1.3",
"eslint": "^8.33.0",
"eslint": "^8.38.0",
"eslint-plugin-ember": "^11.4.8",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-vue": "^9.10.0",
@ -40,6 +45,7 @@
"mocha": "^10.2.0",
"prettier": "^2.8.3",
"selenium-webdriver": "^4.8.0",
"sinon": "^15.0.1"
"sinon": "^15.0.1",
"typescript": "^5.0.4"
}
}

View file

@ -1,84 +0,0 @@
// This file can be customized to report results as needed.
(function () {
if (!window.testRunner && location.search !== "?webkit" && location.hash !== "#webkit")
return;
if (window.testRunner)
window.testRunner.waitUntilDone();
const scriptElement = document.createElement("script");
scriptElement.src = "../resources/runner.js";
document.head.appendChild(scriptElement);
const styleElement = document.createElement("style");
styleElement.textContent = "pre { padding-top: 600px; }";
document.head.appendChild(styleElement);
let createTest;
const valuesByIteration = new Array();
window.onload = () => {
document.body.removeChild(document.querySelector("main"));
startBenchmark();
};
window.benchmarkClient = {
iterationCount: 5, // Use 4 different instances of DRT/WTR to run 5 iterations.
willStartFirstIteration(iterationCount) {
createTest = (name, aggregator, isLastTest, unit = "ms") => {
return {
customIterationCount: iterationCount,
doNotIgnoreInitialRun: true,
doNotMeasureMemoryUsage: true,
continueTesting: !isLastTest,
unit: unit,
name: name,
aggregator: aggregator,
};
};
PerfTestRunner.prepareToMeasureValuesAsync(createTest(null, "Geometric"));
},
didRunSuites(measuredValues) {
PerfTestRunner.measureValueAsync(measuredValues.geomean);
valuesByIteration.push(measuredValues);
},
didFinishLastIteration() {
document.head.removeChild(document.querySelector("style"));
const measuredValuesByFullName = {};
function addToMeasuredValue(value, fullName, aggregator) {
const values = measuredValuesByFullName[fullName] || new Array();
measuredValuesByFullName[fullName] = values;
values.push(value);
values.aggregator = aggregator;
}
const scores = [];
valuesByIteration.forEach((measuredValues) => {
scores.push(measuredValues.score);
for (const suiteName in measuredValues.tests) {
const suite = measuredValues.tests[suiteName];
for (const testName in suite.tests) {
const test = suite.tests[testName];
for (const subtestName in test.tests)
addToMeasuredValue(test.tests[subtestName], `${suiteName} / ${testName} / ${subtestName}`);
addToMeasuredValue(test.total, `${suiteName}/${testName}`, "Total");
}
addToMeasuredValue(suite.total, suiteName, "Total");
}
});
PerfTestRunner.reportValues(createTest(null, null, false, "pt"), scores);
const fullNames = new Array();
for (const fullName in measuredValuesByFullName)
fullNames.push(fullName);
for (let i = 0; i < fullNames.length; i++) {
const values = measuredValuesByFullName[fullNames[i]];
PerfTestRunner.reportValues(createTest(fullNames[i], values.aggregator, i + 1 === fullNames.length), values);
}
},
};
})();

View file

@ -1,4 +1,4 @@
import { Metric, MILLISECONDS_PER_MINUTE } from "./metric.mjs";
import { Metric } from "./metric.mjs";
import { params } from "./params.mjs";
const performance = globalThis.performance;
@ -10,36 +10,80 @@ export class BenchmarkTestStep {
}
}
function getParent(lookupStartNode, path) {
const parent = path.reduce((root, selector) => {
const node = root.querySelector(selector);
return node.shadowRoot ?? node;
}, lookupStartNode);
return parent;
}
class Page {
constructor(frame) {
this._frame = frame;
}
layout() {
const body = this._frame.contentDocument.body.getBoundingClientRect();
this.layout.e = document.elementFromPoint((body.width / 2) | 0, (body.height / 2) | 0);
}
async waitForElement(selector) {
return new Promise((resolve) => {
const resolveIfReady = () => {
const element = this.querySelector(selector);
if (element) {
window.requestAnimationFrame(() => {
return resolve(element);
});
} else {
setTimeout(resolveIfReady, 50);
}
let callback = resolveIfReady;
if (element)
callback = () => resolve(element);
window.requestAnimationFrame(callback);
};
resolveIfReady();
});
}
querySelector(selector) {
const element = this._frame.contentDocument.querySelector(selector);
/**
* Returns the first element within the document that matches the specified selector, or group of selectors.
* If no matches are found, null is returned.
*
* An optional path param is added to be able to target elements within a shadow DOM or nested shadow DOMs.
*
* @example
* // DOM structure: <todo-app> -> #shadow-root -> <todo-list> -> #shadow-root -> <todo-item>
* // return PageElement(<todo-item>)
* querySelector("todo-item", ["todo-app", "todo-list"]);
*
* @param {string} selector A string containing one or more selectors to match.
* @param {string[]} [path] An array containing a path to the parent element.
* @returns PageElement | null
*/
querySelector(selector, path = []) {
const lookupStartNode = this._frame.contentDocument;
const element = getParent(lookupStartNode, path).querySelector(selector);
if (element === null)
return null;
return this._wrapElement(element);
}
querySelectorAll(selector) {
const elements = Array.from(this._frame.contentDocument.querySelectorAll(selector));
/**
* Returns all elements within the document that matches the specified selector, or group of selectors.
* If no matches are found, null is returned.
*
* An optional path param is added to be able to target elements within a shadow DOM or nested shadow DOMs.
*
* @example
* // DOM structure: <todo-app> -> #shadow-root -> <todo-list> -> #shadow-root -> <todo-item>
* // return [PageElement(<todo-item>), PageElement(<todo-item>)]
* querySelectorAll("todo-item", ["todo-app", "todo-list"]);
*
* @param {string} selector A string containing one or more selectors to match.
* @param {string[]} [path] An array containing a path to the parent element.
* @returns array
*/
querySelectorAll(selector, path = []) {
const lookupStartNode = this._frame.contentDocument;
const elements = Array.from(getParent(lookupStartNode, path).querySelectorAll(selector));
for (let i = 0; i < elements.length; i++)
elements[i] = this._wrapElement(elements[i]);
return elements;
@ -52,11 +96,21 @@ class Page {
return this._wrapElement(element);
}
call(function_name) {
this._frame.contentWindow[function_name]();
call(functionName) {
this._frame.contentWindow[functionName]();
return null;
}
callAsync(functionName) {
setTimeout(() => {
this._frame.contentWindow[functionName]();
}, 0);
}
callToGetElement(functionName) {
return this._wrapElement(this._frame.contentWindow[functionName]());
}
_wrapElement(element) {
return new PageElement(element);
}
@ -90,6 +144,10 @@ class PageElement {
this.#node.focus();
}
getElementByMethod(name) {
return new PageElement(this.#node[name]());
}
dispatchEvent(eventName, options = NATIVE_OPTIONS, eventType = Event) {
if (eventName === "submit")
// FIXME FireFox doesn't like `new Event('submit')
@ -106,30 +164,196 @@ class PageElement {
enter(type, options = undefined) {
const ENTER_KEY_CODE = 13;
let eventOptions = {
bubbles: true,
cancelable: true,
keyCode: ENTER_KEY_CODE,
which: ENTER_KEY_CODE,
key: "Enter",
};
return this.dispatchKeyEvent(type, ENTER_KEY_CODE, "Enter", options);
}
dispatchKeyEvent(type, keyCode, key, options) {
let eventOptions = { bubbles: true, cancelable: true, keyCode, which: keyCode, key };
if (options !== undefined)
eventOptions = Object.assign(eventOptions, options);
const event = new KeyboardEvent(type, eventOptions);
this.#node.dispatchEvent(event);
}
dispatchMouseEvent(type, offsetX, offsetY, options) {
const boundingRect = this.#node.getBoundingClientRect();
const clientX = offsetX + boundingRect.x;
const clientY = offsetY + boundingRect.y;
const contentWindow = this.#node.ownerDocument.defaultView;
const screenX = clientX + contentWindow.screenX;
const screenY = clientY + contentWindow.screenY;
let eventOptions = { bubbles: true, cancelable: true, clientX, clientY, screenX, screenY };
if (options !== undefined)
eventOptions = Object.assign(eventOptions, options);
const event = new contentWindow.MouseEvent(type, eventOptions);
this.#node.dispatchEvent(event);
}
/**
* Returns the first element found in a node of a PageElement that matches the specified selector, or group of selectors. If a shadow DOM is present in the node, the shadow DOM is used to query.
* If no matches are found, null is returned.
*
* @param {string} selector A string containing one or more selectors to match.
* @param {string[]} [path] An array containing a path to the parent element.
* @returns PageElement | null
*/
querySelectorInShadowRoot(selector, path = []) {
const lookupStartNode = this.#node.shadowRoot ?? this.#node;
const element = getParent(lookupStartNode, path).querySelector(selector);
if (element === null)
return null;
return new PageElement(element);
}
querySelector(selector) {
const element = this.#node.querySelector(selector);
if (element === null)
return null;
return new PageElement(element);
}
}
function geomeanToScore(geomean) {
return 1000 / geomean;
}
// The WarmupSuite is used to make sure all runner helper functions and
// classes are compiled, to avoid unnecessary pauses due to delayed
// compilation of runner methods in the middle of the measuring cycle.
const WarmupSuite = {
name: "Warmup",
url: "warmup/index.html",
async prepare(page) {
await page.waitForElement("#testItem");
},
tests: [
// Make sure to run ever page.method once at least
new BenchmarkTestStep("WarmingUpPageMethods", (page) => {
let results = [];
results.push(page.querySelector(".testItem"));
results.push(page.querySelectorAll(".item"));
results.push(page.getElementById("testItem"));
}),
new BenchmarkTestStep("WarmingUpPageElementMethods", (page) => {
const item = page.getElementById("testItem");
item.setValue("value");
item.click();
item.focus();
item.dispatchEvent("change");
item.enter("keypress");
item.dispatchEvent("input");
item.enter("keyup");
}),
new BenchmarkTestStep("WarmingUpPageElementMouseMethods", (page) => {
const item = page.getElementById("testItem");
const mouseEventOptions = { clientX: 100, clientY: 100, bubbles: true, cancelable: true };
const wheelEventOptions = {
clientX: 200,
clientY: 200,
deltaMode: 0,
delta: -10,
deltaY: -10,
bubbles: true,
cancelable: true,
};
item.dispatchEvent("mousedown", mouseEventOptions, MouseEvent);
item.dispatchEvent("mousemove", mouseEventOptions, MouseEvent);
item.dispatchEvent("mouseup", mouseEventOptions, MouseEvent);
item.dispatchEvent("wheel", wheelEventOptions, WheelEvent);
}),
],
};
class TestInvoker {
constructor(syncCallback, asyncCallback, reportCallback) {
this._syncCallback = syncCallback;
this._asyncCallback = asyncCallback;
this._reportCallback = reportCallback;
}
}
class TimerTestInvoker extends TestInvoker {
start() {
return new Promise((resolve) => {
setTimeout(() => {
this._syncCallback();
setTimeout(() => {
this._asyncCallback();
requestAnimationFrame(async () => {
await this._reportCallback();
resolve();
});
}, 0);
}, params.waitBeforeSync);
});
}
}
class RAFTestInvoker extends TestInvoker {
start() {
return new Promise((resolve) => {
if (params.waitBeforeSync)
setTimeout(() => this._scheduleCallbacks(resolve), params.waitBeforeSync);
else
this._scheduleCallbacks(resolve);
});
}
_scheduleCallbacks(resolve) {
requestAnimationFrame(() => this._syncCallback());
requestAnimationFrame(() => {
setTimeout(() => {
this._asyncCallback();
setTimeout(async () => {
await this._reportCallback();
resolve();
}, 0);
}, 0);
});
}
}
// https://stackoverflow.com/a/47593316
function seededHashRandomNumberGenerator(a) {
return function () {
var t = a += 0x6d2b79f5;
t = Math.imul(t ^ (t >>> 15), t | 1);
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
return (t ^ (t >>> 14)) >>> 0;
};
}
export class BenchmarkRunner {
constructor(suites, client) {
this._suites = suites;
if (params.useWarmupSuite)
this._suites = [WarmupSuite, ...suites];
this._client = client;
this._page = null;
this._metrics = {
__proto__: null,
Total: new Metric("Total"),
Score: new Metric("Score", "score"),
};
this._metrics = null;
this._iterationCount = params.iterationCount;
if (params.shuffleSeed !== "off")
this._suiteOrderRandomNumberGenerator = seededHashRandomNumberGenerator(params.shuffleSeed);
}
async runMultipleIterations(iterationCount) {
this._iterationCount = iterationCount;
if (this._client?.willStartFirstIteration)
await this._client.willStartFirstIteration(iterationCount);
const iterationStartLabel = "iteration-start";
const iterationEndLabel = "iteration-end";
for (let i = 0; i < iterationCount; i++) {
performance.mark(iterationStartLabel);
await this._runAllSuites();
performance.mark(iterationEndLabel);
performance.measure(`iteration-${i}`, iterationStartLabel, iterationEndLabel);
}
if (this._client?.didFinishLastIteration)
await this._client.didFinishLastIteration(this._metrics);
}
_removeFrame() {
@ -147,16 +371,10 @@ export class BenchmarkRunner {
style.border = "0px none";
style.position = "absolute";
frame.setAttribute("scrolling", "no");
const computedStyle = getComputedStyle(document.body);
const marginLeft = parseInt(computedStyle.marginLeft);
const marginTop = parseInt(computedStyle.marginTop);
if (window.innerWidth > params.viewport.width + marginLeft && window.innerHeight > params.viewport.height + marginTop) {
style.left = `${marginLeft}px`;
style.top = `${marginTop}px`;
} else {
style.left = "0px";
style.top = "0px";
}
frame.className = "test-runner";
style.left = "50%";
style.top = "50%";
style.transform = "translate(-50%, -50%)";
if (this._client?.willAddTestFrame)
await this._client.willAddTestFrame(frame);
@ -166,38 +384,65 @@ export class BenchmarkRunner {
return frame;
}
async runMultipleIterations(iterationCount) {
if (this._client?.willStartFirstIteration)
await this._client.willStartFirstIteration(iterationCount);
for (let i = 0; i < iterationCount; i++)
await this._runAllSuites();
if (this._client?.didFinishLastIteration)
await this._client.didFinishLastIteration(this._metrics);
}
async _runAllSuites() {
this._measuredValues = { tests: {}, total: 0, mean: NaN, geomean: NaN, score: NaN };
const prepareStartLabel = "runner-prepare-start";
const prepareEndLabel = "runner-prepare-end";
performance.mark(prepareStartLabel);
this._removeFrame();
await this._appendFrame();
this._page = new Page(this._frame);
for (const suite of this._suites) {
let suites = [...this._suites];
if (this._suiteOrderRandomNumberGenerator) {
// We just do a simple Fisher-Yates shuffle based on the repeated hash of the
// seed. This is not a high quality RNG, but it's plenty good enough.
for (let i = 0; i < suites.length - 1; i++) {
let j = i + (this._suiteOrderRandomNumberGenerator() % (suites.length - i));
let tmp = suites[i];
suites[i] = suites[j];
suites[j] = tmp;
}
}
performance.mark(prepareEndLabel);
performance.measure("runner-prepare", prepareStartLabel, prepareEndLabel);
for (const suite of suites) {
if (!suite.disabled)
await this._runSuite(suite);
}
const finalizeStartLabel = "runner-finalize-start";
const finalizeEndLabel = "runner-finalize-end";
performance.mark(finalizeStartLabel);
// Remove frame to clear the view for displaying the results.
this._removeFrame();
await this._finalize();
performance.mark(finalizeEndLabel);
performance.measure("runner-finalize", finalizeStartLabel, finalizeEndLabel);
}
async _runSuite(suite) {
const suitePrepareStartLabel = `suite-${suite.name}-prepare-start`;
const suitePrepareEndLabel = `suite-${suite.name}-prepare-end`;
const suiteStartLabel = `suite-${suite.name}-start`;
const suiteEndLabel = `suite-${suite.name}-end`;
performance.mark(suitePrepareStartLabel);
await this._prepareSuite(suite);
performance.mark(`start-suite-${suite.name}`);
performance.mark(suitePrepareEndLabel);
performance.mark(suiteStartLabel);
for (const test of suite.tests)
await this._runTestAndRecordResults(suite, test);
performance.mark(`end-suite-${suite.name}`);
performance.mark(suiteEndLabel);
performance.measure(`suite-${suite.name}-prepare`, suitePrepareStartLabel, suitePrepareEndLabel);
performance.measure(`suite-${suite.name}`, suiteStartLabel, suiteEndLabel);
}
async _prepareSuite(suite) {
@ -213,51 +458,63 @@ export class BenchmarkRunner {
async _runTestAndRecordResults(suite, test) {
/* eslint-disable-next-line no-async-promise-executor */
return new Promise(async (resolve) => {
if (this._client?.willRunTest)
await this._client.willRunTest(suite, test);
if (this._client?.willRunTest)
await this._client.willRunTest(suite, test);
setTimeout(() => {
this._runTest(suite, test, this._page, resolve);
}, 0);
});
}
// This function ought be as simple as possible. Don't even use Promise.
_runTest(suite, test, page, testDoneCallback) {
// Prepare all mark labels outside the measuring loop.
const startLabel = `${suite.name}.${test.name}-start`;
const syncEndLabel = `${suite.name}.${test.name}-sync-end`;
const asyncStartLabel = `${suite.name}.${test.name}-async-start`;
const asyncEndLabel = `${suite.name}.${test.name}-async-end`;
performance.mark(startLabel);
const syncStartTime = performance.now();
test.run(this._prepareValue, page._frame.contentWindow, page._frame.contentDocument);
const syncEndTime = performance.now();
performance.mark(syncEndLabel);
let syncTime;
let asyncStartTime;
let asyncTime;
const runSync = () => {
if (params.warmupBeforeSync) {
performance.mark("warmup-start");
const startTime = performance.now();
// Infinite loop for the specified ms.
while (performance.now() - startTime < params.warmupBeforeSync)
continue;
performance.mark("warmup-end");
}
performance.mark(startLabel);
const syncStartTime = performance.now();
test.run(this._prepareValue, this._page._frame.contentWindow, this._page._frame.contentDocument);
const syncEndTime = performance.now();
performance.mark(syncEndLabel);
const syncTime = syncEndTime - syncStartTime;
syncTime = syncEndTime - syncStartTime;
performance.mark(asyncStartLabel);
const asyncStartTime = performance.now();
setTimeout(() => {
performance.mark(asyncStartLabel);
asyncStartTime = performance.now();
};
const measureAsync = () => {
// Some browsers don't immediately update the layout for paint.
// Force the layout here to ensure we're measuring the layout time.
const height = this._frame.contentDocument.body.getBoundingClientRect().height;
const asyncEndTime = performance.now();
const asyncTime = asyncEndTime - asyncStartTime;
asyncTime = asyncEndTime - asyncStartTime;
this._frame.contentWindow._unusedHeightValue = height; // Prevent dead code elimination.
performance.mark(asyncEndLabel);
if (params.warmupBeforeSync)
performance.measure("warmup", "warmup-start", "warmup-end");
performance.measure(`${suite.name}.${test.name}-sync`, startLabel, syncEndLabel);
performance.measure(`${suite.name}.${test.name}-async`, asyncStartLabel, asyncEndLabel);
window.requestAnimationFrame(() => {
this._recordTestResults(suite, test, syncTime, asyncTime, height, testDoneCallback);
});
}, 0);
};
const report = () => this._recordTestResults(suite, test, syncTime, asyncTime);
const invokerClass = params.measurementMethod === "raf" ? RAFTestInvoker : TimerTestInvoker;
const invoker = new invokerClass(runSync, measureAsync, report);
return invoker.start();
}
async _recordTestResults(suite, test, syncTime, asyncTime, unused_height, testDoneCallback) {
async _recordTestResults(suite, test, syncTime, asyncTime) {
// Skip reporting updates for the warmup suite.
if (suite === WarmupSuite)
return;
const suiteResults = this._measuredValues.tests[suite.name] || { tests: {}, total: 0 };
const total = syncTime + asyncTime;
this._measuredValues.tests[suite.name] = suiteResults;
@ -266,8 +523,6 @@ export class BenchmarkRunner {
if (this._client?.didRunTest)
await this._client.didRunTest(suite, test);
testDoneCallback();
}
async _finalize() {
@ -285,16 +540,21 @@ export class BenchmarkRunner {
const total = values.reduce((a, b) => a + b);
const geomean = Math.pow(product, 1 / values.length);
const correctionFactor = 3; // This factor makes the test score look reasonably fit within 0 to 140.
this._measuredValues.total = total;
this._measuredValues.mean = total / values.length;
this._measuredValues.geomean = geomean;
this._measuredValues.score = (60 * 1000) / geomean / correctionFactor;
this._measuredValues.score = geomeanToScore(geomean);
await this._client.didRunSuites(this._measuredValues);
}
}
_appendIterationMetrics() {
const getMetric = (name) => this._metrics[name] || (this._metrics[name] = new Metric(name));
const getMetric = (name, unit = "ms") => this._metrics[name] || (this._metrics[name] = new Metric(name, unit));
const iterationTotalMetric = (i) => {
if (i >= params.iterationCount)
throw new Error(`Requested iteration=${i} does not exist.`);
return getMetric(`Iteration-${i}-Total`);
};
const collectSubMetrics = (prefix, items, parent) => {
for (let name in items) {
@ -307,16 +567,29 @@ export class BenchmarkRunner {
collectSubMetrics(`${metric.name}${Metric.separator}`, results.tests, metric);
}
};
const initializeMetrics = this._metrics === null;
if (initializeMetrics)
this._metrics = { __proto__: null };
const iterationResults = this._measuredValues.tests;
const iterationTotal = getMetric(`Iteration-${this._metrics.Total.length}-Total`);
collectSubMetrics("", iterationResults);
if (initializeMetrics) {
// Prepare all iteration metrics so they are listed at the end of
// of the _metrics object, before "Total" and "Score".
for (let i = 0; i < this._iterationCount; i++)
iterationTotalMetric(i);
getMetric("Geomean");
getMetric("Score", "score");
}
const geomean = getMetric("Geomean");
const iterationTotal = iterationTotalMetric(geomean.length);
for (const results of Object.values(iterationResults))
iterationTotal.add(results.total);
iterationTotal.computeAggregatedMetrics();
this._metrics.Total.add(iterationTotal.sum);
this._metrics.Score.add(MILLISECONDS_PER_MINUTE / iterationTotal.sum);
collectSubMetrics("", iterationResults);
geomean.add(iterationTotal.geomean);
getMetric("Score").add(geomeanToScore(iterationTotal.geomean));
for (const metric of Object.values(this._metrics))
metric.computeAggregatedMetrics();

View file

@ -0,0 +1,253 @@
import { Suites, Tags } from "./tests.mjs";
import { params, defaultParams } from "./params.mjs";
export function createDeveloperModeContainer() {
const container = document.createElement("div");
container.className = "developer-mode";
const details = document.createElement("details");
const summary = document.createElement("summary");
summary.textContent = "Developer Mode";
details.append(summary);
const content = document.createElement("div");
content.className = "developer-mode-content";
content.append(createUIForSuites());
content.append(document.createElement("hr"));
content.append(createUIForMeasurementMethod());
content.append(document.createElement("br"));
content.append(createUIForWarmupSuite());
content.append(document.createElement("br"));
content.append(createUIForIterationCount());
content.append(document.createElement("hr"));
content.append(createUIForRun());
details.append(content);
container.append(details);
return container;
}
export function createUIForMeasurementMethod() {
let check = document.createElement("input");
check.type = "checkbox";
check.id = "measurement-method";
check.checked = params.measurementMethod === "raf";
check.onchange = () => {
params.measurementMethod = check.checked ? "raf" : "timer";
updateURL();
};
let label = document.createElement("label");
label.append(check, " ", "rAF timing");
return label;
}
export function createUIForWarmupSuite() {
let check = document.createElement("input");
check.type = "checkbox";
check.id = "warmup-suite";
check.checked = !!params.useWarmupSuite;
check.onchange = () => {
params.useWarmupSuite = check.checked;
updateURL();
};
let label = document.createElement("label");
label.append(check, " ", "warmup suite");
return label;
}
export function createUIForIterationCount() {
let range = document.createElement("input");
range.type = "range";
range.id = "iteration-count";
range.min = 1;
range.max = 20;
range.value = params.iterationCount;
let label = document.createElement("label");
let countLabel = document.createElement("span");
countLabel.textContent = params.iterationCount;
label.append("iterations: ", countLabel, document.createElement("br"), range);
range.oninput = () => {
countLabel.textContent = range.value;
};
range.onchange = () => {
params.iterationCount = parseInt(range.value);
updateURL();
};
return label;
}
export function createUIForSuites() {
const control = document.createElement("nav");
const ol = document.createElement("ol");
const checkboxes = [];
const setSuiteEnabled = (suiteIndex, enabled) => {
Suites[suiteIndex].disabled = !enabled;
checkboxes[suiteIndex].checked = enabled;
};
for (const suite of Suites) {
const li = document.createElement("li");
const checkbox = document.createElement("input");
checkbox.id = suite.name;
checkbox.type = "checkbox";
checkbox.checked = !suite.disabled;
checkbox.onchange = () => {
suite.disabled = !checkbox.checked;
updateURL();
};
checkboxes.push(checkbox);
const label = document.createElement("label");
label.append(checkbox, " ", suite.name);
li.appendChild(label);
label.onclick = (event) => {
if (event?.ctrlKey || event?.metaKey) {
for (let suiteIndex = 0; suiteIndex < Suites.length; suiteIndex++) {
if (Suites[suiteIndex] !== suite)
setSuiteEnabled(suiteIndex, false);
else
setSuiteEnabled(suiteIndex, true);
}
}
};
ol.appendChild(li);
}
control.appendChild(ol);
let buttons = control.appendChild(document.createElement("div"));
buttons.className = "button-bar";
let button = document.createElement("button");
button.textContent = "Select all";
button.onclick = () => {
for (let suiteIndex = 0; suiteIndex < Suites.length; suiteIndex++)
setSuiteEnabled(suiteIndex, true);
updateURL();
};
buttons.appendChild(button);
button = document.createElement("button");
button.textContent = "Unselect all";
button.onclick = () => {
for (let suiteIndex = 0; suiteIndex < Suites.length; suiteIndex++)
setSuiteEnabled(suiteIndex, false);
updateURL();
};
buttons.appendChild(button);
let i = 0;
const kTagsPerLine = 3;
for (const tag of Tags) {
if (tag === "all")
continue;
if (!(i % kTagsPerLine)) {
buttons = control.appendChild(document.createElement("div"));
buttons.className = "button-bar";
}
i++;
button = document.createElement("button");
button.className = "tag";
button.textContent = `#${tag}`;
button.dataTag = tag;
button.onclick = (event) => {
const extendSelection = event?.shiftKey;
const invertSelection = event?.ctrlKey || event?.metaKey;
const selectedTag = event.target.dataTag;
for (let suiteIndex = 0; suiteIndex < Suites.length; suiteIndex++) {
let enabled = Suites[suiteIndex].tags.includes(selectedTag);
if (invertSelection)
enabled = !enabled;
if (extendSelection && !enabled)
continue;
setSuiteEnabled(suiteIndex, enabled);
}
updateURL();
};
buttons.appendChild(button);
}
return control;
}
function createUIForRun() {
let button = document.createElement("button");
button.textContent = `Start Test`;
button.onclick = (event) => {
globalThis.benchmarkClient.start();
}
let buttons = document.createElement("div");
buttons.className = "button-bar";
buttons.appendChild(button);
return buttons
}
function updateURL() {
const url = new URL(window.location.href);
// If less than all suites are selected then change the URL "Suites" GET parameter
// to comma separate only the selected
const selectedSuites = Suites.filter((suite) => !suite.disabled);
if (!selectedSuites.length) {
url.searchParams.delete("tags");
url.searchParams.delete("suites");
url.searchParams.delete("suite");
} else {
url.searchParams.delete("tags");
url.searchParams.delete("suite");
// Try finding common tags that would result in the current suite selection.
let commonTags = new Set(selectedSuites[0].tags);
for (const suite of Suites) {
if (suite.disabled)
suite.tags.forEach((tag) => commonTags.delete(tag));
else
commonTags = new Set(suite.tags.filter((tag) => commonTags.has(tag)));
}
if (commonTags.size) {
const tags = [...commonTags][0];
if (tags === "default")
url.searchParams.delete("tags");
else
url.searchParams.set("tags", tags);
url.searchParams.delete("suites");
} else {
url.searchParams.delete("tags");
url.searchParams.set("suites", selectedSuites.map((suite) => suite.name).join(","));
}
}
if (params.measurementMethod !== "raf")
url.searchParams.set("measurementMethod", "timer");
else
url.searchParams.delete("measurementMethod");
if (params.iterationCount !== defaultParams.iterationCount)
url.searchParams.set("iterationCount", params.iterationCount);
else
url.searchParams.delete("iterationCount");
if (params.useWarmupSuite !== defaultParams.useWarmupSuite)
url.searchParams.set("useWarmupSuite", params.useWarmupSuite);
else
url.searchParams.delete("useWarmupSuite");
// Only push state if changed
url.search = decodeURIComponent(url.search);
if (url.href !== window.location.href)
window.history.pushState({}, "", url);
}

View file

@ -196,18 +196,8 @@ function createUIForSuites(suites, onStep, onRunSuites) {
}
function startTest() {
if (params.suites.length > 0) {
if (!Suites.enable(params.suites)) {
const message = `Suite "${params.suites}" does not exist. No tests to run.`;
alert(message);
console.error(
message,
params.suites,
"\nValid values:",
Suites.map((each) => each.name)
);
}
}
if (params.suites.length > 0 || params.tags.length > 0)
Suites.enable(params.suites, params.tags);
const interactiveRunner = new window.BenchmarkRunner(Suites, params.iterationCount);
if (!(interactiveRunner instanceof InteractiveBenchmarkRunner))

View file

@ -5,9 +5,11 @@
--foreground-alpha: rgba(235, 235, 235, 0.2);
--inactive-color: rgb(128, 128, 128);
--background: rgb(46, 51, 55);
--running-background: #f5f5f5;
--highlight: rgb(232, 79, 79);
--text-width: 650px;
--metrics-line-height: 25px;
--scrollbar-width: 10px;
}
body {
@ -30,8 +32,8 @@ body {
::-webkit-scrollbar-track {
background-color: var(--inactive-color);
border-radius: 8px;
width: 10px;
height: 10px;
width: var(--scrollbar-width);
height: var(--scrollbar-width);
}
::-webkit-scrollbar-thumb {
background-color: var(--foreground);
@ -72,16 +74,32 @@ img {
-webkit-user-drag: none;
}
.no-select {
user-select: none;
}
main {
}
.logo {
position: absolute;
left: -70px;
top: 115px;
top: 155px;
width: 75px;
height: 406px;
cursor: pointer;
text-decoration: none;
}
.version {
transform: rotate(-90deg);
font-size: 65px;
font-family: Futura-Medium, Futura, Roboto, "Helvetica Neue", Helvetica, Verdana, sans-serif;
font-style: italic;
letter-spacing: -0.05em;
position: absolute;
right: 0;
top: -105px;
}
h1 {
@ -113,6 +131,23 @@ a {
color: inherit;
}
li {
font-size: 16px;
line-height: 21px;
}
li + li {
margin-top: 21px;
}
li + ul > li {
margin-top: 0;
}
li + ul {
margin-bottom: 21px;
}
.buttons {
margin-top: 30px;
display: flex;
@ -172,6 +207,70 @@ button,
border-color: var(--highlight);
}
.developer-mode {
border-radius: 10px;
padding: 1rem;
background: #602525;
border: 3px solid rgba(255, 255, 255, 0.5);
position: fixed;
left: 10px;
top: 10px;
display: flex;
flex-direction: column;
}
.developer-mode summary {
user-select: none;
cursor: pointer;
padding: 1rem;
margin: -1rem;
}
.developer-mode-content {
flex: 1;
max-height: 80vh;
overflow: auto;
margin-right: calc(0px - var(--scrollbar-width));
padding-right: var(--scrollbar-width);
}
.developer-mode-content ol {
list-style: none;
padding: 0;
margin: 1em 0 0.5em 0;
}
.developer-mode-content .button-bar {
display: flex;
margin-top: 5px;
gap: 3px;
}
.developer-mode-content li + li {
margin-top: 0px;
}
.developer-mode-content button {
background: white;
flex: auto;
padding: 4px;
appearance: button;
border: 2px solid rgba(255, 255, 255, 0.5);
border-radius: 10px;
}
.developer-mode-content .tag {
border-radius: 100vh;
color: white;
background: rgba(255, 255, 255, 0.1);
}
.developer-mode-content label {
width: 100%;
display: inline-block;
}
.developer-mode-content hr {
width: initial;
margin: 10px 0;
}
section {
display: none;
--padding-width: 15px;
@ -231,7 +330,6 @@ button.show-about {
left: 60px;
right: 60px;
height: 6px;
background-color: var(--inactive-color);
border-left: 6px solid var(--background);
border-right: 6px solid var(--background);
}
@ -241,10 +339,27 @@ button.show-about {
top: 0;
left: 0;
height: 6px;
width: 0;
width: 100%;
appearance: none;
border: none;
background-color: var(--inactive-color);
}
#progress-completed::-webkit-progress-value {
background-color: var(--foreground);
}
#progress-completed::-moz-progress-bar {
background-color: var(--foreground);
}
#progress .iteration-marker {
position: absolute;
width: 6px;
height: 100%;
background-color: var(--background);
}
#info {
position: absolute;
bottom: -25px;
@ -265,6 +380,10 @@ button.show-about {
text-align: right;
}
iframe.test-runner {
background: var(--running-background);
}
section#summary > #result-number,
section#summary > #confidence-number {
font-family: "Futura-CondensedMedium", Futura, "Helvetica Neue", Helvetica, Verdana, sans-serif;
@ -324,7 +443,7 @@ section#details h1 {
margin-bottom: 10px;
}
section#about p {
section#about .section-content > * {
width: var(--text-width);
padding-left: calc((var(--viewport-width) - var(--text-width)) / 2);
}
@ -404,6 +523,8 @@ table .number {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
direction: rtl;
text-align: left;
}
section#details h1 {

View file

@ -1,9 +1,9 @@
import { BenchmarkRunner } from "./benchmark-runner.mjs";
import "./benchmark-report.mjs";
import * as Statistics from "./statistics.mjs";
import { Suites } from "./tests.mjs";
import { renderMetricView } from "./metric-ui.mjs";
import { params } from "./params.mjs";
import { createDeveloperModeContainer } from "./developer-mode.mjs";
// FIXME(camillobruni): Add base class
class MainBenchmarkClient {
@ -15,47 +15,70 @@ class MainBenchmarkClient {
_progressCompleted = null;
_isRunning = false;
_hasResults = false;
_developerModeContainer = null;
_metrics = Object.create(null);
constructor() {
window.addEventListener("DOMContentLoaded", () => this.prepareUI());
this._showSection(window.location.hash);
}
startBenchmark() {
start() {
if (this._startBenchmark())
this._showSection("#running");
}
_startBenchmark() {
if (this._isRunning)
return false;
if (params.suites.length > 0) {
if (!Suites.enable(params.suites)) {
const message = `Suite "${params.suites}" does not exist. No tests to run.`;
alert(message);
console.error(
message,
params.suites,
"\nValid values:",
Suites.map((each) => each.name)
);
return false;
if (Suites.every((suite) => suite.disabled)) {
const message = `No suites selected - "${params.suites}" does not exist.`;
alert(message);
console.error(
message,
params.suites,
"\nValid values:",
Suites.map((each) => each.name)
);
return false;
}
this._developerModeContainer?.remove();
this._progressCompleted = document.getElementById("progress-completed");
if (params.iterationCount < 50) {
const progressNode = document.getElementById("progress");
for (let i = 1; i < params.iterationCount; i++) {
const iterationMarker = progressNode.appendChild(document.createElement("div"));
iterationMarker.className = "iteration-marker";
iterationMarker.style.left = `${(i / params.iterationCount) * 100}%`;
}
}
this._metrics = Object.create(null);
this._isRunning = true;
this.developerMode = params.developerMode;
const enabledSuites = Suites.filter((suite) => !suite.disabled);
const totalSubtestsCount = enabledSuites.reduce((testsCount, suite) => {
return testsCount + suite.tests.length;
}, 0);
this.stepCount = params.iterationCount * totalSubtestsCount;
this._progressCompleted.max = this.stepCount;
this.suitesCount = enabledSuites.length;
const runner = new BenchmarkRunner(Suites, this);
runner.runMultipleIterations(params.iterationCount);
return true;
}
get metrics() {
return this._metrics;
}
willAddTestFrame(frame) {
const main = document.querySelector("#running");
const style = getComputedStyle(main);
frame.style.left = `${main.offsetLeft + parseInt(style.borderLeftWidth) + parseInt(style.paddingLeft)}px`;
frame.style.top = `${main.offsetTop + parseInt(style.borderTopWidth) + parseInt(style.paddingTop)}px`;
frame.style.left = "50%";
frame.style.top = "50%";
frame.style.transform = "translate(-50%, -50%)";
}
willRunTest(suite, test) {
@ -65,7 +88,7 @@ class MainBenchmarkClient {
didRunTest() {
this._finishedTestCount++;
this._progressCompleted.style.width = `${(this._finishedTestCount * 100) / this.stepCount}%`;
this._progressCompleted.value = this._finishedTestCount;
}
didRunSuites(measuredValues) {
@ -75,13 +98,13 @@ class MainBenchmarkClient {
willStartFirstIteration() {
this._measuredValuesList = [];
this._finishedTestCount = 0;
this._progressCompleted = document.getElementById("progress-completed");
}
didFinishLastIteration(metrics) {
console.assert(this._isRunning);
this._isRunning = false;
this._hasResults = true;
this._metrics = metrics;
const scoreResults = this._computeResults(this._measuredValuesList, "score");
this._updateGaugeNeedle(scoreResults.mean);
@ -91,7 +114,7 @@ class MainBenchmarkClient {
this._populateDetailedResults(metrics);
if (this.developerMode)
if (params.developerMode)
this.showResultsDetails();
else
this.showResultsSummary();
@ -168,8 +191,8 @@ class MainBenchmarkClient {
const trackHeight = 24;
document.documentElement.style.setProperty("--metrics-line-height", `${trackHeight}px`);
const plotWidth = (params.viewport.width - 120) / 2;
document.getElementById("total-chart").innerHTML = renderMetricView({
metrics: [metrics["Total"]],
document.getElementById("geomean-chart").innerHTML = renderMetricView({
metrics: [metrics.Geomean],
width: plotWidth,
trackHeight,
renderChildren: false,
@ -196,12 +219,17 @@ class MainBenchmarkClient {
document.getElementById("metrics-results").innerHTML = html;
const filePrefix = `speedometer-3-${new Date().toISOString()}`;
const jsonData = this._getFormattedJSONResult();
const jsonLink = document.getElementById("download-json");
let jsonData = this._formattedJSONResult({ modern: false });
let jsonLink = document.getElementById("download-classic-json");
jsonLink.href = URL.createObjectURL(new Blob([jsonData], { type: "application/json" }));
jsonLink.setAttribute("download", `${filePrefix}.json`);
const csvData = this._getFormattedCSVResult();
jsonLink = document.getElementById("download-full-json");
jsonData = this._formattedJSONResult({ modern: true });
jsonLink.href = URL.createObjectURL(new Blob([jsonData], { type: "application/json" }));
jsonLink.setAttribute("download", `${filePrefix}.json`);
const csvData = this._formattedCSVResult();
const csvLink = document.getElementById("download-csv");
csvLink.href = URL.createObjectURL(new Blob([csvData], { type: "text/csv" }));
csvLink.setAttribute("download", `${filePrefix}.csv`);
@ -215,14 +243,22 @@ class MainBenchmarkClient {
document.querySelectorAll("logo").forEach((button) => {
button.onclick = this._logoClickHandler.bind(this);
});
document.getElementById("copy-json").onclick = this.copyJsonResults.bind(this);
document.getElementById("copy-full-json").onclick = this.copyJsonResults.bind(this);
document.getElementById("copy-csv").onclick = this.copyCSVResults.bind(this);
document.querySelectorAll(".start-tests-button").forEach((button) => {
button.onclick = this._startBenchmarkHandler.bind(this);
});
if (params.suites.length > 0 || params.tags.length > 0)
Suites.enable(params.suites, params.tags);
if (params.developerMode) {
this._developerModeContainer = createDeveloperModeContainer(Suites);
document.body.append(this._developerModeContainer);
}
if (params.startAutomatically)
this._startBenchmarkHandler();
this.start();
}
_hashChangeHandler() {
@ -240,8 +276,7 @@ class MainBenchmarkClient {
}
_startBenchmarkHandler() {
if (this.startBenchmark())
this._showSection("#running");
this.start();
}
_logoClickHandler(event) {
@ -260,64 +295,38 @@ class MainBenchmarkClient {
this._showSection("#details");
}
_getFormattedJSONResult() {
_formattedJSONResult({ modern = false }) {
const indent = " ";
if (modern)
return JSON.stringify(this._metrics, undefined, indent);
return JSON.stringify(this._measuredValuesList, undefined, indent);
}
_getFormattedCSVResult() {
let tests = [];
_formattedCSVResult() {
// The CSV format is similar to the details view table. Each measurement is a row with
// the name and N columns with the measurement for each iteration:
// ```
// Measurement,#1,...,#N
// TodoMVC-JavaScript-ES5/Total,num,...,num
// TodoMVC-JavaScript-ES5/Adding100Items,num,...,num
// TodoMVC-JavaScript-ES5/Adding100Items/Sync,num,...,num
// TodoMVC-JavaScript-ES5/Adding100Items/Async,num,...,num
// ...
// TodoMVC-JavaScript-ES6/Total,num,...,num
// TodoMVC-JavaScript-ES6/Adding100Items,num,...,num
// TodoMVC-JavaScript-ES6/Adding100Items/Sync,num,...,num
// TodoMVC-JavaScript-ES6/Adding100Items/Async,num,...,num
// ```
const firstIterationTests = this._measuredValuesList[0].tests;
for (const suiteName in firstIterationTests) {
tests.push([`${suiteName}/Total`]);
for (const testName in firstIterationTests[suiteName].tests) {
tests.push([`${suiteName}/${testName}`]);
for (const subtestName in firstIterationTests[suiteName].tests[testName].tests)
tests.push([`${suiteName}/${testName}/${subtestName}`]);
}
}
// Now push each iteration onto the end of the array
for (const measuredValue of this._measuredValuesList) {
let index = 0;
for (const suiteName in measuredValue.tests) {
const suiteResults = measuredValue.tests[suiteName];
tests[index++].push(suiteResults.total);
for (const testName in suiteResults.tests) {
tests[index++].push(suiteResults.tests[testName].total);
for (const subtestName in suiteResults.tests[testName].tests)
tests[index++].push(suiteResults.tests[testName].tests[subtestName]);
}
}
}
const csv = [["Name"].concat(this._measuredValuesList.map((_, i) => `#${i + 1}`)).join(",")];
for (const test of tests)
csv.push(test.join(","));
const labels = ["Name"];
for (let i = 0; i < params.iterationCount; i++)
labels.push(`#${i + 1}`);
labels.push("Mean");
const metrics = Array.from(Object.values(this._metrics)).filter((metric) => !metric.name.startsWith("Iteration-"));
const metricsValues = metrics.map((metric) => [metric.name, ...metric.values, metric.mean].join(","));
const csv = [labels.join(","), ...metricsValues];
return csv.join("\n");
}
copyJsonResults() {
navigator.clipboard.writeText(this._getFormattedJSONResult());
navigator.clipboard.writeText(this._formattedJSONResult({ modern: true }));
}
copyCSVResults() {
navigator.clipboard.writeText(this._getFormattedCSVResult());
navigator.clipboard.writeText(this._formattedCSVResult());
}
_showSection(hash) {

View file

@ -26,7 +26,7 @@ export function renderMetricView(viewParams) {
.map(
(metric, i) => `
<tr >
<td class="${colors[i % colors.length]}" >●</td>
<td class="${colors[i % colors.length]} no-select" >●</td>
<td class="label">${metric.shortName}</td>
<td class="number">${metric.mean.toFixed(2)}</td>
<td>±</td>
@ -39,12 +39,14 @@ export function renderMetricView(viewParams) {
<dl class="metric">
<dt><h3>${title}<h3></dt>
<dd>
<div class="metric-chart" onclick="document.body.classList.toggle('relative-charts')">
<div class="metric-chart-absolute">
${absoluteScatterPlot}
</div>
<div class="metric-chart-relative">
${normalizedScatterPlot}
<div class="metric-chart"">
<div onclick="document.body.classList.toggle('relative-charts')">
<div class="metric-chart-absolute">
${absoluteScatterPlot}
</div>
<div class="metric-chart-relative">
${normalizedScatterPlot}
</div>
</div>
<table class="chart chart-legend">${legend}</table>
</div>
@ -259,7 +261,7 @@ function renderScatterPlot({ values, width = 500, height, trackHeight, xAxisPosi
<svg class="scatter-plot chart"
width="${width}" height="${height}"
viewBox="${`0 0 ${width} ${height}`}">
<g class="horizontal-axis">
<g class="horizontal-axis no-select">
<line
x1="${0}" x2="${width}"
y1="${axisY - axisMarginY}" y2="${axisY - axisMarginY}"

View file

@ -21,8 +21,6 @@ export class Metric {
this.max = 0.0;
this.values = [];
this.parent = undefined;
this.children = [];
// Mark properties which refer to other Metric objects as
@ -31,9 +29,7 @@ export class Metric {
Object.defineProperties(this, {
parent: {
writable: true,
},
children: {
writable: true,
value: undefined,
},
});
}

View file

@ -3,24 +3,51 @@ class Params {
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;
// 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"; // or "timer"
// 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
// <integer>: use the provided integer as a seed
shuffleSeed = "off";
constructor(searchParams = undefined) {
if (searchParams)
this._copyFromParams(searchParams);
Object.freeze(this.viewport);
Object.freeze(this);
this._copyFromSearchParams(searchParams);
if (!this.developerMode) {
Object.freeze(this.viewport);
Object.freeze(this);
}
}
_copyFromParams(searchParams) {
_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) {
if (searchParams.has("viewport")) {
const viewportParam = searchParams.get("viewport");
const [width, height] = viewportParam.split("x");
this.viewport.width = parseInt(width) || this.viewport.width;
this.viewport.height = parseInt(height) || this.viewport.height;
this.viewport.width = this._parseInt(width, "viewport.width");
this.viewport.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");
@ -28,9 +55,9 @@ class Params {
this.startAutomatically = searchParams.has("startAutomatically");
searchParams.delete("startAutomatically");
if (searchParams.has("iterationCount")) {
this.iterationCount = parseInt(searchParams.get("iterationCount")) || this.iterationCount;
this.iterationCount = this._parseInt(searchParams.get("iterationCount"), "iterationCount");
if (this.iterationCount < 1)
throw new Error(`Invalid iterationCount param: ${this.iterationCount}`);
throw new Error(`Invalid iterationCount param: '${this.iterationCount}', must be > 1.`);
searchParams.delete("iterationCount");
}
if (searchParams.has("suite") || searchParams.has("suites")) {
@ -43,8 +70,58 @@ class Params {
searchParams.delete("suite");
searchParams.delete("suites");
}
if (searchParams.has("tags")) {
if (this.suites.length)
throw new Error("'suites' and 'tags' cannot be used together.");
this.tags = searchParams.get("tags").split(",");
searchParams.delete("tags");
}
this.developerMode = searchParams.has("developerMode");
searchParams.delete("developerMode");
if (searchParams.has("useWarmupSuite")) {
this.useWarmupSuite = true;
searchParams.delete("useWarmupSuite");
}
if (searchParams.has("waitBeforeSync")) {
this.waitBeforeSync = this._parseInt(searchParams.get("waitBeforeSync"), "waitBeforeSync");
if (this.waitBeforeSync < 0)
throw new Error(`Invalid waitBeforeSync param: '${this.waitBeforeSync}', must be >= 0.`);
searchParams.delete("waitBeforeSync");
}
if (searchParams.has("warmupBeforeSync")) {
this.warmupBeforeSync = this._parseInt(searchParams.get("warmupBeforeSync"), "warmupBeforeSync");
if (this.warmupBeforeSync < 0)
throw new Error(`Invalid warmupBeforeSync param: '${this.warmupBeforeSync}', must be >= 0.`);
searchParams.delete("warmupBeforeSync");
}
if (searchParams.has("measurementMethod")) {
this.measurementMethod = searchParams.get("measurementMethod");
if (this.measurementMethod !== "timer" && this.measurementMethod !== "raf")
throw new Error(`Invalid measurement method: '${this.measurementMethod}', must be either 'raf' or 'timer'.`);
searchParams.delete("measurementMethod");
}
if (searchParams.has("shuffleSeed")) {
this.shuffleSeed = searchParams.get("shuffleSeed");
if (this.shuffleSeed !== "off") {
if (this.shuffleSeed === "generate") {
this.shuffleSeed = Math.floor((Math.random() * 1) << 16);
console.log(`Generated a random suite order seed: ${this.shuffleSeed}`);
} else {
this.shuffleSeed = parseInt(this.shuffleSeed);
}
if (!Number.isInteger(this.shuffleSeed))
throw new Error(`Invalid shuffle seed: '${this.shuffleSeed}', must be either 'off', 'generate' or an integer.`);
}
searchParams.delete("shuffleSeed");
}
const unused = Array.from(searchParams.keys());
if (unused.length > 0)
console.error("Got unused search params", unused);
@ -60,7 +137,7 @@ class Params {
export const defaultParams = new Params();
const searchParams = new URLSearchParams(window.location.search);
let maybeCustomParams = defaultParams;
let maybeCustomParams = new Params();
try {
maybeCustomParams = new Params(searchParams);
} catch (e) {

View file

@ -0,0 +1,45 @@
const fs = require("fs").promises;
const { resolve } = require("path");
const dirName = "./dist";
// spelling out words that this file doesn't trigger any bad word filters.
const parts = [
["b", "l", "a", "c", "k", "l", "i", "s", "t"],
["w", "h", "i", "t", "e", "l", "i", "s", "t"],
];
const wordsToUse = ["disallowlist", "allowlist"];
const wordsToReplace = parts.map((part) => part.join(""));
const replacements = new Map();
wordsToReplace.forEach((word, index) => replacements.set(word, wordsToUse[index]));
const stringReplaceRegex = new RegExp(wordsToReplace.join("|"), "gi");
async function getFiles(dir) {
const dirents = await fs.readdir(dir, { withFileTypes: true });
const files = await Promise.all(
dirents.map((dirent) => {
const res = resolve(dir, dirent.name);
return dirent.isDirectory() ? getFiles(res) : res;
})
);
return Array.prototype.concat(...files);
}
async function readAndReplace(fileName) {
const contents = await fs.readFile(fileName, "utf8");
const sanitized = contents.replace(stringReplaceRegex, function (matched) {
return replacements.get(matched);
});
if (contents !== sanitized)
await fs.writeFile(fileName, sanitized);
}
async function sanitize() {
const dir = process.env.OUTPUT_FOLDER ?? dirName;
const files = await getFiles(dir);
await Promise.all(files.map((file) => readAndReplace(file)));
console.log(`** Done sanitizing ${dir}! **`);
}
sanitize();

View file

@ -0,0 +1,6 @@
{
"name": "sanitize-language",
"version": "1.0.0",
"description": "A script to parse files for non-inclusive words.",
"main": "index.js"
}

View file

@ -69,9 +69,9 @@ export function confidenceIntervalDelta(confidenceLevel, numberOfSamples, sum, s
return NaN;
const cdfForProbability = tDistributionInverseCDF[probability];
const degreesOfFreedom = numberOfSamples - 1;
let degreesOfFreedom = numberOfSamples - 1;
if (degreesOfFreedom > cdfForProbability.length)
throw `We only support up to ${deltas.length} degrees of freedom`;
degreesOfFreedom = cdfForProbability.length - 1;
// tDistributionQuantile(degreesOfFreedom, confidenceLevel) * sampleStandardDeviation / sqrt(numberOfSamples) * S/sqrt(numberOfSamples)
const quantile = cdfForProbability[degreesOfFreedom - 1]; // The first entry is for the one degree of freedom.

View file

@ -1,26 +0,0 @@
node_modules
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View file

@ -1,27 +0,0 @@
## Description
Lots of people edit text content in the browser. Lots of that content, like WYSIWYG content or code, is too rich or complex to represent well with a `<textarea>`. Sites typically rely on advanced editor libraries for this, and we should make sure browsers perform well at common patterns used by them.
## Screenshot
![screenshot](./screenshot.jpg)
## What are we testing
- Basic DOM and editing
- Virtualization (DOM content changing during scroll)
- Basic flex layout with SVG icons
## How are we testing
The test simulates a real-world user flow by loading a number of popular editor libraries. After the initial load is complete, the following steps are timed:
- Setting to a fairly large value
- "Formatting" the text - in code editors this means turning on syntax highlighting, and in WYSISWYG this means bolding all of the contents
- Scrolling to the bottom of the editor
## Developer Documentation
The app was created with `npm create vite@latest editors`, and can be previewed with `npm run dev`. In order to update the files run in the harness you have to use `npm run build` which will recreate the `dist/` directory.
The built test can be loaded within the harness using i.e. http://localhost:7000/?suite=Editor-CodeMirror&startAutomatically=true, or the static versions at http://localhost:7000/resources/tentative/editors/dist/.

File diff suppressed because one or more lines are too long

View file

@ -1,200 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="/style.css" />
<title>CodeMirror Test</title>
</head>
<body>
<div id="app">
<div id="controls">
<button id="create">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-square-plus" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z"></path>
<path d="M9 12l6 0"></path>
<path d="M12 9l0 6"></path>
</svg>
Create
</button>
&nbsp;
<button id="big">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-arrow-big-up-lines-filled"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path
d="M10.586 3l-6.586 6.586a2 2 0 0 0 -.434 2.18l.068 .145a2 2 0 0 0 1.78 1.089h2.586v2a1 1 0 0 0 1 1h6l.117 -.007a1 1 0 0 0 .883 -.993l-.001 -2h2.587a2 2 0 0 0 1.414 -3.414l-6.586 -6.586a2 2 0 0 0 -2.828 0z"
stroke-width="0"
fill="currentColor"
></path>
<path d="M15 20a1 1 0 0 1 .117 1.993l-.117 .007h-6a1 1 0 0 1 -.117 -1.993l.117 -.007h6z" stroke-width="0" fill="currentColor"></path>
<path d="M15 17a1 1 0 0 1 .117 1.993l-.117 .007h-6a1 1 0 0 1 -.117 -1.993l.117 -.007h6z" stroke-width="0" fill="currentColor"></path>
</svg>
Big text
</button>
<button id="small">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-arrow-big-down-lines-filled"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path
d="M9 8l-.117 .007a1 1 0 0 0 -.883 .993v1.999l-2.586 .001a2 2 0 0 0 -1.414 3.414l6.586 6.586a2 2 0 0 0 2.828 0l6.586 -6.586a2 2 0 0 0 .434 -2.18l-.068 -.145a2 2 0 0 0 -1.78 -1.089l-2.586 -.001v-1.999a1 1 0 0 0 -1 -1h-6z"
stroke-width="0"
fill="currentColor"
></path>
<path d="M15 2a1 1 0 0 1 .117 1.993l-.117 .007h-6a1 1 0 0 1 -.117 -1.993l.117 -.007h6z" stroke-width="0" fill="currentColor"></path>
<path d="M15 5a1 1 0 0 1 .117 1.993l-.117 .007h-6a1 1 0 0 1 -.117 -1.993l.117 -.007h6z" stroke-width="0" fill="currentColor"></path>
</svg>
Small text
</button>
&nbsp;
<button id="highlight">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-highlight" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M3 19h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4"></path>
<path d="M12.5 5.5l4 4"></path>
<path d="M4.5 13.5l4 4"></path>
<path d="M21 15v4h-8l4 -4z"></path>
</svg>
Highlight
</button>
<button id="unhighlight">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-highlight-off"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M9 9l-6 6v4h4l6 -6m2 -2l2.503 -2.503a2.828 2.828 0 1 0 -4 -4l-2.497 2.497"></path>
<path d="M12.5 5.5l4 4"></path>
<path d="M4.5 13.5l4 4"></path>
<path d="M19 15h2v2m-2 2h-6l3 -3"></path>
<path d="M3 3l18 18"></path>
</svg>
Unhighlight
</button>
&nbsp;
<button id="scroll">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-line-height" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M3 8l3 -3l3 3"></path>
<path d="M3 16l3 3l3 -3"></path>
<path d="M6 5l0 14"></path>
<path d="M13 6l7 0"></path>
<path d="M13 12l7 0"></path>
<path d="M13 18l7 0"></path>
</svg>
Scroll
</button>
&nbsp;
<button id="layout">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-refresh-dot" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4"></path>
<path d="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4"></path>
<path d="M12 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path>
</svg>
</button>
</div>
<div id="editor"></div>
</div>
<script type="module">
import { code as smalltext } from "./smalltext.js";
import { code as bigtext } from "./bigtext.js";
import editor from "./codemirror.js";
let editorContainer = document.querySelector("#editor");
let editorInstance = null;
let editorPromise = null;
let buttons = {
create: document.querySelector("#create"),
highlight: document.querySelector("#highlight"),
unhighlight: document.querySelector("#unhighlight"),
big: document.querySelector("#big"),
small: document.querySelector("#small"),
scroll: document.querySelector("#scroll"),
layout: document.querySelector("#layout"),
};
buttons.scroll.addEventListener("click", scroll);
buttons.highlight.addEventListener("click", highlight);
buttons.unhighlight.addEventListener("click", unhighlight);
buttons.big.addEventListener("click", big);
buttons.small.addEventListener("click", small);
buttons.layout.addEventListener("click", layout);
buttons.create.addEventListener("click", (e) => {
if (!editorPromise) {
editorPromise = editor(editorContainer);
editorPromise.then((instance) => {
editorInstance = instance;
editorInstance.ready.then(() => {
buttons.unhighlight.classList.add("active", "true");
buttons.create.setAttribute("disabled", "true");
});
});
}
});
function layout() {
// Todo - is this necessary with the runner?
const body = document.body.getBoundingClientRect();
layout.e = document.elementFromPoint((body.width / 2) | 0, (body.height / 2) | 0);
}
function highlight() {
buttons.unhighlight.classList.toggle("active", false);
buttons.highlight.classList.toggle("active", true);
editorInstance.format(true);
}
function unhighlight() {
buttons.unhighlight.classList.toggle("active", true);
buttons.highlight.classList.toggle("active", false);
editorInstance.format(false);
}
function big() {
buttons.small.classList.toggle("active", false);
buttons.big.classList.toggle("active", true);
editorInstance.setValue(bigtext);
}
function small() {
buttons.small.classList.toggle("active", true);
buttons.big.classList.toggle("active", false);
editorInstance.setValue(smalltext);
}
function scroll() {
let isTop = editorInstance.getScrollTop() == 0;
editorInstance.setScrollTop(isTop ? editorInstance.getScrollHeight() : 0);
}
</script>
</body>
</html>

View file

@ -1,47 +0,0 @@
// https://codemirror.net/examples/bundle/
import { EditorView, basicSetup } from "codemirror";
import { StateEffect } from "@codemirror/state";
import { javascript } from "@codemirror/lang-javascript";
let lang = javascript();
let extensions = [basicSetup, EditorView.lineWrapping];
export default async function (element, value) {
let view = new EditorView({
extensions,
parent: element,
doc: value,
wordWrapColumn: 80,
});
return {
editor: view,
// Anything before this promise resolves will happen before timing starts
ready: Promise.resolve(),
getScrollHeight() {
return element.scrollHeight;
},
getScrollTop() {
return element.scrollTop;
},
setScrollTop(value) {
element.scrollTop = value;
},
setValue: (value) =>
view.dispatch({
changes: { from: 0, to: view.state.doc.length, insert: value },
}),
format(on) {
// https://codemirror.net/examples/config/
// https://discuss.codemirror.net/t/cm6-dynamically-switching-syntax-theme-w-reconfigure/2858/6
if (on && extensions.length == 2) {
extensions.push(lang);
} else if (!on && extensions.length == 3) {
extensions.pop();
}
view.dispatch({
effects: StateEffect.reconfigure.of(extensions),
});
},
};
}

File diff suppressed because one or more lines are too long

View file

@ -1,63 +0,0 @@
:root {
font-family: "Helvetica Neue", Helvetica, Verdana, sans-serif;
line-height: 1.5;
font-weight: 400;
color: #213547;
background-color: #ffffff;
font-synthesis: none;
text-rendering: optimizeLegibility;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #747bff;
}
body {
margin: 0;
display: flex;
min-width: 320px;
height: 100vh;
padding: 5px;
box-sizing: border-box;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
#app {
flex: 1;
display: flex;
flex-direction: column;
}
#editor {
flex: 1;
overflow: auto;
padding: 2px 5px;
}
#controls {
display: flex;
padding-bottom: 5px;
}
#controls button {
display: flex;
align-items: center;
margin: 0 2px;
padding: 2px;
}
#controls button.active {
color: blue;
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -1,134 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="./vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CodeMirror Test</title>
<script type="module" crossorigin src="./assets/codemirror-22d82ce2.js"></script>
<link rel="modulepreload" crossorigin href="./assets/index.es-8aa334e1.js">
<link rel="stylesheet" href="./assets/index-54888ccc.css">
</head>
<body>
<div id="app">
<div id="controls">
<button id="create">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-square-plus" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z"></path>
<path d="M9 12l6 0"></path>
<path d="M12 9l0 6"></path>
</svg>
Create
</button>
&nbsp;
<button id="big">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-arrow-big-up-lines-filled"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path
d="M10.586 3l-6.586 6.586a2 2 0 0 0 -.434 2.18l.068 .145a2 2 0 0 0 1.78 1.089h2.586v2a1 1 0 0 0 1 1h6l.117 -.007a1 1 0 0 0 .883 -.993l-.001 -2h2.587a2 2 0 0 0 1.414 -3.414l-6.586 -6.586a2 2 0 0 0 -2.828 0z"
stroke-width="0"
fill="currentColor"
></path>
<path d="M15 20a1 1 0 0 1 .117 1.993l-.117 .007h-6a1 1 0 0 1 -.117 -1.993l.117 -.007h6z" stroke-width="0" fill="currentColor"></path>
<path d="M15 17a1 1 0 0 1 .117 1.993l-.117 .007h-6a1 1 0 0 1 -.117 -1.993l.117 -.007h6z" stroke-width="0" fill="currentColor"></path>
</svg>
Big text
</button>
<button id="small">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-arrow-big-down-lines-filled"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path
d="M9 8l-.117 .007a1 1 0 0 0 -.883 .993v1.999l-2.586 .001a2 2 0 0 0 -1.414 3.414l6.586 6.586a2 2 0 0 0 2.828 0l6.586 -6.586a2 2 0 0 0 .434 -2.18l-.068 -.145a2 2 0 0 0 -1.78 -1.089l-2.586 -.001v-1.999a1 1 0 0 0 -1 -1h-6z"
stroke-width="0"
fill="currentColor"
></path>
<path d="M15 2a1 1 0 0 1 .117 1.993l-.117 .007h-6a1 1 0 0 1 -.117 -1.993l.117 -.007h6z" stroke-width="0" fill="currentColor"></path>
<path d="M15 5a1 1 0 0 1 .117 1.993l-.117 .007h-6a1 1 0 0 1 -.117 -1.993l.117 -.007h6z" stroke-width="0" fill="currentColor"></path>
</svg>
Small text
</button>
&nbsp;
<button id="highlight">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-highlight" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M3 19h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4"></path>
<path d="M12.5 5.5l4 4"></path>
<path d="M4.5 13.5l4 4"></path>
<path d="M21 15v4h-8l4 -4z"></path>
</svg>
Highlight
</button>
<button id="unhighlight">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-highlight-off"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M9 9l-6 6v4h4l6 -6m2 -2l2.503 -2.503a2.828 2.828 0 1 0 -4 -4l-2.497 2.497"></path>
<path d="M12.5 5.5l4 4"></path>
<path d="M4.5 13.5l4 4"></path>
<path d="M19 15h2v2m-2 2h-6l3 -3"></path>
<path d="M3 3l18 18"></path>
</svg>
Unhighlight
</button>
&nbsp;
<button id="scroll">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-line-height" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M3 8l3 -3l3 3"></path>
<path d="M3 16l3 3l3 -3"></path>
<path d="M6 5l0 14"></path>
<path d="M13 6l7 0"></path>
<path d="M13 12l7 0"></path>
<path d="M13 18l7 0"></path>
</svg>
Scroll
</button>
&nbsp;
<button id="layout">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-refresh-dot" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4"></path>
<path d="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4"></path>
<path d="M12 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path>
</svg>
</button>
</div>
<div id="editor"></div>
</div>
</body>
</html>

View file

@ -1,32 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="./vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Development UI</title>
<style>
iframe {
border: 0;
margin: 0;
padding: 0;
width: 48%;
height: 800px;
}
</style>
</head>
<body>
<div id="app">
<h1>Demo page (not intended for workload)</h1>
<p>This page shows the various workloads to help with local development. The runner should open only one of them.</p>
<details open>
<summary><strong>Code Editors</strong></summary>
<iframe src="./codemirror.html"></iframe>
</details>
<details open>
<summary><strong>WYSIWYG Editors</strong></summary>
<iframe src="./tiptap.html"></iframe>
</details>
</div>
</body>
</html>

View file

@ -1,134 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="./vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>TipTap Test</title>
<script type="module" crossorigin src="./assets/tiptap-5c6d7a65.js"></script>
<link rel="modulepreload" crossorigin href="./assets/index.es-8aa334e1.js">
<link rel="stylesheet" href="./assets/index-54888ccc.css">
</head>
<body>
<div id="app">
<div id="controls">
<button id="create">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-square-plus" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z"></path>
<path d="M9 12l6 0"></path>
<path d="M12 9l0 6"></path>
</svg>
Create
</button>
&nbsp;
<button id="big">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-arrow-big-up-lines-filled"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path
d="M10.586 3l-6.586 6.586a2 2 0 0 0 -.434 2.18l.068 .145a2 2 0 0 0 1.78 1.089h2.586v2a1 1 0 0 0 1 1h6l.117 -.007a1 1 0 0 0 .883 -.993l-.001 -2h2.587a2 2 0 0 0 1.414 -3.414l-6.586 -6.586a2 2 0 0 0 -2.828 0z"
stroke-width="0"
fill="currentColor"
></path>
<path d="M15 20a1 1 0 0 1 .117 1.993l-.117 .007h-6a1 1 0 0 1 -.117 -1.993l.117 -.007h6z" stroke-width="0" fill="currentColor"></path>
<path d="M15 17a1 1 0 0 1 .117 1.993l-.117 .007h-6a1 1 0 0 1 -.117 -1.993l.117 -.007h6z" stroke-width="0" fill="currentColor"></path>
</svg>
Big text
</button>
<button id="small">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-arrow-big-down-lines-filled"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path
d="M9 8l-.117 .007a1 1 0 0 0 -.883 .993v1.999l-2.586 .001a2 2 0 0 0 -1.414 3.414l6.586 6.586a2 2 0 0 0 2.828 0l6.586 -6.586a2 2 0 0 0 .434 -2.18l-.068 -.145a2 2 0 0 0 -1.78 -1.089l-2.586 -.001v-1.999a1 1 0 0 0 -1 -1h-6z"
stroke-width="0"
fill="currentColor"
></path>
<path d="M15 2a1 1 0 0 1 .117 1.993l-.117 .007h-6a1 1 0 0 1 -.117 -1.993l.117 -.007h6z" stroke-width="0" fill="currentColor"></path>
<path d="M15 5a1 1 0 0 1 .117 1.993l-.117 .007h-6a1 1 0 0 1 -.117 -1.993l.117 -.007h6z" stroke-width="0" fill="currentColor"></path>
</svg>
Small text
</button>
&nbsp;
<button id="highlight">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-highlight" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M3 19h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4"></path>
<path d="M12.5 5.5l4 4"></path>
<path d="M4.5 13.5l4 4"></path>
<path d="M21 15v4h-8l4 -4z"></path>
</svg>
Highlight
</button>
<button id="unhighlight">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-highlight-off"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M9 9l-6 6v4h4l6 -6m2 -2l2.503 -2.503a2.828 2.828 0 1 0 -4 -4l-2.497 2.497"></path>
<path d="M12.5 5.5l4 4"></path>
<path d="M4.5 13.5l4 4"></path>
<path d="M19 15h2v2m-2 2h-6l3 -3"></path>
<path d="M3 3l18 18"></path>
</svg>
Unhighlight
</button>
&nbsp;
<button id="scroll">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-line-height" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M3 8l3 -3l3 3"></path>
<path d="M3 16l3 3l3 -3"></path>
<path d="M6 5l0 14"></path>
<path d="M13 6l7 0"></path>
<path d="M13 12l7 0"></path>
<path d="M13 18l7 0"></path>
</svg>
Scroll
</button>
&nbsp;
<button id="layout">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-refresh-dot" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4"></path>
<path d="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4"></path>
<path d="M12 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path>
</svg>
</button>
</div>
<div id="editor"></div>
</div>
</body>
</html>

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -1,32 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Development UI</title>
<style>
iframe {
border: 0;
margin: 0;
padding: 0;
width: 48%;
height: 800px;
}
</style>
</head>
<body>
<div id="app">
<h1>Demo page (not intended for workload)</h1>
<p>This page shows the various workloads to help with local development. The runner should open only one of them.</p>
<details open>
<summary><strong>Code Editors</strong></summary>
<iframe src="./codemirror.html"></iframe>
</details>
<details open>
<summary><strong>WYSIWYG Editors</strong></summary>
<iframe src="./tiptap.html"></iframe>
</details>
</div>
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -1,18 +0,0 @@
{
"name": "editors",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"devDependencies": {
"@codemirror/lang-javascript": "^6.1.4",
"@tiptap/core": "^2.0.0-beta.217",
"@tiptap/starter-kit": "^2.0.0-beta.217",
"codemirror": "^6.0.1",
"vite": "^4.1.0"
}
}

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

View file

@ -1,9 +0,0 @@
export const code = `
function x() {
console.log("Hello world!");
}
`;
export const text = `
<h1>MOBY-DICK;<br>or, THE WHALE.</h1><p>By Herman Melville</p><hr><h2>CHAPTER 1. Loomings.</h2><p>Call me Ishmael. Some years ago—never mind how long precisely—having little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the world. It is a way I have of driving off the spleen and regulating the circulation. Whenever I find myself growing grim about the mouth; whenever it is a damp, drizzly November in my soul; whenever I find myself involuntarily pausing before coffin warehouses, and bringing up the rear of every funeral I meet; and especially whenever my hypos get such an upper hand of me, that it requires a strong moral principle to prevent me from deliberately stepping into the street, and methodically knocking people’s hats off—then, I account it high time to get to sea as soon as I can. This is my substitute for pistol and ball. With a philosophical flourish Cato throws himself upon his sword; I quietly take to the ship. There is nothing surprising in this. If they but knew it, almost all men in their degree, some time or other, cherish very nearly the same feelings towards the ocean with me.</p>
`;

View file

@ -1,63 +0,0 @@
:root {
font-family: "Helvetica Neue", Helvetica, Verdana, sans-serif;
line-height: 1.5;
font-weight: 400;
color: #213547;
background-color: #ffffff;
font-synthesis: none;
text-rendering: optimizeLegibility;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #747bff;
}
body {
margin: 0;
display: flex;
min-width: 320px;
height: 100vh;
padding: 5px;
box-sizing: border-box;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
#app {
flex: 1;
display: flex;
flex-direction: column;
}
#editor {
flex: 1;
overflow: auto;
padding: 2px 5px;
}
#controls {
display: flex;
padding-bottom: 5px;
}
#controls button {
display: flex;
align-items: center;
margin: 0 2px;
padding: 2px;
}
#controls button.active {
color: blue;
}

View file

@ -1,200 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="/style.css" />
<title>TipTap Test</title>
</head>
<body>
<div id="app">
<div id="controls">
<button id="create">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-square-plus" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z"></path>
<path d="M9 12l6 0"></path>
<path d="M12 9l0 6"></path>
</svg>
Create
</button>
&nbsp;
<button id="big">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-arrow-big-up-lines-filled"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path
d="M10.586 3l-6.586 6.586a2 2 0 0 0 -.434 2.18l.068 .145a2 2 0 0 0 1.78 1.089h2.586v2a1 1 0 0 0 1 1h6l.117 -.007a1 1 0 0 0 .883 -.993l-.001 -2h2.587a2 2 0 0 0 1.414 -3.414l-6.586 -6.586a2 2 0 0 0 -2.828 0z"
stroke-width="0"
fill="currentColor"
></path>
<path d="M15 20a1 1 0 0 1 .117 1.993l-.117 .007h-6a1 1 0 0 1 -.117 -1.993l.117 -.007h6z" stroke-width="0" fill="currentColor"></path>
<path d="M15 17a1 1 0 0 1 .117 1.993l-.117 .007h-6a1 1 0 0 1 -.117 -1.993l.117 -.007h6z" stroke-width="0" fill="currentColor"></path>
</svg>
Big text
</button>
<button id="small">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-arrow-big-down-lines-filled"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path
d="M9 8l-.117 .007a1 1 0 0 0 -.883 .993v1.999l-2.586 .001a2 2 0 0 0 -1.414 3.414l6.586 6.586a2 2 0 0 0 2.828 0l6.586 -6.586a2 2 0 0 0 .434 -2.18l-.068 -.145a2 2 0 0 0 -1.78 -1.089l-2.586 -.001v-1.999a1 1 0 0 0 -1 -1h-6z"
stroke-width="0"
fill="currentColor"
></path>
<path d="M15 2a1 1 0 0 1 .117 1.993l-.117 .007h-6a1 1 0 0 1 -.117 -1.993l.117 -.007h6z" stroke-width="0" fill="currentColor"></path>
<path d="M15 5a1 1 0 0 1 .117 1.993l-.117 .007h-6a1 1 0 0 1 -.117 -1.993l.117 -.007h6z" stroke-width="0" fill="currentColor"></path>
</svg>
Small text
</button>
&nbsp;
<button id="highlight">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-highlight" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M3 19h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4"></path>
<path d="M12.5 5.5l4 4"></path>
<path d="M4.5 13.5l4 4"></path>
<path d="M21 15v4h-8l4 -4z"></path>
</svg>
Highlight
</button>
<button id="unhighlight">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-highlight-off"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M9 9l-6 6v4h4l6 -6m2 -2l2.503 -2.503a2.828 2.828 0 1 0 -4 -4l-2.497 2.497"></path>
<path d="M12.5 5.5l4 4"></path>
<path d="M4.5 13.5l4 4"></path>
<path d="M19 15h2v2m-2 2h-6l3 -3"></path>
<path d="M3 3l18 18"></path>
</svg>
Unhighlight
</button>
&nbsp;
<button id="scroll">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-line-height" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M3 8l3 -3l3 3"></path>
<path d="M3 16l3 3l3 -3"></path>
<path d="M6 5l0 14"></path>
<path d="M13 6l7 0"></path>
<path d="M13 12l7 0"></path>
<path d="M13 18l7 0"></path>
</svg>
Scroll
</button>
&nbsp;
<button id="layout">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-refresh-dot" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4"></path>
<path d="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4"></path>
<path d="M12 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path>
</svg>
</button>
</div>
<div id="editor"></div>
</div>
<script type="module">
import { text as smalltext } from "./smalltext.js";
import { text as bigtext } from "./bigtext.js";
import editor from "./tiptap.js";
let editorContainer = document.querySelector("#editor");
let editorInstance = null;
let editorPromise = null;
let buttons = {
create: document.querySelector("#create"),
highlight: document.querySelector("#highlight"),
unhighlight: document.querySelector("#unhighlight"),
big: document.querySelector("#big"),
small: document.querySelector("#small"),
scroll: document.querySelector("#scroll"),
layout: document.querySelector("#layout"),
};
buttons.scroll.addEventListener("click", scroll);
buttons.highlight.addEventListener("click", highlight);
buttons.unhighlight.addEventListener("click", unhighlight);
buttons.big.addEventListener("click", big);
buttons.small.addEventListener("click", small);
buttons.layout.addEventListener("click", layout);
buttons.create.addEventListener("click", (e) => {
if (!editorPromise) {
editorPromise = editor(editorContainer);
editorPromise.then((instance) => {
editorInstance = instance;
editorInstance.ready.then(() => {
buttons.unhighlight.classList.add("active", "true");
buttons.create.setAttribute("disabled", "true");
});
});
}
});
function layout() {
// Todo - is this necessary with the runner?
const body = document.body.getBoundingClientRect();
layout.e = document.elementFromPoint((body.width / 2) | 0, (body.height / 2) | 0);
}
function highlight() {
buttons.unhighlight.classList.toggle("active", false);
buttons.highlight.classList.toggle("active", true);
editorInstance.format(true);
}
function unhighlight() {
buttons.unhighlight.classList.toggle("active", true);
buttons.highlight.classList.toggle("active", false);
editorInstance.format(false);
}
function big() {
buttons.small.classList.toggle("active", false);
buttons.big.classList.toggle("active", true);
editorInstance.setValue(bigtext);
}
function small() {
buttons.small.classList.toggle("active", true);
buttons.big.classList.toggle("active", false);
editorInstance.setValue(smalltext);
}
function scroll() {
let isTop = editorInstance.getScrollTop() == 0;
editorInstance.setScrollTop(isTop ? editorInstance.getScrollHeight() : 0);
}
</script>
</body>
</html>

View file

@ -1,43 +0,0 @@
// https://tiptap.dev/examples/default
import { Editor } from "@tiptap/core";
import StarterKit from "@tiptap/starter-kit";
export default async function (element, value) {
let editor = new Editor({
element,
extensions: [StarterKit],
content: value,
editorProps: {
attributes: {
spellcheck: "false",
},
},
});
return {
editor,
// Anything before this promise resolves will happen before timing starts
ready: Promise.resolve(),
getScrollHeight() {
return element.scrollHeight;
},
getScrollTop() {
return element.scrollTop;
},
setScrollTop(value) {
element.scrollTop = value;
},
setValue(value) {
// Recommendation is to call focus before most commands
// https://tiptap.dev/api/commands#chain-commands
editor.chain().focus().setContent(value).setTextSelection(0).run();
element.scrollTop = 0;
},
format(on) {
if (on) {
editor.chain().focus().selectAll().setBold().setTextSelection(0).run();
} else {
editor.chain().focus().selectAll().unsetBold().setTextSelection(0).run();
}
},
};
}

View file

@ -1,16 +0,0 @@
import { resolve } from "path";
import { defineConfig } from "vite";
export default defineConfig({
base: "./", // Since this will be loaded from the project root
build: {
modulePreload: { polyfill: false },
minify: false,
rollupOptions: {
input: {
codemirror: resolve(__dirname, "codemirror.html"),
main: resolve(__dirname, "index.html"),
tiptap: resolve(__dirname, "tiptap.html"),
},
},
},
});

View file

@ -1 +0,0 @@
node_modules

View file

@ -1,5 +0,0 @@
Uses [react-stockcharts](https://github.com/rrag/react-stockcharts/blob/master/LICENSE)
Benchmark page adapted from [github.com/rrag/react-stockcharts-examples2/examples/CandleStickChartWithFullStochasticsIndicator](https://github.com/rrag/react-stockcharts-examples2/tree/master/examples/CandleStickChartWithFullStochasticsIndicator)
License: [MIT](https://github.com/rrag/react-stockcharts/blob/master/LICENSE)

View file

@ -1,17 +0,0 @@
{
"files": {
"main.js": "./static/js/main.c5bdbc28.chunk.js",
"main.js.map": "./static/js/main.c5bdbc28.chunk.js.map",
"runtime-main.js": "./static/js/runtime-main.177cc4f8.js",
"runtime-main.js.map": "./static/js/runtime-main.177cc4f8.js.map",
"static/js/2.34438a6b.chunk.js": "./static/js/2.34438a6b.chunk.js",
"static/js/2.34438a6b.chunk.js.map": "./static/js/2.34438a6b.chunk.js.map",
"index.html": "./index.html",
"static/js/2.34438a6b.chunk.js.LICENSE.txt": "./static/js/2.34438a6b.chunk.js.LICENSE.txt"
},
"entrypoints": [
"static/js/runtime-main.177cc4f8.js",
"static/js/2.34438a6b.chunk.js",
"static/js/main.c5bdbc28.chunk.js"
]
}

View file

@ -1 +0,0 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"><title>Stockcharts</title></head><body style="background:#f5f5f5"><div id="root"></div><script>!function(e){function t(t){for(var n,c,a=t[0],l=t[1],f=t[2],p=0,s=[];p<a.length;p++)c=a[p],Object.prototype.hasOwnProperty.call(o,c)&&o[c]&&s.push(o[c][0]),o[c]=0;for(n in l)Object.prototype.hasOwnProperty.call(l,n)&&(e[n]=l[n]);for(i&&i(t);s.length;)s.shift()();return u.push.apply(u,f||[]),r()}function r(){for(var e,t=0;t<u.length;t++){for(var r=u[t],n=!0,a=1;a<r.length;a++){var l=r[a];0!==o[l]&&(n=!1)}n&&(u.splice(t--,1),e=c(c.s=r[0]))}return e}var n={},o={1:0},u=[];function c(t){if(n[t])return n[t].exports;var r=n[t]={i:t,l:!1,exports:{}};return e[t].call(r.exports,r,r.exports,c),r.l=!0,r.exports}c.m=e,c.c=n,c.d=function(e,t,r){c.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},c.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},c.t=function(e,t){if(1&t&&(e=c(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(c.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)c.d(r,n,function(t){return e[t]}.bind(null,n));return r},c.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return c.d(t,"a",t),t},c.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},c.p="./";var a=this["webpackJsonpreact-stockcharts-bench"]=this["webpackJsonpreact-stockcharts-bench"]||[],l=a.push.bind(a);a.push=t,a=a.slice();for(var f=0;f<a.length;f++)t(a[f]);var i=l;r()}([])</script><script src="./static/js/2.34438a6b.chunk.js"></script><script src="./static/js/main.c5bdbc28.chunk.js"></script></body></html>

File diff suppressed because one or more lines are too long

View file

@ -1,23 +0,0 @@
/*
object-assign
(c) Sindre Sorhus
@license MIT
*/
/** @license React v16.2.0
* react-dom.production.min.js
*
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v16.2.0
* react.production.min.js
*
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,2 +0,0 @@
!function(e){function t(t){for(var n,c,a=t[0],l=t[1],f=t[2],p=0,s=[];p<a.length;p++)c=a[p],Object.prototype.hasOwnProperty.call(o,c)&&o[c]&&s.push(o[c][0]),o[c]=0;for(n in l)Object.prototype.hasOwnProperty.call(l,n)&&(e[n]=l[n]);for(i&&i(t);s.length;)s.shift()();return u.push.apply(u,f||[]),r()}function r(){for(var e,t=0;t<u.length;t++){for(var r=u[t],n=!0,a=1;a<r.length;a++){var l=r[a];0!==o[l]&&(n=!1)}n&&(u.splice(t--,1),e=c(c.s=r[0]))}return e}var n={},o={1:0},u=[];function c(t){if(n[t])return n[t].exports;var r=n[t]={i:t,l:!1,exports:{}};return e[t].call(r.exports,r,r.exports,c),r.l=!0,r.exports}c.m=e,c.c=n,c.d=function(e,t,r){c.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},c.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},c.t=function(e,t){if(1&t&&(e=c(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(c.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)c.d(r,n,function(t){return e[t]}.bind(null,n));return r},c.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return c.d(t,"a",t),t},c.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},c.p="./";var a=this["webpackJsonpreact-stockcharts-bench"]=this["webpackJsonpreact-stockcharts-bench"]||[],l=a.push.bind(a);a.push=t,a=a.slice();for(var f=0;f<a.length;f++)t(a[f]);var i=l;r()}([]);
//# sourceMappingURL=runtime-main.177cc4f8.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -1,33 +0,0 @@
{
"name": "react-stockcharts-bench",
"private": true,
"scripts": {
"start": "react-scripts start",
"build": "rm -rf build && react-scripts build"
},
"homepage": "./",
"devDependencies": {
"d3-dsv": "1.0.8",
"d3-format": "1.2.1",
"d3-scale": "1.0.7",
"d3-time-format": "2.1.1",
"prop-types": "15.6.0",
"raw-loader": "4.0.2",
"react": "16.2.0",
"react-dom": "16.2.0",
"react-scripts": "4.0.3",
"react-stockcharts": "0.7.8"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

View file

@ -1,10 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Stockcharts</title>
</head>
<body style="background: #f5f5f5;">
<div id="root"></div>
</body>
</html>

View file

@ -1,198 +0,0 @@
import React from "react";
import PropTypes from "prop-types";
import { format } from "d3-format";
import { timeFormat } from "d3-time-format";
import { ChartCanvas, Chart } from "react-stockcharts";
import {
BarSeries,
StraightLine,
CandlestickSeries,
LineSeries,
StochasticSeries,
} from "react-stockcharts/lib/series";
import { XAxis, YAxis } from "react-stockcharts/lib/axes";
import {
CrossHairCursor,
EdgeIndicator,
CurrentCoordinate,
MouseCoordinateX,
MouseCoordinateY,
} from "react-stockcharts/lib/coordinates";
import {
OHLCTooltip,
MovingAverageTooltip,
StochasticTooltip,
} from "react-stockcharts/lib/tooltip";
import { discontinuousTimeScaleProvider } from "react-stockcharts/lib/scale";
import { ema, stochasticOscillator } from "react-stockcharts/lib/indicator";
import { fitWidth } from "react-stockcharts/lib/helper";
import { last } from "react-stockcharts/lib/utils";
const stoAppearance = {
stroke: Object.assign({},
StochasticSeries.defaultProps.stroke)
};
class BenchmarkChart extends React.Component {
render() {
const overallHeight = 500;
const numCharts = 5;
const chartHeight = overallHeight / numCharts;
const { type, data: initialData, width, ratio } = this.props;
const ema10 = ema()
.options({ windowSize: 10 })
.merge((d, c) => {d.ema10 = c;})
.accessor(d => d.ema10);
const ema20 = ema()
.options({ windowSize: 12 })
.merge((d, c) => {d.ema20 = c;})
.accessor(d => d.ema20);
const slowSTO = stochasticOscillator()
.options({ windowSize: 10, kWindowSize: 3 })
.merge((d, c) => {d.slowSTO = c;})
.accessor(d => d.slowSTO);
const fastSTO = stochasticOscillator()
.options({ windowSize: 10, kWindowSize: 1 })
.merge((d, c) => {d.fastSTO = c;})
.accessor(d => d.fastSTO);
const fullSTO = stochasticOscillator()
.options({ windowSize: 10, kWindowSize: 3, dWindowSize: 4 })
.merge((d, c) => {d.fullSTO = c;})
.accessor(d => d.fullSTO);
const calculatedData = ema10(ema20(slowSTO(fastSTO(fullSTO(initialData)))));
const xScaleProvider = discontinuousTimeScaleProvider
.inputDateAccessor(d => d.date);
const {
data,
xScale,
xAccessor,
displayXAccessor,
} = xScaleProvider(calculatedData);
const start = xAccessor(last(data));
const end = xAccessor(data[Math.max(0, data.length - 150)]);
const xExtents = [start, end];
return (
<ChartCanvas height={overallHeight}
width={width}
ratio={ratio}
type={type}
seriesName="COMPANY"
data={data}
xScale={xScale}
xAccessor={xAccessor}
displayXAccessor={displayXAccessor}
xExtents={xExtents}
>
<Chart id={1} height={chartHeight}
yExtents={d => [d.high, d.low]}
>
<YAxis axisAt="right" orient="right" ticks={5} />
<XAxis axisAt="bottom" orient="bottom" showTicks={false} />
<CandlestickSeries />
<LineSeries yAccessor={ema10.accessor()} stroke={ema10.stroke()}/>
<LineSeries yAccessor={ema20.accessor()} stroke={ema20.stroke()}/>
<OHLCTooltip/>
</Chart>
<Chart id={2}
yExtents={d => d.volume}
height={chartHeight}
origin={[0, chartHeight]}
>
<YAxis axisAt="left" orient="left" ticks={5} tickFormat={format(".2s")}/>
<BarSeries yAccessor={d => d.volume}/>
</Chart>
<Chart id={3}
yExtents={[0, 100]}
height={chartHeight}
origin={[0, 2 * chartHeight]}
>
<XAxis axisAt="bottom" orient="bottom" showTicks={false} />
<YAxis axisAt="right" orient="right"/>
<StochasticSeries
yAccessor={d => d.fastSTO}
{...stoAppearance} />
<StochasticTooltip
yAccessor={slowSTO.accessor()}
options={slowSTO.options()}
appearance={stoAppearance}
label="Slow STO" />
</Chart>
<Chart id={4}
yExtents={[0, 100]}
height={chartHeight}
origin={[0, 3 * chartHeight]}
>
<XAxis axisAt="bottom" orient="bottom" showTicks={false} />
<YAxis axisAt="right" orient="right"/>
<StochasticSeries
yAccessor={fastSTO.accessor()}
{...stoAppearance} />
<StochasticTooltip
yAccessor={fastSTO.accessor()}
options={fastSTO.options()}
appearance={stoAppearance}
label="Fast STO" />
</Chart>
<Chart id={5}
yExtents={[0, 100]}
height={chartHeight}
origin={[0, 4 * chartHeight]}
>
<XAxis axisAt="bottom" orient="bottom" />
<YAxis axisAt="right" orient="right"/>
<MouseCoordinateX
at="bottom"
orient="bottom"
displayFormat={timeFormat("%Y-%m-%d")} />
<MouseCoordinateY
at="right"
orient="right"
displayFormat={format(".2f")} />
<StochasticSeries
yAccessor={fullSTO.accessor()}
{...stoAppearance} />
<StochasticTooltip
yAccessor={fullSTO.accessor()}
options={fullSTO.options()}
appearance={stoAppearance}
label="Full STO" />
</Chart>
</ChartCanvas>
);
}
}
BenchmarkChart.propTypes = {
data: PropTypes.array.isRequired,
width: PropTypes.number.isRequired,
ratio: PropTypes.number.isRequired,
type: PropTypes.oneOf(["svg", "hybrid"]).isRequired,
};
BenchmarkChart.defaultProps = {
type: "svg",
};
BenchmarkChart = fitWidth(BenchmarkChart);
export default BenchmarkChart;

File diff suppressed because it is too large Load diff

View file

@ -1,40 +0,0 @@
import React from 'react';
import { render } from 'react-dom';
import Chart from './chart';
import { getData } from "./utils"
import { TypeChooser } from "react-stockcharts/lib/helper";
class ChartComponent extends React.Component {
constructor() {
super();
this.state = { data: null, render: false };
this.doRender = () => this.setState({ render: true });
}
componentDidMount() {
getData().then(data => {
this.setState({ data })
})
}
render() {
const { render, data } = this.state;
if (!render) {
return <button id="render" onClick={this.doRender}>render</button>;
}
if (data == null) {
return <div>Loading...</div>
}
const type = new URLSearchParams(location.search).get('type') || 'hybrid';
return (
<TypeChooser type={type}>
{type => <Chart type={type} data={data} />}
</TypeChooser>
)
}
}
render(
<ChartComponent />,
document.getElementById("root")
);

View file

@ -1,22 +0,0 @@
import data from '!!raw-loader!./data.tsv';
import { tsvParse } from "d3-dsv";
import { timeParse } from "d3-time-format";
function parseData(parse) {
return function(d) {
d.date = parse(d.date);
d.open = +d.open;
d.high = +d.high;
d.low = +d.low;
d.close = +d.close;
d.volume = +d.volume;
return d;
};
}
const parseDate = timeParse("%Y-%m-%d");
export function getData() {
return Promise.resolve(tsvParse(data, parseData(parseDate)));
}

View file

@ -1,9 +1,62 @@
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];
}
export const Suites = [];
const ENTER_KEY_CODE = 13;
Suites.enable = function (names, tags) {
if (names?.length) {
const lowerCaseNames = names.map((each) => each.toLowerCase());
this.forEach((suite) => {
if (lowerCaseNames.includes(suite.name.toLowerCase()))
suite.disabled = false;
else
suite.disabled = true;
});
} else if (tags?.length) {
tags.forEach((tag) => {
if (!Tags.has(tag))
console.error(`Unknown Suites tag: "${tag}"`);
});
const tagsSet = new Set(tags);
this.forEach((suite) => {
suite.disabled = !suite.tags.some((tag) => tagsSet.has(tag));
});
} else {
console.warn("Neither names nor tags provided. Enabling all default suites.");
this.forEach((suite) => {
suite.disabled = !("default" in suite.tags);
});
}
if (this.some((suite) => !suite.disabled))
return;
let message, debugInfo;
if (names?.length) {
message = `Suites "${names}" does not match any Suite. No tests to run.`;
debugInfo = {
providedNames: names,
validNames: this.map((each) => each.name),
};
} else if (tags?.length) {
message = `Tags "${tags}" does not match any Suite. No tests to run.`;
debugInfo = {
providedTags: tags,
validTags: Array.from(Tags),
};
}
alert(message);
console.error(message, debugInfo);
};
const triggerEnter = function (element, type) {
var event = document.createEvent('Events');
event.initEvent(type, true, true);
@ -582,4 +635,21 @@ Suites.push({
});
Object.freeze(Suites);
Suites.forEach((suite) => {
if (!suite.tags)
suite.tags = [];
if (suite.url.startsWith("tentative/"))
suite.tags.unshift("all", "tentative");
else if (suite.disabled)
suite.tags.unshift("all");
else
suite.tags.unshift("all", "default");
Object.freeze(suite.tags);
Object.freeze(suite.steps);
});
export const Tags = new Set(["all", "default", "tentative", ...Suites.flatMap((suite) => suite.tags)]);
Object.freeze(Tags);
globalThis.Suites = Suites;
globalThis.Tags = Tags;

View file

@ -0,0 +1,11 @@
# Speedometer 2.0: Angular 4 TodoMVC example
## Test locally
1. `npm install`
2. `npm run start-prod`
3. Navigate to `http://localhost:4200/`
## Build (do this before committing!)
1. `npm run build-prod`

View file

@ -0,0 +1,11 @@
# Speedometer 2.0: Backbone.js TodoMVC example
## Test locally
1. Run `npm install` to install the dependencies.
2. Run a local server from this directory. (You could use [`npm i -g http-server`](https://github.com/indexzero/http-server).)
3. Open the URL pointing to the local server in your web browser of choice.
## Build
There is no build step for this example.

View file

@ -0,0 +1,33 @@
# jQuery TodoMVC Example
> jQuery is a fast, small, and feature-rich JavaScript library. It makes things like HTML document traversal and manipulation, event handling, animation, and Ajax much simpler with an easy-to-use API that works across a multitude of browsers. With a combination of versatility and extensibility, jQuery has changed the way that millions of people write JavaScript.
> _[jQuery - jquery.com](http://jquery.com)_
## Learning jQuery
The [jQuery website](http://jquery.com) is a great resource for getting started.
Here are some links you may find helpful:
* [Learning Center](http://learn.jquery.com/)
* [API Reference](http://api.jquery.com)
* [Plugins](http://plugins.jquery.com)
* [Browser Support](http://jquery.com/browser-support)
* [Blog](http://blog.jquery.com)
Articles and guides from the community:
* [Try jQuery](http://try.jquery.com)
* [jQuery Annotated Source](http://github.com/robflaherty/jquery-annotated-source)
* [10 Things I Learned From the jQuery Source](http://paulirish.com/2010/10-things-i-learned-from-the-jquery-source)
Get help from other jQuery users:
* [jQuery on StackOverflow](http://stackoverflow.com/questions/tagged/jquery)
* [Forums](http://forum.jquery.com)
* [jQuery on Twitter](http://twitter.com/jquery)
* [jQuery on Google +](https://plus.google.com/102828491884671003608/posts)
_If you have other helpful links to share, or find any of the links above no longer work, please [let us know](https://github.com/tastejs/todomvc/issues)._

View file

@ -0,0 +1,16 @@
# Speedometer 2.0: Preact TodoMVC example
## Test locally
1. `npm install`
2. `npm start`
## Build
1. `npm run build`
## Notes
The compiled version of the application can be found in the `build` directory.
This is what the benchmark runner uses when running the Preact TodoMVC as part
of a benchmark run.

623
resources/translations.mjs Normal file
View file

@ -0,0 +1,623 @@
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: [
"Электронная гранитная шляпа",
"Беспокойный мягкий стол",
"Эргономичный свежий велосипед",
"Роскошный деревянный сыр",
"Великолепная свежая пицца",
"Деревенская резиновая рубашка",
"Современное резиновое мыло",
"Маленький бронзовый мяч",
"Потрясающие бронзовые туфли",
"Стальное кресло на заказ",
"Практическое пластиковое мыло",
"Невероятный гранитный бекон",
"Элегантная металлическая клавиатура",
"Электронные деревянные колбасы",
"Вкусные деревянные перчатки",
"Роскошный металлический сыр",
"Потрясающие резиновые перчатки",
"Гладкая мягкая машина",
"Лицензированный свежий салат",
"Эргономичные замороженные полотенца",
"Современная резиновая клавиатура",
"Вкусная бетонная пицца",
"Пластиковая курица ручной работы",
"Роскошная резиновая курица",
"Практическая мягкая рыба",
"Эргономичная бронзовая рубашка",
"Пластиковый бекон из ручной работы",
"Непревзойденные пластиковые брюки",
"Современные деревянные колбасы",
"Стальные туфли ручной работы",
"Деревенский стальный велосипед",
"Великолепный замороженный салат",
"Бронзовая курица ручной работы",
"Гладкий гранитный велосипед",
"Общие бетонные колбасы",
"Невероятный пластиковый тунец",
"Созданный свежий сыр",
"Электронный свежий бекон",
"Лицензированный деревянный автомобиль",
"Переработанная свежая рыба",
"Невероятные свежие туфли",
"Практические мягкие чипсы",
"Маленькая мягкая курица",
"Интеллектуальная свежая мышь",
"Современная металлическая мышь",
"Вкусные гранитные перчатки",
"Потрясающий резиновый велосипед",
"Маленькая стальная рубашка",
"Изысканный бетонный компьютер",
"Гладкая замороженная рубашка",
"Интеллектуальные бетонные туфли",
"Резиновый автомобиль ручной работы",
"Гладкие резиновые полотенца",
"Непревзойденная бетонная шляпа",
"Невероятная пластиковая рыба",
"Практические мягкие перчатки",
"Органическая камня пицца",
"Общая деревянная клавиатура",
"Переработанные деревянные чипсы",
"Невероятные резиновые чипсы",
"Эргономичная гранитная рубашка",
"Вкусная замороженная клавиатура",
"Великолепное стальное мыло",
"Роскошное пластиковое кресло",
"Элегантный замороженный велосипед",
"Переработанное стальное кресло",
"Современные бронзовые колбасы",
"Элегантный деревянный сыр",
"Маленькие пластиковые колбаски",
"Роскошные замороженные туфли",
"Гладкие пластиковые колбаски",
"Свежие колбаски ручной работы",
"Невероятный мягкий стул",
"Переработанное деревянное мыло",
"Мягкая резиновая утка",
"Лицензированный бетонный тунец",
"Роскошные гранитные штаны",
"Рафинированная резиновая клавиатура",
"Пластиковый компьютер ручной работы",
"Практический стальной салат",
"Невероятный мягкий бекон",
"Практическая металлическая рыба",
"Элегантная резиновая рубашка",
"Ручной резиновый стол",
"Великолепный деревянный стол",
"Фантастические стальные колбаски",
"Небольшая мягкая клавиатура",
"Общий стальной шар",
"Электронная замороженная шляпа",
"Великолепный свежий стул",
"Гладкие мягкие колбаски",
"Великолепные деревянные полотенца",
"Сделанная на заказ гранитная пицца",
"Общий металлический салат",
"Резиновый сыр ручной работы",
"Фантастическое стальное кресло",
"Замороженный компьютер ручной работы",
"Деревенская резиновая мышь",
"Главная гранитная пицца",
"Великолепная пластиковая клавиатура",
],
};
export const defaultTodoText = {
en: "Something to do",
"zh-cn": "做某事",
ja: "何かをする必要がある",
es: "Algo que hacer",
de: "Etwas zu tun",
ru: "Кое-что сделать",
};

View file

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Warmup Page</title>
</head>
<body style="background-color: white">
<h1>WARMUP</h1>
<div>
<div class="item">item 1</div>
<div class="item">item 2</div>
<div class="item">item 3</div>
<div class="item">item 4</div>
<div class="item">item 5</div>
<div class="item" id="testItem">item 6</div>
</div>
</body>
</html>

View file

@ -91,23 +91,6 @@ describe("BenchmarkRunner", () => {
expect(scrolling).to.be("no");
});
it(`should add body margins to the frame if the window is larger than ${DEFAULT_WIDTH}px x ${DEFAULT_HEIGHT}px`, async () => {
stub(window, "innerWidth").get(() => DEFAULT_WIDTH + 100);
stub(window, "innerHeight").get(() => DEFAULT_HEIGHT + 100);
const { style } = await runner._appendFrame();
expect(style.left).to.be("8px");
expect(style.top).to.be("8px");
});
it(`should not add outer spacing to the frame if the window is smaller than ${DEFAULT_WIDTH}px x ${DEFAULT_HEIGHT}px`, async () => {
stub(window, "innerWidth").get(() => DEFAULT_WIDTH - 100);
const { style } = await runner._appendFrame();
expect(style.left).to.be("0px");
expect(style.top).to.be("0px");
});
it("should insert the frame as the first child in the document body", async () => {
const firstChild = document.createTextNode("First Child");
const insertBeforeSpy = spy(document.body, "insertBefore");
@ -148,7 +131,7 @@ describe("BenchmarkRunner", () => {
});
describe("_runSuite", () => {
let _prepareSuiteStub, _runTestAndRecordResultsStub, peformanceMarkSpy;
let _prepareSuiteStub, _runTestAndRecordResultsStub, performanceMarkSpy;
const suite = SUITES_FIXTURE[0];
@ -157,7 +140,7 @@ describe("BenchmarkRunner", () => {
_runTestAndRecordResultsStub = stub(runner, "_runTestAndRecordResults").callsFake(() => null);
peformanceMarkSpy = spy(window.performance, "mark");
performanceMarkSpy = spy(window.performance, "mark");
runner._runSuite(suite);
});
@ -167,66 +150,38 @@ describe("BenchmarkRunner", () => {
it("should run and record results for every test in suite", async () => {
assert.calledThrice(_runTestAndRecordResultsStub);
assert.calledWith(peformanceMarkSpy, "start-suite-Suite 1");
assert.calledWith(peformanceMarkSpy, "end-suite-Suite 1");
expect(peformanceMarkSpy.callCount).to.equal(2);
assert.calledWith(performanceMarkSpy, "suite-Suite 1-prepare-start");
assert.calledWith(performanceMarkSpy, "suite-Suite 1-prepare-end");
assert.calledWith(performanceMarkSpy, "suite-Suite 1-start");
assert.calledWith(performanceMarkSpy, "suite-Suite 1-end");
expect(performanceMarkSpy.callCount).to.equal(4);
});
});
});
describe("Test", () => {
describe("_runTestAndRecordResults", () => {
let _runTestSpy;
let performanceMarkSpy;
const suite = SUITES_FIXTURE[0];
before(async () => {
await runner._appendFrame();
_runTestSpy = spy(runner, "_runTest");
performanceMarkSpy = spy(window.performance, "mark");
await runner._runTestAndRecordResults(suite, suite.tests[0]);
});
it("should run the test with the correct arguments", () => {
assert.calledWith(_runTestSpy, suite, suite.tests[0], runner._page);
});
it("should run client pre and post hooks if present", () => {
assert.calledWith(runner._client.willRunTest, suite, suite.tests[0]);
assert.calledWith(runner._client.didRunTest, suite, suite.tests[0]);
});
});
describe("_runTest", () => {
let peformanceMarkSpy, _testFnSpy, page;
const callback = stub();
const suite = SUITES_FIXTURE[0];
before(async () => {
page = { _frame: await runner._appendFrame() };
peformanceMarkSpy = spy(window.performance, "mark");
_testFnSpy = suite.tests[0].run.callsFake(() => null);
stubPerformanceNowCalls(8000, 10000, 12000, 13000);
runner._runTest(suite, suite.tests[0], page, callback);
});
it("should write performance marks at the start and end of the test with the correct test name", () => {
assert.calledWith(peformanceMarkSpy, "Suite 1.Test 1-start");
assert.calledWith(peformanceMarkSpy, "Suite 1.Test 1-sync-end");
assert.calledWith(peformanceMarkSpy, "Suite 1.Test 1-async-start");
assert.calledWith(peformanceMarkSpy, "Suite 1.Test 1-async-end");
expect(peformanceMarkSpy.callCount).to.equal(4);
});
it("should run the test", () => {
assert.calledWith(_testFnSpy, page);
});
it("should fire the callback with correct arguments for sync time, async time, and frame height", async () => {
await new Promise((resolve) => requestAnimationFrame(resolve));
assert.calledOnce(callback);
assert.calledWith(performanceMarkSpy, "Suite 1.Test 1-start");
assert.calledWith(performanceMarkSpy, "Suite 1.Test 1-sync-end");
assert.calledWith(performanceMarkSpy, "Suite 1.Test 1-async-start");
assert.calledWith(performanceMarkSpy, "Suite 1.Test 1-async-end");
expect(performanceMarkSpy.callCount).to.equal(4);
});
});
@ -259,7 +214,7 @@ describe("BenchmarkRunner", () => {
const total = syncTime + asyncTime;
const mean = total / suite.tests.length;
const geomean = Math.pow(total, 1 / suite.tests.length);
const score = (60 * 1000) / geomean / 3; // correctionFactor = 3
const score = 1000 / geomean;
const { total: measuredTotal, mean: measuredMean, geomean: measuredGeomean, score: measuredScore } = runner._measuredValues;