@jant/core 0.3.35 → 0.3.37
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/export.js +1 -1
- package/bin/commands/import-site.js +529 -0
- package/bin/commands/reset-password.js +3 -2
- package/dist/client/assets/heic-to-DIRPI3VF.js +1 -0
- package/dist/client/assets/module-RjUF93sV.js +716 -0
- package/dist/client/assets/native-48B9X9Wg.js +1 -0
- package/dist/client/assets/url-FWFqPJPb.js +1 -0
- package/dist/client/client.css +1 -1
- package/dist/client/client.js +4564 -3013
- package/dist/index.js +12885 -8161
- package/package.json +23 -6
- package/src/__tests__/helpers/app.ts +10 -10
- package/src/__tests__/helpers/db.ts +91 -87
- package/src/app.tsx +157 -31
- package/src/auth.ts +20 -2
- package/src/client/archive-nav.js +187 -0
- package/src/client/audio-player.ts +478 -0
- package/src/client/audio-processor.ts +84 -0
- package/src/{lib → client}/avatar-upload.ts +4 -3
- package/src/{lib → client}/collection-form-bridge.ts +2 -2
- package/src/{ui → client}/components/__tests__/jant-collection-form.test.ts +26 -9
- package/src/client/components/__tests__/jant-compose-dialog.test.ts +1140 -0
- package/src/client/components/__tests__/jant-compose-editor.test.ts +504 -0
- package/src/{ui → client}/components/__tests__/jant-post-form.test.ts +37 -17
- package/src/{ui → client}/components/__tests__/jant-settings-avatar.test.ts +2 -2
- package/src/{ui → client}/components/__tests__/jant-settings-general.test.ts +3 -3
- package/src/client/components/collection-sidebar-types.ts +43 -0
- package/src/{ui → client}/components/collection-types.ts +3 -4
- package/src/client/components/compose-types.ts +174 -0
- package/src/client/components/jant-collection-form.ts +667 -0
- package/src/client/components/jant-collection-sidebar.ts +805 -0
- package/src/client/components/jant-compose-dialog.ts +2161 -0
- package/src/client/components/jant-compose-editor.ts +1813 -0
- package/src/client/components/jant-compose-fullscreen.ts +283 -0
- package/src/client/components/jant-media-lightbox.ts +259 -0
- package/src/{ui → client}/components/jant-nav-manager.ts +97 -298
- package/src/{ui → client}/components/jant-post-form.ts +141 -12
- package/src/client/components/jant-post-menu.ts +1019 -0
- package/src/{ui → client}/components/jant-settings-avatar.ts +3 -3
- package/src/{ui → client}/components/jant-settings-general.ts +38 -4
- package/src/client/components/jant-text-preview.ts +232 -0
- package/src/{ui → client}/components/nav-manager-types.ts +6 -18
- package/src/{ui → client}/components/post-form-template.ts +137 -38
- package/src/{ui → client}/components/post-form-types.ts +15 -4
- package/src/client/compose-bridge.ts +583 -0
- package/src/{lib → client}/image-processor.ts +26 -8
- package/src/client/lazy-slugify.ts +51 -0
- package/src/client/media-metadata.ts +247 -0
- package/src/client/multipart-upload.ts +160 -0
- package/src/{lib → client}/nav-manager-bridge.ts +1 -1
- package/src/{lib → client}/post-form-bridge.ts +53 -2
- package/src/{lib → client}/settings-bridge.ts +3 -15
- package/src/client/thread-context.ts +140 -0
- package/src/client/tiptap/bubble-menu.ts +205 -0
- package/src/client/tiptap/create-editor.ts +86 -0
- package/src/client/tiptap/exitable-marks.ts +73 -0
- package/src/client/tiptap/extensions.ts +65 -0
- package/src/client/tiptap/image-node.ts +482 -0
- package/src/client/tiptap/link-toolbar.ts +371 -0
- package/src/client/tiptap/more-break.ts +50 -0
- package/src/client/tiptap/paste-image.ts +129 -0
- package/src/client/tiptap/slash-commands.ts +438 -0
- package/src/{lib → client}/toast.ts +101 -3
- package/src/client/types/sortablejs.d.ts +44 -0
- package/src/client/upload-with-metadata.ts +54 -0
- package/src/client/video-processor.ts +207 -0
- package/src/client.ts +27 -17
- package/src/db/__tests__/migrations.test.ts +118 -0
- package/src/db/index.ts +52 -0
- package/src/db/migrations/0000_baseline.sql +269 -0
- package/src/db/migrations/0001_fts_setup.sql +31 -0
- package/src/db/migrations/meta/0000_snapshot.json +703 -119
- package/src/db/migrations/meta/0001_snapshot.json +1337 -0
- package/src/db/migrations/meta/_journal.json +4 -39
- package/src/db/schema.ts +409 -140
- package/src/i18n/__tests__/detect.test.ts +115 -0
- package/src/i18n/context.tsx +2 -2
- package/src/i18n/detect.ts +85 -1
- package/src/i18n/i18n.ts +1 -1
- package/src/i18n/index.ts +2 -1
- package/src/i18n/locales/en.po +783 -1087
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +867 -812
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +878 -823
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/i18n/middleware.ts +6 -0
- package/src/index.ts +5 -7
- package/src/lib/__tests__/blurhash-placeholder.test.ts +75 -0
- package/src/lib/__tests__/constants.test.ts +0 -1
- package/src/lib/__tests__/markdown-to-tiptap.test.ts +358 -0
- package/src/lib/__tests__/nanoid.test.ts +26 -0
- package/src/lib/__tests__/resolve-config.test.ts +2 -2
- package/src/lib/__tests__/schemas.test.ts +186 -65
- package/src/lib/__tests__/slug.test.ts +126 -0
- package/src/lib/__tests__/sse.test.ts +6 -6
- package/src/lib/__tests__/summary.test.ts +264 -0
- package/src/lib/__tests__/theme.test.ts +1 -1
- package/src/lib/__tests__/timeline.test.ts +33 -30
- package/src/lib/__tests__/tiptap-to-markdown.test.ts +346 -0
- package/src/lib/__tests__/url.test.ts +2 -2
- package/src/lib/__tests__/view.test.ts +140 -65
- package/src/lib/blurhash-placeholder.ts +102 -0
- package/src/lib/constants.ts +3 -1
- package/src/lib/emoji-catalog.ts +963 -0
- package/src/lib/errors.ts +11 -8
- package/src/lib/feed.ts +77 -31
- package/src/lib/html.ts +2 -1
- package/src/lib/icon-catalog.ts +5033 -1
- package/src/lib/icons.ts +3 -2
- package/src/lib/index.ts +0 -1
- package/src/lib/markdown-to-tiptap.ts +286 -0
- package/src/lib/media-helpers.ts +22 -12
- package/src/lib/nanoid.ts +29 -0
- package/src/lib/navigation.ts +1 -1
- package/src/lib/render.tsx +24 -5
- package/src/lib/resolve-config.ts +13 -2
- package/src/lib/schemas.ts +226 -58
- package/src/lib/search-snippet.ts +34 -0
- package/src/lib/slug.ts +96 -0
- package/src/lib/sse.ts +6 -6
- package/src/lib/storage.ts +115 -7
- package/src/lib/summary.ts +158 -0
- package/src/lib/theme.ts +11 -8
- package/src/lib/timeline.ts +76 -34
- package/src/lib/tiptap-render.ts +191 -0
- package/src/lib/tiptap-to-markdown.ts +305 -0
- package/src/lib/upload.ts +263 -14
- package/src/lib/url.ts +37 -22
- package/src/lib/view.ts +236 -55
- package/src/middleware/__tests__/auth.test.ts +191 -11
- package/src/middleware/__tests__/onboarding.test.ts +12 -10
- package/src/middleware/auth.ts +63 -9
- package/src/middleware/error-handler.ts +3 -3
- package/src/middleware/onboarding.ts +1 -1
- package/src/middleware/secure-headers.ts +40 -0
- package/src/preset.css +83 -2
- package/src/routes/__tests__/compose.test.ts +17 -24
- package/src/routes/api/__tests__/collections.test.ts +109 -61
- package/src/routes/api/__tests__/nav-items.test.ts +46 -29
- package/src/routes/api/__tests__/posts.test.ts +132 -68
- package/src/routes/api/__tests__/search.test.ts +15 -2
- package/src/routes/api/__tests__/upload-multipart.test.ts +534 -0
- package/src/routes/api/collections.ts +57 -31
- package/src/routes/api/custom-urls.ts +80 -0
- package/src/routes/api/export.ts +31 -0
- package/src/routes/api/nav-items.ts +23 -19
- package/src/routes/api/posts.ts +81 -62
- package/src/routes/api/search.ts +3 -4
- package/src/routes/api/upload-multipart.ts +245 -0
- package/src/routes/api/upload.ts +92 -24
- package/src/routes/auth/__tests__/setup.test.ts +20 -60
- package/src/routes/auth/reset.tsx +5 -4
- package/src/routes/auth/setup.tsx +39 -31
- package/src/routes/auth/signin.tsx +13 -14
- package/src/routes/compose.tsx +27 -63
- package/src/routes/dash/__tests__/settings-avatar.test.ts +44 -9
- package/src/routes/dash/custom-urls.tsx +414 -0
- package/src/routes/dash/settings.tsx +475 -99
- package/src/routes/feed/__tests__/rss.test.ts +22 -23
- package/src/routes/feed/rss.ts +6 -2
- package/src/routes/feed/sitemap.ts +2 -12
- package/src/routes/pages/__tests__/collections.test.ts +5 -6
- package/src/routes/pages/__tests__/featured.test.ts +36 -18
- package/src/routes/pages/archive.tsx +177 -37
- package/src/routes/pages/collection.tsx +43 -14
- package/src/routes/pages/collections.tsx +11 -2
- package/src/routes/pages/featured.tsx +27 -3
- package/src/routes/pages/home.tsx +15 -14
- package/src/routes/pages/latest.tsx +1 -11
- package/src/routes/pages/new.tsx +39 -0
- package/src/routes/pages/page.tsx +95 -49
- package/src/routes/pages/search.tsx +1 -1
- package/src/services/__tests__/api-token.test.ts +135 -0
- package/src/services/__tests__/collection.test.ts +275 -227
- package/src/services/__tests__/custom-url.test.ts +213 -0
- package/src/services/__tests__/media.test.ts +162 -22
- package/src/services/__tests__/navigation.test.ts +109 -68
- package/src/services/__tests__/post-timeline.test.ts +205 -32
- package/src/services/__tests__/post.test.ts +800 -230
- package/src/services/__tests__/search.test.ts +67 -10
- package/src/services/__tests__/settings.test.ts +3 -3
- package/src/services/api-token.ts +166 -0
- package/src/services/auth.ts +17 -2
- package/src/services/collection.ts +397 -131
- package/src/services/custom-url.ts +188 -0
- package/src/services/export.ts +802 -0
- package/src/services/index.ts +26 -19
- package/src/services/media.ts +100 -22
- package/src/services/navigation.ts +158 -47
- package/src/services/path.ts +339 -0
- package/src/services/post.ts +764 -172
- package/src/services/search.ts +161 -74
- package/src/services/settings.ts +6 -2
- package/src/styles/components.css +293 -62
- package/src/styles/tokens.css +93 -5
- package/src/styles/ui.css +4349 -766
- package/src/types/bindings.ts +8 -0
- package/src/types/config.ts +34 -4
- package/src/types/constants.ts +17 -2
- package/src/types/entities.ts +83 -37
- package/src/types/operations.ts +20 -27
- package/src/types/props.ts +52 -17
- package/src/types/views.ts +48 -24
- package/src/ui/color-themes.ts +133 -23
- package/src/ui/compose/ComposeDialog.tsx +255 -16
- package/src/ui/compose/ComposePrompt.tsx +1 -1
- package/src/ui/dash/CrudPageHeader.tsx +1 -1
- package/src/ui/dash/ListItemRow.tsx +1 -1
- package/src/ui/dash/StatusBadge.tsx +12 -2
- package/src/ui/dash/appearance/AdvancedContent.tsx +71 -59
- package/src/ui/dash/appearance/ColorThemeContent.tsx +48 -33
- package/src/ui/dash/appearance/FontThemeContent.tsx +65 -73
- package/src/ui/dash/appearance/NavigationContent.tsx +106 -135
- package/src/ui/dash/index.ts +0 -3
- package/src/ui/dash/settings/AccountContent.tsx +87 -146
- package/src/ui/dash/settings/AccountMenuContent.tsx +147 -0
- package/src/ui/dash/settings/ApiTokensContent.tsx +232 -0
- package/src/ui/dash/settings/AvatarContent.tsx +78 -0
- package/src/ui/dash/settings/GeneralContent.tsx +3 -62
- package/src/ui/dash/settings/SessionsContent.tsx +159 -0
- package/src/ui/dash/settings/SettingsRootContent.tsx +266 -0
- package/src/ui/feed/LinkCard.tsx +89 -40
- package/src/ui/feed/NoteCard.tsx +39 -25
- package/src/ui/feed/PostStatusBadges.tsx +67 -0
- package/src/ui/feed/QuoteCard.tsx +38 -23
- package/src/ui/feed/ThreadPreview.tsx +90 -26
- package/src/ui/feed/TimelineFeed.tsx +3 -2
- package/src/ui/feed/TimelineItem.tsx +15 -6
- package/src/ui/feed/__tests__/thread-preview.test.ts +107 -0
- package/src/ui/feed/thread-preview-state.ts +61 -0
- package/src/ui/font-themes.ts +2 -2
- package/src/ui/layouts/BaseLayout.tsx +2 -2
- package/src/ui/layouts/SiteLayout.tsx +116 -103
- package/src/ui/pages/ArchivePage.tsx +923 -95
- package/src/ui/pages/CollectionPage.tsx +6 -35
- package/src/ui/pages/CollectionsPage.tsx +2 -1
- package/src/ui/pages/ComposePage.tsx +54 -0
- package/src/ui/pages/FeaturedPage.tsx +2 -1
- package/src/ui/pages/HomePage.tsx +1 -1
- package/src/ui/pages/PostPage.tsx +30 -45
- package/src/ui/pages/SearchPage.tsx +182 -38
- package/src/ui/shared/AdminBreadcrumb.tsx +29 -0
- package/src/ui/shared/CollectionsSidebar.tsx +239 -4
- package/src/ui/shared/MediaGallery.tsx +475 -41
- package/src/ui/shared/PostFooter.tsx +204 -0
- package/src/ui/shared/StarRating.tsx +27 -0
- package/src/ui/shared/__tests__/format-chars.test.ts +35 -0
- package/src/ui/shared/index.ts +0 -1
- package/src/db/migrations/0000_square_wallflower.sql +0 -118
- package/src/db/migrations/0001_add_search_fts.sql +0 -34
- package/src/db/migrations/0002_add_media_attachments.sql +0 -3
- package/src/db/migrations/0003_add_navigation_links.sql +0 -8
- package/src/db/migrations/0004_add_storage_provider.sql +0 -3
- package/src/db/migrations/0005_v2_schema_migration.sql +0 -268
- package/src/db/migrations/0006_rename_slug_to_path.sql +0 -5
- package/src/db/migrations/0007_post_collections_m2m.sql +0 -94
- package/src/db/migrations/0008_add_collection_dividers.sql +0 -8
- package/src/db/migrations/0009_drop_collection_show_divider.sql +0 -2
- package/src/db/migrations/0010_add_performance_indexes.sql +0 -16
- package/src/db/migrations/0011_add_path_registry.sql +0 -23
- package/src/db/migrations/meta/0003_snapshot.json +0 -821
- package/src/lib/__tests__/sqid.test.ts +0 -65
- package/src/lib/collections-reorder.ts +0 -28
- package/src/lib/compose-bridge.ts +0 -280
- package/src/lib/media-upload.ts +0 -148
- package/src/lib/sqid.ts +0 -79
- package/src/routes/api/__tests__/pages.test.ts +0 -218
- package/src/routes/api/pages.ts +0 -73
- package/src/routes/dash/__tests__/pages.test.ts +0 -226
- package/src/routes/dash/appearance.tsx +0 -240
- package/src/routes/dash/collections.tsx +0 -211
- package/src/routes/dash/index.tsx +0 -103
- package/src/routes/dash/media.tsx +0 -132
- package/src/routes/dash/pages.tsx +0 -239
- package/src/routes/dash/posts.tsx +0 -334
- package/src/routes/dash/redirects.tsx +0 -257
- package/src/routes/pages/post.tsx +0 -59
- package/src/services/__tests__/page.test.ts +0 -298
- package/src/services/__tests__/path-registry.test.ts +0 -165
- package/src/services/__tests__/redirect.test.ts +0 -159
- package/src/services/page.ts +0 -203
- package/src/services/path-registry.ts +0 -160
- package/src/services/redirect.ts +0 -97
- package/src/types/sortablejs.d.ts +0 -29
- package/src/ui/components/__tests__/jant-compose-dialog.test.ts +0 -512
- package/src/ui/components/__tests__/jant-compose-editor.test.ts +0 -272
- package/src/ui/components/compose-types.ts +0 -75
- package/src/ui/components/jant-collection-form.ts +0 -512
- package/src/ui/components/jant-compose-dialog.ts +0 -495
- package/src/ui/components/jant-compose-editor.ts +0 -814
- package/src/ui/dash/PageForm.tsx +0 -185
- package/src/ui/dash/PostList.tsx +0 -95
- package/src/ui/dash/appearance/AppearanceNav.tsx +0 -60
- package/src/ui/dash/collections/CollectionForm.tsx +0 -166
- package/src/ui/dash/collections/CollectionsListContent.tsx +0 -146
- package/src/ui/dash/collections/IconPickerGrid.tsx +0 -50
- package/src/ui/dash/collections/ViewCollectionContent.tsx +0 -103
- package/src/ui/dash/media/MediaListContent.tsx +0 -201
- package/src/ui/dash/media/ViewMediaContent.tsx +0 -208
- package/src/ui/dash/pages/PagesContent.tsx +0 -74
- package/src/ui/dash/posts/PostForm.tsx +0 -248
- package/src/ui/dash/settings/SettingsNav.tsx +0 -52
- package/src/ui/layouts/DashLayout.tsx +0 -165
- package/src/ui/pages/SinglePage.tsx +0 -23
- package/src/ui/shared/ThreadView.tsx +0 -136
- /package/src/{ui → client}/components/settings-types.ts +0 -0
|
@@ -0,0 +1,1140 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
|
|
3
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
4
|
+
import type {
|
|
5
|
+
ComposeLabels,
|
|
6
|
+
ComposeCollection,
|
|
7
|
+
ComposeSubmitDetail,
|
|
8
|
+
} from "../compose-types.js";
|
|
9
|
+
import "../jant-compose-editor.js";
|
|
10
|
+
import "../jant-compose-dialog.js";
|
|
11
|
+
import type { JantComposeDialog } from "../jant-compose-dialog.js";
|
|
12
|
+
import type { JantComposeEditor } from "../jant-compose-editor.js";
|
|
13
|
+
|
|
14
|
+
function requireElement<T extends globalThis.Element>(
|
|
15
|
+
element: T | null,
|
|
16
|
+
message: string,
|
|
17
|
+
): T {
|
|
18
|
+
if (!element) {
|
|
19
|
+
throw new Error(message);
|
|
20
|
+
}
|
|
21
|
+
return element;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const labels: ComposeLabels = {
|
|
25
|
+
cancel: "Cancel",
|
|
26
|
+
note: "Note",
|
|
27
|
+
link: "Link",
|
|
28
|
+
quote: "Quote",
|
|
29
|
+
saveDraft: "Save as Draft",
|
|
30
|
+
saveAsDraft: "Save as draft",
|
|
31
|
+
discard: "Discard",
|
|
32
|
+
titlePlaceholder: "Title",
|
|
33
|
+
bodyPlaceholder: "What's on your mind...",
|
|
34
|
+
urlPlaceholder: "Paste a URL...",
|
|
35
|
+
linkTitlePlaceholder: "Give it a title...",
|
|
36
|
+
thoughtsPlaceholder: "Your thoughts (optional)",
|
|
37
|
+
quotePlaceholder: "Type the quote...",
|
|
38
|
+
authorPlaceholder: "Author (optional)",
|
|
39
|
+
sourcePlaceholder: "Source link (optional)",
|
|
40
|
+
attachedText: "Attached Text",
|
|
41
|
+
attachedTextPlaceholder: "Paste text...",
|
|
42
|
+
attachedTextHint: "Supplementary content",
|
|
43
|
+
done: "Done",
|
|
44
|
+
media: "Media",
|
|
45
|
+
rate: "Rate",
|
|
46
|
+
emoji: "Emoji",
|
|
47
|
+
title: "Title",
|
|
48
|
+
collection: "Collection",
|
|
49
|
+
searchCollections: "Search...",
|
|
50
|
+
noCollections: "No collections found.",
|
|
51
|
+
emptyCollections: "Create a collection to get started.",
|
|
52
|
+
post: "Post",
|
|
53
|
+
addAlt: "+ ALT",
|
|
54
|
+
addAltTitle: "Add alt text",
|
|
55
|
+
altPlaceholder: "Describe this...",
|
|
56
|
+
altHint: "Alt text improves accessibility",
|
|
57
|
+
addMore: "Add",
|
|
58
|
+
uploading: "Uploading...",
|
|
59
|
+
published: "Published!",
|
|
60
|
+
view: "View",
|
|
61
|
+
retryAll: "Tap to retry",
|
|
62
|
+
editPost: "Edit post",
|
|
63
|
+
update: "Done",
|
|
64
|
+
confirmCloseTitle: "Save to drafts?",
|
|
65
|
+
confirmCloseSubtitle: "Save to drafts to edit and post at a later time.",
|
|
66
|
+
confirmCloseSave: "Save",
|
|
67
|
+
confirmCloseCancel: "Cancel",
|
|
68
|
+
confirmCloseDiscard: "Don't save",
|
|
69
|
+
confirmEditTitle: "You have unsaved changes",
|
|
70
|
+
confirmEditSubtitle: "Do you want to publish your changes or discard them?",
|
|
71
|
+
confirmEditPublish: "Publish",
|
|
72
|
+
confirmEditDiscard: "Discard",
|
|
73
|
+
drafts: "Drafts",
|
|
74
|
+
draftsEmpty: "No drafts yet. Save a draft to find it here.",
|
|
75
|
+
deleteDraft: "Delete Draft",
|
|
76
|
+
draftDeleted: "Draft deleted.",
|
|
77
|
+
publishFailedDraft: "Couldn't publish. Saved as draft.",
|
|
78
|
+
uploadFailedDraft: "Some uploads failed. Saved as draft.",
|
|
79
|
+
addCollection: "Add Collection",
|
|
80
|
+
collectionCountLabel: "%name% + %count% more",
|
|
81
|
+
draftRestored: "Draft restored.",
|
|
82
|
+
reply: "Reply",
|
|
83
|
+
publishFeatured: "Post as Featured",
|
|
84
|
+
publishUnlisted: "Post Unlisted",
|
|
85
|
+
publishPrivate: "Post as Private",
|
|
86
|
+
showMore: "Show more",
|
|
87
|
+
showLess: "Show less",
|
|
88
|
+
collectionFormLabels: {
|
|
89
|
+
titleLabel: "Title",
|
|
90
|
+
titlePlaceholder: "My Collection",
|
|
91
|
+
slugLabel: "Slug",
|
|
92
|
+
slugHelp: "URL-safe identifier",
|
|
93
|
+
descriptionLabel: "Description (optional)",
|
|
94
|
+
descriptionPlaceholder: "What's this collection about?",
|
|
95
|
+
removeIcon: "Remove",
|
|
96
|
+
iconsTab: "Icons",
|
|
97
|
+
emojisTab: "Emojis",
|
|
98
|
+
searchIconsPlaceholder: "Search icons...",
|
|
99
|
+
searchEmojisPlaceholder: "Search emojis...",
|
|
100
|
+
sortOrderLabel: "Sort Order",
|
|
101
|
+
sortNewest: "Newest first",
|
|
102
|
+
sortOldest: "Oldest first",
|
|
103
|
+
sortRatingDesc: "Highest rated",
|
|
104
|
+
sortRatingAsc: "Lowest rated",
|
|
105
|
+
submitLabel: "Save",
|
|
106
|
+
cancelLabel: "Cancel",
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const collections: ComposeCollection[] = [
|
|
111
|
+
{ id: "col-1", title: "Books", iconHtml: "" },
|
|
112
|
+
{ id: "col-2", title: "Movies", iconHtml: "<span>🎬</span>" },
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
async function createElement(
|
|
116
|
+
cols: ComposeCollection[] = collections,
|
|
117
|
+
): Promise<JantComposeDialog> {
|
|
118
|
+
const el = document.createElement("jant-compose-dialog") as JantComposeDialog;
|
|
119
|
+
el.collections = cols;
|
|
120
|
+
el.labels = labels;
|
|
121
|
+
document.body.appendChild(el);
|
|
122
|
+
await el.updateComplete;
|
|
123
|
+
// Wait for nested editor to also render
|
|
124
|
+
const editor = el.querySelector<JantComposeEditor>("jant-compose-editor");
|
|
125
|
+
if (editor) await editor.updateComplete;
|
|
126
|
+
return el;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
describe("JantComposeDialog", () => {
|
|
130
|
+
beforeEach(() => {
|
|
131
|
+
document.body.innerHTML = "";
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("renders with collections and labels", async () => {
|
|
135
|
+
const el = await createElement();
|
|
136
|
+
|
|
137
|
+
// Header present
|
|
138
|
+
expect(el.querySelector(".compose-dialog-header")).not.toBeNull();
|
|
139
|
+
|
|
140
|
+
// Format buttons present
|
|
141
|
+
const segmentedItems = el.querySelectorAll(".compose-segmented-item");
|
|
142
|
+
expect(segmentedItems.length).toBe(3);
|
|
143
|
+
expect(segmentedItems[0].textContent?.trim()).toBe("Note");
|
|
144
|
+
expect(segmentedItems[1].textContent?.trim()).toBe("Link");
|
|
145
|
+
expect(segmentedItems[2].textContent?.trim()).toBe("Quote");
|
|
146
|
+
|
|
147
|
+
// Post button present (split button with visibility dropdown)
|
|
148
|
+
const postBtn = requireElement(
|
|
149
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-main"),
|
|
150
|
+
"expected post button",
|
|
151
|
+
);
|
|
152
|
+
expect(postBtn.textContent?.trim()).toBe("Post");
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("format switching updates active state", async () => {
|
|
156
|
+
const el = await createElement();
|
|
157
|
+
|
|
158
|
+
// Note is active by default
|
|
159
|
+
const noteBtn = el.querySelectorAll<HTMLButtonElement>(
|
|
160
|
+
".compose-segmented-item",
|
|
161
|
+
)[0];
|
|
162
|
+
expect(noteBtn.classList.contains("compose-segmented-item-active")).toBe(
|
|
163
|
+
true,
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
// Click link
|
|
167
|
+
const linkBtn = el.querySelectorAll<HTMLButtonElement>(
|
|
168
|
+
".compose-segmented-item",
|
|
169
|
+
)[1];
|
|
170
|
+
linkBtn.click();
|
|
171
|
+
await el.updateComplete;
|
|
172
|
+
|
|
173
|
+
expect(el._format).toBe("link");
|
|
174
|
+
expect(linkBtn.classList.contains("compose-segmented-item-active")).toBe(
|
|
175
|
+
true,
|
|
176
|
+
);
|
|
177
|
+
expect(noteBtn.classList.contains("compose-segmented-item-active")).toBe(
|
|
178
|
+
false,
|
|
179
|
+
);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("submit dispatches jant:compose-submit-deferred with correct payload", async () => {
|
|
183
|
+
const el = await createElement();
|
|
184
|
+
const editor = requireElement(
|
|
185
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
186
|
+
"expected compose editor",
|
|
187
|
+
);
|
|
188
|
+
editor._bodyJson = {
|
|
189
|
+
type: "doc",
|
|
190
|
+
content: [
|
|
191
|
+
{ type: "paragraph", content: [{ type: "text", text: "Hello world" }] },
|
|
192
|
+
],
|
|
193
|
+
};
|
|
194
|
+
await editor.updateComplete;
|
|
195
|
+
|
|
196
|
+
let receivedDetail:
|
|
197
|
+
| (ComposeSubmitDetail & { pendingAttachments: unknown[] })
|
|
198
|
+
| null = null;
|
|
199
|
+
el.addEventListener("jant:compose-submit-deferred", (event) => {
|
|
200
|
+
const customEvent = event as CustomEvent<
|
|
201
|
+
ComposeSubmitDetail & { pendingAttachments: unknown[] }
|
|
202
|
+
>;
|
|
203
|
+
receivedDetail = customEvent.detail;
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// Click post button
|
|
207
|
+
requireElement(
|
|
208
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-main"),
|
|
209
|
+
"expected post button",
|
|
210
|
+
).click();
|
|
211
|
+
|
|
212
|
+
expect(receivedDetail).not.toBeNull();
|
|
213
|
+
const detail = receivedDetail as unknown as ComposeSubmitDetail & {
|
|
214
|
+
pendingAttachments: unknown[];
|
|
215
|
+
};
|
|
216
|
+
expect(detail.format).toBe("note");
|
|
217
|
+
expect(detail.body).toContain("Hello world");
|
|
218
|
+
expect(detail.status).toBe("published");
|
|
219
|
+
expect(detail.visibility).toBe("public");
|
|
220
|
+
expect(detail.collectionIds).toEqual([]);
|
|
221
|
+
expect(detail.mediaIds).toEqual([]);
|
|
222
|
+
expect(detail.mediaAlts).toEqual({});
|
|
223
|
+
expect(detail.pendingAttachments).toEqual([]);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("collection selector toggles IDs", async () => {
|
|
227
|
+
const el = await createElement();
|
|
228
|
+
|
|
229
|
+
// Open collection combobox
|
|
230
|
+
const trigger = requireElement(
|
|
231
|
+
el.querySelector<HTMLButtonElement>(".compose-collection-trigger"),
|
|
232
|
+
"expected collection trigger",
|
|
233
|
+
);
|
|
234
|
+
trigger.click();
|
|
235
|
+
await el.updateComplete;
|
|
236
|
+
|
|
237
|
+
const options = el.querySelectorAll<HTMLElement>(
|
|
238
|
+
"[data-popover] [role='option']",
|
|
239
|
+
);
|
|
240
|
+
expect(options.length).toBe(2);
|
|
241
|
+
|
|
242
|
+
// Select first collection
|
|
243
|
+
options[0].click();
|
|
244
|
+
await el.updateComplete;
|
|
245
|
+
expect(el._collectionIds).toEqual(["col-1"]);
|
|
246
|
+
|
|
247
|
+
// Select second collection
|
|
248
|
+
options[1].click();
|
|
249
|
+
await el.updateComplete;
|
|
250
|
+
expect(el._collectionIds).toEqual(["col-1", "col-2"]);
|
|
251
|
+
|
|
252
|
+
// Deselect first
|
|
253
|
+
options[0].click();
|
|
254
|
+
await el.updateComplete;
|
|
255
|
+
expect(el._collectionIds).toEqual(["col-2"]);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it("reset restores initial state", async () => {
|
|
259
|
+
const el = await createElement();
|
|
260
|
+
el._format = "link";
|
|
261
|
+
el._collectionIds = ["col-1", "col-2"];
|
|
262
|
+
el._loading = true;
|
|
263
|
+
el._draftSourceId = "abc123";
|
|
264
|
+
|
|
265
|
+
el.reset();
|
|
266
|
+
|
|
267
|
+
expect(el._format).toBe("note");
|
|
268
|
+
expect(el._collectionIds).toEqual([]);
|
|
269
|
+
expect(el._loading).toBe(false);
|
|
270
|
+
expect(el._draftSourceId).toBeNull();
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("loading state disables submit button", async () => {
|
|
274
|
+
const el = await createElement();
|
|
275
|
+
el._loading = true;
|
|
276
|
+
await el.updateComplete;
|
|
277
|
+
|
|
278
|
+
const postBtn = requireElement(
|
|
279
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-main"),
|
|
280
|
+
"expected post button",
|
|
281
|
+
);
|
|
282
|
+
expect(postBtn.disabled).toBe(true);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("renders collection selector even without collections", async () => {
|
|
286
|
+
const el = await createElement([]);
|
|
287
|
+
|
|
288
|
+
// Collection trigger is still shown so users can create new collections
|
|
289
|
+
expect(el.querySelector(".compose-collection-trigger")).not.toBeNull();
|
|
290
|
+
const actionRow = el.querySelector(".compose-action-row");
|
|
291
|
+
expect(actionRow).not.toBeNull();
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it("draft button with content shows confirm panel", async () => {
|
|
295
|
+
const el = await createElement();
|
|
296
|
+
const editor = requireElement(
|
|
297
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
298
|
+
"expected compose editor",
|
|
299
|
+
);
|
|
300
|
+
editor._bodyJson = {
|
|
301
|
+
type: "doc",
|
|
302
|
+
content: [
|
|
303
|
+
{
|
|
304
|
+
type: "paragraph",
|
|
305
|
+
content: [{ type: "text", text: "Draft content" }],
|
|
306
|
+
},
|
|
307
|
+
],
|
|
308
|
+
};
|
|
309
|
+
await editor.updateComplete;
|
|
310
|
+
|
|
311
|
+
// Click the draft header button — should show confirm panel
|
|
312
|
+
const draftBtn = requireElement(
|
|
313
|
+
el.querySelector<HTMLButtonElement>(".compose-dialog-header-btn"),
|
|
314
|
+
"expected draft button",
|
|
315
|
+
);
|
|
316
|
+
draftBtn.click();
|
|
317
|
+
await el.updateComplete;
|
|
318
|
+
|
|
319
|
+
expect(el._confirmPanelOpen).toBe(true);
|
|
320
|
+
expect(el.querySelector(".compose-confirm-panel")).not.toBeNull();
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it("draft button without content opens drafts panel", async () => {
|
|
324
|
+
const el = await createElement();
|
|
325
|
+
|
|
326
|
+
// Mock fetch for drafts list
|
|
327
|
+
const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue(
|
|
328
|
+
new Response(JSON.stringify({ posts: [] }), {
|
|
329
|
+
status: 200,
|
|
330
|
+
headers: { "Content-Type": "application/json" },
|
|
331
|
+
}),
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
// Click the draft header button — should open drafts panel
|
|
335
|
+
const draftBtn = requireElement(
|
|
336
|
+
el.querySelector<HTMLButtonElement>(".compose-dialog-header-btn"),
|
|
337
|
+
"expected draft button",
|
|
338
|
+
);
|
|
339
|
+
draftBtn.click();
|
|
340
|
+
await el.updateComplete;
|
|
341
|
+
|
|
342
|
+
expect(el._draftsPanelOpen).toBe(true);
|
|
343
|
+
|
|
344
|
+
// Wait for fetch to resolve
|
|
345
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
346
|
+
await el.updateComplete;
|
|
347
|
+
|
|
348
|
+
expect(el._draftsLoading).toBe(false);
|
|
349
|
+
expect(el.querySelector(".compose-drafts-panel")).not.toBeNull();
|
|
350
|
+
|
|
351
|
+
fetchSpy.mockRestore();
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it("does not dispatch submit when loading", async () => {
|
|
355
|
+
const el = await createElement();
|
|
356
|
+
el._loading = true;
|
|
357
|
+
await el.updateComplete;
|
|
358
|
+
|
|
359
|
+
let dispatched = false;
|
|
360
|
+
el.addEventListener("jant:compose-submit-deferred", () => {
|
|
361
|
+
dispatched = true;
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
const postBtn = requireElement(
|
|
365
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-main"),
|
|
366
|
+
"expected post button",
|
|
367
|
+
);
|
|
368
|
+
postBtn.click();
|
|
369
|
+
|
|
370
|
+
expect(dispatched).toBe(false);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it("loading state shows spinner in submit button", async () => {
|
|
374
|
+
const el = await createElement();
|
|
375
|
+
el._loading = true;
|
|
376
|
+
await el.updateComplete;
|
|
377
|
+
|
|
378
|
+
const spinner = el.querySelector(".compose-publish-main .animate-spin");
|
|
379
|
+
expect(spinner).not.toBeNull();
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it("no old media picker dialog is rendered", async () => {
|
|
383
|
+
const el = await createElement();
|
|
384
|
+
|
|
385
|
+
expect(el.querySelector("#compose-media-picker")).toBeNull();
|
|
386
|
+
expect(el.querySelector(".compose-media-picker")).toBeNull();
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it("editor renders attachments when present", async () => {
|
|
390
|
+
const el = await createElement();
|
|
391
|
+
const editor = requireElement(
|
|
392
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
393
|
+
"expected compose editor",
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
// Simulate adding an attachment
|
|
397
|
+
const blob = new Blob(["fake-image"], { type: "image/png" });
|
|
398
|
+
const file = new File([blob], "test.png", { type: "image/png" });
|
|
399
|
+
const previewUrl = URL.createObjectURL(blob);
|
|
400
|
+
|
|
401
|
+
editor._attachments = [
|
|
402
|
+
{
|
|
403
|
+
clientId: "test-id-1",
|
|
404
|
+
file,
|
|
405
|
+
previewUrl,
|
|
406
|
+
status: "done",
|
|
407
|
+
progress: null,
|
|
408
|
+
mediaId: "media-1",
|
|
409
|
+
alt: "",
|
|
410
|
+
error: null,
|
|
411
|
+
summary: null,
|
|
412
|
+
chars: null,
|
|
413
|
+
},
|
|
414
|
+
];
|
|
415
|
+
editor._attachmentOrder = ["test-id-1"];
|
|
416
|
+
await editor.updateComplete;
|
|
417
|
+
|
|
418
|
+
// Thumbnail strip should be visible
|
|
419
|
+
expect(editor.querySelector(".compose-attachments")).not.toBeNull();
|
|
420
|
+
expect(editor.querySelector(".compose-attachment-thumb")).not.toBeNull();
|
|
421
|
+
// ALT button should be visible
|
|
422
|
+
expect(editor.querySelector(".compose-attachment-alt")).not.toBeNull();
|
|
423
|
+
// Media tool button should show inline "Add" label
|
|
424
|
+
const mediaBtn =
|
|
425
|
+
editor.querySelector<HTMLButtonElement>(".compose-tool-btn");
|
|
426
|
+
expect(mediaBtn?.querySelector(".compose-tool-label")?.textContent).toBe(
|
|
427
|
+
"Add",
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
URL.revokeObjectURL(previewUrl);
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
it("remove button clears attachment", async () => {
|
|
434
|
+
const el = await createElement();
|
|
435
|
+
const editor = requireElement(
|
|
436
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
437
|
+
"expected compose editor",
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
const blob = new Blob(["fake-image"], { type: "image/png" });
|
|
441
|
+
const file = new File([blob], "test.png", { type: "image/png" });
|
|
442
|
+
const previewUrl = URL.createObjectURL(blob);
|
|
443
|
+
|
|
444
|
+
editor._attachments = [
|
|
445
|
+
{
|
|
446
|
+
clientId: "test-id-1",
|
|
447
|
+
file,
|
|
448
|
+
previewUrl,
|
|
449
|
+
status: "done",
|
|
450
|
+
progress: null,
|
|
451
|
+
mediaId: "media-1",
|
|
452
|
+
alt: "",
|
|
453
|
+
error: null,
|
|
454
|
+
summary: null,
|
|
455
|
+
chars: null,
|
|
456
|
+
},
|
|
457
|
+
];
|
|
458
|
+
editor._attachmentOrder = ["test-id-1"];
|
|
459
|
+
await editor.updateComplete;
|
|
460
|
+
|
|
461
|
+
// Click remove button
|
|
462
|
+
const removeBtn = requireElement(
|
|
463
|
+
editor.querySelector<HTMLButtonElement>(".compose-attachment-remove"),
|
|
464
|
+
"expected remove button",
|
|
465
|
+
);
|
|
466
|
+
removeBtn.click();
|
|
467
|
+
await editor.updateComplete;
|
|
468
|
+
|
|
469
|
+
// Attachment strip should be gone (no attachments)
|
|
470
|
+
expect(editor.querySelector(".compose-attachments")).toBeNull();
|
|
471
|
+
expect(editor._attachments.length).toBe(0);
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
it("alt panel opens and closes", async () => {
|
|
475
|
+
const el = await createElement();
|
|
476
|
+
const editor = requireElement(
|
|
477
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
478
|
+
"expected compose editor",
|
|
479
|
+
);
|
|
480
|
+
|
|
481
|
+
const blob = new Blob(["fake-image"], { type: "image/png" });
|
|
482
|
+
const file = new File([blob], "test.png", { type: "image/png" });
|
|
483
|
+
const previewUrl = URL.createObjectURL(blob);
|
|
484
|
+
|
|
485
|
+
editor._attachments = [
|
|
486
|
+
{
|
|
487
|
+
clientId: "test-id-1",
|
|
488
|
+
file,
|
|
489
|
+
previewUrl,
|
|
490
|
+
status: "done",
|
|
491
|
+
progress: null,
|
|
492
|
+
mediaId: "media-1",
|
|
493
|
+
alt: "",
|
|
494
|
+
error: null,
|
|
495
|
+
summary: null,
|
|
496
|
+
chars: null,
|
|
497
|
+
},
|
|
498
|
+
];
|
|
499
|
+
editor._attachmentOrder = ["test-id-1"];
|
|
500
|
+
await editor.updateComplete;
|
|
501
|
+
|
|
502
|
+
// Click ALT button
|
|
503
|
+
const altBtn = requireElement(
|
|
504
|
+
editor.querySelector<HTMLButtonElement>(".compose-attachment-alt"),
|
|
505
|
+
"expected alt button",
|
|
506
|
+
);
|
|
507
|
+
altBtn.click();
|
|
508
|
+
await editor.updateComplete;
|
|
509
|
+
await el.updateComplete;
|
|
510
|
+
|
|
511
|
+
// Alt panel should be visible in the dialog (covers entire dialog)
|
|
512
|
+
expect(el.querySelector(".compose-alt-panel")).not.toBeNull();
|
|
513
|
+
expect(editor._showAltPanel).toBe(true);
|
|
514
|
+
|
|
515
|
+
// Click done to close
|
|
516
|
+
const doneBtn = el.querySelector<HTMLButtonElement>(
|
|
517
|
+
".compose-alt-panel .compose-post-btn",
|
|
518
|
+
);
|
|
519
|
+
doneBtn?.click();
|
|
520
|
+
await el.updateComplete;
|
|
521
|
+
|
|
522
|
+
expect(editor._showAltPanel).toBe(true); // Editor still tracks its own state
|
|
523
|
+
expect(el.querySelector(".compose-alt-panel")).toBeNull();
|
|
524
|
+
|
|
525
|
+
URL.revokeObjectURL(previewUrl);
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
it("submit includes mediaIds and mediaAlts from completed attachments", async () => {
|
|
529
|
+
const el = await createElement();
|
|
530
|
+
const editor = requireElement(
|
|
531
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
532
|
+
"expected compose editor",
|
|
533
|
+
);
|
|
534
|
+
|
|
535
|
+
const blob = new Blob(["fake-image"], { type: "image/png" });
|
|
536
|
+
const file = new File([blob], "test.png", { type: "image/png" });
|
|
537
|
+
const previewUrl = URL.createObjectURL(blob);
|
|
538
|
+
|
|
539
|
+
editor._attachments = [
|
|
540
|
+
{
|
|
541
|
+
clientId: "test-id-1",
|
|
542
|
+
file,
|
|
543
|
+
previewUrl,
|
|
544
|
+
status: "done",
|
|
545
|
+
progress: null,
|
|
546
|
+
mediaId: "media-1",
|
|
547
|
+
alt: "A test image",
|
|
548
|
+
error: null,
|
|
549
|
+
summary: null,
|
|
550
|
+
chars: null,
|
|
551
|
+
},
|
|
552
|
+
];
|
|
553
|
+
editor._bodyJson = {
|
|
554
|
+
type: "doc",
|
|
555
|
+
content: [
|
|
556
|
+
{
|
|
557
|
+
type: "paragraph",
|
|
558
|
+
content: [{ type: "text", text: "Post with image" }],
|
|
559
|
+
},
|
|
560
|
+
],
|
|
561
|
+
};
|
|
562
|
+
await editor.updateComplete;
|
|
563
|
+
|
|
564
|
+
let receivedDetail:
|
|
565
|
+
| (ComposeSubmitDetail & { pendingAttachments: unknown[] })
|
|
566
|
+
| null = null;
|
|
567
|
+
el.addEventListener("jant:compose-submit-deferred", (event) => {
|
|
568
|
+
const customEvent = event as CustomEvent<
|
|
569
|
+
ComposeSubmitDetail & { pendingAttachments: unknown[] }
|
|
570
|
+
>;
|
|
571
|
+
receivedDetail = customEvent.detail;
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
requireElement(
|
|
575
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-main"),
|
|
576
|
+
"expected post button",
|
|
577
|
+
).click();
|
|
578
|
+
|
|
579
|
+
expect(receivedDetail).not.toBeNull();
|
|
580
|
+
const detail = receivedDetail as unknown as ComposeSubmitDetail & {
|
|
581
|
+
pendingAttachments: unknown[];
|
|
582
|
+
};
|
|
583
|
+
expect(detail.mediaIds).toEqual(["media-1"]);
|
|
584
|
+
expect(detail.mediaAlts).toEqual({ "media-1": "A test image" });
|
|
585
|
+
expect(detail.pendingAttachments).toEqual([]);
|
|
586
|
+
|
|
587
|
+
URL.revokeObjectURL(previewUrl);
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
it("dispatches deferred submit when uploads are pending", async () => {
|
|
591
|
+
const el = await createElement();
|
|
592
|
+
const editor = requireElement(
|
|
593
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
594
|
+
"expected compose editor",
|
|
595
|
+
);
|
|
596
|
+
|
|
597
|
+
const blob = new Blob(["fake-image"], { type: "image/png" });
|
|
598
|
+
const file = new File([blob], "test.png", { type: "image/png" });
|
|
599
|
+
const previewUrl = URL.createObjectURL(blob);
|
|
600
|
+
|
|
601
|
+
editor._attachments = [
|
|
602
|
+
{
|
|
603
|
+
clientId: "test-id-1",
|
|
604
|
+
file,
|
|
605
|
+
previewUrl,
|
|
606
|
+
status: "uploading",
|
|
607
|
+
progress: null,
|
|
608
|
+
mediaId: null,
|
|
609
|
+
alt: "Alt for pending",
|
|
610
|
+
error: null,
|
|
611
|
+
summary: null,
|
|
612
|
+
chars: null,
|
|
613
|
+
},
|
|
614
|
+
];
|
|
615
|
+
editor._bodyJson = {
|
|
616
|
+
type: "doc",
|
|
617
|
+
content: [
|
|
618
|
+
{
|
|
619
|
+
type: "paragraph",
|
|
620
|
+
content: [{ type: "text", text: "Post with pending upload" }],
|
|
621
|
+
},
|
|
622
|
+
],
|
|
623
|
+
};
|
|
624
|
+
await editor.updateComplete;
|
|
625
|
+
|
|
626
|
+
let deferredEvent: CustomEvent | null = null;
|
|
627
|
+
el.addEventListener("jant:compose-submit-deferred", (event) => {
|
|
628
|
+
deferredEvent = event as CustomEvent;
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
requireElement(
|
|
632
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-main"),
|
|
633
|
+
"expected post button",
|
|
634
|
+
).click();
|
|
635
|
+
|
|
636
|
+
expect(deferredEvent).not.toBeNull();
|
|
637
|
+
expect(
|
|
638
|
+
(deferredEvent as unknown as CustomEvent).detail.pendingAttachments,
|
|
639
|
+
).toHaveLength(1);
|
|
640
|
+
|
|
641
|
+
URL.revokeObjectURL(previewUrl);
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
// ── Close confirmation ─────────────────────────────────────────────
|
|
645
|
+
|
|
646
|
+
it("requestClose on empty form closes immediately without confirmation", async () => {
|
|
647
|
+
const el = await createElement();
|
|
648
|
+
|
|
649
|
+
// Ensure no confirmation panel appears
|
|
650
|
+
el.requestClose();
|
|
651
|
+
await el.updateComplete;
|
|
652
|
+
|
|
653
|
+
expect(el._confirmPanelOpen).toBe(false);
|
|
654
|
+
expect(el.querySelector(".compose-confirm-panel")).toBeNull();
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
it("beforeunload does not warn when dialog was only opened", async () => {
|
|
658
|
+
const el = await createElement();
|
|
659
|
+
vi.spyOn(el, "closest").mockReturnValue({
|
|
660
|
+
open: true,
|
|
661
|
+
addEventListener: vi.fn(),
|
|
662
|
+
removeEventListener: vi.fn(),
|
|
663
|
+
} as unknown as HTMLDialogElement);
|
|
664
|
+
|
|
665
|
+
const event = new Event("beforeunload", {
|
|
666
|
+
cancelable: true,
|
|
667
|
+
}) as globalThis.BeforeUnloadEvent;
|
|
668
|
+
|
|
669
|
+
window.dispatchEvent(event);
|
|
670
|
+
|
|
671
|
+
expect(event.defaultPrevented).toBe(false);
|
|
672
|
+
expect(
|
|
673
|
+
(
|
|
674
|
+
el as unknown as { _hasUnsavedChanges: () => boolean }
|
|
675
|
+
)._hasUnsavedChanges(),
|
|
676
|
+
).toBe(false);
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
it("beforeunload warns after compose content changes", async () => {
|
|
680
|
+
const el = await createElement();
|
|
681
|
+
vi.spyOn(el, "closest").mockReturnValue({
|
|
682
|
+
open: true,
|
|
683
|
+
addEventListener: vi.fn(),
|
|
684
|
+
removeEventListener: vi.fn(),
|
|
685
|
+
} as unknown as HTMLDialogElement);
|
|
686
|
+
const editor = requireElement(
|
|
687
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
688
|
+
"expected compose editor",
|
|
689
|
+
);
|
|
690
|
+
|
|
691
|
+
editor._bodyJson = {
|
|
692
|
+
type: "doc",
|
|
693
|
+
content: [
|
|
694
|
+
{ type: "paragraph", content: [{ type: "text", text: "Unsaved" }] },
|
|
695
|
+
],
|
|
696
|
+
};
|
|
697
|
+
await editor.updateComplete;
|
|
698
|
+
|
|
699
|
+
const event = new Event("beforeunload", {
|
|
700
|
+
cancelable: true,
|
|
701
|
+
}) as globalThis.BeforeUnloadEvent;
|
|
702
|
+
|
|
703
|
+
window.dispatchEvent(event);
|
|
704
|
+
|
|
705
|
+
expect(event.defaultPrevented).toBe(true);
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
it("requestClose with content shows confirmation panel", async () => {
|
|
709
|
+
const el = await createElement();
|
|
710
|
+
const editor = requireElement(
|
|
711
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
712
|
+
"expected compose editor",
|
|
713
|
+
);
|
|
714
|
+
editor._bodyJson = {
|
|
715
|
+
type: "doc",
|
|
716
|
+
content: [
|
|
717
|
+
{ type: "paragraph", content: [{ type: "text", text: "Some text" }] },
|
|
718
|
+
],
|
|
719
|
+
};
|
|
720
|
+
await editor.updateComplete;
|
|
721
|
+
|
|
722
|
+
el.requestClose();
|
|
723
|
+
await el.updateComplete;
|
|
724
|
+
|
|
725
|
+
expect(el._confirmPanelOpen).toBe(true);
|
|
726
|
+
expect(el.querySelector(".compose-confirm-panel")).not.toBeNull();
|
|
727
|
+
expect(
|
|
728
|
+
el.querySelector(".compose-confirm-title")?.textContent?.trim(),
|
|
729
|
+
).toBe("Save to drafts?");
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
it("confirm save draft dispatches submit-deferred with draft status", async () => {
|
|
733
|
+
const el = await createElement();
|
|
734
|
+
const editor = requireElement(
|
|
735
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
736
|
+
"expected compose editor",
|
|
737
|
+
);
|
|
738
|
+
editor._bodyJson = {
|
|
739
|
+
type: "doc",
|
|
740
|
+
content: [
|
|
741
|
+
{ type: "paragraph", content: [{ type: "text", text: "Draft me" }] },
|
|
742
|
+
],
|
|
743
|
+
};
|
|
744
|
+
await editor.updateComplete;
|
|
745
|
+
|
|
746
|
+
el.requestClose();
|
|
747
|
+
await el.updateComplete;
|
|
748
|
+
|
|
749
|
+
let receivedDetail: ComposeSubmitDetail | null = null;
|
|
750
|
+
el.addEventListener("jant:compose-submit-deferred", (event) => {
|
|
751
|
+
receivedDetail = (event as CustomEvent<ComposeSubmitDetail>).detail;
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
const saveBtn = requireElement(
|
|
755
|
+
el.querySelector<HTMLButtonElement>(".compose-confirm-save"),
|
|
756
|
+
"expected save draft button",
|
|
757
|
+
);
|
|
758
|
+
saveBtn.click();
|
|
759
|
+
await el.updateComplete;
|
|
760
|
+
|
|
761
|
+
expect(receivedDetail).not.toBeNull();
|
|
762
|
+
expect((receivedDetail as unknown as ComposeSubmitDetail).status).toBe(
|
|
763
|
+
"draft",
|
|
764
|
+
);
|
|
765
|
+
expect(el._confirmPanelOpen).toBe(false);
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
it("confirm cancel returns to editor without closing", async () => {
|
|
769
|
+
const el = await createElement();
|
|
770
|
+
const editor = requireElement(
|
|
771
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
772
|
+
"expected compose editor",
|
|
773
|
+
);
|
|
774
|
+
editor._bodyJson = {
|
|
775
|
+
type: "doc",
|
|
776
|
+
content: [
|
|
777
|
+
{
|
|
778
|
+
type: "paragraph",
|
|
779
|
+
content: [{ type: "text", text: "Keep editing" }],
|
|
780
|
+
},
|
|
781
|
+
],
|
|
782
|
+
};
|
|
783
|
+
await editor.updateComplete;
|
|
784
|
+
|
|
785
|
+
el.requestClose();
|
|
786
|
+
await el.updateComplete;
|
|
787
|
+
|
|
788
|
+
expect(el._confirmPanelOpen).toBe(true);
|
|
789
|
+
|
|
790
|
+
const cancelBtn = requireElement(
|
|
791
|
+
el.querySelector<HTMLButtonElement>(".compose-confirm-cancel"),
|
|
792
|
+
"expected cancel button",
|
|
793
|
+
);
|
|
794
|
+
const focusSpy = vi.spyOn(editor, "focusInput");
|
|
795
|
+
cancelBtn.click();
|
|
796
|
+
await el.updateComplete;
|
|
797
|
+
|
|
798
|
+
expect(el._confirmPanelOpen).toBe(false);
|
|
799
|
+
expect(focusSpy).toHaveBeenCalled();
|
|
800
|
+
// Editor content should be preserved
|
|
801
|
+
expect(editor._bodyJson).toEqual({
|
|
802
|
+
type: "doc",
|
|
803
|
+
content: [
|
|
804
|
+
{
|
|
805
|
+
type: "paragraph",
|
|
806
|
+
content: [{ type: "text", text: "Keep editing" }],
|
|
807
|
+
},
|
|
808
|
+
],
|
|
809
|
+
});
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
it("requestClose on confirm panel dismisses it (Escape = Cancel)", async () => {
|
|
813
|
+
const el = await createElement();
|
|
814
|
+
const editor = requireElement(
|
|
815
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
816
|
+
"expected compose editor",
|
|
817
|
+
);
|
|
818
|
+
editor._bodyJson = {
|
|
819
|
+
type: "doc",
|
|
820
|
+
content: [
|
|
821
|
+
{ type: "paragraph", content: [{ type: "text", text: "Esc test" }] },
|
|
822
|
+
],
|
|
823
|
+
};
|
|
824
|
+
await editor.updateComplete;
|
|
825
|
+
|
|
826
|
+
el.requestClose();
|
|
827
|
+
await el.updateComplete;
|
|
828
|
+
expect(el._confirmPanelOpen).toBe(true);
|
|
829
|
+
|
|
830
|
+
// Second requestClose (same path as Escape via dialog oncancel)
|
|
831
|
+
el.requestClose();
|
|
832
|
+
await el.updateComplete;
|
|
833
|
+
|
|
834
|
+
expect(el._confirmPanelOpen).toBe(false);
|
|
835
|
+
// Content should be preserved (not discarded)
|
|
836
|
+
expect(editor._bodyJson).toEqual({
|
|
837
|
+
type: "doc",
|
|
838
|
+
content: [
|
|
839
|
+
{ type: "paragraph", content: [{ type: "text", text: "Esc test" }] },
|
|
840
|
+
],
|
|
841
|
+
});
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
it("confirm discard closes and resets", async () => {
|
|
845
|
+
const el = await createElement();
|
|
846
|
+
const editor = requireElement(
|
|
847
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
848
|
+
"expected compose editor",
|
|
849
|
+
);
|
|
850
|
+
editor._bodyJson = {
|
|
851
|
+
type: "doc",
|
|
852
|
+
content: [
|
|
853
|
+
{
|
|
854
|
+
type: "paragraph",
|
|
855
|
+
content: [{ type: "text", text: "Will discard" }],
|
|
856
|
+
},
|
|
857
|
+
],
|
|
858
|
+
};
|
|
859
|
+
await editor.updateComplete;
|
|
860
|
+
|
|
861
|
+
el.requestClose();
|
|
862
|
+
await el.updateComplete;
|
|
863
|
+
|
|
864
|
+
const discardBtn = requireElement(
|
|
865
|
+
el.querySelector<HTMLButtonElement>(".compose-confirm-discard"),
|
|
866
|
+
"expected discard button",
|
|
867
|
+
);
|
|
868
|
+
discardBtn.click();
|
|
869
|
+
await el.updateComplete;
|
|
870
|
+
|
|
871
|
+
expect(el._confirmPanelOpen).toBe(false);
|
|
872
|
+
expect(el._format).toBe("note");
|
|
873
|
+
expect(el._collectionIds).toEqual([]);
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
it("loaded draft shows format switcher and Post button, not edit mode", async () => {
|
|
877
|
+
const el = await createElement();
|
|
878
|
+
|
|
879
|
+
// Simulate what _loadDraft sets (without fetching)
|
|
880
|
+
el._draftSourceId = "draft123";
|
|
881
|
+
el._format = "note";
|
|
882
|
+
await el.updateComplete;
|
|
883
|
+
|
|
884
|
+
// Format switcher should be visible (not "Edit post" title)
|
|
885
|
+
expect(el.querySelector(".compose-segmented")).not.toBeNull();
|
|
886
|
+
expect(el.querySelector(".compose-dialog-title")).toBeNull();
|
|
887
|
+
|
|
888
|
+
// Button should say "Post", not "Done"
|
|
889
|
+
const postBtn = requireElement(
|
|
890
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-main"),
|
|
891
|
+
"expected post button",
|
|
892
|
+
);
|
|
893
|
+
expect(postBtn.textContent?.trim()).toBe("Post");
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
it("discard on loaded draft sends DELETE request", async () => {
|
|
897
|
+
const el = await createElement();
|
|
898
|
+
const editor = requireElement(
|
|
899
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
900
|
+
"expected compose editor",
|
|
901
|
+
);
|
|
902
|
+
|
|
903
|
+
// Simulate loaded draft with content
|
|
904
|
+
el._draftSourceId = "draft456";
|
|
905
|
+
editor._bodyJson = {
|
|
906
|
+
type: "doc",
|
|
907
|
+
content: [
|
|
908
|
+
{
|
|
909
|
+
type: "paragraph",
|
|
910
|
+
content: [{ type: "text", text: "Draft content" }],
|
|
911
|
+
},
|
|
912
|
+
],
|
|
913
|
+
};
|
|
914
|
+
await editor.updateComplete;
|
|
915
|
+
|
|
916
|
+
const fetchSpy = vi
|
|
917
|
+
.spyOn(globalThis, "fetch")
|
|
918
|
+
.mockResolvedValue(new Response(null, { status: 200 }));
|
|
919
|
+
|
|
920
|
+
el.requestClose();
|
|
921
|
+
await el.updateComplete;
|
|
922
|
+
|
|
923
|
+
// Click "Don't save" (discard)
|
|
924
|
+
const discardBtn = requireElement(
|
|
925
|
+
el.querySelector<HTMLButtonElement>(".compose-confirm-discard"),
|
|
926
|
+
"expected discard button",
|
|
927
|
+
);
|
|
928
|
+
discardBtn.click();
|
|
929
|
+
await el.updateComplete;
|
|
930
|
+
|
|
931
|
+
expect(fetchSpy).toHaveBeenCalledWith("/api/posts/draft456", {
|
|
932
|
+
method: "DELETE",
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
fetchSpy.mockRestore();
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
it("submit from loaded draft includes draftSourceId as editPostId", async () => {
|
|
939
|
+
const el = await createElement();
|
|
940
|
+
const editor = requireElement(
|
|
941
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
942
|
+
"expected compose editor",
|
|
943
|
+
);
|
|
944
|
+
|
|
945
|
+
el._draftSourceId = "draft789";
|
|
946
|
+
editor._bodyJson = {
|
|
947
|
+
type: "doc",
|
|
948
|
+
content: [
|
|
949
|
+
{
|
|
950
|
+
type: "paragraph",
|
|
951
|
+
content: [{ type: "text", text: "Publish this draft" }],
|
|
952
|
+
},
|
|
953
|
+
],
|
|
954
|
+
};
|
|
955
|
+
await editor.updateComplete;
|
|
956
|
+
|
|
957
|
+
let receivedDetail: ComposeSubmitDetail | null = null;
|
|
958
|
+
el.addEventListener("jant:compose-submit-deferred", (event) => {
|
|
959
|
+
receivedDetail = (event as CustomEvent<ComposeSubmitDetail>).detail;
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
requireElement(
|
|
963
|
+
el.querySelector<HTMLButtonElement>(".compose-publish-main"),
|
|
964
|
+
"expected post button",
|
|
965
|
+
).click();
|
|
966
|
+
|
|
967
|
+
expect(receivedDetail).not.toBeNull();
|
|
968
|
+
expect((receivedDetail as unknown as ComposeSubmitDetail).editPostId).toBe(
|
|
969
|
+
"draft789",
|
|
970
|
+
);
|
|
971
|
+
expect((receivedDetail as unknown as ComposeSubmitDetail).status).toBe(
|
|
972
|
+
"published",
|
|
973
|
+
);
|
|
974
|
+
});
|
|
975
|
+
|
|
976
|
+
it("draft button confirm save dispatches draft then opens drafts panel", async () => {
|
|
977
|
+
const el = await createElement();
|
|
978
|
+
const editor = requireElement(
|
|
979
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
980
|
+
"expected compose editor",
|
|
981
|
+
);
|
|
982
|
+
editor._bodyJson = {
|
|
983
|
+
type: "doc",
|
|
984
|
+
content: [
|
|
985
|
+
{
|
|
986
|
+
type: "paragraph",
|
|
987
|
+
content: [{ type: "text", text: "Save then browse" }],
|
|
988
|
+
},
|
|
989
|
+
],
|
|
990
|
+
};
|
|
991
|
+
await editor.updateComplete;
|
|
992
|
+
|
|
993
|
+
const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue(
|
|
994
|
+
new Response(JSON.stringify({ posts: [] }), {
|
|
995
|
+
status: 200,
|
|
996
|
+
headers: { "Content-Type": "application/json" },
|
|
997
|
+
}),
|
|
998
|
+
);
|
|
999
|
+
|
|
1000
|
+
let receivedDetail: ComposeSubmitDetail | null = null;
|
|
1001
|
+
el.addEventListener("jant:compose-submit-deferred", (event) => {
|
|
1002
|
+
receivedDetail = (event as CustomEvent<ComposeSubmitDetail>).detail;
|
|
1003
|
+
});
|
|
1004
|
+
|
|
1005
|
+
// Click draft button → confirm panel
|
|
1006
|
+
const draftBtn = requireElement(
|
|
1007
|
+
el.querySelector<HTMLButtonElement>(".compose-dialog-header-btn"),
|
|
1008
|
+
"expected draft button",
|
|
1009
|
+
);
|
|
1010
|
+
draftBtn.click();
|
|
1011
|
+
await el.updateComplete;
|
|
1012
|
+
expect(el._confirmPanelOpen).toBe(true);
|
|
1013
|
+
|
|
1014
|
+
// Click "Save"
|
|
1015
|
+
requireElement(
|
|
1016
|
+
el.querySelector<HTMLButtonElement>(".compose-confirm-save"),
|
|
1017
|
+
"expected save button",
|
|
1018
|
+
).click();
|
|
1019
|
+
await el.updateComplete;
|
|
1020
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
1021
|
+
await el.updateComplete;
|
|
1022
|
+
|
|
1023
|
+
// Draft submitted
|
|
1024
|
+
expect(receivedDetail).not.toBeNull();
|
|
1025
|
+
expect((receivedDetail as unknown as ComposeSubmitDetail).status).toBe(
|
|
1026
|
+
"draft",
|
|
1027
|
+
);
|
|
1028
|
+
// Drafts panel opened instead of dialog closing
|
|
1029
|
+
expect(el._draftsPanelOpen).toBe(true);
|
|
1030
|
+
expect(el._confirmPanelOpen).toBe(false);
|
|
1031
|
+
|
|
1032
|
+
fetchSpy.mockRestore();
|
|
1033
|
+
});
|
|
1034
|
+
|
|
1035
|
+
it("draft button confirm discard opens drafts panel without saving", async () => {
|
|
1036
|
+
const el = await createElement();
|
|
1037
|
+
const editor = requireElement(
|
|
1038
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
1039
|
+
"expected compose editor",
|
|
1040
|
+
);
|
|
1041
|
+
editor._bodyJson = {
|
|
1042
|
+
type: "doc",
|
|
1043
|
+
content: [
|
|
1044
|
+
{
|
|
1045
|
+
type: "paragraph",
|
|
1046
|
+
content: [{ type: "text", text: "Discard then browse" }],
|
|
1047
|
+
},
|
|
1048
|
+
],
|
|
1049
|
+
};
|
|
1050
|
+
await editor.updateComplete;
|
|
1051
|
+
|
|
1052
|
+
const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue(
|
|
1053
|
+
new Response(JSON.stringify({ posts: [] }), {
|
|
1054
|
+
status: 200,
|
|
1055
|
+
headers: { "Content-Type": "application/json" },
|
|
1056
|
+
}),
|
|
1057
|
+
);
|
|
1058
|
+
|
|
1059
|
+
let submitFired = false;
|
|
1060
|
+
el.addEventListener("jant:compose-submit-deferred", () => {
|
|
1061
|
+
submitFired = true;
|
|
1062
|
+
});
|
|
1063
|
+
|
|
1064
|
+
// Click draft button → confirm panel
|
|
1065
|
+
const draftBtn = requireElement(
|
|
1066
|
+
el.querySelector<HTMLButtonElement>(".compose-dialog-header-btn"),
|
|
1067
|
+
"expected draft button",
|
|
1068
|
+
);
|
|
1069
|
+
draftBtn.click();
|
|
1070
|
+
await el.updateComplete;
|
|
1071
|
+
|
|
1072
|
+
// Click "Don't save"
|
|
1073
|
+
requireElement(
|
|
1074
|
+
el.querySelector<HTMLButtonElement>(".compose-confirm-discard"),
|
|
1075
|
+
"expected discard button",
|
|
1076
|
+
).click();
|
|
1077
|
+
await el.updateComplete;
|
|
1078
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
1079
|
+
await el.updateComplete;
|
|
1080
|
+
|
|
1081
|
+
// No submit dispatched
|
|
1082
|
+
expect(submitFired).toBe(false);
|
|
1083
|
+
// Drafts panel opened
|
|
1084
|
+
expect(el._draftsPanelOpen).toBe(true);
|
|
1085
|
+
expect(el._confirmPanelOpen).toBe(false);
|
|
1086
|
+
|
|
1087
|
+
fetchSpy.mockRestore();
|
|
1088
|
+
});
|
|
1089
|
+
|
|
1090
|
+
it("attachments detected as content for confirmation", async () => {
|
|
1091
|
+
const el = await createElement();
|
|
1092
|
+
const editor = requireElement(
|
|
1093
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
1094
|
+
"expected compose editor",
|
|
1095
|
+
);
|
|
1096
|
+
|
|
1097
|
+
const blob = new Blob(["fake-image"], { type: "image/png" });
|
|
1098
|
+
const file = new File([blob], "test.png", { type: "image/png" });
|
|
1099
|
+
const previewUrl = URL.createObjectURL(blob);
|
|
1100
|
+
|
|
1101
|
+
editor._attachments = [
|
|
1102
|
+
{
|
|
1103
|
+
clientId: "test-id-1",
|
|
1104
|
+
file,
|
|
1105
|
+
previewUrl,
|
|
1106
|
+
status: "done",
|
|
1107
|
+
progress: null,
|
|
1108
|
+
mediaId: "media-1",
|
|
1109
|
+
alt: "",
|
|
1110
|
+
error: null,
|
|
1111
|
+
summary: null,
|
|
1112
|
+
chars: null,
|
|
1113
|
+
},
|
|
1114
|
+
];
|
|
1115
|
+
await editor.updateComplete;
|
|
1116
|
+
|
|
1117
|
+
el.requestClose();
|
|
1118
|
+
await el.updateComplete;
|
|
1119
|
+
|
|
1120
|
+
expect(el._confirmPanelOpen).toBe(true);
|
|
1121
|
+
expect(el.querySelector(".compose-confirm-panel")).not.toBeNull();
|
|
1122
|
+
|
|
1123
|
+
URL.revokeObjectURL(previewUrl);
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1126
|
+
it("rating detected as content for confirmation", async () => {
|
|
1127
|
+
const el = await createElement();
|
|
1128
|
+
const editor = requireElement(
|
|
1129
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
1130
|
+
"expected compose editor",
|
|
1131
|
+
);
|
|
1132
|
+
editor._rating = 3;
|
|
1133
|
+
await editor.updateComplete;
|
|
1134
|
+
|
|
1135
|
+
el.requestClose();
|
|
1136
|
+
await el.updateComplete;
|
|
1137
|
+
|
|
1138
|
+
expect(el._confirmPanelOpen).toBe(true);
|
|
1139
|
+
});
|
|
1140
|
+
});
|