Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const jsxA11y = require("eslint-plugin-jsx-a11y");

const warningRules = Object.fromEntries(
Object.entries(jsxA11y.configs.recommended.rules).map(([ruleName, configuration]) => [
ruleName,
Array.isArray(configuration) ? ["warn", ...configuration.slice(1)] : "warn"
])
);

module.exports = {
extends: ["./.eslintrc.cjs", "plugin:jsx-a11y/recommended"],
rules: warningRules
};
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ It correctly bundles React in production mode and optimizes the build for the be
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!

### Accessibility checks

Run `npm run lint:a11y` to report static JSX accessibility warnings. The command is
currently non-blocking so existing warnings can be addressed incrementally.

Run `npm run test:e2e:a11y` to scan the assignment builder and grader with axe. Axe
violations are attached to the Playwright report and annotated, but do not fail the
test yet. Navigation and page-loading assertions still fail normally.

### Production

For production, the React app is currently mounted as a static app within the `assignment` server. This will need to change eventually so that we can server the correct versions of the rest of the runestone javascript.
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,62 @@ import { expect, test } from "../fixtures/scratch";
const scan = (page: import("@playwright/test").Page) =>
new AxeBuilder({ page }).withTags(["wcag2a", "wcag2aa"]).analyze();

const seriousViolations = (results: Awaited<ReturnType<typeof scan>>) =>
results.violations
.filter((v) => v.impact === "serious" || v.impact === "critical")
.map((v) => ({
id: v.id,
impact: v.impact,
nodes: v.nodes.slice(0, 3).map((n) => n.target.join(" "))
}));
const reportViolations = async (
testInfo: import("@playwright/test").TestInfo,
results: Awaited<ReturnType<typeof scan>>
) => {
const violations = results.violations.map((violation) => ({
id: violation.id,
impact: violation.impact,
description: violation.description,
help: violation.help,
helpUrl: violation.helpUrl,
nodes: violation.nodes.map((node) => ({
target: node.target,
html: node.html,
failureSummary: node.failureSummary
}))
}));

await testInfo.attach("axe-accessibility-results", {
body: JSON.stringify(violations, null, 2),
contentType: "application/json"
});

if (violations.length > 0) {
testInfo.annotations.push({
type: "accessibility",
description: `${violations.length} axe violation(s) reported; enforcement is not enabled yet`
});
console.warn(`axe reported ${violations.length} accessibility violation(s)`);
}
};

