diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 802af1e6..520d3fd9 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -16,12 +16,15 @@ updates: - package-ecosystem: "npm" directory: "/" ignore: - # Enzyme only supports React 16 (enzyme-adapter-react-16), so React majors break the unit tests. + # React majors need coordinated changes (react-refetch, the router, the test libraries below), so they're done by hand. - dependency-name: "react" update-types: ["version-update:semver-major"] - dependency-name: "react-dom" update-types: ["version-update:semver-major"] - - dependency-name: "react-test-renderer" + # @testing-library/react 13+ requires React 18, and @testing-library/dom is pinned to the 8.x that 12.x uses. + - dependency-name: "@testing-library/react" + update-types: ["version-update:semver-major"] + - dependency-name: "@testing-library/dom" update-types: ["version-update:semver-major"] # 3.x is a rewrite that's incompatible with react-metrics-graphics (see update-deps/denied.md). - dependency-name: "metrics-graphics" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 662100fa..1514abf0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,6 +35,8 @@ jobs: run: npm run lint - name: Run unit tests run: npm run test:jest + - name: Build + run: npm run build e2e: name: End-to-end tests @@ -55,5 +57,31 @@ jobs: run: npx playwright install --with-deps chromium - name: Compile Stylus run: npm run build:css - - name: Run end-to-end tests - run: npm run test:playwright + - name: Run end-to-end tests on recorded data + run: npm run test:playwright:recorded + + # Not a required check. These are the same specs, run against the real + # data.firefox.com, which changes weekly; a failure usually means the + # recording in tests/playwright/fixtures/ needs updating, not that the + # change under review is broken. + live-data: + name: Live data tests + runs-on: ubuntu-latest + timeout-minutes: 20 + if: github.repository == 'mozilla/ensemble' + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + with: + node-version-file: '.nvmrc' + cache: npm + - name: Install dependencies + run: npm ci + - name: Install Playwright browsers + run: npx playwright install --with-deps chromium + - name: Compile Stylus + run: npm run build:css + - name: Run end-to-end tests on live data + run: npm run test:playwright:live diff --git a/AGENTS.md b/AGENTS.md index 2546a026..98bee337 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -30,7 +30,7 @@ src/ ├── index.jsx entry: ReactDOM.render + BrowserRouter ├── config.json the route/dashboard table + next-button flow ├── registerServiceWorker.js create-react-app leftover; only unregister() is called (SW deliberately off) -├── setupTests.js Vitest setup: enzyme adapter; puts React and shallow on global +├── setupTests.js Vitest setup: puts React on global, jest-dom matchers, fails on console.error ├── components/ │ ├── containers/ 6 stateful/data-fetching .jsx │ ├── decorators/ withTracker.jsx (react-ga), withNextButton.jsx @@ -46,7 +46,9 @@ src/ └── tests/ └── jest/ 2 unit test files, run by Vitest (the directory name predates Vitest) tests/playwright/ -├── specs/ e2e specs (.spec.js, CommonJS), incl. dashboards/ +├── specs/ e2e specs (.spec.js, CommonJS), incl. dashboards/ and fixtures/; run on the recording and on live data +├── fixtures/ recorded transposer JSON, written by `npm run record:fixtures` +├── fixtureData.js `test` that serves fixtures/ (or live data, with liveData) and fails on page/console errors; readFixture └── utils.js shared helpers: linkWorks, linksWork, flagForUpdate, metricTitleIsCorrect ``` @@ -56,7 +58,7 @@ There is no `pages/`, `store/`, `hooks/`, `api/`, `locales/`, or `data/`. `index MozMEAO is taking this repo over. Before that work started there were 810 commits — 515 in 2018, 92 in 2020, **nothing in 2021–2024**, three in 2025, the last `f655559` on 2025-05-07. The toolchain has since been replaced (#441): create-react-app, Jest and Nightwatch gave way to Vite, Vitest and Playwright, and two ESLint configs became one. Code outside the toolchain has barely been exercised since 2020. -- **CI runs on GitHub Actions; there is no CD.** `.github/workflows/ci.yml` runs lint, the Vitest unit tests, and the Playwright end-to-end tests on every push to `main` and every pull request. There is no `.circleci/`, `Dockerfile`, `docker-compose`, or `Jenkinsfile` — earlier automation was deliberately removed: CircleCI disabled 2018-02-22 (`433a465`), Docker/Dockerflow removed 2018-08-23 (`d3bb561`), `.github/dependabot.yml` deleted 2020-07-29 (`7c7006f`, "Disable non-security updates from Dependabot"). +- **CI runs on GitHub Actions; there is no CD.** `.github/workflows/ci.yml` runs lint, the Vitest unit tests, a production build (`npm run build`, not browser-tested), and the Playwright end-to-end tests on every push to `main` and every pull request. The Playwright specs run twice: on recorded data in "End-to-end tests", which the `main` ruleset requires, and on live data in "Live data tests", which it doesn't. There is no `.circleci/`, `Dockerfile`, `docker-compose`, or `Jenkinsfile` — earlier automation was deliberately removed: CircleCI disabled 2018-02-22 (`433a465`), Docker/Dockerflow removed 2018-08-23 (`d3bb561`), `.github/dependabot.yml` deleted 2020-07-29 (`7c7006f`, "Disable non-security updates from Dependabot"). - **The deploy is a static build on Google Cloud Storage** (confirmed by `x-goog-*` headers on the live site). `https://data.firefox.com/version.json` reports `1.2.1` / commit `f655559`, so **the deployed site predates the Vite toolchain on `main`**; nothing built by Vite has shipped yet. The mechanism that pushes `build/` to GCS is **not in this repo and is currently unknown** — finding and documenting it is takeover work, not something to guess at. - **Node 24.** `.nvmrc` holds `24` and `engines` requires `^24.15.0`. A plain `npm install` works with no flags. - **A fresh clone will not render styles until you compile Stylus.** `src/components/views/css/*` is gitignored and every component does a side-effect `import './css/Foo.css';` — run `npm run build:css` or the app, and Vitest, fail to resolve those imports. `npm start` compiles it for you. @@ -76,8 +78,11 @@ npm start # npm-run-all --parallel watch:css watch:ap npm run build:css # one-shot Stylus compile npm run lint # lint:js (flat-config ESLint, .js and .jsx) + lint:styl (stylint) -npm run test:jest # vitest run (Enzyme, 2 files) -npm run test:playwright # playwright test; starts the dev server itself if :3000 is free +npm run test:jest # vitest run (React Testing Library) +npm run test:playwright # every Playwright project; starts the dev server itself if :3000 is free +npm run test:playwright:recorded # specs on tests/playwright/fixtures/ (CI's required "End-to-end tests") +npm run test:playwright:live # the same specs on real data.firefox.com (CI's non-required "Live data tests") +npm run record:fixtures # re-record tests/playwright/fixtures/ from data.firefox.com npm test # lint -> test:jest -> test:playwright npm run build:app # vite build @@ -105,7 +110,7 @@ Run one Vitest file: `npx vitest run src/tests/jest/Dashboard.test.jsx`. Run one **ESLint is one flat config, `eslint.config.js`, covering `.js` and `.jsx`.** `npm run lint:js` runs `eslint .`; the build does not lint. Warnings don't fail the run. -Errors: `eqeqeq`, `no-var`, `prefer-const`, `no-console`, `no-global-assign`, `no-redeclare` and `no-shadow` — the latter two with `builtinGlobals: true`, so **do not name a variable `name`, `status`, `history`, `event`, etc.** Warnings: `semi: always`, `prefer-arrow-callback`, `comma-dangle` (trailing commas **required** for multiline arrays/objects/imports/exports, **never** for function args/params). Off: `react/prop-types`, `react/display-name`, `jsx-a11y/no-onchange`. `react/no-unescaped-entities` forbids only `>` and `}`. `jsx-a11y/recommended` is on, so a11y violations are errors; `jsx-a11y/label-has-associated-control` requires `htmlFor`. `settings.react.version` is `'detect'`. `src/tests/jest/**/*.test.jsx` also gets `@vitest/eslint-plugin`'s recommended rules and the `React`/`shallow` globals. **Quote style is not enforced** (there is no `quotes` rule) — single quotes by convention. The config's own `ignores` excludes `build`. +Errors: `eqeqeq`, `no-var`, `prefer-const`, `no-console`, `no-global-assign`, `no-redeclare` and `no-shadow` — the latter two with `builtinGlobals: true`, so **do not name a variable `name`, `status`, `history`, `event`, etc.** Warnings: `semi: always`, `prefer-arrow-callback`, `comma-dangle` (trailing commas **required** for multiline arrays/objects/imports/exports, **never** for function args/params). Off: `react/prop-types`, `react/display-name`, `jsx-a11y/no-onchange`. `react/no-unescaped-entities` forbids only `>` and `}`. `jsx-a11y/recommended` is on, so a11y violations are errors; `jsx-a11y/label-has-associated-control` requires `htmlFor`. `settings.react.version` is `'detect'`. `src/tests/jest/**/*.test.jsx` also gets `@vitest/eslint-plugin`'s recommended rules, the `React` global, and a `no-shadow` exception for React Testing Library's `screen` (which otherwise collides with `window.screen`). **Quote style is not enforced** (there is no `quotes` rule) — single quotes by convention. The config's own `ignores` excludes `build`. ## Styling: Stylus, outside Vite @@ -139,9 +144,28 @@ This applies to everything written for a person to read: ExecPlans, PR descripti ## Testing Guidelines -**Vitest + enzyme**, configured in the `test` block of `vite.config.mjs` (`jsdom`, `globals: true`, `include: ['src/tests/jest/**/*.test.jsx']`). Only two files, both in `src/tests/jest/`, named `PascalCase.test.jsx` — **not colocated, no `__tests__` directories.** `src/setupTests.js` puts `React` and `shallow` on `global`, so **test files import neither**: they `import Dashboard from '../../components/views/Dashboard';` and call bare `shallow()`. Existing style is top-level `it(...)` with long descriptive sentences, a `beforeAll` that builds a `requiredProps` object, and assertions via `.find(selector).exists()` and `.html()).toContain(...)`. Shallow rendering only; no snapshots; `react-refetch` is never mocked; there are no container tests. +**Vitest + React Testing Library**, configured in the `test` block of `vite.config.mjs` (`jsdom`, `globals: true`, `include: ['src/tests/jest/**/*.test.jsx']`). Files live in `src/tests/jest/`, named `PascalCase.test.jsx` — **not colocated, no `__tests__` directories.** `@testing-library/react` is pinned to 12.x, the last line that supports React 16; `@testing-library/dom` is pinned to the 8.x it uses so `@testing-library/user-event` shares the same copy. `src/setupTests.js` puts `React` on `global` (the classic JSX runtime needs it in scope), so **test files don't import React**; they import `render` and `screen` from `@testing-library/react`. It also registers the jest-dom matchers (`toHaveTextContent`, `toBeInTheDocument`), and **fails any test that calls `console.error` unexpectedly**, so React warnings surface as failures — a test that expects an error replaces `console.error` with `vi.spyOn(console, 'error').mockImplementation(() => {})`. Tests render the real component tree into jsdom and assert on the DOM (`container.querySelector('#dashboard-description')`, or `innerHTML` where the markup itself is the point, as in the sanitization test). Existing style is top-level `it(...)` with long descriptive sentences. No snapshots. -**Playwright** specs in `tests/playwright/specs/` are **`camelCase.spec.js` or `kebab-case.spec.js`, CommonJS**, using `@playwright/test`'s `test`/`expect`. `playwright.config.js` has two Chromium projects: `chromium` runs everything except `jsDisabled.spec.js`, and `chromium-no-js` runs only that spec with `javaScriptEnabled: false`, asserting on the `id="enable-javascript"` `