Skip to content
File

Blob: tests/client/lib/page-tree-model.test.ts

typescript112 lines
1import { describe, expect, it } from "vitest";
2import { createPage } from "@tests/client/util/fixtures";
3import {
4 buildPageMap,
5 getAncestorIds,
6 getSortedSiblings,
7 resolveIndent,
8 resolveMoveDown,
9 resolveMoveRelative,
10 resolveMoveToRoot,
11 resolveMoveUp,
12 resolveOutdent,
13} from "@/client/lib/page-tree-model";
14 
15describe("getSortedSiblings", () => {
16 it("filters by parent, excludes the moved page, and ignores archived rows", () => {
17 const pages = [
18 createPage({ id: "a", parent_id: null, position: 2 }),
19 createPage({ id: "b", parent_id: null, position: 1 }),
20 createPage({ id: "c", parent_id: null, position: 3, archived_at: "2026-04-01T00:00:00Z" }),
21 createPage({ id: "child", parent_id: "a", position: 1 }),
22 ];
23 
24 expect(getSortedSiblings(pages, null, "a").map((page) => page.id)).toEqual(["b"]);
25 });
26});
27 
28describe("getAncestorIds", () => {
29 it("returns the parent chain excluding the starting page itself", () => {
30 const pages = [
31 createPage({ id: "root" }),
32 createPage({ id: "parent", parent_id: "root" }),
33 createPage({ id: "leaf", parent_id: "parent" }),
34 ];
35 const byId = buildPageMap(pages);
36 
37 expect([...getAncestorIds(byId, "leaf")]).toEqual(["parent", "root"]);
38 expect([...getAncestorIds(byId, "root")]).toEqual([]);
39 expect([...getAncestorIds(byId, null)]).toEqual([]);
40 });
41});
42 
43describe("page move model", () => {
44 const pages = [
45 createPage({ id: "a1", title: "A1", position: 1 }),
46 createPage({ id: "a2", title: "A2", position: 2 }),
47 createPage({ id: "a3", title: "A3", position: 3 }),
48 createPage({ id: "b1", title: "B1", parent_id: "a1", position: 1 }),
49 createPage({ id: "b2", title: "B2", parent_id: "a1", position: 2 }),
50 createPage({ id: "c1", title: "C1", parent_id: "a2", position: 1 }),
51 ];
52 
53 it("moves a page up within its sibling list", () => {
54 const result = resolveMoveUp(pages, pages[1]);
55 expect(result.ok).toBe(true);
56 if (!result.ok) return;
57 expect(result.proposal.parentId).toBeNull();
58 expect(result.proposal.insertionIndex).toBe(0);
59 });
60 
61 it("moves a page down within its sibling list", () => {
62 const result = resolveMoveDown(pages, pages[0]);
63 expect(result.ok).toBe(true);
64 if (!result.ok) return;
65 expect(result.proposal.parentId).toBeNull();
66 expect(result.proposal.insertionIndex).toBe(1);
67 });
68 
69 it("indents into the previous sibling as the last child", () => {
70 const result = resolveIndent(pages, pages[1]);
71 expect(result.ok).toBe(true);
72 if (!result.ok) return;
73 expect(result.proposal.parentId).toBe("a1");
74 expect(result.proposal.insertionIndex).toBe(2);
75 });
76 
77 it("outdents to after the former parent subtree", () => {
78 const result = resolveOutdent(pages, pages[3]);
79 expect(result.ok).toBe(true);
80 if (!result.ok) return;
81 expect(result.proposal.parentId).toBeNull();
82 expect(result.proposal.insertionIndex).toBe(1);
83 });
84 
85 it("disables move up for the first sibling in a level", () => {
86 const result = resolveMoveUp(pages, pages[0]);
87 expect(result).toEqual({
88 ok: false,
89 reason: "boundary",
90 message: "Already first in this level",
91 });
92 });
93 
94 it("resolves before/inside/after relations against a target page", () => {
95 const before = resolveMoveRelative({ allPages: pages, page: pages[2], targetPage: pages[1], relation: "before" });
96 const inside = resolveMoveRelative({ allPages: pages, page: pages[0], targetPage: pages[1], relation: "inside" });
97 const after = resolveMoveRelative({ allPages: pages, page: pages[0], targetPage: pages[1], relation: "after" });
98 
99 expect(before.ok && before.proposal.previewLabel).toBe("Move before A2");
100 expect(inside.ok && inside.proposal.parentId).toBe("a2");
101 expect(after.ok && after.proposal.previewLabel).toBe("Move after A2");
102 });
103 
104 it("resolves root top and bottom placements", () => {
105 const top = resolveMoveToRoot(pages, pages[3], "top");
106 const bottom = resolveMoveToRoot(pages, pages[3], "bottom");
107 
108 expect(top.ok && top.proposal.insertionIndex).toBe(0);
109 expect(bottom.ok && bottom.proposal.insertionIndex).toBe(3);
110 });
111});