@jant/core 0.3.23 → 0.3.25
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 +50 -26
- 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 -11
- package/dist/lib/constants.js +2 -4
- package/dist/lib/excerpt.js +76 -0
- package/dist/lib/feed.js +18 -7
- package/dist/lib/nav-reorder.js +1 -1
- package/dist/lib/navigation.js +30 -6
- package/dist/lib/pagination.js +44 -0
- package/dist/lib/render.js +7 -11
- package/dist/lib/schemas.js +80 -38
- package/dist/lib/theme.js +4 -4
- package/dist/lib/time.js +56 -1
- package/dist/lib/timeline.js +95 -0
- package/dist/lib/view.js +61 -72
- package/dist/routes/api/collections.js +124 -0
- package/dist/routes/api/nav-items.js +104 -0
- package/dist/routes/api/pages.js +91 -0
- package/dist/routes/api/posts.js +27 -33
- package/dist/routes/api/search.js +4 -5
- package/dist/routes/api/settings.js +68 -0
- package/dist/routes/api/upload.js +13 -13
- package/dist/routes/compose.js +48 -0
- package/dist/routes/dash/collections.js +24 -42
- package/dist/routes/dash/index.js +3 -3
- package/dist/routes/dash/media.js +2 -2
- package/dist/routes/dash/pages.js +440 -106
- package/dist/routes/dash/posts.js +27 -37
- package/dist/routes/dash/redirects.js +2 -2
- package/dist/routes/dash/settings.js +79 -5
- package/dist/routes/feed/rss.js +4 -6
- package/dist/routes/feed/sitemap.js +11 -8
- package/dist/routes/pages/archive.js +13 -15
- package/dist/routes/pages/collection.js +12 -9
- package/dist/routes/pages/collections.js +28 -0
- package/dist/routes/pages/featured.js +32 -0
- package/dist/routes/pages/home.js +19 -68
- package/dist/routes/pages/page.js +57 -29
- package/dist/routes/pages/post.js +7 -17
- package/dist/routes/pages/search.js +5 -9
- 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 +84 -0
- package/dist/services/post.js +102 -69
- package/dist/services/search.js +24 -18
- package/dist/types.js +24 -40
- package/dist/ui/compose/ComposeDialog.js +452 -0
- package/dist/ui/compose/ComposePrompt.js +55 -0
- package/dist/{theme/components/TypeBadge.js → ui/dash/FormatBadge.js} +3 -15
- package/dist/{theme/components → ui/dash}/PageForm.js +15 -15
- package/dist/{theme/components → ui/dash}/PostForm.js +117 -137
- package/dist/{theme/components → ui/dash}/PostList.js +18 -13
- package/dist/ui/dash/StatusBadge.js +46 -0
- package/dist/{theme/components → ui/dash}/index.js +3 -6
- package/dist/ui/feed/LinkCard.js +72 -0
- package/dist/ui/feed/NoteCard.js +58 -0
- package/dist/{themes/minimal/timeline → ui/feed}/QuoteCard.js +29 -14
- package/dist/{themes/minimal/timeline → ui/feed}/ThreadPreview.js +20 -18
- package/dist/ui/feed/TimelineFeed.js +41 -0
- package/dist/ui/feed/TimelineItem.js +27 -0
- package/dist/{theme → ui}/layouts/BaseLayout.js +10 -0
- package/dist/{theme → ui}/layouts/DashLayout.js +0 -8
- package/dist/ui/layouts/SiteLayout.js +141 -0
- package/dist/{themes/minimal → ui}/pages/ArchivePage.js +37 -50
- package/dist/ui/pages/CollectionPage.js +70 -0
- package/dist/ui/pages/CollectionsPage.js +76 -0
- package/dist/ui/pages/FeaturedPage.js +24 -0
- package/dist/ui/pages/HomePage.js +24 -0
- package/dist/{themes/minimal → ui}/pages/PostPage.js +20 -12
- package/dist/{themes/minimal → ui}/pages/SearchPage.js +19 -18
- package/dist/{themes/minimal → ui}/pages/SinglePage.js +5 -4
- package/dist/ui/shared/MediaGallery.js +35 -0
- package/dist/{theme/components → ui/shared}/Pagination.js +41 -2
- package/dist/{theme/components → ui/shared}/ThreadView.js +3 -3
- package/dist/ui/shared/index.js +5 -0
- package/package.json +2 -9
- package/src/__tests__/helpers/app.ts +4 -0
- package/src/__tests__/helpers/db.ts +53 -73
- package/src/app.tsx +56 -28
- package/src/db/migrations/0005_v2_schema_migration.sql +268 -0
- package/src/db/migrations/0006_rename_slug_to_path.sql +5 -0
- package/src/db/migrations/meta/_journal.json +14 -0
- package/src/db/schema.ts +63 -46
- package/src/i18n/locales/en.po +443 -240
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +443 -240
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +443 -240
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/index.ts +29 -42
- package/src/lib/__tests__/excerpt.test.ts +125 -0
- package/src/lib/__tests__/schemas.test.ts +201 -99
- package/src/lib/__tests__/time.test.ts +62 -0
- package/src/{routes/api → lib}/__tests__/timeline.test.ts +81 -75
- package/src/lib/__tests__/view.test.ts +204 -50
- package/src/lib/constants.ts +2 -4
- package/src/lib/excerpt.ts +87 -0
- package/src/lib/feed.ts +22 -7
- package/src/lib/nav-reorder.ts +1 -1
- package/src/lib/navigation.ts +45 -8
- package/src/lib/pagination.ts +50 -0
- package/src/lib/render.tsx +7 -14
- package/src/lib/schemas.ts +119 -51
- package/src/lib/theme.ts +5 -5
- package/src/lib/time.ts +64 -0
- package/src/lib/timeline.ts +141 -0
- package/src/lib/view.ts +80 -82
- package/src/preset.css +46 -0
- package/src/routes/__tests__/compose.test.ts +199 -0
- package/src/routes/api/__tests__/collections.test.ts +249 -0
- package/src/routes/api/__tests__/nav-items.test.ts +222 -0
- package/src/routes/api/__tests__/pages.test.ts +218 -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/__tests__/settings.test.ts +132 -0
- package/src/routes/api/collections.ts +143 -0
- package/src/routes/api/nav-items.ts +115 -0
- package/src/routes/api/pages.ts +101 -0
- package/src/routes/api/posts.ts +28 -28
- package/src/routes/api/search.ts +3 -3
- package/src/routes/api/settings.ts +91 -0
- package/src/routes/api/upload.ts +16 -6
- package/src/routes/compose.ts +63 -0
- package/src/routes/dash/__tests__/pages.test.ts +225 -0
- package/src/routes/dash/collections.tsx +20 -42
- package/src/routes/dash/index.tsx +3 -3
- package/src/routes/dash/media.tsx +2 -2
- package/src/routes/dash/pages.tsx +480 -122
- package/src/routes/dash/posts.tsx +42 -54
- package/src/routes/dash/redirects.tsx +2 -2
- package/src/routes/dash/settings.tsx +83 -5
- package/src/routes/feed/rss.ts +4 -3
- package/src/routes/feed/sitemap.ts +15 -5
- package/src/routes/pages/__tests__/collections.test.ts +94 -0
- package/src/routes/pages/__tests__/featured.test.ts +94 -0
- package/src/routes/pages/archive.tsx +15 -15
- package/src/routes/pages/collection.tsx +16 -9
- package/src/routes/pages/collections.tsx +36 -0
- package/src/routes/pages/featured.tsx +38 -0
- package/src/routes/pages/home.tsx +21 -92
- package/src/routes/pages/page.tsx +62 -27
- package/src/routes/pages/post.tsx +6 -18
- package/src/routes/pages/search.tsx +3 -7
- 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__/page.test.ts +106 -0
- package/src/services/__tests__/post-timeline.test.ts +92 -88
- package/src/services/__tests__/post.test.ts +432 -197
- 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 +136 -0
- package/src/services/post.ts +141 -101
- package/src/services/search.ts +38 -27
- package/src/styles/tokens.css +47 -0
- package/src/styles/ui.css +491 -0
- package/src/types.ts +212 -198
- package/src/ui/compose/ComposeDialog.tsx +395 -0
- package/src/ui/compose/ComposePrompt.tsx +55 -0
- package/src/ui/dash/FormatBadge.tsx +28 -0
- package/src/{theme/components → ui/dash}/PageForm.tsx +21 -21
- package/src/{theme/components → ui/dash}/PostForm.tsx +110 -131
- package/src/ui/dash/PostList.tsx +101 -0
- package/src/ui/dash/StatusBadge.tsx +61 -0
- package/src/ui/dash/index.ts +10 -0
- package/src/ui/feed/LinkCard.tsx +72 -0
- package/src/ui/feed/NoteCard.tsx +63 -0
- package/src/ui/feed/QuoteCard.tsx +68 -0
- package/src/ui/feed/ThreadPreview.tsx +48 -0
- package/src/ui/feed/TimelineFeed.tsx +49 -0
- package/src/ui/feed/TimelineItem.tsx +45 -0
- package/src/{theme → ui}/layouts/BaseLayout.tsx +11 -1
- package/src/{theme → ui}/layouts/DashLayout.tsx +0 -10
- package/src/ui/layouts/SiteLayout.tsx +150 -0
- package/src/ui/pages/ArchivePage.tsx +162 -0
- package/src/ui/pages/CollectionPage.tsx +70 -0
- package/src/ui/pages/CollectionsPage.tsx +73 -0
- package/src/ui/pages/FeaturedPage.tsx +31 -0
- package/src/ui/pages/HomePage.tsx +37 -0
- package/src/ui/pages/PostPage.tsx +56 -0
- package/src/{themes/minimal → ui}/pages/SearchPage.tsx +24 -20
- package/src/{themes/minimal → ui}/pages/SinglePage.tsx +5 -5
- package/src/ui/shared/MediaGallery.tsx +59 -0
- package/src/{theme/components → ui/shared}/Pagination.tsx +67 -4
- package/src/{theme/components → ui/shared}/ThreadView.tsx +6 -3
- package/src/ui/shared/__tests__/pagination.test.ts +46 -0
- package/src/ui/shared/index.ts +12 -0
- package/bin/jant.js +0 -185
- package/dist/lib/theme-components.js +0 -49
- package/dist/routes/api/timeline.js +0 -120
- package/dist/routes/dash/navigation.js +0 -288
- package/dist/theme/components/MediaGallery.js +0 -107
- package/dist/theme/components/VisibilityBadge.js +0 -37
- package/dist/theme/index.js +0 -18
- package/dist/theme/layouts/index.js +0 -2
- package/dist/themes/minimal/MinimalSiteLayout.js +0 -83
- package/dist/themes/minimal/index.js +0 -65
- package/dist/themes/minimal/pages/CollectionPage.js +0 -65
- package/dist/themes/minimal/pages/HomePage.js +0 -25
- package/dist/themes/minimal/timeline/ArticleCard.js +0 -36
- package/dist/themes/minimal/timeline/ImageCard.js +0 -67
- package/dist/themes/minimal/timeline/LinkCard.js +0 -47
- package/dist/themes/minimal/timeline/NoteCard.js +0 -34
- package/dist/themes/minimal/timeline/TimelineFeed.js +0 -48
- package/dist/themes/minimal/timeline/TimelineItem.js +0 -44
- package/src/lib/__tests__/theme-components.test.ts +0 -126
- package/src/lib/theme-components.ts +0 -68
- package/src/routes/api/timeline.tsx +0 -159
- package/src/routes/dash/navigation.tsx +0 -316
- package/src/theme/components/MediaGallery.tsx +0 -128
- package/src/theme/components/PostList.tsx +0 -92
- package/src/theme/components/TypeBadge.tsx +0 -37
- package/src/theme/components/VisibilityBadge.tsx +0 -45
- package/src/theme/components/index.ts +0 -23
- package/src/theme/index.ts +0 -22
- package/src/theme/layouts/index.ts +0 -7
- package/src/themes/minimal/MinimalSiteLayout.tsx +0 -100
- package/src/themes/minimal/index.ts +0 -83
- package/src/themes/minimal/pages/ArchivePage.tsx +0 -157
- package/src/themes/minimal/pages/CollectionPage.tsx +0 -60
- package/src/themes/minimal/pages/HomePage.tsx +0 -41
- package/src/themes/minimal/pages/PostPage.tsx +0 -43
- package/src/themes/minimal/timeline/ArticleCard.tsx +0 -37
- package/src/themes/minimal/timeline/ImageCard.tsx +0 -63
- package/src/themes/minimal/timeline/LinkCard.tsx +0 -48
- package/src/themes/minimal/timeline/NoteCard.tsx +0 -35
- package/src/themes/minimal/timeline/QuoteCard.tsx +0 -49
- package/src/themes/minimal/timeline/ThreadPreview.tsx +0 -47
- package/src/themes/minimal/timeline/TimelineFeed.tsx +0 -57
- package/src/themes/minimal/timeline/TimelineItem.tsx +0 -75
- /package/dist/{theme → ui}/color-themes.js +0 -0
- /package/dist/{theme/components → ui/dash}/ActionButtons.js +0 -0
- /package/dist/{theme/components → ui/dash}/CrudPageHeader.js +0 -0
- /package/dist/{theme/components → ui/dash}/DangerZone.js +0 -0
- /package/dist/{theme/components → ui/dash}/ListItemRow.js +0 -0
- /package/dist/{theme/components → ui/shared}/EmptyState.js +0 -0
- /package/src/{theme → ui}/color-themes.ts +0 -0
- /package/src/{theme/components → ui/dash}/ActionButtons.tsx +0 -0
- /package/src/{theme/components → ui/dash}/CrudPageHeader.tsx +0 -0
- /package/src/{theme/components → ui/dash}/DangerZone.tsx +0 -0
- /package/src/{theme/components → ui/dash}/ListItemRow.tsx +0 -0
- /package/src/{theme/components → ui/shared}/EmptyState.tsx +0 -0
|
@@ -16,62 +16,59 @@ describe("PostService", () => {
|
|
|
16
16
|
describe("create", () => {
|
|
17
17
|
it("creates a note post with required fields", async () => {
|
|
18
18
|
const post = await postService.create({
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
format: "note",
|
|
20
|
+
body: "Hello world",
|
|
21
21
|
});
|
|
22
22
|
|
|
23
23
|
expect(post.id).toBe(1);
|
|
24
|
-
expect(post.
|
|
25
|
-
expect(post.
|
|
26
|
-
expect(post.
|
|
27
|
-
expect(post.
|
|
24
|
+
expect(post.format).toBe("note");
|
|
25
|
+
expect(post.body).toBe("Hello world");
|
|
26
|
+
expect(post.status).toBe("published"); // default
|
|
27
|
+
expect(post.featured).toBe(0);
|
|
28
|
+
expect(post.pinned).toBe(0);
|
|
29
|
+
expect(post.bodyHtml).toContain("<p>Hello world</p>");
|
|
28
30
|
expect(post.deletedAt).toBeNull();
|
|
29
31
|
});
|
|
30
32
|
|
|
31
33
|
it("creates a post with all fields", async () => {
|
|
32
34
|
const post = await postService.create({
|
|
33
|
-
|
|
34
|
-
title: "My
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
expect(post.
|
|
46
|
-
expect(post.
|
|
47
|
-
expect(post.
|
|
48
|
-
expect(post.
|
|
49
|
-
expect(post.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
35
|
+
format: "link",
|
|
36
|
+
title: "My Link",
|
|
37
|
+
body: "# Introduction\n\nSome content.",
|
|
38
|
+
status: "published",
|
|
39
|
+
featured: true,
|
|
40
|
+
pinned: true,
|
|
41
|
+
path: "my-link",
|
|
42
|
+
url: "https://example.com/source",
|
|
43
|
+
quoteText: "A notable quote",
|
|
44
|
+
rating: 5,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
expect(post.format).toBe("link");
|
|
48
|
+
expect(post.title).toBe("My Link");
|
|
49
|
+
expect(post.status).toBe("published");
|
|
50
|
+
expect(post.featured).toBe(1);
|
|
51
|
+
expect(post.pinned).toBe(1);
|
|
52
|
+
expect(post.path).toBe("my-link");
|
|
53
|
+
expect(post.url).toBe("https://example.com/source");
|
|
54
|
+
expect(post.quoteText).toBe("A notable quote");
|
|
55
|
+
expect(post.rating).toBe(5);
|
|
56
|
+
expect(post.bodyHtml).toContain("<h1>");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("renders markdown body to HTML", async () => {
|
|
53
60
|
const post = await postService.create({
|
|
54
|
-
|
|
55
|
-
|
|
61
|
+
format: "note",
|
|
62
|
+
body: "This is **bold** text",
|
|
56
63
|
});
|
|
57
64
|
|
|
58
|
-
expect(post.
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it("extracts domain from source URL", async () => {
|
|
62
|
-
const post = await postService.create({
|
|
63
|
-
type: "link",
|
|
64
|
-
content: "Check this out",
|
|
65
|
-
sourceUrl: "https://blog.example.org/article",
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
expect(post.sourceDomain).toBe("blog.example.org");
|
|
65
|
+
expect(post.bodyHtml).toContain("<strong>bold</strong>");
|
|
69
66
|
});
|
|
70
67
|
|
|
71
68
|
it("sets publishedAt and timestamps", async () => {
|
|
72
69
|
const post = await postService.create({
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
format: "note",
|
|
71
|
+
body: "test",
|
|
75
72
|
});
|
|
76
73
|
|
|
77
74
|
expect(post.publishedAt).toBeGreaterThan(0);
|
|
@@ -82,8 +79,8 @@ describe("PostService", () => {
|
|
|
82
79
|
it("allows custom publishedAt", async () => {
|
|
83
80
|
const customTime = 1706745600;
|
|
84
81
|
const post = await postService.create({
|
|
85
|
-
|
|
86
|
-
|
|
82
|
+
format: "note",
|
|
83
|
+
body: "test",
|
|
87
84
|
publishedAt: customTime,
|
|
88
85
|
});
|
|
89
86
|
|
|
@@ -92,29 +89,52 @@ describe("PostService", () => {
|
|
|
92
89
|
|
|
93
90
|
it("creates incrementing IDs", async () => {
|
|
94
91
|
const post1 = await postService.create({
|
|
95
|
-
|
|
96
|
-
|
|
92
|
+
format: "note",
|
|
93
|
+
body: "first",
|
|
97
94
|
});
|
|
98
95
|
const post2 = await postService.create({
|
|
99
|
-
|
|
100
|
-
|
|
96
|
+
format: "note",
|
|
97
|
+
body: "second",
|
|
101
98
|
});
|
|
102
99
|
|
|
103
100
|
expect(post2.id).toBeGreaterThan(post1.id);
|
|
104
101
|
});
|
|
102
|
+
|
|
103
|
+
it("creates a quote post", async () => {
|
|
104
|
+
const post = await postService.create({
|
|
105
|
+
format: "quote",
|
|
106
|
+
quoteText: "To be or not to be",
|
|
107
|
+
body: "Shakespeare's famous line",
|
|
108
|
+
url: "https://example.com/hamlet",
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
expect(post.format).toBe("quote");
|
|
112
|
+
expect(post.quoteText).toBe("To be or not to be");
|
|
113
|
+
expect(post.url).toBe("https://example.com/hamlet");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("creates a draft post", async () => {
|
|
117
|
+
const post = await postService.create({
|
|
118
|
+
format: "note",
|
|
119
|
+
body: "draft content",
|
|
120
|
+
status: "draft",
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
expect(post.status).toBe("draft");
|
|
124
|
+
});
|
|
105
125
|
});
|
|
106
126
|
|
|
107
127
|
describe("getById", () => {
|
|
108
128
|
it("returns a post by ID", async () => {
|
|
109
129
|
const created = await postService.create({
|
|
110
|
-
|
|
111
|
-
|
|
130
|
+
format: "note",
|
|
131
|
+
body: "test",
|
|
112
132
|
});
|
|
113
133
|
|
|
114
134
|
const found = await postService.getById(created.id);
|
|
115
135
|
expect(found).not.toBeNull();
|
|
116
136
|
expect(found?.id).toBe(created.id);
|
|
117
|
-
expect(found?.
|
|
137
|
+
expect(found?.body).toBe("test");
|
|
118
138
|
});
|
|
119
139
|
|
|
120
140
|
it("returns null for non-existent ID", async () => {
|
|
@@ -124,8 +144,8 @@ describe("PostService", () => {
|
|
|
124
144
|
|
|
125
145
|
it("excludes soft-deleted posts", async () => {
|
|
126
146
|
const post = await postService.create({
|
|
127
|
-
|
|
128
|
-
|
|
147
|
+
format: "note",
|
|
148
|
+
body: "test",
|
|
129
149
|
});
|
|
130
150
|
await postService.delete(post.id);
|
|
131
151
|
|
|
@@ -137,8 +157,8 @@ describe("PostService", () => {
|
|
|
137
157
|
describe("getByPath", () => {
|
|
138
158
|
it("returns a post by path", async () => {
|
|
139
159
|
await postService.create({
|
|
140
|
-
|
|
141
|
-
|
|
160
|
+
format: "note",
|
|
161
|
+
body: "About page",
|
|
142
162
|
path: "about",
|
|
143
163
|
});
|
|
144
164
|
|
|
@@ -154,8 +174,8 @@ describe("PostService", () => {
|
|
|
154
174
|
|
|
155
175
|
it("excludes soft-deleted posts", async () => {
|
|
156
176
|
const post = await postService.create({
|
|
157
|
-
|
|
158
|
-
|
|
177
|
+
format: "note",
|
|
178
|
+
body: "test",
|
|
159
179
|
path: "test-page",
|
|
160
180
|
});
|
|
161
181
|
await postService.delete(post.id);
|
|
@@ -163,6 +183,18 @@ describe("PostService", () => {
|
|
|
163
183
|
const found = await postService.getByPath("test-page");
|
|
164
184
|
expect(found).toBeNull();
|
|
165
185
|
});
|
|
186
|
+
|
|
187
|
+
it("finds a post with a multi-level path", async () => {
|
|
188
|
+
await postService.create({
|
|
189
|
+
format: "note",
|
|
190
|
+
body: "Blog migration",
|
|
191
|
+
path: "2024/01/my-post",
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const found = await postService.getByPath("2024/01/my-post");
|
|
195
|
+
expect(found).not.toBeNull();
|
|
196
|
+
expect(found?.path).toBe("2024/01/my-post");
|
|
197
|
+
});
|
|
166
198
|
});
|
|
167
199
|
|
|
168
200
|
describe("list", () => {
|
|
@@ -172,9 +204,9 @@ describe("PostService", () => {
|
|
|
172
204
|
});
|
|
173
205
|
|
|
174
206
|
it("returns all non-deleted posts", async () => {
|
|
175
|
-
await postService.create({
|
|
176
|
-
await postService.create({
|
|
177
|
-
await postService.create({
|
|
207
|
+
await postService.create({ format: "note", body: "first" });
|
|
208
|
+
await postService.create({ format: "note", body: "second" });
|
|
209
|
+
await postService.create({ format: "note", body: "third" });
|
|
178
210
|
|
|
179
211
|
const posts = await postService.list();
|
|
180
212
|
expect(posts).toHaveLength(3);
|
|
@@ -182,91 +214,113 @@ describe("PostService", () => {
|
|
|
182
214
|
|
|
183
215
|
it("orders by publishedAt descending", async () => {
|
|
184
216
|
await postService.create({
|
|
185
|
-
|
|
186
|
-
|
|
217
|
+
format: "note",
|
|
218
|
+
body: "old",
|
|
187
219
|
publishedAt: 1000,
|
|
188
220
|
});
|
|
189
221
|
await postService.create({
|
|
190
|
-
|
|
191
|
-
|
|
222
|
+
format: "note",
|
|
223
|
+
body: "new",
|
|
192
224
|
publishedAt: 2000,
|
|
193
225
|
});
|
|
194
226
|
|
|
195
227
|
const posts = await postService.list();
|
|
196
|
-
expect(posts[0]?.
|
|
197
|
-
expect(posts[1]?.
|
|
228
|
+
expect(posts[0]?.body).toBe("new");
|
|
229
|
+
expect(posts[1]?.body).toBe("old");
|
|
198
230
|
});
|
|
199
231
|
|
|
200
|
-
it("filters by
|
|
201
|
-
await postService.create({
|
|
232
|
+
it("filters by format", async () => {
|
|
233
|
+
await postService.create({ format: "note", body: "a note" });
|
|
202
234
|
await postService.create({
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
title: "
|
|
235
|
+
format: "link",
|
|
236
|
+
body: "a link",
|
|
237
|
+
title: "Link",
|
|
238
|
+
url: "https://example.com",
|
|
206
239
|
});
|
|
207
240
|
|
|
208
|
-
const notes = await postService.list({
|
|
241
|
+
const notes = await postService.list({ format: "note" });
|
|
209
242
|
expect(notes).toHaveLength(1);
|
|
210
|
-
expect(notes[0]?.
|
|
243
|
+
expect(notes[0]?.format).toBe("note");
|
|
211
244
|
});
|
|
212
245
|
|
|
213
|
-
it("filters by
|
|
246
|
+
it("filters by status", async () => {
|
|
214
247
|
await postService.create({
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
248
|
+
format: "note",
|
|
249
|
+
body: "published post",
|
|
250
|
+
status: "published",
|
|
218
251
|
});
|
|
219
252
|
await postService.create({
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
253
|
+
format: "note",
|
|
254
|
+
body: "draft post",
|
|
255
|
+
status: "draft",
|
|
223
256
|
});
|
|
224
257
|
|
|
225
|
-
const
|
|
226
|
-
expect(
|
|
227
|
-
expect(
|
|
258
|
+
const published = await postService.list({ status: "published" });
|
|
259
|
+
expect(published).toHaveLength(1);
|
|
260
|
+
expect(published[0]?.status).toBe("published");
|
|
228
261
|
});
|
|
229
262
|
|
|
230
|
-
it("filters by
|
|
263
|
+
it("filters by featured", async () => {
|
|
264
|
+
await postService.create({
|
|
265
|
+
format: "note",
|
|
266
|
+
body: "featured post",
|
|
267
|
+
featured: true,
|
|
268
|
+
});
|
|
231
269
|
await postService.create({
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
visibility: "featured",
|
|
270
|
+
format: "note",
|
|
271
|
+
body: "normal post",
|
|
235
272
|
});
|
|
273
|
+
|
|
274
|
+
const featured = await postService.list({ featured: true });
|
|
275
|
+
expect(featured).toHaveLength(1);
|
|
276
|
+
expect(featured[0]?.featured).toBe(1);
|
|
277
|
+
expect(featured[0]?.body).toBe("featured post");
|
|
278
|
+
|
|
279
|
+
const notFeatured = await postService.list({ featured: false });
|
|
280
|
+
expect(notFeatured).toHaveLength(1);
|
|
281
|
+
expect(notFeatured[0]?.featured).toBe(0);
|
|
282
|
+
expect(notFeatured[0]?.body).toBe("normal post");
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("filters by pinned", async () => {
|
|
236
286
|
await postService.create({
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
287
|
+
format: "note",
|
|
288
|
+
body: "pinned post",
|
|
289
|
+
pinned: true,
|
|
240
290
|
});
|
|
241
291
|
await postService.create({
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
visibility: "draft",
|
|
292
|
+
format: "note",
|
|
293
|
+
body: "normal post",
|
|
245
294
|
});
|
|
246
295
|
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
expect(
|
|
296
|
+
const pinned = await postService.list({ pinned: true });
|
|
297
|
+
expect(pinned).toHaveLength(1);
|
|
298
|
+
expect(pinned[0]?.pinned).toBe(1);
|
|
299
|
+
expect(pinned[0]?.body).toBe("pinned post");
|
|
300
|
+
|
|
301
|
+
const notPinned = await postService.list({ pinned: false });
|
|
302
|
+
expect(notPinned).toHaveLength(1);
|
|
303
|
+
expect(notPinned[0]?.pinned).toBe(0);
|
|
304
|
+
expect(notPinned[0]?.body).toBe("normal post");
|
|
251
305
|
});
|
|
252
306
|
|
|
253
307
|
it("excludes deleted posts by default", async () => {
|
|
254
308
|
const post = await postService.create({
|
|
255
|
-
|
|
256
|
-
|
|
309
|
+
format: "note",
|
|
310
|
+
body: "test",
|
|
257
311
|
});
|
|
258
|
-
await postService.create({
|
|
312
|
+
await postService.create({ format: "note", body: "kept" });
|
|
259
313
|
await postService.delete(post.id);
|
|
260
314
|
|
|
261
315
|
const posts = await postService.list();
|
|
262
316
|
expect(posts).toHaveLength(1);
|
|
263
|
-
expect(posts[0]?.
|
|
317
|
+
expect(posts[0]?.body).toBe("kept");
|
|
264
318
|
});
|
|
265
319
|
|
|
266
320
|
it("includes deleted posts when requested", async () => {
|
|
267
321
|
const post = await postService.create({
|
|
268
|
-
|
|
269
|
-
|
|
322
|
+
format: "note",
|
|
323
|
+
body: "test",
|
|
270
324
|
});
|
|
271
325
|
await postService.delete(post.id);
|
|
272
326
|
|
|
@@ -276,7 +330,7 @@ describe("PostService", () => {
|
|
|
276
330
|
|
|
277
331
|
it("supports limit", async () => {
|
|
278
332
|
for (let i = 0; i < 5; i++) {
|
|
279
|
-
await postService.create({
|
|
333
|
+
await postService.create({ format: "note", body: `post ${i}` });
|
|
280
334
|
}
|
|
281
335
|
|
|
282
336
|
const posts = await postService.list({ limit: 2 });
|
|
@@ -288,8 +342,8 @@ describe("PostService", () => {
|
|
|
288
342
|
for (let i = 0; i < 5; i++) {
|
|
289
343
|
created.push(
|
|
290
344
|
await postService.create({
|
|
291
|
-
|
|
292
|
-
|
|
345
|
+
format: "note",
|
|
346
|
+
body: `post ${i}`,
|
|
293
347
|
publishedAt: 1000 + i,
|
|
294
348
|
}),
|
|
295
349
|
);
|
|
@@ -303,42 +357,130 @@ describe("PostService", () => {
|
|
|
303
357
|
|
|
304
358
|
it("excludes replies when requested", async () => {
|
|
305
359
|
const root = await postService.create({
|
|
306
|
-
|
|
307
|
-
|
|
360
|
+
format: "note",
|
|
361
|
+
body: "root post",
|
|
308
362
|
});
|
|
309
363
|
await postService.create({
|
|
310
|
-
|
|
311
|
-
|
|
364
|
+
format: "note",
|
|
365
|
+
body: "reply",
|
|
312
366
|
replyToId: root.id,
|
|
313
367
|
});
|
|
314
368
|
|
|
315
369
|
const posts = await postService.list({ excludeReplies: true });
|
|
316
370
|
expect(posts).toHaveLength(1);
|
|
317
|
-
expect(posts[0]?.
|
|
371
|
+
expect(posts[0]?.body).toBe("root post");
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it("supports offset pagination", async () => {
|
|
375
|
+
for (let i = 0; i < 5; i++) {
|
|
376
|
+
await postService.create({
|
|
377
|
+
format: "note",
|
|
378
|
+
body: `post ${i}`,
|
|
379
|
+
publishedAt: 1000 + i,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Skip the first 2 posts (newest), get 2 more
|
|
384
|
+
const posts = await postService.list({ limit: 2, offset: 2 });
|
|
385
|
+
expect(posts).toHaveLength(2);
|
|
386
|
+
expect(posts[0]?.body).toBe("post 2");
|
|
387
|
+
expect(posts[1]?.body).toBe("post 1");
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
describe("count", () => {
|
|
392
|
+
it("returns 0 when no posts exist", async () => {
|
|
393
|
+
const count = await postService.count();
|
|
394
|
+
expect(count).toBe(0);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
it("counts all non-deleted posts", async () => {
|
|
398
|
+
await postService.create({ format: "note", body: "first" });
|
|
399
|
+
await postService.create({ format: "note", body: "second" });
|
|
400
|
+
await postService.create({ format: "note", body: "third" });
|
|
401
|
+
|
|
402
|
+
const count = await postService.count();
|
|
403
|
+
expect(count).toBe(3);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it("filters by status", async () => {
|
|
407
|
+
await postService.create({
|
|
408
|
+
format: "note",
|
|
409
|
+
body: "published",
|
|
410
|
+
status: "published",
|
|
411
|
+
});
|
|
412
|
+
await postService.create({
|
|
413
|
+
format: "note",
|
|
414
|
+
body: "draft",
|
|
415
|
+
status: "draft",
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
const count = await postService.count({ status: "published" });
|
|
419
|
+
expect(count).toBe(1);
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
it("filters by featured", async () => {
|
|
423
|
+
await postService.create({
|
|
424
|
+
format: "note",
|
|
425
|
+
body: "featured",
|
|
426
|
+
featured: true,
|
|
427
|
+
});
|
|
428
|
+
await postService.create({ format: "note", body: "normal" });
|
|
429
|
+
|
|
430
|
+
const count = await postService.count({ featured: true });
|
|
431
|
+
expect(count).toBe(1);
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
it("excludes deleted posts by default", async () => {
|
|
435
|
+
const post = await postService.create({
|
|
436
|
+
format: "note",
|
|
437
|
+
body: "to delete",
|
|
438
|
+
});
|
|
439
|
+
await postService.create({ format: "note", body: "keep" });
|
|
440
|
+
await postService.delete(post.id);
|
|
441
|
+
|
|
442
|
+
const count = await postService.count();
|
|
443
|
+
expect(count).toBe(1);
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
it("excludes replies when requested", async () => {
|
|
447
|
+
const root = await postService.create({
|
|
448
|
+
format: "note",
|
|
449
|
+
body: "root",
|
|
450
|
+
});
|
|
451
|
+
await postService.create({
|
|
452
|
+
format: "note",
|
|
453
|
+
body: "reply",
|
|
454
|
+
replyToId: root.id,
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
const count = await postService.count({ excludeReplies: true });
|
|
458
|
+
expect(count).toBe(1);
|
|
318
459
|
});
|
|
319
460
|
});
|
|
320
461
|
|
|
321
462
|
describe("update", () => {
|
|
322
|
-
it("updates post
|
|
463
|
+
it("updates post body", async () => {
|
|
323
464
|
const post = await postService.create({
|
|
324
|
-
|
|
325
|
-
|
|
465
|
+
format: "note",
|
|
466
|
+
body: "original",
|
|
326
467
|
});
|
|
327
468
|
|
|
328
469
|
const updated = await postService.update(post.id, {
|
|
329
|
-
|
|
470
|
+
body: "updated content",
|
|
330
471
|
});
|
|
331
472
|
|
|
332
473
|
expect(updated).not.toBeNull();
|
|
333
|
-
expect(updated?.
|
|
334
|
-
expect(updated?.
|
|
474
|
+
expect(updated?.body).toBe("updated content");
|
|
475
|
+
expect(updated?.bodyHtml).toContain("updated content");
|
|
335
476
|
});
|
|
336
477
|
|
|
337
478
|
it("updates post title", async () => {
|
|
338
479
|
const post = await postService.create({
|
|
339
|
-
|
|
340
|
-
|
|
480
|
+
format: "link",
|
|
481
|
+
body: "body",
|
|
341
482
|
title: "Original Title",
|
|
483
|
+
url: "https://example.com",
|
|
342
484
|
});
|
|
343
485
|
|
|
344
486
|
const updated = await postService.update(post.id, {
|
|
@@ -348,44 +490,43 @@ describe("PostService", () => {
|
|
|
348
490
|
expect(updated?.title).toBe("New Title");
|
|
349
491
|
});
|
|
350
492
|
|
|
351
|
-
it("updates
|
|
493
|
+
it("updates post url", async () => {
|
|
352
494
|
const post = await postService.create({
|
|
353
|
-
|
|
354
|
-
|
|
495
|
+
format: "link",
|
|
496
|
+
body: "link post",
|
|
497
|
+
url: "https://old.com",
|
|
355
498
|
});
|
|
356
499
|
|
|
357
500
|
const updated = await postService.update(post.id, {
|
|
358
|
-
|
|
501
|
+
url: "https://new-source.com/path",
|
|
359
502
|
});
|
|
360
503
|
|
|
361
|
-
expect(updated?.
|
|
362
|
-
expect(updated?.sourceDomain).toBe("new-source.com");
|
|
504
|
+
expect(updated?.url).toBe("https://new-source.com/path");
|
|
363
505
|
});
|
|
364
506
|
|
|
365
|
-
it("clears
|
|
507
|
+
it("clears url when set to null", async () => {
|
|
366
508
|
const post = await postService.create({
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
509
|
+
format: "link",
|
|
510
|
+
body: "test",
|
|
511
|
+
url: "https://example.com",
|
|
370
512
|
});
|
|
371
513
|
|
|
372
514
|
const updated = await postService.update(post.id, {
|
|
373
|
-
|
|
515
|
+
url: null,
|
|
374
516
|
});
|
|
375
517
|
|
|
376
|
-
expect(updated?.
|
|
377
|
-
expect(updated?.sourceDomain).toBeNull();
|
|
518
|
+
expect(updated?.url).toBeNull();
|
|
378
519
|
});
|
|
379
520
|
|
|
380
521
|
it("returns null for non-existent post", async () => {
|
|
381
|
-
const result = await postService.update(9999, {
|
|
522
|
+
const result = await postService.update(9999, { body: "test" });
|
|
382
523
|
expect(result).toBeNull();
|
|
383
524
|
});
|
|
384
525
|
|
|
385
526
|
it("updates updatedAt timestamp", async () => {
|
|
386
527
|
const post = await postService.create({
|
|
387
|
-
|
|
388
|
-
|
|
528
|
+
format: "note",
|
|
529
|
+
body: "test",
|
|
389
530
|
});
|
|
390
531
|
const originalUpdatedAt = post.updatedAt;
|
|
391
532
|
|
|
@@ -393,18 +534,78 @@ describe("PostService", () => {
|
|
|
393
534
|
await new Promise((r) => setTimeout(r, 1100));
|
|
394
535
|
|
|
395
536
|
const updated = await postService.update(post.id, {
|
|
396
|
-
|
|
537
|
+
body: "modified",
|
|
397
538
|
});
|
|
398
539
|
|
|
399
540
|
expect(updated?.updatedAt).toBeGreaterThanOrEqual(originalUpdatedAt);
|
|
400
541
|
});
|
|
542
|
+
|
|
543
|
+
it("updates featured flag", async () => {
|
|
544
|
+
const post = await postService.create({
|
|
545
|
+
format: "note",
|
|
546
|
+
body: "test",
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
expect(post.featured).toBe(0);
|
|
550
|
+
|
|
551
|
+
const updated = await postService.update(post.id, {
|
|
552
|
+
featured: true,
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
expect(updated?.featured).toBe(1);
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
it("updates pinned flag", async () => {
|
|
559
|
+
const post = await postService.create({
|
|
560
|
+
format: "note",
|
|
561
|
+
body: "test",
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
expect(post.pinned).toBe(0);
|
|
565
|
+
|
|
566
|
+
const updated = await postService.update(post.id, {
|
|
567
|
+
pinned: true,
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
expect(updated?.pinned).toBe(1);
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
it("updates path", async () => {
|
|
574
|
+
const post = await postService.create({
|
|
575
|
+
format: "note",
|
|
576
|
+
body: "test",
|
|
577
|
+
path: "old-path",
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
const updated = await postService.update(post.id, {
|
|
581
|
+
path: "new-path",
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
expect(updated?.path).toBe("new-path");
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
it("updates quoteText and rating", async () => {
|
|
588
|
+
const post = await postService.create({
|
|
589
|
+
format: "quote",
|
|
590
|
+
quoteText: "Original quote",
|
|
591
|
+
rating: 3,
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
const updated = await postService.update(post.id, {
|
|
595
|
+
quoteText: "Updated quote",
|
|
596
|
+
rating: 5,
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
expect(updated?.quoteText).toBe("Updated quote");
|
|
600
|
+
expect(updated?.rating).toBe(5);
|
|
601
|
+
});
|
|
401
602
|
});
|
|
402
603
|
|
|
403
604
|
describe("delete (soft delete)", () => {
|
|
404
605
|
it("soft-deletes a post", async () => {
|
|
405
606
|
const post = await postService.create({
|
|
406
|
-
|
|
407
|
-
|
|
607
|
+
format: "note",
|
|
608
|
+
body: "test",
|
|
408
609
|
});
|
|
409
610
|
|
|
410
611
|
const result = await postService.delete(post.id);
|
|
@@ -422,12 +623,12 @@ describe("PostService", () => {
|
|
|
422
623
|
|
|
423
624
|
it("cascade deletes thread when deleting root post", async () => {
|
|
424
625
|
const root = await postService.create({
|
|
425
|
-
|
|
426
|
-
|
|
626
|
+
format: "note",
|
|
627
|
+
body: "root",
|
|
427
628
|
});
|
|
428
629
|
const reply = await postService.create({
|
|
429
|
-
|
|
430
|
-
|
|
630
|
+
format: "note",
|
|
631
|
+
body: "reply",
|
|
431
632
|
replyToId: root.id,
|
|
432
633
|
});
|
|
433
634
|
|
|
@@ -440,17 +641,17 @@ describe("PostService", () => {
|
|
|
440
641
|
|
|
441
642
|
it("only deletes single post when deleting a reply", async () => {
|
|
442
643
|
const root = await postService.create({
|
|
443
|
-
|
|
444
|
-
|
|
644
|
+
format: "note",
|
|
645
|
+
body: "root",
|
|
445
646
|
});
|
|
446
647
|
const reply1 = await postService.create({
|
|
447
|
-
|
|
448
|
-
|
|
648
|
+
format: "note",
|
|
649
|
+
body: "reply1",
|
|
449
650
|
replyToId: root.id,
|
|
450
651
|
});
|
|
451
652
|
await postService.create({
|
|
452
|
-
|
|
453
|
-
|
|
653
|
+
format: "note",
|
|
654
|
+
body: "reply2",
|
|
454
655
|
replyToId: root.id,
|
|
455
656
|
});
|
|
456
657
|
|
|
@@ -466,12 +667,12 @@ describe("PostService", () => {
|
|
|
466
667
|
describe("threads", () => {
|
|
467
668
|
it("sets threadId on reply to a root post", async () => {
|
|
468
669
|
const root = await postService.create({
|
|
469
|
-
|
|
470
|
-
|
|
670
|
+
format: "note",
|
|
671
|
+
body: "root",
|
|
471
672
|
});
|
|
472
673
|
const reply = await postService.create({
|
|
473
|
-
|
|
474
|
-
|
|
674
|
+
format: "note",
|
|
675
|
+
body: "reply",
|
|
475
676
|
replyToId: root.id,
|
|
476
677
|
});
|
|
477
678
|
|
|
@@ -481,17 +682,17 @@ describe("PostService", () => {
|
|
|
481
682
|
|
|
482
683
|
it("inherits threadId from parent in nested replies", async () => {
|
|
483
684
|
const root = await postService.create({
|
|
484
|
-
|
|
485
|
-
|
|
685
|
+
format: "note",
|
|
686
|
+
body: "root",
|
|
486
687
|
});
|
|
487
688
|
const reply1 = await postService.create({
|
|
488
|
-
|
|
489
|
-
|
|
689
|
+
format: "note",
|
|
690
|
+
body: "reply1",
|
|
490
691
|
replyToId: root.id,
|
|
491
692
|
});
|
|
492
693
|
const reply2 = await postService.create({
|
|
493
|
-
|
|
494
|
-
|
|
694
|
+
format: "note",
|
|
695
|
+
body: "reply2",
|
|
495
696
|
replyToId: reply1.id,
|
|
496
697
|
});
|
|
497
698
|
|
|
@@ -500,51 +701,66 @@ describe("PostService", () => {
|
|
|
500
701
|
expect(reply2.threadId).toBe(root.id);
|
|
501
702
|
});
|
|
502
703
|
|
|
503
|
-
it("inherits
|
|
704
|
+
it("inherits status from root post", async () => {
|
|
504
705
|
const root = await postService.create({
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
706
|
+
format: "note",
|
|
707
|
+
body: "root",
|
|
708
|
+
status: "draft",
|
|
508
709
|
});
|
|
509
710
|
const reply = await postService.create({
|
|
510
|
-
|
|
511
|
-
|
|
711
|
+
format: "note",
|
|
712
|
+
body: "reply",
|
|
512
713
|
replyToId: root.id,
|
|
513
714
|
});
|
|
514
715
|
|
|
515
|
-
expect(reply.
|
|
716
|
+
expect(reply.status).toBe("draft");
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
it("inherits featured from root post", async () => {
|
|
720
|
+
const root = await postService.create({
|
|
721
|
+
format: "note",
|
|
722
|
+
body: "root",
|
|
723
|
+
featured: true,
|
|
724
|
+
});
|
|
725
|
+
const reply = await postService.create({
|
|
726
|
+
format: "note",
|
|
727
|
+
body: "reply",
|
|
728
|
+
replyToId: root.id,
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
expect(reply.featured).toBe(1);
|
|
516
732
|
});
|
|
517
733
|
|
|
518
734
|
it("getThread returns all posts in a thread", async () => {
|
|
519
735
|
const root = await postService.create({
|
|
520
|
-
|
|
521
|
-
|
|
736
|
+
format: "note",
|
|
737
|
+
body: "root",
|
|
522
738
|
});
|
|
523
739
|
await postService.create({
|
|
524
|
-
|
|
525
|
-
|
|
740
|
+
format: "note",
|
|
741
|
+
body: "reply1",
|
|
526
742
|
replyToId: root.id,
|
|
527
743
|
});
|
|
528
744
|
await postService.create({
|
|
529
|
-
|
|
530
|
-
|
|
745
|
+
format: "note",
|
|
746
|
+
body: "reply2",
|
|
531
747
|
replyToId: root.id,
|
|
532
748
|
});
|
|
533
749
|
|
|
534
750
|
const thread = await postService.getThread(root.id);
|
|
535
751
|
expect(thread).toHaveLength(3);
|
|
536
752
|
// Ordered by createdAt
|
|
537
|
-
expect(thread[0]?.
|
|
753
|
+
expect(thread[0]?.body).toBe("root");
|
|
538
754
|
});
|
|
539
755
|
|
|
540
756
|
it("getThread excludes deleted posts", async () => {
|
|
541
757
|
const root = await postService.create({
|
|
542
|
-
|
|
543
|
-
|
|
758
|
+
format: "note",
|
|
759
|
+
body: "root",
|
|
544
760
|
});
|
|
545
761
|
const reply = await postService.create({
|
|
546
|
-
|
|
547
|
-
|
|
762
|
+
format: "note",
|
|
763
|
+
body: "reply",
|
|
548
764
|
replyToId: root.id,
|
|
549
765
|
});
|
|
550
766
|
|
|
@@ -554,23 +770,42 @@ describe("PostService", () => {
|
|
|
554
770
|
expect(thread).toHaveLength(1);
|
|
555
771
|
});
|
|
556
772
|
|
|
557
|
-
it("cascades
|
|
773
|
+
it("cascades status changes from root to thread", async () => {
|
|
774
|
+
const root = await postService.create({
|
|
775
|
+
format: "note",
|
|
776
|
+
body: "root",
|
|
777
|
+
status: "published",
|
|
778
|
+
});
|
|
779
|
+
await postService.create({
|
|
780
|
+
format: "note",
|
|
781
|
+
body: "reply",
|
|
782
|
+
replyToId: root.id,
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
await postService.update(root.id, { status: "draft" });
|
|
786
|
+
|
|
787
|
+
const thread = await postService.getThread(root.id);
|
|
788
|
+
for (const post of thread) {
|
|
789
|
+
expect(post.status).toBe("draft");
|
|
790
|
+
}
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
it("cascades featured changes from root to thread", async () => {
|
|
558
794
|
const root = await postService.create({
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
visibility: "quiet",
|
|
795
|
+
format: "note",
|
|
796
|
+
body: "root",
|
|
562
797
|
});
|
|
563
798
|
await postService.create({
|
|
564
|
-
|
|
565
|
-
|
|
799
|
+
format: "note",
|
|
800
|
+
body: "reply",
|
|
566
801
|
replyToId: root.id,
|
|
567
802
|
});
|
|
568
803
|
|
|
569
|
-
await postService.update(root.id, {
|
|
804
|
+
await postService.update(root.id, { featured: true });
|
|
570
805
|
|
|
571
806
|
const thread = await postService.getThread(root.id);
|
|
572
807
|
for (const post of thread) {
|
|
573
|
-
expect(post.
|
|
808
|
+
expect(post.featured).toBe(1);
|
|
574
809
|
}
|
|
575
810
|
});
|
|
576
811
|
});
|
|
@@ -583,17 +818,17 @@ describe("PostService", () => {
|
|
|
583
818
|
|
|
584
819
|
it("returns reply counts for posts", async () => {
|
|
585
820
|
const root = await postService.create({
|
|
586
|
-
|
|
587
|
-
|
|
821
|
+
format: "note",
|
|
822
|
+
body: "root",
|
|
588
823
|
});
|
|
589
824
|
await postService.create({
|
|
590
|
-
|
|
591
|
-
|
|
825
|
+
format: "note",
|
|
826
|
+
body: "reply1",
|
|
592
827
|
replyToId: root.id,
|
|
593
828
|
});
|
|
594
829
|
await postService.create({
|
|
595
|
-
|
|
596
|
-
|
|
830
|
+
format: "note",
|
|
831
|
+
body: "reply2",
|
|
597
832
|
replyToId: root.id,
|
|
598
833
|
});
|
|
599
834
|
|
|
@@ -603,8 +838,8 @@ describe("PostService", () => {
|
|
|
603
838
|
|
|
604
839
|
it("returns 0 (missing) for posts without replies", async () => {
|
|
605
840
|
const post = await postService.create({
|
|
606
|
-
|
|
607
|
-
|
|
841
|
+
format: "note",
|
|
842
|
+
body: "no replies",
|
|
608
843
|
});
|
|
609
844
|
|
|
610
845
|
const counts = await postService.getReplyCounts([post.id]);
|
|
@@ -613,17 +848,17 @@ describe("PostService", () => {
|
|
|
613
848
|
|
|
614
849
|
it("excludes deleted replies from count", async () => {
|
|
615
850
|
const root = await postService.create({
|
|
616
|
-
|
|
617
|
-
|
|
851
|
+
format: "note",
|
|
852
|
+
body: "root",
|
|
618
853
|
});
|
|
619
854
|
const reply = await postService.create({
|
|
620
|
-
|
|
621
|
-
|
|
855
|
+
format: "note",
|
|
856
|
+
body: "reply",
|
|
622
857
|
replyToId: root.id,
|
|
623
858
|
});
|
|
624
859
|
await postService.create({
|
|
625
|
-
|
|
626
|
-
|
|
860
|
+
format: "note",
|
|
861
|
+
body: "reply2",
|
|
627
862
|
replyToId: root.id,
|
|
628
863
|
});
|
|
629
864
|
|