Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create tests root record #907

Merged
merged 8 commits into from
Jul 16, 2024
Merged
135 changes: 73 additions & 62 deletions lib/data/process-test-directory-v1.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ const {
} = require('util');

const csv = require('csv-parser');
const fse = require('fs-extra');
const beautify = require('json-beautify');

const { validate } = require('../util/error');
Expand Down Expand Up @@ -75,36 +74,35 @@ const processTestDirectory = async ({ directory, args = {} }) => {

const validModes = ['reading', 'interaction', 'item'];

/** Name of the test plan. */
const testPlanName = path.basename(directory);

// cwd; @param rootDirectory is dependent on this file not moving from the `lib/data` folder
const libDataDirectory = path.dirname(__filename);
const rootDirectory = path.join(libDataDirectory, '../..');

const testsDirectory = path.join(rootDirectory, 'tests');
const testPlanDirectory = path.join(rootDirectory, directory);
const testPlanDirectory = path.join(rootDirectory, 'tests', testPlanName);

const resourcesDirectory = path.join(testsDirectory, 'resources');

const supportFilePath = path.join(testsDirectory, 'support.json');
const atCommandsCsvFilePath = path.join(testPlanDirectory, 'data', 'commands.csv');
const testsCsvFilePath = path.join(testPlanDirectory, 'data', 'testsV1.csv');
const referencesCsvFilePath = path.join(testPlanDirectory, 'data', 'referencesV1.csv');

// build output folders and file paths setup
const buildDirectory = path.join(rootDirectory, 'build');
const testPlanBuildDirectory = path.join(buildDirectory, directory);
const testPlanBuildDirectory = path.join(buildDirectory, 'tests', testPlanName);
const indexFileBuildOutputPath = path.join(testPlanBuildDirectory, 'index.html');

let backupTestsCsvFile, backupReferencesCsvFile;

// create build directory if it doesn't exist
fse.mkdirSync(buildDirectory, { recursive: true });

const existingBuildPromise = FileRecordChain.read(buildDirectory, {
glob: [
'',
'tests',
`tests/${path.basename(directory)}`,
`tests/${path.basename(directory)}/**`,
`tests/${testPlanName}`,
`tests/${testPlanName}/**`,
'tests/resources',
'tests/resources/*',
'tests/support.json',
Expand All @@ -120,14 +118,26 @@ const processTestDirectory = async ({ directory, args = {} }) => {
const scriptsRecord = testPlanRecord.find('data/js');
const resourcesRecord = resourcesOriginalRecord.filter({ glob: '{aria-at-*,keys,vrender}.mjs' });

// Filter out reference html files with inline scripts. Files that are not
// regenerated will be removed from the filesystem.
const testPlanUpdate = await testPlanRecord.walk(record => {
if (record.entries) {
return {
...record,
entries: record.entries.filter(record => !isScriptedReferenceRecord(record)),
};
}
return record;
});

const newBuild = new FileRecordChain({
entries: [
{
name: 'tests',
entries: [
{
name: path.basename(directory),
entries: testPlanRecord.filter({ glob: 'reference{,/**}' }).record.entries,
name: testPlanName,
entries: testPlanUpdate.filter({ glob: 'reference{,/**}' }).record.entries,
},
{ name: 'resources', ...resourcesRecord.record },
{ name: 'support.json', ...supportRecord.record },
Expand Down Expand Up @@ -574,7 +584,6 @@ ${rows}
var errorCount = 0;
var errors = '';
var indexOfURLs = [];
var checkedSourceHtmlScriptFiles = [];

function addTestError(id, error) {
errorCount += 1;
Expand All @@ -583,27 +592,21 @@ ${rows}

function addCommandError({ testId, task }, key) {
errorCount += 1;
errors += `[Command]: The key reference "${key}" found in "${directory}/data/commands.csv" for "test id ${testId}: ${task}" is invalid. Command may not be defined in "tests/resources/keys.mjs".\n`;
errors += `[Command]: The key reference "${key}" found in "tests/${testPlanName}/data/commands.csv" for "test id ${testId}: ${task}" is invalid. Command may not be defined in "tests/resources/keys.mjs".\n`;
}

const newTestPlan = newBuild.find(`tests/${path.basename(testPlanBuildDirectory)}`);
const newTestPlan = newBuild.find(`tests/${testPlanName}`);

function emitFile(filepath, content) {
newTestPlan.add(path.relative(testPlanBuildDirectory, filepath), {
buffer: toBuffer(content),
});
}

function generateSourceHtmlScriptFile(filePath, content) {
// check that test plan's reference html file path is generated file
if (
filePath.includes('reference') &&
(filePath.split(path.sep).pop().match(/\./g) || []).length > 1
) {
// generate file at `<root>/tests/<directory>/reference/<path>/<directory>.<script>.html
const sourceFilePath = filePath.replace(`build${path.sep}`, '');
emitFile(sourceFilePath, content);
checkedSourceHtmlScriptFiles.push(sourceFilePath);
}
testPlanUpdate.add(path.relative(testPlanDirectory, filePath), {
buffer: toBuffer(content),
});
}

// intended to be an internal helper to reduce some code duplication and make logging for csv errors simpler
Expand Down Expand Up @@ -742,12 +745,14 @@ ${rows}
? referenceQueryable.where({ refId: 'reference' }).value
: '';
if (!examplePathOriginal) {
log.error(`ERROR: Valid 'reference' value not found in "${directory}/data/referencesV1.csv".`);
log.error(
`ERROR: Valid 'reference' value not found in "tests/${testPlanName}/data/referencesV1.csv".`
);
}
const exampleRecord = testPlanRecord.find(examplePathOriginal);
if (!exampleRecord.isFile()) {
log.error(
`ERROR: Invalid 'reference' value path "${examplePathOriginal}" found in "${directory}/data/referencesV1.csv".`
`ERROR: Invalid 'reference' value path "${examplePathOriginal}" found in "tests/${testPlanName}/data/referencesV1.csv".`
);
}
const testLookups = {
Expand All @@ -763,21 +768,23 @@ ${rows}
})
);

const examplePathDirectory = path.dirname(examplePathOriginal);
const examplePathBaseName = path.basename(examplePathOriginal, '.html');
/** @type {function(string): string} */
const examplePathTemplate = scriptName =>
path.join(
path.dirname(examplePathOriginal),
`${path.basename(examplePathOriginal, '.html')}${scriptName ? `.${scriptName}` : ''}.html`
examplePathDirectory,
`${examplePathBaseName}${scriptName ? `.${scriptName}` : ''}.html`
);
const exampleTemplate = validate.reportTo(
reason => log.warning(`[${examplePathOriginal}]: ${reason.message}`),
() => createExampleScriptsTemplate(exampleRecord)
);
const exampleScriptedFiles = [{ name: '', source: '' }, ...scripts].map(({ name, source }) => ({
name,
path: examplePathTemplate(name),
content: exampleTemplate.render(exampleTemplateParams(name, source)).toString(),
}));
const plainScriptedFile = createExampleScriptedFile('', examplePathTemplate, exampleTemplate, '');
const scriptedFiles = scripts.map(({ name, source }) =>
createExampleScriptedFile(name, examplePathTemplate, exampleTemplate, source)
);
const exampleScriptedFiles = [plainScriptedFile, ...scriptedFiles];
const exampleScriptedFilesQueryable = Queryable.from('example', exampleScriptedFiles);

const commandQueryable = Queryable.from('command', commandsValidated);
Expand All @@ -797,10 +804,10 @@ ${rows}
);
});

const files = [
const buildFiles = [
...createScriptFiles(scripts, testPlanBuildDirectory),
...exampleScriptedFiles.map(({ path: pathSuffix, content }) => ({
path: path.join('build', 'tests', path.basename(directory), pathSuffix),
path: path.join('build', 'tests', testPlanName, pathSuffix),
content,
})),
...testsCollected.map(collectedTest =>
Expand All @@ -810,27 +817,12 @@ ${rows}
createCollectedTestHtmlFile(collectedTest, testPlanBuildDirectory)
),
];
files.forEach(file => {
generateSourceHtmlScriptFile(file.path, file.content);
return emitFile(file.path, file.content);
buildFiles.forEach(file => {
emitFile(file.path, file.content);
});
scriptedFiles.forEach(file => {
generateSourceHtmlScriptFile(path.join('tests', testPlanName, file.path), file.content);
});

if (checkedSourceHtmlScriptFiles.length) {
const sourceFolder = checkedSourceHtmlScriptFiles[0]
.split(path.sep)
.slice(0, -1)
.join(path.sep);
fse.readdirSync(sourceFolder).forEach(function (file) {
const filePath = path.join(sourceFolder, file);
// check that test plan's reference html file path is generated file
if (file.includes('.html') && (file.split(path.sep).pop().match(/\./g) || []).length > 1) {
// remove generated html files from source which include scripts which are no longer generated
if (!checkedSourceHtmlScriptFiles.includes(filePath)) {
fse.rmSync(path.join(sourceFolder, file));
}
}
});
}

const atCommandsMap = createCommandTuplesATModeTaskLookup(commandsValidated);
emitFile(
Expand Down Expand Up @@ -866,20 +858,23 @@ ${rows}
emitFile,
});

const existingBuild = await existingBuildPromise;
if (!existingBuild.isDirectory()) {
log.error(`The destination 'build' directory does not exist.`);
}
const existingRoot = new FileRecordChain({});
existingRoot.add('build', await existingBuildPromise);
existingRoot.add(`tests/${testPlanName}`, testPlanRecord);

const buildChanges = newBuild.changesAfter(existingBuild);
const newRoot = new FileRecordChain({});
newRoot.add('build', newBuild);
newRoot.add(`tests/${testPlanName}`, testPlanUpdate);

const buildChanges = newRoot.changesAfter(existingRoot);

if (!VALIDATE_CHECK) {
await buildChanges.commit(buildDirectory);
await buildChanges.commit(rootDirectory);
}

if (errorCount) {
log.warning(
`*** ${errorCount} Errors in tests and/or commands in test plan [${directory}] ***`
`*** ${errorCount} Errors in tests and/or commands in test plan [tests/${testPlanName}] ***`
);
log.warning(errors);
} else {
Expand Down Expand Up @@ -1346,6 +1341,22 @@ function createCollectedTestHtmlFile(test, testPlanBuildDirectory) {
};
}

function isScriptedReferenceRecord(subrecord) {
return (
subrecord.name &&
subrecord.name.endsWith('.html') &&
subrecord.name.indexOf('.') !== subrecord.name.lastIndexOf('.')
);
}

function createExampleScriptedFile(name, examplePathTemplate, exampleTemplate, source) {
return {
name,
path: examplePathTemplate(name),
content: exampleTemplate.render(exampleTemplateParams(name, source)).toString(),
};
}

function toBuffer(content) {
if (Buffer.isBuffer(content) || isArrayBufferView(content) || isArrayBuffer(content)) {
return content;
Expand Down
Loading
Loading