diff --git a/client/components/CandidateReview/CandidateTestPlanRun/CandidateTestPlanRun.css b/client/components/CandidateReview/CandidateTestPlanRun/CandidateTestPlanRun.css index 82cb873aa..67172a8ef 100644 --- a/client/components/CandidateReview/CandidateTestPlanRun/CandidateTestPlanRun.css +++ b/client/components/CandidateReview/CandidateTestPlanRun/CandidateTestPlanRun.css @@ -152,3 +152,8 @@ position: relative; top: -5px; } + +.begin-review-button-container { + margin-left: auto; + flex-grow: 0; +} diff --git a/client/components/CandidateReview/CandidateTestPlanRun/index.jsx b/client/components/CandidateReview/CandidateTestPlanRun/index.jsx index a75525389..37fcb1d60 100644 --- a/client/components/CandidateReview/CandidateTestPlanRun/index.jsx +++ b/client/components/CandidateReview/CandidateTestPlanRun/index.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useRef, useState, useMemo } from 'react'; import { useQuery, useMutation } from '@apollo/client'; import TestNavigator from '../../TestRun/TestNavigator'; import InstructionsRenderer from './InstructionsRenderer'; @@ -33,6 +33,8 @@ import createIssueLink, { import RunHistory from '../../common/RunHistory'; import { useUrlTestIndex } from '../../../hooks/useUrlTestIndex'; import NotApprovedModal from '../CandidateModals/NotApprovedModal'; +import FailingAssertionsSummaryTable from '../../FailingAssertionsSummary/Table'; +import FailingAssertionsSummaryHeading from '../../FailingAssertionsSummary/Heading'; const CandidateTestPlanRun = () => { const { atId, testPlanVersionId } = useParams(); @@ -59,9 +61,12 @@ const CandidateTestPlanRun = () => { const [firstTimeViewing, setFirstTimeViewing] = useState(false); const [viewedTests, setViewedTests] = useState([]); const [testsLength, setTestsLength] = useState(0); - const [currentTestIndex, setCurrentTestIndex] = useUrlTestIndex(testsLength); + const [currentTestIndex, setCurrentTestIndex] = useUrlTestIndex({ + minTestIndex: -1, + maxTestIndex: testsLength + }); const [showTestNavigator, setShowTestNavigator] = useState(true); - const [isFirstTest, setIsFirstTest] = useState(true); + const [isFirstTest, setIsFirstTest] = useState(currentTestIndex === 0); const [isLastTest, setIsLastTest] = useState(false); const [feedbackModalShowing, setFeedbackModalShowing] = useState(false); const [confirmationModal, setConfirmationModal] = useState(null); @@ -70,15 +75,24 @@ const CandidateTestPlanRun = () => { const [showRunHistory, setShowRunHistory] = useState(false); const [showBrowserClicks, setShowBrowserClicks] = useState([]); + const isSummaryView = currentTestIndex === -1; + const isLaptopOrLarger = useMediaQuery({ query: '(min-width: 792px)' }); const toggleTestNavigator = () => setShowTestNavigator(!showTestNavigator); + const hasFailingAssertionsSummary = useMemo(() => { + return data?.testPlanReports[0]?.metrics?.assertionsFailedCount > 0; + }, [data?.testPlanReports]); const handleTestClick = async index => { setCurrentTestIndex(index); - if (index === 0) { + if (index === -1) { + // Summary view + setIsFirstTest(false); + setIsLastTest(false); + } else if (index === 0) { setIsFirstTest(true); setIsLastTest(false); } else if (index === tests.length - 1) { @@ -89,6 +103,7 @@ const CandidateTestPlanRun = () => { setIsLastTest(false); } }; + const handleNextTestClick = async () => { navigateTests( false, @@ -116,6 +131,7 @@ const CandidateTestPlanRun = () => { }; const updateTestViewed = async () => { + if (!currentTest) return; const userPreviouslyViewedTest = viewedTests.includes(currentTest.id); if (!userPreviouslyViewedTest) { setFirstTimeViewing(true); @@ -214,11 +230,11 @@ const CandidateTestPlanRun = () => { if (data) { updateVendorStatus(); updateTestViewed(); - if (currentTestIndex !== 0) setIsFirstTest(false); + setIsFirstTest(currentTestIndex === 0); if (tests?.length === 1) setIsLastTest(true); if (currentTestIndex + 1 === tests?.length) setIsLastTest(true); } - }, [reviewStatus, currentTestIndex]); + }, [reviewStatus, currentTestIndex, data]); useEffect(() => { // Prevent a plan with only 1 test from immediately pushing the focus to the @@ -309,14 +325,14 @@ const CandidateTestPlanRun = () => { issue => issue.isCandidateReview && issue.feedbackType === 'CHANGES_REQUESTED' && - issue.testNumberFilteredByAt === currentTest.seq + issue.testNumberFilteredByAt === currentTest?.seq ); const feedbackIssues = testPlanReport.issues?.filter( issue => issue.isCandidateReview && issue.feedbackType === 'FEEDBACK' && - issue.testNumberFilteredByAt === currentTest.seq + issue.testNumberFilteredByAt === currentTest?.seq ); const issue = { @@ -325,10 +341,10 @@ const CandidateTestPlanRun = () => { testPlanTitle: testPlanVersion.title, testPlanDirectory: testPlanVersion.testPlan.directory, versionString: testPlanVersion.versionString, - testTitle: currentTest.title, - testRowNumber: currentTest.rowNumber, - testSequenceNumber: currentTest.seq, - testRenderedUrl: currentTest.renderedUrl, + testTitle: currentTest?.title, + testRowNumber: currentTest?.rowNumber, + testSequenceNumber: currentTest?.seq, + testRenderedUrl: currentTest?.renderedUrl, atName: testPlanReport.at.name }; @@ -352,7 +368,7 @@ const CandidateTestPlanRun = () => { isCandidateReviewChangesRequested: false, testPlanTitle: testPlanVersion.title, versionString: testPlanVersion.versionString, - testRowNumber: currentTest.rowNumber, + testRowNumber: currentTest?.rowNumber, username: data.me.username, atName: testPlanReport.at.name }; @@ -381,163 +397,209 @@ const CandidateTestPlanRun = () => { '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}`}{' '} - {`${testPlanReport?.latestAtVersionReleasedAt?.name ?? ''}`} - {viewedTests.includes(currentTest.id) && !firstTimeViewing && ' '} - {viewedTests.includes(currentTest.id) && !firstTimeViewing && ( - - Previously Viewed - + const getHeading = () => { + return ( +
+ {isSummaryView ? ( + <> + Candidate Test Plan Review + + + ) : ( + <> +
+ 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}`}{' '} + {`${testPlanReport?.latestAtVersionReleasedAt?.name ?? ''}`} + {viewedTests.includes(currentTest.id) && !firstTimeViewing && ' '} + {viewedTests.includes(currentTest.id) && !firstTimeViewing && ( + + Previously Viewed + + )} +

+ )} -

-
- ); + + ); + }; - const testInfo = ( -
-
-
- Candidate Test Plan:{' '} - - {`${ - testPlanVersion.title || testPlanVersion.testPlan?.directory || '' - } ${testPlanVersion.versionString}`} - + const getTestInfo = () => { + return ( +
+
+
-
-
-
- Review status by {at} Representative: {`${reviewStatusText} `} +
+
+ Review status by {at} Representative:{' '} + {`${reviewStatusText} `} +
-
-
-
- Target Completion Date: - {targetCompletionDate} +
+
+ 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 getFeedback = () => { + if (isSummaryView) { + return null; + } + return ( + 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}` - ), - 'Run History' - ]} - onClick={[ - () => setShowInstructions(!showInstructions), - ...showBrowserClicks, - () => setShowRunHistory(!showRunHistory) - ]} - expanded={[showInstructions, ...showBrowserBools, showRunHistory]} - disclosureContainerView={[ - , - ...testPlanReports.map(testPlanReport => { - const testResult = - testPlanReport.finalizedTestResults[currentTestIndex]; - - const { - assertionsPassedCount, - mustAssertionsFailedCount, - shouldAssertionsFailedCount, - mayAssertionsFailedCount - } = getMetrics({ testResult }); - - const mustShouldAssertionsFailedCount = - mustAssertionsFailedCount + shouldAssertionsFailedCount; - - return ( - <> -

- Test Results ( - {assertionsPassedCount} passed,  - {mustShouldAssertionsFailedCount} failed,  - {mayAssertionsFailedCount} unsupported) -

- { + return ( +
+ {isSummaryView ? ( +
+ `#${assertion.testIndex + 1}`} + /> +
+ ) : ( + <> +

{currentTest.title}

+ + `Test Results for ${testPlanReport.browser.name}` + ), + 'Run History' + ]} + onClick={[ + () => setShowInstructions(!showInstructions), + ...showBrowserClicks, + () => setShowRunHistory(!showRunHistory) + ]} + expanded={[showInstructions, ...showBrowserBools, showRunHistory]} + disclosureContainerView={[ + , + ...testPlanReports.map(testPlanReport => { + const testResult = + testPlanReport.finalizedTestResults[currentTestIndex]; + + const { + assertionsPassedCount, + mustAssertionsFailedCount, + shouldAssertionsFailedCount, + mayAssertionsFailedCount + } = getMetrics({ testResult }); + + const mustShouldAssertionsFailedCount = + mustAssertionsFailedCount + shouldAssertionsFailedCount; + + return ( + <> +

+ Test Results ( + {assertionsPassedCount} passed,  + {mustShouldAssertionsFailedCount} failed,  + {mayAssertionsFailedCount} unsupported) +

+ + + ); + }), + - - ); - }), - - ]} - stacked - >
-
- ); + ]} + stacked + >
+ + )} +
+ ); + }; return ( @@ -557,40 +619,56 @@ const CandidateTestPlanRun = () => { /> - {heading} - {testInfo} + {getHeading()} + {getTestInfo()} - {feedback} - {results} + {getFeedback()} + {getContent()}
    -
  • - -
  • -
  • - -
  • + {isSummaryView || + (isFirstTest && !hasFailingAssertionsSummary) ? null : ( +
  • + +
  • + )} + {isSummaryView ? ( +
  • + +
  • + ) : ( +
  • + +
  • + )}
  • +
  • + AT Interoperability Reports +
  • +
  • + Data Management +
  • +
  • + Test Queue +
  • +
  • + Candidate Review +
  • +
  • + Settings +
  • +
  • +
    + Signed in as joe-the-admin +
    + Sign out +
  • +
+
+ +
+
+
+
+ +
+
+
+ Candidate Test Plan Review +

+ Summary of Failing Assertions (2 must, 1 should) +

+
+
+
+
+ Candidate Test Plan: + Modal Dialog Example V22.05.26 +
+
+
+
+ Review status by JAWS Representative: In Progress +
+
+
+
+ Target Completion Date: January 2, 2023 +
+
+
+
+
+
+
+
+
+
+

+ 2 assertions failed across 26 commands in 2 tests. +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Test NameCommandAssertion PriorityAssertionJAWS Response
+ Close a modal dialog in reading mode + EscapeMUSTRole 'button' is conveyedautomatically seeded sample output
+ Close a modal dialog in interaction + mode + EscapeMUSTRole 'button' is conveyedautomatically seeded sample output
+
+
+
+
+
+
    +
  • + +
  • +
  • + +
  • +
+
+
+
+
+

Test Review Options

+ +
+
+
+
+
+
+
+
+
+ +
+ + + diff --git a/client/tests/e2e/snapshots/saved/_candidate-test-plan_24_1.html b/client/tests/e2e/snapshots/saved/_candidate-test-plan_24_1.html index 81b0004b7..a90440370 100644 --- a/client/tests/e2e/snapshots/saved/_candidate-test-plan_24_1.html +++ b/client/tests/e2e/snapshots/saved/_candidate-test-plan_24_1.html @@ -124,6 +124,13 @@
    +
    + Summary of Failing Assertions +
  1. Open a Modal Dialog in reading mode
  2. diff --git a/client/tests/e2e/snapshots/saved/_report_67_targets_20.html b/client/tests/e2e/snapshots/saved/_report_67_targets_20.html index e131a9c75..7d0493541 100644 --- a/client/tests/e2e/snapshots/saved/_report_67_targets_20.html +++ b/client/tests/e2e/snapshots/saved/_report_67_targets_20.html @@ -367,6 +367,9 @@

    Results for NVDA 2023.3.3 and Chrome

    +

    + Summary of Failing Assertions (0 must, 1 should) +

    Test 1: Navigate forwards to a mixed checkbox in reading diff --git a/client/tests/e2e/snapshots/utils.js b/client/tests/e2e/snapshots/utils.js index 749a0963b..0c4aad1c7 100644 --- a/client/tests/e2e/snapshots/utils.js +++ b/client/tests/e2e/snapshots/utils.js @@ -14,6 +14,7 @@ const snapshotRoutes = [ '/run/2', '/data-management/meter', '/candidate-test-plan/24/1', + '/candidate-test-plan/24/1#summary', '/test-queue/2/conflicts', '/report/67/targets/20' ]; diff --git a/client/utils/navigateTests.js b/client/utils/navigateTests.js index 7c8346f16..aaa2dfab8 100644 --- a/client/utils/navigateTests.js +++ b/client/utils/navigateTests.js @@ -6,6 +6,22 @@ export const navigateTests = ( setIsFirstTest = () => {}, setIsLastTest = () => {} ) => { + // If currentTest is undefined, we are on the summary view + if (!currentTest) { + if (!previous) { + const firstTest = tests.find(t => t.seq === 1); + setCurrentTestIndex(firstTest.index); + setIsFirstTest(true); + setIsLastTest(false); + return { + currentIndex: firstTest.index, + isFirstTest: true, + isLastTest: false + }; + } + // No previous from summary + return { currentIndex: -1, isFirstTest: false, isLastTest: false }; + } // assume navigation forward if previous is false let newTestIndex = currentTest.seq; if (!previous) { @@ -15,6 +31,13 @@ export const navigateTests = ( } else { // previous const newTestIndexToEval = currentTest.seq - 1; + // Go to summary when going previous from first test + if (newTestIndexToEval === 0) { + setCurrentTestIndex(-1); + setIsFirstTest(false); + setIsLastTest(false); + return { currentIndex: -1, isFirstTest: false, isLastTest: false }; + } if (newTestIndexToEval >= 1 && newTestIndexToEval <= tests.length) newTestIndex = newTestIndexToEval; }