@jant/core 0.6.14 → 0.6.15
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/bin/commands/import-site.js +81 -13
- package/bin/commands/migrate.js +55 -0
- package/bin/commands/posts/rebuild-html.js +184 -0
- package/bin/commands/site/snapshot/import.js +14 -10
- package/bin/lib/d1-query.js +23 -5
- package/bin/lib/migration-runner.js +29 -7
- package/bin/lib/node-database.js +32 -0
- package/bin/lib/site-snapshot.js +347 -46
- package/bin/lib/sql-export.js +1 -1
- package/bin/lib/thread-collection-migration.js +326 -0
- package/dist/app-BxCOR3Uc.js +6 -0
- package/dist/{app-2i7-WQHg.js → app-C-oi_t8W.js} +7408 -6343
- package/dist/client/.vite/manifest.json +3 -3
- package/dist/client/_assets/client-B_eGKN5p.css +2 -0
- package/dist/client/_assets/{client-BvID5ucz.js → client-DxY5BFe8.js} +58 -37
- package/dist/client/_assets/client-auth-CakPESv9.js +5533 -0
- package/dist/{export-DWn149b7.js → export-CsFx6F_M.js} +1259 -125
- package/dist/{github-sync-Ckk0sJxK.js → github-sync-Cq1pzzOI.js} +1 -1
- package/dist/{github-sync-C0zyF6XS.js → github-sync-ppWXN3jb.js} +2 -2
- package/dist/index.js +3 -3
- package/dist/node.js +5 -5
- package/package.json +11 -10
- package/src/__tests__/bin/posts-rebuild-html.test.ts +137 -0
- package/src/__tests__/dev-navigation-defaults.test.ts +22 -0
- package/src/__tests__/export-hugo-build.test.ts +63 -4
- package/src/__tests__/export-import-roundtrip.test.ts +6 -4
- package/src/__tests__/export-service.test.ts +142 -9
- package/src/__tests__/helpers/app.ts +3 -0
- package/src/__tests__/helpers/export-fixtures.ts +2 -0
- package/src/__tests__/import-site-command.test.ts +65 -0
- package/src/__tests__/vite-config.test.ts +45 -0
- package/src/app.tsx +16 -3
- package/src/client/__tests__/collection-created-notice.test.ts +36 -0
- package/src/client/__tests__/collection-form-bridge.test.ts +10 -1
- package/src/client/__tests__/collection-page-actions.test.ts +71 -2
- package/src/client/__tests__/compose-bridge.test.ts +64 -0
- package/src/client/__tests__/feed-video-player.test.ts +16 -1
- package/src/client/__tests__/footnote-rail.test.ts +472 -0
- package/src/client/__tests__/hydrate-partial.test.ts +3 -0
- package/src/client/__tests__/toast.test.ts +18 -1
- package/src/client/collection-created-notice.ts +66 -0
- package/src/client/collection-form-bridge.ts +17 -0
- package/src/client/collection-navigation.ts +44 -0
- package/src/client/collection-page-actions.ts +57 -1
- package/src/client/components/__tests__/jant-collection-directory.test.ts +155 -6
- package/src/client/components/__tests__/jant-compose-dialog.test.ts +370 -5
- package/src/client/components/__tests__/jant-compose-editor.test.ts +97 -0
- package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +104 -0
- package/src/client/components/__tests__/jant-config-editor.test.ts +405 -0
- package/src/client/components/__tests__/jant-media-lightbox.test.ts +121 -0
- package/src/client/components/__tests__/jant-nav-manager.test.ts +538 -2
- package/src/client/components/__tests__/jant-post-form.test.ts +2 -2
- package/src/client/components/__tests__/jant-post-menu.test.ts +47 -5
- package/src/client/components/__tests__/jant-settings-avatar.test.ts +2 -2
- package/src/client/components/__tests__/jant-settings-general.test.ts +3 -3
- package/src/client/components/collection-manager-types.ts +9 -3
- package/src/client/components/compose-types.ts +8 -0
- package/src/client/components/jant-collection-directory.ts +165 -30
- package/src/client/components/jant-command-palette.ts +2 -1
- package/src/client/components/jant-compose-dialog.ts +85 -21
- package/src/client/components/jant-compose-editor.ts +13 -7
- package/src/client/components/jant-compose-fullscreen.ts +24 -4
- package/src/client/components/jant-config-editor.ts +722 -0
- package/src/client/components/jant-media-lightbox.ts +100 -18
- package/src/client/components/jant-nav-manager.ts +1283 -54
- package/src/client/components/jant-post-form.ts +3 -3
- package/src/client/components/jant-post-menu.ts +86 -74
- package/src/client/components/nav-manager-types.ts +51 -2
- package/src/client/components/post-form-types.ts +1 -1
- package/src/client/compose-bridge.ts +4 -1
- package/src/client/feed-video-player.ts +62 -2
- package/src/client/footnote-rail.ts +478 -0
- package/src/client/hydrate-partial.ts +2 -0
- package/src/client/media-lightbox-events.ts +63 -0
- package/src/client/site-header-fragment.ts +38 -0
- package/src/client/tiptap/__tests__/blockquote-editing.test.ts +201 -0
- package/src/client/tiptap/__tests__/clear-formatting.test.ts +168 -0
- package/src/client/tiptap/__tests__/list-editing.test.ts +447 -0
- package/src/client/tiptap/__tests__/markdown-clipboard.test.ts +727 -4
- package/src/client/tiptap/__tests__/table-controls.test.ts +263 -0
- package/src/client/tiptap/__tests__/table-size-picker.test.ts +179 -0
- package/src/client/tiptap/bubble-menu.ts +88 -10
- package/src/client/tiptap/continuous-lists.ts +102 -0
- package/src/client/tiptap/create-editor.ts +3 -0
- package/src/client/tiptap/extensions.ts +14 -5
- package/src/client/tiptap/markdown-clipboard.ts +608 -11
- package/src/client/tiptap/slash-commands.ts +8 -1
- package/src/client/tiptap/structural-keymap.ts +211 -0
- package/src/client/tiptap/tab-indent.ts +8 -22
- package/src/client/tiptap/table-control-labels.ts +30 -0
- package/src/client/tiptap/table-controls.ts +537 -0
- package/src/client/tiptap/table-size-picker.ts +254 -0
- package/src/client/toast.ts +8 -6
- package/src/client-auth.ts +1 -0
- package/src/client.ts +1 -0
- package/src/db/__tests__/d1-query.test.ts +43 -1
- package/src/db/__tests__/migration-runner.test.ts +63 -0
- package/src/db/__tests__/migrations.test.ts +469 -0
- package/src/db/__tests__/thread-collection-migration.test.ts +364 -0
- package/src/db/migrations/0027_old_blue_blade.sql +39 -0
- package/src/db/migrations/0028_superb_post.sql +2 -0
- package/src/db/migrations/0029_boring_magma.sql +1 -0
- package/src/db/migrations/0030_typical_vivisector.sql +50 -0
- package/src/db/migrations/meta/0027_snapshot.json +2511 -0
- package/src/db/migrations/meta/0028_snapshot.json +2511 -0
- package/src/db/migrations/meta/0029_snapshot.json +2519 -0
- package/src/db/migrations/meta/0030_snapshot.json +2541 -0
- package/src/db/migrations/meta/_journal.json +28 -0
- package/src/db/migrations/pg/0025_moaning_epoch.sql +39 -0
- package/src/db/migrations/pg/0026_talented_killer_shrike.sql +2 -0
- package/src/db/migrations/pg/0027_short_cannonball.sql +1 -0
- package/src/db/migrations/pg/0028_perfect_sharon_carter.sql +27 -0
- package/src/db/migrations/pg/meta/0025_snapshot.json +3204 -0
- package/src/db/migrations/pg/meta/0026_snapshot.json +3204 -0
- package/src/db/migrations/pg/meta/0027_snapshot.json +3211 -0
- package/src/db/migrations/pg/meta/0028_snapshot.json +3248 -0
- package/src/db/migrations/pg/meta/_journal.json +28 -0
- package/src/db/pg/schema.ts +28 -20
- package/src/db/rehearsal-fixtures/demo-current.json +39 -4
- package/src/db/rehearsal-fixtures/demo-current.sql +121 -6
- package/src/db/schema.ts +28 -20
- package/src/i18n/__tests__/detect.test.ts +8 -2
- package/src/i18n/detect.ts +14 -5
- package/src/i18n/locales/public/en.po +133 -30
- package/src/i18n/locales/public/en.ts +1 -1
- package/src/i18n/locales/public/zh-Hans.po +128 -25
- package/src/i18n/locales/public/zh-Hans.ts +1 -1
- package/src/i18n/locales/public/zh-Hant.po +128 -25
- package/src/i18n/locales/public/zh-Hant.ts +1 -1
- package/src/i18n/locales/settings/en.po +513 -10
- package/src/i18n/locales/settings/en.ts +1 -1
- package/src/i18n/locales/settings/zh-Hans.po +515 -12
- package/src/i18n/locales/settings/zh-Hans.ts +1 -1
- package/src/i18n/locales/settings/zh-Hant.po +515 -12
- package/src/i18n/locales/settings/zh-Hant.ts +1 -1
- package/src/lib/__tests__/api-settings.test.ts +299 -0
- package/src/lib/__tests__/constants.test.ts +5 -0
- package/src/lib/__tests__/feed-policy.test.ts +25 -0
- package/src/lib/__tests__/footnotes-upgrade.test.ts +181 -0
- package/src/lib/__tests__/markdown-roundtrip-embed.test.ts +61 -0
- package/src/lib/__tests__/markdown-to-tiptap.test.ts +32 -4
- package/src/lib/__tests__/markdown.test.ts +8 -6
- package/src/lib/__tests__/navigation.test.ts +59 -2
- package/src/lib/__tests__/post-body-html.test.ts +176 -0
- package/src/lib/__tests__/post-display.test.ts +64 -0
- package/src/lib/__tests__/resolve-config.test.ts +113 -1
- package/src/lib/__tests__/summary.test.ts +82 -0
- package/src/lib/__tests__/timeline.test.ts +109 -70
- package/src/lib/__tests__/timezones.test.ts +31 -3
- package/src/lib/__tests__/tiptap-render.test.ts +268 -9
- package/src/lib/__tests__/tiptap-to-markdown.test.ts +6 -0
- package/src/lib/__tests__/view.test.ts +53 -0
- package/src/lib/__tests__/worker-response-cache.test.ts +18 -1
- package/src/lib/api-settings.ts +239 -12
- package/src/lib/collection-sort.ts +7 -5
- package/src/lib/constants.ts +2 -0
- package/src/lib/feed-path.ts +25 -0
- package/src/lib/feed-policy.ts +64 -0
- package/src/lib/footnotes.ts +264 -0
- package/src/lib/github-sync-site-config.ts +2 -0
- package/src/lib/hugo-markdown.ts +10 -3
- package/src/lib/jant-branding.ts +1 -0
- package/src/lib/markdown-manager.ts +188 -5
- package/src/lib/markdown.ts +10 -3
- package/src/lib/navigation.ts +16 -3
- package/src/lib/post-body-html.ts +131 -0
- package/src/lib/post-display.ts +7 -1
- package/src/lib/render.tsx +9 -2
- package/src/lib/resolve-config.ts +100 -16
- package/src/lib/schemas.ts +133 -1
- package/src/lib/settings-paths.ts +5 -0
- package/src/lib/site-skill.ts +78 -0
- package/src/lib/summary.ts +16 -5
- package/src/lib/timeline.ts +98 -122
- package/src/lib/timezones.ts +54 -14
- package/src/lib/tiptap-render.ts +355 -81
- package/src/lib/view.ts +30 -20
- package/src/lib/worker-response-cache.ts +11 -18
- package/src/middleware/__tests__/public-content-access.test.ts +91 -0
- package/src/middleware/config.ts +2 -2
- package/src/middleware/public-content-access.ts +87 -0
- package/src/node/__tests__/cli-export.test.ts +14 -0
- package/src/node/__tests__/cli-migrate.test.ts +6 -0
- package/src/node/__tests__/cli-site-snapshot.test.ts +205 -2
- package/src/node/__tests__/cli-snapshot-meta.test.ts +100 -0
- package/src/node/index.ts +2 -1
- package/src/preset.css +16 -2
- package/src/routes/__tests__/compose.test.ts +51 -0
- package/src/routes/__tests__/site-skill.test.ts +112 -0
- package/src/routes/api/__tests__/collections.test.ts +91 -19
- package/src/routes/api/__tests__/mcp.test.ts +65 -1
- package/src/routes/api/__tests__/nav-items.test.ts +91 -0
- package/src/routes/api/__tests__/public-access.test.ts +81 -0
- package/src/routes/api/__tests__/settings.test.ts +217 -0
- package/src/routes/api/collections.ts +22 -26
- package/src/routes/api/export.ts +2 -0
- package/src/routes/api/internal/__tests__/post-body-html.test.ts +163 -0
- package/src/routes/api/internal/__tests__/sites.test.ts +35 -2
- package/src/routes/api/internal/post-body-html.ts +43 -0
- package/src/routes/api/internal/sites.ts +24 -0
- package/src/routes/api/nav-items.ts +24 -1
- package/src/routes/api/posts.ts +2 -2
- package/src/routes/api/public/__tests__/archive.test.ts +35 -0
- package/src/routes/api/public/__tests__/posts.test.ts +208 -0
- package/src/routes/api/public/archive.ts +4 -0
- package/src/routes/api/public/posts.ts +41 -24
- package/src/routes/api/search.ts +3 -0
- package/src/routes/api/settings.ts +33 -0
- package/src/routes/auth/__tests__/setup.test.ts +7 -4
- package/src/routes/compose.tsx +2 -1
- package/src/routes/dash/settings.tsx +45 -2
- package/src/routes/feed/__tests__/feed.test.ts +210 -6
- package/src/routes/feed/feed.ts +23 -14
- package/src/routes/pages/__tests__/archive-params.test.ts +92 -3
- package/src/routes/pages/__tests__/collection-routing.test.ts +63 -5
- package/src/routes/pages/__tests__/collections.test.ts +9 -9
- package/src/routes/pages/__tests__/preview.test.ts +174 -0
- package/src/routes/pages/__tests__/search.test.ts +69 -0
- package/src/routes/pages/archive.tsx +27 -7
- package/src/routes/pages/collection.tsx +32 -19
- package/src/routes/pages/collections.tsx +6 -1
- package/src/routes/pages/page.tsx +83 -11
- package/src/routes/site-skill.ts +18 -0
- package/src/services/__tests__/auth.test.ts +7 -5
- package/src/services/__tests__/collection.test.ts +159 -44
- package/src/services/__tests__/navigation.test.ts +229 -21
- package/src/services/__tests__/post-timeline.test.ts +109 -30
- package/src/services/__tests__/post.test.ts +567 -5
- package/src/services/__tests__/search.test.ts +48 -1
- package/src/services/__tests__/settings.test.ts +8 -0
- package/src/services/__tests__/site-admin.test.ts +121 -1
- package/src/services/auth.ts +2 -2
- package/src/services/bootstrap.ts +3 -2
- package/src/services/collection.ts +226 -167
- package/src/services/export-theme/assets/client-site.css +1 -1
- package/src/services/export-theme/assets/client-site.js +46 -25
- package/src/services/export-theme/layouts/_default/list.html +18 -4
- package/src/services/export-theme/layouts/_default/rss.xml +8 -12
- package/src/services/export-theme/layouts/featured/list.html +5 -4
- package/src/services/export-theme/layouts/partials/featured-thread.html +56 -0
- package/src/services/export-theme/layouts/partials/footer.html +1 -1
- package/src/services/export-theme/layouts/partials/media-gallery.html +1 -0
- package/src/services/export-theme/styles/main.css +30 -2
- package/src/services/export.ts +91 -80
- package/src/services/mcp.ts +37 -27
- package/src/services/media.ts +1 -1
- package/src/services/navigation.ts +185 -46
- package/src/services/post.ts +874 -271
- package/src/services/search.ts +8 -2
- package/src/services/settings.ts +2 -10
- package/src/services/site-admin.ts +63 -43
- package/src/style-cjk-jp.css +1 -1
- package/src/style-cjk-kr.css +1 -1
- package/src/style-cjk-tc.css +1 -1
- package/src/style-cjk.css +1 -1
- package/src/styles/components.css +540 -0
- package/src/styles/site-media.css +111 -19
- package/src/styles/tokens.css +14 -9
- package/src/styles/ui.css +829 -29
- package/src/types/bindings.ts +3 -0
- package/src/types/config.ts +243 -12
- package/src/types/constants.ts +22 -1
- package/src/types/entities.ts +9 -8
- package/src/types/operations.ts +18 -10
- package/src/types/props.ts +8 -2
- package/src/types/raw-assets.d.ts +5 -0
- package/src/types/views.ts +16 -4
- package/src/ui/__tests__/font-themes.test.ts +42 -30
- package/src/ui/compose/ComposeDialog.tsx +92 -2
- package/src/ui/dash/appearance/NavigationContent.tsx +332 -4
- package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +23 -0
- package/src/ui/dash/settings/ConfigEditorContent.tsx +578 -0
- package/src/ui/dash/settings/GeneralContent.tsx +15 -5
- package/src/ui/dash/settings/SettingsRootContent.tsx +21 -1
- package/src/ui/dash/settings/__tests__/ConfigEditorContent.test.tsx +98 -0
- package/src/ui/dash/settings/__tests__/SettingsRootContent.test.tsx +15 -0
- package/src/ui/feed/CuratedThreadPreview.tsx +16 -4
- package/src/ui/feed/LinkCard.tsx +2 -12
- package/src/ui/feed/NoteCard.tsx +10 -20
- package/src/ui/feed/QuoteCard.tsx +2 -12
- package/src/ui/feed/ThreadPreview.tsx +26 -34
- package/src/ui/feed/TimelineFeed.tsx +2 -2
- package/src/ui/feed/__tests__/thread-preview.test.ts +197 -56
- package/src/ui/feed/__tests__/timeline-cards.test.ts +178 -3
- package/src/ui/feed/thread-preview-state.ts +5 -7
- package/src/ui/font-themes.ts +74 -26
- package/src/ui/layouts/BaseLayout.tsx +22 -8
- package/src/ui/layouts/__tests__/BaseLayout.test.tsx +64 -1
- package/src/ui/pages/CollectionPage.tsx +101 -22
- package/src/ui/pages/CollectionsPage.tsx +3 -1
- package/src/ui/pages/PostPage.tsx +39 -12
- package/src/ui/pages/SearchPage.tsx +6 -14
- package/src/ui/pages/__tests__/ArchivePage.test.tsx +7 -0
- package/src/ui/pages/__tests__/PostPage.test.tsx +88 -0
- package/src/ui/shared/CollectionDirectory.tsx +13 -9
- package/src/ui/shared/CollectionsManager.tsx +17 -7
- package/src/ui/shared/DraftPreviewBar.tsx +33 -0
- package/src/ui/shared/HomePageBranding.tsx +2 -2
- package/src/ui/shared/MediaGallery.tsx +1 -0
- package/src/ui/shared/PostFooter.tsx +3 -1
- package/src/ui/shared/__tests__/home-page-branding.test.tsx +1 -1
- package/src/ui/shared/__tests__/media-gallery.test.ts +1 -0
- package/src/ui/shared/__tests__/navigation-labels.test.ts +2 -2
- package/src/ui/shared/__tests__/post-footer.test.ts +15 -0
- package/src/ui/shared/collection-management-labels.ts +33 -2
- package/src/ui/shared/navigation-labels.ts +2 -4
- package/src/ui/shared/post-article-attributes.ts +46 -0
- package/dist/app-DZDlmh7C.js +0 -6
- package/dist/client/_assets/client-Cevfpm8H.css +0 -2
- package/dist/client/_assets/client-auth-v3yvhbgZ.js +0 -4736
- package/src/client/tiptap/wrapping-input-rules.ts +0 -102
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { resolveConfig } from "../resolve-config.js";
|
|
2
|
+
import { resolveConfig, resolveSummaryConfig } from "../resolve-config.js";
|
|
3
3
|
import type { Bindings } from "../../types/bindings.js";
|
|
4
4
|
|
|
5
5
|
function makeEnv(overrides: Partial<Bindings> = {}): Bindings {
|
|
@@ -28,11 +28,14 @@ describe("resolveConfig", () => {
|
|
|
28
28
|
expect(config.timeZone).toBe("UTC");
|
|
29
29
|
expect(config.showJantBrandingOnHome).toBe(false);
|
|
30
30
|
expect(config.noindex).toBe(false);
|
|
31
|
+
expect(config.publicApiEnabled).toBe(true);
|
|
32
|
+
expect(config.rssFeedsEnabled).toBe(true);
|
|
31
33
|
expect(config.demoMode).toBe(false);
|
|
32
34
|
expect(config.pageSize).toBe(50);
|
|
33
35
|
expect(config.searchPageSize).toBe(50);
|
|
34
36
|
expect(config.archivePageSize).toBe(50);
|
|
35
37
|
expect(config.rssFeedLimit).toBe(50);
|
|
38
|
+
expect(config.rssPublishDelaySeconds).toBe(300);
|
|
36
39
|
});
|
|
37
40
|
|
|
38
41
|
it("DB settings override ENV and defaults", () => {
|
|
@@ -49,6 +52,16 @@ describe("resolveConfig", () => {
|
|
|
49
52
|
expect(config.siteName).toBe("FromEnv");
|
|
50
53
|
});
|
|
51
54
|
|
|
55
|
+
it("lets an explicit empty DB value override an environment fallback", () => {
|
|
56
|
+
const config = resolveConfig(
|
|
57
|
+
makeEnv({ SITE_DESCRIPTION: "From environment" }),
|
|
58
|
+
{ SITE_DESCRIPTION: "" },
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
expect(config.siteDescription).toBe("");
|
|
62
|
+
expect(config.siteDescriptionExplicit).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
|
|
52
65
|
it("resolves mainRssFeed from DB, env, and defaults", () => {
|
|
53
66
|
const defaultConfig = resolveConfig(makeEnv(), {});
|
|
54
67
|
expect(defaultConfig.mainRssFeed).toBe("featured");
|
|
@@ -196,11 +209,15 @@ describe("resolveConfig", () => {
|
|
|
196
209
|
it("resolves boolean fields correctly", () => {
|
|
197
210
|
const config = resolveConfig(makeEnv(), {
|
|
198
211
|
NOINDEX: "true",
|
|
212
|
+
PUBLIC_API_ENABLED: "false",
|
|
213
|
+
RSS_FEEDS_ENABLED: "false",
|
|
199
214
|
SHOW_HEADER_AVATAR: "true",
|
|
200
215
|
SHOW_JANT_BRANDING_ON_HOME: "true",
|
|
201
216
|
});
|
|
202
217
|
|
|
203
218
|
expect(config.noindex).toBe(true);
|
|
219
|
+
expect(config.publicApiEnabled).toBe(false);
|
|
220
|
+
expect(config.rssFeedsEnabled).toBe(false);
|
|
204
221
|
expect(config.showHeaderAvatar).toBe(true);
|
|
205
222
|
expect(config.showJantBrandingOnHome).toBe(true);
|
|
206
223
|
});
|
|
@@ -232,6 +249,7 @@ describe("resolveConfig", () => {
|
|
|
232
249
|
SEARCH_PAGE_SIZE: 7,
|
|
233
250
|
ARCHIVE_PAGE_SIZE: "9",
|
|
234
251
|
RSS_FEED_LIMIT: 25,
|
|
252
|
+
RSS_PUBLISH_DELAY_SECONDS: "600",
|
|
235
253
|
}),
|
|
236
254
|
{},
|
|
237
255
|
);
|
|
@@ -239,6 +257,13 @@ describe("resolveConfig", () => {
|
|
|
239
257
|
expect(config1.searchPageSize).toBe(7);
|
|
240
258
|
expect(config1.archivePageSize).toBe(9);
|
|
241
259
|
expect(config1.rssFeedLimit).toBe(25);
|
|
260
|
+
expect(config1.rssPublishDelaySeconds).toBe(600);
|
|
261
|
+
|
|
262
|
+
const configWithNoDelay = resolveConfig(
|
|
263
|
+
makeEnv({ RSS_PUBLISH_DELAY_SECONDS: 0 }),
|
|
264
|
+
{},
|
|
265
|
+
);
|
|
266
|
+
expect(configWithNoDelay.rssPublishDelaySeconds).toBe(0);
|
|
242
267
|
|
|
243
268
|
const config2 = resolveConfig(
|
|
244
269
|
makeEnv({
|
|
@@ -246,6 +271,7 @@ describe("resolveConfig", () => {
|
|
|
246
271
|
SEARCH_PAGE_SIZE: 0,
|
|
247
272
|
ARCHIVE_PAGE_SIZE: false,
|
|
248
273
|
RSS_FEED_LIMIT: "invalid",
|
|
274
|
+
RSS_PUBLISH_DELAY_SECONDS: "-1",
|
|
249
275
|
}),
|
|
250
276
|
{},
|
|
251
277
|
);
|
|
@@ -253,6 +279,92 @@ describe("resolveConfig", () => {
|
|
|
253
279
|
expect(config2.searchPageSize).toBe(50);
|
|
254
280
|
expect(config2.archivePageSize).toBe(50);
|
|
255
281
|
expect(config2.rssFeedLimit).toBe(50);
|
|
282
|
+
expect(config2.rssPublishDelaySeconds).toBe(300);
|
|
283
|
+
|
|
284
|
+
const configWithBlankDelay = resolveConfig(
|
|
285
|
+
makeEnv({ RSS_PUBLISH_DELAY_SECONDS: " " }),
|
|
286
|
+
{},
|
|
287
|
+
);
|
|
288
|
+
expect(configWithBlankDelay.rssPublishDelaySeconds).toBe(300);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it("resolves runtime numeric settings from DB before environment values", () => {
|
|
292
|
+
const config = resolveConfig(
|
|
293
|
+
makeEnv({
|
|
294
|
+
PAGE_SIZE: "20",
|
|
295
|
+
SEARCH_PAGE_SIZE: "25",
|
|
296
|
+
ARCHIVE_PAGE_SIZE: "30",
|
|
297
|
+
SUMMARY_MAX_PARAGRAPHS: "6",
|
|
298
|
+
SUMMARY_MAX_CHARS: "600",
|
|
299
|
+
RSS_FEED_LIMIT: "60",
|
|
300
|
+
RSS_PUBLISH_DELAY_SECONDS: "600",
|
|
301
|
+
}),
|
|
302
|
+
{
|
|
303
|
+
PAGE_SIZE: "80",
|
|
304
|
+
SUMMARY_MAX_PARAGRAPHS: "12",
|
|
305
|
+
SUMMARY_MAX_CHARS: "1200",
|
|
306
|
+
RSS_FEED_LIMIT: "120",
|
|
307
|
+
RSS_PUBLISH_DELAY_SECONDS: "0",
|
|
308
|
+
},
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
expect(config.pageSize).toBe(80);
|
|
312
|
+
expect(config.searchPageSize).toBe(25);
|
|
313
|
+
expect(config.archivePageSize).toBe(30);
|
|
314
|
+
expect(config.summaryMaxParagraphs).toBe(12);
|
|
315
|
+
expect(config.summaryMaxChars).toBe(1200);
|
|
316
|
+
expect(config.rssFeedLimit).toBe(120);
|
|
317
|
+
expect(config.rssPublishDelaySeconds).toBe(0);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it("inherits runtime page size and rejects out-of-range numeric values", () => {
|
|
321
|
+
const config = resolveConfig(
|
|
322
|
+
makeEnv({
|
|
323
|
+
PAGE_SIZE: "101",
|
|
324
|
+
SEARCH_PAGE_SIZE: "0",
|
|
325
|
+
ARCHIVE_PAGE_SIZE: "1.5",
|
|
326
|
+
SUMMARY_MAX_PARAGRAPHS: "51",
|
|
327
|
+
SUMMARY_MAX_CHARS: "1501",
|
|
328
|
+
RSS_FEED_LIMIT: "201",
|
|
329
|
+
RSS_PUBLISH_DELAY_SECONDS: "7201",
|
|
330
|
+
}),
|
|
331
|
+
{ PAGE_SIZE: "75" },
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
expect(config.pageSize).toBe(75);
|
|
335
|
+
expect(config.searchPageSize).toBe(75);
|
|
336
|
+
expect(config.archivePageSize).toBe(75);
|
|
337
|
+
expect(config.summaryMaxParagraphs).toBe(5);
|
|
338
|
+
expect(config.summaryMaxChars).toBe(500);
|
|
339
|
+
expect(config.rssFeedLimit).toBe(50);
|
|
340
|
+
expect(config.rssPublishDelaySeconds).toBe(300);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it("resolves summary limits without the full app config", () => {
|
|
344
|
+
expect(
|
|
345
|
+
resolveSummaryConfig(
|
|
346
|
+
makeEnv({
|
|
347
|
+
SUMMARY_MAX_PARAGRAPHS: "3",
|
|
348
|
+
SUMMARY_MAX_CHARS: 240,
|
|
349
|
+
}),
|
|
350
|
+
),
|
|
351
|
+
).toEqual({ maxParagraphs: 3, maxChars: 240 });
|
|
352
|
+
|
|
353
|
+
expect(
|
|
354
|
+
resolveSummaryConfig(
|
|
355
|
+
makeEnv({
|
|
356
|
+
SUMMARY_MAX_PARAGRAPHS: 0,
|
|
357
|
+
SUMMARY_MAX_CHARS: "invalid",
|
|
358
|
+
}),
|
|
359
|
+
),
|
|
360
|
+
).toEqual({ maxParagraphs: 5, maxChars: 500 });
|
|
361
|
+
|
|
362
|
+
expect(
|
|
363
|
+
resolveSummaryConfig(makeEnv({ SUMMARY_MAX_CHARS: "240" }), {
|
|
364
|
+
SUMMARY_MAX_PARAGRAPHS: "8",
|
|
365
|
+
SUMMARY_MAX_CHARS: "900",
|
|
366
|
+
}),
|
|
367
|
+
).toEqual({ maxParagraphs: 8, maxChars: 900 });
|
|
256
368
|
});
|
|
257
369
|
|
|
258
370
|
it("resolves fallbacks without DB values", () => {
|
|
@@ -801,6 +801,88 @@ describe("extractSummaryHtml", () => {
|
|
|
801
801
|
expect(result!.html).toContain(">Link text</a>");
|
|
802
802
|
});
|
|
803
803
|
|
|
804
|
+
it("includes referenced footnote definitions with the requested namespace", () => {
|
|
805
|
+
const doc = JSON.stringify({
|
|
806
|
+
type: "doc",
|
|
807
|
+
content: [
|
|
808
|
+
{
|
|
809
|
+
type: "paragraph",
|
|
810
|
+
content: [
|
|
811
|
+
{ type: "text", text: "Visible" },
|
|
812
|
+
{ type: "footnoteReference", attrs: { label: "note" } },
|
|
813
|
+
],
|
|
814
|
+
},
|
|
815
|
+
{
|
|
816
|
+
type: "paragraph",
|
|
817
|
+
content: [{ type: "text", text: "Hidden tail" }],
|
|
818
|
+
},
|
|
819
|
+
{
|
|
820
|
+
type: "footnoteDefinition",
|
|
821
|
+
attrs: { label: "note" },
|
|
822
|
+
content: [
|
|
823
|
+
{
|
|
824
|
+
type: "paragraph",
|
|
825
|
+
content: [{ type: "text", text: "Definition" }],
|
|
826
|
+
},
|
|
827
|
+
],
|
|
828
|
+
},
|
|
829
|
+
],
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
const result = extractSummaryHtml(doc, 1, 500, 0, {
|
|
833
|
+
namespace: "pst_summary",
|
|
834
|
+
});
|
|
835
|
+
|
|
836
|
+
expect(result?.hasMore).toBe(true);
|
|
837
|
+
expect(result?.html).toContain('id="fn-0pba1ne36oeor-1"');
|
|
838
|
+
expect(result?.html).toContain('role="doc-endnotes"');
|
|
839
|
+
expect(result?.html.match(/<ol class="footnote-list">/g)).toHaveLength(1);
|
|
840
|
+
expect(result?.html).not.toMatch(
|
|
841
|
+
/footnote-document|footnote-main|data-footnote-|--footnote-/,
|
|
842
|
+
);
|
|
843
|
+
expect(result?.html).not.toContain("Hidden tail");
|
|
844
|
+
expect(result!.html.indexOf("Definition")).toBeGreaterThan(
|
|
845
|
+
result!.html.indexOf("Visible"),
|
|
846
|
+
);
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
it("omits a footnote when its first reference is beyond the summary boundary", () => {
|
|
850
|
+
const doc = JSON.stringify({
|
|
851
|
+
type: "doc",
|
|
852
|
+
content: [
|
|
853
|
+
{
|
|
854
|
+
type: "paragraph",
|
|
855
|
+
content: [{ type: "text", text: "Visible summary" }],
|
|
856
|
+
},
|
|
857
|
+
{
|
|
858
|
+
type: "paragraph",
|
|
859
|
+
content: [
|
|
860
|
+
{ type: "text", text: "Hidden reference" },
|
|
861
|
+
{ type: "footnoteReference", attrs: { label: "note" } },
|
|
862
|
+
],
|
|
863
|
+
},
|
|
864
|
+
{
|
|
865
|
+
type: "footnoteDefinition",
|
|
866
|
+
attrs: { label: "note" },
|
|
867
|
+
content: [
|
|
868
|
+
{
|
|
869
|
+
type: "paragraph",
|
|
870
|
+
content: [{ type: "text", text: "Hidden definition" }],
|
|
871
|
+
},
|
|
872
|
+
],
|
|
873
|
+
},
|
|
874
|
+
],
|
|
875
|
+
});
|
|
876
|
+
|
|
877
|
+
const result = extractSummaryHtml(doc, 1, 500);
|
|
878
|
+
|
|
879
|
+
expect(result?.hasMore).toBe(true);
|
|
880
|
+
expect(result?.html).toContain("Visible summary");
|
|
881
|
+
expect(result?.html).not.toContain("Hidden reference");
|
|
882
|
+
expect(result?.html).not.toContain("Hidden definition");
|
|
883
|
+
expect(result?.html).not.toContain('role="doc-endnotes"');
|
|
884
|
+
});
|
|
885
|
+
|
|
804
886
|
function paragraphsDoc(...texts: string[]): string {
|
|
805
887
|
return JSON.stringify({
|
|
806
888
|
type: "doc",
|
|
@@ -18,7 +18,7 @@ import { createPostService } from "../../services/post.js";
|
|
|
18
18
|
import { createMediaService } from "../../services/media.js";
|
|
19
19
|
import { createPathService } from "../../services/path.js";
|
|
20
20
|
import { createCollectionService } from "../../services/collection.js";
|
|
21
|
-
import {
|
|
21
|
+
import { posts as postTable, sites } from "../../db/schema.js";
|
|
22
22
|
import { buildMediaMap } from "../media-helpers.js";
|
|
23
23
|
import {
|
|
24
24
|
assembleCollectionTimeline,
|
|
@@ -157,18 +157,14 @@ describe("Timeline data assembly", () => {
|
|
|
157
157
|
return {
|
|
158
158
|
post: postWithMedia,
|
|
159
159
|
threadPreview: {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
...threadCtx.penultimateReply,
|
|
169
|
-
mediaAttachments: [],
|
|
170
|
-
}
|
|
171
|
-
: undefined,
|
|
160
|
+
leadingReplies: threadCtx.leadingReplies.map((reply) => ({
|
|
161
|
+
...reply,
|
|
162
|
+
mediaAttachments: [],
|
|
163
|
+
})),
|
|
164
|
+
trailingReplies: threadCtx.trailingReplies.map((reply) => ({
|
|
165
|
+
...reply,
|
|
166
|
+
mediaAttachments: [],
|
|
167
|
+
})),
|
|
172
168
|
latestReply: {
|
|
173
169
|
...threadCtx.latestReply,
|
|
174
170
|
mediaAttachments: [],
|
|
@@ -183,7 +179,9 @@ describe("Timeline data assembly", () => {
|
|
|
183
179
|
|
|
184
180
|
expect(items).toHaveLength(1);
|
|
185
181
|
expect(items[0]?.threadPreview).toBeDefined();
|
|
186
|
-
expect(items[0]?.threadPreview?.
|
|
182
|
+
expect(items[0]?.threadPreview?.leadingReplies[0]?.bodyText).toBe(
|
|
183
|
+
"Reply 1",
|
|
184
|
+
);
|
|
187
185
|
expect(items[0]?.threadPreview?.latestReply.bodyText).toBe("Reply 2");
|
|
188
186
|
expect(items[0]?.threadPreview?.totalReplyCount).toBe(2);
|
|
189
187
|
});
|
|
@@ -347,6 +345,7 @@ describe("Timeline data assembly", () => {
|
|
|
347
345
|
|
|
348
346
|
expect(result.items).toHaveLength(1);
|
|
349
347
|
expect(result.items[0]?.post.id).toBe(root.id);
|
|
348
|
+
expect(result.items[0]?.curatedThread?.showContextRatings).toBe(false);
|
|
350
349
|
expect(result.items[0]?.curatedThread?.segments).toEqual([
|
|
351
350
|
expect.objectContaining({
|
|
352
351
|
post: expect.objectContaining({ id: root.id }),
|
|
@@ -526,22 +525,26 @@ describe("Timeline data assembly", () => {
|
|
|
526
525
|
]);
|
|
527
526
|
});
|
|
528
527
|
|
|
529
|
-
it("orders
|
|
528
|
+
it("orders Featured Threads by the latest selected Post publication time", async () => {
|
|
530
529
|
const olderFeaturedRoot = await postService.create({
|
|
531
530
|
format: "note",
|
|
532
531
|
bodyMarkdown: "Older featured root",
|
|
532
|
+
publishedAt: 1000,
|
|
533
533
|
});
|
|
534
534
|
const olderFeaturedReply = await postService.create({
|
|
535
535
|
format: "note",
|
|
536
536
|
bodyMarkdown: "Older featured reply",
|
|
537
537
|
replyToId: olderFeaturedRoot.id,
|
|
538
|
+
publishedAt: 3000,
|
|
538
539
|
});
|
|
539
540
|
const newerFeaturedRoot = await postService.create({
|
|
540
541
|
format: "note",
|
|
541
542
|
bodyMarkdown: "Newer featured root",
|
|
542
543
|
featured: true,
|
|
544
|
+
publishedAt: 2000,
|
|
543
545
|
});
|
|
544
546
|
|
|
547
|
+
// Featured timestamps deliberately disagree with publication order.
|
|
545
548
|
await db
|
|
546
549
|
.update(postTable)
|
|
547
550
|
.set({ featuredAt: 100 })
|
|
@@ -555,6 +558,7 @@ describe("Timeline data assembly", () => {
|
|
|
555
558
|
format: "note",
|
|
556
559
|
bodyMarkdown: "Later non-featured reply",
|
|
557
560
|
replyToId: olderFeaturedReply.id,
|
|
561
|
+
publishedAt: 4000,
|
|
558
562
|
});
|
|
559
563
|
|
|
560
564
|
const result = await assembleFeaturedTimeline(createTimelineContext(), {
|
|
@@ -562,11 +566,63 @@ describe("Timeline data assembly", () => {
|
|
|
562
566
|
});
|
|
563
567
|
|
|
564
568
|
expect(result.items).toHaveLength(2);
|
|
565
|
-
expect(result.items[0]?.post.id).toBe(
|
|
566
|
-
expect(result.items[1]?.post.id).toBe(
|
|
569
|
+
expect(result.items[0]?.post.id).toBe(olderFeaturedRoot.id);
|
|
570
|
+
expect(result.items[1]?.post.id).toBe(newerFeaturedRoot.id);
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
it("loads only the Root, Featured Posts, and final Post for curated display", async () => {
|
|
574
|
+
const root = await postService.create({
|
|
575
|
+
format: "note",
|
|
576
|
+
bodyMarkdown: "Root",
|
|
577
|
+
publishedAt: 1000,
|
|
578
|
+
});
|
|
579
|
+
const hiddenReplyA = await postService.create({
|
|
580
|
+
format: "note",
|
|
581
|
+
bodyMarkdown: "Hidden reply A",
|
|
582
|
+
replyToId: root.id,
|
|
583
|
+
publishedAt: 2000,
|
|
584
|
+
});
|
|
585
|
+
const featuredReply = await postService.create({
|
|
586
|
+
format: "note",
|
|
587
|
+
bodyMarkdown: "Featured reply",
|
|
588
|
+
replyToId: hiddenReplyA.id,
|
|
589
|
+
featured: true,
|
|
590
|
+
publishedAt: 3000,
|
|
591
|
+
});
|
|
592
|
+
const hiddenReplyB = await postService.create({
|
|
593
|
+
format: "note",
|
|
594
|
+
bodyMarkdown: "Hidden reply B",
|
|
595
|
+
replyToId: featuredReply.id,
|
|
596
|
+
publishedAt: 4000,
|
|
597
|
+
});
|
|
598
|
+
const finalReply = await postService.create({
|
|
599
|
+
format: "note",
|
|
600
|
+
bodyMarkdown: "Final reply",
|
|
601
|
+
replyToId: hiddenReplyB.id,
|
|
602
|
+
publishedAt: 5000,
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
const result = await postService.getFeaturedThreadTimelineData([root.id]);
|
|
606
|
+
const thread = result.get(root.id);
|
|
607
|
+
|
|
608
|
+
expect(thread?.posts).toEqual([
|
|
609
|
+
expect.objectContaining({
|
|
610
|
+
post: expect.objectContaining({ id: root.id }),
|
|
611
|
+
position: 0,
|
|
612
|
+
}),
|
|
613
|
+
expect.objectContaining({
|
|
614
|
+
post: expect.objectContaining({ id: featuredReply.id }),
|
|
615
|
+
position: 2,
|
|
616
|
+
}),
|
|
617
|
+
expect.objectContaining({
|
|
618
|
+
post: expect.objectContaining({ id: finalReply.id }),
|
|
619
|
+
position: 4,
|
|
620
|
+
}),
|
|
621
|
+
]);
|
|
622
|
+
expect(thread?.featuredPostIds).toEqual([featuredReply.id]);
|
|
567
623
|
});
|
|
568
624
|
|
|
569
|
-
it("
|
|
625
|
+
it("renders every post in each collected Thread and sorts by Thread activity", async () => {
|
|
570
626
|
const collection = await collectionService.create({
|
|
571
627
|
slug: "reading",
|
|
572
628
|
title: "Reading",
|
|
@@ -595,26 +651,12 @@ describe("Timeline data assembly", () => {
|
|
|
595
651
|
bodyMarkdown: "Second thread root",
|
|
596
652
|
});
|
|
597
653
|
|
|
598
|
-
await
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
},
|
|
605
|
-
{
|
|
606
|
-
siteId: DEFAULT_TEST_SITE_ID,
|
|
607
|
-
postId: collectedReplyB.id,
|
|
608
|
-
collectionId: collection.id,
|
|
609
|
-
createdAt: 200,
|
|
610
|
-
},
|
|
611
|
-
{
|
|
612
|
-
siteId: DEFAULT_TEST_SITE_ID,
|
|
613
|
-
postId: secondRoot.id,
|
|
614
|
-
collectionId: collection.id,
|
|
615
|
-
createdAt: 300,
|
|
616
|
-
},
|
|
617
|
-
]);
|
|
654
|
+
await collectionService.addThread(collection.id, firstRoot.id, {
|
|
655
|
+
createdAt: 200,
|
|
656
|
+
});
|
|
657
|
+
await collectionService.addThread(collection.id, secondRoot.id, {
|
|
658
|
+
createdAt: 300,
|
|
659
|
+
});
|
|
618
660
|
|
|
619
661
|
const result = await assembleCollectionTimeline(createTimelineContext(), {
|
|
620
662
|
collectionIds: [collection.id],
|
|
@@ -625,6 +667,7 @@ describe("Timeline data assembly", () => {
|
|
|
625
667
|
expect(result.items).toHaveLength(2);
|
|
626
668
|
expect(result.items[0]?.post.id).toBe(secondRoot.id);
|
|
627
669
|
expect(result.items[1]?.post.id).toBe(firstRoot.id);
|
|
670
|
+
expect(result.items[1]?.curatedThread?.showContextRatings).toBe(true);
|
|
628
671
|
expect(result.items[1]?.curatedThread?.segments).toEqual([
|
|
629
672
|
expect.objectContaining({
|
|
630
673
|
post: expect.objectContaining({ id: firstRoot.id }),
|
|
@@ -634,17 +677,22 @@ describe("Timeline data assembly", () => {
|
|
|
634
677
|
expect.objectContaining({
|
|
635
678
|
post: expect.objectContaining({ id: collectedReplyA.id }),
|
|
636
679
|
hiddenBeforeCount: 0,
|
|
637
|
-
highlighted:
|
|
680
|
+
highlighted: false,
|
|
681
|
+
}),
|
|
682
|
+
expect.objectContaining({
|
|
683
|
+
post: expect.objectContaining({ id: hiddenMiddleReply.id }),
|
|
684
|
+
hiddenBeforeCount: 0,
|
|
685
|
+
highlighted: false,
|
|
638
686
|
}),
|
|
639
687
|
expect.objectContaining({
|
|
640
688
|
post: expect.objectContaining({ id: collectedReplyB.id }),
|
|
641
|
-
hiddenBeforeCount:
|
|
689
|
+
hiddenBeforeCount: 0,
|
|
642
690
|
highlighted: true,
|
|
643
691
|
}),
|
|
644
692
|
]);
|
|
645
693
|
});
|
|
646
694
|
|
|
647
|
-
it("
|
|
695
|
+
it("renders the complete Thread and highlights its latest Post", async () => {
|
|
648
696
|
const collection = await collectionService.create({
|
|
649
697
|
slug: "root-only",
|
|
650
698
|
title: "Root only",
|
|
@@ -659,10 +707,7 @@ describe("Timeline data assembly", () => {
|
|
|
659
707
|
replyToId: root.id,
|
|
660
708
|
});
|
|
661
709
|
|
|
662
|
-
await
|
|
663
|
-
siteId: DEFAULT_TEST_SITE_ID,
|
|
664
|
-
postId: root.id,
|
|
665
|
-
collectionId: collection.id,
|
|
710
|
+
await collectionService.addThread(collection.id, root.id, {
|
|
666
711
|
createdAt: 100,
|
|
667
712
|
});
|
|
668
713
|
|
|
@@ -678,17 +723,17 @@ describe("Timeline data assembly", () => {
|
|
|
678
723
|
expect.objectContaining({
|
|
679
724
|
post: expect.objectContaining({ id: root.id }),
|
|
680
725
|
hiddenBeforeCount: 0,
|
|
681
|
-
highlighted:
|
|
726
|
+
highlighted: false,
|
|
682
727
|
}),
|
|
683
728
|
expect.objectContaining({
|
|
684
729
|
post: expect.objectContaining({ id: uncollectedReply.id }),
|
|
685
730
|
hiddenBeforeCount: 0,
|
|
686
|
-
highlighted:
|
|
731
|
+
highlighted: true,
|
|
687
732
|
}),
|
|
688
733
|
]);
|
|
689
734
|
});
|
|
690
735
|
|
|
691
|
-
it("
|
|
736
|
+
it("deduplicates Threads selected through multiple collections", async () => {
|
|
692
737
|
const smart = await collectionService.create({
|
|
693
738
|
slug: "smart",
|
|
694
739
|
title: "Smart",
|
|
@@ -721,26 +766,15 @@ describe("Timeline data assembly", () => {
|
|
|
721
766
|
bodyMarkdown: "Second thread root",
|
|
722
767
|
});
|
|
723
768
|
|
|
724
|
-
await
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
postId: movieReply.id,
|
|
734
|
-
collectionId: movies.id,
|
|
735
|
-
createdAt: 200,
|
|
736
|
-
},
|
|
737
|
-
{
|
|
738
|
-
siteId: DEFAULT_TEST_SITE_ID,
|
|
739
|
-
postId: secondRoot.id,
|
|
740
|
-
collectionId: movies.id,
|
|
741
|
-
createdAt: 300,
|
|
742
|
-
},
|
|
743
|
-
]);
|
|
769
|
+
await collectionService.addThread(smart.id, firstRoot.id, {
|
|
770
|
+
createdAt: 100,
|
|
771
|
+
});
|
|
772
|
+
await collectionService.addThread(movies.id, firstRoot.id, {
|
|
773
|
+
createdAt: 200,
|
|
774
|
+
});
|
|
775
|
+
await collectionService.addThread(movies.id, secondRoot.id, {
|
|
776
|
+
createdAt: 300,
|
|
777
|
+
});
|
|
744
778
|
|
|
745
779
|
const result = await assembleCollectionTimeline(createTimelineContext(), {
|
|
746
780
|
collectionIds: [smart.id, movies.id],
|
|
@@ -760,11 +794,16 @@ describe("Timeline data assembly", () => {
|
|
|
760
794
|
expect.objectContaining({
|
|
761
795
|
post: expect.objectContaining({ id: smartReply.id }),
|
|
762
796
|
hiddenBeforeCount: 0,
|
|
763
|
-
highlighted:
|
|
797
|
+
highlighted: false,
|
|
798
|
+
}),
|
|
799
|
+
expect.objectContaining({
|
|
800
|
+
post: expect.objectContaining({ id: hiddenMiddleReply.id }),
|
|
801
|
+
hiddenBeforeCount: 0,
|
|
802
|
+
highlighted: false,
|
|
764
803
|
}),
|
|
765
804
|
expect.objectContaining({
|
|
766
805
|
post: expect.objectContaining({ id: movieReply.id }),
|
|
767
|
-
hiddenBeforeCount:
|
|
806
|
+
hiddenBeforeCount: 0,
|
|
768
807
|
highlighted: true,
|
|
769
808
|
}),
|
|
770
809
|
]);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
2
|
import {
|
|
3
3
|
TIMEZONES,
|
|
4
|
+
getTimeZoneOptions,
|
|
4
5
|
isSupportedTimeZone,
|
|
5
6
|
mapIanaToTimezone,
|
|
6
7
|
normalizeTimeZone,
|
|
@@ -69,8 +70,15 @@ describe("normalizeTimeZone", () => {
|
|
|
69
70
|
expect(normalizeTimeZone("London")).toBe("Europe/London");
|
|
70
71
|
});
|
|
71
72
|
|
|
72
|
-
it("
|
|
73
|
-
expect(normalizeTimeZone("
|
|
73
|
+
it("preserves runtime-supported zones instead of collapsing their rules", () => {
|
|
74
|
+
expect(normalizeTimeZone("America/Phoenix")).toBe("America/Phoenix");
|
|
75
|
+
expect(normalizeTimeZone("Australia/Darwin")).toBe("Australia/Darwin");
|
|
76
|
+
expect(normalizeTimeZone("Etc/GMT-8")).toBe("Etc/GMT-8");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("normalizes fixed offsets and UTC aliases through Intl", () => {
|
|
80
|
+
expect(normalizeTimeZone("+08")).toBe("+08:00");
|
|
81
|
+
expect(normalizeTimeZone("+08:00")).toBe("+08:00");
|
|
74
82
|
expect(normalizeTimeZone("Etc/UTC")).toBe("UTC");
|
|
75
83
|
});
|
|
76
84
|
|
|
@@ -81,9 +89,13 @@ describe("normalizeTimeZone", () => {
|
|
|
81
89
|
});
|
|
82
90
|
|
|
83
91
|
describe("isSupportedTimeZone", () => {
|
|
84
|
-
it("accepts
|
|
92
|
+
it("accepts runtime zones, fixed offsets, and legacy values", () => {
|
|
85
93
|
expect(isSupportedTimeZone("Asia/Shanghai")).toBe(true);
|
|
86
94
|
expect(isSupportedTimeZone("Asia/Calcutta")).toBe(true);
|
|
95
|
+
expect(isSupportedTimeZone("America/Phoenix")).toBe(true);
|
|
96
|
+
expect(isSupportedTimeZone("Australia/Darwin")).toBe(true);
|
|
97
|
+
expect(isSupportedTimeZone("Etc/GMT-8")).toBe(true);
|
|
98
|
+
expect(isSupportedTimeZone("+08:00")).toBe(true);
|
|
87
99
|
expect(isSupportedTimeZone("Beijing")).toBe(true);
|
|
88
100
|
});
|
|
89
101
|
|
|
@@ -92,3 +104,19 @@ describe("isSupportedTimeZone", () => {
|
|
|
92
104
|
expect(isSupportedTimeZone("Unknown/Zone")).toBe(false);
|
|
93
105
|
});
|
|
94
106
|
});
|
|
107
|
+
|
|
108
|
+
describe("getTimeZoneOptions", () => {
|
|
109
|
+
it("adds an unlisted current timezone without expanding the curated list", () => {
|
|
110
|
+
const options = getTimeZoneOptions("America/Phoenix");
|
|
111
|
+
|
|
112
|
+
expect(options).toHaveLength(TIMEZONES.length + 1);
|
|
113
|
+
expect(options.at(-1)).toMatchObject({
|
|
114
|
+
label: "America/Phoenix",
|
|
115
|
+
value: "America/Phoenix",
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("does not duplicate a curated timezone", () => {
|
|
120
|
+
expect(getTimeZoneOptions("Asia/Shanghai")).toBe(TIMEZONES);
|
|
121
|
+
});
|
|
122
|
+
});
|