Skip to content

Commit

Permalink
Support V2 Test Format build (#997)
Browse files Browse the repository at this point in the history
* Add scripts to convert a test plan from v1 to v2 format

* Add and revise support and commands JSON data

support.json:
* add strings to support screen reader settings and assertion tokens.
* Add URL data for ARIA and HTML-AAM specifications.

commands.json: Add all the command string representations necessary for the new V2 test format.

* WIP: Building test index page

* Adding additional TODOs to be covered; start to addressing review page

* Fix references after rebase

* Add additional data validations

* Use MUST/SHOULD/MAY on review page

* Update validations

* Now accounting for modeless ATs

* Additional validations

* Accounting for assertionExceptions

* Additional validations and accounting for assertionExceptions; review page format now also matches 977

* Keys handling in test-io-format.mjs

* Update support.json

* Remove 'v2-made' files

* Change 'NA' for modeless to 'defaultMode'

* Format .mjs files

* Fix lines in keys.mjs

* Remove create-example-tests2

* Revert keys.mjs quotations

* assertionTokens replacement

* show replaced assertion tokens in reviewed tests

* Conditionally show modeInstructions

* testing

* Set mode for commandInfo

* Better support for modeless in aria-at-test-io-format.mjs and aria-at-test-run.mjs

* Better support for modeless in test-reviewer.mjs

* Formatting

* Update v2maker to create a new row for commands previously defined as 'F / Shift + F' for eg.

* Avoid setupScript thrown error when looking for script file

* Formatting test-reviewer.mjs

* Handle unexpected cases with v2 maker related scripts

* Handle thrown error csv properties to pass validations (for now)

* Additional cleanup

* Fix pattern name identifier on index pagae

* Formatting

* Handle all 'at'-commands.csv files without looking specifically for known ats

* Additional error handling for missing at-commands.csv files

* Update jsdocs

* Formatting

* Conditionally build v2 over v1 test format

* Refactor name for clarity

* Edge case handling for test with multiple settings

* Optimize edge case handling

* Misc

* Fix showing mode instructions on review page

* Update validations

* Update error message

* Revert changes in package.json

* Fix issues with Delete command validation

* Remove unused function

* Ensure rows in *-commands.csv are maintained by the presentationNumber

* Fix case where titles and instructions were still unexpectedly referencing reading and interactions modes in v2maker script. This work has been duplicated in #990

* Fix review page to stop using generated commandPresentationNumbers

* Update aria-at-test-run.mjs to drop support for generated command sequence numbers

* Better support of 0-level assertion exceptions on review page

* Fix linter

* Remove 'modes' from v2 test file names

* Remove comment

* Add mode to individual *.collected.(json|html) files

* Fix '1 test's'' editorial error on review page

* Fix for presenting .collected.html when needed

* Add support for multiple settings with .collected

* Don't show assertionPhrase column on review page for v1 build

* Fix when commandListSettingsPreface gets appended

* Change from {at}-{settings} .collected files to a single {at} .collected file which covers all settings

* Fix combined .collected.(json|logic)

* Update results collection form to support pass/fail assertion verdicts (#1003)

* Initial rendering pass

* Initial interaction with assertion checkbox

* Cleanup and use assertionResponseQuestion

* Remove unused required validation, Ensure input fires state change event

* Use assertionResponseQuestion in v1 tests as well

---------

Co-authored-by: Matt King <[email protected]>
Co-authored-by: Stalgia Grigg <[email protected]>
Co-authored-by: Stalgia Grigg <[email protected]>
  • Loading branch information
4 people authored Nov 30, 2023
1 parent 5f904b9 commit 138d026
Show file tree
Hide file tree
Showing 22 changed files with 4,025 additions and 957 deletions.
26 changes: 25 additions & 1 deletion lib/data/command-tuples-at-mode-task-lookup.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,28 @@ function createCommandTuplesATModeTaskLookup(commands) {
return data;
}

exports.createCommandTuplesATModeTaskLookup = createCommandTuplesATModeTaskLookup;
function createAtCommandTuplesATSettingsTestIdLookupByPresentationNumber(commands) {
return commands.reduce((carry, command) => {
const commandTask = carry[command.testId] || {};
const commandTaskMode = commandTask[command.target.at.settings || 'defaultMode'] || {};
const commandTaskModeAT = commandTaskMode[command.target.at.key] || [];
const commandTuples = command.commands.map(({ id, presentationNumber }) => [
`${id}|${presentationNumber}`,
]);
return {
...carry,
[command.testId]: {
...commandTask,
[command.target.at.settings || 'defaultMode']: {
...commandTaskMode,
[command.target.at.key]: [...commandTaskModeAT, ...commandTuples],
},
},
};
}, {});
}

module.exports = {
createCommandTuplesATModeTaskLookup,
createAtCommandTuplesATSettingsTestIdLookupByPresentationNumber,
};
192 changes: 191 additions & 1 deletion lib/data/parse-command-csv-row.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,194 @@ function parseCommandCSVRow(commandRow) {
};
}

exports.parseCommandCSVRow = parseCommandCSVRow;
function flattenObject(obj, parentKey = '') {
const flattened = {};

for (const key in obj) {
if (typeof obj[key] === 'object') {
const subObject = flattenObject(obj[key], parentKey + key + '.');
Object.assign(flattened, subObject);
} else {
flattened[parentKey + key] = obj[key];
}
}

return flattened;
}

function sanitizeWhitespace(value) {
return value.replace(/\s+/g, ' ').trim();
}

function sanitizeCommand(command) {
return {
...command,
command: sanitizeWhitespace(command.command),
};
}

function findValueByKey(keyMappings, keyToFindText) {
const keyToFind = keyToFindText.replace(/\s+/g, ' ').trim();
const keyMap = Object.keys(keyMappings);

// Need to specially handle VO modifier key combination
if (keyToFind === 'vo')
return findValuesByKeys(keyMappings, [keyMappings['modifierAliases.vo']])[0];

if (keyToFind.includes('modifiers.') || keyToFind.includes('keys.')) {
const parts = keyToFind.split('.');
const keyToCheck = parts[parts.length - 1]; // value after the '.'

if (keyMappings[keyToFind])
return {
value: keyMappings[keyToFind],
key: keyToCheck,
};

return null;
}

for (const key of keyMap) {
const parts = key.split('.');
const parentKey = parts[0];
const keyToCheck = parts[parts.length - 1]; // value after the '.'

if (keyToCheck === keyToFind) {
if (parentKey === 'modifierAliases') {
return findValueByKey(keyMappings, `modifiers.${keyMappings[key]}`);
} else if (parentKey === 'keyAliases') {
return findValueByKey(keyMappings, `keys.${keyMappings[key]}`);
}

return {
value: keyMappings[key],
key: keyToCheck,
};
}
}

// Return null if the key is not found
return null;
}

function findValuesByKeys(commandsMapping, 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 = findValueByKey(commandsMapping, 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 = findValueByKey(commandsMapping, 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 = findValueByKey(commandsMapping, keyToFind);
if (keyToFindResult) result.push(keyToFindResult);
}
}

return result;
}

function parseCommandCSVRowV2({ commands }, commandsJson) {
const commandsParsed = [];
const flattenedCommandsJson = flattenObject(commandsJson);

function createParsedCommand(command, atTargetInfo) {
const commandIds = command.command.split(' ');
const commandKVs = findValuesByKeys(flattenedCommandsJson, [command.command]);

const assertionExceptions = command.assertionExceptions.length
? sanitizeWhitespace(command.assertionExceptions).split(' ')
: [];

const commands = commandKVs.map(e => ({
id: e.key,
keystroke: e.value,
keypresses: e.value.split(' then ').map((e, index) => ({
id: commandIds[index],
keystroke: e,
})),
presentationNumber: Number(command.presentationNumber),
assertionExceptions,
}));

// Also account for 'modeless' AT configurations with 'defaultMode'
const settingsValue = command.settings || 'defaultMode';

const foundCommandIndex = commandsParsed.findIndex(
e =>
e.testId === command.testId &&
e.target.at.key === atTargetInfo.key &&
e.target.at.settings === settingsValue &&
(e.commands.length
? e.commands.every(
({ presentationNumber }) =>
parseInt(presentationNumber) === parseInt(command.presentationNumber)
)
: true)
);

// Add new parsed command since none exist
if (foundCommandIndex < 0) {
commandsParsed.push({
testId: command.testId,
target: {
at: {
...atTargetInfo,
settings: settingsValue,
},
},
commands,
});
} else {
commandsParsed[foundCommandIndex].commands.push(...commands);
}
}

const generateCommandsParsed = (commands, key) => {
for (const command of commands) createParsedCommand(command, { key });
};

commands.forEach(command =>
generateCommandsParsed(command.commands.map(sanitizeCommand), command.atKey)
);

return commandsParsed;
}

module.exports = {
parseCommandCSVRow,
parseCommandCSVRowV2,
flattenObject,
};
2 changes: 2 additions & 0 deletions lib/data/parse-support.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ function parseSupport(supportRaw) {
ats: [at],
})),
],
testPlanStrings: supportRaw.testPlanStrings,
references: supportRaw.references,
};
}

Expand Down
Loading

0 comments on commit 138d026

Please sign in to comment.