From 1f5d9314bd343213e9675b621b6f0e0016fca46c Mon Sep 17 00:00:00 2001 From: Jeff Maki Date: Wed, 17 Jun 2026 16:50:57 -0400 Subject: [PATCH 01/20] First pass at tests --- .gitignore | 6 + .nuxtrc | 1 + CLAUDE.md | 120 ++ ISSUES.md | 161 ++ components/SigninForm.vue | 6 + eslint.config.mjs | 9 +- nuxt.config.ts | 5 +- package-lock.json | 1360 ++++++++++++++++- package.json | 11 +- pages/dashboard.vue | 40 +- pages/help.vue | 4 + pages/index.vue | 6 + pages/signin.vue | 6 + pages/temp.vue | 24 - pages/workspace/[id]/edit.vue | 7 + pages/workspace/[id]/export/index.vue | 10 + pages/workspace/[id]/export/tdei.vue | 15 + pages/workspace/[id]/review.vue | 10 + pages/workspace/[id]/settings.vue | 15 + pages/workspace/create/blank.vue | 7 + pages/workspace/create/file.vue | 9 + pages/workspace/create/index.vue | 13 + pages/workspace/create/tdei.vue | 10 + playwright.config.ts | 53 + run-tests.sh | 88 ++ test/contract/openapi.json | 1 + test/e2e/contract.ts | 154 ++ test/e2e/create-blank.spec.ts | 164 ++ ...-the-dashboard-with-it-selected-1.aria.yml | 12 + test/e2e/create-file.spec.ts | 215 +++ ...n-and-Try-again-resets-the-form-1.aria.yml | 16 + ...-and-navigates-to-the-dashboard-1.aria.yml | 13 + test/e2e/create-index.spec.ts | 298 ++++ ...ith-links-to-each-form-snapshot-1.aria.yml | 15 + ...-again-resets-the-form-snapshot-1.aria.yml | 4 + ...rects-to-the-dashboard-snapshot-1.aria.yml | 12 + ...rects-to-the-dashboard-snapshot-2.aria.yml | 2 + ...rects-to-the-dashboard-snapshot-2.aria.yml | 2 + ...rects-to-the-dashboard-snapshot-1.aria.yml | 13 + test/e2e/create-tdei.spec.ts | 249 +++ ...ain-button-that-resets-the-form-1.aria.yml | 4 + ...ashboard-with-the-new-workspace-1.aria.yml | 2 + test/e2e/dashboard.spec.ts | 88 ++ test/e2e/edit.spec.ts | 205 +++ ...-the-OpenSidewalks-Rapid-editor-1.aria.yml | 1 + ...host-editor-rapid-loads-Rapid-2-1.aria.yml | 1 + ...ost-editor-rapid3-loads-Rapid-3-1.aria.yml | 1 + ...-editor-and-shows-the-editor-UI-1.aria.yml | 1 + ...atype-loads-the-Pathways-editor-1.aria.yml | 1 + test/e2e/export-index.spec.ts | 190 +++ ...xes-with-their-buttons-snapshot-1.aria.yml | 11 + ...ve-downloads-the-file-snapshots-2.aria.yml | 3 + ...ve-downloads-the-file-snapshots-1.aria.yml | 1 + ...ve-downloads-the-file-snapshots-3.aria.yml | 2 + ...he-Export-to-TDEI-page-snapshot-1.aria.yml | 9 + test/e2e/export-tdei.spec.ts | 319 ++++ ...er-change-the-version-and-retry-1.aria.yml | 4 + ...ain-button-that-resets-the-form-1.aria.yml | 4 + ...ers-when-the-user-cannot-export-1.aria.yml | 9 + ...ckers-and-a-Dataset-Version-box-1.aria.yml | 14 + test/e2e/fixtures.ts | 56 + test/e2e/help.spec.ts | 29 + test/e2e/index.spec.ts | 53 + .../home-hero-chromium-darwin.png | Bin 0 -> 214404 bytes test/e2e/review.spec.ts | 407 +++++ ...ries-display-and-are-selectable-1.aria.yml | 7 + ...ils-and-an-attribute-diff-panel-1.aria.yml | 5 + ...efresh-reloads-the-sidebar-data-1.aria.yml | 7 + ...-with-a-list-of-items-to-review-1.aria.yml | 7 + test/e2e/settings.spec.ts | 372 +++++ test/e2e/signin.spec.ts | 100 ++ .../signin-form-chromium-darwin.png | Bin 0 -> 31151 bytes test/e2e/smoke.spec.ts | 29 + test/mocks/fixtures.ts | 60 + test/mocks/handlers.ts | 11 + test/mocks/server.ts | 6 + test/setup.ts | 9 + test/unit/services/workspaces.test.ts | 46 + test/unit/util/compare.test.ts | 24 + test/unit/util/roles.test.ts | 10 + test/unit/util/url.test.ts | 22 + util/url.ts | 2 + vitest.config.ts | 28 + 83 files changed, 5279 insertions(+), 47 deletions(-) create mode 100644 .nuxtrc create mode 100644 CLAUDE.md create mode 100644 ISSUES.md delete mode 100644 pages/temp.vue create mode 100644 playwright.config.ts create mode 100755 run-tests.sh create mode 100644 test/contract/openapi.json create mode 100644 test/e2e/contract.ts create mode 100644 test/e2e/create-blank.spec.ts create mode 100644 test/e2e/create-blank.spec.ts-snapshots/create-blank-workspace-fills-the-form-creates-be0b9--and-lands-on-the-dashboard-with-it-selected-1.aria.yml create mode 100644 test/e2e/create-file.spec.ts create mode 100644 test/e2e/create-file.spec.ts-snapshots/create-workspace-from-file-a-creation-API-erro-15c25-or-Try-again-and-Try-again-resets-the-form-1.aria.yml create mode 100644 test/e2e/create-file.spec.ts-snapshots/create-workspace-from-file-from-file-button-re-adfdd-es-a-workspace-and-navigates-to-the-dashboard-1.aria.yml create mode 100644 test/e2e/create-index.spec.ts create mode 100644 test/e2e/create-index.spec.ts-snapshots/create-landing-page-shows-the-three-create-cards-with-links-to-each-form-snapshot-1.aria.yml create mode 100644 test/e2e/create-index.spec.ts-snapshots/create-landing-page-the-file-form-shows-an-err-f656f-e-and-Try-again-resets-the-form-snapshot-1.aria.yml create mode 100644 test/e2e/create-index.spec.ts-snapshots/create-landing-page-the-from-blank-workspace--3d53f-ce-and-redirects-to-the-dashboard-snapshot-1.aria.yml create mode 100644 test/e2e/create-index.spec.ts-snapshots/create-landing-page-the-from-blank-workspace--82358-ce-and-redirects-to-the-dashboard-snapshot-2.aria.yml create mode 100644 test/e2e/create-index.spec.ts-snapshots/create-landing-page-the-from-file-button-ope-9bff2-ce-and-redirects-to-the-dashboard-snapshot-2.aria.yml create mode 100644 test/e2e/create-index.spec.ts-snapshots/create-landing-page-the-from-file-button-ope-fc7c8-ce-and-redirects-to-the-dashboard-snapshot-1.aria.yml create mode 100644 test/e2e/create-tdei.spec.ts create mode 100644 test/e2e/create-tdei.spec.ts-snapshots/create-workspace-from-TDEI-an-API-error-shows--f8474-and-a-Try-again-button-that-resets-the-form-1.aria.yml create mode 100644 test/e2e/create-tdei.spec.ts-snapshots/create-workspace-from-TDEI-valid-submit-shows--e0dca-rects-to-the-dashboard-with-the-new-workspace-1.aria.yml create mode 100644 test/e2e/dashboard.spec.ts create mode 100644 test/e2e/edit.spec.ts create mode 100644 test/e2e/edit.spec.ts-snapshots/workspace-edit-editor-host-datatype-osw-loads-the-OpenSidewalks-Rapid-editor-1.aria.yml create mode 100644 test/e2e/edit.spec.ts-snapshots/workspace-edit-editor-host-editor-rapid-loads-Rapid-2-1.aria.yml create mode 100644 test/e2e/edit.spec.ts-snapshots/workspace-edit-editor-host-editor-rapid3-loads-Rapid-3-1.aria.yml create mode 100644 test/e2e/edit.spec.ts-snapshots/workspace-edit-editor-host-loads-the-Rapid-editor-and-shows-the-editor-UI-1.aria.yml create mode 100644 test/e2e/edit.spec.ts-snapshots/workspace-edit-editor-host-without-datatype-loads-the-Pathways-editor-1.aria.yml create mode 100644 test/e2e/export-index.spec.ts create mode 100644 test/e2e/export-index.spec.ts-snapshots/workspace-export-landing-page-shows-the-Uploa-1641c-Download-boxes-with-their-buttons-snapshot-1.aria.yml create mode 100644 test/e2e/export-index.spec.ts-snapshots/workspace-export-landing-page-the-Download-flo-23e69--clicking-Save-downloads-the-file-snapshots-2.aria.yml create mode 100644 test/e2e/export-index.spec.ts-snapshots/workspace-export-landing-page-the-Download-flo-37315--clicking-Save-downloads-the-file-snapshots-1.aria.yml create mode 100644 test/e2e/export-index.spec.ts-snapshots/workspace-export-landing-page-the-Download-flo-801cf--clicking-Save-downloads-the-file-snapshots-3.aria.yml create mode 100644 test/e2e/export-index.spec.ts-snapshots/workspace-export-landing-page-the-Start-butt-72211-vigates-to-the-Export-to-TDEI-page-snapshot-1.aria.yml create mode 100644 test/e2e/export-tdei.spec.ts create mode 100644 test/e2e/export-tdei.spec.ts-snapshots/a-duplicate-dataset-version-shows-an-error-and-lets-the-user-change-the-version-and-retry-1.aria.yml create mode 100644 test/e2e/export-tdei.spec.ts-snapshots/an-API-error-shows-an-error-message-and-a-Try-again-button-that-resets-the-form-1.aria.yml create mode 100644 test/e2e/export-tdei.spec.ts-snapshots/shows-a-permission-message-and-hides-the-pickers-when-the-user-cannot-export-1.aria.yml create mode 100644 test/e2e/export-tdei.spec.ts-snapshots/shows-an-editable-Dataset-Name-project-group-service-pickers-and-a-Dataset-Version-box-1.aria.yml create mode 100644 test/e2e/fixtures.ts create mode 100644 test/e2e/help.spec.ts create mode 100644 test/e2e/index.spec.ts create mode 100644 test/e2e/index.spec.ts-snapshots/home-hero-chromium-darwin.png create mode 100644 test/e2e/review.spec.ts create mode 100644 test/e2e/review.spec.ts-snapshots/workspace-review-both-changeset-and-feedback-entries-display-and-are-selectable-1.aria.yml create mode 100644 test/e2e/review.spec.ts-snapshots/workspace-review-clicking-an-item-shows-its-details-and-an-attribute-diff-panel-1.aria.yml create mode 100644 test/e2e/review.spec.ts-snapshots/workspace-review-clicking-refresh-reloads-the-sidebar-data-1.aria.yml create mode 100644 test/e2e/review.spec.ts-snapshots/workspace-review-loading-shows-a-sidebar-with-a-list-of-items-to-review-1.aria.yml create mode 100644 test/e2e/settings.spec.ts create mode 100644 test/e2e/signin.spec.ts create mode 100644 test/e2e/signin.spec.ts-snapshots/signin-form-chromium-darwin.png create mode 100644 test/e2e/smoke.spec.ts create mode 100644 test/mocks/fixtures.ts create mode 100644 test/mocks/handlers.ts create mode 100644 test/mocks/server.ts create mode 100644 test/setup.ts create mode 100644 test/unit/services/workspaces.test.ts create mode 100644 test/unit/util/compare.test.ts create mode 100644 test/unit/util/roles.test.ts create mode 100644 test/unit/util/url.test.ts create mode 100644 vitest.config.ts diff --git a/.gitignore b/.gitignore index 4a7f73a..f8a2fa4 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,9 @@ logs .env .env.* !.env.example + +# Playwright +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/.nuxtrc b/.nuxtrc new file mode 100644 index 0000000..640f280 --- /dev/null +++ b/.nuxtrc @@ -0,0 +1 @@ +setups.@nuxt/test-utils="4.0.3" \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..2dcef77 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,120 @@ +# CLAUDE.md + +Guidance for working in this repo. Focused on the test infrastructure and +conventions established for it; see `README.md` for app setup. + +## Project + +- **TDEI Workspaces frontend** — Nuxt 4 / Vue 3 **SPA** (`ssr: false`), + bootstrap-vue-next UI, maplibre-gl + Leaflet maps. +- API access is via class-based HTTP clients in `services/` (all extend + `BaseHttpClient` in [services/http.ts](services/http.ts), which wraps `fetch`). + Clients are constructed in [services/index.ts](services/index.ts) from + `import.meta.env.VITE_*` URLs. +- Types in `types/`; the workspace API shapes mirror the backend OpenAPI spec + (integer `id`, `title` not `name`, uuid `tdeiProjectGroupId`, etc.). + +## Testing + +Stack: **Vitest** (unit) + **Playwright** (e2e) + **MSW** (shared API stubs) + +an **OpenAPI contract validator**. Config: [vitest.config.ts](vitest.config.ts), +[playwright.config.ts](playwright.config.ts). + +### Run + +```bash +./run-tests.sh # eslint (test/ only) + unit + e2e +./run-tests.sh unit # vitest only +./run-tests.sh e2e # playwright only +./run-tests.sh --update-snapshots # extra args pass through to playwright +``` + +The e2e suite **intentionally has failing tests** that document real app bugs — +see [ISSUES.md](ISSUES.md). A non-zero exit on a full run is expected. Unit is +always green. + +### Layout + +``` +test/ + setup.ts # MSW server lifecycle for vitest + mocks/ + fixtures.ts # canned API data — SINGLE SOURCE OF TRUTH (spec-shaped) + handlers.ts # MSW http handlers (vitest) + server.ts # setupServer(...handlers) + contract/openapi.json # vendored backend OpenAPI spec + unit/ # vitest: services/ + util/ (default env happy-dom) + e2e/ + fixtures.ts # Playwright test fixture + seed helpers + contract.ts # recordContract(page) -> OpenAPI validator + *.spec.ts # one per page; *.aria.yml + *.png snapshots alongside +``` + +### Conventions + +- **Unit tests** default to the **happy-dom** environment (several `services/` + modules construct `DOMParser`/`XMLSerializer` at import time; bare node lacks + them). A test can opt into the full Nuxt env with `// @vitest-environment nuxt`. +- **e2e auth/stubs:** import from `./fixtures` — `test`, `expect`, + `seedAuthenticatedSession(page)` (call FIRST for any authed page; user is + "Tester"/`USER_ID`), `seedProjectGroupSelection(page, {id, name})`. Import + canned data + ids (`aWorkspace`, `myWorkspaces`, `projectGroups`, + `PROJECT_GROUP_ID`, `USER_ID`, `TEST_API_BASE`) from `../mocks/fixtures`. +- **API stubbing:** in e2e ALL API base URLs point at host `http://api.test/` + (set in `playwright.config.ts` `webServer.env`). Stub **every** endpoint a page + hits or the page 500s on the failed fetch. New-API response bodies **must be + spec-conformant** (the contract validator checks them). +- **Contract testing:** for a page's "validate API calls match the Swagger spec" + outline, `const c = recordContract(page)` before navigating, drive the page, + then `expect(c.violations()).toEqual([])`. Only `api.test` new-API calls are + checked (TDEI/OSM hosts are out of spec scope). +- **Snapshots:** use **ARIA** snapshots (`toMatchAriaSnapshot()`), not pixel + screenshots — they're text-based and cross-platform stable. Don't snapshot + volatile content (blob URLs, transient toasts). Regenerate with + `--update-snapshots`. +- **Lint:** the stylistic config requires **semicolons** and **no trailing + commas** (set in `nuxt.config.ts` `eslint.config.stylistic`). New code must + comply. App source has a large pre-existing backlog of other lint errors, so + `run-tests.sh` lints `test/` only. + +### The `@test` comment workflow + +Pages carry `@test e2e:` / `@test unit:` outline comments at the top describing +intended behavior. Tests are generated **to the comment (the spec), not the +current code** — if the code diverges, the test is left RED to document the bug +(see ISSUES.md). Don't soften assertions to make buggy code pass. + +## Gotchas (learned the hard way) + +- **OSM URLs include `/api/0.6/`.** The OSM client base is + `http://api.test/osm/api/0.6/`. Route globs must be e.g. + `**/osm/api/0.6/changesets.json**`. Also: glob `*` matches a single path + segment, so `**/osm/changeset/*/upload` will NOT match + `osm/api/0.6/changeset/777/upload`. +- **`DatasetTypeRadio` is a Bootstrap `.btn-check`** (hidden `` + visible + `