Skip to content

Commit 284ae40

Browse files
authored
Merge pull request #1616 from w3c/releases
November 4, 2025 Production Release [v1.24.0] Includes changes recently included in the [releases branch](https://github.com/w3c/aria-at-app/tree/releases) through #1615. [Latest CHANGELOG.md update: v1.24.0](https://github.com/w3c/aria-at-app/blob/development/CHANGELOG.md#1240-2025-11-04).
2 parents 42a9cbc + adb1745 commit 284ae40

File tree

121 files changed

+7173
-408
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

121 files changed

+7173
-408
lines changed

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
## [1.24.0](https://github.com/w3c/aria-at-app/compare/v1.23.0...v1.24.0) (2025-11-04)
2+
3+
4+
### Features
5+
6+
* `AtBugs` for tracking known AT issues ([#1577](https://github.com/w3c/aria-at-app/issues/1577)) ([a92724e](https://github.com/w3c/aria-at-app/commit/a92724eff3897087c1875111d696aee4f54df533))
7+
* tab urls ([#1606](https://github.com/w3c/aria-at-app/issues/1606)) ([d3262e6](https://github.com/w3c/aria-at-app/commit/d3262e628e951f6a461850e7d13c17fd3fe1d55a))
8+
9+
10+
### Bug Fixes
11+
12+
* aria/html feature front end fixes ([#1598](https://github.com/w3c/aria-at-app/issues/1598)) ([52841bc](https://github.com/w3c/aria-at-app/commit/52841bc550894ae74517e4001f496eb9f80e1ce8))
13+
* Incorrect sort order on reports page Test Plans tab ([#1612](https://github.com/w3c/aria-at-app/issues/1612)) ([144e797](https://github.com/w3c/aria-at-app/commit/144e7972a8035a99fa6e2788295039818ecf6d33))
14+
* Show untestable vs passing/failing assertions as conflicts ([#1599](https://github.com/w3c/aria-at-app/issues/1599)) ([4cb0793](https://github.com/w3c/aria-at-app/commit/4cb079316241f1dd1dbbed0afff4120696caf2da))
15+
116
## [1.23.0](https://github.com/w3c/aria-at-app/compare/v1.22.0...v1.23.0) (2025-10-30)
217

318

client/components/CandidateReview/CandidateTestPlanRun/index.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,7 @@ const CandidateTestPlanRun = () => {
655655
<FailingAssertionsSummaryTable
656656
testPlanReport={testPlanReports[0]}
657657
atName={at}
658+
testPlanVersion={testPlanVersion}
658659
getLinkUrl={assertion => `#${assertion.testIndex + 1}`}
659660
/>
660661
</div>
@@ -676,6 +677,7 @@ const CandidateTestPlanRun = () => {
676677
<NegativeSideEffectsSummaryTable
677678
testPlanReport={testPlanReports[0]}
678679
atName={at}
680+
testPlanVersion={testPlanVersion}
679681
getLinkUrl={assertion => `#${assertion.testIndex + 1}`}
680682
/>
681683
</div>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
4+
const AssertionDetails = ({ assertion }) => {
5+
const displayValue = (value, fallback = 'N/A') => {
6+
return value || fallback;
7+
};
8+
9+
return (
10+
<div className="mb-2">
11+
<div>
12+
<strong>Test Name:</strong> {displayValue(assertion?.testTitle)}
13+
</div>
14+
<div>
15+
<strong>Command:</strong> {displayValue(assertion?.scenarioCommands)}
16+
</div>
17+
<div>
18+
<strong>Assertion:</strong> {displayValue(assertion?.assertionText)}
19+
</div>
20+
<div>
21+
<strong>AT Response:</strong> {displayValue(assertion?.output)}
22+
</div>
23+
<div>
24+
<strong>AT Version & Browser version:</strong>{' '}
25+
{assertion?.atVersionName || '—'}
26+
{assertion?.browserVersionName
27+
? `, ${assertion.browserVersionName}`
28+
: ''}
29+
</div>
30+
</div>
31+
);
32+
};
33+
34+
AssertionDetails.propTypes = {
35+
assertion: PropTypes.shape({
36+
testTitle: PropTypes.string,
37+
scenarioCommands: PropTypes.string,
38+
assertionText: PropTypes.string,
39+
output: PropTypes.string,
40+
atVersionName: PropTypes.string,
41+
browserVersionName: PropTypes.string
42+
})
43+
};
44+
45+
export default AssertionDetails;
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import React, { Component } from 'react';
2+
import PropTypes from 'prop-types';
3+
4+
// React Error Boundary requires class components
5+
class BugLinkingErrorBoundary extends Component {
6+
constructor(props) {
7+
super(props);
8+
this.state = { hasError: false };
9+
}
10+
11+
static getDerivedStateFromError() {
12+
// Update state so the next render will show the fallback UI
13+
return { hasError: true };
14+
}
15+
16+
componentDidCatch(error, errorInfo) {
17+
console.error('BugLinking Error:', error, errorInfo);
18+
}
19+
20+
handleRetry() {
21+
this.setState({ hasError: false });
22+
}
23+
24+
render() {
25+
if (this.state.hasError) {
26+
return (
27+
<div className="alert alert-danger mt-3" role="alert">
28+
<h5>Something went wrong</h5>
29+
<p className="mb-3">
30+
There was an error while managing bug links. Please try again or
31+
file an issue on{' '}
32+
<a
33+
href="https://github.com/w3c/aria-at-app/issues"
34+
target="_blank"
35+
rel="noopener noreferrer"
36+
>
37+
GitHub
38+
</a>
39+
.
40+
</p>
41+
<button
42+
type="button"
43+
className="btn btn-outline-danger btn-sm"
44+
onClick={this.handleRetry}
45+
>
46+
Try Again
47+
</button>
48+
</div>
49+
);
50+
}
51+
52+
return this.props.children;
53+
}
54+
}
55+
56+
BugLinkingErrorBoundary.propTypes = {
57+
children: PropTypes.node.isRequired
58+
};
59+
60+
export default BugLinkingErrorBoundary;
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import SearchCombobox from '../../common/SearchCombobox';
4+
5+
const BugSearchCombobox = React.memo(
6+
({
7+
searchText,
8+
onSearchChange,
9+
filteredBugs,
10+
onSelectBug,
11+
onFetchBugs,
12+
loading,
13+
initialFocusRef,
14+
atName = 'AT'
15+
}) => {
16+
const handleSelectBug = bug => {
17+
onSelectBug(bug.id);
18+
};
19+
20+
const bugRenderer = (bug, styles) => (
21+
<>
22+
<strong>{bug.title}</strong>
23+
{bug.url && (
24+
<div className={`small text-truncate ${styles.itemUrl}`}>
25+
{bug.url}
26+
</div>
27+
)}
28+
</>
29+
);
30+
31+
return (
32+
<SearchCombobox
33+
id="bug-search-combobox"
34+
label={`${atName} Bug`}
35+
placeholder="Type to search by title or URL"
36+
searchText={searchText}
37+
onSearchChange={onSearchChange}
38+
items={filteredBugs}
39+
onSelectItem={handleSelectBug}
40+
onFetchItems={onFetchBugs}
41+
loading={loading}
42+
initialFocusRef={initialFocusRef}
43+
itemRenderer={bugRenderer}
44+
ariaLabel={`Available ${atName} bugs`}
45+
/>
46+
);
47+
}
48+
);
49+
50+
BugSearchCombobox.propTypes = {
51+
searchText: PropTypes.string.isRequired,
52+
onSearchChange: PropTypes.func.isRequired,
53+
filteredBugs: PropTypes.arrayOf(
54+
PropTypes.shape({
55+
id: PropTypes.string.isRequired,
56+
title: PropTypes.string.isRequired,
57+
url: PropTypes.string.isRequired
58+
})
59+
).isRequired,
60+
onSelectBug: PropTypes.func.isRequired,
61+
onFetchBugs: PropTypes.func.isRequired,
62+
loading: PropTypes.bool,
63+
initialFocusRef: PropTypes.object,
64+
atName: PropTypes.string
65+
};
66+
67+
export default BugSearchCombobox;
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import React, { useState, useMemo, forwardRef } from 'react';
2+
import PropTypes from 'prop-types';
3+
import FormField from '../../common/FormField';
4+
5+
const CreateBugForm = forwardRef(
6+
(
7+
{ onCreateBug, onCancel, creating, error, checkDuplicateUrl, showButtons },
8+
ref
9+
) => {
10+
const [formData, setFormData] = useState({
11+
title: '',
12+
url: ''
13+
});
14+
15+
const duplicateUrl = useMemo(
16+
() => checkDuplicateUrl?.(formData.url),
17+
[checkDuplicateUrl, formData.url]
18+
);
19+
20+
const hasDuplicate = !!duplicateUrl;
21+
22+
const handleSubmit = async e => {
23+
e.preventDefault();
24+
if (hasDuplicate) return;
25+
26+
const created = await onCreateBug(formData);
27+
if (created) {
28+
setFormData({ title: '', url: '' });
29+
}
30+
};
31+
32+
const handleChange = e => {
33+
const { name, value } = e.target;
34+
setFormData(prev => ({ ...prev, [name]: value }));
35+
};
36+
37+
const getUrlError = () => {
38+
if (!formData.url) {
39+
return null;
40+
}
41+
42+
if (duplicateUrl) {
43+
return `This URL already exists: ${duplicateUrl.title}`;
44+
}
45+
46+
try {
47+
new URL(formData.url);
48+
return null;
49+
} catch {
50+
return 'Invalid URL. Please include the protocol (e.g., https://)';
51+
}
52+
};
53+
54+
const urlError = getUrlError();
55+
56+
const buttonLabel = creating ? 'Saving…' : 'Save';
57+
const buttonClass = 'btn btn-primary btn-sm';
58+
59+
return (
60+
<form ref={ref} onSubmit={handleSubmit}>
61+
<fieldset>
62+
<legend className="sr-only">Add New Bug Link</legend>
63+
64+
<FormField
65+
id="new-bug-title"
66+
name="title"
67+
label="Link Text"
68+
type="text"
69+
value={formData.title}
70+
onChange={handleChange}
71+
required
72+
/>
73+
74+
<FormField
75+
id="new-bug-url"
76+
name="url"
77+
label="URL"
78+
type="url"
79+
value={formData.url}
80+
onChange={handleChange}
81+
required
82+
placeholder="https://example.com/bug-report"
83+
error={urlError}
84+
/>
85+
86+
{showButtons && (
87+
<button
88+
type="submit"
89+
className={buttonClass}
90+
disabled={creating || hasDuplicate}
91+
>
92+
{buttonLabel}
93+
</button>
94+
)}
95+
<button
96+
type="button"
97+
className="btn btn-link btn-sm ms-0 ps-0"
98+
onClick={onCancel}
99+
>
100+
Back to list
101+
</button>
102+
{error && (
103+
<div className="text-danger mt-2" role="alert">
104+
{error.message}
105+
</div>
106+
)}
107+
</fieldset>
108+
</form>
109+
);
110+
}
111+
);
112+
113+
CreateBugForm.displayName = 'CreateBugForm';
114+
115+
CreateBugForm.propTypes = {
116+
onCreateBug: PropTypes.func.isRequired,
117+
onCancel: PropTypes.func.isRequired,
118+
creating: PropTypes.bool,
119+
error: PropTypes.object,
120+
checkDuplicateUrl: PropTypes.func,
121+
showButtons: PropTypes.bool
122+
};
123+
124+
export default CreateBugForm;
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import LinkedItemsList from '../../common/LinkedItemsList';
4+
5+
const LinkedBugsList = React.memo(({ linkedBugs, onUnlink, unlinking }) => {
6+
const bugRenderer = (bug, onRemove, removing, removeButtonClass) => (
7+
<span key={bug.id} className="badge bg-light text-dark me-2">
8+
<a href={bug.url} target="_blank" rel="noopener noreferrer">
9+
{bug.title}
10+
</a>
11+
<button
12+
type="button"
13+
aria-label={`Unlink ${bug.title}`}
14+
className={removeButtonClass}
15+
onClick={() => onRemove(bug.id)}
16+
disabled={removing}
17+
>
18+
×
19+
</button>
20+
</span>
21+
);
22+
23+
return (
24+
<LinkedItemsList
25+
items={linkedBugs}
26+
onRemove={onUnlink}
27+
removing={unlinking}
28+
itemRenderer={bugRenderer}
29+
/>
30+
);
31+
});
32+
33+
LinkedBugsList.propTypes = {
34+
linkedBugs: PropTypes.arrayOf(
35+
PropTypes.shape({
36+
id: PropTypes.string.isRequired,
37+
title: PropTypes.string.isRequired,
38+
url: PropTypes.string.isRequired
39+
})
40+
).isRequired,
41+
onUnlink: PropTypes.func.isRequired,
42+
unlinking: PropTypes.bool
43+
};
44+
45+
export default LinkedBugsList;

0 commit comments

Comments
 (0)