@jant/core 0.3.22 → 0.3.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/dist/app.js +23 -5
  2. package/dist/db/schema.js +72 -47
  3. package/dist/i18n/locales/en.js +1 -1
  4. package/dist/i18n/locales/zh-Hans.js +1 -1
  5. package/dist/i18n/locales/zh-Hant.js +1 -1
  6. package/dist/index.js +5 -6
  7. package/dist/lib/constants.js +1 -4
  8. package/dist/lib/excerpt.js +76 -0
  9. package/dist/lib/feed.js +18 -7
  10. package/dist/lib/navigation.js +4 -5
  11. package/dist/lib/render.js +1 -1
  12. package/dist/lib/schemas.js +80 -38
  13. package/dist/lib/theme-components.js +8 -11
  14. package/dist/lib/time.js +56 -1
  15. package/dist/lib/timeline.js +119 -0
  16. package/dist/lib/view.js +62 -73
  17. package/dist/routes/api/posts.js +29 -35
  18. package/dist/routes/api/search.js +5 -6
  19. package/dist/routes/api/upload.js +13 -13
  20. package/dist/routes/dash/collections.js +22 -40
  21. package/dist/routes/dash/index.js +2 -2
  22. package/dist/routes/dash/navigation.js +25 -24
  23. package/dist/routes/dash/pages.js +42 -57
  24. package/dist/routes/dash/posts.js +27 -35
  25. package/dist/routes/feed/rss.js +2 -4
  26. package/dist/routes/feed/sitemap.js +10 -7
  27. package/dist/routes/pages/archive.js +12 -11
  28. package/dist/routes/pages/collection.js +11 -5
  29. package/dist/routes/pages/home.js +53 -61
  30. package/dist/routes/pages/page.js +60 -29
  31. package/dist/routes/pages/post.js +5 -12
  32. package/dist/routes/pages/search.js +3 -4
  33. package/dist/services/collection.js +52 -64
  34. package/dist/services/index.js +5 -3
  35. package/dist/services/navigation.js +29 -53
  36. package/dist/services/page.js +80 -0
  37. package/dist/services/post.js +68 -69
  38. package/dist/services/search.js +24 -18
  39. package/dist/theme/components/MediaGallery.js +19 -91
  40. package/dist/theme/components/PageForm.js +15 -15
  41. package/dist/theme/components/PostForm.js +136 -129
  42. package/dist/theme/components/PostList.js +13 -8
  43. package/dist/theme/components/ThreadView.js +3 -3
  44. package/dist/theme/components/TypeBadge.js +3 -14
  45. package/dist/theme/components/VisibilityBadge.js +33 -23
  46. package/dist/theme/components/index.js +0 -2
  47. package/dist/theme/index.js +10 -16
  48. package/dist/theme/layouts/index.js +0 -1
  49. package/dist/themes/threads/ThreadsSiteLayout.js +172 -0
  50. package/dist/themes/threads/index.js +81 -0
  51. package/dist/{theme → themes/threads}/pages/ArchivePage.js +31 -47
  52. package/dist/themes/threads/pages/CollectionPage.js +65 -0
  53. package/dist/{theme → themes/threads}/pages/HomePage.js +4 -5
  54. package/dist/{theme → themes/threads}/pages/PostPage.js +10 -8
  55. package/dist/{theme → themes/threads}/pages/SearchPage.js +8 -8
  56. package/dist/{theme → themes/threads}/pages/SinglePage.js +5 -6
  57. package/dist/{theme/components → themes/threads}/timeline/LinkCard.js +20 -11
  58. package/dist/themes/threads/timeline/NoteCard.js +53 -0
  59. package/dist/themes/threads/timeline/QuoteCard.js +59 -0
  60. package/dist/{theme/components → themes/threads}/timeline/ThreadPreview.js +5 -6
  61. package/dist/themes/threads/timeline/TimelineFeed.js +58 -0
  62. package/dist/{theme/components → themes/threads}/timeline/TimelineItem.js +8 -17
  63. package/dist/themes/threads/timeline/TimelineLoadMore.js +23 -0
  64. package/dist/themes/threads/timeline/groupByDate.js +22 -0
  65. package/dist/themes/threads/timeline/timelineMore.js +107 -0
  66. package/dist/types.js +24 -40
  67. package/package.json +2 -1
  68. package/src/__tests__/helpers/app.ts +4 -0
  69. package/src/__tests__/helpers/db.ts +51 -74
  70. package/src/app.tsx +27 -6
  71. package/src/db/migrations/0005_v2_schema_migration.sql +268 -0
  72. package/src/db/migrations/meta/_journal.json +7 -0
  73. package/src/db/schema.ts +63 -46
  74. package/src/i18n/locales/en.po +216 -164
  75. package/src/i18n/locales/en.ts +1 -1
  76. package/src/i18n/locales/zh-Hans.po +216 -164
  77. package/src/i18n/locales/zh-Hans.ts +1 -1
  78. package/src/i18n/locales/zh-Hant.po +216 -164
  79. package/src/i18n/locales/zh-Hant.ts +1 -1
  80. package/src/index.ts +30 -15
  81. package/src/lib/__tests__/excerpt.test.ts +125 -0
  82. package/src/lib/__tests__/schemas.test.ts +166 -105
  83. package/src/lib/__tests__/theme-components.test.ts +4 -25
  84. package/src/lib/__tests__/time.test.ts +62 -0
  85. package/src/{routes/api → lib}/__tests__/timeline.test.ts +108 -66
  86. package/src/lib/__tests__/view.test.ts +217 -67
  87. package/src/lib/constants.ts +1 -4
  88. package/src/lib/excerpt.ts +87 -0
  89. package/src/lib/feed.ts +22 -7
  90. package/src/lib/navigation.ts +6 -7
  91. package/src/lib/render.tsx +1 -1
  92. package/src/lib/schemas.ts +118 -52
  93. package/src/lib/theme-components.ts +10 -13
  94. package/src/lib/time.ts +64 -0
  95. package/src/lib/timeline.ts +170 -0
  96. package/src/lib/view.ts +81 -83
  97. package/src/preset.css +45 -0
  98. package/src/routes/api/__tests__/posts.test.ts +50 -108
  99. package/src/routes/api/__tests__/search.test.ts +2 -3
  100. package/src/routes/api/posts.ts +30 -30
  101. package/src/routes/api/search.ts +4 -4
  102. package/src/routes/api/upload.ts +16 -6
  103. package/src/routes/dash/collections.tsx +18 -40
  104. package/src/routes/dash/index.tsx +2 -2
  105. package/src/routes/dash/navigation.tsx +27 -26
  106. package/src/routes/dash/pages.tsx +45 -60
  107. package/src/routes/dash/posts.tsx +44 -52
  108. package/src/routes/feed/rss.ts +2 -1
  109. package/src/routes/feed/sitemap.ts +14 -4
  110. package/src/routes/pages/archive.tsx +14 -10
  111. package/src/routes/pages/collection.tsx +17 -6
  112. package/src/routes/pages/home.tsx +56 -81
  113. package/src/routes/pages/page.tsx +64 -27
  114. package/src/routes/pages/post.tsx +5 -14
  115. package/src/routes/pages/search.tsx +2 -2
  116. package/src/services/__tests__/collection.test.ts +257 -158
  117. package/src/services/__tests__/media.test.ts +18 -18
  118. package/src/services/__tests__/navigation.test.ts +161 -87
  119. package/src/services/__tests__/post-timeline.test.ts +92 -88
  120. package/src/services/__tests__/post.test.ts +342 -206
  121. package/src/services/__tests__/search.test.ts +19 -25
  122. package/src/services/collection.ts +71 -113
  123. package/src/services/index.ts +9 -8
  124. package/src/services/navigation.ts +38 -71
  125. package/src/services/page.ts +124 -0
  126. package/src/services/post.ts +93 -103
  127. package/src/services/search.ts +38 -27
  128. package/src/styles/components.css +0 -54
  129. package/src/theme/components/MediaGallery.tsx +27 -96
  130. package/src/theme/components/PageForm.tsx +21 -21
  131. package/src/theme/components/PostForm.tsx +122 -118
  132. package/src/theme/components/PostList.tsx +58 -49
  133. package/src/theme/components/ThreadView.tsx +6 -3
  134. package/src/theme/components/TypeBadge.tsx +9 -17
  135. package/src/theme/components/VisibilityBadge.tsx +40 -23
  136. package/src/theme/components/index.ts +0 -13
  137. package/src/theme/index.ts +10 -16
  138. package/src/theme/layouts/index.ts +0 -1
  139. package/src/themes/threads/ThreadsSiteLayout.tsx +194 -0
  140. package/src/themes/threads/index.ts +100 -0
  141. package/src/{theme → themes/threads}/pages/ArchivePage.tsx +52 -55
  142. package/src/themes/threads/pages/CollectionPage.tsx +61 -0
  143. package/src/{theme → themes/threads}/pages/HomePage.tsx +5 -6
  144. package/src/{theme → themes/threads}/pages/PostPage.tsx +11 -8
  145. package/src/{theme → themes/threads}/pages/SearchPage.tsx +9 -13
  146. package/src/themes/threads/pages/SinglePage.tsx +23 -0
  147. package/src/themes/threads/style.css +336 -0
  148. package/src/{theme/components → themes/threads}/timeline/LinkCard.tsx +21 -13
  149. package/src/themes/threads/timeline/NoteCard.tsx +58 -0
  150. package/src/themes/threads/timeline/QuoteCard.tsx +63 -0
  151. package/src/{theme/components → themes/threads}/timeline/ThreadPreview.tsx +6 -6
  152. package/src/themes/threads/timeline/TimelineFeed.tsx +62 -0
  153. package/src/{theme/components → themes/threads}/timeline/TimelineItem.tsx +9 -20
  154. package/src/themes/threads/timeline/TimelineLoadMore.tsx +35 -0
  155. package/src/themes/threads/timeline/groupByDate.ts +30 -0
  156. package/src/themes/threads/timeline/timelineMore.tsx +130 -0
  157. package/src/types.ts +242 -98
  158. package/dist/routes/api/timeline.js +0 -120
  159. package/dist/theme/components/timeline/ArticleCard.js +0 -46
  160. package/dist/theme/components/timeline/ImageCard.js +0 -83
  161. package/dist/theme/components/timeline/NoteCard.js +0 -34
  162. package/dist/theme/components/timeline/QuoteCard.js +0 -48
  163. package/dist/theme/components/timeline/TimelineFeed.js +0 -46
  164. package/dist/theme/components/timeline/index.js +0 -8
  165. package/dist/theme/layouts/SiteLayout.js +0 -131
  166. package/dist/theme/pages/CollectionPage.js +0 -63
  167. package/dist/theme/pages/index.js +0 -11
  168. package/src/routes/api/timeline.tsx +0 -159
  169. package/src/theme/components/timeline/ArticleCard.tsx +0 -45
  170. package/src/theme/components/timeline/ImageCard.tsx +0 -70
  171. package/src/theme/components/timeline/NoteCard.tsx +0 -34
  172. package/src/theme/components/timeline/QuoteCard.tsx +0 -48
  173. package/src/theme/components/timeline/TimelineFeed.tsx +0 -56
  174. package/src/theme/components/timeline/index.ts +0 -8
  175. package/src/theme/layouts/SiteLayout.tsx +0 -132
  176. package/src/theme/pages/CollectionPage.tsx +0 -60
  177. package/src/theme/pages/SinglePage.tsx +0 -24
  178. package/src/theme/pages/index.ts +0 -13
