-
Notifications
You must be signed in to change notification settings - Fork 1
Unit and integration tests #64
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
1f5d931
First pass at tests
jeffmaki 0a7aaf5
Merge branch 'master' into jeff-tests
jeffmaki 0dbc854
New tests
jeffmaki cf04587
Linter fixes + CI integration of lint, typecheck and tests
jeffmaki 8d30f2e
E2E test fixes and updates
jeffmaki 6e4b2b2
New test + linter fixes
jeffmaki 9fcd14e
Update pathways.ts
jeffmaki df03665
Typechecker fixes (any is left for now)
jeffmaki 72d4735
Update ci.yml
jeffmaki 8d269e0
Update ci.yml
jeffmaki 5bfd83e
Linter
jeffmaki 78d9309
Remove editor load fail test
jeffmaki 0b494bb
Update README.md
jeffmaki c446fb0
Change image snapshots to aria tag diffs
jeffmaki d6f463c
Disable Sentry during CI tests
jeffmaki 19af67d
Linter
jeffmaki e3e48fc
Merge branch 'master' into jeff-tests
jeffmaki f2605d0
Update osm.ts
jeffmaki 6335277
Update tdei.ts
jeffmaki d1dff1b
Update ci.yml
jeffmaki 252b25d
Update ci.yml
jeffmaki 2b29b6a
Update tag.yml
jeffmaki File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| { | ||
| "permissions": { | ||
| "allow": [ | ||
| "Bash(npm ls *)", | ||
| "Bash(node -e \"const e=require\\('./node_modules/vue-router/package.json'\\).exports; console.log\\(JSON.stringify\\(Object.keys\\(e\\)\\)\\)\")", | ||
| "Bash(node -e \"console.log\\(require\\('./node_modules/vue-router/package.json'\\).version\\)\")", | ||
| "Bash(node -e \"console.log\\(require\\('./node_modules/@sentry/nuxt/package.json'\\).version\\)\")" | ||
| ] | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| setups.@nuxt/test-utils="4.0.3" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,197 @@ | ||
| # 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 is written **to the `@test` outline comments (the spec), not the | ||
| current code**, so a new e2e failure flags a real app/spec divergence. It | ||
| currently passes aside from intentionally-skipped (`test.fixme`) flows. 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. | ||
| 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 `<input>` + visible | ||
| `<label>`). `.check()` times out — click the label text instead | ||
| (`getByText('GTFS Pathways').click()`). | ||
| - **`app-icon` renders material-icon ligature text** (e.g. "menu_book") into | ||
| element text/accessible names. Match headings/buttons by regex name or stable | ||
| class/href, not exact accessible name. | ||
| - **Playwright `getByRole({ name })` is substring/normalized** unless | ||
| `exact: true`. "Delete this workspace" also matches "...want to delete this | ||
| workspace" — anchor or use `exact: true`. | ||
| - **`selectOption` needs a string/value/index**, not a regex object. | ||
| - **maplibre-gl needs WebGL**, unreliable headless. Pages with a map: stub the | ||
| bbox/data endpoints empty so the map shows its empty state instead of | ||
| initializing; assert DOM, not the GL canvas. The dashboard auto-selects a | ||
| workspace when a project group has workspaces (→ mounts the map), so the | ||
| empty-state tests use empty `workspaces/mine`. | ||
| - **Nuxt dev server compiles routes lazily**, so cold parallel hits can be slow; | ||
| `playwright.config.ts` sets a 10s `expect` timeout to absorb it. Run e2e | ||
| against a production build if you want pre-compiled routes. | ||
| - **Playwright route precedence is most-recently-registered-first** — register | ||
| per-test override routes AFTER (or in a way that wins over) shared stub helpers. | ||
|
|
||
| ## Permission Structure | ||
|
|
||
| Project Group Admin ("POC") | ||
| * Superuser for the whole project group | ||
| * Implied by "poc" role in TDEI | ||
|
|
||
| Lead/Owner/Workspace Admin | ||
| * Admin-level access for a workspace | ||
| * Configures workspace settings and quest definitions | ||
| * Assigns users to workspace teams | ||
| * Ability to merge changes from other workspace | ||
| * Exports data to TDEI (with appropriate TDEI core roles) | ||
| * Granted by Workspaces setting. | ||
|
|
||
| Contributor/Data Generator | ||
| * Modifies workspace data--all modifications need validation | ||
| * Implied by membership in TDEI project group | ||
|
|
||
| Validator | ||
| * Modifies workspace data and approves changes from contributors | ||
| * Granted by Workspaces setting. | ||
|
|
||
| Viewer/Member/Everyone Else | ||
| * Read-only access to workspace data | ||
| * With express TDEI sign-up, the need for this access level diminishes greatly | ||
| * Granted by Workspaces setting. | ||
|
|
||
| ## What Each Role Can Do | ||
|
|
||
| Below is the *intended* capability matrix annotated with **what the frontend | ||
| actually enforces** (validated against the code on 2026-06-25). The frontend | ||
| recognizes only three workspace roles — `lead | validator | contributor` | ||
| ([types/workspaces.ts](types/workspaces.ts)) — exposed via | ||
| [composables/useWorkspaceRole.ts](composables/useWorkspaceRole.ts), which | ||
| provides `isLead` and `isValidator` only (and **`isValidator` is true for leads | ||
| too**). There is no `isContributor`/`isPoc` helper at the workspace-role layer. | ||
|
|
||
| Project Lead | ||
| * Edit Metadata — ✅ enforced (`isLead`, [General.vue](components/settings/panel/General.vue)) | ||
| * Edit Longform Quests — ⚠️ enforced but also requires the workspace be | ||
| published for external apps: `appControlsDisabled = !isLead || !externalAppAccess` | ||
| ([Apps.vue](components/settings/panel/Apps.vue)) | ||
| * Toggle App-Enabled Flag — ✅ enforced (`isLead`, [Apps.vue](components/settings/panel/Apps.vue)) | ||
| * Delete Workspace — ✅ enforced (`isLead`, [Delete.vue](components/settings/panel/Delete.vue)) | ||
| * Define User Teams — ✅ enforced (`isLead`, [teams/index.vue](pages/workspace/[id]/settings/teams/index.vue), [MembersDialog.vue](components/teams/MembersDialog.vue)) | ||
| * Define Groups or Roles — ✅ enforced (`isLead`, [members.vue](pages/workspace/[id]/settings/members.vue)) | ||
| * Export to TDEI — ❌ **not gated by the workspace `lead` role.** Eligibility is | ||
| `pg.roles.includes('poc') || pg.roles.includes('<type>_data_generator')` — | ||
| i.e. **TDEI project-group roles**, not the workspace role | ||
| ([export/tdei.vue](pages/workspace/[id]/export/tdei.vue)). A Lead without one | ||
| of those TDEI roles cannot export; a non-lead with `poc` can. | ||
| * Validate Changeset — ✅ enforced (`isValidator`, [review/Toolbar.vue](components/review/Toolbar.vue)) | ||
| * Move Workspace from Project Group to Project Group — ❌ **not implemented.** | ||
| No `move`/`transfer`/`changeProjectGroup` code exists anywhere in the frontend. | ||
| * Edit POSM Element — ✅ available, but **not role-gated** (see note below) | ||
|
|
||
| Validator | ||
| * Export to TDEI — ❌ same as above — gated on TDEI `poc`/`data_generator`, | ||
| never on the `validator` workspace role | ||
| * Validate Changeset — ✅ enforced (`isValidator` covers `validator`) | ||
| * Edit POSM Element — ✅ available, but **not role-gated** (see note below) | ||
|
|
||
| Contributor | ||
| * Edit POSM Element — ✅ available, but **not role-gated** (see note below) | ||
|
|
||
| Authenticated User With PG/Workspace Association | ||
| * Edit POSM Element — ✅ available, but **not role-gated** (see note below) | ||
|
|
||
| **Note on "Edit POSM Element":** the editor page | ||
| ([edit.vue](pages/workspace/[id]/edit.vue)) has **zero role gating** — the only | ||
| gate is `auth.global.ts` (authentication, not role), so any authenticated user | ||
| who reaches it loads the editor. The "contributor edits need validation" and | ||
| "viewer is read-only" distinctions are **not enforced in this frontend**; they | ||
| would have to be enforced by the backend / changeset-validation flow. | ||
|
|
||
| ## Test status | ||
|
|
||
| The e2e suite currently passes aside from **6 intentionally-skipped tests**: 6 | ||
| `test.fixme` dashboard flows blocked by maplibre/external-editor rendering. Some | ||
| heavy authed pages (e.g. `settings/members`) can flake under high parallelism | ||
| (the lazy-compile issue above) — run serially (`--workers=1`) for deterministic | ||
| results. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,7 @@ | ||
| <template> | ||
| <img class="app-logo img-fluid" src="~/assets/img/tdei-logo.png" alt="" /> | ||
| <img | ||
| class="app-logo img-fluid" | ||
| src="~/assets/img/tdei-logo.png" | ||
| alt="" | ||
| > | ||
| </template> | ||
|
|
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Declare minimal
GITHUB_TOKENpermissions explicitly.This workflow only needs read access to contents plus artifact upload, but without a
permissions:block it inherits the repository default token scope. Locking that down materially reduces the impact of any compromised test step.Suggested hardening
📝 Committable suggestion
🧰 Tools
🪛 zizmor (1.26.1)
[warning] 14-15: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[warning] 10-57: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block
(excessive-permissions)
[error] 15-15: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 18-18: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 52-52: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🤖 Prompt for AI Agents
Source: Linters/SAST tools