@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.
- package/dist/app.js +23 -5
- package/dist/db/schema.js +72 -47
- package/dist/i18n/locales/en.js +1 -1
- package/dist/i18n/locales/zh-Hans.js +1 -1
- package/dist/i18n/locales/zh-Hant.js +1 -1
- package/dist/index.js +5 -6
- package/dist/lib/constants.js +1 -4
- package/dist/lib/excerpt.js +76 -0
- package/dist/lib/feed.js +18 -7
- package/dist/lib/navigation.js +4 -5
- package/dist/lib/render.js +1 -1
- package/dist/lib/schemas.js +80 -38
- package/dist/lib/theme-components.js +8 -11
- package/dist/lib/time.js +56 -1
- package/dist/lib/timeline.js +119 -0
- package/dist/lib/view.js +62 -73
- package/dist/routes/api/posts.js +29 -35
- package/dist/routes/api/search.js +5 -6
- package/dist/routes/api/upload.js +13 -13
- package/dist/routes/dash/collections.js +22 -40
- package/dist/routes/dash/index.js +2 -2
- package/dist/routes/dash/navigation.js +25 -24
- package/dist/routes/dash/pages.js +42 -57
- package/dist/routes/dash/posts.js +27 -35
- package/dist/routes/feed/rss.js +2 -4
- package/dist/routes/feed/sitemap.js +10 -7
- package/dist/routes/pages/archive.js +12 -11
- package/dist/routes/pages/collection.js +11 -5
- package/dist/routes/pages/home.js +53 -61
- package/dist/routes/pages/page.js +60 -29
- package/dist/routes/pages/post.js +5 -12
- package/dist/routes/pages/search.js +3 -4
- package/dist/services/collection.js +52 -64
- package/dist/services/index.js +5 -3
- package/dist/services/navigation.js +29 -53
- package/dist/services/page.js +80 -0
- package/dist/services/post.js +68 -69
- package/dist/services/search.js +24 -18
- package/dist/theme/components/MediaGallery.js +19 -91
- package/dist/theme/components/PageForm.js +15 -15
- package/dist/theme/components/PostForm.js +136 -129
- package/dist/theme/components/PostList.js +13 -8
- package/dist/theme/components/ThreadView.js +3 -3
- package/dist/theme/components/TypeBadge.js +3 -14
- package/dist/theme/components/VisibilityBadge.js +33 -23
- package/dist/theme/components/index.js +0 -2
- package/dist/theme/index.js +10 -16
- package/dist/theme/layouts/index.js +0 -1
- package/dist/themes/threads/ThreadsSiteLayout.js +172 -0
- package/dist/themes/threads/index.js +81 -0
- package/dist/{theme → themes/threads}/pages/ArchivePage.js +31 -47
- package/dist/themes/threads/pages/CollectionPage.js +65 -0
- package/dist/{theme → themes/threads}/pages/HomePage.js +4 -5
- package/dist/{theme → themes/threads}/pages/PostPage.js +10 -8
- package/dist/{theme → themes/threads}/pages/SearchPage.js +8 -8
- package/dist/{theme → themes/threads}/pages/SinglePage.js +5 -6
- package/dist/{theme/components → themes/threads}/timeline/LinkCard.js +20 -11
- package/dist/themes/threads/timeline/NoteCard.js +53 -0
- package/dist/themes/threads/timeline/QuoteCard.js +59 -0
- package/dist/{theme/components → themes/threads}/timeline/ThreadPreview.js +5 -6
- package/dist/themes/threads/timeline/TimelineFeed.js +58 -0
- package/dist/{theme/components → themes/threads}/timeline/TimelineItem.js +8 -17
- package/dist/themes/threads/timeline/TimelineLoadMore.js +23 -0
- package/dist/themes/threads/timeline/groupByDate.js +22 -0
- package/dist/themes/threads/timeline/timelineMore.js +107 -0
- package/dist/types.js +24 -40
- package/package.json +2 -1
- package/src/__tests__/helpers/app.ts +4 -0
- package/src/__tests__/helpers/db.ts +51 -74
- package/src/app.tsx +27 -6
- package/src/db/migrations/0005_v2_schema_migration.sql +268 -0
- package/src/db/migrations/meta/_journal.json +7 -0
- package/src/db/schema.ts +63 -46
- package/src/i18n/locales/en.po +216 -164
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +216 -164
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +216 -164
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/index.ts +30 -15
- package/src/lib/__tests__/excerpt.test.ts +125 -0
- package/src/lib/__tests__/schemas.test.ts +166 -105
- package/src/lib/__tests__/theme-components.test.ts +4 -25
- package/src/lib/__tests__/time.test.ts +62 -0
- package/src/{routes/api → lib}/__tests__/timeline.test.ts +108 -66
- package/src/lib/__tests__/view.test.ts +217 -67
- package/src/lib/constants.ts +1 -4
- package/src/lib/excerpt.ts +87 -0
- package/src/lib/feed.ts +22 -7
- package/src/lib/navigation.ts +6 -7
- package/src/lib/render.tsx +1 -1
- package/src/lib/schemas.ts +118 -52
- package/src/lib/theme-components.ts +10 -13
- package/src/lib/time.ts +64 -0
- package/src/lib/timeline.ts +170 -0
- package/src/lib/view.ts +81 -83
- package/src/preset.css +45 -0
- package/src/routes/api/__tests__/posts.test.ts +50 -108
- package/src/routes/api/__tests__/search.test.ts +2 -3
- package/src/routes/api/posts.ts +30 -30
- package/src/routes/api/search.ts +4 -4
- package/src/routes/api/upload.ts +16 -6
- package/src/routes/dash/collections.tsx +18 -40
- package/src/routes/dash/index.tsx +2 -2
- package/src/routes/dash/navigation.tsx +27 -26
- package/src/routes/dash/pages.tsx +45 -60
- package/src/routes/dash/posts.tsx +44 -52
- package/src/routes/feed/rss.ts +2 -1
- package/src/routes/feed/sitemap.ts +14 -4
- package/src/routes/pages/archive.tsx +14 -10
- package/src/routes/pages/collection.tsx +17 -6
- package/src/routes/pages/home.tsx +56 -81
- package/src/routes/pages/page.tsx +64 -27
- package/src/routes/pages/post.tsx +5 -14
- package/src/routes/pages/search.tsx +2 -2
- package/src/services/__tests__/collection.test.ts +257 -158
- package/src/services/__tests__/media.test.ts +18 -18
- package/src/services/__tests__/navigation.test.ts +161 -87
- package/src/services/__tests__/post-timeline.test.ts +92 -88
- package/src/services/__tests__/post.test.ts +342 -206
- package/src/services/__tests__/search.test.ts +19 -25
- package/src/services/collection.ts +71 -113
- package/src/services/index.ts +9 -8
- package/src/services/navigation.ts +38 -71
- package/src/services/page.ts +124 -0
- package/src/services/post.ts +93 -103
- package/src/services/search.ts +38 -27
- package/src/styles/components.css +0 -54
- package/src/theme/components/MediaGallery.tsx +27 -96
- package/src/theme/components/PageForm.tsx +21 -21
- package/src/theme/components/PostForm.tsx +122 -118
- package/src/theme/components/PostList.tsx +58 -49
- package/src/theme/components/ThreadView.tsx +6 -3
- package/src/theme/components/TypeBadge.tsx +9 -17
- package/src/theme/components/VisibilityBadge.tsx +40 -23
- package/src/theme/components/index.ts +0 -13
- package/src/theme/index.ts +10 -16
- package/src/theme/layouts/index.ts +0 -1
- package/src/themes/threads/ThreadsSiteLayout.tsx +194 -0
- package/src/themes/threads/index.ts +100 -0
- package/src/{theme → themes/threads}/pages/ArchivePage.tsx +52 -55
- package/src/themes/threads/pages/CollectionPage.tsx +61 -0
- package/src/{theme → themes/threads}/pages/HomePage.tsx +5 -6
- package/src/{theme → themes/threads}/pages/PostPage.tsx +11 -8
- package/src/{theme → themes/threads}/pages/SearchPage.tsx +9 -13
- package/src/themes/threads/pages/SinglePage.tsx +23 -0
- package/src/themes/threads/style.css +336 -0
- package/src/{theme/components → themes/threads}/timeline/LinkCard.tsx +21 -13
- package/src/themes/threads/timeline/NoteCard.tsx +58 -0
- package/src/themes/threads/timeline/QuoteCard.tsx +63 -0
- package/src/{theme/components → themes/threads}/timeline/ThreadPreview.tsx +6 -6
- package/src/themes/threads/timeline/TimelineFeed.tsx +62 -0
- package/src/{theme/components → themes/threads}/timeline/TimelineItem.tsx +9 -20
- package/src/themes/threads/timeline/TimelineLoadMore.tsx +35 -0
- package/src/themes/threads/timeline/groupByDate.ts +30 -0
- package/src/themes/threads/timeline/timelineMore.tsx +130 -0
- package/src/types.ts +242 -98
- package/dist/routes/api/timeline.js +0 -120
- package/dist/theme/components/timeline/ArticleCard.js +0 -46
- package/dist/theme/components/timeline/ImageCard.js +0 -83
- package/dist/theme/components/timeline/NoteCard.js +0 -34
- package/dist/theme/components/timeline/QuoteCard.js +0 -48
- package/dist/theme/components/timeline/TimelineFeed.js +0 -46
- package/dist/theme/components/timeline/index.js +0 -8
- package/dist/theme/layouts/SiteLayout.js +0 -131
- package/dist/theme/pages/CollectionPage.js +0 -63
- package/dist/theme/pages/index.js +0 -11
- package/src/routes/api/timeline.tsx +0 -159
- package/src/theme/components/timeline/ArticleCard.tsx +0 -45
- package/src/theme/components/timeline/ImageCard.tsx +0 -70
- package/src/theme/components/timeline/NoteCard.tsx +0 -34
- package/src/theme/components/timeline/QuoteCard.tsx +0 -48
- package/src/theme/components/timeline/TimelineFeed.tsx +0 -56
- package/src/theme/components/timeline/index.ts +0 -8
- package/src/theme/layouts/SiteLayout.tsx +0 -132
- package/src/theme/pages/CollectionPage.tsx +0 -60
- package/src/theme/pages/SinglePage.tsx +0 -24
- 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
|
-
|
|
170
|
-
|
|
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
|
-
|
|
195
|
-
|
|
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
|
-
|
|
207
|
-
|
|
206
|
+
format: "note",
|
|
207
|
+
body: "post 1",
|
|
208
208
|
});
|
|
209
209
|
const post2 = await postService.create({
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
244
|
-
|
|
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
|
-
|
|
313
|
-
|
|
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
|
-
|
|
338
|
-
|
|
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
|
-
|
|
371
|
-
|
|
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
|
-
|
|
391
|
-
|
|
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 {
|
|
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("
|
|
7
|
+
describe("NavItemService", () => {
|
|
7
8
|
let db: Database;
|
|
8
|
-
let
|
|
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
|
-
|
|
15
|
+
navItemService = createNavItemService(db);
|
|
16
|
+
pageService = createPageService(db);
|
|
14
17
|
});
|
|
15
18
|
|
|
16
19
|
describe("create", () => {
|
|
17
|
-
it("creates a
|
|
18
|
-
const
|
|
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(
|
|
24
|
-
expect(
|
|
25
|
-
expect(
|
|
26
|
-
expect(
|
|
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("
|
|
30
|
-
await
|
|
31
|
-
|
|
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
|
|
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(
|
|
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
|
|
52
|
-
const created = await
|
|
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
|
|
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
|
|
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
|
|
70
|
-
const
|
|
71
|
-
expect(
|
|
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
|
|
75
|
-
await
|
|
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
|
|
123
|
+
await navItemService.create({
|
|
124
|
+
type: "link",
|
|
81
125
|
label: "A",
|
|
82
126
|
url: "/a",
|
|
83
127
|
position: 0,
|
|
84
128
|
});
|
|
85
|
-
await
|
|
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
|
|
92
|
-
expect(
|
|
93
|
-
expect(
|
|
94
|
-
expect(
|
|
95
|
-
expect(
|
|
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
|
|
101
|
-
const created = await
|
|
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
|
|
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
|
|
115
|
-
const created = await
|
|
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
|
|
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
|
|
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
|
|
136
|
-
const
|
|
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
|
|
243
|
+
const result = await navItemService.delete(item.id);
|
|
141
244
|
|
|
142
245
|
expect(result).toBe(true);
|
|
143
246
|
|
|
144
|
-
const found = await
|
|
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
|
|
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
|
|
259
|
+
const a = await navItemService.create({
|
|
260
|
+
type: "link",
|
|
157
261
|
label: "A",
|
|
158
262
|
url: "/a",
|
|
159
263
|
});
|
|
160
|
-
const b = await
|
|
264
|
+
const b = await navItemService.create({
|
|
265
|
+
type: "link",
|
|
161
266
|
label: "B",
|
|
162
267
|
url: "/b",
|
|
163
268
|
});
|
|
164
|
-
const c = await
|
|
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
|
|
171
|
-
|
|
172
|
-
const
|
|
173
|
-
expect(
|
|
174
|
-
expect(
|
|
175
|
-
expect(
|
|
176
|
-
expect(
|
|
177
|
-
expect(
|
|
178
|
-
expect(
|
|
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
|
});
|