@@ -166,8 +166,8 @@ describe("MediaService", () => {
166
166
  describe("getByPostId", () => {
167
167
  it("returns media ordered by position", async () => {
168
168
  const post = await postService.create({
169
- type: "note",
170
- content: "test",
169
+ format: "note",
170
+ body: "test",
171
171
  });
172
172
 
173
173
  const m1 = await mediaService.create({
@@ -191,8 +191,8 @@ describe("MediaService", () => {
191
191
 
192
192
  it("returns empty array for post with no media", async () => {
193
193
  const post = await postService.create({
194
- type: "note",
195
- content: "test",
194
+ format: "note",
195
+ body: "test",
196
196
  });
197
197
 
198
198
  const results = await mediaService.getByPostId(post.id);
@@ -203,12 +203,12 @@ describe("MediaService", () => {
203
203
  describe("getByPostIds", () => {
204
204
  it("returns Map grouped by postId", async () => {
205
205
  const post1 = await postService.create({
206
- type: "note",
207
- content: "post 1",
206
+ format: "note",
207
+ body: "post 1",
208
208
  });
209
209
  const post2 = await postService.create({
210
- type: "note",
211
- content: "post 2",
210
+ format: "note",
211
+ body: "post 2",
212
212
  });
213
213
 
214
214
  const m1 = await mediaService.create({
@@ -240,8 +240,8 @@ describe("MediaService", () => {
240
240
 
241
241
  it("returns ordered by position within each post", async () => {
242
242
  const post = await postService.create({
243
- type: "note",
244
- content: "test",
243
+ format: "note",
244
+ body: "test",
245
245
  });
246
246
 
247
247
  const m1 = await mediaService.create({
@@ -309,8 +309,8 @@ describe("MediaService", () => {
309
309
  describe("attachToPost", () => {
310
310
  it("sets postId and position for each media", async () => {
311
311
  const post = await postService.create({
312
- type: "note",
313
- content: "test",
312
+ format: "note",
313
+ body: "test",
314
314
  });
315
315
 
316
316
  const m1 = await mediaService.create({
@@ -334,8 +334,8 @@ describe("MediaService", () => {
334
334
 
335
335
  it("replaces existing attachments", async () => {
336
336
  const post = await postService.create({
337
- type: "note",
338
- content: "test",
337
+ format: "note",
338
+ body: "test",
339
339
  });
340
340
 
341
341
  const m1 = await mediaService.create({
@@ -367,8 +367,8 @@ describe("MediaService", () => {
367
367
 
368
368
  it("handles empty array by clearing all attachments", async () => {
369
369
  const post = await postService.create({
370
- type: "note",
371
- content: "test",
370
+ format: "note",
371
+ body: "test",
372
372
  });
373
373
 
374
374
  const m1 = await mediaService.create({
@@ -387,8 +387,8 @@ describe("MediaService", () => {
387
387
  describe("detachFromPost", () => {
388
388
  it("clears postId and resets position", async () => {
389
389
  const post = await postService.create({
390
- type: "note",
391
- content: "test",
390
+ format: "note",
391
+ body: "test",
392
392
  });
393
393
 
394
394
  const m1 = await mediaService.create({
@@ -1,34 +1,61 @@
1
1
  import { describe, it, expect, beforeEach } from "vitest";
2
2
  import { createTestDatabase } from "../../__tests__/helpers/db.js";
3
- import { createNavigationLinkService } from "../navigation.js";
3
+ import { createNavItemService } from "../navigation.js";
4
+ import { createPageService } from "../page.js";
4
5
  import type { Database } from "../../db/index.js";
5
6
 
6
- describe("NavigationLinkService", () => {
7
+ describe("NavItemService", () => {
7
8
  let db: Database;
8
- let navigationService: ReturnType<typeof createNavigationLinkService>;
9
+ let navItemService: ReturnType<typeof createNavItemService>;
10
+ let pageService: ReturnType<typeof createPageService>;
9
11
 
10
12
  beforeEach(() => {
11
13
  const testDb = createTestDatabase();
12
14
  db = testDb.db as unknown as Database;
13
- navigationService = createNavigationLinkService(db);
15
+ navItemService = createNavItemService(db);
16
+ pageService = createPageService(db);
14
17
  });
15
18
 
16
19
  describe("create", () => {
17
- it("creates a navigation link with auto-assigned position", async () => {
18
- const link = await navigationService.create({
20
+ it("creates a nav item with auto-assigned position", async () => {
21
+ const item = await navItemService.create({
22
+ type: "link",
19
23
  label: "Home",
20
24
  url: "/",
21
25
  });
22
26
 
23
- expect(link.label).toBe("Home");
24
- expect(link.url).toBe("/");
25
- expect(link.position).toBe(0);
26
- expect(link.id).toBe(1);
27
+ expect(item.type).toBe("link");
28
+ expect(item.label).toBe("Home");
29
+ expect(item.url).toBe("/");
30
+ expect(item.pageId).toBeNull();
31
+ expect(item.position).toBe(0);
32
+ expect(item.id).toBe(1);
27
33
  });
28
34
 
29
- it("auto-increments position for subsequent links", async () => {
30
- await navigationService.create({ label: "Home", url: "/" });
31
- const second = await navigationService.create({
35
+ it("creates a page-type nav item with pageId", async () => {
36
+ const page = await pageService.create({ slug: "about", title: "About" });
37
+
38
+ const item = await navItemService.create({
39
+ type: "page",
40
+ label: "About",
41
+ url: "/about",
42
+ pageId: page.id,
43
+ });
44
+
45
+ expect(item.type).toBe("page");
46
+ expect(item.label).toBe("About");
47
+ expect(item.url).toBe("/about");
48
+ expect(item.pageId).toBe(page.id);
49
+ });
50
+
51
+ it("auto-increments position for subsequent items", async () => {
52
+ await navItemService.create({
53
+ type: "link",
54
+ label: "Home",
55
+ url: "/",
56
+ });
57
+ const second = await navItemService.create({
58
+ type: "link",
32
59
  label: "Archive",
33
60
  url: "/archive",
34
61
  });
@@ -37,87 +64,130 @@ describe("NavigationLinkService", () => {
37
64
  });
38
65
 
39
66
  it("uses provided position when specified", async () => {
40
- const link = await navigationService.create({
67
+ const item = await navItemService.create({
68
+ type: "link",
41
69
  label: "Home",
42
70
  url: "/",
43
71
  position: 5,
44
72
  });
45
73
 
46
- expect(link.position).toBe(5);
74
+ expect(item.position).toBe(5);
75
+ });
76
+
77
+ it("sets createdAt and updatedAt timestamps", async () => {
78
+ const item = await navItemService.create({
79
+ type: "link",
80
+ label: "Home",
81
+ url: "/",
82
+ });
83
+
84
+ expect(item.createdAt).toBeGreaterThan(0);
85
+ expect(item.updatedAt).toBeGreaterThan(0);
86
+ expect(item.createdAt).toBe(item.updatedAt);
47
87
  });
48
88
  });
49
89
 
50
90
  describe("getById", () => {
51
- it("returns a link by ID", async () => {
52
- const created = await navigationService.create({
91
+ it("returns a nav item by ID", async () => {
92
+ const created = await navItemService.create({
93
+ type: "link",
53
94
  label: "Home",
54
95
  url: "/",
55
96
  });
56
97
 
57
- const found = await navigationService.getById(created.id);
98
+ const found = await navItemService.getById(created.id);
58
99
  expect(found).not.toBeNull();
59
100
  expect(found?.label).toBe("Home");
101
+ expect(found?.type).toBe("link");
60
102
  });
61
103
 
62
104
  it("returns null for non-existent ID", async () => {
63
- const found = await navigationService.getById(9999);
105
+ const found = await navItemService.getById(9999);
64
106
  expect(found).toBeNull();
65
107
  });
66
108
  });
67
109
 
68
110
  describe("list", () => {
69
- it("returns empty array when no links exist", async () => {
70
- const links = await navigationService.list();
71
- expect(links).toEqual([]);
111
+ it("returns empty array when no items exist", async () => {
112
+ const items = await navItemService.list();
113
+ expect(items).toEqual([]);
72
114
  });
73
115
 
74
- it("returns links ordered by position", async () => {
75
- await navigationService.create({
116
+ it("returns items ordered by position", async () => {
117
+ await navItemService.create({
118
+ type: "link",
76
119
  label: "C",
77
120
  url: "/c",
78
121
  position: 2,
79
122
  });
80
- await navigationService.create({
123
+ await navItemService.create({
124
+ type: "link",
81
125
  label: "A",
82
126
  url: "/a",
83
127
  position: 0,
84
128
  });
85
- await navigationService.create({
129
+ await navItemService.create({
130
+ type: "page",
86
131
  label: "B",
87
132
  url: "/b",
88
133
  position: 1,
89
134
  });
90
135
 
91
- const links = await navigationService.list();
92
- expect(links).toHaveLength(3);
93
- expect(links[0]?.label).toBe("A");
94
- expect(links[1]?.label).toBe("B");
95
- expect(links[2]?.label).toBe("C");
136
+ const items = await navItemService.list();
137
+ expect(items).toHaveLength(3);
138
+ expect(items[0]?.label).toBe("A");
139
+ expect(items[1]?.label).toBe("B");
140
+ expect(items[2]?.label).toBe("C");
141
+ });
142
+
143
+ it("returns items with correct types", async () => {
144
+ const page = await pageService.create({ slug: "about", title: "About" });
145
+
146
+ await navItemService.create({
147
+ type: "link",
148
+ label: "External",
149
+ url: "https://example.com",
150
+ });
151
+ await navItemService.create({
152
+ type: "page",
153
+ label: "About",
154
+ url: "/about",
155
+ pageId: page.id,
156
+ });
157
+
158
+ const items = await navItemService.list();
159
+ expect(items).toHaveLength(2);
160
+ expect(items[0]?.type).toBe("link");
161
+ expect(items[1]?.type).toBe("page");
162
+ expect(items[1]?.pageId).toBe(page.id);
96
163
  });
97
164
  });
98
165
 
99
166
  describe("update", () => {
100
- it("updates a link's label", async () => {
101
- const created = await navigationService.create({
167
+ it("updates a nav item's label", async () => {
168
+ const created = await navItemService.create({
169
+ type: "link",
102
170
  label: "Home",
103
171
  url: "/",
104
172
  });
105
173
 
106
- const updated = await navigationService.update(created.id, {
174
+ const updated = await navItemService.update(created.id, {
107
175
  label: "Main Page",
108
176
  });
109
177
 
110
178
  expect(updated?.label).toBe("Main Page");
111
179
  expect(updated?.url).toBe("/");
180
+ expect(updated?.type).toBe("link");
112
181
  });
113
182
 
114
- it("updates a link's url", async () => {
115
- const created = await navigationService.create({
183
+ it("updates a nav item's url", async () => {
184
+ const created = await navItemService.create({
185
+ type: "link",
116
186
  label: "Blog",
117
187
  url: "/blog",
118
188
  });
119
189
 
120
- const updated = await navigationService.update(created.id, {
190
+ const updated = await navItemService.update(created.id, {
121
191
  url: "/posts",
122
192
  });
123
193
 
@@ -125,89 +195,93 @@ describe("NavigationLinkService", () => {
125
195
  expect(updated?.label).toBe("Blog");
126
196
  });
127
197
 
198
+ it("updates a nav item's type", async () => {
199
+ const page = await pageService.create({ slug: "about", title: "About" });
200
+
201
+ const created = await navItemService.create({
202
+ type: "link",
203
+ label: "About",
204
+ url: "/about",
205
+ });
206
+
207
+ const updated = await navItemService.update(created.id, {
208
+ type: "page",
209
+ pageId: page.id,
210
+ });
211
+
212
+ expect(updated?.type).toBe("page");
213
+ expect(updated?.pageId).toBe(page.id);
214
+ });
215
+
216
+ it("updates updatedAt timestamp", async () => {
217
+ const created = await navItemService.create({
218
+ type: "link",
219
+ label: "Home",
220
+ url: "/",
221
+ });
222
+
223
+ const updated = await navItemService.update(created.id, {
224
+ label: "Updated",
225
+ });
226
+
227
+ expect(updated?.updatedAt).toBeGreaterThanOrEqual(created.updatedAt);
228
+ });
229
+
128
230
  it("returns null for non-existent ID", async () => {
129
- const result = await navigationService.update(9999, { label: "Nope" });
231
+ const result = await navItemService.update(9999, { label: "Nope" });
130
232
  expect(result).toBeNull();
131
233
  });
132
234
  });
133
235
 
134
236
  describe("delete", () => {
135
- it("deletes a link by ID", async () => {
136
- const link = await navigationService.create({
237
+ it("deletes a nav item by ID", async () => {
238
+ const item = await navItemService.create({
239
+ type: "link",
137
240
  label: "Home",
138
241
  url: "/",
139
242
  });
140
- const result = await navigationService.delete(link.id);
243
+ const result = await navItemService.delete(item.id);
141
244
 
142
245
  expect(result).toBe(true);
143
246
 
144
- const found = await navigationService.getById(link.id);
247
+ const found = await navItemService.getById(item.id);
145
248
  expect(found).toBeNull();
146
249
  });
147
250
 
148
251
  it("returns false for non-existent ID", async () => {
149
- const result = await navigationService.delete(9999);
252
+ const result = await navItemService.delete(9999);
150
253
  expect(result).toBe(false);
151
254
  });
152
255
  });
153
256
 
154
257
  describe("reorder", () => {
155
258
  it("updates positions to match array order", async () => {
156
- const a = await navigationService.create({
259
+ const a = await navItemService.create({
260
+ type: "link",
157
261
  label: "A",
158
262
  url: "/a",
159
263
  });
160
- const b = await navigationService.create({
264
+ const b = await navItemService.create({
265
+ type: "link",
161
266
  label: "B",
162
267
  url: "/b",
163
268
  });
164
- const c = await navigationService.create({
269
+ const c = await navItemService.create({
270
+ type: "link",
165
271
  label: "C",
166
272
  url: "/c",
167
273
  });
168
274
 
169
275
  // Reverse the order
170
- await navigationService.reorder([c.id, b.id, a.id]);
171
-
172
- const links = await navigationService.list();
173
- expect(links[0]?.label).toBe("C");
174
- expect(links[0]?.position).toBe(0);
175
- expect(links[1]?.label).toBe("B");
176
- expect(links[1]?.position).toBe(1);
177
- expect(links[2]?.label).toBe("A");
178
- expect(links[2]?.position).toBe(2);
179
- });
180
- });
181
-
182
- describe("ensureDefaults", () => {
183
- it("creates default links when table is empty", async () => {
184
- const links = await navigationService.ensureDefaults();
185
-
186
- expect(links).toHaveLength(3);
187
- expect(links[0]?.label).toBe("Home");
188
- expect(links[0]?.url).toBe("/");
189
- expect(links[1]?.label).toBe("Archive");
190
- expect(links[1]?.url).toBe("/archive");
191
- expect(links[2]?.label).toBe("RSS");
192
- expect(links[2]?.url).toBe("/feed");
193
- });
194
-
195
- it("returns existing links without creating new ones", async () => {
196
- await navigationService.create({ label: "Custom", url: "/custom" });
197
-
198
- const links = await navigationService.ensureDefaults();
199
-
200
- expect(links).toHaveLength(1);
201
- expect(links[0]?.label).toBe("Custom");
202
- });
203
-
204
- it("is idempotent - calling twice returns same result", async () => {
205
- const first = await navigationService.ensureDefaults();
206
- const second = await navigationService.ensureDefaults();
207
-
208
- expect(first).toHaveLength(3);
209
- expect(second).toHaveLength(3);
210
- expect(first[0]?.id).toBe(second[0]?.id);
276
+ await navItemService.reorder([c.id, b.id, a.id]);
277
+
278
+ const items = await navItemService.list();
279
+ expect(items[0]?.label).toBe("C");
280
+ expect(items[0]?.position).toBe(0);
281
+ expect(items[1]?.label).toBe("B");
282
+ expect(items[1]?.position).toBe(1);
283
+ expect(items[2]?.label).toBe("A");
284
+ expect(items[2]?.position).toBe(2);
211
285
  });
212
286
  });
213
287
  });