test(
"assignment list has no serious or critical axe violations",
"reports assignment list axe violations",
{ tag: ["@p2", "@a11y", "@assignments"] },
async ({ page }) => {
async ({ page }, testInfo) => {
await page.goto("/builder");
await expect(page.getByRole("heading", { level: 1, name: "Assignments" })).toBeVisible();

const results = await scan(page);

expect(seriousViolations(results)).toEqual([]);
await reportViolations(testInfo, results);
}
);

test(
"grader assignment list has no serious or critical axe violations",
"reports grader assignment list axe violations",
{ tag: ["@p2", "@a11y", "@grader"] },
async ({ page }) => {
async ({ page }, testInfo) => {
await page.goto("/grader");
await expect(page.getByRole("heading", { level: 1, name: "Assignment grader" })).toBeVisible();
await expect(page.getByRole("heading", { level: 2, name: "Assignments" })).toBeAttached();
await expect(page.getByRole("navigation", { name: "Breadcrumb" })).toContainText("Assignments");
await expect(page.getByRole("region", { name: "Assignments" })).toBeVisible();

const results = await scan(page);

expect(seriousViolations(results)).toEqual([]);
await reportViolations(testInfo, results);
}
);

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@
"docs": "jsdoc -c jsdoc.config.json -r src",
"test": "vitest --coverage.enabled",
"test:eslint": "eslint src --max-warnings=0",
"lint:a11y": "eslint src --config .eslintrc.a11y.cjs",
"test:e2e": "playwright test",
"test:e2e:a11y": "playwright test consolidation/a11y-scan.spec.ts --grep @a11y",
"test:e2e:smoke": "playwright test --grep @p0",
"test:e2e:p1": "playwright test --grep \"@p0|@p1\"",
"test:e2e:docker": "E2E_TARGET=docker playwright test e2e/grader",
Expand Down Expand Up @@ -125,6 +127,7 @@
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-flowtype": "^8.0.3",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-only-warn": "^1.1.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-react": "^7.37.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
gap: var(--rs-space-3);
font-size: var(--rs-font-size-xl);
font-weight: var(--rs-font-weight-heading);
margin: 0;
}
.titleText {
color: var(--rs-text-strong);
Expand Down Expand Up @@ -66,13 +67,23 @@
}

.breadcrumb {
display: flex;
align-items: center;
gap: var(--rs-space-2);
color: var(--rs-text-muted);
font-size: var(--rs-font-size-md);
margin-bottom: var(--rs-space-4);
}
.breadcrumbList {
display: flex;
align-items: center;
gap: var(--rs-space-2);
flex-wrap: wrap;
list-style: none;
margin: 0;
padding: 0;
}
.breadcrumbItem {
display: inline-flex;
align-items: center;
gap: var(--rs-space-2);
}
.breadcrumb a {
color: var(--rs-brand-600);
Expand Down Expand Up @@ -102,7 +113,19 @@
.breadcrumbPath {
margin-left: auto;
font-size: var(--rs-font-size-xs);
color: var(--rs-text-subtle);
color: var(--rs-text-muted);
}

.visuallyHidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}

.grid {
Expand Down Expand Up @@ -235,7 +258,7 @@
margin-top: var(--rs-space-3);
}
.metaIcon {
color: var(--rs-brand-500);
color: var(--rs-brand-600);
}

.iconText {
Expand Down Expand Up @@ -279,11 +302,11 @@
}
.emptyStateIcon {
font-size: 30px;
color: var(--rs-brand-400);
color: var(--rs-brand-600);
}
.emptyStateIconWarn {
font-size: 30px;
color: var(--rs-warning);
color: var(--rs-warning-text);
}

.loadingWrap {
Expand Down Expand Up @@ -359,7 +382,7 @@
color: var(--rs-text);
}
.cellSubtle {
color: var(--rs-text-subtle);
color: var(--rs-text-muted);
font-weight: var(--rs-font-weight-medium);
}
.cellMuted {
Expand All @@ -373,10 +396,10 @@
vertical-align: middle;
}
.cellDash {
color: var(--rs-border-strong);
color: var(--rs-text-muted);
}
.cellLink {
color: var(--rs-brand-500);
color: var(--rs-brand-600);
}
.studentNameCell {
display: flex;
Expand Down Expand Up @@ -441,7 +464,7 @@
box-shadow: 0 0 0 2px var(--rs-border-brand);
}
.dateFilterInput::placeholder {
color: var(--rs-text-subtle);
color: var(--rs-text-muted);
}
.dateFilterWrapper :global(.react-datepicker__close-icon) {
padding: 0 6px 0 0;
Expand Down Expand Up @@ -530,7 +553,7 @@
min-width: 0;
}
.gradePaneAvatar {
color: var(--rs-brand-500);
color: var(--rs-brand-600);
}
.gradePaneIdentity {
flex: 1;
Expand Down Expand Up @@ -651,7 +674,7 @@
padding: 0.25rem;
}
.toastIcon {
color: var(--rs-success);
color: var(--rs-success-text);
}
.toastText {
flex: 1;
Expand Down Expand Up @@ -771,7 +794,7 @@
}
.attemptTimestamp {
font-size: 11px;
color: var(--rs-text-subtle);
color: var(--rs-text-muted);
font-variant-numeric: tabular-nums;
}
.latestBadge {
Expand Down Expand Up @@ -907,7 +930,7 @@
.selectionChip button {
border: none;
background: transparent;
color: var(--rs-brand-500);
color: var(--rs-brand-600);
font-weight: var(--rs-font-weight-semibold);
cursor: pointer;
font-size: var(--rs-font-size-sm);
Expand Down Expand Up @@ -952,7 +975,7 @@
color: var(--rs-text-muted);
}
.textSubtle {
color: var(--rs-text-subtle);
color: var(--rs-text-muted);
}
.textStrong {
color: var(--rs-text-strong);
Expand Down Expand Up @@ -1023,7 +1046,7 @@
color: var(--rs-text-strong);
}
.dialogHeaderIcon {
color: var(--rs-brand-500);
color: var(--rs-brand-600);
}
.sidChip {
font-family: var(--rs-font-mono);
Expand All @@ -1050,7 +1073,7 @@
}
.kbdSep {
margin: 0 6px;
color: var(--rs-text-subtle);
color: var(--rs-text-muted);
}
.shortcutTable {
width: 100%;
Expand All @@ -1068,7 +1091,7 @@
}
.dialogFootnote {
font-size: 11px;
color: var(--rs-text-subtle);
color: var(--rs-text-muted);
border-top: 1px solid var(--rs-border-solid);
padding-top: var(--rs-space-2);
}
Expand All @@ -1079,7 +1102,7 @@
padding: 0;
font-family: inherit;
font-size: 13px;
color: var(--rs-brand-500);
color: var(--rs-brand-600);
cursor: pointer;
font-weight: var(--rs-font-weight-medium);
}
Expand Down Expand Up @@ -1309,7 +1332,7 @@
background: var(--rs-neutral-100);
}
.gradebookCellButton:focus-visible {
outline: 2px solid var(--rs-brand);
outline: 2px solid var(--rs-brand-600);
outline-offset: 1px;
}
.gradebookCellManual {
Expand Down
Loading
Loading