@designcrowd/library.brand-page 6.2.9 → 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-9uC6t5HU.js → CreatedOnBrandCrowd-de-de-CTEj6D-p.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-P6TzcklZ.js → CreatedOnBrandCrowd-en-us-ByFNuMoA.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-DimkGb1_.js → CreatedOnBrandCrowd-es-es-lA_3mWIj.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-CL_iFuJw.js → CreatedOnBrandCrowd-fr-ca-CS_YkUz3.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-CL_iFuJw.js → CreatedOnBrandCrowd-fr-fr-CS_YkUz3.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-DSIjEoMv.js → CreatedOnBrandCrowd-pt-br-BoduQ7zJ.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-DSIjEoMv.js → CreatedOnBrandCrowd-pt-pt-BoduQ7zJ.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-CKRvl9Ky.js → CreatedOnDcom-de-de-Co1OO4_u.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-ChplPDfS.js → CreatedOnDcom-en-us-DqFyqxOJ.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-DCIXsW9X.js → CreatedOnDcom-es-es-DhQWyb7C.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-lakt1nLU.js → CreatedOnDcom-fr-ca-DIxstLTf.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-RsGK1y5g.js → CreatedOnDcom-fr-fr-B-z8V1cH.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-DyA4XodM.js → CreatedOnDcom-pt-br-B7AMR1sc.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-BK51dTyw.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/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/css/library.brand-page-brandCrowd.min.css +1 -1
- package/lib/src/css/library.brand-page-brandPage.min.css +1 -1
- package/lib/src/css/library.brand-page-designCom.min.css +1 -1
- package/lib/src/css/library.brand-page-preview.min.css +1 -1
- package/lib/src/index.mjs +1 -1
- package/lib/src/{lib-Ca7GMpcR.js → lib-Y-hakSU_.js} +265 -233
- 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/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
|
@@ -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"
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
name="sensitiveContent"
|
|
41
41
|
:checked="data.selectedSensitiveSettings === opt.value"
|
|
42
42
|
:value="opt.value"
|
|
43
|
+
:disabled="isSaving || isPublishing"
|
|
43
44
|
/>
|
|
44
45
|
<input
|
|
45
46
|
hidden
|
|
@@ -68,7 +69,7 @@
|
|
|
68
69
|
:label="commonTr('updateSettings')"
|
|
69
70
|
variant="primary"
|
|
70
71
|
type="submit"
|
|
71
|
-
:disabled="isNoChange"
|
|
72
|
+
:disabled="isNoChange || isPublishing || isSaving"
|
|
72
73
|
@click="onSensitiveContentUpdate"
|
|
73
74
|
/>
|
|
74
75
|
</div>
|
|
@@ -97,7 +98,10 @@ import {
|
|
|
97
98
|
SENSITIVE_CONTENT_TYPES,
|
|
98
99
|
type SensitiveContent,
|
|
99
100
|
} from '@brand-page/models/brand-page-context.model';
|
|
100
|
-
import {
|
|
101
|
+
import {
|
|
102
|
+
useBrandPageContainerStore,
|
|
103
|
+
useBrandPageMakerStore,
|
|
104
|
+
} from '@brand-page-maker/stores';
|
|
101
105
|
import { sensitiveSettingTr, commonTr } from '@src/composables/useTranslate';
|
|
102
106
|
|
|
103
107
|
interface Props {
|
|
@@ -106,6 +110,7 @@ interface Props {
|
|
|
106
110
|
designDraftToken?: string;
|
|
107
111
|
}
|
|
108
112
|
const brandPageMakerStore = useBrandPageMakerStore();
|
|
113
|
+
const brandPageContainerStore = useBrandPageContainerStore();
|
|
109
114
|
|
|
110
115
|
const props = defineProps<Props>();
|
|
111
116
|
|
|
@@ -137,12 +142,27 @@ const isNoChange = computed(() => {
|
|
|
137
142
|
return data.selectedSensitiveSettings === props.value;
|
|
138
143
|
});
|
|
139
144
|
|
|
145
|
+
// Scoped to isMaker: the Manage page's own publish/save must never lock the maker's radios, and vice versa.
|
|
146
|
+
const isPublishing = computed<boolean>(
|
|
147
|
+
() => props.isMaker && brandPageContainerStore.isPublishing,
|
|
148
|
+
);
|
|
149
|
+
const isSaving = computed<boolean>(
|
|
150
|
+
() => props.isMaker && brandPageContainerStore.isSaving,
|
|
151
|
+
);
|
|
152
|
+
|
|
140
153
|
const emit = defineEmits([
|
|
141
154
|
'change-sensitive-settings',
|
|
142
155
|
'pre-submit-edit-sensitive-content',
|
|
143
156
|
]);
|
|
144
157
|
|
|
145
|
-
const onSensitiveSettingsClick = (value) => {
|
|
158
|
+
const onSensitiveSettingsClick = (value: SensitiveContent) => {
|
|
159
|
+
// The radio input's own `disabled` doesn't stop this handler — it's on the
|
|
160
|
+
// wrapping div so the whole row is clickable — so gate it here too, or a
|
|
161
|
+
// click would still change the choice while a save is in flight.
|
|
162
|
+
if (isSaving.value || isPublishing.value) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
146
166
|
data.selectedSensitiveSettings = value;
|
|
147
167
|
};
|
|
148
168
|
|
|
@@ -105,12 +105,17 @@ export default defineComponent({
|
|
|
105
105
|
isPublishing() {
|
|
106
106
|
return this.brandPageContainerStore.isPublishing;
|
|
107
107
|
},
|
|
108
|
+
// Scoped to isMaker: the Manage page's save must never gate on the maker's own in-flight state.
|
|
109
|
+
isSaving(): boolean {
|
|
110
|
+
return this.isMaker && this.brandPageContainerStore.isSaving;
|
|
111
|
+
},
|
|
108
112
|
isUpdateButtonEnabled(): any {
|
|
109
113
|
return (
|
|
110
114
|
(this.myPageDescription?.length ?? 0) <=
|
|
111
115
|
BRANDPAGE_DESCRIPTION_CHARACTER_LIMIT &&
|
|
112
116
|
(this.myPageTitle?.length ?? 0) <= BRANDPAGE_TITLE_CHARACTER_LIMIT &&
|
|
113
117
|
!this.brandPageContainerStore.isPublishing &&
|
|
118
|
+
!this.isSaving &&
|
|
114
119
|
this.isDirty &&
|
|
115
120
|
this.isInternalDescriptionValid &&
|
|
116
121
|
this.isInternalTitleValid
|
|
@@ -69,6 +69,7 @@
|
|
|
69
69
|
v-model="internalTitle"
|
|
70
70
|
:label="commonTr('pageTitle')"
|
|
71
71
|
:error="!!internalTitleErrorMessage"
|
|
72
|
+
:disabled="isSaving || isPublishing"
|
|
72
73
|
/>
|
|
73
74
|
<p
|
|
74
75
|
v-show="!isInternalTitleValid"
|
|
@@ -83,6 +84,7 @@
|
|
|
83
84
|
:placeholder="seoSettingsTr('websiteDescription')"
|
|
84
85
|
:error="!!internalDescriptionErrorMessage"
|
|
85
86
|
:resize="false"
|
|
87
|
+
:disabled="isSaving || isPublishing"
|
|
86
88
|
/>
|
|
87
89
|
<p
|
|
88
90
|
v-show="!isInternalDescriptionValid"
|