Skip to content
This repository has been archived by the owner on Sep 18, 2024. It is now read-only.

Commit

Permalink
[Issue #73]: Dynamic sitemap for pa11y-ci (#83)
Browse files Browse the repository at this point in the history
## Summary
Fixes #73 

### Time to review: 5 min

## Changes proposed
- Generate dynamic sitemap with `/app/sitemap.ts` (next convention)
- Split pa11y config into `pa11y-desktop` and `pa11y-mobile`
  • Loading branch information
rylew1 authored Jun 18, 2024
1 parent 65e6840 commit c0acaae
Show file tree
Hide file tree
Showing 10 changed files with 187 additions and 109 deletions.
5 changes: 3 additions & 2 deletions .github/workflows/ci-frontend-a11y.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,10 @@ jobs:
- name: Run pa11y-ci
run: |
set -e # Ensure the script fails if any command fails
npm run test:pa11y
npm run test:pa11y-desktop
npm run test:pa11y-mobile
echo "pa11y-ci tests finished."
- name: Upload screenshots to artifacts
if: always()
uses: actions/upload-artifact@v3
Expand Down
16 changes: 16 additions & 0 deletions frontend/.pa11yci-desktop.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"defaults": {
"timeout": 240000,
"runners": ["axe"],
"ignore": ["color-contrast"],
"concurrency": 1,
"chromeLaunchConfig": {
"ignoreHTTPSErrors": true,
"args": ["--disable-dev-shm-usage", "--no-sandbox"]
},
"actions": [
"wait for element #main-content to be visible",
"screen capture screenshots-output/desktop-main-view.png"
]
}
}
23 changes: 23 additions & 0 deletions frontend/.pa11yci-mobile.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"defaults": {
"timeout": 240000,
"runners": ["axe"],
"ignore": ["color-contrast"],
"concurrency": 1,
"chromeLaunchConfig": {
"ignoreHTTPSErrors": true,
"args": ["--disable-dev-shm-usage", "--no-sandbox"]
},
"viewport": {
"width": 390,
"height": 844,
"mobile": true
},
"actions": [
"wait for element #main-content to be visible",
"screen capture screenshots-output/mobile-main-view.png",
"click element .usa-navbar button",
"screen capture screenshots-output/mobile-expand-menu.png"
]
}
}
100 changes: 0 additions & 100 deletions frontend/.pa11yci.json

This file was deleted.

3 changes: 2 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"storybook": "storybook dev -p 6006",
"storybook-build": "storybook build",
"test": "jest --ci --coverage",
"test:pa11y": "pa11y-ci --config .pa11yci.json",
"test:pa11y-desktop": "pa11y-ci --config .pa11yci-desktop.json --sitemap http://localhost:3000/sitemap.xml",
"test:pa11y-mobile": "pa11y-ci --config .pa11yci-mobile.json --sitemap http://localhost:3000/sitemap.xml",
"test-update": "jest --update-snapshot",
"test-watch": "jest --watch",
"test:e2e": "npx playwright test --config ./tests/playwright.config.ts",
Expand Down
9 changes: 8 additions & 1 deletion frontend/src/app/[locale]/health/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
export default function Health() {
return <>healthy</>;
return (
<>
<head>
<title>Health Check</title>
</head>
<div>healthy</div>
</>
);
}
16 changes: 16 additions & 0 deletions frontend/src/app/sitemap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { MetadataRoute } from "next";
import { getNextRoutes } from "../utils/getRoutes";

export default function sitemap(): MetadataRoute.Sitemap {
const routes = getNextRoutes("./src/app");

const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000";
const sitemap: MetadataRoute.Sitemap = routes.map((route) => ({
url: `${baseUrl}${route || ""}`,
lastModified: new Date().toISOString(),
changeFrequency: "weekly",
priority: 0.5,
}));

return sitemap;
}
10 changes: 5 additions & 5 deletions frontend/src/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { NextRequest, NextResponse } from "next/server";
import { defaultLocale, locales } from "./i18n/config";

import { FeatureFlagsManager } from "./services/FeatureFlagManager";
/**
* @file Middleware allows you to run code before a request is completed. Then, based on
* the incoming request, you can modify the response by rewriting, redirecting,
* modifying the request or response headers, or responding directly.
* @see https://nextjs.org/docs/app/building-your-application/routing/middleware
*/
import createIntlMiddleware from "next-intl/middleware";
import { NextRequest, NextResponse } from "next/server";
import { defaultLocale, locales } from "./i18n/config";

