@designcrowd/library.brand-page 6.1.16 → 6.1.17-ast-header-button
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-CmNhDMIf.js → CreatedOnBrandCrowd-de-de-Cwq9enJs.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-DgplxxWa.js → CreatedOnBrandCrowd-en-us-ILK04gFj.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-DFdhFOa1.js → CreatedOnBrandCrowd-es-es-CbjxLxtu.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-DjLCWBLB.js → CreatedOnBrandCrowd-fr-ca-BwBDiK9z.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-DjLCWBLB.js → CreatedOnBrandCrowd-fr-fr-BwBDiK9z.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-CI2XD0OS.js → CreatedOnBrandCrowd-pt-br-DvhJROpG.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-CI2XD0OS.js → CreatedOnBrandCrowd-pt-pt-DvhJROpG.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-CkLkBztx.js → CreatedOnDcom-de-de-BK9imWOh.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-frnxBJJv.js → CreatedOnDcom-en-us-CyH-_qz1.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-CrqgiERj.js → CreatedOnDcom-es-es-Dh51qYEA.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-CPoEcusj.js → CreatedOnDcom-fr-ca-BArRxwWX.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-BqrRH3dV.js → CreatedOnDcom-fr-fr-CdglI-ZB.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-CpbVi3Sw.js → CreatedOnDcom-pt-br-DCehns-G.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-s0XZ3c7y.js → CreatedOnDcom-pt-pt-B8hl-oew.js} +1 -1
- package/lib/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.mixin.d.ts +27 -2
- package/lib/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.mixin.js +26 -0
- package/lib/src/brand-page/components/page-header/PageHeader.mixin.d.ts +3 -1
- package/lib/src/brand-page/components/page-header/PageHeader.mixin.js +12 -0
- package/lib/src/brand-page/models/index.d.ts +12 -0
- package/lib/src/brand-page/models/index.js +3 -0
- package/lib/src/brand-page/utils/button.util.d.ts +5 -0
- package/lib/src/brand-page/utils/button.util.js +4 -0
- package/lib/src/brand-page/utils/view-model.util.d.ts +1 -0
- package/lib/src/brand-page/utils/view-model.util.js +51 -1
- package/lib/src/brand-page/view-models.d.ts +3 -1
- package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +1 -1
- package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +1 -1
- package/lib/src/brand-page-maker/stores/ai-checkpoint/ai-checkpoint-store.d.ts +1008 -0
- package/lib/src/{bundled-translations-B2q5GWTQ.js → bundled-translations-B8fAEel0.js} +1 -1
- package/lib/src/bundles/bundled-translations.json +7 -1
- package/lib/src/css/library.brand-page-brandCrowd.min.css +315 -122
- package/lib/src/css/library.brand-page-brandPage.min.css +315 -122
- package/lib/src/css/library.brand-page-designCom.min.css +315 -122
- package/lib/src/css/library.brand-page-preview.min.css +315 -122
- package/lib/src/index.d.ts +1 -0
- package/lib/src/index.mjs +45 -44
- package/lib/src/{lib-O_eJgJHP.js → lib-KFjPFfxn.js} +31522 -31118
- package/lib/src/lib.js +1 -0
- package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.d.ts +1 -1
- package/lib/src/shared/content-settings/components/content-settings/ReplaceImageSettingsForContent.mixin.d.ts +1 -1
- package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.d.ts +1 -1
- package/lib/src/shared/content-settings/components/payment-config-field/PaymentConfigField.mixin.d.ts +1 -1
- package/lib/src/shared/ecommerce-settings/util/refreshDuplicatedMetadata.d.ts +11 -0
- package/lib/src/shared/ecommerce-settings/util/refreshDuplicatedMetadata.js +80 -0
- package/lib/src/shared/logo-settings/components/LogoSettings.mixin.d.ts +1 -1
- package/lib/src/shared/page-settings/components/delete-page-modal/DeletePageModal.mixin.d.ts +1 -1
- package/lib/src/shared/page-settings/components/new-page-modal/NewPageModal.mixin.d.ts +1 -1
- package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +113 -1
- package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +117 -2
- package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.js +43 -1
- package/lib/src/shared/page-settings/store/page-settings-store.d.ts +2 -2
- package/package.json +1 -1
- package/src/brand-page/components/button/Button.vue +35 -8
- package/src/brand-page/components/button/Button.vue.test.ts +59 -0
- package/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.mixin.ts +29 -1
- package/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.vue +36 -0
- package/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.vue.test.ts +175 -0
- package/src/brand-page/components/page/Page.vue +6 -0
- package/src/brand-page/components/page-header/HeaderLayout.vue +198 -4
- package/src/brand-page/components/page-header/PageHeader.mixin.ts +16 -1
- package/src/brand-page/components/page-header/PageHeader.vue +23 -1
- package/src/brand-page/components/page-header/PageHeader.vue.test.ts +378 -0
- package/src/brand-page/models/header-button.model.test.ts +86 -0
- package/src/brand-page/models/index.ts +16 -0
- package/src/brand-page/utils/__snapshots__/migration.util.test.ts.snap +6 -0
- package/src/brand-page/utils/__snapshots__/migration2.util.test.ts.snap +2 -0
- package/src/brand-page/utils/__snapshots__/model.util.test.ts.snap +4 -0
- package/src/brand-page/utils/button.util.ts +8 -0
- package/src/brand-page/utils/view-model.util.test.ts +146 -1
- package/src/brand-page/utils/view-model.util.ts +71 -1
- package/src/brand-page/view-models.ts +3 -0
- package/src/bundles/bundled-translations.json +7 -1
- package/src/lib.ts +1 -0
- package/src/shared/ecommerce-settings/util/refreshDuplicatedMetadata.test.ts +399 -0
- package/src/shared/ecommerce-settings/util/refreshDuplicatedMetadata.ts +99 -0
- package/src/shared/page-settings/components/header-settings-panel/HeaderSettingsPanel.button-section.test.ts +126 -0
- package/src/shared/page-settings/components/header-settings-panel/HeaderSettingsPanel.vue +43 -6
- package/src/shared/page-settings/components/page-settings-section-styles/HeaderButtonSettings.test.ts +167 -0
- package/src/shared/page-settings/components/page-settings-section-styles/HeaderButtonSettings.vue +237 -0
- package/src/shared/page-settings/components/page-settings-section-styles/HeaderDisplayStyle.test.ts +160 -0
- package/src/shared/page-settings/components/page-settings-section-styles/HeaderDisplayStyle.vue +37 -8
- package/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.header-button.test.ts +113 -0
- package/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.ts +47 -0
- package/src/shared/page-settings/components/page-settings-section-styles/i18n/page-settings-section.json +7 -1
- package/src/shared/page-settings/store/page-settings-store.ts +2 -0
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
<section class="tw-relative">
|
|
3
3
|
<ButtonStyleWrapper
|
|
4
4
|
v-if="isInternalLink"
|
|
5
|
-
class="bp-button
|
|
6
|
-
:class="
|
|
5
|
+
class="bp-button"
|
|
6
|
+
:class="wrapperClass"
|
|
7
7
|
:selected-button-style-id="viewModel.styles.selectedButtonStyleId"
|
|
8
8
|
:icon-color="iconColor"
|
|
9
|
+
:full-width="!isHeaderVariant"
|
|
9
10
|
>
|
|
10
11
|
<template #default="{ innerClass }">
|
|
11
12
|
<router-link
|
|
@@ -33,11 +34,12 @@
|
|
|
33
34
|
</template>
|
|
34
35
|
</ButtonIcon>
|
|
35
36
|
<span
|
|
36
|
-
v-if="!viewModel.iconUrl && !$slots.icon"
|
|
37
|
+
v-if="!viewModel.iconUrl && !$slots.icon && !isHeaderVariant"
|
|
37
38
|
class="c-button__no-icon"
|
|
38
39
|
/>
|
|
39
40
|
<TextWrapper
|
|
40
|
-
class="c-button__label
|
|
41
|
+
class="c-button__label"
|
|
42
|
+
:class="labelClass"
|
|
41
43
|
:text-style="viewModel.titleStyle"
|
|
42
44
|
:text="viewModel.title"
|
|
43
45
|
element="p"
|
|
@@ -48,10 +50,11 @@
|
|
|
48
50
|
</ButtonStyleWrapper>
|
|
49
51
|
<ButtonStyleWrapper
|
|
50
52
|
v-else
|
|
51
|
-
class="bp-button
|
|
52
|
-
:class="
|
|
53
|
+
class="bp-button"
|
|
54
|
+
:class="wrapperClass"
|
|
53
55
|
:selected-button-style-id="viewModel.styles.selectedButtonStyleId"
|
|
54
56
|
:icon-color="iconColor"
|
|
57
|
+
:full-width="!isHeaderVariant"
|
|
55
58
|
>
|
|
56
59
|
<template #default="{ innerClass }">
|
|
57
60
|
<a
|
|
@@ -76,11 +79,12 @@
|
|
|
76
79
|
</template>
|
|
77
80
|
</ButtonIcon>
|
|
78
81
|
<span
|
|
79
|
-
v-if="!$slots.icon && !viewModel.iconUrl"
|
|
82
|
+
v-if="!$slots.icon && !viewModel.iconUrl && !isHeaderVariant"
|
|
80
83
|
class="c-button__no-icon"
|
|
81
84
|
/>
|
|
82
85
|
<TextWrapper
|
|
83
|
-
class="c-button__label
|
|
86
|
+
class="c-button__label"
|
|
87
|
+
:class="labelClass"
|
|
84
88
|
:text-style="viewModel.titleStyle"
|
|
85
89
|
:text="viewModel.title"
|
|
86
90
|
element="p"
|
|
@@ -116,6 +120,10 @@ import {
|
|
|
116
120
|
findNavItemByPageId,
|
|
117
121
|
toRouteLocation,
|
|
118
122
|
} from '@src/brand-page/utils/route.util';
|
|
123
|
+
import {
|
|
124
|
+
BUTTON_VARIANTS,
|
|
125
|
+
type ButtonVariant,
|
|
126
|
+
} from '@src/brand-page/utils/button.util';
|
|
119
127
|
import ButtonIcon from './ButtonIcon.vue';
|
|
120
128
|
import ButtonStyleWrapper from './ButtonStyleWrapper.vue';
|
|
121
129
|
import TextWrapper from '../text-wrapper/TextWrapper.vue';
|
|
@@ -126,15 +134,34 @@ const props = withDefaults(
|
|
|
126
134
|
context: BrandPageContext;
|
|
127
135
|
contentIndex?: number;
|
|
128
136
|
isPreview?: boolean;
|
|
137
|
+
// 'section' is the standalone in-page button (the only historical shape).
|
|
138
|
+
// 'header' strips the section-only spacing — bottom margin, minimum height and
|
|
139
|
+
// the label offset that compensates for the icon gutter — so the button can sit
|
|
140
|
+
// inline in the header row. Defaults to 'section': existing callers are untouched.
|
|
141
|
+
variant?: ButtonVariant;
|
|
129
142
|
}>(),
|
|
130
143
|
{
|
|
131
144
|
contentIndex: undefined,
|
|
132
145
|
isPreview: false,
|
|
146
|
+
variant: BUTTON_VARIANTS.section,
|
|
133
147
|
},
|
|
134
148
|
);
|
|
135
149
|
|
|
136
150
|
const isModalVisible = ref(false);
|
|
137
151
|
|
|
152
|
+
const isHeaderVariant = computed(
|
|
153
|
+
(): boolean => props.variant === BUTTON_VARIANTS.header,
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
const wrapperClass = computed(() => ({
|
|
157
|
+
'tw-mb-4': !isHeaderVariant.value,
|
|
158
|
+
'tw-min-h-14': !props.isPreview && !isHeaderVariant.value,
|
|
159
|
+
}));
|
|
160
|
+
|
|
161
|
+
const labelClass = computed(() => ({
|
|
162
|
+
'c-button__label--offset-md': !isHeaderVariant.value,
|
|
163
|
+
}));
|
|
164
|
+
|
|
138
165
|
const buttonStyle = computed(
|
|
139
166
|
(): BrandPageCssStyle => props.viewModel.styles?.style || {},
|
|
140
167
|
);
|
|
@@ -130,6 +130,65 @@ const mountButton = async (currentPath: string) => {
|
|
|
130
130
|
return { wrapper, router };
|
|
131
131
|
};
|
|
132
132
|
|
|
133
|
+
describe('Button - section (default) vs header variant', () => {
|
|
134
|
+
beforeEach(() => {
|
|
135
|
+
setActivePinia(createPinia());
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const mountVariant = async (props: Record<string, unknown> = {}) => {
|
|
139
|
+
const router = createTestRouter();
|
|
140
|
+
|
|
141
|
+
router.push(OTHER_PAGE_PATH);
|
|
142
|
+
await router.isReady();
|
|
143
|
+
|
|
144
|
+
return mount(Button, {
|
|
145
|
+
propsData: {
|
|
146
|
+
context: createTestBrandPageContext(),
|
|
147
|
+
viewModel: createTestButtonViewModel({
|
|
148
|
+
url: 'https://example.com/pricing',
|
|
149
|
+
buttonLinkType: 'url',
|
|
150
|
+
clickActionField: 'url',
|
|
151
|
+
page: '',
|
|
152
|
+
}),
|
|
153
|
+
...props,
|
|
154
|
+
},
|
|
155
|
+
global: { plugins: [router] },
|
|
156
|
+
});
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// The variant prop must be inert for the 4 section consumers: same wrapper spacing,
|
|
160
|
+
// same icon gutter, same label offset. Guards the e2e visual snapshots.
|
|
161
|
+
it('keeps the section spacing and label offset by default', async () => {
|
|
162
|
+
const wrapper = await mountVariant();
|
|
163
|
+
const styleWrapper = wrapper.get('.bp-button');
|
|
164
|
+
|
|
165
|
+
expect(styleWrapper.classes()).toContain('tw-mb-4');
|
|
166
|
+
expect(styleWrapper.classes()).toContain('tw-min-h-14');
|
|
167
|
+
expect(wrapper.get('.c-button__label').classes()).toContain(
|
|
168
|
+
'c-button__label--offset-md',
|
|
169
|
+
);
|
|
170
|
+
expect(wrapper.find('.c-button__no-icon').exists()).toBe(true);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('drops the section-only spacing, gutter and offset in the header variant', async () => {
|
|
174
|
+
const wrapper = await mountVariant({ variant: 'header' });
|
|
175
|
+
const styleWrapper = wrapper.get('.bp-button');
|
|
176
|
+
|
|
177
|
+
expect(styleWrapper.classes()).not.toContain('tw-mb-4');
|
|
178
|
+
expect(styleWrapper.classes()).not.toContain('tw-min-h-14');
|
|
179
|
+
expect(wrapper.get('.c-button__label').classes()).not.toContain(
|
|
180
|
+
'c-button__label--offset-md',
|
|
181
|
+
);
|
|
182
|
+
expect(wrapper.find('.c-button__no-icon').exists()).toBe(false);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('keeps dropping tw-min-h-14 in preview, as before', async () => {
|
|
186
|
+
const wrapper = await mountVariant({ isPreview: true });
|
|
187
|
+
|
|
188
|
+
expect(wrapper.get('.bp-button').classes()).not.toContain('tw-min-h-14');
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
133
192
|
describe('Button - page link "back to top" behaviour (BP-5834)', () => {
|
|
134
193
|
let scrollToSpy: ReturnType<typeof vi.spyOn>;
|
|
135
194
|
|
package/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.mixin.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type {
|
|
|
6
6
|
NavigationMobile,
|
|
7
7
|
} from '../../../models';
|
|
8
8
|
import { resolveSubNavItemBackgroundColor } from '../../../models';
|
|
9
|
-
import type { NavItemViewModel } from '../../../view-models';
|
|
9
|
+
import type { ButtonViewModel, NavItemViewModel } from '../../../view-models';
|
|
10
10
|
|
|
11
11
|
export default defineComponent({
|
|
12
12
|
props: {
|
|
@@ -26,6 +26,18 @@ export default defineComponent({
|
|
|
26
26
|
type: Object as PropType<NavigationDesktop | NavigationMobile>,
|
|
27
27
|
required: true,
|
|
28
28
|
},
|
|
29
|
+
headerButtonViewModel: {
|
|
30
|
+
type: Object as PropType<ButtonViewModel>,
|
|
31
|
+
required: false,
|
|
32
|
+
default: undefined,
|
|
33
|
+
},
|
|
34
|
+
// The desktop drawer suppresses the header CTA — the desktop header row already
|
|
35
|
+
// renders it. Only the mobile drawer carries the button.
|
|
36
|
+
isMobile: {
|
|
37
|
+
type: Boolean,
|
|
38
|
+
required: false,
|
|
39
|
+
default: false,
|
|
40
|
+
},
|
|
29
41
|
},
|
|
30
42
|
emits: ['close-side-menu'],
|
|
31
43
|
data() {
|
|
@@ -74,6 +86,9 @@ export default defineComponent({
|
|
|
74
86
|
|
|
75
87
|
return rest;
|
|
76
88
|
},
|
|
89
|
+
showHeaderButton(): boolean {
|
|
90
|
+
return this.isMobile && Boolean(this.headerButtonViewModel);
|
|
91
|
+
},
|
|
77
92
|
routes(): any[] {
|
|
78
93
|
return this.visibleNavItems.map((navItem, _i) => {
|
|
79
94
|
return {
|
|
@@ -114,6 +129,19 @@ export default defineComponent({
|
|
|
114
129
|
closeSideMenu(): void {
|
|
115
130
|
this.$emit('close-side-menu');
|
|
116
131
|
},
|
|
132
|
+
// In the maker preview the CTA is inert (BP-5852 decision): capture-phase so the
|
|
133
|
+
// prevent lands before the inner anchor/router-link fires. On the published site
|
|
134
|
+
// the click follows the link and the drawer closes behind it.
|
|
135
|
+
onDrawerCtaClick(event: MouseEvent): void {
|
|
136
|
+
if (this.context.isPreview) {
|
|
137
|
+
event.preventDefault();
|
|
138
|
+
event.stopPropagation();
|
|
139
|
+
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
this.closeSideMenu();
|
|
144
|
+
},
|
|
117
145
|
toggleExpanded(pageId: string): void {
|
|
118
146
|
const isCurrentlyExpanded = this.expandedItems[pageId];
|
|
119
147
|
|
|
@@ -91,18 +91,37 @@
|
|
|
91
91
|
</template>
|
|
92
92
|
</template>
|
|
93
93
|
</div>
|
|
94
|
+
<div
|
|
95
|
+
v-if="showHeaderButton"
|
|
96
|
+
data-component="navigation-side-menu-cta"
|
|
97
|
+
class="c-drawer-cta"
|
|
98
|
+
>
|
|
99
|
+
<Button
|
|
100
|
+
data-test-header-cta-button="drawer"
|
|
101
|
+
:view-model="headerButtonViewModel"
|
|
102
|
+
:context="context"
|
|
103
|
+
:variant="BUTTON_VARIANTS.header"
|
|
104
|
+
@click.capture="onDrawerCtaClick"
|
|
105
|
+
/>
|
|
106
|
+
</div>
|
|
94
107
|
</div>
|
|
95
108
|
</div>
|
|
96
109
|
</template>
|
|
97
110
|
<script>
|
|
98
111
|
import { Icon } from '@designcrowd/fe-shared-lib';
|
|
99
112
|
import NavigationSideMenuMixin from './NavigationSideMenu.mixin.ts';
|
|
113
|
+
import Button from '../../button/Button.vue';
|
|
114
|
+
import { BUTTON_VARIANTS } from '../../../utils/button.util';
|
|
100
115
|
|
|
101
116
|
export default {
|
|
102
117
|
components: {
|
|
103
118
|
Icon,
|
|
119
|
+
Button,
|
|
104
120
|
},
|
|
105
121
|
mixins: [NavigationSideMenuMixin],
|
|
122
|
+
setup() {
|
|
123
|
+
return { BUTTON_VARIANTS };
|
|
124
|
+
},
|
|
106
125
|
};
|
|
107
126
|
</script>
|
|
108
127
|
<style scoped>
|
|
@@ -146,4 +165,21 @@ export default {
|
|
|
146
165
|
.c-chevron-expanded {
|
|
147
166
|
transform: rotate(180deg);
|
|
148
167
|
}
|
|
168
|
+
|
|
169
|
+
.c-drawer-cta {
|
|
170
|
+
@apply tw-relative tw-p-4;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/* Hairline separator from the nav list, drawn in the drawer's own text colour so it
|
|
174
|
+
works on any panel background. A pseudo-element carries the fade — opacity on the
|
|
175
|
+
section itself would wash out the button. */
|
|
176
|
+
.c-drawer-cta::before {
|
|
177
|
+
content: '';
|
|
178
|
+
position: absolute;
|
|
179
|
+
top: 0;
|
|
180
|
+
left: 0;
|
|
181
|
+
right: 0;
|
|
182
|
+
border-top: 1px solid currentColor;
|
|
183
|
+
opacity: 0.2;
|
|
184
|
+
}
|
|
149
185
|
</style>
|
package/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.vue.test.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import { createRouter, createMemoryHistory } from 'vue-router';
|
|
3
|
+
import { createPinia, setActivePinia } from 'pinia';
|
|
4
|
+
import NavigationSideMenu from './NavigationSideMenu.vue';
|
|
5
|
+
import {
|
|
6
|
+
BRAND_PAGE_TYPES,
|
|
7
|
+
BUTTON_LINK_TYPE,
|
|
8
|
+
MEDIA_SIZES,
|
|
9
|
+
PageHeaderMobile,
|
|
10
|
+
PageStyles,
|
|
11
|
+
type BrandPageContext,
|
|
12
|
+
type HeaderButton,
|
|
13
|
+
} from '@brand-page/models';
|
|
14
|
+
import type { NavItemViewModel } from '@brand-page/view-models';
|
|
15
|
+
import { mapToPageHeaderViewModel } from '@brand-page/utils';
|
|
16
|
+
|
|
17
|
+
const CTA = '[data-test-header-cta-button]';
|
|
18
|
+
const SLUG = 'my-brand';
|
|
19
|
+
|
|
20
|
+
const createContext = (): BrandPageContext =>
|
|
21
|
+
({
|
|
22
|
+
brandPageSlug: SLUG,
|
|
23
|
+
brandPageToken: '',
|
|
24
|
+
isMobile: false,
|
|
25
|
+
isPreview: false,
|
|
26
|
+
isProduction: false,
|
|
27
|
+
isServerRender: false,
|
|
28
|
+
options: { mediaSizes: MEDIA_SIZES.sizeThumb },
|
|
29
|
+
isTemplate: false,
|
|
30
|
+
isDesignCom: false,
|
|
31
|
+
pageTitle: 'My Brand',
|
|
32
|
+
runtimeEnvironment: '',
|
|
33
|
+
stripePublishableKey: undefined,
|
|
34
|
+
recaptchaSiteKey: undefined,
|
|
35
|
+
customDomain: undefined,
|
|
36
|
+
brandPageType: BRAND_PAGE_TYPES.brandSite,
|
|
37
|
+
isBrandPage: false,
|
|
38
|
+
isBrandPageOrIsBrandContact: false,
|
|
39
|
+
isBrandSite: true,
|
|
40
|
+
isBrandContact: false,
|
|
41
|
+
pages: [],
|
|
42
|
+
isCapturingPreview: false,
|
|
43
|
+
currentPageRelativePath: '',
|
|
44
|
+
nav: {
|
|
45
|
+
navItems: [
|
|
46
|
+
{
|
|
47
|
+
pageId: 'home',
|
|
48
|
+
slug: SLUG,
|
|
49
|
+
displayName: 'Home',
|
|
50
|
+
routeName: 'home-route',
|
|
51
|
+
routeParams: { slug: SLUG },
|
|
52
|
+
isRoot: true,
|
|
53
|
+
isHidden: false,
|
|
54
|
+
relativePath: '',
|
|
55
|
+
nestedNavItems: [],
|
|
56
|
+
},
|
|
57
|
+
] as NavItemViewModel[],
|
|
58
|
+
},
|
|
59
|
+
}) as BrandPageContext;
|
|
60
|
+
|
|
61
|
+
const aHeaderButton = (): HeaderButton => ({
|
|
62
|
+
isVisible: true,
|
|
63
|
+
text: 'Book now',
|
|
64
|
+
buttonLinkType: BUTTON_LINK_TYPE.url,
|
|
65
|
+
url: 'https://example.com/book',
|
|
66
|
+
buttonTextStyle: { textStyleType: 'buttons' },
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const mountDrawer = async (options: {
|
|
70
|
+
isMobile?: boolean;
|
|
71
|
+
headerButton?: HeaderButton;
|
|
72
|
+
isPreview?: boolean;
|
|
73
|
+
}) => {
|
|
74
|
+
const router = createRouter({
|
|
75
|
+
history: createMemoryHistory(),
|
|
76
|
+
routes: [
|
|
77
|
+
{
|
|
78
|
+
path: '/:slug',
|
|
79
|
+
name: 'home-route',
|
|
80
|
+
component: { template: '<div />' },
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
router.push(`/${SLUG}`);
|
|
86
|
+
await router.isReady();
|
|
87
|
+
|
|
88
|
+
const headerViewModel = mapToPageHeaderViewModel(
|
|
89
|
+
new PageHeaderMobile({ headerButton: options.headerButton }),
|
|
90
|
+
new PageStyles(),
|
|
91
|
+
[],
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const context = createContext();
|
|
95
|
+
|
|
96
|
+
if (options.isPreview) {
|
|
97
|
+
context.isPreview = true;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return mount(NavigationSideMenu, {
|
|
101
|
+
propsData: {
|
|
102
|
+
context,
|
|
103
|
+
isSideMenuOpen: true,
|
|
104
|
+
slug: SLUG,
|
|
105
|
+
styles: headerViewModel.navigation,
|
|
106
|
+
headerButtonViewModel: headerViewModel.headerButtonViewModel,
|
|
107
|
+
isMobile: options.isMobile ?? false,
|
|
108
|
+
},
|
|
109
|
+
global: { plugins: [router] },
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
describe('NavigationSideMenu - header call-to-action button (BP-5852)', () => {
|
|
114
|
+
beforeEach(() => {
|
|
115
|
+
setActivePinia(createPinia());
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('renders the CTA in the mobile drawer', async () => {
|
|
119
|
+
const wrapper = await mountDrawer({
|
|
120
|
+
isMobile: true,
|
|
121
|
+
headerButton: aHeaderButton(),
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
expect(
|
|
125
|
+
wrapper.find('[data-component="navigation-side-menu-cta"]').exists(),
|
|
126
|
+
).toBe(true);
|
|
127
|
+
expect(wrapper.get(CTA).text()).toContain('Book now');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// The desktop hamburger header row already renders the button, so the desktop
|
|
131
|
+
// drawer must not repeat it.
|
|
132
|
+
test('renders no CTA in the desktop drawer', async () => {
|
|
133
|
+
const wrapper = await mountDrawer({
|
|
134
|
+
isMobile: false,
|
|
135
|
+
headerButton: aHeaderButton(),
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
expect(wrapper.find(CTA).exists()).toBe(false);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('renders no CTA when the site has no header button', async () => {
|
|
142
|
+
const wrapper = await mountDrawer({ isMobile: true });
|
|
143
|
+
|
|
144
|
+
expect(wrapper.find(CTA).exists()).toBe(false);
|
|
145
|
+
expect(
|
|
146
|
+
wrapper.find('[data-component="navigation-side-menu-cta"]').exists(),
|
|
147
|
+
).toBe(false);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test('closes the drawer when the CTA is clicked', async () => {
|
|
151
|
+
const wrapper = await mountDrawer({
|
|
152
|
+
isMobile: true,
|
|
153
|
+
headerButton: aHeaderButton(),
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
await wrapper.get(`${CTA} a`).trigger('click');
|
|
157
|
+
|
|
158
|
+
expect(wrapper.emitted('close-side-menu')).toHaveLength(1);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test('a preview CTA click is inert: no navigation, drawer stays open', async () => {
|
|
162
|
+
const wrapper = await mountDrawer({
|
|
163
|
+
isMobile: true,
|
|
164
|
+
headerButton: aHeaderButton(),
|
|
165
|
+
isPreview: true,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const event = new MouseEvent('click', { bubbles: true, cancelable: true });
|
|
169
|
+
|
|
170
|
+
wrapper.get(`${CTA} a`).element.dispatchEvent(event);
|
|
171
|
+
|
|
172
|
+
expect(event.defaultPrevented).toBe(true);
|
|
173
|
+
expect(wrapper.emitted('close-side-menu')).toBeUndefined();
|
|
174
|
+
});
|
|
175
|
+
});
|
|
@@ -296,6 +296,8 @@
|
|
|
296
296
|
:page-navigation="viewModel.nav"
|
|
297
297
|
@close-side-menu="closeSideMenu"
|
|
298
298
|
/>
|
|
299
|
+
<!-- No header CTA here on purpose: the desktop hamburger header row
|
|
300
|
+
already renders it (BP-5852 placement spec). -->
|
|
299
301
|
</template>
|
|
300
302
|
<template #mobile>
|
|
301
303
|
<NavigationSideMenu
|
|
@@ -304,6 +306,10 @@
|
|
|
304
306
|
:slug="slug"
|
|
305
307
|
:styles="viewModel.headerViewModelMobile.navigation"
|
|
306
308
|
:page-navigation="viewModel.nav"
|
|
309
|
+
:header-button-view-model="
|
|
310
|
+
viewModel.headerViewModelMobile.headerButtonViewModel
|
|
311
|
+
"
|
|
312
|
+
:is-mobile="true"
|
|
307
313
|
@close-side-menu="closeSideMenu"
|
|
308
314
|
/>
|
|
309
315
|
</template>
|