@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
package/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.vue
CHANGED
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
:styles="content[styleName]"
|
|
61
61
|
:label="field.label"
|
|
62
62
|
:fields="field.fields"
|
|
63
|
+
:item-fields="field.itemFields"
|
|
63
64
|
:options-convertor="field.optionsConvertor"
|
|
64
65
|
:value-convertor="field.valueConvertor"
|
|
65
66
|
:variant="variant"
|
|
@@ -114,6 +115,7 @@ import ShapeField from '../shape-field/ShapeField.vue';
|
|
|
114
115
|
import ColorReplacementsField from '../color-replacements-field/ColorReplacementsField.vue';
|
|
115
116
|
import TestimonialItemsField from '../testimonial-items-field/TestimonialItemsField.vue';
|
|
116
117
|
import TextItemsField from '../text-items-field/TextItemsField.vue';
|
|
118
|
+
import ListItemsField from '../list-items-field/ListItemsField.vue';
|
|
117
119
|
import SliderField from '../slider-field/SliderField.vue';
|
|
118
120
|
import PageDropdownField from '../page-dropdown-field/PageDropdownField.vue';
|
|
119
121
|
import PersonalisationOverrideField from '../personalisation-override-field/PersonalisationOverrideField.vue';
|
|
@@ -153,6 +155,7 @@ export default {
|
|
|
153
155
|
'color-replacements-field': ColorReplacementsField,
|
|
154
156
|
'testimonial-items-field': TestimonialItemsField,
|
|
155
157
|
'text-items-field': TextItemsField,
|
|
158
|
+
'list-items-field': ListItemsField,
|
|
156
159
|
'product-config-option-field': ProductConfigOptionField,
|
|
157
160
|
FieldSubHeader,
|
|
158
161
|
Toggle,
|
|
@@ -39,8 +39,13 @@ export default defineComponent({
|
|
|
39
39
|
required: false,
|
|
40
40
|
default: true,
|
|
41
41
|
},
|
|
42
|
+
deleteLabel: {
|
|
43
|
+
type: String,
|
|
44
|
+
required: false,
|
|
45
|
+
default: 'Delete payment option',
|
|
46
|
+
},
|
|
42
47
|
},
|
|
43
|
-
emits: ['items-change', 'items-delete', 'itemsEdit'],
|
|
48
|
+
emits: ['items-change', 'items-delete', 'itemsEdit', 'item-toggle'],
|
|
44
49
|
data: () => ({
|
|
45
50
|
itemStates: {} as Record<number, ItemFieldState>,
|
|
46
51
|
}),
|
|
@@ -88,6 +93,11 @@ export default defineComponent({
|
|
|
88
93
|
methods: {
|
|
89
94
|
toggleCollapsed(index: number) {
|
|
90
95
|
this.itemStates[index].isCollapsed = !this.itemStates[index].isCollapsed;
|
|
96
|
+
this.$emit('item-toggle', index, this.itemStates[index].isCollapsed);
|
|
97
|
+
},
|
|
98
|
+
expand(index: number): void {
|
|
99
|
+
this.itemStates[index] = { index, isCollapsed: false };
|
|
100
|
+
this.$emit('item-toggle', index, false);
|
|
91
101
|
},
|
|
92
102
|
onDelete(index: number): void {
|
|
93
103
|
this.$emit('items-delete', index);
|
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
:disabled="!isDraggable"
|
|
8
8
|
>
|
|
9
9
|
<template #item="{ element, index }">
|
|
10
|
-
<li
|
|
10
|
+
<li
|
|
11
|
+
:key="`items-${element.item.id}-${index}`"
|
|
12
|
+
data-test-items-field-row
|
|
13
|
+
>
|
|
11
14
|
<div
|
|
12
15
|
class="tw-mb-2 tw-bg-grayscale-200 tw-p-4 tw-rounded-md tw-border tw-border-grayscale-400 tw-border-solid"
|
|
13
16
|
>
|
|
@@ -49,14 +52,17 @@
|
|
|
49
52
|
</div>
|
|
50
53
|
<button
|
|
51
54
|
class="tw-bg-transparent tw-border-none tw-cursor-pointer tw-object-center tw-p-0 tw-text-grayscale-600 hover:tw-text-primary-500 tw-transition-colors tw-duration-300 tw-ease-out"
|
|
55
|
+
data-test-items-field-delete
|
|
52
56
|
@click="onDelete(index)"
|
|
53
57
|
>
|
|
54
|
-
<span class="tw-sr-only">
|
|
58
|
+
<span class="tw-sr-only">{{ deleteLabel }}</span>
|
|
55
59
|
<Icon name="delete" size="sm" />
|
|
56
60
|
</button>
|
|
57
61
|
<div
|
|
58
62
|
v-if="canCollapse"
|
|
59
63
|
class="tw-bg-transparent tw-border-none tw-text-grayscale-600 tw-cursor-pointer tw-ml-2"
|
|
64
|
+
data-test-items-field-toggle
|
|
65
|
+
:data-test-collapsed="element.state.isCollapsed"
|
|
60
66
|
@click="toggleCollapsed(index)"
|
|
61
67
|
>
|
|
62
68
|
<Icon
|
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
import { vi } from 'vitest';
|
|
2
|
+
import { defineComponent } from 'vue';
|
|
3
|
+
import { flushPromises, mount } from '@vue/test-utils';
|
|
4
|
+
import { createPinia, setActivePinia } from 'pinia';
|
|
5
|
+
import ListItemsField from './ListItemsField.vue';
|
|
6
|
+
import ItemsField from '../items-field/ItemsField.vue';
|
|
7
|
+
import { useUiContentSettingsStore } from '../../store/content-settings-store';
|
|
8
|
+
import { TextInput } from '@designcrowd/fe-shared-lib';
|
|
9
|
+
import type { ListItemFieldSchema } from '@brand-page-maker/models/content-settings-configs';
|
|
10
|
+
|
|
11
|
+
vi.mock('@src/composables/useTranslate', async (importOriginal) => ({
|
|
12
|
+
...(await importOriginal<Record<string, unknown>>()),
|
|
13
|
+
contentSettingsTr: (key: string, values?: Record<string, unknown>) =>
|
|
14
|
+
values ? `${key}:${JSON.stringify(values)}` : key,
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
const itemFields: ListItemFieldSchema[] = [
|
|
18
|
+
{
|
|
19
|
+
key: 'question',
|
|
20
|
+
kind: 'text',
|
|
21
|
+
title: 'Question',
|
|
22
|
+
defaultValue: () => 'New question?',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
key: 'answer',
|
|
26
|
+
kind: 'textarea',
|
|
27
|
+
title: 'Answer',
|
|
28
|
+
defaultValue: () => 'Add the answer here.',
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const makeItems = (count: number) =>
|
|
33
|
+
Array.from({ length: count }, (_v, i) => ({
|
|
34
|
+
id: `q${i + 1}`,
|
|
35
|
+
question: `Question ${i + 1}`,
|
|
36
|
+
answer: `Answer ${i + 1}`,
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
const mountField = (
|
|
40
|
+
count: number,
|
|
41
|
+
maxLength = 6,
|
|
42
|
+
extraProps: Record<string, unknown> = {},
|
|
43
|
+
) =>
|
|
44
|
+
mount(ListItemsField, {
|
|
45
|
+
props: {
|
|
46
|
+
id: 'faq-1',
|
|
47
|
+
name: 'items',
|
|
48
|
+
value: makeItems(count),
|
|
49
|
+
content: { id: 'faq-1', type: 'faq-section' } as any,
|
|
50
|
+
itemFields,
|
|
51
|
+
maxLength,
|
|
52
|
+
...extraProps,
|
|
53
|
+
},
|
|
54
|
+
global: {
|
|
55
|
+
stubs: { TextareaField: TextareaFieldStub, TextStyleField: true },
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Declares the editor's props so the test can read what the list binds.
|
|
60
|
+
const TextareaFieldStub = defineComponent({
|
|
61
|
+
name: 'TextareaField',
|
|
62
|
+
props: {
|
|
63
|
+
id: { type: String, default: '' },
|
|
64
|
+
name: { type: String, default: '' },
|
|
65
|
+
value: { type: [String, Object], default: '' },
|
|
66
|
+
styles: { type: Object, default: undefined },
|
|
67
|
+
itemStyleTarget: { type: Object, default: undefined },
|
|
68
|
+
maxLength: { type: Number, default: undefined },
|
|
69
|
+
showRichTextEditor: { type: Boolean, default: false },
|
|
70
|
+
},
|
|
71
|
+
emits: ['update:modelValue'],
|
|
72
|
+
template: '<div />',
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const richItemFields: ListItemFieldSchema[] = itemFields.map((schema) => ({
|
|
76
|
+
...schema,
|
|
77
|
+
styleKey: `${schema.key}Style`,
|
|
78
|
+
defaultStyle: () => ({ textStyleType: 'headings' as const }),
|
|
79
|
+
maxLength: schema.key === 'question' ? 200 : 1000,
|
|
80
|
+
}));
|
|
81
|
+
|
|
82
|
+
const styledItems = (count: number) =>
|
|
83
|
+
makeItems(count).map((item, i) => ({
|
|
84
|
+
...item,
|
|
85
|
+
questionStyle: {
|
|
86
|
+
textStyleType: 'headings' as const,
|
|
87
|
+
fontSize: `${i + 1}em`,
|
|
88
|
+
},
|
|
89
|
+
answerStyle: { textStyleType: 'paragraphs' as const },
|
|
90
|
+
}));
|
|
91
|
+
|
|
92
|
+
const boldDoc = (text: string) => ({
|
|
93
|
+
type: 'doc',
|
|
94
|
+
content: [
|
|
95
|
+
{
|
|
96
|
+
type: 'paragraph',
|
|
97
|
+
content: [{ type: 'text', text, marks: [{ type: 'bold' }] }],
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const openFirstRow = async (wrapper: ReturnType<typeof mountField>) => {
|
|
103
|
+
(wrapper.findComponent(ItemsField).vm as any).expand(0);
|
|
104
|
+
await wrapper.vm.$nextTick();
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const lastEmitted = (wrapper: ReturnType<typeof mountField>): any => {
|
|
108
|
+
const events = wrapper.emitted('update:modelValue') ?? [];
|
|
109
|
+
|
|
110
|
+
return events[events.length - 1]?.[0];
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const addButton = (wrapper: ReturnType<typeof mountField>) =>
|
|
114
|
+
wrapper.find('[data-test-list-items-add]');
|
|
115
|
+
|
|
116
|
+
describe('ListItemsField', () => {
|
|
117
|
+
beforeEach(() => {
|
|
118
|
+
setActivePinia(createPinia());
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('adds a new item at the end with the schema defaults, and opens it', async () => {
|
|
122
|
+
const wrapper = mountField(5);
|
|
123
|
+
|
|
124
|
+
await addButton(wrapper).trigger('click');
|
|
125
|
+
await flushPromises();
|
|
126
|
+
|
|
127
|
+
const value = lastEmitted(wrapper).value;
|
|
128
|
+
|
|
129
|
+
expect(value).toHaveLength(6);
|
|
130
|
+
expect(value[5]).toMatchObject({
|
|
131
|
+
question: 'New question?',
|
|
132
|
+
answer: 'Add the answer here.',
|
|
133
|
+
});
|
|
134
|
+
expect(value[5].id).toEqual(expect.any(String));
|
|
135
|
+
expect(useUiContentSettingsStore().activeItemIdByContentId['faq-1']).toBe(
|
|
136
|
+
value[5].id,
|
|
137
|
+
);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test('at the cap the add button is disabled and says so', () => {
|
|
141
|
+
const wrapper = mountField(6);
|
|
142
|
+
|
|
143
|
+
expect(addButton(wrapper).attributes('disabled')).toBeDefined();
|
|
144
|
+
expect(addButton(wrapper).text()).toBe('listItemsMaxReached:{"max":6}');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('below the cap the add button reads "add item"', () => {
|
|
148
|
+
expect(addButton(mountField(3)).text()).toBe('listItemsAddItem');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('delete emits a new array without the item', async () => {
|
|
152
|
+
const wrapper = mountField(3);
|
|
153
|
+
const original = wrapper.props('value');
|
|
154
|
+
|
|
155
|
+
wrapper.findComponent(ItemsField).vm.$emit('items-delete', 1);
|
|
156
|
+
await flushPromises();
|
|
157
|
+
|
|
158
|
+
const value = lastEmitted(wrapper).value;
|
|
159
|
+
|
|
160
|
+
expect(value.map((item: any) => item.id)).toEqual(['q1', 'q3']);
|
|
161
|
+
expect(value).not.toBe(original);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('reorder emits the new order as a new array', async () => {
|
|
165
|
+
const wrapper = mountField(3);
|
|
166
|
+
const reordered = [...makeItems(3)].reverse();
|
|
167
|
+
|
|
168
|
+
wrapper.findComponent(ItemsField).vm.$emit('items-change', reordered);
|
|
169
|
+
await flushPromises();
|
|
170
|
+
|
|
171
|
+
const value = lastEmitted(wrapper).value;
|
|
172
|
+
|
|
173
|
+
expect(value.map((item: any) => item.id)).toEqual(['q3', 'q2', 'q1']);
|
|
174
|
+
expect(value).not.toBe(reordered);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('typing is debounced into one emit', async () => {
|
|
178
|
+
vi.useFakeTimers();
|
|
179
|
+
const wrapper = mountField(2);
|
|
180
|
+
|
|
181
|
+
(wrapper.findComponent(ItemsField).vm as any).expand(0);
|
|
182
|
+
await wrapper.vm.$nextTick();
|
|
183
|
+
|
|
184
|
+
const question = wrapper.findComponent(TextInput);
|
|
185
|
+
|
|
186
|
+
question.vm.$emit('update:modelValue', 'Wh');
|
|
187
|
+
question.vm.$emit('update:modelValue', 'What?');
|
|
188
|
+
expect(wrapper.emitted('update:modelValue')).toBeUndefined();
|
|
189
|
+
|
|
190
|
+
vi.advanceTimersByTime(300);
|
|
191
|
+
expect(wrapper.emitted('update:modelValue')).toHaveLength(1);
|
|
192
|
+
expect(lastEmitted(wrapper).value[0].question).toBe('What?');
|
|
193
|
+
vi.useRealTimers();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Saved plain text is escaped, like every section text field; the renderer
|
|
197
|
+
// outputs it as HTML.
|
|
198
|
+
test('plain text is stored encoded, like a section text field', async () => {
|
|
199
|
+
vi.useFakeTimers();
|
|
200
|
+
const wrapper = mountField(1);
|
|
201
|
+
|
|
202
|
+
await openFirstRow(wrapper);
|
|
203
|
+
wrapper
|
|
204
|
+
.findComponent(TextInput)
|
|
205
|
+
.vm.$emit('update:modelValue', 'Press <Enter> & go');
|
|
206
|
+
vi.advanceTimersByTime(300);
|
|
207
|
+
|
|
208
|
+
expect(lastEmitted(wrapper).value[0].question).toBe(
|
|
209
|
+
'Press <Enter> & go',
|
|
210
|
+
);
|
|
211
|
+
vi.useRealTimers();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test('labels each row with its question', () => {
|
|
215
|
+
const labels = mountField(2)
|
|
216
|
+
.findAll('[data-test-list-item-label]')
|
|
217
|
+
.map((label) => label.text());
|
|
218
|
+
|
|
219
|
+
expect(labels).toEqual(['Question 1', 'Question 2']);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test('announces a generic delete label, not "payment option"', () => {
|
|
223
|
+
const html = mountField(1).html();
|
|
224
|
+
|
|
225
|
+
expect(html).toContain('listItemsDeleteLabel');
|
|
226
|
+
expect(html).not.toContain('Delete payment option');
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test('opening a row marks it as the active item', async () => {
|
|
230
|
+
const wrapper = mountField(3);
|
|
231
|
+
|
|
232
|
+
wrapper.findComponent(ItemsField).vm.$emit('item-toggle', 2, false);
|
|
233
|
+
await flushPromises();
|
|
234
|
+
|
|
235
|
+
expect(useUiContentSettingsStore().activeItemIdByContentId['faq-1']).toBe(
|
|
236
|
+
'q3',
|
|
237
|
+
);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe('rich-text item fields', () => {
|
|
241
|
+
const mountRich = (showRichTextEditor: boolean, count = 2) =>
|
|
242
|
+
mountField(count, 6, {
|
|
243
|
+
itemFields: richItemFields,
|
|
244
|
+
showRichTextEditor,
|
|
245
|
+
value: styledItems(count),
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test("use the section-title editor, bound to the item's own style", async () => {
|
|
249
|
+
const wrapper = mountRich(true);
|
|
250
|
+
|
|
251
|
+
(wrapper.findComponent(ItemsField).vm as any).expand(1);
|
|
252
|
+
await wrapper.vm.$nextTick();
|
|
253
|
+
|
|
254
|
+
const editors = wrapper.findAllComponents(TextareaFieldStub);
|
|
255
|
+
|
|
256
|
+
expect(
|
|
257
|
+
editors.map((e) => [
|
|
258
|
+
e.props('name'),
|
|
259
|
+
e.props('showRichTextEditor'),
|
|
260
|
+
e.props('maxLength'),
|
|
261
|
+
e.props('itemStyleTarget'),
|
|
262
|
+
]),
|
|
263
|
+
).toEqual([
|
|
264
|
+
[
|
|
265
|
+
'question',
|
|
266
|
+
true,
|
|
267
|
+
200,
|
|
268
|
+
{ itemsField: 'items', index: 1, styleField: 'questionStyle' },
|
|
269
|
+
],
|
|
270
|
+
[
|
|
271
|
+
'answer',
|
|
272
|
+
true,
|
|
273
|
+
1000,
|
|
274
|
+
{ itemsField: 'items', index: 1, styleField: 'answerStyle' },
|
|
275
|
+
],
|
|
276
|
+
]);
|
|
277
|
+
expect(editors[0].props('styles')).toEqual(
|
|
278
|
+
styledItems(2)[1].questionStyle,
|
|
279
|
+
);
|
|
280
|
+
expect(wrapper.findComponent(TextInput).exists()).toBe(false);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test('without rich text: plain inputs plus a font-style control per styled text', async () => {
|
|
284
|
+
const wrapper = mountRich(false);
|
|
285
|
+
|
|
286
|
+
await openFirstRow(wrapper);
|
|
287
|
+
|
|
288
|
+
// The section fields' plain inputs: TextField for the question, a plain TextareaField for the answer.
|
|
289
|
+
expect(wrapper.findComponent(TextInput).exists()).toBe(true);
|
|
290
|
+
expect(
|
|
291
|
+
wrapper
|
|
292
|
+
.findAllComponents(TextareaFieldStub)
|
|
293
|
+
.map((e) => [e.props('name'), e.props('showRichTextEditor')]),
|
|
294
|
+
).toEqual([['answer', false]]);
|
|
295
|
+
expect(
|
|
296
|
+
wrapper.findAllComponents({ name: 'TextStyleField' }),
|
|
297
|
+
).toHaveLength(2);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
test('an editor write replaces that field and emits at once', async () => {
|
|
301
|
+
const wrapper = mountRich(true);
|
|
302
|
+
|
|
303
|
+
await openFirstRow(wrapper);
|
|
304
|
+
|
|
305
|
+
wrapper
|
|
306
|
+
.findAllComponents(TextareaFieldStub)[1]
|
|
307
|
+
.vm.$emit('update:modelValue', {
|
|
308
|
+
id: 'faq-1',
|
|
309
|
+
name: 'answer',
|
|
310
|
+
value: boldDoc('Bold answer'),
|
|
311
|
+
});
|
|
312
|
+
await flushPromises();
|
|
313
|
+
|
|
314
|
+
const payload = lastEmitted(wrapper);
|
|
315
|
+
|
|
316
|
+
expect(payload.value[0].answer).toEqual(boldDoc('Bold answer'));
|
|
317
|
+
expect(payload.value[1]).toEqual(styledItems(2)[1]);
|
|
318
|
+
expect(payload.isHydrationWrite).toBeUndefined();
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test("the editor's mount-time rewrite is passed on as a hydration write", async () => {
|
|
322
|
+
const wrapper = mountRich(true);
|
|
323
|
+
|
|
324
|
+
await openFirstRow(wrapper);
|
|
325
|
+
|
|
326
|
+
wrapper
|
|
327
|
+
.findAllComponents(TextareaFieldStub)[0]
|
|
328
|
+
.vm.$emit('update:modelValue', {
|
|
329
|
+
id: 'faq-1',
|
|
330
|
+
name: 'question',
|
|
331
|
+
value: boldDoc('Question 1'),
|
|
332
|
+
isHydrationWrite: true,
|
|
333
|
+
});
|
|
334
|
+
await flushPromises();
|
|
335
|
+
|
|
336
|
+
expect(lastEmitted(wrapper).isHydrationWrite).toBe(true);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
test("a toolbar style write to the store reaches the item's editor", async () => {
|
|
340
|
+
const wrapper = mountRich(true);
|
|
341
|
+
|
|
342
|
+
await openFirstRow(wrapper);
|
|
343
|
+
|
|
344
|
+
const value = styledItems(2);
|
|
345
|
+
|
|
346
|
+
value[0].questionStyle = { ...value[0].questionStyle, fontSize: '3em' };
|
|
347
|
+
await wrapper.setProps({ value });
|
|
348
|
+
|
|
349
|
+
expect(
|
|
350
|
+
wrapper.findAllComponents(TextareaFieldStub)[0].props('styles'),
|
|
351
|
+
).toMatchObject({ fontSize: '3em' });
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
test("a new item copies the last item's styles, as separate objects", async () => {
|
|
355
|
+
const wrapper = mountRich(false);
|
|
356
|
+
|
|
357
|
+
await addButton(wrapper).trigger('click');
|
|
358
|
+
await flushPromises();
|
|
359
|
+
|
|
360
|
+
const value = lastEmitted(wrapper).value;
|
|
361
|
+
const added = value[2];
|
|
362
|
+
|
|
363
|
+
expect(added.questionStyle).toEqual(value[1].questionStyle);
|
|
364
|
+
expect(added.questionStyle).not.toBe(value[1].questionStyle);
|
|
365
|
+
expect(added.answerStyle).toEqual(value[1].answerStyle);
|
|
366
|
+
expect(added.question).toBe('New question?');
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
test('the first item ever added takes the default style', async () => {
|
|
370
|
+
const wrapper = mountField(0, 6, {
|
|
371
|
+
itemFields: richItemFields,
|
|
372
|
+
showRichTextEditor: false,
|
|
373
|
+
value: [],
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
await addButton(wrapper).trigger('click');
|
|
377
|
+
await flushPromises();
|
|
378
|
+
|
|
379
|
+
expect(lastEmitted(wrapper).value[0].questionStyle).toEqual({
|
|
380
|
+
textStyleType: 'headings',
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
test('with rich text, a new item starts as a document styled by its linked role', async () => {
|
|
385
|
+
const wrapper = mountRich(true);
|
|
386
|
+
|
|
387
|
+
await addButton(wrapper).trigger('click');
|
|
388
|
+
await flushPromises();
|
|
389
|
+
|
|
390
|
+
const added = lastEmitted(wrapper).value[2];
|
|
391
|
+
|
|
392
|
+
expect(added.question.type).toBe('doc');
|
|
393
|
+
expect(JSON.stringify(added.question)).toContain('New question?');
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
// After a colour strategy the linked style carries its own colour; a new item
|
|
397
|
+
// must bake that colour, not the linked global one, or it stands out.
|
|
398
|
+
test("a new rich-text item bakes the copied style's own colour", async () => {
|
|
399
|
+
const value = styledItems(2).map((item) => ({
|
|
400
|
+
...item,
|
|
401
|
+
questionStyle: { ...item.questionStyle, color: 'var(--palette-3)' },
|
|
402
|
+
}));
|
|
403
|
+
const wrapper = mountField(2, 6, {
|
|
404
|
+
itemFields: richItemFields,
|
|
405
|
+
showRichTextEditor: true,
|
|
406
|
+
value,
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
await addButton(wrapper).trigger('click');
|
|
410
|
+
await flushPromises();
|
|
411
|
+
|
|
412
|
+
expect(JSON.stringify(lastEmitted(wrapper).value[2].question)).toContain(
|
|
413
|
+
'palette-3',
|
|
414
|
+
);
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
test('labels a row by the plain text of a rich-text question', () => {
|
|
418
|
+
const wrapper = mountField(1, 6, {
|
|
419
|
+
itemFields: richItemFields,
|
|
420
|
+
showRichTextEditor: true,
|
|
421
|
+
value: [{ ...styledItems(1)[0], question: boldDoc('Rich question') }],
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
expect(wrapper.find('[data-test-list-item-label]').text()).toBe(
|
|
425
|
+
'Rich question',
|
|
426
|
+
);
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
});
|