@akinon/pz-theme 2.0.37-rc.0 → 2.0.37
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/CHANGELOG.md +7 -31
- package/package.json +4 -4
- package/src/__tests__/__snapshots__/facet-filters-block.test.tsx.snap +11 -0
- package/src/__tests__/custom-css.test.ts +181 -0
- package/src/__tests__/designer-chrome-clipping.test.tsx +497 -0
- package/src/__tests__/facet-filters-block.test.tsx +707 -0
- package/src/__tests__/item-template.test.tsx +184 -0
- package/src/__tests__/listing-controls.test.tsx +961 -0
- package/src/__tests__/part-style-scope.test.tsx +229 -0
- package/src/__tests__/part-styles.test.ts +311 -0
- package/src/__tests__/registry-parity.test.ts +42 -0
- package/src/__tests__/row-visibility.test.ts +672 -0
- package/src/__tests__/sort-select-page-cursor.test.tsx +336 -0
- package/src/__tests__/theme-block-html-id.test.tsx +167 -0
- package/src/blocks/__tests__/__snapshots__/product-gallery-block.test.tsx.snap +15 -0
- package/src/blocks/__tests__/accordion-block-state.test.tsx +201 -0
- package/src/blocks/__tests__/facet-filters-block.test.tsx +190 -0
- package/src/blocks/__tests__/listing-context-card-model.test.tsx +230 -0
- package/src/blocks/__tests__/listing-lookup-hardening.test.ts +80 -0
- package/src/blocks/__tests__/product-gallery-block.test.tsx +974 -0
- package/src/blocks/__tests__/product-grid-composition.test.tsx +570 -0
- package/src/blocks/__tests__/product-grid-lookups.test.tsx +230 -0
- package/src/blocks/__tests__/product-price-block.test.tsx +248 -0
- package/src/blocks/__tests__/product-recommendations-block.test.tsx +702 -0
- package/src/blocks/__tests__/share-block.test.tsx +392 -0
- package/src/blocks/__tests__/store-locator-block.test.tsx +691 -0
- package/src/blocks/__tests__/variant-selector-block.test.tsx +817 -0
- package/src/blocks/__tests__/variant-selector-golden.json +20 -0
- package/src/blocks/account-menu-block.tsx +264 -0
- package/src/blocks/add-to-cart-block.tsx +417 -0
- package/src/blocks/address-list-block.tsx +348 -0
- package/src/blocks/applied-filters-block.tsx +288 -0
- package/src/blocks/auth-form-block.tsx +295 -0
- package/src/blocks/basket-items-block.tsx +457 -0
- package/src/blocks/basket-summary-block.tsx +404 -0
- package/src/blocks/block-renderer-registry.tsx +94 -0
- package/src/blocks/change-password-form-block.tsx +240 -0
- package/src/blocks/checkout-steps-block.tsx +288 -0
- package/src/blocks/checkout-summary-block.tsx +449 -0
- package/src/blocks/countdown-block.tsx +182 -0
- package/src/blocks/coupon-list-block.tsx +292 -0
- package/src/blocks/facet-filters-block.tsx +1686 -0
- package/src/blocks/faq-accordion-block.tsx +358 -0
- package/src/blocks/favorite-button-block.tsx +252 -0
- package/src/blocks/favourites-grid-block.tsx +510 -0
- package/src/blocks/forgot-password-block.tsx +155 -0
- package/src/blocks/item-template-utils.ts +131 -0
- package/src/blocks/item-template.tsx +164 -0
- package/src/blocks/listing-breadcrumb-block.tsx +175 -0
- package/src/blocks/listing-context.ts +850 -0
- package/src/blocks/listing-pagination-block.tsx +923 -0
- package/src/blocks/listing-result-count-block.tsx +126 -0
- package/src/blocks/listing-title-block.tsx +115 -0
- package/src/blocks/oauth-buttons-block.tsx +205 -0
- package/src/blocks/order-detail-block.tsx +474 -0
- package/src/blocks/order-list-block.tsx +274 -0
- package/src/blocks/order-success-block.tsx +641 -0
- package/src/blocks/order-tracking-block.tsx +533 -0
- package/src/blocks/product-attributes-block.tsx +307 -0
- package/src/blocks/product-block-utils.tsx +639 -0
- package/src/blocks/product-breadcrumb-block.tsx +215 -0
- package/src/blocks/product-description-block.tsx +169 -0
- package/src/blocks/product-gallery-block.tsx +1523 -0
- package/src/blocks/product-grid-block.tsx +966 -0
- package/src/blocks/product-price-block.tsx +373 -0
- package/src/blocks/product-recommendations-block.tsx +1433 -0
- package/src/blocks/product-title-block.tsx +129 -0
- package/src/blocks/profile-form-block.tsx +297 -0
- package/src/blocks/recently-viewed-store.ts +201 -0
- package/src/blocks/register-form-block.tsx +362 -0
- package/src/blocks/share-block.tsx +667 -0
- package/src/blocks/size-guide-block.tsx +313 -0
- package/src/blocks/sort-select-block.tsx +636 -0
- package/src/blocks/stock-status-block.tsx +168 -0
- package/src/blocks/store-locator-block.tsx +1184 -0
- package/src/blocks/variant-selector-block.tsx +1110 -0
- package/src/sections/__tests__/tablet-raw-bucket-readers.test.tsx +186 -0
- package/src/utils/__tests__/part-ownership-guard.test.tsx +29 -4
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render } from '@testing-library/react';
|
|
3
|
+
import ProductGridBlock from '../product-grid-block';
|
|
4
|
+
import { ThemePageContextProvider } from '../../theme-page-context';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The lookup table the grid hands to the row normaliser.
|
|
8
|
+
*
|
|
9
|
+
* `item.badge.*` and `item.colors[0].color` are offered in the binding picker,
|
|
10
|
+
* but commerce sends a badge as a bare key ("new_season") and a colour as a
|
|
11
|
+
* bare value ("kirmizi") — neither carries a label, an image or a hex. The
|
|
12
|
+
* normaliser has always been able to resolve them through an author's table;
|
|
13
|
+
* nothing authored one, so every card that bound those four fields rendered
|
|
14
|
+
* nothing while the panel showed them as live targets.
|
|
15
|
+
*
|
|
16
|
+
* These tests bind them from a designer-authored row layout, which is the only
|
|
17
|
+
* place they can be read, and assert the value reaches the DOM.
|
|
18
|
+
*/
|
|
19
|
+
const payload = {
|
|
20
|
+
listing: {
|
|
21
|
+
title: 'Ayakkabı',
|
|
22
|
+
path: '/kadin-ayakkabi/',
|
|
23
|
+
products: [
|
|
24
|
+
{
|
|
25
|
+
pk: 41,
|
|
26
|
+
name: 'Deri Sandalet',
|
|
27
|
+
absolute_url: '/deri-sandalet-41/',
|
|
28
|
+
image: 'https://cdn.example.com/41.jpg',
|
|
29
|
+
price: '100.00',
|
|
30
|
+
retail_price: '200.00',
|
|
31
|
+
currency_type: 'TRY',
|
|
32
|
+
attributes_kwargs: { badge: { value: 'new_season' } },
|
|
33
|
+
extra_data: {
|
|
34
|
+
variants: [
|
|
35
|
+
{
|
|
36
|
+
attribute_key: 'integration_color',
|
|
37
|
+
options: [
|
|
38
|
+
{
|
|
39
|
+
label: 'Kırmızı',
|
|
40
|
+
value: 'kirmizi',
|
|
41
|
+
is_selectable: true,
|
|
42
|
+
is_selected: true,
|
|
43
|
+
product: { image: 'https://cdn.example.com/41-red.jpg' }
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** A row layout whose children bind the four lookup-fed fields. */
|
|
55
|
+
const rowTemplate = {
|
|
56
|
+
id: 'row',
|
|
57
|
+
type: 'group',
|
|
58
|
+
order: 0,
|
|
59
|
+
hidden: false,
|
|
60
|
+
styles: {},
|
|
61
|
+
properties: { role: 'item-template' },
|
|
62
|
+
blocks: [
|
|
63
|
+
{
|
|
64
|
+
id: 'badge-text',
|
|
65
|
+
type: 'text',
|
|
66
|
+
order: 0,
|
|
67
|
+
hidden: false,
|
|
68
|
+
styles: {},
|
|
69
|
+
properties: { dataBinding: 'item.badge.label' }
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: 'badge-image',
|
|
73
|
+
type: 'image',
|
|
74
|
+
order: 1,
|
|
75
|
+
hidden: false,
|
|
76
|
+
styles: {},
|
|
77
|
+
properties: { dataBinding: 'item.badge.image' }
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: 'badge-colour',
|
|
81
|
+
type: 'text',
|
|
82
|
+
order: 2,
|
|
83
|
+
hidden: false,
|
|
84
|
+
styles: {},
|
|
85
|
+
properties: { dataBinding: 'item.badge.color' }
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: 'swatch-colour',
|
|
89
|
+
type: 'text',
|
|
90
|
+
order: 3,
|
|
91
|
+
hidden: false,
|
|
92
|
+
styles: {},
|
|
93
|
+
properties: { dataBinding: 'item.colors[0].color' }
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const renderGrid = (
|
|
99
|
+
properties: Record<string, unknown> = {},
|
|
100
|
+
blocks: Record<string, unknown>[] = []
|
|
101
|
+
) =>
|
|
102
|
+
render(
|
|
103
|
+
<ThemePageContextProvider value={payload}>
|
|
104
|
+
<ProductGridBlock
|
|
105
|
+
{...({
|
|
106
|
+
block: {
|
|
107
|
+
id: 'grid',
|
|
108
|
+
type: 'product-grid',
|
|
109
|
+
styles: {},
|
|
110
|
+
properties,
|
|
111
|
+
blocks,
|
|
112
|
+
order: 0,
|
|
113
|
+
hidden: false
|
|
114
|
+
},
|
|
115
|
+
placeholderId: 'p',
|
|
116
|
+
sectionId: 's'
|
|
117
|
+
} as never)}
|
|
118
|
+
/>
|
|
119
|
+
</ThemePageContextProvider>
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const authored = {
|
|
123
|
+
badgeAttribute: 'badge',
|
|
124
|
+
badgeMap: 'New_Season | Yeni Sezon | https://cdn.example.com/new.svg | #0f766e',
|
|
125
|
+
swatchMap: 'KIRMIZI | #c81e1e'
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
describe('the grid passes the authored lookup table to the row', () => {
|
|
129
|
+
it('resolves the badge key into text a bound row can draw', () => {
|
|
130
|
+
const { container } = renderGrid(authored, [rowTemplate]);
|
|
131
|
+
|
|
132
|
+
expect(container.innerHTML).toContain('Yeni Sezon');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('resolves the badge image and colour', () => {
|
|
136
|
+
const { container } = renderGrid(authored, [rowTemplate]);
|
|
137
|
+
|
|
138
|
+
expect(container.querySelector('img[src="https://cdn.example.com/new.svg"]')).not.toBeNull();
|
|
139
|
+
expect(container.innerHTML).toContain('#0f766e');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('resolves the colour value into the swatch hex', () => {
|
|
143
|
+
const { container } = renderGrid(authored, [rowTemplate]);
|
|
144
|
+
|
|
145
|
+
expect(container.innerHTML).toContain('#c81e1e');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('matches the key in whatever case the integration wrote it', () => {
|
|
149
|
+
// The payload says `new_season` / `kirmizi`; the table above says
|
|
150
|
+
// `New_Season` / `KIRMIZI`.
|
|
151
|
+
const { container } = renderGrid(
|
|
152
|
+
{ ...authored, badgeMap: 'NEW_SEASON | Yeni Sezon' },
|
|
153
|
+
[rowTemplate]
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
expect(container.innerHTML).toContain('Yeni Sezon');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('publishes nothing for a key the table does not list', () => {
|
|
160
|
+
const { container } = renderGrid(
|
|
161
|
+
{ ...authored, badgeMap: 'clearance | İndirim' },
|
|
162
|
+
[rowTemplate]
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
// Not a fallback to the raw key: "new_season" on a card is the bug the
|
|
166
|
+
// table exists to fix.
|
|
167
|
+
expect(container.innerHTML).not.toContain('new_season');
|
|
168
|
+
expect(container.innerHTML).not.toContain('İndirim');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('reads the attribute the author named', () => {
|
|
172
|
+
const { container } = renderGrid(
|
|
173
|
+
{ ...authored, badgeAttribute: 'not_the_badge_key' },
|
|
174
|
+
[rowTemplate]
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
expect(container.innerHTML).not.toContain('Yeni Sezon');
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe('an authored table is untrusted input', () => {
|
|
182
|
+
it('drops a script-scheme badge image and keeps the rest of the entry', () => {
|
|
183
|
+
const { container } = renderGrid(
|
|
184
|
+
{
|
|
185
|
+
...authored,
|
|
186
|
+
// Browsers strip control characters while parsing a url, so the tab
|
|
187
|
+
// still resolves to a live handler.
|
|
188
|
+
badgeMap: 'new_season | Yeni Sezon | java\tscript:alert(1) | #0f766e'
|
|
189
|
+
},
|
|
190
|
+
[rowTemplate]
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
expect(container.innerHTML).not.toContain('script:alert(1)');
|
|
194
|
+
expect(container.innerHTML).toContain('Yeni Sezon');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('drops a badge colour that could close the rule it lands in', () => {
|
|
198
|
+
const { container } = renderGrid(
|
|
199
|
+
{
|
|
200
|
+
...authored,
|
|
201
|
+
badgeMap: 'new_season | Yeni Sezon | | red;}body{display:none'
|
|
202
|
+
},
|
|
203
|
+
[rowTemplate]
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
expect(container.innerHTML).not.toContain('display:none');
|
|
207
|
+
expect(container.innerHTML).toContain('Yeni Sezon');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('drops a swatch colour that is not a colour', () => {
|
|
211
|
+
const { container } = renderGrid(
|
|
212
|
+
{ ...authored, swatchMap: 'kirmizi | url(javascript:alert(1))' },
|
|
213
|
+
[rowTemplate]
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
expect(container.innerHTML).not.toContain('javascript:alert(1)');
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
describe('the built-in card is untouched by the table', () => {
|
|
221
|
+
it('renders identical markup with and without one authored', () => {
|
|
222
|
+
const withTable = renderGrid(authored).container.innerHTML;
|
|
223
|
+
const withoutTable = renderGrid({}).container.innerHTML;
|
|
224
|
+
|
|
225
|
+
// No row layout means the built-in card, and the built-in card draws none
|
|
226
|
+
// of the lookup-fed fields — a published grid cannot move because someone
|
|
227
|
+
// filled the table in.
|
|
228
|
+
expect(withTable).toBe(withoutTable);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render } from '@testing-library/react';
|
|
3
|
+
import ProductPriceBlock from '../product-price-block';
|
|
4
|
+
import { ThemePageContextProvider } from '../../theme-page-context';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The three additions (price history caption, price range, percent+amount
|
|
8
|
+
* badge) are opt-in, so the first test here is the one that matters most: a
|
|
9
|
+
* block with none of the new properties set must still render exactly what it
|
|
10
|
+
* rendered before they existed.
|
|
11
|
+
*/
|
|
12
|
+
const renderBlock = (
|
|
13
|
+
properties: Record<string, unknown>,
|
|
14
|
+
product: Record<string, unknown> | null,
|
|
15
|
+
extra: Record<string, unknown> = {}
|
|
16
|
+
) =>
|
|
17
|
+
render(
|
|
18
|
+
<ThemePageContextProvider value={product ? { product } : null}>
|
|
19
|
+
<ProductPriceBlock
|
|
20
|
+
{...({
|
|
21
|
+
block: {
|
|
22
|
+
id: 'price',
|
|
23
|
+
type: 'product-price',
|
|
24
|
+
styles: {},
|
|
25
|
+
properties,
|
|
26
|
+
order: 0,
|
|
27
|
+
hidden: false
|
|
28
|
+
},
|
|
29
|
+
placeholderId: 'p',
|
|
30
|
+
sectionId: 's',
|
|
31
|
+
isDesigner: false,
|
|
32
|
+
currentBreakpoint: 'desktop',
|
|
33
|
+
...extra
|
|
34
|
+
} as any)}
|
|
35
|
+
/>
|
|
36
|
+
</ThemePageContextProvider>
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const BASE_PRODUCT = {
|
|
40
|
+
pk: 1,
|
|
41
|
+
price: '1299.00',
|
|
42
|
+
retail_price: '1749.00',
|
|
43
|
+
currency: 'USD'
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
describe('ProductPriceBlock — untouched properties', () => {
|
|
47
|
+
it('renders the price and the struck-through retail price only', () => {
|
|
48
|
+
const { container } = renderBlock({}, BASE_PRODUCT);
|
|
49
|
+
|
|
50
|
+
const price = container.querySelector('[data-product-current-price]');
|
|
51
|
+
expect(price?.textContent).toBe('$1,299.00');
|
|
52
|
+
// Text child only: the range markup must not appear for a block that
|
|
53
|
+
// never asked for it.
|
|
54
|
+
expect(price?.querySelectorAll('[data-part]').length).toBe(0);
|
|
55
|
+
expect(
|
|
56
|
+
container.querySelector('[data-product-retail-price]')?.textContent
|
|
57
|
+
).toBe('$1,749.00');
|
|
58
|
+
expect(container.querySelector('[data-product-previous-price]')).toBeNull();
|
|
59
|
+
expect(container.querySelector('[data-product-discount-badge]')).toBeNull();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('keeps the single-string badge for the percentage mode', () => {
|
|
63
|
+
const { container } = renderBlock(
|
|
64
|
+
{ showDiscountBadge: true },
|
|
65
|
+
BASE_PRODUCT
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const badge = container.querySelector('[data-product-discount-badge]');
|
|
69
|
+
expect(badge?.textContent).toBe('-26%');
|
|
70
|
+
expect(badge?.querySelectorAll('[data-part]').length).toBe(0);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('keeps the single-string badge for the amount mode', () => {
|
|
74
|
+
const { container } = renderBlock(
|
|
75
|
+
{ showDiscountBadge: true, discountMode: 'amount' },
|
|
76
|
+
BASE_PRODUCT
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
expect(
|
|
80
|
+
container.querySelector('[data-product-discount-badge]')?.textContent
|
|
81
|
+
).toBe('-$450.00');
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('ProductPriceBlock — price history caption', () => {
|
|
86
|
+
const withHistory = {
|
|
87
|
+
...BASE_PRODUCT,
|
|
88
|
+
price_extra_field: { previous_price: '1499.00' }
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
it('prints nothing until the caption is switched on', () => {
|
|
92
|
+
const { container } = renderBlock({}, withHistory);
|
|
93
|
+
|
|
94
|
+
expect(container.querySelector('[data-product-previous-price]')).toBeNull();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('reads price_extra_field.previous_price and formats it with the locale', () => {
|
|
98
|
+
const { container } = renderBlock({ showPreviousPrice: true }, withHistory);
|
|
99
|
+
|
|
100
|
+
const line = container.querySelector('[data-product-previous-price]');
|
|
101
|
+
expect(line?.textContent).toBe('Lowest price in the last 30 days: $1,499.00');
|
|
102
|
+
expect(
|
|
103
|
+
line?.querySelector('[data-part="previous-price"]')?.textContent
|
|
104
|
+
).toBe('$1,499.00');
|
|
105
|
+
expect(
|
|
106
|
+
line?.querySelector('[data-part="history-caption"]')?.textContent
|
|
107
|
+
).toBe('Lowest price in the last 30 days: ');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('reads the flattened previousPrice a listing row carries', () => {
|
|
111
|
+
const { container } = renderBlock(
|
|
112
|
+
{ showPreviousPrice: true },
|
|
113
|
+
{ ...BASE_PRODUCT, previousPrice: '1499.00' }
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
expect(
|
|
117
|
+
container.querySelector('[data-part="previous-price"]')?.textContent
|
|
118
|
+
).toBe('$1,499.00');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('prints no caption when commerce sent no price history', () => {
|
|
122
|
+
const { container } = renderBlock({ showPreviousPrice: true }, BASE_PRODUCT);
|
|
123
|
+
|
|
124
|
+
expect(container.querySelector('[data-product-previous-price]')).toBeNull();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('shows the missing-history hint in the designer only', () => {
|
|
128
|
+
const { container } = renderBlock(
|
|
129
|
+
{ showPreviousPrice: true },
|
|
130
|
+
BASE_PRODUCT,
|
|
131
|
+
{ isDesigner: true }
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
expect(
|
|
135
|
+
container.querySelector('[data-theme-block-placeholder]')?.textContent
|
|
136
|
+
).toBe('Lowest price (no price history)');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('honours an authored caption and still prints the amount without {value}', () => {
|
|
140
|
+
const { container } = renderBlock(
|
|
141
|
+
{ showPreviousPrice: true, previousPriceTemplate: 'Son 30 gün' },
|
|
142
|
+
withHistory
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
expect(
|
|
146
|
+
container.querySelector('[data-product-previous-price]')?.textContent
|
|
147
|
+
).toBe('Son 30 gün $1,499.00');
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe('ProductPriceBlock — discount as both', () => {
|
|
152
|
+
it('gives the percentage and the amount their own parts', () => {
|
|
153
|
+
const { container } = renderBlock(
|
|
154
|
+
{ showDiscountBadge: true, discountMode: 'both' },
|
|
155
|
+
BASE_PRODUCT
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
const badge = container.querySelector('[data-product-discount-badge]');
|
|
159
|
+
expect(badge?.textContent).toBe('-26% ($450.00)');
|
|
160
|
+
expect(
|
|
161
|
+
badge?.querySelector('[data-part="discount-percent"]')?.textContent
|
|
162
|
+
).toBe('26%');
|
|
163
|
+
expect(
|
|
164
|
+
badge?.querySelector('[data-part="discount-amount"]')?.textContent
|
|
165
|
+
).toBe('$450.00');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('prints nothing when there is no discount to state', () => {
|
|
169
|
+
const { container } = renderBlock(
|
|
170
|
+
{ showDiscountBadge: true, discountMode: 'both' },
|
|
171
|
+
{ ...BASE_PRODUCT, retail_price: '1299.00' }
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
expect(container.querySelector('[data-product-discount-badge]')).toBeNull();
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe('ProductPriceBlock — price range', () => {
|
|
179
|
+
const grouped = {
|
|
180
|
+
...BASE_PRODUCT,
|
|
181
|
+
variants: [
|
|
182
|
+
{
|
|
183
|
+
attribute_key: 'size',
|
|
184
|
+
options: [
|
|
185
|
+
{ value: 's', product: { price: '1299.00' } },
|
|
186
|
+
{ value: 'm', product: { price: '1899.00' } },
|
|
187
|
+
// Not sellable: its price is not a price a shopper can pay.
|
|
188
|
+
{ value: 'l', is_selectable: false, product: { price: '99.00' } }
|
|
189
|
+
]
|
|
190
|
+
}
|
|
191
|
+
]
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
it('prints one price until the range is switched on', () => {
|
|
195
|
+
const { container } = renderBlock({}, grouped);
|
|
196
|
+
|
|
197
|
+
expect(
|
|
198
|
+
container.querySelector('[data-product-current-price]')?.textContent
|
|
199
|
+
).toBe('$1,299.00');
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('prints the lowest and highest sellable variant price', () => {
|
|
203
|
+
const { container } = renderBlock({ showPriceRange: true }, grouped);
|
|
204
|
+
|
|
205
|
+
const price = container.querySelector('[data-product-current-price]');
|
|
206
|
+
expect(price?.querySelector('[data-part="price-min"]')?.textContent).toBe(
|
|
207
|
+
'$1,299.00'
|
|
208
|
+
);
|
|
209
|
+
expect(price?.querySelector('[data-part="price-max"]')?.textContent).toBe(
|
|
210
|
+
'$1,899.00'
|
|
211
|
+
);
|
|
212
|
+
expect(
|
|
213
|
+
price?.querySelector('[data-part="range-separator"]')?.textContent
|
|
214
|
+
).toBe(' – ');
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('honours an authored separator', () => {
|
|
218
|
+
const { container } = renderBlock(
|
|
219
|
+
{ showPriceRange: true, priceRangeSeparator: ' to ' },
|
|
220
|
+
grouped
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
expect(
|
|
224
|
+
container.querySelector('[data-part="range-separator"]')?.textContent
|
|
225
|
+
).toBe(' to ');
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('falls back to the single price when the variants do not differ', () => {
|
|
229
|
+
const { container } = renderBlock(
|
|
230
|
+
{ showPriceRange: true },
|
|
231
|
+
{
|
|
232
|
+
...BASE_PRODUCT,
|
|
233
|
+
variants: [
|
|
234
|
+
{
|
|
235
|
+
options: [
|
|
236
|
+
{ value: 's', product: { price: '1299.00' } },
|
|
237
|
+
{ value: 'm', product: { price: '1299.00' } }
|
|
238
|
+
]
|
|
239
|
+
}
|
|
240
|
+
]
|
|
241
|
+
}
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
const price = container.querySelector('[data-product-current-price]');
|
|
245
|
+
expect(price?.textContent).toBe('$1,299.00');
|
|
246
|
+
expect(price?.querySelectorAll('[data-part]').length).toBe(0);
|
|
247
|
+
});
|
|
248
|
+
});
|