@jant/core 0.6.14 → 0.6.16
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 +88 -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-C75Zc8dD.js +6 -0
- package/dist/{app-2i7-WQHg.js → app-C7_4oab9.js} +7774 -6425
- package/dist/client/.vite/manifest.json +8 -8
- package/dist/client/_assets/chunks/{url-pLre2DM_.js → url-CU9pEbk5.js} +1 -1
- package/dist/client/_assets/{client-BvID5ucz.js → client-Bq0vre8Y.js} +59 -38
- package/dist/client/_assets/client-D3VZyG4L.css +2 -0
- package/dist/client/_assets/client-auth-F2rb9WfF.js +5571 -0
- package/dist/{env-OHRKGcMj.js → env-BPYViDJJ.js} +1 -1
- package/dist/{export-DWn149b7.js → export-Dl5KCiEs.js} +1348 -128
- package/dist/{github-api-BgSiE71w.js → github-api-BNF35Lkx.js} +1 -1
- package/dist/{github-app-BbklkFmU.js → github-app-DdiD-bC4.js} +1 -1
- package/dist/{github-sync-C0zyF6XS.js → github-sync-Bqg62IvV.js} +3 -3
- package/dist/{github-sync-Ckk0sJxK.js → github-sync-HdK2EgvJ.js} +3 -3
- package/dist/index.js +5 -5
- package/dist/node.js +7 -7
- package/dist/{url-BMYO-Zlt.js → url-CbLAtlZe.js} +517 -12
- 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__/mise-config.test.ts +22 -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 +316 -2
- package/src/client/__tests__/compose-shortcuts.test.ts +20 -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 +82 -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__/compose-format-convert.test.ts +0 -19
- package/src/client/components/__tests__/jant-collection-directory.test.ts +155 -6
- package/src/client/components/__tests__/jant-command-palette.test.ts +51 -0
- package/src/client/components/__tests__/jant-compose-dialog.test.ts +2268 -206
- package/src/client/components/__tests__/jant-compose-editor.test.ts +147 -59
- package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +114 -64
- 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-format-convert.ts +5 -1
- package/src/client/components/compose-types.ts +55 -3
- package/src/client/components/jant-collection-directory.ts +165 -30
- package/src/client/components/jant-command-palette.ts +14 -7
- package/src/client/components/jant-compose-dialog.ts +1542 -694
- package/src/client/components/jant-compose-editor.ts +237 -99
- package/src/client/components/jant-compose-fullscreen.ts +27 -9
- 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 +177 -95
- 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 +147 -123
- package/src/client/compose-launch.ts +13 -0
- package/src/client/compose-shortcuts.ts +5 -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/post-refresh.ts +135 -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__/link-input-rules.test.ts +121 -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/link-input-rules.ts +4 -4
- 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 +133 -39
- package/src/client-auth.ts +1 -0
- package/src/client.ts +1 -0
- package/src/db/__tests__/backfill-thread-activity.test.ts +234 -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-activity.test.ts +113 -0
- package/src/db/__tests__/thread-collection-migration.test.ts +364 -0
- package/src/db/backfills/0005_split_thread_activity_from_quiet_replies.sql +68 -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/0031_many_ego.sql +3 -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/0031_snapshot.json +2562 -0
- package/src/db/migrations/meta/_journal.json +35 -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/0029_rare_hairball.sql +3 -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/0029_snapshot.json +3289 -0
- package/src/db/migrations/pg/meta/_journal.json +35 -0
- package/src/db/pg/schema.ts +46 -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 +48 -20
- package/src/db/thread-activity.ts +101 -0
- package/src/i18n/__tests__/detect.test.ts +8 -2
- package/src/i18n/detect.ts +14 -5
- package/src/i18n/locales/public/en.po +222 -51
- package/src/i18n/locales/public/en.ts +1 -1
- package/src/i18n/locales/public/zh-Hans.po +216 -45
- package/src/i18n/locales/public/zh-Hans.ts +1 -1
- package/src/i18n/locales/public/zh-Hant.po +216 -45
- 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__/feed.test.ts +168 -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 +24 -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__/slug.test.ts +18 -0
- 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__/translit.test.ts +87 -0
- package/src/lib/__tests__/url.test.ts +53 -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/feed.ts +113 -11
- package/src/lib/footnotes.ts +264 -0
- package/src/lib/github-sync-site-config.ts +2 -0
- package/src/lib/hugo-markdown.ts +15 -3
- package/src/lib/jant-branding.ts +1 -0
- package/src/lib/link-preview.ts +25 -0
- package/src/lib/markdown-manager.ts +193 -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 +47 -8
- 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 +137 -131
- package/src/lib/timezones.ts +54 -14
- package/src/lib/tiptap-render.ts +359 -83
- package/src/lib/translit.ts +288 -0
- package/src/lib/url.ts +123 -12
- package/src/lib/view.ts +58 -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__/palette.test.ts +44 -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 +14 -10
- package/src/routes/api/public/__tests__/archive.test.ts +35 -0
- package/src/routes/api/public/__tests__/posts.test.ts +246 -0
- package/src/routes/api/public/archive.ts +4 -0
- package/src/routes/api/public/posts.ts +48 -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 +8 -3
- package/src/routes/dash/settings.tsx +45 -2
- package/src/routes/feed/__tests__/feed.test.ts +210 -6
- package/src/routes/feed/__tests__/sitemap.test.ts +68 -1
- package/src/routes/feed/feed.ts +23 -14
- package/src/routes/pages/__tests__/archive-params.test.ts +384 -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__/featured.test.ts +6 -2
- package/src/routes/pages/__tests__/preview.test.ts +176 -0
- package/src/routes/pages/__tests__/search.test.ts +69 -0
- package/src/routes/pages/archive.tsx +90 -22
- package/src/routes/pages/collection.tsx +32 -19
- package/src/routes/pages/collections.tsx +6 -1
- package/src/routes/pages/page.tsx +86 -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 +190 -44
- package/src/services/__tests__/navigation.test.ts +229 -21
- package/src/services/__tests__/post-timeline.test.ts +249 -39
- package/src/services/__tests__/post.test.ts +844 -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 +224 -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 +94 -85
- package/src/services/mcp.ts +37 -27
- package/src/services/media.ts +1 -1
- package/src/services/navigation.ts +185 -46
- package/src/services/path.ts +4 -1
- package/src/services/post.ts +1142 -342
- package/src/services/search.ts +17 -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 -14
- package/src/styles/site-media.css +111 -19
- package/src/styles/tokens.css +76 -10
- package/src/styles/ui.css +2227 -1347
- 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 +14 -8
- package/src/types/operations.ts +18 -10
- package/src/types/props.ts +20 -2
- package/src/types/raw-assets.d.ts +5 -0
- package/src/types/views.ts +22 -4
- package/src/ui/__tests__/font-themes.test.ts +42 -30
- package/src/ui/compose/ComposeDialog.tsx +109 -19
- 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/LinkPreview.tsx +2 -7
- package/src/ui/feed/NoteCard.tsx +10 -20
- package/src/ui/feed/PostStatusBadges.tsx +42 -3
- 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/ArchivePage.tsx +131 -24
- 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 +70 -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 +49 -0
- package/src/ui/shared/HomePageBranding.tsx +2 -2
- package/src/ui/shared/MediaGallery.tsx +1 -0
- package/src/ui/shared/PostFooter.tsx +38 -13
- 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/custom-icons.ts +5 -0
- package/src/ui/shared/navigation-labels.ts +2 -4
- package/src/ui/shared/post-article-attributes.ts +52 -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,4 +1,4 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
2
|
import { createTestApp } from "../../../__tests__/helpers/app.js";
|
|
3
3
|
import { archiveRoutes } from "../archive.js";
|
|
4
4
|
|
|
@@ -21,13 +21,45 @@ async function fetchFeed(
|
|
|
21
21
|
return res.text();
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
function setupApp() {
|
|
25
|
-
const { app, services } = createTestApp({
|
|
24
|
+
function setupApp(rssPublishDelaySeconds = 0) {
|
|
25
|
+
const { app, services } = createTestApp({
|
|
26
|
+
authenticated: false,
|
|
27
|
+
rssPublishDelaySeconds,
|
|
28
|
+
});
|
|
26
29
|
app.route("/archive", archiveRoutes);
|
|
27
30
|
return { app, services };
|
|
28
31
|
}
|
|
29
32
|
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
vi.restoreAllMocks();
|
|
35
|
+
});
|
|
36
|
+
|
|
30
37
|
describe("archive feed filter params", () => {
|
|
38
|
+
it("applies the RSS publication delay", async () => {
|
|
39
|
+
const currentTime = 2_000_000;
|
|
40
|
+
vi.spyOn(Date, "now").mockReturnValue(currentTime * 1000);
|
|
41
|
+
const { app, services } = setupApp(300);
|
|
42
|
+
await services.posts.create({
|
|
43
|
+
format: "note",
|
|
44
|
+
title: "Eligible Archive Post",
|
|
45
|
+
bodyMarkdown: "Old enough for RSS",
|
|
46
|
+
status: "published",
|
|
47
|
+
publishedAt: currentTime - 300,
|
|
48
|
+
});
|
|
49
|
+
await services.posts.create({
|
|
50
|
+
format: "note",
|
|
51
|
+
title: "Recent Archive Post",
|
|
52
|
+
bodyMarkdown: "Still inside the edit window",
|
|
53
|
+
status: "published",
|
|
54
|
+
publishedAt: currentTime,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const xml = await fetchFeed(app, "");
|
|
58
|
+
|
|
59
|
+
expect(xml).toContain("Eligible Archive Post");
|
|
60
|
+
expect(xml).not.toContain("Recent Archive Post");
|
|
61
|
+
});
|
|
62
|
+
|
|
31
63
|
it("filters by title with the new param and the legacy fallback", async () => {
|
|
32
64
|
const { app, services } = setupApp();
|
|
33
65
|
await services.posts.create({
|
|
@@ -84,10 +116,23 @@ describe("archive feed filter params", () => {
|
|
|
84
116
|
format: "note",
|
|
85
117
|
bodyMarkdown: "text only body",
|
|
86
118
|
});
|
|
119
|
+
await services.media.create({
|
|
120
|
+
filename: "unattached.jpg",
|
|
121
|
+
originalName: "unattached.jpg",
|
|
122
|
+
mimeType: "image/jpeg",
|
|
123
|
+
size: 1024,
|
|
124
|
+
storageKey: "media/unattached.jpg",
|
|
125
|
+
});
|
|
87
126
|
|
|
88
127
|
const fresh = await fetchFeed(app, "?media=none");
|
|
89
128
|
expect(fresh).toContain("text only body");
|
|
90
129
|
|
|
130
|
+
const page = await app.request("/archive?media=none");
|
|
131
|
+
expect(page.status).toBe(200);
|
|
132
|
+
const pageHtml = await page.text();
|
|
133
|
+
expect(pageHtml).toContain('aria-label="1 thread"');
|
|
134
|
+
expect(pageHtml).toContain("text only body");
|
|
135
|
+
|
|
91
136
|
const legacy = await fetchFeed(app, "?hasMedia=0");
|
|
92
137
|
expect(legacy).toContain("text only body");
|
|
93
138
|
|
|
@@ -96,6 +141,134 @@ describe("archive feed filter params", () => {
|
|
|
96
141
|
});
|
|
97
142
|
});
|
|
98
143
|
|
|
144
|
+
describe("archive feed ordering", () => {
|
|
145
|
+
// The feed belongs to the archive page, so it uses the page's default axis.
|
|
146
|
+
// Ordering by activity would let a reply to an old Thread reshuffle the
|
|
147
|
+
// feed window under a fixed rssFeedLimit, and readers key entries by id
|
|
148
|
+
// anyway — a moved entry is not a re-surfaced one. /latest/feed is where
|
|
149
|
+
// activity ordering lives.
|
|
150
|
+
it("orders by publication, not by recent replies", async () => {
|
|
151
|
+
const { app, services } = setupApp();
|
|
152
|
+
const oldThread = await services.posts.create({
|
|
153
|
+
format: "note",
|
|
154
|
+
title: "Old thread with a new reply",
|
|
155
|
+
bodyMarkdown: "root from 2019",
|
|
156
|
+
publishedAt: Date.UTC(2019, 0, 1) / 1000,
|
|
157
|
+
});
|
|
158
|
+
await services.posts.create({
|
|
159
|
+
format: "note",
|
|
160
|
+
bodyMarkdown: "reply from 2026",
|
|
161
|
+
replyToId: oldThread.id,
|
|
162
|
+
publishedAt: Date.UTC(2026, 5, 1) / 1000,
|
|
163
|
+
});
|
|
164
|
+
await services.posts.create({
|
|
165
|
+
format: "note",
|
|
166
|
+
title: "Plain newer post",
|
|
167
|
+
bodyMarkdown: "root from 2025",
|
|
168
|
+
publishedAt: Date.UTC(2025, 0, 1) / 1000,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const xml = await fetchFeed(app, "");
|
|
172
|
+
|
|
173
|
+
expect(xml.indexOf("Plain newer post")).toBeLessThan(
|
|
174
|
+
xml.indexOf("Old thread with a new reply"),
|
|
175
|
+
);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("honors an explicit sort=updated, quiet replies included", async () => {
|
|
179
|
+
const { app, services } = setupApp();
|
|
180
|
+
const oldThread = await services.posts.create({
|
|
181
|
+
format: "note",
|
|
182
|
+
title: "Old thread with a new reply",
|
|
183
|
+
bodyMarkdown: "root from 2019",
|
|
184
|
+
publishedAt: Date.UTC(2019, 0, 1) / 1000,
|
|
185
|
+
});
|
|
186
|
+
await services.posts.create({
|
|
187
|
+
format: "note",
|
|
188
|
+
bodyMarkdown: "quiet addition",
|
|
189
|
+
replyToId: oldThread.id,
|
|
190
|
+
publishedAt: Date.UTC(2026, 5, 1) / 1000,
|
|
191
|
+
quietReply: true,
|
|
192
|
+
});
|
|
193
|
+
await services.posts.create({
|
|
194
|
+
format: "note",
|
|
195
|
+
title: "Plain newer post",
|
|
196
|
+
bodyMarkdown: "root from 2025",
|
|
197
|
+
publishedAt: Date.UTC(2025, 0, 1) / 1000,
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
const updated = await fetchFeed(app, "?sort=updated");
|
|
201
|
+
expect(updated.indexOf("Old thread with a new reply")).toBeLessThan(
|
|
202
|
+
updated.indexOf("Plain newer post"),
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// Same URL param, same axis as the page — the default stays chronological.
|
|
206
|
+
const chronological = await fetchFeed(app, "");
|
|
207
|
+
expect(chronological.indexOf("Plain newer post")).toBeLessThan(
|
|
208
|
+
chronological.indexOf("Old thread with a new reply"),
|
|
209
|
+
);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("scopes a year filter to the axis the feed is sorted on", async () => {
|
|
213
|
+
const { app, services } = setupApp();
|
|
214
|
+
const oldThread = await services.posts.create({
|
|
215
|
+
format: "note",
|
|
216
|
+
title: "Extended in 2026",
|
|
217
|
+
bodyMarkdown: "root from 2019",
|
|
218
|
+
publishedAt: Date.UTC(2019, 0, 1) / 1000,
|
|
219
|
+
});
|
|
220
|
+
await services.posts.create({
|
|
221
|
+
format: "note",
|
|
222
|
+
bodyMarkdown: "addition",
|
|
223
|
+
replyToId: oldThread.id,
|
|
224
|
+
publishedAt: Date.UTC(2026, 5, 1) / 1000,
|
|
225
|
+
});
|
|
226
|
+
await services.posts.create({
|
|
227
|
+
format: "note",
|
|
228
|
+
title: "Published in 2025",
|
|
229
|
+
bodyMarkdown: "root from 2025",
|
|
230
|
+
publishedAt: Date.UTC(2025, 0, 1) / 1000,
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const byActivity = await fetchFeed(app, "?sort=updated&year=2026");
|
|
234
|
+
expect(byActivity).toContain("Extended in 2026");
|
|
235
|
+
expect(byActivity).not.toContain("Published in 2025");
|
|
236
|
+
|
|
237
|
+
const byPublication = await fetchFeed(app, "?year=2025");
|
|
238
|
+
expect(byPublication).toContain("Published in 2025");
|
|
239
|
+
expect(byPublication).not.toContain("Extended in 2026");
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it("keeps the same order after an old thread gains a reply", async () => {
|
|
243
|
+
const { app, services } = setupApp();
|
|
244
|
+
const first = await services.posts.create({
|
|
245
|
+
format: "note",
|
|
246
|
+
title: "Alpha",
|
|
247
|
+
bodyMarkdown: "alpha root",
|
|
248
|
+
publishedAt: Date.UTC(2020, 0, 1) / 1000,
|
|
249
|
+
});
|
|
250
|
+
await services.posts.create({
|
|
251
|
+
format: "note",
|
|
252
|
+
title: "Beta",
|
|
253
|
+
bodyMarkdown: "beta root",
|
|
254
|
+
publishedAt: Date.UTC(2021, 0, 1) / 1000,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
const before = await fetchFeed(app, "");
|
|
258
|
+
const orderBefore = before.indexOf("Beta") < before.indexOf("Alpha");
|
|
259
|
+
|
|
260
|
+
await services.posts.create({
|
|
261
|
+
format: "note",
|
|
262
|
+
bodyMarkdown: "late addition",
|
|
263
|
+
replyToId: first.id,
|
|
264
|
+
publishedAt: Date.UTC(2026, 0, 1) / 1000,
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const after = await fetchFeed(app, "");
|
|
268
|
+
expect(after.indexOf("Beta") < after.indexOf("Alpha")).toBe(orderBefore);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
99
272
|
describe("archive page legacy param redirect", () => {
|
|
100
273
|
it("redirects legacy boolean params to their single-word spelling", async () => {
|
|
101
274
|
const { app } = setupApp();
|
|
@@ -133,3 +306,211 @@ describe("archive page legacy param redirect", () => {
|
|
|
133
306
|
expect(res.headers.get("location")).toBe("/archive?title=any");
|
|
134
307
|
});
|
|
135
308
|
});
|
|
309
|
+
|
|
310
|
+
describe("archive page ordering", () => {
|
|
311
|
+
it("orders threads by their root publication time, not recent replies", async () => {
|
|
312
|
+
const { app, services } = setupApp();
|
|
313
|
+
const olderThread = await services.posts.create({
|
|
314
|
+
format: "note",
|
|
315
|
+
title: "Older active thread",
|
|
316
|
+
bodyMarkdown: "older root",
|
|
317
|
+
publishedAt: Date.UTC(2024, 0, 1) / 1000,
|
|
318
|
+
});
|
|
319
|
+
await services.posts.create({
|
|
320
|
+
format: "note",
|
|
321
|
+
bodyMarkdown: "recent reply",
|
|
322
|
+
replyToId: olderThread.id,
|
|
323
|
+
publishedAt: Date.UTC(2026, 0, 1) / 1000,
|
|
324
|
+
});
|
|
325
|
+
await services.posts.create({
|
|
326
|
+
format: "note",
|
|
327
|
+
title: "Newer root thread",
|
|
328
|
+
bodyMarkdown: "newer root",
|
|
329
|
+
publishedAt: Date.UTC(2025, 0, 1) / 1000,
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
const res = await app.request("/archive");
|
|
333
|
+
expect(res.status).toBe(200);
|
|
334
|
+
const html = await res.text();
|
|
335
|
+
|
|
336
|
+
expect(html.indexOf("Newer root thread")).toBeLessThan(
|
|
337
|
+
html.indexOf("Older active thread"),
|
|
338
|
+
);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it("keeps newer posts ahead of older pinned posts", async () => {
|
|
342
|
+
const { app, services } = setupApp();
|
|
343
|
+
await services.posts.create({
|
|
344
|
+
format: "note",
|
|
345
|
+
title: "Older pinned post",
|
|
346
|
+
bodyMarkdown: "older",
|
|
347
|
+
pinned: true,
|
|
348
|
+
publishedAt: Date.UTC(2025, 0, 1) / 1000,
|
|
349
|
+
});
|
|
350
|
+
await services.posts.create({
|
|
351
|
+
format: "note",
|
|
352
|
+
title: "Newer unpinned post",
|
|
353
|
+
bodyMarkdown: "newer",
|
|
354
|
+
publishedAt: Date.UTC(2026, 0, 1) / 1000,
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
const res = await app.request("/archive?view=list");
|
|
358
|
+
expect(res.status).toBe(200);
|
|
359
|
+
const html = await res.text();
|
|
360
|
+
|
|
361
|
+
expect(html.indexOf("Newer unpinned post")).toBeLessThan(
|
|
362
|
+
html.indexOf("Older pinned post"),
|
|
363
|
+
);
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
describe("archive page sort=updated", () => {
|
|
368
|
+
/**
|
|
369
|
+
* An old thread that recently gained a reply, plus a younger standalone
|
|
370
|
+
* thread. The two axes disagree about their order, which is the whole point
|
|
371
|
+
* of the toggle.
|
|
372
|
+
*/
|
|
373
|
+
async function seedDivergingThreads(services: {
|
|
374
|
+
posts: {
|
|
375
|
+
create: (data: Record<string, unknown>) => Promise<{ id: string }>;
|
|
376
|
+
};
|
|
377
|
+
}) {
|
|
378
|
+
const olderThread = await services.posts.create({
|
|
379
|
+
format: "note",
|
|
380
|
+
title: "Growing thread",
|
|
381
|
+
bodyMarkdown: "root from 2019",
|
|
382
|
+
publishedAt: Date.UTC(2019, 2, 5) / 1000,
|
|
383
|
+
});
|
|
384
|
+
await services.posts.create({
|
|
385
|
+
format: "note",
|
|
386
|
+
bodyMarkdown: "reply from 2026",
|
|
387
|
+
replyToId: olderThread.id,
|
|
388
|
+
publishedAt: Date.UTC(2026, 7, 1) / 1000,
|
|
389
|
+
});
|
|
390
|
+
await services.posts.create({
|
|
391
|
+
format: "note",
|
|
392
|
+
title: "Untouched newer thread",
|
|
393
|
+
bodyMarkdown: "root from 2025",
|
|
394
|
+
publishedAt: Date.UTC(2025, 0, 1) / 1000,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
it("puts recently extended threads first", async () => {
|
|
399
|
+
const { app, services } = setupApp();
|
|
400
|
+
await seedDivergingThreads(services);
|
|
401
|
+
|
|
402
|
+
const res = await app.request("/archive?sort=updated");
|
|
403
|
+
expect(res.status).toBe(200);
|
|
404
|
+
const html = await res.text();
|
|
405
|
+
|
|
406
|
+
expect(html.indexOf("Growing thread")).toBeLessThan(
|
|
407
|
+
html.indexOf("Untouched newer thread"),
|
|
408
|
+
);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it("buckets months on the same axis it sorts by", async () => {
|
|
412
|
+
const { app, services } = setupApp();
|
|
413
|
+
await seedDivergingThreads(services);
|
|
414
|
+
|
|
415
|
+
const byPublished = await (await app.request("/archive")).text();
|
|
416
|
+
expect(byPublished).toContain("March 2019");
|
|
417
|
+
expect(byPublished).not.toContain("August 2026");
|
|
418
|
+
|
|
419
|
+
const byActivity = await (
|
|
420
|
+
await app.request("/archive?sort=updated")
|
|
421
|
+
).text();
|
|
422
|
+
expect(byActivity).toContain("August 2026");
|
|
423
|
+
expect(byActivity).not.toContain("March 2019");
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
it("scopes the year filter to the active axis", async () => {
|
|
427
|
+
const { app, services } = setupApp();
|
|
428
|
+
await seedDivergingThreads(services);
|
|
429
|
+
|
|
430
|
+
const activityYear = await (
|
|
431
|
+
await app.request("/archive?sort=updated&year=2026")
|
|
432
|
+
).text();
|
|
433
|
+
expect(activityYear).toContain("Growing thread");
|
|
434
|
+
expect(activityYear).not.toContain("Untouched newer thread");
|
|
435
|
+
|
|
436
|
+
const publishedYear = await (
|
|
437
|
+
await app.request("/archive?year=2026")
|
|
438
|
+
).text();
|
|
439
|
+
expect(publishedYear).not.toContain("Growing thread");
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it("keeps the default axis unchanged without the param", async () => {
|
|
443
|
+
const { app, services } = setupApp();
|
|
444
|
+
await seedDivergingThreads(services);
|
|
445
|
+
|
|
446
|
+
const html = await (await app.request("/archive")).text();
|
|
447
|
+
expect(html.indexOf("Untouched newer thread")).toBeLessThan(
|
|
448
|
+
html.indexOf("Growing thread"),
|
|
449
|
+
);
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
it("distinguishes the updated view in the document title", async () => {
|
|
453
|
+
const { app, services } = setupApp();
|
|
454
|
+
await seedDivergingThreads(services);
|
|
455
|
+
|
|
456
|
+
const byPublished = await (await app.request("/archive")).text();
|
|
457
|
+
expect(byPublished).toContain("<title>Archive - ");
|
|
458
|
+
expect(byPublished).not.toContain("Recently updated");
|
|
459
|
+
|
|
460
|
+
const byActivity = await (
|
|
461
|
+
await app.request("/archive?sort=updated")
|
|
462
|
+
).text();
|
|
463
|
+
expect(byActivity).toContain("<title>Archive - Recently updated - ");
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
it("ignores an unknown sort value", async () => {
|
|
467
|
+
const { app, services } = setupApp();
|
|
468
|
+
await seedDivergingThreads(services);
|
|
469
|
+
|
|
470
|
+
const html = await (await app.request("/archive?sort=nonsense")).text();
|
|
471
|
+
expect(html.indexOf("Untouched newer thread")).toBeLessThan(
|
|
472
|
+
html.indexOf("Growing thread"),
|
|
473
|
+
);
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
// The quiet-reply switch promises "won't move the thread to the top of
|
|
477
|
+
// latest". The archive is not Latest — it is the canonical all-posts view,
|
|
478
|
+
// and it already shows Hidden-from-Latest content. So its updated sort
|
|
479
|
+
// reports when the Thread actually changed.
|
|
480
|
+
it("surfaces quietly extended threads even though Latest does not", async () => {
|
|
481
|
+
const { app, services } = setupApp();
|
|
482
|
+
const quietThread = await services.posts.create({
|
|
483
|
+
format: "note",
|
|
484
|
+
title: "Quietly extended thread",
|
|
485
|
+
bodyMarkdown: "root from 2019",
|
|
486
|
+
publishedAt: Date.UTC(2019, 2, 5) / 1000,
|
|
487
|
+
});
|
|
488
|
+
await services.posts.create({
|
|
489
|
+
format: "note",
|
|
490
|
+
bodyMarkdown: "quiet addendum",
|
|
491
|
+
replyToId: quietThread.id,
|
|
492
|
+
publishedAt: Date.UTC(2026, 7, 1) / 1000,
|
|
493
|
+
quietReply: true,
|
|
494
|
+
});
|
|
495
|
+
await services.posts.create({
|
|
496
|
+
format: "note",
|
|
497
|
+
title: "Untouched newer thread",
|
|
498
|
+
bodyMarkdown: "root from 2025",
|
|
499
|
+
publishedAt: Date.UTC(2025, 0, 1) / 1000,
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
const html = await (await app.request("/archive?sort=updated")).text();
|
|
503
|
+
expect(html.indexOf("Quietly extended thread")).toBeLessThan(
|
|
504
|
+
html.indexOf("Untouched newer thread"),
|
|
505
|
+
);
|
|
506
|
+
expect(html).toContain("August 2026");
|
|
507
|
+
|
|
508
|
+
// Latest still honors the quiet flag.
|
|
509
|
+
const roots = await services.posts.list({
|
|
510
|
+
status: "published",
|
|
511
|
+
excludeReplies: true,
|
|
512
|
+
excludeLatestHidden: true,
|
|
513
|
+
});
|
|
514
|
+
expect(roots[0]?.title).toBe("Untouched newer thread");
|
|
515
|
+
});
|
|
516
|
+
});
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
2
|
import { createTestApp } from "../../../__tests__/helpers/app.js";
|
|
3
3
|
import { collectionRoutes } from "../collection.js";
|
|
4
4
|
import { collectionsPageRoutes } from "../collections.js";
|
|
5
5
|
import { pageRoutes } from "../page.js";
|
|
6
6
|
|
|
7
|
-
function createCollectionRoutingTestApp() {
|
|
8
|
-
const testApp = createTestApp();
|
|
7
|
+
function createCollectionRoutingTestApp(rssPublishDelaySeconds = 0) {
|
|
8
|
+
const testApp = createTestApp({ rssPublishDelaySeconds });
|
|
9
9
|
const { app } = testApp;
|
|
10
10
|
|
|
11
11
|
app.use("*", async (c, next) => {
|
|
@@ -21,6 +21,10 @@ function createCollectionRoutingTestApp() {
|
|
|
21
21
|
return testApp;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
vi.restoreAllMocks();
|
|
26
|
+
});
|
|
27
|
+
|
|
24
28
|
async function createCollectionWithPost(
|
|
25
29
|
services: ReturnType<typeof createTestApp>["services"],
|
|
26
30
|
{
|
|
@@ -42,7 +46,7 @@ async function createCollectionWithPost(
|
|
|
42
46
|
bodyMarkdown: bodyMarkdown ?? postTitle,
|
|
43
47
|
status: "published",
|
|
44
48
|
});
|
|
45
|
-
await services.collections.
|
|
49
|
+
await services.collections.addThread(collection.id, post.id);
|
|
46
50
|
return { collection, post };
|
|
47
51
|
}
|
|
48
52
|
|
|
@@ -50,11 +54,17 @@ describe("Collection Routing", () => {
|
|
|
50
54
|
it("renders a single collection at its root path", async () => {
|
|
51
55
|
const { app, services } = createCollectionRoutingTestApp();
|
|
52
56
|
|
|
53
|
-
await createCollectionWithPost(services, {
|
|
57
|
+
const { post } = await createCollectionWithPost(services, {
|
|
54
58
|
slug: "reading",
|
|
55
59
|
title: "Reading",
|
|
56
60
|
postTitle: "Book log",
|
|
57
61
|
});
|
|
62
|
+
await services.posts.create({
|
|
63
|
+
format: "note",
|
|
64
|
+
bodyMarkdown: "Thread follow-up",
|
|
65
|
+
status: "published",
|
|
66
|
+
replyToId: post.id,
|
|
67
|
+
});
|
|
58
68
|
|
|
59
69
|
const res = await app.request("/reading");
|
|
60
70
|
|
|
@@ -64,6 +74,23 @@ describe("Collection Routing", () => {
|
|
|
64
74
|
const html = await res.text();
|
|
65
75
|
expect(html).toContain("Reading");
|
|
66
76
|
expect(html).toContain("Book log");
|
|
77
|
+
expect(html).toContain("Thread follow-up");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("hides the collection feed button when feeds are disabled", async () => {
|
|
81
|
+
const { app, services } = createCollectionRoutingTestApp();
|
|
82
|
+
await services.settings.set("RSS_FEEDS_ENABLED", "false");
|
|
83
|
+
await createCollectionWithPost(services, {
|
|
84
|
+
slug: "reading",
|
|
85
|
+
title: "Reading",
|
|
86
|
+
postTitle: "Book log",
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const response = await app.request("/reading");
|
|
90
|
+
const html = await response.text();
|
|
91
|
+
|
|
92
|
+
expect(response.status).toBe(200);
|
|
93
|
+
expect(html).not.toContain('class="feed-link"');
|
|
67
94
|
});
|
|
68
95
|
|
|
69
96
|
it("redirects namespaced single-collection paths to the root canonical URL", async () => {
|
|
@@ -139,6 +166,37 @@ describe("Collection Routing", () => {
|
|
|
139
166
|
expect(xml).toContain("Film log");
|
|
140
167
|
});
|
|
141
168
|
|
|
169
|
+
it("applies the RSS publication delay to Collection feeds", async () => {
|
|
170
|
+
const currentTime = 2_000_000;
|
|
171
|
+
vi.spyOn(Date, "now").mockReturnValue(currentTime * 1000);
|
|
172
|
+
const { app, services } = createCollectionRoutingTestApp(300);
|
|
173
|
+
const collection = await services.collections.create({
|
|
174
|
+
slug: "reading",
|
|
175
|
+
title: "Reading",
|
|
176
|
+
});
|
|
177
|
+
const eligible = await services.posts.create({
|
|
178
|
+
format: "note",
|
|
179
|
+
title: "Eligible Book Log",
|
|
180
|
+
bodyMarkdown: "Old enough for RSS",
|
|
181
|
+
status: "published",
|
|
182
|
+
publishedAt: currentTime - 300,
|
|
183
|
+
});
|
|
184
|
+
const recent = await services.posts.create({
|
|
185
|
+
format: "note",
|
|
186
|
+
title: "Recent Book Log",
|
|
187
|
+
bodyMarkdown: "Still inside the edit window",
|
|
188
|
+
status: "published",
|
|
189
|
+
publishedAt: currentTime,
|
|
190
|
+
});
|
|
191
|
+
await services.collections.addThread(collection.id, eligible.id);
|
|
192
|
+
await services.collections.addThread(collection.id, recent.id);
|
|
193
|
+
|
|
194
|
+
const xml = await (await app.request("/reading/feed")).text();
|
|
195
|
+
|
|
196
|
+
expect(xml).toContain("Eligible Book Log");
|
|
197
|
+
expect(xml).not.toContain("Recent Book Log");
|
|
198
|
+
});
|
|
199
|
+
|
|
142
200
|
it("redirects canonical collection pages and feeds to collection aliases", async () => {
|
|
143
201
|
const { app, services } = createCollectionRoutingTestApp();
|
|
144
202
|
|
|
@@ -30,7 +30,7 @@ describe("Collections Listing Page - Data Logic", () => {
|
|
|
30
30
|
);
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
-
it("returns collections with
|
|
33
|
+
it("returns collections with Thread counts and recent activity", async () => {
|
|
34
34
|
const recipes = await collectionService.create({
|
|
35
35
|
slug: "recipes",
|
|
36
36
|
title: "Recipes",
|
|
@@ -40,7 +40,7 @@ describe("Collections Listing Page - Data Logic", () => {
|
|
|
40
40
|
title: "Travel",
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
-
// Add
|
|
43
|
+
// Add Threads to the recipes Collection.
|
|
44
44
|
const p1 = await postService.create({
|
|
45
45
|
format: "note",
|
|
46
46
|
bodyMarkdown: "Recipe 1",
|
|
@@ -49,8 +49,8 @@ describe("Collections Listing Page - Data Logic", () => {
|
|
|
49
49
|
format: "note",
|
|
50
50
|
bodyMarkdown: "Recipe 2",
|
|
51
51
|
});
|
|
52
|
-
await collectionService.
|
|
53
|
-
await collectionService.
|
|
52
|
+
await collectionService.addThread(recipes.id, p1.id);
|
|
53
|
+
await collectionService.addThread(recipes.id, p2.id);
|
|
54
54
|
|
|
55
55
|
const directory = await collectionService.listDirectoryData();
|
|
56
56
|
|
|
@@ -59,9 +59,9 @@ describe("Collections Listing Page - Data Logic", () => {
|
|
|
59
59
|
(c) => c.slug === "recipes",
|
|
60
60
|
);
|
|
61
61
|
const travelResult = directory.collections.find((c) => c.slug === "travel");
|
|
62
|
-
expect(recipesResult?.
|
|
62
|
+
expect(recipesResult?.threadCount).toBe(2);
|
|
63
63
|
expect(recipesResult?.recentActivityAt).toBe(p2.lastActivityAt);
|
|
64
|
-
expect(travelResult?.
|
|
64
|
+
expect(travelResult?.threadCount).toBe(0);
|
|
65
65
|
expect(travelResult?.recentActivityAt).toBeGreaterThan(0);
|
|
66
66
|
});
|
|
67
67
|
|
|
@@ -86,13 +86,13 @@ describe("Collections Listing Page - Data Logic", () => {
|
|
|
86
86
|
bodyMarkdown: "Will remain",
|
|
87
87
|
});
|
|
88
88
|
|
|
89
|
-
await collectionService.
|
|
90
|
-
await collectionService.
|
|
89
|
+
await collectionService.addThread(col.id, post.id);
|
|
90
|
+
await collectionService.addThread(col.id, post2.id);
|
|
91
91
|
|
|
92
92
|
await postService.delete(post.id);
|
|
93
93
|
|
|
94
94
|
const directory = await collectionService.listDirectoryData();
|
|
95
|
-
expect(directory.collections[0]?.
|
|
95
|
+
expect(directory.collections[0]?.threadCount).toBe(1);
|
|
96
96
|
expect(directory.collections[0]?.recentActivityAt).toBe(
|
|
97
97
|
post2.lastActivityAt,
|
|
98
98
|
);
|
|
@@ -39,12 +39,16 @@ describe("Featured Page - Data Logic", () => {
|
|
|
39
39
|
bodyMarkdown: "Normal post",
|
|
40
40
|
status: "published",
|
|
41
41
|
});
|
|
42
|
-
|
|
42
|
+
// Featuring an unpublished post is refused outright, so the only way a
|
|
43
|
+
// draft carries the flag is by being unpublished after the fact — the
|
|
44
|
+
// case this filter still has to catch.
|
|
45
|
+
const unpublished = await postService.create({
|
|
43
46
|
format: "note",
|
|
44
47
|
bodyMarkdown: "Draft featured",
|
|
45
48
|
featured: true,
|
|
46
|
-
status: "
|
|
49
|
+
status: "published",
|
|
47
50
|
});
|
|
51
|
+
await postService.update(unpublished.id, { status: "draft" });
|
|
48
52
|
|
|
49
53
|
const posts = await postService.list({
|
|
50
54
|
featured: true,
|