import { FeatureFlagsManager } from "./services/FeatureFlagManager";

export const config = {
matcher: [
Expand All @@ -19,7 +19,7 @@ export const config = {
* - _next/image (image optimization files)
* - images (static files in public/images/ directory)
*/
"/((?!api|_next/static|_next/image|public|img|uswds|images|robots.txt|site.webmanifest).*)",
"/((?!api|_next/static|_next/image|sitemap|public|img|uswds|images|robots.txt|site.webmanifest).*)",
/**
* Fix issue where the pattern above was causing middleware
* to not run on the homepage:
Expand Down
40 changes: 40 additions & 0 deletions frontend/src/utils/getRoutes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import fs from "node:fs";
import path from "node:path";

// Helper function to list all paths recursively
export function listPaths(dir: string): string[] {
let fileList: string[] = [];
const files = fs.readdirSync(dir);
files.forEach((file) => {
const filePath = path.join(dir, file);
if (fs.statSync(filePath).isDirectory()) {
fileList = fileList.concat(listPaths(filePath));
} else {
fileList.push(filePath);
}
});
return fileList;
}

// Function to get the Next.js routes
export function getNextRoutes(src: string): string[] {
// Get all paths from the `app` directory
const appPaths = listPaths(src).filter((file) => file.endsWith("page.tsx"));

// Extract the route name for each `page.tsx` file
// Basically anything between [locale] and /page.tsx is extracted,
// which lets us get nested routes such as /newsletter/unsubscribe
const appRoutes = appPaths.map((filePath) => {
const relativePath = path.relative(src, filePath);
const route = relativePath
? "/" +
relativePath
.replace("/page.tsx", "")
.replace(/\[locale\]/g, "")
.replace(/\\/g, "/")
: "/";
return route.replace(/\/\//g, "/");
});

return appRoutes;
}
74 changes: 74 additions & 0 deletions frontend/tests/utils/getRoutes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { getNextRoutes, listPaths } from "../../src/utils/getRoutes";

/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-return */
jest.mock("../../src/utils/getRoutes", () => {
const originalModule = jest.requireActual("../../src/utils/getRoutes");
return {
...originalModule,
listPaths: jest.fn(),
};
});

const mockedListPaths = listPaths as jest.MockedFunction<typeof listPaths>;

describe("getNextRoutes", () => {
beforeEach(() => {
jest.resetAllMocks();
});

it("should get Next.js routes from src directory", () => {
const mockedFiles: string[] = getPaths();

mockedListPaths.mockReturnValue(mockedFiles);

const result = getNextRoutes("src/app");

expect(result).toEqual([
"/dev/feature-flags",
"/health",
"/newsletter/confirmation",
"/newsletter",
"/newsletter/unsubscribe",
"/",
"/process",
"/research",
"/search",
]);
});
});

function getPaths() {
return [
"src/app/[locale]/dev/feature-flags/FeatureFlagsTable.tsx",
"src/app/[locale]/dev/feature-flags/page.tsx",
"src/app/[locale]/health/page.tsx",
"src/app/[locale]/newsletter/NewsletterForm.tsx",
"src/app/[locale]/newsletter/confirmation/page.tsx",
"src/app/[locale]/newsletter/page.tsx",
"src/app/[locale]/newsletter/unsubscribe/page.tsx",
"src/app/[locale]/page.tsx",
"src/app/[locale]/process/ProcessIntro.tsx",
"src/app/[locale]/process/ProcessInvolved.tsx",
"src/app/[locale]/process/ProcessMilestones.tsx",
"src/app/[locale]/process/page.tsx",
"src/app/[locale]/research/ResearchArchetypes.tsx",
"src/app/[locale]/research/ResearchImpact.tsx",
"src/app/[locale]/research/ResearchIntro.tsx",
"src/app/[locale]/research/ResearchMethodology.tsx",
"src/app/[locale]/research/ResearchThemes.tsx",
"src/app/[locale]/research/page.tsx",
"src/app/[locale]/search/SearchForm.tsx",
"src/app/[locale]/search/actions.ts",
"src/app/[locale]/search/error.tsx",
"src/app/[locale]/search/loading.tsx",
"src/app/[locale]/search/page.tsx",
"src/app/api/BaseApi.ts",
"src/app/api/SearchOpportunityAPI.ts",
"src/app/api/mock/APIMockResponse.json",
"src/app/layout.tsx",
"src/app/not-found.tsx",
"src/app/sitemap.ts",
"src/app/template.tsx",
];
}

0 comments on commit c0acaae

Please sign in to comment.