|
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 7m18s
CI / Build container images (pull_request) Successful in 4m41s
CI / Auth e2e pack (pull_request) Successful in 10m4s
CI / Import/export fidelity gate (pull_request) Successful in 56s
CD / Deploy to Test (push) Blocked by required conditions
CD / Smoke tests against Test (push) Blocked by required conditions
CD / Promote to Int (push) Blocked by required conditions
CI / Auth e2e pack (push) Blocked by required conditions
CI / Import/export fidelity gate (push) Blocked by required conditions
CI / Build container images (push) Blocked by required conditions
CD / Build and push images (push) Successful in 33s
CI / Lint, typecheck, test (push) Has been cancelled
prosemirror-tables already ships mergeCells/splitCell and the schema (tableNodes) already carries colspan/rowspan -- only the controls were missing. Adds the two commands, toolbar buttons whose enabled state follows the selection (merge needs a multi-cell selection, split a merged cell), and de+en labels. Both render paths now carry the spans: docToHtml emits colspan/rowspan (read mode, exports via the HTML path), and the markdown serializer pads a colspan with empty cells so every row keeps the table's column count -- rowspan stays lossy there, GFM cannot express it. e2e drives merge and split through the toolbar; the cell selection is made per Shift+Click because a keypress in the same tick as the preceding click races the editor's post-click rendering (keyboard cell selection itself works, verified interactively with a settled editor). Closes #337 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012aoPvnakfBP28nAfijgUY9 |
||
|---|---|---|
| .. | ||
| a11y.spec.ts | ||
| access-rules.spec.ts | ||
| admin-quotas.spec.ts | ||
| admin-settings.spec.ts | ||
| admin-users.spec.ts | ||
| attachments.spec.ts | ||
| auth.spec.ts | ||
| backlinks.spec.ts | ||
| collab-permissions.spec.ts | ||
| collab.spec.ts | ||
| comments.spec.ts | ||
| content.spec.ts | ||
| create-missing-page.spec.ts | ||
| editor.spec.ts | ||
| export.spec.ts | ||
| favorites.spec.ts | ||
| fonts.spec.ts | ||
| graph.spec.ts | ||
| helpers.ts | ||
| image.spec.ts | ||
| import-vault.spec.ts | ||
| import.spec.ts | ||
| invitations.spec.ts | ||
| labels.spec.ts | ||
| legal.spec.ts | ||
| link.spec.ts | ||
| markdown.spec.ts | ||
| members.spec.ts | ||
| mermaid.spec.ts | ||
| offline.spec.ts | ||
| page-tools.spec.ts | ||
| page-tree.spec.ts | ||
| permission-matrix.spec.ts | ||
| plugin-admin.spec.ts | ||
| plugin-blocks.spec.ts | ||
| plugin-fixtures.ts | ||
| plugins.spec.ts | ||
| pond-theme.spec.ts | ||
| public.spec.ts | ||
| README.md | ||
| reorder.spec.ts | ||
| search.spec.ts | ||
| section-styles.spec.ts | ||
| settings-nav.spec.ts | ||
| setup.spec.ts | ||
| sidebar.spec.ts | ||
| smoke.spec.ts | ||
| social.spec.ts | ||
| system.spec.ts | ||
| tasks.spec.ts | ||
| theme-accent.spec.ts | ||
| theme.spec.ts | ||
| trash.spec.ts | ||
| vs-nfd-marking.spec.ts | ||
| wikilink.spec.ts | ||
End-to-end tests
Playwright suites, most local-only — three run in CI/CD:
| Suite | Target | Where it runs |
|---|---|---|
smoke.spec.ts |
any deployed stage | CD pipeline against https://test.dorfteich.cloud after every deploy |
auth.spec.ts |
full local stack with Mailpit | CI job auth-e2e on every PR/push; locally against the dev stack |
content.spec.ts |
full local stack | same CI job auth-e2e (a second step), right after the auth pack |
| everything else | full local stack | locally only — editor/sidebar/image/link/markdown/trash.spec.ts |
content.spec.ts is the M2 content regression pack (issue #32): page
lifecycle, editor basics, image paste, Markdown round-trip, and trash —
enough to catch a regression across the whole content model without
re-running every edge case the feature-specific packs above already cover.
Its Markdown round-trip test is a real regression pin, not just a smoke
check: it compares the seeded "Every Element" fixture page's exported
Markdown byte-for-byte against the checked-in content-page.md (see
"Content fixtures" below) — any schema/serializer change that alters how a
node round-trips fails it, once the seed has re-run against the changed
code (build → migrate → seed → test, exactly CI's order).
Running locally
# 1. Stack: database + Mailpit, api (3001), web dev server (5173)
docker compose -f deploy/compose/docker-compose.yml -f deploy/compose/compose.dev.yml up -d db mailpit
DATABASE_URL=postgresql://dorfteich:dorfteich@localhost:5434/dorfteich pnpm --filter @dorfteich/api db:seed
DATABASE_URL=postgresql://dorfteich:dorfteich@localhost:5434/dorfteich PORT=3001 pnpm --filter @dorfteich/api start:dev &
pnpm --filter @dorfteich/web dev &
# 2. Tests
E2E_BASE_URL=http://localhost:5173 E2E_MAILPIT_URL=http://localhost:8025 pnpm --filter @dorfteich/web e2e
auth.spec.ts skips itself when E2E_MAILPIT_URL is unset, so the CD
smoke run never trips over it.
vs-nfd-marking.spec.ts (issues #244/#245) has three parts selected by
E2E_VS_NFD_MODE (marked, hidden, unset = off); each needs an api
started with the matching VS_NFD_MODE. CI runs the marked half against
the main e2e stack and the hidden half against a second api (port 3006)
on the same database; the off assertions run in local default stacks.
Fixture matrix
Seeded by pnpm --filter @dorfteich/api db:seed (idempotent — re-running
never duplicates). Shared password: fixture passwort 123. Fixtures exist
only on dev machines and disposable CI/Test databases.
| Username | State | Purpose |
|---|---|---|
fixture-admin |
active, Site Admin | admin UI/permissions cases |
fixture-user |
active | regular journeys, settings, sessions |
fixture-editor |
active | second regular account for the collab-permissions pack (reader/editor of another's pond) |
fixture-viewer |
active | signed-in non-member for authenticated/public access-rule cases (issue #55) |
fixture-outsider |
active | the "foreign user" of the permission matrix — member of nothing (issue #60) |
fixture-pending |
e-mail not verified | unverified-login cases |
Permission matrix (permission-matrix.spec.ts, issue #60)
The cross-feature permission hardening pack pins the security-relevant subject × surface combinations against regressions. It is API-level (the UI adds nothing over the resolved status code) and enforces the 404-vs-403 policy: an unauthorized read is 404 (existence hidden), an unauthorized write on something readable is 403.
- Subjects: site admin (
fixture-admin), pond admin / owner (fixture-user), editor (fixture-editor), the same editor label-restricted by asecret-label deny, reader (fixture-viewer), public (anonymous), and the foreign user (fixture-outsider, a member of nothing). - Surfaces: page read, edit (collab-token
rw/ro), sidebar list, search, versions (history = write), media, and the public HTML endpoint. - Extending it: a new permission-touching feature adds a surface here (one
expectrow per subject) rather than a bespoke test, so the matrix stays the one place the policy is pinned. A weakened guard is caught here — verified by temporarily loosening a route decorator and watching the pack go red.
Plugin sandbox (plugins.spec.ts, issue #73)
The sandbox security pack drives the Site-Admin plugin preview page
(/admin/plugins/:id/preview) — the exact sandbox runtime pages embed — with
three fixture plugins built in plugin-fixtures.ts and installed through the
admin API:
- well-behaved: answers
renderand resizes its own frame via the declareduicapability; - malicious (the permanent security regression asset, ADR 0008): probes the
parent DOM, cookies,
localStorage, same-origin and externalfetch, and an undeclared capability — every probe must reportblocked. ALEAKEDverdict is a sandbox escape and fails the build; - hung: never answers, so the host's 5 s deadline must collapse it to the failure placeholder while the surrounding page stays responsive.
Runs in the auth-e2e CI job (needs the api's writable PLUGINS_DIR, satisfied
by the default ./data/plugins).
Attachments (attachments.spec.ts, issue #61)
Non-image attachments: a page's attachments section uploads an allowlisted
file, lists it, and inserts it into the document as a download link (verified
to serve with Content-Disposition: attachment + nosniff, never inline); a
disallowed extension is rejected with the localized allowlist error; the Pond
Admin file manager reports storage usage and flags an orphan (a pond-level
upload with no embedding page). The SVG sanitize/reject policy is covered at
the api level in files.e2e.db.test.ts.
Import (import.spec.ts, issue #64)
The sidebar "import document" action: pick a file, upload, watch progress, open
the new page. .md imports directly (the response is already succeeded);
.docx/.odt poll a conversion job. Cases: a .docx corpus fixture (#63)
opens the converted page, a .md opens directly, an unsupported .txt shows
the localized error and creates no page, and two concurrent .md imports both
complete. The .docx case self-skips unless E2E_PANDOC is set — CI's e2e
stack has no reachable pandoc sidecar (jobs are container-networked; same reason
the api's real-pandoc fixtures test skips in CI, #63), so it runs locally / on a
stage. Run it locally with a sidecar reachable at the api's PANDOC_URL:
docker run -d -p 3030:3030 pandoc/core:3.6 server # api PANDOC_URL → this
E2E_PANDOC=1 E2E_BASE_URL=http://localhost:5990 \
pnpm --filter @dorfteich/web exec playwright test e2e/import.spec.ts
Export (export.spec.ts, issue #65)
The pond-settings "Download pond as ZIP" link (a Markdown ZIP of the readable
pages) and the page-menu office export. The ZIP download needs no sidecar; the
.docx export runs a conversion job and, like the import .docx case,
self-skips unless E2E_PANDOC is set. The permission-filtered ZIP contents,
the docx pandoc output, and the 500-page streaming path are covered at the api
level in export.service.db.test.ts (+ export-markdown.test.ts).
Content fixtures
db:seed also creates a shared pond content-fixtures (owned by
fixture-user) with two pages, for the content regression pack and manual
QA:
- Every Element (
every-element) — every editor schema node and mark (issue #24: headings 1–4, all list types, table, blockquote, code block, horizontal rule, hard break, and all five marks). Loaded from the checked-inapps/api/prisma/fixtures/content-page.yjs, a Yjs snapshot generated from the human-readablecontent-page.mdnext to it —content-page.mdis the thing to read or edit; the.yjsfile is a build artifact of it, not source. - Fixture Image (
fixture-image) — one real, servable uploaded image (the placeholderfileIdinside the Markdown fixture above is not a real attachment; this page's image is). - Classified Note (
classified-note) — a VS-NfD-marked page (issue #206, ADR 0022) so the a11y pack can assert the classification banner (top and bottom, both themes).
Print marking check (print.css, issue #207)
The VS-NfD marking must appear at the top AND bottom of every printed
sheet (ADR 0022). Mechanism: the page content sits in a real
<table class="print-frame"> whose thead/tfoot carry the banners —
the one construct Chromium and Gecko both repeat per page (@page margin
boxes are unimplemented; position: fixed places unreliably). The check,
repeatable against a running local stack (seeded, signed in as
fixture-user):
- Classify a multi-page document:
UPDATE pages SET classification='VS_NFD' WHERE slug='every-element'; - Chromium: print
/p/content-fixtures/every-elementto PDF (browser print dialog, or Playwrightpage.pdf({ preferCSSPageSize: true })after logging in). - Gecko (Firefox): open the same page (or grant it a public reader
and use
/public/content-fixtures/every-element), Cmd+P → save as PDF. Headless equivalent: geckodriver + WebDriverPOST /session/<id>/print. - Every page of both PDFs must show
VS – NUR FÜR DEN DIENSTGEBRAUCHonce at the top and once at the bottom, with no navigation chrome and no overlap with content (quick text check:pypdf→page.extract_text().count(...) == 2per page).
Last verified 2026-07-31: Chromium 140 (Playwright) and Firefox 153, 2 markings on every page of a 2-page PDF each.
Regenerating after editing content-page.md:
pnpm --filter @dorfteich/api fixtures:regenerate
This is deterministic — re-running without editing the Markdown produces a
byte-identical .yjs file (the script pins the Yjs document's clientID,
which is otherwise randomized per Y.Doc instance) — and it refuses to
write a snapshot that isn't a fixed point of the Markdown round-trip
(docToMarkdown(markdownToDoc(x)) === x), so a stale fixture can't get
checked in silently.
Conventions
- New feature packs get their own
<feature>.spec.tsnext to these and extend the fixture matrix here (permission matrix arrives with M5, issue #60). - Use
contextForUser()fromhelpers.tsfor signed-in tests — it logs in through the api and hands you a browser context with the session cookie, no UI login repetition. - Flaky tests are defects (ADR 0014): fix or quarantine immediately.