From 760cab1497035189ba9d0671f33b4bf1a93f70dd Mon Sep 17 00:00:00 2001 From: Devon Powell Date: Tue, 30 Jul 2024 14:14:16 -0400 Subject: [PATCH] Add test reporting to component generation --- package.json | 1 + src/create.js | 46 ++++++++++++++++--- src/generators/demo/index.js | 3 +- src/generators/test-reporting/index.js | 19 ++++++++ .../_d2l-test-reporting.config.json | 4 ++ .../templates/configured/_gitignore | 1 + src/generators/test-unit-axe/index.js | 21 ++++++++- .../_d2l-test-reporting.config.json | 12 +++++ .../.github/workflows/ci-test-reporting.yml | 46 +++++++++++++++++++ src/generators/test-vdiff/index.js | 20 +++++++- .../_d2l-test-reporting.config.json | 8 ++++ .../workflows/vdiff-test-reporting.yml | 27 +++++++++++ src/generators/wc-lit-element/index.js | 3 +- src/helper.js | 45 +++++++++--------- 14 files changed, 220 insertions(+), 36 deletions(-) create mode 100644 src/generators/test-reporting/index.js create mode 100644 src/generators/test-reporting/templates/configured/_d2l-test-reporting.config.json create mode 100644 src/generators/test-reporting/templates/configured/_gitignore create mode 100644 src/generators/test-unit-axe/templates/configured/_d2l-test-reporting.config.json create mode 100644 src/generators/test-unit-axe/templates/static/.github/workflows/ci-test-reporting.yml create mode 100644 src/generators/test-vdiff/templates/configured/_d2l-test-reporting.config.json create mode 100644 src/generators/test-vdiff/templates/static/.github/workflows/vdiff-test-reporting.yml diff --git a/package.json b/package.json index 54c1df7..478ff4d 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "author": "D2L Corporation", "license": "Apache-2.0", "dependencies": { + "deepmerge": "^4", "prompts": "^2" }, "devDependencies": { diff --git a/src/create.js b/src/create.js index c3b4dbb..6460db4 100755 --- a/src/create.js +++ b/src/create.js @@ -8,8 +8,9 @@ import { run as setupElement } from './generators/wc-lit-element/index.js'; import { run as setupLocalization } from './generators/localization/index.js'; import { run as setupRelease } from './generators/release/index.js'; import { run as setupStaticSite } from './generators/static-site/index.js'; +import { run as setupTestReporting } from './generators/test-reporting/index.js'; import { run as setupTestUnitAxe } from './generators/test-unit-axe/index.js'; -import { run as setupTestVdiff } from './generators/test-vdiff/index.js'; +import { run as setupTestVDiff } from './generators/test-vdiff/index.js'; const generatorTypes = { component: 'component', @@ -78,7 +79,7 @@ async function getGeneratorType() { } async function getComponentOptions() { - const questions = [ + let templateData = await prompts([ { type: 'text', name: 'hyphenatedName', @@ -121,12 +122,45 @@ async function getComponentOptions() { { title: 'No', value: false } ] }, - ]; - return await prompts(questions, { + { + type: 'select', + name: 'testReporting', + message: 'Would you like test reporting set up?', + choices: [ + { title: 'Yes', value: true }, + { title: 'No', value: false } + ] + } + ], { onCancel: () => { process.exit(); }, }); + + if (templateData.testReporting) { + const testReportingTemplateData = await prompts([ + { + type: 'text', + name: 'testReportingTool', + message: 'What is your components tool based on the taxonomy in https://expanse.desire2learn.com/pages/source/source.html' + }, { + type: 'text', + name: 'testReportingExperience', + message: 'What is your components experience based on the taxonomy in https://expanse.desire2learn.com/pages/source/source.html' + }, + ], { + onCancel: () => { + process.exit(); + }, + }); + + templateData = { + ...templateData, + ...testReportingTemplateData + }; + } + + return templateData; } async function executeComponentGenerator() { @@ -148,12 +182,12 @@ async function executeComponentGenerator() { setupDefaultContent(options); setupElement(options); + if (options.testReporting) setupTestReporting(options); setupTestUnitAxe(options); - if (options.vdiff) setupTestVdiff(options); + if (options.vdiff) setupTestVDiff(options); setupDemo(options); if (options.localization) setupLocalization(options); setupRelease(options); - } async function executeStaticSiteGenerator() { diff --git a/src/generators/demo/index.js b/src/generators/demo/index.js index f7281b3..3a2c666 100644 --- a/src/generators/demo/index.js +++ b/src/generators/demo/index.js @@ -1,4 +1,4 @@ -import { copyFile, getDestinationPath, mergeJSON, mergeText, replaceText } from '../../helper.js'; +import { copyFile, getDestinationPath, mergeJSON, mergeText, replaceText, sortJSONMembers } from '../../helper.js'; export function run(templateData) { mergeJSON( @@ -15,4 +15,5 @@ export function run(templateData) { `${getDestinationPath(templateData.hyphenatedName)}/demo/index.html` ); replaceText(`${getDestinationPath(templateData.hyphenatedName)}/demo/index.html`, templateData); + sortJSONMembers(`${getDestinationPath(templateData.hyphenatedName)}/package.json`, ['dependencies', 'devDependencies']); } diff --git a/src/generators/test-reporting/index.js b/src/generators/test-reporting/index.js new file mode 100644 index 0000000..5fabc7e --- /dev/null +++ b/src/generators/test-reporting/index.js @@ -0,0 +1,19 @@ +import { copyFile, getDestinationPath, mergeText, replaceText } from '../../helper.js'; + +export function run(templateData) { + templateData = { + ...templateData, + testReportingTool: templateData.testReportingTool?.trim() || 'Unknown', + testReportingExperience: templateData.testReportingExperience?.trim() || 'Unknown' + }; + + mergeText( + `${__dirname}/templates/configured/_gitignore`, + `${getDestinationPath(templateData.hyphenatedName)}/.gitignore` + ); + copyFile( + `${__dirname}/templates/configured/_d2l-test-reporting.config.json`, + `${getDestinationPath(templateData.hyphenatedName)}/d2l-test-reporting.config.json` + ); + replaceText(`${getDestinationPath(templateData.hyphenatedName)}/d2l-test-reporting.config.json`, templateData); +} diff --git a/src/generators/test-reporting/templates/configured/_d2l-test-reporting.config.json b/src/generators/test-reporting/templates/configured/_d2l-test-reporting.config.json new file mode 100644 index 0000000..e3986bf --- /dev/null +++ b/src/generators/test-reporting/templates/configured/_d2l-test-reporting.config.json @@ -0,0 +1,4 @@ +{ + "tool": "<%= testReportingTool %>", + "experience": "<%= testReportingExperience %>" +} diff --git a/src/generators/test-reporting/templates/configured/_gitignore b/src/generators/test-reporting/templates/configured/_gitignore new file mode 100644 index 0000000..40f4b5c --- /dev/null +++ b/src/generators/test-reporting/templates/configured/_gitignore @@ -0,0 +1 @@ +d2l-test-report.json diff --git a/src/generators/test-unit-axe/index.js b/src/generators/test-unit-axe/index.js index 006f8cd..b17f9cb 100644 --- a/src/generators/test-unit-axe/index.js +++ b/src/generators/test-unit-axe/index.js @@ -1,4 +1,4 @@ -import { copyFile, copyFilesInDir, getDestinationPath, mergeJSON, mergeText, replaceText } from '../../helper.js'; +import { copyFile, getDestinationPath, mergeJSON, mergeText, replaceText, sortJSONMembers } from '../../helper.js'; export function run(templateData) { mergeJSON( @@ -17,6 +17,23 @@ export function run(templateData) { `${__dirname}/templates/configured/_element.axe.js`, `${getDestinationPath(templateData.hyphenatedName)}/test/${templateData.hyphenatedName}.axe.js` ); + + if (templateData.testReporting) { + mergeJSON( + `${__dirname}/templates/configured/_d2l-test-reporting.config.json`, + `${getDestinationPath(templateData.hyphenatedName)}/d2l-test-reporting.config.json` + ); + copyFile( + `${__dirname}/templates/static/.github/workflows/ci-test-reporting.yml`, + `${getDestinationPath(templateData.hyphenatedName)}/.github/workflows/ci.yml` + ); + } else { + copyFile( + `${__dirname}/templates/static/.github/workflows/ci.yml`, + `${getDestinationPath(templateData.hyphenatedName)}/.github/workflows/ci.yml` + ); + } + replaceText(`${getDestinationPath(templateData.hyphenatedName)}/test/${templateData.hyphenatedName}.test.js`, templateData); - copyFilesInDir(`${__dirname}/templates/static`, getDestinationPath(templateData.hyphenatedName)); + sortJSONMembers(`${getDestinationPath(templateData.hyphenatedName)}/package.json`, ['dependencies', 'devDependencies']); } diff --git a/src/generators/test-unit-axe/templates/configured/_d2l-test-reporting.config.json b/src/generators/test-unit-axe/templates/configured/_d2l-test-reporting.config.json new file mode 100644 index 0000000..75ecb05 --- /dev/null +++ b/src/generators/test-unit-axe/templates/configured/_d2l-test-reporting.config.json @@ -0,0 +1,12 @@ +{ + "overrides": [ + { + "pattern": "**/test/*.test.js", + "type": "UI Unit" + }, + { + "pattern": "**/test/*.axe.js", + "type": "UI Accessibility" + } + ] +} diff --git a/src/generators/test-unit-axe/templates/static/.github/workflows/ci-test-reporting.yml b/src/generators/test-unit-axe/templates/static/.github/workflows/ci-test-reporting.yml new file mode 100644 index 0000000..b6d6337 --- /dev/null +++ b/src/generators/test-unit-axe/templates/static/.github/workflows/ci-test-reporting.yml @@ -0,0 +1,46 @@ +name: CI +on: pull_request +jobs: + test: + name: Test + timeout-minutes: 5 + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: Brightspace/third-party-actions@actions/checkout + - name: Setup node + uses: Brightspace/third-party-actions@actions/setup-node + with: + node-version-file: .nvmrc + - name: Install dependencies + run: npm install + - name: Lint (JavaScript) + run: npm run lint:eslint + - name: Lint (CSS) + run: npm run lint:style + - name: Accessibility tests + id: at + run: npm run test:axe + - name: Upload test report + if: > + always() && + github.triggering_actor != 'dependabot[bot]' && + (steps.at.outcome == 'failure' || steps.at.outcome == 'success') + uses: Brightspace/test-reporting-action@main + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-session-token: ${{ secrets.AWS_SESSION_TOKEN }} + - name: Unit tests + id: ut + run: npm run test:unit + - name: Upload test report + if: > + always() && + github.triggering_actor != 'dependabot[bot]' && + (steps.ut.outcome == 'failure' || steps.ut.outcome == 'success') + uses: Brightspace/test-reporting-action@main + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-session-token: ${{ secrets.AWS_SESSION_TOKEN }} diff --git a/src/generators/test-vdiff/index.js b/src/generators/test-vdiff/index.js index 273f515..0dba782 100644 --- a/src/generators/test-vdiff/index.js +++ b/src/generators/test-vdiff/index.js @@ -1,4 +1,4 @@ -import { copyFile, copyFilesInDir, getDestinationPath, mergeJSON, mergeText, replaceText } from '../../helper.js'; +import { copyFile, getDestinationPath, mergeJSON, mergeText, replaceText, sortJSONMembers } from '../../helper.js'; export function run(templateData) { mergeJSON( @@ -20,5 +20,21 @@ export function run(templateData) { ); replaceText(`${getDestinationPath(templateData.hyphenatedName)}/test/${templateData.hyphenatedName}.vdiff.js`, templateData); - copyFilesInDir(`${__dirname}/templates/static`, getDestinationPath(templateData.hyphenatedName)); + if (templateData.testReporting) { + mergeJSON( + `${__dirname}/templates/configured/_d2l-test-reporting.config.json`, + `${getDestinationPath(templateData.hyphenatedName)}/d2l-test-reporting.config.json` + ); + copyFile( + `${__dirname}/templates/static/.github/workflows/vdiff-test-reporting.yml`, + `${getDestinationPath(templateData.hyphenatedName)}/.github/workflows/vdiff.yml` + ); + } else { + copyFile( + `${__dirname}/templates/static/.github/workflows/vdiff.yml`, + `${getDestinationPath(templateData.hyphenatedName)}/.github/workflows/vdiff.yml` + ); + } + + sortJSONMembers(`${getDestinationPath(templateData.hyphenatedName)}/package.json`, ['dependencies', 'devDependencies']); } diff --git a/src/generators/test-vdiff/templates/configured/_d2l-test-reporting.config.json b/src/generators/test-vdiff/templates/configured/_d2l-test-reporting.config.json new file mode 100644 index 0000000..600f92d --- /dev/null +++ b/src/generators/test-vdiff/templates/configured/_d2l-test-reporting.config.json @@ -0,0 +1,8 @@ +{ + "overrides": [ + { + "pattern": "**/test/*.vdiff.js", + "type": "UI Visual Diff" + } + ] +} diff --git a/src/generators/test-vdiff/templates/static/.github/workflows/vdiff-test-reporting.yml b/src/generators/test-vdiff/templates/static/.github/workflows/vdiff-test-reporting.yml new file mode 100644 index 0000000..91a9a76 --- /dev/null +++ b/src/generators/test-vdiff/templates/static/.github/workflows/vdiff-test-reporting.yml @@ -0,0 +1,27 @@ +name: vdiff +on: pull_request +jobs: + vdiff: + timeout-minutes: 5 + runs-on: ubuntu-latest + steps: + - uses: Brightspace/third-party-actions@actions/checkout + - uses: Brightspace/third-party-actions@actions/setup-node + with: + node-version-file: .nvmrc + - name: Install Dependencies + run: npm install + - name: vdiff Tests + uses: BrightspaceUI/actions/vdiff@main + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-session-token: ${{ secrets.AWS_SESSION_TOKEN }} + github-token: ${{ secrets.GITHUB_TOKEN }} + - name: Upload test report + if: failure() || success() + uses: Brightspace/test-reporting-action@main + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-session-token: ${{ secrets.AWS_SESSION_TOKEN }} diff --git a/src/generators/wc-lit-element/index.js b/src/generators/wc-lit-element/index.js index 3439c96..62f6c16 100644 --- a/src/generators/wc-lit-element/index.js +++ b/src/generators/wc-lit-element/index.js @@ -1,4 +1,4 @@ -import { copyFile, copyFilesInDir, getDestinationPath, mergeJSON, replaceText } from '../../helper.js'; +import { copyFile, copyFilesInDir, getDestinationPath, mergeJSON, replaceText, sortJSONMembers } from '../../helper.js'; export function run(templateData) { mergeJSON( @@ -20,4 +20,5 @@ export function run(templateData) { replaceText(`${getDestinationPath(templateData.hyphenatedName)}/${templateData.hyphenatedName}.js`, templateDataElement); copyFilesInDir(`${__dirname}/templates/static`, getDestinationPath(templateData.hyphenatedName)); + sortJSONMembers(`${getDestinationPath(templateData.hyphenatedName)}/package.json`, ['dependencies', 'devDependencies']); } diff --git a/src/helper.js b/src/helper.js index 65a2bd7..b0dcbd6 100644 --- a/src/helper.js +++ b/src/helper.js @@ -1,3 +1,4 @@ +import deepMerge from 'deepmerge'; import fs from 'fs'; import path from 'path'; @@ -11,31 +12,9 @@ export function mergeJSON(filePathNewJSON, filePathOriginalJSON) { const newContentJSON = JSON.parse(newContent); const originalContentJSON = JSON.parse(originalContent); + const mergedContentJSON = deepMerge(originalContentJSON, newContentJSON); - Object.keys(originalContentJSON).forEach(key => { - if (newContentJSON[key]) { - let changes = false; - Object.keys(newContentJSON[key]).forEach(subkey => { - originalContentJSON[key][subkey] = newContentJSON[key][subkey]; - changes = true; - }); - if (changes) { - const ordered = {}; - Object.keys(originalContentJSON[key]).sort().forEach(subkey => { - ordered[subkey] = originalContentJSON[key][subkey]; - }); - originalContentJSON[key] = ordered; - } - } - }); - - Object.keys(newContentJSON).forEach(key => { - if (!originalContentJSON[key]) { - originalContentJSON[key] = newContentJSON[key]; - } - }); - - fs.writeFileSync(filePathOriginalJSON, JSON.stringify(originalContentJSON, null, 2)); + fs.writeFileSync(filePathOriginalJSON, JSON.stringify(mergedContentJSON, null, 2)); } export function mergeText(filePathNewText, filePathOriginalText) { @@ -81,6 +60,24 @@ export function replaceText(source, replacements) { fs.writeFileSync(source, result, 'utf8'); } +export const sortJSONMembers = (source, members) => { + const fileContent = fs.readFileSync(source, 'utf8'); + const result = JSON.parse(fileContent); + + members.forEach(member => { + if (!result[member]) { + return; + } + const ordered = {}; + Object.keys(result[member]).sort().forEach(key => { + ordered[key] = result[member][key]; + }); + result[member] = ordered; + }); + + fs.writeFileSync(source, JSON.stringify(result, null, 2)); +}; + function copyAndProcessFile(sourceRoot, destinationRoot, relativePath, fileName, plugins = []) { const context = { sourceRoot,