Removed the inline vi.unstubAllGlobals() call from the end of the
'reveals picker' test body and added it to the shared afterEach hook
so every test in the file gets the same global cleanup regardless of
which test runs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add move-up and move-down tests that verify PUT /items/reorder is called
with the swapped ID order; 50ms delay accounts for two await levels before
csrfFetch is called (click → handleMoveUp → handleReorder → csrfFetch)
- Replace vacuous 'isDirty stays false' test (was asserting a dialog that
never renders) with a meaningful publish-button-enabled assertion after
adding an item
- Update remove button query from 'Wirklich entfernen?' to 'Eintrag entfernen'
to match the new journey_remove_item_aria aria-label
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The remove button was using the confirmation-question text as its
aria-label. Added a new dedicated journey_remove_item_aria key
in all three locales so the button has a clear accessible name
before the confirmation dialog opens.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both move-up and move-down buttons had inline style="min-height: 22px"
which is below the WCAG 2.2 success criterion 2.5.8 (44×44 CSS pixels
minimum). Replaced with Tailwind min-h-[44px] min-w-[44px] classes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously the item list area was blank when no items had been added.
The empty-state paragraph uses the existing journey_empty_state i18n key.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The screen-reader live announcement was calling m.journey_item_moved()
without the required {position, total, newPosition} parameters, which
the i18n template uses to build the full announcement string.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- useBlockDragDrop: add runtime expect() alongside expectTypeOf so
browser-mode runner counts at least one assertion
- JourneyAddBar: use exact:true on 'Hinzufügen' button — partial match
was hitting '+ Brief hinzufügen' and '+ Zwischentext hinzufügen' too
- JourneyEditor: fix 4 issues — drop wrong not.toBeInTheDocument()
(placeholder creates accessible name); pass title:'' in publish-disabled
test (default was non-empty); use getByPlaceholder for interlude
textarea to avoid 4-element strict-mode violation; exact:true for
'Hinzufügen' button
- DocumentPickerDropdown: use .click({force:true}) on aria-disabled
option — userEvent refuses non-enabled elements
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add JourneyEditor, JourneyItemRow, JourneyAddBar, GeschichteSidebar to the
geschichte README props table. Strike @dnd-kit/svelte-dnd-action library refs
and raw orange-*/blue-600 color classes in the editor spec HTML.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Main editing surface for JOURNEY-type Geschichten. Manages sorted item list
with optimistic add/remove/reorder (rollback on failure), drag-and-drop reorder
via createBlockDragDrop, intro textarea, and sidebar via GeschichteSidebar.
Publish requires at least one item + non-empty title.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two add buttons: document picker (DocumentPickerDropdown) and interlude inline
draft form. Interlude confirm is aria-disabled until text is non-empty. Closing
one panel opens the other. Tests cover all three plan test cases.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Item row with drag handle, move-up/down buttons, inline note textarea (PATCH
on blur), interlude visual treatment, and inline confirm for removes that
would discard a note. Interlude note cannot be cleared (blocked on empty).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Moves Status + Persons sections into a shared component so both
GeschichteEditor (STORY) and the upcoming JourneyEditor (JOURNEY) can
use the same sidebar without duplicating markup. Adds <details> mobile
collapsibles with 44px summary hit areas.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Importing layout.css in test-setup.ts activated Tailwind's responsive
breakpoint classes (hidden lg:flex, hidden md:block, etc.), making
42 elements invisible at the default narrow Playwright test viewport.
Revert the CSS import. Instead, add inline style attributes to the three
components whose tests measure computed properties (min-height, font-size)
— these values match what the Tailwind classes produce, so the real app
appearance is unchanged.
Also fix goto mock leakage in the geschichten/[id] delete-failure test:
the delete-success test's goto('/geschichten') call was not cleared before
the failure test ran. Add beforeEach(vi.clearAllMocks) to reset mock state.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The inline publishedAt $derived.by() duplicated the exact logic that
formatPublishedAt() in utils.ts encapsulates. Replace it with the
shared helper and drop the now-unused formatDate import.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without a landmark or widget role, aria-label on a generic <div> is
silently ignored by most screen readers (ARIA spec). Adding role="note"
gives the element an ARIA role that accepts an accessible name, making
the interlude label actually announced.
Also adds a test asserting role="note" and the matching aria-label are
both present on the same element.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mirrors the getBoundingClientRect pattern from JourneyItemCard.svelte.spec.ts.
Tests actual rendered height rather than presence of a CSS class string.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Function names already communicate intent. Comments that restate the
function name add noise without explaining why.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
JourneyReader filters items to only those where document != null before
passing them here — the ! assertion is valid by caller invariant.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CSS class string assertion was fragile — class names can change without
breaking the actual layout. DOM measurement via getBoundingClientRect is the
correct way to verify computed height meets WCAG 2.2 minimum.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
13 tests covering null/undefined inputs, partial names, email fallback,
and TZ-safe date slicing for formatPublishedAt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Moves the confirm-then-delete flow out of StoryReader and JourneyReader into
the single [id]/+page.svelte owner. Both reader components gain an optional
ondelete prop — the delete button calls ondelete?.() so the handler is opt-in
and never duplicated. Tests verify the prop is called on click.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the 3-line inline join with the shared formatAuthorName helper from
utils.ts. Test switches from CSS class string assertion to getComputedStyle
for the badge font-size check.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces aria-label="Kuratorennotiz" with m.journey_interlude_aria_label()
so screen readers get the correct label in all three supported locales.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- GeschichtenCard.svelte: use GeschichteSummary instead of Geschichte
(list endpoint returns summaries; no items/createdAt/updatedAt needed)
- GeschichtenCard.svelte.test.ts: factory returns GeschichteSummary with
lean author shape; drop Geschichte-only fields (createdAt, groups, etc.)
- geschichten/[id]/+page.svelte: add focus:outline-none focus-visible:ring-2
focus-visible:ring-focus-ring to journey item document links (WCAG 2.4.7)
- page.svelte.test.ts ([id]): replace stale documents[] factory field with
items[]; test now checks placeholder text + note caption
- page.svelte.test.ts (new): remove removed initialDocuments from baseData;
rename test to reflect that only initialPersons is passed through
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- GeschichteEditor.svelte.spec.ts: remove docFactory + initialDocuments test;
rename documentIds test to personIds-only; add familyMember+provisional to
personFactory (were pre-existing omissions)
- GeschichtenCard.svelte.spec.ts: add type:'STORY', replace documents:[] with
items:[], change body null→undefined to match Geschichte schema
- GeschichtenCard.svelte.test.ts: add status/type/createdAt/updatedAt to factory;
cast result as Geschichte to avoid spread-widening type inference
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- api.ts: add GeschichteType, JourneyItem, GeschichteSummary schemas;
remove documentId param from list endpoint; change list response to
GeschichteSummary[]; add type + items to Geschichte; remove documents field
- GeschichteEditor: remove DocumentMultiSelect + documentIds from payload
(journey items are managed via the future Lesereisen editor, not here)
- GET /geschichten page: remove documentId filter from server load + URL logic
- geschichten/new: remove documentId pre-population from server load
- geschichten/[id]: replace g.documents with g.items (document-backed JourneyItems)
- geschichten/new + [id]/edit: remove documentIds from submit payload type
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Empty list early return, populated section, write-action link gated on
canWrite, visible-cap of 3, footer show-all link visibility based on
overflow, author name vs email fallback.
9 tests covering ~25 of GeschichtenCard's branches.
Refs #496.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three distinct root causes:
1. hilfe/transkription: Wikipedia link test was checking .textContent but
the accessible text had moved to aria-label in a prior commit.
2. documents/[id]/edit: vi.spyOn on a Svelte 5 compiled .svelte.ts service
object does not reliably track calls in vitest-browser mode; replaced
with a plain closure-based mock.
3. GeschichteEditor: TipTap's onMount steals focus and its ProseMirror
view interferes with Playwright CDP event dispatch. Three workarounds:
- blur: dispatchEvent(new FocusEvent('blur')) bypasses focus-state check
- save buttons: dispatchEvent(new MouseEvent('click')) from in-browser JS
context reliably triggers Svelte 5 onclick vs. Playwright CDP click
- trailing-space fill: input.value + dispatchEvent('input') works where
userEvent.fill('value ') silently fails to update bind:value
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>