diff --git a/.gitignore b/.gitignore
index 50447732f..50eb2d5dc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -125,3 +125,5 @@ server/migrations/test_plan_target_id.csv
# Private Key files (installed by deploy)
jwt-signing-key.pem
+
+client/resources
diff --git a/client/resources/aria-at-harness.mjs b/client/resources/aria-at-harness.mjs
deleted file mode 100644
index bc52077be..000000000
--- a/client/resources/aria-at-harness.mjs
+++ /dev/null
@@ -1,675 +0,0 @@
-import {
- element,
- fragment,
- property,
- attribute,
- className,
- style,
- focus,
- render,
-} from './vrender.mjs';
-import {
- AssertionResultMap,
- userCloseWindow,
- userOpenWindow,
- WhitespaceStyleMap,
- UnexpectedBehaviorImpactMap,
-} from './aria-at-test-run.mjs';
-import { TestRunExport, TestRunInputOutput } from './aria-at-test-io-format.mjs';
-import { TestWindow } from './aria-at-test-window.mjs';
-
-const PAGE_STYLES = `
- table {
- border-collapse: collapse;
- margin-bottom: 1em;
- }
-
- table, td, th {
- border: 1px solid black;
- }
-
- td {
- padding: .5em;
- }
-
- table.record-results tr:first-child {
- font-weight: bold;
- }
-
- textarea {
- width: 100%
- }
-
- fieldset.problem-select {
- margin-top: 1em;
- margin-left: 1em;
- }
-
- div.problem-option-container.enabled {
- margin-bottom: 0.5em;
- }
-
- div.problem-option-container:last-child {
- margin-bottom: 0;
- }
-
- fieldset.assertions {
- margin-bottom: 1em;
- }
-
- label.assertion {
- display: block;
- }
-
- .required:not(.highlight-required) {
- display: none;
- }
-
- .required-other:not(.highlight-required) {
- display: none;
- }
-
- .required.highlight-required {
- color: red;
- }
-
- fieldset.highlight-required {
- border-color: red;
- }
-
- fieldset .highlight-required {
- color: red;
- }
-
- .off-screen {
- position: absolute !important;
- height: 1px;
- width: 1px;
- overflow: hidden;
- clip: rect(1px, 1px, 1px, 1px);
- white-space: nowrap;
- }
-`;
-
-let testRunIO = new TestRunInputOutput();
-testRunIO.setTitleInputFromTitle(document.title);
-testRunIO.setUnexpectedInputFromBuiltin();
-testRunIO.setScriptsInputFromMap(typeof scripts === 'object' ? scripts : {});
-
-/**
- * @param {SupportJSON} newSupport
- * @param {CommandsJSON} newCommandsData
- * @param {AllCommandsJSON} allCommands
- */
-export function initialize(newSupport, newCommandsData, allCommands) {
- testRunIO.setSupportInputFromJSON(newSupport);
- testRunIO.setAllCommandsInputFromJSON(allCommands);
- testRunIO.setConfigInputFromQueryParamsAndSupport(
- Array.from(new URL(document.location).searchParams)
- );
- testRunIO.setKeysInputFromBuiltinAndConfig();
- testRunIO.setCommandsInputFromJSONAndConfigKeys(newCommandsData);
-}
-
-/**
- * @param {BehaviorJSON} atBehavior
- */
-export function verifyATBehavior(atBehavior) {
- if (testRunIO.behaviorInput !== null) {
- throw new Error('Test files should only contain one verifyATBehavior call.');
- }
-
- testRunIO.setBehaviorInputFromJSONAndCommandsConfigKeysTitleUnexpected(atBehavior);
-}
-
-export async function loadCollectedTestAsync(testRoot, testFileName) {
- const collectedTestResponse = await fetch(`${testRoot}/${testFileName}`);
- const collectedTestJson = await collectedTestResponse.json();
-
- // v2 commands.json
- const commandsJsonResponse = await fetch('../commands.json');
- if (commandsJsonResponse.ok) {
- const commandsJson = await commandsJsonResponse.json();
- testRunIO.setAllCommandsInputFromJSON(commandsJson);
- }
-
- await testRunIO.setInputsFromCollectedTestAsync(collectedTestJson, testRoot);
- testRunIO.setConfigInputFromQueryParamsAndSupport([
- ['at', collectedTestJson.target.at.key],
- ...Array.from(new URL(document.location).searchParams),
- ]);
-
- displayInstructionsForBehaviorTest();
-}
-
-export function displayTestPageAndInstructions(testPage) {
- if (document.readyState !== 'complete') {
- window.setTimeout(() => {
- displayTestPageAndInstructions(testPage);
- }, 100);
- return;
- }
-
- testRunIO.setPageUriInputFromPageUri(testPage);
-
- document.querySelector('html').setAttribute('lang', 'en');
- var style = document.createElement('style');
- style.innerHTML = PAGE_STYLES;
- document.head.appendChild(style);
-
- displayInstructionsForBehaviorTest();
-}
-
-function displayInstructionsForBehaviorTest() {
- const windowManager = new TestWindow({
- ...testRunIO.testWindowOptions(),
- hooks: {
- windowOpened() {
- app.dispatch(userOpenWindow());
- },
- windowClosed() {
- app.dispatch(userCloseWindow());
- },
- },
- });
-
- // First, execute necesary set up script in test page if the test page is open from a previous behavior test
- windowManager.prepare();
-
- const app = new TestRunExport({
- hooks: {
- openTestPage() {
- windowManager.open();
- },
- closeTestPage() {
- windowManager.close();
- },
- postResults: () => postResults(testRunIO.resultJSON(app.state)),
- },
- state: testRunIO.testRunState(),
- resultsJSON: state => testRunIO.resultJSON(state),
- });
- app.observe(() => {
- render(document.body, renderVirtualTestPage(app.testPageAndResults()));
- });
- render(document.body, renderVirtualTestPage(app.testPageAndResults()));
-
- // if test is loaded in iFrame
- if (window.parent && window.parent.postMessage) {
- // results can be submitted by parent posting a message to the
- // iFrame with a data.type property of 'submit'
- window.addEventListener('message', function (message) {
- if (!validateMessage(message, 'submit')) return;
- app.hooks.submit();
- });
-
- // send message to parent that test has loaded
- window.parent.postMessage(
- {
- type: 'loaded',
- data: {
- testPageUri: windowManager.pageUri,
- },
- },
- '*'
- );
- }
-}
-
-function validateMessage(message, type) {
- if (window.location.origin !== message.origin) {
- return false;
- }
- if (!message.data || typeof message.data !== 'object') {
- return false;
- }
- if (message.data.type !== type) {
- return false;
- }
- return true;
-}
-
-/**
- * @param {resultsJSON} resultsJSON
- */
-function postResults(resultsJSON) {
- // send message to parent if test is loaded in iFrame
- if (window.parent && window.parent.postMessage) {
- window.parent.postMessage(
- {
- type: 'results',
- data: resultsJSON,
- },
- '*'
- );
- }
-}
-
-function bind(fn, ...args) {
- return (...moreArgs) => fn(...args, ...moreArgs);
-}
-
-const a = bind(element, 'a');
-const br = bind(element, 'br');
-const button = bind(element, 'button');
-const div = bind(element, 'div');
-const em = bind(element, 'em');
-const kbd = bind(element, 'kbd');
-const fieldset = bind(element, 'fieldset');
-const h1 = bind(element, 'h1');
-const h2 = bind(element, 'h2');
-const h3 = bind(element, 'h3');
-const hr = bind(element, 'hr');
-const input = bind(element, 'input');
-const label = bind(element, 'label');
-const select = bind(element, 'select');
-const option = bind(element, 'option');
-const legend = bind(element, 'legend');
-const li = bind(element, 'li');
-const ol = bind(element, 'ol');
-const p = bind(element, 'p');
-const script = bind(element, 'script');
-const section = bind(element, 'section');
-const span = bind(element, 'span');
-const table = bind(element, 'table');
-const td = bind(element, 'td');
-const textarea = bind(element, 'textarea');
-const th = bind(element, 'th');
-const tr = bind(element, 'tr');
-const ul = bind(element, 'ul');
-
-const forInput = bind(attribute, 'for');
-const href = bind(attribute, 'href');
-const id = bind(attribute, 'id');
-const name = bind(attribute, 'name');
-const tabIndex = bind(attribute, 'tabindex');
-const textContent = bind(attribute, 'textContent');
-const type = bind(attribute, 'type');
-const ariaLabel = bind(attribute, 'aria-label');
-const ariaHidden = bind(attribute, 'aria-hidden');
-
-const value = bind(property, 'value');
-const checked = bind(property, 'checked');
-const disabled = bind(property, 'disabled');
-
-/** @type {(cb: (ev: MouseEvent) => void) => any} */
-const onclick = bind(property, 'onclick');
-/** @type {(cb: (ev: InputEvent) => void) => any} */
-const onchange = bind(property, 'onchange');
-/** @type {(cb: (ev: KeyboardEvent) => void) => any} */
-const onkeydown = bind(property, 'onkeydown');
-
-/**
- * @param {Description} value
- */
-function rich(value) {
- if (typeof value === 'string') {
- return value;
- } else if (Array.isArray(value)) {
- return fragment(...value.map(rich));
- } else if (value.kbd) {
- return kbd.bind(value.kbd)(rich(value.kbd));
- } else {
- if ('whitespace' in value) {
- if (value.whitespace === WhitespaceStyleMap.LINE_BREAK) {
- return br();
- }
- return null;
- }
- return (value.href ? a.bind(null, href(value.href)) : span)(
- className([
- value.offScreen ? 'off-screen' : '',
- value.required ? 'required' : '',
- value.highlightRequired ? 'highlight-required' : '',
- ]),
- rich(value.description)
- );
- }
-}
-
-/**
- * @param {TestPageAndResultsDocument} doc
- */
-function renderVirtualTestPage(doc) {
- return fragment(
- 'instructions' in doc
- ? div(
- section(
- id('errors'),
- style({ display: doc.errors && doc.errors.visible ? 'block' : 'none' }),
- h2(doc.errors ? doc.errors.header : ''),
- ul(
- ...(doc.errors && doc.errors.errors ? doc.errors.errors.map(error => li(error)) : [])
- ),
- hr()
- ),
- section(id('instructions'), renderVirtualInstructionDocument(doc.instructions)),
- section(id('record-results'))
- )
- : null,
- 'results' in doc ? renderVirtualResultsTable(doc.results) : null,
- doc.resultsJSON
- ? script(
- type('text/json'),
- id('__ariaatharness__results__'),
- textContent(JSON.stringify(doc.resultsJSON))
- )
- : null
- );
-}
-
-/**
- * @param doc {InstructionDocument}
- */
-function renderVirtualInstructionDocument(doc) {
- function compose(...fns) {
- return around => fns.reduceRight((carry, fn) => fn(carry), around);
- }
-
- const map = (ary, el) => ary.map(item => el(item));
-
- return div(
- instructionHeader(doc.instructions),
-
- instructCommands(doc.instructions.instructions),
-
- instructAssertions(doc.instructions.assertions),
-
- button(
- disabled(!doc.instructions.openTestPage.enabled),
- onclick(doc.instructions.openTestPage.click),
- rich(doc.instructions.openTestPage.button)
- ),
-
- resultHeader(doc.results.header),
-
- section(...doc.results.commands.map(commandResult)),
-
- doc.submit ? button(onclick(doc.submit.click), rich(doc.submit.button)) : null
- );
-
- /**
- * @param {InstructionDocumentResultsHeader} param0
- */
- function resultHeader({ header, description }) {
- return fragment(h2(rich(header)), p(rich(description)));
- }
-
- /**
- * @param {InstructionDocumentResultsCommand} command
- * @param {number} commandIndex
- */
- function commandResult(command, commandIndex) {
- return fragment(
- h3(rich(command.header)),
- p(
- label(rich(command.atOutput.description)),
- textarea(
- value(command.atOutput.value),
- focus(command.atOutput.focus),
- onchange(ev =>
- command.atOutput.change(/** @type {HTMLInputElement} */ (ev.currentTarget).value)
- )
- )
- ),
- fieldset(
- className(['assertions']),
- legend(rich(command.assertionsHeader.descriptionHeader)),
- ...command.assertions.map(bind(commandResultAssertion, commandIndex))
- ),
- ...[command.unexpectedBehaviors].map(bind(commandResultUnexpectedBehavior, commandIndex))
- );
- }
-
- /**
- * @param {number} commandIndex
- * @param {InstructionDocumentResultsCommandsUnexpected} unexpected
- */
- function commandResultUnexpectedBehavior(commandIndex, unexpected) {
- return fieldset(
- id(`cmd-${commandIndex}-problem`),
- rich(unexpected.description),
- div(
- radioChoice(
- `problem-${commandIndex}-true`,
- `problem-${commandIndex}`,
- unexpected.passChoice
- )
- ),
- div(
- radioChoice(
- `problem-${commandIndex}-false`,
- `problem-${commandIndex}`,
- unexpected.failChoice
- )
- ),
- fieldset(
- className(['problem-select']),
- id(`cmd-${commandIndex}-problem-checkboxes`),
- legend(rich(unexpected.failChoice.options.header)),
- ...unexpected.failChoice.options.options.map(failOption => {
- const failOptionId = failOption.description
- .toLowerCase()
- .replace(/[.,]/g, '')
- .replace(/\s+/g, '-');
-
- const undesirableBehaviorCheckbox = div(
- input(
- type('checkbox'),
- value(failOption.description),
- id(`${failOptionId}-${commandIndex}-checkbox`),
- className([`undesirable-${commandIndex}`]),
- disabled(!failOption.enabled),
- checked(failOption.checked),
- focus(failOption.focus),
- onchange(ev =>
- failOption.change(/** @type {HTMLInputElement} */ (ev.currentTarget).checked)
- ),
- onkeydown(ev => {
- if (failOption.keydown(ev.key)) {
- ev.stopPropagation();
- ev.preventDefault();
- }
- })
- ),
- label(
- id(`${failOptionId}-${commandIndex}-label`),
- forInput(`${failOptionId}-${commandIndex}-checkbox`),
- rich(`${failOption.description} behavior occurred`)
- )
- );
-
- const impactSelect = div(
- className([!failOption.checked && 'off-screen']),
- ariaHidden(!failOption.checked),
- label(forInput(`${failOptionId}-${commandIndex}-impact`), rich('Impact:')),
- select(
- id(`${failOptionId}-${commandIndex}-impact`),
- ariaLabel(`Impact for ${failOption.description}`),
- option(UnexpectedBehaviorImpactMap.MODERATE),
- option(UnexpectedBehaviorImpactMap.SEVERE),
- disabled(!failOption.checked),
- onchange(ev =>
- failOption.impactchange(/** @type {HTMLInputElement} */ (ev.currentTarget).value)
- )
- )
- );
-
- const detailsTextInput = div(
- className([!failOption.checked && 'off-screen']),
- ariaHidden(!failOption.checked),
- label(
- forInput(`${failOptionId}-${commandIndex}-details`),
- rich(failOption.more.description)
- ),
- input(
- type('text'),
- id(`${failOptionId}-${commandIndex}-details`),
- ariaLabel(`Details for ${failOption.description}`),
- className(['undesirable-other-input']),
- disabled(!failOption.more.enabled),
- value(failOption.more.value),
- onchange(ev =>
- failOption.more.change(/** @type {HTMLInputElement} */ (ev.currentTarget).value)
- )
- )
- );
-
- return div(
- className(['problem-option-container', failOption.checked && 'enabled']),
- undesirableBehaviorCheckbox,
- impactSelect,
- detailsTextInput
- );
- })
- )
- );
- }
-
- /**
- * @param {number} commandIndex
- * @param {InstructionDocumentResultsCommandsAssertion} assertion
- * @param {number} assertionIndex
- */
- function commandResultAssertion(commandIndex, assertion, assertionIndex) {
- return label(
- className(['assertion']),
- input(
- type('checkbox'),
- id(`cmd-${commandIndex}-${assertionIndex}`),
- checked(assertion.passed === AssertionResultMap.PASS),
- onclick(assertion.click)
- ),
- rich(assertion.description)
- );
- }
-
- /**
- * @param {string} idKey
- * @param {string} nameKey
- * @param {InstructionDocumentAssertionChoice} choice
- */
- function radioChoice(idKey, nameKey, choice) {
- return fragment(
- input(
- type('radio'),
- id(idKey),
- name(nameKey),
- checked(choice.checked),
- focus(choice.focus),
- onclick(choice.click)
- ),
- label(id(`${idKey}-label`), forInput(`${idKey}`), rich(choice.label))
- );
- }
-
- /**
- * @param {InstructionDocumentInstructionsInstructions} param0
- * @returns
- */
- function instructCommands({
- header,
- instructions,
- strongInstructions: boldInstructions,
- commands,
- }) {
- return fragment(
- h2(rich(header)),
- ol(
- ...map(instructions, compose(li, rich)),
- ...map(boldInstructions, compose(li, em, rich)),
- li(rich(commands.description), ul(...map(commands.commands, compose(li, em, rich))))
- )
- );
- }
-
- /**
- * @param {InstructionDocumentInstructions} param0
- */
- function instructionHeader({ header, description }) {
- return fragment(
- h1(id('behavior-header'), tabIndex('0'), focus(header.focus), rich(header.header)),
- p(rich(description))
- );
- }
-
- /**
- * @param {InstructionDocumentInstructionsAssertions} param0
- */
- function instructAssertions({ header, description, assertions }) {
- return fragment(
- h2(rich(header)),
- p(rich(description)),
- ol(...map(assertions, compose(li, em, rich)))
- );
- }
-}
-
-/**
- * @param {ResultsTableDocument} results
- */
-function renderVirtualResultsTable(results) {
- return fragment(
- h1(rich(results.header)),
- h2(id('overallstatus'), rich(results.status.header)),
-
- table(
- (({ description, support, details }) => tr(th(description), th(support), th(details)))(
- results.table.headers
- ),
- results.table.commands.map(
- ({
- description,
- support,
- details: { output, passingAssertions, failingAssertions, unexpectedBehaviors },
- }) =>
- fragment(
- tr(
- td(rich(description)),
- td(rich(support)),
- td(
- p(rich(output)),
- commandDetailsList(passingAssertions),
- commandDetailsList(failingAssertions),
- commandDetailsList(unexpectedBehaviors)
- )
- )
- )
- )
- )
- );
-
- /**
- * @param {object} list
- * @param {Description} list.description
- * @param {Description[]} list.items
- */
- function commandDetailsList({ description, items }) {
- return div(description, ul(...items.map(description => li(rich(description)))));
- }
-}
-
-/** @typedef {import('./aria-at-test-io-format.mjs').SupportJSON} SupportJSON */
-/** @typedef {import('./aria-at-test-io-format.mjs').AllCommandsJSON} AllCommandsJSON */
-/** @typedef {import('./aria-at-test-io-format.mjs').CommandsJSON} CommandsJSON */
-/** @typedef {import('./aria-at-test-io-format.mjs').BehaviorJSON} BehaviorJSON */
-
-/** @typedef {import('./aria-at-test-run.mjs').TestRunState} TestRunState */
-
-/** @typedef {import('./aria-at-test-run.mjs').Description} Description */
-
-/** @typedef {import('./aria-at-test-run.mjs').InstructionDocument} InstructionDocument */
-/** @typedef {import('./aria-at-test-run.mjs').InstructionDocumentInstructions} InstructionDocumentInstructions */
-/** @typedef {import('./aria-at-test-run.mjs').InstructionDocumentInstructionsAssertions} InstructionDocumentInstructionsAssertions */
-/** @typedef {import('./aria-at-test-run.mjs').InstructionDocumentResultsHeader} InstructionDocumentResultsHeader */
-/** @typedef {import('./aria-at-test-run.mjs').InstructionDocumentResultsCommand} InstructionDocumentResultsCommand */
-/** @typedef {import('./aria-at-test-run.mjs').InstructionDocumentResultsCommandsUnexpected} InstructionDocumentResultsCommandsUnexpected */
-/** @typedef {import("./aria-at-test-run.mjs").InstructionDocumentResultsCommandsAssertion} InstructionDocumentResultsCommandsAssertion */
-/** @typedef {import("./aria-at-test-run.mjs").InstructionDocumentAssertionChoice} InstructionDocumentAssertionChoice */
-/** @typedef {import("./aria-at-test-run.mjs").InstructionDocumentInstructionsInstructions} InstructionDocumentInstructionsInstructions */
-
-/** @typedef {import('./aria-at-test-run.mjs').ResultsTableDocument} ResultsTableDocument */
-
-/**
- * @typedef {import('./aria-at-test-io-format.mjs').TestPageAndResultsDocument} TestPageAndResultsDocument
- */
diff --git a/client/resources/aria-at-test-io-format.mjs b/client/resources/aria-at-test-io-format.mjs
deleted file mode 100644
index 8121c4133..000000000
--- a/client/resources/aria-at-test-io-format.mjs
+++ /dev/null
@@ -1,1878 +0,0 @@
-///
-///
-///
-
-import {
- AssertionResultMap,
- CommonResultMap,
- createEnumMap,
- HasUnexpectedBehaviorMap,
- TestRun,
- UserActionMap,
-} from './aria-at-test-run.mjs';
-import * as keysModule from './keys.mjs';
-
-const UNEXPECTED_BEHAVIORS = [
- 'Output is excessively verbose, e.g., includes redundant and/or irrelevant speech',
- 'Reading cursor position changed in an unexpected manner',
- 'Screen reader became extremely sluggish',
- 'Screen reader crashed',
- 'Browser crashed',
-];
-
-/** Depends on ConfigInput. */
-class KeysInput {
- /**
- * @param {object} value
- * @param {string} value.origin
- * @param {{[KEY_ID: string]: string}} value.keys
- * @param {ATJSON} value.at
- * @param {{[atMode in ATMode]: string}} value.modeInstructions
- * @private
- */
- constructor(value) {
- this.errors = [];
-
- /** @private */
- this._value = value;
- }
-
- origin() {
- return this._value.origin;
- }
-
- /**
- * @param {string} keyId
- * @returns {string}
- */
- keysForCommand(keyId) {
- return this._value.keys[keyId];
- }
-
- /**
- * @param {ATMode} atMode
- */
- modeInstructions(atMode) {
- if (this._value.modeInstructions[atMode]) {
- return this._value.modeInstructions[atMode];
- }
- return '';
- }
-
- /**
- * @param {object} data
- * @param {ConfigInput} data.configInput
- */
- static fromBuiltinAndConfig({ configInput }) {
- const keys = keysModule;
- const atKey = configInput.at().key;
-
- invariant(
- ['jaws', 'nvda', 'voiceover_macos'].includes(atKey),
- '%s is one of "jaws", "nvda", or "voiceover_macos"',
- atKey
- );
-
- return new KeysInput({
- origin: 'resources/keys.mjs',
- keys,
- at: atKey,
- modeInstructions: {
- reading: {
- jaws: `Verify the Virtual Cursor is active by pressing ${keys.ALT_DELETE}. If it is not, exit Forms Mode to activate the Virtual Cursor by pressing ${keys.ESC}.`,
- nvda: `Ensure NVDA is in browse mode by pressing ${keys.ESC}. Note: This command has no effect if NVDA is already in browse mode.`,
- voiceover_macos: `Toggle Quick Nav ON by pressing the ${keys.LEFT} and ${keys.RIGHT} keys at the same time.`,
- }[atKey],
- interaction: {
- jaws: `Verify the PC Cursor is active by pressing ${keys.ALT_DELETE}. If it is not, turn off the Virtual Cursor by pressing ${keys.INS_Z}.`,
- nvda: `If NVDA did not make the focus mode sound when the test page loaded, press ${keys.INS_SPACE} to turn focus mode on.`,
- voiceover_macos: `Toggle Quick Nav OFF by pressing the ${keys.LEFT} and ${keys.RIGHT} keys at the same time.`,
- }[atKey],
- },
- });
- }
-
- /** @param {AriaATFile.CollectedTest} collectedTest */
- static fromCollectedTest(collectedTest) {
- return new KeysInput({
- origin: 'test.collected.json',
- keys: collectedTest.commands.reduce((carry, { keypresses }) => {
- return keypresses.reduce((carry, { id, keystroke }) => {
- carry[id] = keystroke;
- return carry;
- }, carry);
- }, {}),
- at: collectedTest.target.at.key,
- modeInstructions: collectedTest.instructions.mode,
- });
- }
-}
-
-class SupportInput {
- /**
- * @param {SupportJSON} value
- * @private
- */
- constructor(value) {
- this.errors = [];
-
- /** @private */
- this._value = value;
- }
-
- defaultAT() {
- return this._value.ats[0];
- }
-
- /**
- * @param {string} atKey
- * @returns {ATJSON | undefined}
- */
- findAT(atKey) {
- const lowercaseATKey = atKey.toLowerCase();
- return this._value.ats.find(({ key }) => key === lowercaseATKey);
- }
-
- /**
- * @param {SupportJSON} json
- */
- static fromJSON(json) {
- return new SupportInput(json);
- }
-
- /**
- * @param {AriaATFile.CollectedTest} collectedTest
- */
- static fromCollectedTest(collectedTest) {
- return new SupportInput({
- ats: [
- typeof collectedTest.target.at.raw === 'object'
- ? collectedTest.target.at.raw
- : { key: collectedTest.target.at.key, name: collectedTest.target.at.name },
- ],
- applies_to: {},
- examples: [],
- });
- }
-}
-
-class AllCommandsInput {
- /**
- * @param {AllCommandsJSON} value
- * @private
- */
- constructor(value) {
- this.errors = [];
-
- /** @private */
- this._value = value;
-
- /** @private */
- this._flattened = this.flattenObject(this._value);
- }
-
- flattenObject(obj, parentKey) {
- const flattened = {};
-
- for (const key in obj) {
- if (typeof obj[key] === 'object') {
- const subObject = this.flattenObject(obj[key], parentKey + key + '.');
- Object.assign(flattened, subObject);
- } else {
- flattened[parentKey + key] = obj[key];
- }
- }
-
- return flattened;
- }
-
- findValueByKey(keyToFind) {
- const keys = Object.keys(this._flattened);
-
- // Need to specially handle VO modifier key combination
- if (keyToFind === 'vo')
- return this.findValuesByKeys([this._flattened['modifierAliases.vo']])[0];
-
- if (keyToFind.includes('modifiers.') || keyToFind.includes('keys.')) {
- const parts = keyToFind.split('.');
- const keyToCheck = parts[parts.length - 1]; // value after the '.'
-
- if (this._flattened[keyToFind])
- return {
- value: this._flattened[keyToFind],
- key: keyToCheck,
- };
-
- return null;
- }
-
- for (const key of keys) {
- const parts = key.split('.');
- const parentKey = parts[0];
- const keyToCheck = parts[parts.length - 1]; // value after the '.'
-
- if (keyToCheck === keyToFind) {
- if (parentKey === 'modifierAliases') {
- return this.findValueByKey(`modifiers.${this._flattened[key]}`);
- } else if (parentKey === 'keyAliases') {
- return this.findValueByKey(`keys.${this._flattened[key]}`);
- }
-
- return {
- value: this._flattened[key],
- key: keyToCheck,
- };
- }
- }
-
- // Return null if the key is not found
- return null;
- }
-
- findValuesByKeys(keysToFind = []) {
- const result = [];
-
- const patternSepWithReplacement = (keyToFind, pattern, replacement) => {
- if (keyToFind.includes(pattern)) {
- let value = '';
- let validKeys = true;
- const keys = keyToFind.split(pattern);
-
- for (const key of keys) {
- const keyResult = this.findValueByKey(key);
- if (keyResult)
- value = value ? `${value}${replacement}${keyResult.value}` : keyResult.value;
- else validKeys = false;
- }
- if (validKeys) return { value, key: keyToFind };
- }
-
- return null;
- };
-
- const patternSepHandler = keyToFind => {
- let value = '';
-
- if (keyToFind.includes(' ') && keyToFind.includes('+')) {
- const keys = keyToFind.split(' ');
- for (let [index, key] of keys.entries()) {
- const keyToFindResult = this.findValueByKey(key);
- if (keyToFindResult) keys[index] = keyToFindResult.value;
- if (key.includes('+')) keys[index] = patternSepWithReplacement(key, '+', '+').value;
- }
- value = keys.join(' then ');
-
- return { value, key: keyToFind };
- } else if (keyToFind.includes(' '))
- return patternSepWithReplacement(keyToFind, ' ', ' then ');
- else if (keyToFind.includes('+')) return patternSepWithReplacement(keyToFind, '+', '+');
- };
-
- for (const keyToFind of keysToFind) {
- if (keyToFind.includes(' ') || keyToFind.includes('+')) {
- result.push(patternSepHandler(keyToFind));
- } else {
- const keyToFindResult = this.findValueByKey(keyToFind);
- if (keyToFindResult) result.push(keyToFindResult);
- }
- }
-
- return result;
- }
-
- static fromJSON(json) {
- return new AllCommandsInput(json);
- }
-}
-
-/** Depends on ConfigInput and KeysInput. */
-class CommandsInput {
- /**
- * @param {object} value
- * @param {CommandsJSON} value.commands
- * @param {ATJSON} value.at
- * @param {KeysInput} keysInput
- * @param {AllCommandsInput} allCommandsInput
- * @private
- */
- constructor(value, keysInput, allCommandsInput) {
- this.errors = [];
-
- /** @private */
- this._value = value;
-
- /** @private */
- this._keysInput = keysInput;
-
- this._allCommandsInput = allCommandsInput;
- }
-
- /**
- * @param {object} config
- * @param {string} config.task
- * @param {ATMode} mode
- * @returns {string[]}
- */
- getCommands({ task }, mode) {
- if (mode === 'reading' || mode === 'interaction') {
- const v1Commands = this.getCommandsV1(task, mode);
- return {
- commands: v1Commands,
- commandsAndSettings: v1Commands.map(command => ({ command })),
- };
- } else {
- return this.getCommandsV2({ task }, mode);
- }
- }
-
- getCommandsV1(task, mode) {
- const assistiveTech = this._value.at;
-
- if (!this._value.commands[task]) {
- throw new Error(
- `Task "${task}" does not exist, please add to at-commands or correct your spelling.`
- );
- } else if (!this._value.commands[task][mode]) {
- throw new Error(
- `Mode "${mode}" instructions for task "${task}" does not exist, please add to at-commands or correct your spelling.`
- );
- }
-
- let commandsData = this._value.commands[task][mode][assistiveTech.key] || [];
- let commands = [];
-
- for (let c of commandsData) {
- let innerCommands = [];
- let commandSequence = c[0].split(',');
- for (let command of commandSequence) {
- command = this._keysInput.keysForCommand(command);
- if (typeof command === 'undefined') {
- throw new Error(
- `Key instruction identifier "${c}" for AT "${assistiveTech.name}", mode "${mode}", task "${task}" is not an available identified. Update you commands.json file to the correct identifier or add your identifier to resources/keys.mjs.`
- );
- }
-
- let furtherInstruction = c[1];
- command = furtherInstruction ? `${command} ${furtherInstruction}` : command;
- innerCommands.push(command);
- }
- commands.push(innerCommands.join(', then '));
- }
-
- return commands;
- }
-
- getCommandsV2({ task }, mode) {
- const assistiveTech = this._value.at;
- let commandsAndSettings = [];
- let commands = [];
-
- // Mode could be in the format of mode1_mode2
- // If they are from the same AT, this needs to return the function in the format of [ [[commands], settings], [[commands], settings], ... ]
- for (const _atMode of mode.split('_')) {
- if (assistiveTech.settings[_atMode] || _atMode === 'defaultMode') {
- const [atMode] = deriveModeWithTextAndInstructions(_atMode, assistiveTech);
-
- if (!this._value.commands[task]) {
- throw new Error(
- `Task "${task}" does not exist, please add to at-commands or correct your spelling.`
- );
- } else if (!this._value.commands[task][atMode]) {
- throw new Error(
- `Mode "${atMode}" instructions for task "${task}" does not exist, please add to at-commands or correct your spelling.`
- );
- }
-
- let commandsData = this._value.commands[task][atMode][assistiveTech.key] || [];
- for (let commandSequence of commandsData) {
- for (const commandWithPresentationNumber of commandSequence) {
- const [commandId, presentationNumber] = commandWithPresentationNumber.split('|');
-
- let command;
- const foundCommandKV = this._allCommandsInput.findValuesByKeys([commandId]);
- if (!foundCommandKV.length) command = undefined;
- else {
- const { value } = this._allCommandsInput.findValuesByKeys([commandId])[0];
- command = value;
- }
-
- if (typeof command === 'undefined') {
- throw new Error(
- `Key instruction identifier "${commandSequence}" for AT "${assistiveTech.name}", mode "${atMode}", task "${task}" is not an available identified. Update your commands.json file to the correct identifier or add your identifier to resources/keys.mjs.`
- );
- }
-
- commands.push(command);
- commandsAndSettings.push({
- command,
- commandId,
- presentationNumber: Number(presentationNumber),
- settings: _atMode,
- settingsText: assistiveTech.settings?.[_atMode]?.screenText || 'default mode active',
- settingsInstructions: assistiveTech.settings?.[_atMode]?.instructions || [
- assistiveTech.defaultConfigurationInstructionsHTML,
- ],
- });
- }
- }
- }
- }
-
- return { commands, commandsAndSettings };
- }
-
- /**
- * @param {CommandsJSON} json
- * @param {object} data
- * @param {ConfigInput} data.configInput
- * @param {KeysInput} data.keysInput
- */
- static fromJSONAndConfigKeys(json, { configInput, keysInput, allCommandsInput }) {
- return new CommandsInput({ commands: json, at: configInput.at() }, keysInput, allCommandsInput);
- }
-
- /**
- * @param {AriaATFile.CollectedTest} collectedTest
- * @param {object} data
- * @param {KeysInput} data.keysInput
- */
- static fromCollectedTestKeys(collectedTest, { keysInput, allCommandsInput }) {
- let settingsForTest = {};
-
- // For v2 test format
- const settings = collectedTest.target.at.settings;
- if (settings) {
- for (const _atMode of settings.split('_')) {
- settingsForTest[_atMode] = {
- // Use settings attribute to verify in filter if available
- [collectedTest.target.at.key]: collectedTest.commands
- .filter(({ settings }) => (settings ? settings === _atMode : true))
- .map(({ id, extraInstruction }) => (extraInstruction ? [id, extraInstruction] : [id])),
- };
- }
- } else {
- settingsForTest = {
- [collectedTest.target.mode]: {
- [collectedTest.target.at.key]: collectedTest.commands.map(({ id, extraInstruction }) =>
- extraInstruction ? [id, extraInstruction] : [id]
- ),
- },
- };
- }
-
- return new CommandsInput(
- {
- commands: {
- [collectedTest.info.task || collectedTest.info.testId]: settingsForTest,
- },
- at:
- typeof collectedTest.target.at.raw === 'object'
- ? collectedTest.target.at.raw
- : collectedTest.target.at,
- },
- keysInput,
- allCommandsInput
- );
- }
-}
-
-/**
- * Depends on SupportInput.
- */
-class ConfigInput {
- /**
- * @param {string[]} errors
- * @param {object} value
- * @param {ATJSON} value.at
- * @param {boolean} value.displaySubmitButton
- * @param {boolean} value.renderResultsAfterSubmit
- * @param {"SubmitResultsJSON" | "TestResultJSON"} value.resultFormat
- * @param {AriaATTestResult.JSON | null} value.resultJSON
- * @private
- */
- constructor(errors, value) {
- this.errors = errors;
-
- /** @private */
- this._value = value;
- }
-
- at() {
- return this._value.at;
- }
-
- displaySubmitButton() {
- return this._value.displaySubmitButton;
- }
-
- renderResultsAfterSubmit() {
- return this._value.renderResultsAfterSubmit;
- }
-
- resultFormat() {
- return this._value.resultFormat;
- }
-
- resultJSON() {
- return this._value.resultJSON;
- }
-
- /**
- * @param {ConfigQueryParams} queryParams
- * @param {object} data
- * @param {SupportInput} data.supportInput
- */
- static fromQueryParamsAndSupport(queryParams, { supportInput }) {
- const errors = [];
-
- let at = supportInput.defaultAT();
- let displaySubmitButton = true;
- let renderResultsAfterSubmit = true;
- let resultFormat = 'SubmitResultsJSON';
- let resultJSON = null;
-
- for (const [key, value] of queryParams) {
- if (key === 'at') {
- const requestedAT = value;
- const knownAt = supportInput.findAT(requestedAT);
- if (knownAt) {
- at = knownAt;
- } else {
- errors.push(
- `Harness does not have commands for the requested assistive technology ('${requestedAT}'), showing commands for assistive technology '${at.name}' instead. To test '${requestedAT}', please contribute command mappings to this project.`
- );
- }
- } else if (key === 'showResults') {
- displaySubmitButton = decodeBooleanParam(value, displaySubmitButton);
- } else if (key === 'showSubmitButton') {
- renderResultsAfterSubmit = decodeBooleanParam(value, renderResultsAfterSubmit);
- } else if (key === 'resultFormat') {
- if (value !== 'SubmitResultsJSON' && value !== 'TestResultJSON') {
- errors.push(
- `resultFormat can be 'SubmitResultsJSON' or 'TestResultJSON'. '${value}' is not supported.`
- );
- continue;
- }
- resultFormat = value;
- } else if (key === 'resultJSON') {
- try {
- resultJSON = JSON.parse(value);
- } catch (error) {
- errors.push(`Failed to parse resultJSON: ${error.message}`);
- }
- }
- }
-
- if (resultJSON && resultFormat !== 'TestResultJSON') {
- errors.push(`resultJSON requires resultFormat to be set to 'TestResultJSON'.`);
- resultJSON = null;
- }
-
- return new ConfigInput(errors, {
- at,
- displaySubmitButton,
- renderResultsAfterSubmit,
- resultFormat,
- resultJSON,
- });
-
- /**
- * @param {string} param
- * @param {boolean} defaultValue
- * @returns {boolean}
- */
- function decodeBooleanParam(param, defaultValue) {
- if (param === 'true') {
- return true;
- } else if (param === 'false') {
- return false;
- }
- return defaultValue;
- }
- }
-}
-
-class ScriptsInput {
- /**
- * @param {object} value
- * @param {SetupScripts} value.scripts
- * @private
- */
- constructor(value) {
- this.errors = [];
-
- /** @private */
- this._value = value;
- }
-
- scripts() {
- return this._value.scripts;
- }
-
- /**
- * @param {SetupScripts} scripts
- */
- static fromScriptsMap(scripts) {
- return new ScriptsInput({ scripts });
- }
-
- /**
- * @param {{source: string}} script
- * @private
- */
- static scriptsFromSource(script) {
- return { [script.name]: new Function('testPageDocument', script.source) };
- }
-
- /**
- * @param {{modulePath: string}} script
- * @param {string} dataUrl
- * @private
- */
- static async scriptsFromModuleAsync(script, dataUrl) {
- return await import(`${dataUrl}/${script.modulePath}`);
- }
-
- /**
- * @param {{jsonpPath: string}} script
- * @param {string} dataUrl
- * @private
- */
- static async scriptsFromJsonpAsync(script, dataUrl) {
- return await Promise.race([
- new Promise(resolve => {
- window.scriptsJsonpLoaded = resolve;
- const scriptTag = document.createElement('script');
- scriptTag.src = script.jsonpPath;
- document.body.appendChild(scriptTag);
- }),
- new Promise((_, reject) =>
- setTimeout(() => reject(new Error('Loading scripts timeout error')), 10000)
- ),
- ]);
- }
-
- /**
- * @param {AriaATFile.CollectedTest} collectedAsync
- * @param {string} dataUrl url to directory where CollectedTest was loaded from
- */
- static async fromCollectedTestAsync({ target: { setupScript } }, dataUrl) {
- if (!setupScript) {
- return new ScriptsInput({ scripts: {} });
- }
- try {
- return new ScriptsInput({ scripts: ScriptsInput.scriptsFromSource(setupScript) });
- } catch (error) {
- try {
- return new ScriptsInput({
- scripts: await ScriptsInput.scriptsFromModuleAsync(setupScript, dataUrl),
- });
- } catch (error2) {
- try {
- return new ScriptsInput({
- scripts: await ScriptsInput.scriptsFromJsonpAsync(setupScript, dataUrl),
- });
- } catch (error3) {
- throw new Error(
- [error, error2, error3].map(error => error.stack || error.message).join('\n\n')
- );
- }
- }
- }
- }
-}
-
-class UnexpectedInput {
- /**
- * @param {object} value
- * @param {BehaviorUnexpectedItem[]} value.behaviors
- * @private
- */
- constructor(value) {
- this.errors = [];
-
- this._value = value;
- }
-
- behaviors() {
- return this._value.behaviors;
- }
-
- static fromBuiltin() {
- return new UnexpectedInput({
- behaviors: [
- ...UNEXPECTED_BEHAVIORS.map(description => ({ description })),
- { description: 'Other' },
- ],
- });
- }
-}
-
-class TitleInput {
- /**
- * @param {object} value
- * @param {string} value.title
- * @private
- */
- constructor(value) {
- this.errors = [];
-
- /** @private */
- this._value = value;
- }
-
- title() {
- return this._value.title;
- }
-
- /** @param {string} title */
- static fromTitle(title) {
- return new TitleInput({
- title,
- });
- }
-}
-
-/** Depends on CommandsInput, ConfigInput, KeysInput, TitleInput, and UnexpectedInput. */
-class BehaviorInput {
- /**
- * @param {object} value
- * @param {Behavior} value.behavior
- * @private
- */
- constructor(value) {
- this.errors = [];
-
- /** @private */
- this._value = value;
- }
-
- behavior() {
- return this._value.behavior;
- }
-
- /**
- * @param {BehaviorJSON} json
- * @param {object} data
- * @param {KeysInput} data.keysInput
- * @param {CommandsInput} data.commandsInput
- * @param {ConfigInput} data.configInput
- * @param {UnexpectedInput} data.unexpectedInput
- * @param {TitleInput} data.titleInput
- */
- static fromJSONCommandsConfigKeysTitleUnexpected(
- json,
- { commandsInput, configInput, keysInput, titleInput, unexpectedInput }
- ) {
- const mode = Array.isArray(json.mode) ? json.mode[0] : json.mode;
- const at = configInput.at();
-
- const { commandsAndSettings } = commandsInput.getCommands({ task: json.task }, mode);
-
- // Use to determine assertionExceptions
- const commandsInfo = json.commandsInfo?.[at.key];
-
- return new BehaviorInput({
- behavior: {
- description: titleInput.title(),
- task: json.task,
- mode,
- modeInstructions: keysInput.modeInstructions(mode),
- appliesTo: json.applies_to,
- specificUserInstruction: json.specific_user_instruction,
- setupScriptDescription: json.setup_script_description,
- setupTestPage: json.setupTestPage,
- assertionResponseQuestion: json.assertionResponseQuestion,
- commands: commandsAndSettings.map(cs => {
- const foundCommandInfo = commandsInfo?.find(
- c =>
- cs.commandId === c.command &&
- cs.presentationNumber === c.presentationNumber &&
- cs.settings === c.settings
- );
- if (!foundCommandInfo || !foundCommandInfo.assertionExceptions) return cs;
-
- // Only works for v2
- let assertionExceptions = json.output_assertions.map(each => each.assertionId);
- foundCommandInfo.assertionExceptions.split(' ').forEach(each => {
- let [priority, assertionId] = each.split(':');
- const index = assertionExceptions.findIndex(each => each === assertionId);
-
- priority = Number(priority);
- assertionExceptions[index] = priority;
- });
- // Preserve default priority or update with exception
- assertionExceptions = assertionExceptions.map((each, index) =>
- isNaN(each) ? json.output_assertions[index].priority : each
- );
-
- return { ...cs, assertionExceptions };
- }),
- assertions: (json.output_assertions ? json.output_assertions : []).map(assertion => {
- // Tuple array [ priorityNumber, assertionText ]
- if (Array.isArray(assertion)) {
- return {
- priority: Number(assertion[0]),
- assertion: assertion[1],
- };
- }
-
- // { assertionId, priority, assertionStatement, assertionPhrase, refIds, tokenizedAssertionStatements, tokenizedAssertionPhrases }
- return {
- priority: assertion.priority,
- assertion:
- assertion.tokenizedAssertionStatements?.[at.key] || assertion.assertionStatement,
- };
- }),
- additionalAssertions: (json.additional_assertions
- ? json.additional_assertions[at.key] || []
- : []
- ).map(assertionTuple => ({
- priority: Number(assertionTuple[0]),
- assertion: assertionTuple[1],
- })),
- unexpectedBehaviors: unexpectedInput.behaviors(),
- },
- });
- }
-
- /**
- * @param {AriaATFile.CollectedTest} collectedTest
- * @param {object} data
- * @param {CommandsInput} data.commandsInput
- * @param {KeysInput} data.keysInput
- * @param {UnexpectedInput} data.unexpectedInput
- */
- static fromCollectedTestCommandsKeysUnexpected(
- { info, target, instructions, assertions, commands },
- { commandsInput, keysInput, unexpectedInput }
- ) {
- // v1:info.task, v2: info.testId | v1:target.mode, v2:target.at.settings
- const { commandsAndSettings } = commandsInput.getCommands(
- { task: info.task || info.testId },
- target.mode || target.at.settings
- );
-
- return new BehaviorInput({
- behavior: {
- description: info.title,
- task: info.task || info.testId,
- mode: target.mode || target.at.settings,
- modeInstructions: instructions.mode,
- appliesTo: [target.at.name],
- specificUserInstruction: instructions.raw || instructions.instructions,
- setupScriptDescription: target.setupScript ? target.setupScript.description : '',
- setupTestPage: target.setupScript ? target.setupScript.name : undefined,
- commands: commandsAndSettings.map(cs => {
- const foundCommandInfo = commands.find(
- c => cs.commandId === c.id && cs.settings === c.settings
- );
- if (!foundCommandInfo || !foundCommandInfo.assertionExceptions) return cs;
-
- // Only works for v2
- let assertionExceptions = assertions.map(each => each.assertionId);
- foundCommandInfo.assertionExceptions.forEach(each => {
- let { priority, assertionId } = each;
- const index = assertionExceptions.findIndex(each => each === assertionId);
-
- priority = Number(priority);
- assertionExceptions[index] = priority;
- });
- // Preserve default priority or update with exception
- assertionExceptions = assertionExceptions.map((each, index) =>
- isNaN(each) ? assertions[index].priority : each
- );
-
- return { ...cs, assertionExceptions };
- }),
- assertions: assertions.map(
- ({ priority, expectation, assertionStatement, tokenizedAssertionStatements }) => {
- let assertion = tokenizedAssertionStatements
- ? tokenizedAssertionStatements[target.at.key]
- : null;
- assertion = assertion || expectation || assertionStatement;
-
- return {
- priority,
- assertion,
- };
- }
- ),
- additionalAssertions: [],
- unexpectedBehaviors: unexpectedInput.behaviors(),
- },
- });
- }
-}
-
-class PageUriInput {
- /**
- * @param {object} value
- * @param {string} value.pageUri
- * @private
- */
- constructor(value) {
- this._errors = [];
- this._value = value;
- }
-
- pageUri() {
- return this._value.pageUri;
- }
-
- /**
- * @param {string} pageUri
- */
- static fromPageUri(pageUri) {
- return new PageUriInput({ pageUri });
- }
-}
-
-export class TestRunInputOutput {
- constructor() {
- /** @type {BehaviorInput} */
- this.behaviorInput = null;
- /** @type {CommandsInput} */
- this.commandsInput = null;
- /** @type {ConfigInput} */
- this.configInput = null;
- /** @type {KeysInput} */
- this.keysInput = null;
- /** @type {PageUriInput} */
- this.pageUriInput = null;
- /** @type {ScriptsInput} */
- this.scriptsInput = null;
- /** @type {SupportInput} */
- this.supportInput = null;
- /** @type {AllCommandsInput} */
- this.allCommandsInput = null;
- /** @type {TitleInput} */
- this.titleInput = null;
- /** @type {UnexpectedInput} */
- this.unexpectedInput = null;
- }
-
- /** @param {BehaviorInput} behaviorInput */
- setBehaviorInput(behaviorInput) {
- this.behaviorInput = behaviorInput;
- }
-
- /** @param {BehaviorJSON} behaviorJSON */
- setBehaviorInputFromJSONAndCommandsConfigKeysTitleUnexpected(behaviorJSON) {
- invariant(
- this.commandsInput !== null,
- 'Call %s or %s before calling %s.',
- this.setCommandsInput.name,
- this.setCommandsInputFromJSONAndConfigKeys.name,
- this.setBehaviorInputFromJSONAndCommandsConfigKeysTitleUnexpected.name
- );
- invariant(
- this.configInput !== null,
- 'Call %s or %s before calling %s.',
- this.setConfigInput.name,
- this.setConfigInputFromQueryParamsAndSupport.name,
- this.setBehaviorInputFromJSONAndCommandsConfigKeysTitleUnexpected.name
- );
- invariant(
- this.keysInput !== null,
- 'Call %s or %s before calling %s.',
- this.setKeysInput.name,
- this.setKeysInputFromBuiltinAndConfig.name,
- this.setBehaviorInputFromJSONAndCommandsConfigKeysTitleUnexpected.name
- );
- invariant(
- this.titleInput !== null,
- 'Call %s or %s before calling %s.',
- this.setTitleInput.name,
- this.setTitleInputFromTitle.name,
- this.setBehaviorInputFromJSONAndCommandsConfigKeysTitleUnexpected.name
- );
- invariant(
- this.unexpectedInput !== null,
- 'Call %s or %s before calling %s.',
- this.setUnexpectedInput.name,
- this.setUnexpectedInputFromBuiltin.name,
- this.setBehaviorInputFromJSONAndCommandsConfigKeysTitleUnexpected.name
- );
-
- this.setBehaviorInput(
- BehaviorInput.fromJSONCommandsConfigKeysTitleUnexpected(behaviorJSON, {
- commandsInput: this.commandsInput,
- configInput: this.configInput,
- keysInput: this.keysInput,
- titleInput: this.titleInput,
- unexpectedInput: this.unexpectedInput,
- })
- );
- }
-
- /**
- * Set all inputs but ConfigInput.
- * @param {AriaATFile.CollectedTest} collectedTest
- * @param {string} dataUrl url to directory where CollectedTest was loaded from
- */
- async setInputsFromCollectedTestAsync(collectedTest, dataUrl) {
- const pageUriInput = PageUriInput.fromPageUri(collectedTest.target.referencePage);
- const titleInput = TitleInput.fromTitle(collectedTest.info.title);
- const supportInput = SupportInput.fromCollectedTest(collectedTest);
- const scriptsInput = await ScriptsInput.fromCollectedTestAsync(collectedTest, dataUrl);
-
- const unexpectedInput = UnexpectedInput.fromBuiltin();
- const keysInput = KeysInput.fromCollectedTest(collectedTest);
- const allCommandsInput = this.allCommandsInput;
- const commandsInput = CommandsInput.fromCollectedTestKeys(collectedTest, {
- keysInput,
- allCommandsInput,
- });
- const behaviorInput = BehaviorInput.fromCollectedTestCommandsKeysUnexpected(collectedTest, {
- commandsInput,
- keysInput,
- unexpectedInput,
- });
-
- this.setTitleInput(titleInput);
- this.setPageUriInput(pageUriInput);
- this.setSupportInput(supportInput);
- this.setScriptsInput(scriptsInput);
-
- this.setUnexpectedInput(unexpectedInput);
- this.setKeysInput(keysInput);
- this.setCommandsInput(commandsInput);
- this.setBehaviorInput(behaviorInput);
- }
-
- /** @param {CommandsInput} commandsInput */
- setCommandsInput(commandsInput) {
- this.commandsInput = commandsInput;
- }
-
- /** @param {CommandsJSON} commandsJSON */
- setCommandsInputFromJSONAndConfigKeys(commandsJSON) {
- invariant(
- this.configInput !== null,
- 'Call %s or %s before calling %s.',
- this.setConfigInput.name,
- this.setConfigInputFromQueryParamsAndSupport.name,
- this.setCommandsInputFromJSONAndConfigKeys.name
- );
- invariant(
- this.keysInput !== null,
- 'Call %s or %s before calling %s.',
- this.setKeysInput.name,
- this.setKeysInputFromBuiltinAndConfig.name,
- this.setCommandsInputFromJSONAndConfigKeys.name
- );
-
- this.setCommandsInput(
- CommandsInput.fromJSONAndConfigKeys(commandsJSON, {
- configInput: this.configInput,
- keysInput: this.keysInput,
- allCommandsInput: this.allCommandsInput,
- })
- );
- }
-
- /** @param {ConfigInput} configInput */
- setConfigInput(configInput) {
- this.configInput = configInput;
- }
-
- /** @param {ConfigQueryParams} queryParams */
- setConfigInputFromQueryParamsAndSupport(queryParams) {
- invariant(
- this.supportInput !== null,
- 'Call %s or %s before calling %s.',
- this.setSupportInput.name,
- this.setSupportInputFromJSON.name,
- this.setConfigInputFromQueryParamsAndSupport.name
- );
-
- this.setConfigInput(
- ConfigInput.fromQueryParamsAndSupport(queryParams, {
- supportInput: this.supportInput,
- })
- );
- }
-
- /** @param {KeysInput} keysInput */
- setKeysInput(keysInput) {
- this.keysInput = keysInput;
- }
-
- setKeysInputFromBuiltinAndConfig() {
- invariant(
- this.configInput !== null,
- 'Call %s or %s before calling %s.',
- this.setConfigInput.name,
- this.setConfigInputFromQueryParamsAndSupport.name,
- this.setCommandsInputFromJSONAndConfigKeys.name
- );
-
- this.setKeysInput(KeysInput.fromBuiltinAndConfig({ configInput: this.configInput }));
- }
-
- /** @param {PageUriInput} pageUriInput */
- setPageUriInput(pageUriInput) {
- this.pageUriInput = pageUriInput;
- }
-
- /** @param {string} pageUri */
- setPageUriInputFromPageUri(pageUri) {
- this.setPageUriInput(PageUriInput.fromPageUri(pageUri));
- }
-
- /** @param {ScriptsInput} scriptsInput */
- setScriptsInput(scriptsInput) {
- this.scriptsInput = scriptsInput;
- }
-
- /** @param {SetupScripts} scriptsMap */
- setScriptsInputFromMap(scriptsMap) {
- this.setScriptsInput(ScriptsInput.fromScriptsMap(scriptsMap));
- }
-
- /** @param {SupportInput} supportInput */
- setSupportInput(supportInput) {
- this.supportInput = supportInput;
- }
-
- /** @param {SupportJSON} supportJSON */
- setSupportInputFromJSON(supportJSON) {
- this.setSupportInput(SupportInput.fromJSON(supportJSON));
- }
-
- /** @param {AllCommandsInput} allCommandsInput */
- setAllCommandsInput(allCommandsInput) {
- this.allCommandsInput = allCommandsInput;
- }
-
- /** @param {AllCommandsJSON} allCommandsJSON */
- setAllCommandsInputFromJSON(allCommandsJSON) {
- this.setAllCommandsInput(AllCommandsInput.fromJSON(allCommandsJSON));
- }
-
- /** @param {TitleInput} titleInput */
- setTitleInput(titleInput) {
- this.titleInput = titleInput;
- }
-
- /** @param {string} title */
- setTitleInputFromTitle(title) {
- this.setTitleInput(TitleInput.fromTitle(title));
- }
-
- /** @param {UnexpectedInput} unexpectedInput */
- setUnexpectedInput(unexpectedInput) {
- this.unexpectedInput = unexpectedInput;
- }
-
- setUnexpectedInputFromBuiltin() {
- this.setUnexpectedInput(UnexpectedInput.fromBuiltin());
- }
-
- /** @returns {AriaATTestRun.State} */
- testRunState() {
- invariant(
- this.behaviorInput !== null,
- 'Call %s or %s before calling %s.',
- this.setBehaviorInput.name,
- this.setBehaviorInputFromJSONAndCommandsConfigKeysTitleUnexpected.name,
- this.testRunState.name
- );
- invariant(
- this.configInput !== null,
- 'Call %s or %s before calling %s.',
- this.setConfigInput.name,
- this.setConfigInputFromQueryParamsAndSupport.name,
- this.testRunState.name
- );
-
- const errors = [
- ...this.behaviorInput.errors,
- ...this.commandsInput.errors,
- ...this.configInput.errors,
- ];
- const test = this.behaviorInput.behavior();
- const config = this.configInput;
-
- function unescapeHTML(input) {
- const textarea = document.createElement('textarea');
- textarea.innerHTML = input;
- return textarea.value;
- }
-
- const [atMode, screenText, instructions] = deriveModeWithTextAndInstructions(
- test.mode,
- config.at()
- );
-
- let state = {
- errors,
- info: {
- description: test.description,
- task: test.task,
- mode: screenText || atMode,
- modeInstructions: Array.isArray(instructions)
- ? unescapeHTML(`${instructions[0]} ${instructions[1]}`)
- : test.modeInstructions,
- userInstructions: test.specificUserInstruction.split('|'),
- setupScriptDescription: test.setupScriptDescription,
- },
- config: {
- at: config.at(),
- displaySubmitButton: config.displaySubmitButton(),
- renderResultsAfterSubmit: config.renderResultsAfterSubmit(),
- },
- currentUserAction: UserActionMap.LOAD_PAGE,
- openTest: {
- enabled: true,
- },
- assertionResponseQuestion: test.assertionResponseQuestion,
- commands: test.commands.map(
- command =>
- /** @type {import("./aria-at-test-run.mjs").TestRunCommand} */ ({
- description: command.command,
- commandSettings: {
- command: command.command,
- description: command.settings,
- text: command.settingsText,
- instructions: command.settingsInstructions,
- assertionExceptions: command.assertionExceptions,
- },
- atOutput: {
- highlightRequired: false,
- value: '',
- },
- assertions: test.assertions.map(assertion => ({
- description: assertion.assertion,
- highlightRequired: false,
- priority: assertion.priority,
- result: CommonResultMap.NOT_SET,
- })),
- additionalAssertions: test.additionalAssertions.map(assertion => ({
- description: assertion.assertion,
- highlightRequired: false,
- priority: assertion.priority,
- result: CommonResultMap.NOT_SET,
- })),
- unexpected: {
- highlightRequired: false,
- hasUnexpected: HasUnexpectedBehaviorMap.NOT_SET,
- tabbedBehavior: 0,
- behaviors: test.unexpectedBehaviors.map(({ description }) => ({
- description,
- checked: false,
- impact: UnexpectedBehaviorImpactMap.MODERATE,
- more: { highlightRequired: false, value: '' },
- })),
- },
- })
- ),
- };
-
- if (this.configInput.resultJSON()) {
- state = this.testRunStateFromTestResultJSON(this.configInput.resultJSON(), state);
- }
-
- return state;
- }
-
- testWindowOptions() {
- invariant(
- this.behaviorInput !== null,
- 'Call %s or %s before calling %s.',
- this.setBehaviorInput.name,
- this.setBehaviorInputFromJSONAndCommandsConfigKeysTitleUnexpected.name,
- this.testWindowOptions.name
- );
- invariant(
- this.pageUriInput !== null,
- 'Call %s or %s before calling %s.',
- this.setPageUriInput.name,
- this.setPageUriInputFromPageUri.name,
- this.testWindowOptions.name
- );
- invariant(
- this.scriptsInput !== null,
- 'Call %s or %s before calling %s.',
- this.setScriptsInput.name,
- this.setScriptsInputFromMap.name,
- this.testWindowOptions.name
- );
-
- return {
- pageUri: this.pageUriInput.pageUri(),
- setupScriptName: this.behaviorInput.behavior().setupTestPage,
- scripts: this.scriptsInput.scripts(),
- };
- }
-
- /**
- * @param {AriaATTestRun.State} state
- * @returns {import("./aria-at-harness.mjs").SubmitResultJSON}
- */
- submitResultsJSON(state) {
- invariant(
- this.behaviorInput !== null,
- 'Call %s or %s before calling %s.',
- this.setBehaviorInput.name,
- this.setBehaviorInputFromJSONAndCommandsConfigKeysTitleUnexpected.name,
- this.submitResultsJSON.name
- );
-
- const behavior = this.behaviorInput.behavior();
-
- /** @type {SubmitResultDetailsJSON} */
- const details = {
- name: state.info.description,
- task: state.info.task,
- specific_user_instruction: behavior.specificUserInstruction,
- summary: {
- 1: {
- pass: countAssertions(
- ({ priority, result }) => priority === 1 && result === CommonResultMap.PASS
- ),
- fail: countAssertions(
- ({ priority, result }) => priority === 1 && result !== CommonResultMap.PASS
- ),
- },
- 2: {
- pass: countAssertions(
- ({ priority, result }) => priority === 2 && result === CommonResultMap.PASS
- ),
- fail: countAssertions(
- ({ priority, result }) => priority === 2 && result !== CommonResultMap.PASS
- ),
- },
- unexpectedCount: countUnexpectedBehaviors(({ checked }) => checked),
- },
- commands: state.commands.map(command => ({
- command: command.description,
- output: command.atOutput.value,
- support: commandSupport(command),
- assertions: [...command.assertions, ...command.additionalAssertions].map(
- assertionToAssertion
- ),
- unexpected_behaviors: command.unexpected.behaviors
- .filter(({ checked }) => checked)
- .map(({ description, more }) => (more ? more.value : description)),
- })),
- };
-
- /** @type {SubmitResultStatusJSON} */
- const status = state.commands
- .map(commandSupport)
- .some(support => support === CommandSupportJSONMap.FAILING)
- ? StatusJSONMap.FAIL
- : StatusJSONMap.PASS;
-
- return {
- test: state.info.description,
- details,
- status,
- };
-
- function commandSupport(command) {
- const allAssertions = [...command.assertions, ...command.additionalAssertions];
- return allAssertions.some(
- ({ priority, result }) => priority === 1 && result !== CommonResultMap.PASS
- ) || command.unexpected.behaviors.some(({ checked }) => checked)
- ? CommandSupportJSONMap.FAILING
- : allAssertions.some(
- ({ priority, result }) => priority === 2 && result !== CommonResultMap.PASS
- )
- ? CommandSupportJSONMap.ALL_REQUIRED
- : CommandSupportJSONMap.FULL;
- }
-
- /**
- * @param {(assertion: TestRunAssertion | TestRunAdditionalAssertion) => boolean} filter
- * @returns {number}
- */
- function countAssertions(filter) {
- return state.commands.reduce(
- (carry, command) =>
- carry + [...command.assertions, ...command.additionalAssertions].filter(filter).length,
- 0
- );
- }
-
- /**
- * @param {(behavior: TestRunUnexpected) => boolean} filter
- * @returns {number}
- */
- function countUnexpectedBehaviors(filter) {
- return state.commands.reduce(
- (carry, command) => carry + command.unexpected.behaviors.filter(filter).length,
- 0
- );
- }
-
- /**
- * @param {TestRunAssertion | TestRunAdditionalAssertion} assertion
- * @returns {SubmitResultAssertionsJSON}
- */
- function assertionToAssertion(assertion) {
- return assertion.result === CommonResultMap.PASS
- ? {
- assertion: assertion.description,
- priority: assertion.priority.toString(),
- pass: AssertionPassJSONMap.GOOD_OUTPUT,
- }
- : {
- assertion: assertion.description,
- priority: assertion.priority.toString(),
- fail:
- assertion.result === AssertionResultMap.FAIL_MISSING
- ? AssertionFailJSONMap.NO_OUTPUT
- : assertion.result === AssertionResultMap.FAIL_INCORRECT
- ? AssertionFailJSONMap.INCORRECT_OUTPUT
- : AssertionFailJSONMap.NO_SUPPORT,
- };
- }
- }
-
- /**
- * Transform a test run state into a test result json for serialization.
- * @param {AriaATTestRun.State} state
- * @returns {AriaATTestResult.JSON}
- */
- testResultJSON(state) {
- return {
- test: {
- title: state.info.description,
- at: {
- id: state.config.at.key,
- },
- atMode: state.info.mode,
- },
- scenarioResults: state.commands.map(command => ({
- scenario: {
- command: {
- id: command.description,
- },
- },
- output: command.atOutput.value,
- assertionResults: command.assertions.map(assertion => ({
- assertion: {
- priority: assertion.priority === 1 ? 'MUST' : 'SHOULD',
- text: assertion.description,
- },
- passed: assertion.result === 'pass',
- failedReason:
- assertion.result === 'failIncorrect'
- ? 'INCORRECT_OUTPUT'
- : assertion.result === 'failMissing'
- ? 'NO_OUTPUT'
- : null,
- })),
- unexpectedBehaviors: command.unexpected.behaviors
- .map(behavior =>
- behavior.checked
- ? {
- text: behavior.description,
- impact: behavior.impact,
- details: behavior.more.value,
- }
- : null
- )
- .filter(Boolean),
- })),
- };
- }
-
- /**
- * @param {AriaATTestRun.State} state
- * @returns {SubmitResultJSON | AriaATTestResult.JSON}
- */
- resultJSON(state) {
- // If ConfigInput is available and resultFormat is TestResultJSON return result in that format.
- if (this.configInput !== null) {
- const resultFormat = this.configInput.resultFormat();
- if (resultFormat === 'TestResultJSON') {
- return this.testResultJSON(state);
- }
- }
-
- return this.submitResultsJSON(state);
- }
-
- /**
- * Set a default or given test run state with the recorded results json. Intermediate state not stored into
- * testResult, like highlightRequired, is to the default.
- * @param {AriaATTestResult.JSON} testResult
- * @param {AriaATTestRun.State} [state]
- * @returns {AriaATTestRun.State}
- */
- testRunStateFromTestResultJSON(testResult, state = this.testRunState()) {
- return {
- ...state,
- commands: state.commands.map((command, commandIndex) => {
- const scenarioResult = testResult.scenarioResults[commandIndex];
- return {
- ...command,
- atOutput: { highlightRequired: false, value: scenarioResult.output },
- assertions: command.assertions.map((assertion, assertionIndex) => {
- const assertionResult = scenarioResult.assertionResults[assertionIndex];
- return {
- ...assertion,
- highlightRequired: false,
- result: assertionResult.passed
- ? 'pass'
- : assertionResult.failedReason === 'INCORRECT_OUTPUT'
- ? 'failIncorrect'
- : assertionResult.failedReason === 'NO_OUTPUT'
- ? 'failMissing'
- : 'fail',
- };
- }),
- unexpected: {
- ...command.unexpected,
- highlightRequired: false,
- hasUnexpected:
- scenarioResult.unexpectedBehaviors.length > 0
- ? 'hasUnexpected'
- : 'doesNotHaveUnexpected',
- tabbedBehavior: 0,
- behaviors: command.unexpected.behaviors.map(behavior => {
- const behaviorResult = scenarioResult.unexpectedBehaviors.find(
- unexpectedResult => unexpectedResult.text === behavior.description
- );
- return {
- ...behavior,
- checked: behaviorResult ? true : false,
- more: behavior.more
- ? {
- highlightRequired: false,
- impact: behaviorResult
- ? behavior.impact
- : UnexpectedBehaviorImpactMap.MODERATE,
- value: behaviorResult ? behaviorResult.details : '',
- }
- : behavior.more,
- };
- }),
- },
- };
- }),
- };
- }
-}
-
-/**
- * Extended TestRun that can access methods to turn the TestRun["state"] into
- * the desired output format.
- */
-export class TestRunExport extends TestRun {
- /**
- * @param {TestRunOptions & TestRunExportOptions} options
- */
- constructor({ resultsJSON, ...parentOptions }) {
- super(parentOptions);
-
- this.resultsJSON = resultsJSON;
- }
-
- testPageAndResults() {
- const testPage = this.testPage();
- if ('results' in testPage) {
- return {
- ...testPage,
- resultsJSON: this.resultsJSON(this.state),
- };
- }
- return {
- ...testPage,
- resultsJSON:
- this.state.currentUserAction === UserActionMap.CLOSE_TEST_WINDOW
- ? this.resultsJSON(this.state)
- : null,
- };
- }
-}
-
-/**
- * @typedef SubmitResultDetailsCommandsAssertionsPass
- * @property {string} assertion
- * @property {string} priority
- * @property {AssertionPassJSON} pass
- */
-
-/**
- * Passing assertion values submitted from the tester result form.
- *
- * In the submitted json object the values contain spaces and are title cased.
- * @typedef {EnumValues} AssertionPassJSON
- */
-
-const AssertionPassJSONMap = createEnumMap({
- GOOD_OUTPUT: 'Good Output',
- PASS: 'Pass',
-});
-
-/**
- * @typedef SubmitResultDetailsCommandsAssertionsFail
- * @property {string} assertion
- * @property {string} priority
- * @property {AssertionFailJSON} fail
- */
-
-/**
- * Failing assertion values from the tester result form as are submitted in the
- * JSON result object.
- *
- * In the submitted json object the values contain spaces and are title cased.
- * @typedef {EnumValues} AssertionFailJSON
- */
-
-const AssertionFailJSONMap = createEnumMap({
- NO_OUTPUT: 'No Output',
- INCORRECT_OUTPUT: 'Incorrect Output',
- NO_SUPPORT: 'No Support',
- FAIL: 'Fail',
-});
-
-const UnexpectedBehaviorImpactMap = createEnumMap({
- MODERATE: 'Moderate',
- SEVERE: 'Severe',
-});
-
-/** @typedef {SubmitResultDetailsCommandsAssertionsPass | SubmitResultDetailsCommandsAssertionsFail} SubmitResultAssertionsJSON */
-
-/**
- * Command result derived from priority 1 and 2 assertions.
- *
- * Support is "FAILING" is priority 1 assertions fail. Support is "ALL REQUIRED"
- * if priority 2 assertions fail.
- *
- * In the submitted json object values may contain spaces and are in ALL CAPS.
- *
- * @typedef {EnumValues} CommandSupportJSON
- */
-
-const CommandSupportJSONMap = createEnumMap({
- FULL: 'FULL',
- FAILING: 'FAILING',
- ALL_REQUIRED: 'ALL REQUIRED',
-});
-
-/**
- * Highest level status submitted from test result.
- *
- * In the submitted json object values are in ALL CAPS.
- *
- * @typedef {EnumValues} SubmitResultStatusJSON
- */
-
-const StatusJSONMap = createEnumMap({
- PASS: 'PASS',
- FAIL: 'FAIL',
-});
-
-/**
- *
- * @param {ATMode} mode
- * @param {ATJSON} at
- * @returns {[ATMode, string, [string]]}
- */
-function deriveModeWithTextAndInstructions(mode, at) {
- let atMode = mode;
- let screenText = '';
- let instructions = [];
-
- if (mode.includes('_')) {
- const atModes = mode.split('_');
- for (const _atMode of atModes) {
- if (at.settings[_atMode]) {
- atMode = _atMode;
- screenText = at.settings[_atMode].screenText;
- instructions = at.settings[_atMode].instructions;
- }
- }
- } else {
- if (at.settings && at.settings[atMode]) {
- screenText = at.settings[atMode]?.screenText;
- instructions = at.settings[atMode]?.instructions;
- }
- }
-
- return [atMode, screenText, instructions];
-}
-
-/**
- * @param {boolean} test
- * @param {string} message
- * @param {any[]} args
- * @returns {asserts test}
- */
-function invariant(test, message, ...args) {
- if (!test) {
- let index = 0;
- throw new Error(message.replace(/%%|%\w/g, match => (match[0] !== '%%' ? args[index++] : '%')));
- }
-}
-
-/** @typedef {ConstructorParameters[0]} TestRunOptions */
-/**
- * @typedef TestRunExportOptions
- * @property {(state: AriaATTestRun.State) => SubmitResultJSON} resultsJSON
- */
-
-/**
- * @typedef ATJSON
- * @property {string} name
- * @property {string} key
- * @property {string} defaultConfigurationInstructionsHTML
- * @property {object} settings
- */
-
-/**
- * @typedef SupportJSON
- * @property {ATJSON[]} ats
- * @property {object} applies_to
- * @property {object[]} examples
- * @property {string} examples[].directory
- * @property {string} examples[].name
- */
-
-/**
- * @typedef AllCommandsJSON
- * @property {object} modifiers
- * @property {object} modifierAliases
- * @property {object} keys
- * @property {object} keyAliases
- */
-
-/**
- * @typedef {([string] | [string, string])[]} CommandATJSON
- */
-
-/**
- * @typedef {{[atMode: string]: CommandATJSON}} CommandModeJSON
- */
-
-/**
- * @typedef CommandJSON
- * @property {CommandModeJSON} [reading]
- * @property {CommandModeJSON} [interaction]
- */
-
-/**
- * @typedef {{[commandDescription: string]: CommandJSON}} CommandsJSON
- */
-
-/**
- * @typedef {["at" | "showSubmitButton" | "showResults" | string, string][]} ConfigQueryParams
- */
-
-/** @typedef {"reading" | "interaction" | "virtualCursor", "pcCursor", "browseMode" | "focusMode" | "quickNavOn" | "quickNavOff" | "defaultMode"} ATMode */
-
-/** @typedef OutputAssertion
- * @property {string} assertionId
- * @property {Number} priority
- * @property {string} assertionStatement
- * @property {string} assertionPhrase
- * @property {string} refIds
- */
-
-/**
- * @typedef BehaviorJSON
- * @property {string} setup_script_description
- * @property {string} setupTestPage
- * @property {string[]} applies_to
- * @property {ATMode | ATMode[]} mode
- * @property {string} task
- * @property {string} specific_user_instruction
- * @property {[string, string][] | [OutputAssertion]} [output_assertions]
- * @property {{[atKey: string]: [number, string][]}} [additional_assertions]
- */
-
-/**
- * @typedef BehaviorAssertion
- * @property {number} priority
- * @property {string} assertion
- */
-
-/**
- * @typedef BehaviorUnexpectedItem
- * @property {string} description
- */
-
-/**
- * @typedef Behavior
- * @property {string} description
- * @property {string} task
- * @property {ATMode} mode
- * @property {string} modeInstructions
- * @property {string[]} appliesTo
- * @property {string} specificUserInstruction
- * @property {string} setupScriptDescription
- * @property {string} setupTestPage
- * @property {string[]} commands
- * @property {BehaviorAssertion[]} assertions
- * @property {BehaviorAssertion[]} additionalAssertions
- * @property {BehaviorUnexpectedItem[]} unexpectedBehaviors
- */
-
-/** @typedef {{[key: string]: (document: Document) => void}} SetupScripts */
-
-/**
- * @typedef SubmitResultJSON
- * @property {string} test
- * @property {SubmitResultDetailsJSON} details
- * @property {SubmitResultStatusJSON} status
- */
-
-/**
- * @typedef SubmitResultSummaryPriorityJSON
- * @property {number} pass
- * @property {number} fail
- */
-
-/**
- * @typedef {{[key in "1" | "2"]: SubmitResultSummaryPriorityJSON}} SubmitResultSummaryPriorityMapJSON
- */
-
-/**
- * @typedef SubmitResultSummaryPropsJSON
- * @property {number} unexpectedCount
- */
-
-/**
- * @typedef {SubmitResultSummaryPriorityMapJSON & SubmitResultSummaryPropsJSON} SubmitResultSummaryJSON
- */
-
-/**
- * @typedef SubmitResultDetailsJSON
- * @property {string} name
- * @property {string} specific_user_instruction
- * @property {string} task
- * @property {object[]} commands
- * @property {string} commands[].command
- * @property {string} commands[].output
- * @property {string[]} commands[].unexpected_behaviors
- * @property {CommandSupportJSON} commands[].support
- * @property {SubmitResultAssertionsJSON[]} commands[].assertions
- * @property {SubmitResultSummaryJSON} summary
- */
-
-/**
- * @typedef ResultJSONDocument
- * @property {SubmitResultJSON | null} resultsJSON
- */
-
-/**
- * @typedef {TestPageDocument & ResultJSONDocument} TestPageAndResultsDocument
- */
-
-/**
- * @typedef {import('./aria-at-test-run.mjs').EnumValues} EnumValues
- * @template T
- */
-
-/** @typedef {import('./aria-at-test-run.mjs').TestRunAssertion} TestRunAssertion */
-/** @typedef {import('./aria-at-test-run.mjs').TestRunAdditionalAssertion} TestRunAdditionalAssertion */
-/** @typedef {import('./aria-at-test-run.mjs').TestRunCommand} TestRunCommand */
-/** @typedef {import("./aria-at-test-run.mjs").TestRunUnexpectedBehavior} TestRunUnexpected */
-
-/** @typedef {import('./aria-at-test-run.mjs').TestPageDocument} TestPageDocument */
diff --git a/client/resources/aria-at-test-run.mjs b/client/resources/aria-at-test-run.mjs
deleted file mode 100644
index c45a3e179..000000000
--- a/client/resources/aria-at-test-run.mjs
+++ /dev/null
@@ -1,1336 +0,0 @@
-export class TestRun {
- /**
- * @param {object} param0
- * @param {Partial} [param0.hooks]
- * @param {TestRunState} param0.state
- */
- constructor({ hooks, state }) {
- /** @type {TestRunState} */
- this.state = state;
-
- const bindDispatch = transform => arg => this.dispatch(transform(arg));
- /** @type {TestRunHooks} */
- this.hooks = {
- closeTestPage: bindDispatch(userCloseWindow),
- focusCommandUnexpectedBehavior: bindDispatch(userFocusCommandUnexpectedBehavior),
- openTestPage: bindDispatch(userOpenWindow),
- postResults: () => {},
- setCommandAdditionalAssertion: bindDispatch(userChangeCommandAdditionalAssertion),
- setCommandAssertion: bindDispatch(userChangeCommandAssertion),
- setCommandHasUnexpectedBehavior: bindDispatch(userChangeCommandHasUnexpectedBehavior),
- setCommandUnexpectedBehavior: bindDispatch(userChangeCommandUnexpectedBehavior),
- setCommandUnexpectedBehaviorImpact: bindDispatch(userChangeCommandUnexpectedBehaviorImpact),
- setCommandUnexpectedBehaviorMore: bindDispatch(userChangeCommandUnexpectedBehaviorMore),
- setCommandOutput: bindDispatch(userChangeCommandOutput),
- submit: () => submitResult(this),
- ...hooks,
- };
-
- this.observers = [];
-
- this.dispatch = this.dispatch.bind(this);
- }
-
- /**
- * @param {(state: TestRunState) => TestRunState} updateMethod
- */
- dispatch(updateMethod) {
- this.state = updateMethod(this.state);
- this.observers.forEach(subscriber => subscriber(this));
- }
-
- /**
- * @param {(app: TestRun) => void} subscriber
- * @returns {() => void}
- */
- observe(subscriber) {
- this.observers.push(subscriber);
- return () => {
- const index = this.observers.indexOf(subscriber);
- if (index > -1) {
- this.observers.splice(index, 1);
- }
- };
- }
-
- testPage() {
- return testPageDocument(this.state, this.hooks);
- }
-
- instructions() {
- return instructionDocument(this.state, this.hooks);
- }
-
- resultsTable() {
- return resultsTableDocument(this.state);
- }
-}
-
-/**
- * @param {U} map
- * @returns {Readonly}
- * @template {string} T
- * @template {{[key: string]: T}} U
- */
-export function createEnumMap(map) {
- return Object.freeze(map);
-}
-
-export const WhitespaceStyleMap = createEnumMap({
- LINE_BREAK: 'lineBreak',
-});
-
-function bind(fn, ...args) {
- return (...moreArgs) => fn(...args, ...moreArgs);
-}
-
-/**
- * @param {TestRunState} resultState
- * @param {TestRunHooks} hooks
- * @returns {InstructionDocument}
- */
-export function instructionDocument(resultState, hooks) {
- const mode = resultState.info.mode;
- const modeInstructions = resultState.info.modeInstructions;
- const userInstructions = resultState.info.userInstructions;
- const lastInstruction = userInstructions[userInstructions.length - 1];
- const setupScriptDescription = resultState.info.setupScriptDescription
- ? ` and runs a script that ${resultState.info.setupScriptDescription}.`
- : resultState.info.setupScriptDescription;
- // As a hack, special case mode instructions for VoiceOver for macOS until we
- // support modeless tests.
- const modePhrase =
- resultState.config.at.name === 'VoiceOver for macOS'
- ? 'Describe '
- : `With ${resultState.config.at.name} in ${mode} mode, describe `;
-
- // TODO: Wrap each command token in
- const commands = resultState.commands.map(({ description }) => description);
- const commandSettings = resultState.commands.map(({ commandSettings }) => commandSettings);
- const assertions = resultState.commands[0].assertions.map(({ description }) => description);
- const additionalAssertions = resultState.commands[0].additionalAssertions.map(
- ({ description }) => description
- );
-
- let firstRequired = true;
- function focusFirstRequired() {
- if (firstRequired) {
- firstRequired = false;
- return true;
- }
- return false;
- }
-
- function convertModeInstructionsToKbdArray(inputString) {
- const container = document.createElement('div');
- container.innerHTML = inputString;
-
- const resultArray = [];
- for (const node of container.childNodes) {
- if (node.nodeName === 'KBD') {
- // Handle elements
- resultArray.push({ kbd: node.innerText.trim() });
- } else {
- // Handle text nodes
- resultArray.push(node.textContent);
- }
- }
-
- return resultArray.length ? resultArray : null;
- }
-
- const convertedModeInstructions =
- modeInstructions !== undefined && !modeInstructions.includes('undefined')
- ? convertModeInstructionsToKbdArray(modeInstructions)
- : null;
-
- let strongInstructions = [...userInstructions];
- if (convertedModeInstructions)
- strongInstructions = [convertedModeInstructions, ...strongInstructions];
-
- return {
- errors: {
- visible: resultState.errors && resultState.errors.length > 0 ? true : false,
- header: 'Test cannot be performed due to error(s)!',
- errors: resultState.errors || [],
- },
- instructions: {
- header: {
- header: `Testing task: ${resultState.info.description}`,
- focus: resultState.currentUserAction === UserActionMap.LOAD_PAGE,
- },
- description: `${modePhrase} how ${resultState.config.at.name} behaves when performing task "${lastInstruction}"`,
- instructions: {
- header: 'Test instructions',
- instructions: [
- [
- `Restore default settings for ${resultState.config.at.name}. For help, read `,
- {
- href: 'https://github.com/w3c/aria-at/wiki/Configuring-Screen-Readers-for-Testing',
- description: 'Configuring Screen Readers for Testing',
- },
- `.`,
- ],
- `Activate the "Open test page" button below, which opens the example to test in a new window${setupScriptDescription}`,
- ],
- strongInstructions: strongInstructions.filter(el => el),
- commands: {
- description: `Using the following commands, ${lastInstruction}`,
- commands: commands.map((command, index) => {
- const { description: settings, text: settingsText } = commandSettings[index];
- return `${command}${
- settingsText && settings !== 'defaultMode' ? ` (${settingsText})` : ''
- }`;
- }),
- },
- },
- assertions: {
- header: 'Success Criteria',
- description: `To pass this test, ${resultState.config.at.name} needs to meet all the following assertions when each specified command is executed:`,
- assertions,
- },
- openTestPage: {
- button: 'Open Test Page',
- enabled: resultState.openTest.enabled,
- click: hooks.openTestPage,
- },
- },
- results: {
- header: {
- header: 'Record Results',
- description: `${resultState.info.description}`,
- },
- commands: commands.map(commandResult),
- },
- submit: resultState.config.displaySubmitButton
- ? {
- button: 'Submit Results',
- click: hooks.submit,
- }
- : null,
- };
-
- /**
- * @param {string} command
- * @param {number} commandIndex
- * @returns {InstructionDocumentResultsCommand}
- */
- function commandResult(command, commandIndex) {
- const resultStateCommand = resultState.commands[commandIndex];
- const resultUnexpectedBehavior = resultStateCommand.unexpected;
-
- const {
- commandSettings: { description: settings, text: settingsText, assertionExceptions },
- } = resultStateCommand;
-
- return {
- header: `After '${command}'${
- settingsText && settings !== 'defaultMode' ? ` (${settingsText})` : ''
- }`,
- atOutput: {
- description: [
- `${resultState.config.at.name} output after ${command}`,
- {
- required: true,
- highlightRequired: resultStateCommand.atOutput.highlightRequired,
- description: '(required)',
- },
- ],
- value: resultStateCommand.atOutput.value,
- focus:
- resultState.currentUserAction === 'validateResults' &&
- resultStateCommand.atOutput.highlightRequired &&
- focusFirstRequired(),
- change: atOutput => hooks.setCommandOutput({ commandIndex, atOutput }),
- },
- assertionsHeader: {
- descriptionHeader: `${resultState.assertionResponseQuestion} ${command}${
- settingsText && settings !== 'defaultMode' ? ` (${settingsText})` : ''
- }?`,
- },
- assertions: [
- ...assertions
- // Ignore assertion if level 0 priority exception found for assertion's command
- .filter((each, index) => (assertionExceptions ? assertionExceptions[index] !== 0 : each))
- .map(each =>
- assertionResult(
- commandIndex,
- each,
- assertions.findIndex(e => e === each)
- )
- ),
- ...additionalAssertions.map(bind(additionalAssertionResult, commandIndex)),
- ],
- unexpectedBehaviors: {
- description: [
- 'Were there additional undesirable behaviors?',
- {
- required: true,
- highlightRequired: resultStateCommand.unexpected.highlightRequired,
- description: '(required)',
- },
- ],
- passChoice: {
- label: 'No, there were no additional undesirable behaviors.',
- checked:
- resultUnexpectedBehavior.hasUnexpected ===
- HasUnexpectedBehaviorMap.DOES_NOT_HAVE_UNEXPECTED,
- focus:
- resultState.currentUserAction === 'validateResults' &&
- resultUnexpectedBehavior.highlightRequired &&
- resultUnexpectedBehavior.hasUnexpected === HasUnexpectedBehaviorMap.NOT_SET &&
- focusFirstRequired(),
- click: () =>
- hooks.setCommandHasUnexpectedBehavior({
- commandIndex,
- hasUnexpected: HasUnexpectedBehaviorMap.DOES_NOT_HAVE_UNEXPECTED,
- }),
- },
- failChoice: {
- label: 'Yes, there were additional undesirable behaviors',
- checked:
- resultUnexpectedBehavior.hasUnexpected === HasUnexpectedBehaviorMap.HAS_UNEXPECTED,
- focus:
- resultState.currentUserAction === 'validateResults' &&
- resultUnexpectedBehavior.highlightRequired &&
- resultUnexpectedBehavior.hasUnexpected === HasUnexpectedBehaviorMap.NOT_SET &&
- focusFirstRequired(),
- click: () =>
- hooks.setCommandHasUnexpectedBehavior({
- commandIndex,
- hasUnexpected: HasUnexpectedBehaviorMap.HAS_UNEXPECTED,
- }),
- options: {
- header: 'Undesirable behaviors',
- options: resultUnexpectedBehavior.behaviors.map((behavior, unexpectedIndex) => {
- return {
- description: behavior.description,
- impact: behavior.impact,
- enabled:
- resultUnexpectedBehavior.hasUnexpected ===
- HasUnexpectedBehaviorMap.HAS_UNEXPECTED,
- tabbable: resultUnexpectedBehavior.tabbedBehavior === unexpectedIndex,
- checked: behavior.checked,
- focus:
- typeof resultState.currentUserAction === 'object' &&
- resultState.currentUserAction.action === UserObjectActionMap.FOCUS_UNDESIRABLE
- ? resultState.currentUserAction.commandIndex === commandIndex &&
- resultUnexpectedBehavior.tabbedBehavior === unexpectedIndex
- : resultState.currentUserAction === UserActionMap.VALIDATE_RESULTS &&
- resultUnexpectedBehavior.hasUnexpected ===
- HasUnexpectedBehaviorMap.HAS_UNEXPECTED &&
- resultUnexpectedBehavior.behaviors.every(({ checked }) => !checked) &&
- focusFirstRequired(),
- change: checked =>
- hooks.setCommandUnexpectedBehavior({ commandIndex, unexpectedIndex, checked }),
- impactchange: impact =>
- hooks.setCommandUnexpectedBehaviorImpact({
- commandIndex,
- unexpectedIndex,
- impact,
- }),
- keydown: key => {
- const increment = keyToFocusIncrement(key);
- if (increment) {
- hooks.focusCommandUnexpectedBehavior({
- commandIndex,
- unexpectedIndex,
- increment,
- });
- return true;
- }
- return false;
- },
- more: {
- description: /** @type {Description[]} */ ([
- `Details:`,
- {
- required: true,
- highlightRequired: behavior.more.highlightRequired,
- description: '(required)',
- },
- ]),
- enabled: behavior.checked,
- value: behavior.more.value,
- focus:
- resultState.currentUserAction === 'validateResults' &&
- behavior.more.highlightRequired &&
- focusFirstRequired(),
- change: value =>
- hooks.setCommandUnexpectedBehaviorMore({
- commandIndex,
- unexpectedIndex,
- more: value,
- }),
- },
- };
- }),
- },
- },
- },
- };
- }
-
- /**
- * @param {number} commandIndex
- * @param {string} assertion
- * @param {number} assertionIndex
- */
- function assertionResult(commandIndex, assertion, assertionIndex) {
- const resultAssertion = resultState.commands[commandIndex].assertions[assertionIndex];
- return /** @type {InstructionDocumentResultsCommandsAssertion} */ ({
- description: [assertion],
- passed: resultAssertion.result === AssertionResultMap.PASS,
- click: () =>
- hooks.setCommandAssertion({
- commandIndex,
- assertionIndex,
- result:
- resultAssertion.result === AssertionResultMap.PASS
- ? AssertionResultMap.FAIL
- : AssertionResultMap.PASS,
- }),
- });
- }
-
- /**
- * @param {number} commandIndex
- * @param {string} assertion
- * @param {number} assertionIndex
- */
- function additionalAssertionResult(commandIndex, assertion, assertionIndex) {
- const resultAdditionalAssertion =
- resultState.commands[commandIndex].additionalAssertions[assertionIndex];
- return /** @type {InstructionDocumentResultsCommandsAssertion} */ ({
- description: [assertion],
- passed: resultAdditionalAssertion.result === CommonResultMap.PASS,
- click: () =>
- hooks.setCommandAssertion({
- commandIndex,
- assertionIndex,
- result:
- resultAdditionalAssertion.result === AssertionResultMap.PASS
- ? AssertionResultMap.FAIL
- : AssertionResultMap.PASS,
- }),
- });
- }
-}
-
-/**
- * @typedef {typeof UserActionMap[keyof typeof UserActionMap]} UserAction
- */
-
-export const UserActionMap = createEnumMap({
- LOAD_PAGE: 'loadPage',
- OPEN_TEST_WINDOW: 'openTestWindow',
- CLOSE_TEST_WINDOW: 'closeTestWindow',
- VALIDATE_RESULTS: 'validateResults',
- CHANGE_TEXT: 'changeText',
- CHANGE_SELECTION: 'changeSelection',
- SHOW_RESULTS: 'showResults',
-});
-
-/**
- * @typedef {typeof UserObjectActionMap[keyof typeof UserObjectActionMap]} UserObjectAction
- */
-
-export const UserObjectActionMap = createEnumMap({
- FOCUS_UNDESIRABLE: 'focusUndesirable',
-});
-
-/**
- * @typedef {UserAction | UserActionFocusUnexpected} TestRunUserAction
- */
-
-/**
- * @typedef {EnumValues} HasUnexpectedBehavior
- */
-
-export const HasUnexpectedBehaviorMap = createEnumMap({
- NOT_SET: 'notSet',
- HAS_UNEXPECTED: 'hasUnexpected',
- DOES_NOT_HAVE_UNEXPECTED: 'doesNotHaveUnexpected',
-});
-
-export const CommonResultMap = createEnumMap({
- NOT_SET: 'notSet',
- PASS: 'pass',
-});
-
-/**
- * @typedef {EnumValues} AdditionalAssertionResult
- */
-
-export const AdditionalAssertionResultMap = createEnumMap({
- ...CommonResultMap,
- FAIL_SUPPORT: 'failSupport',
-});
-
-/**
- * @typedef {EnumValues} AssertionResult
- */
-
-export const AssertionResultMap = createEnumMap({
- ...CommonResultMap,
- FAIL_MISSING: 'failMissing',
- FAIL_INCORRECT: 'failIncorrect',
- FAIL: 'fail',
-});
-
-/**
- * @typedef {EnumValues} UnexpectedBehaviorImpact
- */
-
-export const UnexpectedBehaviorImpactMap = createEnumMap({
- MODERATE: 'Moderate',
- SEVERE: 'Severe',
-});
-
-/**
- * @param {object} props
- * @param {number} props.commandIndex
- * @param {string} props.atOutput
- * @returns {(state: TestRunState) => TestRunState}
- */
-export function userChangeCommandOutput({ commandIndex, atOutput }) {
- return function (state) {
- return {
- ...state,
- currentUserAction: UserActionMap.CHANGE_TEXT,
- commands: state.commands.map((commandState, index) =>
- index !== commandIndex
- ? commandState
- : {
- ...commandState,
- atOutput: {
- ...commandState.atOutput,
- value: atOutput,
- },
- }
- ),
- };
- };
-}
-
-/**
- * @param {object} props
- * @param {number} props.commandIndex
- * @param {number} props.assertionIndex
- * @param {AssertionResult} props.result
- * @returns {(state: TestRunState) => TestRunState}
- */
-export function userChangeCommandAssertion({ commandIndex, assertionIndex, result }) {
- return function (state) {
- return {
- ...state,
- currentUserAction: UserActionMap.CHANGE_SELECTION,
- commands: state.commands.map((command, commandI) =>
- commandI !== commandIndex
- ? command
- : {
- ...command,
- assertions: command.assertions.map((assertion, assertionI) =>
- assertionI !== assertionIndex ? assertion : { ...assertion, result }
- ),
- }
- ),
- };
- };
-}
-
-/**
- * @param {object} props
- * @param {number} props.commandIndex
- * @param {number} props.additionalAssertionIndex
- * @param {AdditionalAssertionResult} props.result
- * @returns {(state: TestRunState) => TestRunState}
- */
-export function userChangeCommandAdditionalAssertion({
- commandIndex,
- additionalAssertionIndex,
- result,
-}) {
- return function (state) {
- return {
- ...state,
- currentUserAction: UserActionMap.CHANGE_SELECTION,
- commands: state.commands.map((command, commandI) =>
- commandI !== commandIndex
- ? command
- : {
- ...command,
- additionalAssertions: command.additionalAssertions.map((assertion, assertionI) =>
- assertionI !== additionalAssertionIndex ? assertion : { ...assertion, result }
- ),
- }
- ),
- };
- };
-}
-
-/**
- * @param {object} props
- * @param {number} props.commandIndex
- * @param {HasUnexpectedBehavior} props.hasUnexpected
- * @returns {(state: TestRunState) => TestRunState}
- */
-export function userChangeCommandHasUnexpectedBehavior({ commandIndex, hasUnexpected }) {
- return function (state) {
- return {
- ...state,
- currentUserAction: UserActionMap.CHANGE_SELECTION,
- commands: state.commands.map((command, commandI) =>
- commandI !== commandIndex
- ? command
- : {
- ...command,
- unexpected: {
- ...command.unexpected,
- hasUnexpected: hasUnexpected,
- tabbedBehavior: hasUnexpected === HasUnexpectedBehaviorMap.HAS_UNEXPECTED ? 0 : -1,
- behaviors: command.unexpected.behaviors.map(behavior => ({
- ...behavior,
- checked: false,
- more: behavior.more ? { ...behavior.more, value: '' } : null,
- })),
- },
- }
- ),
- };
- };
-}
-
-/**
- * @param {object} props
- * @param {number} props.commandIndex
- * @param {number} props.unexpectedIndex
- * @param {boolean} props.checked
- * @returns {(state: TestRunState) => TestRunState}
- */
-export function userChangeCommandUnexpectedBehavior({ commandIndex, unexpectedIndex, checked }) {
- return function (state) {
- return {
- ...state,
- currentUserAction: UserActionMap.CHANGE_SELECTION,
- commands: state.commands.map((command, commandI) =>
- commandI !== commandIndex
- ? command
- : {
- ...command,
- unexpected: {
- ...command.unexpected,
- behaviors: command.unexpected.behaviors.map((unexpected, unexpectedI) =>
- unexpectedI !== unexpectedIndex
- ? unexpected
- : {
- ...unexpected,
- checked,
- }
- ),
- },
- }
- ),
- };
- };
-}
-
-/**
- * @param {object} props
- * @param {number} props.commandIndex
- * @param {number} props.unexpectedIndex
- * @param {string} props.impact
- * @returns {(state: TestRunState) => TestRunState}
- */
-export function userChangeCommandUnexpectedBehaviorImpact({
- commandIndex,
- unexpectedIndex,
- impact,
-}) {
- return function (state) {
- return {
- ...state,
- currentUserAction: UserActionMap.CHANGE_TEXT,
- commands: state.commands.map((command, commandI) =>
- commandI !== commandIndex
- ? command
- : /** @type {TestRunCommand} */ ({
- ...command,
- unexpected: {
- ...command.unexpected,
- behaviors: command.unexpected.behaviors.map((unexpected, unexpectedI) =>
- unexpectedI !== unexpectedIndex
- ? unexpected
- : /** @type {TestRunUnexpectedBehavior} */ ({
- ...unexpected,
- impact: impact,
- })
- ),
- },
- })
- ),
- };
- };
-}
-
-/**
- * @param {object} props
- * @param {number} props.commandIndex
- * @param {number} props.unexpectedIndex
- * @param {string} props.more
- * @returns {(state: TestRunState) => TestRunState}
- */
-export function userChangeCommandUnexpectedBehaviorMore({ commandIndex, unexpectedIndex, more }) {
- return function (state) {
- return {
- ...state,
- currentUserAction: UserActionMap.CHANGE_TEXT,
- commands: state.commands.map((command, commandI) =>
- commandI !== commandIndex
- ? command
- : /** @type {TestRunCommand} */ ({
- ...command,
- unexpected: {
- ...command.unexpected,
- behaviors: command.unexpected.behaviors.map((unexpected, unexpectedI) =>
- unexpectedI !== unexpectedIndex
- ? unexpected
- : /** @type {TestRunUnexpectedBehavior} */ ({
- ...unexpected,
- more: {
- ...unexpected.more,
- value: more,
- },
- })
- ),
- },
- })
- ),
- };
- };
-}
-
-/**
- * @param {string} key
- * @returns {TestRunFocusIncrement}
- */
-function keyToFocusIncrement(key) {
- switch (key) {
- case 'Up':
- case 'ArrowUp':
- case 'Left':
- case 'ArrowLeft':
- return 'previous';
-
- case 'Down':
- case 'ArrowDown':
- case 'Right':
- case 'ArrowRight':
- return 'next';
- }
-}
-
-/**
- * @param {TestRunState} state
- * @param {TestRunHooks} hooks
- * @returns {TestPageDocument}
- */
-function testPageDocument(state, hooks) {
- if (state.currentUserAction === UserActionMap.SHOW_RESULTS) {
- return {
- results: resultsTableDocument(state),
- };
- }
- const instructions = instructionDocument(state, hooks);
- return {
- errors: instructions.errors,
- instructions,
- };
-}
-
-/**
- * @param {TestRun} app
- */
-function submitResult(app) {
- app.dispatch(userValidateState());
-
- if (isSomeFieldRequired(app.state)) {
- return;
- }
-
- app.hooks.postResults();
-
- app.hooks.closeTestPage();
-
- if (app.state.config.renderResultsAfterSubmit) {
- app.dispatch(userShowResults());
- }
-}
-
-export function userShowResults() {
- return function (/** @type {TestRunState} */ state) {
- return /** @type {TestRunState} */ ({
- ...state,
- currentUserAction: UserActionMap.SHOW_RESULTS,
- });
- };
-}
-
-/**
- * @param {TestRunState} state
- * @returns
- */
-function isSomeFieldRequired(state) {
- return state.commands.some(
- command =>
- command.atOutput.value.trim() === '' ||
- command.unexpected.hasUnexpected === HasUnexpectedBehaviorMap.NOT_SET ||
- (command.unexpected.hasUnexpected === HasUnexpectedBehaviorMap.HAS_UNEXPECTED &&
- (command.unexpected.behaviors.every(({ checked }) => !checked) ||
- command.unexpected.behaviors.some(
- behavior => behavior.checked && behavior.more && behavior.more.value.trim() === ''
- )))
- );
-}
-
-/**
- * @param {TestRunState} state
- * @returns {ResultsTableDocument}
- */
-function resultsTableDocument(state) {
- return {
- header: state.info.description,
- status: {
- header: [
- 'Test result: ',
- state.commands.some(
- ({
- assertions,
- additionalAssertions,
- unexpected,
- commandSettings: { assertionExceptions },
- }) =>
- [
- // Ignore assertion if level 0 priority exception found for assertion's command
- ...assertions.filter((each, index) =>
- assertionExceptions ? assertionExceptions[index] !== 0 : each
- ),
- ...additionalAssertions,
- ].some(({ priority, result }) => priority === 1 && result !== CommonResultMap.PASS) ||
- unexpected.behaviors.some(({ checked }) => checked)
- )
- ? 'FAIL'
- : 'PASS',
- ],
- },
- table: {
- headers: {
- description: 'Command',
- support: 'Support',
- details: 'Details',
- },
- commands: state.commands.map(command => {
- const {
- commandSettings: { assertionExceptions },
- } = command;
- const allAssertions = [
- // Ignore assertion if level 0 priority exception found for assertion's command
- ...command.assertions.filter((each, index) =>
- assertionExceptions ? assertionExceptions[index] !== 0 : each
- ),
- ...command.additionalAssertions,
- ];
-
- let passingAssertions = ['No passing assertions'];
- let failingAssertions = ['No failing assertions'];
- let unexpectedBehaviors = ['None'];
-
- if (allAssertions.some(({ result }) => result === CommonResultMap.PASS)) {
- passingAssertions = allAssertions
- .filter(({ result }) => result === CommonResultMap.PASS)
- .map(({ description }) => description);
- }
- if (allAssertions.some(({ result }) => result !== CommonResultMap.PASS)) {
- failingAssertions = allAssertions
- .filter(({ result }) => result !== CommonResultMap.PASS)
- .map(({ description }) => description);
- }
- if (command.unexpected.behaviors.some(({ checked }) => checked)) {
- unexpectedBehaviors = command.unexpected.behaviors
- .filter(({ checked }) => checked)
- .map(({ description, more, impact }) => {
- let result = `${description} (`;
- if (more) result = `${result}Details: ${more.value}, `;
- result = `${result}Impact: ${impact})`;
- return result;
- });
- }
-
- return {
- description: command.description,
- support:
- allAssertions.some(
- ({ priority, result }) => priority === 1 && result !== CommonResultMap.PASS
- ) || command.unexpected.behaviors.some(({ checked }) => checked)
- ? 'FAILING'
- : allAssertions.some(
- ({ priority, result }) => priority === 2 && result !== CommonResultMap.PASS
- )
- ? 'ALL_REQUIRED'
- : 'FULL',
- details: {
- output: /** @type {Description} */ [
- 'Output:',
- /** @type {DescriptionWhitespace} */ ({ whitespace: WhitespaceStyleMap.LINE_BREAK }),
- ' ',
- ...command.atOutput.value.split(/(\r\n|\r|\n)/g).map(output =>
- /\r\n|\r|\n/.test(output)
- ? /** @type {DescriptionWhitespace} */ ({
- whitespace: WhitespaceStyleMap.LINE_BREAK,
- })
- : output
- ),
- ],
- passingAssertions: {
- description: 'Passing Assertions:',
- items: passingAssertions,
- },
- failingAssertions: {
- description: 'Failing Assertions:',
- items: failingAssertions,
- },
- unexpectedBehaviors: {
- description: 'Other behaviors that create negative impact:',
- items: unexpectedBehaviors,
- },
- },
- };
- }),
- },
- };
-}
-
-export function userOpenWindow() {
- return (/** @type {TestRunState} */ state) =>
- /** @type {TestRunState} */ ({
- ...state,
- currentUserAction: UserActionMap.OPEN_TEST_WINDOW,
- openTest: { ...state.openTest, enabled: false },
- });
-}
-
-export function userCloseWindow() {
- return (/** @type {TestRunState} */ state) =>
- /** @type {TestRunState} */ ({
- ...state,
- currentUserAction: UserActionMap.CLOSE_TEST_WINDOW,
- openTest: { ...state.openTest, enabled: true },
- });
-}
-
-/**
- * @param {object} props
- * @param {number} props.commandIndex
- * @param {number} props.unexpectedIndex
- * @param {TestRunFocusIncrement} props.increment
- * @returns {(state: TestRunState) => TestRunState}
- */
-export function userFocusCommandUnexpectedBehavior({ commandIndex, unexpectedIndex, increment }) {
- return function (state) {
- const unexpectedLength = state.commands[commandIndex].unexpected.behaviors.length;
- const incrementValue = increment === 'next' ? 1 : -1;
- const newUnexpectedIndex =
- (unexpectedIndex + incrementValue + unexpectedLength) % unexpectedLength;
-
- return {
- ...state,
- currentUserAction: {
- action: UserObjectActionMap.FOCUS_UNDESIRABLE,
- commandIndex,
- unexpectedIndex: newUnexpectedIndex,
- },
- commands: state.commands.map((command, commandI) => {
- const tabbed = command.unexpected.tabbedBehavior;
- const unexpectedLength = command.unexpected.behaviors.length;
- const newTabbed =
- (tabbed + (increment === 'next' ? 1 : -1) + unexpectedLength) % unexpectedLength;
- return commandI !== commandIndex
- ? command
- : {
- ...command,
- unexpected: {
- ...command.unexpected,
- tabbedBehavior: newTabbed,
- },
- };
- }),
- };
- };
-}
-
-/**
- * @returns {(state: TestRunState) => TestRunState}
- */
-export function userValidateState() {
- return function (state) {
- return {
- ...state,
- currentUserAction: UserActionMap.VALIDATE_RESULTS,
- commands: state.commands.map(command => {
- return {
- ...command,
- atOutput: {
- ...command.atOutput,
- highlightRequired: !command.atOutput.value.trim(),
- },
- unexpected: {
- ...command.unexpected,
- highlightRequired:
- command.unexpected.hasUnexpected === HasUnexpectedBehaviorMap.NOT_SET ||
- (command.unexpected.hasUnexpected === HasUnexpectedBehaviorMap.HAS_UNEXPECTED &&
- command.unexpected.behaviors.every(({ checked }) => !checked)),
- behaviors: command.unexpected.behaviors.map(unexpected => {
- return unexpected.more
- ? {
- ...unexpected,
- more: {
- ...unexpected.more,
- highlightRequired: unexpected.checked && !unexpected.more.value.trim(),
- },
- }
- : unexpected;
- }),
- },
- };
- }),
- };
- };
-}
-
-/**
- * @typedef AT
- * @property {string} name
- * @property {string} key
- */
-
-/**
- * @typedef Behavior
- * @property {string} description
- * @property {string} task
- * @property {string} mode
- * @property {string} modeInstructions
- * @property {string[]} appliesTo
- * @property {string} specificUserInstruction
- * @property {string} setupScriptDescription
- * @property {string} setupTestPage
- * @property {string[]} commands
- * @property {[string, string][]} outputAssertions
- * @property {[number, string][]} additionalAssertions
- */
-
-/**
- * @typedef {"previous" | "next"} TestRunFocusIncrement
- */
-
-/**
- * @typedef {(action: (state: TestRunState) => TestRunState) => void} Dispatcher
- */
-
-/**
- * @typedef InstructionDocumentButton
- * @property {Description} button
- * @property {boolean} [enabled]
- * @property {() => void} click
- */
-
-/**
- * @typedef InstructionDocumentAssertionChoiceOptionsOptionsMore
- * @property {Description} description
- * @property {string} value
- * @property {boolean} enabled
- * @property {boolean} [focus]
- * @property {(value: string) => void} change
- */
-
-/**
- * @typedef InstructionDocumentAssertionChoiceOptionsOption
- * @property {Description} description
- * @property {boolean} checked
- * @property {boolean} enabled
- * @property {boolean} tabbable
- * @property {boolean} [focus]
- * @property {(checked: boolean) => void} change
- * @property {(key: string) => boolean} keydown
- * @property {InstructionDocumentAssertionChoiceOptionsOptionsMore} [more]
- */
-
-/**
- * @typedef InstructionDocumentAssertionChoiceOptions
- * @property {Description} header
- * @property {InstructionDocumentAssertionChoiceOptionsOption[]} options
- */
-
-/**
- * @typedef InstructionDocumentAssertionChoice
- * @property {Description} label
- * @property {boolean} checked
- * @property {boolean} [focus]
- * @property {() => void} click
- * @property {InstructionDocumentAssertionChoiceOptions} [options]
- */
-
-/**
- * @typedef DescriptionRich
- * @property {string} [href]
- * @property {boolean} [required]
- * @property {boolean} [highlightRequired]
- * @property {boolean} [offScreen]
- * @property {Description} description
- */
-
-/**
- * @typedef DescriptionWhitespace
- * @property {typeof WhitespaceStyleMap["LINE_BREAK"]} whitespace
- */
-
-/** @typedef {string | DescriptionRich | DescriptionWhitespace | DescriptionArray} Description */
-
-/** @typedef {Description[]} DescriptionArray */
-
-/**
- * @typedef InstructionDocumentResultsCommandsAssertion
- * @property {Description} description
- * @property {Boolean} passed
- * @property {boolean} [focus]
- * @property {() => void} click
- */
-
-/**
- * @typedef InstructionDocumentResultsCommandsAssertionsHeader
- * @property {Description} descriptionHeader
- */
-
-/**
- * @typedef InstructionDocumentResultsCommandsATOutput
- * @property {Description} description
- * @property {string} value
- * @property {boolean} focus
- * @property {(value: string) => void} change
- */
-
-/**
- * @typedef InstructionDocumentResultsCommandsUnexpected
- * @property {Description} description
- * @property {InstructionDocumentAssertionChoice} passChoice
- * @property {InstructionDocumentAssertionChoice} failChoice
- */
-
-/**
- * @typedef InstructionDocumentResultsCommand
- * @property {Description} header
- * @property {InstructionDocumentResultsCommandsATOutput} atOutput
- * @property {InstructionDocumentResultsCommandsAssertionsHeader} assertionsHeader
- * @property {InstructionDocumentResultsCommandsAssertion[]} assertions
- * @property {InstructionDocumentResultsCommandsUnexpected} unexpectedBehaviors
- */
-
-/**
- * @typedef InstructionDocumentResultsHeader
- * @property {Description} header
- * @property {Description} description
- */
-
-/**
- * @typedef InstructionDocumentResults
- * @property {InstructionDocumentResultsHeader} header
- * @property {InstructionDocumentResultsCommand[]} commands
- */
-
-/**
- * @typedef InstructionDocumentInstructionsInstructionsCommands
- * @property {Description} description
- * @property {Description[]} commands
- */
-
-/**
- * @typedef InstructionDocumentInstructionsInstructions
- * @property {Description} header
- * @property {Description[]} instructions
- * @property {Description[]} strongInstructions
- * @property {InstructionDocumentInstructionsInstructionsCommands} commands
- */
-
-/**
- * @typedef InstructionDocumentErrors
- * @property {boolean} visible
- * @property {Description} header
- * @property {Description[]} errors
- */
-
-/**
- * @typedef InstructionDocumentInstructionsHeader
- * @property {Description} header
- * @property {boolean} focus
- */
-
-/**
- * @typedef InstructionDocumentInstructionsAssertions
- * @property {Description} header
- * @property {Description} description
- * @property {Description[]} assertions
- */
-
-/**
- * @typedef InstructionDocumentInstructions
- * @property {InstructionDocumentInstructionsHeader} header
- * @property {Description} description
- * @property {InstructionDocumentInstructionsInstructions} instructions
- * @property {InstructionDocumentInstructionsAssertions} assertions
- * @property {InstructionDocumentButton} openTestPage
- */
-
-/**
- * @typedef InstructionDocument
- * @property {InstructionDocumentErrors} errors
- * @property {InstructionDocumentInstructions} instructions
- * @property {InstructionDocumentResults} results
- * @property {InstructionDocumentButton} submit
- */
-
-/**
- * @typedef TestRunHooks
- * @property {() => void} closeTestPage
- * @property {(options: {commandIndex: number, unexpectedIndex: number, increment: TestRunFocusIncrement}) => void} focusCommandUnexpectedBehavior
- * @property {() => void} openTestPage
- * @property {() => void} postResults
- * @property {(options: {commandIndex: number, additionalAssertionIndex: number, result: AdditionalAssertionResult}) => void} setCommandAdditionalAssertion
- * @property {(options: {commandIndex: number, assertionIndex: number, result: AssertionResult}) => void} setCommandAssertion
- * @property {(options: {commandIndex: number, hasUnexpected: HasUnexpectedBehavior}) => void } setCommandHasUnexpectedBehavior
- * @property {(options: {commandIndex: number, atOutput: string}) => void} setCommandOutput
- * @property {(options: {commandIndex: number, unexpectedIndex: number, checked}) => void } setCommandUnexpectedBehavior
- * @property {(options: {commandIndex: number, unexpectedIndex: number, impact: string}) => void } setCommandUnexpectedBehaviorImpact
- * @property {(options: {commandIndex: number, unexpectedIndex: number, more: string}) => void } setCommandUnexpectedBehaviorMore
- * @property {() => void} submit
- */
-
-/**
- * @typedef UserActionFocusUnexpected
- * @property {typeof UserObjectActionMap["FOCUS_UNDESIRABLE"]} action
- * @property {number} commandIndex
- * @property {number} unexpectedIndex
- */
-
-/**
- * @typedef {T[keyof T]} EnumValues
- * @template {{[key: string]: string}} T
- */
-
-/**
- * @typedef TestRunAssertion
- * @property {string} description
- * @property {boolean} highlightRequired
- * @property {number} priority
- * @property {AssertionResult} result
- */
-
-/**
- * @typedef TestRunAdditionalAssertion
- * @property {string} description
- * @property {boolean} highlightRequired
- * @property {number} priority
- * @property {AdditionalAssertionResult} result
- */
-
-/**
- * @typedef TestRunUnexpectedBehavior
- * @property {string} description
- * @property {boolean} checked
- * @property {object} [more]
- * @property {boolean} more.highlightRequired
- * @property {string} more.value
- * @property {string} impact
- */
-
-/**
- * @typedef TestRunUnexpectedGroup
- * @property {boolean} highlightRequired
- * @property {HasUnexpectedBehavior} hasUnexpected
- * @property {number} tabbedBehavior
- * @property {TestRunUnexpectedBehavior[]} behaviors
- */
-
-/**
- * @typedef TestRunCommand
- * @property {string} description
- * @property {object} atOutput
- * @property {boolean} atOutput.highlightRequired
- * @property {string} atOutput.value
- * @property {TestRunAssertion[]} assertions
- * @property {TestRunAdditionalAssertion[]} additionalAssertions
- * @property {TestRunUnexpectedGroup} unexpected
- */
-
-/**
- * @typedef TestRunState
- * This state contains all the serializable values that are needed to render any
- * of the documents (InstructionDocument, ResultsTableDocument, and
- * TestPageDocument) from this module.
- *
- * @property {string[] | null} errors
- * @property {object} info
- * @property {string} info.description
- * @property {string} info.task
- * @property {ATMode} info.mode
- * @property {string} info.modeInstructions
- * @property {string[]} info.userInstructions
- * @property {string} info.setupScriptDescription
- * @property {object} config
- * @property {AT} config.at
- * @property {boolean} config.renderResultsAfterSubmit
- * @property {boolean} config.displaySubmitButton
- * @property {TestRunUserAction} currentUserAction
- * @property {TestRunCommand[]} commands
- * @property {object} openTest
- * @property {boolean} openTest.enabled
- */
-
-/**
- * @typedef ResultsTableDetailsList
- * @property {Description} description
- * @property {Description[]} items
- */
-
-/**
- * @typedef ResultsTableDocument
- * @property {string} header
- * @property {object} status
- * @property {Description} status.header
- * @property {object} table
- * @property {object} table.headers
- * @property {string} table.headers.description
- * @property {string} table.headers.support
- * @property {string} table.headers.details
- * @property {object[]} table.commands
- * @property {string} table.commands[].description
- * @property {Description} table.commands[].support
- * @property {object} table.commands[].details
- * @property {Description} table.commands[].details.output
- * @property {ResultsTableDetailsList} table.commands[].details.passingAssertions
- * @property {ResultsTableDetailsList} table.commands[].details.failingAssertions
- * @property {ResultsTableDetailsList} table.commands[].details.unexpectedBehaviors
- */
-
-/**
- * @typedef TestPageDocumentResults
- * @property {ResultsTableDocument} results
- */
-
-/**
- * @typedef TestPageDocumentInstructions
- * @property {string[] | null} errors
- * @property {InstructionDocument} instructions
- */
-
-/** @typedef {TestPageDocumentInstructions | TestPageDocumentResults} TestPageDocument */
-
-/** @typedef {"reading" | "interaction"} ATMode */
diff --git a/client/resources/aria-at-test-window.mjs b/client/resources/aria-at-test-window.mjs
deleted file mode 100644
index c60b8c828..000000000
--- a/client/resources/aria-at-test-window.mjs
+++ /dev/null
@@ -1,73 +0,0 @@
-export class TestWindow {
- /**
- * @param {object} options
- * @param {Window | null} [options.window]
- * @param {string} options.pageUri
- * @param {TestWindowHooks} [options.hooks]
- */
- constructor({ window = null, pageUri, hooks }) {
- /** @type {Window | null} */
- this.window = window;
-
- /** @type {string} */
- this.pageUri = pageUri;
-
- /** @type {TestWindowHooks} */
- this.hooks = {
- windowOpened: () => {},
- windowClosed: () => {},
- ...hooks,
- };
- }
-
- open() {
- this.window = window.open(
- this.pageUri,
- '_blank',
- 'toolbar=0,location=0,menubar=0,width=800,height=800'
- );
-
- this.hooks.windowOpened();
-
- this.prepare();
- }
-
- prepare() {
- if (!this.window) {
- return;
- }
-
- if (this.window.closed) {
- this.window = undefined;
- this.hooks.windowClosed();
- return;
- }
-
- if (
- this.window.location.origin !== window.location.origin || // make sure the origin is the same, and prevent this from firing on an 'about' page
- this.window.document.readyState !== 'complete'
- ) {
- window.setTimeout(() => {
- this.prepare();
- }, 100);
- return;
- }
-
- // If the window is closed, re-enable open popup button
- this.window.onunload = () => {
- window.setTimeout(() => this.prepare(), 100);
- };
- }
-
- close() {
- if (this.window) {
- this.window.close();
- }
- }
-}
-
-/**
- * @typedef TestWindowHooks
- * @property {() => void} windowOpened
- * @property {() => void} windowClosed
- */
diff --git a/client/resources/at-commands.mjs b/client/resources/at-commands.mjs
deleted file mode 100644
index 6bbb5b776..000000000
--- a/client/resources/at-commands.mjs
+++ /dev/null
@@ -1,273 +0,0 @@
-/** @deprecated See aria-at-test-io-format.mjs */
-
-import * as keys from './keys.mjs';
-
-/**
- * Class for getting AT-specific instructions for a test against a design pattern.
- * @deprecated See aria-at-test-io-format.mjs:CommandsInput
- */
-export class commandsAPI {
- /**
- * Creates an API to get AT-specific instructions for a design pattern.
- * @param {object} commands - A data structure which is a nested object with the following format:
- * {
- * task: {
- * mode: {
- * at: [
- * key-command (string corresponding to export in keys.mjs),
- * optional additional instructions to list after key command (string),
- * ]
- * }
- * }
- * }
- * @param {object} supportJson - The data object found in `tests/support.json`
- * @param {object} commandsJson - The data object found in `tests/commands.json`
- */
- constructor(commands, supportJson, commandsJson) {
- if (!commands) {
- throw new Error('You must initialize commandsAPI with a commands data object');
- }
-
- if (!supportJson) {
- throw new Error('You must initialize commandsAPI with a supportJson data object');
- }
-
- if (!commandsJson) {
- throw new Error('You must initialize commandsAPI with a commandsJson data object');
- }
-
- this.AT_COMMAND_MAP = commands;
-
- this.MODE_INSTRUCTIONS = {
- reading: {
- jaws: `Verify the Virtual Cursor is active by pressing ${keys.ALT_DELETE}. If it is not, exit Forms Mode to activate the Virtual Cursor by pressing ${keys.ESC}.`,
- nvda: `Ensure NVDA is in browse mode by pressing ${keys.ESC}. Note: This command has no effect if NVDA is already in browse mode.`,
- voiceover_macos: `Toggle Quick Nav ON by pressing the ${keys.LEFT} and ${keys.RIGHT} keys at the same time.`,
- },
- interaction: {
- jaws: `Verify the PC Cursor is active by pressing ${keys.ALT_DELETE}. If it is not, turn off the Virtual Cursor by pressing ${keys.INS_Z}.`,
- nvda: `If NVDA did not make the focus mode sound when the test page loaded, press ${keys.INS_SPACE} to turn focus mode on.`,
- voiceover_macos: `Toggle Quick Nav OFF by pressing the ${keys.LEFT} and ${keys.RIGHT} keys at the same time.`,
- },
- };
-
- this.supportJson = supportJson;
- this.commandsJson = this.flattenObject(commandsJson);
- }
-
- /**
- * Get AT-specific instruction
- * @param {string} mode - The mode of the screen reader, "reading" or "interaction"
- * @param {string} task - The task of the test.
- * @param {object} assistiveTech - The assistive technology.
- * @return {Array} - A list of commands (strings)
- */
- getATCommands(mode, task, assistiveTech) {
- let commands = [];
-
- for (const _atMode of mode.split('_')) {
- if (this.AT_COMMAND_MAP[task][_atMode][assistiveTech.key]) {
- mode = _atMode;
-
- if (!this.AT_COMMAND_MAP[task]) {
- throw new Error(
- `Task "${task}" does not exist, please add to at-commands or correct your spelling.`
- );
- }
-
- if (!this.AT_COMMAND_MAP[task][mode]) {
- throw new Error(
- `Mode "${mode}" instructions for task "${task}" does not exist, please add to at-commands or correct your spelling.`
- );
- }
-
- let commandsData = this.AT_COMMAND_MAP[task][mode][assistiveTech.key] || [];
-
- // V1
- if (mode === 'reading' || mode === 'interaction') {
- for (let c of commandsData) {
- let innerCommands = [];
- let commandSequence = c[0].split(',');
- for (let command of commandSequence) {
- command = keys[command];
- if (typeof command === 'undefined') {
- throw new Error(
- `Key instruction identifier "${c}" for AT "${assistiveTech.name}", mode "${mode}", task "${task}" is not an available identifier. Update your commands.json file to the correct identifier or add your identifier to resources/keys.mjs.`
- );
- }
-
- let furtherInstruction = c[1];
- command = furtherInstruction ? `${command} ${furtherInstruction}` : command;
- innerCommands.push(command);
- }
- commands.push(innerCommands.join(', then '));
- }
- } else {
- // V2
- for (let c of commandsData) {
- const commandWithPresentationNumber = c[0];
- const [commandId, presentationNumber] = commandWithPresentationNumber.split('|');
-
- const commandKVs = this.findValuesByKeys([commandId]);
- if (!commandKVs.length) {
- throw new Error(
- `Key instruction identifier "${commandId}" for AT "${assistiveTech.name}", mode "${mode}", task "${task}" is not an available identifier. Update your commands.json file to the correct identifier or add your identifier to tests/commands.json.`
- );
- }
-
- commands.push(
- ...commandKVs.map(({ value, key }) => {
- value = assistiveTech.settings[mode].screenText
- ? `${value} (${assistiveTech.settings[mode].screenText})`
- : value;
- return {
- value,
- key,
- settings: mode,
- };
- })
- );
- }
- }
- }
- }
-
- return commands;
- }
-
- /**
- * Get AT-specific mode switching instructions
- * @param {string} mode - The mode of the screen reader, "reading" or "interaction"
- * @param {string} assistiveTech - The assistive technology.
- * @return {string} - Instructions for switching into the correct mode.
- */
- getModeInstructions(mode, assistiveTech) {
- if (this.MODE_INSTRUCTIONS[mode] && this.MODE_INSTRUCTIONS[mode][assistiveTech.key]) {
- return this.MODE_INSTRUCTIONS[mode][assistiveTech.key];
- }
- return '';
- }
-
- /**
- * Get AT-specific instruction
- * @param {string} at - an assitve technology with any capitalization
- * @return {string} - if this API knows instructions for `at`, it will return the `at` with proper capitalization
- */
- isKnownAT(at) {
- return this.supportJson.ats.find(o => o.key === at.toLowerCase());
- }
-
- defaultConfigurationInstructions(at) {
- return this.supportJson.ats.find(o => o.key === at.toLowerCase())
- .defaultConfigurationInstructionsHTML;
- }
-
- flattenObject(obj, parentKey) {
- const flattened = {};
-
- for (const key in obj) {
- if (typeof obj[key] === 'object') {
- const subObject = this.flattenObject(obj[key], parentKey + key + '.');
- Object.assign(flattened, subObject);
- } else {
- flattened[parentKey + key] = obj[key];
- }
- }
-
- return flattened;
- }
-
- findValueByKey(keyToFind) {
- const keys = Object.keys(this.commandsJson);
-
- // Need to specially handle VO modifier key combination
- if (keyToFind === 'vo')
- return this.findValuesByKeys([this.commandsJson['modifierAliases.vo']])[0];
-
- if (keyToFind.includes('modifiers.') || keyToFind.includes('keys.')) {
- const parts = keyToFind.split('.');
- const keyToCheck = parts[parts.length - 1]; // value after the '.'
-
- if (this.commandsJson[keyToFind])
- return {
- value: this.commandsJson[keyToFind],
- key: keyToCheck,
- };
-
- return null;
- }
-
- for (const key of keys) {
- const parts = key.split('.');
- const parentKey = parts[0];
- const keyToCheck = parts[parts.length - 1]; // value after the '.'
-
- if (keyToCheck === keyToFind) {
- if (parentKey === 'modifierAliases') {
- return this.findValueByKey(`modifiers.${this.commandsJson[key]}`);
- } else if (parentKey === 'keyAliases') {
- return this.findValueByKey(`keys.${this.commandsJson[key]}`);
- }
-
- return {
- value: this.commandsJson[key],
- key: keyToCheck,
- };
- }
- }
-
- // Return null if the key is not found
- return null;
- }
-
- findValuesByKeys(keysToFind = []) {
- const result = [];
-
- const patternSepWithReplacement = (keyToFind, pattern, replacement) => {
- if (keyToFind.includes(pattern)) {
- let value = '';
- let validKeys = true;
- const keys = keyToFind.split(pattern);
-
- for (const key of keys) {
- const keyResult = this.findValueByKey(key);
- if (keyResult)
- value = value ? `${value}${replacement}${keyResult.value}` : keyResult.value;
- else validKeys = false;
- }
- if (validKeys) return { value, key: keyToFind };
- }
-
- return null;
- };
-
- const patternSepHandler = keyToFind => {
- let value = '';
-
- if (keyToFind.includes(' ') && keyToFind.includes('+')) {
- const keys = keyToFind.split(' ');
- for (let [index, key] of keys.entries()) {
- const keyToFindResult = this.findValueByKey(key);
- if (keyToFindResult) keys[index] = keyToFindResult.value;
- if (key.includes('+')) keys[index] = patternSepWithReplacement(key, '+', '+').value;
- }
- value = keys.join(' then ');
-
- return { value, key: keyToFind };
- } else if (keyToFind.includes(' '))
- return patternSepWithReplacement(keyToFind, ' ', ' then ');
- else if (keyToFind.includes('+')) return patternSepWithReplacement(keyToFind, '+', '+');
- };
-
- for (const keyToFind of keysToFind) {
- if (keyToFind.includes(' ') || keyToFind.includes('+')) {
- result.push(patternSepHandler(keyToFind));
- } else {
- const keyToFindResult = this.findValueByKey(keyToFind);
- if (keyToFindResult) result.push(keyToFindResult);
- }
- }
-
- return result;
- }
-}
diff --git a/client/resources/commands.json b/client/resources/commands.json
deleted file mode 100644
index 9f5e3cf94..000000000
--- a/client/resources/commands.json
+++ /dev/null
@@ -1,115 +0,0 @@
-{
- "modifiers": {
- "alt": "Alt",
- "opt": "Option",
- "shift": "Shift",
- "ctrl": "Control",
- "cmd": "Command",
- "win": "Windows",
- "ins": "Insert"
- },
- "modifierAliases": {
- "jaws": "ins",
- "nvda": "ins",
- "vo": "ctrl+opt"
- },
- "keys": {
- "a": "a",
- "b": "b",
- "c": "c",
- "d": "d",
- "e": "e",
- "f": "f",
- "g": "g",
- "h": "h",
- "i": "i",
- "j": "j",
- "k": "k",
- "l": "l",
- "m": "m",
- "n": "n",
- "o": "o",
- "p": "p",
- "q": "q",
- "r": "r",
- "s": "s",
- "t": "t",
- "u": "u",
- "v": "v",
- "w": "w",
- "x": "x",
- "y": "y",
- "z": "z",
- "1": "1",
- "2": "2",
- "3": "3",
- "4": "4",
- "5": "5",
- "6": "6",
- "7": "7",
- "8": "8",
- "9": "9",
- "0": "0",
- "dash": "Dash",
- "equals": "Equals",
- "grave": "Grave",
- "leftBracket": "Left Bracket",
- "rightBracket": "Right Bracket",
- "backslash": "Backslash",
- "semicolon": "Semicolon",
- "apostrophe": "Apostrophe",
- "comma": "Comma",
- "period": "Period",
- "slash": "Slash",
- "esc": "Escape",
- "backspace": "Backspace",
- "tab": "Tab",
- "capsLock": "Caps Lock",
- "enter": "Enter",
- "space": "Space",
- "f1": "F1",
- "f2": "F2",
- "f3": "F3",
- "f4": "F4",
- "f5": "F5",
- "f6": "F6",
- "f7": "F7",
- "f8": "F8",
- "f9": "F9",
- "f10": "F10",
- "f11": "F11",
- "f12": "F12",
- "scrollLock": "Scroll Lock",
- "pause": "Pause",
- "home": "Home",
- "end": "End",
- "pageUp": "Page Up",
- "pageDown": "Page Down",
- "del": "Delete",
- "left": "Left Arrow",
- "right": "Right Arrow",
- "up": "Up Arrow",
- "down": "Down Arrow",
- "numLock": "Num Lock",
- "numpadSlash": "Numpad Slash",
- "numpadAsterisk": "Numpad Asterisk",
- "numpadMinus": "Numpad Minus",
- "numpadPlus": "Numpad Plus",
- "numpadEnter": "Numpad Enter",
- "numpad1": "Numpad 1",
- "numpad2": "Numpad 2",
- "numpad3": "Numpad 3",
- "numpad4": "Numpad 4",
- "numpad5": "Numpad 5",
- "numpad6": "Numpad 6",
- "numpad7": "Numpad 7",
- "numpad8": "Numpad 8",
- "numpad9": "Numpad 9",
- "numpad0": "Numpad 0",
- "numpadPeriod": "Numpad Period"
- },
- "keyAliases": {
- "delete": "del",
- "escape": "esc"
- }
-}
\ No newline at end of file
diff --git a/client/resources/keys.json b/client/resources/keys.json
deleted file mode 100644
index 640724f58..000000000
--- a/client/resources/keys.json
+++ /dev/null
@@ -1,117 +0,0 @@
-{
- "CTRL_HOME": "Control+Home",
- "CTRL_OPT_HOME": "Control+Option+Home",
- "CTRL_END": "Control+End",
- "CTRL_OPT_END": "Control+Option+End",
- "CTRL_HOME_THEN_DOWN": "Control+Home followed by Down Arrow",
- "DELETE": "Delete",
- "ALT_DELETE": "Alt+Delete",
- "ALT_DOWN": "Alt+Down",
- "CTRL_ALT_DOWN": "Control+Alt+Down",
- "ALT_UP": "Alt+Up",
- "C_AND_SHIFT_C": "C / Shift+C",
- "SHIFT_C": "Shift+C",
- "CTRL_INS_X": "Control+Insert+X",
- "OPT_DOWN": "Option+Down",
- "OPT_UP": "Option+Up",
- "CTRL_OPT_LEFT": "Ctrl+Option+Left",
- "CTRL_ALT_LEFT": "Control+Alt+Left",
- "CTRL_OPT_RIGHT": "Control+Option+Right",
- "CTRL_ALT_RIGHT": "Control+Alt+Right",
- "CTRL_OPT_UP": "Control+Option+Up",
- "CTRL_OPT_DOWN": "Control+Option+Down",
- "CTRL_OPT_RIGHT_AND_CTRL_OPT_LEFT": "Control+Option+Right / Ctrl+Option+Left",
- "CTRL_OPT_A": "Control+Option+A",
- "CTRL_OPT_CMD_J": "Control+Option+Command+J",
- "CTRL_OPT_CMD_L": "Control+Option+Command+L",
- "CTRL_OPT_CMD_P": "Control+Option+Command+P",
- "SHIFT_CTRL_OPT_CMD_J": "Shift+Control+Option+Command+J",
- "SHIFT_CTRL_OPT_CMD_L": "Shift+Control+Option+Command+L",
- "CTRL_OPT_CMD_J_AND_SHIFT_CTRL_OPT_CMD_J": "Control+Option+Command+J / Shift+Control+Option+Command+J",
- "CTRL_OPT_CMD_C_AND_SHIFT_CTRL_OPT_CMD_C": "Control+Option+Command+C / Shift+Control+Option+Command+C",
- "CTRL_OPT_F3": "Control+Option+F3",
- "CTRL_OPT_F4": "Control+Option+F4",
- "CTRL_OPT_SPACE": "Control+Option+Space",
- "CTRL_OPT_SPACE_THEN_CTRL_OPT_RIGHT": "Control+Option+Space followed by Control+Option+Right",
- "CTRL_U": "Control+U",
- "CMD": "Command",
- "CMD_LEFT": "Command+Left",
- "CMD_RIGHT": "Command+Right",
- "CMD_DOWN": "Command+Down",
- "CMD_UP": "Command+Up",
- "DOWN": "Down Arrow",
- "END": "End",
- "ENTER": "Enter",
- "E_AND_SHIFT_E": "E / Shift+E",
- "ESC": "Escape",
- "F_AND_SHIFT_F": "F / Shift+F",
- "HOME": "Home",
- "INS_DOWN_OR_CAPS_DOWN": "Insert+Down (or CapsLock+Down)",
- "INS_F7_OR_CAPS_F7": "Insert+F7 (or CapsLock+F7)",
- "INS_SPACE": "Insert+Space",
- "INS_TAB": "Insert+Tab",
- "INS_TAB_OR_CAPS_TAB": "Insert+Tab (or CapsLock+Tab)",
- "INS_UP_OR_CAPS_I": "Insert+Up (or CapsLock+I)",
- "INS_UP": "Insert+Up",
- "INS_UP_OR_CAPS_UP": "Insert+Up (or CapsLock+Up)",
- "INS_Z": "Insert+Z",
- "LEFT_AND_RIGHT": "Left Arrow / Right Arrow",
- "LEFT": "Left Arrow",
- "NUMPAD_5": "Numpad 5",
- "INS_NUMPAD_5": "Insert+Numpad 5 (or CapsLock+Numpad 5)",
- "INS_NUMPAD_6": "Insert+Numpad 6 (or CapsLock+Numpad 6)",
- "NUMPAD_PLUS": "Numpad Plus",
- "RIGHT": "Right Arrow",
- "SPACE": "Space",
- "TAB": "Tab",
- "SHIFT_TAB": "Shift+Tab",
- "TAB_AND_SHIFT_TAB": "Tab / Shift+Tab",
- "UP": "Up Arrow",
- "CTRL_ALT_UP": "Control+Alt+Up",
- "UP_AND_DOWN": "Up Arrow / Down Arrow",
- "SHIFT_X": "Shift+X",
- "X_AND_SHIFT_X": "X / Shift+X",
- "A": "A",
- "SHIFT_A": "Shift+A",
- "B": "B",
- "SHIFT_B": "Shift+B",
- "C": "C",
- "D": "D",
- "E": "E",
- "SHIFT_E": "Shift+E",
- "F": "F",
- "SHIFT_F": "Shift+F",
- "G": "G",
- "H": "H",
- "I": "I",
- "SHIFT_I": "Shift+I",
- "J": "J",
- "K": "K",
- "SHIFT_K": "Shift+K",
- "L": "L",
- "SHIFT_L": "Shift+L",
- "M": "M",
- "N": "N",
- "O": "O",
- "P": "P",
- "Q": "Q",
- "R": "R",
- "SHIFT_R": "Shift+R",
- "S": "S",
- "T": "T",
- "SHIFT_T": "Shift+T",
- "CTRL_OPT_CMD_T": "Control+Option+Command+T",
- "T_THEN_DOWN": "T followed by Down Arrow",
- "SHIFT_T_THEN_DOWN": "Shift+T followed by Down Arrow",
- "U": "U",
- "SHIFT_U": "Shift+U",
- "V": "V",
- "W": "W",
- "X": "X",
- "Y": "Y",
- "CTRL_OPT_CMD_Y": "Control+Option+Command+Y",
- "SHIFT_CTRL_OPT_CMD_Y": "Shift+Control+Option+Command+Y",
- "Z": "Z",
- "PAGE_DOWN": "Page Down",
- "PAGE_UP": "Page Up"
-}
diff --git a/client/resources/keys.mjs b/client/resources/keys.mjs
deleted file mode 100644
index bfeff2bde..000000000
--- a/client/resources/keys.mjs
+++ /dev/null
@@ -1,133 +0,0 @@
-// Keys
-export const CTRL_HOME = "Control+Home";
-export const CTRL_OPT_HOME = "Control+Option+Home";
-export const CTRL_END = "Control+End";
-export const CTRL_OPT_END = "Control+Option+End";
-export const CTRL_HOME_THEN_DOWN = "Control+Home followed by Down Arrow";
-export const DELETE = "Delete";
-export const ALT_DELETE = "Alt+Delete";
-export const ALT_DOWN = "Alt+Down";
-export const CTRL_ALT_DOWN = "Control+Alt+Down";
-export const ALT_UP = "Alt+Up";
-export const C_AND_SHIFT_C = "C / Shift+C";
-export const SHIFT_C = "Shift+C";
-export const CTRL_INS_X = "Control+Insert+X";
-export const OPT_DOWN = "Option+Down";
-export const OPT_UP = "Option+Up";
-export const CTRL_OPT_LEFT = "Ctrl+Option+Left";
-export const CTRL_ALT_LEFT = "Control+Alt+Left";
-export const CTRL_OPT_RIGHT = "Control+Option+Right";
-export const CTRL_ALT_RIGHT = "Control+Alt+Right";
-export const CTRL_OPT_UP = "Control+Option+Up";
-export const CTRL_OPT_DOWN = "Control+Option+Down";
-export const CTRL_OPT_RIGHT_AND_CTRL_OPT_LEFT = "Control+Option+Right / Ctrl+Option+Left";
-export const CTRL_OPT_A = "Control+Option+A";
-export const CTRL_OPT_CMD_J = "Control+Option+Command+J";
-export const CTRL_OPT_CMD_L = "Control+Option+Command+L";
-export const CTRL_OPT_CMD_P = "Control+Option+Command+P";
-export const SHIFT_CTRL_OPT_CMD_J = "Shift+Control+Option+Command+J";
-export const SHIFT_CTRL_OPT_CMD_L = "Shift+Control+Option+Command+L";
-export const CTRL_OPT_CMD_J_AND_SHIFT_CTRL_OPT_CMD_J = "Control+Option+Command+J / Shift+Control+Option+Command+J";
-export const CTRL_OPT_CMD_C_AND_SHIFT_CTRL_OPT_CMD_C = "Control+Option+Command+C / Shift+Control+Option+Command+C";
-export const CTRL_OPT_F3 = "Control+Option+F3";
-export const CTRL_OPT_F4 = "Control+Option+F4";
-export const CTRL_OPT_SPACE = "Control+Option+Space";
-export const CTRL_OPT_SPACE_THEN_CTRL_OPT_RIGHT = "Control+Option+Space followed by Control+Option+Right";
-export const CTRL_U = "Control+U";
-export const CMD = "Command";
-export const CMD_LEFT = "Command+Left";
-export const CMD_RIGHT = "Command+Right";
-export const CMD_DOWN = "Command+Down";
-export const CMD_UP = "Command+Up";
-export const DOWN = "Down Arrow";
-export const END = "End";
-export const ENTER = "Enter";
-export const E_AND_SHIFT_E = "E / Shift+E";
-export const ESC = "Escape";
-export const F_AND_SHIFT_F = "F / Shift+F";
-export const HOME = "Home";
-export const INS_DOWN_OR_CAPS_DOWN = "Insert+Down (or CapsLock+Down)";
-export const INS_F7_OR_CAPS_F7 = "Insert+F7 (or CapsLock+F7)";
-export const INS_SPACE = "Insert+Space";
-export const INS_TAB = "Insert+Tab";
-export const INS_TAB_OR_CAPS_TAB = "Insert+Tab (or CapsLock+Tab)";
-export const INS_UP_OR_CAPS_I = "Insert+Up (or CapsLock+I)";
-export const INS_UP = "Insert+Up";
-export const INS_UP_OR_CAPS_UP = "Insert+Up (or CapsLock+Up)";
-export const INS_Z = "Insert+Z";
-export const LEFT_AND_RIGHT = "Left Arrow / Right Arrow";
-export const LEFT = "Left Arrow";
-export const NUMPAD_5 = "Numpad 5";
-export const INS_NUMPAD_5 = "Insert+Numpad 5 (or CapsLock+Numpad 5)";
-export const INS_NUMPAD_6 = "Insert+Numpad 6 (or CapsLock+Numpad 6)";
-export const NUMPAD_PLUS = "Numpad Plus";
-export const RIGHT = "Right Arrow";
-export const SPACE = "Space";
-export const TAB = "Tab";
-export const SHIFT_TAB = "Shift+Tab";
-export const TAB_AND_SHIFT_TAB = "Tab / Shift+Tab";
-export const UP = "Up Arrow";
-export const CTRL_ALT_UP = "Control+Alt+Up";
-export const UP_AND_DOWN = "Up Arrow / Down Arrow";
-export const SHIFT_X = "Shift+X";
-export const X_AND_SHIFT_X = "X / Shift+X";
-export const A = "A";
-export const SHIFT_A = "Shift+A";
-export const B = "B";
-export const SHIFT_B = "Shift+B";
-export const C = "C";
-export const D = "D";
-export const E = "E";
-export const SHIFT_E = "Shift+E";
-export const F = "F";
-export const SHIFT_F = "Shift+F";
-export const G = "G";
-export const H = "H";
-export const I = "I";
-export const SHIFT_I = "Shift+I";
-export const J = "J";
-export const K = "K";
-export const SHIFT_K = "Shift+K";
-export const L = "L";
-export const SHIFT_L = "Shift+L";
-export const M = "M";
-export const N = "N";
-export const O = "O";
-export const P = "P";
-export const Q = "Q";
-export const R = "R";
-export const SHIFT_R = "Shift+R";
-export const S = "S";
-export const T = "T";
-export const SHIFT_T = "Shift+T";
-export const CTRL_OPT_CMD_T = "Control+Option+Command+T";
-export const T_THEN_DOWN = "T followed by Down Arrow";
-export const SHIFT_T_THEN_DOWN = "Shift+T followed by Down Arrow";
-export const U = "U";
-export const SHIFT_U = "Shift+U";
-export const V = "V";
-export const W = "W";
-export const X = "X";
-export const Y = "Y";
-export const CTRL_OPT_CMD_Y = "Control+Option+Command+Y";
-export const SHIFT_CTRL_OPT_CMD_Y = "Shift+Control+Option+Command+Y";
-export const Z = "Z";
-export const PAGE_DOWN = "Page Down";
-export const PAGE_UP = "Page Up";
-export const SHIFT_D = "Shift+D";
-export const CTRL_OPT_CMD_G = "Control+Option+Command+G";
-export const CTRL_OPT_CMD_H = "Control+Option+Command+H";
-export const CTRL_OPT_CMD_X = "Control+Option+Command+X";
-export const SHIFT_CTRL_OPT_CMD_X = "Shift+Control+Option+Command+X";
-export const SHIFT_CTRL_OPT_CMD_G = "Shift+Control+Option+Command+G";
-export const SHIFT_CTRL_OPT_CMD_H = "Shift+Control+Option+Command+H";
-export const SHIFT_CTRL_OPT_CMD_P = "Shift+Control+Option+Command+P";
-export const SHIFT_G = "Shift+G";
-export const SHIFT_H = "Shift+H";
-export const ONE = "1";
-export const TWO = "2";
-export const SHIFT_ONE = "Shift+1";
-export const SHIFT_TWO = "Shift+2";
-export const SHIFT_P = "Shift+P";
-export const COMMA = "Comma";
-export const SHIFT_PERIOD = "Shift+Period";
diff --git a/client/resources/support.json b/client/resources/support.json
deleted file mode 100644
index 4b91b7f71..000000000
--- a/client/resources/support.json
+++ /dev/null
@@ -1,410 +0,0 @@
-{
- "ats": [
- {
- "name": "JAWS",
- "key": "jaws",
- "defaultConfigurationInstructionsHTML": "Configure JAWS with default settings. For help, read <a href="https://github.com/w3c/aria-at/wiki/Configuring-Screen-Readers-for-Testing">Configuring Screen Readers for Testing</a>.",
- "assertionTokens": {
- "screenReader": "JAWS",
- "readingMode": "virtual cursor active",
- "interactionMode": "PC cursor active"
- },
- "settings": {
- "virtualCursor": {
- "screenText": "virtual cursor active",
- "instructions": [
- "Press <kbd>Alt</kbd>+<kbd>Delete</kbd> to determine which cursor is active.",
- "If the PC cursor is active, press <kbd>Escape</kbd> to activate the virtual cursor."
- ]
- },
- "pcCursor": {
- "screenText": "PC cursor active",
- "instructions": [
- "Press <kbd>Alt</kbd>+<kbd>Delete</kbd> to determine which cursor is active.",
- "If the virtual cursor is active, press <kbd>Insert</kbd>+<kbd>z</kbd> to disable the virtual cursor."
- ]
- }
- }
- },
- {
- "name": "NVDA",
- "key": "nvda",
- "defaultConfigurationInstructionsHTML": "Configure NVDA with default settings. For help, read <a href="https://github.com/w3c/aria-at/wiki/Configuring-Screen-Readers-for-Testing">Configuring Screen Readers for Testing</a>.",
- "assertionTokens": {
- "screenReader": "NVDA",
- "readingMode": "browse mode",
- "interactionMode": "focus mode"
- },
- "settings": {
- "browseMode": {
- "screenText": "browse mode on",
- "instructions": [
- "Press <kbd>Insert</kbd>+<kbd>Space</kbd>.",
- "If NVDA made the focus mode sound, press <kbd>Insert</kbd>+<kbd>Space</kbd> again to turn browse mode back on."
- ]
- },
- "focusMode": {
- "screenText": "focus mode on",
- "instructions": [
- "Press <kbd>Insert</kbd>+<kbd>Space</kbd>.",
- "If NVDA made the browse mode sound, press <kbd>Insert</kbd>+<kbd>Space</kbd> again to turn focus mode back on."
- ]
- }
- }
- },
- {
- "name": "VoiceOver for macOS",
- "key": "voiceover_macos",
- "defaultConfigurationInstructionsHTML": "Configure VoiceOver with default settings. For help, read <a href="https://github.com/w3c/aria-at/wiki/Configuring-Screen-Readers-for-Testing">Configuring Screen Readers for Testing</a>.",
- "settings": {
- "quickNavOn": {
- "screenText": "quick nav on",
- "instructions": [
- "Simultaneously press <kbd>Left Arrow</kbd> and <kbd>Right Arrow</kbd>.",
- "If VoiceOver said 'quick nav off', press <kbd>Left Arrow</kbd> and <kbd>Right Arrow</kbd> again to turn it back on."
- ]
- },
- "quickNavOff": {
- "screenText": "quick nav off",
- "instructions": [
- "Simultaneously press <kbd>Left Arrow</kbd> and <kbd>Right Arrow</kbd>.",
- "If VoiceOver said 'quick nav on', press <kbd>Left Arrow</kbd> and <kbd>Right Arrow</kbd> again to turn it back off."
- ]
- }
- }
- }
- ],
- "applies_to": {
- "Desktop Screen Readers": [
- "VoiceOver for macOS",
- "NVDA",
- "JAWS"
- ],
- "Screen Readers": [
- "VoiceOver for macOS",
- "NVDA",
- "JAWS"
- ]
- },
- "testPlanStrings": {
- "ariaSpecsPreface": "Tested ARIA features:",
- "openExampleInstruction": "Activate the "Open test page" button, which opens the example to test in a new window and runs a script that",
- "commandListPreface": "Do this with each of the following commands or command sequences.",
- "commandListSettingsPreface": "If any settings are specified in parentheses, ensure the settings are active before executing the command or command sequence.",
- "settingInstructionsPreface": "To perform a task with",
- "assertionResponseQuestion": "Which statements are true about the response to"
- },
- "references": {
- "aria": {
- "baseUrl": "https://www.w3.org/TR/wai-aria/",
- "linkText": "ARIA Specification",
- "fragmentIds": {
- "alert": "#alert",
- "alertdialog": "#alertdialog",
- "application": "#application",
- "article": "#article",
- "associationlist": "#associationlist",
- "associationlistitemkey": "#associationlistitemkey",
- "associationlistitemvalue": "#associationlistitemvalue",
- "banner": "#banner",
- "blockquote": "#blockquote",
- "button": "#button",
- "caption": "#caption",
- "cell": "#cell",
- "checkbox": "#checkbox",
- "code": "#code",
- "columnheader": "#columnheader",
- "combobox": "#combobox",
- "command": "#command",
- "comment": "#comment",
- "complementary": "#complementary",
- "composite": "#composite",
- "contentinfo": "#contentinfo",
- "definition": "#definition",
- "deletion": "#deletion",
- "dialog": "#dialog",
- "directory": "#directory",
- "document": "#document",
- "emphasis": "#emphasis",
- "feed": "#feed",
- "figure": "#figure",
- "form": "#form",
- "generic": "#generic",
- "grid": "#grid",
- "gridcell": "#gridcell",
- "group": "#group",
- "heading": "#heading",
- "image": "#image",
- "img": "#img",
- "input": "#input",
- "insertion": "#insertion",
- "landmark": "#landmark",
- "link": "#link",
- "list": "#list",
- "listbox": "#listbox",
- "listitem": "#listitem",
- "log": "#log",
- "main": "#main",
- "mark": "#mark",
- "marquee": "#marquee",
- "math": "#math",
- "menu": "#menu",
- "menubar": "#menubar",
- "menuitem": "#menuitem",
- "menuitemcheckbox": "#menuitemcheckbox",
- "menuitemradio": "#menuitemradio",
- "meter": "#meter",
- "navigation": "#navigation",
- "none": "#none",
- "note": "#note",
- "option": "#option",
- "paragraph": "#paragraph",
- "presentation": "#presentation",
- "progressbar": "#progressbar",
- "radio": "#radio",
- "radiogroup": "#radiogroup",
- "range": "#range",
- "region": "#region",
- "roletype": "#roletype",
- "row": "#row",
- "rowgroup": "#rowgroup",
- "rowheader": "#rowheader",
- "scrollbar": "#scrollbar",
- "search": "#search",
- "searchbox": "#searchbox",
- "section": "#section",
- "sectionhead": "#sectionhead",
- "select": "#select",
- "separator": "#separator",
- "slider": "#slider",
- "spinbutton": "#spinbutton",
- "status": "#status",
- "strong": "#strong",
- "structure": "#structure",
- "subscript": "#subscript",
- "suggestion": "#suggestion",
- "superscript": "#superscript",
- "switch": "#switch",
- "tab": "#tab",
- "table": "#table",
- "tablist": "#tablist",
- "tabpanel": "#tabpanel",
- "term": "#term",
- "textbox": "#textbox",
- "time": "#time",
- "timer": "#timer",
- "toolbar": "#toolbar",
- "tooltip": "#tooltip",
- "tree": "#tree",
- "treegrid": "#treegrid",
- "treeitem": "#treeitem",
- "widget": "#widget",
- "window": "#window",
- "aria-activedescendant": "#aria-activedescendant",
- "aria-atomic": "#aria-atomic",
- "aria-autocomplete": "#aria-autocomplete",
- "aria-braillelabel": "#aria-braillelabel",
- "aria-brailleroledescription": "#aria-brailleroledescription",
- "aria-busy": "#aria-busy",
- "aria-checked": "#aria-checked",
- "aria-colcount": "#aria-colcount",
- "aria-colindex": "#aria-colindex",
- "aria-colindextext": "#aria-colindextext",
- "aria-colspan": "#aria-colspan",
- "aria-controls": "#aria-controls",
- "aria-current": "#aria-current",
- "aria-describedby": "#aria-describedby",
- "aria-description": "#aria-description",
- "aria-details": "#aria-details",
- "aria-disabled": "#aria-disabled",
- "aria-errormessage": "#aria-errormessage",
- "aria-expanded": "#aria-expanded",
- "aria-flowto": "#aria-flowto",
- "aria-haspopup": "#aria-haspopup",
- "aria-hidden": "#aria-hidden",
- "aria-invalid": "#aria-invalid",
- "aria-keyshortcuts": "#aria-keyshortcuts",
- "aria-label": "#aria-label",
- "aria-labelledby": "#aria-labelledby",
- "aria-level": "#aria-level",
- "aria-live": "#aria-live",
- "aria-modal": "#aria-modal",
- "aria-multiline": "#aria-multiline",
- "aria-multiselectable": "#aria-multiselectable",
- "aria-orientation": "#aria-orientation",
- "aria-owns": "#aria-owns",
- "aria-placeholder": "#aria-placeholder",
- "aria-posinset": "#aria-posinset",
- "aria-pressed": "#aria-pressed",
- "aria-readonly": "#aria-readonly",
- "aria-relevant": "#aria-relevant",
- "aria-required": "#aria-required",
- "aria-roledescription": "#aria-roledescription",
- "aria-rowcount": "#aria-rowcount",
- "aria-rowindex": "#aria-rowindex",
- "aria-rowindextext": "#aria-rowindextext",
- "aria-rowspan": "#aria-rowspan",
- "aria-selected": "#aria-selected",
- "aria-setsize": "#aria-setsize",
- "aria-sort": "#aria-sort",
- "aria-valuemax": "#aria-valuemax",
- "aria-valuemin": "#aria-valuemin",
- "aria-valuenow": "#aria-valuenow",
- "aria-valuetext": "#aria-valuetext"
- }
- },
- "htmlAam": {
- "baseUrl": "https://www.w3.org/TR/html-aam-1.0/",
- "linkText": "Accessibility API Mapping",
- "fragmentIds": {
- "a": "#el-a",
- "aNoHref": "#el-a-no-href",
- "abbr": "#el-abbr",
- "address": "#el-address",
- "area": "#el-area",
- "areaNoHref": "#el-area-no-href",
- "article": "#el-article",
- "asideBodyOrMainScope": "#el-aside-ancestorbodymain",
- "asideSectionScope": "#el-aside",
- "audio": "#el-audio",
- "autonomous custom element": "#el-autonomous-custom-element",
- "b": "#el-b",
- "base": "#el-base",
- "bdi": "#el-bdi",
- "bdo": "#el-bdo",
- "blockquote": "#el-blockquote",
- "body": "#el-body",
- "br": "#el-br",
- "button": "#el-button",
- "canvas": "#el-canvas",
- "caption": "#el-caption",
- "cite": "#el-cite",
- "code": "#el-code",
- "col": "#el-col",
- "colgroup": "#el-colgroup",
- "data": "#el-data",
- "datalist": "#el-datalist",
- "dd": "#el-dd",
- "del": "#el-del",
- "details": "#el-details",
- "dfn": "#el-dfn",
- "dialog": "#el-dialog",
- "div": "#el-div",
- "dl": "#el-dl",
- "dt": "#el-dt",
- "em": "#el-em",
- "embed": "#el-embed",
- "fieldset": "#el-fieldset",
- "figcaption": "#el-figcaption",
- "figure": "#el-figure",
- "footerBodyScope": "#el-footer-ancestorbody",
- "footerMainScope": "#el-footer",
- "form": "#el-form",
- "formAssociatedCustomElement": "#el-form-associated-custom-element",
- "heading": "#el-h1-h6",
- "head": "#el-head",
- "headerBodyScope": "#el-header-ancestorbody",
- "headerMainScope": "#el-header",
- "hgroup": "#el-hgroup",
- "hr": "#el-hr",
- "html": "#el-html",
- "i": "#el-i",
- "iframe": "#el-iframe",
- "img": "#el-img",
- "imgEmptyAlt": "#el-img-empty-alt",
- "inputTypeButton": "#el-input-button",
- "inputTypeCheckbox": "#el-input-checkbox",
- "inputTypeColor": "#el-input-color",
- "inputTypeDate": "#el-input-date",
- "inputTypeDateTime": "#el-input-datetime-local",
- "inputTypeEmail": "#el-input-email",
- "inputTypeFile": "#el-input-file",
- "inputTypeHidden": "#el-input-hidden",
- "inputTypeImage": "#el-input-image",
- "inputTypeMonth": "#el-input-month",
- "inputTypeNumber": "#el-input-number",
- "inputTypePassword": "#el-input-password",
- "inputTypeRadio": "#el-input-radio",
- "inputTypeRange": "#el-input-range",
- "inputTypeReset": "#el-input-reset",
- "inputTypeSearch": "#el-input-search",
- "inputTypeSubmit": "#el-input-submit",
- "inputTypeTelephone": "#el-input-tel",
- "inputTypeText": "#el-input-text",
- "inputTypeTextAutocomplete": "#el-input-textetc-autocomplete",
- "inputTypeTime": "#el-input-time",
- "inputTypeUrl": "#el-input-url",
- "inputTypeWeek": "#el-input-week",
- "ins": "#el-ins",
- "kbd": "#el-kbd",
- "label": "#el-label",
- "legend": "#el-legend",
- "li": "#el-li",
- "link": "#el-link",
- "main": "#el-main",
- "map": "#el-map",
- "mark": "#el-mark",
- "math": "#el-math",
- "menu": "#el-menu",
- "meta": "#el-meta",
- "meter": "#el-meter",
- "nav": "#el-nav",
- "noscript": "#el-noscript",
- "object": "#el-object",
- "ol": "#el-ol",
- "optgroup": "#el-optgroup",
- "option": "#el-option",
- "output": "#el-output",
- "p": "#el-p",
- "param": "#el-param",
- "picture": "#el-picture",
- "pre": "#el-pre",
- "progress": "#el-progress",
- "q": "#el-q",
- "rb": "#el-rb",
- "rp": "#el-rp",
- "rt": "#el-rt",
- "rtc": "#el-rtc",
- "ruby": "#el-ruby",
- "s": "#el-s",
- "samp": "#el-samp",
- "script": "#el-script",
- "search": "#el-search",
- "section": "#el-section",
- "select": "#el-select-listbox",
- "selectSize1": "#el-select-combobox",
- "slot": "#el-slot",
- "small": "#el-small",
- "source": "#el-source",
- "span": "#el-span",
- "strong": "#el-strong",
- "style": "#el-style",
- "sub": "#el-sub",
- "summary": "#el-summary",
- "sup": "#el-sup",
- "svg": "#el-svg",
- "table": "#el-table",
- "tbody": "#el-tbody",
- "td": "#el-td",
- "tdGridcell": "#el-td-gridcell",
- "template": "#el-template",
- "textarea": "#el-textarea",
- "tfoot": "#el-tfoot",
- "th": "#el-th",
- "thGridcell": "#el-th-gridcell",
- "thColgroupHeader": "#el-th-columnheader",
- "thRowgroupHeader": "#el-th-rowheader",
- "thead": "#el-thead",
- "time": "#el-time",
- "title": "#el-title",
- "tr": "#el-tr",
- "track": "#el-track",
- "u": "#el-u",
- "ul": "#el-ul",
- "var": "#el-var",
- "video": "#el-video",
- "wbr": "#el-wbr"
- }
- }
- }
-}
diff --git a/client/resources/types/aria-at-test-result.js b/client/resources/types/aria-at-test-result.js
deleted file mode 100644
index ec8eab55d..000000000
--- a/client/resources/types/aria-at-test-result.js
+++ /dev/null
@@ -1,39 +0,0 @@
-/**
- * Types of a format of a test result submitted to or received from aria-at-app.
- * @namespace AriaATTestResult
- */
-
-/**
- * @typedef {"MUST"
- * | "SHOULD"} AriaATTestResult.AssertionPriorityJSON
- */
-
-/**
- * @typedef {"INCORRECT_OUTPUT"
- * | "NO_OUTPUT"} AriaATTestResult.AssertionFailedReasonJSON
- */
-
-/**
- * @typedef AriaATTestResult.JSON
- * @property {object} test
- * @property {string} test.title
- * @property {object} test.at
- * @property {string} test.at.id
- * @property {string} test.atMode
- * @property {object[]} scenarioResults
- * @property {object} scenarioResults[].scenario
- * @property {object} scenarioResults[].scenario.command
- * @property {string} scenarioResults[].scenario.command.id
- * @property {string} scenarioResults[].output
- * @property {object[]} scenarioResults[].assertionResults
- * @property {object} scenarioResults[].assertionResults[].assertion
- * @property {AriaATTestResult.AssertionPriorityJSON} scenarioResults[].assertionResults[].assertion.priority
- * @property {string} scenarioResults[].assertionResults[].assertion.text
- * @property {boolean} scenarioResults[].assertionResults[].passed
- * @property {AriaATTestResult.AssertionFailedReasonJSON | null} [scenarioResults[].assertionResults[].failedReason]
- * @property {object[]} scenarioResults[].unexpectedBehaviors
- * @property {string} scenarioResults[].unexpectedBehaviors[].id
- * @property {string} scenarioResults[].unexpectedBehaviors[].text
- * @property {string} scenarioResults[].unexpectedBehaviors[].impact
- * @property {string} scenarioResults[].unexpectedBehaviors[].details
- */
diff --git a/client/resources/types/aria-at-test-run.js b/client/resources/types/aria-at-test-run.js
deleted file mode 100644
index cdeceb90e..000000000
--- a/client/resources/types/aria-at-test-run.js
+++ /dev/null
@@ -1,100 +0,0 @@
-/** @namespace AriaATTestRun */
-
-/**
- * @typedef {"reading"
- * | "interaction"} AriaATTestRun.ATMode
- */
-
-/**
- * @typedef {"loadPage"
- * | "openTestWindow"
- * | "closeTestWindow"
- * | "validateResults"
- * | "changeText"
- * | "changeSelection"
- * | "showResults"} AriaATTestRun.UserActionName
- */
-
-/**
- * @typedef {"focusUndesirable"} AriaATTestRun.UserActionObjectName
- */
-
-/**
- * @typedef AriaATTestRun.UserActionFocusUnexpected
- * @property {"focusUndesirable"} action
- * @property {number} commandIndex
- * @property {number} unexpectedIndex
- */
-
-/**
- * @typedef {AriaATTestRun.UserActionName
- * | AriaATTestRun.UserActionFocusUnexpected} AriaATTestRun.UserAction
- */
-
-/**
- * @typedef {"notSet"
- * | "pass"
- * | "failMissing"
- * | "failIncorrect"} AriaATTestRun.AssertionResult
- */
-
-/**
- * @typedef {"notSet"
- * | "pass"
- * | "failSupport"} AriaATTestRun.AdditionalAssertionResult
- */
-
-/**
- * @typedef {"notSet"
- * | "hasUnexpected"
- * | "doesNotHaveUnexpected"} AriaATTestRun.HasUnexpectedBehavior
- */
-
-/**
- * @typedef AriaATTestRun.State
- * This state contains all the serializable values that are needed to render any of the documents (InstructionDocument,
- * ResultsTableDocument, and TestPageDocument) from the test-run module.
- *
- * @property {string[] | null} errors
- * @property {object} info
- * @property {string} info.description
- * @property {string} info.task
- * @property {AriaATTestRun.ATMode} info.mode
- * @property {string} info.modeInstructions
- * @property {string[]} info.userInstructions
- * @property {string} info.setupScriptDescription
- * @property {object} config
- * @property {object} config.at
- * @property {string} config.at.key
- * @property {string} config.at.name
- * @property {boolean} config.renderResultsAfterSubmit
- * @property {boolean} config.displaySubmitButton
- * @property {AriaATTestRun.UserAction} currentUserAction
- * @property {object[]} commands
- * @property {string} commands[].description
- * @property {object} commands[].atOutput
- * @property {boolean} commands[].atOutput.highlightRequired
- * @property {string} commands[].atOutput.value
- * @property {object[]} commands[].assertions
- * @property {string} commands[].assertions[].description
- * @property {boolean} commands[].assertions[].highlightRequired
- * @property {number} commands[].assertions[].priority
- * @property {AriaATTestRun.AssertionResult} commands[].assertions[].result
- * @property {object[]} commands[].additionalAssertions
- * @property {string} commands[].additionalAssertions[].description
- * @property {boolean} commands[].additionalAssertions[].highlightRequired
- * @property {number} commands[].additionalAssertions[].priority
- * @property {AriaATTestRun.AdditionalAssertionResult} commands[].additionalAssertions[].result
- * @property {object} commands[].unexpected
- * @property {boolean} commands[].unexpected.highlightRequired
- * @property {AriaATTestRun.HasUnexpectedBehavior} commands[].unexpected.hasUnexpected
- * @property {number} commands[].unexpected.tabbedBehavior
- * @property {object[]} commands[].unexpected.behaviors
- * @property {string} commands[].unexpected.behaviors[].description
- * @property {boolean} commands[].unexpected.behaviors[].checked
- * @property {object} [commands[].unexpected.behaviors[].more]
- * @property {boolean} commands[].unexpected.behaviors[].more.highlightRequired
- * @property {string} commands[].unexpected.behaviors[].more.value
- * @property {object} openTest
- * @property {boolean} openTest.enabled
- */
diff --git a/client/resources/vrender.mjs b/client/resources/vrender.mjs
deleted file mode 100644
index f21b6b2dc..000000000
--- a/client/resources/vrender.mjs
+++ /dev/null
@@ -1,987 +0,0 @@
-const mounts = new WeakMap();
-
-/**
- * @param {HTMLElement} mount
- * @param {NodeNode} newValue
- */
-export function render(mount, newValue) {
- let lastValue = mounts.get(mount);
- if (!lastValue) {
- lastValue = ElementType.init(newQueue(), null, null, element(mount.tagName));
- lastValue.ref = mount;
- mounts.set(mount, lastValue);
- }
- const queue = newQueue();
- lastValue.type.diff(queue, lastValue, element(mount.tagName, newValue));
- runQueue(queue);
- if (!newValue) {
- mounts.delete(mount);
- }
-}
-
-/**
- * @param {string} shape
- * @param {...NodeNode} content
- * @returns {ElementNode}
- */
-export function element(shape, ...content) {
- return {
- type: ELEMENT_TYPE_NAME,
- shape,
- content: content.map(asNode),
- };
-}
-
-/**
- * @param {...NodeNode} content
- * @returns {FragmentNode}
- */
-export function fragment(...content) {
- return {
- type: FRAGMENT_TYPE_NAME,
- shape: FRAGMENT_TYPE_NAME,
- content: content.map(asNode),
- };
-}
-
-/**
- * @param {string} content
- * @returns {TextNode}
- */
-export function text(content) {
- return {
- type: TEXT_TYPE_NAME,
- shape: TEXT_TYPE_NAME,
- content,
- };
-}
-
-/**
- * @param {function} shape
- * @param {...NodeNode} content
- * @returns {ComponentNode}
- */
-export function component(shape, ...content) {
- return {
- type: COMPONENT_TYPE_NAME,
- shape,
- content,
- };
-}
-
-/**
- * @param {{[key: string]: string}} styleMap
- * @returns {MemberNode}
- */
-export function style(styleMap) {
- return attribute(
- 'style',
- Object.keys(styleMap)
- .map(key => `${key}: ${styleMap[key]};`)
- .join(' ')
- );
-}
-
-/**
- * @param {string[]} names
- * @returns {MemberNode}
- */
-export function className(names) {
- return attribute('class', names.filter(Boolean).join(' '));
-}
-
-/**
- * @param {string} name
- * @param {string | boolean} value
- * @returns {MemberNode}
- */
-export function attribute(name, value) {
- return {
- type: ATTRIBUTE_TYPE_NAME,
- name,
- value,
- };
-}
-
-/**
- * @param {string} name
- * @param {any} value
- * @returns {MemberNode}
- */
-export function property(name, value) {
- return {
- type: PROPERTY_TYPE_NAME,
- name,
- value,
- };
-}
-
-/**
- * @param {string} name
- * @param {any} value
- * @returns {MemberNode}
- */
-export function meta(name, value) {
- return {
- type: META_TYPE_NAME,
- name,
- value,
- };
-}
-
-const refMap = new WeakMap();
-
-/**
- * @param {{ref: HTMLElement | null}} value
- * @returns {MemberNode}
- */
-export function ref(value) {
- let refHook = refMap.get(value);
- if (!refHook) {
- refHook = (/** @type {HTMLElement} */ element) => {
- value.ref = element;
- };
- refMap.set(value, refHook);
- }
- return {
- type: REF_TYPE_NAME,
- name: 'ref',
- value: refHook,
- };
-}
-
-const noop = function () {};
-
-/**
- * @param {boolean} shouldFocus
- */
-export function focus(shouldFocus) {
- return {
- type: REF_TYPE_NAME,
- name: 'focus',
- value: shouldFocus ? element => element.focus() : noop,
- };
-}
-
-function asNode(item) {
- if (typeof item === 'string') {
- return text(item);
- } else if (Array.isArray(item)) {
- return fragment(...item);
- } else if (item === null || item === undefined) {
- return fragment();
- }
- return item;
-}
-
-const ELEMENT_TYPE_NAME = 'element';
-const FRAGMENT_TYPE_NAME = 'fragment';
-const COMPONENT_TYPE_NAME = 'component';
-const TEXT_TYPE_NAME = 'text';
-const ATTRIBUTE_TYPE_NAME = 'attribute';
-const PROPERTY_TYPE_NAME = 'property';
-const REF_TYPE_NAME = 'ref';
-const META_TYPE_NAME = 'meta';
-
-/** @type ElementStateType */
-const ElementType = {
- name: ELEMENT_TYPE_NAME,
- diffEntry: /** @type {DiffEntryFunction} */ (diffChildEntry),
- init: /** @type {InitNodeFunction} */ (
- function (queue, parent, after, /** @type {ElementNode} */ node) {
- const state = {
- type: ElementType,
- parent,
- after,
- shape: node.shape,
- content: null,
- ref: null,
- refHooks: null,
- rewriteChildIndex: 0,
- children: null,
- rewriteMemberIndex: 0,
- members: null,
- };
- enqueueChange(queue, addElement, state);
- return state;
- }
- ),
- diff: /** @type {DiffFunction} */ (
- function (queue, /** @type {ElementState} */ lastValue, /** @type {ElementNode} */ newValue) {
- lastValue.rewriteMemberIndex = 0;
- diffFragment(queue, lastValue, newValue);
- if (lastValue.members !== null) {
- const group = lastValue.members;
- let index;
- for (index = lastValue.rewriteMemberIndex; index < group.length; index++) {
- const node = group[index];
- node.type.teardown(queue, node);
- }
- if (lastValue.rewriteMemberIndex === 0) {
- lastValue.members = null;
- } else {
- group.length = lastValue.rewriteMemberIndex;
- }
- }
- }
- ),
- teardown: /** @type {TeardownFunction} */ (
- function (queue, /** @type {ElementState} */ state) {
- enqueueChange(queue, removeElement, state);
- const { children } = state;
- if (children !== null) {
- for (let i = 0; i < children.length; i++) {
- children[i].type.softTeardown(children[i]);
- }
- }
- }
- ),
- softTeardown: /** @type {SoftTeardownFunction} */ (softTeardownElement),
-};
-
-/** @type {FragmentStateType} */
-const FragmentType = {
- name: FRAGMENT_TYPE_NAME,
- diffEntry: /** @type {DiffEntryFunction} */ (diffChildEntry),
- init(queue, parent, after) {
- return {
- type: FragmentType,
- parent,
- after,
- shape: FRAGMENT_TYPE_NAME,
- content: null,
- rewriteChildIndex: 0,
- children: null,
- };
- },
- diff: /** @type {DiffFunction} */ (diffFragment),
- teardown: /** @type {TeardownFunction} */ (
- function (queue, /** @type {FragmentState} */ state) {
- const { children } = state;
- if (children !== null) {
- for (let i = 0; i < children.length; i++) {
- children[i].type.teardown(queue, children[i]);
- }
- }
- }
- ),
- softTeardown: /** @type {SoftTeardownFunction} */ (softTeardownElement),
-};
-
-/** @type ComponentStateType */
-const ComponentType = {
- name: COMPONENT_TYPE_NAME,
- diffEntry: /** @type {DiffEntryFunction} */ (diffChildEntry),
- init: /** @type {InitNodeFunction} */ (
- function (queue, parent, after, /** @type {ComponentNode} */ node) {
- return {
- type: ComponentType,
- parent,
- after,
- shape: node.shape,
- content: null,
- rendered: null,
- };
- }
- ),
- diff: /** @type {DiffFunction} */ (
- function (queue, /** @type {ComponentState} */ lastChild, /** @type {ComponentNode} */ node) {
- /** @type {MemberNode} */
- const componentOptionsMeta = node.content.find(isOptions) || null;
- const componentOptions = componentOptionsMeta ? componentOptionsMeta.value : null;
- if (shallowEquals(lastChild.content, componentOptions) === false) {
- lastChild.content = componentOptions;
- queue.prepare.push(lastChild);
- }
- }
- ),
- teardown: /** @type {TeardownFunction} */ (
- function (queue, /** @type {ComponentState} */ state) {
- if (state.rendered !== null) {
- state.rendered.type.teardown(queue, state.rendered);
- }
- }
- ),
- softTeardown: /** @type {SoftTeardownFunction} */ (
- function (/** @type {ComponentState} */ state) {
- if (state.rendered !== null) {
- state.rendered.type.softTeardown(state.rendered);
- }
- }
- ),
-};
-
-/** @type TextStateType */
-const TextType = {
- name: TEXT_TYPE_NAME,
- diffEntry: /** @type {DiffEntryFunction} */ (diffChildEntry),
- init: /** @type {InitNodeFunction} */ (
- function (queue, parent, after, /** @type {TextNode} */ node) {
- /** @type {TextState} */
- const state = {
- type: TextType,
- parent,
- after,
- shape: TEXT_TYPE_NAME,
- content: node.content,
- ref: null,
- };
- enqueueChange(queue, addText, state);
- return state;
- }
- ),
- diff: /** @type {DiffFunction} */ (
- function (queue, /** @type {TextState} */ lastChild, /** @type {TextNode} */ node) {
- if (lastChild.content !== node.content) {
- lastChild.content = node.content;
- enqueueChange(queue, changeText, lastChild);
- }
- }
- ),
- teardown: /** @type {TeardownFunction} */ (
- function (queue, /** @type {TextState} */ state) {
- enqueueChange(queue, removeText, state);
- }
- ),
- softTeardown() {},
-};
-
-/** @type {MemberStateType} */
-const AttributeType = {
- name: ATTRIBUTE_TYPE_NAME,
- diffEntry: /** @type {DiffEntryFunction} */ (diffMemberEntry),
- init(parent, name) {
- return {
- type: AttributeType,
- parent,
- name,
- value: null,
- };
- },
- diff: /** @type {DiffFunction} */ (
- function (queue, /** @type {MemberState} */ old, /** @type {MemberNode} */ memberNode) {
- if (old.value !== memberNode.value) {
- old.value = memberNode.value;
- if (old.value === false) {
- enqueueChange(queue, removeAttribute, old);
- } else {
- enqueueChange(queue, changeAttribute, old);
- }
- }
- }
- ),
- teardown(queue, state) {
- enqueueChange(queue, removeAttribute, state);
- },
-};
-
-/** @type {MemberStateType} */
-const PropertyType = {
- name: PROPERTY_TYPE_NAME,
- diffEntry: /** @type {DiffEntryFunction} */ (diffMemberEntry),
- init(parent, name) {
- return {
- type: PropertyType,
- parent,
- name,
- value: null,
- };
- },
- diff: /** @type {DiffFunction} */ (
- function (queue, /** @type {MemberState} */ old, /** @type {MemberNode} */ memberNode) {
- if (old.value !== memberNode.value) {
- old.value = memberNode.value;
- enqueueChange(queue, changeProperty, old);
- }
- }
- ),
- teardown(queue, state) {
- state.value = undefined;
- enqueueChange(queue, changeProperty, state);
- },
-};
-
-/** @type {MemberStateType} */
-const RefType = {
- name: REF_TYPE_NAME,
- diffEntry: /** @type {DiffEntryFunction} */ (diffMemberEntry),
- init(parent, name) {
- return {
- type: RefType,
- parent,
- name,
- value: null,
- };
- },
- diff: /** @type {DiffFunction} */ (
- function (queue, /** @type {MemberState} */ state, /** @type {MemberNode} */ node) {
- if (state.value !== node.value) {
- state.value = node.value;
- if (state.parent.refHooks === null) {
- state.parent.refHooks = [];
- }
- if (state.parent.refHooks.indexOf(state) === -1) {
- state.parent.refHooks.push(state);
- }
- enqueuePost(queue, updateRef, state);
- }
- }
- ),
- teardown(queue, state) {
- const index = state.parent.refHooks.indexOf(state);
- state.parent.refHooks.splice(index, 1);
- if (state.parent.refHooks.length === 0) {
- state.parent.refHooks = null;
- }
- enqueuePost(queue, unsetRef, state);
- },
-};
-
-const typeMap = {
- [ELEMENT_TYPE_NAME]: ElementType,
- [FRAGMENT_TYPE_NAME]: FragmentType,
- [COMPONENT_TYPE_NAME]: ComponentType,
- [TEXT_TYPE_NAME]: TextType,
- [ATTRIBUTE_TYPE_NAME]: AttributeType,
- [PROPERTY_TYPE_NAME]: PropertyType,
- [REF_TYPE_NAME]: RefType,
-};
-
-/** @type DiffEntryFunction */
-function diffChildEntry(
- queue,
- parent,
- /** @type NodeStateType */ metaType,
- /** @type NodeNode */ element
-) {
- if (!parent.children) {
- parent.children = [];
- }
- const index = parent.rewriteChildIndex++;
- let state = parent.children[index];
- if (!state || state.shape !== element.shape) {
- if (state) {
- state.type.teardown(queue, state);
- }
- state = /** @type {NodeState} */ (
- metaType.init(queue, parent, parent.children[index - 1] || null, element)
- );
- parent.children[index] = state;
-
- const sibling = parent.children[index + 1];
- if (sibling) {
- sibling.after = state;
- }
- }
- state.type.diff(queue, state, element);
-}
-
-/** @type {DiffFunction} */
-function diffFragment(
- queue,
- /** @type {ElementState | FragmentState} */ lastValue,
- /** @type {ElementNode | FragmentNode} */ newValue
-) {
- lastValue.rewriteChildIndex = 0;
- const { content } = newValue;
- for (let i = 0; i < content.length; i++) {
- const node = content[i];
- const metaType = typeMap[node.type];
- metaType.diffEntry(queue, lastValue, metaType, node);
- }
- const children = lastValue.children;
- if (children !== null) {
- const childIndex = lastValue.rewriteChildIndex;
- for (let i = childIndex; i < children.length; i++) {
- const node = children[i];
- node.type.teardown(queue, node);
- }
- if (childIndex === 0) {
- lastValue.children = null;
- } else {
- children.length = childIndex;
- }
- }
-}
-
-/** @type {DiffEntryFunction} */
-function diffMemberEntry(
- queue,
- /** @type {ElementState} */ element,
- /** @type {MemberStateType} */ nodeType,
- /** @type {MemberNode} */ node
-) {
- if (element.members === null) {
- element.members = [];
- }
- const group = element.members;
-
- const writeIndex = element.rewriteMemberIndex++;
- let index;
- for (index = writeIndex; index < group.length; index++) {
- const item = group[index];
- if (item.type.name === node.type && item.name === node.name) {
- break;
- }
- }
-
- let old = group[index];
- if (index !== writeIndex) {
- group[index] = group[writeIndex];
- }
- if (!old) {
- old = nodeType.init(element, node.name);
- group[writeIndex] = old;
- }
- old.type.diff(queue, old, node);
-}
-
-/** @type {SoftTeardownFunction} */
-function softTeardownElement(/** @type {ElementState} */ state) {
- const { children } = state;
- if (children !== null) {
- for (let i = 0; i < children.length; i++) {
- children[i].type.softTeardown(children[i]);
- }
- }
-}
-
-/**
- * @param {Data} node
- * @returns {node is MemberNode}
- */
-function isOptions(node) {
- return node.type === META_TYPE_NAME && node.name === 'options';
-}
-
-/**
- * @param {Queue} queue
- */
-function runQueue(queue) {
- const { prepare, changes: apply, post } = queue;
- for (let i = 0; i < prepare.length; i++) {
- changeViewRender(prepare[i], queue);
- }
- for (let i = 0; i < apply.length; i += 2) {
- apply[i](apply[i + 1]);
- }
- for (let i = 0; i < post.length; i += 2) {
- post[i](post[i + 1]);
- }
-}
-
-/**
- * @param {ComponentState} componentState
- * @param {Queue} queue
- */
-function changeViewRender(componentState, queue) {
- const newRender = componentState.shape(componentState.content) || null;
- if (newRender) {
- const metaType = typeMap[newRender.type];
- let lastRender = componentState.rendered;
- if (!lastRender || lastRender.shape !== newRender.shape) {
- if (lastRender) {
- lastRender.type.teardown(queue, lastRender);
- }
- lastRender = metaType.init(queue, componentState, null, newRender);
- componentState.rendered = lastRender;
- }
- lastRender.type.diff(queue, lastRender, newRender);
- } else if (componentState.rendered) {
- componentState.rendered.type.teardown(queue, componentState.rendered);
- componentState.rendered = null;
- }
-}
-
-/**
- * @param {MemberState} state
- */
-function changeProperty(state) {
- state.parent.ref[state.name] = state.value;
-}
-
-/**
- * @param {MemberState} state
- */
-function removeAttribute(state) {
- state.parent.ref.removeAttribute(state.name);
-}
-
-/**
- * @param {MemberState} state
- */
-function changeAttribute(state) {
- state.parent.ref.setAttribute(state.name, state.value);
-}
-
-/**
- * @param {TextState} state
- */
-function removeText(state) {
- state.ref.parentNode.removeChild(state.ref);
-}
-
-/**
- * @param {TextState} state
- */
-function changeText(state) {
- state.ref.textContent = state.content;
-}
-
-/**
- * @param {TextState} state
- */
-function addText(state) {
- state.ref = document.createTextNode(state.content);
- state.ref.textContent = state.content;
- insertAfterSibling(state);
-}
-
-/**
- * @param {NodeState} after
- * @returns {NodeState}
- */
-function deepestDescendant(after) {
- if (after === null) {
- return after;
- } else if (after.type === FragmentType) {
- const { children } = /** @type {FragmentState} */ (after);
- if (children !== null) {
- return deepestDescendant(children[children.length - 1]);
- }
- } else if (after.type === ComponentType) {
- const { rendered } = /** @type {ComponentState} */ (after);
- if (rendered !== null) {
- return deepestDescendant(rendered);
- }
- }
- return after;
-}
-
-/**
- * @param {ElementState | TextState} element
- */
-function insertAfterSibling(element) {
- /** @type {NodeState} */
- let state = element;
- let after = deepestDescendant(state.after);
- do {
- if (after === null) {
- if (state.parent.type === ElementType) {
- break;
- }
- state = state.parent;
- after = state;
- }
- if (after.type !== ElementType && after.type !== TextType) {
- after = deepestDescendant(after.after);
- }
- } while (after === null || (after.type !== ElementType && after.type !== TextType));
-
- if (after !== null) {
- const { ref } = /** @type {ElementState | TextState} */ (after);
- ref.parentNode.insertBefore(element.ref, ref.nextSibling);
- } else {
- const { ref } = /** @type {ElementState} */ (state.parent);
- if (ref.childNodes.length) {
- ref.insertBefore(element.ref, ref.childNodes[0]);
- } else {
- ref.appendChild(element.ref);
- }
- }
-}
-
-/**
- * @param {ElementState} state
- */
-function removeElement(state) {
- state.ref.parentNode.removeChild(state.ref);
- state.ref = null;
-}
-
-/**
- * @param {ElementState} state
- */
-function addElement(state) {
- state.ref = document.createElement(state.shape);
- insertAfterSibling(state);
-}
-
-/**
- * @param {MemberState} state
- */
-function updateRef(state) {
- state.value(state.parent.ref);
-}
-
-/**
- * @param {MemberState} state
- */
-function unsetRef(state) {
- state.value(null);
-}
-
-function shallowEquals(a, b) {
- if (a === null || b === null) {
- return a === b;
- }
- for (const key of Object.keys(a)) {
- if (key in b) {
- if (a[key] !== b[key]) {
- return false;
- }
- } else {
- return false;
- }
- }
- for (const key of Object.keys(b)) {
- if (!(key in a)) {
- return false;
- }
- }
- return true;
-}
-
-/**
- * @callback StateAction
- * @param {S} state
- * @template {State} S
- */
-
-/**
- * @returns {Queue}
- */
-function newQueue() {
- return { prepare: [], changes: [], post: [] };
-}
-
-/**
- * @param {Queue} queue
- * @param {StateAction} fn
- * @param {S} state
- * @template {State} S
- */
-function enqueueChange(queue, fn, state) {
- queue.changes.push(fn, state);
-}
-
-/**
- * @param {Queue} queue
- * @param {StateAction} fn
- * @param {S} state
- * @template {State} S
- */
-function enqueuePost(queue, fn, state) {
- queue.post.push(fn, state);
-}
-
-/**
- * @typedef Queue
- * @property {Array} prepare
- * @property {Array} changes
- * @property {Array} post
- */
-
-/**
- * @callback DiffEntryFunction
- * @param {Queue} queue
- * @param {ElementState | FragmentState} parent
- * @param {StateType} nodeType
- * @param {Data} node
- * @returns {void}
- */
-
-/**
- * @callback InitNodeFunction
- * @param {Queue} queue
- * @param {NodeState} parent
- * @param {NodeState | null} after
- * @param {NodeNode} node
- * @returns {NodeState}
- */
-
-/**
- * @callback DiffFunction
- * @param {Queue} queue
- * @param {State} state
- * @param {Data} node
- * @returns {void}
- */
-
-/**
- * @callback TeardownFunction
- * @param {Queue} queue
- * @param {NodeState} state
- * @returns {void}
- */
-
-/**
- * @callback SoftTeardownFunction
- * @param {NodeState} state
- * @returns {void}
- */
-
-/**
- * @typedef NodeStateTypeGeneric
- * @property {Name} name
- * @property {DiffEntryFunction} diffEntry
- * @property {DiffFunction} diff
- * @property {InitNodeFunction} init
- * @property {TeardownFunction} teardown
- * @property {SoftTeardownFunction} softTeardown
- * @template {string | symbol} Name
- */
-
-/**
- * @typedef NodeStateTypeCreate
- * @property {Name} name
- * @property {(queue: Queue, parent: ElementState | FragmentState, meta: StateType, node: Node) => void} diffEntry
- * @property {(queue: Queue, state: S, node: Node) => void} diff
- * @property {(queue: Queue, parent: ElementState | FragmentState, after: NodeState, node: Node) => void} init
- * @property {(queue: Queue, state: S) => void} teardown
- * @property {(state: S) => void} softTeardown
- * @template {string | symbol} Name
- * @template {State} S
- * @template {Data} Node
- */
-
-/** @typedef {NodeStateTypeGeneric} ElementStateType */
-/** @typedef {NodeStateTypeGeneric} FragmentStateType */
-/** @typedef {NodeStateTypeGeneric} ComponentStateType */
-/** @typedef {NodeStateTypeGeneric} TextStateType */
-
-/** @typedef {ElementStateType | FragmentStateType| ComponentStateType | TextStateType} NodeStateType */
-
-/**
- * @callback InitMemberFunction
- * @param {ElementState} parent
- * @param {string} name
- * @returns {MemberState}
- */
-
-/**
- * @callback TeardownMemberFunction
- * @param {Queue} queue
- * @param {MemberState} member
- * @returns {void}
- */
-
-/**
- * @typedef MemberStateType
- * @property {string | symbol} name
- * @property {DiffEntryFunction} diffEntry
- * @property {DiffFunction} diff
- * @property {InitMemberFunction} init
- * @property {TeardownMemberFunction} teardown
- */
-
-/**
- * @typedef {NodeStateType | MemberStateType} StateType
- */
-
-/**
- * @typedef ElementState
- * @property {ElementStateType} type
- * @property {NodeState} parent
- * @property {NodeState | null} after
- * @property {string} shape
- * @property {null} content
- * @property {HTMLElement | null} ref
- * @property {MemberState[] | null} refHooks
- * @property {number} rewriteChildIndex
- * @property {NodeState[] | null} children
- * @property {number} rewriteMemberIndex
- * @property {MemberState[] | null} members
- */
-
-/**
- * @typedef FragmentState
- * @property {FragmentStateType} type
- * @property {NodeState} parent
- * @property {NodeState | null} after
- * @property {typeof FRAGMENT_TYPE_NAME} shape
- * @property {null} content
- * @property {number} rewriteChildIndex
- * @property {NodeState[] | null} children
- */
-
-/** @typedef {ElementState | FragmentState} ParentState */
-
-/**
- * @typedef ComponentState
- * @property {ComponentStateType} type
- * @property {NodeState} parent
- * @property {NodeState | null} after
- * @property {function} shape
- * @property {object} content
- * @property {NodeState | null} rendered
- */
-
-/**
- * @typedef TextState
- * @property {TextStateType} type
- * @property {NodeState} parent
- * @property {NodeState | null} after
- * @property {typeof TEXT_TYPE_NAME} shape
- * @property {string} content
- * @property {Text | null} ref
- */
-
-/**
- * @typedef {ElementState | FragmentState | ComponentState | TextState} NodeState
- */
-
-/**
- * @typedef MemberState
- * @property {MemberStateType} type
- * @property {ElementState} parent
- * @property {string} name
- * @property {*} value
- */
-
-/** @typedef {NodeState | MemberState} State */
-
-/** @typedef {typeof ELEMENT_TYPE_NAME | typeof FRAGMENT_TYPE_NAME | typeof COMPONENT_TYPE_NAME | typeof TEXT_TYPE_NAME} NodeNodeType */
-
-/** @typedef {typeof ATTRIBUTE_TYPE_NAME | typeof PROPERTY_TYPE_NAME | typeof REF_TYPE_NAME | typeof META_TYPE_NAME} MemberNodeType */
-
-/**
- * @typedef ElementNode
- * @property {typeof ELEMENT_TYPE_NAME} type
- * @property {string} shape
- * @property {Data[]} content
- */
-
-/**
- * @typedef FragmentNode
- * @property {typeof FRAGMENT_TYPE_NAME} type
- * @property {typeof FRAGMENT_TYPE_NAME} shape
- * @property {Data[]} content
- */
-
-/**
- * @typedef TextNode
- * @property {typeof TEXT_TYPE_NAME} type
- * @property {typeof TEXT_TYPE_NAME} shape
- * @property {string} content
- */
-
-/**
- * @typedef ComponentNode
- * @property {typeof COMPONENT_TYPE_NAME} type
- * @property {function} shape
- * @property {Data[]} content
- */
-
-/**
- * @typedef {ElementNode | FragmentNode | TextNode | ComponentNode} NodeNode
- */
-
-/**
- * @typedef MemberNode
- * @property {MemberNodeType} type
- * @property {string} name
- * @property {*} value
- */
-
-/** @typedef {NodeNode | MemberNode} Data */
diff --git a/docs/local-development.md b/docs/local-development.md
index 33fb84e45..5b494a9d5 100644
--- a/docs/local-development.md
+++ b/docs/local-development.md
@@ -17,6 +17,8 @@
yarn install
```
2. Set up local database using the instructions provided in [database.md](database.md).
+ - Note: You must run `yarn db-import-tests:dev` after setting up your database to import the latest test harness into
+ your project.
3. Run the server
```
yarn dev
diff --git a/server/migrations/20211116172219-commandSequences.js b/server/migrations/20211116172219-commandSequences.js
index 85d6243ac..610924c71 100644
--- a/server/migrations/20211116172219-commandSequences.js
+++ b/server/migrations/20211116172219-commandSequences.js
@@ -1,6 +1,6 @@
const { omit } = require('lodash');
const { TestPlanVersion } = require('../models');
-const commandList = require('../resources/commands.json');
+const commandList = require('../resources/commandsV1.json');
module.exports = {
up: async queryInterface => {
diff --git a/server/resolvers/helpers/retrieveCommands.js b/server/resolvers/helpers/retrieveCommands.js
index a7c42515b..846a404de 100644
--- a/server/resolvers/helpers/retrieveCommands.js
+++ b/server/resolvers/helpers/retrieveCommands.js
@@ -1,4 +1,4 @@
-const commands = require('../../resources/commands.json');
+const commandsV1 = require('../../resources/commandsV1.json');
const commandsV2 = require('../../resources/commandsV2.json');
function findValueByKey(keyMappings, keyToFindText) {
@@ -110,7 +110,7 @@ function findValuesByKeys(commandsMapping, keysToFind = []) {
}
const getCommandV1 = commandId => {
- return commands.find(command => command.id === commandId);
+ return commandsV1.find(command => command.id === commandId);
};
const getCommandV2 = commandId => {
diff --git a/server/resources/commands.json b/server/resources/commandsV1.json
similarity index 100%
rename from server/resources/commands.json
rename to server/resources/commandsV1.json
diff --git a/server/scripts/import-tests/index.js b/server/scripts/import-tests/index.js
index 17860e9c3..b432916ba 100644
--- a/server/scripts/import-tests/index.js
+++ b/server/scripts/import-tests/index.js
@@ -74,6 +74,8 @@ const importTestPlanVersions = async transaction => {
});
console.log('`npm run build` output', buildOutput.stdout.toString());
+ importHarness();
+
const { support } = await updateJsons();
const ats = await At.findAll();
@@ -251,6 +253,41 @@ const readDirectoryGitInfo = directoryPath => {
return { gitSha, gitMessage, gitCommitDate };
};
+const importHarness = () => {
+ const sourceFolder = path.resolve(`${testsDirectory}/resources`);
+ const targetFolder = path.resolve('../', 'client/resources');
+ console.info(`Updating harness directory, ${targetFolder} ...`);
+ fse.rmSync(targetFolder, { recursive: true, force: true });
+
+ // Copy source folder
+ console.info('Importing latest harness files ...');
+ fse.copySync(sourceFolder, targetFolder, {
+ filter: src => {
+ if (fse.lstatSync(src).isDirectory()) {
+ return true;
+ }
+ if (!src.includes('.html')) {
+ return true;
+ }
+ }
+ });
+
+ // Copy files
+ const commandsJson = 'commands.json';
+ const supportJson = 'support.json';
+ if (fse.existsSync(`${testsDirectory}/${commandsJson}`)) {
+ fse.copyFileSync(
+ `${testsDirectory}/${commandsJson}`,
+ `${targetFolder}/${commandsJson}`
+ );
+ }
+ fse.copyFileSync(
+ `${testsDirectory}/${supportJson}`,
+ `${targetFolder}/${supportJson}`
+ );
+ console.info('Harness files update complete.');
+};
+
const getAppUrl = (directoryRelativePath, { gitSha, directoryPath }) => {
return path.join(
'/',
@@ -318,7 +355,7 @@ const updateJsons = async () => {
// Write commands for v1 format
await fse.writeFile(
- path.resolve(__dirname, '../../resources/commands.json'),
+ path.resolve(__dirname, '../../resources/commandsV1.json'),
JSON.stringify(commands, null, 4)
);