@jant/core 0.3.27 → 0.3.29
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/reset-password.js +22 -0
- package/dist/client/client.css +1 -0
- package/dist/client/client.js +31561 -0
- package/dist/index.js +15209 -15
- package/package.json +25 -15
- package/src/__tests__/helpers/app.ts +19 -3
- package/src/__tests__/helpers/db.ts +44 -0
- package/src/__tests__/helpers/lingui-core-macro-mock.ts +33 -0
- package/src/app.tsx +111 -174
- package/src/client.ts +13 -0
- package/src/db/migrations/0007_post_collections_m2m.sql +94 -0
- package/src/db/migrations/0008_add_collection_dividers.sql +8 -0
- package/src/db/migrations/0009_drop_collection_show_divider.sql +2 -0
- package/src/db/migrations/0010_add_performance_indexes.sql +16 -0
- package/src/db/schema.ts +24 -4
- package/src/i18n/locales/en.po +810 -385
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +733 -522
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +733 -522
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/i18n/middleware.ts +7 -11
- package/src/index.ts +1 -1
- package/src/lib/__tests__/icons.test.ts +178 -0
- package/src/lib/__tests__/resolve-config.test.ts +184 -0
- package/src/lib/__tests__/schemas.test.ts +12 -6
- package/src/lib/__tests__/theme.test.ts +62 -0
- package/src/lib/__tests__/timezones.test.ts +1 -1
- package/src/lib/__tests__/url.test.ts +12 -0
- package/src/lib/__tests__/view.test.ts +1 -5
- package/src/lib/avatar-upload.ts +18 -10
- package/src/lib/collection-form-bridge.ts +52 -0
- package/src/lib/collections-reorder.ts +28 -0
- package/src/lib/compose-bridge.ts +251 -0
- package/src/lib/errors.ts +116 -0
- package/src/lib/excerpt.ts +1 -1
- package/src/lib/favicon.ts +3 -5
- package/src/lib/html.ts +22 -0
- package/src/lib/icon-catalog.ts +181 -0
- package/src/lib/icons.ts +202 -0
- package/src/lib/navigation.ts +18 -33
- package/src/lib/pagination.ts +3 -2
- package/src/lib/post-form-bridge.ts +136 -0
- package/src/lib/render.tsx +11 -4
- package/src/lib/resolve-config.ts +157 -0
- package/src/lib/schemas.ts +76 -12
- package/src/lib/settings-bridge.ts +139 -0
- package/src/lib/storage.ts +37 -16
- package/src/lib/theme.ts +5 -7
- package/src/lib/timeline.ts +4 -8
- package/src/lib/toast.ts +134 -0
- package/src/lib/upload.ts +71 -0
- package/src/lib/url.ts +9 -1
- package/src/lib/version.ts +16 -0
- package/src/lib/view.ts +9 -10
- package/src/middleware/__tests__/auth.test.ts +6 -28
- package/src/middleware/__tests__/onboarding.test.ts +1 -1
- package/src/middleware/auth.ts +6 -12
- package/src/middleware/config.ts +51 -0
- package/src/middleware/error-handler.ts +56 -0
- package/src/middleware/onboarding.ts +1 -1
- package/src/preset.css +6 -0
- package/src/routes/__tests__/compose.test.ts +104 -17
- package/src/routes/api/__tests__/collections.test.ts +93 -2
- package/src/routes/api/__tests__/posts.test.ts +2 -1
- package/src/routes/api/__tests__/settings.test.ts +1 -1
- package/src/routes/api/collections.ts +64 -68
- package/src/routes/api/nav-items.ts +21 -59
- package/src/routes/api/pages.ts +18 -46
- package/src/routes/api/posts.ts +64 -86
- package/src/routes/api/search.ts +6 -4
- package/src/routes/api/settings.ts +8 -24
- package/src/routes/api/upload.ts +55 -53
- package/src/routes/auth/__tests__/setup.test.ts +118 -0
- package/src/routes/auth/reset.tsx +17 -66
- package/src/routes/auth/setup.tsx +67 -11
- package/src/routes/auth/signin.tsx +44 -8
- package/src/routes/compose.tsx +194 -0
- package/src/routes/dash/__tests__/font-theme.test.ts +110 -0
- package/src/routes/dash/__tests__/pages.test.ts +2 -2
- package/src/routes/dash/__tests__/settings-avatar.test.ts +23 -12
- package/src/routes/dash/appearance.tsx +173 -0
- package/src/routes/dash/collections.tsx +80 -14
- package/src/routes/dash/index.tsx +12 -14
- package/src/routes/dash/media.tsx +46 -49
- package/src/routes/dash/pages.tsx +85 -37
- package/src/routes/dash/posts.tsx +60 -23
- package/src/routes/dash/redirects.tsx +43 -33
- package/src/routes/dash/settings.tsx +234 -214
- package/src/routes/feed/__tests__/rss.test.ts +7 -3
- package/src/routes/feed/rss.ts +11 -16
- package/src/routes/feed/sitemap.ts +15 -9
- package/src/routes/pages/__tests__/collections.test.ts +9 -8
- package/src/routes/pages/archive.tsx +2 -2
- package/src/routes/pages/collection.tsx +76 -9
- package/src/routes/pages/collections.tsx +3 -1
- package/src/routes/pages/featured.tsx +2 -2
- package/src/routes/pages/home.tsx +3 -3
- package/src/routes/pages/latest.tsx +2 -2
- package/src/routes/pages/page.tsx +2 -2
- package/src/routes/pages/post.tsx +2 -2
- package/src/routes/pages/search.tsx +2 -2
- package/src/services/__tests__/collection.test.ts +324 -34
- package/src/services/__tests__/media.test.ts +1 -1
- package/src/services/__tests__/page.test.ts +116 -1
- package/src/services/auth.ts +88 -0
- package/src/services/collection.ts +169 -30
- package/src/services/index.ts +8 -3
- package/src/services/media.ts +39 -12
- package/src/services/navigation.ts +17 -5
- package/src/services/page.ts +24 -4
- package/src/services/post.ts +87 -19
- package/src/services/search.ts +0 -1
- package/src/services/settings.ts +21 -13
- package/src/style.css +3 -0
- package/src/styles/components.css +42 -1
- package/src/styles/tokens.css +4 -0
- package/src/styles/ui.css +902 -73
- package/src/types/app-context.ts +25 -0
- package/src/types/bindings.ts +1 -0
- package/src/types/config.ts +60 -23
- package/src/types/entities.ts +12 -2
- package/src/types/lingui-react-macro.d.ts +3 -3
- package/src/types/operations.ts +2 -4
- package/src/types/views.ts +1 -3
- package/src/ui/__tests__/font-themes.test.ts +27 -8
- package/src/ui/color-themes.ts +1 -1
- package/src/ui/components/__tests__/jant-collection-form.test.ts +153 -0
- package/src/ui/components/__tests__/jant-compose-dialog.test.ts +512 -0
- package/src/ui/components/__tests__/jant-compose-editor.test.ts +272 -0
- package/src/ui/components/__tests__/jant-post-form.test.ts +172 -0
- package/src/ui/components/__tests__/jant-settings-avatar.test.ts +235 -0
- package/src/ui/components/__tests__/jant-settings-general.test.ts +319 -0
- package/src/ui/components/collection-types.ts +45 -0
- package/src/ui/components/compose-types.ts +75 -0
- package/src/ui/components/jant-collection-form.ts +512 -0
- package/src/ui/components/jant-compose-dialog.ts +494 -0
- package/src/ui/components/jant-compose-editor.ts +799 -0
- package/src/ui/components/jant-post-form.ts +290 -0
- package/src/ui/components/jant-settings-avatar.ts +231 -0
- package/src/ui/components/jant-settings-general.ts +436 -0
- package/src/ui/components/post-form-template.ts +260 -0
- package/src/ui/components/post-form-types.ts +87 -0
- package/src/ui/components/settings-types.ts +62 -0
- package/src/ui/compose/ComposeDialog.tsx +141 -385
- package/src/ui/compose/ComposePrompt.tsx +3 -3
- package/src/ui/dash/PostList.tsx +55 -61
- package/src/ui/dash/appearance/AdvancedContent.tsx +80 -0
- package/src/ui/dash/appearance/AppearanceNav.tsx +56 -0
- package/src/ui/dash/appearance/ColorThemeContent.tsx +129 -0
- package/src/ui/dash/appearance/FontThemeContent.tsx +98 -0
- package/src/ui/dash/collections/CollectionForm.tsx +130 -117
- package/src/ui/dash/collections/CollectionsListContent.tsx +102 -41
- package/src/ui/dash/collections/IconPickerGrid.tsx +50 -0
- package/src/ui/dash/collections/ViewCollectionContent.tsx +14 -3
- package/src/ui/dash/index.ts +1 -1
- package/src/ui/dash/posts/PostForm.tsx +248 -0
- package/src/ui/dash/settings/AccountContent.tsx +69 -80
- package/src/ui/dash/settings/GeneralContent.tsx +159 -478
- package/src/ui/dash/settings/SettingsNav.tsx +4 -4
- package/src/ui/font-themes.ts +115 -32
- package/src/ui/layouts/BaseLayout.tsx +49 -19
- package/src/ui/layouts/DashLayout.tsx +14 -9
- package/src/ui/layouts/SiteLayout.tsx +38 -23
- package/src/ui/pages/CollectionPage.tsx +12 -2
- package/src/ui/pages/CollectionsPage.tsx +27 -27
- package/src/ui/pages/HomePage.tsx +15 -6
- package/src/ui/pages/SearchPage.tsx +1 -2
- package/src/ui/shared/CollectionsSidebar.tsx +59 -0
- package/src/ui/shared/Pagination.tsx +2 -2
- package/dist/app.js +0 -267
- package/dist/auth.js +0 -39
- package/dist/client.js +0 -13
- package/dist/db/index.js +0 -10
- package/dist/db/schema.js +0 -224
- package/dist/i18n/Trans.js +0 -24
- package/dist/i18n/context.js +0 -58
- package/dist/i18n/detect.js +0 -26
- package/dist/i18n/i18n.js +0 -49
- package/dist/i18n/index.js +0 -44
- package/dist/i18n/locales/en.js +0 -1
- package/dist/i18n/locales/zh-Hans.js +0 -1
- package/dist/i18n/locales/zh-Hant.js +0 -1
- package/dist/i18n/locales.js +0 -13
- package/dist/i18n/middleware.js +0 -30
- package/dist/lib/avatar-upload.js +0 -134
- package/dist/lib/config.js +0 -143
- package/dist/lib/constants.js +0 -50
- package/dist/lib/excerpt.js +0 -76
- package/dist/lib/favicon.js +0 -102
- package/dist/lib/feed.js +0 -123
- package/dist/lib/image-processor.js +0 -187
- package/dist/lib/image.js +0 -97
- package/dist/lib/index.js +0 -7
- package/dist/lib/markdown.js +0 -83
- package/dist/lib/media-helpers.js +0 -49
- package/dist/lib/media-upload.js +0 -104
- package/dist/lib/nav-reorder.js +0 -27
- package/dist/lib/navigation.js +0 -79
- package/dist/lib/pagination.js +0 -44
- package/dist/lib/render.js +0 -53
- package/dist/lib/schemas.js +0 -174
- package/dist/lib/sqid.js +0 -72
- package/dist/lib/sse.js +0 -218
- package/dist/lib/storage.js +0 -164
- package/dist/lib/theme.js +0 -65
- package/dist/lib/time.js +0 -159
- package/dist/lib/timeline.js +0 -95
- package/dist/lib/timezones.js +0 -388
- package/dist/lib/url.js +0 -89
- package/dist/lib/view.js +0 -217
- package/dist/middleware/auth.js +0 -52
- package/dist/middleware/onboarding.js +0 -41
- package/dist/routes/api/collections.js +0 -124
- package/dist/routes/api/nav-items.js +0 -104
- package/dist/routes/api/pages.js +0 -91
- package/dist/routes/api/posts.js +0 -218
- package/dist/routes/api/search.js +0 -48
- package/dist/routes/api/settings.js +0 -68
- package/dist/routes/api/upload.js +0 -246
- package/dist/routes/auth/reset.js +0 -221
- package/dist/routes/auth/setup.js +0 -194
- package/dist/routes/auth/signin.js +0 -176
- package/dist/routes/compose.js +0 -48
- package/dist/routes/dash/collections.js +0 -115
- package/dist/routes/dash/index.js +0 -118
- package/dist/routes/dash/media.js +0 -106
- package/dist/routes/dash/pages.js +0 -294
- package/dist/routes/dash/posts.js +0 -244
- package/dist/routes/dash/redirects.js +0 -257
- package/dist/routes/dash/settings.js +0 -379
- package/dist/routes/feed/rss.js +0 -62
- package/dist/routes/feed/sitemap.js +0 -49
- package/dist/routes/pages/archive.js +0 -62
- package/dist/routes/pages/collection.js +0 -34
- package/dist/routes/pages/collections.js +0 -28
- package/dist/routes/pages/featured.js +0 -36
- package/dist/routes/pages/home.js +0 -64
- package/dist/routes/pages/latest.js +0 -45
- package/dist/routes/pages/page.js +0 -68
- package/dist/routes/pages/post.js +0 -44
- package/dist/routes/pages/search.js +0 -54
- package/dist/services/collection.js +0 -109
- package/dist/services/index.js +0 -24
- package/dist/services/media.js +0 -117
- package/dist/services/navigation.js +0 -91
- package/dist/services/page.js +0 -84
- package/dist/services/post.js +0 -229
- package/dist/services/redirect.js +0 -48
- package/dist/services/search.js +0 -67
- package/dist/services/settings.js +0 -68
- package/dist/types/bindings.js +0 -3
- package/dist/types/config.js +0 -147
- package/dist/types/constants.js +0 -27
- package/dist/types/entities.js +0 -3
- package/dist/types/lingui-react-macro.d.js +0 -9
- package/dist/types/operations.js +0 -3
- package/dist/types/props.js +0 -3
- package/dist/types/sortablejs.d.js +0 -5
- package/dist/types/views.js +0 -5
- package/dist/types.js +0 -11
- package/dist/ui/color-themes.js +0 -268
- package/dist/ui/compose/ComposeDialog.js +0 -467
- package/dist/ui/compose/ComposePrompt.js +0 -55
- package/dist/ui/dash/ActionButtons.js +0 -46
- package/dist/ui/dash/CrudPageHeader.js +0 -22
- package/dist/ui/dash/DangerZone.js +0 -36
- package/dist/ui/dash/FormatBadge.js +0 -27
- package/dist/ui/dash/ListItemRow.js +0 -21
- package/dist/ui/dash/PageForm.js +0 -195
- package/dist/ui/dash/PostForm.js +0 -395
- package/dist/ui/dash/PostList.js +0 -83
- package/dist/ui/dash/StatusBadge.js +0 -46
- package/dist/ui/dash/collections/CollectionForm.js +0 -152
- package/dist/ui/dash/collections/CollectionsListContent.js +0 -68
- package/dist/ui/dash/collections/ViewCollectionContent.js +0 -96
- package/dist/ui/dash/index.js +0 -10
- package/dist/ui/dash/media/MediaListContent.js +0 -166
- package/dist/ui/dash/media/ViewMediaContent.js +0 -212
- package/dist/ui/dash/pages/LinkFormContent.js +0 -130
- package/dist/ui/dash/pages/UnifiedPagesContent.js +0 -193
- package/dist/ui/dash/settings/AccountContent.js +0 -209
- package/dist/ui/dash/settings/AppearanceContent.js +0 -259
- package/dist/ui/dash/settings/GeneralContent.js +0 -536
- package/dist/ui/dash/settings/SettingsNav.js +0 -41
- package/dist/ui/feed/LinkCard.js +0 -72
- package/dist/ui/feed/NoteCard.js +0 -58
- package/dist/ui/feed/QuoteCard.js +0 -63
- package/dist/ui/feed/ThreadPreview.js +0 -48
- package/dist/ui/feed/TimelineFeed.js +0 -41
- package/dist/ui/feed/TimelineItem.js +0 -27
- package/dist/ui/font-themes.js +0 -36
- package/dist/ui/layouts/BaseLayout.js +0 -153
- package/dist/ui/layouts/DashLayout.js +0 -141
- package/dist/ui/layouts/SiteLayout.js +0 -169
- package/dist/ui/pages/ArchivePage.js +0 -143
- package/dist/ui/pages/CollectionPage.js +0 -70
- package/dist/ui/pages/CollectionsPage.js +0 -76
- package/dist/ui/pages/FeaturedPage.js +0 -24
- package/dist/ui/pages/HomePage.js +0 -24
- package/dist/ui/pages/PostPage.js +0 -55
- package/dist/ui/pages/SearchPage.js +0 -122
- package/dist/ui/pages/SinglePage.js +0 -23
- package/dist/ui/shared/EmptyState.js +0 -27
- package/dist/ui/shared/MediaGallery.js +0 -35
- package/dist/ui/shared/Pagination.js +0 -195
- package/dist/ui/shared/ThreadView.js +0 -108
- package/dist/ui/shared/index.js +0 -5
- package/dist/vendor/datastar.js +0 -1606
- package/src/lib/__tests__/config.test.ts +0 -192
- package/src/lib/config.ts +0 -167
- package/src/routes/compose.ts +0 -63
- package/src/ui/dash/PostForm.tsx +0 -360
- package/src/ui/dash/settings/AppearanceContent.tsx +0 -254
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
|
|
3
|
+
import { describe, it, expect, beforeEach } 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
|
+
score: "Score",
|
|
46
|
+
title: "Title",
|
|
47
|
+
collection: "Collection",
|
|
48
|
+
searchCollections: "Search...",
|
|
49
|
+
noCollections: "No collections found.",
|
|
50
|
+
post: "Post",
|
|
51
|
+
addAlt: "+ ALT",
|
|
52
|
+
addAltTitle: "Add alt text",
|
|
53
|
+
altPlaceholder: "Describe this...",
|
|
54
|
+
altHint: "Alt text improves accessibility",
|
|
55
|
+
addMore: "Add",
|
|
56
|
+
uploading: "Uploading...",
|
|
57
|
+
published: "Published!",
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const collections: ComposeCollection[] = [
|
|
61
|
+
{ id: 1, title: "Books", iconHtml: "" },
|
|
62
|
+
{ id: 2, title: "Movies", iconHtml: "<span>🎬</span>" },
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
async function createElement(
|
|
66
|
+
cols: ComposeCollection[] = collections,
|
|
67
|
+
): Promise<JantComposeDialog> {
|
|
68
|
+
const el = document.createElement("jant-compose-dialog") as JantComposeDialog;
|
|
69
|
+
el.collections = cols;
|
|
70
|
+
el.labels = labels;
|
|
71
|
+
document.body.appendChild(el);
|
|
72
|
+
await el.updateComplete;
|
|
73
|
+
// Wait for nested editor to also render
|
|
74
|
+
const editor = el.querySelector<JantComposeEditor>("jant-compose-editor");
|
|
75
|
+
if (editor) await editor.updateComplete;
|
|
76
|
+
return el;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
describe("JantComposeDialog", () => {
|
|
80
|
+
beforeEach(() => {
|
|
81
|
+
document.body.innerHTML = "";
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("renders with collections and labels", async () => {
|
|
85
|
+
const el = await createElement();
|
|
86
|
+
|
|
87
|
+
// Header present
|
|
88
|
+
expect(el.querySelector(".compose-dialog-header")).not.toBeNull();
|
|
89
|
+
|
|
90
|
+
// Format buttons present
|
|
91
|
+
const segmentedItems = el.querySelectorAll(".compose-segmented-item");
|
|
92
|
+
expect(segmentedItems.length).toBe(3);
|
|
93
|
+
expect(segmentedItems[0].textContent?.trim()).toBe("Note");
|
|
94
|
+
expect(segmentedItems[1].textContent?.trim()).toBe("Link");
|
|
95
|
+
expect(segmentedItems[2].textContent?.trim()).toBe("Quote");
|
|
96
|
+
|
|
97
|
+
// Post button present
|
|
98
|
+
const postBtn = requireElement(
|
|
99
|
+
el.querySelector<HTMLButtonElement>(".compose-post-btn"),
|
|
100
|
+
"expected post button",
|
|
101
|
+
);
|
|
102
|
+
expect(postBtn.textContent?.trim()).toBe("Post");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("format switching updates active state", async () => {
|
|
106
|
+
const el = await createElement();
|
|
107
|
+
|
|
108
|
+
// Note is active by default
|
|
109
|
+
const noteBtn = el.querySelectorAll<HTMLButtonElement>(
|
|
110
|
+
".compose-segmented-item",
|
|
111
|
+
)[0];
|
|
112
|
+
expect(noteBtn.classList.contains("compose-segmented-item-active")).toBe(
|
|
113
|
+
true,
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
// Click link
|
|
117
|
+
const linkBtn = el.querySelectorAll<HTMLButtonElement>(
|
|
118
|
+
".compose-segmented-item",
|
|
119
|
+
)[1];
|
|
120
|
+
linkBtn.click();
|
|
121
|
+
await el.updateComplete;
|
|
122
|
+
|
|
123
|
+
expect(el._format).toBe("link");
|
|
124
|
+
expect(linkBtn.classList.contains("compose-segmented-item-active")).toBe(
|
|
125
|
+
true,
|
|
126
|
+
);
|
|
127
|
+
expect(noteBtn.classList.contains("compose-segmented-item-active")).toBe(
|
|
128
|
+
false,
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("submit dispatches jant:compose-submit with correct payload", async () => {
|
|
133
|
+
const el = await createElement();
|
|
134
|
+
const editor = requireElement(
|
|
135
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
136
|
+
"expected compose editor",
|
|
137
|
+
);
|
|
138
|
+
editor._body = "Hello world";
|
|
139
|
+
await editor.updateComplete;
|
|
140
|
+
|
|
141
|
+
let receivedDetail: ComposeSubmitDetail | null = null;
|
|
142
|
+
el.addEventListener("jant:compose-submit", (event) => {
|
|
143
|
+
const customEvent = event as CustomEvent<ComposeSubmitDetail>;
|
|
144
|
+
receivedDetail = customEvent.detail;
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Click post button
|
|
148
|
+
requireElement(
|
|
149
|
+
el.querySelector<HTMLButtonElement>(".compose-post-btn"),
|
|
150
|
+
"expected post button",
|
|
151
|
+
).click();
|
|
152
|
+
|
|
153
|
+
expect(receivedDetail).not.toBeNull();
|
|
154
|
+
const detail = receivedDetail as unknown as ComposeSubmitDetail;
|
|
155
|
+
expect(detail.format).toBe("note");
|
|
156
|
+
expect(detail.body).toBe("Hello world");
|
|
157
|
+
expect(detail.status).toBe("published");
|
|
158
|
+
expect(detail.collectionIds).toEqual([]);
|
|
159
|
+
expect(detail.mediaIds).toEqual([]);
|
|
160
|
+
expect(detail.mediaAlts).toEqual({});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("collection selector toggles IDs", async () => {
|
|
164
|
+
const el = await createElement();
|
|
165
|
+
|
|
166
|
+
// Open collection combobox
|
|
167
|
+
const trigger = requireElement(
|
|
168
|
+
el.querySelector<HTMLButtonElement>(".compose-collection-trigger"),
|
|
169
|
+
"expected collection trigger",
|
|
170
|
+
);
|
|
171
|
+
trigger.click();
|
|
172
|
+
await el.updateComplete;
|
|
173
|
+
|
|
174
|
+
const options = el.querySelectorAll<HTMLElement>(
|
|
175
|
+
"[data-popover] [role='option']",
|
|
176
|
+
);
|
|
177
|
+
expect(options.length).toBe(2);
|
|
178
|
+
|
|
179
|
+
// Select first collection
|
|
180
|
+
options[0].click();
|
|
181
|
+
await el.updateComplete;
|
|
182
|
+
expect(el._collectionIds).toEqual([1]);
|
|
183
|
+
|
|
184
|
+
// Select second collection
|
|
185
|
+
options[1].click();
|
|
186
|
+
await el.updateComplete;
|
|
187
|
+
expect(el._collectionIds).toEqual([1, 2]);
|
|
188
|
+
|
|
189
|
+
// Deselect first
|
|
190
|
+
options[0].click();
|
|
191
|
+
await el.updateComplete;
|
|
192
|
+
expect(el._collectionIds).toEqual([2]);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("reset restores initial state", async () => {
|
|
196
|
+
const el = await createElement();
|
|
197
|
+
el._format = "link";
|
|
198
|
+
el._collectionIds = [1, 2];
|
|
199
|
+
el._loading = true;
|
|
200
|
+
|
|
201
|
+
el.reset();
|
|
202
|
+
|
|
203
|
+
expect(el._format).toBe("note");
|
|
204
|
+
expect(el._collectionIds).toEqual([]);
|
|
205
|
+
expect(el._loading).toBe(false);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("loading state disables submit button", async () => {
|
|
209
|
+
const el = await createElement();
|
|
210
|
+
el._loading = true;
|
|
211
|
+
await el.updateComplete;
|
|
212
|
+
|
|
213
|
+
const postBtn = requireElement(
|
|
214
|
+
el.querySelector<HTMLButtonElement>(".compose-post-btn"),
|
|
215
|
+
"expected post button",
|
|
216
|
+
);
|
|
217
|
+
expect(postBtn.disabled).toBe(true);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("renders without collections", async () => {
|
|
221
|
+
const el = await createElement([]);
|
|
222
|
+
|
|
223
|
+
// No collection trigger
|
|
224
|
+
expect(el.querySelector(".compose-collection-trigger")).toBeNull();
|
|
225
|
+
// Spacer div present instead
|
|
226
|
+
const actionRow = el.querySelector(".compose-action-row");
|
|
227
|
+
expect(actionRow).not.toBeNull();
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("draft button dispatches submit with draft status", async () => {
|
|
231
|
+
const el = await createElement();
|
|
232
|
+
const editor = requireElement(
|
|
233
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
234
|
+
"expected compose editor",
|
|
235
|
+
);
|
|
236
|
+
editor._body = "Draft content";
|
|
237
|
+
await editor.updateComplete;
|
|
238
|
+
|
|
239
|
+
let receivedDetail: ComposeSubmitDetail | null = null;
|
|
240
|
+
el.addEventListener("jant:compose-submit", (event) => {
|
|
241
|
+
const customEvent = event as CustomEvent<ComposeSubmitDetail>;
|
|
242
|
+
receivedDetail = customEvent.detail;
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// Click the draft header button
|
|
246
|
+
const draftBtn = requireElement(
|
|
247
|
+
el.querySelector<HTMLButtonElement>(".compose-dialog-header-btn"),
|
|
248
|
+
"expected draft button",
|
|
249
|
+
);
|
|
250
|
+
draftBtn.click();
|
|
251
|
+
|
|
252
|
+
expect(receivedDetail).not.toBeNull();
|
|
253
|
+
const detail = receivedDetail as unknown as ComposeSubmitDetail;
|
|
254
|
+
expect(detail.status).toBe("draft");
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("does not dispatch submit when loading", async () => {
|
|
258
|
+
const el = await createElement();
|
|
259
|
+
el._loading = true;
|
|
260
|
+
await el.updateComplete;
|
|
261
|
+
|
|
262
|
+
let dispatched = false;
|
|
263
|
+
el.addEventListener("jant:compose-submit", () => {
|
|
264
|
+
dispatched = true;
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const postBtn = requireElement(
|
|
268
|
+
el.querySelector<HTMLButtonElement>(".compose-post-btn"),
|
|
269
|
+
"expected post button",
|
|
270
|
+
);
|
|
271
|
+
postBtn.click();
|
|
272
|
+
|
|
273
|
+
expect(dispatched).toBe(false);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it("loading state shows spinner in submit button", async () => {
|
|
277
|
+
const el = await createElement();
|
|
278
|
+
el._loading = true;
|
|
279
|
+
await el.updateComplete;
|
|
280
|
+
|
|
281
|
+
const spinner = el.querySelector(".compose-post-btn .animate-spin");
|
|
282
|
+
expect(spinner).not.toBeNull();
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("no old media picker dialog is rendered", async () => {
|
|
286
|
+
const el = await createElement();
|
|
287
|
+
|
|
288
|
+
expect(el.querySelector("#compose-media-picker")).toBeNull();
|
|
289
|
+
expect(el.querySelector(".compose-media-picker")).toBeNull();
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it("editor renders attachments when present", async () => {
|
|
293
|
+
const el = await createElement();
|
|
294
|
+
const editor = requireElement(
|
|
295
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
296
|
+
"expected compose editor",
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
// Simulate adding an attachment
|
|
300
|
+
const blob = new Blob(["fake-image"], { type: "image/png" });
|
|
301
|
+
const file = new File([blob], "test.png", { type: "image/png" });
|
|
302
|
+
const previewUrl = URL.createObjectURL(blob);
|
|
303
|
+
|
|
304
|
+
editor._attachments = [
|
|
305
|
+
{
|
|
306
|
+
clientId: "test-id-1",
|
|
307
|
+
file,
|
|
308
|
+
previewUrl,
|
|
309
|
+
status: "done",
|
|
310
|
+
mediaId: "media-1",
|
|
311
|
+
alt: "",
|
|
312
|
+
error: null,
|
|
313
|
+
},
|
|
314
|
+
];
|
|
315
|
+
await editor.updateComplete;
|
|
316
|
+
|
|
317
|
+
// Thumbnail strip should be visible
|
|
318
|
+
expect(editor.querySelector(".compose-attachments")).not.toBeNull();
|
|
319
|
+
expect(editor.querySelector(".compose-attachment-thumb")).not.toBeNull();
|
|
320
|
+
// ALT button should be visible
|
|
321
|
+
expect(editor.querySelector(".compose-attachment-alt")).not.toBeNull();
|
|
322
|
+
// Media tool button should show "Add" label
|
|
323
|
+
const mediaBtn =
|
|
324
|
+
editor.querySelector<HTMLButtonElement>(".compose-tool-btn");
|
|
325
|
+
expect(mediaBtn?.querySelector(".compose-tool-tip")?.textContent).toBe(
|
|
326
|
+
"Add",
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
URL.revokeObjectURL(previewUrl);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it("remove button clears attachment", async () => {
|
|
333
|
+
const el = await createElement();
|
|
334
|
+
const editor = requireElement(
|
|
335
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
336
|
+
"expected compose editor",
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
const blob = new Blob(["fake-image"], { type: "image/png" });
|
|
340
|
+
const file = new File([blob], "test.png", { type: "image/png" });
|
|
341
|
+
const previewUrl = URL.createObjectURL(blob);
|
|
342
|
+
|
|
343
|
+
editor._attachments = [
|
|
344
|
+
{
|
|
345
|
+
clientId: "test-id-1",
|
|
346
|
+
file,
|
|
347
|
+
previewUrl,
|
|
348
|
+
status: "done",
|
|
349
|
+
mediaId: "media-1",
|
|
350
|
+
alt: "",
|
|
351
|
+
error: null,
|
|
352
|
+
},
|
|
353
|
+
];
|
|
354
|
+
await editor.updateComplete;
|
|
355
|
+
|
|
356
|
+
// Click remove button
|
|
357
|
+
const removeBtn = requireElement(
|
|
358
|
+
editor.querySelector<HTMLButtonElement>(".compose-attachment-remove"),
|
|
359
|
+
"expected remove button",
|
|
360
|
+
);
|
|
361
|
+
removeBtn.click();
|
|
362
|
+
await editor.updateComplete;
|
|
363
|
+
|
|
364
|
+
// Attachment strip should be gone (no attachments)
|
|
365
|
+
expect(editor.querySelector(".compose-attachments")).toBeNull();
|
|
366
|
+
expect(editor._attachments.length).toBe(0);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it("alt panel opens and closes", async () => {
|
|
370
|
+
const el = await createElement();
|
|
371
|
+
const editor = requireElement(
|
|
372
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
373
|
+
"expected compose editor",
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
const blob = new Blob(["fake-image"], { type: "image/png" });
|
|
377
|
+
const file = new File([blob], "test.png", { type: "image/png" });
|
|
378
|
+
const previewUrl = URL.createObjectURL(blob);
|
|
379
|
+
|
|
380
|
+
editor._attachments = [
|
|
381
|
+
{
|
|
382
|
+
clientId: "test-id-1",
|
|
383
|
+
file,
|
|
384
|
+
previewUrl,
|
|
385
|
+
status: "done",
|
|
386
|
+
mediaId: "media-1",
|
|
387
|
+
alt: "",
|
|
388
|
+
error: null,
|
|
389
|
+
},
|
|
390
|
+
];
|
|
391
|
+
await editor.updateComplete;
|
|
392
|
+
|
|
393
|
+
// Click ALT button
|
|
394
|
+
const altBtn = requireElement(
|
|
395
|
+
editor.querySelector<HTMLButtonElement>(".compose-attachment-alt"),
|
|
396
|
+
"expected alt button",
|
|
397
|
+
);
|
|
398
|
+
altBtn.click();
|
|
399
|
+
await editor.updateComplete;
|
|
400
|
+
|
|
401
|
+
// Alt panel should be visible
|
|
402
|
+
expect(editor.querySelector(".compose-alt-panel")).not.toBeNull();
|
|
403
|
+
expect(editor._showAltPanel).toBe(true);
|
|
404
|
+
|
|
405
|
+
// Click done to close
|
|
406
|
+
const doneBtn = editor.querySelector<HTMLButtonElement>(
|
|
407
|
+
".compose-alt-panel .compose-post-btn",
|
|
408
|
+
);
|
|
409
|
+
doneBtn?.click();
|
|
410
|
+
await editor.updateComplete;
|
|
411
|
+
|
|
412
|
+
expect(editor._showAltPanel).toBe(false);
|
|
413
|
+
expect(editor.querySelector(".compose-alt-panel")).toBeNull();
|
|
414
|
+
|
|
415
|
+
URL.revokeObjectURL(previewUrl);
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
it("submit includes mediaIds and mediaAlts from completed attachments", async () => {
|
|
419
|
+
const el = await createElement();
|
|
420
|
+
const editor = requireElement(
|
|
421
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
422
|
+
"expected compose editor",
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
const blob = new Blob(["fake-image"], { type: "image/png" });
|
|
426
|
+
const file = new File([blob], "test.png", { type: "image/png" });
|
|
427
|
+
const previewUrl = URL.createObjectURL(blob);
|
|
428
|
+
|
|
429
|
+
editor._attachments = [
|
|
430
|
+
{
|
|
431
|
+
clientId: "test-id-1",
|
|
432
|
+
file,
|
|
433
|
+
previewUrl,
|
|
434
|
+
status: "done",
|
|
435
|
+
mediaId: "media-1",
|
|
436
|
+
alt: "A test image",
|
|
437
|
+
error: null,
|
|
438
|
+
},
|
|
439
|
+
];
|
|
440
|
+
editor._body = "Post with image";
|
|
441
|
+
await editor.updateComplete;
|
|
442
|
+
|
|
443
|
+
let receivedDetail: ComposeSubmitDetail | null = null;
|
|
444
|
+
el.addEventListener("jant:compose-submit", (event) => {
|
|
445
|
+
const customEvent = event as CustomEvent<ComposeSubmitDetail>;
|
|
446
|
+
receivedDetail = customEvent.detail;
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
requireElement(
|
|
450
|
+
el.querySelector<HTMLButtonElement>(".compose-post-btn"),
|
|
451
|
+
"expected post button",
|
|
452
|
+
).click();
|
|
453
|
+
|
|
454
|
+
expect(receivedDetail).not.toBeNull();
|
|
455
|
+
const detail = receivedDetail as unknown as ComposeSubmitDetail;
|
|
456
|
+
expect(detail.mediaIds).toEqual(["media-1"]);
|
|
457
|
+
expect(detail.mediaAlts).toEqual({ "media-1": "A test image" });
|
|
458
|
+
|
|
459
|
+
URL.revokeObjectURL(previewUrl);
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
it("dispatches deferred submit when uploads are pending", async () => {
|
|
463
|
+
const el = await createElement();
|
|
464
|
+
const editor = requireElement(
|
|
465
|
+
el.querySelector<JantComposeEditor>("jant-compose-editor"),
|
|
466
|
+
"expected compose editor",
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
const blob = new Blob(["fake-image"], { type: "image/png" });
|
|
470
|
+
const file = new File([blob], "test.png", { type: "image/png" });
|
|
471
|
+
const previewUrl = URL.createObjectURL(blob);
|
|
472
|
+
|
|
473
|
+
editor._attachments = [
|
|
474
|
+
{
|
|
475
|
+
clientId: "test-id-1",
|
|
476
|
+
file,
|
|
477
|
+
previewUrl,
|
|
478
|
+
status: "uploading",
|
|
479
|
+
mediaId: null,
|
|
480
|
+
alt: "Alt for pending",
|
|
481
|
+
error: null,
|
|
482
|
+
},
|
|
483
|
+
];
|
|
484
|
+
editor._body = "Post with pending upload";
|
|
485
|
+
await editor.updateComplete;
|
|
486
|
+
|
|
487
|
+
let deferredEvent: CustomEvent | null = null;
|
|
488
|
+
el.addEventListener("jant:compose-submit-deferred", (event) => {
|
|
489
|
+
deferredEvent = event as CustomEvent;
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
// Prevent dialog.close() from throwing (no parent dialog in test)
|
|
493
|
+
let submitEvent: CustomEvent | null = null;
|
|
494
|
+
el.addEventListener("jant:compose-submit", (event) => {
|
|
495
|
+
submitEvent = event as CustomEvent;
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
requireElement(
|
|
499
|
+
el.querySelector<HTMLButtonElement>(".compose-post-btn"),
|
|
500
|
+
"expected post button",
|
|
501
|
+
).click();
|
|
502
|
+
|
|
503
|
+
// Should have dispatched deferred, not regular submit
|
|
504
|
+
expect(deferredEvent).not.toBeNull();
|
|
505
|
+
expect(submitEvent).toBeNull();
|
|
506
|
+
expect(
|
|
507
|
+
(deferredEvent as unknown as CustomEvent).detail.pendingAttachments,
|
|
508
|
+
).toBeDefined();
|
|
509
|
+
|
|
510
|
+
URL.revokeObjectURL(previewUrl);
|
|
511
|
+
});
|
|
512
|
+
});
|