@designcrowd/library.brand-page 6.2.8 → 6.2.10
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-Bi71mUjJ.js → CreatedOnBrandCrowd-de-de-CTEj6D-p.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-C2kl91cc.js → CreatedOnBrandCrowd-en-us-ByFNuMoA.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-CNK-VpMH.js → CreatedOnBrandCrowd-es-es-lA_3mWIj.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-B_WTCv5k.js → CreatedOnBrandCrowd-fr-ca-CS_YkUz3.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-B_WTCv5k.js → CreatedOnBrandCrowd-fr-fr-CS_YkUz3.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-Dk-k5-Nh.js → CreatedOnBrandCrowd-pt-br-BoduQ7zJ.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-Dk-k5-Nh.js → CreatedOnBrandCrowd-pt-pt-BoduQ7zJ.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-BVaJyfAR.js → CreatedOnDcom-de-de-Co1OO4_u.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-DVzzynRf.js → CreatedOnDcom-en-us-DqFyqxOJ.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-BS2u-QHG.js → CreatedOnDcom-es-es-DhQWyb7C.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-BavFpbyC.js → CreatedOnDcom-fr-ca-DIxstLTf.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-DRchqDHL.js → CreatedOnDcom-fr-fr-B-z8V1cH.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-N7XAWRj_.js → CreatedOnDcom-pt-br-B7AMR1sc.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-Bct14us6.js → CreatedOnDcom-pt-pt-Cdci4yr3.js} +1 -1
- package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +6 -5
- package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +6 -5
- package/lib/src/brand-page-maker/components/color-picker/BpColorPicker.stories.d.ts +3 -0
- package/lib/src/brand-page-maker/components/color-picker/BpColorPicker.stories.js +10 -0
- package/lib/src/brand-page-maker/components/color-picker/components/ColorModeSelect.mixin.d.ts +2 -2
- package/lib/src/brand-page-maker/components/color-picker/components/ColorModeSelect.mixin.js +2 -2
- package/lib/src/brand-page-maker/components/quick-link-bar/QuickLinkBar.mixin.d.ts +6 -5
- package/lib/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.d.ts +7 -5
- package/lib/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.js +5 -0
- package/lib/src/brand-page-maker/components/seo-settings/UrlSettings.mixin.d.ts +6 -5
- package/lib/src/brand-page-maker/components/seo-settings/UseExistingDomainModal.mixin.d.ts +6 -5
- package/lib/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.d.ts +6 -5
- package/lib/src/brand-page-maker/components/upgrade-to-premium-button/UpgradeToPremiumButton.mixin.d.ts +6 -5
- package/lib/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.d.ts +7 -6
- package/lib/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.js +52 -5
- package/lib/src/{bundled-translations-BNA5h0Dx.js → bundled-translations-zfiA5Tsw.js} +1 -1
- package/lib/src/{bundled-translations.de-DE-fyTHrRxT.js → bundled-translations.de-DE-ChsdSyAP.js} +13 -13
- package/lib/src/{bundled-translations.es-ES-BMmvJGGC.js → bundled-translations.es-ES-DflsWQK0.js} +17 -17
- package/lib/src/{bundled-translations.fr-CA-Bo69L_BP.js → bundled-translations.fr-CA-fpDRuZ52.js} +1 -1
- package/lib/src/{bundled-translations.fr-FR-C4yTVFOW.js → bundled-translations.fr-FR-Bub73Zt-.js} +17 -17
- package/lib/src/{bundled-translations.pt-BR-CPMPfny0.js → bundled-translations.pt-BR-BlnWE9TB.js} +13 -13
- package/lib/src/{bundled-translations.pt-PT-C6ZwYs37.js → bundled-translations.pt-PT-DmzXlnaS.js} +30 -30
- package/lib/src/bundles/bundled-translations.de-DE.json +7 -1
- package/lib/src/bundles/bundled-translations.es-ES.json +7 -1
- package/lib/src/bundles/bundled-translations.fr-CA.json +7 -1
- package/lib/src/bundles/bundled-translations.fr-FR.json +7 -1
- package/lib/src/bundles/bundled-translations.json +7 -1
- package/lib/src/bundles/bundled-translations.pt-BR.json +7 -1
- package/lib/src/bundles/bundled-translations.pt-PT.json +7 -1
- package/lib/src/css/library.brand-page-brandCrowd.min.css +4 -4
- package/lib/src/css/library.brand-page-brandPage.min.css +4 -4
- package/lib/src/css/library.brand-page-designCom.min.css +4 -4
- package/lib/src/css/library.brand-page-preview.min.css +4 -4
- package/lib/src/index.mjs +1 -1
- package/lib/src/{lib-BE9sCFvu.js → lib-Y-hakSU_.js} +5456 -5414
- package/lib/src/shared/content-settings/components/payment-config-field/PaymentConfigField.mixin.d.ts +6 -5
- package/lib/src/shared/logo-settings/components/LogoSettings.mixin.d.ts +6 -5
- package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +6 -5
- package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +6 -5
- package/lib/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.mixin.d.ts +6 -5
- package/lib/src/shared/side-bar/components/SideBar.mixin.d.ts +6 -5
- package/lib/src/shared/style-settings/components/StyleSettings.mixin.d.ts +6 -5
- package/package.json +1 -1
- package/src/brand-page-maker/components/brandpage-settings/BrandPageSettings.test.ts +323 -0
- package/src/brand-page-maker/components/brandpage-settings/BrandPageSettings.vue +3 -3
- package/src/brand-page-maker/components/color-picker/BpColorPicker.stories.ts +10 -0
- package/src/brand-page-maker/components/color-picker/components/ColorModeSelect.mixin.ts +2 -2
- package/src/brand-page-maker/components/color-picker/components/ColorModeSelect.test.ts +116 -0
- package/src/brand-page-maker/components/color-picker/components/ColorModeSelect.vue +7 -1
- package/src/brand-page-maker/components/color-picker/components/GradientTypeSelector.test.ts +157 -0
- package/src/brand-page-maker/components/color-picker/components/GradientTypeSelector.vue +10 -4
- package/src/brand-page-maker/components/sensitive-content/SensitiveContent.vue +23 -3
- package/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.ts +5 -0
- package/src/brand-page-maker/components/seo-settings/SeoPageSettings.vue +2 -0
- package/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.save.test.ts +332 -1
- package/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.ts +83 -5
- package/src/bundles/bundled-translations.de-DE.json +7 -1
- package/src/bundles/bundled-translations.es-ES.json +7 -1
- package/src/bundles/bundled-translations.fr-CA.json +7 -1
- package/src/bundles/bundled-translations.fr-FR.json +7 -1
- package/src/bundles/bundled-translations.json +7 -1
- package/src/bundles/bundled-translations.pt-BR.json +7 -1
- package/src/bundles/bundled-translations.pt-PT.json +7 -1
- package/src/shared/color-picker/components/i18n/color-picker.de-DE.json +8 -1
- package/src/shared/color-picker/components/i18n/color-picker.es-ES.json +8 -1
- package/src/shared/color-picker/components/i18n/color-picker.fr-CA.json +8 -1
- package/src/shared/color-picker/components/i18n/color-picker.fr-FR.json +8 -1
- package/src/shared/color-picker/components/i18n/color-picker.json +8 -1
- package/src/shared/color-picker/components/i18n/color-picker.pt-BR.json +8 -1
- package/src/shared/color-picker/components/i18n/color-picker.pt-PT.json +8 -1
|
@@ -351,13 +351,14 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
|
|
|
351
351
|
startBrandPageSlugEditMode(): void;
|
|
352
352
|
triggerLockedContentClickedEvent(contentType: string): Promise<void>;
|
|
353
353
|
unpublishBrandPage(): Promise<void>;
|
|
354
|
-
updateBrandPageSettings(
|
|
355
|
-
description:
|
|
356
|
-
title:
|
|
354
|
+
updateBrandPageSettings(payload: {
|
|
355
|
+
description: string;
|
|
356
|
+
title: string;
|
|
357
357
|
}): Promise<void>;
|
|
358
|
-
updateSensitiveContent(
|
|
359
|
-
sensitiveContent:
|
|
358
|
+
updateSensitiveContent(payload: {
|
|
359
|
+
sensitiveContent: import("@brand-page").SensitiveContent;
|
|
360
360
|
}): Promise<void>;
|
|
361
|
+
saveSiteBeforeFirstSettingsWrite(): Promise<boolean>;
|
|
361
362
|
createBrandPageCustomDomain({ domainName }: {
|
|
362
363
|
domainName: any;
|
|
363
364
|
}): Promise<false | undefined>;
|
|
@@ -647,13 +647,14 @@ declare const _default: import("vue").DefineComponent<{}, {
|
|
|
647
647
|
startBrandPageSlugEditMode(): void;
|
|
648
648
|
triggerLockedContentClickedEvent(contentType: string): Promise<void>;
|
|
649
649
|
unpublishBrandPage(): Promise<void>;
|
|
650
|
-
updateBrandPageSettings(
|
|
651
|
-
description:
|
|
652
|
-
title:
|
|
650
|
+
updateBrandPageSettings(payload: {
|
|
651
|
+
description: string;
|
|
652
|
+
title: string;
|
|
653
653
|
}): Promise<void>;
|
|
654
|
-
updateSensitiveContent(
|
|
655
|
-
sensitiveContent:
|
|
654
|
+
updateSensitiveContent(payload: {
|
|
655
|
+
sensitiveContent: import("../../../brand-page").SensitiveContent;
|
|
656
656
|
}): Promise<void>;
|
|
657
|
+
saveSiteBeforeFirstSettingsWrite(): Promise<boolean>;
|
|
657
658
|
createBrandPageCustomDomain({ domainName }: {
|
|
658
659
|
domainName: any;
|
|
659
660
|
}): Promise<false | undefined>;
|
|
@@ -5083,13 +5083,14 @@ declare const _default: import("vue").DefineComponent<{}, {
|
|
|
5083
5083
|
startBrandPageSlugEditMode(): void;
|
|
5084
5084
|
triggerLockedContentClickedEvent(contentType: string): Promise<void>;
|
|
5085
5085
|
unpublishBrandPage(): Promise<void>;
|
|
5086
|
-
updateBrandPageSettings(
|
|
5087
|
-
description:
|
|
5088
|
-
title:
|
|
5086
|
+
updateBrandPageSettings(payload: {
|
|
5087
|
+
description: string;
|
|
5088
|
+
title: string;
|
|
5089
5089
|
}): Promise<void>;
|
|
5090
|
-
updateSensitiveContent(
|
|
5091
|
-
sensitiveContent:
|
|
5090
|
+
updateSensitiveContent(payload: {
|
|
5091
|
+
sensitiveContent: import("@brand-page").SensitiveContent;
|
|
5092
5092
|
}): Promise<void>;
|
|
5093
|
+
saveSiteBeforeFirstSettingsWrite(): Promise<boolean>;
|
|
5093
5094
|
createBrandPageCustomDomain({ domainName }: {
|
|
5094
5095
|
domainName: any;
|
|
5095
5096
|
}): Promise<false | undefined>;
|
|
@@ -4434,13 +4434,14 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
|
|
|
4434
4434
|
startBrandPageSlugEditMode(): void;
|
|
4435
4435
|
triggerLockedContentClickedEvent(contentType: string): Promise<void>;
|
|
4436
4436
|
unpublishBrandPage(): Promise<void>;
|
|
4437
|
-
updateBrandPageSettings(
|
|
4438
|
-
description:
|
|
4439
|
-
title:
|
|
4437
|
+
updateBrandPageSettings(payload: {
|
|
4438
|
+
description: string;
|
|
4439
|
+
title: string;
|
|
4440
4440
|
}): Promise<void>;
|
|
4441
|
-
updateSensitiveContent(
|
|
4442
|
-
sensitiveContent:
|
|
4441
|
+
updateSensitiveContent(payload: {
|
|
4442
|
+
sensitiveContent: import("@brand-page").SensitiveContent;
|
|
4443
4443
|
}): Promise<void>;
|
|
4444
|
+
saveSiteBeforeFirstSettingsWrite(): Promise<boolean>;
|
|
4444
4445
|
createBrandPageCustomDomain({ domainName }: {
|
|
4445
4446
|
domainName: any;
|
|
4446
4447
|
}): Promise<false | undefined>;
|
package/lib/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.mixin.d.ts
CHANGED
|
@@ -554,13 +554,14 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
|
|
|
554
554
|
startBrandPageSlugEditMode(): void;
|
|
555
555
|
triggerLockedContentClickedEvent(contentType: string): Promise<void>;
|
|
556
556
|
unpublishBrandPage(): Promise<void>;
|
|
557
|
-
updateBrandPageSettings(
|
|
558
|
-
description:
|
|
559
|
-
title:
|
|
557
|
+
updateBrandPageSettings(payload: {
|
|
558
|
+
description: string;
|
|
559
|
+
title: string;
|
|
560
560
|
}): Promise<void>;
|
|
561
|
-
updateSensitiveContent(
|
|
562
|
-
sensitiveContent:
|
|
561
|
+
updateSensitiveContent(payload: {
|
|
562
|
+
sensitiveContent: import("../../brand-page").SensitiveContent;
|
|
563
563
|
}): Promise<void>;
|
|
564
|
+
saveSiteBeforeFirstSettingsWrite(): Promise<boolean>;
|
|
564
565
|
createBrandPageCustomDomain({ domainName }: {
|
|
565
566
|
domainName: any;
|
|
566
567
|
}): Promise<false | undefined>;
|
|
@@ -344,13 +344,14 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
|
|
|
344
344
|
startBrandPageSlugEditMode(): void;
|
|
345
345
|
triggerLockedContentClickedEvent(contentType: string): Promise<void>;
|
|
346
346
|
unpublishBrandPage(): Promise<void>;
|
|
347
|
-
updateBrandPageSettings(
|
|
348
|
-
description:
|
|
349
|
-
title:
|
|
347
|
+
updateBrandPageSettings(payload: {
|
|
348
|
+
description: string;
|
|
349
|
+
title: string;
|
|
350
350
|
}): Promise<void>;
|
|
351
|
-
updateSensitiveContent(
|
|
352
|
-
sensitiveContent:
|
|
351
|
+
updateSensitiveContent(payload: {
|
|
352
|
+
sensitiveContent: import("../../../brand-page").SensitiveContent;
|
|
353
353
|
}): Promise<void>;
|
|
354
|
+
saveSiteBeforeFirstSettingsWrite(): Promise<boolean>;
|
|
354
355
|
createBrandPageCustomDomain({ domainName }: {
|
|
355
356
|
domainName: any;
|
|
356
357
|
}): Promise<false | undefined>;
|
|
@@ -852,13 +852,14 @@ declare const _default: import("vue").DefineComponent<{}, {
|
|
|
852
852
|
startBrandPageSlugEditMode(): void;
|
|
853
853
|
triggerLockedContentClickedEvent(contentType: string): Promise<void>;
|
|
854
854
|
unpublishBrandPage(): Promise<void>;
|
|
855
|
-
updateBrandPageSettings(
|
|
856
|
-
description:
|
|
857
|
-
title:
|
|
855
|
+
updateBrandPageSettings(payload: {
|
|
856
|
+
description: string;
|
|
857
|
+
title: string;
|
|
858
858
|
}): Promise<void>;
|
|
859
|
-
updateSensitiveContent(
|
|
860
|
-
sensitiveContent:
|
|
859
|
+
updateSensitiveContent(payload: {
|
|
860
|
+
sensitiveContent: import("../../../brand-page").SensitiveContent;
|
|
861
861
|
}): Promise<void>;
|
|
862
|
+
saveSiteBeforeFirstSettingsWrite(): Promise<boolean>;
|
|
862
863
|
createBrandPageCustomDomain({ domainName }: {
|
|
863
864
|
domainName: any;
|
|
864
865
|
}): Promise<false | undefined>;
|
package/package.json
CHANGED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { mount, DOMWrapper } from '@vue/test-utils';
|
|
2
|
+
import { createPinia, setActivePinia } from 'pinia';
|
|
3
|
+
import { describe, test, expect, beforeEach, vi } from 'vitest';
|
|
4
|
+
import { TextInput, Textarea } from '@designcrowd/fe-shared-lib';
|
|
5
|
+
import BrandPageSettings from './BrandPageSettings.vue';
|
|
6
|
+
import SeoPageSettings from '../seo-settings/SeoPageSettings.vue';
|
|
7
|
+
import SensitiveContent from '../sensitive-content/SensitiveContent.vue';
|
|
8
|
+
import ImageField from '@shared/content-settings/components/image-field/ImageField.vue';
|
|
9
|
+
import {
|
|
10
|
+
useBrandPageContainerStore,
|
|
11
|
+
useBrandPageMakerStore,
|
|
12
|
+
} from '@brand-page-maker/stores';
|
|
13
|
+
import { SENSITIVE_CONTENT_TYPES } from '@brand-page';
|
|
14
|
+
|
|
15
|
+
// CollapsiblePanel is imported directly in a `<script setup>` template, so
|
|
16
|
+
// `global.stubs` can't intercept it by name (no `resolveComponent` call is
|
|
17
|
+
// emitted for it). It starts collapsed and unmounts its slot when collapsed
|
|
18
|
+
// (maker-settings-panels.md), so open every panel for real by clicking its
|
|
19
|
+
// header, the same way ColorStrategyPanel.test.ts does.
|
|
20
|
+
const mountSettings = async () => {
|
|
21
|
+
const wrapper = mount(BrandPageSettings);
|
|
22
|
+
|
|
23
|
+
for (const header of wrapper.findAll(
|
|
24
|
+
'[data-component="collapsible-panel"] > div',
|
|
25
|
+
)) {
|
|
26
|
+
await header.trigger('click');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return wrapper;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type Wrapper = Awaited<ReturnType<typeof mountSettings>>;
|
|
33
|
+
|
|
34
|
+
const radios = (wrapper: Wrapper) =>
|
|
35
|
+
wrapper.findAll('input[type="radio"][name="sensitiveContent"]');
|
|
36
|
+
|
|
37
|
+
// GoogleAnalytics and SocialShare each render their own TextInput in a
|
|
38
|
+
// DIFFERENT collapsible panel, so scope to the panel holding the SEO
|
|
39
|
+
// Textarea (unique to SeoPageSettings) to find the SEO title input alone.
|
|
40
|
+
const seoTitleInput = (wrapper: Wrapper) => {
|
|
41
|
+
const textarea = wrapper.findComponent(Textarea);
|
|
42
|
+
|
|
43
|
+
if (!textarea.exists()) return undefined;
|
|
44
|
+
|
|
45
|
+
const panel = textarea.element.closest(
|
|
46
|
+
'[data-component="collapsible-panel"]',
|
|
47
|
+
) as HTMLElement;
|
|
48
|
+
|
|
49
|
+
const input = new DOMWrapper(panel).findComponent(TextInput);
|
|
50
|
+
|
|
51
|
+
return input.exists() ? input : undefined;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// The Sensitive Content panel is the only rendered panel with these radios;
|
|
55
|
+
// its Update button is the nearest one in the same collapsible panel.
|
|
56
|
+
const sensitiveContentUpdateButton = (wrapper: Wrapper) => {
|
|
57
|
+
const panel = radios(wrapper)[0].element.closest(
|
|
58
|
+
'[data-component="collapsible-panel"]',
|
|
59
|
+
) as HTMLElement;
|
|
60
|
+
|
|
61
|
+
return new DOMWrapper(panel).find('button');
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const faviconField = (wrapper: Wrapper) =>
|
|
65
|
+
wrapper
|
|
66
|
+
.findAllComponents(ImageField)
|
|
67
|
+
.find((field) => field.props('name') === 'favicon');
|
|
68
|
+
|
|
69
|
+
describe('BrandPageSettings — SEO and Sensitive Content before the first save', () => {
|
|
70
|
+
let containerStore: ReturnType<typeof useBrandPageContainerStore>;
|
|
71
|
+
let brandPageMakerStore: ReturnType<typeof useBrandPageMakerStore>;
|
|
72
|
+
|
|
73
|
+
beforeEach(() => {
|
|
74
|
+
setActivePinia(createPinia());
|
|
75
|
+
|
|
76
|
+
containerStore = useBrandPageContainerStore();
|
|
77
|
+
brandPageMakerStore = useBrandPageMakerStore();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test.each([
|
|
81
|
+
['post-purchase, never saved', {}],
|
|
82
|
+
['pre-purchase, never saved', { isPrePurchase: true }],
|
|
83
|
+
['saved site', { designDraftToken: 'draft-123' }],
|
|
84
|
+
])(
|
|
85
|
+
'shows both settings (%s)',
|
|
86
|
+
async (_case: string, flags: Record<string, boolean | string>) => {
|
|
87
|
+
Object.assign(containerStore, flags);
|
|
88
|
+
|
|
89
|
+
const wrapper = await mountSettings();
|
|
90
|
+
|
|
91
|
+
expect(seoTitleInput(wrapper)).toBeDefined();
|
|
92
|
+
expect(wrapper.findComponent(Textarea).exists()).toBe(true);
|
|
93
|
+
expect(wrapper.find('[data-test-btn-update-seo-settings]').exists()).toBe(
|
|
94
|
+
true,
|
|
95
|
+
);
|
|
96
|
+
expect(radios(wrapper)).toHaveLength(4);
|
|
97
|
+
expect(faviconField(wrapper)).toBeDefined();
|
|
98
|
+
},
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
test('disabled while saving', async () => {
|
|
102
|
+
// isPublishing disables both panels' Update buttons and inputs, and blocks
|
|
103
|
+
// a row click from changing the choice already made — the same as isSaving.
|
|
104
|
+
const publishingWrapper = await mountSettings();
|
|
105
|
+
|
|
106
|
+
// Dirty both panels first — an already-disabled (clean) button can't
|
|
107
|
+
// distinguish "isPublishing disables it" from "it was never enabled".
|
|
108
|
+
await seoTitleInput(publishingWrapper)!.find('input').setValue('A title');
|
|
109
|
+
await new DOMWrapper(
|
|
110
|
+
radios(publishingWrapper)[1].element.parentElement!,
|
|
111
|
+
).trigger('click');
|
|
112
|
+
|
|
113
|
+
containerStore.isPublishing = true;
|
|
114
|
+
await publishingWrapper.vm.$nextTick();
|
|
115
|
+
|
|
116
|
+
expect(
|
|
117
|
+
sensitiveContentUpdateButton(publishingWrapper).attributes('disabled'),
|
|
118
|
+
).toBeDefined();
|
|
119
|
+
expect(radios(publishingWrapper)[0].attributes('disabled')).toBeDefined();
|
|
120
|
+
expect(
|
|
121
|
+
publishingWrapper
|
|
122
|
+
.find('[data-test-btn-update-seo-settings]')
|
|
123
|
+
.attributes('disabled'),
|
|
124
|
+
).toBeDefined();
|
|
125
|
+
expect(seoTitleInput(publishingWrapper)!.props('disabled')).toBe(true);
|
|
126
|
+
expect(publishingWrapper.findComponent(Textarea).props('disabled')).toBe(
|
|
127
|
+
true,
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
// A click on a different row must not change the choice already made
|
|
131
|
+
// above while a publish is in flight.
|
|
132
|
+
await new DOMWrapper(
|
|
133
|
+
radios(publishingWrapper)[2].element.parentElement!,
|
|
134
|
+
).trigger('click');
|
|
135
|
+
expect(
|
|
136
|
+
(radios(publishingWrapper)[1].element as HTMLInputElement).checked,
|
|
137
|
+
).toBe(true);
|
|
138
|
+
|
|
139
|
+
containerStore.isPublishing = false;
|
|
140
|
+
|
|
141
|
+
// isSaving disables both panels' Update buttons and inputs, and blocks a
|
|
142
|
+
// row click from changing the choice already made.
|
|
143
|
+
const wrapper = await mountSettings();
|
|
144
|
+
|
|
145
|
+
// Dirty both panels first — an already-disabled (clean) button can't
|
|
146
|
+
// distinguish "isSaving disables it" from "it was never enabled". The
|
|
147
|
+
// radio's own state changes only through the row's `@click` handler, not
|
|
148
|
+
// a native `change`, so click the row rather than `setValue` the input.
|
|
149
|
+
await seoTitleInput(wrapper)!.find('input').setValue('A title');
|
|
150
|
+
await new DOMWrapper(radios(wrapper)[1].element.parentElement!).trigger(
|
|
151
|
+
'click',
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
expect(
|
|
155
|
+
sensitiveContentUpdateButton(wrapper).attributes('disabled'),
|
|
156
|
+
).toBeUndefined();
|
|
157
|
+
expect(
|
|
158
|
+
wrapper
|
|
159
|
+
.find('[data-test-btn-update-seo-settings]')
|
|
160
|
+
.attributes('disabled'),
|
|
161
|
+
).toBeUndefined();
|
|
162
|
+
|
|
163
|
+
containerStore.isSaving = true;
|
|
164
|
+
await wrapper.vm.$nextTick();
|
|
165
|
+
|
|
166
|
+
expect(
|
|
167
|
+
sensitiveContentUpdateButton(wrapper).attributes('disabled'),
|
|
168
|
+
).toBeDefined();
|
|
169
|
+
expect(radios(wrapper)[0].attributes('disabled')).toBeDefined();
|
|
170
|
+
expect(
|
|
171
|
+
wrapper
|
|
172
|
+
.find('[data-test-btn-update-seo-settings]')
|
|
173
|
+
.attributes('disabled'),
|
|
174
|
+
).toBeDefined();
|
|
175
|
+
expect(seoTitleInput(wrapper)!.props('disabled')).toBe(true);
|
|
176
|
+
expect(wrapper.findComponent(Textarea).props('disabled')).toBe(true);
|
|
177
|
+
|
|
178
|
+
// A click on a different row must not change the choice already made
|
|
179
|
+
// above while a save is in flight.
|
|
180
|
+
await new DOMWrapper(radios(wrapper)[2].element.parentElement!).trigger(
|
|
181
|
+
'click',
|
|
182
|
+
);
|
|
183
|
+
expect((radios(wrapper)[1].element as HTMLInputElement).checked).toBe(true);
|
|
184
|
+
|
|
185
|
+
containerStore.isSaving = false;
|
|
186
|
+
await wrapper.vm.$nextTick();
|
|
187
|
+
|
|
188
|
+
expect(
|
|
189
|
+
sensitiveContentUpdateButton(wrapper).attributes('disabled'),
|
|
190
|
+
).toBeUndefined();
|
|
191
|
+
expect(radios(wrapper)[0].attributes('disabled')).toBeUndefined();
|
|
192
|
+
expect(
|
|
193
|
+
wrapper
|
|
194
|
+
.find('[data-test-btn-update-seo-settings]')
|
|
195
|
+
.attributes('disabled'),
|
|
196
|
+
).toBeUndefined();
|
|
197
|
+
expect(seoTitleInput(wrapper)!.props('disabled')).toBe(false);
|
|
198
|
+
expect(wrapper.findComponent(Textarea).props('disabled')).toBe(false);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test('Manage page', async () => {
|
|
202
|
+
containerStore.isSaving = true;
|
|
203
|
+
vi.spyOn(brandPageMakerStore, 'updateBrandPageSettings').mockResolvedValue(
|
|
204
|
+
undefined,
|
|
205
|
+
);
|
|
206
|
+
vi.spyOn(
|
|
207
|
+
brandPageMakerStore,
|
|
208
|
+
'updateSensitiveContentSettings',
|
|
209
|
+
).mockResolvedValue(undefined);
|
|
210
|
+
|
|
211
|
+
const seoWrapper = mount(SeoPageSettings, { props: { isMaker: false } });
|
|
212
|
+
|
|
213
|
+
await seoWrapper.find('input').setValue('New title');
|
|
214
|
+
const seoUpdateButton = seoWrapper.find(
|
|
215
|
+
'[data-test-btn-update-seo-settings]',
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
expect(seoUpdateButton.attributes('disabled')).toBeUndefined();
|
|
219
|
+
|
|
220
|
+
await seoUpdateButton.trigger('click');
|
|
221
|
+
await seoWrapper.vm.$nextTick();
|
|
222
|
+
|
|
223
|
+
expect(brandPageMakerStore.updateBrandPageSettings).toHaveBeenCalledWith(
|
|
224
|
+
expect.objectContaining({ title: 'New title' }),
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
const sensitiveWrapper = mount(SensitiveContent, {
|
|
228
|
+
props: { isMaker: false, value: SENSITIVE_CONTENT_TYPES.off },
|
|
229
|
+
});
|
|
230
|
+
const sensitiveRadios = sensitiveWrapper.findAll(
|
|
231
|
+
'input[type="radio"][name="sensitiveContent"]',
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
expect(sensitiveRadios[1].attributes('disabled')).toBeUndefined();
|
|
235
|
+
|
|
236
|
+
// The radio has no v-model/@change of its own — a real click lands on
|
|
237
|
+
// the row's wrapping div, same as SensitiveContent's own click handler.
|
|
238
|
+
await new DOMWrapper(sensitiveRadios[1].element.parentElement!).trigger(
|
|
239
|
+
'click',
|
|
240
|
+
);
|
|
241
|
+
await sensitiveWrapper.vm.$nextTick();
|
|
242
|
+
|
|
243
|
+
const sensitiveUpdateButton = sensitiveWrapper.find('button');
|
|
244
|
+
|
|
245
|
+
expect(sensitiveUpdateButton.attributes('disabled')).toBeUndefined();
|
|
246
|
+
|
|
247
|
+
await sensitiveUpdateButton.trigger('click');
|
|
248
|
+
await sensitiveWrapper.vm.$nextTick();
|
|
249
|
+
|
|
250
|
+
expect(
|
|
251
|
+
brandPageMakerStore.updateSensitiveContentSettings,
|
|
252
|
+
).toHaveBeenCalledWith({
|
|
253
|
+
sensitiveContent: SENSITIVE_CONTENT_TYPES.plus18,
|
|
254
|
+
shouldRefresh: true,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// isPublishing is the maker's own in-flight state (set by
|
|
258
|
+
// ManageBrandPageContainer's onPublishClicked) — it must not lock the
|
|
259
|
+
// Manage page's radios either.
|
|
260
|
+
containerStore.isPublishing = true;
|
|
261
|
+
await sensitiveWrapper.vm.$nextTick();
|
|
262
|
+
|
|
263
|
+
expect(sensitiveRadios[2].attributes('disabled')).toBeUndefined();
|
|
264
|
+
|
|
265
|
+
await new DOMWrapper(sensitiveRadios[2].element.parentElement!).trigger(
|
|
266
|
+
'click',
|
|
267
|
+
);
|
|
268
|
+
await sensitiveWrapper.vm.$nextTick();
|
|
269
|
+
|
|
270
|
+
expect((sensitiveRadios[2].element as HTMLInputElement).checked).toBe(true);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test('SEO inputs stay editable on the Manage page while publishing', async () => {
|
|
274
|
+
// The Manage form is dirty when the store's title or description differs
|
|
275
|
+
// from the props, so match them to start clean.
|
|
276
|
+
containerStore.brandPageTitle = 'Old title';
|
|
277
|
+
containerStore.brandPageDescription = 'Old description';
|
|
278
|
+
containerStore.isPublishing = true;
|
|
279
|
+
|
|
280
|
+
const wrapper = mount(SeoPageSettings, {
|
|
281
|
+
props: {
|
|
282
|
+
isMaker: false,
|
|
283
|
+
pageTitle: 'Old title',
|
|
284
|
+
pageDescription: 'Old description',
|
|
285
|
+
},
|
|
286
|
+
});
|
|
287
|
+
const titleInput = wrapper.findComponent(TextInput);
|
|
288
|
+
const descriptionInput = wrapper.findComponent(Textarea);
|
|
289
|
+
|
|
290
|
+
expect(titleInput.props('disabled')).toBe(false);
|
|
291
|
+
expect(descriptionInput.props('disabled')).toBe(false);
|
|
292
|
+
|
|
293
|
+
await titleInput.find('input').setValue('New title');
|
|
294
|
+
await descriptionInput.find('textarea').setValue('New description');
|
|
295
|
+
|
|
296
|
+
expect(titleInput.props('modelValue')).toBe('New title');
|
|
297
|
+
expect(descriptionInput.props('modelValue')).toBe('New description');
|
|
298
|
+
|
|
299
|
+
const updateButton = wrapper.find('[data-test-btn-update-seo-settings]');
|
|
300
|
+
|
|
301
|
+
// The Update button keeps its unscoped isPublishing gate from develop;
|
|
302
|
+
// the form started clean, so releasing it shows the typing made it dirty.
|
|
303
|
+
expect(updateButton.attributes('disabled')).toBeDefined();
|
|
304
|
+
|
|
305
|
+
containerStore.isPublishing = false;
|
|
306
|
+
await wrapper.vm.$nextTick();
|
|
307
|
+
|
|
308
|
+
expect(updateButton.attributes('disabled')).toBeUndefined();
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test('admin template editor', async () => {
|
|
312
|
+
containerStore.isAdminMaker = true;
|
|
313
|
+
|
|
314
|
+
const wrapper = await mountSettings();
|
|
315
|
+
|
|
316
|
+
expect(wrapper.find('[data-test-btn-update-seo-settings]').exists()).toBe(
|
|
317
|
+
false,
|
|
318
|
+
);
|
|
319
|
+
expect(wrapper.findComponent(Textarea).exists()).toBe(false);
|
|
320
|
+
expect(radios(wrapper)).toHaveLength(0);
|
|
321
|
+
expect(faviconField(wrapper)).toBeDefined();
|
|
322
|
+
});
|
|
323
|
+
});
|
|
@@ -31,14 +31,14 @@
|
|
|
31
31
|
:is-collapsed="true"
|
|
32
32
|
>
|
|
33
33
|
<PageSettings
|
|
34
|
-
v-if="
|
|
34
|
+
v-if="!isAdminMaker"
|
|
35
35
|
:is-maker="true"
|
|
36
36
|
@update-settings="onUpdateSettings"
|
|
37
37
|
/>
|
|
38
38
|
<div
|
|
39
39
|
class="tw-border-0 tw-border-solid tw-border-grayscale-400 tw-mt-6"
|
|
40
40
|
:class="{
|
|
41
|
-
'tw-border-t tw-py-8':
|
|
41
|
+
'tw-border-t tw-py-8': !isAdminMaker,
|
|
42
42
|
}"
|
|
43
43
|
>
|
|
44
44
|
<h3 class="tw-text-left tw-font-bold tw-mb-2">
|
|
@@ -117,7 +117,7 @@
|
|
|
117
117
|
</div>
|
|
118
118
|
</CollapsiblePanel>
|
|
119
119
|
<CollapsiblePanel
|
|
120
|
-
v-if="
|
|
120
|
+
v-if="!isAdminMaker"
|
|
121
121
|
data-component="collapsible-panel"
|
|
122
122
|
:title="commonTr('sensitiveContent')"
|
|
123
123
|
:is-collapsed="true"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { defineComponent } from 'vue';
|
|
2
|
+
import { setLocaleAsync } from '@src/composables/useTranslate';
|
|
2
3
|
import ColorPickerV2 from './BpColorPickerV2.vue';
|
|
3
4
|
import ColorModeSelect from './components/ColorModeSelect.vue';
|
|
4
5
|
import GradientTypeSelector from './components/GradientTypeSelector.vue';
|
|
@@ -7,6 +8,15 @@ import GradientColorSlider from './components/GradientColorSlider.vue';
|
|
|
7
8
|
export default {
|
|
8
9
|
title: 'Components/BpColorPicker',
|
|
9
10
|
component: ColorPickerV2,
|
|
11
|
+
// All of this file's stories render labels looked up via colorPickerTr; with
|
|
12
|
+
// i18next 25, t() returns undefined before setLocaleAsync runs. A meta-level
|
|
13
|
+
// loader awaits it once for every story, including BpColorPickerV2Sample's
|
|
14
|
+
// ColorModeSelect/GradientTypeSelector rendered inside the popup.
|
|
15
|
+
loaders: [
|
|
16
|
+
async ({ globals }) => {
|
|
17
|
+
await setLocaleAsync(globals.locale);
|
|
18
|
+
},
|
|
19
|
+
],
|
|
10
20
|
};
|
|
11
21
|
|
|
12
22
|
export const ColorModeSelectComponent = () => {
|
|
@@ -7,11 +7,11 @@ export const COLOR_PICKER_MODE = {
|
|
|
7
7
|
|
|
8
8
|
export const COLOR_PICKER_MODE_OPTIONS = [
|
|
9
9
|
{
|
|
10
|
-
|
|
10
|
+
labelKey: 'colorModeSolid',
|
|
11
11
|
value: COLOR_PICKER_MODE.SOLID,
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
|
-
|
|
14
|
+
labelKey: 'colorModeGradient',
|
|
15
15
|
value: COLOR_PICKER_MODE.GRADIENT,
|
|
16
16
|
},
|
|
17
17
|
];
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import i18next from 'i18next';
|
|
3
|
+
import { describe, expect, test } from 'vitest';
|
|
4
|
+
import {
|
|
5
|
+
setLocaleAsync,
|
|
6
|
+
supportedLocales,
|
|
7
|
+
type LocaleTypes,
|
|
8
|
+
} from '@src/composables/useTranslate';
|
|
9
|
+
import ColorModeSelect from './ColorModeSelect.vue';
|
|
10
|
+
import en from '@src/shared/color-picker/components/i18n/color-picker.json';
|
|
11
|
+
import deDE from '@src/shared/color-picker/components/i18n/color-picker.de-DE.json';
|
|
12
|
+
import esES from '@src/shared/color-picker/components/i18n/color-picker.es-ES.json';
|
|
13
|
+
import frCA from '@src/shared/color-picker/components/i18n/color-picker.fr-CA.json';
|
|
14
|
+
import frFR from '@src/shared/color-picker/components/i18n/color-picker.fr-FR.json';
|
|
15
|
+
import ptBR from '@src/shared/color-picker/components/i18n/color-picker.pt-BR.json';
|
|
16
|
+
import ptPT from '@src/shared/color-picker/components/i18n/color-picker.pt-PT.json';
|
|
17
|
+
|
|
18
|
+
// Expected values come from the locale files, not literals, so Smartling's
|
|
19
|
+
// rewrite of the wording can't turn these tests red.
|
|
20
|
+
const localeFiles: Record<LocaleTypes, typeof en> = {
|
|
21
|
+
'en-US': en,
|
|
22
|
+
'de-DE': deDE,
|
|
23
|
+
'es-ES': esES,
|
|
24
|
+
'fr-CA': frCA,
|
|
25
|
+
'fr-FR': frFR,
|
|
26
|
+
'pt-BR': ptBR,
|
|
27
|
+
'pt-PT': ptPT,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const stringsFor = (locale: LocaleTypes): (typeof en)['colorPicker'] =>
|
|
31
|
+
localeFiles[locale].colorPicker;
|
|
32
|
+
|
|
33
|
+
const NON_ENGLISH_LOCALES = supportedLocales.filter(
|
|
34
|
+
(locale) => locale !== 'en-US',
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
// See RecaptchaDisclosure.vue.test.ts: the shared i18next singleton only takes
|
|
38
|
+
// the language from `setLocaleAsync` on its first call.
|
|
39
|
+
const useLocale = async (locale: string): Promise<void> => {
|
|
40
|
+
await setLocaleAsync(locale as LocaleTypes);
|
|
41
|
+
await i18next.changeLanguage(locale);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const optionTexts = (wrapper: ReturnType<typeof mount>): string[] =>
|
|
45
|
+
wrapper.findAll('option').map((option) => option.text());
|
|
46
|
+
|
|
47
|
+
const mountSelect = (modelValue?: string): ReturnType<typeof mount> =>
|
|
48
|
+
mount(ColorModeSelect, { props: { modelValue } });
|
|
49
|
+
|
|
50
|
+
describe('ColorModeSelect', () => {
|
|
51
|
+
test.each(NON_ENGLISH_LOCALES)(
|
|
52
|
+
'dropdown options use the %s locale file',
|
|
53
|
+
async (locale) => {
|
|
54
|
+
await useLocale(locale);
|
|
55
|
+
|
|
56
|
+
const wrapper = mountSelect();
|
|
57
|
+
|
|
58
|
+
expect(optionTexts(wrapper)).toEqual([
|
|
59
|
+
stringsFor(locale).colorModeSolid,
|
|
60
|
+
stringsFor(locale).colorModeGradient,
|
|
61
|
+
]);
|
|
62
|
+
optionTexts(wrapper).forEach((text) => {
|
|
63
|
+
expect(text).not.toMatch(/^colorPicker\./);
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
test('English dropdown options read word for word', async () => {
|
|
69
|
+
await useLocale('en-US');
|
|
70
|
+
|
|
71
|
+
const wrapper = mountSelect();
|
|
72
|
+
|
|
73
|
+
expect(optionTexts(wrapper)).toEqual(['Solid Color', 'Gradient Color']);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// A key missing from one locale file renders as its key path on the site;
|
|
77
|
+
// the shared test singleton would hide it behind the en-US fallback.
|
|
78
|
+
test.each(supportedLocales)(
|
|
79
|
+
'every locale file defines the colour-mode keys: %s',
|
|
80
|
+
(locale) => {
|
|
81
|
+
(['colorModeSolid', 'colorModeGradient'] as const).forEach((key) => {
|
|
82
|
+
const value = stringsFor(locale)[key];
|
|
83
|
+
|
|
84
|
+
expect(typeof value, `${locale} › ${key}`).toBe('string');
|
|
85
|
+
expect(value.trim(), `${locale} › ${key}`).not.toBe('');
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
test('a gradient colour opens on the translated gradient option', async () => {
|
|
91
|
+
await useLocale('de-DE');
|
|
92
|
+
|
|
93
|
+
const wrapper = mountSelect('gradient');
|
|
94
|
+
const select = wrapper.find('select');
|
|
95
|
+
|
|
96
|
+
expect((select.element as HTMLSelectElement).value).toBe('gradient');
|
|
97
|
+
expect(wrapper.find('option:checked').text()).toBe(
|
|
98
|
+
stringsFor('de-DE').colorModeGradient,
|
|
99
|
+
);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('picking an option emits the same value as today', async () => {
|
|
103
|
+
await useLocale('de-DE');
|
|
104
|
+
|
|
105
|
+
const wrapper = mountSelect('solid');
|
|
106
|
+
const select = wrapper.find('select');
|
|
107
|
+
|
|
108
|
+
await select.setValue('gradient');
|
|
109
|
+
await select.setValue('solid');
|
|
110
|
+
|
|
111
|
+
expect(wrapper.emitted('update:modelValue')).toEqual([
|
|
112
|
+
['gradient'],
|
|
113
|
+
['solid'],
|
|
114
|
+
]);
|
|
115
|
+
});
|
|
116
|
+
});
|