@designcrowd/library.brand-page 6.2.2 → 6.2.3-ast-faq
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/lib/src/{CreatedOnBrandCrowd-de-de-7P2gNr-R.js → CreatedOnBrandCrowd-de-de-BRisMzFY.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-Dl7BymW6.js → CreatedOnBrandCrowd-en-us-CDDsL9GX.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-CO4FvWVw.js → CreatedOnBrandCrowd-es-es-BkZrEo4j.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-DHNqakQu.js → CreatedOnBrandCrowd-fr-ca-sx_ury7R.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-DHNqakQu.js → CreatedOnBrandCrowd-fr-fr-sx_ury7R.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-CGUC-ZbP.js → CreatedOnBrandCrowd-pt-br-DCWiAfkU.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-CGUC-ZbP.js → CreatedOnBrandCrowd-pt-pt-DCWiAfkU.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-Dvds-Vmy.js → CreatedOnDcom-de-de-C1S7f22L.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-fd3C-8Tp.js → CreatedOnDcom-en-us-DgTlWaXE.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-DTXZW35-.js → CreatedOnDcom-es-es-CxSQJe_V.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-DRCYOKPs.js → CreatedOnDcom-fr-ca-Bbam2tt6.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-DpIB8x2x.js → CreatedOnDcom-fr-fr-Cb0XDRPL.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-Esnxy8wX.js → CreatedOnDcom-pt-br-BjdX3-E3.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-CaiVHFXR.js → CreatedOnDcom-pt-pt-BXpbfopB.js} +1 -1
- package/lib/src/brand-page/constants/misc.constants.d.ts +8 -0
- package/lib/src/brand-page/constants/misc.constants.js +7 -0
- package/lib/src/brand-page/constants/styles.constants.d.ts +2 -0
- package/lib/src/brand-page/constants/styles.constants.js +2 -0
- package/lib/src/brand-page/models/brand-page-context.model.d.ts +0 -2
- package/lib/src/brand-page/models/brand-page-context.model.js +0 -12
- package/lib/src/brand-page/models/content.model.d.ts +1 -0
- package/lib/src/brand-page/models/content.model.js +1 -0
- package/lib/src/brand-page/models/index.d.ts +19 -0
- package/lib/src/brand-page/utils/content-callback-util.d.ts +2 -1
- package/lib/src/brand-page/utils/content-callback-util.js +3 -0
- package/lib/src/brand-page/utils/model.util.d.ts +2 -1
- package/lib/src/brand-page/utils/model.util.js +3 -0
- package/lib/src/brand-page/utils/view-model.util.d.ts +2 -0
- package/lib/src/brand-page/utils/view-model.util.js +24 -0
- package/lib/src/brand-page/view-models.d.ts +9 -2
- package/lib/src/brand-page-maker/components/action-bar/ActionBar.mixin.d.ts +0 -1
- package/lib/src/brand-page-maker/components/action-bar/ActionBar.mixin.js +0 -9
- package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +10 -42
- package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +10 -42
- package/lib/src/brand-page-maker/components/preview/Preview.mixin.d.ts +0 -14
- package/lib/src/brand-page-maker/components/quick-link-bar/QuickLinkBar.mixin.d.ts +0 -42
- package/lib/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.d.ts +0 -42
- package/lib/src/brand-page-maker/components/seo-settings/UrlSettings.mixin.d.ts +0 -42
- package/lib/src/brand-page-maker/components/seo-settings/UseExistingDomainModal.mixin.d.ts +0 -42
- package/lib/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.d.ts +0 -42
- package/lib/src/brand-page-maker/components/upgrade-to-premium-button/UpgradeToPremiumButton.mixin.d.ts +0 -42
- package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.d.ts +13 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.js +64 -30
- package/lib/src/brand-page-maker/constants/config-settings-constant.d.ts +2 -1
- package/lib/src/brand-page-maker/constants/config-settings-constant.js +101 -2
- package/lib/src/brand-page-maker/convertors/faq-display-style.convertor.d.ts +3 -0
- package/lib/src/brand-page-maker/convertors/faq-display-style.convertor.js +47 -0
- package/lib/src/brand-page-maker/models/content-settings-configs.d.ts +22 -1
- package/lib/src/brand-page-maker/models/content-settings-configs.js +1 -0
- package/lib/src/brand-page-maker/models/content-settings.d.ts +4 -1
- package/lib/src/brand-page-maker/models/content-settings.js +45 -1
- package/lib/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.d.ts +0 -42
- package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.js +1 -0
- package/lib/src/brand-page-maker/utils/apply-color-strategy.js +47 -17
- package/lib/src/brand-page-maker/utils/rich-text-editor.util.d.ts +6 -0
- package/lib/src/brand-page-maker/utils/rich-text-editor.util.js +24 -1
- package/lib/src/brand-page-maker/validators/field.validator.js +2 -0
- package/lib/src/bundled-translations-DY9YvuL-.js +179 -0
- package/lib/src/bundles/bundled-translations.json +20 -2
- package/lib/src/composables/useTranslate.d.ts +1 -1
- package/lib/src/composables/useTranslate.js +1 -1
- package/lib/src/css/library.brand-page-brandCrowd.min.css +143 -36
- package/lib/src/css/library.brand-page-brandPage.min.css +143 -36
- package/lib/src/css/library.brand-page-designCom.min.css +143 -36
- package/lib/src/css/library.brand-page-preview.min.css +143 -36
- package/lib/src/index.mjs +159 -158
- package/lib/src/{lib-V7aqfuiB.js → lib-DwpSAbLZ.js} +26889 -26178
- package/lib/src/metadata-builder/parsers/section-tool-arguments.d.ts +1 -1
- package/lib/src/shared/color-strategies/color-strategies.js +4 -0
- package/lib/src/shared/color-strategies/rte-field-pairs.d.ts +12 -0
- package/lib/src/shared/color-strategies/rte-field-pairs.js +10 -0
- package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.d.ts +10 -0
- package/lib/src/shared/content-settings/components/content-settings/ReplaceImageSettingsForContent.mixin.d.ts +11 -1
- package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.mixin.d.ts +10 -0
- package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.d.ts +10 -0
- package/lib/src/shared/content-settings/components/focused-content-item-settings/FocusedContentItemSettings.mixin.d.ts +10 -0
- package/lib/src/shared/content-settings/components/items-field/ItemsField.mixin.d.ts +14 -1
- package/lib/src/shared/content-settings/components/items-field/ItemsField.mixin.js +11 -1
- package/lib/src/shared/content-settings/components/payment-config-field/PaymentConfigField.mixin.d.ts +0 -42
- package/lib/src/shared/content-settings/components/textarea-field/TextareaField.mixin.d.ts +12 -0
- package/lib/src/shared/content-settings/components/textarea-field/TextareaField.mixin.js +5 -0
- package/lib/src/shared/content-settings/models.d.ts +6 -0
- package/lib/src/shared/content-settings/store/content-settings-store.d.ts +14 -1
- package/lib/src/shared/content-settings/store/content-settings-store.js +24 -2
- package/lib/src/shared/logo-settings/components/LogoSettings.mixin.d.ts +0 -42
- package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +10 -42
- package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +10 -42
- package/lib/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.mixin.d.ts +0 -56
- package/lib/src/shared/side-bar/components/SideBar.mixin.d.ts +0 -42
- package/lib/src/shared/style-settings/components/StyleSettings.mixin.d.ts +0 -42
- package/package.json +2 -2
- package/src/brand-page/components/faq-section/FaqSection.test.ts +292 -0
- package/src/brand-page/components/faq-section/FaqSection.vue +299 -0
- package/src/brand-page/components/page/PageComponent.vue +2 -0
- package/src/brand-page/components/section-heading/SectionHeading.vue +52 -0
- package/src/brand-page/components/text-wrapper/TextWrapper.test.ts +33 -0
- package/src/brand-page/constants/misc.constants.ts +12 -0
- package/src/brand-page/constants/styles.constants.ts +2 -0
- package/src/brand-page/models/brand-page-context.model.ts +0 -23
- package/src/brand-page/models/content.model.ts +1 -0
- package/src/brand-page/models/index.ts +21 -0
- package/src/brand-page/utils/content-callback-util.ts +5 -0
- package/src/brand-page/utils/model.util.ts +7 -0
- package/src/brand-page/utils/view-model.util.test.ts +96 -0
- package/src/brand-page/utils/view-model.util.ts +49 -0
- package/src/brand-page/view-models.ts +12 -1
- package/src/brand-page-maker/components/action-bar/ActionBar.mixin.ts +0 -10
- package/src/brand-page-maker/components/action-bar/ActionBarNavigation.test.ts +8 -49
- package/src/brand-page-maker/components/action-bar/ActionBarNavigation.vue +1 -1
- package/src/brand-page-maker/components/rich-text-editor/RichTextEditor.test.ts +165 -0
- package/src/brand-page-maker/components/rich-text-editor/RichTextEditor.vue +37 -49
- package/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.ts +87 -30
- package/src/brand-page-maker/constants/config-settings-constant.ts +108 -0
- package/src/brand-page-maker/constants/faq-section-config.test.ts +151 -0
- package/src/brand-page-maker/convertors/faq-display-style.convertor.ts +59 -0
- package/src/brand-page-maker/convertors/i18n/converter.json +4 -1
- package/src/brand-page-maker/models/content-settings-configs.ts +24 -0
- package/src/brand-page-maker/models/content-settings.ts +57 -0
- package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.ts +2 -0
- package/src/brand-page-maker/utils/__snapshots__/apply-color-strategy.golden.test.ts.snap +4063 -895
- package/src/brand-page-maker/utils/apply-color-strategy.test.ts +104 -2
- package/src/brand-page-maker/utils/apply-color-strategy.ts +77 -19
- package/src/brand-page-maker/utils/rich-text-editor.util.test.ts +110 -0
- package/src/brand-page-maker/utils/rich-text-editor.util.ts +66 -0
- package/src/brand-page-maker/validators/field.validator.ts +2 -0
- package/src/bundles/bundled-translations.json +20 -2
- package/src/composables/useTranslate.ts +2 -1
- package/src/metadata-builder/builders/item-id-utils.test.ts +24 -0
- package/src/metadata-builder/parsers/section-tool-arguments.ts +2 -1
- package/src/metadata-builder/parsers/section-tool-map.test.ts +12 -0
- package/src/shared/color-strategies/color-strategies.ts +4 -0
- package/src/shared/color-strategies/rte-field-pairs.ts +23 -0
- package/src/shared/content-settings/components/content-settings/i18n/content-settings.json +17 -1
- package/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.vue +3 -0
- package/src/shared/content-settings/components/items-field/ItemsField.mixin.ts +11 -1
- package/src/shared/content-settings/components/items-field/ItemsField.vue +8 -2
- package/src/shared/content-settings/components/list-items-field/ListItemsField.test.ts +429 -0
- package/src/shared/content-settings/components/list-items-field/ListItemsField.vue +262 -0
- package/src/shared/content-settings/components/textarea-field/TextareaField.mixin.ts +6 -0
- package/src/shared/content-settings/components/textarea-field/TextareaField.vue +1 -0
- package/src/shared/content-settings/models.ts +7 -0
- package/src/shared/content-settings/store/content-settings-store.test.ts +70 -0
- package/src/shared/content-settings/store/content-settings-store.ts +37 -3
- package/src/shared/ecommerce-settings/util/refreshDuplicatedMetadata.test.ts +19 -0
- package/src/shared/page-settings/store/page-settings-store.test.ts +0 -71
- package/src/utils/minify-brand-page-metadata.test.ts +35 -0
- package/lib/src/bundled-translations-BYUfWBT3.js +0 -179
|
@@ -126,6 +126,18 @@ export const IMAGE_RESOLUTION_PRESETS = {
|
|
|
126
126
|
export type ImagesSectionContentDisplayStyle =
|
|
127
127
|
(typeof IMAGES_SECTION_CONTENT_DISPLAY_STYLES)[number];
|
|
128
128
|
|
|
129
|
+
/** Centered and split are accordions; grid shows every answer. */
|
|
130
|
+
export const FAQ_DISPLAY_STYLES = Object.freeze({
|
|
131
|
+
centered: 'faq-centered',
|
|
132
|
+
split: 'faq-split',
|
|
133
|
+
grid: 'faq-grid',
|
|
134
|
+
} as const);
|
|
135
|
+
|
|
136
|
+
export type FaqDisplayStyle =
|
|
137
|
+
(typeof FAQ_DISPLAY_STYLES)[keyof typeof FAQ_DISPLAY_STYLES];
|
|
138
|
+
|
|
139
|
+
export const FAQ_MAX_ITEMS = 6;
|
|
140
|
+
|
|
129
141
|
export const HEADER_CSRF_TOKEN = 'csrf-token';
|
|
130
142
|
|
|
131
143
|
export const STYLE_THEME_KEYS = Object.freeze({
|
|
@@ -40,6 +40,8 @@ export const CONTENT_TEXT_STYLE_PROPERTY_KEYS = {
|
|
|
40
40
|
buttonTextStyle: 'buttonTextStyle',
|
|
41
41
|
copyrightStyle: 'copyrightStyle',
|
|
42
42
|
successMessageStyle: 'successMessageStyle',
|
|
43
|
+
questionStyle: 'questionStyle',
|
|
44
|
+
answerStyle: 'answerStyle',
|
|
43
45
|
} as const;
|
|
44
46
|
|
|
45
47
|
export type ContentTextStylePropertyKey =
|
|
@@ -48,17 +48,10 @@ export interface AbTestResult {
|
|
|
48
48
|
|
|
49
49
|
const AB_TEST_KEYS = {
|
|
50
50
|
BP_3926_FREEMIUM_WATERMARK: 'bc-bp-3926-freemium-cta-in-website-editor',
|
|
51
|
-
// Two separate live-preview experiments — bc-net sends only the one for the
|
|
52
|
-
// current purchase state, and a user's value on each side is independent
|
|
53
|
-
// (BP-5873). Keys must match `CampaignNames.LivePreview*Test` in bc-net.
|
|
54
|
-
BP_5873_LIVE_PREVIEW_PREPURCHASE: 'bc-bp-5873-live-preview-prepurchase',
|
|
55
|
-
BP_5873_LIVE_PREVIEW_POSTPURCHASE: 'bc-bp-5873-live-preview-postpurchase',
|
|
56
51
|
};
|
|
57
52
|
|
|
58
53
|
export interface AbTest {
|
|
59
54
|
bp_3926_freemium_watermark?: AbTestResult;
|
|
60
|
-
bp_5873_live_preview_prepurchase?: AbTestResult;
|
|
61
|
-
bp_5873_live_preview_postpurchase?: AbTestResult;
|
|
62
55
|
}
|
|
63
56
|
|
|
64
57
|
export const MEDIA_SIZES = {
|
|
@@ -139,21 +132,5 @@ export const getAbTestResults = (abtestKeyValues: AbTest): AbTest => {
|
|
|
139
132
|
);
|
|
140
133
|
}
|
|
141
134
|
|
|
142
|
-
// AB Test for BP-5873 - Live preview (pre- and post-purchase run separately)
|
|
143
|
-
|
|
144
|
-
if (abtestKeyValues[AB_TEST_KEYS.BP_5873_LIVE_PREVIEW_PREPURCHASE]) {
|
|
145
|
-
abTests.bp_5873_live_preview_prepurchase = getValueType(
|
|
146
|
-
AB_TEST_KEYS.BP_5873_LIVE_PREVIEW_PREPURCHASE,
|
|
147
|
-
abtestKeyValues[AB_TEST_KEYS.BP_5873_LIVE_PREVIEW_PREPURCHASE],
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (abtestKeyValues[AB_TEST_KEYS.BP_5873_LIVE_PREVIEW_POSTPURCHASE]) {
|
|
152
|
-
abTests.bp_5873_live_preview_postpurchase = getValueType(
|
|
153
|
-
AB_TEST_KEYS.BP_5873_LIVE_PREVIEW_POSTPURCHASE,
|
|
154
|
-
abtestKeyValues[AB_TEST_KEYS.BP_5873_LIVE_PREVIEW_POSTPURCHASE],
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
135
|
return abTests;
|
|
159
136
|
};
|
|
@@ -672,6 +672,27 @@ export interface BookingSectionContent extends Content {
|
|
|
672
672
|
buttonTextStyle: TextStyleDeclaration;
|
|
673
673
|
}
|
|
674
674
|
|
|
675
|
+
export interface FaqItem {
|
|
676
|
+
/** Stable per-item identifier. See ImageItem.id. */
|
|
677
|
+
id: string;
|
|
678
|
+
question: RichTextValue;
|
|
679
|
+
/** Drives `question`, per item — like a section's `titleStyle` drives its title. */
|
|
680
|
+
questionStyle: TextStyleDeclaration;
|
|
681
|
+
answer: RichTextValue;
|
|
682
|
+
answerStyle: TextStyleDeclaration;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
export interface FaqSectionContent extends Content {
|
|
686
|
+
type: 'faq-section';
|
|
687
|
+
title: RichTextValue;
|
|
688
|
+
description: RichTextValue;
|
|
689
|
+
displayStyle: string;
|
|
690
|
+
items: FaqItem[];
|
|
691
|
+
titleStyle: TextStyleDeclaration;
|
|
692
|
+
descriptionStyle: TextStyleDeclaration;
|
|
693
|
+
backgroundStyle: BrandPageCssStyle;
|
|
694
|
+
}
|
|
695
|
+
|
|
675
696
|
export class PageModel {
|
|
676
697
|
title: string;
|
|
677
698
|
contents: Content[];
|
|
@@ -17,6 +17,7 @@ import type {
|
|
|
17
17
|
ButtonContent,
|
|
18
18
|
PageModel,
|
|
19
19
|
BookingSectionContent,
|
|
20
|
+
FaqSectionContent,
|
|
20
21
|
} from '../models';
|
|
21
22
|
|
|
22
23
|
export type ContentCallback = {
|
|
@@ -39,6 +40,7 @@ export type ContentCallback = {
|
|
|
39
40
|
shopSectionContentCallback: (content: ShopContent) => void;
|
|
40
41
|
buttonContentCallback: (content: ButtonContent) => void;
|
|
41
42
|
bookingSectionContentCallback: (content: BookingSectionContent) => void;
|
|
43
|
+
faqSectionContentCallback: (content: FaqSectionContent) => void;
|
|
42
44
|
};
|
|
43
45
|
|
|
44
46
|
export const forEachPageCallContentCallback = (
|
|
@@ -101,6 +103,9 @@ export const forEachPageCallContentCallback = (
|
|
|
101
103
|
content as BookingSectionContent,
|
|
102
104
|
);
|
|
103
105
|
break;
|
|
106
|
+
case 'faq-section':
|
|
107
|
+
callbacks.faqSectionContentCallback(content as FaqSectionContent);
|
|
108
|
+
break;
|
|
104
109
|
case 'general-link':
|
|
105
110
|
case 'website':
|
|
106
111
|
case 'email':
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
type ImageContent,
|
|
22
22
|
type ImageGalleryContent,
|
|
23
23
|
type BookingSectionContent,
|
|
24
|
+
type FaqSectionContent,
|
|
24
25
|
} from '../models';
|
|
25
26
|
|
|
26
27
|
export const isLogoContent = (content?: Content): content is LogoContent => {
|
|
@@ -97,6 +98,12 @@ export const isBookingContent = (
|
|
|
97
98
|
return content?.type === 'booking-section';
|
|
98
99
|
};
|
|
99
100
|
|
|
101
|
+
export const isFaqContent = (
|
|
102
|
+
content?: Content,
|
|
103
|
+
): content is FaqSectionContent => {
|
|
104
|
+
return content?.type === 'faq-section';
|
|
105
|
+
};
|
|
106
|
+
|
|
100
107
|
export const isAboutContent = (
|
|
101
108
|
content?: Content,
|
|
102
109
|
): content is AboutSectionContent => {
|
|
@@ -1121,6 +1121,102 @@ describe('container-aware text colour defaults (view-model layer)', () => {
|
|
|
1121
1121
|
});
|
|
1122
1122
|
});
|
|
1123
1123
|
|
|
1124
|
+
describe('faq-section view model', () => {
|
|
1125
|
+
const faqContent = (overrides: Record<string, unknown> = {}): any => ({
|
|
1126
|
+
id: 'faq-1',
|
|
1127
|
+
type: 'faq-section',
|
|
1128
|
+
title: 'FAQ',
|
|
1129
|
+
description: '',
|
|
1130
|
+
displayStyle: 'faq-split',
|
|
1131
|
+
items: [
|
|
1132
|
+
{
|
|
1133
|
+
id: 'q1',
|
|
1134
|
+
question: 'Q1',
|
|
1135
|
+
questionStyle: { textStyleType: 'headings' },
|
|
1136
|
+
answer: 'A1',
|
|
1137
|
+
answerStyle: { textStyleType: 'paragraphs' },
|
|
1138
|
+
},
|
|
1139
|
+
{
|
|
1140
|
+
id: 'q2',
|
|
1141
|
+
question: 'Q2',
|
|
1142
|
+
questionStyle: { fontSize: '1.5rem' }, // detached
|
|
1143
|
+
answer: 'A2',
|
|
1144
|
+
answerStyle: { textStyleType: 'paragraphs' },
|
|
1145
|
+
},
|
|
1146
|
+
],
|
|
1147
|
+
titleStyle: { textStyleType: 'titles' },
|
|
1148
|
+
descriptionStyle: { textStyleType: 'paragraphs' },
|
|
1149
|
+
backgroundStyle: {},
|
|
1150
|
+
topPadding: 32,
|
|
1151
|
+
bottomPadding: 32,
|
|
1152
|
+
...overrides,
|
|
1153
|
+
});
|
|
1154
|
+
|
|
1155
|
+
test.each([
|
|
1156
|
+
[BRAND_PAGE_TYPES.default, true],
|
|
1157
|
+
[BRAND_PAGE_TYPES.brandContact, true],
|
|
1158
|
+
[BRAND_PAGE_TYPES.brandSite, false],
|
|
1159
|
+
])('%s → isStacked %s', (brandPageType, isStacked) => {
|
|
1160
|
+
const viewModel: any = mapToViewModel(
|
|
1161
|
+
new PageStyles(),
|
|
1162
|
+
brandPageType,
|
|
1163
|
+
{},
|
|
1164
|
+
)(faqContent());
|
|
1165
|
+
|
|
1166
|
+
expect(viewModel.type).toBe('faq-section');
|
|
1167
|
+
expect(viewModel.isStacked).toBe(isStacked);
|
|
1168
|
+
expect(viewModel.items).toHaveLength(2);
|
|
1169
|
+
});
|
|
1170
|
+
|
|
1171
|
+
test('carries the maker panel’s open item only for its own content', () => {
|
|
1172
|
+
const map = (contentId: string): any =>
|
|
1173
|
+
mapToViewModel(new PageStyles(), BRAND_PAGE_TYPES.brandSite, {
|
|
1174
|
+
activeItemIdByContentId: { [contentId]: 'q2' },
|
|
1175
|
+
})(faqContent());
|
|
1176
|
+
|
|
1177
|
+
expect(map('faq-1').activeItemId).toBe('q2');
|
|
1178
|
+
expect(map('another-section').activeItemId).toBeUndefined();
|
|
1179
|
+
});
|
|
1180
|
+
|
|
1181
|
+
test("each item's question and answer follow their global text roles until detached", () => {
|
|
1182
|
+
const pageStyles = new PageStyles();
|
|
1183
|
+
const { headings, paragraphs } = pageStyles.textStyles.textStyleOptions;
|
|
1184
|
+
|
|
1185
|
+
headings.style.fontSize = '1.875rem';
|
|
1186
|
+
headings.style.color = '#00ff00';
|
|
1187
|
+
headings.isModified = true;
|
|
1188
|
+
paragraphs.style.color = '#0000ff';
|
|
1189
|
+
|
|
1190
|
+
const content = faqContent();
|
|
1191
|
+
const viewModel: any = mapToViewModel(
|
|
1192
|
+
pageStyles,
|
|
1193
|
+
BRAND_PAGE_TYPES.brandSite,
|
|
1194
|
+
{},
|
|
1195
|
+
)(content);
|
|
1196
|
+
const [linked, detached] = viewModel.items;
|
|
1197
|
+
|
|
1198
|
+
expect(linked.questionStyle.fontSize).toBe('1.875rem');
|
|
1199
|
+
expect(linked.questionStyle.color).toBe('#00ff00');
|
|
1200
|
+
expect(linked.answerStyle.color).toBe('#0000ff');
|
|
1201
|
+
expect(detached.questionStyle.fontSize).toBe('1.5rem');
|
|
1202
|
+
expect(detached.answerStyle.color).toBe('#0000ff');
|
|
1203
|
+
// Resolution copies; the stored item keeps only its link.
|
|
1204
|
+
expect(content.items[0].questionStyle).toEqual({
|
|
1205
|
+
textStyleType: 'headings',
|
|
1206
|
+
});
|
|
1207
|
+
});
|
|
1208
|
+
|
|
1209
|
+
test('content without items maps without throwing', () => {
|
|
1210
|
+
const viewModel: any = mapToViewModel(
|
|
1211
|
+
new PageStyles(),
|
|
1212
|
+
BRAND_PAGE_TYPES.brandSite,
|
|
1213
|
+
{},
|
|
1214
|
+
)(faqContent({ items: undefined }));
|
|
1215
|
+
|
|
1216
|
+
expect(viewModel.items).toEqual([]);
|
|
1217
|
+
});
|
|
1218
|
+
});
|
|
1219
|
+
|
|
1124
1220
|
describe('mapToPageHeaderViewModel header button', () => {
|
|
1125
1221
|
const headerButton: HeaderButton = {
|
|
1126
1222
|
isVisible: true,
|
|
@@ -36,6 +36,7 @@ import type {
|
|
|
36
36
|
LogoTemplateImageModel,
|
|
37
37
|
ShopContent,
|
|
38
38
|
BookingSectionContent,
|
|
39
|
+
FaqSectionContent,
|
|
39
40
|
TextShapeItem,
|
|
40
41
|
GettyVideoMedia,
|
|
41
42
|
AbstractMedia,
|
|
@@ -56,6 +57,7 @@ import {
|
|
|
56
57
|
BUTTON_LINK_TYPE,
|
|
57
58
|
NavigationMenuStyles,
|
|
58
59
|
type TextItem,
|
|
60
|
+
type FaqItem,
|
|
59
61
|
} from '../models';
|
|
60
62
|
import type {
|
|
61
63
|
ImageViewModel,
|
|
@@ -84,6 +86,7 @@ import type {
|
|
|
84
86
|
ShapesSectionViewModel,
|
|
85
87
|
ShopViewModel,
|
|
86
88
|
BookingSectionViewModel,
|
|
89
|
+
FaqSectionViewModel,
|
|
87
90
|
GettyVideoViewModel,
|
|
88
91
|
PageStylesViewModel,
|
|
89
92
|
BackgroundStylesViewModel,
|
|
@@ -1038,6 +1041,42 @@ const mapToTestimonialSectionViewModel = (
|
|
|
1038
1041
|
);
|
|
1039
1042
|
};
|
|
1040
1043
|
|
|
1044
|
+
// An item's question/answer styles are CONTENT_TEXT_STYLE_PROPERTY_KEYS, so the section
|
|
1045
|
+
// resolver handles them when handed the item.
|
|
1046
|
+
const mapFaqItem = (item: FaqItem, styles: PageStyles): FaqItem =>
|
|
1047
|
+
applyViewModelTextStyles(
|
|
1048
|
+
{
|
|
1049
|
+
...item,
|
|
1050
|
+
questionStyle: { ...item.questionStyle },
|
|
1051
|
+
answerStyle: { ...item.answerStyle },
|
|
1052
|
+
} as unknown as ContentViewModel,
|
|
1053
|
+
styles,
|
|
1054
|
+
) as unknown as FaqItem;
|
|
1055
|
+
|
|
1056
|
+
const mapToFaqSectionViewModel = (
|
|
1057
|
+
content: FaqSectionContent,
|
|
1058
|
+
styles: PageStyles,
|
|
1059
|
+
brandPageType: BrandPageType,
|
|
1060
|
+
options: ViewModelMappingOptions,
|
|
1061
|
+
): FaqSectionViewModel => {
|
|
1062
|
+
return applyViewModelTextStyles(
|
|
1063
|
+
{
|
|
1064
|
+
...content,
|
|
1065
|
+
role: content.type,
|
|
1066
|
+
type: 'faq-section',
|
|
1067
|
+
// No `color` pre-seed — these styles are CONTENT_TEXT_STYLE_PROPERTY_KEYS
|
|
1068
|
+
// (see the testimonial mapper), resolved per item for the item styles.
|
|
1069
|
+
titleStyle: { ...content.titleStyle },
|
|
1070
|
+
descriptionStyle: { ...content.descriptionStyle },
|
|
1071
|
+
items: (content.items ?? []).map((item) => mapFaqItem(item, styles)),
|
|
1072
|
+
isFullWidth: true,
|
|
1073
|
+
isStacked: brandPageType !== BRAND_PAGE_TYPES.brandSite,
|
|
1074
|
+
activeItemId: options.activeItemIdByContentId?.[content.id],
|
|
1075
|
+
},
|
|
1076
|
+
styles,
|
|
1077
|
+
);
|
|
1078
|
+
};
|
|
1079
|
+
|
|
1041
1080
|
const mapToSocialLinksViewModel = (
|
|
1042
1081
|
content: SocialLinksContent,
|
|
1043
1082
|
styles: PageStyles,
|
|
@@ -1511,6 +1550,14 @@ export const mapToViewModel =
|
|
|
1511
1550
|
styles,
|
|
1512
1551
|
);
|
|
1513
1552
|
|
|
1553
|
+
case 'faq-section':
|
|
1554
|
+
return mapToFaqSectionViewModel(
|
|
1555
|
+
content as FaqSectionContent,
|
|
1556
|
+
styles,
|
|
1557
|
+
brandPageType,
|
|
1558
|
+
options,
|
|
1559
|
+
);
|
|
1560
|
+
|
|
1514
1561
|
case 'about-section':
|
|
1515
1562
|
return mapToAboutSectionViewModel(
|
|
1516
1563
|
content as AboutSectionContent,
|
|
@@ -1586,6 +1633,8 @@ interface ViewModelMappingOptions {
|
|
|
1586
1633
|
previewImageUrl: string,
|
|
1587
1634
|
assetId: string,
|
|
1588
1635
|
) => void;
|
|
1636
|
+
/** Maker-only UI state: content id → the item its settings panel has open. Never persisted. */
|
|
1637
|
+
activeItemIdByContentId?: Record<string, string | undefined>;
|
|
1589
1638
|
}
|
|
1590
1639
|
|
|
1591
1640
|
const mapBackgroundStylesToViewModel = (
|
|
@@ -35,6 +35,7 @@ import type {
|
|
|
35
35
|
LogoTemplateImageModel,
|
|
36
36
|
CookieOptions,
|
|
37
37
|
BookingSectionContent,
|
|
38
|
+
FaqSectionContent,
|
|
38
39
|
MediaTypes,
|
|
39
40
|
TextStyles,
|
|
40
41
|
BackgroundStyle,
|
|
@@ -67,7 +68,8 @@ export type ViewModelType =
|
|
|
67
68
|
| 'social-links'
|
|
68
69
|
| 'shapes-section'
|
|
69
70
|
| 'shop-section'
|
|
70
|
-
| 'booking-section'
|
|
71
|
+
| 'booking-section'
|
|
72
|
+
| 'faq-section';
|
|
71
73
|
|
|
72
74
|
export interface ContentViewModel {
|
|
73
75
|
id: string;
|
|
@@ -249,6 +251,15 @@ export interface BookingSectionViewModel
|
|
|
249
251
|
isFullWidth: boolean;
|
|
250
252
|
}
|
|
251
253
|
|
|
254
|
+
export interface FaqSectionViewModel
|
|
255
|
+
extends ContentViewModel, Omit<FaqSectionContent, 'type'> {
|
|
256
|
+
isFullWidth: boolean;
|
|
257
|
+
/** brand.page / brand.contact: split and grid always use their stacked layout. */
|
|
258
|
+
isStacked: boolean;
|
|
259
|
+
/** Maker-only: the item the settings panel has open, so the canvas shows it. */
|
|
260
|
+
activeItemId?: string;
|
|
261
|
+
}
|
|
262
|
+
|
|
252
263
|
export interface PageHeaderViewModel extends Omit<
|
|
253
264
|
PageHeaderDesktop | PageHeaderMobile,
|
|
254
265
|
| 'logoImage'
|
|
@@ -17,16 +17,6 @@ export default defineComponent({
|
|
|
17
17
|
|
|
18
18
|
return {
|
|
19
19
|
isAdminMaker: computed(() => containerStore.isAdminMaker),
|
|
20
|
-
// BP-5873: the Preview button is behind an AB test, and pre/post purchase are
|
|
21
|
-
// two separate experiments. Pick the one for the current purchase state — a
|
|
22
|
-
// user's value on the other side is independent and must not leak across.
|
|
23
|
-
isLivePreviewEnabled: computed(() => {
|
|
24
|
-
const experiment = containerStore.isPrePurchase
|
|
25
|
-
? containerStore.abTests.bp_5873_live_preview_prepurchase
|
|
26
|
-
: containerStore.abTests.bp_5873_live_preview_postpurchase;
|
|
27
|
-
|
|
28
|
-
return !!experiment?.isVariation;
|
|
29
|
-
}),
|
|
30
20
|
isSaving: computed(() => brandPageMakerStore.isSaving),
|
|
31
21
|
isPublished: computed(() => brandPageMakerStore.isPublished),
|
|
32
22
|
isPublishButtonEnabled: computed(
|
|
@@ -3,31 +3,13 @@ import { createPinia, setActivePinia } from 'pinia';
|
|
|
3
3
|
import { shallowMount } from '@vue/test-utils';
|
|
4
4
|
import ActionBarNavigation from './ActionBarNavigation.vue';
|
|
5
5
|
import { useBrandPageContainerStore } from '@brand-page-maker/stores';
|
|
6
|
-
import { getAbTestResults } from '@brand-page/models/brand-page-context.model';
|
|
7
|
-
|
|
8
|
-
// Must match `CampaignNames.LivePreview*` in bc-net. The two experiments share
|
|
9
|
-
// one set of variation values — they're told apart by campaign key, not value.
|
|
10
|
-
const PREPURCHASE = 'bc-bp-5873-live-preview-prepurchase';
|
|
11
|
-
const POSTPURCHASE = 'bc-bp-5873-live-preview-postpurchase';
|
|
12
|
-
const VARIATION_1 = 'bp-5873-variation-1';
|
|
13
|
-
const CONTROL = 'bp-5873-control';
|
|
14
6
|
|
|
15
7
|
const mountBar = () => shallowMount(ActionBarNavigation);
|
|
16
8
|
|
|
17
9
|
const hasPreviewButton = (wrapper: ReturnType<typeof mountBar>) =>
|
|
18
10
|
wrapper.find('[data-test-preview-button]').exists();
|
|
19
11
|
|
|
20
|
-
|
|
21
|
-
// parser rather than hand-building an AbTestResult — that's the contract under test.
|
|
22
|
-
const setAbTest = (
|
|
23
|
-
store: ReturnType<typeof useBrandPageContainerStore>,
|
|
24
|
-
key: string,
|
|
25
|
-
value: string,
|
|
26
|
-
) => {
|
|
27
|
-
store.abTests = getAbTestResults({ [key]: value } as never);
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
describe('ActionBarNavigation — BP-5873 preview AB test', () => {
|
|
12
|
+
describe('ActionBarNavigation — Preview button', () => {
|
|
31
13
|
let containerStore: ReturnType<typeof useBrandPageContainerStore>;
|
|
32
14
|
|
|
33
15
|
beforeEach(() => {
|
|
@@ -35,39 +17,18 @@ describe('ActionBarNavigation — BP-5873 preview AB test', () => {
|
|
|
35
17
|
containerStore = useBrandPageContainerStore();
|
|
36
18
|
});
|
|
37
19
|
|
|
38
|
-
test(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
containerStore.isPrePurchase = false;
|
|
44
|
-
|
|
45
|
-
setAbTest(containerStore, POSTPURCHASE, VARIATION_1);
|
|
46
|
-
expect(hasPreviewButton(mountBar())).toBe(true);
|
|
47
|
-
|
|
48
|
-
setAbTest(containerStore, POSTPURCHASE, CONTROL);
|
|
49
|
-
expect(hasPreviewButton(mountBar())).toBe(false);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test('pre-purchase: shows Preview on variation-1, hides it on control', () => {
|
|
53
|
-
containerStore.isPrePurchase = true;
|
|
20
|
+
test.each([
|
|
21
|
+
['pre-purchase', true],
|
|
22
|
+
['post-purchase', false],
|
|
23
|
+
])('shows Preview to the customer %s', (_, isPrePurchase) => {
|
|
24
|
+
containerStore.isPrePurchase = isPrePurchase;
|
|
54
25
|
|
|
55
|
-
setAbTest(containerStore, PREPURCHASE, VARIATION_1);
|
|
56
26
|
expect(hasPreviewButton(mountBar())).toBe(true);
|
|
57
|
-
|
|
58
|
-
setAbTest(containerStore, PREPURCHASE, CONTROL);
|
|
59
|
-
expect(hasPreviewButton(mountBar())).toBe(false);
|
|
60
27
|
});
|
|
61
28
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
test('the two experiments never read each other', () => {
|
|
65
|
-
containerStore.isPrePurchase = true;
|
|
66
|
-
setAbTest(containerStore, POSTPURCHASE, VARIATION_1);
|
|
67
|
-
expect(hasPreviewButton(mountBar())).toBe(false);
|
|
29
|
+
test('hides Preview in the admin maker', () => {
|
|
30
|
+
containerStore.isAdminMaker = true;
|
|
68
31
|
|
|
69
|
-
containerStore.isPrePurchase = false;
|
|
70
|
-
setAbTest(containerStore, PREPURCHASE, VARIATION_1);
|
|
71
32
|
expect(hasPreviewButton(mountBar())).toBe(false);
|
|
72
33
|
});
|
|
73
34
|
|
|
@@ -75,8 +36,6 @@ describe('ActionBarNavigation — BP-5873 preview AB test', () => {
|
|
|
75
36
|
// Save drops out. Preview must be a real column like the rest — while it was content
|
|
76
37
|
// width, Publish ate the whole remaining row on a published site.
|
|
77
38
|
test('Preview shares the mobile row equally with the other buttons', () => {
|
|
78
|
-
containerStore.isPrePurchase = false;
|
|
79
|
-
setAbTest(containerStore, POSTPURCHASE, VARIATION_1);
|
|
80
39
|
containerStore.brandPageStatus = 'Published';
|
|
81
40
|
|
|
82
41
|
const wrapper = mountBar();
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
class="tw-flex tw-items-center tw-border-t tw-border-solid tw-border-grayscale-600 tw-h-12 tw-px-2 lg:tw-px-0 lg:tw-h-auto lg:tw-border-none tw-gap-3 lg:tw-gap-5 tw-w-full lg:tw-justify-end tw-box-border"
|
|
5
5
|
>
|
|
6
6
|
<Button
|
|
7
|
-
v-if="!isAdminMaker
|
|
7
|
+
v-if="!isAdminMaker"
|
|
8
8
|
:label="constantsTr('previewDisplayName')"
|
|
9
9
|
variant="no-border"
|
|
10
10
|
:white="true"
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { vi } from 'vitest';
|
|
2
|
+
import { flushPromises, mount } from '@vue/test-utils';
|
|
3
|
+
import { createPinia, setActivePinia } from 'pinia';
|
|
4
|
+
import RichTextEditor from './RichTextEditor.vue';
|
|
5
|
+
import { useUiContentSettingsStore } from '@shared/content-settings/store';
|
|
6
|
+
|
|
7
|
+
// The toolbar capitalises translated role names at setup; tests carry no bundles.
|
|
8
|
+
vi.mock('@src/composables/useTranslate', async (importOriginal) => ({
|
|
9
|
+
...(await importOriginal<Record<string, unknown>>()),
|
|
10
|
+
styleSettingsTr: (key: string) => key,
|
|
11
|
+
commonTr: (key: string) => key,
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
const doc = {
|
|
15
|
+
type: 'doc',
|
|
16
|
+
content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Hi' }] }],
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const itemStyleTarget = {
|
|
20
|
+
itemsField: 'items',
|
|
21
|
+
index: 1,
|
|
22
|
+
styleField: 'questionStyle',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const mountEditor = (props: Record<string, unknown> = {}) =>
|
|
26
|
+
mount(RichTextEditor, {
|
|
27
|
+
props: {
|
|
28
|
+
id: 'faq-1-question',
|
|
29
|
+
name: 'question',
|
|
30
|
+
rawId: 'faq-1',
|
|
31
|
+
modelValue: doc,
|
|
32
|
+
...props,
|
|
33
|
+
} as any,
|
|
34
|
+
global: {
|
|
35
|
+
directives: { clickOutside: {} },
|
|
36
|
+
stubs: { FontPicker: true },
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const editorOf = (wrapper: ReturnType<typeof mountEditor>) =>
|
|
41
|
+
(wrapper.find('.ProseMirror').element as any).editor;
|
|
42
|
+
|
|
43
|
+
describe('RichTextEditor', () => {
|
|
44
|
+
beforeEach(() => {
|
|
45
|
+
setActivePinia(createPinia());
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('an item editor has the full section-title toolbar', () => {
|
|
49
|
+
const tags = mountEditor({ itemStyleTarget })
|
|
50
|
+
.find('.style-editor')
|
|
51
|
+
.findAll('[data-test-tag]')
|
|
52
|
+
.map((el) => el.attributes('data-test-tag'));
|
|
53
|
+
|
|
54
|
+
expect(tags).toEqual(
|
|
55
|
+
expect.arrayContaining([
|
|
56
|
+
'text-style-dropdown',
|
|
57
|
+
'font-style-dropdown',
|
|
58
|
+
'font-size-stepper',
|
|
59
|
+
'text-color-picker',
|
|
60
|
+
'bold-toolbar',
|
|
61
|
+
'link-toolbar',
|
|
62
|
+
]),
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test.each([
|
|
67
|
+
['an item editor', { itemStyleTarget }, 'updateListItemTextStyles'],
|
|
68
|
+
['a section field', {}, 'updateTextStyles'],
|
|
69
|
+
])(
|
|
70
|
+
'%s writes its toolbar styles through %s',
|
|
71
|
+
async (_label, props, expectedAction) => {
|
|
72
|
+
const store = useUiContentSettingsStore();
|
|
73
|
+
const spies = {
|
|
74
|
+
updateListItemTextStyles: vi.spyOn(store, 'updateListItemTextStyles'),
|
|
75
|
+
updateTextStyles: vi.spyOn(store, 'updateTextStyles'),
|
|
76
|
+
};
|
|
77
|
+
const wrapper = mountEditor(props);
|
|
78
|
+
|
|
79
|
+
await flushPromises();
|
|
80
|
+
Object.values(spies).forEach((spy) => spy.mockClear());
|
|
81
|
+
await wrapper.find('[data-test-tag="bold-toolbar"]').trigger('click');
|
|
82
|
+
|
|
83
|
+
Object.entries(spies).forEach(([action, spy]) =>
|
|
84
|
+
expect(spy).toHaveBeenCalledTimes(action === expectedAction ? 1 : 0),
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
if (expectedAction === 'updateListItemTextStyles') {
|
|
88
|
+
expect(spies.updateListItemTextStyles).toHaveBeenCalledWith(
|
|
89
|
+
expect.objectContaining({
|
|
90
|
+
id: 'faq-1',
|
|
91
|
+
target: itemStyleTarget,
|
|
92
|
+
value: expect.objectContaining({ fontWeight: 'bold' }),
|
|
93
|
+
}),
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
test('picking a text style on an item editor links that item, not the section', async () => {
|
|
100
|
+
const store = useUiContentSettingsStore();
|
|
101
|
+
const updateListItemTextStyles = vi.spyOn(
|
|
102
|
+
store,
|
|
103
|
+
'updateListItemTextStyles',
|
|
104
|
+
);
|
|
105
|
+
const updateTextStyles = vi.spyOn(store, 'updateTextStyles');
|
|
106
|
+
const wrapper = mountEditor({ itemStyleTarget });
|
|
107
|
+
|
|
108
|
+
await flushPromises();
|
|
109
|
+
updateTextStyles.mockClear();
|
|
110
|
+
|
|
111
|
+
const headings = wrapper
|
|
112
|
+
.findAll('.drop-down-item')
|
|
113
|
+
.find((el) => el.text().toLowerCase().includes('headings'));
|
|
114
|
+
|
|
115
|
+
await headings!.trigger('click');
|
|
116
|
+
|
|
117
|
+
expect(updateListItemTextStyles).toHaveBeenCalledWith(
|
|
118
|
+
expect.objectContaining({
|
|
119
|
+
target: itemStyleTarget,
|
|
120
|
+
value: expect.objectContaining({ textStyleType: 'headings' }),
|
|
121
|
+
}),
|
|
122
|
+
);
|
|
123
|
+
expect(updateTextStyles).not.toHaveBeenCalled();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("a linked item editor's mount-time style sync lands on the item, as a hydration write", async () => {
|
|
127
|
+
const store = useUiContentSettingsStore();
|
|
128
|
+
const updateListItemTextStyles = vi.spyOn(
|
|
129
|
+
store,
|
|
130
|
+
'updateListItemTextStyles',
|
|
131
|
+
);
|
|
132
|
+
const updateTextStyles = vi.spyOn(store, 'updateTextStyles');
|
|
133
|
+
|
|
134
|
+
mountEditor({ itemStyleTarget, styles: { textStyleType: 'headings' } });
|
|
135
|
+
await flushPromises();
|
|
136
|
+
|
|
137
|
+
expect(updateListItemTextStyles).toHaveBeenCalledWith(
|
|
138
|
+
expect.objectContaining({
|
|
139
|
+
target: itemStyleTarget,
|
|
140
|
+
isHydrationWrite: true,
|
|
141
|
+
}),
|
|
142
|
+
);
|
|
143
|
+
expect(updateTextStyles).not.toHaveBeenCalled();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// The toolbar has taken focus by the time its command runs; tagging that
|
|
147
|
+
// write as hydration would fold it into the previous undo entry.
|
|
148
|
+
test('a toolbar command on a selection emits a user write, not a hydration write', async () => {
|
|
149
|
+
const wrapper = mountEditor();
|
|
150
|
+
|
|
151
|
+
await flushPromises();
|
|
152
|
+
|
|
153
|
+
const editor = editorOf(wrapper);
|
|
154
|
+
|
|
155
|
+
editor.commands.selectAll();
|
|
156
|
+
editor.commands.blur();
|
|
157
|
+
await wrapper.find('[data-test-tag="bold-toolbar"]').trigger('click');
|
|
158
|
+
|
|
159
|
+
const emits = wrapper.emitted('update:modelValue') ?? [];
|
|
160
|
+
const [value, meta] = emits[emits.length - 1] as [any, any];
|
|
161
|
+
|
|
162
|
+
expect(JSON.stringify(value)).toContain('"bold"');
|
|
163
|
+
expect(meta.isHydrationWrite).toBe(false);
|
|
164
|
+
});
|
|
165
|
+
});
|