diff --git a/lib/data/process-test-directory-v1.js b/lib/data/process-test-directory-v1.js index c286e2c78..b20b4f066 100644 --- a/lib/data/process-test-directory-v1.js +++ b/lib/data/process-test-directory-v1.js @@ -414,7 +414,7 @@ const processTestDirectory = async ({ directory, args = {} }) => { applies_to: appliesTo, mode: mode, task: task, - assertionResponseQuestion: supportJson.testPlanStrings.assertionResponseQuestion, + testPlanStrings: supportJson.testPlanStrings, specific_user_instruction: test.instructions, output_assertions: assertions, }; diff --git a/lib/data/process-test-directory.js b/lib/data/process-test-directory.js index a0c126cfa..c23bb56a3 100644 --- a/lib/data/process-test-directory.js +++ b/lib/data/process-test-directory.js @@ -528,7 +528,7 @@ const processTestDirectory = async ({ directory, args = {} }) => { setup_script_description: getSetupScriptDescription(test.setupScript.scriptDescription), setupTestPage: test.setupScript.script, specific_user_instruction: test.instructions, - assertionResponseQuestion: supportJson.testPlanStrings.assertionResponseQuestion, + testPlanStrings: supportJson.testPlanStrings, commandsInfo: test.commandsInfo, output_assertions: test.assertions, }; diff --git a/scripts/test-reviewer.mjs b/scripts/test-reviewer.mjs index 7cb63e1d4..0678d4a4c 100644 --- a/scripts/test-reviewer.mjs +++ b/scripts/test-reviewer.mjs @@ -133,7 +133,6 @@ fse.readdirSync(testsBuildDirectory).forEach(function (directory) { commandListPreface, commandListSettingsPreface, settingInstructionsPreface, - assertionResponseQuestion, }, } = support; referencesData = referencesData.map( diff --git a/tests/resources/aria-at-harness.mjs b/tests/resources/aria-at-harness.mjs index ef3ffd202..c33da2cfc 100644 --- a/tests/resources/aria-at-harness.mjs +++ b/tests/resources/aria-at-harness.mjs @@ -361,7 +361,10 @@ function renderVirtualInstructionDocument(doc) { instructCommands(doc.instructions.instructions), - instructAssertions(doc.instructions.assertions), + // Contains the details on "Success Criteria" which is no longer needed + // instructAssertions(doc.instructions.assertions), + + instructSettings(doc.instructions.settings), button( disabled(!doc.instructions.openTestPage.enabled), @@ -532,14 +535,14 @@ function renderVirtualInstructionDocument(doc) { function instructCommands({ header, instructions, - strongInstructions: boldInstructions, + // strongInstructions: boldInstructions, commands, }) { return fragment( h2(rich(header)), ol( ...map(instructions, compose(li, rich)), - ...map(boldInstructions, compose(li, em, rich)), + // ...map(boldInstructions, compose(li, em, rich)), li(rich(commands.description), ul(...map(commands.commands, compose(li, em, rich)))) ) ); @@ -550,8 +553,9 @@ function renderVirtualInstructionDocument(doc) { */ function instructionHeader({ header, description }) { return fragment( - h1(id('behavior-header'), tabIndex('0'), focus(header.focus), rich(header.header)), - p(rich(description)) + h1(id('behavior-header'), tabIndex('0'), focus(header.focus), rich(header.header)) + // No longer needed; contains the old `With {AT} in {atMode} mode, describe how {AT} behaves when performing task, {task}` + // p(rich(description)) ); } @@ -565,6 +569,15 @@ function renderVirtualInstructionDocument(doc) { ol(...map(assertions, compose(li, em, rich))) ); } + + /** + * @param {InstructionDocumentInstructionsSettings} settings + */ + function instructSettings(settings) { + return Object.values(settings).map(({ screenText, instructions }) => { + return fragment(p(rich(screenText)), ol(...map(instructions, compose(li, rich)))); + }); + } } /** diff --git a/tests/resources/aria-at-test-io-format.mjs b/tests/resources/aria-at-test-io-format.mjs index 22b60873b..15bc7dc48 100644 --- a/tests/resources/aria-at-test-io-format.mjs +++ b/tests/resources/aria-at-test-io-format.mjs @@ -20,6 +20,19 @@ const UNEXPECTED_BEHAVIORS = [ 'Browser crashed', ]; +const normalizeString = str => + str.replace( + /&|<|>|'|"/g, + str => + ({ + '&': '&', + '<': '<', + '>': '>', + ''': "'", + '"': '"', + }[str] || str) + ); + /** Depends on ConfigInput. */ class KeysInput { /** @@ -767,7 +780,15 @@ class BehaviorInput { const mode = Array.isArray(json.mode) ? json.mode[0] : json.mode; const at = configInput.at(); - const { commandsAndSettings } = commandsInput.getCommands({ task: json.task }, mode); + let { commandsAndSettings } = commandsInput.getCommands({ task: json.task }, mode); + commandsAndSettings = commandsAndSettings.map(cs => { + return { + ...cs, + settingsInstructions: cs.settingsInstructions + ? cs.settingsInstructions.map(el => normalizeString(el)) + : null, + }; + }); // Use to determine assertionExceptions const commandsInfo = json.commandsInfo?.[at.key]; @@ -782,7 +803,13 @@ class BehaviorInput { specificUserInstruction: json.specific_user_instruction, setupScriptDescription: json.setup_script_description, setupTestPage: json.setupTestPage, - assertionResponseQuestion: json.assertionResponseQuestion, + testPlanStrings: { + openExampleInstruction: normalizeString(json.testPlanStrings.openExampleInstruction), + commandListPreface: json.testPlanStrings.commandListPreface, + commandListSettingsPreface: json.testPlanStrings.commandListSettingsPreface, + settingInstructionsPreface: json.testPlanStrings.settingInstructionsPreface, + assertionResponseQuestion: json.testPlanStrings.assertionResponseQuestion, + }, commands: commandsAndSettings.map(cs => { const foundCommandInfo = commandsInfo?.find( c => @@ -848,10 +875,18 @@ class BehaviorInput { { commandsInput, keysInput, unexpectedInput } ) { // v1:info.task, v2: info.testId | v1:target.mode, v2:target.at.settings - const { commandsAndSettings } = commandsInput.getCommands( + let { commandsAndSettings } = commandsInput.getCommands( { task: info.task || info.testId }, target.mode || target.at.settings ); + commandsAndSettings = commandsAndSettings.map(cs => { + return { + ...cs, + settingsInstructions: cs.settingsInstructions + ? cs.settingsInstructions.map(el => normalizeString(el)) + : null, + }; + }); return new BehaviorInput({ behavior: { @@ -861,8 +896,20 @@ class BehaviorInput { modeInstructions: instructions.mode, appliesTo: [target.at.name], specificUserInstruction: instructions.raw || instructions.instructions, - setupScriptDescription: target.setupScript ? target.setupScript.description : '', + // TODO: Make 'setupScript.description' the only attribute + setupScriptDescription: target.setupScript + ? target.setupScript.description || target.setupScript.scriptDescription + : '', setupTestPage: target.setupScript ? target.setupScript.name : undefined, + testPlanStrings: { + 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', + }, commands: commandsAndSettings.map(cs => { const foundCommandInfo = commands.find( c => cs.commandId === c.id && cs.settings === c.settings @@ -1227,7 +1274,7 @@ export class TestRunInputOutput { openTest: { enabled: true, }, - assertionResponseQuestion: test.assertionResponseQuestion, + testPlanStrings: test.testPlanStrings, commands: test.commands.map( command => /** @type {import("./aria-at-test-run.mjs").TestRunCommand} */ ({ @@ -1765,6 +1812,15 @@ function invariant(test, message, ...args) { * @property {string} refIds */ +/** + * @typedef CommandInfo + * @property {string} assertionExceptions + * @property {string} command + * @property {Number} presentationNumber + * @property {string} settings + * @property {string} testID + */ + /** * @typedef BehaviorJSON * @property {string} setup_script_description @@ -1773,6 +1829,8 @@ function invariant(test, message, ...args) { * @property {ATMode | ATMode[]} mode * @property {string} task * @property {string} specific_user_instruction + * @property {Object} commandsInfo + * @property {object} testPlanStrings * @property {[string, string][] | [OutputAssertion]} [output_assertions] * @property {{[atKey: string]: [number, string][]}} [additional_assertions] */ diff --git a/tests/resources/aria-at-test-run.mjs b/tests/resources/aria-at-test-run.mjs index d1882a3c6..acee70d2a 100644 --- a/tests/resources/aria-at-test-run.mjs +++ b/tests/resources/aria-at-test-run.mjs @@ -93,9 +93,7 @@ export function instructionDocument(resultState, hooks) { 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; + const setupScriptDescription = ` ${resultState.info.setupScriptDescription}`; // As a hack, special case mode instructions for VoiceOver for macOS until we // support modeless tests. const modePhrase = @@ -138,14 +136,24 @@ export function instructionDocument(resultState, hooks) { return resultArray.length ? resultArray : null; } - const convertedModeInstructions = - modeInstructions !== undefined && !modeInstructions.includes('undefined') - ? convertModeInstructionsToKbdArray(modeInstructions) - : null; - - let strongInstructions = [...userInstructions]; - if (convertedModeInstructions) - strongInstructions = [convertedModeInstructions, ...strongInstructions]; + const commandListInstructions = `${resultState.testPlanStrings.commandListPreface}${ + commands.some( + (command, index) => + commandSettings[index]?.description !== 'defaultMode' && commandSettings[index]?.text + ) + ? ` ${resultState.testPlanStrings.commandListSettingsPreface}` + : '' + }`; + + const instructionsForSettings = {}; + commandSettings.forEach(({ description: settings, text: screenText, instructions }) => { + if (settings && settings !== 'defaultMode' && !instructionsForSettings[settings]) { + instructionsForSettings[settings] = { + instructions: instructions.map(el => convertModeInstructionsToKbdArray(el)), + screenText: `${resultState.testPlanStrings.settingInstructionsPreface} ${screenText}:`, + }; + } + }); return { errors: { @@ -155,34 +163,35 @@ export function instructionDocument(resultState, hooks) { }, instructions: { header: { - header: `Testing task: ${resultState.info.description}`, + header: `Test: ${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', + header: 'Instructions', instructions: [ [ - `Restore default settings for ${resultState.config.at.name}. For help, read `, + `Configure ${resultState.config.at.name} with default settings. 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}`, + `${resultState.testPlanStrings.openExampleInstruction}${setupScriptDescription}`, ], - strongInstructions: strongInstructions.filter(el => el), + strongInstructions: [...userInstructions].filter(el => el), commands: { - description: `Using the following commands, ${lastInstruction}`, + description: `${lastInstruction} ${commandListInstructions}`, commands: commands.map((command, index) => { - const { description: settings, text: settingsText } = commandSettings[index]; + const { description: settings, text: screenText } = commandSettings[index]; return `${command}${ - settingsText && settings !== 'defaultMode' ? ` (${settingsText})` : '' + screenText && settings !== 'defaultMode' ? ` (${screenText})` : '' }`; }), }, }, + settings: instructionsForSettings, 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:`, @@ -243,7 +252,7 @@ export function instructionDocument(resultState, hooks) { change: atOutput => hooks.setCommandOutput({ commandIndex, atOutput }), }, assertionsHeader: { - descriptionHeader: `${resultState.assertionResponseQuestion} ${command}${ + descriptionHeader: `${resultState.testPlanStrings.assertionResponseQuestion} ${command}${ settingsText && settings !== 'defaultMode' ? ` (${settingsText})` : '' }?`, }, @@ -1230,6 +1239,7 @@ export function userValidateState() { * @property {boolean} config.displaySubmitButton * @property {TestRunUserAction} currentUserAction * @property {TestRunCommand[]} commands + * @property {object} testPlanStrings * @property {object} openTest * @property {boolean} openTest.enabled */