File
Blob: tests/client/lib/share-page-model.test.ts
| 1 | import { describe, expect, it } from "vitest"; |
| 2 | import { deriveSharePagePresentation } from "@/client/lib/share-page-model"; |
| 3 | import type { ActivePageState } from "@/client/lib/active-page-model"; |
| 4 | import type { ShareRootPage } from "@/client/components/share/use-share-view"; |
| 5 | |
| 6 | const ROOT_PAGE: ShareRootPage = { |
| 7 | id: "page-root", |
| 8 | kind: "doc", |
| 9 | title: "Root", |
| 10 | icon: "๐", |
| 11 | cover_url: "/uploads/root-cover", |
| 12 | permission: "view", |
| 13 | }; |
| 14 | |
| 15 | describe("deriveSharePagePresentation", () => { |
| 16 | it("uses the ready page when the active page is loaded", () => { |
| 17 | const state: ActivePageState = { |
| 18 | kind: "ready", |
| 19 | backing: "live", |
| 20 | snapshot: { id: "page-child", workspaceId: "ws-1", kind: "doc", title: "Child", icon: "๐ฟ", coverUrl: null }, |
| 21 | access: { mode: "edit" }, |
| 22 | ancestors: [], |
| 23 | ancestorsStatus: "ready", |
| 24 | }; |
| 25 | |
| 26 | expect(deriveSharePagePresentation(ROOT_PAGE, "page-child", state)).toMatchObject({ |
| 27 | activePageId: "page-child", |
| 28 | isRootActive: false, |
| 29 | isPageLoading: false, |
| 30 | isAncestorTrailLoading: false, |
| 31 | displayTitle: "Child", |
| 32 | displayIcon: "๐ฟ", |
| 33 | isViewOnly: false, |
| 34 | }); |
| 35 | }); |
| 36 | |
| 37 | it("does not synthesize root metadata when the active page state is still loading", () => { |
| 38 | // Boundary remount + useState(initialSnapshot) makes this transient state |
| 39 | // unreachable for the root page in practice, but the derivation must not |
| 40 | // paper over loading with stale rootPage fallbacks โ display stays empty. |
| 41 | const state: ActivePageState = { kind: "loading" }; |
| 42 | |
| 43 | expect(deriveSharePagePresentation(ROOT_PAGE, "page-root", state)).toMatchObject({ |
| 44 | activePageId: "page-root", |
| 45 | isRootActive: true, |
| 46 | isPageLoading: true, |
| 47 | isAncestorTrailLoading: false, |
| 48 | displayTitle: "", |
| 49 | displayIcon: null, |
| 50 | displayCoverUrl: null, |
| 51 | isViewOnly: true, |
| 52 | }); |
| 53 | }); |
| 54 | |
| 55 | it("does not leak stale ready data from a previous page id", () => { |
| 56 | const state: ActivePageState = { |
| 57 | kind: "ready", |
| 58 | backing: "live", |
| 59 | snapshot: { id: "page-old", workspaceId: "ws-1", kind: "doc", title: "Old", icon: null, coverUrl: null }, |
| 60 | access: { mode: "view" }, |
| 61 | ancestors: [], |
| 62 | ancestorsStatus: "ready", |
| 63 | }; |
| 64 | |
| 65 | expect(deriveSharePagePresentation(ROOT_PAGE, "page-next", state)).toMatchObject({ |
| 66 | activePageId: "page-next", |
| 67 | isRootActive: false, |
| 68 | isPageLoading: true, |
| 69 | isAncestorTrailLoading: false, |
| 70 | page: null, |
| 71 | displayTitle: "", |
| 72 | displayIcon: null, |
| 73 | isViewOnly: true, |
| 74 | }); |
| 75 | }); |
| 76 | |
| 77 | it("surfaces ancestor trail loading separately from page readiness", () => { |
| 78 | const state: ActivePageState = { |
| 79 | kind: "ready", |
| 80 | backing: "live", |
| 81 | snapshot: { id: "page-child", workspaceId: "ws-1", kind: "doc", title: "Child", icon: null, coverUrl: null }, |
| 82 | access: { mode: "view" }, |
| 83 | ancestors: [], |
| 84 | ancestorsStatus: "loading", |
| 85 | }; |
| 86 | |
| 87 | expect(deriveSharePagePresentation(ROOT_PAGE, "page-child", state)).toMatchObject({ |
| 88 | activePageId: "page-child", |
| 89 | isPageLoading: false, |
| 90 | isAncestorTrailLoading: true, |
| 91 | page: { id: "page-child" }, |
| 92 | }); |
| 93 | }); |
| 94 | }); |