diff --git a/.editorconfig b/.editorconfig index b661cc969..19113ae09 100644 --- a/.editorconfig +++ b/.editorconfig @@ -22,7 +22,7 @@ trim_trailing_whitespace = true [*.{html,css,js}] indent_style = space -indent_size = 4 +indent_size = 2 [*.md] trim_trailing_whitespace = false diff --git a/.eslintrc.json b/.eslintrc.json index f869dbd39..774709ebc 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,36 +1,62 @@ { - "env": { - "browser": true, - "es6": true, - "node": true, - "jest/globals": true + "env": { + "browser": true, + "es6": true, + "node": true, + "jest/globals": true + }, + "extends": [ + "eslint:recommended", + "plugin:react/recommended", + "prettier", + "plugin:prettier/recommended", + "plugin:jest/recommended" + ], + "parserOptions": { + "ecmaFeatures": { + "jsx": true }, - "extends": [ - "eslint:recommended", - "plugin:react/recommended", - "prettier", - "plugin:prettier/recommended", - "plugin:jest/recommended" + "ecmaVersion": 2020, + "sourceType": "module" + }, + "plugins": [ + "react", + "json", + "prettier", + "jest" + ], + "rules": { + "linebreak-style": [ + "error", + "unix" ], - "parserOptions": { - "ecmaFeatures": { - "jsx": true - }, - "ecmaVersion": 2020, - "sourceType": "module" - }, - "plugins": ["react", "json", "prettier", "jest"], - "rules": { - "linebreak-style": ["error", "unix"], - "semi": ["error", "always"], - "eol-last": ["error", "always"], - "no-console": ["error", { "allow": ["warn", "error"] }], - "no-use-before-define": ["off"], - "react/display-name": ["off"] - }, - "settings": { - "react": { - "version": "detect" - } + "semi": [ + "error", + "always" + ], + "eol-last": [ + "error", + "always" + ], + "no-console": [ + "error", + { + "allow": [ + "warn", + "error" + ] + } + ], + "no-use-before-define": [ + "off" + ], + "react/display-name": [ + "off" + ] + }, + "settings": { + "react": { + "version": "detect" } + } } diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d4a4245f0..fb5e4c7fa 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,22 +6,6 @@ updates: schedule: interval: "monthly" - - package-ecosystem: "npm" - open-pull-requests-limit: 5 - directory: "/client" - schedule: - interval: "monthly" - commit-message: - prefix: "client" - - - package-ecosystem: "npm" - open-pull-requests-limit: 5 - directory: "/server" - schedule: - interval: "monthly" - commit-message: - prefix: "server" - - package-ecosystem: "github-actions" open-pull-requests-limit: 99 directory: "/" diff --git a/.github/workflows/runtest.yml b/.github/workflows/runtest.yml index 9e07ee492..d249e401a 100644 --- a/.github/workflows/runtest.yml +++ b/.github/workflows/runtest.yml @@ -7,9 +7,9 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Checkout the repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Install NodeJS 18 - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: '18' cache: 'npm' diff --git a/.prettierrc b/.prettierrc index e0fe2cf91..f05cc577a 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,6 +1,6 @@ { "singleQuote": true, - "tabWidth": 4, + "tabWidth": 2, "arrowParens": "avoid", "trailingComma": "none" } \ No newline at end of file diff --git a/admins.txt b/admins.txt index c0096bf4a..03aea1b5f 100644 --- a/admins.txt +++ b/admins.txt @@ -5,17 +5,15 @@ boazsender mfairchild365 gnarf jugglinmike -mzgoddard isaacdurazo sinabahram -evmiguel -alflennik jscholes howard-e mcking65 -lolaodelola stalgiag IsaDC ccanash Paul-Clue gracemccants +outofambit +ChrisC diff --git a/client/.storybook/main.js b/client/.storybook/main.js index 47b716e96..7e913453b 100644 --- a/client/.storybook/main.js +++ b/client/.storybook/main.js @@ -1,7 +1,7 @@ module.exports = { - core: { - builder: 'webpack5' - }, - stories: ['../stories/*.stories.jsx'], - addons: ['@storybook/addon-a11y', '@storybook/addon-controls'] + core: { + builder: 'webpack5' + }, + stories: ['../stories/*.stories.jsx'], + addons: ['@storybook/addon-a11y', '@storybook/addon-controls'] }; diff --git a/client/babel.config.json b/client/babel.config.json index 7d523fbe8..972938f03 100644 --- a/client/babel.config.json +++ b/client/babel.config.json @@ -1,4 +1,9 @@ { - "presets": ["@babel/env", "@babel/preset-react"], - "plugins": ["lodash"] + "presets": [ + "@babel/preset-env", + "@babel/preset-react" + ], + "plugins": [ + "lodash" + ] } diff --git a/client/components/AddTestToQueueWithConfirmation/AddTestToQueueWithConfirmation.css b/client/components/AddTestToQueueWithConfirmation/AddTestToQueueWithConfirmation.css index 47313b99a..8afcbafcb 100644 --- a/client/components/AddTestToQueueWithConfirmation/AddTestToQueueWithConfirmation.css +++ b/client/components/AddTestToQueueWithConfirmation/AddTestToQueueWithConfirmation.css @@ -1,3 +1,3 @@ .add-test-to-queue-confirmation { - max-width: clamp(40vw, 300px, 800px); + max-width: clamp(40vw, 300px, 800px); } diff --git a/client/components/AddTestToQueueWithConfirmation/index.jsx b/client/components/AddTestToQueueWithConfirmation/index.jsx index 9f8670edf..45c6931fb 100644 --- a/client/components/AddTestToQueueWithConfirmation/index.jsx +++ b/client/components/AddTestToQueueWithConfirmation/index.jsx @@ -3,348 +3,358 @@ import PropTypes from 'prop-types'; import { Button } from 'react-bootstrap'; import BasicModal from '../common/BasicModal'; import { useMutation, useQuery } from '@apollo/client'; -import { ADD_TEST_QUEUE_MUTATION } from '../TestQueue/queries'; import { LoadingStatus, useTriggerLoad } from '../common/LoadingStatus'; import { - getBotUsernameFromAtBrowser, - isSupportedByResponseCollector + getBotUsernameFromAtBrowser, + isSupportedByResponseCollector } from '../../utils/automation'; import './AddTestToQueueWithConfirmation.css'; import { - SCHEDULE_COLLECTION_JOB_MUTATION, - EXISTING_TEST_PLAN_REPORTS + SCHEDULE_COLLECTION_JOB_MUTATION, + EXISTING_TEST_PLAN_REPORTS, + ADD_TEST_QUEUE_MUTATION } from './queries'; -import { TEST_QUEUE_PAGE_QUERY } from '../TestQueue2/queries'; +import { TEST_QUEUE_PAGE_QUERY } from '../TestQueue/queries'; import { TEST_PLAN_REPORT_STATUS_DIALOG_QUERY } from '../TestPlanReportStatusDialog/queries'; import { ME_QUERY } from '../App/queries'; function AddTestToQueueWithConfirmation({ - testPlanVersion, - browser, - at, - exactAtVersion, - minimumAtVersion, - disabled = false, - buttonText = 'Add to Test Queue', - triggerUpdate = () => {} + testPlanVersion, + browser, + at, + exactAtVersion, + minimumAtVersion, + disabled = false, + buttonText = 'Add to Test Queue', + triggerUpdate = () => {} }) { - const [showPreserveReportDataMessage, setShowPreserveReportDataMessage] = - useState(false); - const [showConfirmation, setShowConfirmation] = useState(false); - const [canUseOldResults, setCanUseOldResults] = useState(false); + const [showPreserveReportDataMessage, setShowPreserveReportDataMessage] = + useState(false); + const [showConfirmation, setShowConfirmation] = useState(false); + const [canUseOldResults, setCanUseOldResults] = useState(false); - const [addTestPlanReport] = useMutation(ADD_TEST_QUEUE_MUTATION, { - refetchQueries: [ - ME_QUERY, - EXISTING_TEST_PLAN_REPORTS, - TEST_QUEUE_PAGE_QUERY, - TEST_PLAN_REPORT_STATUS_DIALOG_QUERY - ], - awaitRefetchQueries: true - }); + const [addTestPlanReport] = useMutation(ADD_TEST_QUEUE_MUTATION, { + refetchQueries: [ + ME_QUERY, + EXISTING_TEST_PLAN_REPORTS, + TEST_QUEUE_PAGE_QUERY, + TEST_PLAN_REPORT_STATUS_DIALOG_QUERY + ], + awaitRefetchQueries: true + }); - const [scheduleCollection] = useMutation(SCHEDULE_COLLECTION_JOB_MUTATION, { - refetchQueries: [ - ME_QUERY, - EXISTING_TEST_PLAN_REPORTS, - TEST_QUEUE_PAGE_QUERY, - TEST_PLAN_REPORT_STATUS_DIALOG_QUERY - ], - awaitRefetchQueries: true - }); + const [scheduleCollection] = useMutation(SCHEDULE_COLLECTION_JOB_MUTATION, { + refetchQueries: [ + ME_QUERY, + EXISTING_TEST_PLAN_REPORTS, + TEST_QUEUE_PAGE_QUERY, + TEST_PLAN_REPORT_STATUS_DIALOG_QUERY + ], + awaitRefetchQueries: true + }); - const { data: existingTestPlanReportsData } = useQuery( - EXISTING_TEST_PLAN_REPORTS, - { - variables: { - testPlanVersionId: testPlanVersion?.id, - directory: testPlanVersion?.testPlan?.directory - }, - fetchPolicy: 'cache-and-network', - skip: !testPlanVersion?.id - } - ); + const { data: existingTestPlanReportsData } = useQuery( + EXISTING_TEST_PLAN_REPORTS, + { + variables: { + testPlanVersionId: testPlanVersion?.id, + directory: testPlanVersion?.testPlan?.directory + }, + fetchPolicy: 'cache-and-network', + skip: !testPlanVersion?.id + } + ); - const existingTestPlanReports = - existingTestPlanReportsData?.existingTestPlanVersion?.testPlanReports; + const existingTestPlanReports = + existingTestPlanReportsData?.existingTestPlanVersion?.testPlanReports; - let latestOldVersion; - let oldReportToCopyResultsFrom; + let latestOldVersion; + let oldReportToCopyResultsFrom; - // Check if any results data available from a previous result using the - // same testFormatVersion - const oldTestPlanVersions = - existingTestPlanReportsData?.oldTestPlanVersions?.filter( - ({ metadata }) => { - return ( - metadata.testFormatVersion === - existingTestPlanReportsData?.existingTestPlanVersion - ?.metadata.testFormatVersion - ); - } - ) || []; + // Check if any results data available from a previous result using the + // same testFormatVersion + const oldTestPlanVersions = + existingTestPlanReportsData?.oldTestPlanVersions?.filter(({ metadata }) => { + return ( + metadata.testFormatVersion === + existingTestPlanReportsData?.existingTestPlanVersion?.metadata + .testFormatVersion + ); + }) || []; - if (oldTestPlanVersions?.length) { - latestOldVersion = oldTestPlanVersions?.reduce((a, b) => - new Date(a.updatedAt) > new Date(b.updatedAt) ? a : b - ); - if ( - new Date(latestOldVersion?.updatedAt) < - new Date(testPlanVersion?.updatedAt) - ) { - oldReportToCopyResultsFrom = latestOldVersion?.testPlanReports.some( - ({ at: { id: atId }, browser: { id: browserId } }) => - atId == at?.id && browserId == browser?.id - ); - } + if (oldTestPlanVersions?.length) { + latestOldVersion = oldTestPlanVersions?.reduce((a, b) => + new Date(a.updatedAt) > new Date(b.updatedAt) ? a : b + ); + if ( + new Date(latestOldVersion?.updatedAt) < + new Date(testPlanVersion?.updatedAt) + ) { + oldReportToCopyResultsFrom = latestOldVersion?.testPlanReports.some( + ({ at: { id: atId }, browser: { id: browserId } }) => + atId == at?.id && browserId == browser?.id + ); } + } - const { triggerLoad, loadingMessage } = useTriggerLoad(); - const buttonRef = useRef(); + const { triggerLoad, loadingMessage } = useTriggerLoad(); + const buttonRef = useRef(); - const hasAutomationSupport = isSupportedByResponseCollector({ - at, - browser - }); + const hasAutomationSupport = isSupportedByResponseCollector({ + at, + browser, + minimumAtVersion, + exactAtVersion + }); - const alreadyHasBotInTestPlanReport = useMemo( - () => - existingTestPlanReports?.some( - tpr => - tpr.markedFinalAt === null && - tpr.draftTestPlanRuns.some(run => run.initiatedByAutomation) - ), - [existingTestPlanReports] - ); + const alreadyHasBotInTestPlanReport = useMemo( + () => + existingTestPlanReports?.some( + tpr => + tpr.markedFinalAt === null && + tpr.draftTestPlanRuns.some(run => run.initiatedByAutomation) + ), + [existingTestPlanReports] + ); - const feedbackModalTitle = - hasAutomationSupport && testPlanVersion - ? `Adding ${testPlanVersion.title} Test Plan to the Test Queue` - : 'Successfully Added Test Plan'; + const feedbackModalTitle = + hasAutomationSupport && testPlanVersion + ? `Adding ${testPlanVersion.title} Test Plan to the Test Queue` + : 'Successfully Added Test Plan'; - const feedbackModalContent = hasAutomationSupport ? ( - <> - Choose how the report for {at?.name} and {browser?.name} will be - generated. Add it to the queue so it can be assigned to a tester at - a later time or start running automated response collection with{' '} - {getBotUsernameFromAtBrowser(at, browser)}. - - ) : ( - <> - Successfully added {testPlanVersion?.title} for{' '} - - {at?.name} and {browser?.name} - {' '} - to the Test Queue. - - ); + const feedbackModalContent = hasAutomationSupport ? ( + <> + Choose how the report for {at?.name} and {browser?.name} will be + generated. Add it to the queue so it can be assigned to a tester at a + later time or start running automated response collection with{' '} + {getBotUsernameFromAtBrowser(at, browser)}. + + ) : ( + <> + Successfully added {testPlanVersion?.title} for{' '} + + {at?.name} and {browser?.name} + {' '} + to the Test Queue. + + ); - const closeWithUpdate = async () => { - setShowConfirmation(false); - await triggerUpdate(); - await new Promise(resolve => setTimeout(resolve, 0)); - if (buttonRef?.current) { - buttonRef.current.focus(); - } - }; + const closeWithUpdate = async () => { + setShowConfirmation(false); + await triggerUpdate(); + await new Promise(resolve => setTimeout(resolve, 0)); + if (buttonRef?.current) { + buttonRef.current.focus(); + } + }; - const renderConfirmation = () => { - const actions = []; - if (hasAutomationSupport) { - actions.push({ - label: 'Add and run later', - onClick: async () => { - try { - await addTestToQueue( - canUseOldResults - ? { - copyResultsFromTestPlanVersionId: - latestOldVersion.id - } - : {} - ); - await closeWithUpdate(); - } catch (e) { - console.error(e); - } - } - }); + const renderConfirmation = () => { + const actions = []; + if (hasAutomationSupport) { + actions.push({ + label: 'Add and run later', + onClick: async () => { + try { + await addTestToQueue( + canUseOldResults + ? { + copyResultsFromTestPlanVersionId: latestOldVersion.id + } + : {} + ); + await closeWithUpdate(); + } catch (e) { + console.error(e); + } + } + }); - if (!alreadyHasBotInTestPlanReport) { - actions.push({ - label: 'Add and run with bot', - onClick: async () => { - try { - const testPlanReport = await addTestToQueue( - canUseOldResults - ? { - copyResultsFromTestPlanVersionId: - latestOldVersion.id - } - : {} - ); - await scheduleCollectionJob(testPlanReport); - await closeWithUpdate(); - } catch (e) { - console.error(e); - } + if (!alreadyHasBotInTestPlanReport) { + actions.push({ + label: 'Add and run with bot', + onClick: async () => { + try { + const testPlanReport = await addTestToQueue( + canUseOldResults + ? { + copyResultsFromTestPlanVersionId: latestOldVersion.id } - }); + : {} + ); + await scheduleCollectionJob(testPlanReport); + await closeWithUpdate(); + } catch (e) { + console.error(e); } - } - return ( - { - if (hasAutomationSupport) { - setShowConfirmation(false); - } else { - await closeWithUpdate(); - } - }} - /> - ); - }; + } + }); + } + } + return ( + { + if (hasAutomationSupport) { + setShowConfirmation(false); + } else { + await closeWithUpdate(); + } + }} + /> + ); + }; - const renderPreserveReportDataDialog = () => { - return ( - { - setShowPreserveReportDataMessage(false); - if (hasAutomationSupport) { - setShowConfirmation(true); - } else { - await addTestToQueue(); - } - } - }, - { - label: 'Copy older results', - onClick: async () => { - setShowPreserveReportDataMessage(false); - setCanUseOldResults(true); + const renderPreserveReportDataDialog = () => { + return ( + { + setShowPreserveReportDataMessage(false); + if (hasAutomationSupport) { + setShowConfirmation(true); + } else { + await addTestToQueue(); + } + } + }, + { + label: 'Copy older results', + onClick: async () => { + setShowPreserveReportDataMessage(false); + setCanUseOldResults(true); - if (hasAutomationSupport) { - setShowConfirmation(true); - } else { - await addTestToQueue({ - copyResultsFromTestPlanVersionId: - latestOldVersion.id - }); - } - } - } - ]} - useOnHide - handleClose={async () => { - setShowPreserveReportDataMessage(false); - }} - /> - ); - }; + if (hasAutomationSupport) { + setShowConfirmation(true); + } else { + await addTestToQueue({ + copyResultsFromTestPlanVersionId: latestOldVersion.id + }); + } + } + } + ]} + useOnHide + handleClose={async () => { + setShowPreserveReportDataMessage(false); + }} + /> + ); + }; - const addTestToQueue = async ({ - copyResultsFromTestPlanVersionId - } = {}) => { - let tpr; - await triggerLoad(async () => { - const res = await addTestPlanReport({ - variables: { - testPlanVersionId: testPlanVersion.id, - atId: at.id, - minimumAtVersionId: minimumAtVersion?.id, - exactAtVersionId: exactAtVersion?.id, - browserId: browser.id, - copyResultsFromTestPlanVersionId - } - }); - const testPlanReport = - res?.data?.createTestPlanReport?.testPlanReport ?? null; - tpr = testPlanReport; - }, 'Adding Test Plan to Test Queue'); - setShowConfirmation(true); - return tpr; - }; + const addTestToQueue = async ({ copyResultsFromTestPlanVersionId } = {}) => { + let tpr; + await triggerLoad(async () => { + const res = await addTestPlanReport({ + variables: { + testPlanVersionId: testPlanVersion.id, + atId: at.id, + minimumAtVersionId: minimumAtVersion?.id, + exactAtVersionId: exactAtVersion?.id, + browserId: browser.id, + copyResultsFromTestPlanVersionId + } + }); + const testPlanReport = + res?.data?.createTestPlanReport?.testPlanReport ?? null; + tpr = testPlanReport; + }, 'Adding Test Plan to Test Queue'); + setShowConfirmation(true); + return tpr; + }; - const scheduleCollectionJob = async testPlanReport => { - await triggerLoad(async () => { - await scheduleCollection({ - variables: { - testPlanReportId: testPlanReport.id - } - }); - }, 'Scheduling Collection Job'); - setShowConfirmation(true); - }; + const scheduleCollectionJob = async testPlanReport => { + await triggerLoad(async () => { + await scheduleCollection({ + variables: { + testPlanReportId: testPlanReport.id + } + }); + }, 'Scheduling Collection Job'); + setShowConfirmation(true); + }; - return ( - - - {renderPreserveReportDataDialog()} - {renderConfirmation()} - - ); + return ( + + + {renderPreserveReportDataDialog()} + {renderConfirmation()} + + ); } AddTestToQueueWithConfirmation.propTypes = { - testPlanVersion: PropTypes.object, - browser: PropTypes.shape({ - id: PropTypes.string.isRequired, - key: PropTypes.string.isRequired, - name: PropTypes.string.isRequired - }), - at: PropTypes.shape({ + testPlanVersion: PropTypes.object, + browser: PropTypes.shape({ + id: PropTypes.string.isRequired, + key: PropTypes.string.isRequired, + name: PropTypes.string.isRequired + }), + at: PropTypes.shape({ + id: PropTypes.string.isRequired, + key: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + atVersions: PropTypes.arrayOf( + PropTypes.shape({ id: PropTypes.string.isRequired, - key: PropTypes.string.isRequired, - name: PropTypes.string.isRequired - }), - exactAtVersion: PropTypes.object, - minimumAtVersion: PropTypes.object, - buttonRef: PropTypes.object, - onFocus: PropTypes.func, - onBlur: PropTypes.func, - disabled: PropTypes.bool, - buttonText: PropTypes.string, - triggerUpdate: PropTypes.func + name: PropTypes.string.isRequired, + supportedByAutomation: PropTypes.bool.isRequired + }) + ) + }), + exactAtVersion: PropTypes.shape({ + id: PropTypes.string, + name: PropTypes.string, + supportedByAutomation: PropTypes.bool + }), + minimumAtVersion: PropTypes.shape({ + id: PropTypes.string, + name: PropTypes.string, + supportedByAutomation: PropTypes.bool + }), + buttonRef: PropTypes.object, + onFocus: PropTypes.func, + onBlur: PropTypes.func, + disabled: PropTypes.bool, + buttonText: PropTypes.string, + triggerUpdate: PropTypes.func }; export default AddTestToQueueWithConfirmation; diff --git a/client/components/AddTestToQueueWithConfirmation/queries.js b/client/components/AddTestToQueueWithConfirmation/queries.js index 8aafafdf6..fef0e528d 100644 --- a/client/components/AddTestToQueueWithConfirmation/queries.js +++ b/client/components/AddTestToQueueWithConfirmation/queries.js @@ -1,53 +1,85 @@ import { gql } from '@apollo/client'; export const SCHEDULE_COLLECTION_JOB_MUTATION = gql` - mutation ScheduleCollectionJob($testPlanReportId: ID!) { - scheduleCollectionJob(testPlanReportId: $testPlanReportId) { - id - status - } + mutation ScheduleCollectionJob($testPlanReportId: ID!) { + scheduleCollectionJob(testPlanReportId: $testPlanReportId) { + id + status } + } `; export const EXISTING_TEST_PLAN_REPORTS = gql` - query ExistingTestPlanReports( - $testPlanVersionId: ID! - $directory: String! + query ExistingTestPlanReports($testPlanVersionId: ID!, $directory: String!) { + existingTestPlanVersion: testPlanVersion(id: $testPlanVersionId) { + id + testPlanReports { + id + markedFinalAt + isFinal + draftTestPlanRuns { + initiatedByAutomation + } + at { + id + } + browser { + id + } + } + metadata + } + oldTestPlanVersions: testPlanVersions( + phases: [CANDIDATE, RECOMMENDED] + directory: $directory + ) { + id + updatedAt + testPlanReports { + id + at { + id + } + browser { + id + } + } + metadata + } + } +`; + +export const ADD_TEST_QUEUE_MUTATION = gql` + mutation AddTestPlanReport( + $testPlanVersionId: ID! + $atId: ID! + $exactAtVersionId: ID + $minimumAtVersionId: ID + $browserId: ID! + $copyResultsFromTestPlanVersionId: ID + ) { + createTestPlanReport( + input: { + testPlanVersionId: $testPlanVersionId + atId: $atId + exactAtVersionId: $exactAtVersionId + minimumAtVersionId: $minimumAtVersionId + browserId: $browserId + copyResultsFromTestPlanVersionId: $copyResultsFromTestPlanVersionId + } ) { - existingTestPlanVersion: testPlanVersion(id: $testPlanVersionId) { - id - testPlanReports { - id - markedFinalAt - isFinal - draftTestPlanRuns { - initiatedByAutomation - } - at { - id - } - browser { - id - } - } - metadata + testPlanReport { + id + at { + id } - oldTestPlanVersions: testPlanVersions( - phases: [CANDIDATE, RECOMMENDED] - directory: $directory - ) { - id - updatedAt - testPlanReports { - id - at { - id - } - browser { - id - } - } - metadata + browser { + id } + } + testPlanVersion { + id + } } + } `; diff --git a/client/components/App/App.css b/client/components/App/App.css index 920654a10..862482592 100644 --- a/client/components/App/App.css +++ b/client/components/App/App.css @@ -1,24 +1,24 @@ #root { - width: 100%; - height: 100vh; - background: #eaeaea; + width: 100%; + height: 100vh; + background: #eaeaea; } #root > div, main { - overflow: visible; + overflow: visible; } main { - background: white; - height: 100%; + background: white; + height: 100%; } nav.navbar, main.container, .test-run-container { - max-width: 1400px; - margin: auto; + max-width: 1400px; + margin: auto; } .container-fluid, @@ -26,216 +26,216 @@ main.container, .container-md, .container-lg, .container-xl { - padding: 0rem; + padding: 0rem; } .container { - background-color: white; + background-color: white; } nav.navbar { - padding: 0.5rem 1rem; + padding: 0.5rem 1rem; } .nav-link { - position: relative; + position: relative; } .nav-link:hover { - color: #000; - text-decoration: none; + color: #000; + text-decoration: none; } a.nav-link { - color: #0b60ab; + color: #0b60ab; } .nav-link[aria-current='true'] { - color: #000; + color: #000; } .nav-link:hover:after { - background: #cfcece; + background: #cfcece; } .nav-link[aria-current='true']:after { - background: #0b60ab; + background: #0b60ab; } @media all and (min-width: 1200px) { - .navbar-expand-lg .navbar-collapse ul { - display: flex; - flex-basis: auto; - margin-bottom: 0; - } - .nav-link[aria-current='true']:after, - .nav-link:hover:after { - position: absolute; - content: ''; - width: 90%; - height: 2px; - left: 0; - bottom: -12px; - right: 0; - margin: auto; - } + .navbar-expand-lg .navbar-collapse ul { + display: flex; + flex-basis: auto; + margin-bottom: 0; + } + .nav-link[aria-current='true']:after, + .nav-link:hover:after { + position: absolute; + content: ''; + width: 90%; + height: 2px; + left: 0; + bottom: -12px; + right: 0; + margin: auto; + } } @media all and (max-width: 1199px) { - .nav-link[aria-current='true']:after, - .nav-link:hover:after { - position: absolute; - content: ''; - width: 100%; - height: 2px; - left: 0; - bottom: 6px; - } + .nav-link[aria-current='true']:after, + .nav-link:hover:after { + position: absolute; + content: ''; + width: 100%; + height: 2px; + left: 0; + bottom: 6px; + } } main.container-fluid .row { - padding: 0.5rem 1rem; + padding: 0.5rem 1rem; } .row { - margin: 0; + margin: 0; } nav .logo.navbar-brand { - font-weight: 700; - font-size: 1.6em; - /* The logo is focused on every page navigation, making this particular + font-weight: 700; + font-size: 1.6em; + /* The logo is focused on every page navigation, making this particular outline quite distracting. It looks like part of the logo design. Furthermore, the top left of the page is where focus is implicitly expected to be, so the outline is not really adding anything. And Browsers are not consistently showing the outline anyway. */ - outline: none; + outline: none; } a { - color: #0b60ab; - text-decoration: none; + color: #0b60ab; + text-decoration: none; } a:hover { - color: #214e86; - text-decoration: underline; + color: #214e86; + text-decoration: underline; } h1 { - margin-bottom: 0.5em; - border-bottom: 1px solid gainsboro; - padding-bottom: 0.25em; - font-size: 2em; + margin-bottom: 0.5em; + border-bottom: 1px solid gainsboro; + padding-bottom: 0.25em; + font-size: 2em; } h2 { - margin: 1.5em 0 0.5em; - font-size: 1.5em; + margin: 1.5em 0 0.5em; + font-size: 1.5em; } h3 { - margin: 1em 0 0.5em; - font-size: 1.25em; + margin: 1em 0 0.5em; + font-size: 1.25em; } h4 { - margin: 1em 0 0.5em; - font-size: 1em; - font-weight: 600; + margin: 1em 0 0.5em; + font-size: 1em; + font-weight: 600; } h5 { - margin: 1em 0 0.5em; - font-size: 1em; - font-weight: 600; + margin: 1em 0 0.5em; + font-size: 1em; + font-weight: 600; } button { - margin: 0.25em 0; + margin: 0.25em 0; } .table thead th { - border-bottom: 0; - background: #e9ebee; + border-bottom: 0; + background: #e9ebee; } table tbody th { - font-weight: 400; + font-weight: 400; } table tbody tr { - background-color: #fafbfc; + background-color: #fafbfc; } table tbody tr:nth-of-type(odd), .table-striped tbody tr:nth-of-type(odd) { - background-color: #fff; + background-color: #fff; } .table-bordered th, .table-bordered td { - border: 1px solid #d2d5d9; + border: 1px solid #d2d5d9; } .table td { - padding: 0.75rem; + padding: 0.75rem; } table.table-hover tbody tr:hover, table tbody tr:hover { - background-color: #f6f8fa; + background-color: #f6f8fa; } table .form-control { - width: auto; - border: 1px solid #d2d5d9; + width: auto; + border: 1px solid #d2d5d9; } table label { - margin-bottom: 0; + margin-bottom: 0; } table label input { - margin-right: 0.5em; + margin-right: 0.5em; } table tbody tr.assign-testers { - background-color: #f6f8fa; + background-color: #f6f8fa; } .table ul { - margin-bottom: 0; + margin-bottom: 0; } ol { - padding-left: 1.75em; + padding-left: 1.75em; } ul { - padding-left: 0; + padding-left: 0; } ul li { - list-style: none; + list-style: none; } .col-md-9 li { - padding-right: 0.5em; + padding-right: 0.5em; } /* BUTTONS */ /* Button Primary */ .btn { - font-size: 0.9rem; - font-weight: 500; + font-size: 0.9rem; + font-weight: 500; } .btn-primary { - background: #0b60ab; - border: 1px solid #095190; + background: #0b60ab; + border: 1px solid #095190; } .btn-primary:hover { - background: #09589e; + background: #09589e; } .btn-primary:focus, @@ -243,23 +243,23 @@ ul li { .btn-primary:active, .btn-primary.active, .btn-primary:not(:disabled):not(.disabled):active { - background: #0b60ab; - border-color: #095190; - box-shadow: 0 0 0 0.2rem rgba(103, 171, 197, 0.5); + background: #0b60ab; + border-color: #095190; + box-shadow: 0 0 0 0.2rem rgba(103, 171, 197, 0.5); } /* Button Secondary */ .btn-secondary, .btn-danger { - background: white; - color: black; - border: 1px solid #d7d7d7; + background: white; + color: black; + border: 1px solid #d7d7d7; } .btn-secondary:hover { - background: #e8e8e8; - color: black; - border-color: #c2c2c2; + background: #e8e8e8; + color: black; + border-color: #c2c2c2; } .btn-secondary:focus, @@ -268,9 +268,9 @@ ul li { .btn-secondary.active, .btn-secondary:not(:disabled):not(.disabled):active, .show > .btn-secondary.dropdown-toggle { - background: #f4f4f4; - color: black; - box-shadow: 0 0 0 0.2rem rgba(103, 171, 197, 0.5); + background: #f4f4f4; + color: black; + box-shadow: 0 0 0 0.2rem rgba(103, 171, 197, 0.5); } .btn-primary.disabled, @@ -279,17 +279,17 @@ ul li { .btn-secondary:disabled, .btn-danger.disabled, .btn-danger:disabled { - background: #f4f4f4; - color: #999999; - border: 1px solid #d7d7d7; - border: 1px solid #d7d7d7; + background: #f4f4f4; + color: #999999; + border: 1px solid #d7d7d7; + border: 1px solid #d7d7d7; } /* Button Tertiary */ .btn-tertiary { - background: white; - color: #0b60ab; - border: 1px solid #d7d7d7; + background: white; + color: #0b60ab; + border: 1px solid #d7d7d7; } .btn-primary:focus, @@ -297,294 +297,294 @@ ul li { .btn-primary:active, .btn-primary.active, .btn-primary:not(:disabled):not(.disabled):active { - background: white; - color: #0b60ab; + background: white; + color: #0b60ab; } /* Button Danger */ .btn-danger { - color: #d52a49; + color: #d52a49; } .form-group { - margin-bottom: 1rem; + margin-bottom: 1rem; } /* STATUS LABELS */ .status-wrapper { - display: flex; - align-items: center; + display: flex; + align-items: center; } .status-label { - font-size: 0.9rem; - padding: 0.15em 0.65em 0.15em; - border-radius: 22px; - font-weight: 500; - display: inline-block; - white-space: nowrap; - width: 100%; - text-align: center; + font-size: 0.9rem; + padding: 0.15em 0.65em 0.15em; + border-radius: 22px; + font-weight: 500; + display: inline-block; + white-space: nowrap; + width: 100%; + text-align: center; } .status-label.conflicts { - background: #f9aeae; - color: #640303; + background: #f9aeae; + color: #640303; } .status-label.in-progress { - background: #d9f6de; - color: #2c6c37; + background: #d9f6de; + color: #2c6c37; } .status-label.not-started { - background: #e7ecef; - color: #63697e; + background: #e7ecef; + color: #63697e; } .status-label.complete { - background: #bcddfa; - color: #10548f; + background: #bcddfa; + color: #10548f; } .dropdown-toggle { - position: relative; + position: relative; } .dropdown-toggle::after { - position: absolute; - right: 0.5em; - top: 50%; + position: absolute; + right: 0.5em; + top: 50%; } /* Home Page */ main.home-page.container { - padding: 0; + padding: 0; } main.home-page.container h1 { - border: none; - padding-bottom: 0; + border: none; + padding-bottom: 0; } .hero-section { - padding: 1em; + padding: 1em; } .hero-section p { - font-weight: 400; - font-size: 1.2em; - color: #5c728e; + font-weight: 400; + font-size: 1.2em; + color: #5c728e; } .hero-copy { - margin-bottom: 2em; + margin-bottom: 2em; } .hero-video { - display: flex; - justify-content: center; + display: flex; + justify-content: center; } .hero-video iframe { - max-width: 100%; - max-height: calc(calc(100vw - 2em) / 1.75); + max-width: 100%; + max-height: calc(calc(100vw - 2em) / 1.75); } .hero-link { - color: #0072ed; - text-decoration: underline; + color: #0072ed; + text-decoration: underline; } .hero-link:hover, .hero-link:active, .hero-link:focus { - color: #0058b6; + color: #0058b6; } .w3c-authorization-message { - background-color: #fff9ea; - display: flex; - padding: 1em; + background-color: #fff9ea; + display: flex; + padding: 1em; } .w3c-authorization-message i { - padding-right: 0.5em; - font-size: 25px; - font-style: normal; + padding-right: 0.5em; + font-size: 25px; + font-style: normal; } .w3c-authorization-message em { - display: block; - font-style: normal; - font-size: 0.875rem; - color: #654e29; - line-height: 1.3; + display: block; + font-style: normal; + font-size: 0.875rem; + color: #654e29; + line-height: 1.3; } .w3c-authorization-message strong { - font-style: italic; + font-style: italic; } .w3c-authorization-message a { - font-weight: bold; - color: #654e29; + font-weight: bold; + color: #654e29; } .w3c-authorization-message a:hover { - text-decoration: underline; + text-decoration: underline; } .right { - float: right; + float: right; } img { - max-width: 100%; + max-width: 100%; } .improvements { - background-color: #f6fbfe; - padding: 1em; + background-color: #f6fbfe; + padding: 1em; } .improvements-container { - background: none; + background: none; } .improvements-container h2 { - margin: 0 0 1em 0; - text-align: center; - font-weight: bold; + margin: 0 0 1em 0; + text-align: center; + font-weight: bold; } .improvement-list li { - background: white; - padding: 2em 1em; - margin-bottom: 1em; - box-shadow: 0px 6px 11px 0px rgba(0, 0, 0, 0.15); - border-radius: 5px; - padding: 40px; + background: white; + padding: 2em 1em; + margin-bottom: 1em; + box-shadow: 0px 6px 11px 0px rgba(0, 0, 0, 0.15); + border-radius: 5px; + padding: 40px; } .improvement-list img { - width: 100px; - height: 100px; - display: block; - margin: 0 auto 40px auto; + width: 100px; + height: 100px; + display: block; + margin: 0 auto 40px auto; } .get-involved { - padding: 1em; - min-height: 40vh; + padding: 1em; + min-height: 40vh; } .get-involved h2 { - text-align: center; - font-weight: bold; - margin: 1em 0 1.5em; + text-align: center; + font-weight: bold; + margin: 1em 0 1.5em; } .get-involved .container { - max-width: 660px; - padding-bottom: 2em; + max-width: 660px; + padding-bottom: 2em; } ol.breadcrumb { - padding: 0.75rem 1rem; + padding: 0.75rem 1rem; } @media (min-width: 700px) { - nav.navbar { - padding: 0.5rem 3rem; - } - - main.container, - .test-run-container, - .hero-section, - .get-involved, - .improvements { - padding: 3rem; - } - - .get-involved h2 { - margin: 1em 0 1.5em; - } + nav.navbar { + padding: 0.5rem 3rem; + } + + main.container, + .test-run-container, + .hero-section, + .get-involved, + .improvements { + padding: 3rem; + } + + .get-involved h2 { + margin: 1em 0 1.5em; + } } @media (min-width: 800px) { - .hero-section { - padding: 5rem; - } + .hero-section { + padding: 5rem; + } } @media (min-width: 1000px) { - nav.navbar { - padding: 0.5rem 5rem; - } + nav.navbar { + padding: 0.5rem 5rem; + } - main.container, - .test-run-container, - .get-involved, - .improvements { - padding: 5rem; - } + main.container, + .test-run-container, + .get-involved, + .improvements { + padding: 5rem; + } - .improvements-container h2 { - font-size: 1.75em; - } + .improvements-container h2 { + font-size: 1.75em; + } } @media (min-width: 768px) { - .improvement-list { - display: grid; - grid-template-columns: 48.5% 48.5%; - column-gap: 3%; - } + .improvement-list { + display: grid; + grid-template-columns: 48.5% 48.5%; + column-gap: 3%; + } } @media (min-width: 1100px) { - .hero-section { - padding: 5rem; - } + .hero-section { + padding: 5rem; + } - .hero-section h1 { - width: 45%; - } + .hero-section h1 { + width: 45%; + } - .hero-copy { - margin-bottom: 0; - } + .hero-copy { + margin-bottom: 0; + } - .hero-copy-and-video { - display: grid; - grid-template-columns: repeat(2, calc(50% - 20px)); - column-gap: 40px; - } + .hero-copy-and-video { + display: grid; + grid-template-columns: repeat(2, calc(50% - 20px)); + column-gap: 40px; + } - .hero-video iframe { - /* sorry for the magic number */ - max-height: calc(calc(44vw - 2em) / 1.75); - } + .hero-video iframe { + /* sorry for the magic number */ + max-height: calc(calc(44vw - 2em) / 1.75); + } } @media (min-width: 1250px) { - .improvement-list { - grid-template-columns: repeat(4, 1fr); - column-gap: 1.25%; - } + .improvement-list { + grid-template-columns: repeat(4, 1fr); + column-gap: 1.25%; + } } .signed-in-wrapper { - display: flex; - align-items: center; + display: flex; + align-items: center; } .signed-in { - padding-left: 0.75em; - margin: 0.5rem 1rem 0.5rem 0; - border-left: 2px solid #d2d5d9; + padding-left: 0.75em; + margin: 0.5rem 1rem 0.5rem 0; + border-left: 2px solid #d2d5d9; } @media (max-width: 991px) { - .signed-in-wrapper { - align-items: stretch; - flex-direction: column; - } - .signed-in { - border-left: none; - } + .signed-in-wrapper { + align-items: stretch; + flex-direction: column; + } + .signed-in { + border-left: none; + } } diff --git a/client/components/App/App.jsx b/client/components/App/App.jsx index d1282ccf5..143a245a3 100644 --- a/client/components/App/App.jsx +++ b/client/components/App/App.jsx @@ -14,163 +14,141 @@ import SkipLink from '../SkipLink'; import './App.css'; const App = () => { - const location = useLocation(); - const { client, loading, data } = useQuery(ME_QUERY); - const [isNavbarExpanded, setIsNavbarExpanded] = useState(false); + const location = useLocation(); + const { client, loading, data } = useQuery(ME_QUERY); + const [isNavbarExpanded, setIsNavbarExpanded] = useState(false); - const auth = evaluateAuth(data && data.me ? data.me : {}); - const { username, isSignedIn, isAdmin, isVendor } = auth; + const auth = evaluateAuth(data && data.me ? data.me : {}); + const { username, isSignedIn, isAdmin, isVendor } = auth; - const signOut = async () => { - await fetch('/api/auth/signout', { method: 'POST' }); - await client.resetStore(); - }; + const signOut = async () => { + await fetch('/api/auth/signout', { method: 'POST' }); + await client.resetStore(); + }; - useEffect(() => { - setIsNavbarExpanded(false); - }, [location]); + useEffect(() => { + setIsNavbarExpanded(false); + }, [location]); - if (loading) return null; + if (loading) return null; - return ( - - - setIsNavbarExpanded(previous => !previous)} + return ( + + + setIsNavbarExpanded(previous => !previous)} + > + + ARIA-AT + + + + + + + + + {routes()} + + ); }; export default App; diff --git a/client/components/App/queries.js b/client/components/App/queries.js index ad105d709..793c404f5 100644 --- a/client/components/App/queries.js +++ b/client/components/App/queries.js @@ -1,11 +1,11 @@ import { gql } from '@apollo/client'; export const ME_QUERY = gql` - query Me { - me { - id - username - roles - } + query Me { + me { + id + username + roles } + } `; diff --git a/client/components/BotRunTestStatusList/index.js b/client/components/BotRunTestStatusList/index.js index 9a5bf389e..7fbae914a 100644 --- a/client/components/BotRunTestStatusList/index.js +++ b/client/components/BotRunTestStatusList/index.js @@ -5,29 +5,19 @@ import { useQuery } from '@apollo/client'; import styled from '@emotion/styled'; import ReportStatusDot from '../common/ReportStatusDot'; -// TODO: Remove when Test Queue v1 is removed -const BotRunTestStatusUnorderedList = styled.ul` - list-style-type: none; - background-color: #f6f8fa; - font-size: 0.9rem !important; - padding: 0.5rem 0; - margin: 0.5rem 0; - white-space: nowrap; -`; - const BotRunTestContainer = styled.div` - font-size: 0.875rem !important; - padding: 0.5rem 0; - margin: 0.5rem 0; + font-size: 0.875rem !important; + padding: 0.5rem 0; + margin: 0.5rem 0; - background: #f5f5f5; - border-radius: 0.25rem; + background: #f5f5f5; + border-radius: 0.25rem; - white-space: nowrap; + white-space: nowrap; `; -const BotRunTestStatusUnorderedListV2 = styled.ul` - list-style-type: none; +const BotRunTestStatusUnorderedList = styled.ul` + list-style-type: none; `; /** @@ -45,136 +35,99 @@ const BotRunTestStatusUnorderedListV2 = styled.ul` * @returns {string} the pluralized text */ const testCountString = (count, status) => - `${count} Test${count === 1 ? '' : 's'} ${status}`; + `${count} Test${count === 1 ? '' : 's'} ${status}`; const pollInterval = 2000; -const BotRunTestStatusList = ({ - testPlanReportId, - fromTestQueueV2 = false // TODO: Remove when Test Queue v1 is removed -}) => { - const { - data: testPlanRunsQueryResult, - startPolling, - stopPolling - } = useQuery(TEST_PLAN_RUNS_TEST_RESULTS_QUERY, { - variables: { testPlanReportId }, - fetchPolicy: 'cache-and-network', - pollInterval - }); +const BotRunTestStatusList = ({ testPlanReportId }) => { + const { + data: testPlanRunsQueryResult, + startPolling, + stopPolling + } = useQuery(TEST_PLAN_RUNS_TEST_RESULTS_QUERY, { + variables: { testPlanReportId }, + fetchPolicy: 'cache-and-network', + pollInterval + }); - const { COMPLETED, ERROR, RUNNING, CANCELLED, QUEUED } = useMemo(() => { - const counter = { - COMPLETED: 0, - ERROR: 0, - RUNNING: 0, - CANCELLED: 0, - QUEUED: 0 - }; - let anyPossibleUpdates = false; - if (testPlanRunsQueryResult?.testPlanRuns) { - for (const { - collectionJob - } of testPlanRunsQueryResult.testPlanRuns) { - if (collectionJob?.testStatus) { - for (const { status } of collectionJob.testStatus) { - counter[status]++; - if (status === 'QUEUED' || status === 'RUNNING') { - anyPossibleUpdates = true; - } - } - } - } - // it's possible that we got incomplete data on first fetch and - // stopped the polling, so restart the polling if we detect any - // possible future updates, otherwise stop. - if (anyPossibleUpdates) { - startPolling(pollInterval); - } else { - stopPolling(); + const { COMPLETED, ERROR, RUNNING, CANCELLED, QUEUED } = useMemo(() => { + const counter = { + COMPLETED: 0, + ERROR: 0, + RUNNING: 0, + CANCELLED: 0, + QUEUED: 0 + }; + let anyPossibleUpdates = false; + if (testPlanRunsQueryResult?.testPlanRuns) { + for (const { collectionJob } of testPlanRunsQueryResult.testPlanRuns) { + if (collectionJob?.testStatus) { + for (const { status } of collectionJob.testStatus) { + counter[status]++; + if (status === 'QUEUED' || status === 'RUNNING') { + anyPossibleUpdates = true; } + } } - return counter; - }, [testPlanRunsQueryResult, stopPolling, startPolling]); - - if ( - !testPlanRunsQueryResult || - testPlanRunsQueryResult.testPlanRuns.length === 0 - ) { - return null; + } + // it's possible that we got incomplete data on first fetch and + // stopped the polling, so restart the polling if we detect any + // possible future updates, otherwise stop. + if (anyPossibleUpdates) { + startPolling(pollInterval); + } else { + stopPolling(); + } } + return counter; + }, [testPlanRunsQueryResult, stopPolling, startPolling]); + + if ( + !testPlanRunsQueryResult || + testPlanRunsQueryResult.testPlanRuns.length === 0 + ) { + return null; + } - return ( - <> - {fromTestQueueV2 ? ( - - Bot Status: - - {RUNNING > 0 && ( -
  • - - {testCountString(RUNNING, 'Running')} -
  • - )} - {ERROR > 0 && ( -
  • - - {testCountString(ERROR, 'Error')} -
  • - )} -
  • - - {testCountString(COMPLETED, 'Completed')} -
  • -
  • - - {testCountString(QUEUED, 'Queued')} -
  • - {CANCELLED > 0 && ( -
  • - - {testCountString(CANCELLED, 'Cancelled')} -
  • - )} -
    -
    - ) : ( - - {RUNNING > 0 && ( -
  • - - {testCountString(RUNNING, 'Running')} -
  • - )} - {ERROR > 0 && ( -
  • - - {testCountString(ERROR, 'Error')} -
  • - )} -
  • - - {testCountString(COMPLETED, 'Completed')} -
  • -
  • - - {testCountString(QUEUED, 'Queued')} -
  • - {CANCELLED > 0 && ( -
  • - - {testCountString(CANCELLED, 'Cancelled')} -
  • - )} -
    - )} - - ); + return ( + <> + + Bot Status: + + {RUNNING > 0 && ( +
  • + + {testCountString(RUNNING, 'Running')} +
  • + )} + {ERROR > 0 && ( +
  • + + {testCountString(ERROR, 'Error')} +
  • + )} +
  • + + {testCountString(COMPLETED, 'Completed')} +
  • +
  • + + {testCountString(QUEUED, 'Queued')} +
  • + {CANCELLED > 0 && ( +
  • + + {testCountString(CANCELLED, 'Cancelled')} +
  • + )} +
    +
    + + ); }; BotRunTestStatusList.propTypes = { - testPlanReportId: PropTypes.string.isRequired, - fromTestQueueV2: PropTypes.bool + testPlanReportId: PropTypes.string.isRequired }; export default BotRunTestStatusList; diff --git a/client/components/BotRunTestStatusList/queries.js b/client/components/BotRunTestStatusList/queries.js index cd99fb2bc..a12ab4d55 100644 --- a/client/components/BotRunTestStatusList/queries.js +++ b/client/components/BotRunTestStatusList/queries.js @@ -1,27 +1,27 @@ import { gql } from '@apollo/client'; export const TEST_PLAN_RUNS_TEST_RESULTS_QUERY = gql` - query TestPlanRunsTestResults($testPlanReportId: ID!) { - testPlanRuns(testPlanReportId: $testPlanReportId) { - id - tester { - username - } - testResults { - id - scenarioResults { - assertionResults { - passed - failedReason - } - } - } - collectionJob { - status - testStatus { - status - } - } + query TestPlanRunsTestResults($testPlanReportId: ID!) { + testPlanRuns(testPlanReportId: $testPlanReportId) { + id + tester { + username + } + testResults { + id + scenarioResults { + assertionResults { + passed + failedReason + } } + } + collectionJob { + status + testStatus { + status + } + } } + } `; diff --git a/client/components/CandidateReview/CandidateModals/ProvideFeedbackModal/ProvideFeedbackModal.css b/client/components/CandidateReview/CandidateModals/ProvideFeedbackModal/ProvideFeedbackModal.css index 17cadc7e9..f9257bfee 100644 --- a/client/components/CandidateReview/CandidateModals/ProvideFeedbackModal/ProvideFeedbackModal.css +++ b/client/components/CandidateReview/CandidateModals/ProvideFeedbackModal/ProvideFeedbackModal.css @@ -1,72 +1,72 @@ .submit-button { - padding: 5px 25px; - width: auto; + padding: 5px 25px; + width: auto; } .close { - position: relative; - bottom: 15px; - left: 10px; + position: relative; + bottom: 15px; + left: 10px; } .feedback { - min-width: 60%; + min-width: 60%; } .feedback .modal-content { - padding-left: 1em; - padding-bottom: 1em; + padding-left: 1em; + padding-bottom: 1em; } .feedback .modal-header { - padding: 30px 30px 10px 40px; - text-align: left; + padding: 30px 30px 10px 40px; + text-align: left; } .feedback .modal-body { - padding: 0px 30px 0px 40px; + padding: 0px 30px 0px 40px; } .feedback .modal-footer { - padding-right: 30px; + padding-right: 30px; } .feedback-bold { - font-weight: 600; + font-weight: 600; } .feedback-title { - font-size: 24px; - font-weight: normal; - border-bottom: solid #e5e5e5 1px; - margin-bottom: 3px; + font-size: 24px; + font-weight: normal; + border-bottom: solid #e5e5e5 1px; + margin-bottom: 3px; } .feedback-content h2 { - font-size: 16px; - padding-bottom: 5px; + font-size: 16px; + padding-bottom: 5px; } .feedback-content p { - padding: 10px 0px; + padding: 10px 0px; } .feedback-text { - height: 150px; - background-color: #f7f7f7; + height: 150px; + background-color: #f7f7f7; } .radio-text { - padding-bottom: 10px; - color: #656565; - display: block; + padding-bottom: 10px; + color: #656565; + display: block; } .sr-only { - position: absolute; - height: 1px; - width: 1px; - overflow: hidden; - clip: rect(1px, 1px, 1px, 1px); - white-space: nowrap; + position: absolute; + height: 1px; + width: 1px; + overflow: hidden; + clip: rect(1px, 1px, 1px, 1px); + white-space: nowrap; } diff --git a/client/components/CandidateReview/CandidateModals/ProvideFeedbackModal/index.jsx b/client/components/CandidateReview/CandidateModals/ProvideFeedbackModal/index.jsx index 94db801d4..e62e2f4b5 100644 --- a/client/components/CandidateReview/CandidateModals/ProvideFeedbackModal/index.jsx +++ b/client/components/CandidateReview/CandidateModals/ProvideFeedbackModal/index.jsx @@ -8,125 +8,117 @@ import './ProvideFeedbackModal.css'; import FeedbackListItem from '../../FeedbackListItem'; const ProvideFeedbackModal = ({ - at = '', - handleAction = () => {}, - handleHide = () => {}, - feedbackIssues = [], - feedbackGithubUrl = '', - changesRequestedIssues = [], - changesRequestedGithubUrl = '', - testPlan = '', - username = '' + at = '', + handleAction = () => {}, + handleHide = () => {}, + feedbackIssues = [], + feedbackGithubUrl = '', + changesRequestedIssues = [], + changesRequestedGithubUrl = '', + testPlan = '', + username = '' }) => { - const [selectedRadio, setSelectedRadio] = useState('not-approved-input'); + const [selectedRadio, setSelectedRadio] = useState('not-approved-input'); - return ( - - {changesRequestedIssues.length > 0 && ( - - )} - {feedbackIssues.length > 0 && ( - - )} + return ( + + {changesRequestedIssues.length > 0 && ( + + )} + {feedbackIssues.length > 0 && ( + + )} -

    Finish Your Review

    -
    - setSelectedRadio(e.target.id)} - > - - - - Not Approved Yet - - - Submit your Review without explicit approval - - - - - - Approve - - - {' '} - Submit feedback and approve this Test Plan - - - -
    - - } - dialogClassName="feedback" - actions={[ - { - label: 'Submit Review', - className: 'submit-button', - onClick: () => { - handleAction( - selectedRadio === 'approve-input' ? 'APPROVED' : '' - ); - } - } - ]} - title={ - <> -

    - Great {username}! You have reviewed every test in the{' '} - {testPlan} with {at} -

    - - - } - /> - ); +

    Finish Your Review

    +
    + setSelectedRadio(e.target.id)}> + + + + Not Approved Yet + + + Submit your Review without explicit approval + + + + + + Approve + + + {' '} + Submit feedback and approve this Test Plan + + + +
    + + } + dialogClassName="feedback" + actions={[ + { + label: 'Submit Review', + className: 'submit-button', + onClick: () => { + handleAction(selectedRadio === 'approve-input' ? 'APPROVED' : ''); + } + } + ]} + title={ + <> +

    + Great {username}! You have reviewed every test in the {testPlan}{' '} + with {at} +

    + + + } + /> + ); }; ProvideFeedbackModal.propTypes = { - at: PropTypes.string, - handleAction: PropTypes.func, - handleHide: PropTypes.func, - changesRequestedIssues: PropTypes.array, - changesRequestedGithubUrl: PropTypes.string, - feedbackIssues: PropTypes.array, - feedbackGithubUrl: PropTypes.string, - show: PropTypes.bool, - testPlan: PropTypes.string, - username: PropTypes.string + at: PropTypes.string, + handleAction: PropTypes.func, + handleHide: PropTypes.func, + changesRequestedIssues: PropTypes.array, + changesRequestedGithubUrl: PropTypes.string, + feedbackIssues: PropTypes.array, + feedbackGithubUrl: PropTypes.string, + show: PropTypes.bool, + testPlan: PropTypes.string, + username: PropTypes.string }; export default ProvideFeedbackModal; diff --git a/client/components/CandidateReview/CandidateModals/ThankYouModal/ThankYouModal.css b/client/components/CandidateReview/CandidateModals/ThankYouModal/ThankYouModal.css index 88acd7796..5f2cdd0fe 100644 --- a/client/components/CandidateReview/CandidateModals/ThankYouModal/ThankYouModal.css +++ b/client/components/CandidateReview/CandidateModals/ThankYouModal/ThankYouModal.css @@ -1,52 +1,52 @@ .thank-you { - justify-content: center; + justify-content: center; } .thank-you .modal-content { - align-items: center; - width: 360px; - padding-top: 2em; - padding-bottom: 2em; + align-items: center; + width: 360px; + padding-top: 2em; + padding-bottom: 2em; } .thank-you-check { - font-size: 100px; - margin-right: 0 !important; - margin-bottom: 20px; + font-size: 100px; + margin-right: 0 !important; + margin-bottom: 20px; } .thank-you-title { - text-align: center; + text-align: center; } .thank-you-title h1 { - border-bottom: none; - font-size: 1.5em; + border-bottom: none; + font-size: 1.5em; } .thank-you-content { - font-weight: bold; - text-align: center; + font-weight: bold; + text-align: center; } .thank-you-share { - margin-bottom: 0; + margin-bottom: 0; } .thank-you-issue { - text-align: center; - font-size: 12px; - margin-bottom: 0; + text-align: center; + font-size: 12px; + margin-bottom: 0; } .thank-you .modal-header { - padding-bottom: 0; + padding-bottom: 0; } .thank-you .modal-footer { - padding-bottom: 30px; + padding-bottom: 30px; } .thank-you .modal-footer .btn { - width: 100px; + width: 100px; } diff --git a/client/components/CandidateReview/CandidateModals/ThankYouModal/index.jsx b/client/components/CandidateReview/CandidateModals/ThankYouModal/index.jsx index 4a676e638..cbe48499f 100644 --- a/client/components/CandidateReview/CandidateModals/ThankYouModal/index.jsx +++ b/client/components/CandidateReview/CandidateModals/ThankYouModal/index.jsx @@ -7,51 +7,47 @@ import '../common.css'; import './ThankYouModal.css'; const ThankYouModal = ({ handleAction = () => {}, githubUrl = '#' }) => { - return ( - -

    - Your Review has been submitted! -

    -

    - Do you have anything else to share? -

    -

    - - Open a GitHub issue - {' '} - to leave more feedback{' '} -

    - - } - dialogClassName="thank-you" - actions={[ - { - label: 'Close', - onClick: handleAction - } - ]} - title={ -
    - -

    Thank you!

    -
    - } - /> - ); + return ( + +

    Your Review has been submitted!

    +

    Do you have anything else to share?

    +

    + + Open a GitHub issue + {' '} + to leave more feedback{' '} +

    + + } + dialogClassName="thank-you" + actions={[ + { + label: 'Close', + onClick: handleAction + } + ]} + title={ +
    + +

    Thank you!

    +
    + } + /> + ); }; ThankYouModal.propTypes = { - handleAction: PropTypes.func, - githubUrl: PropTypes.string + handleAction: PropTypes.func, + githubUrl: PropTypes.string }; export default ThankYouModal; diff --git a/client/components/CandidateReview/CandidateModals/common.css b/client/components/CandidateReview/CandidateModals/common.css index ba6b281c4..3a92a1be8 100644 --- a/client/components/CandidateReview/CandidateModals/common.css +++ b/client/components/CandidateReview/CandidateModals/common.css @@ -1,11 +1,11 @@ .btn-primary { - background: #0b60ab; + background: #0b60ab; } .modal-footer { - border-top: none; + border-top: none; } .modal-header { - border-bottom: none; + border-bottom: none; } diff --git a/client/components/CandidateReview/CandidateTestPlanRun/CandidateTestPlanRun.css b/client/components/CandidateReview/CandidateTestPlanRun/CandidateTestPlanRun.css index 0f2b4b068..82cb873aa 100644 --- a/client/components/CandidateReview/CandidateTestPlanRun/CandidateTestPlanRun.css +++ b/client/components/CandidateReview/CandidateTestPlanRun/CandidateTestPlanRun.css @@ -1,154 +1,154 @@ .card-body { - padding: 0; + padding: 0; } .card-body > div { - border: none; + border: none; } .current-test-title { - border: none; + border: none; } .current-test-options .options-wrapper { - text-align: center; + text-align: center; } .current-test-options .options-wrapper li { - margin-bottom: 0.5em; + margin-bottom: 0.5em; } .current-test-options .options-wrapper li:last-child { - margin-bottom: 0; + margin-bottom: 0; } .feedback-accordion { - width: 100%; + width: 100%; } .feedback-accordion-header { - display: grid; - grid-template-columns: 50% 50%; + display: grid; + grid-template-columns: 50% 50%; } .feedback-accordion-header span { - text-align: right; + text-align: right; } .feedback-from-text { - font-weight: 400; + font-weight: 400; } .feedback-list-item { - padding: 1em 0 1em 0.5em; - position: relative; - margin: 1em 0 1em 0em; - list-style: none; - font-size: 14px; - border: solid 1px #e5e5e5; - width: 100%; + padding: 1em 0 1em 0.5em; + position: relative; + margin: 1em 0 1em 0em; + list-style: none; + font-size: 14px; + border: solid 1px #e5e5e5; + width: 100%; } .feedback-indicator { - position: absolute; - left: -2em; - top: 1.3em; - width: 16px; - height: 16px; - border-radius: 50px; - border: 2px solid #9a9a9a; + position: absolute; + left: -2em; + top: 1.3em; + width: 16px; + height: 16px; + border-radius: 50px; + border: 2px solid #9a9a9a; } .feedback-list-item:before { - content: ''; - position: absolute; - height: 2.3em; - width: 2px; - top: -1.1em; - left: -1.5em; - background: #d2d5d9; + content: ''; + position: absolute; + height: 2.3em; + width: 2px; + top: -1.1em; + left: -1.5em; + background: #d2d5d9; } .feedback-list-item:after { - content: ''; - position: absolute; - height: calc(100% - 1.3em); - width: 2px; - bottom: -1.2em; - left: -1.5em; - background: #d2d5d9; + content: ''; + position: absolute; + height: calc(100% - 1.3em); + width: 2px; + bottom: -1.2em; + left: -1.5em; + background: #d2d5d9; } .feedback-list-item .feedback-indicator { - background: white; + background: white; } .issues-container { - max-width: 800px; - margin-bottom: -14px; + max-width: 800px; + margin-bottom: -14px; } .issues-container > h2 { - margin: 0 0 0.5em; - font-size: 18px; - padding-bottom: 5px; - border-bottom: solid 1px #e5e5e5; + margin: 0 0 0.5em; + font-size: 18px; + padding-bottom: 5px; + border-bottom: solid 1px #e5e5e5; } .task-label { - font-size: 18px !important; + font-size: 18px !important; } .test-results-header, .test-instructions-header { - border-bottom: none; + border-bottom: none; } .review-status, .target-date, .apg-example-name { - width: 32% !important; + width: 32% !important; } .results-container { - padding: 0px; + padding: 0px; } .results-container-col { - padding: 0; + padding: 0; } .results-container-row { - border: solid 1px #e5e5e5; - padding: 1em; + border: solid 1px #e5e5e5; + padding: 1em; } .results-container { - padding: 0; + padding: 0; } .options-feedback { - top: 35px; + top: 35px; } .test-results-header { - margin: 0 0 1rem; + margin: 0 0 1rem; } .test-results-table { - margin-bottom: 0; - border-left: none; + margin-bottom: 0; + border-left: none; } .using { - font-weight: 300; + font-weight: 300; } .viewed-badge { - background-color: #e5e5e5 !important; - color: black; - font-size: 15px; - font-weight: 400; - position: relative; - top: -5px; + background-color: #e5e5e5 !important; + color: black; + font-size: 15px; + font-weight: 400; + position: relative; + top: -5px; } diff --git a/client/components/CandidateReview/CandidateTestPlanRun/InstructionsRenderer.jsx b/client/components/CandidateReview/CandidateTestPlanRun/InstructionsRenderer.jsx index f55b6f2e8..8eb202170 100644 --- a/client/components/CandidateReview/CandidateTestPlanRun/InstructionsRenderer.jsx +++ b/client/components/CandidateReview/CandidateTestPlanRun/InstructionsRenderer.jsx @@ -4,16 +4,16 @@ import styled from '@emotion/styled'; import { Button, Table } from 'react-bootstrap'; import { unescape } from 'lodash'; import { - parseListContent, - parseSettingsContent + parseListContent, + parseSettingsContent } from '../../TestRenderer/utils'; import { - userCloseWindow, - userOpenWindow + userCloseWindow, + userOpenWindow } from '../../../resources/aria-at-test-run.mjs'; import { - TestRunExport, - TestRunInputOutput + TestRunExport, + TestRunInputOutput } from '../../../resources/aria-at-test-io-format.mjs'; import { TestWindow } from '../../../resources/aria-at-test-window.mjs'; import { evaluateAtNameKey } from '../../../utils/aria.js'; @@ -22,326 +22,301 @@ import supportJson from '../../../resources/support.json'; import { convertAssertionPriority } from 'shared'; const NumberedList = styled.ol` - counter-reset: numbered-list; - list-style: none; - > li { - counter-increment: numbered-list; - position: relative; - margin-bottom: 10px; - } - - > li::before { - content: counter(numbered-list); - position: absolute; - color: #78869c; - font-size: 1em; - --size: 25px; - left: calc(-1 * var(--size) - 10px); - line-height: var(--size); - width: var(--size); - height: var(--size); - top: 2px; - background: #edf6ff; - border-radius: 50%; - border: 1px solid #d5deec; - text-align: center; - } + counter-reset: numbered-list; + list-style: none; + > li { + counter-increment: numbered-list; + position: relative; + margin-bottom: 10px; + } + + > li::before { + content: counter(numbered-list); + position: absolute; + color: #78869c; + font-size: 1em; + --size: 25px; + left: calc(-1 * var(--size) - 10px); + line-height: var(--size); + width: var(--size); + height: var(--size); + top: 2px; + background: #edf6ff; + border-radius: 50%; + border: 1px solid #d5deec; + text-align: center; + } `; const InstructionsRenderer = ({ - test, - testPageUrl, - at, - headingLevel = 2, - testFormatVersion + test, + testPageUrl, + at, + headingLevel = 2, + testFormatVersion }) => { - const { renderableContent } = test; - const [testRunExport, setTestRunExport] = useState(); - const [pageContent, setPageContent] = useState({ - instructions: { - assertions: { assertions: [] }, - instructions: { - instructions: [], - strongInstructions: [], - commands: { commands: [], description: '' } - }, - openTestPage: { enabled: false } + const { renderableContent } = test; + const [testRunExport, setTestRunExport] = useState(); + const [pageContent, setPageContent] = useState({ + instructions: { + assertions: { assertions: [] }, + instructions: { + instructions: [], + strongInstructions: [], + commands: { commands: [], description: '' } + }, + openTestPage: { enabled: false } + } + }); + const setup = async () => { + const testRunIO = new TestRunInputOutput(); + const configQueryParams = [['at', evaluateAtNameKey(at.name)]]; + + testRunIO.setAllCommandsInputFromJSON(commandsJson); + await testRunIO.setInputsFromCollectedTestAsync(renderableContent); + testRunIO.setConfigInputFromQueryParamsAndSupport(configQueryParams); + + if (renderableContent.target?.referencePage) { + const replaceIndex = testPageUrl.indexOf('reference/'); + // sync with proxy url expected for aria-at-app to work properly + const constructedTestPageUrl = + testPageUrl.substring(0, replaceIndex) + + renderableContent.target?.referencePage; + testRunIO.setPageUriInputFromPageUri(constructedTestPageUrl); + } else testRunIO.setPageUriInputFromPageUri(testPageUrl); + + const testWindow = new TestWindow({ + ...testRunIO.testWindowOptions(), + hooks: { + windowOpened() { + testRunExport.dispatch(userOpenWindow()); + }, + windowClosed() { + testRunExport.dispatch(userCloseWindow()); } + } }); - const setup = async () => { - const testRunIO = new TestRunInputOutput(); - const configQueryParams = [['at', evaluateAtNameKey(at.name)]]; - - testRunIO.setAllCommandsInputFromJSON(commandsJson); - await testRunIO.setInputsFromCollectedTestAsync(renderableContent); - testRunIO.setConfigInputFromQueryParamsAndSupport(configQueryParams); - - if (renderableContent.target?.referencePage) { - const replaceIndex = testPageUrl.indexOf('reference/'); - // sync with proxy url expected for aria-at-app to work properly - const constructedTestPageUrl = - testPageUrl.substring(0, replaceIndex) + - renderableContent.target?.referencePage; - testRunIO.setPageUriInputFromPageUri(constructedTestPageUrl); - } else testRunIO.setPageUriInputFromPageUri(testPageUrl); - - const testWindow = new TestWindow({ - ...testRunIO.testWindowOptions(), - hooks: { - windowOpened() { - testRunExport.dispatch(userOpenWindow()); - }, - windowClosed() { - testRunExport.dispatch(userCloseWindow()); - } - } - }); - - const testRunExport = new TestRunExport({ - hooks: { - openTestPage() { - testWindow.open(); - }, - closeTestPage() { - testWindow.close(); - } - }, - resultsJSON: state => testRunIO.submitResultsJSON(state), - state: testRunIO.testRunState() - }); - setTestRunExport(testRunExport); - }; - - useEffect(() => { - setup(); - }, []); - - useEffect(() => { - if (testRunExport) { - setPageContent(testRunExport.instructions()); + + const testRunExport = new TestRunExport({ + hooks: { + openTestPage() { + testWindow.open(); + }, + closeTestPage() { + testWindow.close(); } - }, [testRunExport]); - - let allInstructions; - const isV2 = testFormatVersion === 2; - let settingsContent = []; - - if (isV2) { - // There is at least one defined 'setting' for the list of AT commands - const commandSettingSpecified = renderableContent.commands.some( - ({ settings }) => settings && settings !== 'defaultMode' - ); - - const defaultInstructions = - renderableContent.target.at.raw - .defaultConfigurationInstructionsHTML; - const setupScriptDescription = `${supportJson.testPlanStrings.openExampleInstruction} ${renderableContent.target.setupScript.scriptDescription}`; - const testInstructions = renderableContent.instructions.instructions; - const settingsInstructions = `${ - supportJson.testPlanStrings.commandListPreface - }${ - commandSettingSpecified - ? ` ${supportJson.testPlanStrings.commandListSettingsPreface}` - : '' - }`; - - allInstructions = [ - defaultInstructions, - setupScriptDescription + '.', - testInstructions + ' ' + settingsInstructions - ].map(e => unescape(e)); - settingsContent = parseSettingsContent( - renderableContent.instructions.mode, - renderableContent.target.at.raw.settings - ); - } else { - allInstructions = [ - ...pageContent.instructions.instructions.instructions, - ...pageContent.instructions.instructions.strongInstructions, - pageContent.instructions.instructions.commands.description - ]; + }, + resultsJSON: state => testRunIO.submitResultsJSON(state), + state: testRunIO.testRunState() + }); + setTestRunExport(testRunExport); + }; + + useEffect(() => { + setup(); + }, []); + + useEffect(() => { + if (testRunExport) { + setPageContent(testRunExport.instructions()); } + }, [testRunExport]); - const commands = pageContent.instructions.instructions.commands.commands; - const commandsContent = parseListContent(commands); + let allInstructions; + const isV2 = testFormatVersion === 2; + let settingsContent = []; - const allInstructionsContent = parseListContent( - allInstructions, - commandsContent + if (isV2) { + // There is at least one defined 'setting' for the list of AT commands + const commandSettingSpecified = renderableContent.commands.some( + ({ settings }) => settings && settings !== 'defaultMode' ); - const Heading = `h${headingLevel}`; - - return ( - <> - {allInstructionsContent} - {settingsContent.length ? settingsContent : null} - - {renderableContent.commands.map( - ({ id, settings, assertionExceptions = [] }, i) => { - const settingsScreenText = isV2 - ? renderableContent.target.at.raw.settings[settings] - ?.screenText ?? '' - : null; - - let mustCount = 0; - let shouldCount = 0; - let mayCount = 0; - - let assertions = [...renderableContent.assertions]; - - assertionExceptions.forEach(exception => { - const assertionIndex = assertions.findIndex( - assertion => { - return ( - assertion.assertionId === - exception.assertionId - ); - } - ); - - if (assertionIndex < 0) return; - - if (exception.priority === 0) { - assertions.splice(assertionIndex, 1); - } else { - assertions[assertionIndex] = { - ...assertions[assertionIndex], - priority: exception.priority - }; - } - }); - - // Filter out assertions that were originally set to 0 when declared through - // tests.csv instead of *-commands.csv - assertions = assertions.filter( - ({ priority }) => priority !== 0 - ); - - assertions.forEach(({ priority }) => { - const priorityString = - convertAssertionPriority(priority); - if (priorityString === 'MUST') mustCount += 1; - if (priorityString === 'SHOULD') shouldCount += 1; - if (priorityString === 'MAY') mayCount += 1; - }); - - const settingsScreenTextFormatted = settingsScreenText - ? ` (${settingsScreenText})` - : ''; - - const scenarioTitle = - `${renderableContent.commands[i].keystroke}` + - `${settingsScreenTextFormatted}: ${mustCount} MUST, ` + - `${shouldCount} SHOULD, ${mayCount} MAY Assertions`; - - const scenarioId = - `${at.name}-test${test.rowNumber}-${id}-cmd${i}`.replaceAll( - /[,+\s]+/g, - '_' - ); - - if (isV2) { - return ( - - - {scenarioTitle} - - - - - - - - - - - {assertions.map( - ({ - assertionPhrase, - assertionStatement, - priority, - assertionId - }) => ( - - - - - - ) - )} - -
    PriorityAssertion PhraseAssertion Statement
    - {convertAssertionPriority( - priority - )} - {assertionPhrase} - {assertionStatement} -
    -
    - ); - } else { - return ( - - - {scenarioTitle} - - - - - - - - - - {assertions.map( - ({ expectation, priority }) => ( - - - - - ) - )} - -
    PriorityAssertion Statement
    - {convertAssertionPriority( - priority - )} - {expectation}
    -
    - ); - } - } - )} - - - + const defaultInstructions = + renderableContent.target.at.raw.defaultConfigurationInstructionsHTML; + const setupScriptDescription = `${supportJson.testPlanStrings.openExampleInstruction} ${renderableContent.target.setupScript.scriptDescription}`; + const testInstructions = renderableContent.instructions.instructions; + const settingsInstructions = `${ + supportJson.testPlanStrings.commandListPreface + }${ + commandSettingSpecified + ? ` ${supportJson.testPlanStrings.commandListSettingsPreface}` + : '' + }`; + + allInstructions = [ + defaultInstructions, + setupScriptDescription + '.', + testInstructions + ' ' + settingsInstructions + ].map(e => unescape(e)); + settingsContent = parseSettingsContent( + renderableContent.instructions.mode, + renderableContent.target.at.raw.settings ); + } else { + allInstructions = [ + ...pageContent.instructions.instructions.instructions, + ...pageContent.instructions.instructions.strongInstructions, + pageContent.instructions.instructions.commands.description + ]; + } + + const commands = pageContent.instructions.instructions.commands.commands; + const commandsContent = parseListContent(commands); + + const allInstructionsContent = parseListContent( + allInstructions, + commandsContent + ); + + const Heading = `h${headingLevel}`; + + return ( + <> + {allInstructionsContent} + {settingsContent.length ? settingsContent : null} + + {renderableContent.commands.map( + ({ id, settings, assertionExceptions = [] }, i) => { + const settingsScreenText = isV2 + ? renderableContent.target.at.raw.settings[settings]?.screenText ?? + '' + : null; + + let mustCount = 0; + let shouldCount = 0; + let mayCount = 0; + + let assertions = [...renderableContent.assertions]; + + assertionExceptions.forEach(exception => { + const assertionIndex = assertions.findIndex(assertion => { + return assertion.assertionId === exception.assertionId; + }); + + if (assertionIndex < 0) return; + + if (exception.priority === 0) { + assertions.splice(assertionIndex, 1); + } else { + assertions[assertionIndex] = { + ...assertions[assertionIndex], + priority: exception.priority + }; + } + }); + + // Filter out assertions that were originally set to 0 when declared through + // tests.csv instead of *-commands.csv + assertions = assertions.filter(({ priority }) => priority !== 0); + + assertions.forEach(({ priority }) => { + const priorityString = convertAssertionPriority(priority); + if (priorityString === 'MUST') mustCount += 1; + if (priorityString === 'SHOULD') shouldCount += 1; + if (priorityString === 'MAY') mayCount += 1; + }); + + const settingsScreenTextFormatted = settingsScreenText + ? ` (${settingsScreenText})` + : ''; + + const scenarioTitle = + `${renderableContent.commands[i].keystroke}` + + `${settingsScreenTextFormatted}: ${mustCount} MUST, ` + + `${shouldCount} SHOULD, ${mayCount} MAY Assertions`; + + const scenarioId = + `${at.name}-test${test.rowNumber}-${id}-cmd${i}`.replaceAll( + /[,+\s]+/g, + '_' + ); + + if (isV2) { + return ( + + {scenarioTitle} + + + + + + + + + + {assertions.map( + ({ + assertionPhrase, + assertionStatement, + priority, + assertionId + }) => ( + + + + + + ) + )} + +
    PriorityAssertion PhraseAssertion Statement
    {convertAssertionPriority(priority)}{assertionPhrase}{assertionStatement}
    +
    + ); + } else { + return ( + + {scenarioTitle} + + + + + + + + + {assertions.map(({ expectation, priority }) => ( + + + + + ))} + +
    PriorityAssertion Statement
    {convertAssertionPriority(priority)}{expectation}
    +
    + ); + } + } + )} + + + + ); }; InstructionsRenderer.propTypes = { - test: PropTypes.object.isRequired, - testPageUrl: PropTypes.string, - at: PropTypes.shape({ - name: PropTypes.string.isRequired - }), - testFormatVersion: PropTypes.number, - headingLevel: PropTypes.number + test: PropTypes.object.isRequired, + testPageUrl: PropTypes.string, + at: PropTypes.shape({ + name: PropTypes.string.isRequired + }), + testFormatVersion: PropTypes.number, + headingLevel: PropTypes.number }; export default InstructionsRenderer; diff --git a/client/components/CandidateReview/CandidateTestPlanRun/index.jsx b/client/components/CandidateReview/CandidateTestPlanRun/index.jsx index 10be4e4bd..2123aeaa7 100644 --- a/client/components/CandidateReview/CandidateTestPlanRun/index.jsx +++ b/client/components/CandidateReview/CandidateTestPlanRun/index.jsx @@ -6,9 +6,9 @@ import OptionButton from '../../TestRun/OptionButton'; import PageStatus from '../../common/PageStatus'; import { navigateTests } from '../../../utils/navigateTests'; import { - ADD_VIEWER_MUTATION, - CANDIDATE_REPORTS_QUERY, - PROMOTE_VENDOR_REVIEW_STATUS_REPORT_MUTATION + ADD_VIEWER_MUTATION, + CANDIDATE_REPORTS_QUERY, + PROMOTE_VENDOR_REVIEW_STATUS_REPORT_MUTATION } from './queries'; import Badge from 'react-bootstrap/Badge'; import Container from 'react-bootstrap/Container'; @@ -29,678 +29,635 @@ import ThankYouModal from '../CandidateModals/ThankYouModal'; import FeedbackListItem from '../FeedbackListItem'; import DisclosureComponent from '../../common/DisclosureComponent'; import createIssueLink, { - getIssueSearchLink + getIssueSearchLink } from '../../../utils/createIssueLink'; const CandidateTestPlanRun = () => { - const { atId, testPlanVersionId } = useParams(); - const navigate = useNavigate(); - - let testPlanVersionIds = []; - if (testPlanVersionId.includes(',')) - testPlanVersionIds = testPlanVersionId.split(','); - - const { loading, data, error, refetch } = useQuery( - CANDIDATE_REPORTS_QUERY, - { - variables: testPlanVersionIds.length - ? { testPlanVersionIds, atId } - : { testPlanVersionId, atId } - } + const { atId, testPlanVersionId } = useParams(); + const navigate = useNavigate(); + + let testPlanVersionIds = []; + if (testPlanVersionId.includes(',')) + testPlanVersionIds = testPlanVersionId.split(','); + + const { loading, data, error, refetch } = useQuery(CANDIDATE_REPORTS_QUERY, { + variables: testPlanVersionIds.length + ? { testPlanVersionIds, atId } + : { testPlanVersionId, atId } + }); + const [addViewer] = useMutation(ADD_VIEWER_MUTATION); + const [promoteVendorReviewStatus] = useMutation( + PROMOTE_VENDOR_REVIEW_STATUS_REPORT_MUTATION + ); + + const nextButtonRef = useRef(); + const finishButtonRef = useRef(); + + const [reviewStatus, setReviewStatus] = useState(''); + const [firstTimeViewing, setFirstTimeViewing] = useState(false); + const [viewedTests, setViewedTests] = useState([]); + const [currentTestIndex, setCurrentTestIndex] = useState(0); + const [showTestNavigator, setShowTestNavigator] = useState(true); + const [isFirstTest, setIsFirstTest] = useState(true); + const [isLastTest, setIsLastTest] = useState(false); + const [feedbackModalShowing, setFeedbackModalShowing] = useState(false); + const [thankYouModalShowing, setThankYouModalShowing] = useState(false); + const [showInstructions, setShowInstructions] = useState(false); + const [showBrowserBools, setShowBrowserBools] = useState([]); + const [showBrowserClicks, setShowBrowserClicks] = useState([]); + + const isLaptopOrLarger = useMediaQuery({ + query: '(min-width: 792px)' + }); + + const toggleTestNavigator = () => setShowTestNavigator(!showTestNavigator); + + const handleTestClick = async index => { + setCurrentTestIndex(index); + if (index === 0) { + setIsFirstTest(true); + setIsLastTest(false); + } else if (index === tests.length - 1) { + setIsFirstTest(false); + setIsLastTest(true); + } else { + setIsFirstTest(false); + setIsLastTest(false); + } + }; + const handleNextTestClick = async () => { + navigateTests( + false, + currentTest, + tests, + setCurrentTestIndex, + setIsFirstTest, + setIsLastTest ); - const [addViewer] = useMutation(ADD_VIEWER_MUTATION); - const [promoteVendorReviewStatus] = useMutation( - PROMOTE_VENDOR_REVIEW_STATUS_REPORT_MUTATION + }; + const handlePreviousTestClick = async () => { + const { isFirstTest } = navigateTests( + true, + currentTest, + tests, + setCurrentTestIndex, + setIsFirstTest, + setIsLastTest ); - - const nextButtonRef = useRef(); - const finishButtonRef = useRef(); - - const [reviewStatus, setReviewStatus] = useState(''); - const [firstTimeViewing, setFirstTimeViewing] = useState(false); - const [viewedTests, setViewedTests] = useState([]); - const [currentTestIndex, setCurrentTestIndex] = useState(0); - const [showTestNavigator, setShowTestNavigator] = useState(true); - const [isFirstTest, setIsFirstTest] = useState(true); - const [isLastTest, setIsLastTest] = useState(false); - const [feedbackModalShowing, setFeedbackModalShowing] = useState(false); - const [thankYouModalShowing, setThankYouModalShowing] = useState(false); - const [showInstructions, setShowInstructions] = useState(false); - const [showBrowserBools, setShowBrowserBools] = useState([]); - const [showBrowserClicks, setShowBrowserClicks] = useState([]); - - const isLaptopOrLarger = useMediaQuery({ - query: '(min-width: 792px)' - }); - - const toggleTestNavigator = () => setShowTestNavigator(!showTestNavigator); - - const handleTestClick = async index => { - setCurrentTestIndex(index); - if (index === 0) { - setIsFirstTest(true); - setIsLastTest(false); - } else if (index === tests.length - 1) { - setIsFirstTest(false); - setIsLastTest(true); - } else { - setIsFirstTest(false); - setIsLastTest(false); - } - }; - const handleNextTestClick = async () => { - navigateTests( - false, - currentTest, - tests, - setCurrentTestIndex, - setIsFirstTest, - setIsLastTest - ); - }; - const handlePreviousTestClick = async () => { - const { isFirstTest } = navigateTests( - true, - currentTest, - tests, - setCurrentTestIndex, - setIsFirstTest, - setIsLastTest - ); - if (isFirstTest) nextButtonRef.current.focus(); - }; - - const addViewerToTest = async testId => { - await addViewer({ variables: { testPlanVersionId, testId } }); - }; - - const updateTestViewed = async () => { - const userPreviouslyViewedTest = viewedTests.includes(currentTest.id); - if (!userPreviouslyViewedTest) { - setFirstTimeViewing(true); - setViewedTests(tests => [...tests, currentTest.id]); - await addViewerToTest(currentTest.id); - refetch(); - } else { - setFirstTimeViewing(false); - } - }; - - const updateVendorStatus = async (reportApproved = false) => { - if (reviewStatus === 'READY') { - await Promise.all( - testPlanReports?.map(report => - promoteVendorReviewStatus({ - variables: { testReportId: report.id, reviewStatus } - }) - ) - ); - setReviewStatus('IN_PROGRESS'); - } else if (reviewStatus === 'IN_PROGRESS' && reportApproved) { - await Promise.all( - testPlanReports?.map(report => - promoteVendorReviewStatus({ - variables: { testReportId: report.id, reviewStatus } - }) - ) - ); - setReviewStatus('APPROVED'); - } - }; - - const submitApproval = async (status = '') => { - if (status === 'APPROVED') { - updateVendorStatus(true); - } - setFeedbackModalShowing(false); - setThankYouModalShowing(true); - }; - - useEffect(() => { - if (data) { - if ( - !tests[0].viewers?.find( - viewer => viewer.username === data.me.username - ) - ) { - addViewerToTest(tests[0].id); - setFirstTimeViewing(true); - } - const viewedTests = [ - tests[0].id, - ...tests - .filter(test => - test.viewers?.find( - viewer => viewer.username === data.me.username - ) - ) - .map(test => test.id) - ]; - setViewedTests(viewedTests); - setReviewStatus(vendorReviewStatus); - - const bools = testPlanReports.map(() => false); - setShowBrowserBools(bools); - - const browserClicks = testPlanReports.map((report, index) => () => { - setShowBrowserBools(browserBools => { - let bools = [...browserBools]; - bools[index] = !bools[index]; - return bools; - }); - }); - - setShowBrowserClicks(browserClicks); - } - }, [data]); - - useEffect(() => { - if (data) { - updateVendorStatus(); - } - }, [reviewStatus]); - - useEffect(() => { - if (data) { - updateTestViewed(); - } - }, [currentTestIndex]); - - useEffect(() => { - if (data) { - setIsLastTest(tests?.length === 1); - } - }, [data, tests]); - - useEffect(() => { - if (isLastTest) finishButtonRef.current.focus(); - }, [isLastTest]); - - if (error) - return ( - - ); - - if (loading) { - return ( - - ); + if (isFirstTest) nextButtonRef.current.focus(); + }; + + const addViewerToTest = async testId => { + await addViewer({ variables: { testPlanVersionId, testId } }); + }; + + const updateTestViewed = async () => { + const userPreviouslyViewedTest = viewedTests.includes(currentTest.id); + if (!userPreviouslyViewedTest) { + setFirstTimeViewing(true); + setViewedTests(tests => [...tests, currentTest.id]); + await addViewerToTest(currentTest.id); + refetch(); + } else { + setFirstTimeViewing(false); } + }; + + const updateVendorStatus = async (reportApproved = false) => { + if (reviewStatus === 'READY') { + await Promise.all( + testPlanReports?.map(report => + promoteVendorReviewStatus({ + variables: { testReportId: report.id, reviewStatus } + }) + ) + ); + setReviewStatus('IN_PROGRESS'); + } else if (reviewStatus === 'IN_PROGRESS' && reportApproved) { + await Promise.all( + testPlanReports?.map(report => + promoteVendorReviewStatus({ + variables: { testReportId: report.id, reviewStatus } + }) + ) + ); + setReviewStatus('APPROVED'); + } + }; - if (!data) return null; - - const atMap = { - 1: 'JAWS', - 2: 'NVDA', - 3: 'VoiceOver for macOS' - }; - const at = atMap[atId]; - - const testPlanReports = []; - const _testPlanReports = data.testPlanReports; - if (_testPlanReports.length === 0) return ; - - const getLatestReleasedAtVersionReport = arr => { - return arr.reduce((o1, o2) => { - return new Date(o1.latestAtVersionReleasedAt.releasedAt) > - new Date(o2.latestAtVersionReleasedAt.releasedAt) - ? o1 - : o2; + const submitApproval = async (status = '') => { + if (status === 'APPROVED') { + updateVendorStatus(true); + } + setFeedbackModalShowing(false); + setThankYouModalShowing(true); + }; + + useEffect(() => { + if (data) { + if ( + !tests[0].viewers?.find(viewer => viewer.username === data.me.username) + ) { + addViewerToTest(tests[0].id); + setFirstTimeViewing(true); + } + const viewedTests = [ + tests[0].id, + ...tests + .filter(test => + test.viewers?.find(viewer => viewer.username === data.me.username) + ) + .map(test => test.id) + ]; + setViewedTests(viewedTests); + setReviewStatus(vendorReviewStatus); + + const bools = testPlanReports.map(() => false); + setShowBrowserBools(bools); + + const browserClicks = testPlanReports.map((report, index) => () => { + setShowBrowserBools(browserBools => { + let bools = [...browserBools]; + bools[index] = !bools[index]; + return bools; }); - }; - - Object.keys(atMap).forEach(k => { - const group = _testPlanReports.filter(t => t.browser.id == k); - if (group.length) { - const latestReport = getLatestReleasedAtVersionReport(group); - testPlanReports.push(latestReport); - } - }); + }); - const testPlanReport = testPlanReports.find( - each => - each.testPlanVersion.id === testPlanVersionId || - testPlanVersionIds.includes(each.testPlanVersion.id) - ); - - const tests = testPlanReport.runnableTests.map((test, index) => ({ - ...test, - index, - seq: index + 1 - })); + setShowBrowserClicks(browserClicks); + } + }, [data]); - const currentTest = tests[currentTestIndex]; - const { testPlanVersion, vendorReviewStatus } = testPlanReport; - const { recommendedPhaseTargetDate } = testPlanVersion; + useEffect(() => { + if (data) { + updateVendorStatus(); + } + }, [reviewStatus]); - const vendorReviewStatusMap = { - READY: 'Ready', - IN_PROGRESS: 'In Progress', - APPROVED: 'Approved' - }; + useEffect(() => { + if (data) { + updateTestViewed(); + } + }, [currentTestIndex]); - const reviewStatusText = vendorReviewStatusMap[reviewStatus]; + useEffect(() => { + if (data) { + setIsLastTest(tests?.length === 1); + } + }, [data, tests]); - const targetCompletionDate = convertDateToString( - new Date(recommendedPhaseTargetDate), - 'MMMM D, YYYY' - ); + useEffect(() => { + if (isLastTest) finishButtonRef.current.focus(); + }, [isLastTest]); - // Assumes that the issues are across the entire AT/Browser combination - const changesRequestedIssues = testPlanReport.issues?.filter( - issue => - issue.isCandidateReview && - issue.feedbackType === 'CHANGES_REQUESTED' && - issue.testNumberFilteredByAt === currentTest.seq + if (error) + return ( + ); - const feedbackIssues = testPlanReport.issues?.filter( - issue => - issue.isCandidateReview && - issue.feedbackType === 'FEEDBACK' && - issue.testNumberFilteredByAt === currentTest.seq + if (loading) { + return ( + ); - - const issue = { - isCandidateReview: true, - isCandidateReviewChangesRequested: true, - testPlanTitle: testPlanVersion.title, - testPlanDirectory: testPlanVersion.testPlan.directory, - versionString: testPlanVersion.versionString, - testTitle: currentTest.title, - testRowNumber: currentTest.rowNumber, - testSequenceNumber: currentTest.seq, - testRenderedUrl: currentTest.renderedUrl, - atName: testPlanReport.at.name - }; - - const requestChangesUrl = createIssueLink(issue); - - const feedbackUrl = createIssueLink({ - ...issue, - isCandidateReviewChangesRequested: false - }); - - const generalFeedbackUrl = createIssueLink({ - ...issue, - isCandidateReviewChangesRequested: false, - testTitle: undefined, - testRowNumber: undefined, - testRenderedUrl: undefined - }); - - const issueQuery = { - isCandidateReview: true, - isCandidateReviewChangesRequested: false, - testPlanTitle: testPlanVersion.title, - versionString: testPlanVersion.versionString, - testRowNumber: currentTest.rowNumber, - username: data.me.username, - atName: testPlanReport.at.name - }; - - const feedbackGithubUrl = getIssueSearchLink(issueQuery); - - const changesRequestedGithubUrl = getIssueSearchLink({ - ...issueQuery, - isCandidateReviewChangesRequested: true + } + + if (!data) return null; + + const atMap = { + 1: 'JAWS', + 2: 'NVDA', + 3: 'VoiceOver for macOS' + }; + const at = atMap[atId]; + + const testPlanReports = []; + const _testPlanReports = data.testPlanReports; + if (_testPlanReports.length === 0) return ; + + const getLatestReleasedAtVersionReport = arr => { + return arr.reduce((o1, o2) => { + return new Date(o1.latestAtVersionReleasedAt.releasedAt) > + new Date(o2.latestAtVersionReleasedAt.releasedAt) + ? o1 + : o2; }); + }; - let fileBugUrl; - - const githubAtLabelMap = { - 'VoiceOver for macOS': 'vo', - JAWS: 'jaws', - NVDA: 'nvda' - }; - - if (githubAtLabelMap[at] == 'vo') { - fileBugUrl = - 'https://bugs.webkit.org/buglist.cgi?quicksearch=voiceover'; - } else if (githubAtLabelMap[at] == 'nvda') { - fileBugUrl = 'https://github.com/nvaccess/nvda/issues'; - } else { - fileBugUrl = - 'https://github.com/FreedomScientific/VFO-standards-support/issues'; + Object.keys(atMap).forEach(k => { + const group = _testPlanReports.filter(t => t.browser.id == k); + if (group.length) { + const latestReport = getLatestReleasedAtVersionReport(group); + testPlanReports.push(latestReport); } - - const heading = ( -
    -
    - Viewing Test {currentTest.title}, Test {currentTest.seq} of{' '} - {tests.length} - {currentTest.seq === tests.length - ? 'You are on the last test.' - : ''} -
    - - Reviewing Test {currentTest.seq} of {tests.length}: - -

    - {`${currentTest.seq}. ${currentTest.title}`}{' '} - using {`${at}`} - {viewedTests.includes(currentTest.id) && - !firstTimeViewing && - ' '} - {viewedTests.includes(currentTest.id) && !firstTimeViewing && ( - - Previously Viewed - - )} -

    + }); + + const testPlanReport = testPlanReports.find( + each => + each.testPlanVersion.id === testPlanVersionId || + testPlanVersionIds.includes(each.testPlanVersion.id) + ); + + const tests = testPlanReport.runnableTests.map((test, index) => ({ + ...test, + index, + seq: index + 1 + })); + + const currentTest = tests[currentTestIndex]; + const { testPlanVersion, vendorReviewStatus } = testPlanReport; + const { recommendedPhaseTargetDate } = testPlanVersion; + + const vendorReviewStatusMap = { + READY: 'Ready', + IN_PROGRESS: 'In Progress', + APPROVED: 'Approved' + }; + + const reviewStatusText = vendorReviewStatusMap[reviewStatus]; + + const targetCompletionDate = convertDateToString( + new Date(recommendedPhaseTargetDate), + 'MMMM D, YYYY' + ); + + // Assumes that the issues are across the entire AT/Browser combination + const changesRequestedIssues = testPlanReport.issues?.filter( + issue => + issue.isCandidateReview && + issue.feedbackType === 'CHANGES_REQUESTED' && + issue.testNumberFilteredByAt === currentTest.seq + ); + + const feedbackIssues = testPlanReport.issues?.filter( + issue => + issue.isCandidateReview && + issue.feedbackType === 'FEEDBACK' && + issue.testNumberFilteredByAt === currentTest.seq + ); + + const issue = { + isCandidateReview: true, + isCandidateReviewChangesRequested: true, + testPlanTitle: testPlanVersion.title, + testPlanDirectory: testPlanVersion.testPlan.directory, + versionString: testPlanVersion.versionString, + testTitle: currentTest.title, + testRowNumber: currentTest.rowNumber, + testSequenceNumber: currentTest.seq, + testRenderedUrl: currentTest.renderedUrl, + atName: testPlanReport.at.name + }; + + const requestChangesUrl = createIssueLink(issue); + + const feedbackUrl = createIssueLink({ + ...issue, + isCandidateReviewChangesRequested: false + }); + + const generalFeedbackUrl = createIssueLink({ + ...issue, + isCandidateReviewChangesRequested: false, + testTitle: undefined, + testRowNumber: undefined, + testRenderedUrl: undefined + }); + + const issueQuery = { + isCandidateReview: true, + isCandidateReviewChangesRequested: false, + testPlanTitle: testPlanVersion.title, + versionString: testPlanVersion.versionString, + testRowNumber: currentTest.rowNumber, + username: data.me.username, + atName: testPlanReport.at.name + }; + + const feedbackGithubUrl = getIssueSearchLink(issueQuery); + + const changesRequestedGithubUrl = getIssueSearchLink({ + ...issueQuery, + isCandidateReviewChangesRequested: true + }); + + let fileBugUrl; + + const githubAtLabelMap = { + 'VoiceOver for macOS': 'vo', + JAWS: 'jaws', + NVDA: 'nvda' + }; + + if (githubAtLabelMap[at] == 'vo') { + fileBugUrl = 'https://bugs.webkit.org/buglist.cgi?quicksearch=voiceover'; + } else if (githubAtLabelMap[at] == 'nvda') { + fileBugUrl = 'https://github.com/nvaccess/nvda/issues'; + } else { + fileBugUrl = + 'https://github.com/FreedomScientific/VFO-standards-support/issues'; + } + + const heading = ( +
    +
    + Viewing Test {currentTest.title}, Test {currentTest.seq} of{' '} + {tests.length} + {currentTest.seq === tests.length ? 'You are on the last test.' : ''} +
    + + Reviewing Test {currentTest.seq} of {tests.length}: + +

    + {`${currentTest.seq}. ${currentTest.title}`}{' '} + using {`${at}`} + {viewedTests.includes(currentTest.id) && !firstTimeViewing && ' '} + {viewedTests.includes(currentTest.id) && !firstTimeViewing && ( + + Previously Viewed + + )} +

    +
    + ); + + const testInfo = ( +
    +
    +
    + Candidate Test Plan:{' '} + {`${ + testPlanVersion.title || testPlanVersion.testPlan?.directory || '' + }`}
    - ); - - const testInfo = ( -
    -
    -
    - Candidate Test Plan:{' '} - {`${ - testPlanVersion.title || - testPlanVersion.testPlan?.directory || - '' - }`} -
    -
    -
    -
    - Review status by {at} Representative:{' '} - {`${reviewStatusText} `} -
    -
    -
    -
    - Target Completion Date: - {targetCompletionDate} -
    -
    +
    +
    +
    + Review status by {at} Representative: {`${reviewStatusText} `}
    - ); - - const feedback = testPlanReport.issues.filter( - issue => - issue.isCandidateReview && - issue.testNumberFilteredByAt == currentTest.seq - ).length > 0 && ( -
    -

    - Feedback from{' '} - {at} Representative -

    -
      - {[changesRequestedIssues, feedbackIssues].map((list, index) => { - if (list.length > 0) { - const uniqueAuthors = [ - ...new Set(list.map(issue => issue.author)) - ]; - const differentAuthors = !( - uniqueAuthors.length === 1 && - uniqueAuthors[0] === data.me.username - ); - return ( - - ); - } - })} -
    -
    - ); - - const results = ( -
    -

    {currentTest.title}

    - - `Test Results for ${testPlanReport.browser.name}` - ) - ]} - onClick={[ - () => setShowInstructions(!showInstructions), - ...showBrowserClicks - ]} - expanded={[showInstructions, ...showBrowserBools]} - disclosureContainerView={[ - , - ...testPlanReports.map(testPlanReport => { - const testResult = - testPlanReport.finalizedTestResults[ - currentTestIndex - ]; - - const { assertionsPassedCount, assertionsFailedCount } = - getMetrics({ testResult }); - - return ( - <> -

    - Test Results ( - {assertionsPassedCount} passed,  - {assertionsFailedCount} failed) -

    - - - ); - }) - ]} - stacked - >
    +
    +
    +
    + Target Completion Date: + {targetCompletionDate}
    - ); +
    +
    + ); + + const feedback = testPlanReport.issues.filter( + issue => + issue.isCandidateReview && issue.testNumberFilteredByAt == currentTest.seq + ).length > 0 && ( +
    +

    + Feedback from{' '} + {at} Representative +

    +
      + {[changesRequestedIssues, feedbackIssues].map((list, index) => { + if (list.length > 0) { + const uniqueAuthors = [...new Set(list.map(issue => issue.author))]; + const differentAuthors = !( + uniqueAuthors.length === 1 && + uniqueAuthors[0] === data.me.username + ); + return ( + + ); + } + })} +
    +
    + ); + + const results = ( +
    +

    {currentTest.title}

    + `Test Results for ${testPlanReport.browser.name}` + ) + ]} + onClick={[ + () => setShowInstructions(!showInstructions), + ...showBrowserClicks + ]} + expanded={[showInstructions, ...showBrowserBools]} + disclosureContainerView={[ + , + ...testPlanReports.map(testPlanReport => { + const testResult = + testPlanReport.finalizedTestResults[currentTestIndex]; + + const { assertionsPassedCount, assertionsFailedCount } = getMetrics( + { testResult } + ); - return ( - - - Candidate Test Run Page | ARIA-AT - - - +

    + Test Results ( + {assertionsPassedCount} passed,  + {assertionsFailedCount} failed) +

    + + + ); + }) + ]} + stacked + >
    +
    + ); + + return ( + + + Candidate Test Run Page | ARIA-AT + + + + + + {heading} + {testInfo} + + - - {heading} - {testInfo} - - - - {feedback} - - {results} - - -
      -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    -
    - - -
    -

    - Test Review Options -

    - -
    - -
    - -
    + {feedback} + {results} + +
      +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    +
    -
    - {feedbackModalShowing ? ( - - issue.isCandidateReview && - issue.feedbackType === 'FEEDBACK' && - issue.author == data.me.username - )} - feedbackGithubUrl={feedbackGithubUrl} - changesRequestedIssues={testPlanReport.issues?.filter( - issue => - issue.isCandidateReview && - issue.feedbackType === 'CHANGES_REQUESTED' && - issue.author == data.me.username - )} - changesRequestedGithubUrl={changesRequestedGithubUrl} - handleAction={submitApproval} - handleHide={() => setFeedbackModalShowing(false)} - /> - ) : ( - <> - )} - {thankYouModalShowing ? ( - { - setThankYouModalShowing(false); - navigate('/candidate-review'); - }} - githubUrl={generalFeedbackUrl} - /> - ) : ( - <> - )} -
    - ); + +
    +

    Test Review Options

    + +
    + + + + + + + {feedbackModalShowing ? ( + + issue.isCandidateReview && + issue.feedbackType === 'FEEDBACK' && + issue.author == data.me.username + )} + feedbackGithubUrl={feedbackGithubUrl} + changesRequestedIssues={testPlanReport.issues?.filter( + issue => + issue.isCandidateReview && + issue.feedbackType === 'CHANGES_REQUESTED' && + issue.author == data.me.username + )} + changesRequestedGithubUrl={changesRequestedGithubUrl} + handleAction={submitApproval} + handleHide={() => setFeedbackModalShowing(false)} + /> + ) : ( + <> + )} + {thankYouModalShowing ? ( + { + setThankYouModalShowing(false); + navigate('/candidate-review'); + }} + githubUrl={generalFeedbackUrl} + /> + ) : ( + <> + )} + + ); }; export default CandidateTestPlanRun; diff --git a/client/components/CandidateReview/CandidateTestPlanRun/queries.js b/client/components/CandidateReview/CandidateTestPlanRun/queries.js index 73ee3dae8..f375110af 100644 --- a/client/components/CandidateReview/CandidateTestPlanRun/queries.js +++ b/client/components/CandidateReview/CandidateTestPlanRun/queries.js @@ -1,172 +1,172 @@ import { gql } from '@apollo/client'; export const ADD_VIEWER_MUTATION = gql` - mutation AddViewerMutation($testPlanVersionId: ID!, $testId: ID!) { - addViewer(testPlanVersionId: $testPlanVersionId, testId: $testId) { - username - } + mutation AddViewerMutation($testPlanVersionId: ID!, $testId: ID!) { + addViewer(testPlanVersionId: $testPlanVersionId, testId: $testId) { + username } + } `; export const PROMOTE_VENDOR_REVIEW_STATUS_REPORT_MUTATION = gql` - mutation UpdateVendorReviewStatusReport( - $testReportId: ID! - $reviewStatus: String! - ) { - testPlanReport(id: $testReportId) { - promoteVendorReviewStatus(vendorReviewStatus: $reviewStatus) { - testPlanReport { - id - vendorReviewStatus - } - } + mutation UpdateVendorReviewStatusReport( + $testReportId: ID! + $reviewStatus: String! + ) { + testPlanReport(id: $testReportId) { + promoteVendorReviewStatus(vendorReviewStatus: $reviewStatus) { + testPlanReport { + id + vendorReviewStatus } + } } + } `; export const CANDIDATE_REPORTS_QUERY = gql` - query CandidateReportsQuery( - $atId: ID! - $testPlanVersionId: ID - $testPlanVersionIds: [ID] + query CandidateReportsQuery( + $atId: ID! + $testPlanVersionId: ID + $testPlanVersionIds: [ID] + ) { + me { + id + roles + username + } + testPlanReports( + atId: $atId + testPlanVersionPhases: [CANDIDATE] + testPlanVersionId: $testPlanVersionId + testPlanVersionIds: $testPlanVersionIds + isFinal: true ) { - me { - id - roles - username + id + vendorReviewStatus + issues { + author + isCandidateReview + feedbackType + testNumberFilteredByAt + link + } + at { + id + name + } + latestAtVersionReleasedAt { + id + name + releasedAt + } + browser { + id + name + } + testPlanVersion { + id + title + phase + gitSha + testPlan { + directory } - testPlanReports( - atId: $atId - testPlanVersionPhases: [CANDIDATE] - testPlanVersionId: $testPlanVersionId - testPlanVersionIds: $testPlanVersionIds - isFinal: true - ) { - id - vendorReviewStatus - issues { - author - isCandidateReview - feedbackType - testNumberFilteredByAt - link - } - at { - id - name - } - latestAtVersionReleasedAt { - id - name - releasedAt - } - browser { - id - name + metadata + testPageUrl + updatedAt + versionString + candidatePhaseReachedAt + recommendedPhaseTargetDate + } + runnableTests { + id + title + rowNumber + renderedUrl + renderableContent + viewers { + username + } + } + finalizedTestResults { + id + completedAt + test { + id + rowNumber + title + renderedUrl + renderableContent + } + scenarioResults { + id + scenario { + commands { + id + text } - testPlanVersion { - id - title - phase - gitSha - testPlan { - directory - } - metadata - testPageUrl - updatedAt - versionString - candidatePhaseReachedAt - recommendedPhaseTargetDate + } + output + assertionResults { + id + assertion { + text + phrase } - runnableTests { - id - title - rowNumber - renderedUrl - renderableContent - viewers { - username - } + passed + } + mustAssertionResults: assertionResults(priority: MUST) { + assertion { + text + phrase } - finalizedTestResults { - id - completedAt - test { - id - rowNumber - title - renderedUrl - renderableContent - } - scenarioResults { - id - scenario { - commands { - id - text - } - } - output - assertionResults { - id - assertion { - text - phrase - } - passed - } - mustAssertionResults: assertionResults(priority: MUST) { - assertion { - text - phrase - } - passed - } - shouldAssertionResults: assertionResults(priority: SHOULD) { - assertion { - text - phrase - } - passed - } - mayAssertionResults: assertionResults(priority: MAY) { - assertion { - text - phrase - } - passed - } - unexpectedBehaviors { - id - text - impact - details - } - } + passed + } + shouldAssertionResults: assertionResults(priority: SHOULD) { + assertion { + text + phrase } - draftTestPlanRuns { - tester { - username - } - testPlanReport { - id - } - testResults { - test { - id - } - atVersion { - id - name - } - browserVersion { - id - name - } - completedAt - } + passed + } + mayAssertionResults: assertionResults(priority: MAY) { + assertion { + text + phrase } + passed + } + unexpectedBehaviors { + id + text + impact + details + } + } + } + draftTestPlanRuns { + tester { + username + } + testPlanReport { + id + } + testResults { + test { + id + } + atVersion { + id + name + } + browserVersion { + id + name + } + completedAt } + } } + } `; diff --git a/client/components/CandidateReview/FeedbackListItem/FeedbackListItem.css b/client/components/CandidateReview/FeedbackListItem/FeedbackListItem.css index 7a641bb0e..82dfba9a6 100644 --- a/client/components/CandidateReview/FeedbackListItem/FeedbackListItem.css +++ b/client/components/CandidateReview/FeedbackListItem/FeedbackListItem.css @@ -1,53 +1,53 @@ .feedback-list-item { - padding: 0.5em 0 0.5em 0.5em; - position: relative; - margin: 1em 0 1em 0em; - left: 2em; - bottom: 0.5em; - list-style: none; - font-size: 14px; - border: solid 1px #e5e5e5; - width: calc(100% - 2em); + padding: 0.5em 0 0.5em 0.5em; + position: relative; + margin: 1em 0 1em 0em; + left: 2em; + bottom: 0.5em; + list-style: none; + font-size: 14px; + border: solid 1px #e5e5e5; + width: calc(100% - 2em); } .feedback-list-item:before { - content: ''; - position: absolute; - height: 90%; - width: 2px; - margin-top: 7px; - bottom: 1.7em; - left: -1.5em; - background: #d2d5d9; + content: ''; + position: absolute; + height: 90%; + width: 2px; + margin-top: 7px; + bottom: 1.7em; + left: -1.5em; + background: #d2d5d9; } .feedback-list-item:after { - content: ''; - position: absolute; - height: 60%; - width: 2px; - top: 1.8em; - left: -1.5em; - background: #d2d5d9; + content: ''; + position: absolute; + height: 60%; + width: 2px; + top: 1.8em; + left: -1.5em; + background: #d2d5d9; } @media (max-width: 425px) { - .feedback-list-item:before { - height: 55%; - bottom: 3.3em; - } + .feedback-list-item:before { + height: 55%; + bottom: 3.3em; + } - .feedback-list-item:after { - height: 55%; - } + .feedback-list-item:after { + height: 55%; + } } .feedback-indicator { - position: absolute; - left: -1.9em; - top: 0.9em; - width: 13px; - height: 13px; - border-radius: 50px; - border: 2px solid #9a9a9a; + position: absolute; + left: -1.9em; + top: 0.9em; + width: 13px; + height: 13px; + border-radius: 50px; + border: 2px solid #9a9a9a; } diff --git a/client/components/CandidateReview/FeedbackListItem/index.jsx b/client/components/CandidateReview/FeedbackListItem/index.jsx index 773548e60..e60a8045d 100644 --- a/client/components/CandidateReview/FeedbackListItem/index.jsx +++ b/client/components/CandidateReview/FeedbackListItem/index.jsx @@ -6,76 +6,70 @@ import { faFlag, faCommentAlt } from '@fortawesome/free-solid-svg-icons'; import './FeedbackListItem.css'; const FeedbackListItem = ({ - differentAuthors = false, - type = 'feedback', - issues = [], - individualTest = false, - githubUrl = '#' + differentAuthors = false, + type = 'feedback', + issues = [], + individualTest = false, + githubUrl = '#' }) => { - let content; + let content; - if (!differentAuthors && !individualTest) { - content = ( - - {`You ${ - type === 'feedback' - ? 'left feedback for' - : 'requested changes for' - } + if (!differentAuthors && !individualTest) { + content = ( + + {`You ${ + type === 'feedback' ? 'left feedback for' : 'requested changes for' + } `} - - {issues.length} {issues.length === 1 ? 'test' : 'tests'} - {' '} - in this Test Plan - - ); - } else if (!differentAuthors && individualTest) { - githubUrl = issues[0].link; - content = ( - <> - You{' '} - - {type === 'feedback' - ? 'left feedback' - : 'requested changes'} - {' '} - for this test. - - ); - } else { - content = ( - <> - - {issues.length} {issues.length === 1 ? 'person' : 'people'}{' '} - {type === 'feedback' - ? 'left feedback' - : 'requested changes'} - - {' for this test'} - - ); - } - - return ( -
  • - {type === 'feedback' ? ( - - ) : ( - - )} - {' '} - {content} - -
  • + + {issues.length} {issues.length === 1 ? 'test' : 'tests'} + {' '} + in this Test Plan +
    + ); + } else if (!differentAuthors && individualTest) { + githubUrl = issues[0].link; + content = ( + <> + You{' '} + + {type === 'feedback' ? 'left feedback' : 'requested changes'} + {' '} + for this test. + ); + } else { + content = ( + <> + + {issues.length} {issues.length === 1 ? 'person' : 'people'}{' '} + {type === 'feedback' ? 'left feedback' : 'requested changes'} + + {' for this test'} + + ); + } + + return ( +
  • + {type === 'feedback' ? ( + + ) : ( + + )} + {' '} + {content} + +
  • + ); }; FeedbackListItem.propTypes = { - differentAuthors: PropTypes.bool, - issues: PropTypes.array, - type: PropTypes.string, - individualTest: PropTypes.bool, - githubUrl: PropTypes.string + differentAuthors: PropTypes.bool, + issues: PropTypes.array, + type: PropTypes.string, + individualTest: PropTypes.bool, + githubUrl: PropTypes.string }; export default FeedbackListItem; diff --git a/client/components/CandidateReview/TestPlans/TestPlans.css b/client/components/CandidateReview/TestPlans/TestPlans.css index d76219ffb..fdef0dc1b 100644 --- a/client/components/CandidateReview/TestPlans/TestPlans.css +++ b/client/components/CandidateReview/TestPlans/TestPlans.css @@ -1,18 +1,18 @@ .dropdown-btn-mark-as .btn { - display: flex; - align-items: center; - width: fit-content; - height: 30px; + display: flex; + align-items: center; + width: fit-content; + height: 30px; - padding-right: 1.5em; - font-size: 0.875rem; + padding-right: 1.5em; + font-size: 0.875rem; } .dropdown-btn-mark-as.btn { - display: flex; - align-items: center; - width: fit-content; - height: 30px; + display: flex; + align-items: center; + width: fit-content; + height: 30px; - font-size: 0.875rem; + font-size: 0.875rem; } diff --git a/client/components/CandidateReview/TestPlans/index.jsx b/client/components/CandidateReview/TestPlans/index.jsx index 7a39ec514..ba5f8ac85 100644 --- a/client/components/CandidateReview/TestPlans/index.jsx +++ b/client/components/CandidateReview/TestPlans/index.jsx @@ -6,866 +6,779 @@ import { Container, Table } from 'react-bootstrap'; import { Link } from 'react-router-dom'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { - faFlag, - faCheck, - faChevronUp, - faChevronDown, - faCommentAlt + faFlag, + faCheck, + faChevronUp, + faChevronDown, + faCommentAlt } from '@fortawesome/free-solid-svg-icons'; import alphabetizeObjectBy from '@client/utils/alphabetizeObjectBy'; import { - getTestPlanTargetTitle, - getTestPlanVersionTitle + getTestPlanTargetTitle, + getTestPlanVersionTitle } from '@components/Reports/getTitles'; import ClippedProgressBar from '@components/common/ClippedProgressBar'; import { convertDateToString } from '@client/utils/formatter'; import './TestPlans.css'; const FullHeightContainer = styled(Container)` - min-height: calc(100vh - 64px); + min-height: calc(100vh - 64px); `; const StatusText = styled.span` - height: 1.625em; - font-size: 0.875em; - padding: 4px 10px; - border-radius: 1.625rem; - - overflow: hidden; - white-space: nowrap; - - &.feedback { - border: 2px solid #b253f8; - svg { - color: #b253f8; - } - } + height: 1.625em; + font-size: 0.875em; + padding: 4px 10px; + border-radius: 1.625rem; - &.changes-requested { - border: 2px solid #f87f1b; - svg { - color: #f87f1b; - } - } + overflow: hidden; + white-space: nowrap; - &.ready-for-review { - border: 2px solid #edbb1d; - - span.dot { - height: 10px; - width: 10px; - padding: 0; - margin-right: 8px; - border-radius: 50%; - background: #edbb1d; - } + &.feedback { + border: 2px solid #b253f8; + svg { + color: #b253f8; } + } - &.in-progress { - border: 2px solid #2560ab; - - span.dot { - height: 10px; - width: 10px; - padding: 0; - margin-right: 8px; - border-radius: 50%; - background: #2560ab; - } + &.changes-requested { + border: 2px solid #f87f1b; + svg { + color: #f87f1b; } + } + + &.ready-for-review { + border: 2px solid #edbb1d; + + span.dot { + height: 10px; + width: 10px; + padding: 0; + margin-right: 8px; + border-radius: 50%; + background: #edbb1d; + } + } + + &.in-progress { + border: 2px solid #2560ab; + + span.dot { + height: 10px; + width: 10px; + padding: 0; + margin-right: 8px; + border-radius: 50%; + background: #2560ab; + } + } - &.approved { - border: 2px solid #309d08; - svg { - color: #309d08; - } + &.approved { + border: 2px solid #309d08; + svg { + color: #309d08; } + } `; const DisclosureParent = styled.div` - border: 1px solid #d3d5da; - border-radius: 3px; - margin-bottom: 3rem; + border: 1px solid #d3d5da; + border-radius: 3px; + margin-bottom: 3rem; - h3 { - margin: 0; - padding: 0; - } + h3 { + margin: 0; + padding: 0; + } `; const DisclosureButton = styled.button` - position: relative; - width: 100%; - margin: 0; + position: relative; + width: 100%; + margin: 0; + padding: 0.75rem; + text-align: left; + font-size: 1.5rem; + font-weight: 500; + border: none; + border-radius: 3px; + background-color: transparent; + + &:hover, + &:focus { padding: 0.75rem; - text-align: left; - font-size: 1.5rem; - font-weight: 500; - border: none; - border-radius: 3px; - background-color: transparent; - - &:hover, - &:focus { - padding: 0.75rem; - border: 0 solid #005a9c; - background-color: #def; - cursor: pointer; - } + border: 0 solid #005a9c; + background-color: #def; + cursor: pointer; + } - svg { - position: absolute; - margin: 0; - top: 50%; - right: 1.25rem; + svg { + position: absolute; + margin: 0; + top: 50%; + right: 1.25rem; - color: #969696; - transform: translateY(-50%); - } + color: #969696; + transform: translateY(-50%); + } `; const DisclosureContainer = styled.div` - display: ${({ show }) => (show ? 'flex' : 'none')}; - flex-direction: column; - gap: 1.25rem; + display: ${({ show }) => (show ? 'flex' : 'none')}; + flex-direction: column; + gap: 1.25rem; - background-color: #f8f9fa; + background-color: #f8f9fa; - table { - margin-bottom: 0; - } + table { + margin-bottom: 0; + } `; const CellSubRow = styled.span` - display: flex; - flex-direction: row; - gap: 0.5rem; - margin-top: 0.5rem; - font-size: 0.875rem; - - svg { - align-self: center; - margin: 0; - } + display: flex; + flex-direction: row; + gap: 0.5rem; + margin-top: 0.5rem; + font-size: 0.875rem; + + svg { + align-self: center; + margin: 0; + } `; const CenteredTh = styled.th` - text-align: center; + text-align: center; `; const CenteredTd = styled.td` - text-align: center; - vertical-align: middle !important; + text-align: center; + vertical-align: middle !important; `; const StyledH3 = styled.h3` - padding: 0; - margin: 0 0 0.75rem; - text-align: left; - font-size: 1.5rem; - font-weight: 500; + padding: 0; + margin: 0 0 0.75rem; + text-align: left; + font-size: 1.5rem; + font-weight: 500; `; const None = styled.span` - font-style: italic; - color: #727272; - padding: 12px; + font-style: italic; + color: #727272; + padding: 12px; - &.bordered { - border-top: 1px solid #d2d5d9; - } + &.bordered { + border-top: 1px solid #d2d5d9; + } `; const TestPlans = ({ testPlanVersions }) => { - const [atExpandTableItems, setAtExpandTableItems] = useState({ - 1: true, - 2: true, - 3: true - }); - - const none = None; - const borderedNone = None; - - const onClickExpandAtTable = atId => { - // { jaws/nvda/vo: boolean } ] - if (!atExpandTableItems[atId]) - setAtExpandTableItems({ ...atExpandTableItems, [atId]: true }); - else - setAtExpandTableItems({ - ...atExpandTableItems, - [atId]: !atExpandTableItems[atId] - }); - }; - - const testPlanReportsExist = testPlanVersions.some( - testPlanVersion => testPlanVersion.testPlanReports.length + const [atExpandTableItems, setAtExpandTableItems] = useState({ + 1: true, + 2: true, + 3: true + }); + + const none = None; + const borderedNone = None; + + const onClickExpandAtTable = atId => { + // { jaws/nvda/vo: boolean } ] + if (!atExpandTableItems[atId]) + setAtExpandTableItems({ ...atExpandTableItems, [atId]: true }); + else + setAtExpandTableItems({ + ...atExpandTableItems, + [atId]: !atExpandTableItems[atId] + }); + }; + + const testPlanReportsExist = testPlanVersions.some( + testPlanVersion => testPlanVersion.testPlanReports.length + ); + + if (!testPlanReportsExist) { + return ( + + + Candidate Review | ARIA-AT + +

    Candidate Review

    +

    There are no results to show just yet. Please check back soon!

    +
    ); - - if (!testPlanReportsExist) { - return ( - - - Candidate Review | ARIA-AT - -

    Candidate Review

    -

    - There are no results to show just yet. Please check back - soon! -

    -
    - ); + } + + const getRowStatus = ({ + issues = [], + isInProgressStatusExists, + isApprovedStatusExists + }) => { + let issueChangesRequestedTypeCount = 0; + let issueFeedbackTypeCount = 0; + + for (let i = 0; i < issues.length; i++) { + if (issues[i].feedbackType === 'CHANGES_REQUESTED') + issueChangesRequestedTypeCount++; + else issueFeedbackTypeCount++; } - const getRowStatus = ({ - issues = [], - isInProgressStatusExists, - isApprovedStatusExists - }) => { - let issueChangesRequestedTypeCount = 0; - let issueFeedbackTypeCount = 0; - - for (let i = 0; i < issues.length; i++) { - if (issues[i].feedbackType === 'CHANGES_REQUESTED') - issueChangesRequestedTypeCount++; - else issueFeedbackTypeCount++; - } - - const changesRequestedContent = ( - <> - - - Changes requested for {issueChangesRequestedTypeCount} test - {issueChangesRequestedTypeCount !== 1 ? 's' : ''} - - - ); + const changesRequestedContent = ( + <> + + + Changes requested for {issueChangesRequestedTypeCount} test + {issueChangesRequestedTypeCount !== 1 ? 's' : ''} + + + ); - const issueFeedbackContent = ( - <> - - - Feedback left for {issueFeedbackTypeCount} test - {issueFeedbackTypeCount !== 1 ? 's' : ''} - - - ); + const issueFeedbackContent = ( + <> + + + Feedback left for {issueFeedbackTypeCount} test + {issueFeedbackTypeCount !== 1 ? 's' : ''} + + + ); - const approvedContent = ( - <> - - - Approved - - - ); + const approvedContent = ( + <> + + + Approved + + + ); - const inProgressContent = ( - <> - - - Review in Progress - - - ); + const inProgressContent = ( + <> + + + Review in Progress + + + ); - const readyForReviewContent = ( - <> - - - Ready for Review - - - ); + const readyForReviewContent = ( + <> + + + Ready for Review + + + ); - let result = null; - if (issueChangesRequestedTypeCount) result = changesRequestedContent; - else if (issueFeedbackTypeCount) { - result = issueFeedbackContent; - if (isApprovedStatusExists) - result = ( - <> - {result && ( - <> - {result} -
    -
    - - )} - {approvedContent} - - ); - } else if (isInProgressStatusExists) result = inProgressContent; - else if (isApprovedStatusExists) result = approvedContent; - else result = readyForReviewContent; - - return result; - }; - - const evaluateTestsAssertionsMessage = ({ - totalSupportPercent, - browsersLength, - totalTestsFailedCount, - totalAssertionsFailedCount - }) => { - if (totalSupportPercent === 100) { - return ( - <> - No assertions failed - - ); - } else { - return ( - <> - - {totalAssertionsFailedCount} assertion - {totalAssertionsFailedCount !== 1 ? 's' : ''} - {' '} - failed across{' '} - - {totalTestsFailedCount} test - {totalTestsFailedCount !== 1 ? 's' : ''} - {' '} - run with{' '} - - {browsersLength} browser - {browsersLength !== 1 ? 's' : ''} - - - ); - } - }; - - const uniqueFilter = (element, unique, key) => { - const isDuplicate = unique.includes(element[key]); - if (!isDuplicate) { - unique.push(element[key]); - return true; - } - return false; - }; - - const constructTableForAtById = (atId, atName) => { - const testPlanReportsForAtExists = testPlanVersions.some( - testPlanVersion => - testPlanVersion.testPlanReports.some( - testPlanReport => testPlanReport.at.id == atId - ) + let result = null; + if (issueChangesRequestedTypeCount) result = changesRequestedContent; + else if (issueFeedbackTypeCount) { + result = issueFeedbackContent; + if (isApprovedStatusExists) + result = ( + <> + {result && ( + <> + {result} +
    +
    + + )} + {approvedContent} + ); + } else if (isInProgressStatusExists) result = inProgressContent; + else if (isApprovedStatusExists) result = approvedContent; + else result = readyForReviewContent; + + return result; + }; + + const evaluateTestsAssertionsMessage = ({ + totalSupportPercent, + browsersLength, + totalTestsFailedCount, + totalAssertionsFailedCount + }) => { + if (totalSupportPercent === 100) { + return ( + <> + No assertions failed + + ); + } else { + return ( + <> + + {totalAssertionsFailedCount} assertion + {totalAssertionsFailedCount !== 1 ? 's' : ''} + {' '} + failed across{' '} + + {totalTestsFailedCount} test + {totalTestsFailedCount !== 1 ? 's' : ''} + {' '} + run with{' '} + + {browsersLength} browser + {browsersLength !== 1 ? 's' : ''} + + + ); + } + }; - // return 'None' element if no reports exists for AT - if (!testPlanReportsForAtExists) { - return ( - -

    - onClickExpandAtTable(atId)} - > - {atName} - - -

    - - {borderedNone} - -
    - ); - } - - let testPlanTargetsById = {}; - testPlanVersions.forEach(testPlanVersion => { - const { testPlanReports } = testPlanVersion; - - testPlanReports.forEach(testPlanReport => { - const { at, browser } = testPlanReport; - // Construct testPlanTarget - const testPlanTarget = { - id: `${at.id}${browser.id}`, - at, - browser - }; - testPlanTargetsById[testPlanTarget.id] = testPlanTarget; - }); - }); - testPlanTargetsById = alphabetizeObjectBy( - testPlanTargetsById, - keyValue => getTestPlanTargetTitle(keyValue[1]) - ); + const uniqueFilter = (element, unique, key) => { + const isDuplicate = unique.includes(element[key]); + if (!isDuplicate) { + unique.push(element[key]); + return true; + } + return false; + }; + + const constructTableForAtById = (atId, atName) => { + const testPlanReportsForAtExists = testPlanVersions.some(testPlanVersion => + testPlanVersion.testPlanReports.some( + testPlanReport => testPlanReport.at.id == atId + ) + ); - return ( - -

    - onClickExpandAtTable(atId)} - > - {atName} - - -

    - - - - - - Last Updated - Target Completion Date - Review Status - Results Summary - - - - {Object.values(testPlanVersions) - .sort((a, b) => (a.title < b.title ? -1 : 1)) - .map(testPlanVersion => { - const testPlanReports = - testPlanVersion.testPlanReports.filter( - ({ at }) => at.id === atId - ); - const candidatePhaseReachedAt = - testPlanVersion.candidatePhaseReachedAt; - const recommendedPhaseTargetDate = - testPlanVersion.recommendedPhaseTargetDate; - - const allMetrics = []; - - let testsCount = 0; - let dataExists = false; - - Object.values(testPlanTargetsById).map( - testPlanTarget => { - const testPlanReport = - testPlanReports.find( - testPlanReport => - testPlanReport.at.id === - testPlanTarget.at - .id && - testPlanReport.at.id === - atId && - testPlanReport.browser - .id === - testPlanTarget - .browser.id - ); - - if (testPlanReport) { - const metrics = - testPlanReport.metrics; - allMetrics.push(metrics); - - if ( - !dataExists && - testPlanReport.at.id === - atId - ) { - dataExists = true; - } - - testsCount = - metrics.testsCount > - testsCount - ? metrics.testsCount - : testsCount; - } - } - ); - - const metrics = { - testsCount, - browsersLength: allMetrics.length, - totalTestsFailedCount: - allMetrics.reduce( - (acc, obj) => - acc + obj.testsFailedCount, - 0 - ), - totalAssertionsFailedCount: - allMetrics.reduce( - (acc, obj) => - acc + - obj.shouldAssertionsFailedCount + - obj.mustAssertionsFailedCount, - 0 - ), - totalSupportPercent: - Math.round( - allMetrics.reduce( - (acc, obj) => - acc + - obj.supportPercent, - 0 - ) / allMetrics.length - ) || 0 - }; - - // Make sure issues are unique - const uniqueLinks = []; - const allIssues = testPlanReports - .map(testPlanReport => [ - ...testPlanReport.issues - ]) - .flat() - .filter( - t => t.isCandidateReview === true - ) - .filter(t => - uniqueFilter(t, uniqueLinks, 'link') - ); - - return ( - dataExists && ( - - - - - {convertDateToString( - candidatePhaseReachedAt, - 'MMM D, YYYY' - )} - - - - - {convertDateToString( - recommendedPhaseTargetDate, - 'MMM D, YYYY' - )} - - - - {getRowStatus({ - issues: allIssues, - isInProgressStatusExists: - testPlanReports.some( - testPlanReport => - testPlanReport.vendorReviewStatus === - 'IN_PROGRESS' - ), - isApprovedStatusExists: - testPlanReports.some( - testPlanReport => - testPlanReport.vendorReviewStatus === - 'APPROVED' - ) - })} - - - - - - - - {evaluateTestsAssertionsMessage( - metrics - )} - - - - - ) - ); - })} - -
    Candidate Test Plans
    - - {getTestPlanVersionTitle( - testPlanVersion, - { - includeVersionString: true - } - )}{' '} - ({testsCount} Test - {testsCount === 0 || - testsCount > 1 - ? `s` - : ''} - ) - -
    -
    -
    - ); - }; - - const constructTableForResultsSummary = () => { - if (!testPlanReportsExist) return borderedNone; - - let testPlanTargetsById = {}; - testPlanVersions.forEach(testPlanVersion => { - const { testPlanReports } = testPlanVersion; - - testPlanReports.forEach(testPlanReport => { - const { at, browser } = testPlanReport; - // Construct testPlanTarget - const testPlanTarget = { - id: `${at.id}${browser.id}`, - at, - browser - }; - testPlanTargetsById[testPlanTarget.id] = testPlanTarget; - }); - }); - testPlanTargetsById = alphabetizeObjectBy( - testPlanTargetsById, - keyValue => getTestPlanTargetTitle(keyValue[1]) - ); + // return 'None' element if no reports exists for AT + if (!testPlanReportsForAtExists) { + return ( + +

    + onClickExpandAtTable(atId)} + > + {atName} + + +

    + + {borderedNone} + +
    + ); + } - return ( - <> - Review Status Summary - - - - - JAWS - NVDA - VoiceOver for macOS - - - - {Object.values(testPlanVersions) - .sort((a, b) => (a.title < b.title ? -1 : 1)) - .map(testPlanVersion => { - const testPlanReports = - testPlanVersion.testPlanReports; - - let jawsDataExists = false; - let nvdaDataExists = false; - let voDataExists = false; - - Object.values(testPlanTargetsById).map( - testPlanTarget => { - const testPlanReport = - testPlanReports.find( - testPlanReport => - testPlanReport.at.id === - testPlanTarget.at.id && - testPlanReport.browser - .id === - testPlanTarget.browser - .id - ); - - if (testPlanReport) { - if ( - !jawsDataExists && - testPlanReport.at.id === '1' - ) { - jawsDataExists = true; - } - if ( - !nvdaDataExists && - testPlanReport.at.id === '2' - ) { - nvdaDataExists = true; - } - if ( - !voDataExists && - testPlanReport.at.id === '3' - ) { - voDataExists = true; - } - } - } - ); - - const allJawsIssues = []; - const allNvdaIssues = []; - const allVoIssues = []; - - const jawsTestPlanReports = - testPlanReports.filter(t => { - if (t.at.id === '1') { - allJawsIssues.push(...t.issues); - return true; - } else return false; - }); - const nvdaTestPlanReports = - testPlanReports.filter(t => { - if (t.at.id === '2') { - allNvdaIssues.push(...t.issues); - return true; - } else return false; - }); - const voTestPlanReports = - testPlanReports.filter(t => { - if (t.at.id === '3') { - allVoIssues.push(...t.issues); - return true; - } else return false; - }); - - const uniqueLinks = []; - const jawsIssues = allJawsIssues.filter(t => - uniqueFilter(t, uniqueLinks, 'link') - ); - const nvdaIssues = allNvdaIssues.filter(t => - uniqueFilter(t, uniqueLinks, 'link') - ); - const voIssues = allVoIssues.filter(t => - uniqueFilter(t, uniqueLinks, 'link') - ); - - return ( - - - - {jawsDataExists - ? getRowStatus({ - issues: jawsIssues, - isInProgressStatusExists: - jawsTestPlanReports.some( - testPlanReport => - testPlanReport.vendorReviewStatus === - 'IN_PROGRESS' - ), - isApprovedStatusExists: - jawsTestPlanReports.some( - testPlanReport => - testPlanReport.vendorReviewStatus === - 'APPROVED' - ) - }) - : none} - - - {nvdaDataExists - ? getRowStatus({ - issues: nvdaIssues, - isInProgressStatusExists: - nvdaTestPlanReports.some( - testPlanReport => - testPlanReport.vendorReviewStatus === - 'IN_PROGRESS' - ), - isApprovedStatusExists: - nvdaTestPlanReports.some( - testPlanReport => - testPlanReport.vendorReviewStatus === - 'APPROVED' - ) - }) - : none} - - - {voDataExists - ? getRowStatus({ - issues: voIssues, - isInProgressStatusExists: - voTestPlanReports.some( - testPlanReport => - testPlanReport.vendorReviewStatus === - 'IN_PROGRESS' - ), - isApprovedStatusExists: - voTestPlanReports.some( - testPlanReport => - testPlanReport.vendorReviewStatus === - 'APPROVED' - ) - }) - : none} - - - ); - })} - -
    Test Plan
    - {getTestPlanVersionTitle( - testPlanVersion, - { includeVersionString: true } - )} -
    - - ); - }; + let testPlanTargetsById = {}; + testPlanVersions.forEach(testPlanVersion => { + const { testPlanReports } = testPlanVersion; + + testPlanReports.forEach(testPlanReport => { + const { at, browser } = testPlanReport; + // Construct testPlanTarget + const testPlanTarget = { + id: `${at.id}${browser.id}`, + at, + browser + }; + testPlanTargetsById[testPlanTarget.id] = testPlanTarget; + }); + }); + testPlanTargetsById = alphabetizeObjectBy(testPlanTargetsById, keyValue => + getTestPlanTargetTitle(keyValue[1]) + ); return ( - - - Candidate Review | ARIA-AT - -

    Candidate Review

    -

    Introduction

    -

    - This page summarizes the test results for each AT and Browser - which executed the Test Plan. -

    - {constructTableForAtById('1', 'JAWS')} - {constructTableForAtById('2', 'NVDA')} - {constructTableForAtById('3', 'VoiceOver for macOS')} - {constructTableForResultsSummary()} -
    + +

    + onClickExpandAtTable(atId)} + > + {atName} + + +

    + + + + + + Last Updated + Target Completion Date + Review Status + Results Summary + + + + {Object.values(testPlanVersions) + .sort((a, b) => (a.title < b.title ? -1 : 1)) + .map(testPlanVersion => { + const testPlanReports = + testPlanVersion.testPlanReports.filter( + ({ at }) => at.id === atId + ); + const candidatePhaseReachedAt = + testPlanVersion.candidatePhaseReachedAt; + const recommendedPhaseTargetDate = + testPlanVersion.recommendedPhaseTargetDate; + + const allMetrics = []; + + let testsCount = 0; + let dataExists = false; + + Object.values(testPlanTargetsById).map(testPlanTarget => { + const testPlanReport = testPlanReports.find( + testPlanReport => + testPlanReport.at.id === testPlanTarget.at.id && + testPlanReport.at.id === atId && + testPlanReport.browser.id === testPlanTarget.browser.id + ); + + if (testPlanReport) { + const metrics = testPlanReport.metrics; + allMetrics.push(metrics); + + if (!dataExists && testPlanReport.at.id === atId) { + dataExists = true; + } + + testsCount = + metrics.testsCount > testsCount + ? metrics.testsCount + : testsCount; + } + }); + + const metrics = { + testsCount, + browsersLength: allMetrics.length, + totalTestsFailedCount: allMetrics.reduce( + (acc, obj) => acc + obj.testsFailedCount, + 0 + ), + totalAssertionsFailedCount: allMetrics.reduce( + (acc, obj) => + acc + + obj.shouldAssertionsFailedCount + + obj.mustAssertionsFailedCount, + 0 + ), + totalSupportPercent: + Math.round( + allMetrics.reduce( + (acc, obj) => acc + obj.supportPercent, + 0 + ) / allMetrics.length + ) || 0 + }; + + // Make sure issues are unique + const uniqueLinks = []; + const allIssues = testPlanReports + .map(testPlanReport => [...testPlanReport.issues]) + .flat() + .filter(t => t.isCandidateReview === true) + .filter(t => uniqueFilter(t, uniqueLinks, 'link')); + + return ( + dataExists && ( + + + + + {convertDateToString( + candidatePhaseReachedAt, + 'MMM D, YYYY' + )} + + + + + {convertDateToString( + recommendedPhaseTargetDate, + 'MMM D, YYYY' + )} + + + + {getRowStatus({ + issues: allIssues, + isInProgressStatusExists: testPlanReports.some( + testPlanReport => + testPlanReport.vendorReviewStatus === + 'IN_PROGRESS' + ), + isApprovedStatusExists: testPlanReports.some( + testPlanReport => + testPlanReport.vendorReviewStatus === 'APPROVED' + ) + })} + + + + + + + {evaluateTestsAssertionsMessage(metrics)} + + + + ) + ); + })} + +
    Candidate Test Plans
    + + {getTestPlanVersionTitle(testPlanVersion, { + includeVersionString: true + })}{' '} + ({testsCount} Test + {testsCount === 0 || testsCount > 1 ? `s` : ''}) + +
    +
    +
    + ); + }; + + const constructTableForResultsSummary = () => { + if (!testPlanReportsExist) return borderedNone; + + let testPlanTargetsById = {}; + testPlanVersions.forEach(testPlanVersion => { + const { testPlanReports } = testPlanVersion; + + testPlanReports.forEach(testPlanReport => { + const { at, browser } = testPlanReport; + // Construct testPlanTarget + const testPlanTarget = { + id: `${at.id}${browser.id}`, + at, + browser + }; + testPlanTargetsById[testPlanTarget.id] = testPlanTarget; + }); + }); + testPlanTargetsById = alphabetizeObjectBy(testPlanTargetsById, keyValue => + getTestPlanTargetTitle(keyValue[1]) + ); + + return ( + <> + Review Status Summary + + + + + JAWS + NVDA + VoiceOver for macOS + + + + {Object.values(testPlanVersions) + .sort((a, b) => (a.title < b.title ? -1 : 1)) + .map(testPlanVersion => { + const testPlanReports = testPlanVersion.testPlanReports; + + let jawsDataExists = false; + let nvdaDataExists = false; + let voDataExists = false; + + Object.values(testPlanTargetsById).map(testPlanTarget => { + const testPlanReport = testPlanReports.find( + testPlanReport => + testPlanReport.at.id === testPlanTarget.at.id && + testPlanReport.browser.id === testPlanTarget.browser.id + ); + + if (testPlanReport) { + if (!jawsDataExists && testPlanReport.at.id === '1') { + jawsDataExists = true; + } + if (!nvdaDataExists && testPlanReport.at.id === '2') { + nvdaDataExists = true; + } + if (!voDataExists && testPlanReport.at.id === '3') { + voDataExists = true; + } + } + }); + + const allJawsIssues = []; + const allNvdaIssues = []; + const allVoIssues = []; + + const jawsTestPlanReports = testPlanReports.filter(t => { + if (t.at.id === '1') { + allJawsIssues.push(...t.issues); + return true; + } else return false; + }); + const nvdaTestPlanReports = testPlanReports.filter(t => { + if (t.at.id === '2') { + allNvdaIssues.push(...t.issues); + return true; + } else return false; + }); + const voTestPlanReports = testPlanReports.filter(t => { + if (t.at.id === '3') { + allVoIssues.push(...t.issues); + return true; + } else return false; + }); + + const uniqueLinks = []; + const jawsIssues = allJawsIssues.filter(t => + uniqueFilter(t, uniqueLinks, 'link') + ); + const nvdaIssues = allNvdaIssues.filter(t => + uniqueFilter(t, uniqueLinks, 'link') + ); + const voIssues = allVoIssues.filter(t => + uniqueFilter(t, uniqueLinks, 'link') + ); + + return ( + + + + {jawsDataExists + ? getRowStatus({ + issues: jawsIssues, + isInProgressStatusExists: jawsTestPlanReports.some( + testPlanReport => + testPlanReport.vendorReviewStatus === + 'IN_PROGRESS' + ), + isApprovedStatusExists: jawsTestPlanReports.some( + testPlanReport => + testPlanReport.vendorReviewStatus === 'APPROVED' + ) + }) + : none} + + + {nvdaDataExists + ? getRowStatus({ + issues: nvdaIssues, + isInProgressStatusExists: nvdaTestPlanReports.some( + testPlanReport => + testPlanReport.vendorReviewStatus === + 'IN_PROGRESS' + ), + isApprovedStatusExists: nvdaTestPlanReports.some( + testPlanReport => + testPlanReport.vendorReviewStatus === 'APPROVED' + ) + }) + : none} + + + {voDataExists + ? getRowStatus({ + issues: voIssues, + isInProgressStatusExists: voTestPlanReports.some( + testPlanReport => + testPlanReport.vendorReviewStatus === + 'IN_PROGRESS' + ), + isApprovedStatusExists: voTestPlanReports.some( + testPlanReport => + testPlanReport.vendorReviewStatus === 'APPROVED' + ) + }) + : none} + + + ); + })} + +
    Test Plan
    + {getTestPlanVersionTitle(testPlanVersion, { + includeVersionString: true + })} +
    + ); + }; + + return ( + + + Candidate Review | ARIA-AT + +

    Candidate Review

    +

    Introduction

    +

    + This page summarizes the test results for each AT and Browser which + executed the Test Plan. +

    + {constructTableForAtById('1', 'JAWS')} + {constructTableForAtById('2', 'NVDA')} + {constructTableForAtById('3', 'VoiceOver for macOS')} + {constructTableForResultsSummary()} +
    + ); }; TestPlans.propTypes = { - testPlanVersions: PropTypes.arrayOf( + testPlanVersions: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + phase: PropTypes.string.isRequired, + gitSha: PropTypes.string, + testPlan: PropTypes.shape({ + directory: PropTypes.string + }), + metadata: PropTypes.object, + testPlanReports: PropTypes.arrayOf( PropTypes.shape({ - id: PropTypes.string.isRequired, - title: PropTypes.string.isRequired, - phase: PropTypes.string.isRequired, - gitSha: PropTypes.string, - testPlan: PropTypes.shape({ - directory: PropTypes.string - }), - metadata: PropTypes.object, - testPlanReports: PropTypes.arrayOf( - PropTypes.shape({ - id: PropTypes.string.isRequired, - metrics: PropTypes.object.isRequired, - at: PropTypes.object.isRequired, - browser: PropTypes.object.isRequired - }) - ) + id: PropTypes.string.isRequired, + metrics: PropTypes.object.isRequired, + at: PropTypes.object.isRequired, + browser: PropTypes.object.isRequired }) - ).isRequired, - triggerPageUpdate: PropTypes.func + ) + }) + ).isRequired, + triggerPageUpdate: PropTypes.func }; export default TestPlans; diff --git a/client/components/CandidateReview/index.jsx b/client/components/CandidateReview/index.jsx index 0a8373c25..ba0ded88f 100644 --- a/client/components/CandidateReview/index.jsx +++ b/client/components/CandidateReview/index.jsx @@ -5,35 +5,35 @@ import TestPlans from './TestPlans'; import { CANDIDATE_REVIEW_PAGE_QUERY } from './queries'; const CandidateReview = () => { - const { loading, data, error } = useQuery(CANDIDATE_REVIEW_PAGE_QUERY, { - fetchPolicy: 'cache-and-network' - }); + const { loading, data, error } = useQuery(CANDIDATE_REVIEW_PAGE_QUERY, { + fetchPolicy: 'cache-and-network' + }); - if (error) { - return ( - - ); - } + if (error) { + return ( + + ); + } - if (loading) { - return ( - - ); - } + if (loading) { + return ( + + ); + } - if (!data) return null; + if (!data) return null; - const testPlanVersions = data.testPlanVersions; + const testPlanVersions = data.testPlanVersions; - return ; + return ; }; export default CandidateReview; diff --git a/client/components/CandidateReview/queries.js b/client/components/CandidateReview/queries.js index 193bc82f3..4fd8ab8e2 100644 --- a/client/components/CandidateReview/queries.js +++ b/client/components/CandidateReview/queries.js @@ -1,53 +1,53 @@ import { gql } from '@apollo/client'; export const CANDIDATE_REVIEW_PAGE_QUERY = gql` - query { - testPlanVersions(phases: [CANDIDATE]) { - id - phase - title - gitSha - testPlan { - directory - } - metadata - versionString - candidatePhaseReachedAt - recommendedPhaseTargetDate - testPlanReports(isFinal: true) { - id - metrics - at { - id - name - } - latestAtVersionReleasedAt { - id - name - releasedAt - } - browser { - id - name - } - testPlanVersion { - id - title - gitSha - testPlan { - directory - } - metadata - updatedAt - } - vendorReviewStatus - issues { - link - isOpen - isCandidateReview - feedbackType - } - } + query { + testPlanVersions(phases: [CANDIDATE]) { + id + phase + title + gitSha + testPlan { + directory + } + metadata + versionString + candidatePhaseReachedAt + recommendedPhaseTargetDate + testPlanReports(isFinal: true) { + id + metrics + at { + id + name } + latestAtVersionReleasedAt { + id + name + releasedAt + } + browser { + id + name + } + testPlanVersion { + id + title + gitSha + testPlan { + directory + } + metadata + updatedAt + } + vendorReviewStatus + issues { + link + isOpen + isCandidateReview + feedbackType + } + } } + } `; diff --git a/client/components/ConfirmAuth/index.jsx b/client/components/ConfirmAuth/index.jsx index 6bc96ef9e..c39bd0104 100644 --- a/client/components/ConfirmAuth/index.jsx +++ b/client/components/ConfirmAuth/index.jsx @@ -6,30 +6,30 @@ import { ME_QUERY } from '../App/queries'; import { evaluateAuth } from '../../utils/evaluateAuth'; const ConfirmAuth = ({ children, requiredPermission }) => { - const { data } = useQuery(ME_QUERY); + const { data } = useQuery(ME_QUERY); - const auth = evaluateAuth(data && data.me ? data.me : {}); - const { roles, username, isAdmin, isSignedIn } = auth; + const auth = evaluateAuth(data && data.me ? data.me : {}); + const { roles, username, isAdmin, isSignedIn } = auth; - if (!username) return ; + if (!username) return ; - // If you are an admin, you can access all other role actions by default - const authConfirmed = - isSignedIn && (roles.includes(requiredPermission) || isAdmin); + // If you are an admin, you can access all other role actions by default + const authConfirmed = + isSignedIn && (roles.includes(requiredPermission) || isAdmin); - if (!authConfirmed) { - return ; - } + if (!authConfirmed) { + return ; + } - return children; + return children; }; ConfirmAuth.propTypes = { - children: PropTypes.oneOfType([ - PropTypes.arrayOf(PropTypes.node), - PropTypes.node - ]).isRequired, - requiredPermission: PropTypes.string + children: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.node), + PropTypes.node + ]).isRequired, + requiredPermission: PropTypes.string }; export default ConfirmAuth; diff --git a/client/components/DataManagement/DataManagement.css b/client/components/DataManagement/DataManagement.css index 5513e08cf..b26fd6e51 100644 --- a/client/components/DataManagement/DataManagement.css +++ b/client/components/DataManagement/DataManagement.css @@ -1,52 +1,52 @@ .data-management.table { - padding: 0; - margin: 0; - height: 100%; + padding: 0; + margin: 0; + height: 100%; } .data-management.table thead tr th { - padding-left: 0.75rem; - padding-right: 0.75rem; + padding-left: 0.75rem; + padding-right: 0.75rem; } .data-management.table tbody tr th { - padding: 0.75rem; + padding: 0.75rem; } /* Test Plan column & Covered ATs column */ .data-management.table thead tr th:nth-of-type(-n + 2), .data-management.table tbody tr th:nth-of-type(1), .data-management.table tbody tr td:nth-of-type(1) { - max-width: 200px; + max-width: 200px; } /* RD, DRAFT, CANDIDATE & RECOMMENDED columns */ .data-management.table thead tr th:nth-last-of-type(-n + 4), .data-management.table tbody tr td:nth-last-of-type(-n + 4) { - max-width: 150px; + max-width: 150px; } .data-management.table tbody tr td { - position: relative; - padding: 0; - height: 100%; + position: relative; + padding: 0; + height: 100%; } .data-management.table tbody tr td > * { - padding: 0.75rem; + padding: 0.75rem; } .data-management.table tbody tr:nth-of-type(even), .table-striped tbody tr:nth-of-type(even) { - background-color: #fff; + background-color: #fff; } .change-phase { - margin-left: 12px; - margin-right: 12px; + margin-left: 12px; + margin-right: 12px; } .change-phase .btn { - display: flex; - align-items: center; + display: flex; + align-items: center; } diff --git a/client/components/DataManagement/DataManagementRow/index.jsx b/client/components/DataManagement/DataManagementRow/index.jsx index 14c0c9295..e597f5dfd 100644 --- a/client/components/DataManagement/DataManagementRow/index.jsx +++ b/client/components/DataManagement/DataManagementRow/index.jsx @@ -4,14 +4,14 @@ import PropTypes from 'prop-types'; import styled from '@emotion/styled'; import { Button } from 'react-bootstrap'; import { - UPDATE_TEST_PLAN_VERSION_PHASE, - UPDATE_TEST_PLAN_VERSION_RECOMMENDED_TARGET_DATE + UPDATE_TEST_PLAN_VERSION_PHASE, + UPDATE_TEST_PLAN_VERSION_RECOMMENDED_TARGET_DATE } from '../queries'; import { LoadingStatus, useTriggerLoad } from '../../common/LoadingStatus'; import { - checkDaysBetweenDates, - convertDateToString, - convertStringFormatToAnotherFormat + checkDaysBetweenDates, + convertDateToString, + convertStringFormatToAnotherFormat } from '../../../utils/formatter'; import { derivePhaseName } from '@client/utils/aria'; import { THEMES, useThemedModal } from '@client/hooks/useThemedModal'; @@ -25,1194 +25,1098 @@ import { differenceBy, uniq as unique, uniqBy as uniqueBy } from 'lodash'; import { getVersionData } from '../utils'; const StatusCell = styled.div` - display: flex; - flex-direction: column; - height: 100%; + display: flex; + flex-direction: column; + height: 100%; - .review-text { - margin-top: 1rem; - font-size: 14px; - text-align: center; + .review-text { + margin-top: 1rem; + font-size: 14px; + text-align: center; - margin-bottom: 88px; - } + margin-bottom: 88px; + } - .versions-in-progress { - display: flex; - justify-content: center; - padding: 12px; - font-size: 14px; + .versions-in-progress { + display: flex; + justify-content: center; + padding: 12px; + font-size: 14px; - position: absolute; - bottom: 0; - left: 0; - right: 0; + position: absolute; + bottom: 0; + left: 0; + right: 0; - color: #6a7989; - background: #f6f8fa; + color: #6a7989; + background: #f6f8fa; - > span.pill { - display: flex; - width: fit-content; - height: 20px; + > span.pill { + display: flex; + width: fit-content; + height: 20px; - justify-content: center; - align-items: center; - align-self: center; + justify-content: center; + align-items: center; + align-self: center; - margin-right: 6px; - min-width: 40px; - border-radius: 14px; + margin-right: 6px; + min-width: 40px; + border-radius: 14px; - background: #6a7989; - color: white; - } + background: #6a7989; + color: white; } + } `; const PhaseCell = styled.div` - padding: 0 !important; /* override padding for td and add margins into specific children */ + padding: 0 !important; /* override padding for td and add margins into specific children */ + display: flex; + flex-direction: column; + height: 100%; + + > span.review-complete { + display: block; + font-size: 14px; + text-align: center; + margin: 12px 0.75rem; + color: #333f4d; + } + + > span.more { display: flex; flex-direction: column; - height: 100%; - - > span.review-complete { - display: block; - font-size: 14px; - text-align: center; - margin: 12px 0.75rem; - color: #333f4d; - } - - > span.more { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; + justify-content: center; + align-items: center; - padding: 0.5rem; - font-size: 14px; + padding: 0.5rem; + font-size: 14px; - margin-top: 6px; + margin-top: 6px; - color: #6a7989; - background: #f6f8fa; + color: #6a7989; + background: #f6f8fa; - > span.more-issues-container { - width: 100%; - text-align: center; + > span.more-issues-container { + width: 100%; + text-align: center; - .issues { - margin-right: 4px; - } + .issues { + margin-right: 4px; + } - align-items: center; - } + align-items: center; + } - > span.target-days-container { - text-align: center; + > span.target-days-container { + text-align: center; - button { - appearance: none; - border: none; - background: none; - color: inherit; + button { + appearance: none; + border: none; + background: none; + color: inherit; - margin: 0; - padding: 0; - } - } + margin: 0; + padding: 0; + } } + } - > .advance-button { - margin: 12px 0.75rem; - width: calc(100% - 1.5rem); - } + > .advance-button { + margin: 12px 0.75rem; + width: calc(100% - 1.5rem); + } `; const NoneText = styled.span` - display: flex; - justify-content: center; - align-items: center; - - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - - font-style: italic; - color: #6a7989; + display: flex; + justify-content: center; + align-items: center; + + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + + font-style: italic; + color: #6a7989; `; const DataManagementRow = ({ - isAdmin, - ats, - testPlan, - testPlanVersions, - setTestPlanVersions, - tableRowIndex + isAdmin, + ats, + testPlan, + testPlanVersions, + setTestPlanVersions, + tableRowIndex }) => { - const { triggerLoad, loadingMessage } = useTriggerLoad(); - const { - themedModal, - showThemedModal, - setShowThemedModal, - setThemedModalTitle, - setThemedModalContent, - setFocusRef - } = useThemedModal({ - type: THEMES.WARNING, - title: 'Error Updating Test Plan Status' - }); - const [updateTestPlanVersionPhaseMutation] = useMutation( - UPDATE_TEST_PLAN_VERSION_PHASE + const { triggerLoad, loadingMessage } = useTriggerLoad(); + const { + themedModal, + showThemedModal, + setShowThemedModal, + setThemedModalTitle, + setThemedModalContent, + setFocusRef + } = useThemedModal({ + type: THEMES.WARNING, + title: 'Error Updating Test Plan Status' + }); + const [updateTestPlanVersionPhaseMutation] = useMutation( + UPDATE_TEST_PLAN_VERSION_PHASE + ); + const [updateTestPlanVersionRecommendedTargetDate] = useMutation( + UPDATE_TEST_PLAN_VERSION_RECOMMENDED_TARGET_DATE + ); + + // State + const [activePhases, setActivePhases] = useState({}); + const [rdTestPlanVersions, setRdTestPlanVersions] = useState([]); + const [draftTestPlanVersions, setDraftTestPlanVersions] = useState([]); + const [candidateTestPlanVersions, setCandidateTestPlanVersions] = useState( + [] + ); + const [recommendedTestPlanVersions, setRecommendedTestPlanVersions] = + useState([]); + + const [showAdvanceModal, setShowAdvanceModal] = useState(false); + const [advanceModalData, setAdvanceModalData] = useState({}); + + const [showUpdateTargetModal, setShowUpdateTargetModal] = useState(false); + const [updateTargetModalData, setUpdateTargetModalData] = useState({}); + + const draftVersionStringRef = useRef(); + const candidateVersionStringRef = useRef(); + const recommendedVersionStringRef = useRef(); + const updateTargetRef = useRef(); + + useEffect(() => { + // TestPlanVersions separated by current TestPlan's phase + setActivePhases({}); + setRdTestPlanVersions( + testPlanVersions.filter(({ phase }) => phase === 'RD') + ); + setDraftTestPlanVersions( + testPlanVersions.filter(({ phase }) => phase === 'DRAFT') ); - const [updateTestPlanVersionRecommendedTargetDate] = useMutation( - UPDATE_TEST_PLAN_VERSION_RECOMMENDED_TARGET_DATE + setCandidateTestPlanVersions( + testPlanVersions.filter(({ phase }) => phase === 'CANDIDATE') ); + setRecommendedTestPlanVersions( + testPlanVersions.filter(({ phase }) => phase === 'RECOMMENDED') + ); + }, [testPlanVersions]); + + const completedRequiredReports = testPlanVersion => { + const reportAtBrowsers = testPlanVersion.testPlanReports + .filter(testPlanReport => testPlanReport.isFinal) + .map(report => { + return [report.at.id, report.browser.id]; + }); + + let browsersKey; + if (testPlanVersion.phase === 'DRAFT') { + browsersKey = 'candidateBrowsers'; + } else if (testPlanVersion.phase === 'CANDIDATE') { + browsersKey = 'recommendedBrowsers'; + } else { + throw new Error('Unexpected case'); + } - // State - const [activePhases, setActivePhases] = useState({}); - const [rdTestPlanVersions, setRdTestPlanVersions] = useState([]); - const [draftTestPlanVersions, setDraftTestPlanVersions] = useState([]); - const [candidateTestPlanVersions, setCandidateTestPlanVersions] = useState( - [] + let requiredAtBrowsers = []; + ats.forEach(at => { + const browsers = at[browsersKey]; + browsers.forEach(browser => { + requiredAtBrowsers.push([at.id, browser.id]); + }); + }); + + const missingReports = differenceBy( + requiredAtBrowsers, + reportAtBrowsers, + (atId, browserId) => `${atId},${browserId}` ); - const [recommendedTestPlanVersions, setRecommendedTestPlanVersions] = - useState([]); - const [showAdvanceModal, setShowAdvanceModal] = useState(false); - const [advanceModalData, setAdvanceModalData] = useState({}); + return missingReports.length === 0; + }; + + const handleClickUpdateTestPlanVersionPhase = async ( + testPlanVersionId, + phase, + testPlanVersionDataToInclude + ) => { + try { + await triggerLoad(async () => { + const result = await updateTestPlanVersionPhaseMutation({ + variables: { + testPlanVersionId, + phase, + testPlanVersionDataToIncludeId: testPlanVersionDataToInclude?.id + } + }); - const [showUpdateTargetModal, setShowUpdateTargetModal] = useState(false); - const [updateTargetModalData, setUpdateTargetModalData] = useState({}); + const updatedTestPlanVersion = + result.data.testPlanVersion.updatePhase.testPlanVersion; + setTestPlanVersions(prevTestPlanVersions => { + let testPlanVersions = [...prevTestPlanVersions]; - const draftVersionStringRef = useRef(); - const candidateVersionStringRef = useRef(); - const recommendedVersionStringRef = useRef(); - const updateTargetRef = useRef(); + const index = testPlanVersions.findIndex( + testPlanVersion => testPlanVersion.id === updatedTestPlanVersion.id + ); + if (index !== -1) testPlanVersions[index] = updatedTestPlanVersion; - useEffect(() => { - // TestPlanVersions separated by current TestPlan's phase - setActivePhases({}); - setRdTestPlanVersions( - testPlanVersions.filter(({ phase }) => phase === 'RD') - ); - setDraftTestPlanVersions( - testPlanVersions.filter(({ phase }) => phase === 'DRAFT') - ); - setCandidateTestPlanVersions( - testPlanVersions.filter(({ phase }) => phase === 'CANDIDATE') - ); - setRecommendedTestPlanVersions( - testPlanVersions.filter(({ phase }) => phase === 'RECOMMENDED') - ); - }, [testPlanVersions]); - - const completedRequiredReports = testPlanVersion => { - const reportAtBrowsers = testPlanVersion.testPlanReports - .filter(testPlanReport => testPlanReport.isFinal) - .map(report => { - return [report.at.id, report.browser.id]; - }); - - let browsersKey; - if (testPlanVersion.phase === 'DRAFT') { - browsersKey = 'candidateBrowsers'; - } else if (testPlanVersion.phase === 'CANDIDATE') { - browsersKey = 'recommendedBrowsers'; - } else { - throw new Error('Unexpected case'); - } + return testPlanVersions; + }); - let requiredAtBrowsers = []; - ats.forEach(at => { - const browsers = at[browsersKey]; - browsers.forEach(browser => { - requiredAtBrowsers.push([at.id, browser.id]); - }); + setTimeout(() => { + if (phase === 'DRAFT' && draftVersionStringRef.current) + draftVersionStringRef.current.focus(); + + if (phase === 'CANDIDATE' && candidateVersionStringRef.current) + candidateVersionStringRef.current.focus(); + + if (phase === 'RECOMMENDED' && recommendedVersionStringRef.current) + recommendedVersionStringRef.current.focus(); + }, 250); + }, 'Updating Test Plan Version Phase'); + } catch (e) { + console.error(e.message); + setShowThemedModal(true); + setThemedModalTitle('Error Updating Test Plan Version Phase'); + setThemedModalContent(<>{e.message}); + } + }; + + const handleClickUpdateTestPlanVersionRecommendedPhaseTargetDate = async ({ + updatedDateText + }) => { + setShowUpdateTargetModal(false); + try { + await triggerLoad(async () => { + const result = await updateTestPlanVersionRecommendedTargetDate({ + variables: { + testPlanVersionId: updateTargetModalData.testPlanVersionId, + recommendedPhaseTargetDate: + convertStringFormatToAnotherFormat(updatedDateText) + } + }); + const updatedTestPlanVersion = + result.data.testPlanVersion.updateRecommendedPhaseTargetDate + .testPlanVersion; + setTestPlanVersions(prevTestPlanVersions => { + let testPlanVersions = [...prevTestPlanVersions]; + + const index = testPlanVersions.findIndex( + testPlanVersion => testPlanVersion.id === updatedTestPlanVersion.id + ); + if (index !== -1) testPlanVersions[index] = updatedTestPlanVersion; + + return testPlanVersions; }); - const missingReports = differenceBy( - requiredAtBrowsers, - reportAtBrowsers, - (atId, browserId) => `${atId},${browserId}` - ); + setTimeout(() => { + if (updateTargetRef.current) updateTargetRef.current.focus(); + }, 250); + }, 'Updating Test Plan Version Recommended Phase Target Date'); + } catch (e) { + console.error(e.message); + setShowThemedModal(true); + setThemedModalTitle( + 'Error Updating Test Plan Version Recommended Phase Target Date' + ); + setThemedModalContent(<>{e.message}); + } + }; + + const renderCellForCoveredAts = () => { + const atNames = ats.map(({ name }) => name); + + if (atNames.length > 1) { + return ( +
    + {atNames.map((item, index) => ( + + {item} + {index !== atNames.length - 1 ? ( + index === atNames.length - 2 ? ( + and + ) : ( + , + ) + ) : null} + + ))} +
    + ); + } else if (atNames.length === 1) return {atNames[0]}; + else return N/A; + }; + + const renderCellForOverallStatus = () => { + const phaseView = (phase, versionDate) => { + let phaseText = ''; + + switch (phase) { + case 'RD': + phaseText = 'Complete '; + break; + case 'DRAFT': + case 'CANDIDATE': + phaseText = 'Review Started '; + break; + case 'RECOMMENDED': + phaseText = 'Since '; + break; + } + + const dateString = convertDateToString(versionDate, 'MMM D, YYYY'); + + return ( + <> + {phase} +

    + {phaseText} + {dateString} +

    + + ); + }; - return missingReports.length === 0; + const versionsInProgressView = versionsCount => { + return versionsCount ? ( + + <> + +{versionsCount} New Version + {versionsCount === 1 ? '' : 's'} in Progress + + + ) : null; }; - const handleClickUpdateTestPlanVersionPhase = async ( - testPlanVersionId, - phase, - testPlanVersionDataToInclude + const otherVersionsInProgressCount = ( + currentPhase, // To exclude in check + excludedPhases = [] ) => { - try { - await triggerLoad(async () => { - const result = await updateTestPlanVersionPhaseMutation({ - variables: { - testPlanVersionId, - phase, - testPlanVersionDataToIncludeId: - testPlanVersionDataToInclude?.id - } - }); - - const updatedTestPlanVersion = - result.data.testPlanVersion.updatePhase.testPlanVersion; - setTestPlanVersions(prevTestPlanVersions => { - let testPlanVersions = [...prevTestPlanVersions]; - - const index = testPlanVersions.findIndex( - testPlanVersion => - testPlanVersion.id === updatedTestPlanVersion.id - ); - if (index !== -1) - testPlanVersions[index] = updatedTestPlanVersion; - - return testPlanVersions; - }); - - setTimeout(() => { - if (phase === 'DRAFT' && draftVersionStringRef.current) - draftVersionStringRef.current.focus(); - - if ( - phase === 'CANDIDATE' && - candidateVersionStringRef.current - ) - candidateVersionStringRef.current.focus(); - - if ( - phase === 'RECOMMENDED' && - recommendedVersionStringRef.current - ) - recommendedVersionStringRef.current.focus(); - }, 250); - }, 'Updating Test Plan Version Phase'); - } catch (e) { - console.error(e.message); - setShowThemedModal(true); - setThemedModalTitle('Error Updating Test Plan Version Phase'); - setThemedModalContent(<>{e.message}); - } + const otherVersionsInProgress = Object.keys(activePhases).filter( + e => ![currentPhase, ...excludedPhases].includes(e) + ); + return otherVersionsInProgress.length; }; - const handleClickUpdateTestPlanVersionRecommendedPhaseTargetDate = async ({ - updatedDateText - }) => { - setShowUpdateTargetModal(false); - try { - await triggerLoad(async () => { - const result = await updateTestPlanVersionRecommendedTargetDate( - { - variables: { - testPlanVersionId: - updateTargetModalData.testPlanVersionId, - recommendedPhaseTargetDate: - convertStringFormatToAnotherFormat( - updatedDateText - ) - } - } - ); - const updatedTestPlanVersion = - result.data.testPlanVersion.updateRecommendedPhaseTargetDate - .testPlanVersion; - setTestPlanVersions(prevTestPlanVersions => { - let testPlanVersions = [...prevTestPlanVersions]; - - const index = testPlanVersions.findIndex( - testPlanVersion => - testPlanVersion.id === updatedTestPlanVersion.id - ); - if (index !== -1) - testPlanVersions[index] = updatedTestPlanVersion; - - return testPlanVersions; - }); - - setTimeout(() => { - if (updateTargetRef.current) - updateTargetRef.current.focus(); - }, 250); - }, 'Updating Test Plan Version Recommended Phase Target Date'); - } catch (e) { - console.error(e.message); - setShowThemedModal(true); - setThemedModalTitle( - 'Error Updating Test Plan Version Recommended Phase Target Date' - ); - setThemedModalContent(<>{e.message}); - } + /** + * Uses the truthy state of defined dates for a TestPlanVersion to + * determine if the version was ever in that phase at any given point. + * Useful if the phase of the TestPlanVersion is currently DEPRECATED. + * @param {"RECOMMENDED"|"CANDIDATE"|"DRAFT"} phase + * @returns {TestPlanVersion[]} + */ + const getVersionsThatReachedPhase = phase => { + switch (phase) { + case 'RECOMMENDED': + return testPlanVersions.filter( + ({ recommendedPhaseReachedAt }) => !!recommendedPhaseReachedAt + ); + case 'CANDIDATE': + return testPlanVersions.filter( + ({ candidatePhaseReachedAt, recommendedPhaseReachedAt }) => + !!candidatePhaseReachedAt && !recommendedPhaseReachedAt + ); + case 'DRAFT': + return testPlanVersions.filter( + ({ + draftPhaseReachedAt, + candidatePhaseReachedAt, + recommendedPhaseReachedAt + }) => + !!draftPhaseReachedAt && + !candidatePhaseReachedAt && + !recommendedPhaseReachedAt + ); + } }; - const renderCellForCoveredAts = () => { - const atNames = ats.map(({ name }) => name); - - if (atNames.length > 1) { - return ( -
    - {atNames.map((item, index) => ( - - {item} - {index !== atNames.length - 1 ? ( - index === atNames.length - 2 ? ( - and - ) : ( - , - ) - ) : null} - - ))} -
    - ); - } else if (atNames.length === 1) return {atNames[0]}; - else return N/A; - }; + const versionsThatReachedRecommended = + getVersionsThatReachedPhase('RECOMMENDED'); + const versionsThatReachedCandidate = + getVersionsThatReachedPhase('CANDIDATE'); + const versionsThatReachedDraft = getVersionsThatReachedPhase('DRAFT'); + + if (versionsThatReachedRecommended.length) { + const { earliestVersionDate } = getVersionData( + versionsThatReachedRecommended, + 'recommendedPhaseReachedAt' + ); + const phase = 'RECOMMENDED'; + const versionsInProgressCount = otherVersionsInProgressCount(phase); + + return ( + + {phaseView(phase, earliestVersionDate)} + {versionsInProgressView(versionsInProgressCount)} + + ); + } - const renderCellForOverallStatus = () => { - const phaseView = (phase, versionDate) => { - let phaseText = ''; - - switch (phase) { - case 'RD': - phaseText = 'Complete '; - break; - case 'DRAFT': - case 'CANDIDATE': - phaseText = 'Review Started '; - break; - case 'RECOMMENDED': - phaseText = 'Since '; - break; - } + if (versionsThatReachedCandidate.length) { + const { earliestVersionDate } = getVersionData( + versionsThatReachedCandidate, + 'candidatePhaseReachedAt' + ); + const phase = 'CANDIDATE'; + const versionsInProgressCount = otherVersionsInProgressCount(phase, [ + 'RECOMMENDED' + ]); + + return ( + + {phaseView(phase, earliestVersionDate)} + {versionsInProgressView(versionsInProgressCount)} + + ); + } - const dateString = convertDateToString(versionDate, 'MMM D, YYYY'); + if (versionsThatReachedDraft.length) { + const { earliestVersionDate } = getVersionData( + versionsThatReachedDraft, + 'draftPhaseReachedAt' + ); + const phase = 'DRAFT'; + const versionsInProgressCount = otherVersionsInProgressCount(phase, [ + 'RECOMMENDED', + 'CANDIDATE' + ]); + + return ( + + {phaseView(phase, earliestVersionDate)} + {versionsInProgressView(versionsInProgressCount)} + + ); + } - return ( - <> - {phase} -

    - {phaseText} - {dateString} -

    - - ); - }; + if (rdTestPlanVersions.length) { + const { latestVersion, latestVersionDate } = + getVersionData(rdTestPlanVersions); + const { phase } = latestVersion; + return {phaseView(phase, latestVersionDate)}; + } - const versionsInProgressView = versionsCount => { - return versionsCount ? ( - - <> - +{versionsCount} New - Version - {versionsCount === 1 ? '' : 's'} in Progress - - - ) : null; - }; + // Should never be called but just in case + return null; + }; - const otherVersionsInProgressCount = ( - currentPhase, // To exclude in check - excludedPhases = [] - ) => { - const otherVersionsInProgress = Object.keys(activePhases).filter( - e => ![currentPhase, ...excludedPhases].includes(e) - ); - return otherVersionsInProgress.length; - }; + const renderCellForPhase = (phase, testPlanVersions = []) => { + const defaultView = N/A; - /** - * Uses the truthy state of defined dates for a TestPlanVersion to - * determine if the version was ever in that phase at any given point. - * Useful if the phase of the TestPlanVersion is currently DEPRECATED. - * @param {"RECOMMENDED"|"CANDIDATE"|"DRAFT"} phase - * @returns {TestPlanVersion[]} - */ - const getVersionsThatReachedPhase = phase => { - switch (phase) { - case 'RECOMMENDED': - return testPlanVersions.filter( - ({ recommendedPhaseReachedAt }) => - !!recommendedPhaseReachedAt - ); - case 'CANDIDATE': - return testPlanVersions.filter( - ({ - candidatePhaseReachedAt, - recommendedPhaseReachedAt - }) => - !!candidatePhaseReachedAt && - !recommendedPhaseReachedAt - ); - case 'DRAFT': - return testPlanVersions.filter( - ({ - draftPhaseReachedAt, - candidatePhaseReachedAt, - recommendedPhaseReachedAt - }) => - !!draftPhaseReachedAt && - !candidatePhaseReachedAt && - !recommendedPhaseReachedAt - ); - } + const insertActivePhaseForTestPlan = testPlanVersion => { + if (!activePhases[phase]) { + const result = { + ...activePhases, + [phase]: testPlanVersion }; + setActivePhases(result); + } + }; - const versionsThatReachedRecommended = - getVersionsThatReachedPhase('RECOMMENDED'); - const versionsThatReachedCandidate = - getVersionsThatReachedPhase('CANDIDATE'); - const versionsThatReachedDraft = getVersionsThatReachedPhase('DRAFT'); - - if (versionsThatReachedRecommended.length) { - const { earliestVersionDate } = getVersionData( - versionsThatReachedRecommended, - 'recommendedPhaseReachedAt' - ); - const phase = 'RECOMMENDED'; - const versionsInProgressCount = otherVersionsInProgressCount(phase); - - return ( - - {phaseView(phase, earliestVersionDate)} - {versionsInProgressView(versionsInProgressCount)} - - ); + switch (phase) { + case 'RD': { + // If the latest version of the plan is in the draft, candidate, or recommended + // phase, show string "N/A". This should also apply if there is no R&D phase + // TestPlanVersions + if (!testPlanVersions.length) return defaultView; + + const { latestVersion, latestVersionDate } = + getVersionData(testPlanVersions); + + const otherTestPlanVersions = [ + ...draftTestPlanVersions, + ...candidateTestPlanVersions, + ...recommendedTestPlanVersions + ]; + + if (otherTestPlanVersions.length) { + const { latestVersionDate: otherLatestVersionDate } = getVersionData( + otherTestPlanVersions + ); + if (otherLatestVersionDate > latestVersionDate) { + return defaultView; + } } - if (versionsThatReachedCandidate.length) { - const { earliestVersionDate } = getVersionData( - versionsThatReachedCandidate, - 'candidatePhaseReachedAt' - ); - const phase = 'CANDIDATE'; - const versionsInProgressCount = otherVersionsInProgressCount( - phase, - ['RECOMMENDED'] - ); - - return ( - - {phaseView(phase, earliestVersionDate)} - {versionsInProgressView(versionsInProgressCount)} - - ); + // If there is an earlier version that is draft or higher, and that version has some test plan + // runs in the test queue, this button will run the process for updating existing + // reports and preserving data for tests that have not changed. + let testPlanVersionDataToInclude; + if (otherTestPlanVersions.length) { + const { + latestVersion: otherLatestVersion, + latestVersionDate: otherLatestVersionDate + } = getVersionData(otherTestPlanVersions); + + if (otherLatestVersionDate < latestVersionDate) + testPlanVersionDataToInclude = otherLatestVersion; } - if (versionsThatReachedDraft.length) { - const { earliestVersionDate } = getVersionData( - versionsThatReachedDraft, - 'draftPhaseReachedAt' - ); - const phase = 'DRAFT'; - const versionsInProgressCount = otherVersionsInProgressCount( - phase, - ['RECOMMENDED', 'CANDIDATE'] - ); - - return ( - - {phaseView(phase, earliestVersionDate)} - {versionsInProgressView(versionsInProgressCount)} - + // Otherwise, show VERSION_STRING link with a draft transition button. Phase is + // "active" + insertActivePhaseForTestPlan(latestVersion); + + return ( + + + {latestVersion.versionString} + + {isAdmin && ( + + )} + + ); + } + case 'DRAFT': { + let latestVersion, latestVersionDate; + + let otherTestPlanVersions = [ + ...candidateTestPlanVersions, + ...recommendedTestPlanVersions + ]; + + if (testPlanVersions.length) { + const { + latestVersion: _latestVersion, + latestVersionDate: _latestVersionDate + } = getVersionData(testPlanVersions); + + latestVersion = _latestVersion; + latestVersionDate = _latestVersionDate; + + if (otherTestPlanVersions.length) + otherTestPlanVersions = otherTestPlanVersions.filter( + other => new Date(other.updatedAt) > latestVersionDate ); } - if (rdTestPlanVersions.length) { - const { latestVersion, latestVersionDate } = - getVersionData(rdTestPlanVersions); - const { phase } = latestVersion; - return ( - {phaseView(phase, latestVersionDate)} - ); + // If a version of the plan is not in the draft phase and there are no versions in + // later phases, show string "Not Started" + if (![...testPlanVersions, ...otherTestPlanVersions].length) + return Not Started; + + // If a version of the plan is not in the draft phase and there is a version in at + // least one of candidate or recommended phases, show string "Review of + // VERSION_STRING completed DATE" + if (otherTestPlanVersions.length) { + const { latestVersion: otherLatestVersion } = getVersionData( + otherTestPlanVersions + ); + + const completionDate = otherLatestVersion.candidatePhaseReachedAt; + + return ( + + + {otherLatestVersion.versionString} + + + Review Completed  + {convertDateToString(completionDate, 'MMM D, YYYY')} + + + ); } - // Should never be called but just in case - return null; - }; - - const renderCellForPhase = (phase, testPlanVersions = []) => { - const defaultView = N/A; - - const insertActivePhaseForTestPlan = testPlanVersion => { - if (!activePhases[phase]) { - const result = { - ...activePhases, - [phase]: testPlanVersion - }; - setActivePhases(result); - } - }; - - switch (phase) { - case 'RD': { - // If the latest version of the plan is in the draft, candidate, or recommended - // phase, show string "N/A". This should also apply if there is no R&D phase - // TestPlanVersions - if (!testPlanVersions.length) return defaultView; - - const { latestVersion, latestVersionDate } = - getVersionData(testPlanVersions); - - const otherTestPlanVersions = [ - ...draftTestPlanVersions, - ...candidateTestPlanVersions, - ...recommendedTestPlanVersions - ]; - - if (otherTestPlanVersions.length) { - const { latestVersionDate: otherLatestVersionDate } = - getVersionData(otherTestPlanVersions); - if (otherLatestVersionDate > latestVersionDate) { - return defaultView; - } - } - - // If there is an earlier version that is draft or higher, and that version has some test plan - // runs in the test queue, this button will run the process for updating existing - // reports and preserving data for tests that have not changed. - let testPlanVersionDataToInclude; - if (otherTestPlanVersions.length) { - const { - latestVersion: otherLatestVersion, - latestVersionDate: otherLatestVersionDate - } = getVersionData(otherTestPlanVersions); - - if (otherLatestVersionDate < latestVersionDate) - testPlanVersionDataToInclude = otherLatestVersion; - } - - // Otherwise, show VERSION_STRING link with a draft transition button. Phase is - // "active" - insertActivePhaseForTestPlan(latestVersion); - - return ( - - - {latestVersion.versionString} - - {isAdmin && ( - - )} - - ); - } - case 'DRAFT': { - let latestVersion, latestVersionDate; - - let otherTestPlanVersions = [ - ...candidateTestPlanVersions, - ...recommendedTestPlanVersions - ]; - - if (testPlanVersions.length) { - const { - latestVersion: _latestVersion, - latestVersionDate: _latestVersionDate - } = getVersionData(testPlanVersions); - - latestVersion = _latestVersion; - latestVersionDate = _latestVersionDate; - - if (otherTestPlanVersions.length) - otherTestPlanVersions = otherTestPlanVersions.filter( - other => - new Date(other.updatedAt) > latestVersionDate + // Link with text "VERSION_STRING" that targets the single-page view of the plan. + // If required reports are complete and user is an admin, show "Advance to + // Candidate" button. + if (testPlanVersions.length) { + // If there is an earlier version that is candidate or higher and that version has some + // test plan runs in the test queue, this button will run the process for + // updating existing reports and preserving data for tests that have not + // changed. + let testPlanVersionDataToInclude; + const testPlanVersionsDataToInclude = [ + ...candidateTestPlanVersions, + ...recommendedTestPlanVersions + ]; + if (testPlanVersionsDataToInclude.length) { + const { + latestVersion: testPlanDataToIncludeLatestVersion, + latestVersionDate: testPlanDataToIncludeLatestVersionDate + } = getVersionData(testPlanVersionsDataToInclude); + + if (testPlanDataToIncludeLatestVersionDate < latestVersionDate) + testPlanVersionDataToInclude = testPlanDataToIncludeLatestVersion; + } + + let coveredReports = []; + latestVersion.testPlanReports.forEach(testPlanReport => { + const markedFinalAt = testPlanReport.markedFinalAt; + const atName = testPlanReport.at.name; + const browserName = testPlanReport.browser.name; + const value = `${atName}_${browserName}`; + + if (markedFinalAt && !coveredReports.includes(value)) + coveredReports.push(value); + }); + + // Phase is "active" + insertActivePhaseForTestPlan(latestVersion); + + return ( + + + {latestVersion.versionString} + + {isAdmin && completedRequiredReports(latestVersion) && ( + + )} + + + + + ); + } + return defaultView; + } + case 'CANDIDATE': { + let latestVersion, latestVersionDate; - // Phase is "active" - insertActivePhaseForTestPlan(latestVersion); - - return ( - - - {latestVersion.versionString} - - {isAdmin && - completedRequiredReports(latestVersion) && ( - - )} - - - - - ); - } - return defaultView; - } - case 'CANDIDATE': { - let latestVersion, latestVersionDate; + let otherTestPlanVersions = [...recommendedTestPlanVersions]; - let otherTestPlanVersions = [...recommendedTestPlanVersions]; + if (testPlanVersions.length) { + const { + latestVersion: _latestVersion, + latestVersionDate: _latestVersionDate + } = getVersionData(testPlanVersions); - if (testPlanVersions.length) { - const { - latestVersion: _latestVersion, - latestVersionDate: _latestVersionDate - } = getVersionData(testPlanVersions); + latestVersion = _latestVersion; + latestVersionDate = _latestVersionDate; - latestVersion = _latestVersion; - latestVersionDate = _latestVersionDate; + if (otherTestPlanVersions.length) + otherTestPlanVersions = otherTestPlanVersions.filter( + other => new Date(other.updatedAt) > latestVersionDate + ); + } - if (otherTestPlanVersions.length) - otherTestPlanVersions = otherTestPlanVersions.filter( - other => - new Date(other.updatedAt) > latestVersionDate - ); - } - - // If a version of the plan is not in the candidate phase and there has not yet been - // a recommended version, show string "Not Started" - if (![...testPlanVersions, ...otherTestPlanVersions].length) - return Not Started; - - // If a version of the plan is not in the candidate phase and there is a recommended - // version, show string "Review of VERSION_STRING completed DATE" - if (otherTestPlanVersions.length) { - const { latestVersion: otherLatestVersion } = - getVersionData(otherTestPlanVersions); - - const completionDate = - otherLatestVersion.recommendedPhaseReachedAt; - - return ( - - - {otherLatestVersion.versionString} - - - Review Completed  - - {convertDateToString( - completionDate, - 'MMM D, YYYY' - )} - - - - ); - } - - // Link with text "VERSION_STRING" that targets the single-page view of the plan. - // - // Show string "N Open Review Issues" and if N>=2, append " from N AT" Examples: "3 - // Open Review Issues from 2 AT" or "0 Open Review Issues" - // - // Show button "Advance to Recommended" when the following conditions are met: - // - If there has not yet been a recommended version and open issues = 0 and days - // in review > 120 and user is admin, show the button. - // - If there is already a recommended version and open review issues = 0 and user - // is admin, show the button. - // - If there is an earlier version that is recommended and that version has some - // test plan runs in the test queue, this button will run the process for - // updating existing reports and preserving data for tests that have not changed. - // - if there is an earlier version in the recommended phase, this button will - // sunset that version. This will also sunset any reports completed using that - // version. - if (testPlanVersions.length) { - const uniqueAtsCount = unique( - testPlanVersions - .flatMap( - testPlanVersion => - testPlanVersion.testPlanReports - ) - .filter( - testPlanReport => testPlanReport.issues.length - ) - .map(testPlanReport => testPlanReport.at.id) - ).length; - - const issuesCount = uniqueBy( - testPlanVersions.flatMap(testPlanVersion => - testPlanVersion.testPlanReports.flatMap( - testPlanReport => - testPlanReport.issues.filter( - issue => issue.isOpen - ) - ) - ), - item => item.link - ).length; - - // If there is an earlier version that is recommended and that version has some - // test plan runs in the test queue, this button will run the process for - // updating existing reports and preserving data for tests that have not - // changed. - let testPlanVersionDataToInclude; - if (recommendedTestPlanVersions.length) { - const { - latestVersion: recommendedLatestVersion, - latestVersionDate: recommendedLatestVersionDate - } = getVersionData(recommendedTestPlanVersions); - - if (recommendedLatestVersionDate < latestVersionDate) - testPlanVersionDataToInclude = - recommendedLatestVersion; - } - - const currentDate = new Date(); - const recommendedPhaseTargetDate = new Date( - latestVersion.recommendedPhaseTargetDate - ); - const candidatePhaseReachedDate = new Date( - latestVersion.candidatePhaseReachedAt - ); - const daysInReview = checkDaysBetweenDates( - currentDate, - candidatePhaseReachedDate - ); - const workingModeDaysToReview = 120; - - let timeToTargetDate = 0; - if (currentDate > recommendedPhaseTargetDate) { - // Indicates that this is in the past - timeToTargetDate = checkDaysBetweenDates( - currentDate, - recommendedPhaseTargetDate - ); - timeToTargetDate = -timeToTargetDate; - } else - timeToTargetDate = checkDaysBetweenDates( - recommendedPhaseTargetDate, - currentDate - ); + // If a version of the plan is not in the candidate phase and there has not yet been + // a recommended version, show string "Not Started" + if (![...testPlanVersions, ...otherTestPlanVersions].length) + return Not Started; + + // If a version of the plan is not in the candidate phase and there is a recommended + // version, show string "Review of VERSION_STRING completed DATE" + if (otherTestPlanVersions.length) { + const { latestVersion: otherLatestVersion } = getVersionData( + otherTestPlanVersions + ); + + const completionDate = otherLatestVersion.recommendedPhaseReachedAt; + + return ( + + + {otherLatestVersion.versionString} + + + Review Completed  + {convertDateToString(completionDate, 'MMM D, YYYY')} + + + ); + } - let coveredReports = []; - latestVersion.testPlanReports.forEach(testPlanReport => { - const markedFinalAt = testPlanReport.markedFinalAt; - const atName = testPlanReport.at.name; - const browserName = testPlanReport.browser.name; - const value = `${atName}_${browserName}`; + // Link with text "VERSION_STRING" that targets the single-page view of the plan. + // + // Show string "N Open Review Issues" and if N>=2, append " from N AT" Examples: "3 + // Open Review Issues from 2 AT" or "0 Open Review Issues" + // + // Show button "Advance to Recommended" when the following conditions are met: + // - If there has not yet been a recommended version and open issues = 0 and days + // in review > 120 and user is admin, show the button. + // - If there is already a recommended version and open review issues = 0 and user + // is admin, show the button. + // - If there is an earlier version that is recommended and that version has some + // test plan runs in the test queue, this button will run the process for + // updating existing reports and preserving data for tests that have not changed. + // - if there is an earlier version in the recommended phase, this button will + // sunset that version. This will also sunset any reports completed using that + // version. + if (testPlanVersions.length) { + const uniqueAtsCount = unique( + testPlanVersions + .flatMap(testPlanVersion => testPlanVersion.testPlanReports) + .filter(testPlanReport => testPlanReport.issues.length) + .map(testPlanReport => testPlanReport.at.id) + ).length; + + const issuesCount = uniqueBy( + testPlanVersions.flatMap(testPlanVersion => + testPlanVersion.testPlanReports.flatMap(testPlanReport => + testPlanReport.issues.filter(issue => issue.isOpen) + ) + ), + item => item.link + ).length; + + // If there is an earlier version that is recommended and that version has some + // test plan runs in the test queue, this button will run the process for + // updating existing reports and preserving data for tests that have not + // changed. + let testPlanVersionDataToInclude; + if (recommendedTestPlanVersions.length) { + const { + latestVersion: recommendedLatestVersion, + latestVersionDate: recommendedLatestVersionDate + } = getVersionData(recommendedTestPlanVersions); + + if (recommendedLatestVersionDate < latestVersionDate) + testPlanVersionDataToInclude = recommendedLatestVersion; + } + + const currentDate = new Date(); + const recommendedPhaseTargetDate = new Date( + latestVersion.recommendedPhaseTargetDate + ); + const candidatePhaseReachedDate = new Date( + latestVersion.candidatePhaseReachedAt + ); + const daysInReview = checkDaysBetweenDates( + currentDate, + candidatePhaseReachedDate + ); + const workingModeDaysToReview = 120; + + let timeToTargetDate = 0; + if (currentDate > recommendedPhaseTargetDate) { + // Indicates that this is in the past + timeToTargetDate = checkDaysBetweenDates( + currentDate, + recommendedPhaseTargetDate + ); + timeToTargetDate = -timeToTargetDate; + } else + timeToTargetDate = checkDaysBetweenDates( + recommendedPhaseTargetDate, + currentDate + ); - if (markedFinalAt && !coveredReports.includes(value)) - coveredReports.push(value); + let coveredReports = []; + latestVersion.testPlanReports.forEach(testPlanReport => { + const markedFinalAt = testPlanReport.markedFinalAt; + const atName = testPlanReport.at.name; + const browserName = testPlanReport.browser.name; + const value = `${atName}_${browserName}`; + + if (markedFinalAt && !coveredReports.includes(value)) + coveredReports.push(value); + }); + + // Phase is "active" + insertActivePhaseForTestPlan(latestVersion); + + return ( + + + {latestVersion.versionString} + + {isAdmin && ( + - )} - - - - - - - {issuesCount} Open Issue - {`${issuesCount === 1 ? '' : 's'}`} - {`${ - issuesCount >= 2 - ? ` from ${uniqueAtsCount} ATs` - : '' - }`} - - - {isAdmin ? ( - - ) : ( - <> - Target  - - {Math.abs(timeToTargetDate)}{' '} - Days - -   - {timeToTargetDate < 0 - ? 'Past' - : 'Away'} - - )} - - - - ); - } - return defaultView; - } - case 'RECOMMENDED': { - // If a version of the plan is not in the recommended phase, shows the string "None - // Yet" - if (!testPlanVersions.length) - return None Yet; - - // Link with text "VERSION_STRING" that targets the single-page view of the plan - const { latestVersion } = getVersionData(testPlanVersions); - - const completionDate = latestVersion.recommendedPhaseReachedAt; - - // Phase is "active" - insertActivePhaseForTestPlan(latestVersion); - - return ( - - - {latestVersion.versionString} - - - - - - Approved  - - {convertDateToString( - completionDate, - 'MMM D, YYYY' - )} - - - - ); - } - } - }; - - return ( - - - - - {testPlan.title} - - - {renderCellForCoveredAts()} - {renderCellForOverallStatus()} - {renderCellForPhase('RD', rdTestPlanVersions)} - {renderCellForPhase('DRAFT', draftTestPlanVersions)} - - {renderCellForPhase('CANDIDATE', candidateTestPlanVersions)} - - - {renderCellForPhase( - 'RECOMMENDED', - recommendedTestPlanVersions - )} - - - {showThemedModal && themedModal} - {showAdvanceModal && ( - - {advanceModalData.candidateWorkingModeDaysToReview - - advanceModalData.candidateDaysInReview > - 0 ? ( -

    - Warning! the {testPlan.title} test plan has - been in candidate review for{' '} - {advanceModalData.candidateDaysInReview}{' '} - days, which is{' '} - {advanceModalData.candidateWorkingModeDaysToReview - - advanceModalData.candidateDaysInReview}{' '} - fewer days than recommended by the ARIA-AT - working mode. Confirm this action only if - impacted stakeholders have had sufficient - opportunity to provide consensus. -

    - ) : null} - This version will be updated to  - {advanceModalData.phase}.  - {advanceModalData.coveredReports?.length ? ( - <> -
    -
    - The included reports cover: -
      - {advanceModalData.coveredReports.map( - e => { - const [atName, browserName] = - e.split('_'); - - return ( -
    • - - {atName} and  - {browserName} - -
    • - ); - } - )} -
    - Do you want to continue? - - ) : ( - <>Do you want to continue? - )} - - } - actions={[ - { - label: 'Continue', - onClick: async () => { - await advanceModalData.advanceFunc(); - } - } - ]} - closeLabel="Cancel" - handleClose={() => setShowAdvanceModal(false)} - staticBackdrop={true} - /> - )} - {showUpdateTargetModal && ( - setShowUpdateTargetModal(false)} + }} + > + Advance to Recommended + + )} + + - )} -
    - ); + + + + + {issuesCount} Open Issue + {`${issuesCount === 1 ? '' : 's'}`} + {`${issuesCount >= 2 ? ` from ${uniqueAtsCount} ATs` : ''}`} + + + {isAdmin ? ( + + ) : ( + <> + Target  + {Math.abs(timeToTargetDate)} Days +   + {timeToTargetDate < 0 ? 'Past' : 'Away'} + + )} + + + + ); + } + return defaultView; + } + case 'RECOMMENDED': { + // If a version of the plan is not in the recommended phase, shows the string "None + // Yet" + if (!testPlanVersions.length) return None Yet; + + // Link with text "VERSION_STRING" that targets the single-page view of the plan + const { latestVersion } = getVersionData(testPlanVersions); + + const completionDate = latestVersion.recommendedPhaseReachedAt; + + // Phase is "active" + insertActivePhaseForTestPlan(latestVersion); + + return ( + + + {latestVersion.versionString} + + + + + + Approved  + {convertDateToString(completionDate, 'MMM D, YYYY')} + + + ); + } + } + }; + + return ( + + + + + {testPlan.title} + + + {renderCellForCoveredAts()} + {renderCellForOverallStatus()} + {renderCellForPhase('RD', rdTestPlanVersions)} + {renderCellForPhase('DRAFT', draftTestPlanVersions)} + {renderCellForPhase('CANDIDATE', candidateTestPlanVersions)} + + {renderCellForPhase('RECOMMENDED', recommendedTestPlanVersions)} + + + {showThemedModal && themedModal} + {showAdvanceModal && ( + + {advanceModalData.candidateWorkingModeDaysToReview - + advanceModalData.candidateDaysInReview > + 0 ? ( +

    + Warning! the {testPlan.title} test plan has been in candidate + review for {advanceModalData.candidateDaysInReview} days, + which is{' '} + {advanceModalData.candidateWorkingModeDaysToReview - + advanceModalData.candidateDaysInReview}{' '} + fewer days than recommended by the ARIA-AT working mode. + Confirm this action only if impacted stakeholders have had + sufficient opportunity to provide consensus. +

    + ) : null} + This version will be updated to  + {advanceModalData.phase}.  + {advanceModalData.coveredReports?.length ? ( + <> +
    +
    + The included reports cover: +
      + {advanceModalData.coveredReports.map(e => { + const [atName, browserName] = e.split('_'); + + return ( +
    • + + {atName} and  + {browserName} + +
    • + ); + })} +
    + Do you want to continue? + + ) : ( + <>Do you want to continue? + )} + + } + actions={[ + { + label: 'Continue', + onClick: async () => { + await advanceModalData.advanceFunc(); + } + } + ]} + closeLabel="Cancel" + handleClose={() => setShowAdvanceModal(false)} + staticBackdrop={true} + /> + )} + {showUpdateTargetModal && ( + setShowUpdateTargetModal(false)} + /> + )} +
    + ); }; DataManagementRow.propTypes = { - isAdmin: PropTypes.bool, - ats: PropTypes.arrayOf( - PropTypes.shape({ - id: PropTypes.string, - name: PropTypes.string - }) - ), - testPlan: PropTypes.shape({ - id: PropTypes.string, - title: PropTypes.string, + isAdmin: PropTypes.bool, + ats: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string, + name: PropTypes.string + }) + ), + testPlan: PropTypes.shape({ + id: PropTypes.string, + title: PropTypes.string, + directory: PropTypes.string + }).isRequired, + testPlanVersions: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string, + title: PropTypes.string, + phase: PropTypes.string, + gitSha: PropTypes.string, + testPlan: PropTypes.shape({ directory: PropTypes.string - }).isRequired, - testPlanVersions: PropTypes.arrayOf( - PropTypes.shape({ - id: PropTypes.string, - title: PropTypes.string, - phase: PropTypes.string, - gitSha: PropTypes.string, - testPlan: PropTypes.shape({ - directory: PropTypes.string - }), - updatedAt: PropTypes.string, - draftPhaseReachedAt: PropTypes.string, - candidatePhaseReachedAt: PropTypes.string, - recommendedPhaseReachedAt: PropTypes.string - }) - ).isRequired, - tableRowIndex: PropTypes.number.isRequired, - setTestPlanVersions: PropTypes.func + }), + updatedAt: PropTypes.string, + draftPhaseReachedAt: PropTypes.string, + candidatePhaseReachedAt: PropTypes.string, + recommendedPhaseReachedAt: PropTypes.string + }) + ).isRequired, + tableRowIndex: PropTypes.number.isRequired, + setTestPlanVersions: PropTypes.func }; export default DataManagementRow; diff --git a/client/components/DataManagement/filterSortHooks.js b/client/components/DataManagement/filterSortHooks.js index 4da8cdf18..b5a416c90 100644 --- a/client/components/DataManagement/filterSortHooks.js +++ b/client/components/DataManagement/filterSortHooks.js @@ -1,243 +1,235 @@ import { useMemo, useState } from 'react'; import { - DATA_MANAGEMENT_TABLE_FILTER_OPTIONS, - DATA_MANAGEMENT_TABLE_SORT_OPTIONS, - getVersionData + DATA_MANAGEMENT_TABLE_FILTER_OPTIONS, + DATA_MANAGEMENT_TABLE_SORT_OPTIONS, + getVersionData } from './utils'; import { TEST_PLAN_VERSION_PHASES } from '../../utils/constants'; import { TABLE_SORT_ORDERS } from '../common/SortableTableHeader'; export const useTestPlanVersionsByPhase = testPlanVersions => { - const testPlanVersionsByPhase = useMemo(() => { - const initialPhases = Object.keys(TEST_PLAN_VERSION_PHASES).reduce( - (acc, key) => { - acc[TEST_PLAN_VERSION_PHASES[key]] = []; - return acc; - }, - {} - ); + const testPlanVersionsByPhase = useMemo(() => { + const initialPhases = Object.keys(TEST_PLAN_VERSION_PHASES).reduce( + (acc, key) => { + acc[TEST_PLAN_VERSION_PHASES[key]] = []; + return acc; + }, + {} + ); - return testPlanVersions.reduce((acc, testPlanVersion) => { - acc[testPlanVersion.phase].push(testPlanVersion); - return acc; - }, initialPhases); - }, [testPlanVersions]); + return testPlanVersions.reduce((acc, testPlanVersion) => { + acc[testPlanVersion.phase].push(testPlanVersion); + return acc; + }, initialPhases); + }, [testPlanVersions]); - return { testPlanVersionsByPhase }; + return { testPlanVersionsByPhase }; }; export const useDerivedActivePhasesByTestPlanId = ( - testPlans, - testPlanVersions + testPlans, + testPlanVersions ) => { - const { testPlanVersionsByPhase } = - useTestPlanVersionsByPhase(testPlanVersions); - - const getVersionDataByDirectory = (directory, phaseKey, phaseTimeKey) => { - if ( - testPlanVersionsByPhase[phaseKey].some( - testPlanVersion => - testPlanVersion.testPlan.directory === directory - ) - ) { - const { earliestVersion, latestVersion } = getVersionData( - testPlanVersionsByPhase[phaseKey], - phaseTimeKey - ); - return phaseTimeKey ? earliestVersion?.phase : latestVersion?.phase; - } - return undefined; - }; - - const derivedActivePhasesByTestPlanId = useMemo(() => { - const activeTestPlanVersionsByPhase = {}; - const phases = [ - { - phase: TEST_PLAN_VERSION_PHASES.RECOMMENDED, - timeKey: 'recommendedPhaseReachedAt' - }, - { - phase: TEST_PLAN_VERSION_PHASES.CANDIDATE, - timeKey: 'candidatePhaseReachedAt' - }, - { - phase: TEST_PLAN_VERSION_PHASES.DRAFT, - timeKey: 'draftPhaseReachedAt' - }, - { phase: TEST_PLAN_VERSION_PHASES.RD } - ]; - for (const testPlan of testPlans) { - for (const { phase, timeKey } of phases) { - const derivedPhase = getVersionDataByDirectory( - testPlan.directory, - phase, - timeKey - ); - // Gather all of the phases for a test plan - // The first element in the array is the overall phase - // while the subsequent elements in the array are TestPlanVersions - // that are in other phases - if (derivedPhase) { - // Ensure the array exists or initialize it directly before pushing the new phase - activeTestPlanVersionsByPhase[testPlan.id] = - activeTestPlanVersionsByPhase[testPlan.id] ?? []; - activeTestPlanVersionsByPhase[testPlan.id].push( - derivedPhase - ); - } - } + const { testPlanVersionsByPhase } = + useTestPlanVersionsByPhase(testPlanVersions); + + const getVersionDataByDirectory = (directory, phaseKey, phaseTimeKey) => { + if ( + testPlanVersionsByPhase[phaseKey].some( + testPlanVersion => testPlanVersion.testPlan.directory === directory + ) + ) { + const { earliestVersion, latestVersion } = getVersionData( + testPlanVersionsByPhase[phaseKey], + phaseTimeKey + ); + return phaseTimeKey ? earliestVersion?.phase : latestVersion?.phase; + } + return undefined; + }; + + const derivedActivePhasesByTestPlanId = useMemo(() => { + const activeTestPlanVersionsByPhase = {}; + const phases = [ + { + phase: TEST_PLAN_VERSION_PHASES.RECOMMENDED, + timeKey: 'recommendedPhaseReachedAt' + }, + { + phase: TEST_PLAN_VERSION_PHASES.CANDIDATE, + timeKey: 'candidatePhaseReachedAt' + }, + { + phase: TEST_PLAN_VERSION_PHASES.DRAFT, + timeKey: 'draftPhaseReachedAt' + }, + { phase: TEST_PLAN_VERSION_PHASES.RD } + ]; + for (const testPlan of testPlans) { + for (const { phase, timeKey } of phases) { + const derivedPhase = getVersionDataByDirectory( + testPlan.directory, + phase, + timeKey + ); + // Gather all of the phases for a test plan + // The first element in the array is the overall phase + // while the subsequent elements in the array are TestPlanVersions + // that are in other phases + if (derivedPhase) { + // Ensure the array exists or initialize it directly before pushing the new phase + activeTestPlanVersionsByPhase[testPlan.id] = + activeTestPlanVersionsByPhase[testPlan.id] ?? []; + activeTestPlanVersionsByPhase[testPlan.id].push(derivedPhase); } - return activeTestPlanVersionsByPhase; - }, [testPlans, testPlanVersions]); + } + } + return activeTestPlanVersionsByPhase; + }, [testPlans, testPlanVersions]); - return { derivedActivePhasesByTestPlanId }; + return { derivedActivePhasesByTestPlanId }; }; export const useTestPlansByPhase = (testPlans, testPlanVersions) => { - const { derivedActivePhasesByTestPlanId } = - useDerivedActivePhasesByTestPlanId(testPlans, testPlanVersions); + const { derivedActivePhasesByTestPlanId } = + useDerivedActivePhasesByTestPlanId(testPlans, testPlanVersions); - const testPlansByPhase = useMemo(() => { - const testPlansByPhase = {}; - for (const key of Object.keys(TEST_PLAN_VERSION_PHASES)) { - testPlansByPhase[TEST_PLAN_VERSION_PHASES[key]] = []; - } - for (const testPlan of testPlans) { - for (let phase of derivedActivePhasesByTestPlanId[testPlan.id]) { - testPlansByPhase[phase].push(testPlan); - } - } - return testPlansByPhase; - }, [derivedActivePhasesByTestPlanId]); + const testPlansByPhase = useMemo(() => { + const testPlansByPhase = {}; + for (const key of Object.keys(TEST_PLAN_VERSION_PHASES)) { + testPlansByPhase[TEST_PLAN_VERSION_PHASES[key]] = []; + } + for (const testPlan of testPlans) { + for (let phase of derivedActivePhasesByTestPlanId[testPlan.id]) { + testPlansByPhase[phase].push(testPlan); + } + } + return testPlansByPhase; + }, [derivedActivePhasesByTestPlanId]); - return { testPlansByPhase }; + return { testPlansByPhase }; }; export const useDataManagementTableFiltering = ( - testPlans, - testPlanVersions, - filter + testPlans, + testPlanVersions, + filter ) => { - const { testPlansByPhase } = useTestPlansByPhase( - testPlans, - testPlanVersions - ); + const { testPlansByPhase } = useTestPlansByPhase(testPlans, testPlanVersions); - const filteredTestPlans = useMemo(() => { - if (!filter || filter === DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.ALL) { - return testPlans; - } else { - return testPlansByPhase[filter]; - } - }, [filter, testPlansByPhase, testPlans]); + const filteredTestPlans = useMemo(() => { + if (!filter || filter === DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.ALL) { + return testPlans; + } else { + return testPlansByPhase[filter]; + } + }, [filter, testPlansByPhase, testPlans]); + + const filterLabels = { + [DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.ALL]: `All Plans (${testPlans.length})` + }; + + if (testPlansByPhase[TEST_PLAN_VERSION_PHASES.RD].length > 0) { + filterLabels[DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.RD] = `R&D Complete (${ + testPlansByPhase[TEST_PLAN_VERSION_PHASES.RD].length + })`; + } + + if (testPlansByPhase[TEST_PLAN_VERSION_PHASES.DRAFT].length) { + filterLabels[ + DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.DRAFT + ] = `In Draft Review (${ + testPlansByPhase[TEST_PLAN_VERSION_PHASES.DRAFT].length + })`; + } + + if (testPlansByPhase[TEST_PLAN_VERSION_PHASES.CANDIDATE].length > 0) { + filterLabels[ + DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.CANDIDATE + ] = `In Candidate Review (${ + testPlansByPhase[TEST_PLAN_VERSION_PHASES.CANDIDATE].length + })`; + } + + if (testPlansByPhase[TEST_PLAN_VERSION_PHASES.RECOMMENDED].length > 0) { + filterLabels[ + DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.RECOMMENDED + ] = `Recommended Plans (${ + testPlansByPhase[TEST_PLAN_VERSION_PHASES.RECOMMENDED].length + })`; + } + + return { filteredTestPlans, filterLabels }; +}; - const filterLabels = { - [DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.ALL]: `All Plans (${testPlans.length})` +export const useDataManagementTableSorting = ( + testPlans, + testPlanVersions, + ats, + initialSortDirection = TABLE_SORT_ORDERS.ASC +) => { + const [activeSort, setActiveSort] = useState({ + key: DATA_MANAGEMENT_TABLE_SORT_OPTIONS.PHASE, + direction: initialSortDirection + }); + + const { derivedActivePhasesByTestPlanId } = + useDerivedActivePhasesByTestPlanId(testPlans, testPlanVersions); + + const sortedTestPlans = useMemo(() => { + // Ascending and descending interpreted differently for statuses + // (ascending = earlier phase first, descending = later phase first) + const phaseOrder = { + NOT_STARTED: 4, + RD: 3, + DRAFT: 2, + CANDIDATE: 1, + RECOMMENDED: 0 }; + const directionMod = + activeSort.direction === TABLE_SORT_ORDERS.ASC ? -1 : 1; - if (testPlansByPhase[TEST_PLAN_VERSION_PHASES.RD].length > 0) { - filterLabels[ - DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.RD - ] = `R&D Complete (${ - testPlansByPhase[TEST_PLAN_VERSION_PHASES.RD].length - })`; - } + const sortByName = (a, b, dir = directionMod) => + dir * (a.title < b.title ? 1 : -1); - if (testPlansByPhase[TEST_PLAN_VERSION_PHASES.DRAFT].length) { - filterLabels[ - DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.DRAFT - ] = `In Draft Review (${ - testPlansByPhase[TEST_PLAN_VERSION_PHASES.DRAFT].length - })`; - } + const sortByAts = (a, b) => { + const countA = ats.length; // Stubs based on current rendering in DataManagementRow + const countB = ats.length; + if (countA === countB) return sortByName(a, b, -1); + return directionMod * (countA - countB); + }; - if (testPlansByPhase[TEST_PLAN_VERSION_PHASES.CANDIDATE].length > 0) { - filterLabels[ - DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.CANDIDATE - ] = `In Candidate Review (${ - testPlansByPhase[TEST_PLAN_VERSION_PHASES.CANDIDATE].length - })`; - } + const sortByPhase = (a, b) => { + const testPlanVersionOverallA = + derivedActivePhasesByTestPlanId[a.id][0] ?? 'NOT_STARTED'; + const testPlanVersionOverallB = + derivedActivePhasesByTestPlanId[b.id][0] ?? 'NOT_STARTED'; + if (testPlanVersionOverallA === testPlanVersionOverallB) { + return sortByName(a, b, -1); + } + return ( + directionMod * + (phaseOrder[testPlanVersionOverallA] - + phaseOrder[testPlanVersionOverallB]) + ); + }; - if (testPlansByPhase[TEST_PLAN_VERSION_PHASES.RECOMMENDED].length > 0) { - filterLabels[ - DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.RECOMMENDED - ] = `Recommended Plans (${ - testPlansByPhase[TEST_PLAN_VERSION_PHASES.RECOMMENDED].length - })`; - } + const sortFunctions = { + NAME: sortByName, + ATS: sortByAts, + PHASE: sortByPhase + }; - return { filteredTestPlans, filterLabels }; -}; + return testPlans.slice().sort(sortFunctions[activeSort.key]); + }, [activeSort, testPlans]); -export const useDataManagementTableSorting = ( - testPlans, - testPlanVersions, - ats, - initialSortDirection = TABLE_SORT_ORDERS.ASC -) => { - const [activeSort, setActiveSort] = useState({ - key: DATA_MANAGEMENT_TABLE_SORT_OPTIONS.PHASE, - direction: initialSortDirection - }); - - const { derivedActivePhasesByTestPlanId } = - useDerivedActivePhasesByTestPlanId(testPlans, testPlanVersions); - - const sortedTestPlans = useMemo(() => { - // Ascending and descending interpreted differently for statuses - // (ascending = earlier phase first, descending = later phase first) - const phaseOrder = { - NOT_STARTED: 4, - RD: 3, - DRAFT: 2, - CANDIDATE: 1, - RECOMMENDED: 0 - }; - const directionMod = - activeSort.direction === TABLE_SORT_ORDERS.ASC ? -1 : 1; - - const sortByName = (a, b, dir = directionMod) => - dir * (a.title < b.title ? 1 : -1); - - const sortByAts = (a, b) => { - const countA = ats.length; // Stubs based on current rendering in DataManagementRow - const countB = ats.length; - if (countA === countB) return sortByName(a, b, -1); - return directionMod * (countA - countB); - }; - - const sortByPhase = (a, b) => { - const testPlanVersionOverallA = - derivedActivePhasesByTestPlanId[a.id][0] ?? 'NOT_STARTED'; - const testPlanVersionOverallB = - derivedActivePhasesByTestPlanId[b.id][0] ?? 'NOT_STARTED'; - if (testPlanVersionOverallA === testPlanVersionOverallB) { - return sortByName(a, b, -1); - } - return ( - directionMod * - (phaseOrder[testPlanVersionOverallA] - - phaseOrder[testPlanVersionOverallB]) - ); - }; - - const sortFunctions = { - NAME: sortByName, - ATS: sortByAts, - PHASE: sortByPhase - }; - - return testPlans.slice().sort(sortFunctions[activeSort.key]); - }, [activeSort, testPlans]); - - const updateSort = ({ key, direction }) => { - setActiveSort({ key, direction }); - }; + const updateSort = ({ key, direction }) => { + setActiveSort({ key, direction }); + }; - return { - sortedTestPlans, - updateSort, - activeSort - }; + return { + sortedTestPlans, + updateSort, + activeSort + }; }; diff --git a/client/components/DataManagement/index.jsx b/client/components/DataManagement/index.jsx index a1b14d33e..fcc1e686f 100644 --- a/client/components/DataManagement/index.jsx +++ b/client/components/DataManagement/index.jsx @@ -9,234 +9,229 @@ import DataManagementRow from '@components/DataManagement/DataManagementRow'; import './DataManagement.css'; import { evaluateAuth } from '@client/utils/evaluateAuth'; import SortableTableHeader, { - TABLE_SORT_ORDERS + TABLE_SORT_ORDERS } from '../common/SortableTableHeader'; import FilterButtons from '../common/FilterButtons'; import { - useDataManagementTableFiltering, - useDataManagementTableSorting + useDataManagementTableFiltering, + useDataManagementTableSorting } from './filterSortHooks'; import { - DATA_MANAGEMENT_TABLE_FILTER_OPTIONS, - DATA_MANAGEMENT_TABLE_SORT_OPTIONS + DATA_MANAGEMENT_TABLE_FILTER_OPTIONS, + DATA_MANAGEMENT_TABLE_SORT_OPTIONS } from './utils'; const DataManagement = () => { - const { loading, data, error, refetch } = useQuery( - DATA_MANAGEMENT_PAGE_QUERY, - { fetchPolicy: 'cache-and-network' } + const { loading, data, error, refetch } = useQuery( + DATA_MANAGEMENT_PAGE_QUERY, + { fetchPolicy: 'cache-and-network' } + ); + + const [pageReady, setPageReady] = useState(false); + const [ats, setAts] = useState([]); + const [testPlans, setTestPlans] = useState([]); + const [testPlanVersions, setTestPlanVersions] = useState([]); + const [deprecatedTestPlanVersions, setDeprecatedTestPlanVersions] = useState( + [] + ); + const [filter, setFilter] = useState( + DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.ALL + ); + + const auth = evaluateAuth(data && data.me ? data.me : {}); + const { isAdmin } = auth; + + useEffect(() => { + if (data) { + const { + ats = [], + testPlanVersions = [], + deprecatedTestPlanVersions = [], + testPlans = [] + } = data; + setAts(ats); + setTestPlans(testPlans); + setTestPlanVersions(testPlanVersions); + setDeprecatedTestPlanVersions(deprecatedTestPlanVersions); + setPageReady(true); + } + }, [data]); + + const { filteredTestPlans, filterLabels } = useDataManagementTableFiltering( + testPlans, + testPlanVersions, + filter + ); + + const { sortedTestPlans, updateSort, activeSort } = + useDataManagementTableSorting( + filteredTestPlans, + testPlanVersions, + ats, + TABLE_SORT_ORDERS.DESC ); - const [pageReady, setPageReady] = useState(false); - const [ats, setAts] = useState([]); - const [testPlans, setTestPlans] = useState([]); - const [testPlanVersions, setTestPlanVersions] = useState([]); - const [deprecatedTestPlanVersions, setDeprecatedTestPlanVersions] = - useState([]); - const [filter, setFilter] = useState( - DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.ALL + if (error) { + return ( + ); + } - const auth = evaluateAuth(data && data.me ? data.me : {}); - const { isAdmin } = auth; - - useEffect(() => { - if (data) { - const { - ats = [], - testPlanVersions = [], - deprecatedTestPlanVersions = [], - testPlans = [] - } = data; - setAts(ats); - setTestPlans(testPlans); - setTestPlanVersions(testPlanVersions); - setDeprecatedTestPlanVersions(deprecatedTestPlanVersions); - setPageReady(true); - } - }, [data]); - - const { filteredTestPlans, filterLabels } = useDataManagementTableFiltering( - testPlans, - testPlanVersions, - filter + if (loading || !pageReady) { + return ( + ); - - const { sortedTestPlans, updateSort, activeSort } = - useDataManagementTableSorting( - filteredTestPlans, - testPlanVersions, - ats, - TABLE_SORT_ORDERS.DESC - ); - - if (error) { - return ( - + + Data Management | ARIA-AT + +

    Data Management

    + + {emptyTestPlans && ( +

    + There are no Test Plans available +

    + )} + + {emptyTestPlans && isAdmin && ( + + Add a Test Plan to the Queue + + )} + + {isAdmin ? ( + <> +

    Introduction

    +

    + This page provides a view of the latest test plan version + information, and where they currently are in the{' '} + + ARIA-AT Community Group’s review process + + .
    + Use this page to manage Test Plans in the Test Queue and their + phases. +

    + + + + ) : ( + <> +

    Introduction

    +

    + This page provides a view of the latest test plan version + information, and where they currently are in the{' '} + + ARIA-AT Community Group’s review process + + . +

    + + )} + +

    Test Plans Status Summary

    + + + + + + updateSort({ + key: DATA_MANAGEMENT_TABLE_SORT_OPTIONS.NAME, + direction + }) + } /> - ); - } - - if (loading || !pageReady) { - return ( - + updateSort({ + key: DATA_MANAGEMENT_TABLE_SORT_OPTIONS.ATS, + direction + }) + } /> - ); - } - - const emptyTestPlans = !testPlans.length; - - return ( - - - Data Management | ARIA-AT - -

    Data Management

    - - {emptyTestPlans && ( -

    - There are no Test Plans available -

    - )} - - {emptyTestPlans && isAdmin && ( - - Add a Test Plan to the Queue - - )} - - {isAdmin ? ( - <> -

    Introduction

    -

    - This page provides a view of the latest test plan - version information, and where they currently are in the{' '} - - ARIA-AT Community Group’s review process - - .
    - Use this page to manage Test Plans in the Test Queue and - their phases. -

    - - - - ) : ( - <> -

    Introduction

    -

    - This page provides a view of the latest test plan - version information, and where they currently are in the{' '} - - ARIA-AT Community Group’s review process - - . -

    - - )} - -

    Test Plans Status Summary

    - + updateSort({ + key: DATA_MANAGEMENT_TABLE_SORT_OPTIONS.PHASE, + direction + }) + } + initialSortDirection={TABLE_SORT_ORDERS.DESC} /> -
    - - - - updateSort({ - key: DATA_MANAGEMENT_TABLE_SORT_OPTIONS.NAME, - direction - }) - } - /> - - updateSort({ - key: DATA_MANAGEMENT_TABLE_SORT_OPTIONS.ATS, - direction - }) - } - /> - - updateSort({ - key: DATA_MANAGEMENT_TABLE_SORT_OPTIONS.PHASE, - direction - }) - } - initialSortDirection={TABLE_SORT_ORDERS.DESC} - /> - - - - - - - - {sortedTestPlans.map((testPlan, index) => { - return ( - - testPlanVersion.testPlan.directory === - testPlan.directory - )} - tableRowIndex={index} - setTestPlanVersions={setTestPlanVersions} - /> - ); - })} - -
    R&D VersionDraft ReviewCandidate ReviewRecommended Version
    - - ); + R&D Version + Draft Review + Candidate Review + Recommended Version + + + + {sortedTestPlans.map((testPlan, index) => { + return ( + + testPlanVersion.testPlan.directory === testPlan.directory + )} + tableRowIndex={index} + setTestPlanVersions={setTestPlanVersions} + /> + ); + })} + + + + ); }; export default DataManagement; diff --git a/client/components/DataManagement/queries.js b/client/components/DataManagement/queries.js index a77e69e4f..734b20f50 100644 --- a/client/components/DataManagement/queries.js +++ b/client/components/DataManagement/queries.js @@ -1,210 +1,211 @@ import { gql } from '@apollo/client'; export const DATA_MANAGEMENT_PAGE_QUERY = gql` - query DataManagementPage { - me { - id - username - roles + query DataManagementPage { + me { + id + username + roles + } + ats { + id + key + name + browsers { + id + key + name + } + atVersions { + id + name + releasedAt + supportedByAutomation + } + candidateBrowsers { + id + } + recommendedBrowsers { + id + } + } + testPlans { + id + directory + title + } + deprecatedTestPlanVersions: testPlanVersions(phases: [DEPRECATED]) { + id + phase + updatedAt + draftPhaseReachedAt + candidatePhaseReachedAt + recommendedPhaseTargetDate + recommendedPhaseReachedAt + deprecatedAt + testPlan { + directory + } + } + testPlanVersions(phases: [RD, DRAFT, CANDIDATE, RECOMMENDED]) { + id + title + phase + gitSha + gitMessage + updatedAt + versionString + draftPhaseReachedAt + candidatePhaseReachedAt + recommendedPhaseTargetDate + recommendedPhaseReachedAt + testPlan { + directory + } + testPlanReports { + id + metrics + isFinal + markedFinalAt + at { + id + key + name } - ats { - id - key - name - browsers { - id - key - name - } - atVersions { - id - name - releasedAt - } - candidateBrowsers { - id - } - recommendedBrowsers { - id - } + browser { + id + key + name } - testPlans { - id - directory - title + issues { + link + isOpen + feedbackType } - deprecatedTestPlanVersions: testPlanVersions(phases: [DEPRECATED]) { + draftTestPlanRuns { + tester { + username + } + testPlanReport { id - phase - updatedAt - draftPhaseReachedAt - candidatePhaseReachedAt - recommendedPhaseTargetDate - recommendedPhaseReachedAt - deprecatedAt - testPlan { - directory + } + testResults { + test { + id } - } - testPlanVersions(phases: [RD, DRAFT, CANDIDATE, RECOMMENDED]) { - id - title - phase - gitSha - gitMessage - updatedAt - versionString - draftPhaseReachedAt - candidatePhaseReachedAt - recommendedPhaseTargetDate - recommendedPhaseReachedAt - testPlan { - directory + atVersion { + id + name } - testPlanReports { - id - metrics - isFinal - markedFinalAt - at { - id - key - name - } - browser { - id - key - name - } - issues { - link - isOpen - feedbackType - } - draftTestPlanRuns { - tester { - username - } - testPlanReport { - id - } - testResults { - test { - id - } - atVersion { - id - name - } - browserVersion { - id - name - } - completedAt - } - } + browserVersion { + id + name } - metadata + completedAt + } } + } + metadata } + } `; export const UPDATE_TEST_PLAN_VERSION_PHASE = gql` - mutation UpdateTestPlanVersionPhase( - $testPlanVersionId: ID! - $phase: TestPlanVersionPhase! - $testPlanVersionDataToIncludeId: ID - ) { - testPlanVersion(id: $testPlanVersionId) { - updatePhase( - phase: $phase - testPlanVersionDataToIncludeId: $testPlanVersionDataToIncludeId - ) { - testPlanVersion { - id - title - phase - gitSha - gitMessage - versionString - updatedAt - draftPhaseReachedAt - candidatePhaseReachedAt - recommendedPhaseTargetDate - recommendedPhaseReachedAt - testPlan { - directory - } - testPlanReports { - id - at { - id - key - name - } - browser { - id - key - name - } - issues { - link - isOpen - feedbackType - } - } - metadata - } + mutation UpdateTestPlanVersionPhase( + $testPlanVersionId: ID! + $phase: TestPlanVersionPhase! + $testPlanVersionDataToIncludeId: ID + ) { + testPlanVersion(id: $testPlanVersionId) { + updatePhase( + phase: $phase + testPlanVersionDataToIncludeId: $testPlanVersionDataToIncludeId + ) { + testPlanVersion { + id + title + phase + gitSha + gitMessage + versionString + updatedAt + draftPhaseReachedAt + candidatePhaseReachedAt + recommendedPhaseTargetDate + recommendedPhaseReachedAt + testPlan { + directory + } + testPlanReports { + id + at { + id + key + name } + browser { + id + key + name + } + issues { + link + isOpen + feedbackType + } + } + metadata } + } } + } `; export const UPDATE_TEST_PLAN_VERSION_RECOMMENDED_TARGET_DATE = gql` - mutation UpdateTestPlanReportRecommendedTargetDate( - $testPlanVersionId: ID! - $recommendedPhaseTargetDate: Timestamp! - ) { - testPlanVersion(id: $testPlanVersionId) { - updateRecommendedPhaseTargetDate( - recommendedPhaseTargetDate: $recommendedPhaseTargetDate - ) { - testPlanVersion { - id - title - phase - gitSha - gitMessage - versionString - updatedAt - draftPhaseReachedAt - candidatePhaseReachedAt - recommendedPhaseTargetDate - recommendedPhaseReachedAt - testPlan { - directory - } - testPlanReports { - id - at { - id - key - name - } - browser { - id - key - name - } - issues { - link - isOpen - feedbackType - } - } - metadata - } + mutation UpdateTestPlanReportRecommendedTargetDate( + $testPlanVersionId: ID! + $recommendedPhaseTargetDate: Timestamp! + ) { + testPlanVersion(id: $testPlanVersionId) { + updateRecommendedPhaseTargetDate( + recommendedPhaseTargetDate: $recommendedPhaseTargetDate + ) { + testPlanVersion { + id + title + phase + gitSha + gitMessage + versionString + updatedAt + draftPhaseReachedAt + candidatePhaseReachedAt + recommendedPhaseTargetDate + recommendedPhaseReachedAt + testPlan { + directory + } + testPlanReports { + id + at { + id + key + name + } + browser { + id + key + name + } + issues { + link + isOpen + feedbackType } + } + metadata } + } } + } `; diff --git a/client/components/DataManagement/utils.js b/client/components/DataManagement/utils.js index 292d3a270..21c6763a1 100644 --- a/client/components/DataManagement/utils.js +++ b/client/components/DataManagement/utils.js @@ -4,31 +4,31 @@ import { TEST_PLAN_VERSION_PHASES } from '../../utils/constants'; // TestPlanVersions export const getVersionData = (testPlanVersions, dateKey = 'updatedAt') => { - const earliestVersion = testPlanVersions.reduce((a, b) => - new Date(a[dateKey]) < new Date(b[dateKey]) ? a : b - ); - const earliestVersionDate = new Date(earliestVersion[dateKey]); + const earliestVersion = testPlanVersions.reduce((a, b) => + new Date(a[dateKey]) < new Date(b[dateKey]) ? a : b + ); + const earliestVersionDate = new Date(earliestVersion[dateKey]); - const latestVersion = testPlanVersions.reduce((a, b) => - new Date(a[dateKey]) > new Date(b[dateKey]) ? a : b - ); - const latestVersionDate = new Date(latestVersion[dateKey]); + const latestVersion = testPlanVersions.reduce((a, b) => + new Date(a[dateKey]) > new Date(b[dateKey]) ? a : b + ); + const latestVersionDate = new Date(latestVersion[dateKey]); - return { - earliestVersion, - earliestVersionDate, - latestVersion, - latestVersionDate - }; + return { + earliestVersion, + earliestVersionDate, + latestVersion, + latestVersionDate + }; }; export const DATA_MANAGEMENT_TABLE_SORT_OPTIONS = { - NAME: 'NAME', - ATS: 'ATS', - PHASE: 'PHASE' + NAME: 'NAME', + ATS: 'ATS', + PHASE: 'PHASE' }; export const DATA_MANAGEMENT_TABLE_FILTER_OPTIONS = { - ALL: 'ALL', - ...TEST_PLAN_VERSION_PHASES + ALL: 'ALL', + ...TEST_PLAN_VERSION_PHASES }; diff --git a/client/components/DeleteResultsModal/index.jsx b/client/components/DeleteResultsModal/index.jsx index d834320e8..07d26e4d8 100644 --- a/client/components/DeleteResultsModal/index.jsx +++ b/client/components/DeleteResultsModal/index.jsx @@ -3,74 +3,73 @@ import { Button, Modal } from 'react-bootstrap'; import PropTypes from 'prop-types'; const DeleteResultsModal = ({ - show = false, - isAdmin = false, - details = { - title: 'Title N/A', - username: null - }, - handleClose = () => {}, - handleAction = () => {} + show = false, + isAdmin = false, + details = { + title: 'Title N/A', + username: null + }, + handleClose = () => {}, + handleAction = () => {} }) => { - const { title, username } = details; + const { title, username } = details; - return ( - - - - {isAdmin - ? `You are about to delete results for ${username}` - : 'You are about to delete your results'} - - - - {isAdmin ? ( - <> -

    - The following results for {username} will be - deleted: -

    -

    - {title} -

    -

    - Please press Delete to confirm this action. -

    - - ) : ( - <> -

    Your results will be deleted for:

    -

    - {title} -

    -

    - Please press Delete to confirm this action. -

    - - )} -
    - - - - -
    - ); + return ( + + + + {isAdmin + ? `You are about to delete results for ${username}` + : 'You are about to delete your results'} + + + + {isAdmin ? ( + <> +

    + The following results for {username} will be deleted: +

    +

    + {title} +

    +

    + Please press Delete to confirm this action. +

    + + ) : ( + <> +

    Your results will be deleted for:

    +

    + {title} +

    +

    + Please press Delete to confirm this action. +

    + + )} +
    + + + + +
    + ); }; DeleteResultsModal.propTypes = { - show: PropTypes.bool, - isAdmin: PropTypes.bool, - details: PropTypes.object, - handleClose: PropTypes.func, - handleAction: PropTypes.func + show: PropTypes.bool, + isAdmin: PropTypes.bool, + details: PropTypes.object, + handleClose: PropTypes.func, + handleAction: PropTypes.func }; export default DeleteResultsModal; diff --git a/client/components/DeleteTestPlanReportModal/index.jsx b/client/components/DeleteTestPlanReportModal/index.jsx index 0fdd61bf0..1b6add454 100644 --- a/client/components/DeleteTestPlanReportModal/index.jsx +++ b/client/components/DeleteTestPlanReportModal/index.jsx @@ -3,54 +3,54 @@ import { Button, Modal } from 'react-bootstrap'; import PropTypes from 'prop-types'; const DeleteTestPlanModal = ({ - show = false, - details = { - title: 'Title N/A' - }, - handleClose = () => {}, - handleAction = () => {} + show = false, + details = { + title: 'Title N/A' + }, + handleClose = () => {}, + handleAction = () => {} }) => { - const { title } = details; + const { title } = details; - return ( - - - - {`You are about the delete the Test Plan Report for ${title}`} - - - - <> -

    The following Test Plan Report will be deleted:

    -

    - {title} -

    -

    - Please press Delete to confirm this action. -

    - -
    - - - - -
    - ); + return ( + + + + {`You are about the delete the Test Plan Report for ${title}`} + + + + <> +

    The following Test Plan Report will be deleted:

    +

    + {title} +

    +

    + Please press Delete to confirm this action. +

    + +
    + + + + +
    + ); }; DeleteTestPlanModal.propTypes = { - show: PropTypes.bool, - details: PropTypes.object, - handleClose: PropTypes.func, - handleAction: PropTypes.func + show: PropTypes.bool, + details: PropTypes.object, + handleClose: PropTypes.func, + handleAction: PropTypes.func }; export default DeleteTestPlanModal; diff --git a/client/components/DisclaimerInfo/index.jsx b/client/components/DisclaimerInfo/index.jsx index 822dd8739..5730e5433 100644 --- a/client/components/DisclaimerInfo/index.jsx +++ b/client/components/DisclaimerInfo/index.jsx @@ -5,127 +5,127 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faExclamationCircle } from '@fortawesome/free-solid-svg-icons'; const Container = styled.div` - display: flex; - flex-direction: column; + display: flex; + flex-direction: column; - font-size: 1rem; - padding: 0.5rem 1rem; - margin-top: 0.5rem; - margin-bottom: 0.5rem; - background: #f7f7f7; + font-size: 1rem; + padding: 0.5rem 1rem; + margin-top: 0.5rem; + margin-bottom: 0.5rem; + background: #f7f7f7; - border: 2px solid #d3d5d9; - border-radius: 0.5rem; + border: 2px solid #d3d5d9; + border-radius: 0.5rem; - details { - font-weight: normal; - font-size: 1rem; + details { + font-weight: normal; + font-size: 1rem; - span > ol { - margin-bottom: 0; - } + span > ol { + margin-bottom: 0; + } - summary { - width: fit-content; + summary { + width: fit-content; - list-style: none; + list-style: none; - font-weight: bold; - text-align: left; - border: thin solid transparent; - background-color: transparent; + font-weight: bold; + text-align: left; + border: thin solid transparent; + background-color: transparent; - :hover, - :focus { - background-color: #eee; - } + :hover, + :focus { + background-color: #eee; + } - ::-webkit-details-marker { - display: none; - } - } + ::-webkit-details-marker { + display: none; + } } + } `; const candidateTitle = 'Unapproved Report'; const candidateMessageContent = ( - <> - The information in this report is generated from candidate tests. - Candidate ARIA-AT tests are in review by assistive technology developers - and lack consensus regarding: -
      -
    1. applicability and validity of the tests, and
    2. -
    3. accuracy of test results.
    4. -
    - + <> + The information in this report is generated from candidate tests. Candidate + ARIA-AT tests are in review by assistive technology developers and lack + consensus regarding: +
      +
    1. applicability and validity of the tests, and
    2. +
    3. accuracy of test results.
    4. +
    + ); const recommendedTitle = 'Approved Report'; const recommendedMessageContent = ( - <> - The information in this report is generated from recommended tests. - Recommended ARIA-AT tests have been reviewed by assistive technology - developers and represent consensus regarding: applicability and validity - of the tests, and accuracy of test results. - + <> + The information in this report is generated from recommended tests. + Recommended ARIA-AT tests have been reviewed by assistive technology + developers and represent consensus regarding: applicability and validity of + the tests, and accuracy of test results. + ); const deprecatedTitle = 'Deprecated Report'; const deprecatedMessageContent = ( - <> - The information in this report is generated from previously set - candidate or recommended tests. - + <> + The information in this report is generated from previously set candidate or + recommended tests. + ); const content = { - CANDIDATE: { - title: candidateTitle, - messageContent: candidateMessageContent - }, - RECOMMENDED: { - title: recommendedTitle, - messageContent: recommendedMessageContent - }, - DEPRECATED: { - title: deprecatedTitle, - messageContent: deprecatedMessageContent - } + CANDIDATE: { + title: candidateTitle, + messageContent: candidateMessageContent + }, + RECOMMENDED: { + title: recommendedTitle, + messageContent: recommendedMessageContent + }, + DEPRECATED: { + title: deprecatedTitle, + messageContent: deprecatedMessageContent + } }; const DisclaimerInfo = ({ phase }) => { - const [expanded, setExpanded] = useState(false); - - const title = content[phase]?.title || content.CANDIDATE.title; - const messageContent = - content[phase]?.messageContent || content.CANDIDATE.messageContent; - - return ( - -
    - setExpanded(!expanded)} - aria-label={`Warning! ${title}`} - > - - {title} - - {messageContent} -
    -
    - ); + const [expanded, setExpanded] = useState(false); + + const title = content[phase]?.title || content.CANDIDATE.title; + const messageContent = + content[phase]?.messageContent || content.CANDIDATE.messageContent; + + return ( + +
    + setExpanded(!expanded)} + aria-label={`Warning! ${title}`} + > + + {title} + + {messageContent} +
    +
    + ); }; DisclaimerInfo.propTypes = { - phase: PropTypes.string + phase: PropTypes.string }; export default DisclaimerInfo; diff --git a/client/components/GraphQLProvider/GraphQLProvider.jsx b/client/components/GraphQLProvider/GraphQLProvider.jsx index cb0fa25b1..98a2a2ef9 100644 --- a/client/components/GraphQLProvider/GraphQLProvider.jsx +++ b/client/components/GraphQLProvider/GraphQLProvider.jsx @@ -1,69 +1,69 @@ import React from 'react'; import PropTypes from 'prop-types'; import { - ApolloClient, - InMemoryCache, - ApolloProvider, - ApolloLink, - HttpLink, - concat + ApolloClient, + InMemoryCache, + ApolloProvider, + ApolloLink, + HttpLink, + concat } from '@apollo/client'; // Dynamically set GraphQL request headers // See https://www.apollographql.com/docs/react/networking/advanced-http-networking#customizing-request-logic const headerMiddleware = new ApolloLink((operation, forward) => { - const currentTransactionId = sessionStorage.getItem('currentTransactionId'); - if (currentTransactionId) { - operation.setContext(({ headers = {} }) => ({ - headers: { - ...headers, - 'x-transaction-id': currentTransactionId - } - })); - } - return forward(operation); + const currentTransactionId = sessionStorage.getItem('currentTransactionId'); + if (currentTransactionId) { + operation.setContext(({ headers = {} }) => ({ + headers: { + ...headers, + 'x-transaction-id': currentTransactionId + } + })); + } + return forward(operation); }); const client = new ApolloClient({ - link: concat(headerMiddleware, new HttpLink({ uri: '/api/graphql' })), - cache: new InMemoryCache({ - addTypename: false, - typePolicies: { - Query: { - fields: { - me: { merge: true }, - testPlanVersion: { merge: true }, - testPlanVersions: { merge: false }, - testPlanReport: { merge: true }, - testPlanReports: { merge: false }, - collectionJobByTestPlanRunId: { - merge(existing, incoming) { - return { ...existing, ...incoming }; - } - } - } - }, - Mutation: { - fields: { - testPlanReport: { merge: false }, - testPlanRun: { merge: false }, - testPlanVersion: { merge: false } - } + link: concat(headerMiddleware, new HttpLink({ uri: '/api/graphql' })), + cache: new InMemoryCache({ + addTypename: false, + typePolicies: { + Query: { + fields: { + me: { merge: true }, + testPlanVersion: { merge: true }, + testPlanVersions: { merge: false }, + testPlanReport: { merge: true }, + testPlanReports: { merge: false }, + collectionJobByTestPlanRunId: { + merge(existing, incoming) { + return { ...existing, ...incoming }; } + } } - }) + }, + Mutation: { + fields: { + testPlanReport: { merge: false }, + testPlanRun: { merge: false }, + testPlanVersion: { merge: false } + } + } + } + }) }); const resetCache = async () => { - await client.clearStore(); + await client.clearStore(); }; const GraphQLProvider = ({ children }) => { - return {children}; + return {children}; }; GraphQLProvider.propTypes = { - children: PropTypes.node.isRequired + children: PropTypes.node.isRequired }; export { resetCache }; diff --git a/client/components/Home/Home.jsx b/client/components/Home/Home.jsx index 08a5b3216..3ccc7b6c6 100644 --- a/client/components/Home/Home.jsx +++ b/client/components/Home/Home.jsx @@ -10,207 +10,190 @@ import iconReviewAssertions from '../../assets/review-assertion.jpg'; // import iconFixIssue from '../../assets/fix-issue.jpg'; const Home = () => { - return ( - - - Home | ARIA-AT - -
    -

    - Enabling Interoperability for Assistive Technology Users -

    -
    -
    -

    - - - Note: The{' '} - - ARIA-AT Project - {' '} - is managed by the{' '} - - ARIA-AT Community Group - {' '} - in coordination with the{' '} - - Authoring Practices Task Force - {' '} - of the{' '} - - ARIA Working Group - - . The W3C staff contact is{' '} - - Daniel Montalvo - - . - -

    -

    - Today, different screen readers often yield - conflicting experiences when presenting a web page, - disadvantaging or even excluding some users. These - differences also create accessibility design and - test barriers for web developers. -

    -

    - On the other hand, browsers are interoperable for - people who do not use assistive technologies. That - is, different browsers provide equivalent - experiences. Browser interoperability facilitates an - inclusive web. -

    -

    - Assistive technology users deserve equal inclusion. - The ARIA-AT project aims to empower equal inclusion - by realizing interoperability for AT users. -

    -

    - - Read more about how the AT interoperability gap - hinders inclusion on the web for people with - disabilities. - -

    -
    -
    - - ); - } -} - -export default TestIframe; - -TestIframe.propTypes = { - saveTestResultOrProgress: PropTypes.func, - git_hash: PropTypes.string, - file: PropTypes.string, - at_key: PropTypes.string, - serializedForm: PropTypes.array -}; diff --git a/client/components/TestRun/TestNavigator.jsx b/client/components/TestRun/TestNavigator.jsx index e1aba2b0c..0fe1fd43c 100644 --- a/client/components/TestRun/TestNavigator.jsx +++ b/client/components/TestRun/TestNavigator.jsx @@ -1,171 +1,164 @@ import PropTypes from 'prop-types'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { - faAlignLeft, - faArrowLeft, - faArrowRight + faAlignLeft, + faArrowLeft, + faArrowRight } from '@fortawesome/free-solid-svg-icons'; import { Col } from 'react-bootstrap'; -import React, { useMemo } from 'react'; +import React, { useContext, useMemo } from 'react'; +import { Context as CollectionJobContext } from './CollectionJobContext'; import '@fortawesome/fontawesome-svg-core/styles.css'; -import { COLLECTION_JOB_STATUS_BY_TEST_PLAN_RUN_ID_QUERY } from './queries'; -import { useQuery } from '@apollo/client'; const TestNavigator = ({ - show = true, - isSignedIn = false, - viewedTests = [], - isVendor = false, - testPlanReport = {}, - tests = [], - currentTestIndex = 0, - toggleShowClick = () => {}, - handleTestClick = () => {}, - testPlanRun = null + show = true, + isSignedIn = false, + viewedTests = [], + isVendor = false, + testPlanReport = {}, + tests = [], + currentTestIndex = 0, + toggleShowClick = () => {}, + handleTestClick = () => {}, + testPlanRun = null }) => { - const isBotCompletedTest = testPlanRun?.tester?.isBot; + const isBotCompletedTest = testPlanRun?.tester?.isBot; - const { data: collectionJobQuery } = useQuery( - COLLECTION_JOB_STATUS_BY_TEST_PLAN_RUN_ID_QUERY, - { - variables: { - testPlanRunId: testPlanRun?.id - } - } - ); - - const status = useMemo(() => { - return collectionJobQuery?.collectionJobByTestPlanRunId?.status; - }, [collectionJobQuery]); + const { + state: { collectionJob } + } = useContext(CollectionJobContext); + const testStatus = useMemo( + () => collectionJob?.testStatus ?? [], + [collectionJob] + ); - return ( - -
    -

    - -

    -
    - + + ); }; TestNavigator.propTypes = { - show: PropTypes.bool, - isSignedIn: PropTypes.bool, - isVendor: PropTypes.bool, - testPlanReport: PropTypes.object, - tests: PropTypes.array, - testResult: PropTypes.object, - conflicts: PropTypes.object, - currentTestIndex: PropTypes.number, - viewedTests: PropTypes.array, - toggleShowClick: PropTypes.func, - handleTestClick: PropTypes.func, - testPlanRun: PropTypes.object + show: PropTypes.bool, + isSignedIn: PropTypes.bool, + isVendor: PropTypes.bool, + testPlanReport: PropTypes.object, + tests: PropTypes.array, + testResult: PropTypes.object, + conflicts: PropTypes.object, + currentTestIndex: PropTypes.number, + viewedTests: PropTypes.array, + toggleShowClick: PropTypes.func, + handleTestClick: PropTypes.func, + testPlanRun: PropTypes.object }; export default TestNavigator; diff --git a/client/components/TestRun/TestRun.css b/client/components/TestRun/TestRun.css index 25cc521fd..d69d6bfb7 100644 --- a/client/components/TestRun/TestRun.css +++ b/client/components/TestRun/TestRun.css @@ -1,322 +1,343 @@ @import url('https://use.fontawesome.com/releases/v5.0.1/css/all.css'); .main-test-area { - position: relative; + position: relative; } /* Test Navigator */ .test-navigator h2 { - margin-top: 0; + margin-top: 0; } #test-navigator-heading { - font-size: 1em; - margin-top: 0.75em; + font-size: 1em; + margin-top: 0.75em; } button.test-navigator-toggle { - border: 0; - background-color: transparent; - color: #929292; + border: 0; + background-color: transparent; + color: #929292; } button.test-navigator-toggle:hover, button.test-navigator-toggle:focus { - color: #0b60ab; + color: #0b60ab; } .test-navigator-toggle-container { - display: flex; - flex-direction: row; + display: flex; + flex-direction: row; } .test-navigator-toggle-inner-container { - width: fit-content; + width: fit-content; } @media all and (max-width: 767px) { - .test-navigator-toggle-inner-container { - position: relative; - top: -0.15em; - } + .test-navigator-toggle-inner-container { + position: relative; + top: -0.15em; + } } @media all and (min-width: 768px) { - .test-navigator-toggle-inner-container { - position: absolute; - top: -0.15em; - left: -2.75em; - } + .test-navigator-toggle-inner-container { + position: absolute; + top: -0.15em; + left: -2.75em; + } } .test-navigator-list { - position: relative; - margin-top: 1em; - font-size: 0.9em; - padding-left: 3.25em; + position: relative; + margin-top: 1em; + font-size: 0.9em; + padding-left: 3.25em; } .test-name-wrapper { - position: relative; - margin: 0 0 1em 0.25em; - list-style: unset; + position: relative; + margin: 0 0 1em 0.25em; + list-style: unset; } .test-name-wrapper:before { - content: ''; - height: 100%; - left: -2.8em; - position: absolute; - top: 1.2em; - background: #d2d5d9; - width: 2px; + content: ''; + height: 100%; + left: -2.8em; + position: absolute; + top: 1.2em; + background: #d2d5d9; + width: 2px; } .test-name-wrapper:last-child:before { - height: 0%; + height: 0%; } .complete.test-name-wrapper:before, .changes-requested.test-name-wrapper:before { - background: #1d8f37; + background: #1d8f37; } .progress-indicator { - position: absolute; - left: -3.35em; - top: 0.2em; - width: 18px; - height: 18px; - border-radius: 50px; + position: absolute; + left: -3.35em; + top: 0.2em; + width: 18px; + height: 18px; + border-radius: 50px; } .test-name-wrapper .test-name { - display: block; - color: #2f2f2f; + display: block; + color: #2f2f2f; } .test-name-wrapper .test-name:hover { - color: #0b60ab; + color: #0b60ab; } .test-navigator a[aria-current='true'] ~ span { - box-shadow: 0px 0px 0px 2px #ffffff, 0px 0px 0px 4px #1d8f37; + box-shadow: 0px 0px 0px 2px #ffffff, 0px 0px 0px 4px #1d8f37; } /* Test States in Test Navigator */ .test-name-wrapper.not-started .progress-indicator { - background: #d2d5d9; + background: #d2d5d9; } .test-name-wrapper.in-progress .progress-indicator { - background: #1e8f37; - background: linear-gradient( - 135deg, - #1e8f37 0%, - #1e8f37 50%, - rgba(255, 255, 255, 1) 50%, - rgba(255, 255, 255, 1) 100% - ); - border: 2px solid #1e8f37; + background: #1e8f37; + background: linear-gradient( + 135deg, + #1e8f37 0%, + #1e8f37 50%, + rgba(255, 255, 255, 1) 50%, + rgba(255, 255, 255, 1) 100% + ); + border: 2px solid #1e8f37; } .test-name-wrapper.changes-requested .progress-indicator { - background: #f87f1c; + background: #f87f1c; } .test-name-wrapper.changes-requested .progress-indicator:before { - position: relative; - content: '\f024'; - font-family: 'Font Awesome 5 Free'; - font-weight: 900; - color: white; - font-size: 10px; - top: -3px; - left: 4px; + position: relative; + content: '\f024'; + font-family: 'Font Awesome 5 Free'; + font-weight: 900; + color: white; + font-size: 10px; + top: -3px; + left: 4px; } .test-name-wrapper.skipped .progress-indicator { - background: White; - border: 2px dashed black; + background: White; + border: 2px dashed black; } .test-name-wrapper.bot-complete .progress-indicator { - background: #1e8f37; + background: #1e8f37; } .test-name-wrapper.bot-complete .progress-indicator:before { - display: inline-block; - text-rendering: auto; - -webkit-font-smoothing: antialiased; - font-family: 'Font Awesome 5 Free'; - font-weight: 900; - content: '\f00c'; - color: white; - font-size: 10px; - position: relative; - top: -3px; - left: 4px; + display: inline-block; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + font-family: 'Font Awesome 5 Free'; + font-weight: 900; + content: '\f00c'; + color: white; + font-size: 10px; + position: relative; + top: -3px; + left: 4px; +} + +.test-name-wrapper.bot-running .progress-indicator { + background: #d2d5d9; + border: 2px solid #1e8f37; +} + +.test-name-wrapper.bot-error .progress-indicator { + background: #e3261f; +} +.test-name-wrapper.bot-error .progress-indicator:before { + display: inline-block; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + font-family: 'Font Awesome 5 Free'; + font-weight: 900; + content: '\f071'; + color: white; + font-size: 10px; + position: relative; + top: -4px; + left: 3px; } - .test-name-wrapper.bot-queued .progress-indicator { - background: #295fa6; + background: #295fa6; } .test-name-wrapper.bot-queued .progress-indicator:before { - display: inline-block; - text-rendering: auto; - -webkit-font-smoothing: antialiased; - font-family: 'Font Awesome 5 Free'; - font-weight: 900; - content: '\f061'; - color: white; - font-size: 10px; - position: relative; - top: -3px; - left: 4px; + display: inline-block; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + font-family: 'Font Awesome 5 Free'; + font-weight: 900; + content: '\f061'; + color: white; + font-size: 10px; + position: relative; + top: -3px; + left: 4px; } .test-name-wrapper.bot-cancelled .progress-indicator { - background: #a331fe; + background: #a331fe; } .test-name-wrapper.bot-cancelled .progress-indicator:before { - display: inline-block; - text-rendering: auto; - -webkit-font-smoothing: antialiased; - font-family: 'Font Awesome 5 Free'; - font-weight: 900; - content: '\f05e'; - color: white; - font-size: 10px; - position: relative; - top: -3px; - left: 4px; + display: inline-block; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + font-family: 'Font Awesome 5 Free'; + font-weight: 900; + content: '\f05e'; + color: white; + font-size: 10px; + position: relative; + top: -3px; + left: 4px; } .test-name-wrapper.conflicts .progress-indicator { - background: #ffcd00; + background: #ffcd00; } .test-name-wrapper.conflicts .progress-indicator:before, .test-name-wrapper.conflicts .progress-indicator:after { - content: ''; - position: absolute; - background: #976005; - width: 2px; - left: 8px; + content: ''; + position: absolute; + background: #976005; + width: 2px; + left: 8px; } .test-name-wrapper.conflicts .progress-indicator:before { - top: 3px; - height: 7px; + top: 3px; + height: 7px; } .test-name-wrapper.conflicts .progress-indicator:after { - top: 12px; - height: 2px; + top: 12px; + height: 2px; } .test-name-wrapper.complete .progress-indicator { - background: #1e8f37; + background: #1e8f37; } .test-name-wrapper.complete .progress-indicator:before, .test-name-wrapper.complete .progress-indicator:after { - content: ''; - position: absolute; - background: white; - -ms-transform: rotate(45deg); /* IE 9 */ - -webkit-transform: rotate(45deg); /* Chrome, Safari, Opera */ - transform: rotate(45deg); + content: ''; + position: absolute; + background: white; + -ms-transform: rotate(45deg); /* IE 9 */ + -webkit-transform: rotate(45deg); /* Chrome, Safari, Opera */ + transform: rotate(45deg); } .test-name-wrapper.complete .progress-indicator:before { - width: 4px; - height: 3px; - top: 8px; - left: 4px; + width: 4px; + height: 3px; + top: 8px; + left: 4px; } .test-name-wrapper.complete .progress-indicator:after { - width: 3px; - height: 9px; - top: 5px; - left: 9px; + width: 3px; + height: 9px; + top: 5px; + left: 9px; } .test-iframe-container { - padding: 0; + padding: 0; } main.container-fluid .test-iframe-container > .row { - padding-top: 0; + padding-top: 0; } #test-iframe { - width: 80vw; - height: 65vh; - border: 1px solid #d2d5d9; - border-radius: 3px; - padding: 0 1em; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, - 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', - 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + width: 80vw; + height: 65vh; + border: 1px solid #d2d5d9; + border-radius: 3px; + padding: 0 1em; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, + 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', + 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; } .test-run-toolbar { - display: flex; - width: 100%; + display: flex; + width: 100%; - padding-left: 0; - padding-right: 0; + padding-left: 0; + padding-right: 0; } .test-run-toolbar li:first-of-type { - flex-grow: 1; + flex-grow: 1; } .test-run-toolbar li:last-of-type { - padding-right: 0; + padding-right: 0; } .dot { - height: 10px; - width: 10px; - background-color: #6bda84; - border-radius: 50%; - display: inline-block; + height: 10px; + width: 10px; + background-color: #6bda84; + border-radius: 50%; + display: inline-block; } .test-navigator a[aria-current='true'] { - color: black; - font-weight: bold; + color: black; + font-weight: bold; } .test-navigator a[aria-current='true']:hover { - text-decoration: none; - color: black; + text-decoration: none; + color: black; } /* Test Headings and information */ .task-label { - display: block; - font-size: 0.6em; - font-weight: normal; - border-radius: 3px; + display: block; + font-size: 0.6em; + font-weight: normal; + border-radius: 3px; } .test-info-wrapper { - display: flex; - flex-direction: row; - justify-content: space-between; - flex-wrap: wrap; + display: flex; + flex-direction: row; + justify-content: space-between; + flex-wrap: wrap; - padding: 0; + padding: 0; } .test-info-entity { - padding: 0.5em 0.75em; - border-radius: 3px; - margin-bottom: 1em; - font-size: 0.9em; + padding: 0.5em 0.75em; + border-radius: 3px; + margin-bottom: 1em; + font-size: 0.9em; } .apg-example-name, @@ -324,156 +345,156 @@ main.container-fluid .test-iframe-container > .row { .test-version, .review-status, .target-date { - background: #f5f8fa; - border: 1px solid #d2d5d9; - width: 37%; - text-align: center; + background: #f5f8fa; + border: 1px solid #d2d5d9; + width: 37%; + text-align: center; } .at-browser { - display: flex; - flex-direction: row; - align-items: flex-start; + display: flex; + flex-direction: row; + align-items: flex-start; - background: #f5f8fa; - border: 1px solid #d2d5d9; - width: 37%; - /*text-align: center;*/ + background: #f5f8fa; + border: 1px solid #d2d5d9; + width: 37%; + /*text-align: center;*/ } .at-browser-row { - width: 100%; - display: flex; - flex-direction: column; - align-items: flex-start; - justify-content: space-between; + width: 100%; + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: space-between; } #edit-fa-button { - padding: 0; - margin: 0; - background: none; - color: #919191; - border: none; + padding: 0; + margin: 0; + background: none; + color: #919191; + border: none; } #edit-fa-button svg { - padding: 0; - margin: 0; + padding: 0; + margin: 0; } .tests-completed { - width: 23.5%; + width: 23.5%; } .reviewing-as { - background: #ecf6ff; - border: 1px solid #c4d1e2; - width: 100%; + background: #ecf6ff; + border: 1px solid #c4d1e2; + width: 100%; } .reviewing-as p { - margin: 0; - font-style: italic; - margin-left: 0.5em; - display: inline-block; + margin: 0; + font-style: italic; + margin-left: 0.5em; + display: inline-block; } .reviewing-as.bot { - background: #f0e1ff; - border: 1px solid #d29fff; + background: #f0e1ff; + border: 1px solid #d29fff; } /* Current Test Options */ .current-test-options { - padding-right: 0; + padding-right: 0; } .current-test-options > div { - background: #f5f8fa; - border: 1px solid #d2d5d9; - border-radius: 3px; + background: #f5f8fa; + border: 1px solid #d2d5d9; + border-radius: 3px; } .current-test-options h2 { - margin-top: 0; - padding: 0.9em; - background: #e9ebee; - font-size: 1em; - font-weight: 700; - border-bottom: 1px solid #d2d5d9; - text-align: center; + margin-top: 0; + padding: 0.9em; + background: #e9ebee; + font-size: 1em; + font-weight: 700; + border-bottom: 1px solid #d2d5d9; + text-align: center; } .current-test-options .options-wrapper { - padding: 0.05em 0.75em; + padding: 0.05em 0.75em; } .help-link { - font-size: 0.9em; - text-align: center; - padding-top: 0.7em; + font-size: 0.9em; + text-align: center; + padding-top: 0.7em; } .fa-exclamation-circle, .fa-redo, .fa-pen, .fa-external-link-alt { - color: #959595; + color: #959595; } .fa-check { - color: #1e8f37; + color: #1e8f37; } .status-bar { - display: flex; - align-items: center; + display: flex; + align-items: center; } .at-browser-details-modal-alert { - display: flex; + display: flex; - grid-column: 1 / -1; + grid-column: 1 / -1; } fieldset .at-browser-details-modal-alert { - margin-bottom: 0; + margin-bottom: 0; } .btn-options { - width: 100%; + width: 100%; } a.btn-options:hover { - text-decoration: none; + text-decoration: none; } .at-browser-details-modal-alert span { - font-size: 0.875rem; - line-height: 1.125rem; + font-size: 0.875rem; + line-height: 1.125rem; } .at-browser-details-full-column { - grid-column: 1 / -1; + grid-column: 1 / -1; } .modal-header, .modal-body, .modal-footer { - padding: 1.5rem 2.5rem; + padding: 1.5rem 2.5rem; } @media (min-width: 768px) { - .modal-50w { - min-width: 50%; - } + .modal-50w { + min-width: 50%; + } - .modal-60w { - min-width: 60%; - } + .modal-60w { + min-width: 60%; + } } .form-control.is-invalid { - background-image: none; + background-image: none; } diff --git a/client/components/TestRun/index.jsx b/client/components/TestRun/index.jsx index db7320fbf..27a7ce149 100644 --- a/client/components/TestRun/index.jsx +++ b/client/components/TestRun/index.jsx @@ -5,13 +5,11 @@ import useRouterQuery from '../../hooks/useRouterQuery'; import { useMutation, useQuery } from '@apollo/client'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { - faPen, - faEdit, - faRedo, - faCheck, - faCheckCircle, - faExclamationCircle, - faRobot + faPen, + faRedo, + faCheck, + faCheckCircle, + faExclamationCircle } from '@fortawesome/free-solid-svg-icons'; import nextId from 'react-id-generator'; import { Alert, Button, Col, Container, Row } from 'react-bootstrap'; @@ -20,6 +18,7 @@ import ReviewConflictsModal from './ReviewConflictsModal'; import StatusBar from './StatusBar'; import TestRenderer from '../TestRenderer'; import OptionButton from './OptionButton'; +import Heading from './Heading'; import PageStatus from '../common/PageStatus'; import BasicModal from '../common/BasicModal'; import BasicThemedModal from '../common/BasicThemedModal'; @@ -28,1404 +27,1242 @@ import { useDetectUa } from '../../hooks/useDetectUa'; import DisplayNone from '../../utils/DisplayNone'; import { navigateTests } from '../../utils/navigateTests'; import { - COLLECTION_JOB_STATUS_BY_TEST_PLAN_RUN_ID_QUERY, - DELETE_TEST_RESULT_MUTATION, - FIND_OR_CREATE_BROWSER_VERSION_MUTATION, - FIND_OR_CREATE_TEST_RESULT_MUTATION, - SAVE_TEST_RESULT_MUTATION, - SUBMIT_TEST_RESULT_MUTATION, - TEST_RUN_PAGE_ANON_QUERY, - TEST_RUN_PAGE_QUERY + DELETE_TEST_RESULT_MUTATION, + FIND_OR_CREATE_BROWSER_VERSION_MUTATION, + FIND_OR_CREATE_TEST_RESULT_MUTATION, + SAVE_TEST_RESULT_MUTATION, + SUBMIT_TEST_RESULT_MUTATION, + TEST_RUN_PAGE_ANON_QUERY, + TEST_RUN_PAGE_QUERY } from './queries'; import { evaluateAuth } from '../../utils/evaluateAuth'; import './TestRun.css'; import ReviewConflicts from '../ReviewConflicts'; import createIssueLink from '../../utils/createIssueLink'; import { convertDateToString } from '../../utils/formatter'; +import { Provider as CollectionJobContextProvider } from './CollectionJobContext'; const TestRun = () => { - const params = useParams(); - const navigate = useNavigate(); - const routerQuery = useRouterQuery(); - - // Detect UA information - const { uaBrowser, uaMajor } = useDetectUa(); - - const titleRef = useRef(); - // To prevent default AT/Browser versions being set before initial - // AT & Browser Details Modal is saved - const testRunStateRef = useRef(); - // HACK: Temporary fix to allow for consistency of TestRenderer after - // testRunStateRef is nullified during unmount. - // See 'unmount' of 'pageContent' hook in the TestRenderer component - const recentTestRunStateRef = useRef(); - const testRunResultRef = useRef(); - const testRendererSubmitButtonRef = useRef(); - const conflictMarkdownRef = useRef(); - const adminReviewerCheckedRef = useRef(false); - const adminReviewerOriginalTestRef = useRef(); - const editAtBrowserDetailsButtonRef = useRef(); - - const { runId: testPlanRunId, testPlanReportId } = params; - - const { loading, data, error } = useQuery( - testPlanRunId ? TEST_RUN_PAGE_QUERY : TEST_RUN_PAGE_ANON_QUERY, - { - fetchPolicy: 'cache-and-network', - variables: { testPlanRunId, testPlanReportId } - } - ); - - const { data: collectionJobQuery } = useQuery( - COLLECTION_JOB_STATUS_BY_TEST_PLAN_RUN_ID_QUERY, - { - variables: { testPlanRunId }, - fetchPolicy: 'cache-and-network' - } - ); - - const [createTestResult, { loading: createTestResultLoading }] = - useMutation(FIND_OR_CREATE_TEST_RESULT_MUTATION); - const [saveTestResult] = useMutation(SAVE_TEST_RESULT_MUTATION); - const [submitTestResult] = useMutation(SUBMIT_TEST_RESULT_MUTATION); - const [deleteTestResult] = useMutation(DELETE_TEST_RESULT_MUTATION); - const [createBrowserVersion] = useMutation( - FIND_OR_CREATE_BROWSER_VERSION_MUTATION - ); + const params = useParams(); + const navigate = useNavigate(); + const routerQuery = useRouterQuery(); + + // Detect UA information + const { uaBrowser, uaMajor } = useDetectUa(); + + const titleRef = useRef(); + // To prevent default AT/Browser versions being set before initial + // AT & Browser Details Modal is saved + const testRunStateRef = useRef(); + // HACK: Temporary fix to allow for consistency of TestRenderer after + // testRunStateRef is nullified during unmount. + // See 'unmount' of 'pageContent' hook in the TestRenderer component + const recentTestRunStateRef = useRef(); + const testRunResultRef = useRef(); + const testRendererSubmitButtonRef = useRef(); + const conflictMarkdownRef = useRef(); + const adminReviewerCheckedRef = useRef(false); + const adminReviewerOriginalTestRef = useRef(); + const editAtBrowserDetailsButtonRef = useRef(); + + const { runId: testPlanRunId, testPlanReportId } = params; + + const { loading, data, error } = useQuery( + testPlanRunId ? TEST_RUN_PAGE_QUERY : TEST_RUN_PAGE_ANON_QUERY, + { + fetchPolicy: 'cache-and-network', + variables: { testPlanRunId, testPlanReportId }, + pollInterval: 0 + } + ); + + useEffect(() => { + if (data) setup(data); + }, [data]); + + const [createTestResult, { loading: createTestResultLoading }] = useMutation( + FIND_OR_CREATE_TEST_RESULT_MUTATION + ); + const [saveTestResult] = useMutation(SAVE_TEST_RESULT_MUTATION); + const [submitTestResult] = useMutation(SUBMIT_TEST_RESULT_MUTATION); + const [deleteTestResult] = useMutation(DELETE_TEST_RESULT_MUTATION); + const [createBrowserVersion] = useMutation( + FIND_OR_CREATE_BROWSER_VERSION_MUTATION + ); + + const [isRendererReady, setIsRendererReady] = useState(false); + const [isSavingForm, setIsSavingForm] = useState(false); + const [isTestSubmitClicked, setIsTestSubmitClicked] = useState(false); + const [isTestEditClicked, setIsTestEditClicked] = useState(false); + const [showTestNavigator, setShowTestNavigator] = useState(true); + const [showStartOverModal, setShowStartOverModal] = useState(false); + const [showReviewConflictsModal, setShowReviewConflictsModal] = + useState(false); + const [showGetInvolvedModal, setShowGetInvolvedModal] = useState(false); + + // Modal State Values + const [isShowingAtBrowserModal, setIsShowingAtBrowserModal] = useState(true); + const [showThemedModal, setShowThemedModal] = useState(false); + const [themedModalTitle, setThemedModalTitle] = useState(''); + const [themedModalContent, setThemedModalContent] = useState(<>); + const [themedModalOtherButton, setThemedModalOtherButton] = useState(null); + const [isEditAtBrowserDetailsModalClick, setIsEditAtBrowserDetailsClicked] = + useState(false); + const [updateMessageComponent, setUpdateMessageComponent] = useState(null); + + // Queried State Values + const [testPlanRun, setTestPlanRun] = useState({}); + const [users, setUsers] = useState([]); + const [tester, setTester] = useState(); + const [tests, setTests] = useState([]); + const [testResults, setTestResults] = useState([]); + const [testPlanReport, setTestPlanReport] = useState({}); + const [testPlanVersion, setTestPlanVersion] = useState(); + const [currentTest, setCurrentTest] = useState({}); + const [currentTestIndex, setCurrentTestIndex] = useState(0); + const [currentTestAtVersionId, setCurrentTestAtVersionId] = useState(''); + const [currentTestBrowserVersionId, setCurrentTestBrowserVersionId] = + useState(''); + const [currentAtVersion, setCurrentAtVersion] = useState(''); + const [currentBrowserVersion, setCurrentBrowserVersion] = useState(''); + const [pageReady, setPageReady] = useState(false); + + const auth = evaluateAuth(data && data.me ? data.me : {}); + let { id: userId, isSignedIn, isAdmin } = auth; + + // if a signed in user navigates to this page, treat them as anon to prevent + // invalid save attempts + if (testPlanReportId) isSignedIn = false; + + // check to ensure an admin that manually went to a test run url doesn't + // run the test as themselves + const openAsUserId = + routerQuery.get('user') || (tester && tester.id !== userId) + ? tester?.id + : null; + const testerId = openAsUserId || userId; + const isAdminReviewer = !!(isAdmin && openAsUserId); + const openAsUser = users?.find(user => user.id === openAsUserId); + + useEffect(() => { + reset(); + + // Set up for the current test + const currentTest = tests[currentTestIndex]; + if (currentTest) { + setPageReady(false); + if (isSignedIn) { + (async () => { + const { testPlanRun, testPlanReport } = + await createTestResultForRenderer( + currentTest.id, + currentTestAtVersionId, + currentTestBrowserVersionId + ); + updateLocalState(testPlanRun, testPlanReport); + setPageReady(true); + })(); + } else { + // To account for ANON viewing + setCurrentTest(tests[currentTestIndex]); + setPageReady(true); + } + } else if (data) setup(data); + }, [currentTestIndex]); - const [isRendererReady, setIsRendererReady] = useState(false); - const [isSavingForm, setIsSavingForm] = useState(false); - const [isTestSubmitClicked, setIsTestSubmitClicked] = useState(false); - const [isTestEditClicked, setIsTestEditClicked] = useState(false); - const [showTestNavigator, setShowTestNavigator] = useState(true); - const [showStartOverModal, setShowStartOverModal] = useState(false); - const [showReviewConflictsModal, setShowReviewConflictsModal] = - useState(false); - const [showGetInvolvedModal, setShowGetInvolvedModal] = useState(false); - - // Modal State Values - const [isShowingAtBrowserModal, setIsShowingAtBrowserModal] = - useState(true); - const [showThemedModal, setShowThemedModal] = useState(false); - const [themedModalTitle, setThemedModalTitle] = useState(''); - const [themedModalContent, setThemedModalContent] = useState(<>); - const [themedModalOtherButton, setThemedModalOtherButton] = useState(null); - const [isEditAtBrowserDetailsModalClick, setIsEditAtBrowserDetailsClicked] = - useState(false); - const [updateMessageComponent, setUpdateMessageComponent] = useState(null); - - // Queried State Values - const [testPlanRun, setTestPlanRun] = useState({}); - const [users, setUsers] = useState([]); - const [tester, setTester] = useState(); - const [tests, setTests] = useState([]); - const [testResults, setTestResults] = useState([]); - const [testPlanReport, setTestPlanReport] = useState({}); - const [testPlanVersion, setTestPlanVersion] = useState(); - const [currentTest, setCurrentTest] = useState({}); - const [currentTestIndex, setCurrentTestIndex] = useState(0); - const [currentTestAtVersionId, setCurrentTestAtVersionId] = useState(''); - const [currentTestBrowserVersionId, setCurrentTestBrowserVersionId] = - useState(''); - const [currentAtVersion, setCurrentAtVersion] = useState(''); - const [currentBrowserVersion, setCurrentBrowserVersion] = useState(''); - const [pageReady, setPageReady] = useState(false); - - const auth = evaluateAuth(data && data.me ? data.me : {}); - let { id: userId, isSignedIn, isAdmin } = auth; + const setup = data => { + const { testPlanRun, users } = data; + const { tester, testResults = [] } = testPlanRun || {}; + let { testPlanReport } = testPlanRun || {}; // if a signed in user navigates to this page, treat them as anon to prevent // invalid save attempts if (testPlanReportId) isSignedIn = false; - - // check to ensure an admin that manually went to a test run url doesn't - // run the test as themselves - const openAsUserId = - routerQuery.get('user') || (tester && tester.id !== userId) - ? tester?.id - : null; - const testerId = openAsUserId || userId; - const isAdminReviewer = !!(isAdmin && openAsUserId); - const openAsUser = users?.find(user => user.id === openAsUserId); - - useEffect(() => { - if (data) setup(data); - }, [data]); - - useEffect(() => { - reset(); - - // Set up for the current test - const currentTest = tests[currentTestIndex]; - if (currentTest) { - setPageReady(false); - if (isSignedIn) { - (async () => { - const { testPlanRun, testPlanReport } = - await createTestResultForRenderer( - currentTest.id, - currentTestAtVersionId, - currentTestBrowserVersionId - ); - updateLocalState(testPlanRun, testPlanReport); - setPageReady(true); - })(); - } else { - // To account for ANON viewing - setCurrentTest(tests[currentTestIndex]); - setPageReady(true); - } - } else if (data) setup(data); - }, [currentTestIndex]); - - const setup = data => { - const { testPlanRun, users } = data; - const { tester, testResults = [] } = testPlanRun || {}; - let { testPlanReport } = testPlanRun || {}; - - // if a signed in user navigates to this page, treat them as anon to prevent - // invalid save attempts - if (testPlanReportId) isSignedIn = false; - if (!isSignedIn) testPlanReport = data.testPlanReport; - - const { - testPlanVersion, - runnableTests = [], - conflicts = [] - } = testPlanReport || {}; - - const tests = runnableTests.map((test, index) => ({ - ...test, - index, - seq: index + 1, - testResult: testResults.find(t => t.test.id === test.id), - hasConflicts: !!conflicts.find(c => c.source.test.id === test.id) - })); - const currentTest = tests[currentTestIndex]; - - // Capture the AT & Browser Versions - const defaultAtVersionId = testPlanReport.at.atVersions[0].id; - const defaultBrowserVersionId = - testPlanReport.browser.browserVersions[0].id; - - const currentTestAtVersionId = - currentTest.testResult?.atVersion?.id || defaultAtVersionId; - - const currentTestBrowserVersionId = - currentTest.testResult?.browserVersion?.id || - defaultBrowserVersionId; - - const currentAtVersion = - currentTest.testResult?.atVersion || - testPlanReport.at.atVersions.find( - item => item.id === currentTestAtVersionId - ) || - 'N/A'; - - let currentBrowserVersion = - currentTest.testResult?.browserVersion || - testPlanReport.browser.browserVersions.find( - item => item.id === currentTestBrowserVersionId - ) || - 'N/A'; - - // Only show major version of browser - currentBrowserVersion = { - id: currentAtVersion.id, - name: currentBrowserVersion.name.split('.')[0] - }; - - // Auto batch the states - setUsers(users); - setTester(tester); - setTestPlanRun(testPlanRun); - setTestPlanReport(testPlanReport); - setTestPlanVersion(testPlanVersion); - setTests(tests); - setTestResults(testResults); - setCurrentTest(currentTest); - setCurrentTestAtVersionId(currentTestAtVersionId); - setCurrentTestBrowserVersionId(currentTestBrowserVersionId); - setCurrentAtVersion(currentAtVersion); - setCurrentBrowserVersion(currentBrowserVersion); - // Testers do not need to change AT/Browser versions - // while assigning verdicts for previously automated tests - if (testPlanRun?.initiatedByAutomation) { - setIsShowingAtBrowserModal(false); - } - setPageReady(true); + if (!isSignedIn) testPlanReport = data.testPlanReport; + + const { + testPlanVersion, + runnableTests = [], + conflicts = [] + } = testPlanReport || {}; + + const tests = runnableTests.map((test, index) => ({ + ...test, + index, + seq: index + 1, + testResult: testResults.find(t => t.test.id === test.id), + hasConflicts: !!conflicts.find(c => c.source.test.id === test.id) + })); + const currentTest = tests[currentTestIndex]; + + // Capture the AT & Browser Versions + const defaultAtVersionId = testPlanReport.at.atVersions[0].id; + const defaultBrowserVersionId = + testPlanReport.browser.browserVersions[0].id; + + const currentTestAtVersionId = + currentTest.testResult?.atVersion?.id || defaultAtVersionId; + + const currentTestBrowserVersionId = + currentTest.testResult?.browserVersion?.id || defaultBrowserVersionId; + + const currentAtVersion = + currentTest.testResult?.atVersion || + testPlanReport.at.atVersions.find( + item => item.id === currentTestAtVersionId + ) || + 'N/A'; + + let currentBrowserVersion = + currentTest.testResult?.browserVersion || + testPlanReport.browser.browserVersions.find( + item => item.id === currentTestBrowserVersionId + ) || + 'N/A'; + + // Only show major version of browser + currentBrowserVersion = { + id: currentAtVersion.id, + name: currentBrowserVersion.name.split('.')[0] }; - const reset = () => { - testRunStateRef.current = null; - recentTestRunStateRef.current = null; - testRunResultRef.current = null; - conflictMarkdownRef.current = null; - - setPageReady(false); - setIsRendererReady(false); - setIsTestSubmitClicked(false); - - if (titleRef.current) titleRef.current.focus(); - }; - - const updateLocalState = (updatedTestPlanRun, updatedTestPlanReport) => { - const { conflicts, runnableTests } = updatedTestPlanReport; - - const testResults = updatedTestPlanRun.testResults; - const tests = runnableTests.map((test, index) => ({ - ...test, - index, - seq: index + 1, - testResult: testResults.find(t => t.test.id === test.id), - hasConflicts: !!conflicts.find(c => c.source.test.id === test.id) - })); + // Auto batch the states + setUsers(users); + setTester(tester); + setTestPlanRun(testPlanRun); + setTestPlanReport(testPlanReport); + setTestPlanVersion(testPlanVersion); + setTests(tests); + setTestResults(testResults); + setCurrentTest(currentTest); + setCurrentTestAtVersionId(currentTestAtVersionId); + setCurrentTestBrowserVersionId(currentTestBrowserVersionId); + setCurrentAtVersion(currentAtVersion); + setCurrentBrowserVersion(currentBrowserVersion); + // Testers do not need to change AT/Browser versions + // while assigning verdicts for previously automated tests + if (testPlanRun?.initiatedByAutomation) { + setIsShowingAtBrowserModal(false); + } + setPageReady(true); + }; + + const reset = () => { + testRunStateRef.current = null; + recentTestRunStateRef.current = null; + testRunResultRef.current = null; + conflictMarkdownRef.current = null; + + setPageReady(false); + setIsRendererReady(false); + setIsTestSubmitClicked(false); + + if (titleRef.current) titleRef.current.focus(); + }; + + const updateLocalState = (updatedTestPlanRun, updatedTestPlanReport) => { + const { conflicts, runnableTests } = updatedTestPlanReport; + + const testResults = updatedTestPlanRun.testResults; + const tests = runnableTests.map((test, index) => ({ + ...test, + index, + seq: index + 1, + testResult: testResults.find(t => t.test.id === test.id), + hasConflicts: !!conflicts.find(c => c.source.test.id === test.id) + })); + + setTests(tests); + setTestResults(testResults); + setCurrentTest(tests[currentTestIndex]); + setTestPlanReport({ ...testPlanReport, conflicts }); + }; + + if (error) { + const { message } = error; + return ( + + ); + } - setTests(tests); - setTestResults(testResults); - setCurrentTest(tests[currentTestIndex]); - setTestPlanReport({ ...testPlanReport, conflicts }); - }; + if (!data || loading || createTestResultLoading || isSavingForm) { + return ( + + ); + } - if (error) { - const { message } = error; - return ( - - ); + if (isSignedIn && !testPlanRun) { + return ( + + ); + } + + const toggleTestNavigator = () => setShowTestNavigator(!showTestNavigator); + + const createTestResultForRenderer = async ( + testId, + atVersionId, + browserVersionId + ) => { + const result = await createTestResult({ + variables: { + testPlanRunId, + testId, + atVersionId, + browserVersionId + } + }); + return result.data.testPlanRun.findOrCreateTestResult; + }; + + // Check to see if there are tests to run + const testCount = tests.length; + + // Check if this test is being run as an admin + if ( + adminReviewerOriginalTestRef.current && + adminReviewerOriginalTestRef.current !== currentTest.id + ) + adminReviewerCheckedRef.current = false; + + if ( + isAdminReviewer && + currentTest.testResult && + !adminReviewerCheckedRef.current + ) + adminReviewerOriginalTestRef.current = currentTest; + + adminReviewerCheckedRef.current = true; + + let issueLink; + const hasLoadingCompleted = Object.keys(currentTest).length; + if (hasLoadingCompleted) { + issueLink = createIssueLink({ + testPlanTitle: testPlanVersion.title, + testPlanDirectory: testPlanVersion.testPlan.directory, + versionString: `V${convertDateToString( + testPlanVersion.updatedAt, + 'YY.MM.DD' + )}`, + testTitle: currentTest.title, + testRowNumber: currentTest.rowNumber, + testSequenceNumber: currentTest.seq, + testRenderedUrl: currentTest.renderedUrl, + atName: testPlanReport.at.name, + browserName: testPlanReport.browser.name, + atVersionName: currentAtVersion?.name, + browserVersionName: currentBrowserVersion?.name, + conflictMarkdown: conflictMarkdownRef.current + }); + } + + const remapScenarioResults = ( + rendererState, + scenarioResults, + captureHighlightRequired = false + ) => { + let newScenarioResults = []; + if (!rendererState || !scenarioResults) { + throw new Error( + `Unable to merge invalid results:rendererState:${rendererState} | scenarioResults:${scenarioResults}` + ); } - if (!data || loading || createTestResultLoading || isSavingForm) { - return ( - - ); + const { commands } = rendererState; + if (!commands || commands.length !== scenarioResults.length) { + throw new Error( + `Unable to merge invalid results:commands:${commands} | commands.length !== scenarioResults.length:${ + commands.length !== scenarioResults.length + }` + ); } - if (isSignedIn && !testPlanRun) { - return ( - + const UnexpectedBehaviorsArray = [ + 'EXCESSIVELY_VERBOSE', + 'UNEXPECTED_CURSOR_POSITION', + 'SLUGGISH', + 'AT_CRASHED', + 'BROWSER_CRASHED', + 'OTHER' + ]; + + for (let i = 0; i < commands.length; i++) { + let scenarioResult = { ...scenarioResults[i] }; + let assertionResults = []; + let unexpectedBehaviors = null; + + // collect variables + const { atOutput, assertions, unexpected } = commands[i]; + + // process assertion results + for (let j = 0; j < assertions.length; j++) { + const { description, result, highlightRequired } = assertions[j]; + const assertionResult = { + ...scenarioResult.assertionResults.find( + ({ assertion: { text } }) => text === description + ), + passed: result === 'pass' + }; + assertionResults.push( + captureHighlightRequired + ? { ...assertionResult, highlightRequired } + : assertionResult ); + } + + // process unexpected behaviors + const { hasUnexpected, behaviors, highlightRequired } = unexpected; + if (hasUnexpected === 'hasUnexpected') { + unexpectedBehaviors = []; + /** + * 0 = EXCESSIVELY_VERBOSE + * 1 = UNEXPECTED_CURSOR_POSITION + * 2 = SLUGGISH + * 3 = AT_CRASHED + * 4 = BROWSER_CRASHED + * 5 = OTHER + */ + for (let i = 0; i < behaviors.length; i++) { + const behavior = behaviors[i]; + if (behavior.checked) { + unexpectedBehaviors.push({ + id: UnexpectedBehaviorsArray[i], + text: behavior.description, + details: behavior.more.value, + impact: behavior.impact.toUpperCase(), + highlightRequired: captureHighlightRequired + ? behavior.more.highlightRequired + : false + }); + } + } + } else if (hasUnexpected === 'doesNotHaveUnexpected') + unexpectedBehaviors = []; + + // re-assign scenario result due to read only values + scenarioResult.output = atOutput.value ? atOutput.value : null; + if (captureHighlightRequired) + scenarioResult.highlightRequired = atOutput.highlightRequired; + scenarioResult.assertionResults = [...assertionResults]; + scenarioResult.unexpectedBehaviors = unexpectedBehaviors + ? [...unexpectedBehaviors] + : null; + if (captureHighlightRequired) + scenarioResult.unexpectedBehaviorHighlightRequired = highlightRequired; + + newScenarioResults.push(scenarioResult); } - const toggleTestNavigator = () => setShowTestNavigator(!showTestNavigator); - - const createTestResultForRenderer = async ( - testId, - atVersionId, - browserVersionId - ) => { - const result = await createTestResult({ - variables: { - testPlanRunId, - testId, - atVersionId, - browserVersionId - } - }); - return result.data.testPlanRun.findOrCreateTestResult; - }; - - // Check to see if there are tests to run - const hasTestsToRun = tests.length; - - // Check if this test is being run as an admin - if ( - adminReviewerOriginalTestRef.current && - adminReviewerOriginalTestRef.current !== currentTest.id - ) - adminReviewerCheckedRef.current = false; + return newScenarioResults; + }; + const remapState = (rendererState, testResult) => { if ( - isAdminReviewer && - currentTest.testResult && - !adminReviewerCheckedRef.current + !rendererState || + !testResult.scenarioResults || + rendererState.commands.length !== testResult.scenarioResults.length ) - adminReviewerOriginalTestRef.current = currentTest; - - adminReviewerCheckedRef.current = true; - - let issueLink; - const hasLoadingCompleted = Object.keys(currentTest).length; - if (hasLoadingCompleted) { - issueLink = createIssueLink({ - testPlanTitle: testPlanVersion.title, - testPlanDirectory: testPlanVersion.testPlan.directory, - versionString: `V${convertDateToString( - testPlanVersion.updatedAt, - 'YY.MM.DD' - )}`, - testTitle: currentTest.title, - testRowNumber: currentTest.rowNumber, - testSequenceNumber: currentTest.seq, - testRenderedUrl: currentTest.renderedUrl, - atName: testPlanReport.at.name, - browserName: testPlanReport.browser.name, - atVersionName: currentAtVersion?.name, - browserVersionName: currentBrowserVersion?.name, - conflictMarkdown: conflictMarkdownRef.current - }); - } + return testResult; - const remapScenarioResults = ( - rendererState, - scenarioResults, - captureHighlightRequired = false + const scenarioResults = remapScenarioResults( + rendererState, + testResult.scenarioResults, + true + ); + return { ...testResult, scenarioResults }; + }; + + const performButtonAction = async (action, index) => { + // TODO: Revise function + const saveForm = async ( + withResult = false, + forceSave = false, + forceEdit = false ) => { - let newScenarioResults = []; - if (!rendererState || !scenarioResults) { - throw new Error( - `Unable to merge invalid results:rendererState:${rendererState} | scenarioResults:${scenarioResults}` - ); - } - - const { commands } = rendererState; - if (!commands || commands.length !== scenarioResults.length) { - throw new Error( - `Unable to merge invalid results:commands:${commands} | commands.length !== scenarioResults.length:${ - commands.length !== scenarioResults.length - }` - ); - } - - const UnexpectedBehaviorsArray = [ - 'EXCESSIVELY_VERBOSE', - 'UNEXPECTED_CURSOR_POSITION', - 'SLUGGISH', - 'AT_CRASHED', - 'BROWSER_CRASHED', - 'OTHER' - ]; - - for (let i = 0; i < commands.length; i++) { - let scenarioResult = { ...scenarioResults[i] }; - let assertionResults = []; - let unexpectedBehaviors = null; - - // collect variables - const { atOutput, assertions, unexpected } = commands[i]; - - // process assertion results - for (let j = 0; j < assertions.length; j++) { - const { description, result, highlightRequired } = - assertions[j]; - const assertionResult = { - ...scenarioResult.assertionResults.find( - ({ assertion: { text } }) => text === description - ), - passed: result === 'pass' - }; - assertionResults.push( - captureHighlightRequired - ? { ...assertionResult, highlightRequired } - : assertionResult - ); - } + if (updateMessageComponent) { + setUpdateMessageComponent(null); + } + try { + if (forceEdit) setIsTestEditClicked(true); + else setIsTestEditClicked(false); - // process unexpected behaviors - const { hasUnexpected, behaviors, highlightRequired } = unexpected; - if (hasUnexpected === 'hasUnexpected') { - unexpectedBehaviors = []; - /** - * 0 = EXCESSIVELY_VERBOSE - * 1 = UNEXPECTED_CURSOR_POSITION - * 2 = SLUGGISH - * 3 = AT_CRASHED - * 4 = BROWSER_CRASHED - * 5 = OTHER - */ - for (let i = 0; i < behaviors.length; i++) { - const behavior = behaviors[i]; - if (behavior.checked) { - unexpectedBehaviors.push({ - id: UnexpectedBehaviorsArray[i], - text: behavior.description, - details: behavior.more.value, - impact: behavior.impact.toUpperCase(), - highlightRequired: captureHighlightRequired - ? behavior.more.highlightRequired - : false - }); - } - } - } else if (hasUnexpected === 'doesNotHaveUnexpected') - unexpectedBehaviors = []; - - // re-assign scenario result due to read only values - scenarioResult.output = atOutput.value ? atOutput.value : null; - if (captureHighlightRequired) - scenarioResult.highlightRequired = atOutput.highlightRequired; - scenarioResult.assertionResults = [...assertionResults]; - scenarioResult.unexpectedBehaviors = unexpectedBehaviors - ? [...unexpectedBehaviors] - : null; - if (captureHighlightRequired) - scenarioResult.unexpectedBehaviorHighlightRequired = - highlightRequired; - - newScenarioResults.push(scenarioResult); - } - - return newScenarioResults; - }; - - const remapState = (rendererState, testResult) => { - if ( - !rendererState || - !testResult.scenarioResults || - rendererState.commands.length !== testResult.scenarioResults.length - ) - return testResult; + if (!isSignedIn) return true; + if (!forceEdit && currentTest.testResult?.completedAt) return true; + setIsSavingForm(true); const scenarioResults = remapScenarioResults( - rendererState, - testResult.scenarioResults, - true + testRunStateRef.current || recentTestRunStateRef.current, + currentTest.testResult?.scenarioResults, + false ); - return { ...testResult, scenarioResults }; - }; - const performButtonAction = async (action, index) => { - // TODO: Revise function - const saveForm = async ( - withResult = false, - forceSave = false, - forceEdit = false - ) => { - if (updateMessageComponent) { - setUpdateMessageComponent(null); - } - try { - if (forceEdit) setIsTestEditClicked(true); - else setIsTestEditClicked(false); - - if (!isSignedIn) return true; - if (!forceEdit && currentTest.testResult?.completedAt) - return true; - - setIsSavingForm(true); - const scenarioResults = remapScenarioResults( - testRunStateRef.current || recentTestRunStateRef.current, - currentTest.testResult?.scenarioResults, - false - ); - - await handleSaveOrSubmitTestResultAction( - { - atVersionId: currentTestAtVersionId, - browserVersionId: currentTestBrowserVersionId, - scenarioResults - }, - forceSave ? false : !!testRunResultRef.current - ); - - if (withResult && !forceSave) { - setIsSavingForm(false); - return !!testRunResultRef.current; - } - - setIsSavingForm(false); - return true; - } catch (e) { - console.error('save.error', e); - setIsSavingForm(false); - } - }; + await handleSaveOrSubmitTestResultAction( + { + atVersionId: currentTestAtVersionId, + browserVersionId: currentTestBrowserVersionId, + scenarioResults + }, + forceSave ? false : !!testRunResultRef.current + ); - switch (action) { - case 'goToTestAtIndex': { - // Save renderer's form state - await saveForm(false, true); - setCurrentTestIndex(index); - break; - } - case 'goToNextTest': { - // Save renderer's form state - await saveForm(false, true); - navigateTests(false, currentTest, tests, setCurrentTestIndex); - break; - } - case 'goToPreviousTest': { - // Save renderer's form state - await saveForm(false, true); - navigateTests(true, currentTest, tests, setCurrentTestIndex); - break; - } - case 'editTest': { - testRunResultRef.current = null; - await saveForm(false, true, true); - if (titleRef.current) titleRef.current.focus(); - break; - } - case 'saveTest': { - if (!isSignedIn) { - setShowGetInvolvedModal(true); - break; - } - if (testRendererSubmitButtonRef.current) { - testRendererSubmitButtonRef.current.click(); - setIsTestSubmitClicked(true); - - // check to see if form was successfully submitted, if so, return to top of summary document - const forceFocusOnSave = await saveForm(true); - if (forceFocusOnSave) - if (titleRef.current) titleRef.current.focus(); - } - break; - } - case 'closeTest': { - // Save renderer's form state - await saveForm(); - navigate('/test-queue'); - break; - } + if (withResult && !forceSave) { + setIsSavingForm(false); + return !!testRunResultRef.current; } + + setIsSavingForm(false); + return true; + } catch (e) { + console.error('save.error', e); + setIsSavingForm(false); + } }; - const handleTestClick = async index => - await performButtonAction('goToTestAtIndex', index); + switch (action) { + case 'goToTestAtIndex': { + // Save renderer's form state + await saveForm(false, true); + setCurrentTestIndex(index); + break; + } + case 'goToNextTest': { + // Save renderer's form state + await saveForm(false, true); + navigateTests(false, currentTest, tests, setCurrentTestIndex); + break; + } + case 'goToPreviousTest': { + // Save renderer's form state + await saveForm(false, true); + navigateTests(true, currentTest, tests, setCurrentTestIndex); + break; + } + case 'editTest': { + testRunResultRef.current = null; + await saveForm(false, true, true); + if (titleRef.current) titleRef.current.focus(); + break; + } + case 'saveTest': { + if (!isSignedIn) { + setShowGetInvolvedModal(true); + break; + } + if (testRendererSubmitButtonRef.current) { + testRendererSubmitButtonRef.current.click(); + setIsTestSubmitClicked(true); - const handleSaveClick = async () => performButtonAction('saveTest'); + // check to see if form was successfully submitted, if so, return to top of summary document + const forceFocusOnSave = await saveForm(true); + if (forceFocusOnSave) if (titleRef.current) titleRef.current.focus(); + } + break; + } + case 'closeTest': { + // Save renderer's form state + await saveForm(); + navigate('/test-queue'); + break; + } + } + }; - const handleNextTestClick = async () => performButtonAction('goToNextTest'); + const handleTestClick = async index => + await performButtonAction('goToTestAtIndex', index); - const handlePreviousTestClick = async () => - performButtonAction('goToPreviousTest'); + const handleSaveClick = async () => performButtonAction('saveTest'); - const handleCloseRunClick = async () => performButtonAction('closeTest'); + const handleNextTestClick = async () => performButtonAction('goToNextTest'); - const handleEditResultsClick = async () => performButtonAction('editTest'); + const handlePreviousTestClick = async () => + performButtonAction('goToPreviousTest'); - const handleStartOverButtonClick = async () => setShowStartOverModal(true); + const handleCloseRunClick = async () => performButtonAction('closeTest'); - const handleStartOverAction = async () => { - const { id } = currentTest.testResult; - let variables = { - id - }; - await deleteTestResult({ variables }); + const handleEditResultsClick = async () => performButtonAction('editTest'); - reset(); - setPageReady(false); - const { testPlanRun, testPlanReport } = - await createTestResultForRenderer( - currentTest.id, - currentTestAtVersionId, - currentTestBrowserVersionId - ); - updateLocalState(testPlanRun, testPlanReport); - setPageReady(true); + const handleStartOverButtonClick = async () => setShowStartOverModal(true); - // close modal after action - setShowStartOverModal(false); + const handleStartOverAction = async () => { + const { id } = currentTest.testResult; + let variables = { + id }; - - const handleSaveOrSubmitTestResultAction = async ( - { atVersionId, browserVersionId, scenarioResults = [] }, - isSubmit = false - ) => { - const { id } = currentTest.testResult; - - /* - * The shape of scenarioResults should be: - * - * { - * ..id, - * ..output, - * ..assertionResults: [ - * ....{ - * ......id - * ......passed - * ....}, - * ....other assertionResults, - * ..], - * ..unexpectedBehaviors: [ - * ....{ - * ......id - * ......impact - * ......details - * ....}, - * ....other unexpectedBehaviors, - * ..] - * } - * */ - const formattedScenarioResults = scenarioResults.map( - ({ assertionResults, id, output, unexpectedBehaviors }) => ({ - id, - output: output, - unexpectedBehaviors: unexpectedBehaviors?.map( - ({ id, impact, details }) => ({ - id, - impact, - details - }) - ), - assertionResults: assertionResults - // All assertions are always being passed from the TestRenderer results, but - // when there is a 0-priority assertion exception, an id won't be provided, - // so do not include that result. - // This is due to the TestRenderer still requiring the position of the - // excluded assertion, but it can be removed at this point before being passed - // to the server - .filter(el => !!el.id) - .map(({ id, passed }) => ({ - id, - passed - })) - }) - ); - - let variables = { + await deleteTestResult({ variables }); + + reset(); + setPageReady(false); + const { testPlanRun, testPlanReport } = await createTestResultForRenderer( + currentTest.id, + currentTestAtVersionId, + currentTestBrowserVersionId + ); + updateLocalState(testPlanRun, testPlanReport); + setPageReady(true); + + // close modal after action + setShowStartOverModal(false); + }; + + const handleSaveOrSubmitTestResultAction = async ( + { atVersionId, browserVersionId, scenarioResults = [] }, + isSubmit = false + ) => { + const { id } = currentTest.testResult; + + /* + * The shape of scenarioResults should be: + * + * { + * ..id, + * ..output, + * ..assertionResults: [ + * ....{ + * ......id + * ......passed + * ....}, + * ....other assertionResults, + * ..], + * ..unexpectedBehaviors: [ + * ....{ + * ......id + * ......impact + * ......details + * ....}, + * ....other unexpectedBehaviors, + * ..] + * } + * */ + const formattedScenarioResults = scenarioResults.map( + ({ assertionResults, id, output, unexpectedBehaviors }) => ({ + id, + output: output, + unexpectedBehaviors: unexpectedBehaviors?.map( + ({ id, impact, details }) => ({ id, - atVersionId, - browserVersionId, - scenarioResults: formattedScenarioResults - }; + impact, + details + }) + ), + assertionResults: assertionResults + // All assertions are always being passed from the TestRenderer results, but + // when there is a 0-priority assertion exception, an id won't be provided, + // so do not include that result. + // This is due to the TestRenderer still requiring the position of the + // excluded assertion, but it can be removed at this point before being passed + // to the server + .filter(el => !!el.id) + .map(({ id, passed }) => ({ + id, + passed + })) + }) + ); - if (isSubmit) { - const result = await submitTestResult({ variables }); - const { testPlanRun, testPlanReport } = - result.data.testResult.submitTestResult; - updateLocalState(testPlanRun, testPlanReport); - } else { - const result = await saveTestResult({ variables }); - const { testPlanRun, testPlanReport } = - result.data.testResult.saveTestResult; - updateLocalState(testPlanRun, testPlanReport); - } + let variables = { + id, + atVersionId, + browserVersionId, + scenarioResults: formattedScenarioResults }; - const handleReviewConflictsButtonClick = async () => - setShowReviewConflictsModal(true); - - const handleEditAtBrowserDetailsClick = async () => { - setIsEditAtBrowserDetailsClicked(true); - - if (isAdminReviewer && adminReviewerOriginalTestRef.current) { - if (testPlanReport.browser.name !== uaBrowser) { - setThemedModalTitle( - 'Your Browser is different than the one used to record this result' - ); - setThemedModalContent( - <> - You are currently using{' '} - - {uaBrowser} {uaMajor} - - , but are trying to edit a test result that was - submitted with{' '} - - {testPlanReport.browser.name}{' '} - { - adminReviewerOriginalTestRef.current.testResult - .browserVersion.name - } - - .
    -
    - You can't change the Browser type but can make - other changes. Please proceed with caution. - - ); - setThemedModalOtherButton(null); - setShowThemedModal(true); - return; - } + if (isSubmit) { + const result = await submitTestResult({ variables }); + const { testPlanRun, testPlanReport } = + result.data.testResult.submitTestResult; + updateLocalState(testPlanRun, testPlanReport); + } else { + const result = await saveTestResult({ variables }); + const { testPlanRun, testPlanReport } = + result.data.testResult.saveTestResult; + updateLocalState(testPlanRun, testPlanReport); + } + }; - if ( - currentTest.testResult?.atVersion?.name !== - adminReviewerOriginalTestRef.current.testResult?.atVersion?.name - ) { - setThemedModalTitle( - 'Your AT Version is different than the one used to record this result' - ); - setThemedModalContent( - <> - You are currently running{' '} - - {testPlanReport.at.name}{' '} - {currentTest.testResult?.atVersion?.name} - - , but are editing a test result that was submitted with{' '} - - {testPlanReport.at.name}{' '} - { - adminReviewerOriginalTestRef.current.testResult - ?.atVersion?.name - } - - .
    -
    - Do you want to update the AT version used to record this - test result? - - ); - setThemedModalOtherButton({ - text: 'Update AT Version', - action: () => { - setShowThemedModal(false); - setIsShowingAtBrowserModal(true); - } - }); - setShowThemedModal(true); - return; - } + const handleReviewConflictsButtonClick = async () => + setShowReviewConflictsModal(true); - if ( - !adminReviewerOriginalTestRef.current.testResult?.browserVersion?.name.includes( - `${uaMajor}` - ) - ) { - setThemedModalTitle( - 'Your Browser Version is different than the one used to record this result' - ); - setThemedModalContent( - <> - You are currently using{' '} - - {uaBrowser} {uaMajor} - - , but are trying to edit a test result that was - submitted with{' '} - - {testPlanReport.browser.name}{' '} - { - adminReviewerOriginalTestRef.current.testResult - ?.browserVersion?.name - } - - .
    -
    - Do you want to update the Browser version used to record - this test result? - - ); - setThemedModalOtherButton({ - text: 'Update Browser Version', - action: () => { - setShowThemedModal(false); - setIsShowingAtBrowserModal(true); - } - }); - setShowThemedModal(true); - return; - } - } - setIsShowingAtBrowserModal(true); - }; + const handleEditAtBrowserDetailsClick = async () => { + setIsEditAtBrowserDetailsClicked(true); - const handleAtAndBrowserDetailsModalAction = async ( - updatedAtVersionName, - updatedBrowserVersionName, - updateMessage - ) => { - // Get version id for selected atVersion and browserVersion from name - const atVersion = testPlanReport.at.atVersions.find( - item => item.name === updatedAtVersionName + if (isAdminReviewer && adminReviewerOriginalTestRef.current) { + if (testPlanReport.browser.name !== uaBrowser) { + setThemedModalTitle( + 'Your Browser is different than the one used to record this result' ); - - let browserVersion = testPlanReport.browser.browserVersions.find( - item => item.name === updatedBrowserVersionName + setThemedModalContent( + <> + You are currently using{' '} + + {uaBrowser} {uaMajor} + + , but are trying to edit a test result that was submitted with{' '} + + {testPlanReport.browser.name}{' '} + { + adminReviewerOriginalTestRef.current.testResult.browserVersion + .name + } + + .
    +
    + You can't change the Browser type but can make other changes. + Please proceed with caution. + ); - - // create version if not exists (accounting for admin providing new versions) - if (!browserVersion) { - const createBrowserVersionResult = await createBrowserVersion({ - variables: { - browserId: testPlanReport.browser.id, - browserVersionName: updatedBrowserVersionName - } - }); - browserVersion = - createBrowserVersionResult.data?.browser - ?.findOrCreateBrowserVersion; - } - - // Only show major browser version - browserVersion = { - id: browserVersion.id, - name: browserVersion.name.split('.')[0] - }; - - const updateMessageComponent = updateMessage ? ( - <> - - {updateMessage} - - ) : null; - - setCurrentTestAtVersionId(atVersion.id); - setCurrentTestBrowserVersionId(browserVersion.id); - setCurrentAtVersion(atVersion); - setCurrentBrowserVersion(browserVersion); - setUpdateMessageComponent(updateMessageComponent); - - const { testPlanRun: _testPlanRun, testPlanReport: _testPlanReport } = - await createTestResultForRenderer( - currentTest.id, - atVersion.id, - browserVersion.id - ); - updateLocalState(_testPlanRun, _testPlanReport); - handleAtAndBrowserDetailsModalCloseAction(); - }; - - const handleAtAndBrowserDetailsModalCloseAction = () => { - setIsShowingAtBrowserModal(false); - if (isEditAtBrowserDetailsModalClick) - editAtBrowserDetailsButtonRef.current.focus(); - }; - - const onThemedModalClose = () => { - setShowThemedModal(false); - editAtBrowserDetailsButtonRef.current.focus(); - }; - - const renderTestsCompletedInfoBox = () => { - let isReviewingBot = false; - if (openAsUserId) { - isReviewingBot = openAsUser.isBot; - } - - let content; - - if (isReviewingBot) { - content = ( - <> - {`${testResults.reduce( - (acc, { scenarioResults }) => - acc + - (scenarioResults && - scenarioResults.every(({ output }) => !!output) - ? 1 - : 0), - 0 - )} of ${tests.length}`}{' '} - responses collected. - - ); - } else if (!isSignedIn) { - content = {tests.length} tests to view; - } else if (hasTestsToRun) { - content = ( - <> - {' '} - {`${testResults.reduce( - (acc, { completedAt }) => acc + (completedAt ? 1 : 0), - 0 - )} of ${tests.length}`}{' '} - tests completed - - ); - } else { - content =
    No tests for this AT and Browser combination
    ; - } - return ( -
    -
    - - {content} -
    -
    + setThemedModalOtherButton(null); + setShowThemedModal(true); + return; + } + + if ( + currentTest.testResult?.atVersion?.name !== + adminReviewerOriginalTestRef.current.testResult?.atVersion?.name + ) { + setThemedModalTitle( + 'Your AT Version is different than the one used to record this result' ); - }; - - const renderTestContent = (testPlanReport, currentTest, heading) => { - const { index } = currentTest; - const isComplete = currentTest.testResult - ? !!currentTest.testResult.completedAt - : false; - const isFirstTest = index === 0; - const isLastTest = currentTest.seq === tests.length; - - let primaryButtons; // These are the list of buttons that will appear below the tests - let forwardButtons = []; // These are buttons that navigate to next tests and continue - - const nextButton = ( - + setThemedModalContent( + <> + You are currently running{' '} + + {testPlanReport.at.name} {currentTest.testResult?.atVersion?.name} + + , but are editing a test result that was submitted with{' '} + + {testPlanReport.at.name}{' '} + {adminReviewerOriginalTestRef.current.testResult?.atVersion?.name} + + .
    +
    + Do you want to update the AT version used to record this test + result? + ); + setThemedModalOtherButton({ + text: 'Update AT Version', + action: () => { + setShowThemedModal(false); + setIsShowingAtBrowserModal(true); + } + }); + setShowThemedModal(true); + return; + } - const previousButton = ( - + if ( + !adminReviewerOriginalTestRef.current.testResult?.browserVersion?.name.includes( + `${uaMajor}` + ) + ) { + setThemedModalTitle( + 'Your Browser Version is different than the one used to record this result' ); + setThemedModalContent( + <> + You are currently using{' '} + + {uaBrowser} {uaMajor} + + , but are trying to edit a test result that was submitted with{' '} + + {testPlanReport.browser.name}{' '} + { + adminReviewerOriginalTestRef.current.testResult?.browserVersion + ?.name + } + + .
    +
    + Do you want to update the Browser version used to record this test + result? + + ); + setThemedModalOtherButton({ + text: 'Update Browser Version', + action: () => { + setShowThemedModal(false); + setIsShowingAtBrowserModal(true); + } + }); + setShowThemedModal(true); + return; + } + } + setIsShowingAtBrowserModal(true); + }; + + const handleAtAndBrowserDetailsModalAction = async ( + updatedAtVersionName, + updatedBrowserVersionName, + updateMessage + ) => { + // Get version id for selected atVersion and browserVersion from name + const atVersion = testPlanReport.at.atVersions.find( + item => item.name === updatedAtVersionName + ); - if (isComplete) { - const editButton = ( - - ); - - const continueButton = ( - - ); + let browserVersion = testPlanReport.browser.browserVersions.find( + item => item.name === updatedBrowserVersionName + ); - if (!isLastTest) forwardButtons = [nextButton]; - primaryButtons = [ - previousButton, - editButton, - ...forwardButtons, - continueButton - ]; - } else { - // same key to maintain focus - const saveResultsButton = ( - - ); - if (!isLastTest) forwardButtons = [nextButton]; - primaryButtons = [ - previousButton, - ...forwardButtons, - saveResultsButton - ]; + // create version if not exists (accounting for admin providing new versions) + if (!browserVersion) { + const createBrowserVersionResult = await createBrowserVersion({ + variables: { + browserId: testPlanReport.browser.id, + browserVersionName: updatedBrowserVersionName } + }); + browserVersion = + createBrowserVersionResult.data?.browser?.findOrCreateBrowserVersion; + } - const externalLogsUrl = - collectionJobQuery?.collectionJobByTestPlanRunId?.externalLogsUrl; - - const menuRightOfContent = ( -
    -

    Test Options

    -
      -
    • - - } - target="_blank" - href={issueLink} - /> -
    • - {openAsUser?.isBot && externalLogsUrl ? ( -
    • - -
    • - ) : ( -
    • - - } - onClick={handleStartOverButtonClick} - disabled={!isSignedIn} - /> -
    • - )} - -
    • - -
    • -
    • - - Email us if you need help - -
    • -
    -
    - ); - - return ( - <> -

    - Test {currentTest.seq}: - {currentTest.title} -

    - {heading} - - {pageReady && (isSignedIn ? currentTest.testResult : true) && ( - - - - - - {isRendererReady && ( - -

    - Test Controls -

    -
      - {primaryButtons.map(button => ( -
    • {button}
    • - ))} -
    -
    - )} - - - {menuRightOfContent} - -
    - )} - - - - - {/* Modals */} - {showStartOverModal && ( - setShowStartOverModal(false)} - /> - )} - {showGetInvolvedModal && ( - - Only members of the ARIA-AT test team can submit - data. If you fill in this form, your data will - not be saved! Check out the{' '} - home page to learn more about - how to get involved. - - } - closeLabel="Close" - handleClose={() => setShowGetInvolvedModal(false)} - /> - )} - {showReviewConflictsModal && ( - setShowReviewConflictsModal(false)} - /> - )} - - ); + // Only show major browser version + browserVersion = { + id: browserVersion.id, + name: browserVersion.name.split('.')[0] }; - let heading; - let content; - let openAsUserHeading = null; - - if (openAsUserId) { - if (openAsUser.isBot) { - openAsUserHeading = ( -
    - Reviewing tests of{' '} - {' '} - {`${openAsUser.username}`}. -
    - ); - } else { - openAsUserHeading = ( -
    - Reviewing tests of {`${openAsUser.username}`}. -

    {`All changes will be saved as performed by ${openAsUser.username}.`}

    -
    - ); - } - } + const updateMessageComponent = updateMessage ? ( + <> + + {updateMessage} + + ) : null; + + setCurrentTestAtVersionId(atVersion.id); + setCurrentTestBrowserVersionId(browserVersion.id); + setCurrentAtVersion(atVersion); + setCurrentBrowserVersion(browserVersion); + setUpdateMessageComponent(updateMessageComponent); + + const { testPlanRun: _testPlanRun, testPlanReport: _testPlanReport } = + await createTestResultForRenderer( + currentTest.id, + atVersion.id, + browserVersion.id + ); + updateLocalState(_testPlanRun, _testPlanReport); + handleAtAndBrowserDetailsModalCloseAction(); + }; + + const handleAtAndBrowserDetailsModalCloseAction = () => { + setIsShowingAtBrowserModal(false); + if (isEditAtBrowserDetailsModalClick) + editAtBrowserDetailsButtonRef.current.focus(); + }; + + const onThemedModalClose = () => { + setShowThemedModal(false); + editAtBrowserDetailsButtonRef.current.focus(); + }; + + const renderTestContent = (testPlanReport, currentTest, heading) => { + const { index } = currentTest; + const isComplete = currentTest.testResult + ? !!currentTest.testResult.completedAt + : false; + const isFirstTest = index === 0; + const isLastTest = currentTest.seq === tests.length; + + let primaryButtons; // These are the list of buttons that will appear below the tests + let forwardButtons = []; // These are buttons that navigate to next tests and continue + + const nextButton = ( + + ); - heading = pageReady && ( - <> -
    -
    -
    - Test Plan:{' '} - {`${ - testPlanVersion.title || - testPlanVersion.testPlan?.directory || - '' - }`} -
    -
    -
    -
    -
    - AT:{' '} - {`${testPlanReport.at?.name}${ - isSignedIn ? ` ${currentAtVersion?.name}` : '' - }`} -
    -
    - Browser:{' '} - {`${testPlanReport.browser?.name}${ - isSignedIn - ? ` ${currentBrowserVersion?.name || ''}` - : '' - }`} -
    -
    - {isSignedIn && ( - - )} -
    - {renderTestsCompletedInfoBox()} -
    - {openAsUserHeading} - + const previousButton = ( + ); - if (!isSignedIn || !testPlanRun?.isComplete) { - content = hasTestsToRun ? ( - renderTestContent(testPlanReport, currentTest, heading) - ) : ( - // No tests loaded - <> - {heading} -
    No tests for this At and Browser combination
    - - ); + if (isComplete) { + const editButton = ( + + ); + + const continueButton = ( + + ); + + if (!isLastTest) forwardButtons = [nextButton]; + primaryButtons = [ + previousButton, + editButton, + ...forwardButtons, + continueButton + ]; } else { - content = ( -
    - {heading} - - - Thanks! Your results - have been submitted. Please return to the{' '} - Test Queue. - - -
    - ); + // same key to maintain focus + const saveResultsButton = ( + + ); + if (!isLastTest) forwardButtons = [nextButton]; + primaryButtons = [previousButton, ...forwardButtons, saveResultsButton]; } + const externalLogsUrl = testPlanRun?.collectionJob?.externalLogsUrl; + + const menuRightOfContent = ( +
    +

    Test Options

    +
      +
    • + + } + target="_blank" + href={issueLink} + /> +
    • + {openAsUser?.isBot && externalLogsUrl ? ( +
    • + +
    • + ) : ( +
    • + } + onClick={handleStartOverButtonClick} + disabled={!isSignedIn} + /> +
    • + )} + +
    • + +
    • +
    • + Email us if you need help +
    • +
    +
    + ); + + // we are ready enough to show the page and all the buttons when the above code is + // pageReady and we have an anon view, bot test run, or a test result to display + const completeRender = pageReady && (!isSignedIn || currentTest.testResult); + return ( - pageReady && ( - - - - {hasTestsToRun - ? `${currentTest.title} for ${testPlanReport.at?.name} ${currentAtVersion?.name} and ${testPlanReport.browser?.name} ${currentBrowserVersion?.name} ` + - `| ARIA-AT` - : 'No tests for this AT and Browser | ARIA-AT'} - - - {updateMessageComponent && ( - - {updateMessageComponent} - - )} + <> +

    + Test {currentTest.seq}: + {currentTest.title} +

    + {heading} + + {completeRender && ( + + + + + + {isRendererReady && ( - - - - {content} - - +

    + Test Controls +

    +
      + {primaryButtons.map(button => ( +
    • {button}
    • + ))} +
    - {showThemedModal && ( - - )} - {isSignedIn && isShowingAtBrowserModal && ( - { - // Only provide at version options that released - // at the same time or later than the minimum - // AT version - let earliestReleasedAt = null; - if (testPlanReport.minimumAtVersion) { - earliestReleasedAt = new Date( - testPlanReport.minimumAtVersion.releasedAt - ); - return ( - new Date(item.releasedAt) >= - earliestReleasedAt - ); - } - return item; - }) - .map(item => item.name)} - browserName={testPlanReport.browser.name} - browserVersion={ - currentTest.testResult?.browserVersion?.name - } - browserVersions={testPlanReport.browser.browserVersions.map( - item => item.name - )} - patternName={testPlanVersion.title} - testerName={tester.username} - exactAtVersion={testPlanReport.exactAtVersion} - handleAction={handleAtAndBrowserDetailsModalAction} - handleClose={handleAtAndBrowserDetailsModalCloseAction} - /> - )} -
    - ) + )} + + + {menuRightOfContent} + + + )} + + + + + {/* Modals */} + {showStartOverModal && ( + setShowStartOverModal(false)} + /> + )} + {showGetInvolvedModal && ( + + Only members of the ARIA-AT test team can submit data. If you + fill in this form, your data will not be saved! Check out the{' '} + home page to learn more about how to get + involved. + + } + closeLabel="Close" + handleClose={() => setShowGetInvolvedModal(false)} + /> + )} + {showReviewConflictsModal && ( + setShowReviewConflictsModal(false)} + /> + )} + + ); + }; + + let heading; + let content; + + heading = pageReady && ( + + ); + + if (!isSignedIn || !testPlanRun?.isComplete) { + content = testCount ? ( + renderTestContent(testPlanReport, currentTest, heading) + ) : ( + // No tests loaded + <> + {heading} +
    No tests for this At and Browser combination
    + ); + } else { + content = ( +
    + {heading} + + + Thanks! Your results have been + submitted. Please return to the{' '} + Test Queue. + + +
    + ); + } + + return ( + pageReady && ( + + + + + {testCount + ? `${currentTest.title} for ${testPlanReport.at?.name} ${currentAtVersion?.name} and ${testPlanReport.browser?.name} ${currentBrowserVersion?.name} ` + + `| ARIA-AT` + : 'No tests for this AT and Browser | ARIA-AT'} + + + {updateMessageComponent && ( + + {updateMessageComponent} + + )} + + + + + {content} + + + + {showThemedModal && ( + + )} + {isSignedIn && isShowingAtBrowserModal && ( + { + // Only provide at version options that released + // at the same time or later than the minimum + // AT version + let earliestReleasedAt = null; + if (testPlanReport.minimumAtVersion) { + earliestReleasedAt = new Date( + testPlanReport.minimumAtVersion.releasedAt + ); + return new Date(item.releasedAt) >= earliestReleasedAt; + } + return item; + }) + .map(item => item.name)} + exactAtVersion={testPlanReport.exactAtVersion} + browserName={testPlanReport.browser.name} + browserVersion={currentTest.testResult?.browserVersion?.name} + browserVersions={testPlanReport.browser.browserVersions.map( + item => item.name + )} + patternName={testPlanVersion.title} + testerName={tester.username} + handleAction={handleAtAndBrowserDetailsModalAction} + handleClose={handleAtAndBrowserDetailsModalCloseAction} + /> + )} + + + ) + ); }; export default TestRun; diff --git a/client/components/TestRun/queries.js b/client/components/TestRun/queries.js index aed6ca3e1..1499f6f87 100644 --- a/client/components/TestRun/queries.js +++ b/client/components/TestRun/queries.js @@ -1,1269 +1,1268 @@ import { gql } from '@apollo/client'; export const TEST_RUN_PAGE_QUERY = gql` - query TestPlanRunPage($testPlanRunId: ID!) { - testPlanRun(id: $testPlanRunId) { + query TestPlanRunPage($testPlanRunId: ID!) { + testPlanRun(id: $testPlanRunId) { + id + initiatedByAutomation + collectionJob { + id + status + externalLogsUrl + testStatus { + test { id - initiatedByAutomation - tester { - id + } + status + } + } + tester { + id + username + isBot + } + testResults { + id + startedAt + completedAt + test { + id + rowNumber + title + renderedUrl + renderableContent + } + scenarioResults { + id + scenario { + commands { + id + text + } + } + output + assertionResults { + id + assertion { + text + phrase + } + passed + } + mustAssertionResults: assertionResults(priority: MUST) { + assertion { + text + phrase + } + passed + } + shouldAssertionResults: assertionResults(priority: SHOULD) { + assertion { + text + phrase + } + passed + } + mayAssertionResults: assertionResults(priority: MAY) { + assertion { + text + phrase + } + passed + } + unexpectedBehaviors { + id + text + impact + details + } + } + atVersion { + id + name + } + browserVersion { + id + name + } + } + testPlanReport { + id + conflicts { + source { + test { + id + title + rowNumber + } + scenario { + id + commands { + text + } + } + assertion { + id + text + phrase + } + } + conflictingResults { + testPlanRun { + id + tester { username - isBot + } + } + scenarioResult { + output + unexpectedBehaviors { + text + impact + details + } + } + assertionResult { + passed + } + } + } + at { + id + name + atVersions { + id + name + releasedAt + } + } + minimumAtVersion { + id + name + releasedAt + } + exactAtVersion { + id + name + } + browser { + id + name + browserVersions { + id + name + } + } + testPlanVersion { + id + title + phase + updatedAt + gitSha + testPageUrl + testPlan { + directory + } + metadata + } + runnableTests { + id + rowNumber + title + ats { + id + name + } + renderedUrl + scenarios { + id + at { + id + name + } + commands { + id + text + } + } + assertions { + id + priority + text + } + } + } + } + me { + id + username + roles + } + users { + id + username + isBot + } + } +`; + +export const COLLECTION_JOB_UPDATES_QUERY = gql` + query CollectionJob($collectionJobId: ID!) { + collectionJob(id: $collectionJobId) { + id + status + externalLogsUrl + testStatus { + test { + id + } + status + } + } + } +`; + +export const TEST_RUN_PAGE_ANON_QUERY = gql` + query TestPlanRunAnonPage($testPlanReportId: ID!) { + testPlanReport(id: $testPlanReportId) { + id + conflicts { + source { + test { + id + title + rowNumber + } + scenario { + id + commands { + text + } + } + assertion { + id + text + phrase + } + } + conflictingResults { + testPlanRun { + id + tester { + username + isBot + } + } + scenarioResult { + output + unexpectedBehaviors { + text + impact + details + } + } + assertionResult { + passed + } + } + } + at { + id + name + atVersions { + id + name + } + } + browser { + id + name + browserVersions { + id + name + } + } + testPlanVersion { + id + title + phase + updatedAt + gitSha + testPageUrl + testPlan { + directory + } + metadata + } + runnableTests { + id + rowNumber + title + ats { + id + name + } + renderedUrl + renderableContent + scenarios { + id + at { + id + name + } + commands { + id + text + } + } + assertions { + id + priority + text + } + } + } + } +`; + +export const FIND_OR_CREATE_TEST_RESULT_MUTATION = gql` + mutation FindOrCreateTestResult( + $testPlanRunId: ID! + $testId: ID! + $atVersionId: ID! + $browserVersionId: ID! + ) { + testPlanRun(id: $testPlanRunId) { + findOrCreateTestResult( + testId: $testId + atVersionId: $atVersionId + browserVersionId: $browserVersionId + ) { + locationOfData + testPlanRun { + id + tester { + id + username + isBot + } + testResults { + id + startedAt + completedAt + test { + id + rowNumber + title + renderedUrl + renderableContent } - testResults { + scenarioResults { + id + scenario { + commands { + id + text + } + } + output + assertionResults { id - startedAt - completedAt - test { - id - rowNumber - title - renderedUrl - renderableContent + assertion { + text + phrase } - scenarioResults { - id - scenario { - commands { - id - text - } - } - output - assertionResults { - id - assertion { - text - phrase - } - passed - } - mustAssertionResults: assertionResults(priority: MUST) { - assertion { - text - phrase - } - passed - } - shouldAssertionResults: assertionResults(priority: SHOULD) { - assertion { - text - phrase - } - passed - } - mayAssertionResults: assertionResults(priority: MAY) { - assertion { - text - phrase - } - passed - } - unexpectedBehaviors { - id - text - impact - details - } + passed + } + mustAssertionResults: assertionResults(priority: MUST) { + assertion { + text + phrase } - atVersion { - id - name + passed + } + shouldAssertionResults: assertionResults(priority: SHOULD) { + assertion { + text + phrase } - browserVersion { - id - name + passed + } + mayAssertionResults: assertionResults(priority: MAY) { + assertion { + text + phrase } - } - testPlanReport { + passed + } + unexpectedBehaviors { id - conflicts { - source { - test { - id - title - rowNumber - } - scenario { - id - commands { - text - } - } - assertion { - id - text - phrase - } - } - conflictingResults { - testPlanRun { - id - tester { - username - } - } - scenarioResult { - output - unexpectedBehaviors { - text - impact - details - } - } - assertionResult { - passed - } - } + text + impact + details + } + } + atVersion { + id + name + } + browserVersion { + id + name + } + } + testPlanReport { + id + conflicts { + source { + test { + id + title + rowNumber } - at { - id - name - atVersions { - id - name - releasedAt - } + scenario { + id + commands { + text + } } - minimumAtVersion { - id - name - releasedAt + assertion { + id + text + phrase } - exactAtVersion { - id - name + } + conflictingResults { + testPlanRun { + id + tester { + username + } } - browser { - id - name - browserVersions { - id - name - } + scenarioResult { + output + unexpectedBehaviors { + text + impact + details + } } - testPlanVersion { - id - title - phase - updatedAt - gitSha - testPageUrl - testPlan { - directory - } - metadata + assertionResult { + passed } - runnableTests { - id - rowNumber - title - ats { - id - name - } - renderedUrl - scenarios { - id - at { - id - name - } - commands { - id - text - } - } - assertions { - id - priority - text - } + } + } + at { + id + name + atVersions { + id + name + releasedAt + } + } + minimumAtVersion { + id + name + releasedAt + } + exactAtVersion { + id + name + } + browser { + id + name + browserVersions { + id + name + } + } + testPlanVersion { + id + title + phase + updatedAt + gitSha + testPageUrl + testPlan { + directory + } + metadata + } + runnableTests { + id + rowNumber + title + ats { + id + name + } + renderedUrl + scenarios { + id + at { + id + name } + commands { + id + text + } + } + assertions { + id + priority + text + } } + } } - me { + testPlanReport { + id + conflicts { + source { + test { + id + title + rowNumber + } + scenario { + id + commands { + text + } + } + assertion { + id + text + phrase + } + } + conflictingResults { + testPlanRun { + id + tester { + isBot + username + } + } + scenarioResult { + output + unexpectedBehaviors { + text + impact + details + } + } + assertionResult { + passed + } + } + } + at { id - username - roles - } - users { + name + atVersions { + id + name + releasedAt + } + } + minimumAtVersion { id - username - isBot + name + releasedAt + } + exactAtVersion { + id + name + } + browser { + id + name + browserVersions { + id + name + } + } + testPlanVersion { + id + title + phase + updatedAt + gitSha + testPageUrl + testPlan { + directory + } + metadata + } + runnableTests { + id + rowNumber + title + ats { + id + name + } + renderedUrl + scenarios { + id + at { + id + name + } + commands { + id + text + } + } + assertions { + id + priority + text + } + } } + } } + } `; -export const TEST_RUN_PAGE_ANON_QUERY = gql` - query TestPlanRunAnonPage($testPlanReportId: ID!) { - testPlanReport(id: $testPlanReportId) { +export const SAVE_TEST_RESULT_MUTATION = gql` + mutation SaveTestResult( + $id: ID! + $atVersionId: ID! + $browserVersionId: ID! + $scenarioResults: [ScenarioResultInput]! + ) { + testResult(id: $id) { + saveTestResult( + input: { + id: $id + atVersionId: $atVersionId + browserVersionId: $browserVersionId + scenarioResults: $scenarioResults + } + ) { + locationOfData + testPlanRun { + id + tester { + id + username + isBot + } + testResults { + id + startedAt + completedAt + test { + id + rowNumber + title + renderedUrl + renderableContent + } + scenarioResults { + id + scenario { + commands { + id + text + } + } + output + assertionResults { + id + assertion { + text + phrase + } + passed + } + mustAssertionResults: assertionResults(priority: MUST) { + assertion { + text + phrase + } + passed + } + shouldAssertionResults: assertionResults(priority: SHOULD) { + assertion { + text + phrase + } + passed + } + mayAssertionResults: assertionResults(priority: MAY) { + assertion { + text + phrase + } + passed + } + unexpectedBehaviors { + id + text + impact + details + } + } + atVersion { + id + name + } + browserVersion { + id + name + } + } + testPlanReport { id conflicts { - source { - test { - id - title - rowNumber - } - scenario { - id - commands { - text - } - } - assertion { - id - text - phrase - } + source { + test { + id + title + rowNumber + } + scenario { + id + commands { + text + } } - conflictingResults { - testPlanRun { - id - tester { - username - isBot - } - } - scenarioResult { - output - unexpectedBehaviors { - text - impact - details - } - } - assertionResult { - passed - } + assertion { + id + text + phrase + } + } + conflictingResults { + testPlanRun { + id + tester { + username + } } + scenarioResult { + output + unexpectedBehaviors { + text + impact + details + } + } + assertionResult { + passed + } + } } at { + id + name + atVersions { id name - atVersions { - id - name - } + releasedAt + } + } + minimumAtVersion { + id + name + releasedAt + } + exactAtVersion { + id + name } browser { + id + name + browserVersions { id name - browserVersions { - id - name - } + } } testPlanVersion { + id + title + phase + updatedAt + gitSha + testPageUrl + testPlan { + directory + } + metadata + } + runnableTests { + id + rowNumber + title + ats { id - title - phase - updatedAt - gitSha - testPageUrl - testPlan { - directory + name + } + renderedUrl + scenarios { + id + at { + id + name } - metadata + commands { + id + text + } + } + assertions { + id + priority + text + } } - runnableTests { + } + } + testPlanReport { + id + conflicts { + source { + test { id - rowNumber title - ats { - id - name + rowNumber + } + scenario { + id + commands { + text } - renderedUrl - renderableContent - scenarios { - id - at { - id - name - } - commands { - id - text - } + } + assertion { + id + text + phrase + } + } + conflictingResults { + testPlanRun { + id + tester { + username } - assertions { - id - priority - text + } + scenarioResult { + output + unexpectedBehaviors { + text + impact + details } + } + assertionResult { + passed + } + } + } + at { + id + name + atVersions { + id + name + releasedAt + } + } + minimumAtVersion { + id + name + releasedAt + } + exactAtVersion { + id + name + } + browser { + id + name + browserVersions { + id + name + } + } + testPlanVersion { + id + title + phase + updatedAt + gitSha + testPageUrl + testPlan { + directory + } + metadata + } + runnableTests { + id + rowNumber + title + ats { + id + name + } + renderedUrl + scenarios { + id + at { + id + name + } + commands { + id + text + } + } + assertions { + id + priority + text } + } } + } } + } `; -export const FIND_OR_CREATE_TEST_RESULT_MUTATION = gql` - mutation FindOrCreateTestResult( - $testPlanRunId: ID! - $testId: ID! - $atVersionId: ID! - $browserVersionId: ID! - ) { - testPlanRun(id: $testPlanRunId) { - findOrCreateTestResult( - testId: $testId - atVersionId: $atVersionId - browserVersionId: $browserVersionId - ) { - locationOfData - testPlanRun { - id - tester { - id - username - isBot - } - testResults { - id - startedAt - completedAt - test { - id - rowNumber - title - renderedUrl - renderableContent - } - scenarioResults { - id - scenario { - commands { - id - text - } - } - output - assertionResults { - id - assertion { - text - phrase - } - passed - } - mustAssertionResults: assertionResults( - priority: MUST - ) { - assertion { - text - phrase - } - passed - } - shouldAssertionResults: assertionResults( - priority: SHOULD - ) { - assertion { - text - phrase - } - passed - } - mayAssertionResults: assertionResults( - priority: MAY - ) { - assertion { - text - phrase - } - passed - } - unexpectedBehaviors { - id - text - impact - details - } - } - atVersion { - id - name - } - browserVersion { - id - name - } - } - testPlanReport { - id - conflicts { - source { - test { - id - title - rowNumber - } - scenario { - id - commands { - text - } - } - assertion { - id - text - phrase - } - } - conflictingResults { - testPlanRun { - id - tester { - username - } - } - scenarioResult { - output - unexpectedBehaviors { - text - impact - details - } - } - assertionResult { - passed - } - } - } - at { - id - name - atVersions { - id - name - releasedAt - } - } - minimumAtVersion { - id - name - releasedAt - } - exactAtVersion { - id - name - } - browser { - id - name - browserVersions { - id - name - } - } - testPlanVersion { - id - title - phase - updatedAt - gitSha - testPageUrl - testPlan { - directory - } - metadata - } - runnableTests { - id - rowNumber - title - ats { - id - name - } - renderedUrl - scenarios { - id - at { - id - name - } - commands { - id - text - } - } - assertions { - id - priority - text - } - } - } +export const SUBMIT_TEST_RESULT_MUTATION = gql` + mutation SubmitTestResult( + $id: ID! + $atVersionId: ID! + $browserVersionId: ID! + $scenarioResults: [ScenarioResultInput]! + ) { + testResult(id: $id) { + submitTestResult( + input: { + id: $id + atVersionId: $atVersionId + browserVersionId: $browserVersionId + scenarioResults: $scenarioResults + } + ) { + locationOfData + testPlanRun { + id + tester { + id + username + } + testResults { + id + startedAt + completedAt + test { + id + rowNumber + title + renderedUrl + renderableContent + } + scenarioResults { + id + scenario { + commands { + id + text } - testPlanReport { - id - conflicts { - source { - test { - id - title - rowNumber - } - scenario { - id - commands { - text - } - } - assertion { - id - text - phrase - } - } - conflictingResults { - testPlanRun { - id - tester { - isBot - username - } - } - scenarioResult { - output - unexpectedBehaviors { - text - impact - details - } - } - assertionResult { - passed - } - } - } - at { - id - name - atVersions { - id - name - releasedAt - } - } - minimumAtVersion { - id - name - releasedAt - } - exactAtVersion { - id - name - } - browser { - id - name - browserVersions { - id - name - } - } - testPlanVersion { - id - title - phase - updatedAt - gitSha - testPageUrl - testPlan { - directory - } - metadata - } - runnableTests { - id - rowNumber - title - ats { - id - name - } - renderedUrl - scenarios { - id - at { - id - name - } - commands { - id - text - } - } - assertions { - id - priority - text - } - } + } + output + assertionResults { + id + assertion { + text + phrase } + passed + } + mustAssertionResults: assertionResults(priority: MUST) { + assertion { + text + phrase + } + passed + } + shouldAssertionResults: assertionResults(priority: SHOULD) { + assertion { + text + phrase + } + passed + } + mayAssertionResults: assertionResults(priority: MAY) { + assertion { + text + phrase + } + passed + } + unexpectedBehaviors { + id + text + impact + details + } } - } - } -`; - -export const SAVE_TEST_RESULT_MUTATION = gql` - mutation SaveTestResult( - $id: ID! - $atVersionId: ID! - $browserVersionId: ID! - $scenarioResults: [ScenarioResultInput]! - ) { - testResult(id: $id) { - saveTestResult( - input: { - id: $id - atVersionId: $atVersionId - browserVersionId: $browserVersionId - scenarioResults: $scenarioResults + atVersion { + id + name + } + browserVersion { + id + name + } + } + testPlanReport { + id + conflicts { + source { + test { + id + title + rowNumber + } + scenario { + id + commands { + text + } + } + assertion { + id + text + phrase } - ) { - locationOfData + } + conflictingResults { testPlanRun { - id - tester { - id - username - isBot - } - testResults { - id - startedAt - completedAt - test { - id - rowNumber - title - renderedUrl - renderableContent - } - scenarioResults { - id - scenario { - commands { - id - text - } - } - output - assertionResults { - id - assertion { - text - phrase - } - passed - } - mustAssertionResults: assertionResults( - priority: MUST - ) { - assertion { - text - phrase - } - passed - } - shouldAssertionResults: assertionResults( - priority: SHOULD - ) { - assertion { - text - phrase - } - passed - } - mayAssertionResults: assertionResults( - priority: MAY - ) { - assertion { - text - phrase - } - passed - } - unexpectedBehaviors { - id - text - impact - details - } - } - atVersion { - id - name - } - browserVersion { - id - name - } - } - testPlanReport { - id - conflicts { - source { - test { - id - title - rowNumber - } - scenario { - id - commands { - text - } - } - assertion { - id - text - phrase - } - } - conflictingResults { - testPlanRun { - id - tester { - username - } - } - scenarioResult { - output - unexpectedBehaviors { - text - impact - details - } - } - assertionResult { - passed - } - } - } - at { - id - name - atVersions { - id - name - releasedAt - } - } - minimumAtVersion { - id - name - releasedAt - } - exactAtVersion { - id - name - } - browser { - id - name - browserVersions { - id - name - } - } - testPlanVersion { - id - title - phase - updatedAt - gitSha - testPageUrl - testPlan { - directory - } - metadata - } - runnableTests { - id - rowNumber - title - ats { - id - name - } - renderedUrl - scenarios { - id - at { - id - name - } - commands { - id - text - } - } - assertions { - id - priority - text - } - } - } + id + tester { + username + } } - testPlanReport { - id - conflicts { - source { - test { - id - title - rowNumber - } - scenario { - id - commands { - text - } - } - assertion { - id - text - phrase - } - } - conflictingResults { - testPlanRun { - id - tester { - username - } - } - scenarioResult { - output - unexpectedBehaviors { - text - impact - details - } - } - assertionResult { - passed - } - } - } - at { - id - name - atVersions { - id - name - releasedAt - } - } - minimumAtVersion { - id - name - releasedAt - } - exactAtVersion { - id - name - } - browser { - id - name - browserVersions { - id - name - } - } - testPlanVersion { - id - title - phase - updatedAt - gitSha - testPageUrl - testPlan { - directory - } - metadata - } - runnableTests { - id - rowNumber - title - ats { - id - name - } - renderedUrl - scenarios { - id - at { - id - name - } - commands { - id - text - } - } - assertions { - id - priority - text - } - } + scenarioResult { + output + unexpectedBehaviors { + text + impact + details + } + } + assertionResult { + passed } + } } + at { + id + name + atVersions { + id + name + releasedAt + } + } + minimumAtVersion { + id + name + releasedAt + } + exactAtVersion { + id + name + } + browser { + id + name + browserVersions { + id + name + } + } + testPlanVersion { + id + title + phase + updatedAt + gitSha + testPageUrl + testPlan { + directory + } + metadata + } + runnableTests { + id + rowNumber + title + ats { + id + name + } + renderedUrl + scenarios { + id + at { + id + name + } + commands { + id + text + } + } + assertions { + id + priority + text + } + } + } } - } -`; - -export const SUBMIT_TEST_RESULT_MUTATION = gql` - mutation SubmitTestResult( - $id: ID! - $atVersionId: ID! - $browserVersionId: ID! - $scenarioResults: [ScenarioResultInput]! - ) { - testResult(id: $id) { - submitTestResult( - input: { - id: $id - atVersionId: $atVersionId - browserVersionId: $browserVersionId - scenarioResults: $scenarioResults + testPlanReport { + id + conflicts { + source { + test { + id + title + rowNumber + } + scenario { + id + commands { + text } - ) { - locationOfData - testPlanRun { - id - tester { - id - username - } - testResults { - id - startedAt - completedAt - test { - id - rowNumber - title - renderedUrl - renderableContent - } - scenarioResults { - id - scenario { - commands { - id - text - } - } - output - assertionResults { - id - assertion { - text - phrase - } - passed - } - mustAssertionResults: assertionResults( - priority: MUST - ) { - assertion { - text - phrase - } - passed - } - shouldAssertionResults: assertionResults( - priority: SHOULD - ) { - assertion { - text - phrase - } - passed - } - mayAssertionResults: assertionResults( - priority: MAY - ) { - assertion { - text - phrase - } - passed - } - unexpectedBehaviors { - id - text - impact - details - } - } - atVersion { - id - name - } - browserVersion { - id - name - } - } - testPlanReport { - id - conflicts { - source { - test { - id - title - rowNumber - } - scenario { - id - commands { - text - } - } - assertion { - id - text - phrase - } - } - conflictingResults { - testPlanRun { - id - tester { - username - } - } - scenarioResult { - output - unexpectedBehaviors { - text - impact - details - } - } - assertionResult { - passed - } - } - } - at { - id - name - atVersions { - id - name - releasedAt - } - } - minimumAtVersion { - id - name - releasedAt - } - exactAtVersion { - id - name - } - browser { - id - name - browserVersions { - id - name - } - } - testPlanVersion { - id - title - phase - updatedAt - gitSha - testPageUrl - testPlan { - directory - } - metadata - } - runnableTests { - id - rowNumber - title - ats { - id - name - } - renderedUrl - scenarios { - id - at { - id - name - } - commands { - id - text - } - } - assertions { - id - priority - text - } - } - } + } + assertion { + id + text + phrase + } + } + conflictingResults { + testPlanRun { + id + tester { + username } - testPlanReport { - id - conflicts { - source { - test { - id - title - rowNumber - } - scenario { - id - commands { - text - } - } - assertion { - id - text - phrase - } - } - conflictingResults { - testPlanRun { - id - tester { - username - } - } - scenarioResult { - output - unexpectedBehaviors { - text - impact - details - } - } - assertionResult { - passed - } - } - } - at { - id - name - atVersions { - id - name - releasedAt - } - } - minimumAtVersion { - id - name - releasedAt - } - exactAtVersion { - id - name - } - browser { - id - name - browserVersions { - id - name - } - } - testPlanVersion { - id - title - phase - updatedAt - gitSha - testPageUrl - testPlan { - directory - } - metadata - } - runnableTests { - id - rowNumber - title - ats { - id - name - } - renderedUrl - scenarios { - id - at { - id - name - } - commands { - id - text - } - } - assertions { - id - priority - text - } - } + } + scenarioResult { + output + unexpectedBehaviors { + text + impact + details } + } + assertionResult { + passed + } } + } + at { + id + name + atVersions { + id + name + releasedAt + } + } + minimumAtVersion { + id + name + releasedAt + } + exactAtVersion { + id + name + } + browser { + id + name + browserVersions { + id + name + } + } + testPlanVersion { + id + title + phase + updatedAt + gitSha + testPageUrl + testPlan { + directory + } + metadata + } + runnableTests { + id + rowNumber + title + ats { + id + name + } + renderedUrl + scenarios { + id + at { + id + name + } + commands { + id + text + } + } + assertions { + id + priority + text + } + } } + } } + } `; export const DELETE_TEST_RESULT_MUTATION = gql` - mutation DeleteTestResult($id: ID!) { - testResult(id: $id) { - deleteTestResult { - locationOfData - } - } + mutation DeleteTestResult($id: ID!) { + testResult(id: $id) { + deleteTestResult { + locationOfData + } } + } `; export const FIND_OR_CREATE_BROWSER_VERSION_MUTATION = gql` - mutation FindOrCreateBrowserVersion( - $browserId: ID! - $browserVersionName: String! - ) { - browser(id: $browserId) { - findOrCreateBrowserVersion(input: { name: $browserVersionName }) { - id - name - } - } - } -`; - -export const COLLECTION_JOB_STATUS_BY_TEST_PLAN_RUN_ID_QUERY = gql` - query CollectionJobIdByTestPlanRunId($testPlanRunId: ID!) { - collectionJobByTestPlanRunId(testPlanRunId: $testPlanRunId) { - id - status - externalLogsUrl - } + mutation FindOrCreateBrowserVersion( + $browserId: ID! + $browserVersionName: String! + ) { + browser(id: $browserId) { + findOrCreateBrowserVersion(input: { name: $browserVersionName }) { + id + name + } } + } `; diff --git a/client/components/UserSettings/UserSettings.jsx b/client/components/UserSettings/UserSettings.jsx index f28e34a60..3a8f99d22 100644 --- a/client/components/UserSettings/UserSettings.jsx +++ b/client/components/UserSettings/UserSettings.jsx @@ -6,151 +6,137 @@ import PageStatus from '../common/PageStatus'; import { CURRENT_SETTINGS_QUERY, UPDATE_ME_MUTATION } from './queries'; const UserSettings = () => { - const { loading, data, error } = useQuery(CURRENT_SETTINGS_QUERY); + const { loading, data, error } = useQuery(CURRENT_SETTINGS_QUERY); - const [updateMe] = useMutation(UPDATE_ME_MUTATION, { - refetchQueries: [{ query: CURRENT_SETTINGS_QUERY }] - }); + const [updateMe] = useMutation(UPDATE_ME_MUTATION, { + refetchQueries: [{ query: CURRENT_SETTINGS_QUERY }] + }); - const [checkedAts, setCheckedAts] = useState(undefined); + const [checkedAts, setCheckedAts] = useState(undefined); - const handleCheckedAt = useCallback( - event => { - const atId = event.target.id; - const isChecked = checkedAts.includes(atId); - if (isChecked) { - setCheckedAts(checkedAts.filter(item => item !== atId)); - } else { - setCheckedAts([...checkedAts, atId]); - } - }, - [checkedAts] - ); + const handleCheckedAt = useCallback( + event => { + const atId = event.target.id; + const isChecked = checkedAts.includes(atId); + if (isChecked) { + setCheckedAts(checkedAts.filter(item => item !== atId)); + } else { + setCheckedAts([...checkedAts, atId]); + } + }, + [checkedAts] + ); - const handleSave = useCallback( - event => { - event.preventDefault(); - updateMe({ variables: { input: { atIds: checkedAts } } }); - }, - [checkedAts] - ); + const handleSave = useCallback( + event => { + event.preventDefault(); + updateMe({ variables: { input: { atIds: checkedAts } } }); + }, + [checkedAts] + ); - useEffect(() => { - if (!data) return; - setCheckedAts(data.me.ats.map(at => at.id)); - }, [data]); + useEffect(() => { + if (!data) return; + setCheckedAts(data.me.ats.map(at => at.id)); + }, [data]); - if (error) { - return ( - - ); - } + if (error) { + return ( + + ); + } - if (loading) { - return ( - - ); - } + if (loading) { + return ( + + ); + } - if (!data || !checkedAts) return null; + if (!data || !checkedAts) return null; - const { - ats, - me: { username, ats: userAtsData } - } = data; - const savedAts = userAtsData.map(at => at.id); + const { + ats, + me: { username, ats: userAtsData } + } = data; + const savedAts = userAtsData.map(at => at.id); - return ( - - - - Settings | ARIA-AT - -

    Settings

    -
    -

    User Details

    -

    - - {username} - -

    -

    Assistive Technology Settings

    -
    - {savedAts.length > 0 ? ( -
    -

    - You can currently test the following - assistive technologies: -

    -
      - {ats - .filter(({ id: atId }) => - savedAts.includes(atId) - ) - .map(at => ( -
    • {at.name}
    • - ))} -
    -
    - ) : ( -

    - You currently are not configured to run any - assistive technologies. -

    - )} -
    -

    - Submit the form below to update the assistive - technologies you can test: -

    -
    -

    ATs

    - - {ats && - ats.map(at => { - return ( - atId === at.id - ) - } - /> - ); - })} - - -
    -
    -
    -
    - ); + return ( + + + + Settings | ARIA-AT + +

    Settings

    +
    +

    User Details

    +

    + + {username} + +

    +

    Assistive Technology Settings

    +
    + {savedAts.length > 0 ? ( +
    +

    + You can currently test the following assistive technologies: +

    +
      + {ats + .filter(({ id: atId }) => savedAts.includes(atId)) + .map(at => ( +
    • {at.name}
    • + ))} +
    +
    + ) : ( +

    + You currently are not configured to run any assistive + technologies. +

    + )} +
    +

    + Submit the form below to update the assistive technologies you can + test: +

    +
    +

    ATs

    + + {ats && + ats.map(at => { + return ( + atId === at.id)} + /> + ); + })} + + +
    +
    +
    +
    + ); }; export default UserSettings; diff --git a/client/components/UserSettings/queries.js b/client/components/UserSettings/queries.js index a04a00da9..552a47c2d 100644 --- a/client/components/UserSettings/queries.js +++ b/client/components/UserSettings/queries.js @@ -1,27 +1,27 @@ import { gql } from '@apollo/client'; export const CURRENT_SETTINGS_QUERY = gql` - query CurrentSettings { - me { - id - username - ats { - id - } - } - ats { - id - name - } + query CurrentSettings { + me { + id + username + ats { + id + } } + ats { + id + name + } + } `; export const UPDATE_ME_MUTATION = gql` - mutation UpdateMe($input: UserInput) { - updateMe(input: $input) { - ats { - id - } - } + mutation UpdateMe($input: UserInput) { + updateMe(input: $input) { + ats { + id + } } + } `; diff --git a/client/components/common/AssignTesterDropdown/AssignTesterDropdown.css b/client/components/common/AssignTesterDropdown/AssignTesterDropdown.css new file mode 100644 index 000000000..e1ba49d3d --- /dev/null +++ b/client/components/common/AssignTesterDropdown/AssignTesterDropdown.css @@ -0,0 +1,34 @@ +.assignees { + margin-top: 0.3em; + font-size: 0.9rem; + text-align: center; +} + +.assign-tester { + border-radius: 25px; + height: 2.5em; + border-style: dashed; + padding: 0.4em 0.6em; +} + +.assign-tester .fa-user-plus { + margin: 0; +} + +.assign-tester.dropdown-toggle:after { + display: none; +} + +.no-assignees { + text-align: center; + line-height: 2; +} + +.not-assigned.human { + padding-left: 1.5em; +} + +[role='menu'].assign-menu { + max-height: 200px; + overflow-y: scroll; +} diff --git a/client/components/common/AssignTesterDropdown/index.jsx b/client/components/common/AssignTesterDropdown/index.jsx new file mode 100644 index 000000000..f576549fd --- /dev/null +++ b/client/components/common/AssignTesterDropdown/index.jsx @@ -0,0 +1,233 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Dropdown } from 'react-bootstrap'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { + faCheck, + faChevronDown, + faRobot, + faUserPlus +} from '@fortawesome/free-solid-svg-icons'; +import { + ASSIGN_TESTER_MUTATION, + REMOVE_TESTER_MUTATION, + TEST_PLAN_REPORT_AT_BROWSER_QUERY +} from './queries'; +import { useMutation, useQuery } from '@apollo/client'; +import { LoadingStatus, useTriggerLoad } from '../LoadingStatus'; +import { SCHEDULE_COLLECTION_JOB_MUTATION } from '../../AddTestToQueueWithConfirmation/queries'; +import { isSupportedByResponseCollector } from '../../../utils/automation'; + +import './AssignTesterDropdown.css'; + +const AssignTesterDropdown = ({ + testPlanReportId, + testPlanRun, + draftTestPlanRuns, + possibleTesters, + onChange, + label, + dropdownAssignTesterButtonRef, + setAlertMessage = () => {} +}) => { + const { triggerLoad, loadingMessage } = useTriggerLoad(); + + const { data: testPlanReportAtBrowserQuery } = useQuery( + TEST_PLAN_REPORT_AT_BROWSER_QUERY, + { + variables: { + testPlanReportId: testPlanReportId + }, + fetchPolicy: 'cache-and-network' + } + ); + + const [removeTester] = useMutation(REMOVE_TESTER_MUTATION); + const [assignTester] = useMutation(ASSIGN_TESTER_MUTATION); + const [scheduleCollection] = useMutation(SCHEDULE_COLLECTION_JOB_MUTATION); + + const isTesterAssigned = username => { + if (testPlanRun) { + return testPlanRun.tester?.username === username; + } else if (draftTestPlanRuns?.length) { + return draftTestPlanRuns.some( + testPlanRun => testPlanRun.tester?.username === username + ); + } else { + return false; + } + }; + + const toggleTesterAssign = async username => { + const testerIsAssigned = isTesterAssigned(username); + const tester = possibleTesters.find(tester => tester.username === username); + + if (testerIsAssigned) { + await triggerLoad(async () => { + await removeTester({ + variables: { + testReportId: testPlanReportId, + testerId: tester.id + } + }); + }, `Updating Test Plan Assignees. Deleting Test Plan Run for ${tester.username}`); + } else { + if (tester.isBot) { + await triggerLoad(async () => { + await scheduleCollection({ + variables: { + testPlanReportId: testPlanReportId + } + }); + }, 'Scheduling Collection Job'); + } else { + await triggerLoad(async () => { + await assignTester({ + variables: { + testReportId: testPlanReportId, + testerId: tester.id, + testPlanRunId: testPlanRun?.id // if we are assigning to an existing test plan run, pass the id + } + }); + }, 'Updating Test Plan Assignees'); + } + } + }; + + const renderLabel = () => { + if (label) { + return ( + + {label} + + ); + } else { + return ; + } + }; + const clearAriaLiveRegion = () => { + setAlertMessage(''); + }; + + const handleKeyDown = event => { + const { key } = event; + if (key.match(/[0-9a-zA-Z]/)) { + const container = event.target.closest('[role=menu]'); + const matchingMenuItem = Array.from(container.children).find(menuItem => { + return menuItem.innerText + .trim() + .toLowerCase() + .startsWith(key.toLowerCase()); + }); + + if (matchingMenuItem) { + matchingMenuItem.focus(); + } + } + }; + + return ( + + + + {renderLabel()} + + + {possibleTesters?.length ? ( + possibleTesters.map(tester => { + const { username, isBot, ats } = tester; + const testerIsAssigned = isTesterAssigned(username); + const classname = [ + testerIsAssigned ? 'assigned' : 'not-assigned', + isBot ? 'bot' : 'human' + ].join(' '); + let icon; + if (testerIsAssigned) { + icon = faCheck; + } else if (isBot) { + // if our bot doesn't have a link to the at - hide it from the list + if ( + !ats.find( + ({ id }) => + id === testPlanReportAtBrowserQuery?.testPlanReport.at.id + ) + ) { + return null; + } + + const supportedByBot = isSupportedByResponseCollector( + testPlanReportAtBrowserQuery?.testPlanReport + ); + if (!supportedByBot) { + return null; + } + icon = faRobot; + } + return ( + { + const updatedIsAssigned = !testerIsAssigned; + setAlertMessage( + `${username} ${ + updatedIsAssigned + ? 'now checked' + : `now unchecked. ${tester.username}'s test plan run has been deleted.` + }` + ); + setTimeout(clearAriaLiveRegion, 6000); + await toggleTesterAssign(username); + await onChange(); + }} + > + {icon && } + {`${tester.username}`} + + ); + }) + ) : ( + No testers to assign + )} + + + + ); +}; + +AssignTesterDropdown.propTypes = { + testPlanReportId: PropTypes.string.isRequired, + possibleTesters: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + username: PropTypes.string.isRequired, + isBot: PropTypes.bool.isRequired, + ats: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + key: PropTypes.string.isRequired + }) + ) + }) + ).isRequired, + onChange: PropTypes.func.isRequired, + testPlanRun: PropTypes.object, + label: PropTypes.string, + draftTestPlanRuns: PropTypes.array, + setAlertMessage: PropTypes.func, + dropdownAssignTesterButtonRef: PropTypes.object +}; + +export default AssignTesterDropdown; diff --git a/client/components/common/AssignTesterDropdown/queries.js b/client/components/common/AssignTesterDropdown/queries.js new file mode 100644 index 000000000..557cb9aeb --- /dev/null +++ b/client/components/common/AssignTesterDropdown/queries.js @@ -0,0 +1,60 @@ +import { gql } from '@apollo/client'; + +export const ASSIGN_TESTER_MUTATION = gql` + mutation AssignTester( + $testReportId: ID! + $testerId: ID! + $testPlanRunId: ID + ) { + testPlanReport(id: $testReportId) { + assignTester(userId: $testerId, testPlanRunId: $testPlanRunId) { + testPlanReport { + draftTestPlanRuns { + initiatedByAutomation + tester { + id + username + isBot + } + } + } + } + } + } +`; + +export const REMOVE_TESTER_MUTATION = gql` + mutation RemoveTester($testReportId: ID!, $testerId: ID!) { + testPlanReport(id: $testReportId) { + deleteTestPlanRun(userId: $testerId) { + testPlanReport { + draftTestPlanRuns { + tester { + id + username + isBot + } + } + } + } + } + } +`; + +export const TEST_PLAN_REPORT_AT_BROWSER_QUERY = gql` + query TestPlanReportAtBrowser($testPlanReportId: ID!) { + testPlanReport(id: $testPlanReportId) { + id + at { + id + key + name + } + browser { + id + key + name + } + } + } +`; diff --git a/client/components/common/AtAndBrowserDetailsModal/index.jsx b/client/components/common/AtAndBrowserDetailsModal/index.jsx index 11dd21c25..962034a37 100644 --- a/client/components/common/AtAndBrowserDetailsModal/index.jsx +++ b/client/components/common/AtAndBrowserDetailsModal/index.jsx @@ -5,685 +5,601 @@ import { Form, Alert } from 'react-bootstrap'; import styled from '@emotion/styled'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { - faInfoCircle, - faExclamationTriangle + faInfoCircle, + faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'; import { useDetectUa } from '../../../hooks/useDetectUa'; import BasicModal from '../BasicModal'; const ModalInnerSectionContainer = styled.div` - display: flex; - flex-direction: column; + display: flex; + flex-direction: column; `; const FieldsetRow = styled.fieldset` - display: grid; - grid-template-columns: 1fr 1fr; - grid-gap: 1rem; + display: grid; + grid-template-columns: 1fr 1fr; + grid-gap: 1rem; - legend { - float: inherit; - } + legend { + float: inherit; + } `; const ModalSubtitleStyle = styled.h2` - font-size: 0.8em; - margin: 0; - padding: 0; + font-size: 0.8em; + margin: 0; + padding: 0; `; const Required = styled.span` - color: #ce1b4c; + color: #ce1b4c; - :after { - content: '*'; - } + :after { + content: '*'; + } `; const AtAndBrowserDetailsModal = ({ - show = false, - isAdmin = false, - firstLoad = true, - atName = '', - atVersion = '', - atVersions = [], - browserName = '', - browserVersion = '', - patternName = '', // admin related prop - testerName = '', // admin related prop - exactAtVersion = null, - handleAction = () => {}, - handleClose = () => {} + show = false, + isAdmin = false, + firstLoad = true, + atName = '', + atVersion = '', + atVersions = [], + browserName = '', + browserVersion = '', + patternName = '', // admin related prop + testerName = '', // admin related prop + exactAtVersion = null, + handleAction = () => {}, + handleClose = () => {} }) => { - // Only take into account the major version - // of the provided browser version - if (browserVersion.includes('.')) { - browserVersion = browserVersion.split('.')[0]; + // Only take into account the major version + // of the provided browser version + if (browserVersion.includes('.')) { + browserVersion = browserVersion.split('.')[0]; + } + // Detect UA information + const { uaBrowser, uaMajor, uaMinor, uaPatch } = useDetectUa(); + + const navigate = useNavigate(); + + const updatedAtVersionDropdownRef = useRef(); + const updatedBrowserVersionTextRef = useRef(); + + const [showExitModal, setShowExitModal] = useState(false); + const [isFirstLoad, setIsFirstLoad] = useState(true); + const [updatedAtVersion, setUpdatedAtVersion] = useState( + exactAtVersion ? exactAtVersion.name : 'Select a Version' + ); + const [updatedBrowserVersion, setUpdatedBrowserVersion] = useState(''); + + const [isAtVersionError, setIsAtVersionError] = useState(false); + const [isBrowserVersionError, setIsBrowserVersionError] = useState(false); + + const [ + forceBrowserVersionUpdateMessage, + setForceBrowserVersionUpdateMessage + ] = useState(false); + const [browserVersionMismatchMessage, setBrowserVersionMismatchMessage] = + useState(false); + + useEffect(() => { + setIsFirstLoad(firstLoad); + if (!firstLoad) { + setUpdatedAtVersion(atVersion); + setUpdatedBrowserVersion(browserVersion); + } + if (uaMajor === '0') { + setUpdatedBrowserVersion(''); + updatedBrowserVersionTextRef.current.focus(); } - // Detect UA information - const { uaBrowser, uaMajor, uaMinor, uaPatch } = useDetectUa(); - - const navigate = useNavigate(); - - const updatedAtVersionDropdownRef = useRef(); - const updatedBrowserVersionTextRef = useRef(); - - const [showExitModal, setShowExitModal] = useState(false); - const [isFirstLoad, setIsFirstLoad] = useState(true); - const [updatedAtVersion, setUpdatedAtVersion] = useState( - exactAtVersion ? exactAtVersion.name : 'Select a Version' - ); - const [updatedBrowserVersion, setUpdatedBrowserVersion] = useState(''); - - const [isAtVersionError, setIsAtVersionError] = useState(false); - const [isBrowserVersionError, setIsBrowserVersionError] = useState(false); - - const [ - forceBrowserVersionUpdateMessage, - setForceBrowserVersionUpdateMessage - ] = useState(false); - const [browserVersionMismatchMessage, setBrowserVersionMismatchMessage] = - useState(false); - - useEffect(() => { - setIsFirstLoad(firstLoad); - if (!firstLoad) { - setUpdatedAtVersion(atVersion); - setUpdatedBrowserVersion(browserVersion); - } - if (uaMajor === '0') { - setUpdatedBrowserVersion(''); - updatedBrowserVersionTextRef.current.focus(); - } - const foundBrowserVersion = - uaBrowser === browserName && uaMajor !== '0' && uaMajor; - - if ( - // don't force browserVersion update with admin (unless first run) - (!isAdmin || (isAdmin && firstLoad)) && - // check that saved browserVersion is the same as detected - !browserVersion.includes(uaMajor) && - uaBrowser === browserName && - foundBrowserVersion - ) { - setForceBrowserVersionUpdateMessage(true); - setUpdatedBrowserVersion(foundBrowserVersion); - } - }, [uaBrowser, uaMajor, uaMinor, uaPatch]); - - useEffect(() => { - // check to support Tester Scenario 5 - if (uaMajor !== '0' && !updatedBrowserVersion.includes(`${uaMajor}`)) - setBrowserVersionMismatchMessage(true); - else setBrowserVersionMismatchMessage(!isAdmin && false); - }, [updatedBrowserVersion, uaMajor, uaMinor, uaPatch]); - - useEffect(() => { - const forceFocusOnBrowserVersion = - !isFirstLoad && !isAdmin && forceBrowserVersionUpdateMessage; - - if (forceFocusOnBrowserVersion) - updatedBrowserVersionTextRef.current.focus(); - }, [forceBrowserVersionUpdateMessage]); - - const handleAtVersionChange = e => { - const value = e.target.value; - setUpdatedAtVersion(value); - setIsAtVersionError(false); - }; - - const handleBrowserVersionChange = e => { - const value = e.target.value; - - setUpdatedBrowserVersion(value); - - // remove message once browser has been changed - !isAdmin && setForceBrowserVersionUpdateMessage(false); - setIsBrowserVersionError(false); - }; - - const onSubmit = () => { - // Passed action prop should account for AtVersion & BrowserVersion - const updatedAtVersionError = updatedAtVersion === 'Select a Version'; - const isBrowserVersionTextError = !updatedBrowserVersion; - - if (updatedAtVersionError || isBrowserVersionTextError) { - setIsAtVersionError(updatedAtVersionError); - updatedAtVersionDropdownRef.current.focus(); - - setIsBrowserVersionError(isBrowserVersionTextError); - if (!updatedAtVersionError) - updatedBrowserVersionTextRef.current.focus(); - return; - } + const foundBrowserVersion = + uaBrowser === browserName && uaMajor !== '0' && uaMajor; + + if ( + // don't force browserVersion update with admin (unless first run) + (!isAdmin || (isAdmin && firstLoad)) && + // check that saved browserVersion is the same as detected + !browserVersion.includes(uaMajor) && + uaBrowser === browserName && + foundBrowserVersion + ) { + setForceBrowserVersionUpdateMessage(true); + setUpdatedBrowserVersion(foundBrowserVersion); + } + }, [uaBrowser, uaMajor, uaMinor, uaPatch]); + + useEffect(() => { + // check to support Tester Scenario 5 + if (uaMajor !== '0' && !updatedBrowserVersion.includes(`${uaMajor}`)) + setBrowserVersionMismatchMessage(true); + else setBrowserVersionMismatchMessage(!isAdmin && false); + }, [updatedBrowserVersion, uaMajor, uaMinor, uaPatch]); + + useEffect(() => { + const forceFocusOnBrowserVersion = + !isFirstLoad && !isAdmin && forceBrowserVersionUpdateMessage; + + if (forceFocusOnBrowserVersion) + updatedBrowserVersionTextRef.current.focus(); + }, [forceBrowserVersionUpdateMessage]); + + const handleAtVersionChange = e => { + const value = e.target.value; + setUpdatedAtVersion(value); + setIsAtVersionError(false); + }; + + const handleBrowserVersionChange = e => { + const value = e.target.value; + + setUpdatedBrowserVersion(value); + + // remove message once browser has been changed + !isAdmin && setForceBrowserVersionUpdateMessage(false); + setIsBrowserVersionError(false); + }; + + const onSubmit = () => { + // Passed action prop should account for AtVersion & BrowserVersion + const updatedAtVersionError = updatedAtVersion === 'Select a Version'; + const isBrowserVersionTextError = !updatedBrowserVersion; + + if (updatedAtVersionError || isBrowserVersionTextError) { + setIsAtVersionError(updatedAtVersionError); + updatedAtVersionDropdownRef.current.focus(); + + setIsBrowserVersionError(isBrowserVersionTextError); + if (!updatedAtVersionError) updatedBrowserVersionTextRef.current.focus(); + return; + } - let updateMessage = null; - if (isFirstLoad) - updateMessage = - 'Your AT and Browser versions have been successfully saved.'; - else { - if ( - updatedAtVersion !== atVersion && - updatedBrowserVersion !== browserVersion - ) - updateMessage = ( - <> - Your version of {atName} has been updated to{' '} - {updatedAtVersion}. Your previous version was{' '} - {atVersion}. -
    - Your version of {browserName} has been updated to{' '} - {updatedBrowserVersion}. Your previous version - was {browserVersion}. - - ); - else if (updatedAtVersion !== atVersion) - updateMessage = ( - <> - Your version of {atName} has been updated to{' '} - {updatedAtVersion}. Your previous version was{' '} - {atVersion}. - - ); - else if (updatedBrowserVersion !== browserVersion) - updateMessage = ( - <> - Your version of {browserName} has been updated to{' '} - {updatedBrowserVersion}. Your previous version - was {browserVersion}. - - ); - } + let updateMessage = null; + if (isFirstLoad) + updateMessage = + 'Your AT and Browser versions have been successfully saved.'; + else { + if ( + updatedAtVersion !== atVersion && + updatedBrowserVersion !== browserVersion + ) + updateMessage = ( + <> + Your version of {atName} has been updated to{' '} + {updatedAtVersion}. Your previous version was{' '} + {atVersion}. +
    + Your version of {browserName} has been updated to{' '} + {updatedBrowserVersion}. Your previous version was{' '} + {browserVersion}. + + ); + else if (updatedAtVersion !== atVersion) + updateMessage = ( + <> + Your version of {atName} has been updated to{' '} + {updatedAtVersion}. Your previous version was{' '} + {atVersion}. + + ); + else if (updatedBrowserVersion !== browserVersion) + updateMessage = ( + <> + Your version of {browserName} has been updated to{' '} + {updatedBrowserVersion}. Your previous version was{' '} + {browserVersion}. + + ); + } - // Save full version even though we don't display it. - // Discard trailing zeros in found minor and patches. - // This also accounts for trailing numbers after the patch. - let fullVersion = ''; - let [majorVersion, minorVersion = '0', patchVersion = '0', ...rest] = - updatedBrowserVersion.split('.'); - - if (minorVersion === '0' && patchVersion === '0') { - fullVersion = `${majorVersion}`; - } else if (patchVersion === '0') { - fullVersion = `${majorVersion}.${minorVersion}`; - } else if (rest === undefined || rest.length === 0) { - fullVersion = `${majorVersion}.${minorVersion}.${patchVersion}`; - } else { - fullVersion = `${majorVersion}.${minorVersion}.${patchVersion}.${rest.join( - '.' - )}`; - } + // Save full version even though we don't display it. + // Discard trailing zeros in found minor and patches. + // This also accounts for trailing numbers after the patch. + let fullVersion = ''; + let [majorVersion, minorVersion = '0', patchVersion = '0', ...rest] = + updatedBrowserVersion.split('.'); + + if (minorVersion === '0' && patchVersion === '0') { + fullVersion = `${majorVersion}`; + } else if (patchVersion === '0') { + fullVersion = `${majorVersion}.${minorVersion}`; + } else if (rest === undefined || rest.length === 0) { + fullVersion = `${majorVersion}.${minorVersion}.${patchVersion}`; + } else { + fullVersion = `${majorVersion}.${minorVersion}.${patchVersion}.${rest.join( + '.' + )}`; + } - handleAction(updatedAtVersion, fullVersion, updateMessage); - }; - - const handleHide = () => setShowExitModal(true); - - // All scenarios here are based on - // https://github.com/w3c/aria-at-app/issues/406 & - // https://github.com/w3c/aria-at-app/issues/436 - return ( - <> - setShowExitModal(true); + + // All scenarios here are based on + // https://github.com/w3c/aria-at-app/issues/406 & + // https://github.com/w3c/aria-at-app/issues/436 + return ( + <> + + You will be taken back to the Test Queue. Any changes you + made to your Assistive Technology and Browser versions will not be + saved. + + } + actions={[ + { + label: 'Ok', + onClick: () => navigate('/test-queue') + } + ]} + closeLabel="Cancel" + handleClose={() => setShowExitModal(false)} + staticBackdrop={true} + /> + + {!showExitModal && ( + + {/* Admin Scenario 1 */} + {isAdmin && ( + + + + You are about to review {patternName} as{' '} + {testerName}. Your Assistive Technology and Browser + might be different. Please proceed with caution. + + + )} + Your Assistive Technology and Browser Details are the following. + Please make sure this information is still accurate. +
    +
    + + + + Assistive Technology Details + + + {/* Tester Scenario 6 */} + {!isFirstLoad && updatedAtVersion !== atVersion && ( + + + + You have selected {updatedAtVersion} for{' '} + {atName}. This version is different from the one + previously selected which was {atVersion}. +
    +
    + This change doesn't affect results that have already + been submitted for this plan. However, results you submit + during this session will be recorded with the versions + specified in this form. +
    +
    + )} + {exactAtVersion ? ( + + + + Results collected for this test plan require{' '} + + {atName} {updatedAtVersion} + + . By continuing, you confirm that your results are being + recorded using the specified version of the Assistive + Technology. + + + ) : null} + + Assistive Technology Name + + + + {exactAtVersion ? ( <> - You will be taken back to the Test Queue. Any - changes you made to your Assistive Technology and - Browser versions will not be saved. + Assistive Technology Version + - } - actions={[ - { - label: 'Ok', - onClick: () => navigate('/test-queue') - } - ]} - closeLabel="Cancel" - handleClose={() => setShowExitModal(false)} - staticBackdrop={true} - /> - - {!showExitModal && ( - - {/* Admin Scenario 1 */} - {isAdmin && ( - - - - You are about to review{' '} - {patternName} as{' '} - {testerName}. Your Assistive - Technology and Browser might be - different. Please proceed with caution. - - - )} - Your Assistive Technology and Browser Details are - the following. Please make sure this information is - still accurate. -
    -
    - - - - Assistive Technology Details - - - {/* Tester Scenario 6 */} - {!isFirstLoad && - updatedAtVersion !== atVersion && ( - - - - You have selected{' '} - {updatedAtVersion} for{' '} - {atName}. This version is - different from the one - previously selected which was{' '} - {atVersion}. -
    -
    - This change doesn't affect - results that have already been - submitted for this plan. - However, results you submit - during this session will be - recorded with the versions - specified in this form. -
    -
    - )} - {exactAtVersion ? ( - - - - Results collected for this test plan - require{' '} - - {atName} {updatedAtVersion} - - . By continuing, you confirm that - your results are being recorded - using the specified version of the - Assistive Technology. - - - ) : null} - - - Assistive Technology Name - - - - - {exactAtVersion ? ( - <> - - Assistive Technology Version - - - - ) : ( - <> - - Assistive Technology Version - - - - {[ - 'Select a Version', - ...atVersions - ].map(item => ( - - ))} - - {isAtVersionError && ( - - Please select an Assistive - Technology Version. - - )} - - )} - -
    - - - - Browser Details - - - {/* Tester Scenario 1 */} - {isFirstLoad && - uaBrowser && - uaMajor !== '0' && ( - - - - We have automatically detected - your version of {uaBrowser} - - - )} - {/* Tester Scenario 3 */} - {!isFirstLoad && - !isAdmin && - forceBrowserVersionUpdateMessage && ( - - - - We have automatically detected - you are using a different - version of {uaBrowser}{' '} - and we have updated it below. - The previous version you were - using was{' '} - {browserVersion}. -
    -
    - This change doesn't affect - results that have already been - submitted for this plan. - However, results you submit - during this session will be - recorded with the versions - specified in this form. -
    -
    - )} - {isFirstLoad && - uaBrowser !== browserName && - uaMajor !== '0' && ( - - - - We have automatically detected - you are using{' '} - - {uaBrowser} {uaMajor} - - . This test plan requires{' '} - {browserName}. If you are - recording results on behalf of - someone else, please provide the - Browser version below. - - - )} - {/* Tester Scenario 4 */} - {!isAdmin && - !isFirstLoad && - uaBrowser !== browserName && - uaMajor !== '0' && ( - - - - We have automatically detected - you are now using{' '} - - {uaBrowser} {uaMajor} - - , which is a different browser - from the last one you were - testing with, which was{' '} - - {browserName}{' '} - {browserVersion} - - . -
    -
    - You can't edit your Browser - type, but you can continue with{' '} - - {uaBrowser} {uaMajor} - - . Keep in mind that your test - results will be recorded as if - you were still using{' '} - - {browserName}{' '} - {browserVersion} - - . -
    -
    - )} - {/* Tester Scenario 5 */} - {!isAdmin && - uaBrowser === browserName && - browserVersionMismatchMessage && ( - - - - The version of {browserName} you - have set is different from the - one we have automatically - detected, which is{' '} - {uaMajor} - . -
    -
    - This change doesn't affect - results that have already been - submitted for this plan. - However, results you submit - during this session will be - recorded with the versions - specified in this form. -
    -
    - )} - {/* Tester Scenario 7 */} - {uaMajor === '0' && ( - - - - We could not automatically detect - what version of {browserName}{' '} - you are using. Before continuing, - please provide your version number. - - - )} - {/* Admin Scenario 1 */} - {isAdmin && - (uaBrowser !== browserName || - browserVersionMismatchMessage) && ( - - - - We have automatically detected - you are using{' '} - - {uaBrowser} {uaMajor} - - . This version is different than - what {testerName} was - using last time, which was{' '} - - {browserName}{' '} - {browserVersion} - - . -
    -
    - We have not updated this - information below. Please - proceed with caution. -
    -
    - )} - - Browser Name - - - - - - Browser Version - - - - {isBrowserVersionError && ( - - Please enter a valid Browser - Version. - - )} - -
    - - } - actions={[ - { - label: - updatedAtVersion !== atVersion || - updatedBrowserVersion !== browserVersion - ? 'Save and Continue' - : 'Continue', - onClick: - updatedAtVersion !== atVersion || - updatedBrowserVersion !== browserVersion - ? onSubmit - : handleClose - } - ]} - handleClose={!isFirstLoad ? handleClose : null} - handleHide={handleHide} - staticBackdrop={true} - useOnHide={true} - /> - )} - - ); + ) : ( + <> + + Assistive Technology Version + + + + {['Select a Version', ...atVersions].map(item => ( + + ))} + + {isAtVersionError && ( + + Please select an Assistive Technology Version. + + )} + + )} +
    +
    + + + Browser Details + + {/* Tester Scenario 1 */} + {isFirstLoad && uaBrowser && uaMajor !== '0' && ( + + + + We have automatically detected your version of {uaBrowser} + + + )} + {/* Tester Scenario 3 */} + {!isFirstLoad && + !isAdmin && + forceBrowserVersionUpdateMessage && ( + + + + We have automatically detected you are using a different + version of {uaBrowser} and we have updated it + below. The previous version you were using was{' '} + {browserVersion}. +
    +
    + This change doesn't affect results that have + already been submitted for this plan. However, results + you submit during this session will be recorded with the + versions specified in this form. +
    +
    + )} + {isFirstLoad && + uaBrowser !== browserName && + uaMajor !== '0' && ( + + + + We have automatically detected you are using{' '} + + {uaBrowser} {uaMajor} + + . This test plan requires {browserName}. If you + are recording results on behalf of someone else, please + provide the Browser version below. + + + )} + {/* Tester Scenario 4 */} + {!isAdmin && + !isFirstLoad && + uaBrowser !== browserName && + uaMajor !== '0' && ( + + + + We have automatically detected you are now using{' '} + + {uaBrowser} {uaMajor} + + , which is a different browser from the last one you + were testing with, which was{' '} + + {browserName} {browserVersion} + + . +
    +
    + You can't edit your Browser type, but you can + continue with{' '} + + {uaBrowser} {uaMajor} + + . Keep in mind that your test results will be recorded + as if you were still using{' '} + + {browserName} {browserVersion} + + . +
    +
    + )} + {/* Tester Scenario 5 */} + {!isAdmin && + uaBrowser === browserName && + browserVersionMismatchMessage && ( + + + + The version of {browserName} you have set is different + from the one we have automatically detected, which is{' '} + {uaMajor} + . +
    +
    + This change doesn't affect results that have + already been submitted for this plan. However, results + you submit during this session will be recorded with the + versions specified in this form. +
    +
    + )} + {/* Tester Scenario 7 */} + {uaMajor === '0' && ( + + + + We could not automatically detect what version of{' '} + {browserName} you are using. Before continuing, + please provide your version number. + + + )} + {/* Admin Scenario 1 */} + {isAdmin && + (uaBrowser !== browserName || + browserVersionMismatchMessage) && ( + + + + We have automatically detected you are using{' '} + + {uaBrowser} {uaMajor} + + . This version is different than what{' '} + {testerName} was using last time, which was{' '} + + {browserName} {browserVersion} + + . +
    +
    + We have not updated this information below. Please + proceed with caution. +
    +
    + )} + + Browser Name + + + + + + Browser Version + + + + {isBrowserVersionError && ( + + Please enter a valid Browser Version. + + )} + +
    + + } + actions={[ + { + label: + updatedAtVersion !== atVersion || + updatedBrowserVersion !== browserVersion + ? 'Save and Continue' + : 'Continue', + onClick: + updatedAtVersion !== atVersion || + updatedBrowserVersion !== browserVersion + ? onSubmit + : handleClose + } + ]} + handleClose={!isFirstLoad ? handleClose : null} + handleHide={handleHide} + staticBackdrop={true} + useOnHide={true} + /> + )} + + ); }; AtAndBrowserDetailsModal.propTypes = { - show: PropTypes.bool, - isAdmin: PropTypes.bool, - firstLoad: PropTypes.bool, - atName: PropTypes.string, - atVersion: PropTypes.string, - atVersions: PropTypes.arrayOf(PropTypes.string), - browserName: PropTypes.string, - browserVersion: PropTypes.string, - browserVersions: PropTypes.arrayOf(PropTypes.string), - patternName: PropTypes.string, - testerName: PropTypes.string, - exactAtVersion: PropTypes.shape({ - id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired - }), - handleClose: PropTypes.func, - handleAction: PropTypes.func + show: PropTypes.bool, + isAdmin: PropTypes.bool, + firstLoad: PropTypes.bool, + atName: PropTypes.string, + atVersion: PropTypes.string, + atVersions: PropTypes.arrayOf(PropTypes.string), + browserName: PropTypes.string, + browserVersion: PropTypes.string, + browserVersions: PropTypes.arrayOf(PropTypes.string), + patternName: PropTypes.string, + testerName: PropTypes.string, + exactAtVersion: PropTypes.shape({ + id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired + }), + handleClose: PropTypes.func, + handleAction: PropTypes.func }; export default AtAndBrowserDetailsModal; diff --git a/client/components/common/AtBrowserVersion/index.jsx b/client/components/common/AtBrowserVersion/index.jsx index 0d0e49a97..6398e2728 100644 --- a/client/components/common/AtBrowserVersion/index.jsx +++ b/client/components/common/AtBrowserVersion/index.jsx @@ -3,52 +3,52 @@ import PropTypes from 'prop-types'; import styled from '@emotion/styled'; const VersionContainer = styled.div` + display: inline-block; + flex-wrap: wrap; + background: #f5f5f5; + border-radius: 4px; + padding: 0 5px; + font-weight: bold; + + & span { + font-weight: initial; display: inline-block; - flex-wrap: wrap; - background: #f5f5f5; - border-radius: 4px; - padding: 0 5px; - font-weight: bold; - - & span { - font-weight: initial; - display: inline-block; - margin-left: 2px; - } + margin-left: 2px; + } `; const AtVersion = ({ at, minimumAtVersion, exactAtVersion }) => { - const atVersionFormatted = minimumAtVersion - ? `${minimumAtVersion.name} or later` - : exactAtVersion.name; - - return ( - - {at.name}  - {atVersionFormatted} - - ); + const atVersionFormatted = minimumAtVersion + ? `${minimumAtVersion.name} or later` + : exactAtVersion.name; + + return ( + + {at.name}  + {atVersionFormatted} + + ); }; AtVersion.propTypes = { - at: PropTypes.shape({ name: PropTypes.string.isRequired }).isRequired, - minimumAtVersion: PropTypes.shape({ name: PropTypes.string.isRequired }), - exactAtVersion: PropTypes.shape({ name: PropTypes.string.isRequired }) + at: PropTypes.shape({ name: PropTypes.string.isRequired }).isRequired, + minimumAtVersion: PropTypes.shape({ name: PropTypes.string.isRequired }), + exactAtVersion: PropTypes.shape({ name: PropTypes.string.isRequired }) }; const BrowserVersion = ({ browser }) => { - return ( - - {browser.name}  - Any version - - ); + return ( + + {browser.name}  + Any version + + ); }; BrowserVersion.propTypes = { - browser: PropTypes.shape({ - name: PropTypes.string.isRequired - }).isRequired + browser: PropTypes.shape({ + name: PropTypes.string.isRequired + }).isRequired }; export { AtVersion, BrowserVersion }; diff --git a/client/components/common/BasicModal/BasicModal.css b/client/components/common/BasicModal/BasicModal.css index c602831e3..37afcaa0c 100644 --- a/client/components/common/BasicModal/BasicModal.css +++ b/client/components/common/BasicModal/BasicModal.css @@ -1,19 +1,19 @@ /* Allow multiple dialogs to be open at the same time */ div[role='dialog'][aria-modal='true']:nth-last-child(1) { - z-index: 1125; + z-index: 1125; } .modal-backdrop.show:nth-last-child(2) { - z-index: 1100; + z-index: 1100; } div[role='dialog'][aria-modal='true']:nth-last-child(3) { - z-index: 1075; + z-index: 1075; } .modal-backdrop.show:nth-last-child(4) { - z-index: 1050; + z-index: 1050; } div[role='dialog'][aria-modal='true']:nth-last-child(5) { - z-index: 1025; + z-index: 1025; } .modal-backdrop.show:nth-last-child(6) { - z-index: 1000; + z-index: 1000; } diff --git a/client/components/common/BasicModal/index.jsx b/client/components/common/BasicModal/index.jsx index 89a59ee97..11d487264 100644 --- a/client/components/common/BasicModal/index.jsx +++ b/client/components/common/BasicModal/index.jsx @@ -8,147 +8,143 @@ import { uniqueId } from 'lodash'; import './BasicModal.css'; const ModalTitleStyle = styled.h1` - border: 0; - padding: 0; - font-size: 1.5rem; + border: 0; + padding: 0; + font-size: 1.5rem; `; const BasicModal = ({ - show = false, - centered = false, - animation = true, - closeButton = true, - cancelButton = true, - headerSep = true, - showFooter = true, - dialogClassName = '', - title = null, - content = null, - closeLabel = 'Cancel', - handleClose = null, - handleHide = null, - staticBackdrop = false, - useOnHide = false, - actions = [], - initialFocusRef = null + show = false, + centered = false, + animation = true, + closeButton = true, + cancelButton = true, + headerSep = true, + showFooter = true, + dialogClassName = '', + title = null, + content = null, + closeLabel = 'Cancel', + handleClose = null, + handleHide = null, + staticBackdrop = false, + useOnHide = false, + actions = [], + initialFocusRef = null }) => { - const headerRef = useRef(); + const headerRef = useRef(); - useEffect(() => { - if (!show) return; - if (initialFocusRef?.current) { - initialFocusRef.current.focus(); - } else { - headerRef.current.focus(); - } - }, [show]); + useEffect(() => { + if (!show) return; + if (initialFocusRef?.current) { + initialFocusRef.current.focus(); + } else { + headerRef.current.focus(); + } + }, [show]); - const id = useMemo(() => { - return uniqueId('modal-'); - }, []); + const id = useMemo(() => { + return uniqueId('modal-'); + }, []); - const renderAction = (action, index) => { - if (action.component) { - return React.createElement( - action.component, - { key: `CustomComponent_${index}`, ...action.props }, - null - ); - } else { - return ( - - ); - } - }; + const renderAction = (action, index) => { + if (action.component) { + return React.createElement( + action.component, + { key: `CustomComponent_${index}`, ...action.props }, + null + ); + } else { + return ( + + ); + } + }; - return ( - + {}} + onExit={!useOnHide ? handleHide || handleClose : () => {}} + /* Disabled due to buggy implementation which jumps the page */ + autoFocus={false} + aria-labelledby={`title-${id}`} + dialogClassName={dialogClassName} + backdrop={staticBackdrop ? 'static' : true} + > + - - - - {title} - - - {content} - {showFooter && ( - - {cancelButton && handleClose && ( - - )} - {actions.map((action, index) => - renderAction(action, index) - )} - - )} - - - ); + + {title} + + + {content} + {showFooter && ( + + {cancelButton && handleClose && ( + + )} + {actions.map((action, index) => renderAction(action, index))} + + )} + + + ); }; BasicModal.propTypes = { - show: PropTypes.bool, - centered: PropTypes.bool, - animation: PropTypes.bool, - closeButton: PropTypes.bool, - cancelButton: PropTypes.bool, - headerSep: PropTypes.bool, - showFooter: PropTypes.bool, - dialogClassName: PropTypes.string, - title: PropTypes.node.isRequired, - content: PropTypes.node, - closeLabel: PropTypes.string, - handleClose: PropTypes.func, - handleHide: PropTypes.func, - staticBackdrop: PropTypes.bool, - useOnHide: PropTypes.bool, - actions: PropTypes.arrayOf( - PropTypes.shape({ - label: PropTypes.string, - onClick: PropTypes.func, - variant: PropTypes.string, - className: PropTypes.string, - component: PropTypes.elementType, - props: PropTypes.object - }) - ), - initialFocusRef: PropTypes.shape({ - current: PropTypes.any + show: PropTypes.bool, + centered: PropTypes.bool, + animation: PropTypes.bool, + closeButton: PropTypes.bool, + cancelButton: PropTypes.bool, + headerSep: PropTypes.bool, + showFooter: PropTypes.bool, + dialogClassName: PropTypes.string, + title: PropTypes.node.isRequired, + content: PropTypes.node, + closeLabel: PropTypes.string, + handleClose: PropTypes.func, + handleHide: PropTypes.func, + staticBackdrop: PropTypes.bool, + useOnHide: PropTypes.bool, + actions: PropTypes.arrayOf( + PropTypes.shape({ + label: PropTypes.string, + onClick: PropTypes.func, + variant: PropTypes.string, + className: PropTypes.string, + component: PropTypes.elementType, + props: PropTypes.object }) + ), + initialFocusRef: PropTypes.shape({ + current: PropTypes.any + }) }; export default BasicModal; diff --git a/client/components/common/BasicThemedModal/index.jsx b/client/components/common/BasicThemedModal/index.jsx index 788ac6966..d2dafffb2 100644 --- a/client/components/common/BasicThemedModal/index.jsx +++ b/client/components/common/BasicThemedModal/index.jsx @@ -7,132 +7,130 @@ import styled from '@emotion/styled'; import { uniqueId } from 'lodash'; const ModalTitleStyle = styled.h1` - border: 0; - padding: 0; - font-size: 1.5rem; + border: 0; + padding: 0; + font-size: 1.5rem; `; const ModalInnerSectionContainer = styled.div` - display: grid; - grid-auto-flow: column; + display: grid; + grid-auto-flow: column; - grid-template-columns: 50px auto; - grid-gap: 10px; + grid-template-columns: 50px auto; + grid-gap: 10px; `; const ColorStrip = styled.div` - width: 100%; - height: 10px; - ${props => props.hideHeadline && `display: none;`} - background-color: ${({ theme }) => - theme === 'danger' ? '#ce1b4c' : '#fab700'}; + width: 100%; + height: 10px; + ${props => props.hideHeadline && `display: none;`} + background-color: ${({ theme }) => + theme === 'danger' ? '#ce1b4c' : '#fab700'}; - border-top-left-radius: calc(0.3rem - 1px); - border-top-right-radius: calc(0.3rem - 1px); + border-top-left-radius: calc(0.3rem - 1px); + border-top-right-radius: calc(0.3rem - 1px); `; const BasicThemedModal = ({ - show = false, - centered = false, - animation = true, - theme = 'warning', // warning, danger - dialogClassName = '', - title = null, - content = null, - actionButtons = [], - closeLabel = 'Cancel', - handleClose = null, - showCloseAction = true + show = false, + centered = false, + animation = true, + theme = 'warning', // warning, danger + dialogClassName = '', + title = null, + content = null, + actionButtons = [], + closeLabel = 'Cancel', + handleClose = null, + showCloseAction = true }) => { - const headerRef = useRef(); + const headerRef = useRef(); - useEffect(() => { - if (!show) return; - headerRef.current.focus(); - }, [show]); + useEffect(() => { + if (!show) return; + headerRef.current.focus(); + }, [show]); - const id = useMemo(() => { - return uniqueId('modal-'); - }, []); + const id = useMemo(() => { + return uniqueId('modal-'); + }, []); - return ( - <> - + + + + + + + {title} + + + + + + <> +
    +
    {content}
    + + + + + {showCloseAction && ( + + )} + {actionButtons.map(({ action, text }) => ( + - )} - {actionButtons.map(({ action, text }) => ( - - ))} - - - - ); + {text} + + ))} + + + + ); }; BasicThemedModal.propTypes = { - show: PropTypes.bool, - centered: PropTypes.bool, - animation: PropTypes.bool, - theme: PropTypes.string, - dialogClassName: PropTypes.string, - title: PropTypes.node.isRequired, - content: PropTypes.node.isRequired, - actionButtons: PropTypes.arrayOf( - PropTypes.shape({ - text: PropTypes.string, - action: PropTypes.func - }) - ), - closeLabel: PropTypes.string, - handleClose: PropTypes.func, - showCloseAction: PropTypes.bool + show: PropTypes.bool, + centered: PropTypes.bool, + animation: PropTypes.bool, + theme: PropTypes.string, + dialogClassName: PropTypes.string, + title: PropTypes.node.isRequired, + content: PropTypes.node.isRequired, + actionButtons: PropTypes.arrayOf( + PropTypes.shape({ + text: PropTypes.string, + action: PropTypes.func + }) + ), + closeLabel: PropTypes.string, + handleClose: PropTypes.func, + showCloseAction: PropTypes.bool }; export default BasicThemedModal; diff --git a/client/components/common/ClippedProgressBar/ClippedProgressBar.css b/client/components/common/ClippedProgressBar/ClippedProgressBar.css index a0c483da1..d81763427 100644 --- a/client/components/common/ClippedProgressBar/ClippedProgressBar.css +++ b/client/components/common/ClippedProgressBar/ClippedProgressBar.css @@ -1,38 +1,38 @@ .progress { - height: 1.2rem; - border-radius: 1rem; - margin-top: 0.2em; - border: 1px solid gray; + height: 1.2rem; + border-radius: 1rem; + margin-top: 0.2em; + border: 1px solid gray; } .progress .progress-bar.bg-info { - background-color: #0b60ab !important; + background-color: #0b60ab !important; } .progress.clipped { - position: relative; - display: flex; - overflow: hidden; + position: relative; + display: flex; + overflow: hidden; } .progress.clipped .front { - position: absolute; - display: flex; - justify-content: center; - align-items: center; - left: 0; - right: 0; - top: 0; - bottom: 0; - background: #e9ecef; - color: black; + position: absolute; + display: flex; + justify-content: center; + align-items: center; + left: 0; + right: 0; + top: 0; + bottom: 0; + background: #e9ecef; + color: black; } .progress.clipped .back { - display: flex; - justify-content: center; - align-items: center; - width: 100%; - background: #0b60ab; - color: white; + display: flex; + justify-content: center; + align-items: center; + width: 100%; + background: #0b60ab; + color: white; } diff --git a/client/components/common/ClippedProgressBar/index.jsx b/client/components/common/ClippedProgressBar/index.jsx index 057251c89..ebe7d8bcd 100644 --- a/client/components/common/ClippedProgressBar/index.jsx +++ b/client/components/common/ClippedProgressBar/index.jsx @@ -3,48 +3,46 @@ import PropTypes from 'prop-types'; import './ClippedProgressBar.css'; const ProgressBar = ({ - progress = 0, - label = '', - clipped = true, - decorative + progress = 0, + label = '', + clipped = true, + decorative }) => { - return ( - <> - {clipped ? ( -
    -
    - {decorative ? null : `${progress}%`} -
    -
    - {decorative ? null : `${progress}%`} -
    -
    - ) : ( -
    -
    - {decorative ? null : `${progress}%`} -
    -
    - )} - - ); + return ( + <> + {clipped ? ( +
    +
    + {decorative ? null : `${progress}%`} +
    +
    {decorative ? null : `${progress}%`}
    +
    + ) : ( +
    +
    + {decorative ? null : `${progress}%`} +
    +
    + )} + + ); }; ProgressBar.propTypes = { - progress: PropTypes.number, - label: PropTypes.string, - clipped: PropTypes.bool, - decorative: PropTypes.bool + progress: PropTypes.number, + label: PropTypes.string, + clipped: PropTypes.bool, + decorative: PropTypes.bool }; export default ProgressBar; diff --git a/client/components/common/DeleteButton/index.jsx b/client/components/common/DeleteButton/index.jsx index 0bd9cc24d..73dfb203a 100644 --- a/client/components/common/DeleteButton/index.jsx +++ b/client/components/common/DeleteButton/index.jsx @@ -6,29 +6,29 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faTrash } from '@fortawesome/free-solid-svg-icons'; const StyledDeleteButton = styled(Button)` - color: #ce1b4c; + color: #ce1b4c; - &:hover { - background: #ce1b4c; - color: white; - } + &:hover { + background: #ce1b4c; + color: white; + } `; const DeleteButton = ({ onClick, ariaLabel }) => { - return ( - - Delete - - ); + return ( + + Delete + + ); }; DeleteButton.propTypes = { - onClick: PropTypes.func.isRequired, - ariaLabel: PropTypes.string.isRequired + onClick: PropTypes.func.isRequired, + ariaLabel: PropTypes.string.isRequired }; export default DeleteButton; diff --git a/client/components/common/DisclosureComponent/index.jsx b/client/components/common/DisclosureComponent/index.jsx index 88ac56902..a0273aeae 100644 --- a/client/components/common/DisclosureComponent/index.jsx +++ b/client/components/common/DisclosureComponent/index.jsx @@ -5,21 +5,21 @@ import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons'; import PropTypes from 'prop-types'; const DisclosureParent = styled.div` - border: 1px solid #d3d5da; - border-radius: 3px; - width: 100%; + border: 1px solid #d3d5da; + border-radius: 3px; + width: 100%; - h1, - h2, - h3, - h4 { - margin: 0; - padding: 0; - } + h1, + h2, + h3, + h4 { + margin: 0; + padding: 0; + } - ${({ stacked }) => - stacked && - ` + ${({ stacked }) => + stacked && + ` h1:not(:first-of-type) button, h2:not(:first-of-type) button, h3:not(:first-of-type) button, @@ -29,173 +29,167 @@ const DisclosureParent = styled.div` `; const DisclosureButton = styled.button` - position: relative; - width: 100%; - margin: 0; - padding: 1.25rem 40px 1.25rem 1.25rem; - text-align: left; - font-size: 1rem; - font-weight: bold; - border: none; - border-radius: 3px; - background-color: transparent; + position: relative; + width: 100%; + margin: 0; + padding: 1.25rem 40px 1.25rem 1.25rem; + text-align: left; + font-size: 1rem; + font-weight: bold; + border: none; + border-radius: 3px; + background-color: transparent; - ${({ stacked }) => - stacked && - ` + ${({ stacked }) => + stacked && + ` &:nth-last-of-type(1) { border-radius: 0 0 3px 3px; } `} - &:hover, + &:hover, &:focus { - padding: 1.25rem; - border: 0 solid #005a9c; - background-color: #def; - cursor: pointer; - } + padding: 1.25rem; + border: 0 solid #005a9c; + background-color: #def; + cursor: pointer; + } - .disclosure-icon { - position: absolute; - margin: 0; - top: 50%; - right: 1.25rem; + .disclosure-icon { + position: absolute; + margin: 0; + top: 50%; + right: 1.25rem; - color: #969696; - transform: translateY(-50%); - } + color: #969696; + transform: translateY(-50%); + } `; const DisclosureContainer = styled.div` - display: ${({ show, stacked }) => - show && stacked ? 'block' : show ? 'flex' : 'none'}; - flex-direction: ${({ stacked }) => !stacked && 'column'}; - gap: ${({ stacked }) => !stacked && '1.25rem'}; + display: ${({ show, stacked }) => + show && stacked ? 'block' : show ? 'flex' : 'none'}; + flex-direction: ${({ stacked }) => !stacked && 'column'}; + gap: ${({ stacked }) => !stacked && '1.25rem'}; - background-color: #f8f9fa; - padding: ${({ component }) => - component === 'test-management' ? '0' : '1.25rem'}; - border-top: 1px solid #d3d5da; + background-color: #f8f9fa; + padding: ${({ component }) => + component === 'test-management' ? '0' : '1.25rem'}; + border-top: 1px solid #d3d5da; `; const DisclosureComponent = ({ - componentId, - title = null, - disclosureContainerView = null, - onClick = null, - expanded = false, - stacked = false, - headingLevel = '3', - className = null + componentId, + title = null, + disclosureContainerView = null, + onClick = null, + expanded = false, + stacked = false, + headingLevel = '3', + className = null }) => { - const [isExpanded, setIsExpanded] = useState(expanded); - const Tag = `h${headingLevel}`; + const [isExpanded, setIsExpanded] = useState(expanded); + const Tag = `h${headingLevel}`; - return ( - <> - {stacked ? ( - - {title.map((_, index) => { - const buttonTitle = title[index]; - const labelTitle = - typeof buttonTitle === 'string' - ? buttonTitle - : index; - const buttonExpanded = expanded[index]; - const buttonOnClick = onClick[index]; - const buttonDisclosureContainerView = - disclosureContainerView[index]; + return ( + <> + {stacked ? ( + + {title.map((_, index) => { + const buttonTitle = title[index]; + const labelTitle = + typeof buttonTitle === 'string' ? buttonTitle : index; + const buttonExpanded = expanded[index]; + const buttonOnClick = onClick[index]; + const buttonDisclosureContainerView = + disclosureContainerView[index]; - return ( - - - - {buttonTitle} - - - - - {buttonDisclosureContainerView} - - - ); - })} - - ) : ( - - - setIsExpanded(!isExpanded)} - > - {title} - - - - - {disclosureContainerView} - - - )} - - ); + return ( + + + + {buttonTitle} + + + + + {buttonDisclosureContainerView} + + + ); + })} + + ) : ( + + + setIsExpanded(!isExpanded)} + > + {title} + + + + + {disclosureContainerView} + + + )} + + ); }; DisclosureComponent.propTypes = { - componentId: PropTypes.string, - title: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.node, - PropTypes.arrayOf(PropTypes.string), - PropTypes.arrayOf(PropTypes.node) - ]), - disclosureContainerView: PropTypes.oneOfType([ - PropTypes.node, - PropTypes.arrayOf(PropTypes.node) - ]), - onClick: PropTypes.oneOfType([ - PropTypes.func, - PropTypes.arrayOf(PropTypes.func) - ]), - expanded: PropTypes.oneOfType([ - PropTypes.bool, - PropTypes.arrayOf(PropTypes.bool) - ]), - stacked: PropTypes.bool, - headingLevel: PropTypes.string, - className: PropTypes.string + componentId: PropTypes.string, + title: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.node, + PropTypes.arrayOf(PropTypes.string), + PropTypes.arrayOf(PropTypes.node) + ]), + disclosureContainerView: PropTypes.oneOfType([ + PropTypes.node, + PropTypes.arrayOf(PropTypes.node) + ]), + onClick: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.arrayOf(PropTypes.func) + ]), + expanded: PropTypes.oneOfType([ + PropTypes.bool, + PropTypes.arrayOf(PropTypes.bool) + ]), + stacked: PropTypes.bool, + headingLevel: PropTypes.string, + className: PropTypes.string }; export default DisclosureComponent; diff --git a/client/components/common/FilterButtons/index.jsx b/client/components/common/FilterButtons/index.jsx index d8938e81a..d9b35ea3c 100644 --- a/client/components/common/FilterButtons/index.jsx +++ b/client/components/common/FilterButtons/index.jsx @@ -4,64 +4,64 @@ import styled from '@emotion/styled'; import { Button } from 'react-bootstrap'; const StyledFilterButton = styled(Button)` - background: #e9ebee; - border-radius: 16px; - margin-left: 12px; - background-color: white; - font-weight: 400; + background: #e9ebee; + border-radius: 16px; + margin-left: 12px; + background-color: white; + font-weight: 400; - &.active, - &:active { - background: #eaf3fe !important; - border: #517dbc 2px solid !important; - box-shadow: none !important; + &.active, + &:active { + background: #eaf3fe !important; + border: #517dbc 2px solid !important; + box-shadow: none !important; - &:hover, - :focus { - box-shadow: 0 0 0 0.2rem rgba(103, 171, 197, 0.5) !important; - } + &:hover, + :focus { + box-shadow: 0 0 0 0.2rem rgba(103, 171, 197, 0.5) !important; } + } `; const FilterButtons = ({ - filterLabel, - filterAriaLabel, - filterOptions, - activeFilter, - onFilterChange + filterLabel, + filterAriaLabel, + filterOptions, + activeFilter, + onFilterChange }) => { - return ( -
      - - {Object.entries(filterOptions).map(([value, label]) => { - const isActive = activeFilter === value; - return ( -
    • - onFilterChange(value)} - > - {label} - -
    • - ); - })} -
    - ); + return ( +
      + + {Object.entries(filterOptions).map(([value, label]) => { + const isActive = activeFilter === value; + return ( +
    • + onFilterChange(value)} + > + {label} + +
    • + ); + })} +
    + ); }; FilterButtons.propTypes = { - filterLabel: PropTypes.string.isRequired, - filterAriaLabel: PropTypes.string, - filterOptions: PropTypes.objectOf(PropTypes.string).isRequired, - activeFilter: PropTypes.string.isRequired, - onFilterChange: PropTypes.func.isRequired + filterLabel: PropTypes.string.isRequired, + filterAriaLabel: PropTypes.string, + filterOptions: PropTypes.objectOf(PropTypes.string).isRequired, + activeFilter: PropTypes.string.isRequired, + onFilterChange: PropTypes.func.isRequired }; export default FilterButtons; diff --git a/client/components/common/FocusTrapper/index.jsx b/client/components/common/FocusTrapper/index.jsx index 60e27642f..af846e188 100644 --- a/client/components/common/FocusTrapper/index.jsx +++ b/client/components/common/FocusTrapper/index.jsx @@ -3,90 +3,89 @@ import PropTypes from 'prop-types'; import { useEffect, useRef } from 'react'; const FocusTrapper = ({ children, isActive, initialFocusRef, trappedElId }) => { - const focusableElsRef = useRef([]); - const updateFocusableElements = () => { - if (focusableElsRef.current.length > 0) return; + const focusableElsRef = useRef([]); + const updateFocusableElements = () => { + if (focusableElsRef.current.length > 0) return; - // Must use getElementById because a ref is not consistently populated - const el = document.getElementById(trappedElId); - if (!el) return; + // Must use getElementById because a ref is not consistently populated + const el = document.getElementById(trappedElId); + if (!el) return; - focusableElsRef.current = Array.from( - el.querySelectorAll( - 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' - ) - ); + focusableElsRef.current = Array.from( + el.querySelectorAll( + 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' + ) + ); - // Create two elements to trap focus before and after the dialog - // APG Example for Dialog used as reference - const before = document.createElement('div'); - const after = document.createElement('div'); - before.tabIndex = 0; - after.tabIndex = 0; - el.prepend(before); - el.append(after); + // Create two elements to trap focus before and after the dialog + // APG Example for Dialog used as reference + const before = document.createElement('div'); + const after = document.createElement('div'); + before.tabIndex = 0; + after.tabIndex = 0; + el.prepend(before); + el.append(after); - focusableElsRef.current.unshift(before); - focusableElsRef.current.push(after); - }; + focusableElsRef.current.unshift(before); + focusableElsRef.current.push(after); + }; - const trapFocus = event => { - const isTabPressed = event.key === 'Tab' || event.keyCode === 9; - if (!isTabPressed) return; + const trapFocus = event => { + const isTabPressed = event.key === 'Tab' || event.keyCode === 9; + if (!isTabPressed) return; - const focusableEls = focusableElsRef.current; + const focusableEls = focusableElsRef.current; - // First focusable element is after the blank 'before' div - const firstFocusableEl = focusableEls[1]; - // Last focusable element is before the blank 'after' div - const lastFocusableEl = focusableEls[focusableEls.length - 2]; - // When SHIFT + TAB is pressed and the active element is the first focusable element - if ( - event.shiftKey && - (document.activeElement === firstFocusableEl || - document.activeElement === focusableEls[0] || - document.activeElement === initialFocusRef.current) - ) { - lastFocusableEl.focus(); - event.preventDefault(); - } - // When TAB is pressed and the active element is the last focusable element - else if ( - !event.shiftKey && - (document.activeElement === lastFocusableEl || - document.activeElement === - focusableEls[focusableEls.length - 1]) - ) { - firstFocusableEl.focus(); - event.preventDefault(); - } - }; + // First focusable element is after the blank 'before' div + const firstFocusableEl = focusableEls[1]; + // Last focusable element is before the blank 'after' div + const lastFocusableEl = focusableEls[focusableEls.length - 2]; + // When SHIFT + TAB is pressed and the active element is the first focusable element + if ( + event.shiftKey && + (document.activeElement === firstFocusableEl || + document.activeElement === focusableEls[0] || + document.activeElement === initialFocusRef.current) + ) { + lastFocusableEl.focus(); + event.preventDefault(); + } + // When TAB is pressed and the active element is the last focusable element + else if ( + !event.shiftKey && + (document.activeElement === lastFocusableEl || + document.activeElement === focusableEls[focusableEls.length - 1]) + ) { + firstFocusableEl.focus(); + event.preventDefault(); + } + }; - useEffect(() => { - if (isActive) { - updateFocusableElements(); - document.addEventListener('keydown', trapFocus); - } else { - document.removeEventListener('keydown', trapFocus); - focusableElsRef.current = []; - } + useEffect(() => { + if (isActive) { + updateFocusableElements(); + document.addEventListener('keydown', trapFocus); + } else { + document.removeEventListener('keydown', trapFocus); + focusableElsRef.current = []; + } - return () => { - focusableElsRef.current = []; - document.removeEventListener('keydown', trapFocus); - }; - }, [isActive]); + return () => { + focusableElsRef.current = []; + document.removeEventListener('keydown', trapFocus); + }; + }, [isActive]); - return
    {children}
    ; + return
    {children}
    ; }; FocusTrapper.propTypes = { - children: PropTypes.node.isRequired, - isActive: PropTypes.bool.isRequired, - trappedElId: PropTypes.string.isRequired, - initialFocusRef: PropTypes.shape({ - current: PropTypes.object - }) + children: PropTypes.node.isRequired, + isActive: PropTypes.bool.isRequired, + trappedElId: PropTypes.string.isRequired, + initialFocusRef: PropTypes.shape({ + current: PropTypes.object + }) }; export default FocusTrapper; diff --git a/client/components/common/LoadingStatus/LoadingStatus.js b/client/components/common/LoadingStatus/LoadingStatus.js index c5da4f084..55af3d9df 100644 --- a/client/components/common/LoadingStatus/LoadingStatus.js +++ b/client/components/common/LoadingStatus/LoadingStatus.js @@ -5,52 +5,52 @@ import styled from '@emotion/styled'; import BasicModal from '../../common/BasicModal'; const Container = styled.div` - display: flex; - justify-content: center; + display: flex; + justify-content: center; `; const LoadingStatus = ({ message, children }) => { - const [isLoading, setIsLoading] = useState(false); + const [isLoading, setIsLoading] = useState(false); - useEffect(() => { - if (message) setIsLoading(true); - else setIsLoading(false); - }, [message]); + useEffect(() => { + if (message) setIsLoading(true); + else setIsLoading(false); + }, [message]); - return ( - <> - {isLoading && ( - - - - } - /> - )} - {children} - - ); + return ( + <> + {isLoading && ( + + + + } + /> + )} + {children} + + ); }; LoadingStatus.propTypes = { - message: PropTypes.string, - children: PropTypes.node + message: PropTypes.string, + children: PropTypes.node }; export default LoadingStatus; diff --git a/client/components/common/LoadingStatus/useTriggerLoad.js b/client/components/common/LoadingStatus/useTriggerLoad.js index 55e4889a2..8301b3d15 100644 --- a/client/components/common/LoadingStatus/useTriggerLoad.js +++ b/client/components/common/LoadingStatus/useTriggerLoad.js @@ -1,22 +1,22 @@ import { useState } from 'react'; export function useTriggerLoad() { - const [loadingMessage, setLoadingMessage] = useState(''); + const [loadingMessage, setLoadingMessage] = useState(''); - const triggerLoad = (loadFn, message) => { - return new Promise((resolve, reject) => { - setLoadingMessage(message); - loadFn() - .then(() => { - setLoadingMessage(''); - resolve(); - }) - .catch(e => { - setLoadingMessage(''); - reject(e); - }); + const triggerLoad = (loadFn, message) => { + return new Promise((resolve, reject) => { + setLoadingMessage(message); + loadFn() + .then(() => { + setLoadingMessage(''); + resolve(); + }) + .catch(e => { + setLoadingMessage(''); + reject(e); }); - }; + }); + }; - return { triggerLoad, loadingMessage }; + return { triggerLoad, loadingMessage }; } diff --git a/client/components/common/PageStatus/PageStatus.css b/client/components/common/PageStatus/PageStatus.css index f4dca95fe..59449a4e1 100644 --- a/client/components/common/PageStatus/PageStatus.css +++ b/client/components/common/PageStatus/PageStatus.css @@ -1,11 +1,11 @@ .loading { - display: inline-block; - clip-path: inset(0 1ch 0 0); - animation: dots 1s steps(4) infinite; + display: inline-block; + clip-path: inset(0 1ch 0 0); + animation: dots 1s steps(4) infinite; } @keyframes dots { - to { - clip-path: inset(0 -1ch 0 0); - } + to { + clip-path: inset(0 -1ch 0 0); + } } diff --git a/client/components/common/PageStatus/index.jsx b/client/components/common/PageStatus/index.jsx index 6886633ce..066dfd9f2 100644 --- a/client/components/common/PageStatus/index.jsx +++ b/client/components/common/PageStatus/index.jsx @@ -5,37 +5,37 @@ import { Helmet } from 'react-helmet'; import './PageStatus.css'; const Loading = ({ - title, - heading, - message = 'Loading ...', - isError = false + title, + heading, + message = 'Loading ...', + isError = false }) => { - let className = isError ? 'alert alert-danger' : ''; - className = message === 'Loading ...' ? `${className} loading` : className; + let className = isError ? 'alert alert-danger' : ''; + className = message === 'Loading ...' ? `${className} loading` : className; - return ( - - - {title} - -

    {heading}

    + return ( + + + {title} + +

    {heading}

    -
    - {message} -
    -
    - ); +
    + {message} +
    +
    + ); }; Loading.propTypes = { - title: PropTypes.string, - heading: PropTypes.string, - message: PropTypes.string, - isError: PropTypes.bool + title: PropTypes.string, + heading: PropTypes.string, + message: PropTypes.string, + isError: PropTypes.bool }; export default Loading; diff --git a/client/components/common/PhasePill/index.jsx b/client/components/common/PhasePill/index.jsx index 992dce3b8..15ee3ca22 100644 --- a/client/components/common/PhasePill/index.jsx +++ b/client/components/common/PhasePill/index.jsx @@ -4,63 +4,63 @@ import { derivePhaseName } from '@client/utils/aria'; import styled from '@emotion/styled'; const PhaseText = styled.span` - display: inline-block; - padding: 2px 4px; - border-radius: 14px; + display: inline-block; + padding: 2px 4px; + border-radius: 14px; - text-align: center; - overflow: hidden; - white-space: nowrap; - color: white; + text-align: center; + overflow: hidden; + white-space: nowrap; + color: white; - &.full-width { - width: 100%; - } - &:not(.full-width) { - width: min-content; - padding: 2px 15px; - vertical-align: middle; - position: relative; - top: -4px; - margin-top: 4px; /* Improve appearance when text wraps */ - } + &.full-width { + width: 100%; + } + &:not(.full-width) { + width: min-content; + padding: 2px 15px; + vertical-align: middle; + position: relative; + top: -4px; + margin-top: 4px; /* Improve appearance when text wraps */ + } - &.rd { - background: #4177de; - } + &.rd { + background: #4177de; + } - &.draft { - background: #818f98; - } + &.draft { + background: #818f98; + } - &.candidate { - background: #ff6c00; - } + &.candidate { + background: #ff6c00; + } - &.recommended { - background: #8441de; - } + &.recommended { + background: #8441de; + } - &.deprecated { - background: #ce1b4c; - } + &.deprecated { + background: #ce1b4c; + } `; const PhasePill = ({ fullWidth = true, children: phase }) => { - return ( - str) - .join(' ')} - > - {derivePhaseName(phase)} - - ); + return ( + str) + .join(' ')} + > + {derivePhaseName(phase)} + + ); }; PhasePill.propTypes = { - fullWidth: PropTypes.bool, - children: PropTypes.string.isRequired + fullWidth: PropTypes.bool, + children: PropTypes.string.isRequired }; export default PhasePill; diff --git a/client/components/common/RadioBox/index.jsx b/client/components/common/RadioBox/index.jsx index b1f8f1296..ff9057646 100644 --- a/client/components/common/RadioBox/index.jsx +++ b/client/components/common/RadioBox/index.jsx @@ -3,85 +3,85 @@ import PropTypes from 'prop-types'; import styled from '@emotion/styled'; const ContainerDiv = styled.div` - display: flex; + display: flex; `; const Label = styled.label` - border: 1px solid #ced4da; - padding: 0.375rem 0.9rem 0.375rem 0.75rem; - background-color: ${props => - props.applyCheckedStyles ? `#F6F8FA` : 'white'}; + border: 1px solid #ced4da; + padding: 0.375rem 0.9rem 0.375rem 0.75rem; + background-color: ${props => + props.applyCheckedStyles ? `#F6F8FA` : 'white'}; - &:first-of-type { - border-radius: 0.375rem 0 0 0.375rem; - } - &:last-of-type { - border-radius: 0 0.375rem 0.375rem 0; - } - &:not(:last-of-type) { - border-right: none; - } + &:first-of-type { + border-radius: 0.375rem 0 0 0.375rem; + } + &:last-of-type { + border-radius: 0 0.375rem 0.375rem 0; + } + &:not(:last-of-type) { + border-right: none; + } `; const Input = styled.input` - margin-right: 0.375rem; + margin-right: 0.375rem; `; const RadioBox = ({ name, labels, selectedLabel, onSelect }) => { - const getOnChange = label => event => { - if (event.target.checked) onSelect(label); - }; + const getOnChange = label => event => { + if (event.target.checked) onSelect(label); + }; - return ( - - {labels.map(label => { - const isChecked = selectedLabel === label; - return ( - - ); - })} - - ); + return ( + + {labels.map(label => { + const isChecked = selectedLabel === label; + return ( + + ); + })} + + ); }; const TextThatWontShiftWhenBold = ({ isBold, children: text }) => { - return ( - - - {text} - - - {text} - - - ); + return ( + + + {text} + + + {text} + + + ); }; TextThatWontShiftWhenBold.propTypes = { - isBold: PropTypes.bool.isRequired, - children: PropTypes.string.isRequired + isBold: PropTypes.bool.isRequired, + children: PropTypes.string.isRequired }; RadioBox.propTypes = { - name: PropTypes.string.isRequired, - labels: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired, - selectedLabel: PropTypes.string, - onSelect: PropTypes.func.isRequired + name: PropTypes.string.isRequired, + labels: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired, + selectedLabel: PropTypes.string, + onSelect: PropTypes.func.isRequired }; export default RadioBox; diff --git a/client/components/common/ReportStatusDot/index.jsx b/client/components/common/ReportStatusDot/index.jsx index 0c44c6521..e43bf78b6 100644 --- a/client/components/common/ReportStatusDot/index.jsx +++ b/client/components/common/ReportStatusDot/index.jsx @@ -1,47 +1,47 @@ import styled from '@emotion/styled'; const ReportStatusDot = styled.span` - display: inline-block; - height: 10px; - width: 10px; - padding: 0; - margin-right: 8px; - border-radius: 50%; - - &.issues { - background: #f2ba00; - } - - &.tests-skipped, - &.reports-not-started { - background: #7c7c7c; - } - - &.tests-running { - border: 2px solid #1e8f37; - background: #d2d5d9; - } - - &.tests-error { - background: #e3261f; - } - - &.tests-queued, - &.reports-in-progress { - background: #3876e8; - } - - &.tests-complete, - &.reports-complete { - background: #2ba51c; - } - - &.tests-cancelled { - background: #a231ff; - } - &.reports-missing { - background: #ce1b4c; - } + display: inline-block; + height: 10px; + width: 10px; + padding: 0; + margin-right: 8px; + border-radius: 50%; + + &.issues { + background: #f2ba00; + } + + &.tests-skipped, + &.reports-not-started { + background: #7c7c7c; + } + + &.tests-running { + border: 2px solid #1e8f37; + background: #d2d5d9; + } + + &.tests-error { + background: #e3261f; + } + + &.tests-queued, + &.reports-in-progress { + background: #3876e8; + } + + &.tests-complete, + &.reports-complete { + background: #2ba51c; + } + + &.tests-cancelled { + background: #a231ff; + } + &.reports-missing { + background: #ce1b4c; + } `; export default ReportStatusDot; diff --git a/client/components/common/ReportStatusSummary/index.jsx b/client/components/common/ReportStatusSummary/index.jsx index 904f81b9e..6e1ebf29e 100644 --- a/client/components/common/ReportStatusSummary/index.jsx +++ b/client/components/common/ReportStatusSummary/index.jsx @@ -5,93 +5,91 @@ import { convertDateToString } from '../../../utils/formatter'; import { calculatePercentComplete } from '../../../utils/calculatePercentComplete'; const IncompleteStatusReport = styled.span` - min-width: 5rem; - display: inline-block; + min-width: 5rem; + display: inline-block; `; const ReportStatusSummary = ({ - testPlanVersion, - testPlanReport, - fromTestQueue = false + testPlanVersion, + testPlanReport, + fromTestQueue = false }) => { - const renderCompleteReportStatus = testPlanReport => { - const formattedDate = convertDateToString( - testPlanReport.markedFinalAt, - 'MMM D, YYYY' + const renderCompleteReportStatus = testPlanReport => { + const formattedDate = convertDateToString( + testPlanReport.markedFinalAt, + 'MMM D, YYYY' + ); + return ( + + Report completed on {formattedDate} + + ); + }; + + const renderPartialCompleteReportStatus = testPlanReport => { + const { metrics, draftTestPlanRuns } = testPlanReport; + + const conflictsCount = metrics.conflictsCount ?? 0; + const percentComplete = calculatePercentComplete(testPlanReport); + switch (draftTestPlanRuns?.length) { + case 0: + return fromTestQueue ? ( + No testers assigned + ) : ( + In test queue with no testers assigned ); + case 1: return ( + + {percentComplete}% complete by  - Report completed on {formattedDate} + {draftTestPlanRuns[0].tester.username} +  with {conflictsCount} conflicts + ); - }; - - const renderPartialCompleteReportStatus = testPlanReport => { - const { metrics, draftTestPlanRuns } = testPlanReport; - - const conflictsCount = metrics.conflictsCount ?? 0; - const percentComplete = calculatePercentComplete(testPlanReport); - switch (draftTestPlanRuns?.length) { - case 0: - return fromTestQueue ? ( - No testers assigned - ) : ( - In test queue with no testers assigned - ); - case 1: - return ( - - {percentComplete}% complete by  - - {draftTestPlanRuns[0].tester.username} - -  with {conflictsCount} conflicts - - ); - default: - return ( - - {percentComplete}% complete by  - {draftTestPlanRuns.length} testers with {conflictsCount} -  conflicts - - ); - } - }; + default: + return ( + + {percentComplete}% complete by  + {draftTestPlanRuns.length} testers with {conflictsCount} +  conflicts + + ); + } + }; - if (testPlanReport) { - const { markedFinalAt } = testPlanReport; - if (markedFinalAt) { - return renderCompleteReportStatus(testPlanReport); - } else { - return renderPartialCompleteReportStatus(testPlanReport); - } + if (testPlanReport) { + const { markedFinalAt } = testPlanReport; + if (markedFinalAt) { + return renderCompleteReportStatus(testPlanReport); + } else { + return renderPartialCompleteReportStatus(testPlanReport); } + } - return Missing; + return Missing; }; ReportStatusSummary.propTypes = { - testPlanVersion: PropTypes.shape({ - id: PropTypes.string.isRequired - }).isRequired, - testPlanReport: PropTypes.shape({ - id: PropTypes.string.isRequired, - markedFinalAt: PropTypes.string, - metrics: PropTypes.object, - draftTestPlanRuns: PropTypes.arrayOf( - PropTypes.shape({ - tester: PropTypes.shape({ - username: PropTypes.string.isRequired - }).isRequired - }) - ).isRequired - }), - fromTestQueue: PropTypes.bool + testPlanVersion: PropTypes.shape({ + id: PropTypes.string.isRequired + }).isRequired, + testPlanReport: PropTypes.shape({ + id: PropTypes.string.isRequired, + markedFinalAt: PropTypes.string, + metrics: PropTypes.object, + draftTestPlanRuns: PropTypes.arrayOf( + PropTypes.shape({ + tester: PropTypes.shape({ + username: PropTypes.string.isRequired + }).isRequired + }) + ).isRequired + }), + fromTestQueue: PropTypes.bool }; export default ReportStatusSummary; diff --git a/client/components/common/SortableTableHeader/index.js b/client/components/common/SortableTableHeader/index.js index b274d3079..c1fb828a2 100644 --- a/client/components/common/SortableTableHeader/index.js +++ b/client/components/common/SortableTableHeader/index.js @@ -4,141 +4,141 @@ import { Button } from 'react-bootstrap'; import styled from '@emotion/styled'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { - faArrowDownShortWide, - faArrowUpShortWide + faArrowDownShortWide, + faArrowUpShortWide } from '@fortawesome/free-solid-svg-icons'; import { useAriaLiveRegion } from '../../providers/AriaLiveRegionProvider'; const SortableTableHeaderWrapper = styled.th` - position: relative; - padding: 0; + position: relative; + padding: 0; `; const SortableTableHeaderButton = styled(Button)` + background: #e9ebee; + border: none; + color: black; + font-size: 1rem; + padding: 0; + font-weight: 700; + text-align: left; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + justify-content: space-between; + align-items: flex-end; + padding: 8px 12px; + border-radius: 0; + z-index: 0; + display: flex; + &:hover, + &:focus { background: #e9ebee; - border: none; - color: black; - font-size: 1rem; - padding: 0; - font-weight: 700; - text-align: left; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - justify-content: space-between; - align-items: flex-end; - padding: 8px 12px; - border-radius: 0; - z-index: 0; - display: flex; - &:hover, - &:focus { - background: #e9ebee; - z-index: 1; - color: #0b60ab; - background-color: var(--bs-table-hover-bg); - } + z-index: 1; + color: #0b60ab; + background-color: var(--bs-table-hover-bg); + } - &:hover { - border: none; - } + &:hover { + border: none; + } `; const InactiveIcon = styled(FontAwesomeIcon)` - color: rgb(155, 155, 155); + color: rgb(155, 155, 155); `; export const TABLE_SORT_ORDERS = { - ASC: 'ASCENDING', - DESC: 'DESCENDING' + ASC: 'ASCENDING', + DESC: 'DESCENDING' }; const SortableTableHeader = ({ - title, - active, - onSort = () => {}, - initialSortDirection = TABLE_SORT_ORDERS.ASC + title, + active, + onSort = () => {}, + initialSortDirection = TABLE_SORT_ORDERS.ASC }) => { - const [currentSortOrder, setCurrentSortOrder] = - useState(initialSortDirection); - - const setAlertMessage = useAriaLiveRegion(); - - useEffect(() => { - if (active) { - const message = - `Test Plans Status Summary Table, now sorted by ${title} in ` + - `${currentSortOrder.toLowerCase()} order`; - setAlertMessage(message); - } - }, [active, currentSortOrder, setAlertMessage, title]); - - const handleClick = () => { - if (active) { - const newSortOrder = - currentSortOrder === TABLE_SORT_ORDERS.ASC - ? TABLE_SORT_ORDERS.DESC - : TABLE_SORT_ORDERS.ASC; - setCurrentSortOrder(newSortOrder); - onSort(newSortOrder); - } else { - onSort(currentSortOrder); - } - }; + const [currentSortOrder, setCurrentSortOrder] = + useState(initialSortDirection); - const getIcon = () => { - const icon = - currentSortOrder === TABLE_SORT_ORDERS.ASC - ? faArrowUpShortWide - : faArrowDownShortWide; - - const attribs = { - 'aria-hidden': 'true', - focusable: 'false', - icon: icon - }; - - if (active) { - return ; - } else { - return ; - } - }; + const setAlertMessage = useAriaLiveRegion(); - const getAriaSort = () => { - if (!active) { - return 'none'; - } else { - return currentSortOrder === TABLE_SORT_ORDERS.ASC - ? 'ascending' - : 'descending'; - } + useEffect(() => { + if (active) { + const message = + `Test Plans Status Summary Table, now sorted by ${title} in ` + + `${currentSortOrder.toLowerCase()} order`; + setAlertMessage(message); + } + }, [active, currentSortOrder, setAlertMessage, title]); + + const handleClick = () => { + if (active) { + const newSortOrder = + currentSortOrder === TABLE_SORT_ORDERS.ASC + ? TABLE_SORT_ORDERS.DESC + : TABLE_SORT_ORDERS.ASC; + setCurrentSortOrder(newSortOrder); + onSort(newSortOrder); + } else { + onSort(currentSortOrder); + } + }; + + const getIcon = () => { + const icon = + currentSortOrder === TABLE_SORT_ORDERS.ASC + ? faArrowUpShortWide + : faArrowDownShortWide; + + const attribs = { + 'aria-hidden': 'true', + focusable: 'false', + icon: icon }; - return ( - - - {title} - {getIcon()} - - - ); + if (active) { + return ; + } else { + return ; + } + }; + + const getAriaSort = () => { + if (!active) { + return 'none'; + } else { + return currentSortOrder === TABLE_SORT_ORDERS.ASC + ? 'ascending' + : 'descending'; + } + }; + + return ( + + + {title} + {getIcon()} + + + ); }; SortableTableHeader.propTypes = { - title: PropTypes.string.isRequired, - active: PropTypes.bool.isRequired, - onSort: PropTypes.func.isRequired, - initialSortDirection: PropTypes.oneOf([ - TABLE_SORT_ORDERS.ASC, - TABLE_SORT_ORDERS.DESC - ]) + title: PropTypes.string.isRequired, + active: PropTypes.bool.isRequired, + onSort: PropTypes.func.isRequired, + initialSortDirection: PropTypes.oneOf([ + TABLE_SORT_ORDERS.ASC, + TABLE_SORT_ORDERS.DESC + ]) }; export default SortableTableHeader; diff --git a/client/components/common/TestPlanResultsTable/TestPlanResultsTable.css b/client/components/common/TestPlanResultsTable/TestPlanResultsTable.css index 31b97fd63..a217a3beb 100644 --- a/client/components/common/TestPlanResultsTable/TestPlanResultsTable.css +++ b/client/components/common/TestPlanResultsTable/TestPlanResultsTable.css @@ -1,24 +1,24 @@ table.test-plan-results-table { - /* To override `caption-side: bottom;` coming from _reboot.scss */ - caption-side: unset; + /* To override `caption-side: bottom;` coming from _reboot.scss */ + caption-side: unset; } table.test-plan-unexpected-behaviors-table { - margin-top: 0.5rem; + margin-top: 0.5rem; } p.test-plan-results-response-p { - margin: 0; + margin: 0; } blockquote.test-plan-results-blockquote { - background: #f7f7f7; - border-left: 4px solid #d3d5d9; - margin: 0.5em 0; - padding: 0.5em; + background: #f7f7f7; + border-left: 4px solid #d3d5d9; + margin: 0.5em 0; + padding: 0.5em; } li.test-plan-results-li { - margin-left: 1rem; - list-style: disc; + margin-left: 1rem; + list-style: disc; } diff --git a/client/components/common/TestPlanResultsTable/index.jsx b/client/components/common/TestPlanResultsTable/index.jsx index 30dccaa36..c29bcb688 100644 --- a/client/components/common/TestPlanResultsTable/index.jsx +++ b/client/components/common/TestPlanResultsTable/index.jsx @@ -5,207 +5,204 @@ import nextId from 'react-id-generator'; import { getMetrics } from 'shared'; import './TestPlanResultsTable.css'; +const getAssertionResultText = (passed, priority) => { + if (priority === 'MAY') { + return passed ? 'Supported' : 'Unsupported'; + } + return passed ? 'Passed' : 'Failed'; +}; + const renderAssertionRow = (assertionResult, priorityString) => { - return ( - - {priorityString} - - {assertionResult.assertion.phrase - ? assertionResult.assertion.phrase.charAt(0).toUpperCase() + - assertionResult.assertion.phrase.slice(1) - : assertionResult.assertion.text.charAt(0).toUpperCase() + - assertionResult.assertion.text.slice(1)} - - {assertionResult.passed ? 'Passed' : 'Failed'} - - ); + return ( + + {priorityString} + + {assertionResult.assertion.phrase + ? assertionResult.assertion.phrase.charAt(0).toUpperCase() + + assertionResult.assertion.phrase.slice(1) + : assertionResult.assertion.text.charAt(0).toUpperCase() + + assertionResult.assertion.text.slice(1)} + + {getAssertionResultText(assertionResult.passed, priorityString)} + + ); }; const TestPlanResultsTable = ({ - test, - testResult, - tableClassName = '', - optionalHeader = null, - commandHeadingLevel = 3 + test, + testResult, + tableClassName = '', + optionalHeader = null, + commandHeadingLevel = 3 }) => { - const CommandHeading = `h${commandHeadingLevel}`; + const CommandHeading = `h${commandHeadingLevel}`; - return ( - <> - {optionalHeader} - {testResult.scenarioResults.map((scenarioResult, index) => { - const { - assertionsPassedCount, - assertionsFailedCount, - severeImpactPassedAssertionCount, - moderateImpactPassedAssertionCount - } = getMetrics({ scenarioResult }); + return ( + <> + {optionalHeader} + {testResult.scenarioResults.map((scenarioResult, index) => { + const { + assertionsPassedCount, + assertionsFailedCount, + severeImpactPassedAssertionCount, + moderateImpactPassedAssertionCount + } = getMetrics({ scenarioResult }); - const hasNoSevereUnexpectedBehavior = - severeImpactPassedAssertionCount > 0; - const hasNoModerateUnexpectedBehavior = - moderateImpactPassedAssertionCount > 0; + const hasNoSevereUnexpectedBehavior = + severeImpactPassedAssertionCount > 0; + const hasNoModerateUnexpectedBehavior = + moderateImpactPassedAssertionCount > 0; - // Rows are sorted by priority descending, then result (failures then passes), then - // assertion order. Assertion order refers to the order of assertion columns in the - // tests.csv file. - const mustAssertionResults = scenarioResult.mustAssertionResults - .slice() - .sort((a, b) => - a.passed === b.passed ? 0 : a.passed ? 1 : -1 - ); + // Rows are sorted by priority descending, then result (failures then passes), then + // assertion order. Assertion order refers to the order of assertion columns in the + // tests.csv file. + const mustAssertionResults = scenarioResult.mustAssertionResults + .slice() + .sort((a, b) => (a.passed === b.passed ? 0 : a.passed ? 1 : -1)); - const shouldAssertionResults = - scenarioResult.shouldAssertionResults - .slice() - .sort((a, b) => - a.passed === b.passed ? 0 : a.passed ? 1 : -1 - ); + const shouldAssertionResults = scenarioResult.shouldAssertionResults + .slice() + .sort((a, b) => (a.passed === b.passed ? 0 : a.passed ? 1 : -1)); - const mayAssertionResults = scenarioResult.mayAssertionResults - .slice() - .sort((a, b) => - a.passed === b.passed ? 0 : a.passed ? 1 : -1 - ); + const mayAssertionResults = scenarioResult.mayAssertionResults + .slice() + .sort((a, b) => (a.passed === b.passed ? 0 : a.passed ? 1 : -1)); - // Workaround: - // Remove instances of content inside '()' to address edge case of - // COMMAND_TEXT (OPERATING_MODE) then COMMAND_TEXT (OPERATING_MODE). - // OPERATING_MODE should only show once - const bracketsRegex = /\((.*?)\)/g; + // Workaround: + // Remove instances of content inside '()' to address edge case of + // COMMAND_TEXT (OPERATING_MODE) then COMMAND_TEXT (OPERATING_MODE). + // OPERATING_MODE should only show once + const bracketsRegex = /\((.*?)\)/g; - const commandsString = scenarioResult.scenario.commands - .map(({ text }, index) => { - if ( - index !== - scenarioResult.scenario.commands.length - 1 - ) - text = text.replace(bracketsRegex, ''); - return text.trim(); - }) - .join(' then '); + const commandsString = scenarioResult.scenario.commands + .map(({ text }, index) => { + if (index !== scenarioResult.scenario.commands.length - 1) + text = text.replace(bracketsRegex, ''); + return text.trim(); + }) + .join(' then '); - const sortedAssertionResults = [ - ...mustAssertionResults.map(e => ({ - ...e, - priorityString: 'MUST' - })), - { - id: `UnexpectedBehavior_MUST_${nextId()}`, - assertion: { - text: 'Other behaviors that create severe negative impacts are not exhibited' - }, - passed: hasNoSevereUnexpectedBehavior, - priorityString: 'MUST' - }, - ...shouldAssertionResults.map(e => ({ - ...e, - priorityString: 'SHOULD' - })), - { - id: `UnexpectedBehavior_SHOULD_${nextId()}`, - assertion: { - text: 'Other behaviors that create moderate negative impacts are not exhibited' - }, - passed: hasNoModerateUnexpectedBehavior, - priorityString: 'SHOULD' - }, - ...mayAssertionResults.map(e => ({ - ...e, - priorityString: 'MAY' - })) - ].sort((a, b) => a.passed - b.passed); + const sortedAssertionResults = [ + ...mustAssertionResults.map(e => ({ + ...e, + priorityString: 'MUST' + })), + { + id: `UnexpectedBehavior_MUST_${nextId()}`, + assertion: { + text: 'Other behaviors that create severe negative impacts are not exhibited' + }, + passed: hasNoSevereUnexpectedBehavior, + priorityString: 'MUST' + }, + ...shouldAssertionResults.map(e => ({ + ...e, + priorityString: 'SHOULD' + })), + { + id: `UnexpectedBehavior_SHOULD_${nextId()}`, + assertion: { + text: 'Other behaviors that create moderate negative impacts are not exhibited' + }, + passed: hasNoModerateUnexpectedBehavior, + priorityString: 'SHOULD' + }, + ...mayAssertionResults.map(e => ({ + ...e, + priorityString: 'MAY' + })) + ].sort((a, b) => a.passed - b.passed); - return ( - - - {commandsString} Results:  - {assertionsPassedCount} passed,  - {assertionsFailedCount} failed - -

    - {test.at?.name} Response: -

    -
    - {scenarioResult.output} -
    - - - - - - - - - - - {sortedAssertionResults.map(assertionResult => - renderAssertionRow( - assertionResult, - assertionResult.priorityString - ) - )} - -
    {commandsString} Results
    PriorityAssertionResult
    - Other behaviors that create negative impact:{' '} - {scenarioResult.unexpectedBehaviors.length ? ( - - - - - - - - - - {scenarioResult.unexpectedBehaviors.map( - ({ id, text, details, impact }) => ( - - - - - - ) - )} - -
    BehaviorDetailsImpact
    {text}{details}{impact}
    - ) : ( - 'None' - )} - {/* Do not show separator below last item */} - {index !== testResult.scenarioResults.length - 1 ? ( - - ) : null} -
    - ); - })} - - ); + return ( + + + {commandsString} Results:  + {assertionsPassedCount} passed,  + {assertionsFailedCount} failed + +

    + {test.at?.name} Response: +

    +
    + {scenarioResult.output} +
    + + + + + + + + + + + {sortedAssertionResults.map(assertionResult => + renderAssertionRow( + assertionResult, + assertionResult.priorityString + ) + )} + +
    {commandsString} Results
    PriorityAssertionResult
    + Other behaviors that create negative impact:{' '} + {scenarioResult.unexpectedBehaviors.length ? ( + + + + + + + + + + {scenarioResult.unexpectedBehaviors.map( + ({ id, text, details, impact }) => ( + + + + + + ) + )} + +
    BehaviorDetailsImpact
    {text}{details}{impact}
    + ) : ( + 'None' + )} + {/* Do not show separator below last item */} + {index !== testResult.scenarioResults.length - 1 ? ( + + ) : null} +
    + ); + })} + + ); }; TestPlanResultsTable.propTypes = { - test: PropTypes.shape({ - title: PropTypes.string.isRequired, - at: PropTypes.shape({ - name: PropTypes.string.isRequired - }).isRequired - }), - testResult: PropTypes.shape({ - scenarioResults: PropTypes.array.isRequired - }), - tableClassName: PropTypes.string, - optionalHeader: PropTypes.node, - commandHeadingLevel: PropTypes.number + test: PropTypes.shape({ + title: PropTypes.string.isRequired, + at: PropTypes.shape({ + name: PropTypes.string.isRequired + }).isRequired + }), + testResult: PropTypes.shape({ + scenarioResults: PropTypes.array.isRequired + }), + tableClassName: PropTypes.string, + optionalHeader: PropTypes.node, + commandHeadingLevel: PropTypes.number }; export default TestPlanResultsTable; diff --git a/client/components/common/ThemeTable/index.jsx b/client/components/common/ThemeTable/index.jsx index 12d2e739c..aef2a86e1 100644 --- a/client/components/common/ThemeTable/index.jsx +++ b/client/components/common/ThemeTable/index.jsx @@ -2,37 +2,37 @@ import { Table } from 'react-bootstrap'; import styled from '@emotion/styled'; export const ThemeTableHeaderH2 = styled.h2` - background-color: var(--bs-table-bg) !important; - font-size: 1.5rem; - font-weight: 600; - border: solid 1px #d2d5d9; - border-bottom: none; - padding: 0.5rem 1rem; - margin: 0.5rem 0 0 0; + background-color: var(--bs-table-bg) !important; + font-size: 1.5rem; + font-weight: 600; + border: solid 1px #d2d5d9; + border-bottom: none; + padding: 0.5rem 1rem; + margin: 0.5rem 0 0 0; `; export const ThemeTableHeaderH3 = styled.h3` - background-color: var(--bs-table-bg) !important; - font-size: 1.25rem; - font-weight: 600; - border: solid 1px #d2d5d9; - border-bottom: none; - padding: 0.5rem 1rem; - margin: 0.5rem 0 0 0; + background-color: var(--bs-table-bg) !important; + font-size: 1.25rem; + font-weight: 600; + border: solid 1px #d2d5d9; + border-bottom: none; + padding: 0.5rem 1rem; + margin: 0.5rem 0 0 0; `; export const ThemeTable = styled(Table)` - margin-bottom: 0; + margin-bottom: 0; - td, - th { - padding-left: 1rem; - min-width: 165px; - vertical-align: middle; - } + td, + th { + padding-left: 1rem; + min-width: 165px; + vertical-align: middle; + } `; export const ThemeTableUnavailable = styled.div` - border: solid 1px #d2d5d9; - padding: 0.5rem 1rem; + border: solid 1px #d2d5d9; + padding: 0.5rem 1rem; `; diff --git a/client/components/common/UpdateTargetDateModal/index.jsx b/client/components/common/UpdateTargetDateModal/index.jsx index 85105190e..17bc30929 100644 --- a/client/components/common/UpdateTargetDateModal/index.jsx +++ b/client/components/common/UpdateTargetDateModal/index.jsx @@ -6,138 +6,136 @@ import BasicModal from '../BasicModal'; import { convertDateToString, isValidDate } from '../../../utils/formatter'; const ModalInnerSectionContainer = styled.div` - display: flex; - flex-direction: column; + display: flex; + flex-direction: column; `; const UpdateTargetDateModal = ({ - show = false, - title = null, - dateText = '', - handleAction = () => {}, - handleClose = () => {} + show = false, + title = null, + dateText = '', + handleAction = () => {}, + handleClose = () => {} }) => { - const dateTextRef = useRef(); + const dateTextRef = useRef(); - const [updatedDateText, setUpdatedDateText] = useState( - convertDateToString(dateText) - ); - const [isDateError, setIsDateError] = useState(false); + const [updatedDateText, setUpdatedDateText] = useState( + convertDateToString(dateText) + ); + const [isDateError, setIsDateError] = useState(false); - useEffect(() => { - setUpdatedDateText(convertDateToString(dateText)); - }, [dateText]); + useEffect(() => { + setUpdatedDateText(convertDateToString(dateText)); + }, [dateText]); - const handleDateTextChange = e => { - const value = e.target.value; - setIsDateError(false); - setUpdatedDateText(value); - }; + const handleDateTextChange = e => { + const value = e.target.value; + setIsDateError(false); + setUpdatedDateText(value); + }; - const handleDateTextKeyPress = e => { - /** - * Only accept the following ASCII characters: - * 45: - - * 48: 0 - * 49: 1 - * 50: 2 - * 51: 3 - * 52: 4 - * 53: 5 - * 54: 6 - * 55: 7 - * 56: 8 - * 57: 9 - */ - const HYPHEN = 45; - const DIGIT_ZERO = 48; - const DIGIT_NINE = 57; - if ( - (e.charCode < DIGIT_ZERO && e.charCode !== HYPHEN) || - e.charCode > DIGIT_NINE - ) { - e.preventDefault(); - } + const handleDateTextKeyPress = e => { + /** + * Only accept the following ASCII characters: + * 45: - + * 48: 0 + * 49: 1 + * 50: 2 + * 51: 3 + * 52: 4 + * 53: 5 + * 54: 6 + * 55: 7 + * 56: 8 + * 57: 9 + */ + const HYPHEN = 45; + const DIGIT_ZERO = 48; + const DIGIT_NINE = 57; + if ( + (e.charCode < DIGIT_ZERO && e.charCode !== HYPHEN) || + e.charCode > DIGIT_NINE + ) { + e.preventDefault(); + } - let input = e.target; - let inputLength = input.value.length; - if (inputLength !== 1 || inputLength !== 3) { - if (e.charCode === HYPHEN) e.preventDefault(); - } - if (inputLength === 2) input.value += '-'; - if (inputLength === 5) input.value += '-'; - }; + let input = e.target; + let inputLength = input.value.length; + if (inputLength !== 1 || inputLength !== 3) { + if (e.charCode === HYPHEN) e.preventDefault(); + } + if (inputLength === 2) input.value += '-'; + if (inputLength === 5) input.value += '-'; + }; - const onSubmit = () => { - // Passed handleAction prop should account for dateText - const dateTextError = - !updatedDateText || - updatedDateText.length !== 10 || - !isValidDate(updatedDateText); + const onSubmit = () => { + // Passed handleAction prop should account for dateText + const dateTextError = + !updatedDateText || + updatedDateText.length !== 10 || + !isValidDate(updatedDateText); - if (dateTextError) { - setIsDateError(dateTextError); - dateTextRef.current.focus(); - return; - } + if (dateTextError) { + setIsDateError(dateTextError); + dateTextRef.current.focus(); + return; + } - handleAction({ updatedDateText }); - }; + handleAction({ updatedDateText }); + }; - return ( - - - - Target Date - - - {isDateError && ( - - Please enter a valid date. - - )} - - - } - actions={[ - { - label: 'Save', - onClick: onSubmit - } - ]} - handleClose={handleClose} - useOnHide={true} - handleHide={handleClose} - initialFocusRef={dateTextRef} - /> - ); + return ( + + + Target Date + + {isDateError && ( + + Please enter a valid date. + + )} + + + } + actions={[ + { + label: 'Save', + onClick: onSubmit + } + ]} + handleClose={handleClose} + useOnHide={true} + handleHide={handleClose} + initialFocusRef={dateTextRef} + /> + ); }; UpdateTargetDateModal.propTypes = { - show: PropTypes.bool, - title: PropTypes.node.isRequired, - dateText: PropTypes.string, - handleAction: PropTypes.func, - handleClose: PropTypes.func + show: PropTypes.bool, + title: PropTypes.node.isRequired, + dateText: PropTypes.string, + handleAction: PropTypes.func, + handleClose: PropTypes.func }; export default UpdateTargetDateModal; diff --git a/client/components/common/UpdateVersionModal/index.jsx b/client/components/common/UpdateVersionModal/index.jsx index ab64c2401..83a5daad9 100644 --- a/client/components/common/UpdateVersionModal/index.jsx +++ b/client/components/common/UpdateVersionModal/index.jsx @@ -6,178 +6,174 @@ import BasicModal from '../BasicModal'; import { convertDateToString, isValidDate } from '../../../utils/formatter'; const ModalInnerSectionContainer = styled.div` - display: flex; - flex-direction: column; + display: flex; + flex-direction: column; `; const UpdateVersionModal = ({ - show = false, - title = null, - actionType = 'add', // or edit - versionText = '', - dateAvailabilityText = '', - handleAction = () => {}, - handleClose = () => {} + show = false, + title = null, + actionType = 'add', // or edit + versionText = '', + dateAvailabilityText = '', + handleAction = () => {}, + handleClose = () => {} }) => { - const versionTextRef = useRef(); - const dateAvailabilityTextRef = useRef(); - - const [updatedVersionText, setUpdatedVersionText] = useState(versionText); - const [updatedDateAvailabilityText, setUpdatedDateAvailabilityText] = - useState(convertDateToString(dateAvailabilityText)); - const [isVersionError, setIsVersionError] = useState(false); - const [isDateError, setIsDateError] = useState(false); - - useEffect(() => { - setUpdatedVersionText(versionText); - setUpdatedDateAvailabilityText( - convertDateToString(dateAvailabilityText) - ); - }, [versionText, dateAvailabilityText]); - - const handleVersionTextChange = e => { - const value = e.target.value; - setIsVersionError(false); - setUpdatedVersionText(value); - }; - - const handleDateAvailabilityTextChange = e => { - const value = e.target.value; - setIsDateError(false); - setUpdatedDateAvailabilityText(value); - }; - - const handleDateAvailabilityTextKeyPress = e => { - /** - * Only accept the following ASCII characters: - * 45: - - * 48: 0 - * 49: 1 - * 50: 2 - * 51: 3 - * 52: 4 - * 53: 5 - * 54: 6 - * 55: 7 - * 56: 8 - * 57: 9 - */ - const HYPHEN = 45; - const DIGIT_ZERO = 48; - const DIGIT_NINE = 57; - if ( - (e.charCode < DIGIT_ZERO && e.charCode !== HYPHEN) || - e.charCode > DIGIT_NINE - ) { - e.preventDefault(); + const versionTextRef = useRef(); + const dateAvailabilityTextRef = useRef(); + + const [updatedVersionText, setUpdatedVersionText] = useState(versionText); + const [updatedDateAvailabilityText, setUpdatedDateAvailabilityText] = + useState(convertDateToString(dateAvailabilityText)); + const [isVersionError, setIsVersionError] = useState(false); + const [isDateError, setIsDateError] = useState(false); + + useEffect(() => { + setUpdatedVersionText(versionText); + setUpdatedDateAvailabilityText(convertDateToString(dateAvailabilityText)); + }, [versionText, dateAvailabilityText]); + + const handleVersionTextChange = e => { + const value = e.target.value; + setIsVersionError(false); + setUpdatedVersionText(value); + }; + + const handleDateAvailabilityTextChange = e => { + const value = e.target.value; + setIsDateError(false); + setUpdatedDateAvailabilityText(value); + }; + + const handleDateAvailabilityTextKeyPress = e => { + /** + * Only accept the following ASCII characters: + * 45: - + * 48: 0 + * 49: 1 + * 50: 2 + * 51: 3 + * 52: 4 + * 53: 5 + * 54: 6 + * 55: 7 + * 56: 8 + * 57: 9 + */ + const HYPHEN = 45; + const DIGIT_ZERO = 48; + const DIGIT_NINE = 57; + if ( + (e.charCode < DIGIT_ZERO && e.charCode !== HYPHEN) || + e.charCode > DIGIT_NINE + ) { + e.preventDefault(); + } + + let input = e.target; + let inputLength = input.value.length; + if (inputLength !== 1 || inputLength !== 3) { + if (e.charCode === HYPHEN) e.preventDefault(); + } + if (inputLength === 2) input.value += '-'; + if (inputLength === 5) input.value += '-'; + }; + + const onSubmit = () => { + // Passed action prop should account for actionType, versionText and dateAvailabilityText + const versionTextError = !updatedVersionText; + const dateTextError = + !updatedDateAvailabilityText || + updatedDateAvailabilityText.length !== 10 || + !isValidDate(updatedDateAvailabilityText); + + if (versionTextError || dateTextError) { + setIsVersionError(versionTextError); + versionTextRef.current.focus(); + + setIsDateError(dateTextError); + if (!versionTextError) dateAvailabilityTextRef.current.focus(); + return; + } + + handleAction(actionType, { + updatedVersionText, + updatedDateAvailabilityText + }); + }; + + return ( + + + Version Number + + {isVersionError && ( + + Please enter a valid version number. + + )} + + + + Approximate date of availability + + {isDateError && ( + + Please enter a valid date. + + )} + + + } + actions={[ + { + label: actionType === 'add' ? 'Add Version' : 'Save', + onClick: onSubmit } - - let input = e.target; - let inputLength = input.value.length; - if (inputLength !== 1 || inputLength !== 3) { - if (e.charCode === HYPHEN) e.preventDefault(); - } - if (inputLength === 2) input.value += '-'; - if (inputLength === 5) input.value += '-'; - }; - - const onSubmit = () => { - // Passed action prop should account for actionType, versionText and dateAvailabilityText - const versionTextError = !updatedVersionText; - const dateTextError = - !updatedDateAvailabilityText || - updatedDateAvailabilityText.length !== 10 || - !isValidDate(updatedDateAvailabilityText); - - if (versionTextError || dateTextError) { - setIsVersionError(versionTextError); - versionTextRef.current.focus(); - - setIsDateError(dateTextError); - if (!versionTextError) dateAvailabilityTextRef.current.focus(); - return; - } - - handleAction(actionType, { - updatedVersionText, - updatedDateAvailabilityText - }); - }; - - return ( - - - Version Number - - {isVersionError && ( - - Please enter a valid version number. - - )} - - - - - Approximate date of availability - - - {isDateError && ( - - Please enter a valid date. - - )} - - - } - actions={[ - { - label: actionType === 'add' ? 'Add Version' : 'Save', - onClick: onSubmit - } - ]} - handleClose={handleClose} - /> - ); + ]} + handleClose={handleClose} + /> + ); }; UpdateVersionModal.propTypes = { - show: PropTypes.bool, - title: PropTypes.node.isRequired, - actionType: PropTypes.string, - versionText: PropTypes.string, - dateAvailabilityText: PropTypes.string, - onVersionChange: PropTypes.func, - onDateAvailabilityChange: PropTypes.func, - handleAction: PropTypes.func, - handleClose: PropTypes.func + show: PropTypes.bool, + title: PropTypes.node.isRequired, + actionType: PropTypes.string, + versionText: PropTypes.string, + dateAvailabilityText: PropTypes.string, + onVersionChange: PropTypes.func, + onDateAvailabilityChange: PropTypes.func, + handleAction: PropTypes.func, + handleClose: PropTypes.func }; export default UpdateVersionModal; diff --git a/client/components/common/VersionString/index.js b/client/components/common/VersionString/index.js index 5b74cd837..4045d67f0 100644 --- a/client/components/common/VersionString/index.js +++ b/client/components/common/VersionString/index.js @@ -5,93 +5,93 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import styled from '@emotion/styled'; const StyledPill = styled.span` - display: inline-block; + display: inline-block; - line-height: 2rem; - border-radius: 4px; + line-height: 2rem; + border-radius: 4px; - background: #f6f8fa; - white-space: nowrap; - text-align: center; + background: #f6f8fa; + white-space: nowrap; + text-align: center; - // Needed for presenting component on Version History page - &.full-width { - width: 100%; + // Needed for presenting component on Version History page + &.full-width { + width: 100%; - /* Version strings can have different character counts and this keeps + /* Version strings can have different character counts and this keeps them lined up in lists */ - & b { - min-width: 6em; - display: inline-block; - text-align: left; - } + & b { + min-width: 6em; + display: inline-block; + text-align: left; } + } - &:not(.full-width) { - width: 8em; - margin-right: 10px; - } + &:not(.full-width) { + width: 8em; + margin-right: 10px; + } - // Needed for presenting component on Data Management page - // Override full-width's width if both are set - &.auto-width { - width: auto; - margin: 0.75rem; - } + // Needed for presenting component on Data Management page + // Override full-width's width if both are set + &.auto-width { + width: auto; + margin: 0.75rem; + } `; const VersionString = ({ - fullWidth = true, - autoWidth = true, - iconColor = '#818F98', - linkRef, - linkHref, - children: versionString, - ...restProps + fullWidth = true, + autoWidth = true, + iconColor = '#818F98', + linkRef, + linkHref, + children: versionString, + ...restProps }) => { - const body = ( - - - {versionString} - - ); + const body = ( + + + {versionString} + + ); - let possibleLink; - if (linkHref) { - if (linkRef) { - possibleLink = ( - - {body} - - ); - } else { - possibleLink = {body}; - } + let possibleLink; + if (linkHref) { + if (linkRef) { + possibleLink = ( + + {body} + + ); } else { - possibleLink = body; + possibleLink = {body}; } + } else { + possibleLink = body; + } - let classes = fullWidth ? 'full-width' : ''; - classes = autoWidth ? `${classes} auto-width` : classes; + let classes = fullWidth ? 'full-width' : ''; + classes = autoWidth ? `${classes} auto-width` : classes; - return ( - - {possibleLink} - - ); + return ( + + {possibleLink} + + ); }; VersionString.propTypes = { - fullWidth: PropTypes.bool, - autoWidth: PropTypes.bool, - iconColor: PropTypes.string, - linkRef: PropTypes.shape({ current: PropTypes.any }), - linkHref: PropTypes.string, - children: PropTypes.string + fullWidth: PropTypes.bool, + autoWidth: PropTypes.bool, + iconColor: PropTypes.string, + linkRef: PropTypes.shape({ current: PropTypes.any }), + linkHref: PropTypes.string, + children: PropTypes.string }; export default VersionString; diff --git a/client/components/providers/AriaLiveRegionProvider/index.js b/client/components/providers/AriaLiveRegionProvider/index.js index f32de8537..5a66230c0 100644 --- a/client/components/providers/AriaLiveRegionProvider/index.js +++ b/client/components/providers/AriaLiveRegionProvider/index.js @@ -5,39 +5,39 @@ import styled from '@emotion/styled'; const AriaLiveRegionContext = React.createContext(); const VisuallyHiddenAriaLiveRegion = styled.span` - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - width: 1px; + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; `; export const useAriaLiveRegion = () => { - const context = useContext(AriaLiveRegionContext); - if (!context) { - throw new Error( - 'useAriaLiveRegion must be used within an AriaLiveRegionProvider' - ); - } - return context; + const context = useContext(AriaLiveRegionContext); + if (!context) { + throw new Error( + 'useAriaLiveRegion must be used within an AriaLiveRegionProvider' + ); + } + return context; }; export const AriaLiveRegionProvider = ({ children }) => { - const [alertMessage, setAlertMessage] = useState(''); + const [alertMessage, setAlertMessage] = useState(''); - return ( - - {children} - - {alertMessage} - - - ); + return ( + + {children} + + {alertMessage} + + + ); }; AriaLiveRegionProvider.propTypes = { - children: PropTypes.node.isRequired + children: PropTypes.node.isRequired }; diff --git a/client/hooks/useConfirmationModal.js b/client/hooks/useConfirmationModal.js index a14bbe56e..983e28192 100644 --- a/client/hooks/useConfirmationModal.js +++ b/client/hooks/useConfirmationModal.js @@ -5,38 +5,38 @@ import useForceUpdate from './useForceUpdate'; const ConfirmationContext = createContext(); const ConfirmationModalProvider = ({ children }) => { - const forceUpdate = useForceUpdate(); - const modalContent = useRef(); - - const showConfirmationModal = newModalContent => { - modalContent.current = newModalContent; - forceUpdate(); - }; - - const hideConfirmationModal = async () => { - modalContent.current = null; - forceUpdate(); - }; - - return ( - - {children} - {modalContent.current} - - ); + const forceUpdate = useForceUpdate(); + const modalContent = useRef(); + + const showConfirmationModal = newModalContent => { + modalContent.current = newModalContent; + forceUpdate(); + }; + + const hideConfirmationModal = async () => { + modalContent.current = null; + forceUpdate(); + }; + + return ( + + {children} + {modalContent.current} + + ); }; ConfirmationModalProvider.propTypes = { - children: PropTypes.node.isRequired + children: PropTypes.node.isRequired }; const useConfirmationModal = () => { - const { showConfirmationModal, hideConfirmationModal } = - useContext(ConfirmationContext); + const { showConfirmationModal, hideConfirmationModal } = + useContext(ConfirmationContext); - return { showConfirmationModal, hideConfirmationModal }; + return { showConfirmationModal, hideConfirmationModal }; }; export { ConfirmationModalProvider }; diff --git a/client/hooks/useDetectUa.js b/client/hooks/useDetectUa.js index 3a6e787a5..b335684a8 100644 --- a/client/hooks/useDetectUa.js +++ b/client/hooks/useDetectUa.js @@ -2,24 +2,24 @@ import { useState, useEffect } from 'react'; import uaParser from 'ua-parser-js'; export function useDetectUa() { - const [uaBrowser, setUaBrowser] = useState(); - const [uaMajor, setUaMajor] = useState(); - const [uaMinor, setUaMinor] = useState(); - const [uaPatch, setUaPatch] = useState(); + const [uaBrowser, setUaBrowser] = useState(); + const [uaMajor, setUaMajor] = useState(); + const [uaMinor, setUaMinor] = useState(); + const [uaPatch, setUaPatch] = useState(); - useEffect(() => { - // Detect UA information - const ua = uaParser(); - const uaBrowser = ua?.browser?.name || 'Unknown'; - const uaMajor = ua?.browser?.major || '0'; - const uaMinor = ua?.browser?.version?.split('.')?.[1] || '0'; - const uaPatch = ua?.browser?.version?.split('.')?.[2] || '0'; + useEffect(() => { + // Detect UA information + const ua = uaParser(); + const uaBrowser = ua?.browser?.name || 'Unknown'; + const uaMajor = ua?.browser?.major || '0'; + const uaMinor = ua?.browser?.version?.split('.')?.[1] || '0'; + const uaPatch = ua?.browser?.version?.split('.')?.[2] || '0'; - setUaBrowser(uaBrowser); - setUaMajor(uaMajor); - setUaMinor(uaMinor); - setUaPatch(uaPatch); - }); + setUaBrowser(uaBrowser); + setUaMajor(uaMajor); + setUaMinor(uaMinor); + setUaPatch(uaPatch); + }); - return { uaBrowser, uaMajor, uaMinor, uaPatch }; + return { uaBrowser, uaMajor, uaMinor, uaPatch }; } diff --git a/client/hooks/useForceUpdate.js b/client/hooks/useForceUpdate.js index 923186af9..d74a3cff5 100644 --- a/client/hooks/useForceUpdate.js +++ b/client/hooks/useForceUpdate.js @@ -2,9 +2,9 @@ import React from 'react'; // https://stackoverflow.com/a/53215514 const useForceUpdate = () => { - const [, updateState] = React.useState(); - const forceUpdate = React.useCallback(() => updateState({}), []); - return forceUpdate; + const [, updateState] = React.useState(); + const forceUpdate = React.useCallback(() => updateState({}), []); + return forceUpdate; }; export default useForceUpdate; diff --git a/client/hooks/useRouterQuery.js b/client/hooks/useRouterQuery.js index e9fd2f3ce..5c174b900 100644 --- a/client/hooks/useRouterQuery.js +++ b/client/hooks/useRouterQuery.js @@ -1,5 +1,5 @@ import { useLocation } from 'react-router-dom'; export default function useRouterQuery() { - return new URLSearchParams(useLocation().search); + return new URLSearchParams(useLocation().search); } diff --git a/client/hooks/useTestPlanRunIsFinished.js b/client/hooks/useTestPlanRunIsFinished.js index 2a32b27d0..af1909796 100644 --- a/client/hooks/useTestPlanRunIsFinished.js +++ b/client/hooks/useTestPlanRunIsFinished.js @@ -3,36 +3,36 @@ import { useMemo } from 'react'; const { gql, useQuery } = require('@apollo/client'); const TEST_PLAN_RUN_TEST_RESULTS_COMPLETION_STATUS = gql` - query TestPlanRunTestResultsCompletionStatus($testPlanRunId: ID!) { - testPlanRun(id: $testPlanRunId) { - id - testResults { - id - completedAt - } - } + query TestPlanRunTestResultsCompletionStatus($testPlanRunId: ID!) { + testPlanRun(id: $testPlanRunId) { + id + testResults { + id + completedAt + } } + } `; export const useTestPlanRunIsFinished = testPlanRunId => { - const { data: testPlanRunCompletionQuery } = useQuery( - TEST_PLAN_RUN_TEST_RESULTS_COMPLETION_STATUS, - { - variables: { - testPlanRunId - }, - fetchPolicy: 'cache-and-network' - } - ); + const { data: testPlanRunCompletionQuery } = useQuery( + TEST_PLAN_RUN_TEST_RESULTS_COMPLETION_STATUS, + { + variables: { + testPlanRunId + }, + fetchPolicy: 'cache-and-network' + } + ); - const runIsFinished = useMemo(() => { - if (!testPlanRunCompletionQuery?.testPlanRun?.testResults.length) { - return false; - } + const runIsFinished = useMemo(() => { + if (!testPlanRunCompletionQuery?.testPlanRun?.testResults.length) { + return false; + } - return testPlanRunCompletionQuery.testPlanRun.testResults.every( - testResult => testResult.completedAt !== null - ); - }, [testPlanRunId, testPlanRunCompletionQuery]); + return testPlanRunCompletionQuery.testPlanRun.testResults.every( + testResult => testResult.completedAt !== null + ); + }, [testPlanRunId, testPlanRunCompletionQuery]); - return { runIsFinished }; + return { runIsFinished }; }; diff --git a/client/hooks/useTestPlanRunValidatedAssertionCounts.js b/client/hooks/useTestPlanRunValidatedAssertionCounts.js index 6e229213f..64bb738a4 100644 --- a/client/hooks/useTestPlanRunValidatedAssertionCounts.js +++ b/client/hooks/useTestPlanRunValidatedAssertionCounts.js @@ -3,85 +3,84 @@ import { useQuery } from '@apollo/client'; import { TEST_PLAN_RUN_ASSERTION_RESULTS_QUERY } from '../components/TestQueueCompletionStatusListItem/queries'; export const useTestPlanRunValidatedAssertionCounts = ( - testPlanRun, - pollInterval = null + testPlanRun, + pollInterval = null ) => { - const { - data: testPlanRunAssertionsQueryResult, - startPolling, - stopPolling, - refetch - } = useQuery(TEST_PLAN_RUN_ASSERTION_RESULTS_QUERY, { - variables: { - testPlanRunId: testPlanRun.id - }, - fetchPolicy: 'cache-and-network', - pollInterval - }); - - const testResultsLength = useMemo(() => { - return ( - testPlanRunAssertionsQueryResult?.testPlanRun?.testResults - ?.length || 0 - ); - }, [testPlanRunAssertionsQueryResult]); + const { + data: testPlanRunAssertionsQueryResult, + startPolling, + stopPolling, + refetch + } = useQuery(TEST_PLAN_RUN_ASSERTION_RESULTS_QUERY, { + variables: { + testPlanRunId: testPlanRun.id + }, + fetchPolicy: 'cache-and-network', + pollInterval + }); - const totalPossibleAssertions = useMemo(() => { - if (testPlanRunAssertionsQueryResult) { - let count = 0; - if (!testPlanRunAssertionsQueryResult?.testPlanRun) { - return 0; - } - const { - testPlanRun: { testResults } - } = testPlanRunAssertionsQueryResult; - for (let i = 0; i < testResults.length; i++) { - const scenarios = testResults[i].scenarioResults; - for (let j = 0; j < scenarios.length; j++) { - const assertions = scenarios[j].assertionResults; - count += assertions.length; - } - } - return count; - } else { - return 0; - } - }, [testPlanRunAssertionsQueryResult]); + const testResultsLength = useMemo(() => { + return ( + testPlanRunAssertionsQueryResult?.testPlanRun?.testResults?.length || 0 + ); + }, [testPlanRunAssertionsQueryResult]); - const totalValidatedAssertions = useMemo(() => { - if (!testPlanRunAssertionsQueryResult?.testPlanRun) { - return 0; + const totalPossibleAssertions = useMemo(() => { + if (testPlanRunAssertionsQueryResult) { + let count = 0; + if (!testPlanRunAssertionsQueryResult?.testPlanRun) { + return 0; + } + const { + testPlanRun: { testResults } + } = testPlanRunAssertionsQueryResult; + for (let i = 0; i < testResults.length; i++) { + const scenarios = testResults[i].scenarioResults; + for (let j = 0; j < scenarios.length; j++) { + const assertions = scenarios[j].assertionResults; + count += assertions.length; } - - const { - testPlanRun: { testResults } - } = testPlanRunAssertionsQueryResult; - return testResults.reduce((acc, test) => { - return ( - acc + - (test.completedAt - ? test.scenarioResults.reduce((acc, scenario) => { - return acc + scenario.assertionResults.length; - }, 0) - : 0) - ); - }, 0); - }, [testPlanRunAssertionsQueryResult]); - if (pollInterval) { - return { - testResultsLength, - totalPossibleAssertions, - totalValidatedAssertions, - refetch, - stopPolling, - startPolling - }; + } + return count; } else { - return { - testResultsLength, - totalPossibleAssertions, - totalValidatedAssertions, - refetch - }; + return 0; } + }, [testPlanRunAssertionsQueryResult]); + + const totalValidatedAssertions = useMemo(() => { + if (!testPlanRunAssertionsQueryResult?.testPlanRun) { + return 0; + } + + const { + testPlanRun: { testResults } + } = testPlanRunAssertionsQueryResult; + return testResults.reduce((acc, test) => { + return ( + acc + + (test.completedAt + ? test.scenarioResults.reduce((acc, scenario) => { + return acc + scenario.assertionResults.length; + }, 0) + : 0) + ); + }, 0); + }, [testPlanRunAssertionsQueryResult]); + if (pollInterval) { + return { + testResultsLength, + totalPossibleAssertions, + totalValidatedAssertions, + refetch, + stopPolling, + startPolling + }; + } else { + return { + testResultsLength, + totalPossibleAssertions, + totalValidatedAssertions, + refetch + }; + } }; diff --git a/client/hooks/useThemedModal.js b/client/hooks/useThemedModal.js index f058f474b..a4bca0901 100644 --- a/client/hooks/useThemedModal.js +++ b/client/hooks/useThemedModal.js @@ -2,84 +2,84 @@ import React, { useState, useRef, useEffect } from 'react'; import BasicThemedModal from '@components/common/BasicThemedModal'; const THEMES = { - WARNING: 'warning', - DANGER: 'danger' + WARNING: 'warning', + DANGER: 'danger' }; function useThemedModal({ show, type, title, content }) { - const focusElementRef = useRef(); + const focusElementRef = useRef(); - const [showThemedModal, setShowThemedModal] = useState(false); - const [themedModalType, setThemedModalType] = useState(THEMES.WARNING); - const [themedModalTitle, setThemedModalTitle] = useState(''); - const [themedModalContent, setThemedModalContent] = useState(<>); - const [themedModalActions, setThemedModalActions] = useState(null); - const [themedModalShowCloseAction, setThemedModalShowCloseAction] = - useState(false); + const [showThemedModal, setShowThemedModal] = useState(false); + const [themedModalType, setThemedModalType] = useState(THEMES.WARNING); + const [themedModalTitle, setThemedModalTitle] = useState(''); + const [themedModalContent, setThemedModalContent] = useState(<>); + const [themedModalActions, setThemedModalActions] = useState(null); + const [themedModalShowCloseAction, setThemedModalShowCloseAction] = + useState(false); - useEffect(() => { - setShowThemedModal(showThemedModal || show); - setThemedModalType(themedModalType || type); - setThemedModalTitle(themedModalTitle || title); - setThemedModalContent(themedModalContent || content); - }); + useEffect(() => { + setShowThemedModal(showThemedModal || show); + setThemedModalType(themedModalType || type); + setThemedModalTitle(themedModalTitle || title); + setThemedModalContent(themedModalContent || content); + }); - const hideThemedModal = () => { - setShowThemedModal(false); - setThemedModalType(THEMES.WARNING); - setThemedModalTitle(''); - setThemedModalContent(<>); - setThemedModalActions(null); - setThemedModalShowCloseAction(false); - }; + const hideThemedModal = () => { + setShowThemedModal(false); + setThemedModalType(THEMES.WARNING); + setThemedModalTitle(''); + setThemedModalContent(<>); + setThemedModalActions(null); + setThemedModalShowCloseAction(false); + }; - const onThemedModalClose = () => { - setShowThemedModal(false); - if (focusElementRef.current) focusElementRef.current.focus(); - }; + const onThemedModalClose = () => { + setShowThemedModal(false); + if (focusElementRef.current) focusElementRef.current.focus(); + }; - const setFocusRef = focusElement => - (focusElementRef.current = focusElement?.current || focusElement); + const setFocusRef = focusElement => + (focusElementRef.current = focusElement?.current || focusElement); - const focus = () => { - if (focusElementRef.current) focusElementRef.current.focus(); - }; + const focus = () => { + if (focusElementRef.current) focusElementRef.current.focus(); + }; - const themedModal = ( - - ); + const themedModal = ( + + ); - return { - themedModal, - showThemedModal, - setShowThemedModal, - setThemedModalType, - setThemedModalTitle, - setThemedModalContent, - setThemedModalActions, - setThemedModalShowCloseAction, - focus, - setFocusRef, - hideThemedModal - }; + return { + themedModal, + showThemedModal, + setShowThemedModal, + setThemedModalType, + setThemedModalTitle, + setThemedModalContent, + setThemedModalActions, + setThemedModalShowCloseAction, + focus, + setFocusRef, + hideThemedModal + }; } export { useThemedModal, THEMES }; diff --git a/client/index.js b/client/index.js index 537cd22a8..5d19d83a4 100644 --- a/client/index.js +++ b/client/index.js @@ -13,61 +13,61 @@ const container = document.getElementById('root'); const root = createRoot(container); root.render( - - - - - - - - - + + + + + + + + + ); const signMeInCommon = async user => { - if (!user.username) throw new Error('Please provide a username'); + if (!user.username) throw new Error('Please provide a username'); - const response = await fetch('/api/auth/fake-user', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(user) - }); + const response = await fetch('/api/auth/fake-user', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(user) + }); - const responseText = await response.text(); - if (!response.ok) throw responseText; + const responseText = await response.text(); + if (!response.ok) throw responseText; - location.reload(); + location.reload(); }; window.signMeInAsAdmin = username => { - return signMeInCommon({ - username, - roles: [{ name: 'ADMIN' }, { name: 'TESTER' }, { name: 'VENDOR' }] - }); + return signMeInCommon({ + username, + roles: [{ name: 'ADMIN' }, { name: 'TESTER' }, { name: 'VENDOR' }] + }); }; window.signMeInAsTester = username => { - return signMeInCommon({ username, roles: [{ name: 'TESTER' }] }); + return signMeInCommon({ username, roles: [{ name: 'TESTER' }] }); }; window.signMeInAsVendor = username => { - return signMeInCommon({ username, roles: [{ name: 'VENDOR' }] }); + return signMeInCommon({ username, roles: [{ name: 'VENDOR' }] }); }; window.startTestTransaction = async () => { - const response = await fetch('/api/transactions', { method: 'POST' }); - const { transactionId } = await response.json(); - sessionStorage.setItem('currentTransactionId', transactionId); + const response = await fetch('/api/transactions', { method: 'POST' }); + const { transactionId } = await response.json(); + sessionStorage.setItem('currentTransactionId', transactionId); }; window.endTestTransaction = async () => { - const currentTransactionId = sessionStorage.getItem('currentTransactionId'); - if (!currentTransactionId) throw new Error('Nothing to roll back'); - await fetch('/api/transactions', { - method: 'DELETE', - headers: { 'x-transaction-id': currentTransactionId } - }); - sessionStorage.removeItem('currentTransactionId'); - await resetCache(); - location.reload(); + const currentTransactionId = sessionStorage.getItem('currentTransactionId'); + if (!currentTransactionId) throw new Error('Nothing to roll back'); + await fetch('/api/transactions', { + method: 'DELETE', + headers: { 'x-transaction-id': currentTransactionId } + }); + sessionStorage.removeItem('currentTransactionId'); + await resetCache(); + location.reload(); }; diff --git a/client/jest.config.js b/client/jest.config.js index 6a84408f2..f9f6b9cf3 100644 --- a/client/jest.config.js +++ b/client/jest.config.js @@ -1,20 +1,20 @@ module.exports = { - moduleNameMapper: { - '^@client(.*)': '/$1', - '^@components(.*)': '/components/$1', - '\\.(css|less)$': '/tests/__mocks__/styleMock.js', - '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': - '/tests/__mocks__/fileMock.js' - }, - setupFiles: ['core-js'], - setupFilesAfterEnv: ['/tests/util/jestSuiteSetup.js'], - globalSetup: '/tests/util/jestGlobalSetup.js', - globalTeardown: '/tests/util/jestGlobalTeardown.js', - testEnvironment: '/tests/util/jestPuppeteer.js', - testTimeout: 60000, - transform: { - '^.+\\.(js|jsx|mjs)$': 'babel-jest' - }, - testPathIgnorePatterns: ['/node_modules/', '/resources/'], - coveragePathIgnorePatterns: ['/resources/'] + moduleNameMapper: { + '^@client(.*)': '/$1', + '^@components(.*)': '/components/$1', + '\\.(css|less)$': '/tests/__mocks__/styleMock.js', + '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': + '/tests/__mocks__/fileMock.js' + }, + setupFiles: ['core-js'], + setupFilesAfterEnv: ['/tests/util/jestSuiteSetup.js'], + globalSetup: '/tests/util/jestGlobalSetup.js', + globalTeardown: '/tests/util/jestGlobalTeardown.js', + testEnvironment: '/tests/util/jestPuppeteer.js', + testTimeout: 60000, + transform: { + '^.+\\.(js|jsx|mjs)$': 'babel-jest' + }, + testPathIgnorePatterns: ['/node_modules/', '/resources/'], + coveragePathIgnorePatterns: ['/resources/'] }; diff --git a/client/package.json b/client/package.json index 233d87e81..1e167c527 100644 --- a/client/package.json +++ b/client/package.json @@ -23,43 +23,38 @@ }, "homepage": "https://github.com/bocoup/aria-at-app#readme", "dependencies": { - "@apollo/client": "^3.7.9", - "@emotion/core": "^11.0.0", - "@emotion/react": "^11.10.6", - "@emotion/styled": "^11.10.6", - "@fortawesome/fontawesome-svg-core": "^6.2.1", - "@fortawesome/free-solid-svg-icons": "^6.2.1", - "@fortawesome/react-fontawesome": "^0.2.0", - "bootstrap": "^5.2.3", - "core-js": "^3.8.0", - "graphql": "^16.6.0", + "@apollo/client": "^3.10.8", + "@emotion/react": "^11.11.4", + "@emotion/styled": "^11.11.5", + "@fortawesome/fontawesome-svg-core": "^6.5.2", + "@fortawesome/free-solid-svg-icons": "^6.5.2", + "@fortawesome/react-fontawesome": "^0.2.2", + "bootstrap": "^5.3.3", + "core-js": "^3.37.1", + "graphql": "^16.9.0", "lodash": "^4.17.21", - "moment": "^2.25.3", - "node-fetch": "^2.6.1", - "object-hash": "^3.0.0", - "octicons-react": "^1.0.4", - "prop-types": "^15.7.2", - "react": "^18.2.0", - "react-bootstrap": "^2.7.0", - "react-dom": "^18.2.0", - "react-helmet": "^6.0.0", + "moment": "^2.30.1", + "node-fetch": "^2.7.0", + "octicons-react": "^1.0.9", + "prop-types": "^15.8.1", + "react": "^18.3.1", + "react-bootstrap": "^2.10.4", + "react-dom": "^18.3.1", + "react-helmet": "^6.1.0", "react-html-parser": "^2.0.2", - "react-id-generator": "^3.0.0", - "react-responsive": "^9.0.0-beta.10", - "react-router-bootstrap": "^0.26.2", - "react-router-dom": "^6.8.1", + "react-id-generator": "^3.0.2", + "react-responsive": "^10.0.0", + "react-router-bootstrap": "^0.26.3", + "react-router-dom": "^6.24.1", "shared": "1.0.0", - "turndown": "^7.1.1", - "ua-parser-js": "1.0.33" + "turndown": "^7.2.0", + "ua-parser-js": "1.0.38" }, "devDependencies": { - "@babel/code-frame": "^7.8.3", - "@babel/core": "^7.21.4", - "@babel/plugin-proposal-class-properties": "^7.10.1", - "@babel/plugin-transform-runtime": "^7.21.0", - "@babel/preset-env": "^7.8.7", - "@babel/preset-react": "^7.8.3", - "@lhci/cli": "^0.11.0", + "@babel/core": "^7.24.7", + "@babel/preset-env": "^7.24.7", + "@babel/preset-react": "^7.24.7", + "@lhci/cli": "^0.11.1", "@storybook/addon-a11y": "^6.5.16", "@storybook/addon-actions": "^6.5.16", "@storybook/addon-controls": "^6.5.16", @@ -68,41 +63,38 @@ "@storybook/builder-webpack5": "^6.5.16", "@storybook/manager-webpack5": "^6.5.16", "@storybook/react": "^6.5.16", - "@testing-library/dom": "^9.0.0", - "@testing-library/jest-dom": "^5.16.5", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.4.3", - "babel-jest": "^29.4.3", - "babel-loader": "^9.1.2", + "@testing-library/jest-dom": "^6.4.6", + "@testing-library/react": "^16.0.0", + "babel-jest": "^29.7.0", + "babel-loader": "^9.1.3", "babel-plugin-lodash": "^3.3.4", "babel-polyfill": "^6.26.0", "copy-webpack-plugin": "^11.0.0", - "css-loader": "^6.7.3", - "dotenv-webpack": "^8.0.1", + "css-loader": "^6.11.0", + "dotenv-webpack": "^8.1.0", "enzyme": "^3.11.0", - "enzyme-adapter-react-16": "^1.15.2", - "eslint": "^8.31.0", - "eslint-config-prettier": "^8.6.0", - "eslint-plugin-jest": "^27.2.1", + "enzyme-adapter-react-16": "^1.15.8", + "eslint": "^8.57.0", + "eslint-config-prettier": "^8.10.0", + "eslint-plugin-jest": "^27.9.0", "eslint-plugin-json": "^3.1.0", "eslint-plugin-prettier": "^4.2.1", - "eslint-plugin-react": "^7.31.11", + "eslint-plugin-react": "^7.34.3", "file-loader": "^6.0.0", - "jest": "^29.4.1", - "jest-environment-jsdom": "^29.3.1", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", "jest-enzyme": "^7.1.2", - "moxios": "^0.4.0", - "prettier": "^2.8.4", - "puppeteer": "^21.7.0", - "storybook-addon-apollo-client": "^4.0.12", + "prettier": "^2.8.8", + "puppeteer": "^21.11.0", + "storybook-addon-apollo-client": "^4.1.4", "storybook-react-router": "^1.0.8", - "style-loader": "^3.3.1", + "style-loader": "^3.3.4", "tree-kill": "^1.2.2", - "webpack": "^5.75.0", - "webpack-cli": "^5.0.1", - "webpack-dev-server": "^4.11.1" + "webpack": "^5.92.1", + "webpack-cli": "^5.1.4", + "webpack-dev-server": "^4.15.2" }, "resolutions": { - "jest-environment-jsdom": "^29.3.1" + "jest-environment-jsdom": "^29.7.0" } } diff --git a/client/routes/index.js b/client/routes/index.js index cab1ab6f7..ffb92a6cc 100644 --- a/client/routes/index.js +++ b/client/routes/index.js @@ -8,7 +8,6 @@ import { Reports, Report } from '@components/Reports'; import CandidateReview from '@components/CandidateReview'; import SignupInstructions from '@components/SignupInstructions'; import TestQueue from '@components/TestQueue'; -import TestQueue2 from '@components/TestQueue2'; import TestRun from '@components/TestRun'; import UserSettings from '@components/UserSettings'; import CandidateTestPlanRun from '@components/CandidateReview/CandidateTestPlanRun'; @@ -17,72 +16,66 @@ import TestPlanVersionsPage from '../components/TestPlanVersionsPage'; import TestReview from '../components/TestReview'; export default () => ( - - } /> - } - /> - - - - } - /> - - - - } - /> - {/* TODO: Deprecate and remove */} - } /> - } /> - } - /> - - - - } - /> - } - /> - } /> - } /> - - - - } - /> - } /> - } - /> - } /> - } /> - } /> - + + } /> + } /> + + + + } + /> + + + + } + /> + } /> + } + /> + + + + } + /> + } + /> + } /> + } /> + + + + } + /> + } /> + } + /> + } /> + } /> + } /> + ); diff --git a/client/static/index.css b/client/static/index.css index ae5656889..1794ae56b 100644 --- a/client/static/index.css +++ b/client/static/index.css @@ -1,6 +1,6 @@ html, body { - width: 100%; - height: 100vh; - background: #eaeaea; + width: 100%; + height: 100vh; + background: #eaeaea; } diff --git a/client/stories/ProvideFeedbackModal.stories.jsx b/client/stories/ProvideFeedbackModal.stories.jsx index a2ed6ea9f..8abfee2b9 100644 --- a/client/stories/ProvideFeedbackModal.stories.jsx +++ b/client/stories/ProvideFeedbackModal.stories.jsx @@ -2,37 +2,37 @@ import React from 'react'; import ProvideFeedbackModal from '../components/CandidateReview/CandidateModals/ProvideFeedbackModal'; export default { - component: ProvideFeedbackModal, - title: 'ProvideFeedbackModal' + component: ProvideFeedbackModal, + title: 'ProvideFeedbackModal' }; const Template = args => ; export const Default = Template.bind({}); Default.args = { - at: 'JAWS', - changesRequestedIssues: [ - { author: 'evmiguel', feedbackType: 'feedback', link: 'link to issue' }, - { author: 'evmiguel', feedbackType: 'feedback', link: 'link to issue' } - ], - feedbackIssues: [ - { - author: 'evmiguel', - feedbackType: 'changes-requested', - link: 'link to issue' - }, - { - author: 'evmiguel', - feedbackType: 'changes-requested', - link: 'link to issue' - }, - { - author: 'evmiguel', - feedbackType: 'changes-requested', - link: 'link to issue' - } - ], - show: true, - testPlan: 'Disclosure Navigation Example', - username: 'evmiguel' + at: 'JAWS', + changesRequestedIssues: [ + { author: 'evmiguel', feedbackType: 'feedback', link: 'link to issue' }, + { author: 'evmiguel', feedbackType: 'feedback', link: 'link to issue' } + ], + feedbackIssues: [ + { + author: 'evmiguel', + feedbackType: 'changes-requested', + link: 'link to issue' + }, + { + author: 'evmiguel', + feedbackType: 'changes-requested', + link: 'link to issue' + }, + { + author: 'evmiguel', + feedbackType: 'changes-requested', + link: 'link to issue' + } + ], + show: true, + testPlan: 'Disclosure Navigation Example', + username: 'evmiguel' }; diff --git a/client/stories/ThankYouModal.stories.jsx b/client/stories/ThankYouModal.stories.jsx index 1c6828b62..c6ad4cd77 100644 --- a/client/stories/ThankYouModal.stories.jsx +++ b/client/stories/ThankYouModal.stories.jsx @@ -2,13 +2,13 @@ import React from 'react'; import ThankYouModal from '../components/CandidateReview/CandidateModals/ThankYouModal/index.jsx'; export default { - component: ThankYouModal, - title: 'ThankYouModal' + component: ThankYouModal, + title: 'ThankYouModal' }; const Template = args => ; export const Default = Template.bind({}); Default.args = { - show: true + show: true }; diff --git a/client/tests/AddTestToQueueWithConfirmation.test.jsx b/client/tests/AddTestToQueueWithConfirmation.test.jsx index 9a6fa86be..71acd9c07 100644 --- a/client/tests/AddTestToQueueWithConfirmation.test.jsx +++ b/client/tests/AddTestToQueueWithConfirmation.test.jsx @@ -17,112 +17,110 @@ jest.mock('@apollo/client'); let mutationMock; let mockTestPlanVersion, - mockBrowser, - mockAt, - mockButtonText, - getByTestId, - findByRole; + mockBrowser, + mockAt, + mockButtonText, + getByTestId, + findByRole; const mockTestPlanReportsQueryResult = { - testPlanVersion: { - testPlanReports: [ - { - id: 'report1', - testPlanRun: { - id: 'testPlanRunId', - isInitiatedByAutomation: false, - markedFinalAt: null - }, - isFinal: false, - at: { - id: '1', - key: 'jaws' - }, - browser: { - id: '2', - key: 'firefox' - } - } - ] - } + testPlanVersion: { + testPlanReports: [ + { + id: 'report1', + testPlanRun: { + id: 'testPlanRunId', + isInitiatedByAutomation: false, + markedFinalAt: null + }, + isFinal: false, + at: { + id: '1', + key: 'jaws' + }, + browser: { + id: '2', + key: 'firefox' + } + } + ] + } }; const setup = (props, mockMutation) => { - useMutation.mockReturnValue([mockMutation, {}]); - useQuery.mockReturnValue({ - data: mockTestPlanReportsQueryResult - }); - return render( - - - - - - ); + useMutation.mockReturnValue([mockMutation, {}]); + useQuery.mockReturnValue({ + data: mockTestPlanReportsQueryResult + }); + return render( + + + + + + ); }; const commonSetup = mockMutation => { - mockTestPlanVersion = { id: 5 }; - mockBrowser = { id: '2', key: 'firefox', name: 'Firefox' }; - mockAt = { id: '3', key: 'voiceover_macos', name: 'VoiceOver' }; - mockButtonText = 'Add to Test Queue'; - - const renderResult = setup( - { - testPlanVersion: mockTestPlanVersion, - browser: mockBrowser, - at: mockAt, - buttonText: mockButtonText - }, - mockMutation - ); - - getByTestId = renderResult.getByTestId; - findByRole = renderResult.findByRole; + mockTestPlanVersion = { id: 5 }; + mockBrowser = { id: '2', key: 'firefox', name: 'Firefox' }; + mockAt = { id: '3', key: 'voiceover_macos', name: 'VoiceOver' }; + mockButtonText = 'Add to Test Queue'; + + const renderResult = setup( + { + testPlanVersion: mockTestPlanVersion, + browser: mockBrowser, + at: mockAt, + buttonText: mockButtonText + }, + mockMutation + ); + + getByTestId = renderResult.getByTestId; + findByRole = renderResult.findByRole; }; describe('AddTestToQueueWithConfirmation', () => { - beforeEach(() => { - mutationMock = jest.fn().mockResolvedValue(TEST_QUEUE_MUTATION_MOCK); - commonSetup(mutationMock); - }); - - test('renders Button without error', async () => { - await waitFor(() => - expect(getByTestId('add-button')).toBeInTheDocument() - ); - }); - - test('Button has correct text', async () => { - await waitFor(() => - expect(getByTestId('add-button')).toHaveTextContent(mockButtonText) - ); - }); + beforeEach(() => { + mutationMock = jest.fn().mockResolvedValue(TEST_QUEUE_MUTATION_MOCK); + commonSetup(mutationMock); + }); + + test('renders Button without error', async () => { + await waitFor(() => expect(getByTestId('add-button')).toBeInTheDocument()); + }); + + test('Button has correct text', async () => { + await waitFor(() => + expect(getByTestId('add-button')).toHaveTextContent(mockButtonText) + ); + }); - test('renders BasicModal without error', async () => { - fireEvent.click(getByTestId('add-button')); + test('renders BasicModal without error', async () => { + fireEvent.click(getByTestId('add-button')); - await waitFor(async () => { - const modal = await findByRole('dialog'); - expect(modal).toBeInTheDocument(); - }); + await waitFor(async () => { + const modal = await findByRole('dialog'); + expect(modal).toBeInTheDocument(); }); - - test('calls mutation on button click with correct variables', async () => { - fireEvent.click(getByTestId('add-button')); - - await waitFor(() => { - expect(mutationMock).toHaveBeenCalled(); - expect(mutationMock).toHaveBeenCalledWith({ - variables: { - testPlanVersionId: mockTestPlanVersion.id, - atId: mockAt.id, - browserId: mockBrowser.id - } - }); - }); + }); + + test('calls mutation on button click with correct variables', async () => { + fireEvent.click(getByTestId('add-button')); + + await waitFor(() => { + expect(mutationMock).toHaveBeenCalled(); + expect(mutationMock).toHaveBeenCalledWith({ + variables: { + testPlanVersionId: mockTestPlanVersion.id, + atId: mockAt.id, + browserId: mockBrowser.id + } + }); }); + }); }); diff --git a/client/tests/App.test.jsx b/client/tests/App.test.jsx index 45b5c66e1..8d8400f69 100644 --- a/client/tests/App.test.jsx +++ b/client/tests/App.test.jsx @@ -11,15 +11,15 @@ import GraphQLProvider from '../components/GraphQLProvider'; Enzyme.configure({ adapter: new EnzymeAdapter() }); const setup = () => { - const wrapper = shallow( - - - - ).dive(); - return wrapper; + const wrapper = shallow( + + + + ).dive(); + return wrapper; }; test('renders without crashing', () => { - const wrapper = setup(); - expect(wrapper).toBeTruthy(); + const wrapper = setup(); + expect(wrapper).toBeTruthy(); }); diff --git a/client/tests/AssignTesterDropdown.test.jsx b/client/tests/AssignTesterDropdown.test.jsx index 1768b0867..4fffe9257 100644 --- a/client/tests/AssignTesterDropdown.test.jsx +++ b/client/tests/AssignTesterDropdown.test.jsx @@ -1,368 +1,355 @@ /** * @jest-environment jsdom */ -import React from 'react'; +import React, { act } from 'react'; import { - render, - fireEvent, - waitFor, - screen, - cleanup + render, + fireEvent, + waitFor, + screen, + cleanup } from '@testing-library/react'; import { MockedProvider } from '@apollo/client/testing'; -import AssignTesterDropdown from '../components/TestQueue/AssignTesterDropdown'; +import AssignTesterDropdown from '../components/common/AssignTesterDropdown'; import { - ASSIGN_TESTER_MUTATION, - REMOVE_TESTER_MUTATION, - TEST_PLAN_REPORT_AT_BROWSER_QUERY -} from '../components/TestQueue/queries'; -import { SCHEDULE_COLLECTION_JOB_MUTATION } from '../components/AddTestToQueueWithConfirmation/queries'; -import '@testing-library/jest-dom/extend-expect'; + ASSIGN_TESTER_MUTATION, + REMOVE_TESTER_MUTATION, + TEST_PLAN_REPORT_AT_BROWSER_QUERY +} from '@components/common/AssignTesterDropdown/queries'; +import { SCHEDULE_COLLECTION_JOB_MUTATION } from '@components/AddTestToQueueWithConfirmation/queries'; +import '@testing-library/jest-dom'; jest.mock('@apollo/client', () => { - const original = jest.requireActual('@apollo/client'); - return { - ...original, - useMutation: jest.fn() - }; + const original = jest.requireActual('@apollo/client'); + return { + ...original, + useMutation: jest.fn() + }; }); const mockPossibleTesters = [ - { id: '1', username: 'bee', isBot: false, ats: [] }, - { id: '2', username: 'puppy', isBot: false, ats: [] }, - { - id: '9999', - username: 'NVDA Bot', - isBot: true, - ats: [{ id: '2', key: 'nvda' }] - }, - { - id: '9998', - username: 'VoiceOver Bot', - isBot: true, - ats: [{ id: '3', key: 'voiceover_macos' }] - } + { id: '1', username: 'bee', isBot: false, ats: [] }, + { id: '2', username: 'puppy', isBot: false, ats: [] }, + { + id: '9999', + username: 'NVDA Bot', + isBot: true, + ats: [{ id: '2', key: 'nvda' }] + }, + { + id: '9998', + username: 'VoiceOver Bot', + isBot: true, + ats: [{ id: '3', key: 'voiceover_macos' }] + } ]; const mockProps = { - testPlanReportId: 'report1', - possibleTesters: mockPossibleTesters, - onChange: jest.fn(), - testPlanRun: null, - draftTestPlanRuns: [], - label: 'Assign testers', - setAlertMessage: jest.fn() + testPlanReportId: 'report1', + possibleTesters: mockPossibleTesters, + onChange: jest.fn(), + testPlanRun: null, + draftTestPlanRuns: [], + label: 'Assign testers', + setAlertMessage: jest.fn() }; import { useMutation } from '@apollo/client'; -import { act } from 'react-dom/test-utils'; // Mock useMutation hook useMutation.mockImplementation(mutation => { - let response; - - if (mutation === ASSIGN_TESTER_MUTATION) { - response = 'ASSIGN'; - } else if (mutation === REMOVE_TESTER_MUTATION) { - response = 'REMOVE'; - } else if (mutation === SCHEDULE_COLLECTION_JOB_MUTATION) { - response = 'SCHEDULE'; - } + let response; + + if (mutation === ASSIGN_TESTER_MUTATION) { + response = 'ASSIGN'; + } else if (mutation === REMOVE_TESTER_MUTATION) { + response = 'REMOVE'; + } else if (mutation === SCHEDULE_COLLECTION_JOB_MUTATION) { + response = 'SCHEDULE'; + } - return [jest.fn(() => response), { loading: false, error: null }]; + return [jest.fn(() => response), { loading: false, error: null }]; }); // Mocked GraphQL responses const getMocks = (atKey, browserKey) => { - const at = { - nvda: { - id: '2', - name: 'NVDA', - key: 'nvda' - }, - jaws: { - id: '1', - name: 'JAWS', - key: 'jaws' - }, - voiceover_macos: { - id: '3', - name: 'VoiceOver for MacOS', - key: 'voiceover_macos' - } - }[atKey]; - - const browser = { - chrome: { - id: '2', - name: 'Chrome', - key: 'chrome' - }, - safari_macos: { - id: '3', - name: 'Safari for MacOS', - key: 'safari_macos' - }, - voiceover_macos: { - id: '3', - name: 'VoiceOver for MacOS', - key: 'voiceover_macos' + const at = { + nvda: { + id: '2', + name: 'NVDA', + key: 'nvda' + }, + jaws: { + id: '1', + name: 'JAWS', + key: 'jaws' + }, + voiceover_macos: { + id: '3', + name: 'VoiceOver for MacOS', + key: 'voiceover_macos' + } + }[atKey]; + + const browser = { + chrome: { + id: '2', + name: 'Chrome', + key: 'chrome' + }, + safari_macos: { + id: '3', + name: 'Safari for MacOS', + key: 'safari_macos' + }, + voiceover_macos: { + id: '3', + name: 'VoiceOver for MacOS', + key: 'voiceover_macos' + } + }[browserKey]; + + if (!at) throw new Error('Unsupported AT key for mocks'); + if (!browser) throw new Error('Unsupported browser key for mocks'); + + return [ + { + request: { + query: ASSIGN_TESTER_MUTATION, + variables: { + testReportId: 'report1', + testerId: '1' } - }[browserKey]; - - if (!at) throw new Error('Unsupported AT key for mocks'); - if (!browser) throw new Error('Unsupported browser key for mocks'); - - return [ - { - request: { - query: ASSIGN_TESTER_MUTATION, - variables: { - testReportId: 'report1', - testerId: '1' - } - }, - result: { - data: { - testPlanReport: { - assignTester: { - testPlanReport: { - draftTestPlanRuns: [ - { - initiatedByAutomation: false, - tester: { - id: '1', - username: 'bee' - } - } - ] - } - } - } - } - } - }, - { - request: { - query: REMOVE_TESTER_MUTATION, - variables: { - testReportId: 'report1', - testerId: '1' - } - }, - result: { - data: { - testPlanReport: { - deleteTestPlanRun: { - testPlanReport: { - draftTestPlanRuns: [] - } - } - } - } - } - }, - { - request: { - query: SCHEDULE_COLLECTION_JOB_MUTATION, - variables: { - testPlanReportId: 'report1' - } - }, - result: { - data: { - scheduleCollectionJob: { - id: 'some-job-id', - status: 'pending' + }, + result: { + data: { + testPlanReport: { + assignTester: { + testPlanReport: { + draftTestPlanRuns: [ + { + initiatedByAutomation: false, + tester: { + id: '1', + username: 'bee' } - } + } + ] + } } - }, - { - request: { - query: TEST_PLAN_REPORT_AT_BROWSER_QUERY, - variables: { - testPlanReportId: 'report1' - } - }, - result: { - data: { - testPlanReport: { - id: 'report1', - at, - browser - } - } + } + } + } + }, + { + request: { + query: REMOVE_TESTER_MUTATION, + variables: { + testReportId: 'report1', + testerId: '1' + } + }, + result: { + data: { + testPlanReport: { + deleteTestPlanRun: { + testPlanReport: { + draftTestPlanRuns: [] + } } + } } - ]; + } + }, + { + request: { + query: SCHEDULE_COLLECTION_JOB_MUTATION, + variables: { + testPlanReportId: 'report1' + } + }, + result: { + data: { + scheduleCollectionJob: { + id: 'some-job-id', + status: 'pending' + } + } + } + }, + { + request: { + query: TEST_PLAN_REPORT_AT_BROWSER_QUERY, + variables: { + testPlanReportId: 'report1' + } + }, + result: { + data: { + testPlanReport: { + id: 'report1', + at, + browser + } + } + } + } + ]; }; describe('AssignTesterDropdown', () => { - beforeEach(() => { - cleanup(); + beforeEach(() => { + cleanup(); + }); + + it('renders without crashing', () => { + render( + + + + ); + expect(screen.getByLabelText('Assign testers')).toBeInTheDocument(); + }); + + it('assigns tester correctly and calls assignTester mutation', async () => { + render( + + + + ); + + const button = await screen.getByRole('button', { + name: /assign testers/i + }); + fireEvent.click(button); + + const items = await screen.findAllByText(/bee/); + expect(items.length).toBe(1); + fireEvent.click(items[0]); + + await waitFor(async () => { + expect(useMutation).toHaveBeenCalledWith(ASSIGN_TESTER_MUTATION); + expect(mockProps.onChange).toHaveBeenCalledTimes(1); + expect(mockProps.setAlertMessage).toHaveBeenCalledTimes(1); + expect(mockProps.setAlertMessage).toHaveBeenCalledWith( + expect.stringContaining('bee now checked') + ); }); + }); - it('renders without crashing', () => { - render( - - - - ); - expect(screen.getByLabelText('Assign testers')).toBeInTheDocument(); + it('assigns bot correctly and calls scheduleCollection mutation', async () => { + render( + + + + ); + + const button = await screen.getByRole('button', { + name: /assign testers/i }); + fireEvent.click(button); + + const items = await screen.findAllByText(/NVDA Bot/); + expect(items.length).toBe(1); + fireEvent.click(items[0]); - it('assigns tester correctly and calls assignTester mutation', async () => { - render( - - - - ); - - const button = await screen.getByRole('button', { - name: /assign testers/i - }); - fireEvent.click(button); - - const items = await screen.findAllByText(/bee/); - expect(items.length).toBe(1); - fireEvent.click(items[0]); - - await waitFor(async () => { - expect(useMutation).toHaveBeenCalledWith(ASSIGN_TESTER_MUTATION); - expect(mockProps.onChange).toHaveBeenCalledTimes(1); - expect(mockProps.setAlertMessage).toHaveBeenCalledTimes(1); - expect(mockProps.setAlertMessage).toHaveBeenCalledWith( - expect.stringContaining('bee now checked') - ); - }); + await waitFor(() => { + expect(useMutation).toHaveBeenCalledWith( + SCHEDULE_COLLECTION_JOB_MUTATION + ); + expect(mockProps.onChange).toHaveBeenCalledTimes(1); + }); + }); + + it('does not list bot when run does not support automation', async () => { + await act(async () => { + render( + + + + ); + }); + let button; + await waitFor(async () => { + button = await screen.getByRole('button', { + name: /assign testers/i + }); }); - it('assigns bot correctly and calls scheduleCollection mutation', async () => { - render( - - - - ); - - const button = await screen.getByRole('button', { - name: /assign testers/i - }); - fireEvent.click(button); - - const items = await screen.findAllByText(/NVDA Bot/); - expect(items.length).toBe(1); - fireEvent.click(items[0]); - - await waitFor(() => { - expect(useMutation).toHaveBeenCalledWith( - SCHEDULE_COLLECTION_JOB_MUTATION - ); - expect(mockProps.onChange).toHaveBeenCalledTimes(1); - }); + await act(async () => { + fireEvent.click(button); }); - it('does not list bot when run does not support automation', async () => { - await act(async () => { - render( - - - - ); - }); - let button; - await waitFor(async () => { - button = await screen.getByRole('button', { - name: /assign testers/i - }); - }); - - await act(async () => { - fireEvent.click(button); - }); - - await waitFor(async () => { - const items = await screen.queryByText(/NVDA Bot/); - expect(items).toBeNull(); - }); + await waitFor(async () => { + const items = await screen.queryByText(/NVDA Bot/); + expect(items).toBeNull(); + }); + }); + + it('only lists supported bot', async () => { + await act(async () => { + render( + + + + ); + }); + let button; + await waitFor(async () => { + button = await screen.getByRole('button', { + name: /assign testers/i + }); }); - it('only lists supported bot', async () => { - await act(async () => { - render( - - - - ); - }); - let button; - await waitFor(async () => { - button = await screen.getByRole('button', { - name: /assign testers/i - }); - }); - - await act(async () => { - fireEvent.click(button); - }); - - await waitFor(async () => { - const items = await screen.queryByText(/NVDA Bot/); - expect(items).toBeNull(); - }); - - await waitFor(async () => { - const items = await screen.queryAllByText(/VoiceOver Bot/); - expect(items).toHaveLength(1); - }); + await act(async () => { + fireEvent.click(button); }); - it('removes tester correctly and calls removeTester mutation', async () => { - mockProps.draftTestPlanRuns = [ - { - initiatedByAutomation: false, - tester: { - id: '1', - username: 'bee' - } - } - ]; - - render( - - - - ); - - const button = await screen.getByRole('button', { - name: /assign testers/i - }); - - fireEvent.click(button); - - const items = await screen.findAllByText(/bee/); - fireEvent.click(items[0]); - - await waitFor(() => { - expect(useMutation).toHaveBeenCalledWith(REMOVE_TESTER_MUTATION); - expect(mockProps.setAlertMessage).toHaveBeenCalledWith( - expect.stringContaining('bee now unchecked') - ); - }); + await waitFor(async () => { + const items = await screen.queryByText(/NVDA Bot/); + expect(items).toBeNull(); + }); + + await waitFor(async () => { + const items = await screen.queryAllByText(/VoiceOver Bot/); + expect(items).toHaveLength(1); + }); + }); + + it('removes tester correctly and calls removeTester mutation', async () => { + mockProps.draftTestPlanRuns = [ + { + initiatedByAutomation: false, + tester: { + id: '1', + username: 'bee' + } + } + ]; + + render( + + + + ); + + const button = await screen.getByRole('button', { + name: /assign testers/i + }); + + fireEvent.click(button); + + const items = await screen.findAllByText(/bee/); + fireEvent.click(items[0]); + + await waitFor(() => { + expect(useMutation).toHaveBeenCalledWith(REMOVE_TESTER_MUTATION); + expect(mockProps.setAlertMessage).toHaveBeenCalledWith( + expect.stringContaining('bee now unchecked') + ); }); + }); }); diff --git a/client/tests/AtVersions.test.js b/client/tests/AtVersions.test.js deleted file mode 100644 index 368b74c1e..000000000 --- a/client/tests/AtVersions.test.js +++ /dev/null @@ -1,47 +0,0 @@ -import getPage from './util/getPage'; - -describe('AT Version UI', () => { - test('should add, edit, then remove an AtVersion', async () => { - /* prettier-ignore */ - await getPage({ role: 'admin', url: '/test-queue' }, async page => { - const openTrayIfClosed = async () => { - await page.waitForSelector('button ::-p-text(Manage Assistive Technology Versions)'); - const isTrayClosed = !!(await page.$('::-p-text(Select an assistive technology and manage its versions)')); - if (isTrayClosed) { - await page.click('button ::-p-text(Manage Assistive Technology Versions)'); - await page.waitForSelector('::-p-text(Select an assistive technology and manage its versions)'); - } - }; - await openTrayIfClosed(); - await page.click('button ::-p-text(Add a New Version)'); - await page.waitForSelector('.modal-title ::-p-text(Add a New Version for JAWS)'); - await page.waitForSelector('.modal-body .form-group:nth-child(1) ::-p-text(Version Number)'); - await page.waitForSelector('.modal-body .form-group:nth-child(2) ::-p-text(Approximate date of availability)'); - await page.type('.modal-body .form-group:nth-child(1) input', '99.0.1'); - await page.type('.modal-body .form-group:nth-child(2) input', '01-01-2000'); - await page.click('.modal-footer button ::-p-text(Add Version)'); - await page.waitForNetworkIdle({ idleTime: 5000 }); - await page.click('.modal-footer button ::-p-text(Ok)'); - await page.waitForSelector('.at-versions-container option:nth-child(2) ::-p-text(99.0.1)'); - const optionValue = await page.$eval('.at-versions-container option:nth-child(2)', option => option.value); - await page.select('.at-versions-container select', optionValue); - await page.click('.at-versions-container button ::-p-text(Edit)'); - const input = await page.waitForSelector('.modal-body .form-group:nth-child(1) input'); - for (let i = 0; i < 6; i += 1) { - await input.press('Backspace'); - } - await page.type('.modal-body .form-group:nth-child(1) input', '99.0.99'); - await page.click('.modal-footer button ::-p-text(Save)'); - await page.waitForNetworkIdle({ idleTime: 5000 }); - await page.click('.modal-footer button ::-p-text(Ok)'); - await page.waitForSelector('.at-versions-container option ::-p-text(99.0.99)'); - await page.select('.at-versions-container select', optionValue); - await page.click('.at-versions-container button ::-p-text(Remove)'); - await page.waitForSelector('.modal-title ::-p-text(Remove JAWS Version 99.0.99)'); - await page.click('.modal-footer button ::-p-text(Remove)'); - await page.waitForNetworkIdle(); - const option = await page.$('.at-versions-container option ::-p-text(99.0.99)'); - expect(option).toBeNull(); - }); - }); -}); diff --git a/client/tests/BasicModal.test.jsx b/client/tests/BasicModal.test.jsx index dc83d1518..d67660136 100644 --- a/client/tests/BasicModal.test.jsx +++ b/client/tests/BasicModal.test.jsx @@ -3,138 +3,130 @@ */ import React from 'react'; import { render, fireEvent, screen } from '@testing-library/react'; -import '@testing-library/jest-dom/extend-expect'; +import '@testing-library/jest-dom'; import BasicModal from '../components/common/BasicModal'; const MockCustomComponent = ({ customProp }) => ; // eslint-disable-line react/prop-types describe('BasicModal', () => { - test('renders modal when show is true', () => { - render( - - ); - expect(screen.getByRole('dialog')).toBeInTheDocument(); - }); + test('renders modal when show is true', () => { + render( + + ); + expect(screen.getByRole('dialog')).toBeInTheDocument(); + }); - test('does not render modal when show is false', () => { - render( - - ); - expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); - }); + test('does not render modal when show is false', () => { + render( + + ); + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + }); - test('renders title and content correctly', () => { - render( - - ); - expect(screen.getByText('My Title')).toBeInTheDocument(); - expect(screen.getByText('My Content')).toBeInTheDocument(); - }); + test('renders title and content correctly', () => { + render(); + expect(screen.getByText('My Title')).toBeInTheDocument(); + expect(screen.getByText('My Content')).toBeInTheDocument(); + }); - test('multiple action buttons trigger correct functions', () => { - const saveFunction = jest.fn(); - const deleteFunction = jest.fn(); - render( - - ); - fireEvent.click(screen.getByText('Save')); - expect(saveFunction).toHaveBeenCalledTimes(1); - fireEvent.click(screen.getByText('Delete')); - expect(deleteFunction).toHaveBeenCalledTimes(1); - expect(saveFunction).toHaveBeenCalledTimes(1); - }); + test('multiple action buttons trigger correct functions', () => { + const saveFunction = jest.fn(); + const deleteFunction = jest.fn(); + render( + + ); + fireEvent.click(screen.getByText('Save')); + expect(saveFunction).toHaveBeenCalledTimes(1); + fireEvent.click(screen.getByText('Delete')); + expect(deleteFunction).toHaveBeenCalledTimes(1); + expect(saveFunction).toHaveBeenCalledTimes(1); + }); - test('close button triggers handleClose function', () => { - const handleClose = jest.fn(); - render( - - ); - fireEvent.click(screen.getByText('Cancel')); - expect(handleClose).toHaveBeenCalledTimes(1); - }); + test('close button triggers handleClose function', () => { + const handleClose = jest.fn(); + render( + + ); + fireEvent.click(screen.getByText('Cancel')); + expect(handleClose).toHaveBeenCalledTimes(1); + }); - test('cancel button triggers handleClose function when cancelButton is true', () => { - const handleClose = jest.fn(); - render( - - ); - fireEvent.click(screen.getByText('Cancel')); - expect(handleClose).toHaveBeenCalledTimes(1); - }); - test('renders custom component in actions', () => { - render( - - ); + test('cancel button triggers handleClose function when cancelButton is true', () => { + const handleClose = jest.fn(); + render( + + ); + fireEvent.click(screen.getByText('Cancel')); + expect(handleClose).toHaveBeenCalledTimes(1); + }); + test('renders custom component in actions', () => { + render( + + ); - expect(screen.getByText('Custom Button')).toBeInTheDocument(); - }); + expect(screen.getByText('Custom Button')).toBeInTheDocument(); + }); - test('custom component triggers appropriate action', () => { - const customAction = jest.fn(); + test('custom component triggers appropriate action', () => { + const customAction = jest.fn(); - render( - ( - - ), - props: {} - } - ]} - /> - ); + render( + ( + + ), + props: {} + } + ]} + /> + ); - fireEvent.click(screen.getByText('Custom Action')); - expect(customAction).toHaveBeenCalledTimes(1); - }); + fireEvent.click(screen.getByText('Custom Action')); + expect(customAction).toHaveBeenCalledTimes(1); + }); - test('has aria-labelledby matching the modal title id', () => { - const { getByRole, getByText } = render( - - ); + test('has aria-labelledby matching the modal title id', () => { + const { getByRole, getByText } = render( + + ); - const modal = getByRole('dialog'); - const title = getByText('Test Title'); + const modal = getByRole('dialog'); + const title = getByText('Test Title'); - expect(modal).toHaveAttribute('aria-labelledby', title.id); - }); + expect(modal).toHaveAttribute('aria-labelledby', title.id); + }); }); diff --git a/client/tests/BotRunTestStatusList.test.jsx b/client/tests/BotRunTestStatusList.test.jsx index 2aec3b4c8..d6e5cc5a3 100644 --- a/client/tests/BotRunTestStatusList.test.jsx +++ b/client/tests/BotRunTestStatusList.test.jsx @@ -6,168 +6,168 @@ import { render, waitFor } from '@testing-library/react'; import { MockedProvider } from '@apollo/client/testing'; import BotRunTestStatusList from '../components/BotRunTestStatusList'; import { TEST_PLAN_RUNS_TEST_RESULTS_QUERY } from '../components/BotRunTestStatusList/queries'; -import '@testing-library/jest-dom/extend-expect'; +import '@testing-library/jest-dom'; import { COLLECTION_JOB_STATUS } from '../../server/util/enums'; const getMocks = testPlanRuns => { - const testPlanRunMock = { - request: { - query: TEST_PLAN_RUNS_TEST_RESULTS_QUERY, - variables: { testPlanReportId: '1' } - }, - result: { data: { testPlanRuns } } - }; - - return [testPlanRunMock]; + const testPlanRunMock = { + request: { + query: TEST_PLAN_RUNS_TEST_RESULTS_QUERY, + variables: { testPlanReportId: '1' } + }, + result: { data: { testPlanRuns } } + }; + + return [testPlanRunMock]; }; test('correctly displays statuses for single COMPLETED test run', async () => { - const testPlanRuns = [ - { - id: '0', - testResults: new Array(3).fill(null), - tester: { username: 'bot' }, - collectionJob: { - status: COLLECTION_JOB_STATUS.COMPLETED, - testStatus: [ - { status: COLLECTION_JOB_STATUS.COMPLETED }, - { status: COLLECTION_JOB_STATUS.COMPLETED }, - { status: COLLECTION_JOB_STATUS.COMPLETED } - ] - } - } - ]; - - const mocks = getMocks(testPlanRuns); - - const screen = render( - - - - ); - - const { getByText } = screen; - - await waitFor(() => { - expect(getByText('3 Tests Completed')).toBeInTheDocument(); - expect(getByText('0 Tests Queued')).toBeInTheDocument(); - }); + const testPlanRuns = [ + { + id: '0', + testResults: new Array(3).fill(null), + tester: { username: 'bot' }, + collectionJob: { + status: COLLECTION_JOB_STATUS.COMPLETED, + testStatus: [ + { status: COLLECTION_JOB_STATUS.COMPLETED }, + { status: COLLECTION_JOB_STATUS.COMPLETED }, + { status: COLLECTION_JOB_STATUS.COMPLETED } + ] + } + } + ]; + + const mocks = getMocks(testPlanRuns); + + const screen = render( + + + + ); + + const { getByText } = screen; + + await waitFor(() => { + expect(getByText('3 Tests Completed')).toBeInTheDocument(); + expect(getByText('0 Tests Queued')).toBeInTheDocument(); + }); }); test('correctly ignores test results from a human-submitted test plan run', async () => { - const testPlanRuns = [ - { - id: '0', - testResults: new Array(2).fill(null), - tester: { username: 'bot' }, - collectionJob: { - status: COLLECTION_JOB_STATUS.COMPLETED, - testStatus: [ - { status: COLLECTION_JOB_STATUS.COMPLETED }, - { status: COLLECTION_JOB_STATUS.COMPLETED } - ] - } - }, - { - id: '1', - testResults: new Array(2).fill(null), - tester: { username: 'human' }, - collectionJob: null - } - ]; - - const mocks = getMocks(testPlanRuns); - - const { getByText } = render( - - - - ); - - await waitFor(async () => { - expect(getByText('2 Tests Completed')).toBeInTheDocument(); - expect(getByText('0 Tests Queued')).toBeInTheDocument(); - }); + const testPlanRuns = [ + { + id: '0', + testResults: new Array(2).fill(null), + tester: { username: 'bot' }, + collectionJob: { + status: COLLECTION_JOB_STATUS.COMPLETED, + testStatus: [ + { status: COLLECTION_JOB_STATUS.COMPLETED }, + { status: COLLECTION_JOB_STATUS.COMPLETED } + ] + } + }, + { + id: '1', + testResults: new Array(2).fill(null), + tester: { username: 'human' }, + collectionJob: null + } + ]; + + const mocks = getMocks(testPlanRuns); + + const { getByText } = render( + + + + ); + + await waitFor(async () => { + expect(getByText('2 Tests Completed')).toBeInTheDocument(); + expect(getByText('0 Tests Queued')).toBeInTheDocument(); + }); }); test('correctly displays statuses for CANCELLED test run', async () => { - const testPlanRuns = [ - { - id: '0', - testResults: new Array(2).fill(null), - tester: { username: 'bot' }, - collectionJob: { - status: COLLECTION_JOB_STATUS.CANCELLED, - testStatus: [ - { status: COLLECTION_JOB_STATUS.COMPLETED }, - { status: COLLECTION_JOB_STATUS.COMPLETED }, - { status: COLLECTION_JOB_STATUS.CANCELLED } - ] - } - } - ]; - - const mocks = getMocks(testPlanRuns); - - const { getByText } = render( - - - - ); - - await waitFor(() => { - expect(getByText('2 Tests Completed')).toBeInTheDocument(); - expect(getByText('0 Tests Queued')).toBeInTheDocument(); - expect(getByText('1 Test Cancelled')).toBeInTheDocument(); - }); + const testPlanRuns = [ + { + id: '0', + testResults: new Array(2).fill(null), + tester: { username: 'bot' }, + collectionJob: { + status: COLLECTION_JOB_STATUS.CANCELLED, + testStatus: [ + { status: COLLECTION_JOB_STATUS.COMPLETED }, + { status: COLLECTION_JOB_STATUS.COMPLETED }, + { status: COLLECTION_JOB_STATUS.CANCELLED } + ] + } + } + ]; + + const mocks = getMocks(testPlanRuns); + + const { getByText } = render( + + + + ); + + await waitFor(() => { + expect(getByText('2 Tests Completed')).toBeInTheDocument(); + expect(getByText('0 Tests Queued')).toBeInTheDocument(); + expect(getByText('1 Test Cancelled')).toBeInTheDocument(); + }); }); test('correctly displays statuses for multiple RUNNING and QUEUED test runs', async () => { - const testPlanRuns = [ - { - id: '0', - testResults: new Array(2).fill(null), - tester: { username: 'bot' }, - collectionJob: { - status: COLLECTION_JOB_STATUS.RUNNING, - testStatus: [ - { status: COLLECTION_JOB_STATUS.RUNNING }, - { status: COLLECTION_JOB_STATUS.COMPLETED }, - { status: COLLECTION_JOB_STATUS.QUEUED } - ] - } - }, - { - id: '1', - testResults: new Array(2).fill(null), - tester: { username: 'bot' }, - collectionJob: { - status: COLLECTION_JOB_STATUS.CANCELLED, - testStatus: [ - { status: COLLECTION_JOB_STATUS.CANCELLED }, - { status: COLLECTION_JOB_STATUS.COMPLETED }, - { status: COLLECTION_JOB_STATUS.CANCELLED } - ] - } - } - ]; - - const mocks = getMocks(testPlanRuns); - - const { getByText } = render( - - - - ); - - await waitFor(async () => { - // Wait for the component to update - // Imperfect but prevents needing to detect loading removal - await setTimeout(() => { - expect(getByText('1 Test Running')).toBeInTheDocument(); - expect(getByText('2 Tests Completed')).toBeInTheDocument(); - expect(getByText('1 Test Queued')).toBeInTheDocument(); - expect(getByText('2 Tests Cancelled')).toBeInTheDocument(); - }, 500); - }); + const testPlanRuns = [ + { + id: '0', + testResults: new Array(2).fill(null), + tester: { username: 'bot' }, + collectionJob: { + status: COLLECTION_JOB_STATUS.RUNNING, + testStatus: [ + { status: COLLECTION_JOB_STATUS.RUNNING }, + { status: COLLECTION_JOB_STATUS.COMPLETED }, + { status: COLLECTION_JOB_STATUS.QUEUED } + ] + } + }, + { + id: '1', + testResults: new Array(2).fill(null), + tester: { username: 'bot' }, + collectionJob: { + status: COLLECTION_JOB_STATUS.CANCELLED, + testStatus: [ + { status: COLLECTION_JOB_STATUS.CANCELLED }, + { status: COLLECTION_JOB_STATUS.COMPLETED }, + { status: COLLECTION_JOB_STATUS.CANCELLED } + ] + } + } + ]; + + const mocks = getMocks(testPlanRuns); + + const { getByText } = render( + + + + ); + + await waitFor(async () => { + // Wait for the component to update + // Imperfect but prevents needing to detect loading removal + await setTimeout(() => { + expect(getByText('1 Test Running')).toBeInTheDocument(); + expect(getByText('2 Tests Completed')).toBeInTheDocument(); + expect(getByText('1 Test Queued')).toBeInTheDocument(); + expect(getByText('2 Tests Cancelled')).toBeInTheDocument(); + }, 500); + }); }); diff --git a/client/tests/DataManagement.test.jsx b/client/tests/DataManagement.test.jsx index 73a46fadb..c379eea11 100644 --- a/client/tests/DataManagement.test.jsx +++ b/client/tests/DataManagement.test.jsx @@ -2,300 +2,292 @@ * @jest-environment jsdom */ -import React from 'react'; +import React, { act } from 'react'; import { render, renderHook, waitFor } from '@testing-library/react'; import { InMemoryCache } from '@apollo/client'; import { MockedProvider } from '@apollo/client/testing'; import { BrowserRouter } from 'react-router-dom'; -import '@testing-library/jest-dom/extend-expect'; - +import '@testing-library/jest-dom'; import DataManagement from '../components/DataManagement'; // eslint-disable-next-line jest/no-mocks-import import { DATA_MANAGEMENT_PAGE_POPULATED_MOCK_DATA } from './__mocks__/GraphQLMocks'; -import { act } from 'react-dom/test-utils'; import { - useDataManagementTableFiltering, - useDataManagementTableSorting, - useDerivedActivePhasesByTestPlanId, - useTestPlanVersionsByPhase, - useTestPlansByPhase + useDataManagementTableFiltering, + useDataManagementTableSorting, + useDerivedActivePhasesByTestPlanId, + useTestPlanVersionsByPhase, + useTestPlansByPhase } from '../components/DataManagement/filterSortHooks'; import { - DATA_MANAGEMENT_TABLE_FILTER_OPTIONS, - DATA_MANAGEMENT_TABLE_SORT_OPTIONS + DATA_MANAGEMENT_TABLE_FILTER_OPTIONS, + DATA_MANAGEMENT_TABLE_SORT_OPTIONS } from '../components/DataManagement/utils'; import { TABLE_SORT_ORDERS } from '../components/common/SortableTableHeader'; import { AriaLiveRegionProvider } from '../components/providers/AriaLiveRegionProvider'; const setup = (mocks = []) => { - return render( - - - - - - - - ); + return render( + + + + + + + + ); }; describe('Data Management page', () => { - let wrapper; + let wrapper; - beforeEach(() => { - wrapper = setup(DATA_MANAGEMENT_PAGE_POPULATED_MOCK_DATA); - }); + beforeEach(() => { + wrapper = setup(DATA_MANAGEMENT_PAGE_POPULATED_MOCK_DATA); + }); - it('renders loading state on initialization', async () => { - const { getByTestId } = wrapper; - const element = getByTestId('page-status'); + it('renders loading state on initialization', async () => { + const { getByTestId } = wrapper; + const element = getByTestId('page-status'); - expect(element).toBeTruthy(); - expect(element).toHaveTextContent('Loading'); - }); + expect(element).toBeTruthy(); + expect(element).toHaveTextContent('Loading'); + }); - it('renders Status Summary component', async () => { - // allow page time to load - await waitFor(() => new Promise(res => setTimeout(res, 0))); + it('renders Status Summary component', async () => { + // allow page time to load + await waitFor(() => new Promise(res => setTimeout(res, 0))); - const { queryAllByText } = wrapper; - const statusSummaryElement = queryAllByText( - /Test Plans Status Summary/i - ); - const testPlanElement = queryAllByText(/Test Plan/i); - const coveredAtElement = queryAllByText(/Covered AT/i); - const overallStatusElement = queryAllByText(/Overall Status/i); - const rdElement = queryAllByText(/R&D Version/i); - const draftElement = queryAllByText(/Draft Review/i); - const candidateElement = queryAllByText(/Candidate Review/i); - const recommendedElement = queryAllByText(/Recommended Version/i); + const { queryAllByText } = wrapper; + const statusSummaryElement = queryAllByText(/Test Plans Status Summary/i); + const testPlanElement = queryAllByText(/Test Plan/i); + const coveredAtElement = queryAllByText(/Covered AT/i); + const overallStatusElement = queryAllByText(/Overall Status/i); + const rdElement = queryAllByText(/R&D Version/i); + const draftElement = queryAllByText(/Draft Review/i); + const candidateElement = queryAllByText(/Candidate Review/i); + const recommendedElement = queryAllByText(/Recommended Version/i); - expect(statusSummaryElement.length).toBeGreaterThanOrEqual(1); - expect(testPlanElement.length).toBeGreaterThanOrEqual(1); - expect(coveredAtElement.length).toBeGreaterThanOrEqual(1); - expect(overallStatusElement.length).toBeGreaterThanOrEqual(1); - expect(rdElement.length).toBeGreaterThanOrEqual(1); - expect(draftElement.length).toBeGreaterThanOrEqual(1); - expect(candidateElement.length).toBeGreaterThanOrEqual(1); - expect(recommendedElement.length).toBeGreaterThanOrEqual(1); - }); + expect(statusSummaryElement.length).toBeGreaterThanOrEqual(1); + expect(testPlanElement.length).toBeGreaterThanOrEqual(1); + expect(coveredAtElement.length).toBeGreaterThanOrEqual(1); + expect(overallStatusElement.length).toBeGreaterThanOrEqual(1); + expect(rdElement.length).toBeGreaterThanOrEqual(1); + expect(draftElement.length).toBeGreaterThanOrEqual(1); + expect(candidateElement.length).toBeGreaterThanOrEqual(1); + expect(recommendedElement.length).toBeGreaterThanOrEqual(1); + }); }); const testPlans = [ - { title: 'Test A', directory: 'dirA', id: '1' }, - { title: 'Test B', directory: 'dirB', id: '2' }, - { title: 'Test C', directory: 'dirC', id: '3' }, - { title: 'Test D', directory: 'dirD', id: '4' } + { title: 'Test A', directory: 'dirA', id: '1' }, + { title: 'Test B', directory: 'dirB', id: '2' }, + { title: 'Test C', directory: 'dirC', id: '3' }, + { title: 'Test D', directory: 'dirD', id: '4' } ]; const testPlanVersions = [ - { - phase: 'RD', - id: '101', - testPlan: { directory: 'dirA' }, - updatedAt: '2022-03-17T18:34:51.000Z' - }, - { - phase: 'DRAFT', - id: '102', - testPlan: { directory: 'dirB' }, - draftStatusReachedAt: '2022-05-18T20:51:40.000Z' - }, - { - phase: 'CANDIDATE', - id: '103', - testPlan: { directory: 'dirC' }, - candidatePhaseReachedAt: '2022-04-10T00:00:00.000Z' - }, - { - phase: 'RD', - id: '104', - testPlan: { directory: 'dirD' }, - updatedAt: '2022-03-18T18:34:51.000Z' - }, - { - phase: 'RECOMMENDED', - id: '105', - testPlan: { directory: 'dirD' }, - recommendedPhaseReachedAt: '2022-05-18T20:51:40.000Z' - }, - { - phase: 'DRAFT', - id: '106', - testPlan: { directory: 'dirD' }, - draftStatusReachedAt: '2024-01-08T20:51:40.000Z' - } + { + phase: 'RD', + id: '101', + testPlan: { directory: 'dirA' }, + updatedAt: '2022-03-17T18:34:51.000Z' + }, + { + phase: 'DRAFT', + id: '102', + testPlan: { directory: 'dirB' }, + draftStatusReachedAt: '2022-05-18T20:51:40.000Z' + }, + { + phase: 'CANDIDATE', + id: '103', + testPlan: { directory: 'dirC' }, + candidatePhaseReachedAt: '2022-04-10T00:00:00.000Z' + }, + { + phase: 'RD', + id: '104', + testPlan: { directory: 'dirD' }, + updatedAt: '2022-03-18T18:34:51.000Z' + }, + { + phase: 'RECOMMENDED', + id: '105', + testPlan: { directory: 'dirD' }, + recommendedPhaseReachedAt: '2022-05-18T20:51:40.000Z' + }, + { + phase: 'DRAFT', + id: '106', + testPlan: { directory: 'dirD' }, + draftStatusReachedAt: '2024-01-08T20:51:40.000Z' + } ]; const ats = []; // ATs are stubbed until this model is defined describe('useDataManagementTableSorting hook', () => { - it('sorts by phase by default', () => { - const { result } = renderHook(() => - useDataManagementTableSorting( - testPlans, - testPlanVersions, - ats, - TABLE_SORT_ORDERS.DESC - ) - ); - expect(result.current.sortedTestPlans).toEqual([ - testPlans[3], // RECOMMENDED - testPlans[2], // CANDIDATE - testPlans[1], // DRAFT - testPlans[0] // RD - ]); - }); + it('sorts by phase by default', () => { + const { result } = renderHook(() => + useDataManagementTableSorting( + testPlans, + testPlanVersions, + ats, + TABLE_SORT_ORDERS.DESC + ) + ); + expect(result.current.sortedTestPlans).toEqual([ + testPlans[3], // RECOMMENDED + testPlans[2], // CANDIDATE + testPlans[1], // DRAFT + testPlans[0] // RD + ]); + }); - it('can sort by name', () => { - const { result } = renderHook(() => - useDataManagementTableSorting(testPlans, testPlanVersions, ats) - ); - act(() => - result.current.updateSort({ - key: DATA_MANAGEMENT_TABLE_SORT_OPTIONS.NAME, - direction: TABLE_SORT_ORDERS.ASC - }) - ); - expect(result.current.sortedTestPlans).toEqual(testPlans); - }); + it('can sort by name', () => { + const { result } = renderHook(() => + useDataManagementTableSorting(testPlans, testPlanVersions, ats) + ); + act(() => + result.current.updateSort({ + key: DATA_MANAGEMENT_TABLE_SORT_OPTIONS.NAME, + direction: TABLE_SORT_ORDERS.ASC + }) + ); + expect(result.current.sortedTestPlans).toEqual(testPlans); + }); }); describe('useDataManagementTableFiltering hook', () => { - it('shows all plans by default', () => { - const { result } = renderHook(() => - useDataManagementTableFiltering( - testPlans, - testPlanVersions, - DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.ALL - ) - ); - expect(result.current.filteredTestPlans).toEqual(testPlans); - expect( - result.current.filterLabels[ - DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.ALL - ] - ).toEqual(`All Plans (${testPlans.length})`); - }); + it('shows all plans by default', () => { + const { result } = renderHook(() => + useDataManagementTableFiltering( + testPlans, + testPlanVersions, + DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.ALL + ) + ); + expect(result.current.filteredTestPlans).toEqual(testPlans); + expect( + result.current.filterLabels[DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.ALL] + ).toEqual(`All Plans (${testPlans.length})`); + }); - it('can filter by RD phase', () => { - const { result } = renderHook(() => - useDataManagementTableFiltering( - testPlans, - testPlanVersions, - DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.RD - ) - ); - expect(result.current.filteredTestPlans).toEqual([ - testPlans[0], // RD - testPlans[3] - ]); - expect( - result.current.filterLabels[DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.RD] - ).toEqual(`R&D Complete (2)`); - }); + it('can filter by RD phase', () => { + const { result } = renderHook(() => + useDataManagementTableFiltering( + testPlans, + testPlanVersions, + DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.RD + ) + ); + expect(result.current.filteredTestPlans).toEqual([ + testPlans[0], // RD + testPlans[3] + ]); + expect( + result.current.filterLabels[DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.RD] + ).toEqual(`R&D Complete (2)`); + }); - it('can filter by DRAFT phase', () => { - const { result } = renderHook(() => - useDataManagementTableFiltering( - testPlans, - testPlanVersions, - DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.DRAFT - ) - ); - expect(result.current.filteredTestPlans).toEqual([ - testPlans[1], // DRAFT - testPlans[3] // DRAFT - ]); - expect( - result.current.filterLabels[ - DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.DRAFT - ] - ).toEqual(`In Draft Review (2)`); // Test plan 106 is in DRAFT while the overall plan is RECOMMENDED - }); + it('can filter by DRAFT phase', () => { + const { result } = renderHook(() => + useDataManagementTableFiltering( + testPlans, + testPlanVersions, + DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.DRAFT + ) + ); + expect(result.current.filteredTestPlans).toEqual([ + testPlans[1], // DRAFT + testPlans[3] // DRAFT + ]); + expect( + result.current.filterLabels[DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.DRAFT] + ).toEqual(`In Draft Review (2)`); // Test plan 106 is in DRAFT while the overall plan is RECOMMENDED + }); - it('can filter by CANDIDATE phase', () => { - const { result } = renderHook(() => - useDataManagementTableFiltering( - testPlans, - testPlanVersions, - DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.CANDIDATE - ) - ); - expect(result.current.filteredTestPlans).toEqual([ - testPlans[2] // CANDIDATE - ]); - expect( - result.current.filterLabels[ - DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.CANDIDATE - ] - ).toEqual(`In Candidate Review (1)`); - }); + it('can filter by CANDIDATE phase', () => { + const { result } = renderHook(() => + useDataManagementTableFiltering( + testPlans, + testPlanVersions, + DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.CANDIDATE + ) + ); + expect(result.current.filteredTestPlans).toEqual([ + testPlans[2] // CANDIDATE + ]); + expect( + result.current.filterLabels[ + DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.CANDIDATE + ] + ).toEqual(`In Candidate Review (1)`); + }); - it('can filter by RECOMMENDED phase', () => { - const { result } = renderHook(() => - useDataManagementTableFiltering( - testPlans, - testPlanVersions, - DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.RECOMMENDED - ) - ); - expect(result.current.filteredTestPlans).toEqual([ - testPlans[3] // RECOMMENDED - ]); - expect( - result.current.filterLabels[ - DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.RECOMMENDED - ] - ).toEqual(`Recommended Plans (1)`); - }); + it('can filter by RECOMMENDED phase', () => { + const { result } = renderHook(() => + useDataManagementTableFiltering( + testPlans, + testPlanVersions, + DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.RECOMMENDED + ) + ); + expect(result.current.filteredTestPlans).toEqual([ + testPlans[3] // RECOMMENDED + ]); + expect( + result.current.filterLabels[ + DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.RECOMMENDED + ] + ).toEqual(`Recommended Plans (1)`); + }); }); describe('useTestPlanVersionsByPhase hook', () => { - it('returns an object with test plan versions grouped by phase', () => { - const { result } = renderHook(() => - useTestPlanVersionsByPhase(testPlanVersions) - ); - const { testPlanVersionsByPhase } = result.current; - expect(testPlanVersionsByPhase).toEqual({ - RD: [testPlanVersions[0], testPlanVersions[3]], - DRAFT: [testPlanVersions[1], testPlanVersions[5]], - CANDIDATE: [testPlanVersions[2]], - RECOMMENDED: [testPlanVersions[4]] - }); + it('returns an object with test plan versions grouped by phase', () => { + const { result } = renderHook(() => + useTestPlanVersionsByPhase(testPlanVersions) + ); + const { testPlanVersionsByPhase } = result.current; + expect(testPlanVersionsByPhase).toEqual({ + RD: [testPlanVersions[0], testPlanVersions[3]], + DRAFT: [testPlanVersions[1], testPlanVersions[5]], + CANDIDATE: [testPlanVersions[2]], + RECOMMENDED: [testPlanVersions[4]] }); + }); }); describe('useDerivedTestPlanOverallPhase hook', () => { - it('returns an object with the active phases mapped to each test plan id', () => { - const { result } = renderHook(() => - useDerivedActivePhasesByTestPlanId(testPlans, testPlanVersions) - ); - const { derivedActivePhasesByTestPlanId } = result.current; - expect(derivedActivePhasesByTestPlanId).toEqual({ - 1: ['RD'], - 2: ['DRAFT'], - 3: ['CANDIDATE'], - 4: ['RECOMMENDED', 'DRAFT', 'RD'] - }); + it('returns an object with the active phases mapped to each test plan id', () => { + const { result } = renderHook(() => + useDerivedActivePhasesByTestPlanId(testPlans, testPlanVersions) + ); + const { derivedActivePhasesByTestPlanId } = result.current; + expect(derivedActivePhasesByTestPlanId).toEqual({ + 1: ['RD'], + 2: ['DRAFT'], + 3: ['CANDIDATE'], + 4: ['RECOMMENDED', 'DRAFT', 'RD'] }); + }); }); describe('useTestPlansByPhase hook', () => { - it('returns an object with test plans with an array of active Test Plan Versions', () => { - const { result } = renderHook(() => - useTestPlansByPhase(testPlans, testPlanVersions) - ); - const { testPlansByPhase } = result.current; - expect(testPlansByPhase).toEqual({ - RD: [testPlans[0], testPlans[3]], - DRAFT: [testPlans[1], testPlans[3]], - CANDIDATE: [testPlans[2]], - RECOMMENDED: [testPlans[3]] - }); + it('returns an object with test plans with an array of active Test Plan Versions', () => { + const { result } = renderHook(() => + useTestPlansByPhase(testPlans, testPlanVersions) + ); + const { testPlansByPhase } = result.current; + expect(testPlansByPhase).toEqual({ + RD: [testPlans[0], testPlans[3]], + DRAFT: [testPlans[1], testPlans[3]], + CANDIDATE: [testPlans[2]], + RECOMMENDED: [testPlans[3]] }); + }); }); diff --git a/client/tests/FilterButtons.test.jsx b/client/tests/FilterButtons.test.jsx index 26a43cc63..8edf3d32d 100644 --- a/client/tests/FilterButtons.test.jsx +++ b/client/tests/FilterButtons.test.jsx @@ -9,54 +9,52 @@ import FilterButtons from '../components/common/FilterButtons'; import { DATA_MANAGEMENT_TABLE_FILTER_OPTIONS } from '../components/DataManagement/utils'; describe('FilterButtons', () => { - const filterOptions = { - [DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.RD]: `R&D Complete`, - [DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.DRAFT]: `In Draft Review`, - [DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.CANDIDATE]: `In Candidate Review`, - [DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.RECOMMENDED]: `Recommended Plans`, - [DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.ALL]: `All Plans` - }; - const defaultProps = { - filterOptions, - filterLabel: 'Filter', - activeFilter: DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.ALL, - onFilterChange: () => {} - }; + const filterOptions = { + [DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.RD]: `R&D Complete`, + [DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.DRAFT]: `In Draft Review`, + [DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.CANDIDATE]: `In Candidate Review`, + [DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.RECOMMENDED]: `Recommended Plans`, + [DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.ALL]: `All Plans` + }; + const defaultProps = { + filterOptions, + filterLabel: 'Filter', + activeFilter: DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.ALL, + onFilterChange: () => {} + }; - it('should render without crashing', () => { - render(); - expect(screen.getByRole('group')).toBeInTheDocument(); - }); + it('should render without crashing', () => { + render(); + expect(screen.getByRole('group')).toBeInTheDocument(); + }); - it('should render the correct filter labels', () => { - render(); - Object.values(filterOptions).forEach(label => { - expect(screen.getByText(label)).toBeInTheDocument(); - }); + it('should render the correct filter labels', () => { + render(); + Object.values(filterOptions).forEach(label => { + expect(screen.getByText(label)).toBeInTheDocument(); }); + }); - it('should render the active filter with correct styles', () => { - render(); - const activeButton = screen - .getByText(filterOptions[DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.ALL]) - .closest('button'); - expect(activeButton).toHaveAttribute('aria-pressed', 'true'); - expect(activeButton).toHaveClass('active'); - }); + it('should render the active filter with correct styles', () => { + render(); + const activeButton = screen + .getByText(filterOptions[DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.ALL]) + .closest('button'); + expect(activeButton).toHaveAttribute('aria-pressed', 'true'); + expect(activeButton).toHaveClass('active'); + }); - it('should change filter on button click', () => { - const onFilterChange = jest.fn(); - render( - - ); - const button = screen - .getByText( - filterOptions[DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.RECOMMENDED] - ) - .closest('button'); - fireEvent.click(button); - expect(onFilterChange).toHaveBeenCalledWith( - DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.RECOMMENDED - ); - }); + it('should change filter on button click', () => { + const onFilterChange = jest.fn(); + render(); + const button = screen + .getByText( + filterOptions[DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.RECOMMENDED] + ) + .closest('button'); + fireEvent.click(button); + expect(onFilterChange).toHaveBeenCalledWith( + DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.RECOMMENDED + ); + }); }); diff --git a/client/tests/FocusTrapper.test.jsx b/client/tests/FocusTrapper.test.jsx index a4c114d85..aa252755d 100644 --- a/client/tests/FocusTrapper.test.jsx +++ b/client/tests/FocusTrapper.test.jsx @@ -6,120 +6,116 @@ import { render, fireEvent, act } from '@testing-library/react'; import FocusTrapper from '../components/common/FocusTrapper'; describe('FocusTrapper', () => { - let trappedDiv, initialFocusRef; - - beforeEach(() => { - trappedDiv = document.createElement('div'); - trappedDiv.id = 'trapped-div'; - document.body.appendChild(trappedDiv); - }); - - afterEach(() => { - document.body.removeChild(trappedDiv); + let trappedDiv, initialFocusRef; + + beforeEach(() => { + trappedDiv = document.createElement('div'); + trappedDiv.id = 'trapped-div'; + document.body.appendChild(trappedDiv); + }); + + afterEach(() => { + document.body.removeChild(trappedDiv); + }); + + const renderEls = async () => { + initialFocusRef = React.createRef(); + return render( + +

    + Modal Header +

    + + Link +
    , + { container: document.body.appendChild(trappedDiv) } + ); + }; + + it('should identify focusable elements', async () => { + await renderEls(); + + const focusables = trappedDiv.querySelectorAll( + 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' + ); + + // Two original focusable elements plus 2 for before and after trap + expect(focusables.length).toBe(4); + }); + + it('should trap focus and allow forward navigation when isActive is true', async () => { + await renderEls(); + + const container = document.getElementById('trapped-div'); + + const firstFocusable = container.querySelector('button'); + const lastFocusable = container.querySelector('a'); + + act(() => { + lastFocusable.focus(); }); - const renderEls = async () => { - initialFocusRef = React.createRef(); - return render( - -

    - Modal Header -

    - - Link -
    , - { container: document.body.appendChild(trappedDiv) } - ); - }; - - it('should identify focusable elements', async () => { - await renderEls(); - - const focusables = trappedDiv.querySelectorAll( - 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' - ); - - // Two original focusable elements plus 2 for before and after trap - expect(focusables.length).toBe(4); + act(() => { + fireEvent.keyDown(container, { + key: 'Tab', + code: 9, + shiftKey: false + }); }); - it('should trap focus and allow forward navigation when isActive is true', async () => { - await renderEls(); - - const container = document.getElementById('trapped-div'); + expect(document.activeElement).toBe(firstFocusable); + }); - const firstFocusable = container.querySelector('button'); - const lastFocusable = container.querySelector('a'); + it('should trap focus when and allow backward navigation when isActive is true', async () => { + await renderEls(); - act(() => { - lastFocusable.focus(); - }); + const container = document.getElementById('trapped-div'); - act(() => { - fireEvent.keyDown(container, { - key: 'Tab', - code: 9, - shiftKey: false - }); - }); + const lastFocusable = container.querySelector('a'); - expect(document.activeElement).toBe(firstFocusable); + act(() => { + initialFocusRef.current.focus(); }); - it('should trap focus when and allow backward navigation when isActive is true', async () => { - await renderEls(); - - const container = document.getElementById('trapped-div'); + act(() => { + fireEvent.keyDown(container, { + key: 'Tab', + code: 9, + shiftKey: true + }); + }); - const lastFocusable = container.querySelector('a'); + expect(document.activeElement).toBe(lastFocusable); + }); - act(() => { - initialFocusRef.current.focus(); - }); + it('should not trap focus when isActive is false', async () => { + const { container } = render( + + + + , + { container: document.body.appendChild(trappedDiv) } + ); - act(() => { - fireEvent.keyDown(container, { - key: 'Tab', - code: 9, - shiftKey: true - }); - }); + const firstFocusable = container.querySelector('button'); + const lastFocusable = container.querySelector('input'); - expect(document.activeElement).toBe(lastFocusable); + act(() => { + firstFocusable.focus(); }); - it('should not trap focus when isActive is false', async () => { - const { container } = render( - - - - , - { container: document.body.appendChild(trappedDiv) } - ); - - const firstFocusable = container.querySelector('button'); - const lastFocusable = container.querySelector('input'); - - act(() => { - firstFocusable.focus(); - }); - - act(() => { - fireEvent.keyDown(container, { - key: 'Tab', - code: 9, - shiftKey: true - }); - }); - - expect(document.activeElement).not.toBe(lastFocusable); + act(() => { + fireEvent.keyDown(container, { + key: 'Tab', + code: 9, + shiftKey: true + }); }); + + expect(document.activeElement).not.toBe(lastFocusable); + }); }); diff --git a/client/tests/SortableTableHeader.test.jsx b/client/tests/SortableTableHeader.test.jsx index 84829dcef..37a2bfa1e 100644 --- a/client/tests/SortableTableHeader.test.jsx +++ b/client/tests/SortableTableHeader.test.jsx @@ -5,71 +5,71 @@ import React from 'react'; import { render, fireEvent, screen } from '@testing-library/react'; import SortableTableHeader, { - TABLE_SORT_ORDERS + TABLE_SORT_ORDERS } from '../components/common/SortableTableHeader'; import '@testing-library/jest-dom'; import { AriaLiveRegionProvider } from '../components/providers/AriaLiveRegionProvider'; const renderComponent = props => - render( - - - - - - - -
    -
    - ); + render( + + + + + + + +
    +
    + ); const getAriaSort = (active, sortOrder) => - active - ? sortOrder === TABLE_SORT_ORDERS.ASC - ? 'ascending' - : 'descending' - : 'none'; + active + ? sortOrder === TABLE_SORT_ORDERS.ASC + ? 'ascending' + : 'descending' + : 'none'; describe('SortableTableHeader component', () => { - const defaultProps = { title: 'Header', active: false, onSort: () => {} }; + const defaultProps = { title: 'Header', active: false, onSort: () => {} }; - it('should render without crashing', () => { - renderComponent(defaultProps); - expect(screen.getByRole('columnheader')).toBeInTheDocument(); - }); + it('should render without crashing', () => { + renderComponent(defaultProps); + expect(screen.getByRole('columnheader')).toBeInTheDocument(); + }); - it('should render the correct title', () => { - renderComponent(defaultProps); - expect(screen.getByText('Header')).toBeInTheDocument(); - }); + it('should render the correct title', () => { + renderComponent(defaultProps); + expect(screen.getByText('Header')).toBeInTheDocument(); + }); - it('should render the inactive icon when active is false', () => { - renderComponent(defaultProps); - expect(screen.getByRole('img', { hidden: true })).toBeInTheDocument(); - }); + it('should render the inactive icon when active is false', () => { + renderComponent(defaultProps); + expect(screen.getByRole('img', { hidden: true })).toBeInTheDocument(); + }); - it('should handle sorting order and aria-sort attribute when active is true and clicked', () => { - const onSort = jest.fn(); - renderComponent({ ...defaultProps, active: true, onSort }); - const button = screen.getByRole('button'); + it('should handle sorting order and aria-sort attribute when active is true and clicked', () => { + const onSort = jest.fn(); + renderComponent({ ...defaultProps, active: true, onSort }); + const button = screen.getByRole('button'); - expect(screen.getByRole('columnheader')).toHaveAttribute( - 'aria-sort', - getAriaSort(true, TABLE_SORT_ORDERS.ASC) - ); + expect(screen.getByRole('columnheader')).toHaveAttribute( + 'aria-sort', + getAriaSort(true, TABLE_SORT_ORDERS.ASC) + ); - fireEvent.click(button); - expect(onSort).toHaveBeenCalledWith(TABLE_SORT_ORDERS.DESC); - expect(screen.getByRole('columnheader')).toHaveAttribute( - 'aria-sort', - getAriaSort(true, TABLE_SORT_ORDERS.DESC) - ); + fireEvent.click(button); + expect(onSort).toHaveBeenCalledWith(TABLE_SORT_ORDERS.DESC); + expect(screen.getByRole('columnheader')).toHaveAttribute( + 'aria-sort', + getAriaSort(true, TABLE_SORT_ORDERS.DESC) + ); - fireEvent.click(button); - expect(onSort).toHaveBeenCalledWith(TABLE_SORT_ORDERS.ASC); - expect(screen.getByRole('columnheader')).toHaveAttribute( - 'aria-sort', - getAriaSort(true, TABLE_SORT_ORDERS.ASC) - ); - }); + fireEvent.click(button); + expect(onSort).toHaveBeenCalledWith(TABLE_SORT_ORDERS.ASC); + expect(screen.getByRole('columnheader')).toHaveAttribute( + 'aria-sort', + getAriaSort(true, TABLE_SORT_ORDERS.ASC) + ); + }); }); diff --git a/client/tests/TestPlanReportStatusDialog.test.jsx b/client/tests/TestPlanReportStatusDialog.test.jsx index 5e3064ccf..c542a01c3 100644 --- a/client/tests/TestPlanReportStatusDialog.test.jsx +++ b/client/tests/TestPlanReportStatusDialog.test.jsx @@ -16,61 +16,59 @@ import { TEST_PLAN_REPORT_STATUS_DIALOG_MOCK_DATA } from './__mocks__/GraphQLMoc import { mockedTestPlanVersion } from './__mocks__/GraphQLMocks/TestPlanReportStatusDialogMock'; const setup = (props, mocks = []) => { - return render( - - - - - - ); + return render( + + + + + + ); }; describe('TestPlanReportStatusDialog', () => { - let getByRole, getByText; + let getByRole, getByText; - beforeEach(() => { - const show = true; - const handleHide = jest.fn(); - const testPlanVersion = mockedTestPlanVersion; - const [ - , - { - result: { - data: { ats } - } - } - ] = TEST_PLAN_REPORT_STATUS_DIALOG_MOCK_DATA; + beforeEach(() => { + const show = true; + const handleHide = jest.fn(); + const testPlanVersion = mockedTestPlanVersion; + const [ + , + { + result: { + data: { ats } + } + } + ] = TEST_PLAN_REPORT_STATUS_DIALOG_MOCK_DATA; - const result = setup( - { testPlanVersion, show, ats, handleHide }, - TEST_PLAN_REPORT_STATUS_DIALOG_MOCK_DATA - ); + const result = setup( + { testPlanVersion, show, ats, handleHide }, + TEST_PLAN_REPORT_STATUS_DIALOG_MOCK_DATA + ); - getByRole = result.getByRole; - getByText = result.getByText; - }); + getByRole = result.getByRole; + getByText = result.getByText; + }); - test('renders without error', async () => { - await waitFor(() => expect(getByRole('dialog')).toBeInTheDocument()); - }); + test('renders without error', async () => { + await waitFor(() => expect(getByRole('dialog')).toBeInTheDocument()); + }); - test('displays the dialog title', async () => { - await waitFor(() => { - expect( - getByText('Report Status for the Test Plan') - ).toBeInTheDocument(); - }); + test('displays the dialog title', async () => { + await waitFor(() => { + expect(getByText('Report Status for the Test Plan')).toBeInTheDocument(); }); + }); - test('displays the table headers', async () => { - await waitFor(() => { - expect(getByText('Required')).toBeInTheDocument(); - expect(getByText('AT')).toBeInTheDocument(); - expect(getByText('Browser')).toBeInTheDocument(); - expect(getByText('Report Status')).toBeInTheDocument(); - }); + test('displays the table headers', async () => { + await waitFor(() => { + expect(getByText('Required')).toBeInTheDocument(); + expect(getByText('AT')).toBeInTheDocument(); + expect(getByText('Browser')).toBeInTheDocument(); + expect(getByText('Report Status')).toBeInTheDocument(); }); + }); }); diff --git a/client/tests/TestQueue.test.jsx b/client/tests/TestQueue.test.jsx index f349ae3bd..d64af01f9 100644 --- a/client/tests/TestQueue.test.jsx +++ b/client/tests/TestQueue.test.jsx @@ -3,320 +3,187 @@ */ import React from 'react'; -import { render, screen, waitFor, fireEvent } from '@testing-library/react'; +import { render, waitFor, fireEvent } from '@testing-library/react'; import { InMemoryCache } from '@apollo/client'; import { MockedProvider } from '@apollo/client/testing'; import { BrowserRouter } from 'react-router-dom'; -import '@testing-library/jest-dom/extend-expect'; +import '@testing-library/jest-dom'; import TestQueue from '../components/TestQueue'; // eslint-disable-next-line jest/no-mocks-import import { - TEST_QUEUE_PAGE_ADMIN_NOT_POPULATED_MOCK_DATA, - TEST_QUEUE_PAGE_TESTER_NOT_POPULATED_MOCK_DATA, - TEST_QUEUE_PAGE_ADMIN_POPULATED_MOCK_DATA, - TEST_QUEUE_PAGE_TESTER_POPULATED_MOCK_DATA, - TEST_QUEUE_PAGE_BASE_MOCK_DATA + TEST_QUEUE_PAGE_ADMIN_NOT_POPULATED_MOCK_DATA, + TEST_QUEUE_PAGE_TESTER_NOT_POPULATED_MOCK_DATA, + TEST_QUEUE_PAGE_BASE_MOCK_DATA } from './__mocks__/GraphQLMocks'; import { AriaLiveRegionProvider } from '../components/providers/AriaLiveRegionProvider'; const setup = (mocks = []) => { - const mergedMocks = [...TEST_QUEUE_PAGE_BASE_MOCK_DATA, ...mocks]; - return render( - - - - - - - - ); + const mergedMocks = [...TEST_QUEUE_PAGE_BASE_MOCK_DATA, ...mocks]; + return render( + + + + + + + + ); }; describe('Render TestQueue/index.jsx', () => { - let wrapper; + let wrapper; - describe('[NOT ADMIN] when no test plan reports exist', () => { - beforeEach(() => { - wrapper = setup(TEST_QUEUE_PAGE_TESTER_NOT_POPULATED_MOCK_DATA); - }); - - it('renders loading state on initialization', async () => { - const { getByTestId } = wrapper; - const element = getByTestId('page-status'); - - expect(element).toBeTruthy(); - expect(element).toHaveTextContent('Loading'); - }); - - // TODO: Revise timeout with pageReady check - it.skip('renders Test Queue page instructions', async () => { - // allow page time to load - - await waitFor(() => new Promise(res => setTimeout(res, 0))); - - const { queryByTestId, getByTestId } = wrapper; - const loadingElement = queryByTestId('page-status'); - const element = getByTestId('test-queue-no-test-plans-p'); - - expect(loadingElement).not.toBeInTheDocument(); - expect(element).toBeTruthy(); - expect(element).toHaveTextContent( - /Please configure your preferred Assistive Technologies in/i - ); - }); - - it('renders no AT-specific sections', async () => { - // allow page time to load - await waitFor(() => new Promise(res => setTimeout(res, 0))); - - const { queryAllByText } = wrapper; - const nvdaElements = queryAllByText(/nvda/i); - const jawsElements = queryAllByText(/jaws/i); - const voiceOverElements = queryAllByText(/voiceover/i); - - expect(nvdaElements.length).toEqual(0); - expect(jawsElements.length).toEqual(0); - expect(voiceOverElements.length).toEqual(0); - }); - - it('does not render add test plan modal button', async () => { - // allow page time to load - await waitFor(() => new Promise(res => setTimeout(res, 0))); - - const { queryByTestId } = wrapper; - const button = queryByTestId( - 'test-queue-add-test-plan-to-queue-button' - ); - - expect(button).not.toBeTruthy(); - }); + describe('not admin when no test plan reports exist', () => { + beforeEach(() => { + wrapper = setup(TEST_QUEUE_PAGE_TESTER_NOT_POPULATED_MOCK_DATA); }); - describe('[NOT ADMIN] when test plan reports exist', () => { - beforeEach(() => { - wrapper = setup(TEST_QUEUE_PAGE_TESTER_POPULATED_MOCK_DATA); - }); - - it('renders loading state on initialization', async () => { - const { getByTestId } = wrapper; - const element = getByTestId('page-status'); - - expect(element).toBeTruthy(); - expect(element).toHaveTextContent('Loading'); - }); - - it.skip('renders Test Queue page instructions', async () => { - // allow page time to load - await waitFor(() => new Promise(res => setTimeout(res, 0))); - - const { queryByTestId, getByTestId } = wrapper; - const loadingElement = queryByTestId('page-status'); - const element = getByTestId('test-queue-instructions'); - - expect(loadingElement).not.toBeInTheDocument(); - expect(element).toBeTruthy(); - expect(element).toHaveTextContent( - 'Assign yourself a test plan or start executing one that is already assigned to you.' - ); - }); - - it('renders AT-specific sections', async () => { - // allow page time to load - await waitFor(() => new Promise(res => setTimeout(res, 0))); - - const { queryAllByText } = wrapper; - const nvdaElements = queryAllByText(/nvda/i); - const jawsElements = queryAllByText(/jaws/i); - const voiceOverElements = queryAllByText(/voiceover/i); - - expect(nvdaElements.length).toBeGreaterThanOrEqual(1); - expect(jawsElements.length).toBeGreaterThanOrEqual(1); - expect(voiceOverElements.length).toBeGreaterThanOrEqual(1); - }); - - it('renders testers are assigned to Test Plans', async () => { - // allow page time to load - await waitFor(() => new Promise(res => setTimeout(res, 0))); - - const { queryAllByText } = wrapper; - const userAAssignedElements = queryAllByText(/foo-bar/i); - const userBAssignedElements = queryAllByText(/bar-foo/i); - const userCAssignedElements = queryAllByText(/boo-far/i); - const assignedTestsElements = queryAllByText( - /\d+ of \d+ tests complete/i - ); - - expect(userAAssignedElements.length).toBeGreaterThanOrEqual(1); - expect(userBAssignedElements.length).toBeGreaterThanOrEqual(1); - expect(userCAssignedElements.length).toBeGreaterThanOrEqual(1); - expect(assignedTestsElements.length).toBeGreaterThanOrEqual(1); - }); - - it('does not render add test plan modal button', async () => { - // allow page time to load - await waitFor(() => new Promise(res => setTimeout(res, 0))); - - const { queryByTestId } = wrapper; - const button = queryByTestId( - 'test-queue-add-test-plan-to-queue-button' - ); + it('renders loading state on initialization', async () => { + const { queryByTestId } = wrapper; + const element = queryByTestId('page-status'); - expect(button).not.toBeTruthy(); - }); + expect(element).toBeTruthy(); + expect(element).toHaveTextContent('Loading'); }); - describe('[IS ADMIN] when no test plan reports exist', () => { - beforeEach(() => { - wrapper = setup(TEST_QUEUE_PAGE_ADMIN_NOT_POPULATED_MOCK_DATA); - }); - - it('renders loading state on initialization', async () => { - const { getByTestId } = wrapper; - const element = getByTestId('page-status'); - - expect(element).toBeTruthy(); - expect(element).toHaveTextContent('Loading'); - }); - - it('renders Test Queue page instructions', async () => { - // allow page time to load - await waitFor(() => new Promise(res => setTimeout(res, 0))); - - const { queryByTestId, getByTestId } = wrapper; - const loadingElement = queryByTestId('page-status'); - const element = getByTestId('test-queue-no-test-plans-p'); - - expect(loadingElement).not.toBeInTheDocument(); - expect(element).toBeTruthy(); - expect(element).toHaveTextContent(/Add a Test Plan to the Queue/i); - }); - - it('renders no AT-specific sections', async () => { - // allow page time to load - await waitFor(() => new Promise(res => setTimeout(res, 0))); - - const { queryAllByText } = wrapper; - const nvdaElements = queryAllByText(/nvda/i); - const jawsElements = queryAllByText(/jaws/i); - const voiceOverElements = queryAllByText(/voiceover/i); - - expect(nvdaElements.length).toEqual(0); - expect(jawsElements.length).toEqual(0); - expect(voiceOverElements.length).toEqual(0); - }); - - it.skip('renders add test plan modal on button click', async () => { - // allow page time to load - await waitFor(() => new Promise(res => setTimeout(res, 0))); - - const { queryByTestId } = wrapper; - const button = queryByTestId( - 'test-queue-add-test-plan-to-queue-button' - ); - - expect(button).toBeTruthy(); - - // opens modal - fireEvent.click(button); - - expect( - screen.getByText('Select an AT and Version') - ).toBeInTheDocument(); - expect( - screen.getByText('Select a Browser and Version') - ).toBeInTheDocument(); - expect( - screen.getByText('Select a Test Plan and Version') - ).toBeInTheDocument(); - }); + it('renders Test Queue page instructions', async () => { + // allow page time to load + await waitFor(() => new Promise(res => setTimeout(res, 0))); + + const { queryByTestId } = wrapper; + const loadingElement = queryByTestId('page-status'); + const noTestPlansMessageElement = queryByTestId('no-test-plans'); + const addTestPlanToQueueMessageElement = queryByTestId( + 'add-test-plans-queue' + ); + + expect(loadingElement).not.toBeInTheDocument(); + expect(noTestPlansMessageElement).toBeTruthy(); + expect(noTestPlansMessageElement).toHaveTextContent( + /There are currently no test plan reports available/i + ); + expect(addTestPlanToQueueMessageElement).not.toBeTruthy(); }); - describe('[IS ADMIN] when test plan reports exist', () => { - beforeEach(() => { - wrapper = setup(TEST_QUEUE_PAGE_ADMIN_POPULATED_MOCK_DATA); - }); - - it('renders loading state on initialization', async () => { - const { getByTestId } = wrapper; - const element = getByTestId('page-status'); - - expect(element).toBeTruthy(); - expect(element).toHaveTextContent('Loading'); - }); + it('renders no AT-specific sections', async () => { + // allow page time to load + await waitFor(() => new Promise(res => setTimeout(res, 0))); - it.skip('renders Test Queue page instructions', async () => { - // allow page time to load - await waitFor(() => new Promise(res => setTimeout(res, 0))); + const { queryAllByText } = wrapper; + const nvdaElements = queryAllByText(/nvda/i); + const jawsElements = queryAllByText(/jaws/i); + const voiceOverElements = queryAllByText(/voiceover/i); - const { queryByTestId, getByTestId } = wrapper; - const loadingElement = queryByTestId('page-status'); - const element = getByTestId('test-queue-instructions'); - - expect(loadingElement).not.toBeInTheDocument(); - expect(element).toBeTruthy(); - expect(element).toHaveTextContent( - 'Assign yourself a test plan or start executing one that is already assigned to you.' - ); - }); - - it('renders AT-specific sections', async () => { - // allow page time to load - await waitFor(() => new Promise(res => setTimeout(res, 0))); + expect(nvdaElements.length).toEqual(0); + expect(jawsElements.length).toEqual(0); + expect(voiceOverElements.length).toEqual(0); + }); - const { queryAllByText } = wrapper; - const nvdaElements = queryAllByText(/nvda/i); - const jawsElements = queryAllByText(/jaws/i); - const voiceOverElements = queryAllByText(/voiceover/i); + it('does not render ManageTestQueue component', async () => { + // allow page time to load + await waitFor(() => new Promise(res => setTimeout(res, 0))); + + const { queryByText } = wrapper; + expect( + queryByText('Manage Assistive Technology Versions') + ).not.toBeInTheDocument(); + expect( + queryByText('Add Test Plans to the Test Queue') + ).not.toBeInTheDocument(); + }); + }); - expect(nvdaElements.length).toBeGreaterThanOrEqual(1); - expect(jawsElements.length).toBeGreaterThanOrEqual(1); - expect(voiceOverElements.length).toBeGreaterThanOrEqual(1); - }); + describe('is admin when no test plan reports exist', () => { + beforeEach(() => { + wrapper = setup(TEST_QUEUE_PAGE_ADMIN_NOT_POPULATED_MOCK_DATA); + }); - it('renders testers are assigned to Test Plans', async () => { - // allow page time to load - await waitFor(() => new Promise(res => setTimeout(res, 0))); + it('renders loading state on initialization', async () => { + const { queryByTestId } = wrapper; + const element = queryByTestId('page-status'); - const { queryAllByText } = wrapper; - const userAAssignedElements = queryAllByText(/esmeralda-baggins/i); - const userBAssignedElements = queryAllByText(/tom-proudfeet/i); - const assignedTestsElements = queryAllByText( - /\d+ of \d+ tests complete/i - ); + expect(element).toBeTruthy(); + expect(element).toHaveTextContent('Loading'); + }); - expect(userAAssignedElements.length).toBeGreaterThanOrEqual(1); - expect(userBAssignedElements.length).toBeGreaterThanOrEqual(1); - expect(assignedTestsElements.length).toBeGreaterThanOrEqual(1); - }); + it('renders Test Queue page instructions', async () => { + // allow page time to load + await waitFor(() => new Promise(res => setTimeout(res, 0))); + + const { queryByTestId } = wrapper; + const loadingElement = queryByTestId('page-status'); + const noTestPlansMessageElement = queryByTestId('no-test-plans'); + const addTestPlanToQueueMessageElement = queryByTestId( + 'add-test-plans-queue' + ); + + expect(loadingElement).not.toBeInTheDocument(); + expect(noTestPlansMessageElement).toBeTruthy(); + expect(noTestPlansMessageElement).toHaveTextContent( + /There are currently no test plan reports available/i + ); + expect(addTestPlanToQueueMessageElement).toHaveTextContent( + /Add a Test Plan to the Queue/i + ); + }); - it.skip('renders add test plan modal on button click', async () => { - // allow page time to load - await waitFor(() => new Promise(res => setTimeout(res, 0))); + it('renders no AT-specific sections', async () => { + // allow page time to load + await waitFor(() => new Promise(res => setTimeout(res, 0))); - const { queryByTestId } = wrapper; - const button = queryByTestId( - 'test-queue-add-test-plan-to-queue-button' - ); + const { queryAllByText } = wrapper; + const nvdaElements = queryAllByText(/nvda/i); + const jawsElements = queryAllByText(/jaws/i); + const voiceOverElements = queryAllByText(/voiceover/i); - expect(button).toBeTruthy(); + expect(nvdaElements.length).toEqual(0); + expect(jawsElements.length).toEqual(0); + expect(voiceOverElements.length).toEqual(0); + }); - // opens modal - fireEvent.click(button); + it('renders ManageTestQueue component', async () => { + // allow page time to load + await waitFor(() => new Promise(res => setTimeout(res, 0))); + + const { queryByText } = wrapper; + expect( + queryByText('Manage Assistive Technology Versions') + ).toBeInTheDocument(); + expect( + queryByText('Add Test Plans to the Test Queue') + ).toBeInTheDocument(); + }); - expect( - screen.getByText('Select an AT and Version') - ).toBeInTheDocument(); - expect( - screen.getByText('Select a Browser and Version') - ).toBeInTheDocument(); - expect( - screen.getByText('Select a Test Plan and Version') - ).toBeInTheDocument(); - }); + it('renders add test plan modal on button click', async () => { + // allow page time to load + await waitFor(() => new Promise(res => setTimeout(res, 0))); + + const { queryByRole, queryByText } = wrapper; + const manageAssistiveTechnologyVersionsButton = queryByRole('button', { + name: 'Manage Assistive Technology Versions' + }); + const addTestPlansToTestQueueButton = queryByRole('button', { + name: 'Add Test Plans to the Test Queue' + }); + + fireEvent.click(manageAssistiveTechnologyVersionsButton); + fireEvent.click(addTestPlansToTestQueueButton); + + expect( + queryByText( + 'Select an assistive technology and manage its versions in the ARIA-AT App' + ) + ).toBeVisible(); + expect( + queryByText( + 'Select a test plan, assistive technology and browser to add a new test plan report to the test queue.' + ) + ).toBeVisible(); }); + }); }); diff --git a/client/tests/TestRenderer/OutputTextArea.test.jsx b/client/tests/TestRenderer/OutputTextArea.test.jsx index 4a466a714..20b9acabb 100644 --- a/client/tests/TestRenderer/OutputTextArea.test.jsx +++ b/client/tests/TestRenderer/OutputTextArea.test.jsx @@ -5,134 +5,134 @@ import React from 'react'; import { render, fireEvent, screen } from '@testing-library/react'; import OutputTextArea from '../../components/TestRenderer/OutputTextArea'; -import '@testing-library/jest-dom/extend-expect'; +import '@testing-library/jest-dom'; import { NO_OUTPUT_STRING } from '../../components/TestRenderer/OutputTextArea/constants'; describe('OutputTextArea', () => { - let atOutputMock; - const change = value => { - atOutputMock.value = value; - }; + let atOutputMock; + const change = value => { + atOutputMock.value = value; + }; - atOutputMock = { - description: [ - 'Description', - { - required: true, - highlightRequired: true, - description: 'Highlight Description' - } - ], - value: '', - change: change, - focus: false - }; + atOutputMock = { + description: [ + 'Description', + { + required: true, + highlightRequired: true, + description: 'Highlight Description' + } + ], + value: '', + change: change, + focus: false + }; - it('should render with default settings', () => { - render( - - ); - expect(screen.getByLabelText('Description')).toBeInTheDocument(); - expect(screen.getByLabelText('No output')).not.toBeChecked(); - }); + it('should render with default settings', () => { + render( + + ); + expect(screen.getByLabelText('Description')).toBeInTheDocument(); + expect(screen.getByLabelText('No output')).not.toBeChecked(); + }); - it('should render correct label and checkbox', () => { - render( - - ); - expect(screen.getByLabelText('Description')).toBeInTheDocument(); - expect(screen.getByLabelText('No output')).toBeInTheDocument(); - }); + it('should render correct label and checkbox', () => { + render( + + ); + expect(screen.getByLabelText('Description')).toBeInTheDocument(); + expect(screen.getByLabelText('No output')).toBeInTheDocument(); + }); - it('should update the textarea with the correct value when checkbox is checked', () => { - render( - - ); - const checkbox = screen.getByLabelText('No output'); - const textarea = screen.getByLabelText('Description'); + it('should update the textarea with the correct value when checkbox is checked', () => { + render( + + ); + const checkbox = screen.getByLabelText('No output'); + const textarea = screen.getByLabelText('Description'); - expect(textarea.value).toBe(''); + expect(textarea.value).toBe(''); - fireEvent.click(checkbox); - expect(atOutputMock.value).toBe(NO_OUTPUT_STRING); - }); + fireEvent.click(checkbox); + expect(atOutputMock.value).toBe(NO_OUTPUT_STRING); + }); - it('should handle textarea change', () => { - render( - - ); - const textarea = screen.getByLabelText('Description'); - fireEvent.change(textarea, { target: { value: 'test value' } }); - expect(atOutputMock.value).toBe('test value'); - }); + it('should handle textarea change', () => { + render( + + ); + const textarea = screen.getByLabelText('Description'); + fireEvent.change(textarea, { target: { value: 'test value' } }); + expect(atOutputMock.value).toBe('test value'); + }); - it('should disable the textarea when the checkbox is checked', () => { - render( - - ); - const checkbox = screen.getByLabelText('No output'); - const textarea = screen.getByLabelText('Description'); + it('should disable the textarea when the checkbox is checked', () => { + render( + + ); + const checkbox = screen.getByLabelText('No output'); + const textarea = screen.getByLabelText('Description'); - expect(textarea).not.toBeDisabled(); - fireEvent.click(checkbox); - expect(textarea).toBeDisabled(); - }); + expect(textarea).not.toBeDisabled(); + fireEvent.click(checkbox); + expect(textarea).toBeDisabled(); + }); - it('should have associated label for textarea', () => { - render( - - ); - const textarea = screen.getByLabelText('Description'); - expect(textarea).toHaveAttribute('id', 'speechoutput-0'); - }); + it('should have associated label for textarea', () => { + render( + + ); + const textarea = screen.getByLabelText('Description'); + expect(textarea).toHaveAttribute('id', 'speechoutput-0'); + }); - it('should disable checkbox when textarea has a value', () => { - const prefilledMock = { ...atOutputMock, value: 'test value' }; - render( - - ); - const checkbox = screen.getByLabelText('No output'); - expect(checkbox).toBeDisabled(); - }); + it('should disable checkbox when textarea has a value', () => { + const prefilledMock = { ...atOutputMock, value: 'test value' }; + render( + + ); + const checkbox = screen.getByLabelText('No output'); + expect(checkbox).toBeDisabled(); + }); - it('should enable checkbox when textarea loads with no output default value', () => { - const prefilledMock = { ...atOutputMock, value: NO_OUTPUT_STRING }; - render( - - ); - const checkbox = screen.getByLabelText('No output'); - expect(checkbox).not.toBeDisabled(); - }); + it('should enable checkbox when textarea loads with no output default value', () => { + const prefilledMock = { ...atOutputMock, value: NO_OUTPUT_STRING }; + render( + + ); + const checkbox = screen.getByLabelText('No output'); + expect(checkbox).not.toBeDisabled(); + }); }); diff --git a/client/tests/TestRun.test.jsx b/client/tests/TestRun.test.jsx index b4359669a..d74b000d5 100644 --- a/client/tests/TestRun.test.jsx +++ b/client/tests/TestRun.test.jsx @@ -10,40 +10,40 @@ import { findByTestAttr } from './util'; import TestRun from '../components/TestRun'; const setup = () => { - // Step into the higher order connected component and step into the contents of the UserSettings component - const wrapper = shallow( - - ) - .dive() - .dive(); - return wrapper; + // Step into the higher order connected component and step into the contents of the UserSettings component + const wrapper = shallow( + + ) + .dive() + .dive(); + return wrapper; }; describe.skip('render', () => { - describe('loading when there are no tests', () => { - let wrapper; - beforeEach(() => { - wrapper = setup(); - }); - test('renders not logged in text', () => { - const component = findByTestAttr(wrapper, 'test-run-page-status'); - expect(component.text()).toContain('Loading'); - }); + describe('loading when there are no tests', () => { + let wrapper; + beforeEach(() => { + wrapper = setup(); }); - describe('tests are loaded', () => { - let wrapper; - beforeEach(() => { - wrapper = setup(); - wrapper.setState({ currentTestIndex: 1 }); - }); - test('renders testing headings', () => { - let component = findByTestAttr(wrapper, 'apg-example-name'); - expect(component.length).toBe(1); - expect(component.text()).toContain('Test Plan'); + test('renders not logged in text', () => { + const component = findByTestAttr(wrapper, 'test-run-page-status'); + expect(component.text()).toContain('Loading'); + }); + }); + describe('tests are loaded', () => { + let wrapper; + beforeEach(() => { + wrapper = setup(); + wrapper.setState({ currentTestIndex: 1 }); + }); + test('renders testing headings', () => { + let component = findByTestAttr(wrapper, 'apg-example-name'); + expect(component.length).toBe(1); + expect(component.text()).toContain('Test Plan'); - component = findByTestAttr(wrapper, 'at-browser'); - expect(component.length).toBe(1); - expect(component.text()).toContain('with'); - }); + component = findByTestAttr(wrapper, 'at-browser'); + expect(component.length).toBe(1); + expect(component.text()).toContain('with'); }); + }); }); diff --git a/client/tests/UserSettings.test.jsx b/client/tests/UserSettings.test.jsx index f639c453f..6eed0a264 100644 --- a/client/tests/UserSettings.test.jsx +++ b/client/tests/UserSettings.test.jsx @@ -6,50 +6,44 @@ import { findByTestAttr } from './util'; import UserSettings from '../components/UserSettings'; const setup = () => { - // Step into the higher order connected component and step into the contents of the UserSettings component - const wrapper = shallow() - .dive() - .dive(); - return wrapper; + // Step into the higher order connected component and step into the contents of the UserSettings component + const wrapper = shallow() + .dive() + .dive(); + return wrapper; }; describe.skip('render', () => { - describe('user is not signed in', () => { - let wrapper; - beforeEach(() => { - wrapper = setup(); - }); - test('renders component without error', () => { - const component = findByTestAttr(wrapper, 'user-settings-contents'); - expect(component.length).toBe(1); - }); - test('renders not signed in text', () => { - const component = findByTestAttr( - wrapper, - 'user-settings-unauthorized' - ); - expect(component.length).toBe(1); - }); + describe('user is not signed in', () => { + let wrapper; + beforeEach(() => { + wrapper = setup(); }); - describe('user is signed in', () => { - let wrapper; - beforeEach(() => { - wrapper = setup(); - }); - test('renders component without error', () => { - const component = findByTestAttr(wrapper, 'user-settings-contents'); - expect(component.length).toBe(1); - }); - test('renders username', () => { - const component = findByTestAttr( - wrapper, - 'user-settings-authorized' - ); - expect(component.length).toBe(1); - expect(component.text()).toContain('User Details'); - expect(component.find('a').at(0).props().href).toContain( - 'https://github.com' - ); - }); + test('renders component without error', () => { + const component = findByTestAttr(wrapper, 'user-settings-contents'); + expect(component.length).toBe(1); }); + test('renders not signed in text', () => { + const component = findByTestAttr(wrapper, 'user-settings-unauthorized'); + expect(component.length).toBe(1); + }); + }); + describe('user is signed in', () => { + let wrapper; + beforeEach(() => { + wrapper = setup(); + }); + test('renders component without error', () => { + const component = findByTestAttr(wrapper, 'user-settings-contents'); + expect(component.length).toBe(1); + }); + test('renders username', () => { + const component = findByTestAttr(wrapper, 'user-settings-authorized'); + expect(component.length).toBe(1); + expect(component.text()).toContain('User Details'); + expect(component.find('a').at(0).props().href).toContain( + 'https://github.com' + ); + }); + }); }); diff --git a/client/tests/__mocks__/GraphQLMocks/DataManagementPagePopulatedMock.js b/client/tests/__mocks__/GraphQLMocks/DataManagementPagePopulatedMock.js index 60973e8d7..7053cf593 100644 --- a/client/tests/__mocks__/GraphQLMocks/DataManagementPagePopulatedMock.js +++ b/client/tests/__mocks__/GraphQLMocks/DataManagementPagePopulatedMock.js @@ -1,7286 +1,7417 @@ export default ( - meQuery, - dataManagementPageQuery, - testPlanReportStatusDialogQuery + meQuery, + dataManagementPageQuery, + testPlanReportStatusDialogQuery ) => [ - { - request: { - query: meQuery - }, - result: { - data: { - me: { - id: '1', - username: 'foo-bar', - roles: ['ADMIN', 'TESTER'] - } - } + { + request: { + query: meQuery + }, + result: { + data: { + me: { + id: '1', + username: 'foo-bar', + roles: ['ADMIN', 'TESTER'] } + } + } + }, + { + request: { + query: dataManagementPageQuery }, - { - request: { - query: dataManagementPageQuery + result: { + data: { + me: { + id: '1', + username: 'foo-bar', + roles: ['ADMIN', 'TESTER'] }, - result: { - data: { - me: { - id: '1', - username: 'foo-bar', - roles: ['ADMIN', 'TESTER'] - }, - ats: [ - { - id: '1', - key: 'jaws', - name: 'JAWS', - atVersions: [ - { - id: '1', - name: '2021.2111.13', - releasedAt: '2021-11-01T04:00:00.000Z' - } - ], - browsers: [ - { - id: '2', - key: 'chrome', - name: 'Chrome' - }, - { - id: '1', - key: 'firefox', - name: 'Firefox' - } - ], - candidateBrowsers: [{ id: '2' }], - recommendedBrowsers: [{ id: '1' }, { id: '2' }] - }, - { - id: '2', - key: 'nvda', - name: 'NVDA', - atVersions: [ - { - id: '2', - name: '2020.4', - releasedAt: '2021-02-19T05:00:00.000Z' - } - ], - browsers: [ - { - id: '2', - key: 'chrome', - name: 'Chrome' - }, - { - id: '1', - key: 'firefox', - name: 'Firefox' - } - ], - candidateBrowsers: [{ id: '2' }], - recommendedBrowsers: [{ id: '1' }, { id: '2' }] - }, + ats: [ + { + id: '1', + key: 'jaws', + name: 'JAWS', + atVersions: [ + { + id: '1', + name: '2021.2111.13', + releasedAt: '2021-11-01T04:00:00.000Z', + supportedByAutomation: false + } + ], + browsers: [ + { + id: '2', + key: 'chrome', + name: 'Chrome' + }, + { + id: '1', + key: 'firefox', + name: 'Firefox' + } + ], + candidateBrowsers: [{ id: '2' }], + recommendedBrowsers: [{ id: '1' }, { id: '2' }] + }, + { + id: '2', + key: 'nvda', + name: 'NVDA', + atVersions: [ + { + id: '2', + name: '2020.4', + releasedAt: '2021-02-19T05:00:00.000Z', + supportedByAutomation: false + } + ], + browsers: [ + { + id: '2', + key: 'chrome', + name: 'Chrome' + }, + { + id: '1', + key: 'firefox', + name: 'Firefox' + } + ], + candidateBrowsers: [{ id: '2' }], + recommendedBrowsers: [{ id: '1' }, { id: '2' }] + }, + { + id: '3', + key: 'voiceover_macos', + name: 'VoiceOver for macOS', + atVersions: [ + { + id: '3', + name: '11.6 (20G165)', + releasedAt: '2019-09-01T04:00:00.000Z', + supportedByAutomation: false + } + ], + browsers: [ + { + id: '2', + key: 'chrome', + name: 'Chrome' + }, + { + id: '1', + key: 'firefox', + name: 'Firefox' + }, + { + id: '3', + key: 'safari_macos', + name: 'Safari' + } + ], + candidateBrowsers: [{ id: '3' }], + recommendedBrowsers: [{ id: '2' }, { id: '3' }] + } + ], + testPlans: [ + { + id: '27', + directory: 'radiogroup-aria-activedescendant', + title: 'Radio Group Example Using aria-activedescendant' + }, + { + id: '28', + directory: 'radiogroup-roving-tabindex', + title: 'Radio Group Example Using Roving tabindex' + }, + { + id: '31', + directory: 'slider-multithumb', + title: 'Horizontal Multi-Thumb Slider' + }, + { + id: '16', + directory: 'link-css', + title: + 'Link Example 3 (CSS :before content property on a span element)' + }, + { + id: '17', + directory: 'link-img-alt', + title: 'Link Example 2 (img element with alt attribute)' + }, + { + id: '1', + directory: 'alert', + title: 'Alert Example' + }, + { + id: '13', + directory: 'disclosure-navigation', + title: 'Disclosure Navigation Menu Example' + }, + { + id: '5', + directory: 'checkbox-tri-state', + title: 'Checkbox Example (Mixed-State)' + }, + { + id: '3', + directory: 'breadcrumb', + title: 'Breadcrumb Example' + }, + { + id: '19', + directory: 'main', + title: 'Main Landmark' + }, + { + id: '24', + directory: 'meter', + title: 'Meter' + }, + { + id: '32', + directory: 'switch', + title: 'Switch Example' + }, + { + id: '26', + directory: 'modal-dialog', + title: 'Modal Dialog Example' + }, + { + id: '22', + directory: 'menu-button-navigation', + title: 'Navigation Menu Button' + }, + { + id: '34', + directory: 'toggle-button', + title: 'Toggle Button' + }, + { + id: '18', + directory: 'link-span-text', + title: 'Link Example 1 (span element with text content)' + }, + { + id: '8', + directory: 'command-button', + title: 'Command Button Example' + }, + { + id: '15', + directory: 'horizontal-slider', + title: 'Color Viewer Slider' + }, + { + id: '6', + directory: 'combobox-autocomplete-both-updated', + title: 'Combobox with Both List and Inline Autocomplete Example' + }, + { + id: '7', + directory: 'combobox-select-only', + title: 'Select Only Combobox Example' + }, + { + id: '4', + directory: 'checkbox', + title: 'Checkbox Example (Two State)' + }, + { + id: '9', + directory: 'complementary', + title: 'Complementary Landmark' + }, + { + id: '10', + directory: 'contentinfo', + title: 'Contentinfo Landmark' + }, + { + id: '11', + directory: 'datepicker-spin-button', + title: 'Date Picker Spin Button Example' + }, + { + id: '12', + directory: 'disclosure-faq', + title: 'Disclosure of Answers to Frequently Asked Questions Example' + }, + { + id: '14', + directory: 'form', + title: 'Form Landmark' + }, + { + id: '20', + directory: 'menu-button-actions', + title: 'Action Menu Button Example Using element.focus()' + }, + { + id: '21', + directory: 'menu-button-actions-active-descendant', + title: 'Action Menu Button Example Using aria-activedescendant' + }, + { + id: '23', + directory: 'menubar-editor', + title: 'Editor Menubar Example' + }, + { + id: '25', + directory: 'minimal-data-grid', + title: 'Data Grid Example 1: Minimal Data Grid' + }, + { + id: '29', + directory: 'rating-slider', + title: 'Rating Slider' + }, + { + id: '30', + directory: 'seek-slider', + title: 'Media Seek Slider' + }, + { + id: '33', + directory: 'tabs-manual-activation', + title: 'Tabs with Manual Activation' + }, + { + id: '35', + directory: 'vertical-temperature-slider', + title: 'Vertical Temperature Slider' + }, + { + id: '2', + directory: 'banner', + title: 'Banner Landmark' + } + ], + deprecatedTestPlanVersions: [], + testPlanVersions: [ + { + id: '28', + title: 'Radio Group Example Using Roving tabindex', + phase: 'RD', + gitSha: '1768070bd68beefef29284b568d2da910b449c14', + gitMessage: + 'Remove Tab and Shift+Tab from radiogroup tests when navigating out of the start and end of a radio group (reading mode and VoiceOver only) (#928)', + updatedAt: '2023-04-10T18:22:22.000Z', + versionString: 'V23.04.10', + draftPhaseReachedAt: null, + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'radiogroup-roving-tabindex' + }, + testPlanReports: [], + metadata: {} + }, + { + id: '27', + title: 'Radio Group Example Using aria-activedescendant', + phase: 'RD', + gitSha: '1768070bd68beefef29284b568d2da910b449c14', + gitMessage: + 'Remove Tab and Shift+Tab from radiogroup tests when navigating out of the start and end of a radio group (reading mode and VoiceOver only) (#928)', + updatedAt: '2023-04-10T18:22:22.000Z', + versionString: 'V23.04.10', + draftPhaseReachedAt: null, + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'radiogroup-aria-activedescendant' + }, + testPlanReports: [], + metadata: {} + }, + { + id: '31', + title: 'Horizontal Multi-Thumb Slider', + phase: 'RD', + gitSha: 'b5fe3efd569518e449ef9a0978b0dec1f2a08bd6', + gitMessage: + 'Create tests for APG design pattern example: Horizontal Multi-Thumb Slider (#511)', + updatedAt: '2023-03-20T21:24:41.000Z', + versionString: 'V23.03.20', + draftPhaseReachedAt: null, + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'slider-multithumb' + }, + testPlanReports: [], + metadata: {} + }, + { + id: '16', + title: + 'Link Example 3 (CSS :before content property on a span element)', + phase: 'RD', + gitSha: '7a8454bca6de980199868101431817cea03cce35', + gitMessage: + 'Create tests for APG design pattern example: Link Example 3 (CSS :before content property on a span element) (#518)', + updatedAt: '2023-03-13T22:10:13.000Z', + versionString: 'V23.03.13', + draftPhaseReachedAt: null, + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'link-css' + }, + testPlanReports: [], + metadata: {} + }, + { + id: '17', + title: 'Link Example 2 (img element with alt attribute)', + phase: 'RD', + gitSha: 'dc637636cff74b51f5c468ff3b81bd1f38aefbb2', + gitMessage: + 'Create tests for APG design pattern example: Link Example 2 (img element with alt attribute) (#516)', + updatedAt: '2023-03-13T19:51:48.000Z', + versionString: 'V23.03.13', + draftPhaseReachedAt: null, + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'link-img-alt' + }, + testPlanReports: [], + metadata: {} + }, + { + id: '1', + title: 'Alert Example', + phase: 'DRAFT', + gitSha: '0928bcf530efcf4faa677285439701537674e014', + gitMessage: 'Alert and Radiogroup/activedescendent updates (#865)', + updatedAt: '2022-12-08T21:47:42.000Z', + versionString: 'V22.12.08', + draftPhaseReachedAt: '2022-07-06T00:00:00.000Z', + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'alert' + }, + testPlanReports: [ + { + id: '7', + metrics: {}, + markedFinalAt: null, + isFinal: false, + at: { + id: '3', + key: 'voiceover_macos', + name: 'VoiceOver for macOS', + atVersions: [ { - id: '3', - key: 'voiceover_macos', - name: 'VoiceOver for macOS', - atVersions: [ - { - id: '3', - name: '11.6 (20G165)', - releasedAt: '2019-09-01T04:00:00.000Z' - } - ], - browsers: [ - { - id: '2', - key: 'chrome', - name: 'Chrome' - }, - { - id: '1', - key: 'firefox', - name: 'Firefox' - }, - { - id: '3', - key: 'safari_macos', - name: 'Safari' - } - ], - candidateBrowsers: [{ id: '3' }], - recommendedBrowsers: [{ id: '2' }, { id: '3' }] + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' } - ], - testPlans: [ - { - id: '27', - directory: 'radiogroup-aria-activedescendant', - title: 'Radio Group Example Using aria-activedescendant' - }, - { - id: '28', - directory: 'radiogroup-roving-tabindex', - title: 'Radio Group Example Using Roving tabindex' - }, - { - id: '31', - directory: 'slider-multithumb', - title: 'Horizontal Multi-Thumb Slider' - }, - { - id: '16', - directory: 'link-css', - title: 'Link Example 3 (CSS :before content property on a span element)' - }, - { - id: '17', - directory: 'link-img-alt', - title: 'Link Example 2 (img element with alt attribute)' - }, - { - id: '1', - directory: 'alert', - title: 'Alert Example' - }, - { - id: '13', - directory: 'disclosure-navigation', - title: 'Disclosure Navigation Menu Example' - }, - { - id: '5', - directory: 'checkbox-tri-state', - title: 'Checkbox Example (Mixed-State)' - }, - { - id: '3', - directory: 'breadcrumb', - title: 'Breadcrumb Example' - }, - { - id: '19', - directory: 'main', - title: 'Main Landmark' - }, - { - id: '24', - directory: 'meter', - title: 'Meter' + ] + }, + browser: { + id: '1', + key: 'firefox', + name: 'Firefox' + }, + issues: [], + draftTestPlanRuns: [] + } + ], + metadata: {} + }, + { + id: '13', + title: 'Disclosure Navigation Menu Example', + phase: 'RD', + gitSha: '179ba0f438aaa5781b3ec8a4033d6bf9f757360b', + gitMessage: + 'Delete up arrow command for VoiceOver when navigating backwards to a disclosure button (#845)', + updatedAt: '2022-10-31T19:29:17.000Z', + versionString: 'V22.10.31', + draftPhaseReachedAt: null, + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'disclosure-navigation' + }, + testPlanReports: [], + metadata: {} + }, + { + id: '3', + title: 'Breadcrumb Example', + phase: 'RD', + gitSha: '1aa3b74d24d340362e9f511eae33788d55487d12', + gitMessage: + 'Add down arrow command to the Navigate forwards out of the Breadcrumb navigation landmark task for JAWS (#803)', + updatedAt: '2022-08-10T18:44:16.000Z', + versionString: 'V22.08.10', + draftPhaseReachedAt: null, + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'breadcrumb' + }, + testPlanReports: [], + metadata: {} + }, + { + id: '19', + title: 'Main Landmark', + phase: 'RD', + gitSha: 'c87a66ea13a2b6fac6d79fe1fb0b7a2f721dcd22', + gitMessage: + 'Create updated tests for APG design pattern example: Main landmark (#707)', + updatedAt: '2022-08-05T17:46:37.000Z', + versionString: 'V22.08.05', + draftPhaseReachedAt: null, + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'main' + }, + testPlanReports: [], + metadata: {} + }, + { + id: '24', + title: 'Meter', + phase: 'RD', + gitSha: '32d2d9db48becfc008fc566b569ac1563576ceb9', + gitMessage: + 'Create updated tests for APG design pattern example: Meter (#692)', + updatedAt: '2022-08-05T17:02:59.000Z', + versionString: 'V22.08.05', + draftPhaseReachedAt: null, + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'meter' + }, + testPlanReports: [], + metadata: {} + }, + { + id: '32', + title: 'Switch Example', + phase: 'RD', + gitSha: '9d0e4e3d1040d64d9db69647e615c4ec0be723c2', + gitMessage: + 'Create updated tests for APG design pattern example: Switch (#691)', + updatedAt: '2022-08-05T16:13:44.000Z', + versionString: 'V22.08.05', + draftPhaseReachedAt: null, + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'switch' + }, + testPlanReports: [], + metadata: {} + }, + { + id: '22', + title: 'Navigation Menu Button', + phase: 'RD', + gitSha: 'ecf05f484292189789f4db8b1ec41b19db38e567', + gitMessage: + 'Tasks 4, 5 and 6: corrected link name "Navigate backwards from here" (#734)', + updatedAt: '2022-05-26T16:14:17.000Z', + versionString: 'V22.05.26', + draftPhaseReachedAt: null, + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'menu-button-navigation' + }, + testPlanReports: [], + metadata: {} + }, + { + id: '34', + title: 'Toggle Button', + phase: 'DRAFT', + gitSha: '022340081280b8cafb8ae0716a5b67e9ab942ef4', + gitMessage: + 'Delete duplicated assertion for operating a not pressed togle button (VoiceOver) (#716)', + updatedAt: '2022-05-18T20:51:40.000Z', + versionString: 'V22.05.18', + draftPhaseReachedAt: '2022-07-06T00:00:00.000Z', + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'toggle-button' + }, + testPlanReports: [ + { + id: '1', + metrics: { + testsCount: 16, + supportLevel: 'FAILING', + conflictsCount: 0, + supportPercent: 93, + testsFailedCount: 14, + testsPassedCount: 2, + shouldFormatted: false, + mustFormatted: '28 of 30 passed', + shouldAssertionsCount: 0, + mustAssertionsCount: 30, + unexpectedBehaviorCount: 1, + unexpectedBehaviorsFormatted: '1 found', + shouldAssertionsFailedCount: 0, + shouldAssertionsPassedCount: 0, + mustAssertionsFailedCount: 2, + mustAssertionsPassedCount: 28 + }, + markedFinalAt: null, + isFinal: false, + at: { + id: '1', + key: 'jaws', + name: 'JAWS' + }, + browser: { + id: '2', + key: 'chrome', + name: 'Chrome' + }, + issues: [], + draftTestPlanRuns: [ + { + tester: { + username: 'esmeralda-baggins' }, - { - id: '32', - directory: 'switch', - title: 'Switch Example' + testPlanReport: { + id: '1' }, + testResults: [ + { + test: { + id: 'OWY5NeyIyIjoiMzQifQTRmOD' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2023-08-18T03:17:07.154Z' + }, + { + test: { + id: 'NGFjMeyIyIjoiMzQifQjQxY2' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: null + }, + { + test: { + id: 'NTAwOeyIyIjoiMzQifQWI5YT' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: null + }, + { + test: { + id: 'YThjMeyIyIjoiMzQifQzIyYT' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: null + }, + { + test: { + id: 'YTgxMeyIyIjoiMzQifQzExOW' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2023-08-18T03:17:07.381Z' + }, + { + test: { + id: 'NGMwNeyIyIjoiMzQifQ2IwN2' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2023-08-18T03:17:07.464Z' + }, + { + test: { + id: 'YzQxNeyIyIjoiMzQifQjY5ND' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2023-08-18T03:17:07.537Z' + }, + { + test: { + id: 'MjgwNeyIyIjoiMzQifQzk3YT' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2023-08-18T03:17:07.610Z' + } + ] + } + ] + } + ], + metadata: {} + }, + { + id: '8', + title: 'Command Button Example', + phase: 'RD', + gitSha: '0c466eec96c8cafc9961232c85e14758c4589525', + gitMessage: + 'Fix navigation link positions in three test plans: link, command button and toggle button (#709)', + updatedAt: '2022-05-04T21:33:31.000Z', + versionString: 'V22.05.04', + draftPhaseReachedAt: null, + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'command-button' + }, + testPlanReports: [], + metadata: {} + }, + { + id: '18', + title: 'Link Example 1 (span element with text content)', + phase: 'RD', + gitSha: '0c466eec96c8cafc9961232c85e14758c4589525', + gitMessage: + 'Fix navigation link positions in three test plans: link, command button and toggle button (#709)', + updatedAt: '2022-05-04T21:33:31.000Z', + versionString: 'V22.05.04', + draftPhaseReachedAt: null, + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'link-span-text' + }, + testPlanReports: [], + metadata: {} + }, + { + id: '15', + title: 'Color Viewer Slider', + phase: 'RD', + gitSha: '1c6ef2fbef5fc056c622c802bebedaa14f2c8d40', + gitMessage: + 'Create updated tests for APG design pattern example: Color Viewer Slider (#686)', + updatedAt: '2022-04-14T18:06:40.000Z', + versionString: 'V22.04.14', + draftPhaseReachedAt: null, + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'horizontal-slider' + }, + testPlanReports: [], + metadata: {} + }, + { + id: '6', + title: 'Combobox with Both List and Inline Autocomplete Example', + phase: 'RD', + gitSha: '6b2cbcbdbd5f6867cd3c9e96362817c353335187', + gitMessage: "typo: double word 'the' (#595)", + updatedAt: '2022-03-29T16:02:56.000Z', + versionString: 'V22.03.29', + draftPhaseReachedAt: null, + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'combobox-autocomplete-both-updated' + }, + testPlanReports: [], + metadata: {} + }, + { + id: '21', + title: 'Action Menu Button Example Using aria-activedescendant', + phase: 'RD', + gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', + gitMessage: + 'Generate html source script to support aria-at-app (#646)', + updatedAt: '2022-03-17T18:34:51.000Z', + versionString: 'V22.03.17', + draftPhaseReachedAt: null, + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'menu-button-actions-active-descendant' + }, + testPlanReports: [], + metadata: {} + }, + { + id: '20', + title: 'Action Menu Button Example Using element.focus()', + phase: 'RD', + gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', + gitMessage: + 'Generate html source script to support aria-at-app (#646)', + updatedAt: '2022-03-17T18:34:51.000Z', + versionString: 'V22.03.17', + draftPhaseReachedAt: null, + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'menu-button-actions' + }, + testPlanReports: [], + metadata: {} + }, + { + id: '2', + title: 'Banner Landmark', + phase: 'RD', + gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', + gitMessage: + 'Generate html source script to support aria-at-app (#646)', + updatedAt: '2022-03-17T18:34:51.000Z', + versionString: 'V22.03.17', + draftPhaseReachedAt: null, + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'banner' + }, + testPlanReports: [], + metadata: {} + }, + { + id: '4', + title: 'Checkbox Example (Two State)', + phase: 'RD', + gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', + gitMessage: + 'Generate html source script to support aria-at-app (#646)', + updatedAt: '2022-03-17T18:34:51.000Z', + versionString: 'V22.03.17', + draftPhaseReachedAt: null, + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'checkbox' + }, + testPlanReports: [], + metadata: {} + }, + { + id: '9', + title: 'Complementary Landmark', + phase: 'RD', + gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', + gitMessage: + 'Generate html source script to support aria-at-app (#646)', + updatedAt: '2022-03-17T18:34:51.000Z', + versionString: 'V22.03.17', + draftPhaseReachedAt: null, + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'complementary' + }, + testPlanReports: [], + metadata: {} + }, + { + id: '10', + title: 'Contentinfo Landmark', + phase: 'RD', + gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', + gitMessage: + 'Generate html source script to support aria-at-app (#646)', + updatedAt: '2022-03-17T18:34:51.000Z', + versionString: 'V22.03.17', + draftPhaseReachedAt: null, + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'contentinfo' + }, + testPlanReports: [], + metadata: {} + }, + { + id: '25', + title: 'Data Grid Example 1: Minimal Data Grid', + phase: 'RD', + gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', + gitMessage: + 'Generate html source script to support aria-at-app (#646)', + updatedAt: '2022-03-17T18:34:51.000Z', + versionString: 'V22.03.17', + draftPhaseReachedAt: null, + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'minimal-data-grid' + }, + testPlanReports: [], + metadata: {} + }, + { + id: '11', + title: 'Date Picker Spin Button Example', + phase: 'RD', + gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', + gitMessage: + 'Generate html source script to support aria-at-app (#646)', + updatedAt: '2022-03-17T18:34:51.000Z', + versionString: 'V22.03.17', + draftPhaseReachedAt: null, + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'datepicker-spin-button' + }, + testPlanReports: [], + metadata: {} + }, + { + id: '12', + title: + 'Disclosure of Answers to Frequently Asked Questions Example', + phase: 'RD', + gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', + gitMessage: + 'Generate html source script to support aria-at-app (#646)', + updatedAt: '2022-03-17T18:34:51.000Z', + versionString: 'V22.03.17', + draftPhaseReachedAt: null, + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'disclosure-faq' + }, + testPlanReports: [], + metadata: {} + }, + { + id: '23', + title: 'Editor Menubar Example', + phase: 'RD', + gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', + gitMessage: + 'Generate html source script to support aria-at-app (#646)', + updatedAt: '2022-03-17T18:34:51.000Z', + versionString: 'V22.03.17', + draftPhaseReachedAt: null, + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'menubar-editor' + }, + testPlanReports: [], + metadata: {} + }, + { + id: '14', + title: 'Form Landmark', + phase: 'RD', + gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', + gitMessage: + 'Generate html source script to support aria-at-app (#646)', + updatedAt: '2022-03-17T18:34:51.000Z', + versionString: 'V22.03.17', + draftPhaseReachedAt: null, + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'form' + }, + testPlanReports: [], + metadata: {} + }, + { + id: '30', + title: 'Media Seek Slider', + phase: 'RD', + gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', + gitMessage: + 'Generate html source script to support aria-at-app (#646)', + updatedAt: '2022-03-17T18:34:51.000Z', + versionString: 'V22.03.17', + draftPhaseReachedAt: null, + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'seek-slider' + }, + testPlanReports: [], + metadata: {} + }, + { + id: '29', + title: 'Rating Slider', + phase: 'RD', + gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', + gitMessage: + 'Generate html source script to support aria-at-app (#646)', + updatedAt: '2022-03-17T18:34:51.000Z', + versionString: 'V22.03.17', + draftPhaseReachedAt: null, + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'rating-slider' + }, + testPlanReports: [], + metadata: {} + }, + { + id: '7', + title: 'Select Only Combobox Example', + phase: 'DRAFT', + gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', + gitMessage: + 'Generate html source script to support aria-at-app (#646)', + updatedAt: '2022-03-17T18:34:51.000Z', + versionString: 'V22.03.17', + draftPhaseReachedAt: '2022-07-06T00:00:00.000Z', + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'combobox-select-only' + }, + testPlanReports: [ + { + id: '2', + metrics: { + testsCount: 21, + supportLevel: 'FAILING', + conflictsCount: 5, + supportPercent: 96, + testsFailedCount: 16, + testsPassedCount: 5, + shouldFormatted: '3 of 3 passed', + mustFormatted: '48 of 50 passed', + shouldAssertionsCount: 3, + mustAssertionsCount: 50, + unexpectedBehaviorCount: 3, + unexpectedBehaviorsFormatted: '3 found', + shouldAssertionsFailedCount: 0, + shouldAssertionsPassedCount: 3, + mustAssertionsFailedCount: 2, + mustAssertionsPassedCount: 48 + }, + markedFinalAt: null, + isFinal: false, + at: { + id: '2', + key: 'nvda', + name: 'NVDA', + atVersions: [ { - id: '26', - directory: 'modal-dialog', - title: 'Modal Dialog Example' + id: '2', + name: '2020.4', + supportedByAutomation: false, + releasedAt: '2020-04-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '1', + key: 'firefox', + name: 'Firefox' + }, + issues: [], + draftTestPlanRuns: [ + { + tester: { + username: 'tom-proudfeet' }, - { - id: '22', - directory: 'menu-button-navigation', - title: 'Navigation Menu Button' + testPlanReport: { + id: '2' }, - { - id: '34', - directory: 'toggle-button', - title: 'Toggle Button' + testResults: [ + { + test: { + id: 'Nzg5NeyIyIjoiNyJ9zNjZj' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2023-08-18T03:17:08.240Z' + }, + { + test: { + id: 'MmY0YeyIyIjoiNyJ9jRkZD' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2023-08-18T03:17:08.332Z' + }, + { + test: { + id: 'ZjUwNeyIyIjoiNyJ9mE2ZT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2023-08-18T03:17:08.412Z' + }, + { + test: { + id: 'MDNiMeyIyIjoiNyJ9Dk1MT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2023-08-18T03:17:08.501Z' + }, + { + test: { + id: 'MjRmNeyIyIjoiNyJ92MyMT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2023-08-18T03:17:08.593Z' + }, + { + test: { + id: 'ZmVlMeyIyIjoiNyJ9mUyYj' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: null + }, + { + test: { + id: 'YWFiNeyIyIjoiNyJ9zE2Zj' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2023-08-18T03:17:08.811Z' + }, + { + test: { + id: 'YjZkYeyIyIjoiNyJ9WIxZm' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2023-08-18T03:17:08.902Z' + }, + { + test: { + id: 'ZmIzMeyIyIjoiNyJ9TQ1NW' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2023-08-18T03:17:08.996Z' + } + ] + }, + { + tester: { + username: 'esmeralda-baggins' }, - { - id: '18', - directory: 'link-span-text', - title: 'Link Example 1 (span element with text content)' + testPlanReport: { + id: '2' }, - { - id: '8', - directory: 'command-button', - title: 'Command Button Example' + testResults: [ + { + test: { + id: 'Nzg5NeyIyIjoiNyJ9zNjZj' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2023-08-18T03:17:07.718Z' + }, + { + test: { + id: 'MmY0YeyIyIjoiNyJ9jRkZD' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2023-08-18T03:17:07.813Z' + }, + { + test: { + id: 'ZjUwNeyIyIjoiNyJ9mE2ZT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2023-08-18T03:17:07.914Z' + }, + { + test: { + id: 'MDNiMeyIyIjoiNyJ9Dk1MT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2023-08-18T03:17:07.988Z' + }, + { + test: { + id: 'MjRmNeyIyIjoiNyJ92MyMT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2023-08-18T03:17:08.074Z' + }, + { + test: { + id: 'ZmVlMeyIyIjoiNyJ9mUyYj' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: null + } + ] + } + ] + } + ], + metadata: {} + }, + { + id: '33', + title: 'Tabs with Manual Activation', + phase: 'RD', + gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', + gitMessage: + 'Generate html source script to support aria-at-app (#646)', + updatedAt: '2022-03-17T18:34:51.000Z', + versionString: 'V22.03.17', + draftPhaseReachedAt: null, + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'tabs-manual-activation' + }, + testPlanReports: [], + metadata: {} + }, + { + id: '35', + title: 'Vertical Temperature Slider', + phase: 'RD', + gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', + gitMessage: + 'Generate html source script to support aria-at-app (#646)', + updatedAt: '2022-03-17T18:34:51.000Z', + versionString: 'V22.03.17', + draftPhaseReachedAt: null, + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'vertical-temperature-slider' + }, + testPlanReports: [], + metadata: {} + }, + { + id: '5', + title: 'Checkbox Example (Mixed-State)', + phase: 'RECOMMENDED', + gitSha: 'b3d0576a2901ea7f100f49a994b64edbecf81cff', + gitMessage: 'Modify VoiceOver commands for task 7 (#842)', + updatedAt: '2022-10-24T21:33:12.000Z', + versionString: 'V22.10.24', + draftPhaseReachedAt: null, + candidatePhaseReachedAt: '2022-07-06T00:00:00.000Z', + recommendedPhaseTargetDate: '2023-01-02T00:00:00.000Z', + recommendedPhaseReachedAt: '2023-01-03T00:00:00.000Z', + testPlan: { + directory: 'checkbox-tri-state' + }, + testPlanReports: [ + { + id: '6', + metrics: { + testsCount: 7, + supportLevel: 'FAILING', + conflictsCount: 0, + supportPercent: 96, + testsFailedCount: 3, + testsPassedCount: 4, + shouldFormatted: '4 of 4 passed', + mustFormatted: '44 of 46 passed', + shouldAssertionsCount: 4, + mustAssertionsCount: 46, + unexpectedBehaviorCount: 0, + unexpectedBehaviorsFormatted: false, + shouldAssertionsFailedCount: 0, + shouldAssertionsPassedCount: 4, + mustAssertionsFailedCount: 2, + mustAssertionsPassedCount: 44 + }, + markedFinalAt: '2022-07-06T00:00:00.000Z', + isFinal: true, + at: { + id: '3', + key: 'voiceover_macos', + name: 'VoiceOver for macOS' + }, + browser: { + id: '3', + key: 'safari_macos', + name: 'Safari' + }, + issues: [], + draftTestPlanRuns: [ + { + tester: { + username: 'tom-proudfeet' }, - { - id: '15', - directory: 'horizontal-slider', - title: 'Color Viewer Slider' + testPlanReport: { + id: '6' }, - { - id: '6', - directory: 'combobox-autocomplete-both-updated', - title: 'Combobox with Both List and Inline Autocomplete Example' + testResults: [ + { + test: { + id: 'YTE3NeyIyIjoiNSJ9WJlMj' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2023-08-18T03:17:10.341Z' + }, + { + test: { + id: 'YWJiOeyIyIjoiNSJ9GQ5Zm' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2023-08-18T03:17:10.405Z' + }, + { + test: { + id: 'ZGFlYeyIyIjoiNSJ9TJlMW' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2023-08-18T03:17:10.474Z' + }, + { + test: { + id: 'YjI2MeyIyIjoiNSJ9WE1OT' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2023-08-18T03:17:10.537Z' + }, + { + test: { + id: 'ZjAwZeyIyIjoiNSJ9TZmZj' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2023-08-18T03:17:10.605Z' + }, + { + test: { + id: 'MGRjZeyIyIjoiNSJ9WNiZD' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2023-08-18T03:17:10.670Z' + }, + { + test: { + id: 'OTZmYeyIyIjoiNSJ9TU5Ym' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2023-08-18T03:17:10.739Z' + } + ] + } + ] + }, + { + id: '12', + metrics: { + testsCount: 14, + supportLevel: 'FULL', + conflictsCount: 0, + supportPercent: 100, + testsFailedCount: 12, + testsPassedCount: 2, + shouldFormatted: false, + mustFormatted: '25 of 25 passed', + shouldAssertionsCount: 0, + mustAssertionsCount: 25, + unexpectedBehaviorCount: 0, + unexpectedBehaviorsFormatted: false, + shouldAssertionsFailedCount: 0, + shouldAssertionsPassedCount: 0, + mustAssertionsFailedCount: 0, + mustAssertionsPassedCount: 25 + }, + markedFinalAt: null, + isFinal: false, + at: { + id: '1', + key: 'jaws', + name: 'JAWS' + }, + browser: { + id: '2', + key: 'chrome', + name: 'Chrome' + }, + issues: [], + draftTestPlanRuns: [ + { + tester: { + username: 'esmeralda-baggins' }, - { - id: '7', - directory: 'combobox-select-only', - title: 'Select Only Combobox Example' + testPlanReport: { + id: '12' }, + testResults: [ + { + test: { + id: 'MTVlZeyIyIjoiNSJ9DUzMz' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2023-08-18T03:17:11.764Z' + }, + { + test: { + id: 'OThhMeyIyIjoiNSJ9WMxM2' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2023-08-18T03:17:11.828Z' + }, + { + test: { + id: 'YWNhNeyIyIjoiNSJ9TliN2' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2023-08-18T03:17:11.892Z' + } + ] + } + ] + }, + { + id: '13', + metrics: { + testsCount: 14, + supportLevel: 'FULL', + conflictsCount: 0, + supportPercent: 100, + testsFailedCount: 12, + testsPassedCount: 2, + shouldFormatted: false, + mustFormatted: '25 of 25 passed', + shouldAssertionsCount: 0, + mustAssertionsCount: 25, + unexpectedBehaviorCount: 0, + unexpectedBehaviorsFormatted: false, + shouldAssertionsFailedCount: 0, + shouldAssertionsPassedCount: 0, + mustAssertionsFailedCount: 0, + mustAssertionsPassedCount: 25 + }, + markedFinalAt: null, + isFinal: false, + at: { + id: '2', + key: 'nvda', + name: 'NVDA', + atVersions: [ { - id: '4', - directory: 'checkbox', - title: 'Checkbox Example (Two State)' + id: '2', + name: '2020.4', + supportedByAutomation: false, + releasedAt: '2020-04-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '2', + key: 'chrome', + name: 'Chrome' + }, + issues: [], + draftTestPlanRuns: [ + { + tester: { + username: 'esmeralda-baggins' }, - { - id: '9', - directory: 'complementary', - title: 'Complementary Landmark' + testPlanReport: { + id: '13' }, - { - id: '10', - directory: 'contentinfo', - title: 'Contentinfo Landmark' + testResults: [ + { + test: { + id: 'MTVlZeyIyIjoiNSJ9DUzMz' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2023-08-18T03:17:11.955Z' + }, + { + test: { + id: 'OThhMeyIyIjoiNSJ9WMxM2' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2023-08-18T03:17:12.017Z' + }, + { + test: { + id: 'YWNhNeyIyIjoiNSJ9TliN2' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2023-08-18T03:17:12.083Z' + } + ] + } + ] + } + ], + metadata: {} + }, + { + id: '26', + title: 'Modal Dialog Example', + phase: 'CANDIDATE', + gitSha: 'd0e16b42179de6f6c070da2310e99de837c71215', + gitMessage: + 'Delete down arrow command for navigating to the beginning of a dialog with JAWS and add the ESC command to exit forms or focus mode (#759)', + updatedAt: '2022-06-22T17:56:16.000Z', + versionString: 'V22.06.22', + draftPhaseReachedAt: '2022-07-06T00:00:00.000Z', + candidatePhaseReachedAt: '2022-07-06T00:00:00.000Z', + recommendedPhaseTargetDate: '2023-01-02T00:00:00.000Z', + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'modal-dialog' + }, + testPlanReports: [ + { + id: '10', + metrics: { + testsCount: 11, + supportLevel: 'FULL', + conflictsCount: 0, + supportPercent: 100, + testsFailedCount: 9, + testsPassedCount: 2, + shouldFormatted: false, + mustFormatted: '14 of 14 passed', + shouldAssertionsCount: 0, + mustAssertionsCount: 14, + unexpectedBehaviorCount: 0, + unexpectedBehaviorsFormatted: false, + shouldAssertionsFailedCount: 0, + shouldAssertionsPassedCount: 0, + mustAssertionsFailedCount: 0, + mustAssertionsPassedCount: 14 + }, + markedFinalAt: null, + isFinal: false, + at: { + id: '3', + key: 'voiceover_macos', + name: 'VoiceOver for macOS' + }, + browser: { + id: '1', + key: 'firefox', + name: 'Firefox' + }, + issues: [], + draftTestPlanRuns: [ + { + tester: { + username: 'esmeralda-baggins' }, - { - id: '11', - directory: 'datepicker-spin-button', - title: 'Date Picker Spin Button Example' + testPlanReport: { + id: '10' }, + testResults: [ + { + test: { + id: 'MzlmYeyIyIjoiMjYifQzIxY2' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2023-08-18T03:17:11.295Z' + }, + { + test: { + id: 'N2FkZeyIyIjoiMjYifQDQ5NT' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2023-08-18T03:17:11.369Z' + }, + { + test: { + id: 'ZDJkYeyIyIjoiMjYifQzRkYj' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2023-08-18T03:17:11.450Z' + } + ] + } + ] + }, + { + id: '9', + metrics: { + testsCount: 18, + supportLevel: 'FULL', + conflictsCount: 0, + supportPercent: 100, + testsFailedCount: 16, + testsPassedCount: 2, + shouldFormatted: false, + mustFormatted: '16 of 16 passed', + shouldAssertionsCount: 0, + mustAssertionsCount: 16, + unexpectedBehaviorCount: 0, + unexpectedBehaviorsFormatted: false, + shouldAssertionsFailedCount: 0, + shouldAssertionsPassedCount: 0, + mustAssertionsFailedCount: 0, + mustAssertionsPassedCount: 16 + }, + markedFinalAt: null, + isFinal: false, + at: { + id: '2', + key: 'nvda', + name: 'NVDA', + atVersions: [ { - id: '12', - directory: 'disclosure-faq', - title: 'Disclosure of Answers to Frequently Asked Questions Example' + id: '2', + name: '2020.4', + supportedByAutomation: false, + releasedAt: '2020-04-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '1', + key: 'firefox', + name: 'Firefox' + }, + issues: [], + draftTestPlanRuns: [ + { + tester: { + username: 'esmeralda-baggins' }, - { - id: '14', - directory: 'form', - title: 'Form Landmark' + testPlanReport: { + id: '9' }, - { - id: '20', - directory: 'menu-button-actions', - title: 'Action Menu Button Example Using element.focus()' + testResults: [ + { + test: { + id: 'MThhNeyIyIjoiMjYifQmEyMj' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2023-08-18T03:17:11.059Z' + }, + { + test: { + id: 'ODY5MeyIyIjoiMjYifQzhmNW' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2023-08-18T03:17:11.137Z' + }, + { + test: { + id: 'NWVkNeyIyIjoiMjYifQTZkOT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2023-08-18T03:17:11.218Z' + } + ] + } + ] + }, + { + id: '3', + metrics: { + testsCount: 18, + supportLevel: 'FAILING', + conflictsCount: 0, + supportPercent: 88, + testsFailedCount: 16, + testsPassedCount: 2, + shouldFormatted: false, + mustFormatted: '14 of 16 passed', + shouldAssertionsCount: 0, + mustAssertionsCount: 16, + unexpectedBehaviorCount: 1, + unexpectedBehaviorsFormatted: '1 found', + shouldAssertionsFailedCount: 0, + shouldAssertionsPassedCount: 0, + mustAssertionsFailedCount: 2, + mustAssertionsPassedCount: 14 + }, + markedFinalAt: '2022-07-06T00:00:00.000Z', + isFinal: true, + at: { + id: '1', + key: 'jaws', + name: 'JAWS' + }, + browser: { + id: '2', + key: 'chrome', + name: 'Chrome' + }, + issues: [], + draftTestPlanRuns: [ + { + tester: { + username: 'esmeralda-baggins' }, - { - id: '21', - directory: 'menu-button-actions-active-descendant', - title: 'Action Menu Button Example Using aria-activedescendant' + testPlanReport: { + id: '3' }, - { - id: '23', - directory: 'menubar-editor', - title: 'Editor Menubar Example' + testResults: [ + { + test: { + id: 'MThhNeyIyIjoiMjYifQmEyMj' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2023-08-18T03:17:09.074Z' + }, + { + test: { + id: 'NWVkNeyIyIjoiMjYifQTZkOT' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2023-08-18T03:17:09.134Z' + }, + { + test: { + id: 'NWM4NeyIyIjoiMjYifQDEwM2' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2023-08-18T03:17:09.202Z' + }, + { + test: { + id: 'NGFiZeyIyIjoiMjYifQWZiYW' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2023-08-18T03:17:09.268Z' + }, + { + test: { + id: 'MzQzYeyIyIjoiMjYifQzU5Zm' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2023-08-18T03:17:09.336Z' + } + ] + } + ] + }, + { + id: '11', + metrics: { + testsCount: 11, + supportLevel: 'FULL', + conflictsCount: 0, + supportPercent: 100, + testsFailedCount: 9, + testsPassedCount: 2, + shouldFormatted: false, + mustFormatted: '14 of 14 passed', + shouldAssertionsCount: 0, + mustAssertionsCount: 14, + unexpectedBehaviorCount: 0, + unexpectedBehaviorsFormatted: false, + shouldAssertionsFailedCount: 0, + shouldAssertionsPassedCount: 0, + mustAssertionsFailedCount: 0, + mustAssertionsPassedCount: 14 + }, + markedFinalAt: null, + isFinal: false, + at: { + id: '3', + key: 'voiceover_macos', + name: 'VoiceOver for macOS' + }, + browser: { + id: '2', + key: 'chrome', + name: 'Chrome' + }, + issues: [], + draftTestPlanRuns: [ + { + tester: { + username: 'esmeralda-baggins' }, - { - id: '25', - directory: 'minimal-data-grid', - title: 'Data Grid Example 1: Minimal Data Grid' + testPlanReport: { + id: '11' }, + testResults: [ + { + test: { + id: 'MzlmYeyIyIjoiMjYifQzIxY2' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2023-08-18T03:17:11.532Z' + }, + { + test: { + id: 'N2FkZeyIyIjoiMjYifQDQ5NT' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2023-08-18T03:17:11.611Z' + }, + { + test: { + id: 'ZDJkYeyIyIjoiMjYifQzRkYj' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2023-08-18T03:17:11.696Z' + } + ] + } + ] + }, + { + id: '4', + metrics: { + testsCount: 18, + supportLevel: 'FAILING', + conflictsCount: 0, + supportPercent: 91, + testsFailedCount: 14, + testsPassedCount: 4, + shouldFormatted: false, + mustFormatted: '20 of 22 passed', + shouldAssertionsCount: 0, + mustAssertionsCount: 22, + unexpectedBehaviorCount: 1, + unexpectedBehaviorsFormatted: '1 found', + shouldAssertionsFailedCount: 0, + shouldAssertionsPassedCount: 0, + mustAssertionsFailedCount: 2, + mustAssertionsPassedCount: 20 + }, + markedFinalAt: '2022-07-06T00:00:00.000Z', + isFinal: true, + at: { + id: '2', + key: 'nvda', + name: 'NVDA', + atVersions: [ { - id: '29', - directory: 'rating-slider', - title: 'Rating Slider' + id: '2', + name: '2020.4', + supportedByAutomation: false, + releasedAt: '2020-04-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '2', + key: 'chrome', + name: 'Chrome' + }, + issues: [], + draftTestPlanRuns: [ + { + tester: { + username: 'esmeralda-baggins' }, - { - id: '30', - directory: 'seek-slider', - title: 'Media Seek Slider' + testPlanReport: { + id: '4' }, - { - id: '33', - directory: 'tabs-manual-activation', - title: 'Tabs with Manual Activation' - }, - { - id: '35', - directory: 'vertical-temperature-slider', - title: 'Vertical Temperature Slider' - }, - { - id: '2', - directory: 'banner', - title: 'Banner Landmark' - } - ], - deprecatedTestPlanVersions: [], - testPlanVersions: [ - { - id: '28', - title: 'Radio Group Example Using Roving tabindex', - phase: 'RD', - gitSha: '1768070bd68beefef29284b568d2da910b449c14', - gitMessage: - 'Remove Tab and Shift+Tab from radiogroup tests when navigating out of the start and end of a radio group (reading mode and VoiceOver only) (#928)', - updatedAt: '2023-04-10T18:22:22.000Z', - versionString: 'V23.04.10', - draftPhaseReachedAt: null, - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'radiogroup-roving-tabindex' - }, - testPlanReports: [], - metadata: {} - }, - { - id: '27', - title: 'Radio Group Example Using aria-activedescendant', - phase: 'RD', - gitSha: '1768070bd68beefef29284b568d2da910b449c14', - gitMessage: - 'Remove Tab and Shift+Tab from radiogroup tests when navigating out of the start and end of a radio group (reading mode and VoiceOver only) (#928)', - updatedAt: '2023-04-10T18:22:22.000Z', - versionString: 'V23.04.10', - draftPhaseReachedAt: null, - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'radiogroup-aria-activedescendant' - }, - testPlanReports: [], - metadata: {} - }, - { - id: '31', - title: 'Horizontal Multi-Thumb Slider', - phase: 'RD', - gitSha: 'b5fe3efd569518e449ef9a0978b0dec1f2a08bd6', - gitMessage: - 'Create tests for APG design pattern example: Horizontal Multi-Thumb Slider (#511)', - updatedAt: '2023-03-20T21:24:41.000Z', - versionString: 'V23.03.20', - draftPhaseReachedAt: null, - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'slider-multithumb' - }, - testPlanReports: [], - metadata: {} - }, - { - id: '16', - title: 'Link Example 3 (CSS :before content property on a span element)', - phase: 'RD', - gitSha: '7a8454bca6de980199868101431817cea03cce35', - gitMessage: - 'Create tests for APG design pattern example: Link Example 3 (CSS :before content property on a span element) (#518)', - updatedAt: '2023-03-13T22:10:13.000Z', - versionString: 'V23.03.13', - draftPhaseReachedAt: null, - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'link-css' - }, - testPlanReports: [], - metadata: {} - }, - { - id: '17', - title: 'Link Example 2 (img element with alt attribute)', - phase: 'RD', - gitSha: 'dc637636cff74b51f5c468ff3b81bd1f38aefbb2', - gitMessage: - 'Create tests for APG design pattern example: Link Example 2 (img element with alt attribute) (#516)', - updatedAt: '2023-03-13T19:51:48.000Z', - versionString: 'V23.03.13', - draftPhaseReachedAt: null, - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'link-img-alt' - }, - testPlanReports: [], - metadata: {} - }, - { - id: '1', - title: 'Alert Example', - phase: 'DRAFT', - gitSha: '0928bcf530efcf4faa677285439701537674e014', - gitMessage: - 'Alert and Radiogroup/activedescendent updates (#865)', - updatedAt: '2022-12-08T21:47:42.000Z', - versionString: 'V22.12.08', - draftPhaseReachedAt: '2022-07-06T00:00:00.000Z', - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'alert' - }, - testPlanReports: [ - { - id: '7', - metrics: {}, - markedFinalAt: null, - isFinal: false, - at: { - id: '3', - key: 'voiceover_macos', - name: 'VoiceOver for macOS' - }, - browser: { - id: '1', - key: 'firefox', - name: 'Firefox' - }, - issues: [], - draftTestPlanRuns: [] - } - ], - metadata: {} - }, - { - id: '13', - title: 'Disclosure Navigation Menu Example', - phase: 'RD', - gitSha: '179ba0f438aaa5781b3ec8a4033d6bf9f757360b', - gitMessage: - 'Delete up arrow command for VoiceOver when navigating backwards to a disclosure button (#845)', - updatedAt: '2022-10-31T19:29:17.000Z', - versionString: 'V22.10.31', - draftPhaseReachedAt: null, - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'disclosure-navigation' - }, - testPlanReports: [], - metadata: {} - }, - { - id: '3', - title: 'Breadcrumb Example', - phase: 'RD', - gitSha: '1aa3b74d24d340362e9f511eae33788d55487d12', - gitMessage: - 'Add down arrow command to the Navigate forwards out of the Breadcrumb navigation landmark task for JAWS (#803)', - updatedAt: '2022-08-10T18:44:16.000Z', - versionString: 'V22.08.10', - draftPhaseReachedAt: null, - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'breadcrumb' - }, - testPlanReports: [], - metadata: {} - }, - { - id: '19', - title: 'Main Landmark', - phase: 'RD', - gitSha: 'c87a66ea13a2b6fac6d79fe1fb0b7a2f721dcd22', - gitMessage: - 'Create updated tests for APG design pattern example: Main landmark (#707)', - updatedAt: '2022-08-05T17:46:37.000Z', - versionString: 'V22.08.05', - draftPhaseReachedAt: null, - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'main' - }, - testPlanReports: [], - metadata: {} - }, - { - id: '24', - title: 'Meter', - phase: 'RD', - gitSha: '32d2d9db48becfc008fc566b569ac1563576ceb9', - gitMessage: - 'Create updated tests for APG design pattern example: Meter (#692)', - updatedAt: '2022-08-05T17:02:59.000Z', - versionString: 'V22.08.05', - draftPhaseReachedAt: null, - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'meter' - }, - testPlanReports: [], - metadata: {} - }, - { - id: '32', - title: 'Switch Example', - phase: 'RD', - gitSha: '9d0e4e3d1040d64d9db69647e615c4ec0be723c2', - gitMessage: - 'Create updated tests for APG design pattern example: Switch (#691)', - updatedAt: '2022-08-05T16:13:44.000Z', - versionString: 'V22.08.05', - draftPhaseReachedAt: null, - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'switch' - }, - testPlanReports: [], - metadata: {} - }, - { - id: '22', - title: 'Navigation Menu Button', - phase: 'RD', - gitSha: 'ecf05f484292189789f4db8b1ec41b19db38e567', - gitMessage: - 'Tasks 4, 5 and 6: corrected link name "Navigate backwards from here" (#734)', - updatedAt: '2022-05-26T16:14:17.000Z', - versionString: 'V22.05.26', - draftPhaseReachedAt: null, - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'menu-button-navigation' - }, - testPlanReports: [], - metadata: {} - }, - { - id: '34', - title: 'Toggle Button', - phase: 'DRAFT', - gitSha: '022340081280b8cafb8ae0716a5b67e9ab942ef4', - gitMessage: - 'Delete duplicated assertion for operating a not pressed togle button (VoiceOver) (#716)', - updatedAt: '2022-05-18T20:51:40.000Z', - versionString: 'V22.05.18', - draftPhaseReachedAt: '2022-07-06T00:00:00.000Z', - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'toggle-button' - }, - testPlanReports: [ - { - id: '1', - metrics: { - testsCount: 16, - supportLevel: 'FAILING', - conflictsCount: 0, - supportPercent: 93, - testsFailedCount: 14, - testsPassedCount: 2, - shouldFormatted: false, - mustFormatted: '28 of 30 passed', - shouldAssertionsCount: 0, - mustAssertionsCount: 30, - unexpectedBehaviorCount: 1, - unexpectedBehaviorsFormatted: '1 found', - shouldAssertionsFailedCount: 0, - shouldAssertionsPassedCount: 0, - mustAssertionsFailedCount: 2, - mustAssertionsPassedCount: 28 - }, - markedFinalAt: null, - isFinal: false, - at: { - id: '1', - key: 'jaws', - name: 'JAWS' - }, - browser: { - id: '2', - key: 'chrome', - name: 'Chrome' - }, - issues: [], - draftTestPlanRuns: [ - { - tester: { - username: 'esmeralda-baggins' - }, - testPlanReport: { - id: '1' - }, - testResults: [ - { - test: { - id: 'OWY5NeyIyIjoiMzQifQTRmOD' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2023-08-18T03:17:07.154Z' - }, - { - test: { - id: 'NGFjMeyIyIjoiMzQifQjQxY2' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: null - }, - { - test: { - id: 'NTAwOeyIyIjoiMzQifQWI5YT' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: null - }, - { - test: { - id: 'YThjMeyIyIjoiMzQifQzIyYT' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: null - }, - { - test: { - id: 'YTgxMeyIyIjoiMzQifQzExOW' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2023-08-18T03:17:07.381Z' - }, - { - test: { - id: 'NGMwNeyIyIjoiMzQifQ2IwN2' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2023-08-18T03:17:07.464Z' - }, - { - test: { - id: 'YzQxNeyIyIjoiMzQifQjY5ND' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2023-08-18T03:17:07.537Z' - }, - { - test: { - id: 'MjgwNeyIyIjoiMzQifQzk3YT' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2023-08-18T03:17:07.610Z' - } - ] - } - ] - } - ], - metadata: {} - }, - { - id: '8', - title: 'Command Button Example', - phase: 'RD', - gitSha: '0c466eec96c8cafc9961232c85e14758c4589525', - gitMessage: - 'Fix navigation link positions in three test plans: link, command button and toggle button (#709)', - updatedAt: '2022-05-04T21:33:31.000Z', - versionString: 'V22.05.04', - draftPhaseReachedAt: null, - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'command-button' - }, - testPlanReports: [], - metadata: {} - }, - { - id: '18', - title: 'Link Example 1 (span element with text content)', - phase: 'RD', - gitSha: '0c466eec96c8cafc9961232c85e14758c4589525', - gitMessage: - 'Fix navigation link positions in three test plans: link, command button and toggle button (#709)', - updatedAt: '2022-05-04T21:33:31.000Z', - versionString: 'V22.05.04', - draftPhaseReachedAt: null, - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'link-span-text' - }, - testPlanReports: [], - metadata: {} + testResults: [ + { + test: { + id: 'MThhNeyIyIjoiMjYifQmEyMj' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2023-08-18T03:17:09.409Z' + }, + { + test: { + id: 'NWVkNeyIyIjoiMjYifQTZkOT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2023-08-18T03:17:09.478Z' + }, + { + test: { + id: 'NWM4NeyIyIjoiMjYifQDEwM2' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2023-08-18T03:17:09.551Z' + }, + { + test: { + id: 'NGFiZeyIyIjoiMjYifQWZiYW' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2023-08-18T03:17:09.629Z' + }, + { + test: { + id: 'MzQzYeyIyIjoiMjYifQzU5Zm' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2023-08-18T03:17:09.704Z' + }, + { + test: { + id: 'MmI1MeyIyIjoiMjYifQmU3Yz' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2023-08-18T03:17:09.777Z' + }, + { + test: { + id: 'YmRmYeyIyIjoiMjYifQjEyMT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2023-08-18T03:17:09.852Z' + } + ] + } + ] + }, + { + id: '5', + metrics: { + testsCount: 11, + supportLevel: 'FAILING', + conflictsCount: 0, + supportPercent: 92, + testsFailedCount: 8, + testsPassedCount: 3, + shouldFormatted: false, + mustFormatted: '23 of 25 passed', + shouldAssertionsCount: 0, + mustAssertionsCount: 25, + unexpectedBehaviorCount: 1, + unexpectedBehaviorsFormatted: '1 found', + shouldAssertionsFailedCount: 0, + shouldAssertionsPassedCount: 0, + mustAssertionsFailedCount: 2, + mustAssertionsPassedCount: 23 + }, + markedFinalAt: '2022-07-06T00:00:00.000Z', + isFinal: true, + at: { + id: '3', + key: 'voiceover_macos', + name: 'VoiceOver for macOS' + }, + browser: { + id: '3', + key: 'safari_macos', + name: 'Safari' + }, + issues: [], + draftTestPlanRuns: [ + { + tester: { + username: 'esmeralda-baggins' }, - { - id: '15', - title: 'Color Viewer Slider', - phase: 'RD', - gitSha: '1c6ef2fbef5fc056c622c802bebedaa14f2c8d40', - gitMessage: - 'Create updated tests for APG design pattern example: Color Viewer Slider (#686)', - updatedAt: '2022-04-14T18:06:40.000Z', - versionString: 'V22.04.14', - draftPhaseReachedAt: null, - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'horizontal-slider' - }, - testPlanReports: [], - metadata: {} + testPlanReport: { + id: '5' }, - { - id: '6', - title: 'Combobox with Both List and Inline Autocomplete Example', - phase: 'RD', - gitSha: '6b2cbcbdbd5f6867cd3c9e96362817c353335187', - gitMessage: "typo: double word 'the' (#595)", - updatedAt: '2022-03-29T16:02:56.000Z', - versionString: 'V22.03.29', - draftPhaseReachedAt: null, - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'combobox-autocomplete-both-updated' - }, - testPlanReports: [], - metadata: {} + testResults: [ + { + test: { + id: 'MzlmYeyIyIjoiMjYifQzIxY2' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2023-08-18T03:17:09.923Z' + }, + { + test: { + id: 'ZDJkYeyIyIjoiMjYifQzRkYj' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2023-08-18T03:17:09.991Z' + }, + { + test: { + id: 'ZmQyNeyIyIjoiMjYifQ2M2ND' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2023-08-18T03:17:10.059Z' + }, + { + test: { + id: 'OGE3YeyIyIjoiMjYifQjU1ND' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2023-08-18T03:17:10.129Z' + }, + { + test: { + id: 'YWI3OeyIyIjoiMjYifQWJlNW' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2023-08-18T03:17:10.198Z' + }, + { + test: { + id: 'M2RiOeyIyIjoiMjYifQGY1Nj' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2023-08-18T03:17:10.272Z' + } + ] + } + ] + }, + { + id: '8', + metrics: { + testsCount: 18, + supportLevel: 'FULL', + conflictsCount: 0, + supportPercent: 100, + testsFailedCount: 16, + testsPassedCount: 2, + shouldFormatted: false, + mustFormatted: '16 of 16 passed', + shouldAssertionsCount: 0, + mustAssertionsCount: 16, + unexpectedBehaviorCount: 0, + unexpectedBehaviorsFormatted: false, + shouldAssertionsFailedCount: 0, + shouldAssertionsPassedCount: 0, + mustAssertionsFailedCount: 0, + mustAssertionsPassedCount: 16 + }, + markedFinalAt: null, + isFinal: false, + at: { + id: '1', + key: 'jaws', + name: 'JAWS' + }, + browser: { + id: '1', + key: 'firefox', + name: 'Firefox' + }, + issues: [], + draftTestPlanRuns: [ + { + tester: { + username: 'esmeralda-baggins' }, - { - id: '21', - title: 'Action Menu Button Example Using aria-activedescendant', - phase: 'RD', - gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', - gitMessage: - 'Generate html source script to support aria-at-app (#646)', - updatedAt: '2022-03-17T18:34:51.000Z', - versionString: 'V22.03.17', - draftPhaseReachedAt: null, - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'menu-button-actions-active-descendant' - }, - testPlanReports: [], - metadata: {} + testPlanReport: { + id: '8' }, - { - id: '20', - title: 'Action Menu Button Example Using element.focus()', - phase: 'RD', - gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', - gitMessage: - 'Generate html source script to support aria-at-app (#646)', - updatedAt: '2022-03-17T18:34:51.000Z', - versionString: 'V22.03.17', - draftPhaseReachedAt: null, - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'menu-button-actions' - }, - testPlanReports: [], - metadata: {} + testResults: [ + { + test: { + id: 'MThhNeyIyIjoiMjYifQmEyMj' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2023-08-18T03:17:10.817Z' + }, + { + test: { + id: 'ODY5MeyIyIjoiMjYifQzhmNW' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2023-08-18T03:17:10.894Z' + }, + { + test: { + id: 'NWVkNeyIyIjoiMjYifQTZkOT' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2023-08-18T03:17:10.979Z' + } + ] + } + ] + } + ], + metadata: {} + } + ] + } + } + }, + { + request: { + query: testPlanReportStatusDialogQuery, + variables: { testPlanVersionId: '1' } + }, + result: { + data: { + testPlanVersion: { + id: '1', + title: 'Alert Example', + phase: 'DRAFT', + gitSha: 'c665367f3742c2b607f7b3c2655782188b93f302', + gitMessage: + 'Create updated tests for APG design pattern example: Alert (#685)', + updatedAt: '2022-04-14T17:59:42.000Z', + draftPhaseReachedAt: '2022-07-06T00:00:00.000Z', + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'alert' + }, + testPlanReportStatuses: [ + { + isRequired: true, + at: { + id: '1', + key: 'jaws', + name: 'JAWS', + atVersions: [ + { + id: '1', + name: '2021.2111.13', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '2', + key: 'chrome', + name: 'Chrome' + }, + minimumAtVersion: { + id: '1', + name: '2021.2111.13', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: { + id: '101', + metrics: {}, + isFinal: false, + markedFinalAt: null, + issues: [], + draftTestPlanRuns: [] + } + }, + { + isRequired: false, + at: { + id: '1', + key: 'jaws', + name: 'JAWS', + atVersions: [ + { + id: '1', + name: '2021.2111.13', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '1', + key: 'chrome', + name: 'Firefox' + }, + minimumAtVersion: { + id: '1', + name: '2021.2111.13', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: null + }, + { + isRequired: true, + at: { + id: '2', + key: 'nvda', + name: 'NVDA', + atVersions: [ + { + id: '2', + name: '2020.4', + supportedByAutomation: false, + releasedAt: '2020-04-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '2', + key: 'chrome', + name: 'Chrome' + }, + minimumAtVersion: { + id: '2', + name: '2020.4', + supportedByAutomation: false, + releasedAt: '2020-04-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: null + }, + { + isRequired: false, + at: { + id: '2', + key: 'nvda', + name: 'NVDA', + atVersions: [ + { + id: '2', + name: '2020.4', + supportedByAutomation: false, + releasedAt: '2020-04-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '1', + key: 'firefox', + name: 'Firefox' + }, + minimumAtVersion: { + id: '2', + name: '2020.4', + supportedByAutomation: false, + releasedAt: '2020-04-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: null + }, + { + isRequired: true, + at: { + id: '3', + key: 'voiceover_macos', + name: 'VoiceOver for macOS', + atVersions: [ + { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '3', + key: 'safari_macos', + name: 'Safari' + }, + minimumAtVersion: { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: null + }, + { + isRequired: false, + at: { + id: '3', + key: 'voiceover_macos', + name: 'VoiceOver for macOS', + atVersions: [ + { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '2', + key: 'chrome', + name: 'Chrome' + }, + minimumAtVersion: { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: null + }, + { + isRequired: false, + at: { + id: '3', + key: 'voiceover_macos', + name: 'VoiceOver for macOS', + atVersions: [ + { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '1', + key: 'firefox', + name: 'Firefox' + }, + minimumAtVersion: { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: { + id: '7', + metrics: {}, + isFinal: false, + markedFinalAt: null, + issues: [], + draftTestPlanRuns: [] + } + } + ] + } + } + } + }, + { + request: { + query: testPlanReportStatusDialogQuery, + variables: { testPlanVersionId: '5' } + }, + result: { + data: { + testPlanVersion: { + id: '69', + title: 'Checkbox Example (Mixed-State)', + phase: 'RECOMMENDED', + gitSha: '836fb2a997f5b2844035b8c934f8fda9833cd5b2', + gitMessage: 'Validation for test csv formats (#980)', + updatedAt: '2023-08-23T20:30:34.000Z', + draftPhaseReachedAt: null, + candidatePhaseReachedAt: '2022-07-06T00:00:00.000Z', + recommendedPhaseTargetDate: '2023-01-02T00:00:00.000Z', + recommendedPhaseReachedAt: '2023-01-03T00:00:00.000Z', + testPlan: { + directory: 'checkbox-tri-state' + }, + testPlanReportStatuses: [ + { + isRequired: true, + at: { + id: '1', + key: 'jaws', + name: 'JAWS', + atVersions: [ + { + id: '1', + name: '2021.2111.13', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '2', + key: 'chrome', + name: 'Chrome' + }, + minimumAtVersion: { + id: '1', + name: '2021.2111.13', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: { + id: '12', + metrics: { + testsCount: 14, + mayFormatted: false, + supportLevel: 'FULL', + commandsCount: 26, + mustFormatted: '116 of 116 passed', + conflictsCount: 0, + supportPercent: 100, + shouldFormatted: '34 of 34 passed', + testsFailedCount: 0, + testsPassedCount: 14, + mayAssertionsCount: 0, + mustAssertionsCount: 116, + assertionsFailedCount: 0, + assertionsPassedCount: 150, + shouldAssertionsCount: 34, + unexpectedBehaviorCount: 0, + mayAssertionsFailedCount: 0, + mayAssertionsPassedCount: 0, + mustAssertionsFailedCount: 0, + mustAssertionsPassedCount: 116, + shouldAssertionsFailedCount: 0, + shouldAssertionsPassedCount: 34, + unexpectedBehaviorsFormatted: false, + severeImpactFailedAssertionCount: 0, + severeImpactPassedAssertionCount: 26, + moderateImpactFailedAssertionCount: 0, + moderateImpactPassedAssertionCount: 26 + }, + isFinal: true, + markedFinalAt: '2022-07-06T00:00:00.000Z', + issues: [], + draftTestPlanRuns: [ + { + tester: { + username: 'esmeralda-baggins' }, - { - id: '2', - title: 'Banner Landmark', - phase: 'RD', - gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', - gitMessage: - 'Generate html source script to support aria-at-app (#646)', - updatedAt: '2022-03-17T18:34:51.000Z', - versionString: 'V22.03.17', - draftPhaseReachedAt: null, - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'banner' - }, - testPlanReports: [], - metadata: {} + testPlanReport: { + id: '12' }, - { - id: '4', - title: 'Checkbox Example (Two State)', - phase: 'RD', - gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', - gitMessage: - 'Generate html source script to support aria-at-app (#646)', - updatedAt: '2022-03-17T18:34:51.000Z', - versionString: 'V22.03.17', - draftPhaseReachedAt: null, - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'checkbox' - }, - testPlanReports: [], - metadata: {} + testResults: [ + { + test: { + id: 'YWYzOeyIyIjoiNjkifQTQ0MT' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:52.053Z' + }, + { + test: { + id: 'OGZjNeyIyIjoiNjkifQjQxZW' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:52.149Z' + }, + { + test: { + id: 'NjM3ZeyIyIjoiNjkifQmUxYz' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:52.242Z' + }, + { + test: { + id: 'ZWQ0MeyIyIjoiNjkifQGZhYT' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:52.321Z' + }, + { + test: { + id: 'ZGI3ZeyIyIjoiNjkifQTc5Mj' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:52.409Z' + }, + { + test: { + id: 'MDZjOeyIyIjoiNjkifQGJkYz' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:52.510Z' + }, + { + test: { + id: 'ZmI3NeyIyIjoiNjkifQzUwMT' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:52.614Z' + }, + { + test: { + id: 'NmY2YeyIyIjoiNjkifQTczOW' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:52.731Z' + }, + { + test: { + id: 'MjIwYeyIyIjoiNjkifQmUzZj' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:52.882Z' + }, + { + test: { + id: 'ODg0OeyIyIjoiNjkifQWFlYm' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:53.038Z' + }, + { + test: { + id: 'ZDQ2MeyIyIjoiNjkifQjlmZj' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:53.184Z' + }, + { + test: { + id: 'MjdlYeyIyIjoiNjkifQTgyNj' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:53.317Z' + }, + { + test: { + id: 'OGE5MeyIyIjoiNjkifQGZjOT' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:53.428Z' + }, + { + test: { + id: 'YWNlNeyIyIjoiNjkifQjQzOW' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:53.567Z' + } + ] + } + ] + } + }, + { + isRequired: true, + at: { + id: '1', + key: 'jaws', + name: 'JAWS', + atVersions: [ + { + id: '1', + name: '2021.2111.13', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '1', + key: 'firefox', + name: 'Firefox' + }, + minimumAtVersion: { + id: '1', + name: '2021.2111.13', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: null + }, + { + isRequired: true, + at: { + id: '2', + key: 'nvda', + name: 'NVDA', + atVersions: [ + { + id: '2', + name: '2020.4', + supportedByAutomation: false, + releasedAt: '2020-04-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '2', + key: 'chrome', + name: 'Chrome' + }, + minimumAtVersion: { + id: '2', + name: '2020.4', + supportedByAutomation: false, + releasedAt: '2020-04-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: { + id: '13', + metrics: { + testsCount: 14, + mayFormatted: false, + supportLevel: 'FULL', + commandsCount: 28, + mustFormatted: '124 of 124 passed', + conflictsCount: 0, + supportPercent: 100, + shouldFormatted: '36 of 36 passed', + testsFailedCount: 0, + testsPassedCount: 14, + mayAssertionsCount: 0, + mustAssertionsCount: 124, + assertionsFailedCount: 0, + assertionsPassedCount: 160, + shouldAssertionsCount: 36, + unexpectedBehaviorCount: 0, + mayAssertionsFailedCount: 0, + mayAssertionsPassedCount: 0, + mustAssertionsFailedCount: 0, + mustAssertionsPassedCount: 124, + shouldAssertionsFailedCount: 0, + shouldAssertionsPassedCount: 36, + unexpectedBehaviorsFormatted: false, + severeImpactFailedAssertionCount: 0, + severeImpactPassedAssertionCount: 28, + moderateImpactFailedAssertionCount: 0, + moderateImpactPassedAssertionCount: 28 + }, + isFinal: true, + markedFinalAt: '2022-07-07T00:00:00.000Z', + issues: [], + draftTestPlanRuns: [ + { + tester: { + username: 'esmeralda-baggins' }, - { - id: '9', - title: 'Complementary Landmark', - phase: 'RD', - gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', - gitMessage: - 'Generate html source script to support aria-at-app (#646)', - updatedAt: '2022-03-17T18:34:51.000Z', - versionString: 'V22.03.17', - draftPhaseReachedAt: null, - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'complementary' - }, - testPlanReports: [], - metadata: {} + testPlanReport: { + id: '13' }, - { - id: '10', - title: 'Contentinfo Landmark', - phase: 'RD', - gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', - gitMessage: - 'Generate html source script to support aria-at-app (#646)', - updatedAt: '2022-03-17T18:34:51.000Z', - versionString: 'V22.03.17', - draftPhaseReachedAt: null, - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'contentinfo' - }, - testPlanReports: [], - metadata: {} + testResults: [ + { + test: { + id: 'YWYzOeyIyIjoiNjkifQTQ0MT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:53.698Z' + }, + { + test: { + id: 'OGZjNeyIyIjoiNjkifQjQxZW' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:53.814Z' + }, + { + test: { + id: 'NjM3ZeyIyIjoiNjkifQmUxYz' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:53.921Z' + }, + { + test: { + id: 'ZWQ0MeyIyIjoiNjkifQGZhYT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:54.038Z' + }, + { + test: { + id: 'ZGI3ZeyIyIjoiNjkifQTc5Mj' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:54.181Z' + }, + { + test: { + id: 'MDZjOeyIyIjoiNjkifQGJkYz' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:54.327Z' + }, + { + test: { + id: 'ZmI3NeyIyIjoiNjkifQzUwMT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:54.421Z' + }, + { + test: { + id: 'NmY2YeyIyIjoiNjkifQTczOW' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:54.541Z' + }, + { + test: { + id: 'MjIwYeyIyIjoiNjkifQmUzZj' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:54.666Z' + }, + { + test: { + id: 'ODg0OeyIyIjoiNjkifQWFlYm' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:54.794Z' + }, + { + test: { + id: 'ZDQ2MeyIyIjoiNjkifQjlmZj' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:54.881Z' + }, + { + test: { + id: 'MjdlYeyIyIjoiNjkifQTgyNj' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:54.967Z' + }, + { + test: { + id: 'OGE5MeyIyIjoiNjkifQGZjOT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:55.059Z' + }, + { + test: { + id: 'YWNlNeyIyIjoiNjkifQjQzOW' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:55.140Z' + } + ] + } + ] + } + }, + { + isRequired: true, + at: { + id: '2', + key: 'nvda', + name: 'NVDA', + atVersions: [ + { + id: '2', + name: '2020.4', + supportedByAutomation: false, + releasedAt: '2020-04-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '1', + key: 'firefox', + name: 'Firefox' + }, + minimumAtVersion: { + id: '2', + name: '2020.4', + supportedByAutomation: false, + releasedAt: '2020-04-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: null + }, + { + isRequired: true, + at: { + id: '3', + key: 'voiceover_macos', + name: 'VoiceOver for macOS', + atVersions: [ + { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '2', + key: 'chrome', + name: 'Chrome' + }, + minimumAtVersion: { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: null + }, + { + isRequired: true, + at: { + id: '3', + key: 'voiceover_macos', + name: 'VoiceOver for macOS', + atVersions: [ + { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '3', + key: 'safari_macos', + name: 'Safari' + }, + minimumAtVersion: { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: { + id: '6', + metrics: { + testsCount: 7, + mayFormatted: false, + supportLevel: 'FAILING', + commandsCount: 16, + mustFormatted: '66 of 68 passed', + conflictsCount: 0, + supportPercent: 97, + shouldFormatted: '20 of 20 passed', + testsFailedCount: 2, + testsPassedCount: 5, + mayAssertionsCount: 0, + mustAssertionsCount: 68, + assertionsFailedCount: 2, + assertionsPassedCount: 86, + shouldAssertionsCount: 20, + unexpectedBehaviorCount: 0, + mayAssertionsFailedCount: 0, + mayAssertionsPassedCount: 0, + mustAssertionsFailedCount: 2, + mustAssertionsPassedCount: 66, + shouldAssertionsFailedCount: 0, + shouldAssertionsPassedCount: 20, + unexpectedBehaviorsFormatted: false, + severeImpactFailedAssertionCount: 0, + severeImpactPassedAssertionCount: 16, + moderateImpactFailedAssertionCount: 0, + moderateImpactPassedAssertionCount: 16 + }, + isFinal: true, + markedFinalAt: '2022-07-06T00:00:00.000Z', + issues: [], + draftTestPlanRuns: [ + { + tester: { + username: 'tom-proudfeet' }, - { - id: '25', - title: 'Data Grid Example 1: Minimal Data Grid', - phase: 'RD', - gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', - gitMessage: - 'Generate html source script to support aria-at-app (#646)', - updatedAt: '2022-03-17T18:34:51.000Z', - versionString: 'V22.03.17', - draftPhaseReachedAt: null, - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'minimal-data-grid' - }, - testPlanReports: [], - metadata: {} + testPlanReport: { + id: '6' }, - { - id: '11', - title: 'Date Picker Spin Button Example', - phase: 'RD', - gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', - gitMessage: - 'Generate html source script to support aria-at-app (#646)', - updatedAt: '2022-03-17T18:34:51.000Z', - versionString: 'V22.03.17', - draftPhaseReachedAt: null, - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'datepicker-spin-button' - }, - testPlanReports: [], - metadata: {} + testResults: [ + { + test: { + id: 'NmUzMeyIyIjoiNjkifQmU0OT' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2024-04-25T16:44:45.554Z' + }, + { + test: { + id: 'Y2UyYeyIyIjoiNjkifQ2Y1Mz' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2024-04-25T16:44:45.671Z' + }, + { + test: { + id: 'ODc2OeyIyIjoiNjkifQTA1Yz' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2024-04-25T16:44:45.794Z' + }, + { + test: { + id: 'OTgwZeyIyIjoiNjkifQDZjOG' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2024-04-25T16:44:45.909Z' + }, + { + test: { + id: 'ODA3ZeyIyIjoiNjkifQjI4Y2' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2024-04-25T16:44:45.997Z' + }, + { + test: { + id: 'OWI4MeyIyIjoiNjkifQzFlZD' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2024-04-25T16:44:46.091Z' + }, + { + test: { + id: 'MzhiZeyIyIjoiNjkifQWE4Nj' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2024-04-25T16:44:46.189Z' + } + ] + } + ] + } + }, + { + isRequired: false, + at: { + id: '3', + key: 'voiceover_macos', + name: 'VoiceOver for macOS', + atVersions: [ + { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '1', + key: 'firefox', + name: 'Firefox' + }, + minimumAtVersion: { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: null + } + ] + } + } + } + }, + { + request: { + query: testPlanReportStatusDialogQuery, + variables: { testPlanVersionId: '7' } + }, + result: { + data: { + testPlanVersion: { + id: '7', + title: 'Select Only Combobox Example', + phase: 'DRAFT', + gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', + gitMessage: + 'Generate html source script to support aria-at-app (#646)', + updatedAt: '2022-03-17T18:34:51.000Z', + draftPhaseReachedAt: '2022-07-06T00:00:00.000Z', + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'combobox-select-only' + }, + testPlanReportStatuses: [ + { + isRequired: true, + at: { + id: '1', + key: 'jaws', + name: 'JAWS', + atVersions: [ + { + id: '1', + name: '2021.2111.13', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '2', + key: 'chrome', + name: 'Chrome' + }, + minimumAtVersion: { + id: '1', + name: '2021.2111.13', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: null + }, + { + isRequired: false, + at: { + id: '1', + key: 'jaws', + name: 'JAWS', + atVersions: [ + { + id: '1', + name: '2021.2111.13', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '1', + key: 'firefox', + name: 'Firefox' + }, + minimumAtVersion: { + id: '1', + name: '2021.2111.13', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: null + }, + { + isRequired: true, + at: { + id: '2', + key: 'nvda', + name: 'NVDA', + atVersions: [ + { + id: '2', + name: '2020.4', + supportedByAutomation: false, + releasedAt: '2020-04-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '2', + key: 'chrome', + name: 'Chrome' + }, + minimumAtVersion: { + id: '2', + name: '2020.4', + supportedByAutomation: false, + releasedAt: '2020-04-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: null + }, + { + isRequired: false, + at: { + id: '2', + key: 'nvda', + name: 'NVDA', + atVersions: [ + { + id: '2', + name: '2020.4', + supportedByAutomation: false, + releasedAt: '2020-04-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '1', + key: 'firefox', + name: 'Firefox' + }, + minimumAtVersion: { + id: '2', + name: '2020.4', + supportedByAutomation: false, + releasedAt: '2020-04-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: { + id: '2', + metrics: { + testsCount: 21, + mayFormatted: false, + supportLevel: 'FAILING', + commandsCount: 24, + mustFormatted: '118 of 122 passed', + conflictsCount: 3, + supportPercent: 97, + shouldFormatted: '34 of 36 passed', + testsFailedCount: 6, + testsPassedCount: 15, + mayAssertionsCount: 0, + mustAssertionsCount: 122, + assertionsFailedCount: 6, + assertionsPassedCount: 152, + shouldAssertionsCount: 36, + unexpectedBehaviorCount: 3, + mayAssertionsFailedCount: 0, + mayAssertionsPassedCount: 0, + mustAssertionsFailedCount: 4, + mustAssertionsPassedCount: 118, + shouldAssertionsFailedCount: 2, + shouldAssertionsPassedCount: 34, + unexpectedBehaviorsFormatted: '3 found', + severeImpactFailedAssertionCount: 1, + severeImpactPassedAssertionCount: 23, + moderateImpactFailedAssertionCount: 2, + moderateImpactPassedAssertionCount: 22 + }, + isFinal: false, + markedFinalAt: null, + issues: [ + { + link: 'https://github.com/bocoup/aria-at/issues/128#issue-2157878584', + isOpen: true, + feedbackType: 'FEEDBACK' + } + ], + draftTestPlanRuns: [ + { + tester: { + username: 'tom-proudfeet' }, - { - id: '12', - title: 'Disclosure of Answers to Frequently Asked Questions Example', - phase: 'RD', - gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', - gitMessage: - 'Generate html source script to support aria-at-app (#646)', - updatedAt: '2022-03-17T18:34:51.000Z', - versionString: 'V22.03.17', - draftPhaseReachedAt: null, - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'disclosure-faq' - }, - testPlanReports: [], - metadata: {} + testPlanReport: { + id: '2' }, - { - id: '23', - title: 'Editor Menubar Example', - phase: 'RD', - gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', - gitMessage: - 'Generate html source script to support aria-at-app (#646)', - updatedAt: '2022-03-17T18:34:51.000Z', - versionString: 'V22.03.17', - draftPhaseReachedAt: null, - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'menubar-editor' - }, - testPlanReports: [], - metadata: {} + testResults: [ + { + test: { + id: 'Nzg5NeyIyIjoiNyJ9zNjZj' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:38.949Z' + }, + { + test: { + id: 'MmY0YeyIyIjoiNyJ9jRkZD' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:39.070Z' + }, + { + test: { + id: 'ZjUwNeyIyIjoiNyJ9mE2ZT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:39.180Z' + }, + { + test: { + id: 'MDNiMeyIyIjoiNyJ9Dk1MT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:39.292Z' + }, + { + test: { + id: 'MjRmNeyIyIjoiNyJ92MyMT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:39.406Z' + }, + { + test: { + id: 'ZmVlMeyIyIjoiNyJ9mUyYj' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: null + }, + { + test: { + id: 'YWFiNeyIyIjoiNyJ9zE2Zj' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:39.640Z' + }, + { + test: { + id: 'YjZkYeyIyIjoiNyJ9WIxZm' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:39.761Z' + }, + { + test: { + id: 'ZmIzMeyIyIjoiNyJ9TQ1NW' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:39.888Z' + }, + { + test: { + id: 'MmZkNeyIyIjoiNyJ9zIwN2' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:40.006Z' + }, + { + test: { + id: 'ZmQwOeyIyIjoiNyJ9DEzYz' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:40.122Z' + }, + { + test: { + id: 'MGViNeyIyIjoiNyJ9GQ3MT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:40.234Z' + }, + { + test: { + id: 'YTg5MeyIyIjoiNyJ9WEzOT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:40.355Z' + }, + { + test: { + id: 'NTRjMeyIyIjoiNyJ9zQ0OD' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:40.467Z' + }, + { + test: { + id: 'MjRlZeyIyIjoiNyJ9DcyY2' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:40.588Z' + }, + { + test: { + id: 'YWQzNeyIyIjoiNyJ9mE2Nm' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:40.712Z' + }, + { + test: { + id: 'OTYxOeyIyIjoiNyJ9TdmYj' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:40.826Z' + }, + { + test: { + id: 'MjgzNeyIyIjoiNyJ9TZjNz' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:40.948Z' + }, + { + test: { + id: 'NWNiZeyIyIjoiNyJ9jI2MD' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:41.075Z' + } + ] + }, + { + tester: { + username: 'esmeralda-baggins' }, - { - id: '14', - title: 'Form Landmark', - phase: 'RD', - gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', - gitMessage: - 'Generate html source script to support aria-at-app (#646)', - updatedAt: '2022-03-17T18:34:51.000Z', - versionString: 'V22.03.17', - draftPhaseReachedAt: null, - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'form' - }, - testPlanReports: [], - metadata: {} + testPlanReport: { + id: '2' }, - { - id: '30', - title: 'Media Seek Slider', - phase: 'RD', - gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', - gitMessage: - 'Generate html source script to support aria-at-app (#646)', - updatedAt: '2022-03-17T18:34:51.000Z', - versionString: 'V22.03.17', - draftPhaseReachedAt: null, - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'seek-slider' - }, - testPlanReports: [], - metadata: {} + testResults: [ + { + test: { + id: 'Nzg5NeyIyIjoiNyJ9zNjZj' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:36.666Z' + }, + { + test: { + id: 'MmY0YeyIyIjoiNyJ9jRkZD' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:36.793Z' + }, + { + test: { + id: 'ZjUwNeyIyIjoiNyJ9mE2ZT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:36.914Z' + }, + { + test: { + id: 'MDNiMeyIyIjoiNyJ9Dk1MT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:37.031Z' + }, + { + test: { + id: 'MjRmNeyIyIjoiNyJ92MyMT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:37.150Z' + }, + { + test: { + id: 'ZmVlMeyIyIjoiNyJ9mUyYj' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: null + }, + { + test: { + id: 'YWFiNeyIyIjoiNyJ9zE2Zj' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:37.384Z' + }, + { + test: { + id: 'YjZkYeyIyIjoiNyJ9WIxZm' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:37.512Z' + }, + { + test: { + id: 'ZmIzMeyIyIjoiNyJ9TQ1NW' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:37.638Z' + }, + { + test: { + id: 'MmZkNeyIyIjoiNyJ9zIwN2' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:37.760Z' + }, + { + test: { + id: 'ZmQwOeyIyIjoiNyJ9DEzYz' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:37.883Z' + }, + { + test: { + id: 'MGViNeyIyIjoiNyJ9GQ3MT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:38.014Z' + }, + { + test: { + id: 'YTg5MeyIyIjoiNyJ9WEzOT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:38.145Z' + }, + { + test: { + id: 'NTRjMeyIyIjoiNyJ9zQ0OD' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:38.268Z' + }, + { + test: { + id: 'MjRlZeyIyIjoiNyJ9DcyY2' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:38.382Z' + }, + { + test: { + id: 'YWQzNeyIyIjoiNyJ9mE2Nm' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:38.481Z' + }, + { + test: { + id: 'OTYxOeyIyIjoiNyJ9TdmYj' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:38.596Z' + }, + { + test: { + id: 'MjgzNeyIyIjoiNyJ9TZjNz' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:38.701Z' + }, + { + test: { + id: 'NWNiZeyIyIjoiNyJ9jI2MD' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:38.811Z' + } + ] + } + ] + } + }, + { + isRequired: true, + at: { + id: '3', + key: 'voiceover_macos', + name: 'VoiceOver for macOS', + atVersions: [ + { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '3', + key: 'safari_macos', + name: 'Safari' + }, + minimumAtVersion: { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: null + }, + { + isRequired: false, + at: { + id: '3', + key: 'voiceover_macos', + name: 'VoiceOver for macOS', + atVersions: [ + { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '2', + key: 'chrome', + name: 'Chrome' + }, + minimumAtVersion: { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: null + }, + { + isRequired: false, + at: { + id: '3', + key: 'voiceover_macos', + name: 'VoiceOver for macOS', + atVersions: [ + { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '1', + key: 'firefox', + name: 'Firefox' + }, + minimumAtVersion: { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: null + } + ] + } + } + } + }, + { + request: { + query: testPlanReportStatusDialogQuery, + variables: { testPlanVersionId: '26' } + }, + result: { + data: { + testPlanVersion: { + id: '24', + title: 'Modal Dialog Example', + phase: 'CANDIDATE', + gitSha: '5fe7afd82fe51c185b8661276105190a59d47322', + gitMessage: 'Task 7: delete incorrect instructions (#733)', + updatedAt: '2022-05-26T16:10:10.000Z', + draftPhaseReachedAt: '2022-07-06T00:00:00.000Z', + candidatePhaseReachedAt: '2022-07-06T00:00:00.000Z', + recommendedPhaseTargetDate: '2023-01-02T00:00:00.000Z', + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'modal-dialog' + }, + testPlanReportStatuses: [ + { + isRequired: true, + at: { + id: '1', + key: 'jaws', + name: 'JAWS', + atVersions: [ + { + id: '1', + name: '2021.2111.13', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '2', + key: 'chrome', + name: 'Chrome' + }, + minimumAtVersion: { + id: '1', + name: '2021.2111.13', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: { + id: '3', + metrics: { + testsCount: 18, + mayFormatted: false, + supportLevel: 'FAILING', + commandsCount: 26, + mustFormatted: '115 of 117 passed', + conflictsCount: 0, + supportPercent: 98, + shouldFormatted: '25 of 26 passed', + testsFailedCount: 2, + testsPassedCount: 16, + mayAssertionsCount: 0, + mustAssertionsCount: 117, + assertionsFailedCount: 3, + assertionsPassedCount: 140, + shouldAssertionsCount: 26, + unexpectedBehaviorCount: 1, + mayAssertionsFailedCount: 0, + mayAssertionsPassedCount: 0, + mustAssertionsFailedCount: 2, + mustAssertionsPassedCount: 115, + shouldAssertionsFailedCount: 1, + shouldAssertionsPassedCount: 25, + unexpectedBehaviorsFormatted: '1 found', + severeImpactFailedAssertionCount: 0, + severeImpactPassedAssertionCount: 26, + moderateImpactFailedAssertionCount: 1, + moderateImpactPassedAssertionCount: 25 + }, + isFinal: true, + markedFinalAt: '2022-07-06T00:00:00.000Z', + issues: [], + draftTestPlanRuns: [ + { + tester: { + username: 'esmeralda-baggins' }, - { - id: '29', - title: 'Rating Slider', - phase: 'RD', - gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', - gitMessage: - 'Generate html source script to support aria-at-app (#646)', - updatedAt: '2022-03-17T18:34:51.000Z', - versionString: 'V22.03.17', - draftPhaseReachedAt: null, - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'rating-slider' - }, - testPlanReports: [], - metadata: {} + testPlanReport: { + id: '3' }, - { - id: '7', - title: 'Select Only Combobox Example', - phase: 'DRAFT', - gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', - gitMessage: - 'Generate html source script to support aria-at-app (#646)', - updatedAt: '2022-03-17T18:34:51.000Z', - versionString: 'V22.03.17', - draftPhaseReachedAt: '2022-07-06T00:00:00.000Z', - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'combobox-select-only' - }, - testPlanReports: [ - { - id: '2', - metrics: { - testsCount: 21, - supportLevel: 'FAILING', - conflictsCount: 5, - supportPercent: 96, - testsFailedCount: 16, - testsPassedCount: 5, - shouldFormatted: '3 of 3 passed', - mustFormatted: '48 of 50 passed', - shouldAssertionsCount: 3, - mustAssertionsCount: 50, - unexpectedBehaviorCount: 3, - unexpectedBehaviorsFormatted: '3 found', - shouldAssertionsFailedCount: 0, - shouldAssertionsPassedCount: 3, - mustAssertionsFailedCount: 2, - mustAssertionsPassedCount: 48 - }, - markedFinalAt: null, - isFinal: false, - at: { - id: '2', - key: 'nvda', - name: 'NVDA' - }, - browser: { - id: '1', - key: 'firefox', - name: 'Firefox' - }, - issues: [], - draftTestPlanRuns: [ - { - tester: { - username: 'tom-proudfeet' - }, - testPlanReport: { - id: '2' - }, - testResults: [ - { - test: { - id: 'Nzg5NeyIyIjoiNyJ9zNjZj' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2023-08-18T03:17:08.240Z' - }, - { - test: { - id: 'MmY0YeyIyIjoiNyJ9jRkZD' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2023-08-18T03:17:08.332Z' - }, - { - test: { - id: 'ZjUwNeyIyIjoiNyJ9mE2ZT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2023-08-18T03:17:08.412Z' - }, - { - test: { - id: 'MDNiMeyIyIjoiNyJ9Dk1MT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2023-08-18T03:17:08.501Z' - }, - { - test: { - id: 'MjRmNeyIyIjoiNyJ92MyMT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2023-08-18T03:17:08.593Z' - }, - { - test: { - id: 'ZmVlMeyIyIjoiNyJ9mUyYj' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: null - }, - { - test: { - id: 'YWFiNeyIyIjoiNyJ9zE2Zj' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2023-08-18T03:17:08.811Z' - }, - { - test: { - id: 'YjZkYeyIyIjoiNyJ9WIxZm' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2023-08-18T03:17:08.902Z' - }, - { - test: { - id: 'ZmIzMeyIyIjoiNyJ9TQ1NW' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2023-08-18T03:17:08.996Z' - } - ] - }, - { - tester: { - username: 'esmeralda-baggins' - }, - testPlanReport: { - id: '2' - }, - testResults: [ - { - test: { - id: 'Nzg5NeyIyIjoiNyJ9zNjZj' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2023-08-18T03:17:07.718Z' - }, - { - test: { - id: 'MmY0YeyIyIjoiNyJ9jRkZD' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2023-08-18T03:17:07.813Z' - }, - { - test: { - id: 'ZjUwNeyIyIjoiNyJ9mE2ZT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2023-08-18T03:17:07.914Z' - }, - { - test: { - id: 'MDNiMeyIyIjoiNyJ9Dk1MT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2023-08-18T03:17:07.988Z' - }, - { - test: { - id: 'MjRmNeyIyIjoiNyJ92MyMT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2023-08-18T03:17:08.074Z' - }, - { - test: { - id: 'ZmVlMeyIyIjoiNyJ9mUyYj' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: null - } - ] - } - ] - } - ], - metadata: {} + testResults: [ + { + test: { + id: 'ZjE0NeyIyIjoiMjQifQmI0NT' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:41.177Z' + }, + { + test: { + id: 'YjZlNeyIyIjoiMjQifQTc5ZW' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:41.256Z' + }, + { + test: { + id: 'NWM0MeyIyIjoiMjQifQzhiYz' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:41.349Z' + }, + { + test: { + id: 'YzM0ZeyIyIjoiMjQifQmRmMz' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:41.419Z' + }, + { + test: { + id: 'ZjVjMeyIyIjoiMjQifQDRhY2' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:41.496Z' + }, + { + test: { + id: 'YmUzMeyIyIjoiMjQifQmRmNm' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:41.574Z' + }, + { + test: { + id: 'ZGJmMeyIyIjoiMjQifQzU5Yz' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:41.661Z' + }, + { + test: { + id: 'Nzg1OeyIyIjoiMjQifQTYxM2' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:41.741Z' + }, + { + test: { + id: 'ZTI0MeyIyIjoiMjQifQzM4YT' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:41.823Z' + }, + { + test: { + id: 'MzY5ZeyIyIjoiMjQifQmQ0OT' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:41.950Z' + }, + { + test: { + id: 'ZjVmYeyIyIjoiMjQifQjJjYW' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:42.044Z' + }, + { + test: { + id: 'ZTMwNeyIyIjoiMjQifQzI5Nz' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:42.112Z' + }, + { + test: { + id: 'NGY0MeyIyIjoiMjQifQ2FjMj' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:42.191Z' + }, + { + test: { + id: 'OTI0OeyIyIjoiMjQifQTU1ZT' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:42.271Z' + }, + { + test: { + id: 'MDRhMeyIyIjoiMjQifQWEzMj' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:42.344Z' + }, + { + test: { + id: 'ZThlZeyIyIjoiMjQifQjQ2Nz' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:42.412Z' + }, + { + test: { + id: 'NjhjMeyIyIjoiMjQifQGE0ND' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:42.482Z' + }, + { + test: { + id: 'YTAzZeyIyIjoiMjQifQTc5ZD' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:42.575Z' + } + ] + } + ] + } + }, + { + isRequired: false, + at: { + id: '1', + key: 'jaws', + name: 'JAWS', + atVersions: [ + { + id: '1', + name: '2021.2111.13', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '2', + key: 'chrome', + name: 'Chrome' + }, + minimumAtVersion: null, + exactAtVersion: { + id: '1', + name: '2021.2111.13', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + }, + testPlanReport: { + id: '105', + metrics: {}, + isFinal: false, + markedFinalAt: null, + issues: [], + draftTestPlanRuns: [] + } + }, + { + isRequired: false, + at: { + id: '1', + key: 'jaws', + name: 'JAWS', + atVersions: [ + { + id: '1', + name: '2021.2111.13', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '1', + key: 'firefox', + name: 'Firefox' + }, + minimumAtVersion: { + id: '1', + name: '2021.2111.13', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: { + id: '8', + metrics: { + testsCount: 18, + mayFormatted: false, + supportLevel: 'FULL', + commandsCount: 26, + mustFormatted: '117 of 117 passed', + conflictsCount: 0, + supportPercent: 100, + shouldFormatted: '26 of 26 passed', + testsFailedCount: 0, + testsPassedCount: 18, + mayAssertionsCount: 0, + mustAssertionsCount: 117, + assertionsFailedCount: 0, + assertionsPassedCount: 143, + shouldAssertionsCount: 26, + unexpectedBehaviorCount: 0, + mayAssertionsFailedCount: 0, + mayAssertionsPassedCount: 0, + mustAssertionsFailedCount: 0, + mustAssertionsPassedCount: 117, + shouldAssertionsFailedCount: 0, + shouldAssertionsPassedCount: 26, + unexpectedBehaviorsFormatted: false, + severeImpactFailedAssertionCount: 0, + severeImpactPassedAssertionCount: 26, + moderateImpactFailedAssertionCount: 0, + moderateImpactPassedAssertionCount: 26 + }, + isFinal: false, + markedFinalAt: null, + issues: [], + draftTestPlanRuns: [ + { + tester: { + username: 'esmeralda-baggins' + }, + testPlanReport: { + id: '8' + }, + testResults: [ + { + test: { + id: 'ZjE0NeyIyIjoiMjQifQmI0NT' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:46.300Z' + }, + { + test: { + id: 'YjZlNeyIyIjoiMjQifQTc5ZW' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:46.396Z' + }, + { + test: { + id: 'NWM0MeyIyIjoiMjQifQzhiYz' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:46.490Z' + }, + { + test: { + id: 'YzM0ZeyIyIjoiMjQifQmRmMz' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:46.572Z' + }, + { + test: { + id: 'ZjVjMeyIyIjoiMjQifQDRhY2' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:46.656Z' + }, + { + test: { + id: 'YmUzMeyIyIjoiMjQifQmRmNm' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:46.743Z' + }, + { + test: { + id: 'ZGJmMeyIyIjoiMjQifQzU5Yz' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:46.828Z' + }, + { + test: { + id: 'Nzg1OeyIyIjoiMjQifQTYxM2' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:46.912Z' + }, + { + test: { + id: 'ZTI0MeyIyIjoiMjQifQzM4YT' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:46.990Z' + }, + { + test: { + id: 'MzY5ZeyIyIjoiMjQifQmQ0OT' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:47.087Z' + }, + { + test: { + id: 'ZjVmYeyIyIjoiMjQifQjJjYW' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:47.215Z' + }, + { + test: { + id: 'ZTMwNeyIyIjoiMjQifQzI5Nz' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:47.355Z' + }, + { + test: { + id: 'NGY0MeyIyIjoiMjQifQ2FjMj' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:47.484Z' + }, + { + test: { + id: 'OTI0OeyIyIjoiMjQifQTU1ZT' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:47.614Z' + }, + { + test: { + id: 'MDRhMeyIyIjoiMjQifQWEzMj' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:47.738Z' + }, + { + test: { + id: 'ZThlZeyIyIjoiMjQifQjQ2Nz' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:47.862Z' + }, + { + test: { + id: 'NjhjMeyIyIjoiMjQifQGE0ND' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:47.981Z' + }, + { + test: { + id: 'YTAzZeyIyIjoiMjQifQTc5ZD' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:48.105Z' + } + ] + } + ] + } + }, + { + isRequired: true, + at: { + id: '2', + key: 'nvda', + name: 'NVDA', + atVersions: [ + { + id: '2', + name: '2020.4', + supportedByAutomation: false, + releasedAt: '2020-04-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '2', + key: 'chrome', + name: 'Chrome' + }, + minimumAtVersion: { + id: '2', + name: '2020.4', + supportedByAutomation: false, + releasedAt: '2020-04-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: { + id: '4', + metrics: { + testsCount: 18, + mayFormatted: false, + supportLevel: 'FAILING', + commandsCount: 26, + mustFormatted: '115 of 117 passed', + conflictsCount: 0, + supportPercent: 98, + shouldFormatted: '25 of 26 passed', + testsFailedCount: 2, + testsPassedCount: 16, + mayAssertionsCount: 0, + mustAssertionsCount: 117, + assertionsFailedCount: 3, + assertionsPassedCount: 140, + shouldAssertionsCount: 26, + unexpectedBehaviorCount: 1, + mayAssertionsFailedCount: 0, + mayAssertionsPassedCount: 0, + mustAssertionsFailedCount: 2, + mustAssertionsPassedCount: 115, + shouldAssertionsFailedCount: 1, + shouldAssertionsPassedCount: 25, + unexpectedBehaviorsFormatted: '1 found', + severeImpactFailedAssertionCount: 0, + severeImpactPassedAssertionCount: 26, + moderateImpactFailedAssertionCount: 1, + moderateImpactPassedAssertionCount: 25 + }, + isFinal: true, + markedFinalAt: '2022-07-06T00:00:00.000Z', + issues: [], + draftTestPlanRuns: [ + { + tester: { + username: 'esmeralda-baggins' + }, + testPlanReport: { + id: '4' + }, + testResults: [ + { + test: { + id: 'ZjE0NeyIyIjoiMjQifQmI0NT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:42.683Z' + }, + { + test: { + id: 'YjZlNeyIyIjoiMjQifQTc5ZW' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:42.772Z' + }, + { + test: { + id: 'NWM0MeyIyIjoiMjQifQzhiYz' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:42.848Z' + }, + { + test: { + id: 'YzM0ZeyIyIjoiMjQifQmRmMz' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:42.928Z' + }, + { + test: { + id: 'ZjVjMeyIyIjoiMjQifQDRhY2' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:43.005Z' + }, + { + test: { + id: 'YmUzMeyIyIjoiMjQifQmRmNm' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:43.093Z' + }, + { + test: { + id: 'ZGJmMeyIyIjoiMjQifQzU5Yz' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:43.173Z' + }, + { + test: { + id: 'Nzg1OeyIyIjoiMjQifQTYxM2' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:43.245Z' + }, + { + test: { + id: 'ZTI0MeyIyIjoiMjQifQzM4YT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:43.318Z' + }, + { + test: { + id: 'MzY5ZeyIyIjoiMjQifQmQ0OT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:43.399Z' + }, + { + test: { + id: 'ZjVmYeyIyIjoiMjQifQjJjYW' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:43.473Z' + }, + { + test: { + id: 'ZTMwNeyIyIjoiMjQifQzI5Nz' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:43.554Z' + }, + { + test: { + id: 'NGY0MeyIyIjoiMjQifQ2FjMj' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:43.643Z' + }, + { + test: { + id: 'OTI0OeyIyIjoiMjQifQTU1ZT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:43.738Z' + }, + { + test: { + id: 'MDRhMeyIyIjoiMjQifQWEzMj' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:43.844Z' + }, + { + test: { + id: 'ZThlZeyIyIjoiMjQifQjQ2Nz' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:43.950Z' + }, + { + test: { + id: 'NjhjMeyIyIjoiMjQifQGE0ND' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:44.078Z' + }, + { + test: { + id: 'YTAzZeyIyIjoiMjQifQTc5ZD' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:44.219Z' + } + ] + } + ] + } + }, + { + isRequired: false, + at: { + id: '2', + key: 'nvda', + name: 'NVDA', + atVersions: [ + { + id: '2', + name: '2020.4', + supportedByAutomation: false, + releasedAt: '2020-04-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '1', + key: 'firefox', + name: 'Firefox' + }, + minimumAtVersion: { + id: '2', + name: '2020.4', + supportedByAutomation: false, + releasedAt: '2020-04-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: { + id: '9', + metrics: { + testsCount: 18, + mayFormatted: false, + supportLevel: 'FULL', + commandsCount: 26, + mustFormatted: '117 of 117 passed', + conflictsCount: 0, + supportPercent: 100, + shouldFormatted: '26 of 26 passed', + testsFailedCount: 0, + testsPassedCount: 18, + mayAssertionsCount: 0, + mustAssertionsCount: 117, + assertionsFailedCount: 0, + assertionsPassedCount: 143, + shouldAssertionsCount: 26, + unexpectedBehaviorCount: 0, + mayAssertionsFailedCount: 0, + mayAssertionsPassedCount: 0, + mustAssertionsFailedCount: 0, + mustAssertionsPassedCount: 117, + shouldAssertionsFailedCount: 0, + shouldAssertionsPassedCount: 26, + unexpectedBehaviorsFormatted: false, + severeImpactFailedAssertionCount: 0, + severeImpactPassedAssertionCount: 26, + moderateImpactFailedAssertionCount: 0, + moderateImpactPassedAssertionCount: 26 + }, + isFinal: false, + markedFinalAt: null, + issues: [], + draftTestPlanRuns: [ + { + tester: { + username: 'esmeralda-baggins' + }, + testPlanReport: { + id: '9' + }, + testResults: [ + { + test: { + id: 'ZjE0NeyIyIjoiMjQifQmI0NT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:48.229Z' + }, + { + test: { + id: 'YjZlNeyIyIjoiMjQifQTc5ZW' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:48.339Z' + }, + { + test: { + id: 'NWM0MeyIyIjoiMjQifQzhiYz' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:48.439Z' + }, + { + test: { + id: 'YzM0ZeyIyIjoiMjQifQmRmMz' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:48.537Z' + }, + { + test: { + id: 'ZjVjMeyIyIjoiMjQifQDRhY2' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:48.636Z' + }, + { + test: { + id: 'YmUzMeyIyIjoiMjQifQmRmNm' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:48.736Z' + }, + { + test: { + id: 'ZGJmMeyIyIjoiMjQifQzU5Yz' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:48.824Z' + }, + { + test: { + id: 'Nzg1OeyIyIjoiMjQifQTYxM2' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:48.901Z' + }, + { + test: { + id: 'ZTI0MeyIyIjoiMjQifQzM4YT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:48.978Z' + }, + { + test: { + id: 'MzY5ZeyIyIjoiMjQifQmQ0OT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:49.070Z' + }, + { + test: { + id: 'ZjVmYeyIyIjoiMjQifQjJjYW' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:49.161Z' + }, + { + test: { + id: 'ZTMwNeyIyIjoiMjQifQzI5Nz' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:49.264Z' + }, + { + test: { + id: 'NGY0MeyIyIjoiMjQifQ2FjMj' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:49.387Z' + }, + { + test: { + id: 'OTI0OeyIyIjoiMjQifQTU1ZT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:49.527Z' + }, + { + test: { + id: 'MDRhMeyIyIjoiMjQifQWEzMj' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:49.646Z' + }, + { + test: { + id: 'ZThlZeyIyIjoiMjQifQjQ2Nz' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:49.747Z' + }, + { + test: { + id: 'NjhjMeyIyIjoiMjQifQGE0ND' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:49.847Z' + }, + { + test: { + id: 'YTAzZeyIyIjoiMjQifQTc5ZD' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:49.951Z' + } + ] + } + ] + } + }, + { + isRequired: true, + at: { + id: '3', + key: 'voiceover_macos', + name: 'VoiceOver for macOS', + atVersions: [ + { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '3', + key: 'safari_macos', + name: 'Safari' + }, + minimumAtVersion: { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: { + id: '5', + metrics: { + testsCount: 11, + mayFormatted: false, + supportLevel: 'FAILING', + commandsCount: 20, + mustFormatted: '88 of 90 passed', + conflictsCount: 0, + supportPercent: 98, + shouldFormatted: '19 of 20 passed', + testsFailedCount: 2, + testsPassedCount: 9, + mayAssertionsCount: 0, + mustAssertionsCount: 90, + assertionsFailedCount: 3, + assertionsPassedCount: 107, + shouldAssertionsCount: 20, + unexpectedBehaviorCount: 1, + mayAssertionsFailedCount: 0, + mayAssertionsPassedCount: 0, + mustAssertionsFailedCount: 2, + mustAssertionsPassedCount: 88, + shouldAssertionsFailedCount: 1, + shouldAssertionsPassedCount: 19, + unexpectedBehaviorsFormatted: '1 found', + severeImpactFailedAssertionCount: 0, + severeImpactPassedAssertionCount: 20, + moderateImpactFailedAssertionCount: 1, + moderateImpactPassedAssertionCount: 19 + }, + isFinal: true, + markedFinalAt: '2022-07-06T00:00:00.000Z', + issues: [], + draftTestPlanRuns: [ + { + tester: { + username: 'esmeralda-baggins' + }, + testPlanReport: { + id: '5' + }, + testResults: [ + { + test: { + id: 'NjM0MeyIyIjoiMjQifQTdiZG' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2024-04-25T16:44:44.402Z' + }, + { + test: { + id: 'YWYzOeyIyIjoiMjQifQDBjN2' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2024-04-25T16:44:44.489Z' + }, + { + test: { + id: 'ZmJjYeyIyIjoiMjQifQWJiNT' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2024-04-25T16:44:44.594Z' + }, + { + test: { + id: 'MjU2NeyIyIjoiMjQifQTk2YW' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2024-04-25T16:44:44.694Z' + }, + { + test: { + id: 'MWNlNeyIyIjoiMjQifQTRhNT' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2024-04-25T16:44:44.781Z' + }, + { + test: { + id: 'YzFlYeyIyIjoiMjQifQjE5Yj' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2024-04-25T16:44:44.877Z' + }, + { + test: { + id: 'N2UwMeyIyIjoiMjQifQTQ1OT' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2024-04-25T16:44:44.966Z' + }, + { + test: { + id: 'OTYwOeyIyIjoiMjQifQTE3ND' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2024-04-25T16:44:45.061Z' + }, + { + test: { + id: 'OWI2MeyIyIjoiMjQifQmE0ZD' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2024-04-25T16:44:45.165Z' + }, + { + test: { + id: 'YTU0MeyIyIjoiMjQifQjNhNj' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2024-04-25T16:44:45.296Z' + }, + { + test: { + id: 'NTM4MeyIyIjoiMjQifQGVlNm' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2024-04-25T16:44:45.433Z' + } + ] + } + ] + } + }, + { + isRequired: false, + at: { + id: '3', + key: 'voiceover_macos', + name: 'VoiceOver for macOS', + atVersions: [ + { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '2', + key: 'chrome', + name: 'Chrome' + }, + minimumAtVersion: { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: { + id: '11', + metrics: { + testsCount: 11, + mayFormatted: false, + supportLevel: 'FULL', + commandsCount: 20, + mustFormatted: '90 of 90 passed', + conflictsCount: 0, + supportPercent: 100, + shouldFormatted: '20 of 20 passed', + testsFailedCount: 0, + testsPassedCount: 11, + mayAssertionsCount: 0, + mustAssertionsCount: 90, + assertionsFailedCount: 0, + assertionsPassedCount: 110, + shouldAssertionsCount: 20, + unexpectedBehaviorCount: 0, + mayAssertionsFailedCount: 0, + mayAssertionsPassedCount: 0, + mustAssertionsFailedCount: 0, + mustAssertionsPassedCount: 90, + shouldAssertionsFailedCount: 0, + shouldAssertionsPassedCount: 20, + unexpectedBehaviorsFormatted: false, + severeImpactFailedAssertionCount: 0, + severeImpactPassedAssertionCount: 20, + moderateImpactFailedAssertionCount: 0, + moderateImpactPassedAssertionCount: 20 + }, + isFinal: false, + markedFinalAt: null, + issues: [], + draftTestPlanRuns: [ + { + tester: { + username: 'esmeralda-baggins' }, - { - id: '33', - title: 'Tabs with Manual Activation', - phase: 'RD', - gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', - gitMessage: - 'Generate html source script to support aria-at-app (#646)', - updatedAt: '2022-03-17T18:34:51.000Z', - versionString: 'V22.03.17', - draftPhaseReachedAt: null, - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'tabs-manual-activation' - }, - testPlanReports: [], - metadata: {} + testPlanReport: { + id: '11' }, - { - id: '35', - title: 'Vertical Temperature Slider', - phase: 'RD', - gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', - gitMessage: - 'Generate html source script to support aria-at-app (#646)', - updatedAt: '2022-03-17T18:34:51.000Z', - versionString: 'V22.03.17', - draftPhaseReachedAt: null, - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'vertical-temperature-slider' - }, - testPlanReports: [], - metadata: {} + testResults: [ + { + test: { + id: 'NjM0MeyIyIjoiMjQifQTdiZG' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:51.034Z' + }, + { + test: { + id: 'YWYzOeyIyIjoiMjQifQDBjN2' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:51.120Z' + }, + { + test: { + id: 'ZmJjYeyIyIjoiMjQifQWJiNT' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:51.205Z' + }, + { + test: { + id: 'MjU2NeyIyIjoiMjQifQTk2YW' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:51.313Z' + }, + { + test: { + id: 'MWNlNeyIyIjoiMjQifQTRhNT' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:51.410Z' + }, + { + test: { + id: 'YzFlYeyIyIjoiMjQifQjE5Yj' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:51.489Z' + }, + { + test: { + id: 'N2UwMeyIyIjoiMjQifQTQ1OT' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:51.561Z' + }, + { + test: { + id: 'OTYwOeyIyIjoiMjQifQTE3ND' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:51.648Z' + }, + { + test: { + id: 'OWI2MeyIyIjoiMjQifQmE0ZD' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:51.736Z' + }, + { + test: { + id: 'YTU0MeyIyIjoiMjQifQjNhNj' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:51.832Z' + }, + { + test: { + id: 'NTM4MeyIyIjoiMjQifQGVlNm' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:51.954Z' + } + ] + } + ] + } + }, + { + isRequired: false, + at: { + id: '3', + key: 'voiceover_macos', + name: 'VoiceOver for macOS', + atVersions: [ + { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '1', + key: 'firefox', + name: 'Firefox' + }, + minimumAtVersion: { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: { + id: '10', + metrics: { + testsCount: 11, + mayFormatted: false, + supportLevel: 'FULL', + commandsCount: 20, + mustFormatted: '90 of 90 passed', + conflictsCount: 0, + supportPercent: 100, + shouldFormatted: '20 of 20 passed', + testsFailedCount: 0, + testsPassedCount: 11, + mayAssertionsCount: 0, + mustAssertionsCount: 90, + assertionsFailedCount: 0, + assertionsPassedCount: 110, + shouldAssertionsCount: 20, + unexpectedBehaviorCount: 0, + mayAssertionsFailedCount: 0, + mayAssertionsPassedCount: 0, + mustAssertionsFailedCount: 0, + mustAssertionsPassedCount: 90, + shouldAssertionsFailedCount: 0, + shouldAssertionsPassedCount: 20, + unexpectedBehaviorsFormatted: false, + severeImpactFailedAssertionCount: 0, + severeImpactPassedAssertionCount: 20, + moderateImpactFailedAssertionCount: 0, + moderateImpactPassedAssertionCount: 20 + }, + isFinal: false, + markedFinalAt: null, + issues: [], + draftTestPlanRuns: [ + { + tester: { + username: 'esmeralda-baggins' }, - { - id: '5', - title: 'Checkbox Example (Mixed-State)', - phase: 'RECOMMENDED', - gitSha: 'b3d0576a2901ea7f100f49a994b64edbecf81cff', - gitMessage: - 'Modify VoiceOver commands for task 7 (#842)', - updatedAt: '2022-10-24T21:33:12.000Z', - versionString: 'V22.10.24', - draftPhaseReachedAt: null, - candidatePhaseReachedAt: '2022-07-06T00:00:00.000Z', - recommendedPhaseTargetDate: '2023-01-02T00:00:00.000Z', - recommendedPhaseReachedAt: '2023-01-03T00:00:00.000Z', - testPlan: { - directory: 'checkbox-tri-state' - }, - testPlanReports: [ - { - id: '6', - metrics: { - testsCount: 7, - supportLevel: 'FAILING', - conflictsCount: 0, - supportPercent: 96, - testsFailedCount: 3, - testsPassedCount: 4, - shouldFormatted: '4 of 4 passed', - mustFormatted: '44 of 46 passed', - shouldAssertionsCount: 4, - mustAssertionsCount: 46, - unexpectedBehaviorCount: 0, - unexpectedBehaviorsFormatted: false, - shouldAssertionsFailedCount: 0, - shouldAssertionsPassedCount: 4, - mustAssertionsFailedCount: 2, - mustAssertionsPassedCount: 44 - }, - markedFinalAt: '2022-07-06T00:00:00.000Z', - isFinal: true, - at: { - id: '3', - key: 'voiceover_macos', - name: 'VoiceOver for macOS' - }, - browser: { - id: '3', - key: 'safari_macos', - name: 'Safari' - }, - issues: [], - draftTestPlanRuns: [ - { - tester: { - username: 'tom-proudfeet' - }, - testPlanReport: { - id: '6' - }, - testResults: [ - { - test: { - id: 'YTE3NeyIyIjoiNSJ9WJlMj' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2023-08-18T03:17:10.341Z' - }, - { - test: { - id: 'YWJiOeyIyIjoiNSJ9GQ5Zm' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2023-08-18T03:17:10.405Z' - }, - { - test: { - id: 'ZGFlYeyIyIjoiNSJ9TJlMW' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2023-08-18T03:17:10.474Z' - }, - { - test: { - id: 'YjI2MeyIyIjoiNSJ9WE1OT' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2023-08-18T03:17:10.537Z' - }, - { - test: { - id: 'ZjAwZeyIyIjoiNSJ9TZmZj' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2023-08-18T03:17:10.605Z' - }, - { - test: { - id: 'MGRjZeyIyIjoiNSJ9WNiZD' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2023-08-18T03:17:10.670Z' - }, - { - test: { - id: 'OTZmYeyIyIjoiNSJ9TU5Ym' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2023-08-18T03:17:10.739Z' - } - ] - } - ] - }, - { - id: '12', - metrics: { - testsCount: 14, - supportLevel: 'FULL', - conflictsCount: 0, - supportPercent: 100, - testsFailedCount: 12, - testsPassedCount: 2, - shouldFormatted: false, - mustFormatted: '25 of 25 passed', - shouldAssertionsCount: 0, - mustAssertionsCount: 25, - unexpectedBehaviorCount: 0, - unexpectedBehaviorsFormatted: false, - shouldAssertionsFailedCount: 0, - shouldAssertionsPassedCount: 0, - mustAssertionsFailedCount: 0, - mustAssertionsPassedCount: 25 - }, - markedFinalAt: null, - isFinal: false, - at: { - id: '1', - key: 'jaws', - name: 'JAWS' - }, - browser: { - id: '2', - key: 'chrome', - name: 'Chrome' - }, - issues: [], - draftTestPlanRuns: [ - { - tester: { - username: 'esmeralda-baggins' - }, - testPlanReport: { - id: '12' - }, - testResults: [ - { - test: { - id: 'MTVlZeyIyIjoiNSJ9DUzMz' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2023-08-18T03:17:11.764Z' - }, - { - test: { - id: 'OThhMeyIyIjoiNSJ9WMxM2' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2023-08-18T03:17:11.828Z' - }, - { - test: { - id: 'YWNhNeyIyIjoiNSJ9TliN2' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2023-08-18T03:17:11.892Z' - } - ] - } - ] - }, - { - id: '13', - metrics: { - testsCount: 14, - supportLevel: 'FULL', - conflictsCount: 0, - supportPercent: 100, - testsFailedCount: 12, - testsPassedCount: 2, - shouldFormatted: false, - mustFormatted: '25 of 25 passed', - shouldAssertionsCount: 0, - mustAssertionsCount: 25, - unexpectedBehaviorCount: 0, - unexpectedBehaviorsFormatted: false, - shouldAssertionsFailedCount: 0, - shouldAssertionsPassedCount: 0, - mustAssertionsFailedCount: 0, - mustAssertionsPassedCount: 25 - }, - markedFinalAt: null, - isFinal: false, - at: { - id: '2', - key: 'nvda', - name: 'NVDA' - }, - browser: { - id: '2', - key: 'chrome', - name: 'Chrome' - }, - issues: [], - draftTestPlanRuns: [ - { - tester: { - username: 'esmeralda-baggins' - }, - testPlanReport: { - id: '13' - }, - testResults: [ - { - test: { - id: 'MTVlZeyIyIjoiNSJ9DUzMz' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2023-08-18T03:17:11.955Z' - }, - { - test: { - id: 'OThhMeyIyIjoiNSJ9WMxM2' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2023-08-18T03:17:12.017Z' - }, - { - test: { - id: 'YWNhNeyIyIjoiNSJ9TliN2' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2023-08-18T03:17:12.083Z' - } - ] - } - ] - } - ], - metadata: {} + testPlanReport: { + id: '10' }, - { - id: '26', - title: 'Modal Dialog Example', - phase: 'CANDIDATE', - gitSha: 'd0e16b42179de6f6c070da2310e99de837c71215', - gitMessage: - 'Delete down arrow command for navigating to the beginning of a dialog with JAWS and add the ESC command to exit forms or focus mode (#759)', - updatedAt: '2022-06-22T17:56:16.000Z', - versionString: 'V22.06.22', - draftPhaseReachedAt: '2022-07-06T00:00:00.000Z', - candidatePhaseReachedAt: '2022-07-06T00:00:00.000Z', - recommendedPhaseTargetDate: '2023-01-02T00:00:00.000Z', - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'modal-dialog' - }, - testPlanReports: [ - { - id: '10', - metrics: { - testsCount: 11, - supportLevel: 'FULL', - conflictsCount: 0, - supportPercent: 100, - testsFailedCount: 9, - testsPassedCount: 2, - shouldFormatted: false, - mustFormatted: '14 of 14 passed', - shouldAssertionsCount: 0, - mustAssertionsCount: 14, - unexpectedBehaviorCount: 0, - unexpectedBehaviorsFormatted: false, - shouldAssertionsFailedCount: 0, - shouldAssertionsPassedCount: 0, - mustAssertionsFailedCount: 0, - mustAssertionsPassedCount: 14 - }, - markedFinalAt: null, - isFinal: false, - at: { - id: '3', - key: 'voiceover_macos', - name: 'VoiceOver for macOS' - }, - browser: { - id: '1', - key: 'firefox', - name: 'Firefox' - }, - issues: [], - draftTestPlanRuns: [ - { - tester: { - username: 'esmeralda-baggins' - }, - testPlanReport: { - id: '10' - }, - testResults: [ - { - test: { - id: 'MzlmYeyIyIjoiMjYifQzIxY2' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2023-08-18T03:17:11.295Z' - }, - { - test: { - id: 'N2FkZeyIyIjoiMjYifQDQ5NT' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2023-08-18T03:17:11.369Z' - }, - { - test: { - id: 'ZDJkYeyIyIjoiMjYifQzRkYj' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2023-08-18T03:17:11.450Z' - } - ] - } - ] - }, - { - id: '9', - metrics: { - testsCount: 18, - supportLevel: 'FULL', - conflictsCount: 0, - supportPercent: 100, - testsFailedCount: 16, - testsPassedCount: 2, - shouldFormatted: false, - mustFormatted: '16 of 16 passed', - shouldAssertionsCount: 0, - mustAssertionsCount: 16, - unexpectedBehaviorCount: 0, - unexpectedBehaviorsFormatted: false, - shouldAssertionsFailedCount: 0, - shouldAssertionsPassedCount: 0, - mustAssertionsFailedCount: 0, - mustAssertionsPassedCount: 16 - }, - markedFinalAt: null, - isFinal: false, - at: { - id: '2', - key: 'nvda', - name: 'NVDA' - }, - browser: { - id: '1', - key: 'firefox', - name: 'Firefox' - }, - issues: [], - draftTestPlanRuns: [ - { - tester: { - username: 'esmeralda-baggins' - }, - testPlanReport: { - id: '9' - }, - testResults: [ - { - test: { - id: 'MThhNeyIyIjoiMjYifQmEyMj' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2023-08-18T03:17:11.059Z' - }, - { - test: { - id: 'ODY5MeyIyIjoiMjYifQzhmNW' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2023-08-18T03:17:11.137Z' - }, - { - test: { - id: 'NWVkNeyIyIjoiMjYifQTZkOT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2023-08-18T03:17:11.218Z' - } - ] - } - ] - }, - { - id: '3', - metrics: { - testsCount: 18, - supportLevel: 'FAILING', - conflictsCount: 0, - supportPercent: 88, - testsFailedCount: 16, - testsPassedCount: 2, - shouldFormatted: false, - mustFormatted: '14 of 16 passed', - shouldAssertionsCount: 0, - mustAssertionsCount: 16, - unexpectedBehaviorCount: 1, - unexpectedBehaviorsFormatted: '1 found', - shouldAssertionsFailedCount: 0, - shouldAssertionsPassedCount: 0, - mustAssertionsFailedCount: 2, - mustAssertionsPassedCount: 14 - }, - markedFinalAt: '2022-07-06T00:00:00.000Z', - isFinal: true, - at: { - id: '1', - key: 'jaws', - name: 'JAWS' - }, - browser: { - id: '2', - key: 'chrome', - name: 'Chrome' - }, - issues: [], - draftTestPlanRuns: [ - { - tester: { - username: 'esmeralda-baggins' - }, - testPlanReport: { - id: '3' - }, - testResults: [ - { - test: { - id: 'MThhNeyIyIjoiMjYifQmEyMj' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2023-08-18T03:17:09.074Z' - }, - { - test: { - id: 'NWVkNeyIyIjoiMjYifQTZkOT' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2023-08-18T03:17:09.134Z' - }, - { - test: { - id: 'NWM4NeyIyIjoiMjYifQDEwM2' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2023-08-18T03:17:09.202Z' - }, - { - test: { - id: 'NGFiZeyIyIjoiMjYifQWZiYW' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2023-08-18T03:17:09.268Z' - }, - { - test: { - id: 'MzQzYeyIyIjoiMjYifQzU5Zm' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2023-08-18T03:17:09.336Z' - } - ] - } - ] - }, - { - id: '11', - metrics: { - testsCount: 11, - supportLevel: 'FULL', - conflictsCount: 0, - supportPercent: 100, - testsFailedCount: 9, - testsPassedCount: 2, - shouldFormatted: false, - mustFormatted: '14 of 14 passed', - shouldAssertionsCount: 0, - mustAssertionsCount: 14, - unexpectedBehaviorCount: 0, - unexpectedBehaviorsFormatted: false, - shouldAssertionsFailedCount: 0, - shouldAssertionsPassedCount: 0, - mustAssertionsFailedCount: 0, - mustAssertionsPassedCount: 14 - }, - markedFinalAt: null, - isFinal: false, - at: { - id: '3', - key: 'voiceover_macos', - name: 'VoiceOver for macOS' - }, - browser: { - id: '2', - key: 'chrome', - name: 'Chrome' - }, - issues: [], - draftTestPlanRuns: [ - { - tester: { - username: 'esmeralda-baggins' - }, - testPlanReport: { - id: '11' - }, - testResults: [ - { - test: { - id: 'MzlmYeyIyIjoiMjYifQzIxY2' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2023-08-18T03:17:11.532Z' - }, - { - test: { - id: 'N2FkZeyIyIjoiMjYifQDQ5NT' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2023-08-18T03:17:11.611Z' - }, - { - test: { - id: 'ZDJkYeyIyIjoiMjYifQzRkYj' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2023-08-18T03:17:11.696Z' - } - ] - } - ] - }, - { - id: '4', - metrics: { - testsCount: 18, - supportLevel: 'FAILING', - conflictsCount: 0, - supportPercent: 91, - testsFailedCount: 14, - testsPassedCount: 4, - shouldFormatted: false, - mustFormatted: '20 of 22 passed', - shouldAssertionsCount: 0, - mustAssertionsCount: 22, - unexpectedBehaviorCount: 1, - unexpectedBehaviorsFormatted: '1 found', - shouldAssertionsFailedCount: 0, - shouldAssertionsPassedCount: 0, - mustAssertionsFailedCount: 2, - mustAssertionsPassedCount: 20 - }, - markedFinalAt: '2022-07-06T00:00:00.000Z', - isFinal: true, - at: { - id: '2', - key: 'nvda', - name: 'NVDA' - }, - browser: { - id: '2', - key: 'chrome', - name: 'Chrome' - }, - issues: [], - draftTestPlanRuns: [ - { - tester: { - username: 'esmeralda-baggins' - }, - testPlanReport: { - id: '4' - }, - testResults: [ - { - test: { - id: 'MThhNeyIyIjoiMjYifQmEyMj' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2023-08-18T03:17:09.409Z' - }, - { - test: { - id: 'NWVkNeyIyIjoiMjYifQTZkOT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2023-08-18T03:17:09.478Z' - }, - { - test: { - id: 'NWM4NeyIyIjoiMjYifQDEwM2' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2023-08-18T03:17:09.551Z' - }, - { - test: { - id: 'NGFiZeyIyIjoiMjYifQWZiYW' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2023-08-18T03:17:09.629Z' - }, - { - test: { - id: 'MzQzYeyIyIjoiMjYifQzU5Zm' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2023-08-18T03:17:09.704Z' - }, - { - test: { - id: 'MmI1MeyIyIjoiMjYifQmU3Yz' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2023-08-18T03:17:09.777Z' - }, - { - test: { - id: 'YmRmYeyIyIjoiMjYifQjEyMT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2023-08-18T03:17:09.852Z' - } - ] - } - ] - }, - { - id: '5', - metrics: { - testsCount: 11, - supportLevel: 'FAILING', - conflictsCount: 0, - supportPercent: 92, - testsFailedCount: 8, - testsPassedCount: 3, - shouldFormatted: false, - mustFormatted: '23 of 25 passed', - shouldAssertionsCount: 0, - mustAssertionsCount: 25, - unexpectedBehaviorCount: 1, - unexpectedBehaviorsFormatted: '1 found', - shouldAssertionsFailedCount: 0, - shouldAssertionsPassedCount: 0, - mustAssertionsFailedCount: 2, - mustAssertionsPassedCount: 23 - }, - markedFinalAt: '2022-07-06T00:00:00.000Z', - isFinal: true, - at: { - id: '3', - key: 'voiceover_macos', - name: 'VoiceOver for macOS' - }, - browser: { - id: '3', - key: 'safari_macos', - name: 'Safari' - }, - issues: [], - draftTestPlanRuns: [ - { - tester: { - username: 'esmeralda-baggins' - }, - testPlanReport: { - id: '5' - }, - testResults: [ - { - test: { - id: 'MzlmYeyIyIjoiMjYifQzIxY2' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2023-08-18T03:17:09.923Z' - }, - { - test: { - id: 'ZDJkYeyIyIjoiMjYifQzRkYj' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2023-08-18T03:17:09.991Z' - }, - { - test: { - id: 'ZmQyNeyIyIjoiMjYifQ2M2ND' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2023-08-18T03:17:10.059Z' - }, - { - test: { - id: 'OGE3YeyIyIjoiMjYifQjU1ND' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2023-08-18T03:17:10.129Z' - }, - { - test: { - id: 'YWI3OeyIyIjoiMjYifQWJlNW' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2023-08-18T03:17:10.198Z' - }, - { - test: { - id: 'M2RiOeyIyIjoiMjYifQGY1Nj' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2023-08-18T03:17:10.272Z' - } - ] - } - ] - }, - { - id: '8', - metrics: { - testsCount: 18, - supportLevel: 'FULL', - conflictsCount: 0, - supportPercent: 100, - testsFailedCount: 16, - testsPassedCount: 2, - shouldFormatted: false, - mustFormatted: '16 of 16 passed', - shouldAssertionsCount: 0, - mustAssertionsCount: 16, - unexpectedBehaviorCount: 0, - unexpectedBehaviorsFormatted: false, - shouldAssertionsFailedCount: 0, - shouldAssertionsPassedCount: 0, - mustAssertionsFailedCount: 0, - mustAssertionsPassedCount: 16 - }, - markedFinalAt: null, - isFinal: false, - at: { - id: '1', - key: 'jaws', - name: 'JAWS' - }, - browser: { - id: '1', - key: 'firefox', - name: 'Firefox' - }, - issues: [], - draftTestPlanRuns: [ - { - tester: { - username: 'esmeralda-baggins' - }, - testPlanReport: { - id: '8' - }, - testResults: [ - { - test: { - id: 'MThhNeyIyIjoiMjYifQmEyMj' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2023-08-18T03:17:10.817Z' - }, - { - test: { - id: 'ODY5MeyIyIjoiMjYifQzhmNW' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2023-08-18T03:17:10.894Z' - }, - { - test: { - id: 'NWVkNeyIyIjoiMjYifQTZkOT' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2023-08-18T03:17:10.979Z' - } - ] - } - ] - } - ], - metadata: {} - } + testResults: [ + { + test: { + id: 'NjM0MeyIyIjoiMjQifQTdiZG' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:50.048Z' + }, + { + test: { + id: 'YWYzOeyIyIjoiMjQifQDBjN2' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:50.128Z' + }, + { + test: { + id: 'ZmJjYeyIyIjoiMjQifQWJiNT' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:50.203Z' + }, + { + test: { + id: 'MjU2NeyIyIjoiMjQifQTk2YW' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:50.278Z' + }, + { + test: { + id: 'MWNlNeyIyIjoiMjQifQTRhNT' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:50.355Z' + }, + { + test: { + id: 'YzFlYeyIyIjoiMjQifQjE5Yj' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:50.426Z' + }, + { + test: { + id: 'N2UwMeyIyIjoiMjQifQTQ1OT' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:50.508Z' + }, + { + test: { + id: 'OTYwOeyIyIjoiMjQifQTE3ND' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:50.591Z' + }, + { + test: { + id: 'OWI2MeyIyIjoiMjQifQmE0ZD' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:50.687Z' + }, + { + test: { + id: 'YTU0MeyIyIjoiMjQifQjNhNj' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:50.788Z' + }, + { + test: { + id: 'NTM4MeyIyIjoiMjQifQGVlNm' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:50.914Z' + } + ] + } ] + } } + ] } + } + } + }, + { + request: { + query: testPlanReportStatusDialogQuery, + variables: { testPlanVersionId: '34' } }, - { - request: { - query: testPlanReportStatusDialogQuery, - variables: { testPlanVersionId: '1' } - }, - result: { - data: { - testPlanVersion: { + result: { + data: { + testPlanVersion: { + id: '31', + title: 'Toggle Button', + phase: 'DRAFT', + gitSha: '022340081280b8cafb8ae0716a5b67e9ab942ef4', + gitMessage: + 'Delete duplicated assertion for operating a not pressed togle button (VoiceOver) (#716)', + updatedAt: '2022-05-18T20:51:40.000Z', + draftPhaseReachedAt: '2022-07-06T00:00:00.000Z', + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'toggle-button' + }, + testPlanReportStatuses: [ + { + isRequired: true, + at: { + id: '1', + key: 'jaws', + name: 'JAWS', + atVersions: [ + { id: '1', - title: 'Alert Example', - phase: 'DRAFT', - gitSha: 'c665367f3742c2b607f7b3c2655782188b93f302', - gitMessage: - 'Create updated tests for APG design pattern example: Alert (#685)', - updatedAt: '2022-04-14T17:59:42.000Z', - draftPhaseReachedAt: '2022-07-06T00:00:00.000Z', - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'alert' + name: '2021.2111.13', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '2', + key: 'chrome', + name: 'Chrome' + }, + minimumAtVersion: { + id: '1', + name: '2021.2111.13', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: { + id: '1', + metrics: { + testsCount: 16, + mayFormatted: false, + supportLevel: 'FAILING', + commandsCount: 26, + mustFormatted: '86 of 88 passed', + conflictsCount: 0, + supportPercent: 98, + shouldFormatted: '25 of 26 passed', + testsFailedCount: 6, + testsPassedCount: 10, + mayAssertionsCount: 0, + mustAssertionsCount: 88, + assertionsFailedCount: 3, + assertionsPassedCount: 111, + shouldAssertionsCount: 26, + unexpectedBehaviorCount: 1, + mayAssertionsFailedCount: 0, + mayAssertionsPassedCount: 0, + mustAssertionsFailedCount: 2, + mustAssertionsPassedCount: 86, + shouldAssertionsFailedCount: 1, + shouldAssertionsPassedCount: 25, + unexpectedBehaviorsFormatted: '1 found', + severeImpactFailedAssertionCount: 0, + severeImpactPassedAssertionCount: 26, + moderateImpactFailedAssertionCount: 1, + moderateImpactPassedAssertionCount: 25 + }, + isFinal: false, + markedFinalAt: null, + issues: [], + draftTestPlanRuns: [ + { + tester: { + username: 'esmeralda-baggins' }, - testPlanReportStatuses: [ - { - isRequired: true, - at: { - id: '1', - key: 'jaws', - name: 'JAWS' - }, - browser: { - id: '2', - key: 'chrome', - name: 'Chrome' - }, - minimumAtVersion: { - id: '1', - name: '2021.2111.13' - }, - exactAtVersion: null, - testPlanReport: { - id: '101', - metrics: {}, - isFinal: false, - markedFinalAt: null, - issues: [], - draftTestPlanRuns: [] - } - }, - { - isRequired: false, - at: { - id: '1', - key: 'jaws', - name: 'JAWS' - }, - browser: { - id: '1', - key: 'chrome', - name: 'Firefox' - }, - minimumAtVersion: { - id: '1', - name: '2021.2111.13' - }, - exactAtVersion: null, - testPlanReport: null - }, - { - isRequired: true, - at: { - id: '2', - key: 'nvda', - name: 'NVDA' - }, - browser: { - id: '2', - key: 'chrome', - name: 'Chrome' - }, - minimumAtVersion: { - id: '2', - name: '2020.4' - }, - exactAtVersion: null, - testPlanReport: null - }, - { - isRequired: false, - at: { - id: '2', - key: 'nvda', - name: 'NVDA' - }, - browser: { - id: '1', - key: 'firefox', - name: 'Firefox' - }, - minimumAtVersion: { - id: '2', - name: '2020.4' - }, - exactAtVersion: null, - testPlanReport: null - }, - { - isRequired: true, - at: { - id: '3', - key: 'voiceover_macos', - name: 'VoiceOver for macOS' - }, - browser: { - id: '3', - key: 'safari_macos', - name: 'Safari' - }, - minimumAtVersion: { - id: '3', - name: '11.6 (20G165)' - }, - exactAtVersion: null, - testPlanReport: null - }, - { - isRequired: false, - at: { - id: '3', - key: 'voiceover_macos', - name: 'VoiceOver for macOS' - }, - browser: { - id: '2', - key: 'chrome', - name: 'Chrome' - }, - minimumAtVersion: { - id: '3', - name: '11.6 (20G165)' - }, - exactAtVersion: null, - testPlanReport: null - }, - { - isRequired: false, - at: { - id: '3', - key: 'voiceover_macos', - name: 'VoiceOver for macOS' - }, - browser: { - id: '1', - key: 'firefox', - name: 'Firefox' - }, - minimumAtVersion: { - id: '3', - name: '11.6 (20G165)' - }, - exactAtVersion: null, - testPlanReport: { - id: '7', - metrics: {}, - isFinal: false, - markedFinalAt: null, - issues: [], - draftTestPlanRuns: [] - } - } - ] - } - } - } - }, - { - request: { - query: testPlanReportStatusDialogQuery, - variables: { testPlanVersionId: '5' } - }, - result: { - data: { - testPlanVersion: { - id: '69', - title: 'Checkbox Example (Mixed-State)', - phase: 'RECOMMENDED', - gitSha: '836fb2a997f5b2844035b8c934f8fda9833cd5b2', - gitMessage: 'Validation for test csv formats (#980)', - updatedAt: '2023-08-23T20:30:34.000Z', - draftPhaseReachedAt: null, - candidatePhaseReachedAt: '2022-07-06T00:00:00.000Z', - recommendedPhaseTargetDate: '2023-01-02T00:00:00.000Z', - recommendedPhaseReachedAt: '2023-01-03T00:00:00.000Z', - testPlan: { - directory: 'checkbox-tri-state' + testPlanReport: { + id: '1' }, - testPlanReportStatuses: [ - { - isRequired: true, - at: { - id: '1', - key: 'jaws', - name: 'JAWS' - }, - browser: { - id: '2', - key: 'chrome', - name: 'Chrome' - }, - minimumAtVersion: { - id: '1', - name: '2021.2111.13' - }, - exactAtVersion: null, - testPlanReport: { - id: '12', - metrics: { - testsCount: 14, - mayFormatted: false, - supportLevel: 'FULL', - commandsCount: 26, - mustFormatted: '116 of 116 passed', - conflictsCount: 0, - supportPercent: 100, - shouldFormatted: '34 of 34 passed', - testsFailedCount: 0, - testsPassedCount: 14, - mayAssertionsCount: 0, - mustAssertionsCount: 116, - assertionsFailedCount: 0, - assertionsPassedCount: 150, - shouldAssertionsCount: 34, - unexpectedBehaviorCount: 0, - mayAssertionsFailedCount: 0, - mayAssertionsPassedCount: 0, - mustAssertionsFailedCount: 0, - mustAssertionsPassedCount: 116, - shouldAssertionsFailedCount: 0, - shouldAssertionsPassedCount: 34, - unexpectedBehaviorsFormatted: false, - severeImpactFailedAssertionCount: 0, - severeImpactPassedAssertionCount: 26, - moderateImpactFailedAssertionCount: 0, - moderateImpactPassedAssertionCount: 26 - }, - isFinal: true, - markedFinalAt: '2022-07-06T00:00:00.000Z', - issues: [], - draftTestPlanRuns: [ - { - tester: { - username: 'esmeralda-baggins' - }, - testPlanReport: { - id: '12' - }, - testResults: [ - { - test: { - id: 'YWYzOeyIyIjoiNjkifQTQ0MT' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:52.053Z' - }, - { - test: { - id: 'OGZjNeyIyIjoiNjkifQjQxZW' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:52.149Z' - }, - { - test: { - id: 'NjM3ZeyIyIjoiNjkifQmUxYz' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:52.242Z' - }, - { - test: { - id: 'ZWQ0MeyIyIjoiNjkifQGZhYT' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:52.321Z' - }, - { - test: { - id: 'ZGI3ZeyIyIjoiNjkifQTc5Mj' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:52.409Z' - }, - { - test: { - id: 'MDZjOeyIyIjoiNjkifQGJkYz' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:52.510Z' - }, - { - test: { - id: 'ZmI3NeyIyIjoiNjkifQzUwMT' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:52.614Z' - }, - { - test: { - id: 'NmY2YeyIyIjoiNjkifQTczOW' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:52.731Z' - }, - { - test: { - id: 'MjIwYeyIyIjoiNjkifQmUzZj' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:52.882Z' - }, - { - test: { - id: 'ODg0OeyIyIjoiNjkifQWFlYm' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:53.038Z' - }, - { - test: { - id: 'ZDQ2MeyIyIjoiNjkifQjlmZj' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:53.184Z' - }, - { - test: { - id: 'MjdlYeyIyIjoiNjkifQTgyNj' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:53.317Z' - }, - { - test: { - id: 'OGE5MeyIyIjoiNjkifQGZjOT' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:53.428Z' - }, - { - test: { - id: 'YWNlNeyIyIjoiNjkifQjQzOW' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:53.567Z' - } - ] - } - ] - } - }, - { - isRequired: true, - at: { - id: '1', - key: 'jaws', - name: 'JAWS' - }, - browser: { - id: '1', - key: 'firefox', - name: 'Firefox' - }, - minimumAtVersion: { - id: '1', - name: '2021.2111.13' - }, - exactAtVersion: null, - testPlanReport: null - }, - { - isRequired: true, - at: { - id: '2', - key: 'nvda', - name: 'NVDA' - }, - browser: { - id: '2', - key: 'chrome', - name: 'Chrome' - }, - minimumAtVersion: { - id: '2', - name: '2020.4' - }, - exactAtVersion: null, - testPlanReport: { - id: '13', - metrics: { - testsCount: 14, - mayFormatted: false, - supportLevel: 'FULL', - commandsCount: 28, - mustFormatted: '124 of 124 passed', - conflictsCount: 0, - supportPercent: 100, - shouldFormatted: '36 of 36 passed', - testsFailedCount: 0, - testsPassedCount: 14, - mayAssertionsCount: 0, - mustAssertionsCount: 124, - assertionsFailedCount: 0, - assertionsPassedCount: 160, - shouldAssertionsCount: 36, - unexpectedBehaviorCount: 0, - mayAssertionsFailedCount: 0, - mayAssertionsPassedCount: 0, - mustAssertionsFailedCount: 0, - mustAssertionsPassedCount: 124, - shouldAssertionsFailedCount: 0, - shouldAssertionsPassedCount: 36, - unexpectedBehaviorsFormatted: false, - severeImpactFailedAssertionCount: 0, - severeImpactPassedAssertionCount: 28, - moderateImpactFailedAssertionCount: 0, - moderateImpactPassedAssertionCount: 28 - }, - isFinal: true, - markedFinalAt: '2022-07-07T00:00:00.000Z', - issues: [], - draftTestPlanRuns: [ - { - tester: { - username: 'esmeralda-baggins' - }, - testPlanReport: { - id: '13' - }, - testResults: [ - { - test: { - id: 'YWYzOeyIyIjoiNjkifQTQ0MT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:53.698Z' - }, - { - test: { - id: 'OGZjNeyIyIjoiNjkifQjQxZW' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:53.814Z' - }, - { - test: { - id: 'NjM3ZeyIyIjoiNjkifQmUxYz' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:53.921Z' - }, - { - test: { - id: 'ZWQ0MeyIyIjoiNjkifQGZhYT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:54.038Z' - }, - { - test: { - id: 'ZGI3ZeyIyIjoiNjkifQTc5Mj' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:54.181Z' - }, - { - test: { - id: 'MDZjOeyIyIjoiNjkifQGJkYz' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:54.327Z' - }, - { - test: { - id: 'ZmI3NeyIyIjoiNjkifQzUwMT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:54.421Z' - }, - { - test: { - id: 'NmY2YeyIyIjoiNjkifQTczOW' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:54.541Z' - }, - { - test: { - id: 'MjIwYeyIyIjoiNjkifQmUzZj' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:54.666Z' - }, - { - test: { - id: 'ODg0OeyIyIjoiNjkifQWFlYm' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:54.794Z' - }, - { - test: { - id: 'ZDQ2MeyIyIjoiNjkifQjlmZj' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:54.881Z' - }, - { - test: { - id: 'MjdlYeyIyIjoiNjkifQTgyNj' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:54.967Z' - }, - { - test: { - id: 'OGE5MeyIyIjoiNjkifQGZjOT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:55.059Z' - }, - { - test: { - id: 'YWNlNeyIyIjoiNjkifQjQzOW' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:55.140Z' - } - ] - } - ] - } - }, - { - isRequired: true, - at: { - id: '2', - key: 'nvda', - name: 'NVDA' - }, - browser: { - id: '1', - key: 'firefox', - name: 'Firefox' - }, - minimumAtVersion: { - id: '2', - name: '2020.4' - }, - exactAtVersion: null, - testPlanReport: null - }, - { - isRequired: true, - at: { - id: '3', - key: 'voiceover_macos', - name: 'VoiceOver for macOS' - }, - browser: { - id: '2', - key: 'chrome', - name: 'Chrome' - }, - minimumAtVersion: { - id: '3', - name: '11.6 (20G165)' - }, - exactAtVersion: null, - testPlanReport: null - }, - { - isRequired: true, - at: { - id: '3', - key: 'voiceover_macos', - name: 'VoiceOver for macOS' - }, - browser: { - id: '3', - key: 'safari_macos', - name: 'Safari' - }, - minimumAtVersion: { - id: '3', - name: '11.6 (20G165)' - }, - exactAtVersion: null, - testPlanReport: { - id: '6', - metrics: { - testsCount: 7, - mayFormatted: false, - supportLevel: 'FAILING', - commandsCount: 16, - mustFormatted: '66 of 68 passed', - conflictsCount: 0, - supportPercent: 97, - shouldFormatted: '20 of 20 passed', - testsFailedCount: 2, - testsPassedCount: 5, - mayAssertionsCount: 0, - mustAssertionsCount: 68, - assertionsFailedCount: 2, - assertionsPassedCount: 86, - shouldAssertionsCount: 20, - unexpectedBehaviorCount: 0, - mayAssertionsFailedCount: 0, - mayAssertionsPassedCount: 0, - mustAssertionsFailedCount: 2, - mustAssertionsPassedCount: 66, - shouldAssertionsFailedCount: 0, - shouldAssertionsPassedCount: 20, - unexpectedBehaviorsFormatted: false, - severeImpactFailedAssertionCount: 0, - severeImpactPassedAssertionCount: 16, - moderateImpactFailedAssertionCount: 0, - moderateImpactPassedAssertionCount: 16 - }, - isFinal: true, - markedFinalAt: '2022-07-06T00:00:00.000Z', - issues: [], - draftTestPlanRuns: [ - { - tester: { - username: 'tom-proudfeet' - }, - testPlanReport: { - id: '6' - }, - testResults: [ - { - test: { - id: 'NmUzMeyIyIjoiNjkifQmU0OT' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2024-04-25T16:44:45.554Z' - }, - { - test: { - id: 'Y2UyYeyIyIjoiNjkifQ2Y1Mz' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2024-04-25T16:44:45.671Z' - }, - { - test: { - id: 'ODc2OeyIyIjoiNjkifQTA1Yz' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2024-04-25T16:44:45.794Z' - }, - { - test: { - id: 'OTgwZeyIyIjoiNjkifQDZjOG' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2024-04-25T16:44:45.909Z' - }, - { - test: { - id: 'ODA3ZeyIyIjoiNjkifQjI4Y2' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2024-04-25T16:44:45.997Z' - }, - { - test: { - id: 'OWI4MeyIyIjoiNjkifQzFlZD' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2024-04-25T16:44:46.091Z' - }, - { - test: { - id: 'MzhiZeyIyIjoiNjkifQWE4Nj' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2024-04-25T16:44:46.189Z' - } - ] - } - ] - } - }, - { - isRequired: false, - at: { - id: '3', - key: 'voiceover_macos', - name: 'VoiceOver for macOS' - }, - browser: { - id: '1', - key: 'firefox', - name: 'Firefox' - }, - minimumAtVersion: { - id: '3', - name: '11.6 (20G165)' - }, - exactAtVersion: null, - testPlanReport: null - } + testResults: [ + { + test: { + id: 'MTExZeyIyIjoiMzEifQWZhZG' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:35.281Z' + }, + { + test: { + id: 'MzJkZeyIyIjoiMzEifQTAzMm' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: null + }, + { + test: { + id: 'NDBjMeyIyIjoiMzEifQjc1NT' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: null + }, + { + test: { + id: 'MjE2MeyIyIjoiMzEifQ2M0NW' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: null + }, + { + test: { + id: 'MWZiZeyIyIjoiMzEifQjhhYz' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:35.541Z' + }, + { + test: { + id: 'NmI4NeyIyIjoiMzEifQDU2OD' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:35.636Z' + }, + { + test: { + id: 'YmExNeyIyIjoiMzEifQWE5Nj' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:35.730Z' + }, + { + test: { + id: 'YzA3NeyIyIjoiMzEifQGZhYT' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:35.831Z' + }, + { + test: { + id: 'YmYxOeyIyIjoiMzEifQDAxY2' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:35.928Z' + }, + { + test: { + id: 'YzIwOeyIyIjoiMzEifQGE2Yz' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:36.034Z' + }, + { + test: { + id: 'YWMwNeyIyIjoiMzEifQDQ5MG' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:36.126Z' + }, + { + test: { + id: 'MjQyMeyIyIjoiMzEifQWExMm' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:36.210Z' + }, + { + test: { + id: 'NDFiYeyIyIjoiMzEifQzg4MD' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:36.332Z' + }, + { + test: { + id: 'M2RmNeyIyIjoiMzEifQzQ0MG' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:36.409Z' + }, + { + test: { + id: 'ODhlYeyIyIjoiMzEifQmVmMT' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:36.510Z' + } ] - } - } - } - }, - { - request: { - query: testPlanReportStatusDialogQuery, - variables: { testPlanVersionId: '7' } - }, - result: { - data: { - testPlanVersion: { - id: '7', - title: 'Select Only Combobox Example', - phase: 'DRAFT', - gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', - gitMessage: - 'Generate html source script to support aria-at-app (#646)', - updatedAt: '2022-03-17T18:34:51.000Z', - draftPhaseReachedAt: '2022-07-06T00:00:00.000Z', - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'combobox-select-only' + } + ] + } + }, + { + isRequired: false, + at: { + id: '1', + key: 'jaws', + name: 'JAWS', + atVersions: [ + { + id: '1', + name: '2021.2111.13', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '1', + key: 'firefox', + name: 'Firefox' + }, + minimumAtVersion: { + id: '1', + name: '2021.2111.13', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: null + }, + { + isRequired: true, + at: { + id: '2', + key: 'nvda', + name: 'NVDA', + atVersions: [ + { + id: '2', + name: '2020.4', + supportedByAutomation: false, + releasedAt: '2020-04-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '2', + key: 'chrome', + name: 'Chrome' + }, + minimumAtVersion: { + id: '2', + name: '2020.4', + supportedByAutomation: false, + releasedAt: '2020-04-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: { + id: '14', + metrics: { + testsCount: 16, + mayFormatted: false, + supportLevel: 'FULL', + commandsCount: 36, + mustFormatted: '128 of 128 passed', + conflictsCount: 0, + supportPercent: 100, + shouldFormatted: '36 of 36 passed', + testsFailedCount: 0, + testsPassedCount: 16, + mayAssertionsCount: 0, + mustAssertionsCount: 128, + assertionsFailedCount: 0, + assertionsPassedCount: 164, + shouldAssertionsCount: 36, + unexpectedBehaviorCount: 0, + mayAssertionsFailedCount: 0, + mayAssertionsPassedCount: 0, + mustAssertionsFailedCount: 0, + mustAssertionsPassedCount: 128, + shouldAssertionsFailedCount: 0, + shouldAssertionsPassedCount: 36, + unexpectedBehaviorsFormatted: false, + severeImpactFailedAssertionCount: 0, + severeImpactPassedAssertionCount: 36, + moderateImpactFailedAssertionCount: 0, + moderateImpactPassedAssertionCount: 36 + }, + isFinal: true, + markedFinalAt: '2022-07-07T00:00:00.000Z', + issues: [], + draftTestPlanRuns: [ + { + tester: { + username: 'esmeralda-baggins' }, - testPlanReportStatuses: [ - { - isRequired: true, - at: { - id: '1', - key: 'jaws', - name: 'JAWS' - }, - browser: { - id: '2', - key: 'chrome', - name: 'Chrome' - }, - minimumAtVersion: { - id: '1', - name: '2021.2111.13' - }, - exactAtVersion: null, - testPlanReport: null - }, - { - isRequired: false, - at: { - id: '1', - key: 'jaws', - name: 'JAWS' - }, - browser: { - id: '1', - key: 'firefox', - name: 'Firefox' - }, - minimumAtVersion: { - id: '1', - name: '2021.2111.13' - }, - exactAtVersion: null, - testPlanReport: null - }, - { - isRequired: true, - at: { - id: '2', - key: 'nvda', - name: 'NVDA' - }, - browser: { - id: '2', - key: 'chrome', - name: 'Chrome' - }, - minimumAtVersion: { - id: '2', - name: '2020.4' - }, - exactAtVersion: null, - testPlanReport: null - }, - { - isRequired: false, - at: { - id: '2', - key: 'nvda', - name: 'NVDA' - }, - browser: { - id: '1', - key: 'firefox', - name: 'Firefox' - }, - minimumAtVersion: { - id: '2', - name: '2020.4' - }, - exactAtVersion: null, - testPlanReport: { - id: '2', - metrics: { - testsCount: 21, - mayFormatted: false, - supportLevel: 'FAILING', - commandsCount: 24, - mustFormatted: '118 of 122 passed', - conflictsCount: 3, - supportPercent: 97, - shouldFormatted: '34 of 36 passed', - testsFailedCount: 6, - testsPassedCount: 15, - mayAssertionsCount: 0, - mustAssertionsCount: 122, - assertionsFailedCount: 6, - assertionsPassedCount: 152, - shouldAssertionsCount: 36, - unexpectedBehaviorCount: 3, - mayAssertionsFailedCount: 0, - mayAssertionsPassedCount: 0, - mustAssertionsFailedCount: 4, - mustAssertionsPassedCount: 118, - shouldAssertionsFailedCount: 2, - shouldAssertionsPassedCount: 34, - unexpectedBehaviorsFormatted: '3 found', - severeImpactFailedAssertionCount: 1, - severeImpactPassedAssertionCount: 23, - moderateImpactFailedAssertionCount: 2, - moderateImpactPassedAssertionCount: 22 - }, - isFinal: false, - markedFinalAt: null, - issues: [ - { - link: 'https://github.com/bocoup/aria-at/issues/128#issue-2157878584', - isOpen: true, - feedbackType: 'FEEDBACK' - } - ], - draftTestPlanRuns: [ - { - tester: { - username: 'tom-proudfeet' - }, - testPlanReport: { - id: '2' - }, - testResults: [ - { - test: { - id: 'Nzg5NeyIyIjoiNyJ9zNjZj' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:38.949Z' - }, - { - test: { - id: 'MmY0YeyIyIjoiNyJ9jRkZD' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:39.070Z' - }, - { - test: { - id: 'ZjUwNeyIyIjoiNyJ9mE2ZT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:39.180Z' - }, - { - test: { - id: 'MDNiMeyIyIjoiNyJ9Dk1MT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:39.292Z' - }, - { - test: { - id: 'MjRmNeyIyIjoiNyJ92MyMT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:39.406Z' - }, - { - test: { - id: 'ZmVlMeyIyIjoiNyJ9mUyYj' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: null - }, - { - test: { - id: 'YWFiNeyIyIjoiNyJ9zE2Zj' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:39.640Z' - }, - { - test: { - id: 'YjZkYeyIyIjoiNyJ9WIxZm' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:39.761Z' - }, - { - test: { - id: 'ZmIzMeyIyIjoiNyJ9TQ1NW' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:39.888Z' - }, - { - test: { - id: 'MmZkNeyIyIjoiNyJ9zIwN2' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:40.006Z' - }, - { - test: { - id: 'ZmQwOeyIyIjoiNyJ9DEzYz' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:40.122Z' - }, - { - test: { - id: 'MGViNeyIyIjoiNyJ9GQ3MT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:40.234Z' - }, - { - test: { - id: 'YTg5MeyIyIjoiNyJ9WEzOT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:40.355Z' - }, - { - test: { - id: 'NTRjMeyIyIjoiNyJ9zQ0OD' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:40.467Z' - }, - { - test: { - id: 'MjRlZeyIyIjoiNyJ9DcyY2' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:40.588Z' - }, - { - test: { - id: 'YWQzNeyIyIjoiNyJ9mE2Nm' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:40.712Z' - }, - { - test: { - id: 'OTYxOeyIyIjoiNyJ9TdmYj' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:40.826Z' - }, - { - test: { - id: 'MjgzNeyIyIjoiNyJ9TZjNz' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:40.948Z' - }, - { - test: { - id: 'NWNiZeyIyIjoiNyJ9jI2MD' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:41.075Z' - } - ] - }, - { - tester: { - username: 'esmeralda-baggins' - }, - testPlanReport: { - id: '2' - }, - testResults: [ - { - test: { - id: 'Nzg5NeyIyIjoiNyJ9zNjZj' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:36.666Z' - }, - { - test: { - id: 'MmY0YeyIyIjoiNyJ9jRkZD' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:36.793Z' - }, - { - test: { - id: 'ZjUwNeyIyIjoiNyJ9mE2ZT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:36.914Z' - }, - { - test: { - id: 'MDNiMeyIyIjoiNyJ9Dk1MT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:37.031Z' - }, - { - test: { - id: 'MjRmNeyIyIjoiNyJ92MyMT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:37.150Z' - }, - { - test: { - id: 'ZmVlMeyIyIjoiNyJ9mUyYj' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: null - }, - { - test: { - id: 'YWFiNeyIyIjoiNyJ9zE2Zj' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:37.384Z' - }, - { - test: { - id: 'YjZkYeyIyIjoiNyJ9WIxZm' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:37.512Z' - }, - { - test: { - id: 'ZmIzMeyIyIjoiNyJ9TQ1NW' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:37.638Z' - }, - { - test: { - id: 'MmZkNeyIyIjoiNyJ9zIwN2' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:37.760Z' - }, - { - test: { - id: 'ZmQwOeyIyIjoiNyJ9DEzYz' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:37.883Z' - }, - { - test: { - id: 'MGViNeyIyIjoiNyJ9GQ3MT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:38.014Z' - }, - { - test: { - id: 'YTg5MeyIyIjoiNyJ9WEzOT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:38.145Z' - }, - { - test: { - id: 'NTRjMeyIyIjoiNyJ9zQ0OD' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:38.268Z' - }, - { - test: { - id: 'MjRlZeyIyIjoiNyJ9DcyY2' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:38.382Z' - }, - { - test: { - id: 'YWQzNeyIyIjoiNyJ9mE2Nm' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:38.481Z' - }, - { - test: { - id: 'OTYxOeyIyIjoiNyJ9TdmYj' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:38.596Z' - }, - { - test: { - id: 'MjgzNeyIyIjoiNyJ9TZjNz' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:38.701Z' - }, - { - test: { - id: 'NWNiZeyIyIjoiNyJ9jI2MD' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:38.811Z' - } - ] - } - ] - } - }, - { - isRequired: true, - at: { - id: '3', - key: 'voiceover_macos', - name: 'VoiceOver for macOS' - }, - browser: { - id: '3', - key: 'safari_macos', - name: 'Safari' - }, - minimumAtVersion: { - id: '3', - name: '11.6 (20G165)' - }, - exactAtVersion: null, - testPlanReport: null - }, - { - isRequired: false, - at: { - id: '3', - key: 'voiceover_macos', - name: 'VoiceOver for macOS' - }, - browser: { - id: '2', - key: 'chrome', - name: 'Chrome' - }, - minimumAtVersion: { - id: '3', - name: '11.6 (20G165)' - }, - exactAtVersion: null, - testPlanReport: null - }, - { - isRequired: false, - at: { - id: '3', - key: 'voiceover_macos', - name: 'VoiceOver for macOS' - }, - browser: { - id: '1', - key: 'firefox', - name: 'Firefox' - }, - minimumAtVersion: { - id: '3', - name: '11.6 (20G165)' - }, - exactAtVersion: null, - testPlanReport: null - } - ] - } - } - } - }, - { - request: { - query: testPlanReportStatusDialogQuery, - variables: { testPlanVersionId: '26' } - }, - result: { - data: { - testPlanVersion: { - id: '24', - title: 'Modal Dialog Example', - phase: 'CANDIDATE', - gitSha: '5fe7afd82fe51c185b8661276105190a59d47322', - gitMessage: 'Task 7: delete incorrect instructions (#733)', - updatedAt: '2022-05-26T16:10:10.000Z', - draftPhaseReachedAt: '2022-07-06T00:00:00.000Z', - candidatePhaseReachedAt: '2022-07-06T00:00:00.000Z', - recommendedPhaseTargetDate: '2023-01-02T00:00:00.000Z', - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'modal-dialog' + testPlanReport: { + id: '14' }, - testPlanReportStatuses: [ - { - isRequired: true, - at: { - id: '1', - key: 'jaws', - name: 'JAWS' - }, - browser: { - id: '2', - key: 'chrome', - name: 'Chrome' - }, - minimumAtVersion: { - id: '1', - name: '2021.2111.13' - }, - exactAtVersion: null, - testPlanReport: { - id: '3', - metrics: { - testsCount: 18, - mayFormatted: false, - supportLevel: 'FAILING', - commandsCount: 26, - mustFormatted: '115 of 117 passed', - conflictsCount: 0, - supportPercent: 98, - shouldFormatted: '25 of 26 passed', - testsFailedCount: 2, - testsPassedCount: 16, - mayAssertionsCount: 0, - mustAssertionsCount: 117, - assertionsFailedCount: 3, - assertionsPassedCount: 140, - shouldAssertionsCount: 26, - unexpectedBehaviorCount: 1, - mayAssertionsFailedCount: 0, - mayAssertionsPassedCount: 0, - mustAssertionsFailedCount: 2, - mustAssertionsPassedCount: 115, - shouldAssertionsFailedCount: 1, - shouldAssertionsPassedCount: 25, - unexpectedBehaviorsFormatted: '1 found', - severeImpactFailedAssertionCount: 0, - severeImpactPassedAssertionCount: 26, - moderateImpactFailedAssertionCount: 1, - moderateImpactPassedAssertionCount: 25 - }, - isFinal: true, - markedFinalAt: '2022-07-06T00:00:00.000Z', - issues: [], - draftTestPlanRuns: [ - { - tester: { - username: 'esmeralda-baggins' - }, - testPlanReport: { - id: '3' - }, - testResults: [ - { - test: { - id: 'ZjE0NeyIyIjoiMjQifQmI0NT' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:41.177Z' - }, - { - test: { - id: 'YjZlNeyIyIjoiMjQifQTc5ZW' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:41.256Z' - }, - { - test: { - id: 'NWM0MeyIyIjoiMjQifQzhiYz' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:41.349Z' - }, - { - test: { - id: 'YzM0ZeyIyIjoiMjQifQmRmMz' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:41.419Z' - }, - { - test: { - id: 'ZjVjMeyIyIjoiMjQifQDRhY2' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:41.496Z' - }, - { - test: { - id: 'YmUzMeyIyIjoiMjQifQmRmNm' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:41.574Z' - }, - { - test: { - id: 'ZGJmMeyIyIjoiMjQifQzU5Yz' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:41.661Z' - }, - { - test: { - id: 'Nzg1OeyIyIjoiMjQifQTYxM2' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:41.741Z' - }, - { - test: { - id: 'ZTI0MeyIyIjoiMjQifQzM4YT' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:41.823Z' - }, - { - test: { - id: 'MzY5ZeyIyIjoiMjQifQmQ0OT' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:41.950Z' - }, - { - test: { - id: 'ZjVmYeyIyIjoiMjQifQjJjYW' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:42.044Z' - }, - { - test: { - id: 'ZTMwNeyIyIjoiMjQifQzI5Nz' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:42.112Z' - }, - { - test: { - id: 'NGY0MeyIyIjoiMjQifQ2FjMj' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:42.191Z' - }, - { - test: { - id: 'OTI0OeyIyIjoiMjQifQTU1ZT' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:42.271Z' - }, - { - test: { - id: 'MDRhMeyIyIjoiMjQifQWEzMj' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:42.344Z' - }, - { - test: { - id: 'ZThlZeyIyIjoiMjQifQjQ2Nz' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:42.412Z' - }, - { - test: { - id: 'NjhjMeyIyIjoiMjQifQGE0ND' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:42.482Z' - }, - { - test: { - id: 'YTAzZeyIyIjoiMjQifQTc5ZD' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:42.575Z' - } - ] - } - ] - } - }, - { - isRequired: false, - at: { - id: '1', - key: 'jaws', - name: 'JAWS' - }, - browser: { - id: '2', - key: 'chrome', - name: 'Chrome' - }, - minimumAtVersion: null, - exactAtVersion: { - id: '1', - name: '2021.2111.13' - }, - testPlanReport: { - id: '105', - metrics: {}, - isFinal: false, - markedFinalAt: null, - issues: [], - draftTestPlanRuns: [] - } - }, - { - isRequired: false, - at: { - id: '1', - key: 'jaws', - name: 'JAWS' - }, - browser: { - id: '1', - key: 'firefox', - name: 'Firefox' - }, - minimumAtVersion: { - id: '1', - name: '2021.2111.13' - }, - exactAtVersion: null, - testPlanReport: { - id: '8', - metrics: { - testsCount: 18, - mayFormatted: false, - supportLevel: 'FULL', - commandsCount: 26, - mustFormatted: '117 of 117 passed', - conflictsCount: 0, - supportPercent: 100, - shouldFormatted: '26 of 26 passed', - testsFailedCount: 0, - testsPassedCount: 18, - mayAssertionsCount: 0, - mustAssertionsCount: 117, - assertionsFailedCount: 0, - assertionsPassedCount: 143, - shouldAssertionsCount: 26, - unexpectedBehaviorCount: 0, - mayAssertionsFailedCount: 0, - mayAssertionsPassedCount: 0, - mustAssertionsFailedCount: 0, - mustAssertionsPassedCount: 117, - shouldAssertionsFailedCount: 0, - shouldAssertionsPassedCount: 26, - unexpectedBehaviorsFormatted: false, - severeImpactFailedAssertionCount: 0, - severeImpactPassedAssertionCount: 26, - moderateImpactFailedAssertionCount: 0, - moderateImpactPassedAssertionCount: 26 - }, - isFinal: false, - markedFinalAt: null, - issues: [], - draftTestPlanRuns: [ - { - tester: { - username: 'esmeralda-baggins' - }, - testPlanReport: { - id: '8' - }, - testResults: [ - { - test: { - id: 'ZjE0NeyIyIjoiMjQifQmI0NT' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:46.300Z' - }, - { - test: { - id: 'YjZlNeyIyIjoiMjQifQTc5ZW' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:46.396Z' - }, - { - test: { - id: 'NWM0MeyIyIjoiMjQifQzhiYz' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:46.490Z' - }, - { - test: { - id: 'YzM0ZeyIyIjoiMjQifQmRmMz' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:46.572Z' - }, - { - test: { - id: 'ZjVjMeyIyIjoiMjQifQDRhY2' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:46.656Z' - }, - { - test: { - id: 'YmUzMeyIyIjoiMjQifQmRmNm' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:46.743Z' - }, - { - test: { - id: 'ZGJmMeyIyIjoiMjQifQzU5Yz' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:46.828Z' - }, - { - test: { - id: 'Nzg1OeyIyIjoiMjQifQTYxM2' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:46.912Z' - }, - { - test: { - id: 'ZTI0MeyIyIjoiMjQifQzM4YT' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:46.990Z' - }, - { - test: { - id: 'MzY5ZeyIyIjoiMjQifQmQ0OT' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:47.087Z' - }, - { - test: { - id: 'ZjVmYeyIyIjoiMjQifQjJjYW' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:47.215Z' - }, - { - test: { - id: 'ZTMwNeyIyIjoiMjQifQzI5Nz' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:47.355Z' - }, - { - test: { - id: 'NGY0MeyIyIjoiMjQifQ2FjMj' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:47.484Z' - }, - { - test: { - id: 'OTI0OeyIyIjoiMjQifQTU1ZT' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:47.614Z' - }, - { - test: { - id: 'MDRhMeyIyIjoiMjQifQWEzMj' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:47.738Z' - }, - { - test: { - id: 'ZThlZeyIyIjoiMjQifQjQ2Nz' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:47.862Z' - }, - { - test: { - id: 'NjhjMeyIyIjoiMjQifQGE0ND' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:47.981Z' - }, - { - test: { - id: 'YTAzZeyIyIjoiMjQifQTc5ZD' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:48.105Z' - } - ] - } - ] - } - }, - { - isRequired: true, - at: { - id: '2', - key: 'nvda', - name: 'NVDA' - }, - browser: { - id: '2', - key: 'chrome', - name: 'Chrome' - }, - minimumAtVersion: { - id: '2', - name: '2020.4' - }, - exactAtVersion: null, - testPlanReport: { - id: '4', - metrics: { - testsCount: 18, - mayFormatted: false, - supportLevel: 'FAILING', - commandsCount: 26, - mustFormatted: '115 of 117 passed', - conflictsCount: 0, - supportPercent: 98, - shouldFormatted: '25 of 26 passed', - testsFailedCount: 2, - testsPassedCount: 16, - mayAssertionsCount: 0, - mustAssertionsCount: 117, - assertionsFailedCount: 3, - assertionsPassedCount: 140, - shouldAssertionsCount: 26, - unexpectedBehaviorCount: 1, - mayAssertionsFailedCount: 0, - mayAssertionsPassedCount: 0, - mustAssertionsFailedCount: 2, - mustAssertionsPassedCount: 115, - shouldAssertionsFailedCount: 1, - shouldAssertionsPassedCount: 25, - unexpectedBehaviorsFormatted: '1 found', - severeImpactFailedAssertionCount: 0, - severeImpactPassedAssertionCount: 26, - moderateImpactFailedAssertionCount: 1, - moderateImpactPassedAssertionCount: 25 - }, - isFinal: true, - markedFinalAt: '2022-07-06T00:00:00.000Z', - issues: [], - draftTestPlanRuns: [ - { - tester: { - username: 'esmeralda-baggins' - }, - testPlanReport: { - id: '4' - }, - testResults: [ - { - test: { - id: 'ZjE0NeyIyIjoiMjQifQmI0NT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:42.683Z' - }, - { - test: { - id: 'YjZlNeyIyIjoiMjQifQTc5ZW' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:42.772Z' - }, - { - test: { - id: 'NWM0MeyIyIjoiMjQifQzhiYz' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:42.848Z' - }, - { - test: { - id: 'YzM0ZeyIyIjoiMjQifQmRmMz' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:42.928Z' - }, - { - test: { - id: 'ZjVjMeyIyIjoiMjQifQDRhY2' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:43.005Z' - }, - { - test: { - id: 'YmUzMeyIyIjoiMjQifQmRmNm' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:43.093Z' - }, - { - test: { - id: 'ZGJmMeyIyIjoiMjQifQzU5Yz' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:43.173Z' - }, - { - test: { - id: 'Nzg1OeyIyIjoiMjQifQTYxM2' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:43.245Z' - }, - { - test: { - id: 'ZTI0MeyIyIjoiMjQifQzM4YT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:43.318Z' - }, - { - test: { - id: 'MzY5ZeyIyIjoiMjQifQmQ0OT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:43.399Z' - }, - { - test: { - id: 'ZjVmYeyIyIjoiMjQifQjJjYW' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:43.473Z' - }, - { - test: { - id: 'ZTMwNeyIyIjoiMjQifQzI5Nz' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:43.554Z' - }, - { - test: { - id: 'NGY0MeyIyIjoiMjQifQ2FjMj' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:43.643Z' - }, - { - test: { - id: 'OTI0OeyIyIjoiMjQifQTU1ZT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:43.738Z' - }, - { - test: { - id: 'MDRhMeyIyIjoiMjQifQWEzMj' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:43.844Z' - }, - { - test: { - id: 'ZThlZeyIyIjoiMjQifQjQ2Nz' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:43.950Z' - }, - { - test: { - id: 'NjhjMeyIyIjoiMjQifQGE0ND' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:44.078Z' - }, - { - test: { - id: 'YTAzZeyIyIjoiMjQifQTc5ZD' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:44.219Z' - } - ] - } - ] - } - }, - { - isRequired: false, - at: { - id: '2', - key: 'nvda', - name: 'NVDA' - }, - browser: { - id: '1', - key: 'firefox', - name: 'Firefox' - }, - minimumAtVersion: { - id: '2', - name: '2020.4' - }, - exactAtVersion: null, - testPlanReport: { - id: '9', - metrics: { - testsCount: 18, - mayFormatted: false, - supportLevel: 'FULL', - commandsCount: 26, - mustFormatted: '117 of 117 passed', - conflictsCount: 0, - supportPercent: 100, - shouldFormatted: '26 of 26 passed', - testsFailedCount: 0, - testsPassedCount: 18, - mayAssertionsCount: 0, - mustAssertionsCount: 117, - assertionsFailedCount: 0, - assertionsPassedCount: 143, - shouldAssertionsCount: 26, - unexpectedBehaviorCount: 0, - mayAssertionsFailedCount: 0, - mayAssertionsPassedCount: 0, - mustAssertionsFailedCount: 0, - mustAssertionsPassedCount: 117, - shouldAssertionsFailedCount: 0, - shouldAssertionsPassedCount: 26, - unexpectedBehaviorsFormatted: false, - severeImpactFailedAssertionCount: 0, - severeImpactPassedAssertionCount: 26, - moderateImpactFailedAssertionCount: 0, - moderateImpactPassedAssertionCount: 26 - }, - isFinal: false, - markedFinalAt: null, - issues: [], - draftTestPlanRuns: [ - { - tester: { - username: 'esmeralda-baggins' - }, - testPlanReport: { - id: '9' - }, - testResults: [ - { - test: { - id: 'ZjE0NeyIyIjoiMjQifQmI0NT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:48.229Z' - }, - { - test: { - id: 'YjZlNeyIyIjoiMjQifQTc5ZW' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:48.339Z' - }, - { - test: { - id: 'NWM0MeyIyIjoiMjQifQzhiYz' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:48.439Z' - }, - { - test: { - id: 'YzM0ZeyIyIjoiMjQifQmRmMz' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:48.537Z' - }, - { - test: { - id: 'ZjVjMeyIyIjoiMjQifQDRhY2' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:48.636Z' - }, - { - test: { - id: 'YmUzMeyIyIjoiMjQifQmRmNm' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:48.736Z' - }, - { - test: { - id: 'ZGJmMeyIyIjoiMjQifQzU5Yz' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:48.824Z' - }, - { - test: { - id: 'Nzg1OeyIyIjoiMjQifQTYxM2' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:48.901Z' - }, - { - test: { - id: 'ZTI0MeyIyIjoiMjQifQzM4YT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:48.978Z' - }, - { - test: { - id: 'MzY5ZeyIyIjoiMjQifQmQ0OT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:49.070Z' - }, - { - test: { - id: 'ZjVmYeyIyIjoiMjQifQjJjYW' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:49.161Z' - }, - { - test: { - id: 'ZTMwNeyIyIjoiMjQifQzI5Nz' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:49.264Z' - }, - { - test: { - id: 'NGY0MeyIyIjoiMjQifQ2FjMj' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:49.387Z' - }, - { - test: { - id: 'OTI0OeyIyIjoiMjQifQTU1ZT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:49.527Z' - }, - { - test: { - id: 'MDRhMeyIyIjoiMjQifQWEzMj' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:49.646Z' - }, - { - test: { - id: 'ZThlZeyIyIjoiMjQifQjQ2Nz' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:49.747Z' - }, - { - test: { - id: 'NjhjMeyIyIjoiMjQifQGE0ND' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:49.847Z' - }, - { - test: { - id: 'YTAzZeyIyIjoiMjQifQTc5ZD' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:49.951Z' - } - ] - } - ] - } - }, - { - isRequired: true, - at: { - id: '3', - key: 'voiceover_macos', - name: 'VoiceOver for macOS' - }, - browser: { - id: '3', - key: 'safari_macos', - name: 'Safari' - }, - minimumAtVersion: { - id: '3', - name: '11.6 (20G165)' - }, - exactAtVersion: null, - testPlanReport: { - id: '5', - metrics: { - testsCount: 11, - mayFormatted: false, - supportLevel: 'FAILING', - commandsCount: 20, - mustFormatted: '88 of 90 passed', - conflictsCount: 0, - supportPercent: 98, - shouldFormatted: '19 of 20 passed', - testsFailedCount: 2, - testsPassedCount: 9, - mayAssertionsCount: 0, - mustAssertionsCount: 90, - assertionsFailedCount: 3, - assertionsPassedCount: 107, - shouldAssertionsCount: 20, - unexpectedBehaviorCount: 1, - mayAssertionsFailedCount: 0, - mayAssertionsPassedCount: 0, - mustAssertionsFailedCount: 2, - mustAssertionsPassedCount: 88, - shouldAssertionsFailedCount: 1, - shouldAssertionsPassedCount: 19, - unexpectedBehaviorsFormatted: '1 found', - severeImpactFailedAssertionCount: 0, - severeImpactPassedAssertionCount: 20, - moderateImpactFailedAssertionCount: 1, - moderateImpactPassedAssertionCount: 19 - }, - isFinal: true, - markedFinalAt: '2022-07-06T00:00:00.000Z', - issues: [], - draftTestPlanRuns: [ - { - tester: { - username: 'esmeralda-baggins' - }, - testPlanReport: { - id: '5' - }, - testResults: [ - { - test: { - id: 'NjM0MeyIyIjoiMjQifQTdiZG' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2024-04-25T16:44:44.402Z' - }, - { - test: { - id: 'YWYzOeyIyIjoiMjQifQDBjN2' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2024-04-25T16:44:44.489Z' - }, - { - test: { - id: 'ZmJjYeyIyIjoiMjQifQWJiNT' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2024-04-25T16:44:44.594Z' - }, - { - test: { - id: 'MjU2NeyIyIjoiMjQifQTk2YW' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2024-04-25T16:44:44.694Z' - }, - { - test: { - id: 'MWNlNeyIyIjoiMjQifQTRhNT' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2024-04-25T16:44:44.781Z' - }, - { - test: { - id: 'YzFlYeyIyIjoiMjQifQjE5Yj' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2024-04-25T16:44:44.877Z' - }, - { - test: { - id: 'N2UwMeyIyIjoiMjQifQTQ1OT' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2024-04-25T16:44:44.966Z' - }, - { - test: { - id: 'OTYwOeyIyIjoiMjQifQTE3ND' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2024-04-25T16:44:45.061Z' - }, - { - test: { - id: 'OWI2MeyIyIjoiMjQifQmE0ZD' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2024-04-25T16:44:45.165Z' - }, - { - test: { - id: 'YTU0MeyIyIjoiMjQifQjNhNj' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2024-04-25T16:44:45.296Z' - }, - { - test: { - id: 'NTM4MeyIyIjoiMjQifQGVlNm' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2024-04-25T16:44:45.433Z' - } - ] - } - ] - } - }, - { - isRequired: false, - at: { - id: '3', - key: 'voiceover_macos', - name: 'VoiceOver for macOS' - }, - browser: { - id: '2', - key: 'chrome', - name: 'Chrome' - }, - minimumAtVersion: { - id: '3', - name: '11.6 (20G165)' - }, - exactAtVersion: null, - testPlanReport: { - id: '11', - metrics: { - testsCount: 11, - mayFormatted: false, - supportLevel: 'FULL', - commandsCount: 20, - mustFormatted: '90 of 90 passed', - conflictsCount: 0, - supportPercent: 100, - shouldFormatted: '20 of 20 passed', - testsFailedCount: 0, - testsPassedCount: 11, - mayAssertionsCount: 0, - mustAssertionsCount: 90, - assertionsFailedCount: 0, - assertionsPassedCount: 110, - shouldAssertionsCount: 20, - unexpectedBehaviorCount: 0, - mayAssertionsFailedCount: 0, - mayAssertionsPassedCount: 0, - mustAssertionsFailedCount: 0, - mustAssertionsPassedCount: 90, - shouldAssertionsFailedCount: 0, - shouldAssertionsPassedCount: 20, - unexpectedBehaviorsFormatted: false, - severeImpactFailedAssertionCount: 0, - severeImpactPassedAssertionCount: 20, - moderateImpactFailedAssertionCount: 0, - moderateImpactPassedAssertionCount: 20 - }, - isFinal: false, - markedFinalAt: null, - issues: [], - draftTestPlanRuns: [ - { - tester: { - username: 'esmeralda-baggins' - }, - testPlanReport: { - id: '11' - }, - testResults: [ - { - test: { - id: 'NjM0MeyIyIjoiMjQifQTdiZG' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:51.034Z' - }, - { - test: { - id: 'YWYzOeyIyIjoiMjQifQDBjN2' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:51.120Z' - }, - { - test: { - id: 'ZmJjYeyIyIjoiMjQifQWJiNT' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:51.205Z' - }, - { - test: { - id: 'MjU2NeyIyIjoiMjQifQTk2YW' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:51.313Z' - }, - { - test: { - id: 'MWNlNeyIyIjoiMjQifQTRhNT' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:51.410Z' - }, - { - test: { - id: 'YzFlYeyIyIjoiMjQifQjE5Yj' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:51.489Z' - }, - { - test: { - id: 'N2UwMeyIyIjoiMjQifQTQ1OT' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:51.561Z' - }, - { - test: { - id: 'OTYwOeyIyIjoiMjQifQTE3ND' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:51.648Z' - }, - { - test: { - id: 'OWI2MeyIyIjoiMjQifQmE0ZD' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:51.736Z' - }, - { - test: { - id: 'YTU0MeyIyIjoiMjQifQjNhNj' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:51.832Z' - }, - { - test: { - id: 'NTM4MeyIyIjoiMjQifQGVlNm' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:51.954Z' - } - ] - } - ] - } - }, - { - isRequired: false, - at: { - id: '3', - key: 'voiceover_macos', - name: 'VoiceOver for macOS' - }, - browser: { - id: '1', - key: 'firefox', - name: 'Firefox' - }, - minimumAtVersion: { - id: '3', - name: '11.6 (20G165)' - }, - exactAtVersion: null, - testPlanReport: { - id: '10', - metrics: { - testsCount: 11, - mayFormatted: false, - supportLevel: 'FULL', - commandsCount: 20, - mustFormatted: '90 of 90 passed', - conflictsCount: 0, - supportPercent: 100, - shouldFormatted: '20 of 20 passed', - testsFailedCount: 0, - testsPassedCount: 11, - mayAssertionsCount: 0, - mustAssertionsCount: 90, - assertionsFailedCount: 0, - assertionsPassedCount: 110, - shouldAssertionsCount: 20, - unexpectedBehaviorCount: 0, - mayAssertionsFailedCount: 0, - mayAssertionsPassedCount: 0, - mustAssertionsFailedCount: 0, - mustAssertionsPassedCount: 90, - shouldAssertionsFailedCount: 0, - shouldAssertionsPassedCount: 20, - unexpectedBehaviorsFormatted: false, - severeImpactFailedAssertionCount: 0, - severeImpactPassedAssertionCount: 20, - moderateImpactFailedAssertionCount: 0, - moderateImpactPassedAssertionCount: 20 - }, - isFinal: false, - markedFinalAt: null, - issues: [], - draftTestPlanRuns: [ - { - tester: { - username: 'esmeralda-baggins' - }, - testPlanReport: { - id: '10' - }, - testResults: [ - { - test: { - id: 'NjM0MeyIyIjoiMjQifQTdiZG' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:50.048Z' - }, - { - test: { - id: 'YWYzOeyIyIjoiMjQifQDBjN2' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:50.128Z' - }, - { - test: { - id: 'ZmJjYeyIyIjoiMjQifQWJiNT' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:50.203Z' - }, - { - test: { - id: 'MjU2NeyIyIjoiMjQifQTk2YW' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:50.278Z' - }, - { - test: { - id: 'MWNlNeyIyIjoiMjQifQTRhNT' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:50.355Z' - }, - { - test: { - id: 'YzFlYeyIyIjoiMjQifQjE5Yj' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:50.426Z' - }, - { - test: { - id: 'N2UwMeyIyIjoiMjQifQTQ1OT' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:50.508Z' - }, - { - test: { - id: 'OTYwOeyIyIjoiMjQifQTE3ND' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:50.591Z' - }, - { - test: { - id: 'OWI2MeyIyIjoiMjQifQmE0ZD' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:50.687Z' - }, - { - test: { - id: 'YTU0MeyIyIjoiMjQifQjNhNj' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:50.788Z' - }, - { - test: { - id: 'NTM4MeyIyIjoiMjQifQGVlNm' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: - '2024-04-25T16:44:50.914Z' - } - ] - } - ] - } - } + testResults: [ + { + test: { + id: 'MTExZeyIyIjoiMzEifQWZhZG' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:55.237Z' + }, + { + test: { + id: 'MzJkZeyIyIjoiMzEifQTAzMm' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:55.347Z' + }, + { + test: { + id: 'NDBjMeyIyIjoiMzEifQjc1NT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:55.423Z' + }, + { + test: { + id: 'MjE2MeyIyIjoiMzEifQ2M0NW' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:55.518Z' + }, + { + test: { + id: 'MmZmNeyIyIjoiMzEifQ2IxOG' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:55.613Z' + }, + { + test: { + id: 'MWZiZeyIyIjoiMzEifQjhhYz' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:55.721Z' + }, + { + test: { + id: 'NmI4NeyIyIjoiMzEifQDU2OD' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:55.838Z' + }, + { + test: { + id: 'YmExNeyIyIjoiMzEifQWE5Nj' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:55.965Z' + }, + { + test: { + id: 'YzA3NeyIyIjoiMzEifQGZhYT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:56.079Z' + }, + { + test: { + id: 'YmYxOeyIyIjoiMzEifQDAxY2' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:56.177Z' + }, + { + test: { + id: 'YzIwOeyIyIjoiMzEifQGE2Yz' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:56.286Z' + }, + { + test: { + id: 'YWMwNeyIyIjoiMzEifQDQ5MG' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:56.389Z' + }, + { + test: { + id: 'MjQyMeyIyIjoiMzEifQWExMm' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:56.509Z' + }, + { + test: { + id: 'NDFiYeyIyIjoiMzEifQzg4MD' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:56.639Z' + }, + { + test: { + id: 'M2RmNeyIyIjoiMzEifQzQ0MG' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:56.783Z' + }, + { + test: { + id: 'ODhlYeyIyIjoiMzEifQmVmMT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: '2024-04-25T16:44:56.935Z' + } ] - } - } - } - }, - { - request: { - query: testPlanReportStatusDialogQuery, - variables: { testPlanVersionId: '34' } - }, - result: { - data: { - testPlanVersion: { - id: '31', - title: 'Toggle Button', - phase: 'DRAFT', - gitSha: '022340081280b8cafb8ae0716a5b67e9ab942ef4', - gitMessage: - 'Delete duplicated assertion for operating a not pressed togle button (VoiceOver) (#716)', - updatedAt: '2022-05-18T20:51:40.000Z', - draftPhaseReachedAt: '2022-07-06T00:00:00.000Z', - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'toggle-button' + } + ] + } + }, + { + isRequired: false, + at: { + id: '2', + key: 'nvda', + name: 'NVDA', + atVersions: [ + { + id: '2', + name: '2020.4', + supportedByAutomation: false, + releasedAt: '2020-04-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '1', + key: 'firefox', + name: 'Firefox' + }, + minimumAtVersion: { + id: '2', + name: '2020.4', + supportedByAutomation: false, + releasedAt: '2020-04-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: null + }, + { + isRequired: true, + at: { + id: '3', + key: 'voiceover_macos', + name: 'VoiceOver for macOS', + atVersions: [ + { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '3', + key: 'safari_macos', + name: 'Safari' + }, + minimumAtVersion: { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: { + id: '15', + metrics: { + testsCount: 8, + mayFormatted: false, + supportLevel: 'FULL', + commandsCount: 22, + mustFormatted: '76 of 76 passed', + conflictsCount: 0, + supportPercent: 100, + shouldFormatted: '22 of 22 passed', + testsFailedCount: 0, + testsPassedCount: 8, + mayAssertionsCount: 0, + mustAssertionsCount: 76, + assertionsFailedCount: 0, + assertionsPassedCount: 98, + shouldAssertionsCount: 22, + unexpectedBehaviorCount: 0, + mayAssertionsFailedCount: 0, + mayAssertionsPassedCount: 0, + mustAssertionsFailedCount: 0, + mustAssertionsPassedCount: 76, + shouldAssertionsFailedCount: 0, + shouldAssertionsPassedCount: 22, + unexpectedBehaviorsFormatted: false, + severeImpactFailedAssertionCount: 0, + severeImpactPassedAssertionCount: 22, + moderateImpactFailedAssertionCount: 0, + moderateImpactPassedAssertionCount: 22 + }, + isFinal: true, + markedFinalAt: '2022-07-07T00:00:00.000Z', + issues: [], + draftTestPlanRuns: [ + { + tester: { + username: 'esmeralda-baggins' }, - testPlanReportStatuses: [ - { - isRequired: true, - at: { - id: '1', - key: 'jaws', - name: 'JAWS' - }, - browser: { - id: '2', - key: 'chrome', - name: 'Chrome' - }, - minimumAtVersion: { - id: '1', - name: '2021.2111.13' - }, - exactAtVersion: null, - testPlanReport: { - id: '1', - metrics: { - testsCount: 16, - mayFormatted: false, - supportLevel: 'FAILING', - commandsCount: 26, - mustFormatted: '86 of 88 passed', - conflictsCount: 0, - supportPercent: 98, - shouldFormatted: '25 of 26 passed', - testsFailedCount: 6, - testsPassedCount: 10, - mayAssertionsCount: 0, - mustAssertionsCount: 88, - assertionsFailedCount: 3, - assertionsPassedCount: 111, - shouldAssertionsCount: 26, - unexpectedBehaviorCount: 1, - mayAssertionsFailedCount: 0, - mayAssertionsPassedCount: 0, - mustAssertionsFailedCount: 2, - mustAssertionsPassedCount: 86, - shouldAssertionsFailedCount: 1, - shouldAssertionsPassedCount: 25, - unexpectedBehaviorsFormatted: '1 found', - severeImpactFailedAssertionCount: 0, - severeImpactPassedAssertionCount: 26, - moderateImpactFailedAssertionCount: 1, - moderateImpactPassedAssertionCount: 25 - }, - isFinal: false, - markedFinalAt: null, - issues: [], - draftTestPlanRuns: [ - { - tester: { - username: 'esmeralda-baggins' - }, - testPlanReport: { - id: '1' - }, - testResults: [ - { - test: { - id: 'MTExZeyIyIjoiMzEifQWZhZG' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:35.281Z' - }, - { - test: { - id: 'MzJkZeyIyIjoiMzEifQTAzMm' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: null - }, - { - test: { - id: 'NDBjMeyIyIjoiMzEifQjc1NT' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: null - }, - { - test: { - id: 'MjE2MeyIyIjoiMzEifQ2M0NW' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: null - }, - { - test: { - id: 'MWZiZeyIyIjoiMzEifQjhhYz' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:35.541Z' - }, - { - test: { - id: 'NmI4NeyIyIjoiMzEifQDU2OD' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:35.636Z' - }, - { - test: { - id: 'YmExNeyIyIjoiMzEifQWE5Nj' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:35.730Z' - }, - { - test: { - id: 'YzA3NeyIyIjoiMzEifQGZhYT' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:35.831Z' - }, - { - test: { - id: 'YmYxOeyIyIjoiMzEifQDAxY2' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:35.928Z' - }, - { - test: { - id: 'YzIwOeyIyIjoiMzEifQGE2Yz' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:36.034Z' - }, - { - test: { - id: 'YWMwNeyIyIjoiMzEifQDQ5MG' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:36.126Z' - }, - { - test: { - id: 'MjQyMeyIyIjoiMzEifQWExMm' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:36.210Z' - }, - { - test: { - id: 'NDFiYeyIyIjoiMzEifQzg4MD' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:36.332Z' - }, - { - test: { - id: 'M2RmNeyIyIjoiMzEifQzQ0MG' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:36.409Z' - }, - { - test: { - id: 'ODhlYeyIyIjoiMzEifQmVmMT' - }, - atVersion: { - id: '1', - name: '2021.2111.13' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:36.510Z' - } - ] - } - ] - } - }, - { - isRequired: false, - at: { - id: '1', - key: 'jaws', - name: 'JAWS' - }, - browser: { - id: '1', - key: 'firefox', - name: 'Firefox' - }, - minimumAtVersion: { - id: '1', - name: '2021.2111.13' - }, - exactAtVersion: null, - testPlanReport: null - }, - { - isRequired: true, - at: { - id: '2', - key: 'nvda', - name: 'NVDA' - }, - browser: { - id: '2', - key: 'chrome', - name: 'Chrome' - }, - minimumAtVersion: { - id: '2', - name: '2020.4' - }, - exactAtVersion: null, - testPlanReport: { - id: '14', - metrics: { - testsCount: 16, - mayFormatted: false, - supportLevel: 'FULL', - commandsCount: 36, - mustFormatted: '128 of 128 passed', - conflictsCount: 0, - supportPercent: 100, - shouldFormatted: '36 of 36 passed', - testsFailedCount: 0, - testsPassedCount: 16, - mayAssertionsCount: 0, - mustAssertionsCount: 128, - assertionsFailedCount: 0, - assertionsPassedCount: 164, - shouldAssertionsCount: 36, - unexpectedBehaviorCount: 0, - mayAssertionsFailedCount: 0, - mayAssertionsPassedCount: 0, - mustAssertionsFailedCount: 0, - mustAssertionsPassedCount: 128, - shouldAssertionsFailedCount: 0, - shouldAssertionsPassedCount: 36, - unexpectedBehaviorsFormatted: false, - severeImpactFailedAssertionCount: 0, - severeImpactPassedAssertionCount: 36, - moderateImpactFailedAssertionCount: 0, - moderateImpactPassedAssertionCount: 36 - }, - isFinal: true, - markedFinalAt: '2022-07-07T00:00:00.000Z', - issues: [], - draftTestPlanRuns: [ - { - tester: { - username: 'esmeralda-baggins' - }, - testPlanReport: { - id: '14' - }, - testResults: [ - { - test: { - id: 'MTExZeyIyIjoiMzEifQWZhZG' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:55.237Z' - }, - { - test: { - id: 'MzJkZeyIyIjoiMzEifQTAzMm' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:55.347Z' - }, - { - test: { - id: 'NDBjMeyIyIjoiMzEifQjc1NT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:55.423Z' - }, - { - test: { - id: 'MjE2MeyIyIjoiMzEifQ2M0NW' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:55.518Z' - }, - { - test: { - id: 'MmZmNeyIyIjoiMzEifQ2IxOG' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:55.613Z' - }, - { - test: { - id: 'MWZiZeyIyIjoiMzEifQjhhYz' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:55.721Z' - }, - { - test: { - id: 'NmI4NeyIyIjoiMzEifQDU2OD' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:55.838Z' - }, - { - test: { - id: 'YmExNeyIyIjoiMzEifQWE5Nj' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:55.965Z' - }, - { - test: { - id: 'YzA3NeyIyIjoiMzEifQGZhYT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:56.079Z' - }, - { - test: { - id: 'YmYxOeyIyIjoiMzEifQDAxY2' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:56.177Z' - }, - { - test: { - id: 'YzIwOeyIyIjoiMzEifQGE2Yz' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:56.286Z' - }, - { - test: { - id: 'YWMwNeyIyIjoiMzEifQDQ5MG' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:56.389Z' - }, - { - test: { - id: 'MjQyMeyIyIjoiMzEifQWExMm' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:56.509Z' - }, - { - test: { - id: 'NDFiYeyIyIjoiMzEifQzg4MD' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:56.639Z' - }, - { - test: { - id: 'M2RmNeyIyIjoiMzEifQzQ0MG' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:56.783Z' - }, - { - test: { - id: 'ODhlYeyIyIjoiMzEifQmVmMT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '2', - name: '99.0.4844.84' - }, - completedAt: - '2024-04-25T16:44:56.935Z' - } - ] - } - ] - } - }, - { - isRequired: false, - at: { - id: '2', - key: 'nvda', - name: 'NVDA' - }, - browser: { - id: '1', - key: 'firefox', - name: 'Firefox' - }, - minimumAtVersion: { - id: '2', - name: '2020.4' - }, - exactAtVersion: null, - testPlanReport: null - }, - { - isRequired: true, - at: { - id: '3', - key: 'voiceover_macos', - name: 'VoiceOver for macOS' - }, - browser: { - id: '3', - key: 'safari_macos', - name: 'Safari' - }, - minimumAtVersion: { - id: '3', - name: '11.6 (20G165)' - }, - exactAtVersion: null, - testPlanReport: { - id: '15', - metrics: { - testsCount: 8, - mayFormatted: false, - supportLevel: 'FULL', - commandsCount: 22, - mustFormatted: '76 of 76 passed', - conflictsCount: 0, - supportPercent: 100, - shouldFormatted: '22 of 22 passed', - testsFailedCount: 0, - testsPassedCount: 8, - mayAssertionsCount: 0, - mustAssertionsCount: 76, - assertionsFailedCount: 0, - assertionsPassedCount: 98, - shouldAssertionsCount: 22, - unexpectedBehaviorCount: 0, - mayAssertionsFailedCount: 0, - mayAssertionsPassedCount: 0, - mustAssertionsFailedCount: 0, - mustAssertionsPassedCount: 76, - shouldAssertionsFailedCount: 0, - shouldAssertionsPassedCount: 22, - unexpectedBehaviorsFormatted: false, - severeImpactFailedAssertionCount: 0, - severeImpactPassedAssertionCount: 22, - moderateImpactFailedAssertionCount: 0, - moderateImpactPassedAssertionCount: 22 - }, - isFinal: true, - markedFinalAt: '2022-07-07T00:00:00.000Z', - issues: [], - draftTestPlanRuns: [ - { - tester: { - username: 'esmeralda-baggins' - }, - testPlanReport: { - id: '15' - }, - testResults: [ - { - test: { - id: 'NWUyZeyIyIjoiMzEifQDVkND' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2024-04-25T16:44:57.086Z' - }, - { - test: { - id: 'N2I0YeyIyIjoiMzEifQjEwYj' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2024-04-25T16:44:57.206Z' - }, - { - test: { - id: 'NmZjOeyIyIjoiMzEifQGY5ZT' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2024-04-25T16:44:57.334Z' - }, - { - test: { - id: 'YmU1MeyIyIjoiMzEifQzBiYj' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2024-04-25T16:44:57.464Z' - }, - { - test: { - id: 'MGQyYeyIyIjoiMzEifQzcxZm' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2024-04-25T16:44:57.599Z' - }, - { - test: { - id: 'ZmI0YeyIyIjoiMzEifQTU2Nz' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2024-04-25T16:44:57.734Z' - }, - { - test: { - id: 'YmRjZeyIyIjoiMzEifQGQyND' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2024-04-25T16:44:57.880Z' - }, - { - test: { - id: 'YWFmMeyIyIjoiMzEifQzMwMT' - }, - atVersion: { - id: '3', - name: '11.6 (20G165)' - }, - browserVersion: { - id: '3', - name: '14.1.2' - }, - completedAt: - '2024-04-25T16:44:58.035Z' - } - ] - } - ] - } - }, - { - isRequired: false, - at: { - id: '3', - key: 'voiceover_macos', - name: 'VoiceOver for macOS' - }, - browser: { - id: '2', - key: 'chrome', - name: 'Chrome' - }, - minimumAtVersion: { - id: '3', - name: '11.6 (20G165)' - }, - exactAtVersion: null, - testPlanReport: null - }, - { - isRequired: false, - at: { - id: '3', - key: 'voiceover_macos', - name: 'VoiceOver for macOS' - }, - browser: { - id: '1', - key: 'firefox', - name: 'Firefox' - }, - minimumAtVersion: { - id: '3', - name: '11.6 (20G165)' - }, - exactAtVersion: null, - testPlanReport: null - } + testPlanReport: { + id: '15' + }, + testResults: [ + { + test: { + id: 'NWUyZeyIyIjoiMzEifQDVkND' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2024-04-25T16:44:57.086Z' + }, + { + test: { + id: 'N2I0YeyIyIjoiMzEifQjEwYj' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2024-04-25T16:44:57.206Z' + }, + { + test: { + id: 'NmZjOeyIyIjoiMzEifQGY5ZT' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2024-04-25T16:44:57.334Z' + }, + { + test: { + id: 'YmU1MeyIyIjoiMzEifQzBiYj' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2024-04-25T16:44:57.464Z' + }, + { + test: { + id: 'MGQyYeyIyIjoiMzEifQzcxZm' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2024-04-25T16:44:57.599Z' + }, + { + test: { + id: 'ZmI0YeyIyIjoiMzEifQTU2Nz' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2024-04-25T16:44:57.734Z' + }, + { + test: { + id: 'YmRjZeyIyIjoiMzEifQGQyND' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2024-04-25T16:44:57.880Z' + }, + { + test: { + id: 'YWFmMeyIyIjoiMzEifQzMwMT' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: '2024-04-25T16:44:58.035Z' + } ] - } + } + ] + } + }, + { + isRequired: false, + at: { + id: '3', + key: 'voiceover_macos', + name: 'VoiceOver for macOS', + atVersions: [ + { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '2', + key: 'chrome', + name: 'Chrome' + }, + minimumAtVersion: { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: null + }, + { + isRequired: false, + at: { + id: '3', + key: 'voiceover_macos', + name: 'VoiceOver for macOS', + atVersions: [ + { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '1', + key: 'firefox', + name: 'Firefox' + }, + minimumAtVersion: { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: null } + ] } + } } + } ]; diff --git a/client/tests/__mocks__/GraphQLMocks/TestPlanReportStatusDialogMock.js b/client/tests/__mocks__/GraphQLMocks/TestPlanReportStatusDialogMock.js index 32a9a824b..84015446a 100644 --- a/client/tests/__mocks__/GraphQLMocks/TestPlanReportStatusDialogMock.js +++ b/client/tests/__mocks__/GraphQLMocks/TestPlanReportStatusDialogMock.js @@ -1,818 +1,888 @@ export const mockedTestPlanVersion = { - id: '7', - title: 'Select Only Combobox Example', - phase: 'DRAFT', - gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', - gitMessage: 'Generate html source script to support aria-at-app (#646)', - updatedAt: '2022-03-17T18:34:51.000Z', - draftPhaseReachedAt: '2022-07-06T00:00:00.000Z', - candidatePhaseReachedAt: null, - recommendedPhaseTargetDate: null, - recommendedPhaseReachedAt: null, - testPlan: { - directory: 'combobox-select-only' + id: '7', + title: 'Select Only Combobox Example', + phase: 'DRAFT', + gitSha: '7c4b5dce23c74fcf280ed164bdb903e02e0e7726', + gitMessage: 'Generate html source script to support aria-at-app (#646)', + updatedAt: '2022-03-17T18:34:51.000Z', + draftPhaseReachedAt: '2022-07-06T00:00:00.000Z', + candidatePhaseReachedAt: null, + recommendedPhaseTargetDate: null, + recommendedPhaseReachedAt: null, + testPlan: { + directory: 'combobox-select-only' + }, + testPlanReportStatuses: [ + { + isRequired: true, + at: { + id: '1', + key: 'jaws', + name: 'JAWS', + atVersions: [ + { + id: '1', + name: '2021.2111.13', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '2', + key: 'chrome', + name: 'Chrome' + }, + minimumAtVersion: { + id: '1', + name: '2021.2111.13', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: null }, - testPlanReportStatuses: [ - { - isRequired: true, - at: { - id: '1', - key: 'jaws', - name: 'JAWS' - }, - browser: { - id: '2', - key: 'chrome', - name: 'Chrome' - }, - minimumAtVersion: { - id: '1', - name: '2021.2111.13' - }, - exactAtVersion: null, - testPlanReport: null - }, - { - isRequired: false, - at: { - id: '1', - key: 'jaws', - name: 'JAWS' - }, - browser: { - id: '1', - key: 'firefox', - name: 'Firefox' - }, - minimumAtVersion: { - id: '1', - name: '2021.2111.13' - }, - exactAtVersion: null, - testPlanReport: null - }, - { - isRequired: true, - at: { - id: '2', - key: 'nvda', - name: 'NVDA' - }, - browser: { - id: '2', - key: 'chrome', - name: 'Chrome' - }, - minimumAtVersion: { - id: '2', - name: '2020.4' - }, - exactAtVersion: null, - testPlanReport: null + { + isRequired: false, + at: { + id: '1', + key: 'jaws', + name: 'JAWS', + atVersions: [ + { + id: '1', + name: '2021.2111.13', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '1', + key: 'firefox', + name: 'Firefox' + }, + minimumAtVersion: { + id: '1', + name: '2021.2111.13', + supportedByAutomation: false, + releasedAt: '2021-11-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: null + }, + { + isRequired: true, + at: { + id: '2', + key: 'nvda', + name: 'NVDA', + atVersions: [ + { + id: '2', + name: '2020.4', + supportedByAutomation: false, + releasedAt: '2020-04-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '2', + key: 'chrome', + name: 'Chrome' + }, + minimumAtVersion: { + id: '2', + name: '2020.4', + supportedByAutomation: false, + releasedAt: '2020-04-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: null + }, + { + isRequired: false, + at: { + id: '2', + key: 'nvda', + name: 'NVDA', + atVersions: [ + { + id: '2', + name: '2020.4', + supportedByAutomation: false, + releasedAt: '2020-04-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '1', + key: 'firefox', + name: 'Firefox' + }, + minimumAtVersion: { + id: '2', + name: '2020.4', + supportedByAutomation: false, + releasedAt: '2020-04-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: { + id: '2', + metrics: { + testsCount: 21, + mayFormatted: false, + supportLevel: 'FAILING', + commandsCount: 24, + mustFormatted: '118 of 122 passed', + conflictsCount: 3, + supportPercent: 97, + shouldFormatted: '34 of 36 passed', + testsFailedCount: 6, + testsPassedCount: 15, + mayAssertionsCount: 0, + mustAssertionsCount: 122, + assertionsFailedCount: 6, + assertionsPassedCount: 152, + shouldAssertionsCount: 36, + unexpectedBehaviorCount: 3, + mayAssertionsFailedCount: 0, + mayAssertionsPassedCount: 0, + mustAssertionsFailedCount: 4, + mustAssertionsPassedCount: 118, + shouldAssertionsFailedCount: 2, + shouldAssertionsPassedCount: 34, + unexpectedBehaviorsFormatted: '3 found', + severeImpactFailedAssertionCount: 1, + severeImpactPassedAssertionCount: 23, + moderateImpactFailedAssertionCount: 2, + moderateImpactPassedAssertionCount: 22 }, - { - isRequired: false, - at: { - id: '2', - key: 'nvda', - name: 'NVDA' - }, - browser: { - id: '1', - key: 'firefox', - name: 'Firefox' + isFinal: false, + markedFinalAt: null, + issues: [ + { + link: 'https://github.com/bocoup/aria-at/issues/128#issue-2157878584', + isOpen: true, + feedbackType: 'FEEDBACK' + } + ], + draftTestPlanRuns: [ + { + tester: { + username: 'tom-proudfeet' }, - minimumAtVersion: { - id: '2', - name: '2020.4' - }, - exactAtVersion: null, testPlanReport: { - id: '2', - metrics: { - testsCount: 21, - mayFormatted: false, - supportLevel: 'FAILING', - commandsCount: 24, - mustFormatted: '118 of 122 passed', - conflictsCount: 3, - supportPercent: 97, - shouldFormatted: '34 of 36 passed', - testsFailedCount: 6, - testsPassedCount: 15, - mayAssertionsCount: 0, - mustAssertionsCount: 122, - assertionsFailedCount: 6, - assertionsPassedCount: 152, - shouldAssertionsCount: 36, - unexpectedBehaviorCount: 3, - mayAssertionsFailedCount: 0, - mayAssertionsPassedCount: 0, - mustAssertionsFailedCount: 4, - mustAssertionsPassedCount: 118, - shouldAssertionsFailedCount: 2, - shouldAssertionsPassedCount: 34, - unexpectedBehaviorsFormatted: '3 found', - severeImpactFailedAssertionCount: 1, - severeImpactPassedAssertionCount: 23, - moderateImpactFailedAssertionCount: 2, - moderateImpactPassedAssertionCount: 22 - }, - isFinal: false, - markedFinalAt: null, - issues: [ - { - link: 'https://github.com/bocoup/aria-at/issues/128#issue-2157878584', - isOpen: true, - feedbackType: 'FEEDBACK' - } - ], - draftTestPlanRuns: [ - { - tester: { - username: 'tom-proudfeet' - }, - testPlanReport: { - id: '2' - }, - testResults: [ - { - test: { - id: 'Nzg5NeyIyIjoiNyJ9zNjZj' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:38.949Z' - }, - { - test: { - id: 'MmY0YeyIyIjoiNyJ9jRkZD' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:39.070Z' - }, - { - test: { - id: 'ZjUwNeyIyIjoiNyJ9mE2ZT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:39.180Z' - }, - { - test: { - id: 'MDNiMeyIyIjoiNyJ9Dk1MT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:39.292Z' - }, - { - test: { - id: 'MjRmNeyIyIjoiNyJ92MyMT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:39.406Z' - }, - { - test: { - id: 'ZmVlMeyIyIjoiNyJ9mUyYj' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: null - }, - { - test: { - id: 'YWFiNeyIyIjoiNyJ9zE2Zj' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:39.640Z' - }, - { - test: { - id: 'YjZkYeyIyIjoiNyJ9WIxZm' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:39.761Z' - }, - { - test: { - id: 'ZmIzMeyIyIjoiNyJ9TQ1NW' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:39.888Z' - }, - { - test: { - id: 'MmZkNeyIyIjoiNyJ9zIwN2' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:40.006Z' - }, - { - test: { - id: 'ZmQwOeyIyIjoiNyJ9DEzYz' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:40.122Z' - }, - { - test: { - id: 'MGViNeyIyIjoiNyJ9GQ3MT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:40.234Z' - }, - { - test: { - id: 'YTg5MeyIyIjoiNyJ9WEzOT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:40.355Z' - }, - { - test: { - id: 'NTRjMeyIyIjoiNyJ9zQ0OD' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:40.467Z' - }, - { - test: { - id: 'MjRlZeyIyIjoiNyJ9DcyY2' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:40.588Z' - }, - { - test: { - id: 'YWQzNeyIyIjoiNyJ9mE2Nm' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:40.712Z' - }, - { - test: { - id: 'OTYxOeyIyIjoiNyJ9TdmYj' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:40.826Z' - }, - { - test: { - id: 'MjgzNeyIyIjoiNyJ9TZjNz' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:40.948Z' - }, - { - test: { - id: 'NWNiZeyIyIjoiNyJ9jI2MD' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:41.075Z' - } - ] - }, - { - tester: { - username: 'esmeralda-baggins' - }, - testPlanReport: { - id: '2' - }, - testResults: [ - { - test: { - id: 'Nzg5NeyIyIjoiNyJ9zNjZj' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:36.666Z' - }, - { - test: { - id: 'MmY0YeyIyIjoiNyJ9jRkZD' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:36.793Z' - }, - { - test: { - id: 'ZjUwNeyIyIjoiNyJ9mE2ZT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:36.914Z' - }, - { - test: { - id: 'MDNiMeyIyIjoiNyJ9Dk1MT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:37.031Z' - }, - { - test: { - id: 'MjRmNeyIyIjoiNyJ92MyMT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:37.150Z' - }, - { - test: { - id: 'ZmVlMeyIyIjoiNyJ9mUyYj' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: null - }, - { - test: { - id: 'YWFiNeyIyIjoiNyJ9zE2Zj' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:37.384Z' - }, - { - test: { - id: 'YjZkYeyIyIjoiNyJ9WIxZm' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:37.512Z' - }, - { - test: { - id: 'ZmIzMeyIyIjoiNyJ9TQ1NW' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:37.638Z' - }, - { - test: { - id: 'MmZkNeyIyIjoiNyJ9zIwN2' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:37.760Z' - }, - { - test: { - id: 'ZmQwOeyIyIjoiNyJ9DEzYz' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:37.883Z' - }, - { - test: { - id: 'MGViNeyIyIjoiNyJ9GQ3MT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:38.014Z' - }, - { - test: { - id: 'YTg5MeyIyIjoiNyJ9WEzOT' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:38.145Z' - }, - { - test: { - id: 'NTRjMeyIyIjoiNyJ9zQ0OD' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:38.268Z' - }, - { - test: { - id: 'MjRlZeyIyIjoiNyJ9DcyY2' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:38.382Z' - }, - { - test: { - id: 'YWQzNeyIyIjoiNyJ9mE2Nm' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:38.481Z' - }, - { - test: { - id: 'OTYxOeyIyIjoiNyJ9TdmYj' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:38.596Z' - }, - { - test: { - id: 'MjgzNeyIyIjoiNyJ9TZjNz' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:38.701Z' - }, - { - test: { - id: 'NWNiZeyIyIjoiNyJ9jI2MD' - }, - atVersion: { - id: '2', - name: '2020.4' - }, - browserVersion: { - id: '1', - name: '99.0.1' - }, - completedAt: '2024-04-25T16:44:38.811Z' - } - ] - } - ] - } - }, - { - isRequired: true, - at: { - id: '3', - key: 'voiceover_macos', - name: 'VoiceOver for macOS' - }, - browser: { - id: '3', - key: 'safari_macos', - name: 'Safari' - }, - minimumAtVersion: { - id: '3', - name: '11.6 (20G165)' - }, - exactAtVersion: null, - testPlanReport: null - }, - { - isRequired: false, - at: { - id: '3', - key: 'voiceover_macos', - name: 'VoiceOver for macOS' - }, - browser: { - id: '2', - key: 'chrome', - name: 'Chrome' - }, - minimumAtVersion: { - id: '3', - name: '11.6 (20G165)' + id: '2' }, - exactAtVersion: null, - testPlanReport: null - }, - { - isRequired: false, - at: { - id: '3', - key: 'voiceover_macos', - name: 'VoiceOver for macOS' - }, - browser: { - id: '1', - key: 'firefox', - name: 'Firefox' + testResults: [ + { + test: { + id: 'Nzg5NeyIyIjoiNyJ9zNjZj' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:38.949Z' + }, + { + test: { + id: 'MmY0YeyIyIjoiNyJ9jRkZD' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:39.070Z' + }, + { + test: { + id: 'ZjUwNeyIyIjoiNyJ9mE2ZT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:39.180Z' + }, + { + test: { + id: 'MDNiMeyIyIjoiNyJ9Dk1MT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:39.292Z' + }, + { + test: { + id: 'MjRmNeyIyIjoiNyJ92MyMT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:39.406Z' + }, + { + test: { + id: 'ZmVlMeyIyIjoiNyJ9mUyYj' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: null + }, + { + test: { + id: 'YWFiNeyIyIjoiNyJ9zE2Zj' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:39.640Z' + }, + { + test: { + id: 'YjZkYeyIyIjoiNyJ9WIxZm' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:39.761Z' + }, + { + test: { + id: 'ZmIzMeyIyIjoiNyJ9TQ1NW' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:39.888Z' + }, + { + test: { + id: 'MmZkNeyIyIjoiNyJ9zIwN2' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:40.006Z' + }, + { + test: { + id: 'ZmQwOeyIyIjoiNyJ9DEzYz' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:40.122Z' + }, + { + test: { + id: 'MGViNeyIyIjoiNyJ9GQ3MT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:40.234Z' + }, + { + test: { + id: 'YTg5MeyIyIjoiNyJ9WEzOT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:40.355Z' + }, + { + test: { + id: 'NTRjMeyIyIjoiNyJ9zQ0OD' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:40.467Z' + }, + { + test: { + id: 'MjRlZeyIyIjoiNyJ9DcyY2' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:40.588Z' + }, + { + test: { + id: 'YWQzNeyIyIjoiNyJ9mE2Nm' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:40.712Z' + }, + { + test: { + id: 'OTYxOeyIyIjoiNyJ9TdmYj' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:40.826Z' + }, + { + test: { + id: 'MjgzNeyIyIjoiNyJ9TZjNz' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:40.948Z' + }, + { + test: { + id: 'NWNiZeyIyIjoiNyJ9jI2MD' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:41.075Z' + } + ] + }, + { + tester: { + username: 'esmeralda-baggins' }, - minimumAtVersion: { - id: '3', - name: '11.6 (20G165)' + testPlanReport: { + id: '2' }, - exactAtVersion: null, - testPlanReport: null - } - ] + testResults: [ + { + test: { + id: 'Nzg5NeyIyIjoiNyJ9zNjZj' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:36.666Z' + }, + { + test: { + id: 'MmY0YeyIyIjoiNyJ9jRkZD' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:36.793Z' + }, + { + test: { + id: 'ZjUwNeyIyIjoiNyJ9mE2ZT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:36.914Z' + }, + { + test: { + id: 'MDNiMeyIyIjoiNyJ9Dk1MT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:37.031Z' + }, + { + test: { + id: 'MjRmNeyIyIjoiNyJ92MyMT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:37.150Z' + }, + { + test: { + id: 'ZmVlMeyIyIjoiNyJ9mUyYj' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: null + }, + { + test: { + id: 'YWFiNeyIyIjoiNyJ9zE2Zj' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:37.384Z' + }, + { + test: { + id: 'YjZkYeyIyIjoiNyJ9WIxZm' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:37.512Z' + }, + { + test: { + id: 'ZmIzMeyIyIjoiNyJ9TQ1NW' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:37.638Z' + }, + { + test: { + id: 'MmZkNeyIyIjoiNyJ9zIwN2' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:37.760Z' + }, + { + test: { + id: 'ZmQwOeyIyIjoiNyJ9DEzYz' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:37.883Z' + }, + { + test: { + id: 'MGViNeyIyIjoiNyJ9GQ3MT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:38.014Z' + }, + { + test: { + id: 'YTg5MeyIyIjoiNyJ9WEzOT' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:38.145Z' + }, + { + test: { + id: 'NTRjMeyIyIjoiNyJ9zQ0OD' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:38.268Z' + }, + { + test: { + id: 'MjRlZeyIyIjoiNyJ9DcyY2' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:38.382Z' + }, + { + test: { + id: 'YWQzNeyIyIjoiNyJ9mE2Nm' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:38.481Z' + }, + { + test: { + id: 'OTYxOeyIyIjoiNyJ9TdmYj' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:38.596Z' + }, + { + test: { + id: 'MjgzNeyIyIjoiNyJ9TZjNz' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:38.701Z' + }, + { + test: { + id: 'NWNiZeyIyIjoiNyJ9jI2MD' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '1', + name: '99.0.1' + }, + completedAt: '2024-04-25T16:44:38.811Z' + } + ] + } + ] + } + }, + { + isRequired: true, + at: { + id: '3', + key: 'voiceover_macos', + name: 'VoiceOver for macOS', + atVersions: [ + { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2020-04-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '3', + key: 'safari_macos', + name: 'Safari' + }, + minimumAtVersion: { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2020-04-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: null + }, + { + isRequired: false, + at: { + id: '3', + key: 'voiceover_macos', + name: 'VoiceOver for macOS', + atVersions: [ + { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2020-04-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '2', + key: 'chrome', + name: 'Chrome' + }, + minimumAtVersion: { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2020-04-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: null + }, + { + isRequired: false, + at: { + id: '3', + key: 'voiceover_macos', + name: 'VoiceOver for macOS', + atVersions: [ + { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2020-04-01T04:00:00.000Z' + } + ] + }, + browser: { + id: '1', + key: 'firefox', + name: 'Firefox' + }, + minimumAtVersion: { + id: '3', + name: '11.6 (20G165)', + supportedByAutomation: false, + releasedAt: '2020-04-01T04:00:00.000Z' + }, + exactAtVersion: null, + testPlanReport: null + } + ] }; export default ( - meQuery, - testPlanReportStatusDialogQuery, - existingTestPlanReportsQuery + meQuery, + testPlanReportStatusDialogQuery, + existingTestPlanReportsQuery ) => [ - { - request: { - query: meQuery - }, - result: { - data: { - me: { - id: '1', - username: 'foo-bar', - roles: ['ADMIN', 'TESTER'] - } - } - } + { + request: { + query: meQuery }, - { - request: { - query: testPlanReportStatusDialogQuery, - variables: { testPlanVersionId: '7' } - }, - result: { - data: { - testPlanVersion: mockedTestPlanVersion - } + result: { + data: { + me: { + id: '1', + username: 'foo-bar', + roles: ['ADMIN', 'TESTER'] } + } + } + }, + { + request: { + query: testPlanReportStatusDialogQuery, + variables: { testPlanVersionId: '7' } }, - { - request: { - query: existingTestPlanReportsQuery, - variables: { - testPlanVersionId: '7', - directory: 'combobox-select-only' + result: { + data: { + testPlanVersion: mockedTestPlanVersion + } + } + }, + { + request: { + query: existingTestPlanReportsQuery, + variables: { + testPlanVersionId: '7', + directory: 'combobox-select-only' + } + }, + result: { + data: { + existingTestPlanVersion: { + id: '7', + testPlanReports: [ + { + id: '1', + markedFinalAt: '2021-01-01T00:00:00.000Z', + isFinal: true, + draftTestPlanRuns: { + initiatedByAutomation: true + }, + at: { + id: '1', + key: 'jaws', + name: 'JAWS' + }, + browser: { + id: '2', + key: 'firefox', + name: 'Firefox' + } } + ], + metadata: { + exampleUrl: 'https://fakeurl.com/exampleUrl', + designPatternUrl: 'https://fakeurl.com/designPattern', + testFormatVersion: 1 + } }, - result: { - data: { - existingTestPlanVersion: { - id: '7', - testPlanReports: [ - { - id: '1', - markedFinalAt: '2021-01-01T00:00:00.000Z', - isFinal: true, - draftTestPlanRuns: { - initiatedByAutomation: true - }, - at: { - id: '1', - key: 'jaws', - name: 'JAWS' - }, - browser: { - id: '2', - key: 'firefox', - name: 'Firefox' - } - } - ], - metadata: { - exampleUrl: 'https://fakeurl.com/exampleUrl', - designPatternUrl: 'https://fakeurl.com/designPattern', - testFormatVersion: 1 - } - }, - oldTestPlanVersions: [] - } - } + oldTestPlanVersions: [] + } } + } ]; diff --git a/client/tests/__mocks__/GraphQLMocks/TestQueuePageAdminNotPopulatedMock.js b/client/tests/__mocks__/GraphQLMocks/TestQueuePageAdminNotPopulatedMock.js index 98ccbfc20..32c8de0cf 100644 --- a/client/tests/__mocks__/GraphQLMocks/TestQueuePageAdminNotPopulatedMock.js +++ b/client/tests/__mocks__/GraphQLMocks/TestQueuePageAdminNotPopulatedMock.js @@ -1,45 +1,47 @@ export default testQueuePageQuery => [ - { - request: { - query: testQueuePageQuery + { + request: { + query: testQueuePageQuery + }, + result: { + data: { + me: { + __typename: 'User', + id: '1', + username: 'foo-bar', + roles: ['ADMIN', 'TESTER'] }, - result: { - data: { - me: { - id: '1', - username: 'foo-bar', - roles: ['ADMIN', 'TESTER'], - __typename: 'User' - }, - ats: [], - browsers: [], - users: [ - { - id: '1', - username: 'foo-bar', - roles: ['ADMIN', 'TESTER'], - isBot: false, - ats: [] - }, - { - id: '4', - username: 'bar-foo', - roles: ['TESTER'], - isBot: false, - ats: [] - }, - { - id: '5', - username: 'boo-far', - roles: ['TESTER'], - isBot: false, - ats: [] - } - ], - testPlanVersions: [], - testPlanReports: [], - testPlans: [] - } - } + users: [ + { + __typename: 'User', + id: '1', + username: 'foo-bar', + roles: ['ADMIN', 'TESTER'], + isBot: false, + ats: [] + }, + { + __typename: 'User', + id: '4', + username: 'bar-foo', + roles: ['TESTER'], + isBot: false, + ats: [] + }, + { + __typename: 'User', + id: '5', + username: 'boo-far', + roles: ['TESTER'], + isBot: false, + ats: [] + } + ], + ats: [], + testPlans: [], + testPlanVersions: [], + testPlanReports: [] + } } + } ]; diff --git a/client/tests/__mocks__/GraphQLMocks/TestQueuePageAdminPopulatedMock.js b/client/tests/__mocks__/GraphQLMocks/TestQueuePageAdminPopulatedMock.js deleted file mode 100644 index 7012bd518..000000000 --- a/client/tests/__mocks__/GraphQLMocks/TestQueuePageAdminPopulatedMock.js +++ /dev/null @@ -1,379 +0,0 @@ -export default testQueuePageQuery => [ - { - request: { - query: testQueuePageQuery - }, - result: { - data: { - me: { - id: '101', - username: 'alflennik', - roles: ['ADMIN', 'TESTER'], - isBot: false - }, - ats: [ - { - id: '1', - name: 'JAWS', - key: 'jaws', - atVersions: [ - { - id: '1', - name: '2021.2111.13', - releasedAt: '2021-11-01T04:00:00.000Z' - } - ], - browsers: [ - { - id: '3', - key: 'safari_macos', - name: 'Safari' - }, - { - id: '1', - key: 'firefox', - name: 'Firefox' - }, - { - id: '2', - key: 'chrome', - name: 'Chrome' - } - ], - candidateBrowsers: [ - { - id: '2', - name: 'Chrome' - } - ], - recommendedBrowsers: [ - { - id: '1', - name: 'Firefox' - }, - { - id: '2', - name: 'Chrome' - } - ] - }, - { - id: '2', - key: 'nvda', - name: 'NVDA', - atVersions: [ - { - id: '5', - name: '2020.4', - releasedAt: '2022-01-01T12:00:00.000Z' - }, - { - id: '4', - name: '2020.3', - releasedAt: '2022-01-01T12:00:00.000Z' - }, - { - id: '3', - name: '2020.2', - releasedAt: '2022-01-01T12:00:00.000Z' - }, - { - id: '2', - name: '2020.1', - releasedAt: '2022-01-01T12:00:00.000Z' - }, - { - id: '1', - name: '2019.3', - releasedAt: '2022-01-01T12:00:00.000Z' - } - ], - browsers: [ - { - id: '3', - key: 'safari_macos', - name: 'Safari' - }, - { - id: '1', - key: 'firefox', - name: 'Firefox' - }, - { - id: '2', - key: 'chrome', - name: 'Chrome' - } - ], - candidateBrowsers: [ - { - id: '2', - name: 'Chrome' - } - ], - recommendedBrowsers: [ - { - id: '1', - name: 'Firefox' - }, - { - id: '2', - name: 'Chrome' - } - ] - }, - { - id: '3', - key: 'voiceover_macos', - name: 'VoiceOver for macOS', - atVersions: [ - { - id: '3', - name: '11.6 (20G165)', - releasedAt: '2019-09-01T04:00:00.000Z' - } - ], - browsers: [ - { - id: '3', - key: 'safari_macos', - name: 'Safari' - }, - { - id: '1', - key: 'firefox', - name: 'Firefox' - }, - { - id: '2', - key: 'chrome', - name: 'Chrome' - } - ], - candidateBrowsers: [ - { - id: '3', - name: 'Safari' - } - ], - recommendedBrowsers: [ - { - id: '3', - name: 'Safari' - }, - { - id: '2', - name: 'Chrome' - } - ] - } - ], - browsers: [ - { - id: '2', - key: 'chrome', - name: 'Chrome' - }, - { - id: '1', - key: 'firefox', - name: 'Firefox' - }, - { - id: '3', - key: 'safari_macos', - name: 'Safari' - } - ], - users: [ - { - id: '1', - username: 'esmeralda-baggins', - roles: ['TESTER', 'ADMIN'], - isBot: false, - ats: [] - }, - { - id: '2', - username: 'tom-proudfeet', - roles: ['TESTER'], - isBot: false, - ats: [] - }, - { - id: '101', - username: 'alflennik', - roles: ['TESTER', 'ADMIN'], - isBot: false, - ats: [] - } - ], - testPlanVersions: [ - { - id: '1', - title: 'Alert Example', - phase: 'DRAFT', - gitSha: '97d4bd6c2078849ad4ee01eeeb3667767ca6f992', - gitMessage: - 'Create tests for APG design pattern example: Navigation Menu Button (#524)', - testPlan: { - directory: 'alert' - }, - updatedAt: '2022-04-15T19:09:53.000Z' - }, - { - id: '2', - title: 'Banner Landmark', - phase: 'DRAFT', - gitSha: '97d4bd6c2078849ad4ee01eeeb3667767ca6f992', - gitMessage: - 'Create tests for APG design pattern example: Navigation Menu Button (#524)', - testPlan: { - directory: 'banner' - }, - updatedAt: '2022-04-15T19:09:53.000Z' - }, - { - id: '3', - title: 'Breadcrumb Example', - phase: 'DRAFT', - gitSha: '97d4bd6c2078849ad4ee01eeeb3667767ca6f992', - gitMessage: - 'Create tests for APG design pattern example: Navigation Menu Button (#524)', - testPlan: { - directory: 'breadcrumb' - }, - updatedAt: '2022-04-15T19:09:53.000Z' - } - ], - testPlanReports: [ - { - id: '1', - status: 'DRAFT', - conflictsLength: 0, - runnableTestsLength: 17, - markedFinalAt: null, - at: { id: '1', name: 'JAWS', key: 'jaws' }, - minimumAtVersion: { id: '1', name: '2024.3321.1' }, - exactAtVersion: null, - browser: { id: '2', name: 'Chrome', key: 'chrome' }, - testPlanVersion: { - id: '1', - title: 'Checkbox Example (Two State)', - phase: 'DRAFT', - gitSha: 'b7078039f789c125e269cb8f8632f57a03d4c50b', - gitMessage: 'The message for this SHA', - testPlan: { directory: 'checkbox' }, - versionString: 'V21.11.30' - }, - draftTestPlanRuns: [ - { - id: '1', - tester: { - id: '1', - username: 'esmeralda-baggins', - isBot: false - }, - testResultsLength: 0, - initiatedByAutomation: false - } - ] - }, - { - id: '2', - status: 'DRAFT', - conflictsLength: 0, - runnableTestsLength: 17, - markedFinalAt: null, - at: { - id: '3', - name: 'VoiceOver for macOS', - key: 'voiceover_macos' - }, - minimumAtVersion: { id: '3', name: '14.5' }, - exactAtVersion: null, - browser: { - id: '3', - name: 'Safari', - key: 'safari_macos' - }, - testPlanVersion: { - id: '1', - title: 'Checkbox Example (Two State)', - phase: 'DRAFT', - gitSha: 'b7078039f789c125e269cb8f8632f57a03d4c50b', - gitMessage: 'The message for this SHA', - testPlan: { directory: 'checkbox' }, - versionString: 'V21.11.30' - }, - draftTestPlanRuns: [ - { - id: '1', - tester: { - id: '1', - username: 'esmeralda-baggins', - isBot: false - }, - testResultsLength: 0, - initiatedByAutomation: false - } - ] - }, - { - id: '3', - status: 'DRAFT', - conflictsLength: 3, - runnableTestsLength: 17, - markedFinalAt: null, - at: { id: '2', name: 'NVDA', key: 'nvda' }, - minimumAtVersion: null, - exactAtVersion: { id: '2', name: '2024.2' }, - browser: { id: '1', name: 'Firefox', key: 'firefox' }, - testPlanVersion: { - id: '1', - title: 'Checkbox Example (Two State)', - phase: 'DRAFT', - gitSha: 'b7078039f789c125e269cb8f8632f57a03d4c50b', - gitMessage: 'The message for this SHA', - testPlan: { directory: 'checkbox' }, - versionString: 'V21.11.30' - }, - draftTestPlanRuns: [ - { - id: '3', - tester: { - id: '2', - username: 'tom-proudfeet', - isBot: false - }, - testResultsLength: 3, - initiatedByAutomation: false - }, - { - id: '101', - tester: { - id: '101', - username: 'alflennik', - isBot: false - }, - testResultsLength: 1, - initiatedByAutomation: false - }, - { - id: '2', - tester: { - id: '1', - username: 'esmeralda-baggins', - isBot: false - }, - testResultsLength: 3, - initiatedByAutomation: false - } - ] - } - ], - testPlans: [] - } - } - } -]; diff --git a/client/tests/__mocks__/GraphQLMocks/TestQueuePageBaseMock.js b/client/tests/__mocks__/GraphQLMocks/TestQueuePageBaseMock.js index 13afebebd..a7e800ce6 100644 --- a/client/tests/__mocks__/GraphQLMocks/TestQueuePageBaseMock.js +++ b/client/tests/__mocks__/GraphQLMocks/TestQueuePageBaseMock.js @@ -1,117 +1,114 @@ export default (testPlanReportAtBrowserQuery, existingTestPlanReportsQuery) => [ - { - request: { - query: testPlanReportAtBrowserQuery, - variables: { - testPlanReportId: '3' - } - }, - result: { - data: { - testPlanReport: { - id: '3', - at: { - id: '1', - key: 'jaws', - name: 'JAWS' - }, - browser: { - id: '2', - key: 'chrome', - name: 'Chrome' - } - } - } + { + request: { + query: testPlanReportAtBrowserQuery, + variables: { + testPlanReportId: '3' + } + }, + result: { + data: { + testPlanReport: { + id: '3', + at: { + id: '1', + key: 'jaws', + name: 'JAWS' + }, + browser: { + id: '2', + key: 'chrome', + name: 'Chrome' + } } + } + } + }, + { + request: { + query: testPlanReportAtBrowserQuery, + variables: { + testPlanReportId: '2' + } }, - { - request: { - query: testPlanReportAtBrowserQuery, - variables: { - testPlanReportId: '2' - } - }, - result: { - data: { - testPlanReport: { - id: '2', - at: { - id: '2', - key: 'nvda', - name: 'NVDA' - }, - browser: { - id: '1', - key: 'firefox', - name: 'Firefox' - } - } - } + result: { + data: { + testPlanReport: { + id: '2', + at: { + id: '2', + key: 'nvda', + name: 'NVDA' + }, + browser: { + id: '1', + key: 'firefox', + name: 'Firefox' + } } + } + } + }, + { + request: { + query: testPlanReportAtBrowserQuery, + variables: { + testPlanReportId: '1' + } }, - { - request: { - query: testPlanReportAtBrowserQuery, - variables: { - testPlanReportId: '1' - } - }, - result: { - data: { - testPlanReport: { - id: '1', - at: { - id: '1', - key: 'jaws', - name: 'JAWS' - }, - browser: { - id: '2', - key: 'chrome', - name: 'Chrome' - } - } - } + result: { + data: { + testPlanReport: { + id: '1', + at: { + id: '1', + key: 'jaws', + name: 'JAWS' + }, + browser: { + id: '2', + key: 'chrome', + name: 'Chrome' + } } + } + } + }, + { + request: { + query: existingTestPlanReportsQuery, + variables: { + testPlanVersionId: '1', + directory: 'alert' + } }, - { - request: { - query: existingTestPlanReportsQuery, - variables: { - testPlanVersionId: '1', - directory: 'alert' + result: { + data: { + existingTestPlanVersion: { + id: '1', + testPlanReports: [ + { + id: '7', + markedFinalAt: null, + isFinal: false, + draftTestPlanRuns: [], + at: { + id: '3' + }, + browser: { + id: '1' + } } + ], + metadata: { + exampleUrl: + 'https://w3c.github.io/aria-practices/examples/alert/alert.html', + designPatternUrl: 'https://w3c.github.io/aria-practices/#alert', + testFormatVersion: 1 + } }, - result: { - data: { - existingTestPlanVersion: { - id: '1', - testPlanReports: [ - { - id: '1', - markedFinalAt: '2021-01-01T00:00:00.000Z', - isFinal: true, - draftTestPlanRuns: { - initiatedByAutomation: true - }, - at: { - id: '1', - key: 'jaws' - }, - browser: { - id: '2', - key: 'chrome' - } - } - ], - metadata: { - exampleUrl: 'https://fakeurl.com/exampleUrl', - designPatternUrl: 'https://fakeurl.com/designPattern', - testFormatVersion: 1 - } - }, - oldTestPlanVersions: [] - } - } + oldTestPlanVersions: [] + } } + } ]; diff --git a/client/tests/__mocks__/GraphQLMocks/TestQueuePageTesterNotPopulatedMock.js b/client/tests/__mocks__/GraphQLMocks/TestQueuePageTesterNotPopulatedMock.js index 8b600c669..186f6ac4f 100644 --- a/client/tests/__mocks__/GraphQLMocks/TestQueuePageTesterNotPopulatedMock.js +++ b/client/tests/__mocks__/GraphQLMocks/TestQueuePageTesterNotPopulatedMock.js @@ -1,48 +1,47 @@ export default testQueuePageQuery => [ - { - request: { - query: testQueuePageQuery + { + request: { + query: testQueuePageQuery + }, + result: { + data: { + me: { + __typename: 'User', + id: '4', + username: 'bar-foo', + roles: ['TESTER'] }, - result: { - data: { - me: { - id: '4', - username: 'bar-foo', - roles: ['TESTER'], - __typename: 'User' - }, - ats: [], - browsers: [], - users: [ - { - id: '1', - username: 'foo-bar', - roles: ['ADMIN', 'TESTER'], - isBot: false, - ats: [], - __typename: 'User' - }, - { - id: '4', - username: 'bar-foo', - roles: ['TESTER'], - isBot: false, - ats: [], - __typename: 'User' - }, - { - id: '5', - username: 'boo-far', - roles: ['TESTER'], - isBot: false, - ats: [], - __typename: 'User' - } - ], - testPlanVersions: [], - testPlanReports: [], - testPlans: [] - } - } + users: [ + { + __typename: 'User', + id: '1', + username: 'foo-bar', + roles: ['ADMIN', 'TESTER'], + isBot: false, + ats: [] + }, + { + __typename: 'User', + id: '4', + username: 'bar-foo', + roles: ['TESTER'], + isBot: false, + ats: [] + }, + { + __typename: 'User', + id: '5', + username: 'boo-far', + roles: ['TESTER'], + isBot: false, + ats: [] + } + ], + ats: [], + testPlans: [], + testPlanVersions: [], + testPlanReports: [] + } } + } ]; diff --git a/client/tests/__mocks__/GraphQLMocks/TestQueuePageTesterPopulatedMock.js b/client/tests/__mocks__/GraphQLMocks/TestQueuePageTesterPopulatedMock.js deleted file mode 100644 index f4be09013..000000000 --- a/client/tests/__mocks__/GraphQLMocks/TestQueuePageTesterPopulatedMock.js +++ /dev/null @@ -1,380 +0,0 @@ -export default testQueuePageQuery => [ - { - request: { - query: testQueuePageQuery - }, - result: { - data: { - me: { - id: '4', - username: 'bar-foo', - roles: ['TESTER'], - __typename: 'User' - }, - ats: [ - { - id: '1', - key: 'jaws', - name: 'JAWS', - atVersions: [ - { - id: '1', - name: '2021.2111.13', - releasedAt: '2021-11-01T04:00:00.000Z' - } - ], - browsers: [ - { - id: '3', - key: 'safari_macos', - name: 'Safari' - }, - { - id: '1', - key: 'firefox', - name: 'Firefox' - }, - { - id: '2', - key: 'chrome', - name: 'Chrome' - } - ], - candidateBrowsers: [ - { - id: '2', - name: 'Chrome' - } - ], - recommendedBrowsers: [ - { - id: '1', - name: 'Firefox' - }, - { - id: '2', - name: 'Chrome' - } - ] - }, - { - id: '2', - key: 'nvda', - name: 'NVDA', - atVersions: [ - { - id: '5', - name: '2020.4', - releasedAt: '2022-01-01T12:00:00.000Z' - }, - { - id: '4', - name: '2020.3', - releasedAt: '2022-01-01T12:00:00.000Z' - }, - { - id: '3', - name: '2020.2', - releasedAt: '2022-01-01T12:00:00.000Z' - }, - { - id: '2', - name: '2020.1', - releasedAt: '2022-01-01T12:00:00.000Z' - }, - { - id: '1', - name: '2019.3', - releasedAt: '2022-01-01T12:00:00.000Z' - } - ], - browsers: [ - { - id: '3', - key: 'safari_macos', - name: 'Safari' - }, - { - id: '1', - key: 'firefox', - name: 'Firefox' - }, - { - id: '2', - key: 'chrome', - name: 'Chrome' - } - ], - candidateBrowsers: [ - { - id: '2', - name: 'Chrome' - } - ], - recommendedBrowsers: [ - { - id: '1', - name: 'Firefox' - }, - { - id: '2', - name: 'Chrome' - } - ] - }, - { - id: '3', - key: 'voiceover_macos', - name: 'VoiceOver for macOS', - atVersions: [ - { - id: '3', - name: '11.6 (20G165)', - releasedAt: '2019-09-01T04:00:00.000Z' - } - ], - browsers: [ - { - id: '3', - key: 'safari_macos', - name: 'Safari' - }, - { - id: '1', - key: 'firefox', - name: 'Firefox' - }, - { - id: '2', - key: 'chrome', - name: 'Chrome' - } - ], - candidateBrowsers: [ - { - id: '3', - name: 'Safari' - } - ], - recommendedBrowsers: [ - { - id: '3', - name: 'Safari' - }, - { - id: '2', - name: 'Chrome' - } - ] - } - ], - browsers: [ - { - id: '2', - key: 'chrome', - name: 'Chrome' - }, - { - id: '1', - key: 'firefox', - name: 'Firefox' - }, - { - id: '3', - key: 'safari_macos', - name: 'Safari' - } - ], - users: [ - { - id: '1', - username: 'foo-bar', - roles: ['ADMIN', 'TESTER'], - isBot: false, - ats: [] - }, - { - id: '4', - username: 'bar-foo', - roles: ['TESTER'], - isBot: false, - ats: [] - }, - { - id: '5', - username: 'boo-far', - roles: ['TESTER'], - isBot: false, - ats: [] - } - ], - testPlanVersions: [ - { - id: '1', - title: 'Alert Example', - phase: 'DRAFT', - gitSha: '97d4bd6c2078849ad4ee01eeeb3667767ca6f992', - gitMessage: - 'Create tests for APG design pattern example: Navigation Menu Button (#524)', - testPlan: { - directory: 'alert' - }, - updatedAt: '2022-04-15T19:09:53.000Z' - }, - { - id: '2', - title: 'Banner Landmark', - phase: 'DRAFT', - gitSha: '97d4bd6c2078849ad4ee01eeeb3667767ca6f992', - gitMessage: - 'Create tests for APG design pattern example: Navigation Menu Button (#524)', - testPlan: { - directory: 'banner' - }, - updatedAt: '2022-04-15T19:09:53.000Z' - }, - { - id: '3', - title: 'Breadcrumb Example', - phase: 'DRAFT', - gitSha: '97d4bd6c2078849ad4ee01eeeb3667767ca6f992', - gitMessage: - 'Create tests for APG design pattern example: Navigation Menu Button (#524)', - testPlan: { - directory: 'breadcrumb' - }, - updatedAt: '2022-04-15T19:09:53.000Z' - } - ], - testPlanReports: [ - { - id: '10', - status: 'DRAFT', - conflictsLength: 0, - runnableTestsLength: 17, - markedFinalAt: null, - at: { - id: '2', - key: 'nvda', - name: 'NVDA' - }, - minimumAtVersion: { id: '1', name: '2024.3321.1' }, - exactAtVersion: null, - browser: { - id: '1', - key: 'firefox', - name: 'Firefox' - }, - testPlanVersion: { - id: '65', - title: 'Checkbox Example (Two State)', - phase: 'DRAFT', - gitSha: 'aea64f84b8fa8b21e94f5d9afd7035570bc1bed3', - gitMessage: 'The message for this SHA', - testPlan: { - directory: 'checkbox' - }, - versionString: 'V21-11-30' - }, - draftTestPlanRuns: [ - { - id: '18', - tester: { - id: '1', - username: 'foo-bar', - isBot: false - }, - testResultsLength: 0, - initiatedByAutomation: false - }, - { - id: '19', - tester: { - id: '4', - username: 'bar-foo', - isBot: false - }, - testResultsLength: 0, - initiatedByAutomation: false - } - ] - }, - { - id: '11', - status: 'DRAFT', - conflictsLength: 0, - runnableTestsLength: 17, - markedFinalAt: null, - at: { - id: '2', - key: 'jaws', - name: 'JAWS' - }, - minimumAtVersion: { id: '3', name: '14.5' }, - exactAtVersion: null, - browser: { - id: '1', - key: 'firefox', - name: 'Firefox' - }, - testPlanVersion: { - id: '65', - title: 'Checkbox Example (Two State)', - phase: 'DRAFT', - gitSha: 'aea64f84b8fa8b21e94f5d9afd7035570bc1bed3', - gitMessage: 'The message for this SHA', - testPlan: { - directory: 'checkbox' - }, - versionString: 'V21-11-30' - }, - draftTestPlanRuns: [ - { - id: '20', - tester: { - id: '5', - username: 'boo-far', - isBot: false - }, - testResultsLength: 0, - initiatedByAutomation: false - } - ] - }, - { - id: '12', - status: 'DRAFT', - conflictsLength: 0, - runnableTestsLength: 15, - markedFinalAt: null, - at: { - id: '3', - key: 'voiceover_macos', - name: 'VoiceOver for macOS' - }, - minimumAtVersion: null, - exactAtVersion: { id: '2', name: '2024.2' }, - browser: { - id: '1', - key: 'firefox', - name: 'Firefox' - }, - testPlanVersion: { - id: '74', - title: 'Editor Menubar Example', - phase: 'DRAFT', - gitSha: 'aea64f84b8fa8b21e94f5d9afd7035570bc1bed3', - gitMessage: 'The message for this SHA', - testPlan: { - directory: 'menubar-editor' - }, - versionString: 'V21-11-30' - }, - draftTestPlanRuns: [] - } - ], - testPlans: [] - } - } - } -]; diff --git a/client/tests/__mocks__/GraphQLMocks/index.js b/client/tests/__mocks__/GraphQLMocks/index.js index 395e83e9b..f2e5142a4 100644 --- a/client/tests/__mocks__/GraphQLMocks/index.js +++ b/client/tests/__mocks__/GraphQLMocks/index.js @@ -1,45 +1,38 @@ import { TEST_QUEUE_PAGE_QUERY } from '@components/TestQueue/queries'; +import { TEST_PLAN_REPORT_AT_BROWSER_QUERY } from '@components/common/AssignTesterDropdown/queries'; import { DATA_MANAGEMENT_PAGE_QUERY } from '@components/DataManagement/queries'; import { TEST_PLAN_REPORT_STATUS_DIALOG_QUERY } from '@components/TestPlanReportStatusDialog/queries'; -import { TEST_PLAN_REPORT_AT_BROWSER_QUERY } from '@components/TestQueue/queries'; import { EXISTING_TEST_PLAN_REPORTS } from '@components/AddTestToQueueWithConfirmation/queries'; import { ME_QUERY } from '@components/App/queries'; import TestQueuePageAdminNotPopulatedMock from './TestQueuePageAdminNotPopulatedMock'; -import TestQueuePageAdminPopulatedMock from './TestQueuePageAdminPopulatedMock'; import TestQueuePageTesterNotPopulatedMock from './TestQueuePageTesterNotPopulatedMock'; -import TestQueuePageTesterPopulatedMock from './TestQueuePageTesterPopulatedMock'; + import DataManagementPagePopulatedMock from './DataManagementPagePopulatedMock'; import TestPlanReportStatusDialogMock from './TestPlanReportStatusDialogMock'; import TestQueuePageBaseMock from './TestQueuePageBaseMock'; export const TEST_QUEUE_PAGE_ADMIN_NOT_POPULATED_MOCK_DATA = - TestQueuePageAdminNotPopulatedMock(TEST_QUEUE_PAGE_QUERY); - -export const TEST_QUEUE_PAGE_ADMIN_POPULATED_MOCK_DATA = - TestQueuePageAdminPopulatedMock(TEST_QUEUE_PAGE_QUERY); + TestQueuePageAdminNotPopulatedMock(TEST_QUEUE_PAGE_QUERY); export const TEST_QUEUE_PAGE_TESTER_NOT_POPULATED_MOCK_DATA = - TestQueuePageTesterNotPopulatedMock(TEST_QUEUE_PAGE_QUERY); - -export const TEST_QUEUE_PAGE_TESTER_POPULATED_MOCK_DATA = - TestQueuePageTesterPopulatedMock(TEST_QUEUE_PAGE_QUERY); + TestQueuePageTesterNotPopulatedMock(TEST_QUEUE_PAGE_QUERY); export const TEST_QUEUE_PAGE_BASE_MOCK_DATA = TestQueuePageBaseMock( - TEST_PLAN_REPORT_AT_BROWSER_QUERY, - EXISTING_TEST_PLAN_REPORTS + TEST_PLAN_REPORT_AT_BROWSER_QUERY, + EXISTING_TEST_PLAN_REPORTS ); export const DATA_MANAGEMENT_PAGE_POPULATED_MOCK_DATA = - DataManagementPagePopulatedMock( - ME_QUERY, - DATA_MANAGEMENT_PAGE_QUERY, - TEST_PLAN_REPORT_STATUS_DIALOG_QUERY - ); + DataManagementPagePopulatedMock( + ME_QUERY, + DATA_MANAGEMENT_PAGE_QUERY, + TEST_PLAN_REPORT_STATUS_DIALOG_QUERY + ); export const TEST_PLAN_REPORT_STATUS_DIALOG_MOCK_DATA = - TestPlanReportStatusDialogMock( - ME_QUERY, - TEST_PLAN_REPORT_STATUS_DIALOG_QUERY, - EXISTING_TEST_PLAN_REPORTS - ); + TestPlanReportStatusDialogMock( + ME_QUERY, + TEST_PLAN_REPORT_STATUS_DIALOG_QUERY, + EXISTING_TEST_PLAN_REPORTS + ); diff --git a/client/tests/calculatePercentComplete.test.js b/client/tests/calculatePercentComplete.test.js index 03cda0adf..a38f12d7b 100644 --- a/client/tests/calculatePercentComplete.test.js +++ b/client/tests/calculatePercentComplete.test.js @@ -1,68 +1,68 @@ import { calculatePercentComplete } from '../utils/calculatePercentComplete'; describe('calculatePercentComplete', () => { - const testResult = (id, completedAt = null) => ({ id, completedAt }); + const testResult = (id, completedAt = null) => ({ id, completedAt }); - test('returns 0 when metrics or draftTestPlanRuns is not defined', () => { - expect(calculatePercentComplete({})).toBe(0); - expect(calculatePercentComplete({ metrics: {} })).toBe(0); - expect( - calculatePercentComplete({ - draftTestPlanRuns: [] - }) - ).toBe(0); - }); + test('returns 0 when metrics or draftTestPlanRuns is not defined', () => { + expect(calculatePercentComplete({})).toBe(0); + expect(calculatePercentComplete({ metrics: {} })).toBe(0); + expect( + calculatePercentComplete({ + draftTestPlanRuns: [] + }) + ).toBe(0); + }); - test('returns 0 when draftTestPlanRuns is empty', () => { - expect( - calculatePercentComplete({ - metrics: { testsCount: 5 }, - draftTestPlanRuns: [] - }) - ).toBe(0); - }); + test('returns 0 when draftTestPlanRuns is empty', () => { + expect( + calculatePercentComplete({ + metrics: { testsCount: 5 }, + draftTestPlanRuns: [] + }) + ).toBe(0); + }); - test('returns 0 and not Infinity when total tests possible is 0', () => { - const metrics = { testsCount: 0 }; - const t1 = testResult(1); - const t2 = testResult(2); - const t3 = testResult(3); - const t4 = testResult(4); - const t5 = testResult(5); + test('returns 0 and not Infinity when total tests possible is 0', () => { + const metrics = { testsCount: 0 }; + const t1 = testResult(1); + const t2 = testResult(2); + const t3 = testResult(3); + const t4 = testResult(4); + const t5 = testResult(5); - const draftTestPlanRuns = [ - { testResults: [t1, t2, t3] }, - { testResults: [t1, t2, t3, t4, t5] } - ]; + const draftTestPlanRuns = [ + { testResults: [t1, t2, t3] }, + { testResults: [t1, t2, t3, t4, t5] } + ]; - expect( - calculatePercentComplete({ - metrics, - draftTestPlanRuns - }) - ).toBe(0); - }); + expect( + calculatePercentComplete({ + metrics, + draftTestPlanRuns + }) + ).toBe(0); + }); - test('calculates and returns the correct percentage when draftTestPlanRuns has testResults', () => { - const metrics = { testsCount: 5 }; - const date = new Date(); - const t1 = testResult(1, date); - const t2 = testResult(2, date); - const t3 = testResult(3, date); + test('calculates and returns the correct percentage when draftTestPlanRuns has testResults', () => { + const metrics = { testsCount: 5 }; + const date = new Date(); + const t1 = testResult(1, date); + const t2 = testResult(2, date); + const t3 = testResult(3, date); - const draftTestPlanRuns = [ - { testResults: [t1, t2] }, - { testResults: [t1, t2, t3] } - ]; + const draftTestPlanRuns = [ + { testResults: [t1, t2] }, + { testResults: [t1, t2, t3] } + ]; - // Output should follow this formula: - // (NUMBER_COMPLETED_TESTS_BY_ALL_TESTERS / (NUMBER_ASSIGNED_TESTERS * NUMBER_TESTS_IN_PLAN)) * 100 - // (5 / (2 * 5)) * 100 = 50 - expect( - calculatePercentComplete({ - metrics, - draftTestPlanRuns - }) - ).toBe(50); - }); + // Output should follow this formula: + // (NUMBER_COMPLETED_TESTS_BY_ALL_TESTERS / (NUMBER_ASSIGNED_TESTERS * NUMBER_TESTS_IN_PLAN)) * 100 + // (5 / (2 * 5)) * 100 = 50 + expect( + calculatePercentComplete({ + metrics, + draftTestPlanRuns + }) + ).toBe(50); + }); }); diff --git a/client/tests/e2e/AtVersions.e2e.test.js b/client/tests/e2e/AtVersions.e2e.test.js new file mode 100644 index 000000000..518a2f217 --- /dev/null +++ b/client/tests/e2e/AtVersions.e2e.test.js @@ -0,0 +1,47 @@ +import getPage from '../util/getPage'; + +describe('AT Version UI', () => { + test('should add, edit, then remove an AtVersion', async () => { + /* prettier-ignore */ + await getPage({ role: 'admin', url: '/test-queue' }, async page => { + const openTrayIfClosed = async () => { + await page.waitForSelector('button ::-p-text(Manage Assistive Technology Versions)'); + const isTrayClosed = !!(await page.$('::-p-text(Select an assistive technology and manage its versions)')); + if (isTrayClosed) { + await page.click('button ::-p-text(Manage Assistive Technology Versions)'); + await page.waitForSelector('::-p-text(Select an assistive technology and manage its versions)'); + } + }; + await openTrayIfClosed(); + await page.click('button ::-p-text(Add a New Version)'); + await page.waitForSelector('.modal-title ::-p-text(Add a New Version for JAWS)'); + await page.waitForSelector('.modal-body .form-group:nth-child(1) ::-p-text(Version Number)'); + await page.waitForSelector('.modal-body .form-group:nth-child(2) ::-p-text(Approximate date of availability)'); + await page.type('.modal-body .form-group:nth-child(1) input', '99.0.1'); + await page.type('.modal-body .form-group:nth-child(2) input', '01-01-2000'); + await page.click('.modal-footer button ::-p-text(Add Version)'); + await page.waitForNetworkIdle({ idleTime: 5000 }); + await page.click('.modal-footer button ::-p-text(Ok)'); + await page.waitForSelector('.at-versions-container option:nth-child(2) ::-p-text(99.0.1)'); + const optionValue = await page.$eval('.at-versions-container option:nth-child(2)', option => option.value); + await page.select('.at-versions-container select', optionValue); + await page.click('.at-versions-container button ::-p-text(Edit)'); + const input = await page.waitForSelector('.modal-body .form-group:nth-child(1) input'); + for (let i = 0; i < 6; i += 1) { + await input.press('Backspace'); + } + await page.type('.modal-body .form-group:nth-child(1) input', '99.0.99'); + await page.click('.modal-footer button ::-p-text(Save)'); + await page.waitForNetworkIdle({ idleTime: 5000 }); + await page.click('.modal-footer button ::-p-text(Ok)'); + await page.waitForSelector('.at-versions-container option ::-p-text(99.0.99)'); + await page.select('.at-versions-container select', optionValue); + await page.click('.at-versions-container button ::-p-text(Remove)'); + await page.waitForSelector('.modal-title ::-p-text(Remove JAWS Version 99.0.99)'); + await page.click('.modal-footer button ::-p-text(Remove)'); + await page.waitForNetworkIdle(); + const option = await page.$('.at-versions-container option ::-p-text(99.0.99)'); + expect(option).toBeNull(); + }); + }); +}); diff --git a/client/tests/e2e/TestQueue.e2e.test.js b/client/tests/e2e/TestQueue.e2e.test.js new file mode 100644 index 000000000..f0dc1b409 --- /dev/null +++ b/client/tests/e2e/TestQueue.e2e.test.js @@ -0,0 +1,495 @@ +import getPage from '../util/getPage'; + +const text = async (page, selector, { wait = true } = {}) => { + if (wait) await page.waitForSelector(selector); + return page.$eval(selector, el => el.innerText); +}; + +const display = async (page, selector, { wait = true } = {}) => { + if (wait) await page.waitForSelector(selector); + return page.$eval(selector, el => { + const styles = window.getComputedStyle(el); + return styles.getPropertyValue('display'); + }); +}; + +describe('Test Queue common traits', () => { + it('renders page h1', async () => { + await getPage({ role: false, url: '/test-queue' }, async page => { + const h1Element = await text(page, 'h1'); + expect(h1Element).toBe('Test Queue'); + }); + }); +}); + +describe('Test Queue admin traits when reports exist', () => { + it('renders page h1', async () => { + await getPage({ role: 'admin', url: '/test-queue' }, async page => { + const h1Element = await text(page, 'h1'); + expect(h1Element).toBe('Test Queue'); + }); + }); + + it('renders page with instructions', async () => { + await getPage({ role: 'admin', url: '/test-queue' }, async page => { + const instructionsSelector = '[data-testid="test-queue-instructions"]'; + const instructionsText = await text(page, instructionsSelector); + + expect(instructionsText).toBe( + 'Manage the test plans, assign yourself a test plan or start executing one that is already assigned to you.' + ); + }); + }); + + it('renders page with known pattern sections', async () => { + await getPage({ role: 'admin', url: '/test-queue' }, async page => { + const alertSectionHeaderSelector = 'h2 ::-p-text(Alert Example)'; + const alertSectionContainerSelector = 'div#disclosure-container-alert-0'; + const modalDialogSectionHeaderSelector = + 'h2 ::-p-text(Modal Dialog Example)'; + const modalDialogSectionContainerSelector = + 'div#disclosure-container-modal-dialog-0'; + + const alertSectionTitle = await text(page, alertSectionHeaderSelector); + const alertSectionDisplay = await display( + page, + alertSectionContainerSelector + ); + const modalDialogSectionTitle = await text( + page, + modalDialogSectionHeaderSelector + ); + const modalDialogSectionDisplay = await display( + page, + modalDialogSectionContainerSelector + ); + + expect(alertSectionTitle).toBe('Alert Example'); + expect(alertSectionDisplay).toBe('none'); + expect(modalDialogSectionTitle).toBe('Modal Dialog Example'); + expect(modalDialogSectionDisplay).toBe('none'); + }); + }); + + it("renders page and open pattern section's table", async () => { + await getPage({ role: 'admin', url: '/test-queue' }, async page => { + const modalDialogSectionContainerSelector = + 'div#disclosure-container-modal-dialog-0'; + const modalDialogSectionButtonSelector = + 'button#disclosure-btn-modal-dialog-0'; + const modalDialogTableSelector = + 'table[aria-label="Reports for Modal Dialog Example V24.06.07 in draft phase"]'; + + await page.waitForSelector(modalDialogSectionButtonSelector); + + const modalDialogSectionButton = await text( + page, + modalDialogSectionContainerSelector + ); + const preClickModalDialogSectionDisplay = await display( + page, + modalDialogSectionContainerSelector + ); + + // Expand Modal Dialog's V24.06.07 section + await page.click(modalDialogSectionButtonSelector); + const postClickModalDialogSectionDisplay = await display( + page, + modalDialogSectionContainerSelector + ); + + // Wait for the table to render + await page.waitForSelector(modalDialogTableSelector); + + // Check if the table contains expected column data + const validTable = await page.$eval(modalDialogTableSelector, el => { + const sanitizedText = text => + text + .replaceAll(String.fromCharCode(160), ' ') // remove   being included + .trim(); + + // Check if any cell in the table contains expected text + const cells = Array.from(el.querySelectorAll('td')); + + // Assistive Technology Column + const atColumn = cells[0]; + const atColumnText = sanitizedText(atColumn.innerText); + const atColumnCondition = atColumnText.includes('NVDA 2020.4 or later'); + + // Browser Column + const browserColumn = cells[1]; + const browserColumnText = sanitizedText(browserColumn.innerText); + const browserColumnCondition = + browserColumnText.includes('Chrome Any version'); + + // Testers Column + const testersColumn = cells[2]; + const testersColumnText = sanitizedText(testersColumn.innerText); + const testersColumnCondition = + testersColumnText.includes('Assign Testers') && // sr-only label applied to the assign testers dropdown component + testersColumnText.includes('Assign Yourself') && + testersColumnText.includes('esmeralda-baggins') && + testersColumnText.includes('tests complete'); + + // Status Column + const statusColumn = cells[3]; + const statusColumnText = sanitizedText(statusColumn.innerText); + const statusColumnCondition = statusColumnText.includes( + '100% complete by esmeralda-baggins with 0 conflicts' + ); + + // Actions Column + const actionsColumn = cells[4]; + const actionsColumnText = sanitizedText(actionsColumn.innerText); + const actionsColumnCondition = + actionsColumnText.includes('Start Testing') && + actionsColumnText.includes('Open run as...') && + actionsColumnText.includes('Mark as Final') && + actionsColumnText.includes('Mark as Final'); + + return ( + atColumnCondition && + browserColumnCondition && + testersColumnCondition && + statusColumnCondition && + actionsColumnCondition + ); + }); + + expect(modalDialogSectionButton.includes('V24.06.07')).toBe(true); + expect(preClickModalDialogSectionDisplay).toBe('none'); + expect(postClickModalDialogSectionDisplay).toBe('block'); + expect(validTable).toBe(true); + }); + }); + + it("renders page, opens pattern section's table and assigns bot and shows context related action", async () => { + await getPage({ role: 'admin', url: '/test-queue' }, async page => { + const modalDialogSectionButtonSelector = + 'button#disclosure-btn-modal-dialog-0'; + const modalDialogTableSelector = + 'table[aria-label="Reports for Modal Dialog Example V24.06.07 in draft phase"]'; + + await page.waitForSelector(modalDialogSectionButtonSelector); + + // Expand Modal Dialog's V24.06.07 section + await page.click(modalDialogSectionButtonSelector); + + // Wait for the table to render + await page.waitForSelector(modalDialogTableSelector); + + const assignTestersDropdownButton = await page.evaluateHandle(() => { + const modalDialogTableSelector = + 'table[aria-label="Reports for Modal Dialog Example V24.06.07 in draft phase"]'; + const modalDialogTable = document.querySelector( + modalDialogTableSelector + ); + + // Check if any cell in the table contains expected text + const cells = Array.from(modalDialogTable.querySelectorAll('td')); + + // Testers Column + const testersColumn = cells[2]; + return testersColumn.querySelector('div.dropdown button'); + }); + + await assignTestersDropdownButton.click(); + + // Will be first option on page after clicking the dropdown button + const assignTestersMenuSelector = 'div [role="menu"]'; + await page.waitForSelector(assignTestersMenuSelector); + + const assignBotOptionButton = await page.evaluateHandle(() => { + const assignTestersMenuSelector = 'div [role="menu"]'; + const assignTestersMenu = document.querySelector( + assignTestersMenuSelector + ); + const assignTesterOptions = Array.from( + assignTestersMenu.querySelectorAll('[role="menuitemcheckbox"]') + ); + + return assignTesterOptions.find(option => + option.innerText.includes('Bot') + ); + }); + + // Assign the bot user found in the assign testers list + await assignBotOptionButton.click(); + await page.waitForSelector('::-p-text(Manage NVDA Bot Run)'); + + // Check if the table contains expected column data + const validTable = await page.$eval(modalDialogTableSelector, el => { + const sanitizedText = text => + text + .replaceAll(String.fromCharCode(160), ' ') // remove   being included + .trim(); + + // Check if any cell in the table contains expected text + const cells = Array.from(el.querySelectorAll('td')); + + // Actions Column + const actionsColumn = cells[4]; + const actionsColumnText = sanitizedText(actionsColumn.innerText); + return ( + actionsColumnText.includes('Start Testing') && + actionsColumnText.includes('Open run as...') && + actionsColumnText.includes('Mark as Final') && + actionsColumnText.includes('Mark as Final') && + actionsColumnText.includes('Manage NVDA Bot Run') + ); + }); + + expect(validTable).toBe(true); + }); + }); +}); + +describe('Test Queue tester traits when reports exist', () => { + it('renders page h1', async () => { + await getPage({ role: 'tester', url: '/test-queue' }, async page => { + const h1Element = await text(page, 'h1'); + expect(h1Element).toBe('Test Queue'); + }); + }); + + it('renders page with instructions', async () => { + await getPage({ role: 'tester', url: '/test-queue' }, async page => { + const instructionsSelector = '[data-testid="test-queue-instructions"]'; + const instructionsText = await text(page, instructionsSelector); + + expect(instructionsText).toBe( + 'Assign yourself a test plan or start executing one that is already assigned to you.' + ); + }); + }); + + it('renders page with known pattern sections', async () => { + await getPage({ role: 'tester', url: '/test-queue' }, async page => { + const alertSectionHeaderSelector = 'h2 ::-p-text(Alert Example)'; + const alertSectionContainerSelector = 'div#disclosure-container-alert-0'; + const modalDialogSectionHeaderSelector = + 'h2 ::-p-text(Modal Dialog Example)'; + const modalDialogSectionContainerSelector = + 'div#disclosure-container-modal-dialog-0'; + + const alertSectionTitle = await text(page, alertSectionHeaderSelector); + const alertSectionDisplay = await display( + page, + alertSectionContainerSelector + ); + const modalDialogSectionTitle = await text( + page, + modalDialogSectionHeaderSelector + ); + const modalDialogSectionDisplay = await display( + page, + modalDialogSectionContainerSelector + ); + + expect(alertSectionTitle).toBe('Alert Example'); + expect(alertSectionDisplay).toBe('none'); + expect(modalDialogSectionTitle).toBe('Modal Dialog Example'); + expect(modalDialogSectionDisplay).toBe('none'); + }); + }); + + it("renders page and open pattern section's table", async () => { + await getPage({ role: 'tester', url: '/test-queue' }, async page => { + const modalDialogSectionContainerSelector = + 'div#disclosure-container-modal-dialog-0'; + const modalDialogSectionButtonSelector = + 'button#disclosure-btn-modal-dialog-0'; + const modalDialogTableSelector = + 'table[aria-label="Reports for Modal Dialog Example V24.06.07 in draft phase"]'; + + await page.waitForSelector(modalDialogSectionButtonSelector); + + const modalDialogSectionButton = await text( + page, + modalDialogSectionContainerSelector + ); + const preClickModalDialogSectionDisplay = await display( + page, + modalDialogSectionContainerSelector + ); + + // Expand Modal Dialog's V24.06.07 section + await page.click(modalDialogSectionButtonSelector); + const postClickModalDialogSectionDisplay = await display( + page, + modalDialogSectionContainerSelector + ); + + // Wait for the table to render + await page.waitForSelector(modalDialogTableSelector); + + // Check if the table contains expected column data + const validTable = await page.$eval(modalDialogTableSelector, el => { + const sanitizedText = text => + text + .replaceAll(String.fromCharCode(160), ' ') // remove   being included + .trim(); + + // Check if any cell in the table contains expected text + const cells = Array.from(el.querySelectorAll('td')); + + // Assistive Technology Column + const atColumn = cells[0]; + const atColumnText = sanitizedText(atColumn.innerText); + const atColumnCondition = atColumnText.includes('NVDA 2020.4 or later'); + + // Browser Column + const browserColumn = cells[1]; + const browserColumnText = sanitizedText(browserColumn.innerText); + const browserColumnCondition = + browserColumnText.includes('Chrome Any version'); + + // Testers Column + const testersColumn = cells[2]; + const testersColumnText = sanitizedText(testersColumn.innerText); + const testersColumnCondition = + !testersColumnText.includes('Assign Testers') && // doesn't show unless admin + testersColumnText.includes('Assign Yourself') && + testersColumnText.includes('esmeralda-baggins') && + testersColumnText.includes('tests complete'); + + // Status Column + const statusColumn = cells[3]; + const statusColumnText = sanitizedText(statusColumn.innerText); + const statusColumnCondition = statusColumnText.includes( + '100% complete by esmeralda-baggins with 0 conflicts' + ); + + // Actions Column + const actionsColumn = cells[4]; + const actionsColumnText = sanitizedText(actionsColumn.innerText); + const actionsColumnCondition = + actionsColumnText.includes('Start Testing') && + !actionsColumnText.includes('Open run as...') && + !actionsColumnText.includes('Mark as Final') && + !actionsColumnText.includes('Mark as Final') && + !actionsColumnText.includes('Manage NVDA Bot Run'); + + return ( + atColumnCondition && + browserColumnCondition && + testersColumnCondition && + statusColumnCondition && + actionsColumnCondition + ); + }); + + expect(modalDialogSectionButton.includes('V24.06.07')).toBe(true); + expect(preClickModalDialogSectionDisplay).toBe('none'); + expect(postClickModalDialogSectionDisplay).toBe('block'); + expect(validTable).toBe(true); + }); + }); + + it("renders page, opens pattern section's table and assigns yourself and shows proper state of context related action", async () => { + await getPage({ role: 'tester', url: '/test-queue' }, async page => { + const modalDialogSectionButtonSelector = + 'button#disclosure-btn-modal-dialog-0'; + const modalDialogTableSelector = + 'table[aria-label="Reports for Modal Dialog Example V24.06.07 in draft phase"]'; + + await page.waitForSelector(modalDialogSectionButtonSelector); + + // Expand Modal Dialog's V24.06.07 section + await page.click(modalDialogSectionButtonSelector); + + // Wait for the table to render + await page.waitForSelector(modalDialogTableSelector); + + const preAssignValidTable = await page.$eval( + modalDialogTableSelector, + el => { + const sanitizedText = text => + text + .replaceAll(String.fromCharCode(160), ' ') // remove   being included + .trim(); + + // Check if any cell in the table contains expected text + const cells = Array.from(el.querySelectorAll('td')); + + // Actions Column + const actionsColumn = cells[4]; + + // Normal disabled button unless assigned + const startTestingButton = actionsColumn.querySelector('button'); + const startTestingButtonText = sanitizedText( + startTestingButton.innerText + ); + const actionsColumnText = sanitizedText(actionsColumn.innerText); + return ( + startTestingButton.disabled && + startTestingButtonText.includes('Start Testing') && + !actionsColumnText.includes('Open run as...') && + !actionsColumnText.includes('Mark as Final') && + !actionsColumnText.includes('Mark as Final') && + !actionsColumnText.includes('Manage NVDA Bot Run') + ); + } + ); + + const assignYourselfButton = await page.evaluateHandle(() => { + const modalDialogTableSelector = + 'table[aria-label="Reports for Modal Dialog Example V24.06.07 in draft phase"]'; + const modalDialogTable = document.querySelector( + modalDialogTableSelector + ); + + // Check if any cell in the table contains expected text + const cells = Array.from(modalDialogTable.querySelectorAll('td')); + + // Testers Column + const testersColumn = cells[2]; + + // First button will be the 'Assign Yourself' button + return testersColumn.querySelector('button'); + }); + + // Assign yourself to the test plan report + await assignYourselfButton.click(); + + // Allow self assignment to happen + await new Promise(resolve => setTimeout(resolve, 2000)); + + // Check if the table contains expected column data + const postAssignValidTable = await page.$eval( + modalDialogTableSelector, + el => { + const sanitizedText = text => + text + .replaceAll(String.fromCharCode(160), ' ') // remove   being included + .trim(); + + // Check if any cell in the table contains expected text + const cells = Array.from(el.querySelectorAll('td')); + + // Actions Column + const actionsColumn = cells[4]; + + // The button gets converted to + const startTestingButton = + actionsColumn.querySelector('a[role="button"]'); + const startTestingButtonText = sanitizedText( + startTestingButton.innerText + ); + const actionsColumnText = sanitizedText(actionsColumn.innerText); + return ( + startTestingButton && + startTestingButtonText.includes('Start Testing') && + !actionsColumnText.includes('Open run as...') && + !actionsColumnText.includes('Mark as Final') && + !actionsColumnText.includes('Mark as Final') && + !actionsColumnText.includes('Manage NVDA Bot Run') + ); + } + ); + + expect(preAssignValidTable).toBe(true); + expect(postAssignValidTable).toBe(true); + }); + }); +}); diff --git a/client/tests/e2e/smokeTest.e2e.test.js b/client/tests/e2e/smokeTest.e2e.test.js new file mode 100644 index 000000000..b49f8bbd5 --- /dev/null +++ b/client/tests/e2e/smokeTest.e2e.test.js @@ -0,0 +1,97 @@ +import getPage from '../util/getPage'; + +describe('smoke test', () => { + it('end-to-end tests can simultaneously sign in with all roles', async () => { + await Promise.all([ + getPage({ role: 'admin', url: '/test-queue' }, async page => { + // Only admins can remove rows from the test queue + await page.waitForSelector( + 'td [type="button"] ::-p-text(Delete Report)' + ); + }), + + getPage({ role: 'tester', url: '/test-queue' }, async page => { + // Testers can assign themselves + await page.waitForSelector('table ::-p-text(Assign Yourself)'); + const adminOnlyRemoveButton = await page.$( + 'td [type="button"] ::-p-text(Delete Report)' + ); + expect(adminOnlyRemoveButton).toBe(null); + }), + + getPage( + { role: 'vendor', url: '/test-queue' }, + async (page, { baseUrl }) => { + // Vendors get the same test queue as signed-out users + await page.waitForSelector('button ::-p-text(V22.04.14)'); + await page.click('button ::-p-text(V22.04.14)'); + + await page.waitForSelector('td [role="button"]'); + const buttonText = await page.$eval( + 'td [role="button"]', + button => button.textContent + ); + expect(buttonText).toEqual('View Tests'); + + // Unlike signed-out users, they will get tables on this page + await page.goto(`${baseUrl}/candidate-review`); + await page.waitForSelector('table'); + } + ), + + getPage({ role: false, url: '/test-queue' }, async page => { + // Signed-out users can only view tests, not run them + await page.waitForSelector('td [role="button"] ::-p-text(View Tests)'); + }) + ]); + }); + + it('loads various pages without crashing', async () => { + await Promise.all([ + getPage({ role: false, url: '/' }, async page => { + await page.waitForSelector('h1'); + const h1Handle = await page.waitForSelector('h1'); + const h1Text = await h1Handle.evaluate(h1 => h1.innerText); + expect(h1Text).toBe( + 'Enabling Interoperability for Assistive Technology Users' + ); + }), + getPage({ role: false, url: '/reports' }, async page => { + // Wait for an h2 because an h1 will show while the page is + // still loading + await page.waitForSelector('h2'); + const h1Handle = await page.waitForSelector('h1'); + const h1Text = await h1Handle.evaluate(h1 => h1.innerText); + expect(h1Text).toBe('Assistive Technology Interoperability Reports'); + }), + getPage({ role: false, url: '/data-management' }, async page => { + // Wait for an h2 because an h1 will show while the page is + // still loading + await page.waitForSelector('h2'); + const h1Handle = await page.waitForSelector('h1'); + const h1Text = await h1Handle.evaluate(h1 => h1.innerText); + expect(h1Text).toBe('Data Management'); + }), + getPage({ role: false, url: '/test-plan-report/15' }, async page => { + // Wait for an h2 because an h1 will show while the page is + // still loading + await page.waitForSelector('h2'); + const h1Handle = await page.waitForSelector('h1'); + const h1Text = await h1Handle.evaluate(h1 => h1.innerText); + expect(h1Text).toBe( + 'Test 1:\nNavigate forwards to a not pressed toggle button' + ); + }), + getPage({ role: 'admin', url: '/test-plan-report/15' }, async page => { + // Wait for an h2 because an h1 will show while the page is + // still loading + await page.waitForSelector('h2'); + const h1Handle = await page.waitForSelector('h1'); + const h1Text = await h1Handle.evaluate(h1 => h1.innerText); + expect(h1Text).toBe( + 'Test 1:\nNavigate forwards to a not pressed toggle button' + ); + }) + ]); + }); +}); diff --git a/client/tests/smokeTest.test.js b/client/tests/smokeTest.test.js deleted file mode 100644 index 3818b1d3b..000000000 --- a/client/tests/smokeTest.test.js +++ /dev/null @@ -1,117 +0,0 @@ -import getPage from './util/getPage'; - -describe('smoke test', () => { - it('end-to-end tests can simultaneously sign in with all roles [old]', async () => { - await Promise.all([ - getPage({ role: 'admin', url: '/test-queue-old' }, async page => { - // Only admins can remove rows from the test queue - await page.waitForSelector('td.actions ::-p-text(Remove)'); - }), - - getPage({ role: 'tester', url: '/test-queue-old' }, async page => { - // Testers can assign themselves - await page.waitForSelector('table ::-p-text(Assign Yourself)'); - const adminOnlyRemoveButton = await page.$( - 'td.actions ::-p-text(Remove)' - ); - expect(adminOnlyRemoveButton).toBe(null); - }), - - getPage( - { role: 'vendor', url: '/test-queue-old' }, - async (page, { baseUrl }) => { - // Vendors get the same test queue as signed-out users - await page.waitForSelector( - 'td.actions ::-p-text(View tests)' - ); - // Unlike signed-out users, they will get tables on this page - await page.goto(`${baseUrl}/candidate-review`); - await page.waitForSelector('table'); - } - ), - - getPage({ role: false, url: '/test-queue-old' }, async page => { - // Signed-out users can only view tests, not run them - await page.waitForSelector('td.actions ::-p-text(View tests)'); - }) - ]); - }); - - it('end-to-end tests can simultaneously sign in with all roles', async () => { - await Promise.all([ - getPage({ role: 'admin', url: '/test-queue' }, async page => { - // Only admins can remove rows from the test queue - await page.waitForSelector( - 'td [type="button"] ::-p-text(Delete Report)' - ); - }), - - getPage({ role: 'tester', url: '/test-queue' }, async page => { - // Testers can assign themselves - await page.waitForSelector('table ::-p-text(Assign Yourself)'); - const adminOnlyRemoveButton = await page.$( - 'td [type="button"] ::-p-text(Delete Report)' - ); - expect(adminOnlyRemoveButton).toBe(null); - }), - - getPage( - { role: 'vendor', url: '/test-queue' }, - async (page, { baseUrl }) => { - // Vendors get the same test queue as signed-out users - await page.waitForSelector('button ::-p-text(V22.04.14)'); - await page.click('button ::-p-text(V22.04.14)'); - - await page.waitForSelector('td [role="button"]'); - const buttonText = await page.$eval( - 'td [role="button"]', - button => button.textContent - ); - expect(buttonText).toEqual('View Tests'); - - // Unlike signed-out users, they will get tables on this page - await page.goto(`${baseUrl}/candidate-review`); - await page.waitForSelector('table'); - } - ), - - getPage({ role: false, url: '/test-queue' }, async page => { - // Signed-out users can only view tests, not run them - await page.waitForSelector( - 'td [role="button"] ::-p-text(View Tests)' - ); - }) - ]); - }); - - it('loads various pages without crashing', async () => { - await Promise.all([ - getPage({ role: false, url: '/' }, async page => { - await page.waitForSelector('h1'); - const h1Handle = await page.waitForSelector('h1'); - const h1Text = await h1Handle.evaluate(h1 => h1.innerText); - expect(h1Text).toBe( - 'Enabling Interoperability for Assistive Technology Users' - ); - }), - getPage({ role: false, url: '/reports' }, async page => { - // Wait for an h2 because an h1 will show while the page is - // still loading - await page.waitForSelector('h2'); - const h1Handle = await page.waitForSelector('h1'); - const h1Text = await h1Handle.evaluate(h1 => h1.innerText); - expect(h1Text).toBe( - 'Assistive Technology Interoperability Reports' - ); - }), - getPage({ role: false, url: '/data-management' }, async page => { - // Wait for an h2 because an h1 will show while the page is - // still loading - await page.waitForSelector('h2'); - const h1Handle = await page.waitForSelector('h1'); - const h1Text = await h1Handle.evaluate(h1 => h1.innerText); - expect(h1Text).toBe('Data Management'); - }) - ]); - }); -}); diff --git a/client/tests/util/getPage.js b/client/tests/util/getPage.js index 6ce14f783..8e841d7c5 100644 --- a/client/tests/util/getPage.js +++ b/client/tests/util/getPage.js @@ -15,98 +15,96 @@ const AUTOMATION_SCHEDULER_PORT = 8833; const baseUrl = `http://localhost:${CLIENT_PORT}`; const startServer = async serverOrClient => { - return new Promise(resolve => { - const server = spawn( - 'yarn', - serverOrClient === 'server' - ? ['workspace', 'server', 'dev-debug'] - : ['workspace', 'client', 'dev'], - { - cwd: path.resolve(__dirname, '../../'), - env: { - PATH: process.env.PATH, - PORT, - CLIENT_PORT, - AUTOMATION_SCHEDULER_PORT, - API_SERVER: `http://localhost:${PORT}`, - APP_SERVER: baseUrl, - AUTOMATION_SCHEDULER_URL: `http://localhost:${AUTOMATION_SCHEDULER_PORT}`, - PGDATABASE: 'aria_at_report_test', - PGPORT: 5432, - ENVIRONMENT: 'test' - } - } - ); - - server.on('error', error => { - throw new Error('Error raised by startServer process', { - cause: error - }); - }); + return new Promise(resolve => { + const server = spawn( + 'yarn', + serverOrClient === 'server' + ? ['workspace', 'server', 'dev-debug'] + : ['workspace', 'client', 'dev'], + { + cwd: path.resolve(__dirname, '../../'), + env: { + PATH: process.env.PATH, + PORT, + CLIENT_PORT, + AUTOMATION_SCHEDULER_PORT, + API_SERVER: `http://localhost:${PORT}`, + APP_SERVER: baseUrl, + AUTOMATION_SCHEDULER_URL: `http://localhost:${AUTOMATION_SCHEDULER_PORT}`, + PGDATABASE: 'aria_at_report_test', + PGPORT: 5432, + ENVIRONMENT: 'test' + } + } + ); - server.on('exit', (code, signal) => { - if (code) { - console.error('startServer exited with code', code); - } else if (signal) { - console.error('startServer was killed with signal', signal); - } else { - console.info('startServer exited with no errors.'); // eslint-disable-line no-console - } - }); + server.on('error', error => { + throw new Error('Error raised by startServer process', { + cause: error + }); + }); - const killServer = async () => { - await new Promise((resolve, reject) => { - treeKill(server.pid, error => { - if (error) return reject(error); - resolve(); - }); - }); - }; - - server.stdout.on('data', data => { - const output = stripAnsi(data.toString()); - console.info(output); // eslint-disable-line no-console - - if ( - (serverOrClient === 'server' && - output.includes(`Listening on ${PORT}`)) || - (serverOrClient === 'client' && - output.includes('compiled successfully')) - ) { - resolve({ close: killServer }); - } - }); + server.on('exit', (code, signal) => { + if (code) { + console.error('startServer exited with code', code); + } else if (signal) { + console.error('startServer was killed with signal', signal); + } else { + console.info('startServer exited with no errors.'); // eslint-disable-line no-console + } + }); - server.stderr.on('data', data => { - const output = data.toString(); - console.info(output); // eslint-disable-line no-console + const killServer = async () => { + await new Promise((resolve, reject) => { + treeKill(server.pid, error => { + if (error) return reject(error); + resolve(); }); + }); + }; + + server.stdout.on('data', data => { + const output = stripAnsi(data.toString()); + console.info(output); // eslint-disable-line no-console + + if ( + (serverOrClient === 'server' && + output.includes(`Listening on ${PORT}`)) || + (serverOrClient === 'client' && + output.includes('compiled successfully')) + ) { + resolve({ close: killServer }); + } }); + + server.stderr.on('data', data => { + const output = data.toString(); + console.info(output); // eslint-disable-line no-console + }); + }); }; const setup = async () => { - // eslint-disable-next-line no-console - console.info( - 'Starting dev servers. This is required for end-to-end testing' - ); - - [clientServer, backendServer] = await Promise.all([ - startServer('client'), - startServer('server') - ]); - - return puppeteer.launch({ - headless: isDebugMode ? false : 'new', - args: ['--no-sandbox'], // Required for GitHub environment - devtools: isDebugMode // Allows `debugger;` statements to work - }); + // eslint-disable-next-line no-console + console.info('Starting dev servers. This is required for end-to-end testing'); + + [clientServer, backendServer] = await Promise.all([ + startServer('client'), + startServer('server') + ]); + + return puppeteer.launch({ + headless: isDebugMode ? false : 'new', + args: ['--no-sandbox'], // Required for GitHub environment + devtools: isDebugMode // Allows `debugger;` statements to work + }); }; const teardown = async () => { - await Promise.all([backendServer.close(), clientServer.close()]); + await Promise.all([backendServer.close(), clientServer.close()]); - // Browser might not be defined, if it failed to start - if (global.browser) await global.browser.close(); + // Browser might not be defined, if it failed to start + if (global.browser) await global.browser.close(); }; let incognitoContexts = {}; @@ -123,60 +121,60 @@ let incognitoContexts = {}; * needed for navigation. */ const getPage = async (options, callback) => { - const { role, url } = options; - if (role == null || !['admin', 'tester', 'vendor', false].includes(role)) { - throw new Error('Please provide a valid role'); - } - - const foundExistingIncognitoContext = !!incognitoContexts[role]; + const { role, url } = options; + if (role == null || !['admin', 'tester', 'vendor', false].includes(role)) { + throw new Error('Please provide a valid role'); + } - if (!foundExistingIncognitoContext) { - incognitoContexts[role] = - await global.browser.createIncognitoBrowserContext(); - } - const incognitoContext = incognitoContexts[role]; + const foundExistingIncognitoContext = !!incognitoContexts[role]; - const page = await incognitoContext.newPage(); + if (!foundExistingIncognitoContext) { + incognitoContexts[role] = + await global.browser.createIncognitoBrowserContext(); + } + const incognitoContext = incognitoContexts[role]; - if (!url) { - throw new Error('Please provide a URL, even if it it is simply "/"'); - } + const page = await incognitoContext.newPage(); - if (isDebugMode) { - page.setDefaultTimeout(604800000 /* a week should be enough */); - } + if (!url) { + throw new Error('Please provide a URL, even if it it is simply "/"'); + } - await page.goto(`${baseUrl}${url}`); + if (isDebugMode) { + page.setDefaultTimeout(604800000 /* a week should be enough */); + } - await page.waitForNetworkIdle(); + await page.goto(`${baseUrl}${url}`); - await page.evaluate('startTestTransaction()'); + await page.waitForNetworkIdle(); - if (role && !foundExistingIncognitoContext) { - await page.waitForSelector('::-p-text(Sign in with GitHub)'); + await page.evaluate('startTestTransaction()'); - const username = `joe-the-${role}`; + if (role && !foundExistingIncognitoContext) { + await page.waitForSelector('::-p-text(Sign in with GitHub)'); - if (role === 'admin') { - await page.evaluate(`signMeInAsAdmin("${username}")`); - } - if (role === 'tester') { - await page.evaluate(`signMeInAsTester("${username}")`); - } - if (role === 'vendor') { - await page.evaluate(`signMeInAsVendor("${username}")`); - } + const username = `joe-the-${role}`; - await page.waitForSelector('::-p-text(Signed in)'); + if (role === 'admin') { + await page.evaluate(`signMeInAsAdmin("${username}")`); } - - try { - await callback(page, { baseUrl }); - } finally { - await page.evaluate('endTestTransaction()'); + if (role === 'tester') { + await page.evaluate(`signMeInAsTester("${username}")`); } + if (role === 'vendor') { + await page.evaluate(`signMeInAsVendor("${username}")`); + } + + await page.waitForSelector('::-p-text(Signed in)'); + } + + try { + await callback(page, { baseUrl }); + } finally { + await page.evaluate('endTestTransaction()'); + } - await page.close(); + await page.close(); }; module.exports = getPage; diff --git a/client/tests/util/index.js b/client/tests/util/index.js index a527bf716..96c576e9f 100644 --- a/client/tests/util/index.js +++ b/client/tests/util/index.js @@ -1,3 +1,3 @@ export const findByTestAttr = function (wrapper, val) { - return wrapper.find(`[data-test="${val}"]`); + return wrapper.find(`[data-test="${val}"]`); }; diff --git a/client/tests/util/jestGlobalSetup.js b/client/tests/util/jestGlobalSetup.js index 192c96945..18b1276ba 100644 --- a/client/tests/util/jestGlobalSetup.js +++ b/client/tests/util/jestGlobalSetup.js @@ -6,18 +6,18 @@ const { setup } = require('./getPage'); const isDebugMode = process.argv.some(arg => arg.startsWith('--testTimeout')); module.exports = async () => { - const browser = await setup(); + const browser = await setup(); - // See https://jestjs.io/docs/puppeteer for more information + // See https://jestjs.io/docs/puppeteer for more information - // store the browser instance so we can teardown it later - // this global is only available in the teardown but not in TestEnvironments - global.browser = browser; + // store the browser instance so we can teardown it later + // this global is only available in the teardown but not in TestEnvironments + global.browser = browser; - if (!isDebugMode) { - // use the file system to expose the wsEndpoint for TestEnvironments - const DIR = path.join(os.tmpdir(), 'jest_puppeteer_global_setup'); - await mkdir(DIR, { recursive: true }); - await writeFile(path.join(DIR, 'wsEndpoint'), browser.wsEndpoint()); - } + if (!isDebugMode) { + // use the file system to expose the wsEndpoint for TestEnvironments + const DIR = path.join(os.tmpdir(), 'jest_puppeteer_global_setup'); + await mkdir(DIR, { recursive: true }); + await writeFile(path.join(DIR, 'wsEndpoint'), browser.wsEndpoint()); + } }; diff --git a/client/tests/util/jestGlobalTeardown.js b/client/tests/util/jestGlobalTeardown.js index bd59a209f..a2b66a252 100644 --- a/client/tests/util/jestGlobalTeardown.js +++ b/client/tests/util/jestGlobalTeardown.js @@ -4,10 +4,10 @@ const path = require('path'); const { teardown } = require('./getPage'); module.exports = async () => { - // See https://jestjs.io/docs/puppeteer for more information - // clean-up the wsEndpoint file - const DIR = path.join(os.tmpdir(), 'jest_puppeteer_global_setup'); - await fs.rm(DIR, { recursive: true, force: true }); + // See https://jestjs.io/docs/puppeteer for more information + // clean-up the wsEndpoint file + const DIR = path.join(os.tmpdir(), 'jest_puppeteer_global_setup'); + await fs.rm(DIR, { recursive: true, force: true }); - await teardown(); + await teardown(); }; diff --git a/client/tests/util/jestPuppeteer.js b/client/tests/util/jestPuppeteer.js index c9a25f9c1..337fa5f17 100644 --- a/client/tests/util/jestPuppeteer.js +++ b/client/tests/util/jestPuppeteer.js @@ -11,40 +11,40 @@ const isDebugMode = process.argv.some(arg => arg.startsWith('--testTimeout')); const DIR = path.join(os.tmpdir(), 'jest_puppeteer_global_setup'); class PuppeteerEnvironment extends NodeEnvironment { - constructor(config) { - super(config); + constructor(config) { + super(config); + } + + async setup() { + await super.setup(); + // get the wsEndpoint + const wsEndpoint = await readFile(path.join(DIR, 'wsEndpoint'), 'utf8'); + if (!wsEndpoint) { + throw new Error('wsEndpoint not found'); } - async setup() { - await super.setup(); - // get the wsEndpoint - const wsEndpoint = await readFile(path.join(DIR, 'wsEndpoint'), 'utf8'); - if (!wsEndpoint) { - throw new Error('wsEndpoint not found'); - } - - // connect to puppeteer - this.global.browser = await puppeteer.connect({ - browserWSEndpoint: wsEndpoint - }); - } + // connect to puppeteer + this.global.browser = await puppeteer.connect({ + browserWSEndpoint: wsEndpoint + }); + } - async teardown() { - if (this.global.browser) { - this.global.browser.disconnect(); - } - await super.teardown(); + async teardown() { + if (this.global.browser) { + this.global.browser.disconnect(); } + await super.teardown(); + } - getVmContext() { - return super.getVmContext(); - } + getVmContext() { + return super.getVmContext(); + } } class DoNothingEnvironment extends NodeEnvironment { - constructor(config) { - super(config); - } + constructor(config) { + super(config); + } } module.exports = isDebugMode ? DoNothingEnvironment : PuppeteerEnvironment; diff --git a/client/tests/util/stripAnsi.js b/client/tests/util/stripAnsi.js index 1480dd458..302a8aa7b 100644 --- a/client/tests/util/stripAnsi.js +++ b/client/tests/util/stripAnsi.js @@ -1,12 +1,12 @@ // borrowed from ansi-regex / strip-ansi / chalk but reproduced due to ESM vs require function ansiRegex({ onlyFirst = false } = {}) { - const pattern = [ - '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)', - '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))' - ].join('|'); + const pattern = [ + '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)', + '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))' + ].join('|'); - return new RegExp(pattern, onlyFirst ? undefined : 'g'); + return new RegExp(pattern, onlyFirst ? undefined : 'g'); } const re = ansiRegex(); diff --git a/client/utils/DisplayNone.jsx b/client/utils/DisplayNone.jsx index 6a3888df4..83f7322fb 100644 --- a/client/utils/DisplayNone.jsx +++ b/client/utils/DisplayNone.jsx @@ -2,11 +2,11 @@ import React from 'react'; import PropTypes from 'prop-types'; const DisplayNone = ({ children }) => { - return
    {children}
    ; + return
    {children}
    ; }; DisplayNone.propTypes = { - children: PropTypes.node.isRequired + children: PropTypes.node.isRequired }; export default DisplayNone; diff --git a/client/utils/ScrollFixer.jsx b/client/utils/ScrollFixer.jsx index aec5334f4..3f84f6d5b 100644 --- a/client/utils/ScrollFixer.jsx +++ b/client/utils/ScrollFixer.jsx @@ -9,61 +9,61 @@ import { useLocation } from 'react-router'; * that it must be inside React Router's Router component. */ const ScrollFixer = ({ children }) => { - const location = useLocation(); + const location = useLocation(); - useLayoutEffect(() => { - const scrollTop = () => { - window.scroll(0, 0); - // When switching pages, the focus should jump to the top. Otherwise - // screen readers' focus might be lingering in a nonsensical - // location partly down the page. - document.querySelector('a').focus(); - }; - if (!location.hash) return scrollTop(); - (async () => { - // The point at which the window jumping down the page would become - // disorienting. This must include time for the page's API requests - // to complete. - const timeout = 8000; - const element = await pollForElement(location.hash, { timeout }); - if (!element) return scrollTop(); - element.scrollIntoView(); - element.focus(); - })(); - }, [location]); + useLayoutEffect(() => { + const scrollTop = () => { + window.scroll(0, 0); + // When switching pages, the focus should jump to the top. Otherwise + // screen readers' focus might be lingering in a nonsensical + // location partly down the page. + document.querySelector('a').focus(); + }; + if (!location.hash) return scrollTop(); + (async () => { + // The point at which the window jumping down the page would become + // disorienting. This must include time for the page's API requests + // to complete. + const timeout = 8000; + const element = await pollForElement(location.hash, { timeout }); + if (!element) return scrollTop(); + element.scrollIntoView(); + element.focus(); + })(); + }, [location]); - return children; + return children; }; ScrollFixer.propTypes = { - children: PropTypes.node.isRequired + children: PropTypes.node.isRequired }; const pollForElement = async (selector, { timeout }) => { - let element = document.querySelector(selector); - if (element) return element; + let element = document.querySelector(selector); + if (element) return element; - let timeoutExceeded = false; - window.setTimeout(() => { - timeoutExceeded = true; - }, timeout); + let timeoutExceeded = false; + window.setTimeout(() => { + timeoutExceeded = true; + }, timeout); - return new Promise(resolve => { - const interval = window.setInterval(() => { - element = document.querySelector(selector); + return new Promise(resolve => { + const interval = window.setInterval(() => { + element = document.querySelector(selector); - if (element) { - clearInterval(interval); - resolve(element); - return; - } + if (element) { + clearInterval(interval); + resolve(element); + return; + } - if (timeoutExceeded) { - clearInterval(interval); - resolve(null); - } - }, 10); - }); + if (timeoutExceeded) { + clearInterval(interval); + resolve(null); + } + }, 10); + }); }; export default ScrollFixer; diff --git a/client/utils/alphabetizeObjectBy.js b/client/utils/alphabetizeObjectBy.js index 9d4968f36..5a26a2319 100644 --- a/client/utils/alphabetizeObjectBy.js +++ b/client/utils/alphabetizeObjectBy.js @@ -11,12 +11,12 @@ * @returns - The same object but with its keys sorted. */ const alphabetizeObjectBy = (object, getString) => { - return Object.fromEntries( - Object.entries(object).sort((a, b) => { - // https://stackoverflow.com/a/45544166/3888572 - return getString(a).localeCompare(getString(b)); - }) - ); + return Object.fromEntries( + Object.entries(object).sort((a, b) => { + // https://stackoverflow.com/a/45544166/3888572 + return getString(a).localeCompare(getString(b)); + }) + ); }; export default alphabetizeObjectBy; diff --git a/client/utils/alphabetizeObjectBy.test.js b/client/utils/alphabetizeObjectBy.test.js index 419e3ff4d..8b801727d 100644 --- a/client/utils/alphabetizeObjectBy.test.js +++ b/client/utils/alphabetizeObjectBy.test.js @@ -1,22 +1,22 @@ import alphabetizeObjectBy from './alphabetizeObjectBy'; describe('alphabetizeObjectBy', () => { - it('alphabetizes an object', () => { - const before = { z: 1, y: 2, x: 3 }; - const after = alphabetizeObjectBy(before, ([key]) => key); - expect(Object.entries(after)).toEqual([ - ['x', 3], - ['y', 2], - ['z', 1] - ]); - }); + it('alphabetizes an object', () => { + const before = { z: 1, y: 2, x: 3 }; + const after = alphabetizeObjectBy(before, ([key]) => key); + expect(Object.entries(after)).toEqual([ + ['x', 3], + ['y', 2], + ['z', 1] + ]); + }); - it('returns a shallow copy', () => { - const identicalObject = {}; - const before = { c: 1, b: 2, a: identicalObject }; - const after = alphabetizeObjectBy(before, ([key]) => key); - identicalObject.testAddingAProperty = true; - expect(after.a).toBe(identicalObject); - expect(after.a.testAddingAProperty).toBe(true); - }); + it('returns a shallow copy', () => { + const identicalObject = {}; + const before = { c: 1, b: 2, a: identicalObject }; + const after = alphabetizeObjectBy(before, ([key]) => key); + identicalObject.testAddingAProperty = true; + expect(after.a).toBe(identicalObject); + expect(after.a.testAddingAProperty).toBe(true); + }); }); diff --git a/client/utils/aria.js b/client/utils/aria.js index 6118c86f5..6a9b97d8e 100644 --- a/client/utils/aria.js +++ b/client/utils/aria.js @@ -1,25 +1,25 @@ export const evaluateAtNameKey = atName => { - // Could probably add back support for AT keys from the database level - if (atName.toLowerCase().includes('voiceover')) return 'voiceover_macos'; - else return atName.toLowerCase(); + // Could probably add back support for AT keys from the database level + if (atName.toLowerCase().includes('voiceover')) return 'voiceover_macos'; + else return atName.toLowerCase(); }; export const buildTestPageUrl = (gitSha, directory, testReferencePath) => { - const BASE_PATH = '/aria-at'; - return `${BASE_PATH}/${gitSha}/build/tests/${directory}/${testReferencePath}`; + const BASE_PATH = '/aria-at'; + return `${BASE_PATH}/${gitSha}/build/tests/${directory}/${testReferencePath}`; }; export const derivePhaseName = name => { - switch (name) { - case 'RD': - return 'R&D'; - case 'DRAFT': - return 'Draft'; - case 'CANDIDATE': - return 'Candidate'; - case 'RECOMMENDED': - return 'Recommended'; - case 'DEPRECATED': - return 'Deprecated'; - } + switch (name) { + case 'RD': + return 'R&D'; + case 'DRAFT': + return 'Draft'; + case 'CANDIDATE': + return 'Candidate'; + case 'RECOMMENDED': + return 'Recommended'; + case 'DEPRECATED': + return 'Deprecated'; + } }; diff --git a/client/utils/automation.js b/client/utils/automation.js index 71d3d978e..208091473 100644 --- a/client/utils/automation.js +++ b/client/utils/automation.js @@ -1,16 +1,79 @@ +/** + * Checks if the AT, browser, and AT version requirements are supported by the automation + * @param {object} ctx + * @param {object} ctx.at + * @param {object} ctx.browser + * @param {object} ctx.minimumAtVersion optional + * @param {object} ctx.exactAtVersion optional + * @returns {boolean} + */ export const isSupportedByResponseCollector = ctx => { - if (!ctx || !ctx.at || !ctx.browser) { - return false; - } - const { - at: { key: atKey }, - browser: { key: browserKey } - } = ctx; - return ( - (atKey === 'nvda' && - (browserKey === 'chrome' || browserKey === 'firefox')) || - (atKey === 'voiceover_macos' && browserKey === 'safari_macos') + if (!ctx || !ctx.at || !ctx.browser) { + return false; + } + + const { at, browser } = ctx; + const { key: atKey } = at; + const { key: browserKey } = browser; + + // Check if the AT and browser are supported by the automation + const isNvdaWithSupportedBrowser = + atKey === 'nvda' && (browserKey === 'chrome' || browserKey === 'firefox'); + const isVoiceOverMacWithSafari = + atKey === 'voiceover_macos' && browserKey === 'safari_macos'; + + if (!isNvdaWithSupportedBrowser && !isVoiceOverMacWithSafari) { + return false; + } + + // If there are version requirements, check if they are supported by automation + if (ctx.minimumAtVersion || ctx.exactAtVersion) { + return atVersionRequirementsSupportedByAutomation(ctx); + } + return true; +}; + +/** + * Checks if the version requirements are supported by automation + * @param {object} ctx + * @param {object} ctx.at + * @param {object} ctx.minimumAtVersion + * @param {object} ctx.exactAtVersion + * @returns {boolean} + */ +const atVersionRequirementsSupportedByAutomation = ({ + at, + minimumAtVersion, + exactAtVersion +}) => { + if (!at || !(exactAtVersion || minimumAtVersion)) { + return false; + } + + if (minimumAtVersion && exactAtVersion) { + console.warn( + 'Both minimumAtVersion and exactAtVersion are set. This is invalid. Using exactAtVersion' + ); + minimumAtVersion = null; + } + + const { atVersions } = at; + if (!atVersions) { + console.warn( + 'Version requirements check was done without atVersions in the supplied at object' + ); + return false; + } + + if (exactAtVersion) { + return exactAtVersion.supportedByAutomation; + } else { + return atVersions.some( + version => + version.supportedByAutomation && + new Date(version.releasedAt) >= new Date(minimumAtVersion.releasedAt) ); + } }; /** @@ -20,13 +83,13 @@ export const isSupportedByResponseCollector = ctx => { * @returns {string|undefined} */ export const getBotUsernameFromAtBrowser = (at, browser) => { - if ( - at?.key === 'nvda' && - (browser?.key === 'chrome' || browser?.key === 'firefox') - ) { - return 'NVDA Bot'; - } - if (at?.key === 'voiceover_macos' && browser?.key === 'safari_macos') { - return 'VoiceOver Bot'; - } + if ( + at?.key === 'nvda' && + (browser?.key === 'chrome' || browser?.key === 'firefox') + ) { + return 'NVDA Bot'; + } + if (at?.key === 'voiceover_macos' && browser?.key === 'safari_macos') { + return 'VoiceOver Bot'; + } }; diff --git a/client/utils/calculatePercentComplete.js b/client/utils/calculatePercentComplete.js index f437e0a3c..941c19a55 100644 --- a/client/utils/calculatePercentComplete.js +++ b/client/utils/calculatePercentComplete.js @@ -1,14 +1,14 @@ export const calculatePercentComplete = ({ metrics, draftTestPlanRuns }) => { - if (!metrics || !draftTestPlanRuns) return 0; - const assignedUserCount = draftTestPlanRuns.length || 1; - const totalTestsPossible = metrics.testsCount * assignedUserCount; - let totalTestsCompleted = 0; - draftTestPlanRuns.forEach(draftTestPlanRun => { - totalTestsCompleted += draftTestPlanRun.testResults.filter( - ({ completedAt }) => !!completedAt - ).length; - }); - const percentage = (totalTestsCompleted / totalTestsPossible) * 100; - if (isNaN(percentage) || !isFinite(percentage)) return 0; - return Math.floor(percentage); + if (!metrics || !draftTestPlanRuns) return 0; + const assignedUserCount = draftTestPlanRuns.length || 1; + const totalTestsPossible = metrics.testsCount * assignedUserCount; + let totalTestsCompleted = 0; + draftTestPlanRuns.forEach(draftTestPlanRun => { + totalTestsCompleted += draftTestPlanRun.testResults.filter( + ({ completedAt }) => !!completedAt + ).length; + }); + const percentage = (totalTestsCompleted / totalTestsPossible) * 100; + if (isNaN(percentage) || !isFinite(percentage)) return 0; + return Math.floor(percentage); }; diff --git a/client/utils/collectionJobStatus.js b/client/utils/collectionJobStatus.js new file mode 100644 index 000000000..c0a251ef0 --- /dev/null +++ b/client/utils/collectionJobStatus.js @@ -0,0 +1,16 @@ +// server side version of this enum. +// don't forget to also update the client side version in +// server/util/enums.js + +export const COLLECTION_JOB_STATUS = { + QUEUED: 'QUEUED', + RUNNING: 'RUNNING', + COMPLETED: 'COMPLETED', + ERROR: 'ERROR', + CANCELLED: 'CANCELLED' +}; + +export const isJobStatusFinal = status => + status === COLLECTION_JOB_STATUS.COMPLETED || + status === COLLECTION_JOB_STATUS.CANCELLED || + status === COLLECTION_JOB_STATUS.ERROR; diff --git a/client/utils/constants.js b/client/utils/constants.js index 8f496c7cf..95ac65805 100644 --- a/client/utils/constants.js +++ b/client/utils/constants.js @@ -1,6 +1,6 @@ export const TEST_PLAN_VERSION_PHASES = { - RD: 'RD', - DRAFT: 'DRAFT', - CANDIDATE: 'CANDIDATE', - RECOMMENDED: 'RECOMMENDED' + RD: 'RD', + DRAFT: 'DRAFT', + CANDIDATE: 'CANDIDATE', + RECOMMENDED: 'RECOMMENDED' }; diff --git a/client/utils/createIssueLink.js b/client/utils/createIssueLink.js index 4085b398f..a2bd73d10 100644 --- a/client/utils/createIssueLink.js +++ b/client/utils/createIssueLink.js @@ -1,12 +1,12 @@ const GITHUB_ISSUES_URL = - process.env.ENVIRONMENT === 'production' - ? 'https://github.com/w3c/aria-at' - : 'https://github.com/bocoup/aria-at'; + process.env.ENVIRONMENT === 'production' + ? 'https://github.com/w3c/aria-at' + : 'https://github.com/bocoup/aria-at'; const atLabelMap = { - 'VoiceOver for macOS': 'vo', - JAWS: 'jaws', - NVDA: 'nvda' + 'VoiceOver for macOS': 'vo', + JAWS: 'jaws', + NVDA: 'nvda' }; /** @@ -32,122 +32,122 @@ const atLabelMap = { * @throws {Error} If required parameters are missing */ const createIssueLink = ({ - isCandidateReview = false, - isCandidateReviewChangesRequested = false, - testPlanDirectory, - testPlanTitle, - versionString, - testTitle = null, - testSequenceNumber = null, - testRowNumber = null, - testRenderedUrl = null, - atName, - atVersionName = null, - browserName = null, - browserVersionName = null, - conflictMarkdown = null, - reportLink = null + isCandidateReview = false, + isCandidateReviewChangesRequested = false, + testPlanDirectory, + testPlanTitle, + versionString, + testTitle = null, + testSequenceNumber = null, + testRowNumber = null, + testRenderedUrl = null, + atName, + atVersionName = null, + browserName = null, + browserVersionName = null, + conflictMarkdown = null, + reportLink = null }) => { - if (!(testPlanDirectory || testPlanTitle || versionString || atName)) { - throw new Error('Cannot create issue link due to missing parameters'); - } - - const hasTest = !!( - testTitle && - testSequenceNumber && - testRowNumber && - testRenderedUrl - ); - - let title; - if (hasTest) { - let titleStart; - if (isCandidateReview) { - titleStart = isCandidateReviewChangesRequested - ? `${atName} Changes Requested` - : `${atName} Feedback`; - } else { - titleStart = 'Feedback'; - } - - title = - `${titleStart}: "${testTitle}" (${testPlanTitle}, ` + - `Test ${testSequenceNumber}, ${versionString})`; + if (!(testPlanDirectory || testPlanTitle || versionString || atName)) { + throw new Error('Cannot create issue link due to missing parameters'); + } + + const hasTest = !!( + testTitle && + testSequenceNumber && + testRowNumber && + testRenderedUrl + ); + + let title; + if (hasTest) { + let titleStart; + if (isCandidateReview) { + titleStart = isCandidateReviewChangesRequested + ? `${atName} Changes Requested` + : `${atName} Feedback`; } else { - title = `${atName} General Feedback: ${testPlanTitle} ${versionString}`; + titleStart = 'Feedback'; } - const labels = - (isCandidateReview ? 'candidate-review,' : '') + - `${atLabelMap[atName]},` + - (isCandidateReviewChangesRequested ? 'changes-requested' : 'feedback'); + title = + `${titleStart}: "${testTitle}" (${testPlanTitle}, ` + + `Test ${testSequenceNumber}, ${versionString})`; + } else { + title = `${atName} General Feedback: ${testPlanTitle} ${versionString}`; + } + + const labels = + (isCandidateReview ? 'candidate-review,' : '') + + `${atLabelMap[atName]},` + + (isCandidateReviewChangesRequested ? 'changes-requested' : 'feedback'); + + let reportLinkFormatted = ''; + if (reportLink) { + reportLinkFormatted = `- Report Page: [Link](${reportLink})\n`; + } + + let testSetupFormatted = ''; + if (hasTest) { + // TODO: fix renderedUrl + let modifiedRenderedUrl = testRenderedUrl.replace( + /.+(?=\/tests)/, + 'https://aria-at.netlify.app' + ); - let reportLinkFormatted = ''; - if (reportLink) { - reportLinkFormatted = `- Report Page: [Link](${reportLink})\n`; - } + const shortenedUrl = modifiedRenderedUrl?.match(/[^/]+$/)[0]; - let testSetupFormatted = ''; - if (hasTest) { - // TODO: fix renderedUrl - let modifiedRenderedUrl = testRenderedUrl.replace( - /.+(?=\/tests)/, - 'https://aria-at.netlify.app' - ); - - const shortenedUrl = modifiedRenderedUrl?.match(/[^/]+$/)[0]; - - let atFormatted; - if (atVersionName) { - atFormatted = `- AT: ${atName} (Version ${atVersionName})\n`; - } else { - atFormatted = `- AT: ${atName}\n`; - } - - let browserFormatted = ''; - if (browserName && browserVersionName) { - browserFormatted = `- Browser: ${browserName} (Version ${browserVersionName})\n`; - } else if (browserName) { - browserFormatted = `- Browser: ${browserName}\n`; - } - - testSetupFormatted = - `## Test Setup\n\n` + - `- Test File: ` + - `[${shortenedUrl}](${modifiedRenderedUrl})\n` + - reportLinkFormatted + - atFormatted + - browserFormatted + - '\n'; + let atFormatted; + if (atVersionName) { + atFormatted = `- AT: ${atName} (Version ${atVersionName})\n`; + } else { + atFormatted = `- AT: ${atName}\n`; } - const hiddenIssueMetadata = JSON.stringify({ - testPlanDirectory, - versionString, - atName, - browserName, - testRowNumber, - testSequenceNumber, - isCandidateReview, - isCandidateReviewChangesRequested - }); - - let body = - `## Description of Behavior\n\n` + - `\n\n` + - testSetupFormatted + - `\n` + - ``; - - if (conflictMarkdown) { - body += `\n${conflictMarkdown}`; + let browserFormatted = ''; + if (browserName && browserVersionName) { + browserFormatted = `- Browser: ${browserName} (Version ${browserVersionName})\n`; + } else if (browserName) { + browserFormatted = `- Browser: ${browserName}\n`; } - return ( - `${GITHUB_ISSUES_URL}/issues/new?title=${encodeURI(title)}&` + - `labels=${labels}&body=${encodeURIComponent(body)}` - ); + testSetupFormatted = + `## Test Setup\n\n` + + `- Test File: ` + + `[${shortenedUrl}](${modifiedRenderedUrl})\n` + + reportLinkFormatted + + atFormatted + + browserFormatted + + '\n'; + } + + const hiddenIssueMetadata = JSON.stringify({ + testPlanDirectory, + versionString, + atName, + browserName, + testRowNumber, + testSequenceNumber, + isCandidateReview, + isCandidateReviewChangesRequested + }); + + let body = + `## Description of Behavior\n\n` + + `\n\n` + + testSetupFormatted + + `\n` + + ``; + + if (conflictMarkdown) { + body += `\n${conflictMarkdown}`; + } + + return ( + `${GITHUB_ISSUES_URL}/issues/new?title=${encodeURI(title)}&` + + `labels=${labels}&body=${encodeURIComponent(body)}` + ); }; /** @@ -164,37 +164,37 @@ const createIssueLink = ({ * @returns {string} The URL for searching issues on the GitHub repository */ export const getIssueSearchLink = ({ - isCandidateReview = false, - isCandidateReviewChangesRequested = false, - username = null, - atName, - testPlanTitle, - versionString, - testSequenceNumber = null + isCandidateReview = false, + isCandidateReviewChangesRequested = false, + username = null, + atName, + testPlanTitle, + versionString, + testSequenceNumber = null }) => { - let atKey; - if (atName === 'JAWS' || atName === 'NVDA') { - atKey = atName.toLowerCase(); - } else { - atKey = 'vo'; - } - - const query = [ - isCandidateReview ? `label:candidate-review` : '', - isCandidateReviewChangesRequested - ? `label:changes-requested` - : 'label:feedback', - `label:${atLabelMap[atName]}`, - username ? `author:${username}` : '', - `label:${atKey}`, - `"${testPlanTitle}"`, - testSequenceNumber ? `Test ${testSequenceNumber}` : '', - versionString - ] - .filter(str => str) - .join(' '); - - return `${GITHUB_ISSUES_URL}/issues?q=${encodeURI(query)}`; + let atKey; + if (atName === 'JAWS' || atName === 'NVDA') { + atKey = atName.toLowerCase(); + } else { + atKey = 'vo'; + } + + const query = [ + isCandidateReview ? `label:candidate-review` : '', + isCandidateReviewChangesRequested + ? `label:changes-requested` + : 'label:feedback', + `label:${atLabelMap[atName]}`, + username ? `author:${username}` : '', + `label:${atKey}`, + `"${testPlanTitle}"`, + testSequenceNumber ? `Test ${testSequenceNumber}` : '', + versionString + ] + .filter(str => str) + .join(' '); + + return `${GITHUB_ISSUES_URL}/issues?q=${encodeURI(query)}`; }; export default createIssueLink; diff --git a/client/utils/evaluateAuth.js b/client/utils/evaluateAuth.js index 34fcc145a..6b1fd246b 100644 --- a/client/utils/evaluateAuth.js +++ b/client/utils/evaluateAuth.js @@ -10,22 +10,22 @@ * @returns {Auth} - evaluated auth object */ export const evaluateAuth = user => { - if (!user) user = {}; + if (!user) user = {}; - let roles = user.roles ?? []; + let roles = user.roles ?? []; - return { - // calculated booleans - isAdmin: roles.includes('ADMIN'), - isTester: roles.includes('TESTER'), - isVendor: roles.includes('VENDOR'), - isSignedIn: !!user.username, + return { + // calculated booleans + isAdmin: roles.includes('ADMIN'), + isTester: roles.includes('TESTER'), + isVendor: roles.includes('VENDOR'), + isSignedIn: !!user.username, - // user object values - id: user.id ?? null, - username: user.username ?? null, - roles - }; + // user object values + id: user.id ?? null, + username: user.username ?? null, + roles + }; }; /** diff --git a/client/utils/formSerialization.js b/client/utils/formSerialization.js index f2f7018a3..a8b0f1508 100644 --- a/client/utils/formSerialization.js +++ b/client/utils/formSerialization.js @@ -5,46 +5,46 @@ const selector = 'input, button, textarea, select, fieldset, optgroup, option'; // 'name', 'disabled', and 'selected' // state export function serialize(root) { - const nodes = root.querySelectorAll(selector); - // serialized element state is saved in an - // array since element traversal order will - // always be the same - let serialized = []; + const nodes = root.querySelectorAll(selector); + // serialized element state is saved in an + // array since element traversal order will + // always be the same + let serialized = []; - for (let node of nodes) { - let nodeState = {}; + for (let node of nodes) { + let nodeState = {}; - switch (node.tagName.toLowerCase()) { - case 'input': - if (node.type == 'checkbox' || node.type == 'radio') { - nodeState.checked = node.checked; - nodeState.indeterminate = node.indeterminate; - } - // falls through - case 'button': - case 'textarea': - nodeState.value = node.value; - // falls through - case 'select': - case 'fieldset': - nodeState.name = node.name; - // falls through - case 'optgroup': - nodeState.disabled = node.disabled; - break; - case 'option': - nodeState.value = node.value; - nodeState.disabled = node.disabled; - nodeState.selected = node.selected; - break; - default: - break; + switch (node.tagName.toLowerCase()) { + case 'input': + if (node.type == 'checkbox' || node.type == 'radio') { + nodeState.checked = node.checked; + nodeState.indeterminate = node.indeterminate; } - - serialized.push(nodeState); + // falls through + case 'button': + case 'textarea': + nodeState.value = node.value; + // falls through + case 'select': + case 'fieldset': + nodeState.name = node.name; + // falls through + case 'optgroup': + nodeState.disabled = node.disabled; + break; + case 'option': + nodeState.value = node.value; + nodeState.disabled = node.disabled; + nodeState.selected = node.selected; + break; + default: + break; } - return serialized; + serialized.push(nodeState); + } + + return serialized; } // hydrate form elements by restoring @@ -52,32 +52,32 @@ export function serialize(root) { // 'name', 'disabled', and 'selected' // state export function hydrate(serialized, root) { - const nodes = root.querySelectorAll(selector); + const nodes = root.querySelectorAll(selector); - if (nodes.length !== serialized.length) - throw new Error( - 'Form deserialization failed: serialized nodes are not the same shape as given nodes' - ); + if (nodes.length !== serialized.length) + throw new Error( + 'Form deserialization failed: serialized nodes are not the same shape as given nodes' + ); - for (let [i, node] of nodes.entries()) { - const nodeState = serialized[i]; - if (typeof nodeState.disabled !== 'undefined') { - node.disabled = nodeState.disabled; - } - if (typeof nodeState.name !== 'undefined') { - node.name = nodeState.name; - } - if (typeof nodeState.value !== 'undefined') { - node.value = nodeState.value; - } - if (typeof nodeState.selected !== 'undefined') { - node.selected = nodeState.selected; - } - if (typeof nodeState.indeterminate !== 'undefined') { - node.indeterminate = nodeState.indeterminate; - } - if (typeof nodeState.checked !== 'undefined') { - node.checked = nodeState.checked; - } + for (let [i, node] of nodes.entries()) { + const nodeState = serialized[i]; + if (typeof nodeState.disabled !== 'undefined') { + node.disabled = nodeState.disabled; + } + if (typeof nodeState.name !== 'undefined') { + node.name = nodeState.name; + } + if (typeof nodeState.value !== 'undefined') { + node.value = nodeState.value; + } + if (typeof nodeState.selected !== 'undefined') { + node.selected = nodeState.selected; + } + if (typeof nodeState.indeterminate !== 'undefined') { + node.indeterminate = nodeState.indeterminate; + } + if (typeof nodeState.checked !== 'undefined') { + node.checked = nodeState.checked; } + } } diff --git a/client/utils/formatter.js b/client/utils/formatter.js index 7951603bc..c5f4487e7 100644 --- a/client/utils/formatter.js +++ b/client/utils/formatter.js @@ -12,40 +12,40 @@ import moment from 'moment'; * @returns {string} - transformed string */ export const capitalizeEachWord = ( - sentence, - { splitChar = ' ', joinChar = ' ' } + sentence, + { splitChar = ' ', joinChar = ' ' } ) => { - const words = sentence.toLowerCase().split(splitChar); - for (let i = 0; i < words.length; i++) - words[i] = `${words[i][0].toUpperCase() + words[i].substr(1)}`; + const words = sentence.toLowerCase().split(splitChar); + for (let i = 0; i < words.length; i++) + words[i] = `${words[i][0].toUpperCase() + words[i].substr(1)}`; - return words.join(joinChar); + return words.join(joinChar); }; export const convertDateToString = (date, format = 'DD-MM-YYYY') => { - if (!date) return ''; - return moment(date).format(format); + if (!date) return ''; + return moment(date).format(format); }; export const convertStringToDate = (date, format = 'DD-MM-YYYY') => { - return moment(date, format).toDate(); + return moment(date, format).toDate(); }; export const convertStringFormatToAnotherFormat = ( - date, - fromFormat = 'DD-MM-YYYY', - toFormat = 'MM-DD-YYYY' + date, + fromFormat = 'DD-MM-YYYY', + toFormat = 'MM-DD-YYYY' ) => { - return moment(date, fromFormat).format(toFormat); + return moment(date, fromFormat).format(toFormat); }; export const isValidDate = (date, format = 'DD-MM-YYYY') => { - return moment(date, format).isValid(); + return moment(date, format).isValid(); }; export const checkDaysBetweenDates = (date, otherDate) => { - const _date = moment(date); - const _otherDate = moment(otherDate); - const hours = _date.diff(_otherDate, 'hours'); - return Math.ceil(hours / 24); + const _date = moment(date); + const _otherDate = moment(otherDate); + const hours = _date.diff(_otherDate, 'hours'); + return Math.ceil(hours / 24); }; diff --git a/client/utils/gitUtils.js b/client/utils/gitUtils.js index 4de5da85d..d3ad2cd04 100644 --- a/client/utils/gitUtils.js +++ b/client/utils/gitUtils.js @@ -3,15 +3,15 @@ const timeZone = 'UTC'; const options = { month: 'short' }; export const gitUpdatedDateToString = (dateString, locale = 'default') => { - const date = new Date(dateString); - const month = date.toLocaleString(locale, options); - const day = date.getDate(); - const year = date.getFullYear(); - const time = date - .toLocaleTimeString(locale, { timeZone: timeZone }) - .replace(/\s/g, ' '); + const date = new Date(dateString); + const month = date.toLocaleString(locale, options); + const day = date.getDate(); + const year = date.getFullYear(); + const time = date + .toLocaleTimeString(locale, { timeZone: timeZone }) + .replace(/\s/g, ' '); - const timeStamp = `${month} ${day}, ${year} at ${time} ${timeZone}`; + const timeStamp = `${month} ${day}, ${year} at ${time} ${timeZone}`; - return lc('PM', lc('AM', timeStamp)); + return lc('PM', lc('AM', timeStamp)); }; diff --git a/client/utils/gitUtils.test.js b/client/utils/gitUtils.test.js index e6d7d480d..793e255de 100644 --- a/client/utils/gitUtils.test.js +++ b/client/utils/gitUtils.test.js @@ -1,28 +1,28 @@ import { gitUpdatedDateToString } from './gitUtils'; describe('gitUpdatedDateToString', () => { - it('returns a formatted string AM', () => { - const date = '2021-11-30T09:51:28.000Z'; - const formattedDate = gitUpdatedDateToString(date, 'en-US'); - expect(formattedDate).toBe('Nov 30, 2021 at 9:51:28 am UTC'); - }); + it('returns a formatted string AM', () => { + const date = '2021-11-30T09:51:28.000Z'; + const formattedDate = gitUpdatedDateToString(date, 'en-US'); + expect(formattedDate).toBe('Nov 30, 2021 at 9:51:28 am UTC'); + }); - it('returns a formatted string PM', () => { - const date = '2021-11-30T14:51:28.000Z'; - const formattedDate = gitUpdatedDateToString(date, 'en-US'); - expect(formattedDate).toBe('Nov 30, 2021 at 2:51:28 pm UTC'); - }); + it('returns a formatted string PM', () => { + const date = '2021-11-30T14:51:28.000Z'; + const formattedDate = gitUpdatedDateToString(date, 'en-US'); + expect(formattedDate).toBe('Nov 30, 2021 at 2:51:28 pm UTC'); + }); - it('returns a formatted string when using 24-hour notation in a different locale (French)', () => { - const date = '2021-11-30T14:51:28.000Z'; - const formattedDate = gitUpdatedDateToString(date, 'fr-FR'); - // French time uses a lowercase month notation - expect(formattedDate).toBe('nov. 30, 2021 at 14:51:28 UTC'); - }); + it('returns a formatted string when using 24-hour notation in a different locale (French)', () => { + const date = '2021-11-30T14:51:28.000Z'; + const formattedDate = gitUpdatedDateToString(date, 'fr-FR'); + // French time uses a lowercase month notation + expect(formattedDate).toBe('nov. 30, 2021 at 14:51:28 UTC'); + }); - it('returns a formatted string when using a different locale (Korean)', () => { - const date = '2021-11-30T14:51:28.000Z'; - const formattedDate = gitUpdatedDateToString(date, 'ko-KR'); - expect(formattedDate).toBe('11월 30, 2021 at 오후 2:51:28 UTC'); - }); + it('returns a formatted string when using a different locale (Korean)', () => { + const date = '2021-11-30T14:51:28.000Z'; + const formattedDate = gitUpdatedDateToString(date, 'ko-KR'); + expect(formattedDate).toBe('11월 30, 2021 at 오후 2:51:28 UTC'); + }); }); diff --git a/client/utils/navigateTests.js b/client/utils/navigateTests.js index 8bc4ea6ba..7c8346f16 100644 --- a/client/utils/navigateTests.js +++ b/client/utils/navigateTests.js @@ -1,32 +1,31 @@ export const navigateTests = ( - previous = false, - currentTest, - tests = [], - setCurrentTestIndex = () => {}, - setIsFirstTest = () => {}, - setIsLastTest = () => {} + previous = false, + currentTest, + tests = [], + setCurrentTestIndex = () => {}, + setIsFirstTest = () => {}, + setIsLastTest = () => {} ) => { - // assume navigation forward if previous is false - let newTestIndex = currentTest.seq; - if (!previous) { - // next - const newTestIndexToEval = currentTest.seq + 1; - if (newTestIndexToEval <= tests.length) - newTestIndex = newTestIndexToEval; - } else { - // previous - const newTestIndexToEval = currentTest.seq - 1; - if (newTestIndexToEval >= 1 && newTestIndexToEval <= tests.length) - newTestIndex = newTestIndexToEval; - } + // assume navigation forward if previous is false + let newTestIndex = currentTest.seq; + if (!previous) { + // next + const newTestIndexToEval = currentTest.seq + 1; + if (newTestIndexToEval <= tests.length) newTestIndex = newTestIndexToEval; + } else { + // previous + const newTestIndexToEval = currentTest.seq - 1; + if (newTestIndexToEval >= 1 && newTestIndexToEval <= tests.length) + newTestIndex = newTestIndexToEval; + } - const currentIndex = tests.find(t => t.seq === newTestIndex).index; - const isFirstTest = newTestIndex - 1 === 0; - const isLastTest = newTestIndex === tests.length; + const currentIndex = tests.find(t => t.seq === newTestIndex).index; + const isFirstTest = newTestIndex - 1 === 0; + const isLastTest = newTestIndex === tests.length; - setCurrentTestIndex && setCurrentTestIndex(currentIndex); - setIsFirstTest && setIsFirstTest(isFirstTest); - setIsLastTest && setIsLastTest(isLastTest); + setCurrentTestIndex && setCurrentTestIndex(currentIndex); + setIsFirstTest && setIsFirstTest(isFirstTest); + setIsLastTest && setIsLastTest(isLastTest); - return { currentIndex, isFirstTest, isLastTest }; + return { currentIndex, isFirstTest, isLastTest }; }; diff --git a/client/webpack.config.js b/client/webpack.config.js index b74fa1861..a75d50806 100644 --- a/client/webpack.config.js +++ b/client/webpack.config.js @@ -4,92 +4,92 @@ const CopyWebpackPlugin = require('copy-webpack-plugin'); require('dotenv').config({ path: '../config/dev.env' }); module.exports = { - entry: ['babel-polyfill', './index.js'], - mode: 'development', - devtool: 'inline-source-map', - module: { - rules: [ - { - test: /\.(js|jsx)$/, - exclude: /(node_modules)/, - loader: 'babel-loader', - options: { - babelrcRoots: ['.', '../..'] - } - }, - { - test: /\.css$/, - use: ['style-loader', 'css-loader'] - }, - { - test: /\.s[ac]ss$/i, - use: [ - // Creates `style` nodes from JS strings - 'style-loader', - // Translates CSS into CommonJS - 'css-loader' - ] - }, - { - test: /\.(png|jpe?g|gif)$/i, - use: [ - { - loader: 'file-loader' - } - ] - } + entry: ['babel-polyfill', './index.js'], + mode: 'development', + devtool: 'inline-source-map', + module: { + rules: [ + { + test: /\.(js|jsx)$/, + exclude: /(node_modules)/, + loader: 'babel-loader', + options: { + babelrcRoots: ['.', '../..'] + } + }, + { + test: /\.css$/, + use: ['style-loader', 'css-loader'] + }, + { + test: /\.s[ac]ss$/i, + use: [ + // Creates `style` nodes from JS strings + 'style-loader', + // Translates CSS into CommonJS + 'css-loader' ] - }, - output: { - path: path.resolve(__dirname, './dist/'), - filename: 'bundle.js' - }, - resolve: { - alias: { - '@': path.resolve(__dirname, '..'), - '@client': __dirname, - '@components': path.resolve(__dirname, 'components'), - '@server': path.resolve(__dirname, '../server') - }, - extensions: ['*', '.js', '.jsx'] - }, - devServer: { - static: { - directory: path.join(__dirname, 'static') - }, - port: process.env.CLIENT_PORT || 3000, - // Allows access to the dev server over your local network. Note that - // you will need to use your computer's address, e.g. 192.168.0.20:3000, - // and that logging in will require you to manually change the URL from - // localhost:3000 to 192.168.0.20:3000 each time a redirect occurs. - host: '0.0.0.0', - devMiddleware: { - publicPath: '/' - }, - historyApiFallback: true, - hot: 'only', - proxy: [ - { - context: ['/aria-at', '/api', '/embed'], - target: `http://localhost:${process.env.PORT || 8000}` - } + }, + { + test: /\.(png|jpe?g|gif)$/i, + use: [ + { + loader: 'file-loader' + } ] + } + ] + }, + output: { + path: path.resolve(__dirname, './dist/'), + filename: 'bundle.js' + }, + resolve: { + alias: { + '@': path.resolve(__dirname, '..'), + '@client': __dirname, + '@components': path.resolve(__dirname, 'components'), + '@server': path.resolve(__dirname, '../server') + }, + extensions: ['*', '.js', '.jsx'] + }, + devServer: { + static: { + directory: path.join(__dirname, 'static') }, - watchOptions: { - aggregateTimeout: 300, - poll: 1000 + port: process.env.CLIENT_PORT || 3000, + // Allows access to the dev server over your local network. Note that + // you will need to use your computer's address, e.g. 192.168.0.20:3000, + // and that logging in will require you to manually change the URL from + // localhost:3000 to 192.168.0.20:3000 each time a redirect occurs. + host: '0.0.0.0', + devMiddleware: { + publicPath: '/' }, - plugins: [ - new CopyWebpackPlugin({ - patterns: [ - { - from: 'static' - } - ] - }), - new webpack.DefinePlugin({ - 'process.env.API_SERVER': JSON.stringify(process.env.API_SERVER), - 'process.env.ENVIRONMENT': JSON.stringify(process.env.ENVIRONMENT) - }) + historyApiFallback: true, + hot: 'only', + proxy: [ + { + context: ['/aria-at', '/api', '/embed'], + target: `http://localhost:${process.env.PORT || 8000}` + } ] + }, + watchOptions: { + aggregateTimeout: 300, + poll: 1000 + }, + plugins: [ + new CopyWebpackPlugin({ + patterns: [ + { + from: 'static' + } + ] + }), + new webpack.DefinePlugin({ + 'process.env.API_SERVER': JSON.stringify(process.env.API_SERVER), + 'process.env.ENVIRONMENT': JSON.stringify(process.env.ENVIRONMENT) + }) + ] }; diff --git a/client/webpack.prod.js b/client/webpack.prod.js index 61ebb6124..06d73b603 100644 --- a/client/webpack.prod.js +++ b/client/webpack.prod.js @@ -3,70 +3,70 @@ const webpack = require('webpack'); const CopyWebpackPlugin = require('copy-webpack-plugin'); module.exports = { - entry: ['babel-polyfill', './index.js'], - mode: 'production', - module: { - rules: [ - { - test: /\.(js|jsx)$/, - exclude: /(node_modules)/, - loader: 'babel-loader', - options: { - babelrcRoots: ['.', '../..'] - } - }, - { - test: /\.css$/, - use: ['style-loader', 'css-loader'] - }, - { - test: /\.s[ac]ss$/i, - use: [ - // Creates `style` nodes from JS strings - 'style-loader', - // Translates CSS into CommonJS - 'css-loader' - ] - }, - { - test: /\.(png|jpe?g|gif)$/i, - use: [ - { - loader: 'file-loader' - } - ] - } + entry: ['babel-polyfill', './index.js'], + mode: 'production', + module: { + rules: [ + { + test: /\.(js|jsx)$/, + exclude: /(node_modules)/, + loader: 'babel-loader', + options: { + babelrcRoots: ['.', '../..'] + } + }, + { + test: /\.css$/, + use: ['style-loader', 'css-loader'] + }, + { + test: /\.s[ac]ss$/i, + use: [ + // Creates `style` nodes from JS strings + 'style-loader', + // Translates CSS into CommonJS + 'css-loader' ] + }, + { + test: /\.(png|jpe?g|gif)$/i, + use: [ + { + loader: 'file-loader' + } + ] + } + ] + }, + output: { + path: path.resolve(__dirname, './dist/'), + filename: 'bundle.js' + }, + resolve: { + alias: { + '@': path.resolve(__dirname, '..'), + '@client': __dirname, + '@components': path.resolve(__dirname, 'components'), + '@server': path.resolve(__dirname, '../server') }, - output: { - path: path.resolve(__dirname, './dist/'), - filename: 'bundle.js' - }, - resolve: { - alias: { - '@': path.resolve(__dirname, '..'), - '@client': __dirname, - '@components': path.resolve(__dirname, 'components'), - '@server': path.resolve(__dirname, '../server') - }, - extensions: ['*', '.js', '.jsx'] - }, - plugins: [ - new CopyWebpackPlugin({ - patterns: [ - { - from: 'static' - } - ] - }), - new webpack.DefinePlugin({ - 'process.env.API_SERVER': JSON.stringify(process.env.API_SERVER), - 'process.env.ENVIRONMENT': JSON.stringify(process.env.ENVIRONMENT) - }) - ], - performance: { - hints: false, - maxEntrypointSize: 512000, - maxAssetSize: 512000 - } + extensions: ['*', '.js', '.jsx'] + }, + plugins: [ + new CopyWebpackPlugin({ + patterns: [ + { + from: 'static' + } + ] + }), + new webpack.DefinePlugin({ + 'process.env.API_SERVER': JSON.stringify(process.env.API_SERVER), + 'process.env.ENVIRONMENT': JSON.stringify(process.env.ENVIRONMENT) + }) + ], + performance: { + hints: false, + maxEntrypointSize: 512000, + maxAssetSize: 512000 + } }; diff --git a/config/config.js b/config/config.js index f32cfdff0..9f2f10954 100644 --- a/config/config.js +++ b/config/config.js @@ -1,17 +1,17 @@ module.exports = { - development: { - database: process.env.PGDATABASE, - username: process.env.PGUSER, - password: process.env.PGPASSWORD, - host: process.env.PGHOST, - port: process.env.PGPORT, - dialect: 'postgres', - dialectOption: { - ssl: true, - native: true, - multipleStatements: true - }, - seederStorage: 'sequelize', - logging: false // console.log // eslint-disable-line no-console - } + development: { + database: process.env.PGDATABASE, + username: process.env.PGUSER, + password: process.env.PGPASSWORD, + host: process.env.PGHOST, + port: process.env.PGPORT, + dialect: 'postgres', + dialectOption: { + ssl: true, + native: true, + multipleStatements: true + }, + seederStorage: 'sequelize', + logging: false // console.log // eslint-disable-line no-console + } }; diff --git a/index.js b/index.js index 4aeae28bf..afe12585d 100644 --- a/index.js +++ b/index.js @@ -8,5 +8,5 @@ const { listener } = require('./server/server'); listener.use(history()).use(express.static('./client/dist')); listener.get('*', (req, res) => { - res.sendFile(path.join(__dirname, './client/dist/index.html')); + res.sendFile(path.join(__dirname, './client/dist/index.html')); }); diff --git a/package.json b/package.json index 430f2a578..01312348a 100644 --- a/package.json +++ b/package.json @@ -33,15 +33,15 @@ "url": "https://github.com/w3c/aria-at-app/issues" }, "homepage": "https://github.com/w3c/aria-at-app#readme", - "dependencies": { - "patch-package": "^6.5.1", - "postinstall-postinstall": "^2.1.0" - }, "workspaces": [ "client", "server", "shared" ], + "dependencies": { + "patch-package": "^8.0.0", + "postinstall-postinstall": "^2.1.0" + }, "devDependencies": { "npm-run-all": "^4.1.5" } diff --git a/server/app.js b/server/app.js index ff96b3899..90b3217b5 100644 --- a/server/app.js +++ b/server/app.js @@ -10,7 +10,9 @@ const transactionRoutes = require('./routes/transactions'); const automationSchedulerRoutes = require('./routes/automation'); const path = require('path'); const apolloServer = require('./graphql-server'); -const setupMockAutomationSchedulerServer = require('./tests/util/mock-automation-scheduler-server'); +const { + setupMockAutomationSchedulerServer +} = require('./tests/util/mock-automation-scheduler-server'); const transactionMiddleware = require('./middleware/transactionMiddleware'); const app = express(); @@ -24,7 +26,7 @@ app.use('/transactions', transactionRoutes); app.use('/jobs', automationSchedulerRoutes); apolloServer.start().then(() => { - apolloServer.applyMiddleware({ app }); + apolloServer.applyMiddleware({ app }); }); const listener = express(); @@ -34,35 +36,35 @@ const baseUrl = 'https://raw.githubusercontent.com'; const onlyStatus200 = (req, res) => res.statusCode === 200; listener.route('/aria-at/:branch*').get( - cacheMiddleware('7 days', onlyStatus200), - (req, res, next) => { - req.url = path.join('w3c', req.url); - next(); - }, - proxyMiddleware.fileRedirect(baseUrl), - proxyMiddleware.proxyPath(baseUrl) + cacheMiddleware('7 days', onlyStatus200), + (req, res, next) => { + req.url = path.join('w3c', req.url); + next(); + }, + proxyMiddleware.fileRedirect(baseUrl), + proxyMiddleware.proxyPath(baseUrl) ); // Conditionally initialize github workflow service, or mock automation scheduler if ( - process.env.ENVIRONMENT === 'production' || - process.env.ENVIRONMENT === 'staging' || - process.env.ENVIRONMENT === 'sandbox' || - process.env.AUTOMATION_CALLBACK_FQDN + process.env.ENVIRONMENT === 'production' || + process.env.ENVIRONMENT === 'staging' || + process.env.ENVIRONMENT === 'sandbox' || + process.env.AUTOMATION_CALLBACK_FQDN ) { - require('./services/GithubWorkflowService').setup(); + require('./services/GithubWorkflowService').setup(); } else { - setupMockAutomationSchedulerServer().catch(error => { - console.error('Failed to initialize mock automation server:', error); - }); + setupMockAutomationSchedulerServer().catch(error => { + console.error('Failed to initialize mock automation server:', error); + }); } app.use(transactionMiddleware.errorware); // Error handling must be the last middleware listener.use((error, req, res, next) => { - console.error(error); - next(error); + console.error(error); + next(error); }); module.exports = { app, listener }; diff --git a/server/apps/embed.js b/server/apps/embed.js index bb1f34c11..fafd3821c 100644 --- a/server/apps/embed.js +++ b/server/apps/embed.js @@ -10,10 +10,10 @@ const app = express(); const handlebarsPath = path.resolve(__dirname, '../handlebars/embed'); const hbs = create({ - layoutsDir: path.resolve(handlebarsPath, 'views/layouts'), - extname: 'hbs', - defaultLayout: 'index', - helpers: require(path.resolve(handlebarsPath, 'helpers')) + layoutsDir: path.resolve(handlebarsPath, 'views/layouts'), + extname: 'hbs', + defaultLayout: 'index', + helpers: require(path.resolve(handlebarsPath, 'helpers')) }); app.engine('hbs', hbs.engine); @@ -29,121 +29,121 @@ app.set('views', path.resolve(handlebarsPath, 'views')); const millisecondsUntilStale = 5000; const renderEmbed = async ({ - queryTitle, - testPlanDirectory, - protocol, - host + queryTitle, + testPlanDirectory, + protocol, + host }) => { - const { data, errors } = await apolloServer.executeOperation({ - query: gql` - query TestPlanQuery($testPlanDirectory: ID!) { - ats { - id - name - browsers { - id - name - } - } - testPlan(id: $testPlanDirectory) { - testPlanVersions { - id - title - phase - testPlanReports(isFinal: true) { - id - metrics - at { - id - name - } - browser { - id - name - } - latestAtVersionReleasedAt { - id - name - releasedAt - } - } - } - } - } - `, - variables: { testPlanDirectory } - }); - - if (errors) { - throw new Error(errors); - } - - let testPlanVersion; - - const recommendedTestPlanVersion = data.testPlan?.testPlanVersions.find( - testPlanVersion => testPlanVersion.phase === 'RECOMMENDED' - ); - - if (data.testPlan && recommendedTestPlanVersion) { - testPlanVersion = recommendedTestPlanVersion; - } else if (data.testPlan) { - testPlanVersion = data.testPlan.testPlanVersions.find( - testPlanVersion => testPlanVersion.phase === 'CANDIDATE' - ); - } - - const testPlanReports = (testPlanVersion?.testPlanReports ?? []).sort( - (a, b) => { - if (a.at.name !== b.at.name) { - return a.at.name.localeCompare(b.at.name); + const { data, errors } = await apolloServer.executeOperation({ + query: gql` + query TestPlanQuery($testPlanDirectory: ID!) { + ats { + id + name + browsers { + id + name + } + } + testPlan(id: $testPlanDirectory) { + testPlanVersions { + id + title + phase + testPlanReports(isFinal: true) { + id + metrics + at { + id + name + } + browser { + id + name + } + latestAtVersionReleasedAt { + id + name + releasedAt + } } - return a.browser.name.localeCompare(b.browser.name); + } } + } + `, + variables: { testPlanDirectory } + }); + + if (errors) { + throw new Error(errors); + } + + let testPlanVersion; + + const recommendedTestPlanVersion = data.testPlan?.testPlanVersions.find( + testPlanVersion => testPlanVersion.phase === 'RECOMMENDED' + ); + + if (data.testPlan && recommendedTestPlanVersion) { + testPlanVersion = recommendedTestPlanVersion; + } else if (data.testPlan) { + testPlanVersion = data.testPlan.testPlanVersions.find( + testPlanVersion => testPlanVersion.phase === 'CANDIDATE' ); - - return hbs.renderView(path.resolve(handlebarsPath, 'views/main.hbs'), { - layout: 'index', - dataEmpty: !testPlanVersion?.testPlanReports.length, - title: queryTitle || testPlanVersion?.title || 'Pattern Not Found', - phase: testPlanVersion?.phase, - testPlanVersionId: testPlanVersion?.id, - testPlanReports, - protocol, - host, - completeReportLink: `${protocol}${host}/report/${testPlanVersion?.id}`, - embedLink: `${protocol}${host}/embed/reports/${testPlanDirectory}` - }); + } + + const testPlanReports = (testPlanVersion?.testPlanReports ?? []).sort( + (a, b) => { + if (a.at.name !== b.at.name) { + return a.at.name.localeCompare(b.at.name); + } + return a.browser.name.localeCompare(b.browser.name); + } + ); + + return hbs.renderView(path.resolve(handlebarsPath, 'views/main.hbs'), { + layout: 'index', + dataEmpty: !testPlanVersion?.testPlanReports.length, + title: queryTitle || testPlanVersion?.title || 'Pattern Not Found', + phase: testPlanVersion?.phase, + testPlanVersionId: testPlanVersion?.id, + testPlanReports, + protocol, + host, + completeReportLink: `${protocol}${host}/report/${testPlanVersion?.id}`, + embedLink: `${protocol}${host}/embed/reports/${testPlanDirectory}` + }); }; // staleWhileRevalidate() caching allows this page to handle very high traffic like // it will see on the APG website. It works by immediately serving a recent // version of the page and checks for updates in the background. const renderEmbedCached = staleWhileRevalidate(renderEmbed, { - getCacheKeyFromArguments: ({ testPlanDirectory }) => testPlanDirectory, - millisecondsUntilStale + getCacheKeyFromArguments: ({ testPlanDirectory }) => testPlanDirectory, + millisecondsUntilStale }); app.get('/reports/:testPlanDirectory', async (req, res) => { - // In the instance where an editor doesn't want to display a certain title - // as it has defined when importing into the ARIA-AT database for being too - // verbose, etc. eg. `Link Example 1 (span element with text content)` - // Usage: https://aria-at.w3.org/embed/reports/command-button?title=Link+Example+(span+element+with+text+content) - const queryTitle = req.query.title; - const testPlanDirectory = req.params.testPlanDirectory; - const host = req.headers.host; - const protocol = /dev|vagrant/.test(process.env.ENVIRONMENT) - ? 'http://' - : 'https://'; - const embedRendered = await renderEmbedCached({ - queryTitle, - testPlanDirectory, - protocol, - host - }); - - // Disable browser-based caching which could potentially make the embed - // contents appear stale even after being refreshed - res.set('cache-control', 'must-revalidate').send(embedRendered); + // In the instance where an editor doesn't want to display a certain title + // as it has defined when importing into the ARIA-AT database for being too + // verbose, etc. eg. `Link Example 1 (span element with text content)` + // Usage: https://aria-at.w3.org/embed/reports/command-button?title=Link+Example+(span+element+with+text+content) + const queryTitle = req.query.title; + const testPlanDirectory = req.params.testPlanDirectory; + const host = req.headers.host; + const protocol = /dev|vagrant/.test(process.env.ENVIRONMENT) + ? 'http://' + : 'https://'; + const embedRendered = await renderEmbedCached({ + queryTitle, + testPlanDirectory, + protocol, + host + }); + + // Disable browser-based caching which could potentially make the embed + // contents appear stale even after being refreshed + res.set('cache-control', 'must-revalidate').send(embedRendered); }); app.use(express.static(path.resolve(`${handlebarsPath}/public`))); diff --git a/server/controllers/AuthController.js b/server/controllers/AuthController.js index c1076ebb1..18f36508a 100644 --- a/server/controllers/AuthController.js +++ b/server/controllers/AuthController.js @@ -6,88 +6,88 @@ const getUsersFromFile = require('../util/getUsersFromFile'); const APP_SERVER = process.env.APP_SERVER; const oauthRedirectToGithubController = async (req, res) => { - const oauthUrl = GithubService.getOauthUrl(); - res.redirect(303, oauthUrl); - res.end(); + const oauthUrl = GithubService.getOauthUrl(); + res.redirect(303, oauthUrl); + res.end(); }; const oauthRedirectFromGithubController = async (req, res) => { - const loginSucceeded = () => { - res.redirect(303, `${APP_SERVER}/test-queue`); - }; - const loginFailedDueToRole = async () => { - if (req.session) { - await new Promise(resolve => req.session.destroy(resolve)); - } - res.redirect(303, `${APP_SERVER}/signup-instructions`); - }; - const loginFailedDueToGitHub = () => { - res.status(401).send( - ` + const loginSucceeded = () => { + res.redirect(303, `${APP_SERVER}/test-queue`); + }; + const loginFailedDueToRole = async () => { + if (req.session) { + await new Promise(resolve => req.session.destroy(resolve)); + } + res.redirect(303, `${APP_SERVER}/signup-instructions`); + }; + const loginFailedDueToGitHub = () => { + res.status(401).send( + ` ARIA-AT App failed to access GitHub.
    Return to home page. ` - ); - }; + ); + }; - const { code } = req.query; + const { code } = req.query; - const githubAccessToken = await GithubService.getGithubAccessToken(code); - if (!githubAccessToken) return loginFailedDueToGitHub(); + const githubAccessToken = await GithubService.getGithubAccessToken(code); + if (!githubAccessToken) return loginFailedDueToGitHub(); - const githubUsername = await GithubService.getGithubUsername( - githubAccessToken - ); + const githubUsername = await GithubService.getGithubUsername( + githubAccessToken + ); - if (!githubUsername) return loginFailedDueToGitHub(); + if (!githubUsername) return loginFailedDueToGitHub(); - const admins = await getUsersFromFile('admins.txt'); - const testers = await getUsersFromFile('testers.txt'); - const vendors = await getUsersFromFile('vendors.txt'); + const admins = await getUsersFromFile('admins.txt'); + const testers = await getUsersFromFile('testers.txt'); + const vendors = await getUsersFromFile('vendors.txt'); - const roles = []; - if (admins.includes(githubUsername)) { - roles.push({ name: User.ADMIN }); - } - if (admins.includes(githubUsername) || testers.includes(githubUsername)) { - roles.push({ name: User.TESTER }); // Admins are always testers - } + const roles = []; + if (admins.includes(githubUsername)) { + roles.push({ name: User.ADMIN }); + } + if (admins.includes(githubUsername) || testers.includes(githubUsername)) { + roles.push({ name: User.TESTER }); // Admins are always testers + } - if ( - admins.includes(githubUsername) || - vendors.some(vendor => vendor.split('|')[0] === githubUsername) - ) { - roles.push({ name: User.VENDOR }); - } + if ( + admins.includes(githubUsername) || + vendors.some(vendor => vendor.split('|')[0] === githubUsername) + ) { + roles.push({ name: User.VENDOR }); + } - if (roles.length === 0) return loginFailedDueToRole(); + if (roles.length === 0) return loginFailedDueToRole(); - let [user] = await getOrCreateUser({ - where: { username: githubUsername }, - values: { roles }, - atAttributes: [], - testPlanRunAttributes: [], - transaction: req.transaction - }); + let [user] = await getOrCreateUser({ + where: { username: githubUsername }, + values: { roles }, + atAttributes: [], + testPlanRunAttributes: [], + transaction: req.transaction + }); - req.session.user = user; + req.session.user = user; - return loginSucceeded(); + return loginSucceeded(); }; const signoutController = (req, res) => { - req.session.destroy(err => { - if (err) { - res.status(500); - } else { - res.status(200); - } - res.end(); - }); + req.session.destroy(err => { + if (err) { + res.status(500); + } else { + res.status(200); + } + res.end(); + }); }; module.exports = { - oauthRedirectToGithubController, - oauthRedirectFromGithubController, - signoutController + oauthRedirectToGithubController, + oauthRedirectFromGithubController, + signoutController }; diff --git a/server/controllers/AutomationController.js b/server/controllers/AutomationController.js index bdc0fcc61..62e49a039 100644 --- a/server/controllers/AutomationController.js +++ b/server/controllers/AutomationController.js @@ -1,27 +1,27 @@ const axios = require('axios'); const { - getCollectionJobById, - updateCollectionJobById, - updateCollectionJobTestStatusByQuery + getCollectionJobById, + updateCollectionJobById, + updateCollectionJobTestStatusByQuery } = require('../models/services/CollectionJobService'); const { - findOrCreateTestResult + findOrCreateTestResult } = require('../models/services/TestResultWriteService'); const convertTestResultToInput = require('../resolvers/TestPlanRunOperations/convertTestResultToInput'); const saveTestResultCommon = require('../resolvers/TestResultOperations/saveTestResultCommon'); const { - getAts, - findOrCreateAtVersion + getAts, + findOrCreateAtVersion } = require('../models/services/AtService'); const { - getBrowsers, - findOrCreateBrowserVersion + getBrowsers, + findOrCreateBrowserVersion } = require('../models/services/BrowserService'); const { HttpQueryError } = require('apollo-server-core'); const { COLLECTION_JOB_STATUS, isJobStatusFinal } = require('../util/enums'); const populateData = require('../services/PopulatedData/populateData'); const { - getFinalizedTestResults + getFinalizedTestResults } = require('../models/services/TestResultReadService'); const http = require('http'); const { NO_OUTPUT_STRING } = require('../util/constants'); @@ -30,330 +30,321 @@ const getGraphQLContext = require('../graphql-context'); const httpAgent = new http.Agent({ family: 4 }); const axiosConfig = { - headers: { - 'x-automation-secret': process.env.AUTOMATION_SCHEDULER_SECRET - }, - timeout: 1000, - httpAgent + timeout: 1000, + httpAgent }; const throwNoJobFoundError = jobId => { - throw new HttpQueryError( - 404, - `Could not find job with jobId: ${jobId}`, - true - ); + throw new HttpQueryError( + 404, + `Could not find job with jobId: ${jobId}`, + true + ); }; const throwNoTestFoundError = rowNumber => { - throw new HttpQueryError( - 404, - `Could not find test at row number ${rowNumber}`, - true - ); + throw new HttpQueryError( + 404, + `Could not find test at row number ${rowNumber}`, + true + ); }; const throwSchedulerError = schedulerResponse => { - throw new HttpQueryError( - 502, - `Response scheduler did not give a correct response: ${schedulerResponse}`, - false - ); + throw new HttpQueryError( + 502, + `Response scheduler did not give a correct response: ${schedulerResponse}`, + false + ); }; const cancelJob = async (req, res) => { - const automationSchedulerResponse = await axios.post( - `${process.env.AUTOMATION_SCHEDULER_URL}/jobs/${req.params.jobID}/cancel`, - {}, - axiosConfig - ); - - if (!automationSchedulerResponse.data) { - throwSchedulerError(automationSchedulerResponse); - } - - if ( - automationSchedulerResponse.data.status === - COLLECTION_JOB_STATUS.CANCELLED - ) { - const graphqlRes = await updateCollectionJobById({ - id: req.params.jobID, - values: { status: COLLECTION_JOB_STATUS.CANCELLED }, - transaction: req.transaction - }); - if (!graphqlRes) { - throwNoJobFoundError(req.params.jobID); - } + const automationSchedulerResponse = await axios.post( + `${process.env.AUTOMATION_SCHEDULER_URL}/jobs/${req.params.jobID}/cancel`, + {}, + axiosConfig + ); + + if (!automationSchedulerResponse.data) { + throwSchedulerError(automationSchedulerResponse); + } + + if ( + automationSchedulerResponse.data.status === COLLECTION_JOB_STATUS.CANCELLED + ) { + const graphqlRes = await updateCollectionJobById({ + id: req.params.jobID, + values: { status: COLLECTION_JOB_STATUS.CANCELLED }, + transaction: req.transaction + }); + if (!graphqlRes) { + throwNoJobFoundError(req.params.jobID); } - res.json(automationSchedulerResponse.data); + } + res.json(automationSchedulerResponse.data); }; const updateJobStatus = async (req, res) => { - const { status, externalLogsUrl } = req.body; - - if (!Object.values(COLLECTION_JOB_STATUS).includes(status)) { - throw new HttpQueryError(400, `Invalid status: ${status}`, true); - } - - const updatePayload = { - status, - ...(externalLogsUrl != null && { externalLogsUrl }) - }; - - // When new status is considered "final" ('COMPLETED' or 'ERROR' or 'CANCELLED') - if (isJobStatusFinal(status)) { - // update any CollectionJobTestStatus children still 'QUEUED' to be 'CANCELLED' - await updateCollectionJobTestStatusByQuery({ - where: { - collectionJobId: req.params.jobID, - status: COLLECTION_JOB_STATUS.QUEUED - }, - values: { status: COLLECTION_JOB_STATUS.CANCELLED }, - transaction: req.transaction - }); - // update any CollectionJobTestStatus children still 'RUNNING' to be 'ERROR' or 'CANCELLED' - let runningTestNewStatus = - status === COLLECTION_JOB_STATUS.ERROR - ? COLLECTION_JOB_STATUS.ERROR - : COLLECTION_JOB_STATUS.CANCELLED; - await updateCollectionJobTestStatusByQuery({ - where: { - collectionJobId: req.params.jobID, - status: COLLECTION_JOB_STATUS.RUNNING - }, - values: { status: runningTestNewStatus }, - transaction: req.transaction - }); - } - - const graphqlResponse = await updateCollectionJobById({ - id: req.params.jobID, - values: updatePayload, - transaction: req.transaction + const { status, externalLogsUrl } = req.body; + + if (!Object.values(COLLECTION_JOB_STATUS).includes(status)) { + throw new HttpQueryError(400, `Invalid status: ${status}`, true); + } + + const updatePayload = { + status, + ...(externalLogsUrl != null && { externalLogsUrl }) + }; + + // When new status is considered "final" ('COMPLETED' or 'ERROR' or 'CANCELLED') + if (isJobStatusFinal(status)) { + // update any CollectionJobTestStatus children still 'QUEUED' to be 'CANCELLED' + await updateCollectionJobTestStatusByQuery({ + where: { + collectionJobId: req.params.jobID, + status: COLLECTION_JOB_STATUS.QUEUED + }, + values: { status: COLLECTION_JOB_STATUS.CANCELLED }, + transaction: req.transaction }); + // update any CollectionJobTestStatus children still 'RUNNING' to be 'ERROR' or 'CANCELLED' + let runningTestNewStatus = + status === COLLECTION_JOB_STATUS.ERROR + ? COLLECTION_JOB_STATUS.ERROR + : COLLECTION_JOB_STATUS.CANCELLED; + await updateCollectionJobTestStatusByQuery({ + where: { + collectionJobId: req.params.jobID, + status: COLLECTION_JOB_STATUS.RUNNING + }, + values: { status: runningTestNewStatus }, + transaction: req.transaction + }); + } - if (!graphqlResponse) { - throwNoJobFoundError(req.params.jobID); - } + const graphqlResponse = await updateCollectionJobById({ + id: req.params.jobID, + values: updatePayload, + transaction: req.transaction + }); - res.json(graphqlResponse); + if (!graphqlResponse) { + throwNoJobFoundError(req.params.jobID); + } + + res.json(graphqlResponse); }; const getApprovedFinalizedTestResults = async (testPlanRun, context) => { - const { - testPlanReport: { testPlanVersion } - } = testPlanRun; - - // To be considered "Approved", a test plan run must be associated with a test plan report - // that is associated with a test plan version that is in "CANDIDATE" or "RECOMMENDED" or - // "DRAFT" phase and the test plan report been marked as final. - const { phase } = testPlanVersion; - - if ( - phase === 'RD' || - (phase === 'DRAFT' && testPlanRun.testPlanReport.markedFinalAt === null) - ) { - return null; - } - - const { testPlanReport } = await populateData( - { testPlanReportId: testPlanRun.testPlanReport.id }, - { context } - ); - - return getFinalizedTestResults({ testPlanReport, context }); + const { + testPlanReport: { testPlanVersion } + } = testPlanRun; + + // To be considered "Approved", a test plan run must be associated with a test plan report + // that is associated with a test plan version that is in "CANDIDATE" or "RECOMMENDED" or + // "DRAFT" phase and the test plan report been marked as final. + const { phase } = testPlanVersion; + + if ( + phase === 'RD' || + (phase === 'DRAFT' && testPlanRun.testPlanReport.markedFinalAt === null) + ) { + return null; + } + + const { testPlanReport } = await populateData( + { testPlanReportId: testPlanRun.testPlanReport.id }, + { context } + ); + + return getFinalizedTestResults({ testPlanReport, context }); }; const getTestByRowNumber = async ({ testPlanRun, testRowNumber, context }) => { - const tests = await runnableTestsResolver( - testPlanRun.testPlanReport, - null, - context - ); - return tests.find(test => String(test.rowNumber) === String(testRowNumber)); + const tests = await runnableTestsResolver( + testPlanRun.testPlanReport, + null, + context + ); + return tests.find(test => String(test.rowNumber) === String(testRowNumber)); }; const updateOrCreateTestResultWithResponses = async ({ + testId, + testPlanRun, + responses, + atVersionId, + browserVersionId, + context +}) => { + const { testResult } = await findOrCreateTestResult({ testId, - testPlanRun, - responses, + testPlanRunId: testPlanRun.id, atVersionId, browserVersionId, context -}) => { - const { testResult } = await findOrCreateTestResult({ - testId, - testPlanRunId: testPlanRun.id, - atVersionId, - browserVersionId, - context - }); + }); - const historicalTestResults = await getApprovedFinalizedTestResults( - testPlanRun, - context + const historicalTestResults = await getApprovedFinalizedTestResults( + testPlanRun, + context + ); + + const historicalTestResult = historicalTestResults?.find(each => { + return each.testId === testId; + }); + + if ( + historicalTestResult && + historicalTestResult.scenarioResults?.length !== + testResult.scenarioResults.length + ) { + throw new Error( + 'Historical test result does not match current test result' ); + } - const historicalTestResult = historicalTestResults?.find(each => { - return each.testId === testId; - }); - - if ( + const getAutomatedResultFromOutput = ({ baseTestResult, outputs }) => ({ + ...baseTestResult, + atVersionId, + browserVersionId, + scenarioResults: baseTestResult.scenarioResults.map((scenarioResult, i) => { + // Check if output matches historical output + const outputMatches = historicalTestResult && - historicalTestResult.scenarioResults?.length !== - testResult.scenarioResults.length - ) { - throw new Error( - 'Historical test result does not match current test result' - ); - } - - const getAutomatedResultFromOutput = ({ baseTestResult, outputs }) => ({ - ...baseTestResult, - atVersionId, - browserVersionId, - scenarioResults: baseTestResult.scenarioResults.map( - (scenarioResult, i) => { - // Check if output matches historical output - const outputMatches = - historicalTestResult && - historicalTestResult.scenarioResults[i] && - historicalTestResult.scenarioResults[i].output === - outputs[i]; - - return { - ...scenarioResult, - output: outputs[i], - assertionResults: scenarioResult.assertionResults.map( - (assertionResult, j) => ({ - ...assertionResult, - passed: outputMatches - ? historicalTestResult.scenarioResults[i] - .assertionResults[j].passed - : false, - failedReason: outputMatches - ? historicalTestResult.scenarioResults[i] - .assertionResults[j].failedReason - : 'AUTOMATED_OUTPUT' - }) - ), - unexpectedBehaviors: null - }; - } - ) - }); - - return saveTestResultCommon({ - testResultId: testResult.id, - input: convertTestResultToInput( - getAutomatedResultFromOutput({ - baseTestResult: testResult, - outputs: responses - }) + historicalTestResult.scenarioResults[i] && + historicalTestResult.scenarioResults[i].output === outputs[i]; + + return { + ...scenarioResult, + output: outputs[i], + assertionResults: scenarioResult.assertionResults.map( + (assertionResult, j) => ({ + ...assertionResult, + passed: outputMatches + ? historicalTestResult.scenarioResults[i].assertionResults[j] + .passed + : false, + failedReason: outputMatches + ? historicalTestResult.scenarioResults[i].assertionResults[j] + .failedReason + : 'AUTOMATED_OUTPUT' + }) ), - isSubmit: false, - context - }); + unexpectedBehaviors: null + }; + }) + }); + + return saveTestResultCommon({ + testResultId: testResult.id, + input: convertTestResultToInput( + getAutomatedResultFromOutput({ + baseTestResult: testResult, + outputs: responses + }) + ), + isSubmit: false, + context + }); }; const updateJobResults = async (req, res) => { - const { jobID: id, testRowNumber } = req.params; - const context = getGraphQLContext({ req }); - const { transaction } = context; - const { - responses, - status, - capabilities: { - atName, - atVersion: atVersionName, - browserName, - browserVersion: browserVersionName - } = {} - } = req.body; - - const job = await getCollectionJobById({ id, transaction }); - if (!job) { - throwNoJobFoundError(id); - } - - if (job.status !== COLLECTION_JOB_STATUS.RUNNING) { - throw new Error( - `Job with id ${id} is not running, cannot update results` - ); - } - if (status && !Object.values(COLLECTION_JOB_STATUS).includes(status)) { - throw new HttpQueryError(400, `Invalid status: ${status}`, true); - } - const { testPlanRun } = job; - - const testId = ( - await getTestByRowNumber({ - testPlanRun, - testRowNumber, - context - }) - )?.id; - - if (testId === undefined) { - throwNoTestFoundError(testRowNumber); - } - - // status only update, or responses were provided (default to complete) - if (status || responses) { - await updateCollectionJobTestStatusByQuery({ - where: { collectionJobId: id, testId }, - // default to completed if not specified (when results are present) - values: { status: status ?? COLLECTION_JOB_STATUS.COMPLETED }, - transaction: req.transaction - }); - } + const { jobID: id, testRowNumber } = req.params; + const context = getGraphQLContext({ req }); + const { transaction } = context; + const { + responses, + status, + capabilities: { + atName, + atVersion: atVersionName, + browserName, + browserVersion: browserVersionName + } = {} + } = req.body; + + const job = + req.collectionJob ?? (await getCollectionJobById({ id, transaction })); + if (!job) { + throwNoJobFoundError(id); + } + + if (job.status !== COLLECTION_JOB_STATUS.RUNNING) { + throw new Error(`Job with id ${id} is not running, cannot update results`); + } + if (status && !Object.values(COLLECTION_JOB_STATUS).includes(status)) { + throw new HttpQueryError(400, `Invalid status: ${status}`, true); + } + const { testPlanRun } = job; + + const testId = ( + await getTestByRowNumber({ + testPlanRun, + testRowNumber, + context + }) + )?.id; + + if (testId === undefined) { + throwNoTestFoundError(testRowNumber); + } + + // status only update, or responses were provided (default to complete) + if (status || responses) { + await updateCollectionJobTestStatusByQuery({ + where: { collectionJobId: id, testId }, + // default to completed if not specified (when results are present) + values: { status: status ?? COLLECTION_JOB_STATUS.COMPLETED }, + transaction: req.transaction + }); + } + + // responses were provided + if (responses) { + /* TODO: Change this to use a better key based lookup system after gh-958 */ + const [at] = await getAts({ search: atName, transaction }); + const [browser] = await getBrowsers({ + search: browserName, + transaction + }); - // responses were provided - if (responses) { - /* TODO: Change this to use a better key based lookup system after gh-958 */ - const [at] = await getAts({ search: atName, transaction }); - const [browser] = await getBrowsers({ - search: browserName, - transaction - }); - - const [atVersion, browserVersion] = await Promise.all([ - findOrCreateAtVersion({ - where: { atId: at.id, name: atVersionName }, - transaction - }), - findOrCreateBrowserVersion({ - where: { browserId: browser.id, name: browserVersionName }, - transaction - }) - ]); - - const processedResponses = - convertEmptyStringsToNoOutputMessages(responses); - - await updateOrCreateTestResultWithResponses({ - testId, - responses: processedResponses, - testPlanRun, - atVersionId: atVersion.id, - browserVersionId: browserVersion.id, - context - }); - } + const [atVersion, browserVersion] = await Promise.all([ + findOrCreateAtVersion({ + where: { atId: at.id, name: atVersionName }, + transaction + }), + findOrCreateBrowserVersion({ + where: { browserId: browser.id, name: browserVersionName }, + transaction + }) + ]); + + const processedResponses = convertEmptyStringsToNoOutputMessages(responses); + + await updateOrCreateTestResultWithResponses({ + testId, + responses: processedResponses, + testPlanRun, + atVersionId: atVersion.id, + browserVersionId: browserVersion.id, + context + }); + } - res.json({ success: true }); + res.json({ success: true }); }; // Human test runners are able to use a checkbox to indicate no output was detected. // This checkbox stores 'No output was detected.' as the output value for that scenarioResult. const convertEmptyStringsToNoOutputMessages = outputs => - outputs.map(output => - output === null || output.trim() === '' ? NO_OUTPUT_STRING : output - ); + outputs.map(output => + output === null || output.trim() === '' ? NO_OUTPUT_STRING : output + ); module.exports = { - cancelJob, - updateJobStatus, - updateJobResults, - axiosConfig + cancelJob, + updateJobStatus, + updateJobResults, + axiosConfig }; diff --git a/server/controllers/FakeUserController.js b/server/controllers/FakeUserController.js index e5a2d5297..ab870ff03 100644 --- a/server/controllers/FakeUserController.js +++ b/server/controllers/FakeUserController.js @@ -3,33 +3,31 @@ const { getOrCreateUser } = require('../models/services/UserService'); const ALLOW_FAKE_USER = process.env.ALLOW_FAKE_USER === 'true'; const setFakeUserController = async (req, res) => { - if (!ALLOW_FAKE_USER) { - return res - .status(400) - .send('Feature not supported in this environment'); - } + if (!ALLOW_FAKE_USER) { + return res.status(400).send('Feature not supported in this environment'); + } - const userToCreate = req.body; - if ( - !userToCreate || - !userToCreate.username || - !userToCreate?.roles.length || - userToCreate.roles.some( - role => !['TESTER', 'ADMIN', 'VENDOR'].includes(role.name) - ) - ) { - return res.status(400).send('Invalid user'); - } + const userToCreate = req.body; + if ( + !userToCreate || + !userToCreate.username || + !userToCreate?.roles.length || + userToCreate.roles.some( + role => !['TESTER', 'ADMIN', 'VENDOR'].includes(role.name) + ) + ) { + return res.status(400).send('Invalid user'); + } - let [user] = await getOrCreateUser({ - where: { username: userToCreate.username }, - values: { roles: userToCreate.roles }, - transaction: req.transaction - }); + let [user] = await getOrCreateUser({ + where: { username: userToCreate.username }, + values: { roles: userToCreate.roles }, + transaction: req.transaction + }); - req.session.user = user; + req.session.user = user; - res.status(200).send(''); + res.status(200).send(''); }; module.exports = setFakeUserController; diff --git a/server/controllers/TestController.js b/server/controllers/TestController.js index b40cec90c..f59d4bcf1 100644 --- a/server/controllers/TestController.js +++ b/server/controllers/TestController.js @@ -1,19 +1,19 @@ const TestService = require('../services/TestService'); async function importTests(req, res) { - const { git_hash } = req.body; - try { - await TestService.importTests(git_hash); - res.sendStatus(200); - } catch (error) { - // eslint-disable-next-line no-console - console.log(error.message); - // This is when the script fails because the git hash is invalid - // Sending semantic error. - res.sendStatus(422); - } + const { git_hash } = req.body; + try { + await TestService.importTests(git_hash); + res.sendStatus(200); + } catch (error) { + // eslint-disable-next-line no-console + console.log(error.message); + // This is when the script fails because the git hash is invalid + // Sending semantic error. + res.sendStatus(422); + } } module.exports = { - importTests + importTests }; diff --git a/server/graphql-context.js b/server/graphql-context.js index 86c6434d1..2f10719ca 100644 --- a/server/graphql-context.js +++ b/server/graphql-context.js @@ -2,16 +2,14 @@ const AtLoader = require('./models/loaders/AtLoader'); const BrowserLoader = require('./models/loaders/BrowserLoader'); const getGraphQLContext = ({ req }) => { - const user = - req && req.session && req.session.user ? req.session.user : null; + const user = req && req.session && req.session.user ? req.session.user : null; - // Req will not be defined when queries are made with apolloServer.executeOperation() - let transaction = req ? req.transaction : false; + // Req will not be defined when queries are made with apolloServer.executeOperation() + let transaction = req ? req.transaction : false; - const atLoader = AtLoader(); - const browserLoader = BrowserLoader(); - - return { user, atLoader, browserLoader, transaction }; + const atLoader = AtLoader(); + const browserLoader = BrowserLoader(); + return { user, atLoader, browserLoader, transaction }; }; module.exports = getGraphQLContext; diff --git a/server/graphql-schema.js b/server/graphql-schema.js index 440c04f9f..b6b50adfa 100644 --- a/server/graphql-schema.js +++ b/server/graphql-schema.js @@ -1,1573 +1,1577 @@ const { gql } = require('apollo-server'); const graphqlSchema = gql` + """ + Freeform data. + """ + scalar Any + + """ + The field does not return a response (useful for some mutations). + """ + scalar NoResponse + + """ + ISO-8601-formatted timestamp. + """ + scalar Timestamp + + """ + The categories of actions a user can complete in the app. + """ + enum Role { + """ + Whether the user can perform testing. Testers are specified in + testers.txt. Note that all admins are testers. + """ + TESTER + """ + Whether the user can perform administrative actions. Admins are members + of a special GitHub team, which is different for each app environment. + """ + ADMIN + """ + Whether the user can perform vendor actions, such as reviewing + candidate test plans. Vendors are specified in vendors.txt. + """ + VENDOR + } + + type User { + """ + Postgres-provided numeric ID. + """ + id: ID! + """ + The GitHub username of the person. + """ + username: String! + """ + List of types of actions the user can complete. + """ + roles: [Role]! + """ + Whether the user is an automation bot user. + """ + isBot: Boolean! + # TODO: Either use the recorded data somewhere or eliminate the field. + """ + The ATs the user has indicated they are able to test. + """ + ats: [At]! + } + + """ + The fields of the User type which can be updated after the User has been + created. It is a short list since most of the User fields originate outside + the app, i.e. the User roles are set by a GitHub team and txt file, and the + username is set in GitHub, etc. + """ + input UserInput { + """ + See User type for more information. + """ + atIds: [ID]! + } + + """ + The possible statuses for a CollectionJob. + """ + enum CollectionJobStatus { + QUEUED + RUNNING + COMPLETED + ERROR + CANCELLED + } + """ + A job which was scheduled to collect automated test results using the Response Collection System. + """ + type CollectionJob { + """ + Job Scheduler server-provided ID. + """ + id: ID! + """ + The status of the job, which can be "QUEUED", "RUNNING", "COMPLETED", + "ERROR", or "CANCELLED". + """ + status: CollectionJobStatus! + """ + An ID for the Test Plan Run which was created as a result of the Collection Job. + This will store the test results. + """ + testPlanRun: TestPlanRun + """ + The URL where the logs for the job can be found. + """ + externalLogsUrl: String + """ + An array of individual test status for every runnable test in the Job. + """ + testStatus: [CollectionJobTestStatus] + } + + """ + A status for a specific Test on a specific CollectionJob. + """ + type CollectionJobTestStatus { + """ + The test this status reflects. + """ + test: Test! + """ + The status of the test, which can be "QUEUED", "RUNNING", "COMPLETED", + "ERROR", or "CANCELLED" + """ + status: CollectionJobStatus! + } + + type Browser { + """ + Postgres-provided numeric ID. + """ + id: ID! + """ + Browser name like "Chrome". + """ + name: String! + """ + Consistent key name for browser like "chrome" or "safari_macos" + """ + key: String! + """ + A fully-qualified version like "99.0.4844.84" + """ + browserVersions: [BrowserVersion]! + """ + The Ats which can be run with the specific browser, for example, Jaws can be run with Chrome but not Safari, and Safari works with VoiceOver only. + """ + ats: [At]! + """ + The Ats which are required to move a TestPlanVersion to CANDIDATE phase. + """ + candidateAts: [At]! + """ + The Ats which are required to move a TestPlanVersion to RECOMMENDED phase. + """ + recommendedAts: [At]! + } + + """ + A version which has been used to collect test results. + """ + type BrowserVersion { + """ + Postgres-provided numeric ID + """ + id: ID! + """ + Version string + """ + name: String! + } + + """ + The fields on the BrowserVersion type which must be provided to create new + BrowserVersions. + """ + input BrowserVersionInput { + """ + See BrowserVersion type for more information. + """ + name: String! + } + + """ + An assistive technology to be tested, such as NVDA or JAWS. + """ + type At { + """ + Postgres-provided numeric ID. + """ + id: ID! + """ + Human-readable name for the AT, such as "NVDA". + """ + name: String! + """ + Consistent key value for lookups. + """ + key: String! + """ + Recorded version numbers + """ + atVersions: [AtVersion]! + """ + The browsers which can run the At, for example, Safari can run VoiceOver but not Jaws because Jaws is Windows only. + """ + browsers: [Browser]! + """ + The browsers which are required to move a TestPlanVersion to CANDIDATE phase. + """ + candidateBrowsers: [Browser]! + """ + The browsers which are required to move a TestPlanVersion to RECOMMENDED phase. + """ + recommendedBrowsers: [Browser]! + } + + """ + The version for a given assistive technology. + """ + type AtVersion { + """ + Postgres-provided numeric ID. + """ + id: ID! + """ + Human-readable name for the version, such as "2020.1". + """ + name: String! + """ + Date for approximate availability of the version. + """ + releasedAt: Timestamp! + """ + Whether this AT version is supported by automation. + """ + supportedByAutomation: Boolean! + } + + """ + The fields on the AtVersion type which can be used to create or update the + AtVersion. + """ + input AtVersionInput { + """ + See AtVersion type for more information. + """ + name: String! + """ + See AtVersion type for more information. + """ + releasedAt: Timestamp + } + + """ + A suite of tests which keeps its identity as it evolves over time. + """ + type TestPlan { + """ + This is the same as the directory field. Sometimes you want to think of + that string as an ID and sometimes you want to explicitly refer to it + as a directory, and this allows you to do both. + """ + id: ID! + """ + The formal name of the test plan + """ + title: String! + """ + Corresponds to directory in the ARIA-AT repo which stores the test plan, + e.g. "checkbox-tri-state" or "disclosure-navigation" + """ + directory: String! + # TODO: determine what to do when a directory is removed from the + # ARIA-AT repo + # isObsolete: Boolean! + """ + Gets the most recent version imported from the test plan's directory. + """ + latestTestPlanVersion: TestPlanVersion + """ + Gets all historic versions of the test plan. + """ + testPlanVersions: [TestPlanVersion]! + """ + A list of all issues which have filed through "Raise an Issue" buttons + in the app. Note that results will be cached for at least ten seconds. + """ + issues: [Issue]! + } + + """ + The life-cycle of a TestPlanVersion from the point it is imported automatically + or by an admin until it is saved an available to the public on the reports page. + """ + enum TestPlanVersionPhase { + """ + Accepting new TestPlanRuns from testers. + """ + RD + """ + Accepting new TestPlanRuns from testers. + """ + DRAFT + """ + Testing is complete and consistent, and ready to be displayed in the + Candidate Tests and Reports section of the app. + """ + CANDIDATE + """ + Testing is complete and consistent, and ready to be displayed in the + Reports section of the app as being recommended. + """ + RECOMMENDED + """ + The TestPlanVersion is now outdated and replaced by another version. + """ + DEPRECATED + } + + """ + A snapshot of time for a test plan, containing all the test plan data, + including the actual executable tests. + """ + type TestPlanVersion { + """ + Postgres-provided numeric ID. + """ + id: ID! + # TODO: fix bug where the title is missing and make this field required + """ + The title of the TestPlan at this point in time. + """ + title: String + """ + See TestPlanVersionPhase type for more information. + """ + phase: TestPlanVersionPhase! + """ + Date of when the TestPlanVersion last updated to the 'Draft' + phase. + """ + draftPhaseReachedAt: Timestamp + """ + Date of when the TestPlanVersion was last updated to the 'Candidate' + phase. + """ + candidatePhaseReachedAt: Timestamp + """ + Date of when the TestPlanVersion was last updated to the 'Recommended' + phase. + """ + recommendedPhaseReachedAt: Timestamp + """ + The intended target date for the final TestPlanVersion phase promotion. + Based on the ARIA-AT Working Mode. + https://github.com/w3c/aria-at/wiki/Working-Mode + """ + recommendedPhaseTargetDate: Timestamp + """ + The date when the TestPlanVersion was deprecated. + """ + deprecatedAt: Timestamp + """ + The TestPlan this TestPlanVersion is a snapshot of. + """ + testPlan: TestPlan! + """ + A git sha corresponding to the current git commit at the time the + version was imported from the ARIA-AT repo. Used to version the test + plan over time. + """ + gitSha: String! + """ + Git commit message corresponding to the git sha's commit. + """ + gitMessage: String! + """ + The date (originating in Git) corresponding to the Git sha's commit. + This can also be considered as the time for when R & D was complete + """ + updatedAt: Timestamp! + """ + An easily readable representation of the date associated with the + version, formatted like V23.09.28 (or V23.09.28-1 in the case that + there are multiple versions on the same day). + """ + versionString: String! + # TODO: consider moving to the Scenario type if we support multiple + # test pages for one TestPlanVersion (i.e. testing that + # and