diff --git a/.github/workflows/E2E.yml b/.github/workflows/E2E.yml index 7958948..528ed43 100644 --- a/.github/workflows/E2E.yml +++ b/.github/workflows/E2E.yml @@ -6,40 +6,109 @@ on: branches: [development] jobs: - test: - timeout-minutes: 60 + linting: + name: ESLint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup pnpm + uses: pnpm/action-setup@v2 + with: + version: 8.8.0 - - name: Install Node.js + - name: Setup node uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 18 + cache: "pnpm" + # run: pnpm site install + + - name: Install Dependencies + run: pnpm site install + + - name: Lint + run: pnpm site run lint - - uses: pnpm/action-setup@v2 - name: Install pnpm - id: pnpm-install + build: + name: Next Build + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Setup pnpm + uses: pnpm/action-setup@v2 + with: + version: 8.8.0 + + - name: Setup node + uses: actions/setup-node@v3 with: - version: 7 - run_install: false + node-version: 18 + cache: "pnpm" + # run: pnpm site install - - name: Get pnpm store directory - id: pnpm-cache - shell: bash - run: | - echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT + # - name: Setup pnpm + # uses: pnpm/action-setup@v2 + # with: + # version: 8.8.0 - - uses: actions/cache@v3 - name: Setup pnpm cache + - name: Install Dependencies + run: pnpm site install + + - name: Run build + run: pnpm site run build + + test: + name: Playwright Tests + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Setup pnpm + uses: pnpm/action-setup@v2 + with: + version: 8.8.0 + + - name: Setup node + uses: actions/setup-node@v3 with: - path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- + node-version: 18 + cache: "pnpm" + # run: pnpm site install - name: Install Dependencies - run: pnpm install + run: pnpm site install - - name: Build Site + - name: Run build run: pnpm site run build + + # - name: Get pnpm store directory + # id: pnpm-cache + # shell: bash + # run: | + # echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT + + # - uses: actions/cache@v3 + # name: Setup pnpm cache + # with: + # path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} + # key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + # restore-keys: | + # ${{ runner.os }}-pnpm-store- + + - name: Install Playwright Browsers + run: pnpm site playwright install --with-deps + + - name: Run Playwright tests + run: pnpm site playwright test + + - uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/apps/site/.gitignore b/apps/site/.gitignore index 6d4c0aa..29cc74f 100644 --- a/apps/site/.gitignore +++ b/apps/site/.gitignore @@ -19,3 +19,6 @@ pnpm-debug.log* # macOS-specific files .DS_Store +/test-results/ +/playwright-report/ +/playwright/.cache/ diff --git a/apps/site/package.json b/apps/site/package.json index 2ab1658..4d2e80c 100644 --- a/apps/site/package.json +++ b/apps/site/package.json @@ -7,7 +7,9 @@ "start": "astro dev", "build": "astro build", "preview": "astro preview", - "astro": "astro" + "astro": "astro", + "playwright": "playwright", + "playwright:test": "playwright test" }, "dependencies": { "@astrojs/image": "^0.14.1", @@ -25,6 +27,9 @@ "zod": "^3.21.4" }, "devDependencies": { + "@axe-core/playwright": "^4.8.1", + "@playwright/test": "^1.39.0", + "@types/node": "^20.8.9", "prettier-plugin-astro": "^0.8.0", "typescript": "4.9.5" } diff --git a/apps/site/playwright.config.ts b/apps/site/playwright.config.ts new file mode 100644 index 0000000..49c3a2f --- /dev/null +++ b/apps/site/playwright.config.ts @@ -0,0 +1,83 @@ +import { defineConfig, devices } from "@playwright/test"; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: "./tests", + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: "html", + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: "http://localhost:3000", + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: "on-first-retry", + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + + { + name: "firefox", + use: { ...devices["Desktop Firefox"] }, + }, + + { + name: "webkit", + use: { ...devices["Desktop Safari"] }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + webServer: { + command: "pnpm run preview", + url: "http://localhost:3000", + reuseExistingServer: !process.env.CI, + stdout: "ignore", + stderr: "pipe", + }, + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); diff --git a/apps/site/src/styles/base.css b/apps/site/src/styles/base.css index 54cfc90..46572e4 100644 --- a/apps/site/src/styles/base.css +++ b/apps/site/src/styles/base.css @@ -7,6 +7,12 @@ body { @apply bg-breadgray-ultra-white dark:bg-breadgray-darkest; @apply text-breadgray-charcoal dark:text-breadgray-white; - @apply font-redhat; + @apply font-redhat; + } + * { + @apply focus:outline focus:outline-4 focus:outline-green-400 focus:outline-offset-4 rounded ; + } + .transition-display { + transition: display 1000ms; } } diff --git a/apps/site/tests/example.spec.ts b/apps/site/tests/example.spec.ts new file mode 100644 index 0000000..5de5c61 --- /dev/null +++ b/apps/site/tests/example.spec.ts @@ -0,0 +1,32 @@ +import { test, expect } from "@playwright/test"; +import AxeBuilder from "@axe-core/playwright"; + +test.describe("homepage", () => { + // test("has title", async ({ page }) => { + // await page.goto("/"); + + // await expect(page).toHaveTitle(/Breadchain/); + // }); + + test("should not have any automatically detectable accessibility issues", async ({ + page, + }) => { + await page.goto("/"); // 3 + + const accessibilityScanResults = await new AxeBuilder({ page }).analyze(); // 4 + + expect(accessibilityScanResults.violations).toEqual([]); // 5 + }); +}); + +// test("should not have any automatically detectable WCAG A or AA violations", async ({ +// page, +// }) => { +// await page.goto("/"); + +// const accessibilityScanResults = await new AxeBuilder({ page }) +// .withTags(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"]) +// .analyze(); + +// expect(accessibilityScanResults.violations).toEqual([]); +// }); diff --git a/packages/site-ui/components/ButtonLink/ButtonLink.tsx b/packages/site-ui/components/ButtonLink/ButtonLink.tsx index 7a9e050..d9995a6 100644 --- a/packages/site-ui/components/ButtonLink/ButtonLink.tsx +++ b/packages/site-ui/components/ButtonLink/ButtonLink.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode } from "react"; +import type { ReactNode } from "react"; interface IProps { children: ReactNode; diff --git a/packages/site-ui/components/ColorToggle/ColorToggle.tsx b/packages/site-ui/components/ColorToggle/ColorToggle.tsx index a07b735..8a6ec8b 100644 --- a/packages/site-ui/components/ColorToggle/ColorToggle.tsx +++ b/packages/site-ui/components/ColorToggle/ColorToggle.tsx @@ -30,7 +30,11 @@ export function ColorToggle() { return (
{colorMode && ( - */} + - About - + + About + - - Projects - + + Projects + - - Blog - -
- -
+ + Blog + +
{ + if (event.code === "Tab" && !event.shiftKey) { + handleNavToggle("KEYPRESS"); + } + }} + > + +
+
); diff --git a/packages/site-ui/components/MobileNavigation/MobileNavigationToggle.tsx b/packages/site-ui/components/MobileNavigation/MobileNavigationToggle.tsx index d0aa163..dda2fe3 100644 --- a/packages/site-ui/components/MobileNavigation/MobileNavigationToggle.tsx +++ b/packages/site-ui/components/MobileNavigation/MobileNavigationToggle.tsx @@ -1,19 +1,61 @@ -import React from "react"; +import classNames from "classnames"; +import React, { useRef, useState } from "react"; -function MobileNavigationToggle({ handleClick }: { handleClick: () => void }) { +function MobileNavigationToggle({ + handleClick, + navIsOpen, +}: { + navIsOpen: boolean; + handleClick: (type?: "CLICK" | "KEYPRESS") => void; +}) { + const [isKeypress, setIsKeypress] = useState(false); + const buttonRef = useRef(null); + + function handleKeyDown(event) { + setIsKeypress(true); + } + + function handleClickHere() { + if (isKeypress) { + handleClick("KEYPRESS"); + } else { + buttonRef.current?.blur(); + handleClick("CLICK"); + } + } return ( ); } diff --git a/packages/site-ui/components/Overlay.tsx b/packages/site-ui/components/Overlay.tsx index 8b98b52..1083903 100644 --- a/packages/site-ui/components/Overlay.tsx +++ b/packages/site-ui/components/Overlay.tsx @@ -19,8 +19,8 @@ function Overlay({ // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
diff --git a/packages/site-ui/components/TertiaryLink/TertiaryLink.tsx b/packages/site-ui/components/TertiaryLink/TertiaryLink.tsx index 64c6a18..6311909 100644 --- a/packages/site-ui/components/TertiaryLink/TertiaryLink.tsx +++ b/packages/site-ui/components/TertiaryLink/TertiaryLink.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode } from "react"; +import type { ReactNode } from "react"; interface IProps { children: ReactNode; @@ -8,7 +8,7 @@ interface IProps { export function TertiaryLink({ children, href, isExternal }: IProps) { const classes = - "inline-block font-redhat font-bold md:text-xl px-4 py-2.5 md:px-6 md:py-3 text-breadpink-300 hover:text-breadpink-200 active:text-breadpink-200 "; + "inline-block font-redhat font-bold md:text-xl px-4 py-2.5 md:px-6 md:py-3 text-breadpink-500 hover:text-breadpink-600 active:text-breadpink-600 "; if (isExternal) { return ( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b68d33d..114dddc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,5 +1,9 @@ lockfileVersion: '6.0' +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + importers: .: @@ -95,9 +99,6 @@ importers: '@astrojs/tailwind': specifier: ^3.0.1 version: 3.0.1(astro@2.5.0)(tailwindcss@3.0.24) - '@breadchain.xyz/shared-ui': - specifier: workspace:* - version: link:../../packages/shared-ui '@breadchain.xyz/site-ui': specifier: workspace:* version: link:../../packages/site-ui @@ -112,7 +113,7 @@ importers: version: 18.0.6 astro: specifier: ^2.5.0 - version: 2.5.0 + version: 2.5.0(@types/node@20.8.9) classnames: specifier: ^2.3.2 version: 2.3.2 @@ -129,6 +130,15 @@ importers: specifier: ^3.21.4 version: 3.21.4 devDependencies: + '@axe-core/playwright': + specifier: ^4.8.1 + version: 4.8.1(playwright-core@1.39.0) + '@playwright/test': + specifier: ^1.39.0 + version: 1.39.0 + '@types/node': + specifier: ^20.8.9 + version: 20.8.9 prettier-plugin-astro: specifier: ^0.8.0 version: 0.8.0 @@ -167,7 +177,7 @@ importers: version: 18.0.27 astro: specifier: ^2.5.0 - version: 2.5.0 + version: 2.5.0(@types/node@20.8.9) classnames: specifier: ^2.3.2 version: 2.3.2 @@ -294,7 +304,7 @@ packages: optional: true dependencies: '@altano/tiny-async-pool': 1.0.2 - astro: 2.5.0 + astro: 2.5.0(@types/node@20.8.9) http-cache-semantics: 4.1.1 image-size: 1.0.2 kleur: 4.1.5 @@ -327,7 +337,7 @@ packages: astro: ^2.5.0 dependencies: '@astrojs/prism': 2.1.2 - astro: 2.5.0 + astro: 2.5.0(@types/node@20.8.9) github-slugger: 1.5.0 import-meta-resolve: 2.2.2 rehype-raw: 6.1.1 @@ -377,7 +387,7 @@ packages: tailwindcss: ^3.0.24 dependencies: '@proload/core': 0.3.3 - astro: 2.5.0 + astro: 2.5.0(@types/node@20.8.9) autoprefixer: 10.4.14(postcss@8.4.23) postcss: 8.4.23 postcss-load-config: 4.0.1(postcss@8.4.23) @@ -408,6 +418,15 @@ packages: undici: 5.22.1 dev: false + /@axe-core/playwright@4.8.1(playwright-core@1.39.0): + resolution: {integrity: sha512-KC1X++UdRAwMLRvB+BIKFheyLHUnbJTL0t0Wbv6TJMozn2V2QyEtAcN6jyUiudtGiLUGhHCtj/eWorBnVZ4dAA==} + peerDependencies: + playwright-core: '>= 1.0.0' + dependencies: + axe-core: 4.8.2 + playwright-core: 1.39.0 + dev: true + /@babel/code-frame@7.21.4: resolution: {integrity: sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==} engines: {node: '>=6.9.0'} @@ -2172,6 +2191,14 @@ packages: picocolors: 1.0.0 tslib: 2.5.2 + /@playwright/test@1.39.0: + resolution: {integrity: sha512-3u1iFqgzl7zr004bGPYiN/5EZpRUSFddQBra8Rqll5N0/vfpqlP9I9EXqAoGacuAbX6c9Ulg/Cjqglp5VkK6UQ==} + engines: {node: '>=16'} + hasBin: true + dependencies: + playwright: 1.39.0 + dev: true + /@proload/core@0.3.3: resolution: {integrity: sha512-7dAFWsIK84C90AMl24+N/ProHKm4iw0akcnoKjRvbfHifJZBLhaDsDus1QJmhG12lXj4e/uB/8mB/0aduCW+NQ==} dependencies: @@ -2336,6 +2363,11 @@ packages: /@types/node@20.1.3: resolution: {integrity: sha512-NP2yfZpgmf2eDRPmgGq+fjGjSwFgYbihA8/gK+ey23qT9RkxsgNTZvGOEpXgzIGqesTYkElELLgtKoMQTys5vA==} + /@types/node@20.8.9: + resolution: {integrity: sha512-UzykFsT3FhHb1h7yD4CA4YhBHq545JC0YnEz41xkipN88eKQtL6rSgocL5tbAP6Ola9Izm/Aw4Ora8He4x0BHg==} + dependencies: + undici-types: 5.26.5 + /@types/parse5@6.0.3: resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==} dev: false @@ -2759,7 +2791,7 @@ packages: engines: {node: '>=8'} dev: true - /astro@2.5.0: + /astro@2.5.0(@types/node@20.8.9): resolution: {integrity: sha512-dZZuK2vEpfinbVALthUW31NOVUFPobgyi0+2PR3FH3diy6X9HBw1PLbS5wRsWOKaEXRbzxBkXc39Rbm0yRzKaA==} engines: {node: '>=16.12.0', npm: '>=6.14.0'} hasBin: true @@ -2820,7 +2852,7 @@ packages: typescript: 4.9.5 unist-util-visit: 4.1.2 vfile: 5.3.7 - vite: 4.3.9 + vite: 4.3.9(@types/node@20.8.9) vitefu: 0.2.4(vite@4.3.9) yargs-parser: 21.1.1 zod: 3.21.4 @@ -2865,6 +2897,11 @@ packages: engines: {node: '>=4'} dev: false + /axe-core@4.8.2: + resolution: {integrity: sha512-/dlp0fxyM3R8YW7MFzaHWXrf4zzbr0vaYb23VBFCl83R7nWNPg/yaQw2Dc8jzCMmDVLhSdzH8MjrsuIUuvX+6g==} + engines: {node: '>=4'} + dev: true + /axobject-query@3.2.1: resolution: {integrity: sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==} dependencies: @@ -4284,7 +4321,6 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] requiresBuild: true - dev: false optional: true /function-bind@1.1.1: @@ -6170,6 +6206,22 @@ packages: find-up: 4.1.0 dev: false + /playwright-core@1.39.0: + resolution: {integrity: sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==} + engines: {node: '>=16'} + hasBin: true + dev: true + + /playwright@1.39.0: + resolution: {integrity: sha512-naE5QT11uC/Oiq0BwZ50gDmy8c8WLPRTEWuSSFVG2egBka/1qMoSqYQcROMT9zLwJ86oPofcTH2jBY/5wWOgIw==} + engines: {node: '>=16'} + hasBin: true + dependencies: + playwright-core: 1.39.0 + optionalDependencies: + fsevents: 2.3.2 + dev: true + /postcss-js@4.0.1(postcss@8.4.23): resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} engines: {node: ^12 || ^14 || >= 16} @@ -7377,6 +7429,9 @@ packages: engines: {node: '>=0.10.0'} dev: true + /undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + /undici@5.22.1: resolution: {integrity: sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==} engines: {node: '>=14.0'} @@ -7577,7 +7632,7 @@ packages: d3-timer: 3.0.1 dev: false - /vite@4.3.9: + /vite@4.3.9(@types/node@20.8.9): resolution: {integrity: sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true @@ -7602,6 +7657,7 @@ packages: terser: optional: true dependencies: + '@types/node': 20.8.9 esbuild: 0.17.19 postcss: 8.4.23 rollup: 3.23.0 @@ -7617,7 +7673,7 @@ packages: vite: optional: true dependencies: - vite: 4.3.9 + vite: 4.3.9(@types/node@20.8.9) dev: false /vscode-css-languageservice@6.2.5: