@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.
Files changed (85) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-CmNhDMIf.js → CreatedOnBrandCrowd-de-de-Cwq9enJs.js} +1 -1
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-DgplxxWa.js → CreatedOnBrandCrowd-en-us-ILK04gFj.js} +1 -1
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-DFdhFOa1.js → CreatedOnBrandCrowd-es-es-CbjxLxtu.js} +1 -1
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-DjLCWBLB.js → CreatedOnBrandCrowd-fr-ca-BwBDiK9z.js} +1 -1
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-DjLCWBLB.js → CreatedOnBrandCrowd-fr-fr-BwBDiK9z.js} +1 -1
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-CI2XD0OS.js → CreatedOnBrandCrowd-pt-br-DvhJROpG.js} +1 -1
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-CI2XD0OS.js → CreatedOnBrandCrowd-pt-pt-DvhJROpG.js} +1 -1
  8. package/lib/src/{CreatedOnDcom-de-de-CkLkBztx.js → CreatedOnDcom-de-de-BK9imWOh.js} +1 -1
  9. package/lib/src/{CreatedOnDcom-en-us-frnxBJJv.js → CreatedOnDcom-en-us-CyH-_qz1.js} +1 -1
  10. package/lib/src/{CreatedOnDcom-es-es-CrqgiERj.js → CreatedOnDcom-es-es-Dh51qYEA.js} +1 -1
  11. package/lib/src/{CreatedOnDcom-fr-ca-CPoEcusj.js → CreatedOnDcom-fr-ca-BArRxwWX.js} +1 -1
  12. package/lib/src/{CreatedOnDcom-fr-fr-BqrRH3dV.js → CreatedOnDcom-fr-fr-CdglI-ZB.js} +1 -1
  13. package/lib/src/{CreatedOnDcom-pt-br-CpbVi3Sw.js → CreatedOnDcom-pt-br-DCehns-G.js} +1 -1
  14. package/lib/src/{CreatedOnDcom-pt-pt-s0XZ3c7y.js → CreatedOnDcom-pt-pt-B8hl-oew.js} +1 -1
  15. package/lib/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.mixin.d.ts +27 -2
  16. package/lib/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.mixin.js +26 -0
  17. package/lib/src/brand-page/components/page-header/PageHeader.mixin.d.ts +3 -1
  18. package/lib/src/brand-page/components/page-header/PageHeader.mixin.js +12 -0
  19. package/lib/src/brand-page/models/index.d.ts +12 -0
  20. package/lib/src/brand-page/models/index.js +3 -0
  21. package/lib/src/brand-page/utils/button.util.d.ts +5 -0
  22. package/lib/src/brand-page/utils/button.util.js +4 -0
  23. package/lib/src/brand-page/utils/view-model.util.d.ts +1 -0
  24. package/lib/src/brand-page/utils/view-model.util.js +51 -1
  25. package/lib/src/brand-page/view-models.d.ts +3 -1
  26. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +1 -1
  27. package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +1 -1
  28. package/lib/src/brand-page-maker/stores/ai-checkpoint/ai-checkpoint-store.d.ts +1008 -0
  29. package/lib/src/{bundled-translations-B2q5GWTQ.js → bundled-translations-B8fAEel0.js} +1 -1
  30. package/lib/src/bundles/bundled-translations.json +7 -1
  31. package/lib/src/css/library.brand-page-brandCrowd.min.css +315 -122
  32. package/lib/src/css/library.brand-page-brandPage.min.css +315 -122
  33. package/lib/src/css/library.brand-page-designCom.min.css +315 -122
  34. package/lib/src/css/library.brand-page-preview.min.css +315 -122
  35. package/lib/src/index.d.ts +1 -0
  36. package/lib/src/index.mjs +45 -44
  37. package/lib/src/{lib-O_eJgJHP.js → lib-KFjPFfxn.js} +31522 -31118
  38. package/lib/src/lib.js +1 -0
  39. package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.d.ts +1 -1
  40. package/lib/src/shared/content-settings/components/content-settings/ReplaceImageSettingsForContent.mixin.d.ts +1 -1
  41. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.d.ts +1 -1
  42. package/lib/src/shared/content-settings/components/payment-config-field/PaymentConfigField.mixin.d.ts +1 -1
  43. package/lib/src/shared/ecommerce-settings/util/refreshDuplicatedMetadata.d.ts +11 -0
  44. package/lib/src/shared/ecommerce-settings/util/refreshDuplicatedMetadata.js +80 -0
  45. package/lib/src/shared/logo-settings/components/LogoSettings.mixin.d.ts +1 -1
  46. package/lib/src/shared/page-settings/components/delete-page-modal/DeletePageModal.mixin.d.ts +1 -1
  47. package/lib/src/shared/page-settings/components/new-page-modal/NewPageModal.mixin.d.ts +1 -1
  48. package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +113 -1
  49. package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +117 -2
  50. package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.js +43 -1
  51. package/lib/src/shared/page-settings/store/page-settings-store.d.ts +2 -2
  52. package/package.json +1 -1
  53. package/src/brand-page/components/button/Button.vue +35 -8
  54. package/src/brand-page/components/button/Button.vue.test.ts +59 -0
  55. package/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.mixin.ts +29 -1
  56. package/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.vue +36 -0
  57. package/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.vue.test.ts +175 -0
  58. package/src/brand-page/components/page/Page.vue +6 -0
  59. package/src/brand-page/components/page-header/HeaderLayout.vue +198 -4
  60. package/src/brand-page/components/page-header/PageHeader.mixin.ts +16 -1
  61. package/src/brand-page/components/page-header/PageHeader.vue +23 -1
  62. package/src/brand-page/components/page-header/PageHeader.vue.test.ts +378 -0
  63. package/src/brand-page/models/header-button.model.test.ts +86 -0
  64. package/src/brand-page/models/index.ts +16 -0
  65. package/src/brand-page/utils/__snapshots__/migration.util.test.ts.snap +6 -0
  66. package/src/brand-page/utils/__snapshots__/migration2.util.test.ts.snap +2 -0
  67. package/src/brand-page/utils/__snapshots__/model.util.test.ts.snap +4 -0
  68. package/src/brand-page/utils/button.util.ts +8 -0
  69. package/src/brand-page/utils/view-model.util.test.ts +146 -1
  70. package/src/brand-page/utils/view-model.util.ts +71 -1
  71. package/src/brand-page/view-models.ts +3 -0
  72. package/src/bundles/bundled-translations.json +7 -1
  73. package/src/lib.ts +1 -0
  74. package/src/shared/ecommerce-settings/util/refreshDuplicatedMetadata.test.ts +399 -0
  75. package/src/shared/ecommerce-settings/util/refreshDuplicatedMetadata.ts +99 -0
  76. package/src/shared/page-settings/components/header-settings-panel/HeaderSettingsPanel.button-section.test.ts +126 -0
  77. package/src/shared/page-settings/components/header-settings-panel/HeaderSettingsPanel.vue +43 -6
  78. package/src/shared/page-settings/components/page-settings-section-styles/HeaderButtonSettings.test.ts +167 -0
  79. package/src/shared/page-settings/components/page-settings-section-styles/HeaderButtonSettings.vue +237 -0
  80. package/src/shared/page-settings/components/page-settings-section-styles/HeaderDisplayStyle.test.ts +160 -0
  81. package/src/shared/page-settings/components/page-settings-section-styles/HeaderDisplayStyle.vue +37 -8
  82. package/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.header-button.test.ts +113 -0
  83. package/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.ts +47 -0
  84. package/src/shared/page-settings/components/page-settings-section-styles/i18n/page-settings-section.json +7 -1
  85. 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 tw-mb-4"
6
- :class="{ 'tw-min-h-14': !isPreview }"
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 c-button__label--offset-md"
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 tw-mb-4"
52
- :class="{ 'tw-min-h-14': !isPreview }"
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 c-button__label--offset-md"
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
 
@@ -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>
@@ -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>