Files
FabledCurator/frontend/test/router.spec.js
T
bvandeusenandClaude Opus 5 ecd72015a7
Build images / sign-extension (push) Successful in 4s
CI / lint (push) Successful in 3s
Build images / build-ml (push) Successful in 8s
Build images / build-agent (push) Successful in 9s
CI / extension-version (push) Successful in 3s
CI / frontend-build (push) Successful in 28s
CI / backend-lint-and-test (push) Successful in 35s
Build images / build-web (push) Successful in 1m19s
Build images / smoke-web (push) Skipped
Build images / promote (push) Skipped
CI / integration (push) Successful in 2m47s
feat: the front door answers "what arrived?" (milestone 387 step B1)
Moves FRONT_DOOR from /showcase to /latest. Showcase is a random
TABLESAMPLE — lean-back, and it can never tell you anything is wrong.
The feed is the only view where a failing source surfaces on its own,
as a creator who has gone quiet. The per-artist "new since last visit"
badges (#597) were a workaround for this view not being the door.

Showcase is demoted to a nav entry, not removed. Nothing is being
replaced, so rule 22's delete-the-legacy-path does not apply.

Deviation from the filed plan, deliberate: the step said write a new
LatestView.vue. Rejected — PostsView is ALREADY a self-contained feed
(own container, own store, infinite scroll, filters, deep-link
anchoring, empty state), and Browse only ever wrapped it in a tab
strip. A new view would have duplicated 231 working lines to gain
nothing. Mounting PostsView directly at its own route IS the whole
difference the promotion was after: a door you arrive at, not a hub
you navigate out of. Rule 28.

Backend untouched, as scoped — PostFeedService.scroll already does
cursor-paginated newest-first.

Two things this shook loose:

PostsView's deep-link "All posts" button was a hard `{ name: 'posts' }`,
which redirects into Browse. Correct while the view only ever rendered
inside Browse's tab; from the front door it would have yanked the
operator sideways into a different surface. Now returns to the current
route minus post_id, so Browse keeps its tab and any active scope.

The README claimed "A Showcase front page". That block is the SOURCE
the release notes quote (scripts/release_notes.py product_overview),
not a generated copy, so it is fixed here — a document contradicting
the code is the characteristic defect of the public-surface area.

No stickyChrome on the route: unlike Browse/Gallery/Settings this view
has no sticky sub-header for the nav to butt against.

The router spec pinned FRONT_DOOR to /showcase and now pins /latest,
plus that Showcase stayed reachable and in the nav — the demotion is
asserted, not assumed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LNXXULQDjVZmbuNa2G9mD9
2026-09-09 22:46:04 -04:00

78 lines
3.1 KiB
JavaScript

import { describe, it, expect } from 'vitest'
import router, { FRONT_DOOR } from '../src/router.js'
describe('router', () => {
it('FRONT_DOOR is the post feed', () => {
// Moved from /showcase in #387 B1. Asserted on the constant rather than on
// a navigation so the intent is pinned: the door is the feed, and moving it
// again should be a deliberate edit to a failing test, not a quiet change.
expect(FRONT_DOOR).toBe('/latest')
})
it('/latest is the feed, mounted outside the Browse tab strip', () => {
const r = router.resolve('/latest')
expect(r.name).toBe('latest')
// A nav entry in its own right (meta.title is what TopNav lists on), and
// first in the order.
expect(r.meta.title).toBe('Latest')
expect(r.meta.navOrder).toBe(5)
// Deliberately NOT stickyChrome — it has no sticky sub-header for the nav
// to butt against, unlike Browse/Gallery/Settings.
expect(r.meta.stickyChrome).toBeUndefined()
})
it('showcase is demoted, not removed — still reachable and still in the nav', () => {
const r = router.resolve('/showcase')
expect(r.name).toBe('showcase')
expect(r.meta.title).toBe('Showcase')
})
it('/ redirects to FRONT_DOOR', async () => {
// resolve() does not follow redirects (they apply on navigation), so
// actually navigate and assert the resulting route.
await router.push('/')
expect(router.currentRoute.value.path).toBe(FRONT_DOOR)
})
it('gallery is reachable at /gallery', () => {
expect(router.resolve('/gallery').name).toBe('gallery')
})
it('showcase, artist resolve to named routes', () => {
expect(router.resolve('/showcase').name).toBe('showcase')
expect(router.resolve('/artist/some-slug').name).toBe('artist')
expect(router.resolve('/artist/some-slug').params.slug).toBe('some-slug')
})
it('browse hub is reachable and old axis paths redirect into its tabs', async () => {
expect(router.resolve('/browse').name).toBe('browse')
await router.push('/tags')
expect(router.currentRoute.value.name).toBe('browse')
expect(router.currentRoute.value.query.tab).toBe('tags')
await router.push('/artists')
expect(router.currentRoute.value.query.tab).toBe('artists')
// A posts deep link survives the redirect into the hub tab.
await router.push({ path: '/posts', query: { post_id: '7' } })
expect(router.currentRoute.value.name).toBe('browse')
expect(router.currentRoute.value.query.tab).toBe('posts')
expect(router.currentRoute.value.query.post_id).toBe('7')
})
it('/system redirects into the Settings System tab', async () => {
// It shipped as a standalone page for one build; the health dot and any
// bookmark from it must still land on the surface, which is now a tab.
await router.push('/system')
expect(router.currentRoute.value.name).toBe('settings')
expect(router.currentRoute.value.query.tab).toBe('system')
})
it('series-read is an immersive route', () => {
const r = router.resolve('/series/5/read')
expect(r.name).toBe('series-read')
expect(r.meta.immersive).toBe(true)
})
})