Skip to content

Commit 098cda8

Browse files
authored
Merge pull request #1271 from w3c/development
Create November 7, 2024 Release Includes the following changes: * #1265, to address #1248 * #1266, to address #1251 and #1252 * #1269, to address #1250 * #1272
2 parents 475230a + 77ba7cf commit 098cda8

File tree

20 files changed

+662
-191
lines changed

20 files changed

+662
-191
lines changed

client/components/CandidateReview/CandidateTestPlanRun/index.jsx

+11-4
Original file line numberDiff line numberDiff line change
@@ -507,16 +507,23 @@ const CandidateTestPlanRun = () => {
507507
const testResult =
508508
testPlanReport.finalizedTestResults[currentTestIndex];
509509

510-
const { assertionsPassedCount, assertionsFailedCount } = getMetrics(
511-
{ testResult }
512-
);
510+
const {
511+
assertionsPassedCount,
512+
mustAssertionsFailedCount,
513+
shouldAssertionsFailedCount,
514+
mayAssertionsFailedCount
515+
} = getMetrics({ testResult });
516+
517+
const mustShouldAssertionsFailedCount =
518+
mustAssertionsFailedCount + shouldAssertionsFailedCount;
513519

514520
return (
515521
<>
516522
<h2 className="test-results-header">
517523
Test Results&nbsp;(
518524
{assertionsPassedCount} passed,&nbsp;
519-
{assertionsFailedCount} failed)
525+
{mustShouldAssertionsFailedCount} failed,&nbsp;
526+
{mayAssertionsFailedCount} unsupported)
520527
</h2>
521528
<TestPlanResultsTable
522529
key={`${testPlanReport.id} + ${testResult.id}`}

client/components/Reports/SummarizeTestPlanReport.jsx

+12-3
Original file line numberDiff line numberDiff line change
@@ -272,17 +272,26 @@ const SummarizeTestPlanReport = ({ testPlanVersion, testPlanReports }) => {
272272
'https://aria-at.netlify.app'
273273
);
274274

275-
const { assertionsPassedCount, assertionsFailedCount } = getMetrics({
275+
const {
276+
assertionsPassedCount,
277+
mustAssertionsFailedCount,
278+
shouldAssertionsFailedCount,
279+
mayAssertionsFailedCount
280+
} = getMetrics({
276281
testResult
277282
});
278283

284+
const mustShouldAssertionsFailedCount =
285+
mustAssertionsFailedCount + shouldAssertionsFailedCount;
286+
279287
return (
280288
<Fragment key={testResult.id}>
281289
<div className="test-result-heading">
282290
<h2 id={`result-${testResult.id}`} tabIndex="-1">
283291
Test {index + 1}: {test.title}&nbsp;(
284-
{assertionsPassedCount}
285-
&nbsp;passed, {assertionsFailedCount} failed)
292+
{assertionsPassedCount} passed,&nbsp;
293+
{mustShouldAssertionsFailedCount} failed,&nbsp;
294+
{mayAssertionsFailedCount} unsupported)
286295
<DisclaimerInfo phase={testPlanVersion.phase} />
287296
</h2>
288297
<div className="test-result-buttons">
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
import React, { useMemo, useState } from 'react';
2+
import { ThemeTable, ThemeTableUnavailable } from '../common/ThemeTable';
3+
import { dates } from 'shared';
4+
import { NoneText } from '../TestPlanVersionsPage';
5+
import SortableTableHeader, {
6+
TABLE_SORT_ORDERS
7+
} from '../common/SortableTableHeader';
8+
import FilterButtons from '../common/FilterButtons';
9+
import { IssuePropType } from '../common/proptypes';
10+
import PropTypes from 'prop-types';
11+
12+
const FILTER_OPTIONS = {
13+
OPEN: 'Open',
14+
CLOSED: 'Closed',
15+
ALL: 'All'
16+
};
17+
18+
const SORT_FIELDS = {
19+
AUTHOR: 'author',
20+
TITLE: 'title',
21+
STATUS: 'status',
22+
AT: 'at',
23+
CREATED_AT: 'createdAt',
24+
CLOSED_AT: 'closedAt'
25+
};
26+
27+
const SortableIssuesTable = ({ issues }) => {
28+
const [activeSort, setActiveSort] = useState(SORT_FIELDS.STATUS);
29+
const [sortOrder, setSortOrder] = useState(TABLE_SORT_ORDERS.ASC);
30+
const [activeFilter, setActiveFilter] = useState('OPEN');
31+
32+
const issueStats = useMemo(() => {
33+
const openIssues = issues.filter(issue => issue.isOpen).length;
34+
const closedIssues = issues.length - openIssues;
35+
return { openIssues, closedIssues };
36+
}, [issues]);
37+
38+
// Helper function to get sortable value from issue
39+
const getSortableValue = (issue, sortField) => {
40+
switch (sortField) {
41+
case SORT_FIELDS.AUTHOR:
42+
return issue.author;
43+
case SORT_FIELDS.TITLE:
44+
return issue.title;
45+
case SORT_FIELDS.AT:
46+
return issue.at?.name ?? '';
47+
case SORT_FIELDS.CREATED_AT:
48+
return new Date(issue.createdAt);
49+
case SORT_FIELDS.CLOSED_AT:
50+
return issue.closedAt ? new Date(issue.closedAt) : new Date(0);
51+
default:
52+
return '';
53+
}
54+
};
55+
56+
const compareByStatus = (a, b) => {
57+
if (a.isOpen !== b.isOpen) {
58+
if (sortOrder === TABLE_SORT_ORDERS.ASC) {
59+
return a.isOpen ? -1 : 1; // Open first for ascending
60+
}
61+
return a.isOpen ? 1 : -1; // Closed first for descending
62+
}
63+
// If status is the same, sort by date created (newest first)
64+
return new Date(b.createdAt) - new Date(a.createdAt);
65+
};
66+
67+
const compareValues = (aValue, bValue) => {
68+
return sortOrder === TABLE_SORT_ORDERS.ASC
69+
? aValue < bValue
70+
? -1
71+
: 1
72+
: aValue > bValue
73+
? -1
74+
: 1;
75+
};
76+
77+
const sortedAndFilteredIssues = useMemo(() => {
78+
// Filter issues
79+
const filtered =
80+
activeFilter === 'ALL'
81+
? issues
82+
: issues.filter(issue => issue.isOpen === (activeFilter === 'OPEN'));
83+
84+
// Sort issues
85+
return filtered.sort((a, b) => {
86+
// Special handling for status sorting
87+
if (activeSort === SORT_FIELDS.STATUS) {
88+
return compareByStatus(a, b);
89+
}
90+
91+
// Normal sorting for other fields
92+
const aValue = getSortableValue(a, activeSort);
93+
const bValue = getSortableValue(b, activeSort);
94+
return compareValues(aValue, bValue);
95+
});
96+
}, [issues, activeSort, sortOrder, activeFilter]);
97+
98+
const handleSort = column => newSortOrder => {
99+
setActiveSort(column);
100+
setSortOrder(newSortOrder);
101+
};
102+
103+
const renderTableHeader = () => (
104+
<thead>
105+
<tr>
106+
{[
107+
{ field: SORT_FIELDS.AUTHOR, title: 'Author' },
108+
{ field: SORT_FIELDS.TITLE, title: 'Issue' },
109+
{ field: SORT_FIELDS.STATUS, title: 'Status' },
110+
{ field: SORT_FIELDS.AT, title: 'Assistive Technology' },
111+
{ field: SORT_FIELDS.CREATED_AT, title: 'Created On' },
112+
{ field: SORT_FIELDS.CLOSED_AT, title: 'Closed On' }
113+
].map(({ field, title }) => (
114+
<SortableTableHeader
115+
key={field}
116+
title={title}
117+
active={activeSort === field}
118+
onSort={handleSort(field)}
119+
data-test={`sort-${field.toLowerCase()}`}
120+
/>
121+
))}
122+
</tr>
123+
</thead>
124+
);
125+
126+
const renderTableBody = () => (
127+
<tbody>
128+
{sortedAndFilteredIssues.map(issue => (
129+
<tr
130+
key={issue.link}
131+
data-test="issue-row"
132+
data-status={issue.isOpen ? 'open' : 'closed'}
133+
>
134+
<td>
135+
<a
136+
target="_blank"
137+
rel="noreferrer"
138+
href={`https://github.com/${issue.author}`}
139+
>
140+
{issue.author}
141+
</a>
142+
</td>
143+
<td>
144+
<a target="_blank" rel="noreferrer" href={issue.link}>
145+
{issue.title}
146+
</a>
147+
</td>
148+
<td data-test="issue-status">{issue.isOpen ? 'Open' : 'Closed'}</td>
149+
<td>{issue.at?.name ?? 'AT not specified'}</td>
150+
<td>{dates.convertDateToString(issue.createdAt, 'MMM D, YYYY')}</td>
151+
<td>
152+
{!issue.closedAt ? (
153+
<NoneText>N/A</NoneText>
154+
) : (
155+
dates.convertDateToString(issue.closedAt, 'MMM D, YYYY')
156+
)}
157+
</td>
158+
</tr>
159+
))}
160+
</tbody>
161+
);
162+
163+
return (
164+
<>
165+
<h2 id="github-issues">
166+
GitHub Issues ({issueStats.openIssues} open, {issueStats.closedIssues}
167+
&nbsp;closed)
168+
</h2>
169+
<FilterButtons
170+
filterLabel="Filter"
171+
filterAriaLabel="Filter GitHub issues"
172+
filterOptions={FILTER_OPTIONS}
173+
activeFilter={activeFilter}
174+
onFilterChange={setActiveFilter}
175+
/>
176+
{!sortedAndFilteredIssues.length ? (
177+
<ThemeTableUnavailable aria-labelledby="github-issues">
178+
No GitHub Issues
179+
</ThemeTableUnavailable>
180+
) : (
181+
<ThemeTable
182+
bordered
183+
aria-labelledby="github-issues"
184+
data-test="issues-table"
185+
>
186+
{renderTableHeader()}
187+
{renderTableBody()}
188+
</ThemeTable>
189+
)}
190+
</>
191+
);
192+
};
193+
194+
SortableIssuesTable.propTypes = {
195+
issues: PropTypes.arrayOf(IssuePropType).isRequired
196+
};
197+
198+
export default SortableIssuesTable;

client/components/TestPlanVersionsPage/index.jsx

+13-70
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useRef } from 'react';
1+
import React, { useMemo, useRef } from 'react';
22
import { useQuery } from '@apollo/client';
33
import { TEST_PLAN_VERSIONS_PAGE_QUERY } from './queries';
44
import PageStatus from '../common/PageStatus';
@@ -7,7 +7,6 @@ import { Helmet } from 'react-helmet';
77
import { Container } from 'react-bootstrap';
88
import {
99
ThemeTable,
10-
ThemeTableUnavailable,
1110
ThemeTableHeaderH3 as UnstyledThemeTableHeader
1211
} from '../common/ThemeTable';
1312
import VersionString from '../common/VersionString';
@@ -22,6 +21,7 @@ import {
2221
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
2322
import DisclosureComponentUnstyled from '../common/DisclosureComponent';
2423
import useForceUpdate from '../../hooks/useForceUpdate';
24+
import SortableIssuesTable from '../SortableIssuesTable';
2525

2626
const DisclosureContainer = styled.div`
2727
.timeline-for-version-table {
@@ -40,7 +40,7 @@ const DisclosureComponent = styled(DisclosureComponentUnstyled)`
4040
}
4141
`;
4242

43-
const NoneText = styled.span`
43+
export const NoneText = styled.span`
4444
font-style: italic;
4545
color: #6a7989;
4646
`;
@@ -88,6 +88,12 @@ const TestPlanVersionsPage = () => {
8888
const expandedVersionSections = useRef();
8989
const toggleVersionSections = useRef();
9090

91+
// GraphQL results are read only so they need to be cloned
92+
// before passing to SortableIssuesTable
93+
const issues = useMemo(() => {
94+
return data ? [...data.testPlan.issues] : [];
95+
}, [data]);
96+
9197
if (error) {
9298
return (
9399
<PageStatus
@@ -192,23 +198,12 @@ const TestPlanVersionsPage = () => {
192198
return derivedPhaseDeprecatedDuring;
193199
};
194200

195-
const testPlan = data.testPlan;
201+
const { testPlan, ats } = data;
196202

197-
// GraphQL results are read only so they need to be cloned before sorting
198-
const issues = [...testPlan.issues].sort((a, b) => {
199-
const aCreatedAt = new Date(a.createdAt);
200-
const bCreatedAt = new Date(b.createdAt);
201-
return bCreatedAt - aCreatedAt;
203+
const testPlanVersions = testPlan.testPlanVersions.slice().sort((a, b) => {
204+
return new Date(b.updatedAt) - new Date(a.updatedAt);
202205
});
203206

204-
const ats = data.ats;
205-
206-
const testPlanVersions = data.testPlan.testPlanVersions
207-
.slice()
208-
.sort((a, b) => {
209-
return new Date(b.updatedAt) - new Date(a.updatedAt);
210-
});
211-
212207
const timelineForAllVersions = [];
213208

214209
testPlanVersions.forEach(testPlanVersion => {
@@ -390,59 +385,7 @@ const TestPlanVersionsPage = () => {
390385
<PageSpacer />
391386
</>
392387
)}
393-
<ThemeTableHeader id="github-issues">GitHub Issues</ThemeTableHeader>
394-
{!issues.length ? (
395-
<ThemeTableUnavailable aria-labelledby="github-issues">
396-
No GitHub Issues
397-
</ThemeTableUnavailable>
398-
) : (
399-
<ThemeTable bordered responsive aria-labelledby="github-issues">
400-
<thead>
401-
<tr>
402-
<th>Author</th>
403-
<th>Issue</th>
404-
<th>Status</th>
405-
<th>AT</th>
406-
<th>Created On</th>
407-
<th>Closed On</th>
408-
</tr>
409-
</thead>
410-
<tbody>
411-
{issues.map(issue => {
412-
return (
413-
<tr key={issue.link}>
414-
<td>
415-
<a
416-
target="_blank"
417-
rel="noreferrer"
418-
href={`https://github.com/${issue.author}`}
419-
>
420-
{issue.author}
421-
</a>
422-
</td>
423-
<td>
424-
<a target="_blank" rel="noreferrer" href={issue.link}>
425-
{issue.title}
426-
</a>
427-
</td>
428-
<td>{issue.isOpen ? 'Open' : 'Closed'}</td>
429-
<td>{issue.at?.name ?? 'AT not specified'}</td>
430-
<td>
431-
{dates.convertDateToString(issue.createdAt, 'MMM D, YYYY')}
432-
</td>
433-
<td>
434-
{!issue.closedAt ? (
435-
<NoneText>N/A</NoneText>
436-
) : (
437-
dates.convertDateToString(issue.closedAt, 'MMM D, YYYY')
438-
)}
439-
</td>
440-
</tr>
441-
);
442-
})}
443-
</tbody>
444-
</ThemeTable>
445-
)}
388+
<SortableIssuesTable issues={issues} />
446389
<PageSpacer />
447390
<ThemeTableHeader id="timeline-for-all-versions">
448391
Timeline for All Versions

0 commit comments

Comments
 (0)