fontdue-js 3.6.0 → 3.8.0
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 +14 -0
- package/README.md +5 -4
- package/dist/__generated__/AddToCartBannerQuery.graphql.d.ts +1 -1
- package/dist/__generated__/AddToCartBannerQuery.graphql.js +16 -10
- package/dist/__generated__/AddToCartBanner_item.graphql.d.ts +4 -1
- package/dist/__generated__/AddToCartBanner_item.graphql.js +7 -2
- package/dist/__generated__/AddToCartBanner_order.graphql.d.ts +4 -1
- package/dist/__generated__/AddToCartBanner_order.graphql.js +6 -1
- package/dist/__generated__/BuyButtonIDQuery.graphql.d.ts +3 -3
- package/dist/__generated__/BuyButtonIDQuery.graphql.js +31 -16
- package/dist/__generated__/BuyButtonPackIDQuery.graphql.d.ts +21 -0
- package/dist/__generated__/BuyButtonPackIDQuery.graphql.js +129 -0
- package/dist/__generated__/BuyButtonPackSlugQuery.graphql.d.ts +25 -0
- package/dist/__generated__/BuyButtonPackSlugQuery.graphql.js +167 -0
- package/dist/__generated__/BuyButtonSlugQuery.graphql.d.ts +3 -3
- package/dist/__generated__/BuyButtonSlugQuery.graphql.js +45 -17
- package/dist/__generated__/BuyButton_product.graphql.d.ts +27 -0
- package/dist/__generated__/BuyButton_product.graphql.js +72 -0
- package/dist/__generated__/CartItemProduct_product.graphql.d.ts +1 -1
- package/dist/__generated__/CartItemProduct_product.graphql.js +7 -2
- package/dist/__generated__/CartOrderCompleteOrderMutation.graphql.d.ts +1 -1
- package/dist/__generated__/CartOrderCompleteOrderMutation.graphql.js +26 -20
- package/dist/__generated__/CartOrderRemoveDiscountMutation.graphql.d.ts +1 -1
- package/dist/__generated__/CartOrderRemoveDiscountMutation.graphql.js +26 -20
- package/dist/__generated__/CartOrderUpdateMutation.graphql.d.ts +1 -1
- package/dist/__generated__/CartOrderUpdateMutation.graphql.js +26 -20
- package/dist/__generated__/CartQuery.graphql.d.ts +1 -1
- package/dist/__generated__/CartQuery.graphql.js +26 -20
- package/dist/__generated__/CartStateUpdateMutation.graphql.d.ts +1 -1
- package/dist/__generated__/CartStateUpdateMutation.graphql.js +26 -20
- package/dist/__generated__/CheckoutUpdateCustomerMutation.graphql.d.ts +1 -1
- package/dist/__generated__/CheckoutUpdateCustomerMutation.graphql.js +26 -20
- package/dist/__generated__/CheckoutUpdateOrderMutation.graphql.d.ts +1 -1
- package/dist/__generated__/CheckoutUpdateOrderMutation.graphql.js +26 -20
- package/dist/__generated__/CollectionAa_Query.graphql.d.ts +1 -1
- package/dist/__generated__/CollectionAa_Query.graphql.js +20 -14
- package/dist/__generated__/CollectionAa_product.graphql.d.ts +7 -1
- package/dist/__generated__/CollectionAa_product.graphql.js +22 -16
- package/dist/__generated__/FontFamiliesQuery.graphql.d.ts +4 -1
- package/dist/__generated__/FontFamiliesQuery.graphql.js +46 -39
- package/dist/__generated__/NodePasswordFormElementIDQuery.graphql.d.ts +3 -3
- package/dist/__generated__/NodePasswordFormElementIDQuery.graphql.js +9 -9
- package/dist/__generated__/NodePasswordFormElementPackSlugQuery.graphql.d.ts +25 -0
- package/dist/__generated__/NodePasswordFormElementPackSlugQuery.graphql.js +108 -0
- package/dist/__generated__/PrecartAddToCartMutation.graphql.d.ts +1 -1
- package/dist/__generated__/PrecartAddToCartMutation.graphql.js +32 -26
- package/dist/__generated__/PriceBar_node.graphql.d.ts +4 -1
- package/dist/__generated__/PriceBar_node.graphql.js +23 -16
- package/dist/__generated__/StoreModalCartQuery.graphql.d.ts +1 -1
- package/dist/__generated__/StoreModalCartQuery.graphql.js +37 -31
- package/dist/__generated__/StoreModalFamily_collection.graphql.d.ts +3 -2
- package/dist/__generated__/StoreModalFamily_collection.graphql.js +3 -3
- package/dist/__generated__/StoreModalIndexPackItem_fontPack.graphql.d.ts +23 -0
- package/dist/__generated__/StoreModalIndexPackItem_fontPack.graphql.js +58 -0
- package/dist/__generated__/StoreModalIndexPaginationQuery.graphql.d.ts +23 -0
- package/dist/__generated__/StoreModalIndexPaginationQuery.graphql.js +303 -0
- package/dist/__generated__/StoreModalIndexQuery.graphql.d.ts +6 -3
- package/dist/__generated__/StoreModalIndexQuery.graphql.js +218 -117
- package/dist/__generated__/StoreModalIndex_viewer.graphql.d.ts +24 -0
- package/dist/__generated__/StoreModalIndex_viewer.graphql.js +134 -0
- package/dist/__generated__/StoreModalPackButton_fontPack.graphql.d.ts +33 -0
- package/dist/__generated__/StoreModalPackButton_fontPack.graphql.js +119 -0
- package/dist/__generated__/StoreModalPack_fontPack.graphql.d.ts +45 -0
- package/dist/__generated__/StoreModalPack_fontPack.graphql.js +150 -0
- package/dist/__generated__/StoreModalProductContent_collection.graphql.d.ts +6 -2
- package/dist/__generated__/StoreModalProductContent_collection.graphql.js +43 -19
- package/dist/__generated__/{StoreModalProductLicenseSelection_collection.graphql.d.ts → StoreModalProductLicenseSelection_licenseable.graphql.d.ts} +7 -6
- package/dist/__generated__/StoreModalProductLicenseSelection_licenseable.graphql.js +43 -0
- package/dist/__generated__/StoreModalProductQuery.graphql.d.ts +1 -1
- package/dist/__generated__/StoreModalProductQuery.graphql.js +199 -142
- package/dist/__generated__/StoreModalProductRefetchQuery.graphql.d.ts +1 -1
- package/dist/__generated__/StoreModalProductRefetchQuery.graphql.js +190 -133
- package/dist/__generated__/StoreModalProductSlugQuery.graphql.d.ts +5 -1
- package/dist/__generated__/StoreModalProductSlugQuery.graphql.js +254 -176
- package/dist/__generated__/StoreModalProductSummaryRefetchQuery.graphql.d.ts +1 -1
- package/dist/__generated__/StoreModalProductSummaryRefetchQuery.graphql.js +9 -4
- package/dist/__generated__/StoreModalProductSummary_viewer.graphql.d.ts +7 -1
- package/dist/__generated__/StoreModalProductSummary_viewer.graphql.js +7 -2
- package/dist/__generated__/TestFontsFormUpdateCustomerMutation.graphql.d.ts +1 -1
- package/dist/__generated__/TestFontsFormUpdateCustomerMutation.graphql.js +26 -20
- package/dist/__generated__/TypeTestersIDQuery.graphql.d.ts +1 -1
- package/dist/__generated__/TypeTestersIDQuery.graphql.js +17 -10
- package/dist/__generated__/TypeTestersRefetchQuery.graphql.d.ts +1 -1
- package/dist/__generated__/TypeTestersRefetchQuery.graphql.js +17 -10
- package/dist/__generated__/TypeTestersSlugQuery.graphql.d.ts +1 -1
- package/dist/__generated__/TypeTestersSlugQuery.graphql.js +17 -10
- package/dist/__generated__/productState_Query.graphql.d.ts +99 -1
- package/dist/__generated__/productState_Query.graphql.js +43 -4
- package/dist/__generated__/productState_fontPack.graphql.d.ts +111 -0
- package/dist/__generated__/productState_fontPack.graphql.js +117 -0
- package/dist/__generated__/storefrontEventsTrackStorefrontEventMutation.graphql.d.ts +41 -0
- package/dist/__generated__/storefrontEventsTrackStorefrontEventMutation.graphql.js +65 -0
- package/dist/__tests__/bundleSelectionOrder.test.js +94 -0
- package/dist/__tests__/collectionBundleSelection.test.js +443 -1446
- package/dist/__tests__/fontPackSelection.test.js +435 -0
- package/dist/__tests__/infiniteScroll.test.js +180 -0
- package/dist/__tests__/licenseExclusions.test.js +4 -1
- package/dist/__tests__/storeModalPackButton.test.js +102 -0
- package/dist/__tests__/storeModalPackUnlisted.test.js +112 -0
- package/dist/__tests__/storeModalRoutes.test.js +9 -0
- package/dist/__tests__/storefrontEvents.test.js +167 -0
- package/dist/__tests__/typeTesterAutofitFeatures.test.js +138 -0
- package/dist/components/AddToCartBanner/index.js +1 -1
- package/dist/components/BuyButton/index.d.ts +29 -9
- package/dist/components/BuyButton/index.js +123 -26
- package/dist/components/BuyButton/index.server.d.ts +4 -2
- package/dist/components/BuyButton/index.server.js +24 -3
- package/dist/components/Cart/CartItem/CartItemProduct.js +3 -3
- package/dist/components/Cart/Checkout.js +11 -1
- package/dist/components/Cart/storefrontEvents.d.ts +34 -0
- package/dist/components/Cart/storefrontEvents.js +138 -0
- package/dist/components/CollectionAa/index.js +2 -2
- package/dist/components/FontFamilies/index.js +4 -3
- package/dist/components/NodePasswordForm/NodePasswordFormElement.d.ts +1 -1
- package/dist/components/NodePasswordForm/NodePasswordFormElement.js +45 -21
- package/dist/components/NodePasswordForm/index.d.ts +7 -4
- package/dist/components/NodePasswordForm/index.js +6 -6
- package/dist/components/Precart/index.js +6 -0
- package/dist/components/PriceBar/index.js +5 -3
- package/dist/components/Root/productState.d.ts +2 -8
- package/dist/components/Root/productState.js +100 -61
- package/dist/components/SKUPrice/index.js +7 -8
- package/dist/components/StoreModal/StoreModalFamily.d.ts +7 -0
- package/dist/components/StoreModal/StoreModalFamily.js +11 -7
- package/dist/components/StoreModal/StoreModalIndex.js +58 -7
- package/dist/components/StoreModal/StoreModalIndexEntry.d.ts +13 -0
- package/dist/components/StoreModal/StoreModalIndexEntry.js +43 -0
- package/dist/components/StoreModal/StoreModalIndexItem.js +7 -30
- package/dist/components/StoreModal/StoreModalIndexPackItem.d.ts +7 -0
- package/dist/components/StoreModal/StoreModalIndexPackItem.js +21 -0
- package/dist/components/StoreModal/StoreModalPack.d.ts +9 -0
- package/dist/components/StoreModal/StoreModalPack.js +79 -0
- package/dist/components/StoreModal/StoreModalPackButton.d.ts +8 -0
- package/dist/components/StoreModal/StoreModalPackButton.js +53 -0
- package/dist/components/StoreModal/StoreModalProduct.js +49 -19
- package/dist/components/StoreModalProductLicenseSelection/StoreModalProductLicense.d.ts +2 -1
- package/dist/components/StoreModalProductLicenseSelection/StoreModalProductLicense.js +8 -3
- package/dist/components/StoreModalProductLicenseSelection/index.d.ts +3 -3
- package/dist/components/StoreModalProductLicenseSelection/index.js +7 -6
- package/dist/components/StoreModalProductSummary/index.js +1 -1
- package/dist/components/TypeTester/useTypeTesterStyler.js +1 -0
- package/dist/components/elements/StoreModalContainer/StoreModalContainerContext.d.ts +1 -0
- package/dist/components/elements/StoreModalContainer/index.js +9 -4
- package/dist/components/elements/StoreModalFamily/index.d.ts +1 -1
- package/dist/components/elements/StoreModalFamily/index.js +1 -1
- package/dist/components/elements/StoreModalIndexLoadMore/index.d.ts +8 -0
- package/dist/components/elements/StoreModalIndexLoadMore/index.js +21 -0
- package/dist/components/elements/StoreModalUnifiedCheckout.js +12 -0
- package/dist/components/useInfiniteScroll.d.ts +28 -0
- package/dist/components/useInfiniteScroll.js +50 -0
- package/dist/fontdue.css +39 -0
- package/dist/hooks/useAutofit.d.ts +2 -1
- package/dist/hooks/useAutofit.js +10 -4
- package/dist/reducer.d.ts +12 -5
- package/dist/reducer.js +18 -87
- package/dist/selection.d.ts +44 -0
- package/dist/selection.js +539 -0
- package/dist/storeModalRoutes.js +5 -4
- package/dist/utils.d.ts +1 -12
- package/dist/utils.js +1 -36
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/dist/__generated__/BuyButton_collection.graphql.d.ts +0 -25
- package/dist/__generated__/BuyButton_collection.graphql.js +0 -49
- package/dist/__generated__/StoreModalProductLicenseSelection_collection.graphql.js +0 -40
- package/fontdue.css +0 -2733
- package/fontdue.css.map +0 -1
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
|
|
3
|
+
// The pack's row at the top of its product page: the name is shown exactly as
|
|
4
|
+
// the admin entered it, the à-la-carte total is struck through with an <s>,
|
|
5
|
+
// and a pack without a SKU is not clickable.
|
|
6
|
+
|
|
7
|
+
import React, { act } from 'react';
|
|
8
|
+
import { createRoot } from 'react-dom/client';
|
|
9
|
+
import { renderToStaticMarkup } from 'react-dom/server';
|
|
10
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
11
|
+
const {
|
|
12
|
+
stateRef
|
|
13
|
+
} = vi.hoisted(() => ({
|
|
14
|
+
stateRef: {
|
|
15
|
+
current: null
|
|
16
|
+
}
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
// The fragment reader hands back the data it was given; the redux selector
|
|
20
|
+
// runs against a default store's state.
|
|
21
|
+
vi.mock('react-relay', () => ({
|
|
22
|
+
graphql: () => null,
|
|
23
|
+
fetchQuery: vi.fn(),
|
|
24
|
+
useFragment: (_fragment, key) => key
|
|
25
|
+
}));
|
|
26
|
+
vi.mock('react-redux', async importOriginal => ({
|
|
27
|
+
...(await importOriginal()),
|
|
28
|
+
useSelector: selector => selector(stateRef.current)
|
|
29
|
+
}));
|
|
30
|
+
import StoreModalPackButton from '../components/StoreModal/StoreModalPackButton.js';
|
|
31
|
+
import { createDefaultStore } from '../reducer.js';
|
|
32
|
+
const money = amount => ({
|
|
33
|
+
amount,
|
|
34
|
+
currency: 'USD'
|
|
35
|
+
});
|
|
36
|
+
const pack = function () {
|
|
37
|
+
let overrides = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
38
|
+
return {
|
|
39
|
+
name: 'Pack of Basics',
|
|
40
|
+
sku: {
|
|
41
|
+
id: 'pack-sku',
|
|
42
|
+
price: money(15000)
|
|
43
|
+
},
|
|
44
|
+
featureStyle: null,
|
|
45
|
+
totalStyles: 3,
|
|
46
|
+
totalStylesPrice: money(30000),
|
|
47
|
+
...overrides
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
const markup = packData => renderToStaticMarkup( /*#__PURE__*/React.createElement(StoreModalPackButton, {
|
|
51
|
+
pack: packData,
|
|
52
|
+
onSelectSku: () => {}
|
|
53
|
+
}));
|
|
54
|
+
const mount = async (packData, onSelectSku) => {
|
|
55
|
+
const container = document.createElement('div');
|
|
56
|
+
document.body.appendChild(container);
|
|
57
|
+
const root = createRoot(container);
|
|
58
|
+
await act(async () => {
|
|
59
|
+
root.render( /*#__PURE__*/React.createElement(StoreModalPackButton, {
|
|
60
|
+
pack: packData,
|
|
61
|
+
onSelectSku: onSelectSku
|
|
62
|
+
}));
|
|
63
|
+
});
|
|
64
|
+
return container.querySelector('button');
|
|
65
|
+
};
|
|
66
|
+
describe('StoreModalPackButton', () => {
|
|
67
|
+
beforeEach(() => {
|
|
68
|
+
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
|
|
69
|
+
stateRef.current = createDefaultStore(undefined).getState();
|
|
70
|
+
document.body.innerHTML = '';
|
|
71
|
+
});
|
|
72
|
+
it("shows the pack's name as entered, without adding a suffix", () => {
|
|
73
|
+
expect(markup(pack())).toContain('>Pack of Basics<');
|
|
74
|
+
expect(markup(pack())).not.toContain('Pack of Basics Pack');
|
|
75
|
+
expect(markup(pack({
|
|
76
|
+
name: 'Essentials'
|
|
77
|
+
}))).toContain('>Essentials<');
|
|
78
|
+
});
|
|
79
|
+
it('strikes through the à-la-carte total with an <s> when the pack is cheaper', () => {
|
|
80
|
+
expect(markup(pack())).toMatch(/<s class="store-modal__family-button__styles-price">/);
|
|
81
|
+
expect(markup(pack({
|
|
82
|
+
totalStylesPrice: money(15000)
|
|
83
|
+
}))).not.toContain('store-modal__family-button__styles-price');
|
|
84
|
+
});
|
|
85
|
+
it('selects the pack SKU on click', async () => {
|
|
86
|
+
const onSelectSku = vi.fn();
|
|
87
|
+
const button = await mount(pack(), onSelectSku);
|
|
88
|
+
expect(button.dataset.clickable).toBe('true');
|
|
89
|
+
await act(async () => button.click());
|
|
90
|
+
expect(onSelectSku).toHaveBeenCalledWith('pack-sku', true);
|
|
91
|
+
});
|
|
92
|
+
it('is not clickable without a SKU', async () => {
|
|
93
|
+
const onSelectSku = vi.fn();
|
|
94
|
+
const button = await mount(pack({
|
|
95
|
+
sku: null,
|
|
96
|
+
totalStylesPrice: null
|
|
97
|
+
}), onSelectSku);
|
|
98
|
+
expect(button.dataset.clickable).toBe('false');
|
|
99
|
+
await act(async () => button.click());
|
|
100
|
+
expect(onSelectSku).not.toHaveBeenCalled();
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
|
|
3
|
+
// A pack page renders a block per member. An unlisted collection has no block
|
|
4
|
+
// anywhere in the Store Modal (FD-1239), so the pack page leaves it out too –
|
|
5
|
+
// the pack still grants it, and the pack button still sells it.
|
|
6
|
+
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import { renderToStaticMarkup } from 'react-dom/server';
|
|
9
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
10
|
+
const {
|
|
11
|
+
stateRef
|
|
12
|
+
} = vi.hoisted(() => ({
|
|
13
|
+
stateRef: {
|
|
14
|
+
current: null
|
|
15
|
+
}
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
// The fragment reader hands back the data it was given; the redux selector
|
|
19
|
+
// runs against a default store's state.
|
|
20
|
+
vi.mock('react-relay', () => ({
|
|
21
|
+
graphql: () => null,
|
|
22
|
+
fetchQuery: vi.fn(),
|
|
23
|
+
useFragment: (_fragment, key) => key
|
|
24
|
+
}));
|
|
25
|
+
vi.mock('react-redux', async importOriginal => ({
|
|
26
|
+
...(await importOriginal()),
|
|
27
|
+
useSelector: selector => selector(stateRef.current)
|
|
28
|
+
}));
|
|
29
|
+
import StoreModalPack from '../components/StoreModal/StoreModalPack.js';
|
|
30
|
+
import { createDefaultStore } from '../reducer.js';
|
|
31
|
+
const money = amount => ({
|
|
32
|
+
amount,
|
|
33
|
+
currency: 'USD'
|
|
34
|
+
});
|
|
35
|
+
const style = name => ({
|
|
36
|
+
id: `style:${name}`,
|
|
37
|
+
name: 'Regular',
|
|
38
|
+
supportedLanguages: ['en'],
|
|
39
|
+
variableInstances: [],
|
|
40
|
+
sku: {
|
|
41
|
+
id: `sku:style:${name}`,
|
|
42
|
+
price: money(4000)
|
|
43
|
+
},
|
|
44
|
+
cssFamily: name,
|
|
45
|
+
cssWeight: '400',
|
|
46
|
+
cssStretch: 'normal',
|
|
47
|
+
cssStyle: 'normal'
|
|
48
|
+
});
|
|
49
|
+
const family = function (name, visibility) {
|
|
50
|
+
let children = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
|
|
51
|
+
return {
|
|
52
|
+
__typename: 'FontCollection',
|
|
53
|
+
id: `collection:${name}`,
|
|
54
|
+
name,
|
|
55
|
+
visibility,
|
|
56
|
+
collectionType: 'family',
|
|
57
|
+
isVariableFont: false,
|
|
58
|
+
sku: {
|
|
59
|
+
id: `sku:${name}`,
|
|
60
|
+
price: money(20000)
|
|
61
|
+
},
|
|
62
|
+
totalStyles: 1,
|
|
63
|
+
totalStylesPrice: money(4000),
|
|
64
|
+
featureStyle: null,
|
|
65
|
+
bundles: [],
|
|
66
|
+
fontStyles: [style(name)],
|
|
67
|
+
children
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
const styleMember = (familyName, visibility) => ({
|
|
71
|
+
__typename: 'FontStyle',
|
|
72
|
+
...style(familyName),
|
|
73
|
+
family: {
|
|
74
|
+
id: `collection:${familyName}`,
|
|
75
|
+
name: familyName,
|
|
76
|
+
isVariableFont: false,
|
|
77
|
+
visibility
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
const pack = {
|
|
81
|
+
name: 'Anniversary pack',
|
|
82
|
+
sku: {
|
|
83
|
+
id: 'sku:pack',
|
|
84
|
+
price: money(30000)
|
|
85
|
+
},
|
|
86
|
+
featureStyle: null,
|
|
87
|
+
totalStyles: 6,
|
|
88
|
+
totalStylesPrice: money(60000),
|
|
89
|
+
members: [family('Alpha', 'VISIBLE', [family('Bravo', 'VISIBLE'), family('Charlie', 'UNLISTED')]), family('Delta', 'UNLISTED'), styleMember('Echo', 'VISIBLE'), styleMember('Foxtrot', 'UNLISTED')]
|
|
90
|
+
};
|
|
91
|
+
describe('StoreModalPack member blocks', () => {
|
|
92
|
+
beforeEach(() => {
|
|
93
|
+
stateRef.current = createDefaultStore(undefined).getState();
|
|
94
|
+
});
|
|
95
|
+
const markup = () => renderToStaticMarkup( /*#__PURE__*/React.createElement(StoreModalPack, {
|
|
96
|
+
pack: pack,
|
|
97
|
+
onSelect: () => {}
|
|
98
|
+
}));
|
|
99
|
+
it('renders a block for each visible member and child family', () => {
|
|
100
|
+
expect(markup()).toContain('Alpha');
|
|
101
|
+
expect(markup()).toContain('Bravo');
|
|
102
|
+
expect(markup()).toContain('Echo');
|
|
103
|
+
});
|
|
104
|
+
it('leaves out an unlisted member, an unlisted child and an unlisted style member', () => {
|
|
105
|
+
expect(markup()).not.toContain('Charlie');
|
|
106
|
+
expect(markup()).not.toContain('Delta');
|
|
107
|
+
expect(markup()).not.toContain('Foxtrot');
|
|
108
|
+
});
|
|
109
|
+
it('still sells the pack as a whole', () => {
|
|
110
|
+
expect(markup()).toContain('Anniversary pack');
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -69,6 +69,15 @@ describe('storeModalUrlActions', () => {
|
|
|
69
69
|
it('opens the modal on its default route for ?store=open', () => {
|
|
70
70
|
expect(storeModalUrlActions('?store=open', config(['unlisted']))).toEqual([OPEN]);
|
|
71
71
|
});
|
|
72
|
+
it('opens the modal on the browse page for ?store=index', () => {
|
|
73
|
+
expect(storeModalUrlActions('?store=index', config(['unlisted']))).toEqual([{
|
|
74
|
+
type: 'STORE_MODAL_REPLACE',
|
|
75
|
+
route: 'index',
|
|
76
|
+
variables: {
|
|
77
|
+
excludeTags: ['unlisted']
|
|
78
|
+
}
|
|
79
|
+
}, OPEN]);
|
|
80
|
+
});
|
|
72
81
|
it('opens the modal on a product for ?store=product/<id>', () => {
|
|
73
82
|
expect(storeModalUrlActions(`?store=product/${PRODUCT_ID}`, config())).toEqual([{
|
|
74
83
|
type: 'STORE_MODAL_REPLACE',
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
// @vitest-environment-options { "url": "https://store.example.com/fonts/archivo" }
|
|
3
|
+
|
|
4
|
+
// The browser-only storefront moments reach the server through
|
|
5
|
+
// `trackStorefrontEvent`. A buyer clicks through licences in bursts and React
|
|
6
|
+
// re-runs effects, so what matters here is what *doesn't* get reported: the
|
|
7
|
+
// steps before a configuration settles, a configuration already reported, and
|
|
8
|
+
// a second look at the same collection that is really one mount.
|
|
9
|
+
|
|
10
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
11
|
+
const commitMutation = vi.fn();
|
|
12
|
+
vi.mock('react-relay', () => ({
|
|
13
|
+
graphql: () => null,
|
|
14
|
+
commitMutation: function () {
|
|
15
|
+
return commitMutation(...arguments);
|
|
16
|
+
},
|
|
17
|
+
useRelayEnvironment: vi.fn()
|
|
18
|
+
}));
|
|
19
|
+
import { licenseSelectionsFromOptions, trackPaymentInfoEntered, trackProductCustomized, trackProductViewed } from '../components/Cart/storefrontEvents.js';
|
|
20
|
+
const environment = {};
|
|
21
|
+
function reportedInputs() {
|
|
22
|
+
return commitMutation.mock.calls.map(_ref => {
|
|
23
|
+
let [, config] = _ref;
|
|
24
|
+
return config.variables.input;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
commitMutation.mockReset();
|
|
29
|
+
vi.useFakeTimers();
|
|
30
|
+
});
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
vi.useRealTimers();
|
|
33
|
+
});
|
|
34
|
+
describe('licenseSelectionsFromOptions', () => {
|
|
35
|
+
it('takes the licence choices in the shape createOrderItems takes them, sorted', () => {
|
|
36
|
+
const options = [{
|
|
37
|
+
licenseId: 'web',
|
|
38
|
+
licenseOptions: {}
|
|
39
|
+
}, {
|
|
40
|
+
licenseId: 'desktop',
|
|
41
|
+
licenseOptions: {
|
|
42
|
+
users: 'five',
|
|
43
|
+
seats: 'one'
|
|
44
|
+
}
|
|
45
|
+
}];
|
|
46
|
+
expect(licenseSelectionsFromOptions(options)).toEqual([{
|
|
47
|
+
licenseId: 'desktop',
|
|
48
|
+
licenseVariableId: 'seats',
|
|
49
|
+
licenseOptionId: 'one'
|
|
50
|
+
}, {
|
|
51
|
+
licenseId: 'desktop',
|
|
52
|
+
licenseVariableId: 'users',
|
|
53
|
+
licenseOptionId: 'five'
|
|
54
|
+
}, {
|
|
55
|
+
licenseId: 'web'
|
|
56
|
+
}]);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
describe('trackProductCustomized', () => {
|
|
60
|
+
it('reports the configuration a burst of clicks settles on, once', () => {
|
|
61
|
+
let options = [{
|
|
62
|
+
licenseId: 'desktop',
|
|
63
|
+
licenseOptions: {
|
|
64
|
+
users: 'one'
|
|
65
|
+
}
|
|
66
|
+
}];
|
|
67
|
+
const current = () => options;
|
|
68
|
+
trackProductCustomized(environment, 'collection-burst', current);
|
|
69
|
+
options = [{
|
|
70
|
+
licenseId: 'desktop',
|
|
71
|
+
licenseOptions: {
|
|
72
|
+
users: 'five'
|
|
73
|
+
}
|
|
74
|
+
}];
|
|
75
|
+
trackProductCustomized(environment, 'collection-burst', current);
|
|
76
|
+
options = [{
|
|
77
|
+
licenseId: 'desktop',
|
|
78
|
+
licenseOptions: {
|
|
79
|
+
users: 'ten'
|
|
80
|
+
}
|
|
81
|
+
}];
|
|
82
|
+
trackProductCustomized(environment, 'collection-burst', current);
|
|
83
|
+
expect(commitMutation).not.toHaveBeenCalled();
|
|
84
|
+
vi.advanceTimersByTime(1500);
|
|
85
|
+
expect(reportedInputs()).toEqual([expect.objectContaining({
|
|
86
|
+
event: 'PRODUCT_CUSTOMIZED',
|
|
87
|
+
collectionId: 'collection-burst',
|
|
88
|
+
licenseSelections: [{
|
|
89
|
+
licenseId: 'desktop',
|
|
90
|
+
licenseVariableId: 'users',
|
|
91
|
+
licenseOptionId: 'ten'
|
|
92
|
+
}],
|
|
93
|
+
tracking: expect.objectContaining({
|
|
94
|
+
url: 'https://store.example.com/fonts/archivo'
|
|
95
|
+
})
|
|
96
|
+
})]);
|
|
97
|
+
});
|
|
98
|
+
it('skips a configuration already reported for that collection', () => {
|
|
99
|
+
let options = [{
|
|
100
|
+
licenseId: 'desktop',
|
|
101
|
+
licenseOptions: {}
|
|
102
|
+
}];
|
|
103
|
+
const current = () => options;
|
|
104
|
+
trackProductCustomized(environment, 'collection-repeat', current);
|
|
105
|
+
vi.advanceTimersByTime(1500);
|
|
106
|
+
|
|
107
|
+
// Over to web and back again: the buyer ends where they were.
|
|
108
|
+
options = [{
|
|
109
|
+
licenseId: 'web',
|
|
110
|
+
licenseOptions: {}
|
|
111
|
+
}];
|
|
112
|
+
trackProductCustomized(environment, 'collection-repeat', current);
|
|
113
|
+
options = [{
|
|
114
|
+
licenseId: 'desktop',
|
|
115
|
+
licenseOptions: {}
|
|
116
|
+
}];
|
|
117
|
+
trackProductCustomized(environment, 'collection-repeat', current);
|
|
118
|
+
vi.advanceTimersByTime(1500);
|
|
119
|
+
|
|
120
|
+
// Then a real change.
|
|
121
|
+
options = [{
|
|
122
|
+
licenseId: 'web',
|
|
123
|
+
licenseOptions: {}
|
|
124
|
+
}];
|
|
125
|
+
trackProductCustomized(environment, 'collection-repeat', current);
|
|
126
|
+
vi.advanceTimersByTime(1500);
|
|
127
|
+
expect(reportedInputs().map(input => input.licenseSelections)).toEqual([[{
|
|
128
|
+
licenseId: 'desktop'
|
|
129
|
+
}], [{
|
|
130
|
+
licenseId: 'web'
|
|
131
|
+
}]]);
|
|
132
|
+
});
|
|
133
|
+
it('reports nothing when every licence is unticked', () => {
|
|
134
|
+
trackProductCustomized(environment, 'collection-empty', () => []);
|
|
135
|
+
vi.advanceTimersByTime(1500);
|
|
136
|
+
expect(commitMutation).not.toHaveBeenCalled();
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
describe('trackProductViewed', () => {
|
|
140
|
+
it('counts an effect run twice for one mount as one view', () => {
|
|
141
|
+
trackProductViewed(environment, 'collection-view');
|
|
142
|
+
trackProductViewed(environment, 'collection-view');
|
|
143
|
+
trackProductViewed(environment, 'collection-other');
|
|
144
|
+
|
|
145
|
+
// Coming back to the first collection later is a new look.
|
|
146
|
+
vi.advanceTimersByTime(2000);
|
|
147
|
+
trackProductViewed(environment, 'collection-view');
|
|
148
|
+
expect(reportedInputs().map(_ref2 => {
|
|
149
|
+
let {
|
|
150
|
+
event,
|
|
151
|
+
collectionId
|
|
152
|
+
} = _ref2;
|
|
153
|
+
return [event, collectionId];
|
|
154
|
+
})).toEqual([['PRODUCT_VIEWED', 'collection-view'], ['PRODUCT_VIEWED', 'collection-other'], ['PRODUCT_VIEWED', 'collection-view']]);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
describe('trackPaymentInfoEntered', () => {
|
|
158
|
+
it('reports with the buyer’s tracking context', () => {
|
|
159
|
+
trackPaymentInfoEntered(environment);
|
|
160
|
+
expect(reportedInputs()).toEqual([expect.objectContaining({
|
|
161
|
+
event: 'PAYMENT_INFO_ENTERED',
|
|
162
|
+
tracking: expect.objectContaining({
|
|
163
|
+
analyticsConsent: false
|
|
164
|
+
})
|
|
165
|
+
})]);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import "../global-shim.js";
|
|
2
|
+
// @vitest-environment happy-dom
|
|
3
|
+
import React, { act, useState } from 'react';
|
|
4
|
+
import { createRoot } from 'react-dom/client';
|
|
5
|
+
import { ContentState, EditorState } from 'draft-js';
|
|
6
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
7
|
+
import TypeTesterFitter from '../components/TypeTester/TypeTesterFitter.js';
|
|
8
|
+
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
|
|
9
|
+
|
|
10
|
+
// happy-dom has no text layout, so the two measuring primitives are stubbed
|
|
11
|
+
// with a toy font: every glyph is 0.5em wide, and the `ss01` alternates are
|
|
12
|
+
// 0.6em. Canvas only ever sees the default glyphs – that is true of real
|
|
13
|
+
// browsers too (`ctx.font` has no feature settings), and is why autofit sized
|
|
14
|
+
// a tester for the default glyphs and then rendered it with the alternates.
|
|
15
|
+
const DEFAULT_ADVANCE = 0.5;
|
|
16
|
+
const SS01_ADVANCE = 0.6;
|
|
17
|
+
const TEXT = 'UNICASED CIRCUMSTANCES';
|
|
18
|
+
const CONTAINER_WIDTH = 1010;
|
|
19
|
+
// useTypeTesterStyler keeps 10px of slack when autofit is on.
|
|
20
|
+
const AVAILABLE_WIDTH = CONTAINER_WIDTH - 10;
|
|
21
|
+
const renderedWidth = (size, features) => TEXT.length * size * (features.includes('ss01') ? SS01_ADVANCE : DEFAULT_ADVANCE);
|
|
22
|
+
let domMeasure;
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
vi.stubGlobal('ResizeObserver', class {
|
|
25
|
+
constructor(callback) {
|
|
26
|
+
this.callback = callback;
|
|
27
|
+
}
|
|
28
|
+
observe() {
|
|
29
|
+
this.callback([{
|
|
30
|
+
contentRect: {
|
|
31
|
+
width: CONTAINER_WIDTH
|
|
32
|
+
}
|
|
33
|
+
}], this);
|
|
34
|
+
}
|
|
35
|
+
disconnect() {}
|
|
36
|
+
});
|
|
37
|
+
Object.defineProperty(document, 'fonts', {
|
|
38
|
+
configurable: true,
|
|
39
|
+
value: {
|
|
40
|
+
load: () => Promise.resolve([])
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
vi.spyOn(HTMLCanvasElement.prototype, 'getContext').mockReturnValue({
|
|
44
|
+
font: '',
|
|
45
|
+
letterSpacing: '',
|
|
46
|
+
measureText: text => ({
|
|
47
|
+
width: text.length * 100 * DEFAULT_ADVANCE
|
|
48
|
+
})
|
|
49
|
+
});
|
|
50
|
+
domMeasure = vi.spyOn(Element.prototype, 'getBoundingClientRect').mockImplementation(function () {
|
|
51
|
+
const {
|
|
52
|
+
fontSize,
|
|
53
|
+
fontFeatureSettings
|
|
54
|
+
} = this.style;
|
|
55
|
+
const advance = fontFeatureSettings.includes('ss01') ? SS01_ADVANCE : DEFAULT_ADVANCE;
|
|
56
|
+
return {
|
|
57
|
+
width: (this.textContent ?? '').length * parseFloat(fontSize) * advance
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
afterEach(() => {
|
|
62
|
+
vi.restoreAllMocks();
|
|
63
|
+
vi.unstubAllGlobals();
|
|
64
|
+
});
|
|
65
|
+
let currentSize = 0;
|
|
66
|
+
function Tester(_ref) {
|
|
67
|
+
let {
|
|
68
|
+
features
|
|
69
|
+
} = _ref;
|
|
70
|
+
const [size, setSize] = useState(72);
|
|
71
|
+
const [content] = useState(() => EditorState.createWithContent(ContentState.createFromText(TEXT)));
|
|
72
|
+
currentSize = size;
|
|
73
|
+
return /*#__PURE__*/React.createElement(TypeTesterFitter, {
|
|
74
|
+
autofit: true,
|
|
75
|
+
autofitOnChange: false,
|
|
76
|
+
min: 10,
|
|
77
|
+
max: 200,
|
|
78
|
+
size: size,
|
|
79
|
+
setSize: setSize,
|
|
80
|
+
lineHeight: 1.2,
|
|
81
|
+
letterSpacing: 0,
|
|
82
|
+
features: features,
|
|
83
|
+
focused: false,
|
|
84
|
+
truncate: false,
|
|
85
|
+
fontStyles: {
|
|
86
|
+
fontFamily: 'Toy Sans',
|
|
87
|
+
fontWeight: 200,
|
|
88
|
+
fontStyle: 'italic'
|
|
89
|
+
},
|
|
90
|
+
content: content,
|
|
91
|
+
contentEdited: false,
|
|
92
|
+
alignment: "left",
|
|
93
|
+
variableSettings: null,
|
|
94
|
+
columns: false
|
|
95
|
+
}, TEXT);
|
|
96
|
+
}
|
|
97
|
+
describe('type tester autofit with OpenType features', () => {
|
|
98
|
+
let root;
|
|
99
|
+
let container;
|
|
100
|
+
const render = features => act(async () => {
|
|
101
|
+
root.render( /*#__PURE__*/React.createElement(Tester, {
|
|
102
|
+
features: features
|
|
103
|
+
}));
|
|
104
|
+
});
|
|
105
|
+
beforeEach(() => {
|
|
106
|
+
container = document.createElement('div');
|
|
107
|
+
document.body.appendChild(container);
|
|
108
|
+
root = createRoot(container);
|
|
109
|
+
});
|
|
110
|
+
afterEach(async () => {
|
|
111
|
+
await act(async () => root.unmount());
|
|
112
|
+
container.remove();
|
|
113
|
+
});
|
|
114
|
+
it('fits text on one line when an enabled feature widens it', async () => {
|
|
115
|
+
await render([]);
|
|
116
|
+
const plainSize = currentSize;
|
|
117
|
+
expect(renderedWidth(plainSize, [])).toBeLessThanOrEqual(AVAILABLE_WIDTH);
|
|
118
|
+
await act(async () => root.unmount());
|
|
119
|
+
root = createRoot(container);
|
|
120
|
+
await render(['ss01']);
|
|
121
|
+
expect(currentSize).toBeLessThan(plainSize);
|
|
122
|
+
expect(renderedWidth(currentSize, ['ss01'])).toBeLessThanOrEqual(AVAILABLE_WIDTH);
|
|
123
|
+
});
|
|
124
|
+
it('refits when a feature is toggled', async () => {
|
|
125
|
+
await render([]);
|
|
126
|
+
const plainSize = currentSize;
|
|
127
|
+
await render(['ss01']);
|
|
128
|
+
const featureSize = currentSize;
|
|
129
|
+
expect(featureSize).toBeLessThan(plainSize);
|
|
130
|
+
expect(renderedWidth(featureSize, ['ss01'])).toBeLessThanOrEqual(AVAILABLE_WIDTH);
|
|
131
|
+
await render([]);
|
|
132
|
+
expect(currentSize).toBe(plainSize);
|
|
133
|
+
});
|
|
134
|
+
it('keeps the canvas fast path when no feature is on', async () => {
|
|
135
|
+
await render([]);
|
|
136
|
+
expect(domMeasure).not.toHaveBeenCalled();
|
|
137
|
+
});
|
|
138
|
+
});
|
|
@@ -21,7 +21,7 @@ const getOrderItemName = item => {
|
|
|
21
21
|
if ('name' in product) return product.name;
|
|
22
22
|
return null;
|
|
23
23
|
};
|
|
24
|
-
_AddToCartBanner_item.hash && _AddToCartBanner_item.hash !== "
|
|
24
|
+
_AddToCartBanner_item.hash && _AddToCartBanner_item.hash !== "5b6c677fd518073e74b1651f3b872e8a" && console.error("The definition of 'AddToCartBanner_item' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _AddToCartBanner_item;
|
|
25
25
|
function AddToCartBanner(_ref) {
|
|
26
26
|
var _order$orderItems, _order$orderItems2;
|
|
27
27
|
let {
|
|
@@ -1,19 +1,35 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { BuyButtonIDQuery } from '../../__generated__/BuyButtonIDQuery.graphql.js';
|
|
3
3
|
import { BuyButtonSlugQuery } from '../../__generated__/BuyButtonSlugQuery.graphql.js';
|
|
4
|
+
import { BuyButtonPackIDQuery } from '../../__generated__/BuyButtonPackIDQuery.graphql.js';
|
|
5
|
+
import { BuyButtonPackSlugQuery } from '../../__generated__/BuyButtonPackSlugQuery.graphql.js';
|
|
4
6
|
import { SerializablePreloadedQuery, type LoadQueryOptions } from '../../relay/loadSerializableQuery.js';
|
|
5
7
|
import { Config } from '../ConfigContext.js';
|
|
6
8
|
export interface BuyButton_props {
|
|
7
9
|
collectionName?: string;
|
|
8
10
|
label?: string;
|
|
9
11
|
}
|
|
10
|
-
export type BuyButtonPreloadedQuery = SerializablePreloadedQuery<BuyButtonIDQuery> | SerializablePreloadedQuery<BuyButtonSlugQuery>;
|
|
12
|
+
export type BuyButtonPreloadedQuery = SerializablePreloadedQuery<BuyButtonIDQuery> | SerializablePreloadedQuery<BuyButtonSlugQuery> | SerializablePreloadedQuery<BuyButtonPackIDQuery> | SerializablePreloadedQuery<BuyButtonPackSlugQuery>;
|
|
11
13
|
export type LoadBuyButtonQueryVariables = {
|
|
12
14
|
collectionId: string;
|
|
13
15
|
collectionSlug?: never;
|
|
16
|
+
fontPackId?: never;
|
|
17
|
+
fontPackSlug?: never;
|
|
14
18
|
} | {
|
|
15
19
|
collectionId?: never;
|
|
16
20
|
collectionSlug: string;
|
|
21
|
+
fontPackId?: never;
|
|
22
|
+
fontPackSlug?: never;
|
|
23
|
+
} | {
|
|
24
|
+
collectionId?: never;
|
|
25
|
+
collectionSlug?: never;
|
|
26
|
+
fontPackId: string;
|
|
27
|
+
fontPackSlug?: never;
|
|
28
|
+
} | {
|
|
29
|
+
collectionId?: never;
|
|
30
|
+
collectionSlug?: never;
|
|
31
|
+
fontPackId?: never;
|
|
32
|
+
fontPackSlug: string;
|
|
17
33
|
};
|
|
18
34
|
export declare function loadBuyButtonQuery(variables: LoadBuyButtonQueryVariables, options?: LoadQueryOptions): Promise<BuyButtonPreloadedQuery>;
|
|
19
35
|
export declare function BuyButtonPreloadedIDQueryRenderer({ preloadedQuery, ...rest }: BuyButton_props & {
|
|
@@ -22,19 +38,23 @@ export declare function BuyButtonPreloadedIDQueryRenderer({ preloadedQuery, ...r
|
|
|
22
38
|
export declare function BuyButtonPreloadedSlugQueryRenderer({ preloadedQuery, ...rest }: BuyButton_props & {
|
|
23
39
|
preloadedQuery: SerializablePreloadedQuery<BuyButtonSlugQuery>;
|
|
24
40
|
}): React.JSX.Element;
|
|
41
|
+
export declare function BuyButtonPreloadedPackIDQueryRenderer({ preloadedQuery, ...rest }: BuyButton_props & {
|
|
42
|
+
preloadedQuery: SerializablePreloadedQuery<BuyButtonPackIDQuery>;
|
|
43
|
+
}): React.JSX.Element;
|
|
44
|
+
export declare function BuyButtonPreloadedPackSlugQueryRenderer({ preloadedQuery, ...rest }: BuyButton_props & {
|
|
45
|
+
preloadedQuery: SerializablePreloadedQuery<BuyButtonPackSlugQuery>;
|
|
46
|
+
}): React.JSX.Element;
|
|
47
|
+
type BuyButton_lazyProps = LoadBuyButtonQueryVariables;
|
|
25
48
|
export type BuyButton_unifiedProps = BuyButton_props & {
|
|
26
49
|
config?: Config;
|
|
27
50
|
} & ({
|
|
28
51
|
preloadedQuery: BuyButtonPreloadedQuery;
|
|
29
52
|
collectionId?: never;
|
|
30
53
|
collectionSlug?: never;
|
|
31
|
-
|
|
54
|
+
fontPackId?: never;
|
|
55
|
+
fontPackSlug?: never;
|
|
56
|
+
} | ({
|
|
32
57
|
preloadedQuery?: never;
|
|
33
|
-
|
|
34
|
-
collectionSlug?: never;
|
|
35
|
-
} | {
|
|
36
|
-
preloadedQuery?: never;
|
|
37
|
-
collectionId?: never;
|
|
38
|
-
collectionSlug: string;
|
|
39
|
-
});
|
|
58
|
+
} & BuyButton_lazyProps));
|
|
40
59
|
export default function BuyButton(props: BuyButton_unifiedProps): React.JSX.Element;
|
|
60
|
+
export {};
|