@designcrowd/library.brand-page 6.1.3 → 6.1.4-ast-prepurchase
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-Lk8J1vHg.js → CreatedOnBrandCrowd-de-de-BFDE6VfS.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-BbXj5fb1.js → CreatedOnBrandCrowd-en-us-CRhl6-Cd.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-CjnqvYCi.js → CreatedOnBrandCrowd-es-es-VaP_-7dv.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-Dbjv4qpN.js → CreatedOnBrandCrowd-fr-ca-BySGqvWO.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-Dbjv4qpN.js → CreatedOnBrandCrowd-fr-fr-BySGqvWO.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-BZIOPEvL.js → CreatedOnBrandCrowd-pt-br-BHjXVcSk.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-BZIOPEvL.js → CreatedOnBrandCrowd-pt-pt-BHjXVcSk.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-aN4JMRDI.js → CreatedOnDcom-de-de-DIFO5IZY.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-f3VD6Pz4.js → CreatedOnDcom-en-us-CcpDa04T.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-CQhNdtRP.js → CreatedOnDcom-es-es-DHwY0lWp.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-CFsC_XoO.js → CreatedOnDcom-fr-ca-BP3P0hJL.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-J4ZtCBO9.js → CreatedOnDcom-fr-fr-Bai1giy3.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-C1yFugh_.js → CreatedOnDcom-pt-br-GgKiDrbK.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-ycOo5lDn.js → CreatedOnDcom-pt-pt-uuuxCYR3.js} +1 -1
- package/lib/src/PreviewModal-DY2aAN0a.js +19931 -0
- package/lib/src/brand-page/components/page-footer/PageFooter.mixin.d.ts +0 -1
- package/lib/src/brand-page/components/page-footer/PageFooter.mixin.js +1 -4
- package/lib/src/brand-page/models/brand-page-context.model.d.ts +2 -0
- package/lib/src/brand-page/models/brand-page-context.model.js +12 -0
- package/lib/src/brand-page-maker/components/action-bar/ActionBar.mixin.d.ts +2 -0
- package/lib/src/brand-page-maker/components/action-bar/ActionBar.mixin.js +14 -1
- package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +134 -0
- package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.js +50 -0
- package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +130 -0
- package/lib/src/brand-page-maker/components/navigation/Navigation.mixin.d.ts +1 -0
- package/lib/src/brand-page-maker/components/navigation/Navigation.mixin.js +3 -0
- package/lib/src/brand-page-maker/components/preview/Preview.mixin.d.ts +14 -0
- package/lib/src/brand-page-maker/components/quick-link-bar/QuickLinkBar.mixin.d.ts +130 -0
- package/lib/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.d.ts +130 -0
- package/lib/src/brand-page-maker/components/seo-settings/UrlSettings.mixin.d.ts +130 -0
- package/lib/src/brand-page-maker/components/seo-settings/UseExistingDomainModal.mixin.d.ts +130 -0
- package/lib/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.d.ts +130 -0
- package/lib/src/brand-page-maker/components/upgrade-to-premium-button/UpgradeToPremiumButton.mixin.d.ts +130 -0
- package/lib/src/brand-page-maker/composables/useEventTracker.d.ts +1 -0
- package/lib/src/brand-page-maker/composables/useEventTracker.js +1 -0
- package/lib/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.d.ts +132 -0
- package/lib/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.js +22 -6
- package/lib/src/bundled-translations-D42tE0D2.js +179 -0
- package/lib/src/bundled-translations.de-DE-D9aWGgFt.js +179 -0
- package/lib/src/bundled-translations.es-ES-lk4B0xQ7.js +179 -0
- package/lib/src/bundled-translations.fr-CA-DhOBNt6q.js +179 -0
- package/lib/src/bundled-translations.fr-FR-CnpafQeG.js +179 -0
- package/lib/src/bundled-translations.pt-BR-C4PnKrd9.js +179 -0
- package/lib/src/bundled-translations.pt-PT-CfKDVQC-.js +179 -0
- package/lib/src/bundles/bundled-translations.de-DE.json +5 -0
- package/lib/src/bundles/bundled-translations.es-ES.json +5 -0
- package/lib/src/bundles/bundled-translations.fr-CA.json +5 -0
- package/lib/src/bundles/bundled-translations.fr-FR.json +5 -0
- package/lib/src/bundles/bundled-translations.json +5 -0
- package/lib/src/bundles/bundled-translations.pt-BR.json +5 -0
- package/lib/src/bundles/bundled-translations.pt-PT.json +5 -0
- package/lib/src/clients/brand-page-client.d.ts +1 -0
- package/lib/src/composables/useTranslate.d.ts +2 -1
- package/lib/src/composables/useTranslate.js +2 -1
- package/lib/src/constants/brand-page-maker.d.ts +2 -0
- package/lib/src/constants/brand-page-maker.js +5 -0
- package/lib/src/constants/live-preview.constants.d.ts +15 -0
- package/lib/src/constants/live-preview.constants.js +15 -0
- package/lib/src/css/library.brand-page-brandCrowd.min.css +264 -121
- package/lib/src/css/library.brand-page-brandPage.min.css +268 -121
- package/lib/src/css/library.brand-page-designCom.min.css +264 -121
- package/lib/src/css/library.brand-page-preview.min.css +264 -121
- package/lib/src/index.d.ts +2 -1
- package/lib/src/index.mjs +102150 -225
- package/lib/src/lib.js +2 -1
- package/lib/src/preview-modal.d.ts +5 -0
- package/lib/src/preview-modal.js +12 -0
- package/lib/src/preview-modal.mjs +6 -0
- package/lib/src/shared/content-settings/components/payment-config-field/PaymentConfigField.mixin.d.ts +130 -0
- package/lib/src/shared/logo-settings/components/LogoSettings.mixin.d.ts +130 -0
- package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +130 -0
- package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +130 -0
- package/lib/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.mixin.d.ts +144 -0
- package/lib/src/shared/side-bar/components/SideBar.mixin.d.ts +130 -0
- package/lib/src/shared/style-settings/components/StyleSettings.mixin.d.ts +130 -0
- package/lib/src/tracker.mjs +1 -1
- package/lib/src/{useEventTracker-DmQ6AkNn.js → useEventTracker-Dmc2jFdN.js} +5 -4
- package/package.json +5 -1
- package/src/brand-page/components/button/Button.vue +11 -0
- package/src/brand-page/components/button/Button.vue.test.ts +90 -0
- package/src/brand-page/components/image-gallery/masonry/MasonryGallery.vue +1 -0
- package/src/brand-page/components/images-section/ImagesSection.vue +10 -0
- package/src/brand-page/components/media/Video.vue +60 -1
- package/src/brand-page/components/page-footer/PageFooter.mixin.ts +1 -4
- package/src/brand-page/components/page-footer/PageFooter.vue +0 -1
- package/src/brand-page/components/payment/Payment.vue +3 -0
- package/src/brand-page/components/payment/request-payment/RequestPayment.vue +1 -0
- package/src/brand-page/components/payment/support-me/SupportMe.vue +3 -0
- package/src/brand-page/components/shop/CartModal.vue +9 -0
- package/src/brand-page/components/shop/Shop.vue +19 -0
- package/src/brand-page/components/shop/Shop.vue.test.ts +182 -2
- package/src/brand-page/components/social-links/SocialLinks.vue +1 -9
- package/src/brand-page/components/social-links/SocialLinks.vue.test.ts +28 -8
- package/src/brand-page/components/social-links/SocialLinksItems.vue +6 -2
- package/src/brand-page/components/testimonial-section/TestimonialSection.vue +2 -2
- package/src/brand-page/components/video/Video.vue +3 -0
- package/src/brand-page/models/brand-page-context.model.ts +23 -0
- package/src/brand-page-maker/components/action-bar/ActionBar.mixin.ts +15 -0
- package/src/brand-page-maker/components/action-bar/ActionBarNavigation.test.ts +95 -0
- package/src/brand-page-maker/components/action-bar/ActionBarNavigation.vue +19 -4
- package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.ts +57 -0
- package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.preview-gate.test.ts +113 -0
- package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.vue +26 -1
- package/src/brand-page-maker/components/navigation/Navigation.mixin.ts +3 -0
- package/src/brand-page-maker/components/navigation/Navigation.vue +1 -0
- package/src/brand-page-maker/components/preview-modal/PreviewModal.test.ts +162 -0
- package/src/brand-page-maker/components/preview-modal/PreviewModal.vue +347 -0
- package/src/brand-page-maker/components/preview-modal/PreviewModalWithState.test.ts +226 -0
- package/src/brand-page-maker/components/preview-modal/PreviewModalWithState.vue +149 -0
- package/src/brand-page-maker/components/preview-modal/i18n/preview-modal.de-DE.json +6 -0
- package/src/brand-page-maker/components/preview-modal/i18n/preview-modal.es-ES.json +6 -0
- package/src/brand-page-maker/components/preview-modal/i18n/preview-modal.fr-CA.json +6 -0
- package/src/brand-page-maker/components/preview-modal/i18n/preview-modal.fr-FR.json +6 -0
- package/src/brand-page-maker/components/preview-modal/i18n/preview-modal.json +6 -0
- package/src/brand-page-maker/components/preview-modal/i18n/preview-modal.pt-BR.json +6 -0
- package/src/brand-page-maker/components/preview-modal/i18n/preview-modal.pt-PT.json +6 -0
- package/src/brand-page-maker/components/rich-text-editor/RichTextEditor.vue +29 -17
- package/src/brand-page-maker/composables/useEventTracker.ts +1 -0
- package/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.save.test.ts +106 -0
- package/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.ts +40 -8
- package/src/bundles/bundled-translations.de-DE.json +5 -0
- package/src/bundles/bundled-translations.es-ES.json +5 -0
- package/src/bundles/bundled-translations.fr-CA.json +5 -0
- package/src/bundles/bundled-translations.fr-FR.json +5 -0
- package/src/bundles/bundled-translations.json +5 -0
- package/src/bundles/bundled-translations.pt-BR.json +5 -0
- package/src/bundles/bundled-translations.pt-PT.json +5 -0
- package/src/clients/brand-page-client.ts +2 -0
- package/src/composables/useTranslate.ts +2 -0
- package/src/constants/brand-page-maker.ts +5 -0
- package/src/constants/live-preview.constants.ts +15 -0
- package/src/lib.ts +5 -1
- package/src/preview-modal.ts +14 -0
- package/src/shared/common/i18n/common.de-DE.json +1 -0
- package/src/shared/common/i18n/common.es-ES.json +1 -0
- package/src/shared/common/i18n/common.fr-CA.json +1 -0
- package/src/shared/common/i18n/common.fr-FR.json +1 -0
- package/src/shared/common/i18n/common.json +1 -0
- package/src/shared/common/i18n/common.pt-BR.json +1 -0
- package/src/shared/common/i18n/common.pt-PT.json +1 -0
- package/src/shared/ecommerce-settings/store/cart-store.test.ts +38 -0
- package/lib/src/bundled-translations-2HkRBbBD.js +0 -177
- package/lib/src/bundled-translations.de-DE-CL9ZEhU2.js +0 -177
- package/lib/src/bundled-translations.es-ES-uj7Sliu0.js +0 -177
- package/lib/src/bundled-translations.fr-CA-BMnBtr_w.js +0 -177
- package/lib/src/bundled-translations.fr-FR-9PEZedzq.js +0 -177
- package/lib/src/bundled-translations.pt-BR-B3oNDkoe.js +0 -177
- package/lib/src/bundled-translations.pt-PT-DM7Vsf-h.js +0 -177
- package/lib/src/lib-CVwyHyBz.js +0 -121538
package/lib/src/tracker.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as N, E as O, G as _, P as A, a as I, S as a, T as S, U as s, u as C } from "./useEventTracker-
|
|
1
|
+
import { A as N, E as O, G as _, P as A, a as I, S as a, T as S, U as s, u as C } from "./useEventTracker-Dmc2jFdN.js";
|
|
2
2
|
export {
|
|
3
3
|
N as AI_CTA_LOCATIONS,
|
|
4
4
|
O as EVENTS,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { shallowRef as
|
|
1
|
+
import { shallowRef as I } from "vue";
|
|
2
2
|
const E = {
|
|
3
3
|
EXPERIMENT_STARTED: "$experiment_started",
|
|
4
4
|
WEBSITE_MAKER_ADD_CONTENT: "website_maker_add_content",
|
|
@@ -53,6 +53,7 @@ const E = {
|
|
|
53
53
|
ADD_CONTENT: "add_content",
|
|
54
54
|
EDITOR_PUBLISH_TOP: "editor_publish_top",
|
|
55
55
|
EDITOR_PUBLISH_BOTTOM: "editor_publish_bottom",
|
|
56
|
+
EDITOR_PREVIEW_PUBLISH: "editor_preview_publish",
|
|
56
57
|
SLUG_CHANGE_MAKER_SIDEBAR_SETTINGS: "slug_change_maker_sidebar_settings",
|
|
57
58
|
SLUG_CHANGE_MANAGE_PAGE: "slug_change_manage_page",
|
|
58
59
|
GOOGLE_ADSENSE: "google_adsense",
|
|
@@ -82,18 +83,18 @@ const E = {
|
|
|
82
83
|
PREMIUM: "premium",
|
|
83
84
|
EXIT: "exit",
|
|
84
85
|
FREEMIUM: "freemium"
|
|
85
|
-
}, i =
|
|
86
|
+
}, i = I(), n = I({
|
|
86
87
|
upsellOpenEventProps: {},
|
|
87
88
|
domainUpsellOpenEventProps: {},
|
|
88
89
|
publishOpenEventProps: {}
|
|
89
|
-
}), l =
|
|
90
|
+
}), l = I([]), _ = (o, A, e = !1, t = "maker_editor") => {
|
|
90
91
|
l.value.forEach((s) => {
|
|
91
92
|
s({
|
|
92
93
|
scope: t,
|
|
93
94
|
eventName: o,
|
|
94
95
|
eventProps: {
|
|
95
96
|
...e ? {} : i.value,
|
|
96
|
-
...
|
|
97
|
+
...A
|
|
97
98
|
}
|
|
98
99
|
});
|
|
99
100
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@designcrowd/library.brand-page",
|
|
3
3
|
"description": "A collection of Maker/Designer applications",
|
|
4
|
-
"version": "6.1.
|
|
4
|
+
"version": "6.1.4-ast-prepurchase",
|
|
5
5
|
"author": "Design.com Eng",
|
|
6
6
|
"license": "UNLICENSED",
|
|
7
7
|
"type": "module",
|
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
"types": "./lib/src/tracker.d.ts",
|
|
20
20
|
"import": "./lib/src/tracker.mjs"
|
|
21
21
|
},
|
|
22
|
+
"./preview-modal": {
|
|
23
|
+
"types": "./lib/src/preview-modal.d.ts",
|
|
24
|
+
"import": "./lib/src/preview-modal.mjs"
|
|
25
|
+
},
|
|
22
26
|
"./lib/src/css/*": "./lib/src/css/*",
|
|
23
27
|
"./package.json": "./package.json"
|
|
24
28
|
},
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
:class="[innerClass, buttonStateClass]"
|
|
63
63
|
:style="buttonStyle"
|
|
64
64
|
rel="noopener"
|
|
65
|
+
:data-preview-interactive="previewInteractive"
|
|
65
66
|
@click="onClick"
|
|
66
67
|
>
|
|
67
68
|
<ButtonIcon
|
|
@@ -89,6 +90,7 @@
|
|
|
89
90
|
</template>
|
|
90
91
|
</ButtonStyleWrapper>
|
|
91
92
|
<HashRouteModal
|
|
93
|
+
data-component="button-image-modal"
|
|
92
94
|
mode="image"
|
|
93
95
|
:is-hash-route-enabled="false"
|
|
94
96
|
:visible="isModalVisible"
|
|
@@ -149,6 +151,15 @@ const isModalImageUrlValid = computed((): boolean =>
|
|
|
149
151
|
isURL(props.viewModel.modalImageUrl),
|
|
150
152
|
);
|
|
151
153
|
|
|
154
|
+
// The live preview is inert by default, so the "Open Design" button needs an
|
|
155
|
+
// explicit opt-in to stay clickable. Only the modal-image action qualifies —
|
|
156
|
+
// external/mailto/tel buttons stay inert in preview. Returns null (not false) so
|
|
157
|
+
// Vue omits the attribute rather than rendering "false", which would still match
|
|
158
|
+
// the gate's selector.
|
|
159
|
+
const previewInteractive = computed((): '' | null =>
|
|
160
|
+
isModalUrl.value && isModalImageUrlValid.value ? '' : null,
|
|
161
|
+
);
|
|
162
|
+
|
|
152
163
|
const isBrushStrokeIcon = computed(
|
|
153
164
|
(): boolean =>
|
|
154
165
|
props.viewModel.styles.selectedButtonStyleId === 'BUTTON_STYLE_19',
|
|
@@ -173,3 +173,93 @@ describe('Button - page link "back to top" behaviour (BP-5834)', () => {
|
|
|
173
173
|
expect(scrollToSpy).not.toHaveBeenCalled();
|
|
174
174
|
});
|
|
175
175
|
});
|
|
176
|
+
|
|
177
|
+
describe('Button - "Open Design" image modal in live preview (BP-5955)', () => {
|
|
178
|
+
beforeEach(() => {
|
|
179
|
+
setActivePinia(createPinia());
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const mountModalButton = async (overrides: Partial<ButtonViewModel> = {}) => {
|
|
183
|
+
const router = createTestRouter();
|
|
184
|
+
|
|
185
|
+
router.push(OTHER_PAGE_PATH);
|
|
186
|
+
await router.isReady();
|
|
187
|
+
|
|
188
|
+
return mount(Button, {
|
|
189
|
+
propsData: {
|
|
190
|
+
context: createTestBrandPageContext(),
|
|
191
|
+
viewModel: createTestButtonViewModel({
|
|
192
|
+
buttonLinkType: 'url',
|
|
193
|
+
clickActionField: 'modalImageUrl',
|
|
194
|
+
modalImageUrl: 'https://assets.example.com/design.png',
|
|
195
|
+
...overrides,
|
|
196
|
+
}),
|
|
197
|
+
},
|
|
198
|
+
global: {
|
|
199
|
+
plugins: [router],
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
test('opts the anchor into the preview gate for a valid modal image (data-preview-interactive="")', async () => {
|
|
205
|
+
const wrapper = await mountModalButton();
|
|
206
|
+
|
|
207
|
+
expect(wrapper.get('a').attributes('data-preview-interactive')).toBe('');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test('omits the opt-in attribute entirely for a url-action button (never renders "false")', async () => {
|
|
211
|
+
const wrapper = await mountModalButton({
|
|
212
|
+
clickActionField: 'url',
|
|
213
|
+
url: 'https://example.com',
|
|
214
|
+
modalImageUrl: '',
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
expect(
|
|
218
|
+
wrapper.get('a').attributes('data-preview-interactive'),
|
|
219
|
+
).toBeUndefined();
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test('omits the opt-in attribute when the modal image url is invalid', async () => {
|
|
223
|
+
const wrapper = await mountModalButton({ modalImageUrl: 'not-a-url' });
|
|
224
|
+
|
|
225
|
+
expect(
|
|
226
|
+
wrapper.get('a').attributes('data-preview-interactive'),
|
|
227
|
+
).toBeUndefined();
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// Doubles as the fallthrough-attr runtime check: the data-component must actually
|
|
231
|
+
// land on a queryable element (Modal's v-show root keeps it in the DOM).
|
|
232
|
+
test('anchors the popup with data-component="button-image-modal" for the dismissal allow-list', async () => {
|
|
233
|
+
const wrapper = await mountModalButton();
|
|
234
|
+
|
|
235
|
+
expect(wrapper.find('[data-component="button-image-modal"]').exists()).toBe(
|
|
236
|
+
true,
|
|
237
|
+
);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test('opens the image popup on click and closes it on dismiss', async () => {
|
|
241
|
+
const wrapper = await mountModalButton();
|
|
242
|
+
|
|
243
|
+
expect(
|
|
244
|
+
wrapper
|
|
245
|
+
.get('[data-component="button-image-modal"]')
|
|
246
|
+
.attributes('aria-hidden'),
|
|
247
|
+
).toBe('true');
|
|
248
|
+
|
|
249
|
+
await wrapper.get('a').trigger('click');
|
|
250
|
+
|
|
251
|
+
expect(
|
|
252
|
+
wrapper
|
|
253
|
+
.get('[data-component="button-image-modal"]')
|
|
254
|
+
.attributes('aria-hidden'),
|
|
255
|
+
).toBe('false');
|
|
256
|
+
|
|
257
|
+
await wrapper.get('[data-test-modal-close-btn]').trigger('click');
|
|
258
|
+
|
|
259
|
+
expect(
|
|
260
|
+
wrapper
|
|
261
|
+
.get('[data-component="button-image-modal"]')
|
|
262
|
+
.attributes('aria-hidden'),
|
|
263
|
+
).toBe('true');
|
|
264
|
+
});
|
|
265
|
+
});
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
:class="{
|
|
14
14
|
'tw-cursor-pointer': shouldShowCursor(item),
|
|
15
15
|
}"
|
|
16
|
+
:data-preview-interactive="previewInteractive(item)"
|
|
16
17
|
@click="shouldBeClickable(item) ? onSelectImage(item) : undefined"
|
|
17
18
|
/>
|
|
18
19
|
<FallbackImage
|
|
@@ -29,6 +30,7 @@
|
|
|
29
30
|
:alt="itemImageAltText(item)"
|
|
30
31
|
:context="context"
|
|
31
32
|
:content-index="contentIndex"
|
|
33
|
+
:data-preview-interactive="previewInteractive(item)"
|
|
32
34
|
@click="shouldBeClickable(item) ? onSelectImage(item) : undefined"
|
|
33
35
|
/>
|
|
34
36
|
<Placeholder
|
|
@@ -222,6 +224,14 @@ const shouldBeClickable = (item: TextMediaItem): boolean => {
|
|
|
222
224
|
);
|
|
223
225
|
};
|
|
224
226
|
|
|
227
|
+
// The live preview is inert by default, so a clickable image needs an explicit
|
|
228
|
+
// opt-in. It mirrors `shouldBeClickable` exactly: whatever an image does on the live
|
|
229
|
+
// site, it does in the preview — the lightbox, or opening its link in a new tab.
|
|
230
|
+
// Returns null (not false) so Vue omits the attribute rather than rendering "false",
|
|
231
|
+
// which would still match the gate's selector.
|
|
232
|
+
const previewInteractive = (item: TextMediaItem): '' | null =>
|
|
233
|
+
shouldBeClickable(item) ? '' : null;
|
|
234
|
+
|
|
225
235
|
const onImageModalClose = () => {
|
|
226
236
|
imageModalSelectedImageIndex.value = undefined;
|
|
227
237
|
};
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div v-if="!context.options?.renderVideoAsImage" :class="videoClasses">
|
|
3
|
+
<!-- Poster paints the thumbnail immediately instead of a blank box while the video
|
|
4
|
+
streams. `autoplay` uses isPreview, not isPreviewMode, on purpose: the live
|
|
5
|
+
preview must play like the published site. -->
|
|
3
6
|
<video
|
|
4
7
|
ref="videoElement"
|
|
5
8
|
class="tw-w-full tw-h-full tw-object-cover"
|
|
6
9
|
:src="media?.videoUrl"
|
|
10
|
+
:poster="media?.previewImageUrl"
|
|
7
11
|
:autoplay="!context.isPreview"
|
|
8
12
|
muted
|
|
9
13
|
loop
|
|
@@ -11,6 +15,7 @@
|
|
|
11
15
|
playsinline
|
|
12
16
|
preload="auto"
|
|
13
17
|
@canplay="attemptAutoplay"
|
|
18
|
+
@error="scheduleVideoRetry"
|
|
14
19
|
/>
|
|
15
20
|
</div>
|
|
16
21
|
<FallbackImage
|
|
@@ -25,7 +30,7 @@
|
|
|
25
30
|
/>
|
|
26
31
|
</template>
|
|
27
32
|
<script setup lang="ts">
|
|
28
|
-
import { nextTick, onMounted, ref, watch } from 'vue';
|
|
33
|
+
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
|
|
29
34
|
import { type BrandPageContext, type GettyVideoViewModel } from '../../../lib';
|
|
30
35
|
import FallbackImage from '../fallback-image/FallbackImage.vue';
|
|
31
36
|
|
|
@@ -45,6 +50,60 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
45
50
|
|
|
46
51
|
const videoElement = ref<HTMLVideoElement>();
|
|
47
52
|
|
|
53
|
+
// The video URL is derived from the asset token, and that address only exists once
|
|
54
|
+
// Getty has finished copying the asset into public storage. A video added moments
|
|
55
|
+
// ago is still copying, so the first load 404s and the customer gets a blank box
|
|
56
|
+
// until they refresh. Retry instead — published pages hit this too, not only preview.
|
|
57
|
+
//
|
|
58
|
+
// Spread wide on purpose: a measured copy was still not ready after 30s, so short
|
|
59
|
+
// intervals only burn attempts. Backing off to ~4 minutes total costs six 404s.
|
|
60
|
+
const RETRY_DELAYS_MS = [5000, 10000, 20000, 40000, 60000, 120000];
|
|
61
|
+
|
|
62
|
+
let retryCount = 0;
|
|
63
|
+
let retryTimer: ReturnType<typeof setTimeout> | undefined;
|
|
64
|
+
|
|
65
|
+
const clearRetry = () => {
|
|
66
|
+
if (retryTimer) {
|
|
67
|
+
clearTimeout(retryTimer);
|
|
68
|
+
retryTimer = undefined;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const scheduleVideoRetry = () => {
|
|
73
|
+
const delay = RETRY_DELAYS_MS[retryCount];
|
|
74
|
+
|
|
75
|
+
if (delay === undefined) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
retryCount += 1;
|
|
80
|
+
clearRetry();
|
|
81
|
+
|
|
82
|
+
retryTimer = setTimeout(() => {
|
|
83
|
+
const element = videoElement.value;
|
|
84
|
+
|
|
85
|
+
if (!element || !props.media?.videoUrl) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// CloudFront caches 404s briefly, so a plain reload would keep serving the miss.
|
|
90
|
+
// The buster is only on retries; the first (normal) load stays cacheable.
|
|
91
|
+
element.src = `${props.media.videoUrl}?retry=${retryCount}`;
|
|
92
|
+
element.load();
|
|
93
|
+
}, delay);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// A different video means a fresh budget.
|
|
97
|
+
watch(
|
|
98
|
+
() => props.media?.videoUrl,
|
|
99
|
+
() => {
|
|
100
|
+
retryCount = 0;
|
|
101
|
+
clearRetry();
|
|
102
|
+
},
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
onUnmounted(clearRetry);
|
|
106
|
+
|
|
48
107
|
const attemptAutoplay = async () => {
|
|
49
108
|
if (
|
|
50
109
|
props.context.options?.renderVideoAsImage ||
|
|
@@ -2,7 +2,7 @@ import { defineComponent } from 'vue';
|
|
|
2
2
|
import type { PageFooterViewModel } from '@brand-page/view-models';
|
|
3
3
|
import type { PropType } from 'vue';
|
|
4
4
|
import type { BrandPageContext } from '@brand-page/models';
|
|
5
|
-
import { getRenderText
|
|
5
|
+
import { getRenderText } from '@brand-page/utils';
|
|
6
6
|
import { FOOTER_DISPLAY } from '../../constants';
|
|
7
7
|
|
|
8
8
|
export default defineComponent({
|
|
@@ -27,9 +27,6 @@ export default defineComponent({
|
|
|
27
27
|
},
|
|
28
28
|
emits: ['on-logo-click', 'footer-container-click'],
|
|
29
29
|
computed: {
|
|
30
|
-
isInPreviewMode(): boolean {
|
|
31
|
-
return isPreviewMode(this.context);
|
|
32
|
-
},
|
|
33
30
|
containerStyle(): BrandPageCssStyle {
|
|
34
31
|
return this.viewModel.backgroundStyle;
|
|
35
32
|
},
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
<section>
|
|
3
3
|
<div v-if="showPaymentBlock" class="tw-p-4" :style="containerStyle">
|
|
4
4
|
<!-- Start incomplete config state -->
|
|
5
|
+
<!-- Only the header opts in — pay/checkout below stay inert. Payment rolls its own
|
|
6
|
+
header, so the `collapsible` allow-list entry can't reach it. -->
|
|
5
7
|
<div
|
|
8
|
+
data-preview-interactive
|
|
6
9
|
class="tw-relative tw-text-center tw-w-full hover:tw-cursor-pointer"
|
|
7
10
|
:class="{ 'tw-mb-4': !isCollapsed }"
|
|
8
11
|
@click="onHeaderToggleClick"
|
|
@@ -31,9 +31,12 @@
|
|
|
31
31
|
'tw-grid-cols-1': paymentOptions.length < 2,
|
|
32
32
|
}"
|
|
33
33
|
>
|
|
34
|
+
<!-- Picking an amount is local state only — no cart, no charge. Marked per
|
|
35
|
+
control, not on the root, which would expose pay/checkout too. -->
|
|
34
36
|
<li
|
|
35
37
|
v-for="paymentOption in paymentOptions"
|
|
36
38
|
:key="`payment-option-${paymentOption.id}`"
|
|
39
|
+
data-preview-interactive
|
|
37
40
|
class="payment-option tw-flex tw-flex-row tw-flex-wrap tw-justify-center tw-text-sm tw-my-0"
|
|
38
41
|
:class="{
|
|
39
42
|
'payment-option--selected': selectedPaymentOptionIds.some(
|
|
@@ -197,7 +197,12 @@
|
|
|
197
197
|
}"
|
|
198
198
|
@click="onClose"
|
|
199
199
|
/>
|
|
200
|
+
<!-- Hidden in the live preview: checkout is hard-blocked upstream
|
|
201
|
+
(`Shop.vue.onShowPaymentModal` bails on `isPreviewMode`), so a
|
|
202
|
+
dead Proceed button would only mislead. The checkout "preview
|
|
203
|
+
prompt" UI that replaces it is deferred (needs Figma). -->
|
|
200
204
|
<Button
|
|
205
|
+
v-if="!props.isCheckoutBlocked"
|
|
201
206
|
classes="tw-px-8 tw-w-full"
|
|
202
207
|
:label="btnLabel"
|
|
203
208
|
variant="primary"
|
|
@@ -247,10 +252,14 @@ import { calculatePrice } from '../../../utils/currency.util';
|
|
|
247
252
|
interface Props {
|
|
248
253
|
isVisible: boolean;
|
|
249
254
|
buttonStyle: BrandPageCssStyle;
|
|
255
|
+
// True in the live preview: the cart is browsable but checkout is hard-blocked,
|
|
256
|
+
// so the Proceed button is hidden.
|
|
257
|
+
isCheckoutBlocked?: boolean;
|
|
250
258
|
}
|
|
251
259
|
|
|
252
260
|
const props = withDefaults(defineProps<Props>(), {
|
|
253
261
|
isVisible: true,
|
|
262
|
+
isCheckoutBlocked: false,
|
|
254
263
|
});
|
|
255
264
|
|
|
256
265
|
const cartSettingsStore = useCartStore();
|
|
@@ -63,9 +63,14 @@
|
|
|
63
63
|
:icon-color="viewModel.buttonStyles?.style?.backgroundColor"
|
|
64
64
|
:no-margin="true"
|
|
65
65
|
>
|
|
66
|
+
<!-- `data-preview-interactive`: opening the cart to look at it charges
|
|
67
|
+
nothing, so it stays clickable in the live preview even though the shop
|
|
68
|
+
is inert by default. Scoped to this button — everything inside the cart,
|
|
69
|
+
Checkout included, stays blocked. -->
|
|
66
70
|
<button
|
|
67
71
|
class="tw-mt-4 tw-block tw-w-full tw-border-0 tw-py-3 tw-select-none tw-text-base hover:tw-cursor-pointer tw-rounded"
|
|
68
72
|
:style="buttonStyle"
|
|
73
|
+
data-preview-interactive
|
|
69
74
|
@click="onShowCartModal"
|
|
70
75
|
>
|
|
71
76
|
<TextWrapper :text-style="buttonTextStyle" :text="viewCartText" />
|
|
@@ -83,8 +88,12 @@
|
|
|
83
88
|
:label="shopTr('itemAddedToCart')"
|
|
84
89
|
>
|
|
85
90
|
<template #cta>
|
|
91
|
+
<!-- `data-preview-interactive`: the success-bar's View Cart opens the cart
|
|
92
|
+
to look at it (charges nothing), so it stays clickable in the live
|
|
93
|
+
preview — same opt-in as the main View Cart button above. -->
|
|
86
94
|
<p
|
|
87
95
|
data-component="shop"
|
|
96
|
+
data-preview-interactive
|
|
88
97
|
class="tw-underline tw-cursor-pointer"
|
|
89
98
|
@click="onShowCartModal"
|
|
90
99
|
>
|
|
@@ -106,6 +115,7 @@
|
|
|
106
115
|
class="modal-cart"
|
|
107
116
|
:is-visible="data.isCartModalVisible"
|
|
108
117
|
:button-style="buttonStyle"
|
|
118
|
+
:is-checkout-blocked="isPreviewMode(props.context)"
|
|
109
119
|
@on-close="onCloseCart"
|
|
110
120
|
@on-show-payment="onShowPaymentModal"
|
|
111
121
|
/>
|
|
@@ -227,6 +237,15 @@ const onShowSuccess = () => {
|
|
|
227
237
|
};
|
|
228
238
|
|
|
229
239
|
const onShowPaymentModal = () => {
|
|
240
|
+
// Safety-critical hard-block: `PaymentModal` mounts `createCart()` on mount, which
|
|
241
|
+
// POSTs a real cart and creates a Stripe payment intent against the live key the
|
|
242
|
+
// preview carries. In any preview the shop is browsable but checkout must never
|
|
243
|
+
// reach payment, so we bail before the modal can mount. Mirrors
|
|
244
|
+
// Payment.mixin `canMakePayment` (`!isPreviewMode(context) && ...`).
|
|
245
|
+
if (isPreviewMode(props.context)) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
230
249
|
data.isCartModalVisible = false;
|
|
231
250
|
data.isPaymentModalVisible = true;
|
|
232
251
|
};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { vi } from 'vitest';
|
|
2
|
-
import { shallowMount } from '@vue/test-utils';
|
|
2
|
+
import { flushPromises, mount, shallowMount } from '@vue/test-utils';
|
|
3
|
+
import { Button } from '@designcrowd/fe-shared-lib';
|
|
3
4
|
import Shop from './Shop.vue';
|
|
4
5
|
import PaymentModal from './PaymentModal.vue';
|
|
6
|
+
import CartModal from './CartModal.vue';
|
|
5
7
|
import { createPinia, setActivePinia } from 'pinia';
|
|
6
8
|
import {
|
|
7
9
|
BRAND_PAGE_TYPES,
|
|
@@ -10,6 +12,9 @@ import {
|
|
|
10
12
|
type BrandPageContext,
|
|
11
13
|
} from '@brand-page/models';
|
|
12
14
|
import type { ShopViewModel } from '@brand-page/view-models';
|
|
15
|
+
import brandPageClient from '../../clients/brand-page.client';
|
|
16
|
+
import { useShopConfigStore } from '@shared/ecommerce-settings/store/shop-settings-store';
|
|
17
|
+
import { useCartStore } from '@shared/ecommerce-settings/store/cart-settings-store';
|
|
13
18
|
|
|
14
19
|
vi.mock('../../clients/brand-page.client', () => ({
|
|
15
20
|
default: {
|
|
@@ -22,7 +27,9 @@ vi.mock('@brand-page/utils/raygun.util', () => ({
|
|
|
22
27
|
default: { safeSendRaygunError: vi.fn() },
|
|
23
28
|
}));
|
|
24
29
|
|
|
25
|
-
const createContext = (
|
|
30
|
+
const createContext = (
|
|
31
|
+
overrides: Partial<BrandPageContext> = {},
|
|
32
|
+
): BrandPageContext => ({
|
|
26
33
|
brandPageSlug: 'test',
|
|
27
34
|
brandPageToken: '',
|
|
28
35
|
isMobile: false,
|
|
@@ -45,6 +52,7 @@ const createContext = (): BrandPageContext => ({
|
|
|
45
52
|
isCapturingPreview: false,
|
|
46
53
|
recaptchaSiteKey: undefined,
|
|
47
54
|
nav: { navItems: [] },
|
|
55
|
+
...overrides,
|
|
48
56
|
});
|
|
49
57
|
|
|
50
58
|
const PRODUCT = {
|
|
@@ -207,3 +215,175 @@ describe('PaymentModal', () => {
|
|
|
207
215
|
expect((flatCss as any).style).toBeUndefined();
|
|
208
216
|
});
|
|
209
217
|
});
|
|
218
|
+
|
|
219
|
+
// BP-5961 SAFETY-CRITICAL: in any preview the shop is browsable but checkout must be
|
|
220
|
+
// hard-blocked. `/api/cart/createCart` and `/api/stripe/createPaymentIntent` are
|
|
221
|
+
// UNGUARDED on the backend and the preview carries a real Stripe key, so this guard
|
|
222
|
+
// in `Shop.vue.onShowPaymentModal` is the SOLE thing preventing a real charge.
|
|
223
|
+
describe('Shop checkout hard-block (BP-5961)', () => {
|
|
224
|
+
beforeEach(() => {
|
|
225
|
+
setActivePinia(createPinia());
|
|
226
|
+
vi.clearAllMocks();
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// PaymentModal stays REAL (its onMounted → createBrandPageCart is what we assert on);
|
|
230
|
+
// CartModal stays REAL so we can emit `on-show-payment` from it; everything heavy is
|
|
231
|
+
// stubbed. Stubbing CartModal's `Modal` to `true` also drops its slot content, so we
|
|
232
|
+
// don't need to stub NumberStepper/Icon/etc.
|
|
233
|
+
const HARD_BLOCK_STUBS = {
|
|
234
|
+
HelloBar: true,
|
|
235
|
+
ButtonStyleWrapper: true,
|
|
236
|
+
TextWrapper: true,
|
|
237
|
+
TextDisplay: true,
|
|
238
|
+
ThumbnailDisplay: true,
|
|
239
|
+
FeaturedDisplay: true,
|
|
240
|
+
ProductDetailModal: true,
|
|
241
|
+
Modal: true,
|
|
242
|
+
Loader: true,
|
|
243
|
+
Button: true,
|
|
244
|
+
RequestPaymentStripe: true,
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const seedShopWithCartItem = () => {
|
|
248
|
+
const shopStore = useShopConfigStore();
|
|
249
|
+
|
|
250
|
+
shopStore.setPaymentConfig({ id: 'pc-1' } as any);
|
|
251
|
+
shopStore.addProduct({
|
|
252
|
+
id: '1',
|
|
253
|
+
name: 'Test',
|
|
254
|
+
hasVariants: false,
|
|
255
|
+
skus: [{ id: 'sku-1', skuKey: 'sku-1', price: 10, variant: [] }],
|
|
256
|
+
} as any);
|
|
257
|
+
|
|
258
|
+
useCartStore().cartItems.push({
|
|
259
|
+
productId: '1',
|
|
260
|
+
productSkuId: 'sku-1',
|
|
261
|
+
skuKey: 'sku-1',
|
|
262
|
+
unitPrice: 10,
|
|
263
|
+
quantity: 1,
|
|
264
|
+
});
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const mountShopForCheckout = (context: BrandPageContext) => {
|
|
268
|
+
seedShopWithCartItem();
|
|
269
|
+
|
|
270
|
+
return mount(Shop, {
|
|
271
|
+
props: {
|
|
272
|
+
context,
|
|
273
|
+
viewModel: createViewModel({ products: [PRODUCT] }),
|
|
274
|
+
contentIndex: 0,
|
|
275
|
+
},
|
|
276
|
+
global: { stubs: HARD_BLOCK_STUBS },
|
|
277
|
+
});
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const triggerCheckout = async (wrapper: ReturnType<typeof mount>) => {
|
|
281
|
+
// The cart's Proceed button emits this; going through the emit exercises the real
|
|
282
|
+
// `onShowPaymentModal` guard rather than reaching into component internals.
|
|
283
|
+
wrapper.findComponent(CartModal).vm.$emit('on-show-payment');
|
|
284
|
+
await flushPromises();
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
it.each([
|
|
288
|
+
['customer live preview', { isPreview: false, isLivePreview: true }],
|
|
289
|
+
['editor preview', { isPreview: true, isLivePreview: false }],
|
|
290
|
+
])(
|
|
291
|
+
'no-ops checkout — PaymentModal never mounts and createBrandPageCart is never called (%s)',
|
|
292
|
+
async (_label, flags) => {
|
|
293
|
+
const wrapper = mountShopForCheckout(createContext(flags));
|
|
294
|
+
|
|
295
|
+
await triggerCheckout(wrapper);
|
|
296
|
+
|
|
297
|
+
expect(wrapper.findComponent(PaymentModal).exists()).toBe(false);
|
|
298
|
+
expect(brandPageClient.createBrandPageCart).not.toHaveBeenCalled();
|
|
299
|
+
},
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
it('published site is UNCHANGED — PaymentModal mounts and createBrandPageCart is called', async () => {
|
|
303
|
+
const wrapper = mountShopForCheckout(
|
|
304
|
+
createContext({ isPreview: false, isLivePreview: false }),
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
await triggerCheckout(wrapper);
|
|
308
|
+
|
|
309
|
+
expect(wrapper.findComponent(PaymentModal).exists()).toBe(true);
|
|
310
|
+
expect(brandPageClient.createBrandPageCart).toHaveBeenCalledTimes(1);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('passes is-checkout-blocked to CartModal only in a preview', () => {
|
|
314
|
+
const previewWrapper = mountShopForCheckout(
|
|
315
|
+
createContext({ isPreview: false, isLivePreview: true }),
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
expect(
|
|
319
|
+
previewWrapper.findComponent(CartModal).props('isCheckoutBlocked'),
|
|
320
|
+
).toBe(true);
|
|
321
|
+
|
|
322
|
+
setActivePinia(createPinia());
|
|
323
|
+
|
|
324
|
+
const publishedWrapper = mountShopForCheckout(
|
|
325
|
+
createContext({ isPreview: false, isLivePreview: false }),
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
expect(
|
|
329
|
+
publishedWrapper.findComponent(CartModal).props('isCheckoutBlocked'),
|
|
330
|
+
).toBe(false);
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// BP-5961: the cart is browsable in a preview, so it can hold items — the Proceed
|
|
335
|
+
// (checkout) button is what must disappear, driven by the new `isCheckoutBlocked` prop.
|
|
336
|
+
describe('CartModal Proceed button (BP-5961)', () => {
|
|
337
|
+
beforeEach(() => {
|
|
338
|
+
setActivePinia(createPinia());
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
const mountCartModal = (isCheckoutBlocked: boolean) => {
|
|
342
|
+
const shopStore = useShopConfigStore();
|
|
343
|
+
|
|
344
|
+
shopStore.addProduct({
|
|
345
|
+
id: '1',
|
|
346
|
+
name: 'Test',
|
|
347
|
+
hasVariants: false,
|
|
348
|
+
imageUrl: '',
|
|
349
|
+
skus: [{ id: 'sku-1', skuKey: 'sku-1', price: 10, variant: [] }],
|
|
350
|
+
} as any);
|
|
351
|
+
|
|
352
|
+
useCartStore().cartItems.push({
|
|
353
|
+
productId: '1',
|
|
354
|
+
productSkuId: 'sku-1',
|
|
355
|
+
skuKey: 'sku-1',
|
|
356
|
+
unitPrice: 10,
|
|
357
|
+
quantity: 1,
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
return mount(CartModal, {
|
|
361
|
+
props: { isVisible: true, buttonStyle: {} as any, isCheckoutBlocked },
|
|
362
|
+
global: {
|
|
363
|
+
stubs: {
|
|
364
|
+
// Render Modal's slots so the footer (with the Proceed button) is present.
|
|
365
|
+
Modal: {
|
|
366
|
+
template:
|
|
367
|
+
'<div><slot name="header" /><slot /><slot name="footer" /></div>',
|
|
368
|
+
},
|
|
369
|
+
NumberStepper: true,
|
|
370
|
+
Textarea: true,
|
|
371
|
+
Icon: true,
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
});
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
const findProceedButton = (wrapper: ReturnType<typeof mount>) =>
|
|
378
|
+
wrapper
|
|
379
|
+
.findAllComponents(Button)
|
|
380
|
+
.find((b) => (b.props('attrs') as any)?.['data-test-tag'] === 'add-btn');
|
|
381
|
+
|
|
382
|
+
it('hides the Proceed button when checkout is blocked', () => {
|
|
383
|
+
expect(findProceedButton(mountCartModal(true))).toBeUndefined();
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it('shows the Proceed button when checkout is not blocked', () => {
|
|
387
|
+
expect(findProceedButton(mountCartModal(false))).toBeDefined();
|
|
388
|
+
});
|
|
389
|
+
});
|