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.
Files changed (167) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +5 -4
  3. package/dist/__generated__/AddToCartBannerQuery.graphql.d.ts +1 -1
  4. package/dist/__generated__/AddToCartBannerQuery.graphql.js +16 -10
  5. package/dist/__generated__/AddToCartBanner_item.graphql.d.ts +4 -1
  6. package/dist/__generated__/AddToCartBanner_item.graphql.js +7 -2
  7. package/dist/__generated__/AddToCartBanner_order.graphql.d.ts +4 -1
  8. package/dist/__generated__/AddToCartBanner_order.graphql.js +6 -1
  9. package/dist/__generated__/BuyButtonIDQuery.graphql.d.ts +3 -3
  10. package/dist/__generated__/BuyButtonIDQuery.graphql.js +31 -16
  11. package/dist/__generated__/BuyButtonPackIDQuery.graphql.d.ts +21 -0
  12. package/dist/__generated__/BuyButtonPackIDQuery.graphql.js +129 -0
  13. package/dist/__generated__/BuyButtonPackSlugQuery.graphql.d.ts +25 -0
  14. package/dist/__generated__/BuyButtonPackSlugQuery.graphql.js +167 -0
  15. package/dist/__generated__/BuyButtonSlugQuery.graphql.d.ts +3 -3
  16. package/dist/__generated__/BuyButtonSlugQuery.graphql.js +45 -17
  17. package/dist/__generated__/BuyButton_product.graphql.d.ts +27 -0
  18. package/dist/__generated__/BuyButton_product.graphql.js +72 -0
  19. package/dist/__generated__/CartItemProduct_product.graphql.d.ts +1 -1
  20. package/dist/__generated__/CartItemProduct_product.graphql.js +7 -2
  21. package/dist/__generated__/CartOrderCompleteOrderMutation.graphql.d.ts +1 -1
  22. package/dist/__generated__/CartOrderCompleteOrderMutation.graphql.js +26 -20
  23. package/dist/__generated__/CartOrderRemoveDiscountMutation.graphql.d.ts +1 -1
  24. package/dist/__generated__/CartOrderRemoveDiscountMutation.graphql.js +26 -20
  25. package/dist/__generated__/CartOrderUpdateMutation.graphql.d.ts +1 -1
  26. package/dist/__generated__/CartOrderUpdateMutation.graphql.js +26 -20
  27. package/dist/__generated__/CartQuery.graphql.d.ts +1 -1
  28. package/dist/__generated__/CartQuery.graphql.js +26 -20
  29. package/dist/__generated__/CartStateUpdateMutation.graphql.d.ts +1 -1
  30. package/dist/__generated__/CartStateUpdateMutation.graphql.js +26 -20
  31. package/dist/__generated__/CheckoutUpdateCustomerMutation.graphql.d.ts +1 -1
  32. package/dist/__generated__/CheckoutUpdateCustomerMutation.graphql.js +26 -20
  33. package/dist/__generated__/CheckoutUpdateOrderMutation.graphql.d.ts +1 -1
  34. package/dist/__generated__/CheckoutUpdateOrderMutation.graphql.js +26 -20
  35. package/dist/__generated__/CollectionAa_Query.graphql.d.ts +1 -1
  36. package/dist/__generated__/CollectionAa_Query.graphql.js +20 -14
  37. package/dist/__generated__/CollectionAa_product.graphql.d.ts +7 -1
  38. package/dist/__generated__/CollectionAa_product.graphql.js +22 -16
  39. package/dist/__generated__/FontFamiliesQuery.graphql.d.ts +4 -1
  40. package/dist/__generated__/FontFamiliesQuery.graphql.js +46 -39
  41. package/dist/__generated__/NodePasswordFormElementIDQuery.graphql.d.ts +3 -3
  42. package/dist/__generated__/NodePasswordFormElementIDQuery.graphql.js +9 -9
  43. package/dist/__generated__/NodePasswordFormElementPackSlugQuery.graphql.d.ts +25 -0
  44. package/dist/__generated__/NodePasswordFormElementPackSlugQuery.graphql.js +108 -0
  45. package/dist/__generated__/PrecartAddToCartMutation.graphql.d.ts +1 -1
  46. package/dist/__generated__/PrecartAddToCartMutation.graphql.js +32 -26
  47. package/dist/__generated__/PriceBar_node.graphql.d.ts +4 -1
  48. package/dist/__generated__/PriceBar_node.graphql.js +23 -16
  49. package/dist/__generated__/StoreModalCartQuery.graphql.d.ts +1 -1
  50. package/dist/__generated__/StoreModalCartQuery.graphql.js +37 -31
  51. package/dist/__generated__/StoreModalFamily_collection.graphql.d.ts +3 -2
  52. package/dist/__generated__/StoreModalFamily_collection.graphql.js +3 -3
  53. package/dist/__generated__/StoreModalIndexPackItem_fontPack.graphql.d.ts +23 -0
  54. package/dist/__generated__/StoreModalIndexPackItem_fontPack.graphql.js +58 -0
  55. package/dist/__generated__/StoreModalIndexPaginationQuery.graphql.d.ts +23 -0
  56. package/dist/__generated__/StoreModalIndexPaginationQuery.graphql.js +303 -0
  57. package/dist/__generated__/StoreModalIndexQuery.graphql.d.ts +6 -3
  58. package/dist/__generated__/StoreModalIndexQuery.graphql.js +218 -117
  59. package/dist/__generated__/StoreModalIndex_viewer.graphql.d.ts +24 -0
  60. package/dist/__generated__/StoreModalIndex_viewer.graphql.js +134 -0
  61. package/dist/__generated__/StoreModalPackButton_fontPack.graphql.d.ts +33 -0
  62. package/dist/__generated__/StoreModalPackButton_fontPack.graphql.js +119 -0
  63. package/dist/__generated__/StoreModalPack_fontPack.graphql.d.ts +45 -0
  64. package/dist/__generated__/StoreModalPack_fontPack.graphql.js +150 -0
  65. package/dist/__generated__/StoreModalProductContent_collection.graphql.d.ts +6 -2
  66. package/dist/__generated__/StoreModalProductContent_collection.graphql.js +43 -19
  67. package/dist/__generated__/{StoreModalProductLicenseSelection_collection.graphql.d.ts → StoreModalProductLicenseSelection_licenseable.graphql.d.ts} +7 -6
  68. package/dist/__generated__/StoreModalProductLicenseSelection_licenseable.graphql.js +43 -0
  69. package/dist/__generated__/StoreModalProductQuery.graphql.d.ts +1 -1
  70. package/dist/__generated__/StoreModalProductQuery.graphql.js +199 -142
  71. package/dist/__generated__/StoreModalProductRefetchQuery.graphql.d.ts +1 -1
  72. package/dist/__generated__/StoreModalProductRefetchQuery.graphql.js +190 -133
  73. package/dist/__generated__/StoreModalProductSlugQuery.graphql.d.ts +5 -1
  74. package/dist/__generated__/StoreModalProductSlugQuery.graphql.js +254 -176
  75. package/dist/__generated__/StoreModalProductSummaryRefetchQuery.graphql.d.ts +1 -1
  76. package/dist/__generated__/StoreModalProductSummaryRefetchQuery.graphql.js +9 -4
  77. package/dist/__generated__/StoreModalProductSummary_viewer.graphql.d.ts +7 -1
  78. package/dist/__generated__/StoreModalProductSummary_viewer.graphql.js +7 -2
  79. package/dist/__generated__/TestFontsFormUpdateCustomerMutation.graphql.d.ts +1 -1
  80. package/dist/__generated__/TestFontsFormUpdateCustomerMutation.graphql.js +26 -20
  81. package/dist/__generated__/TypeTestersIDQuery.graphql.d.ts +1 -1
  82. package/dist/__generated__/TypeTestersIDQuery.graphql.js +17 -10
  83. package/dist/__generated__/TypeTestersRefetchQuery.graphql.d.ts +1 -1
  84. package/dist/__generated__/TypeTestersRefetchQuery.graphql.js +17 -10
  85. package/dist/__generated__/TypeTestersSlugQuery.graphql.d.ts +1 -1
  86. package/dist/__generated__/TypeTestersSlugQuery.graphql.js +17 -10
  87. package/dist/__generated__/productState_Query.graphql.d.ts +99 -1
  88. package/dist/__generated__/productState_Query.graphql.js +43 -4
  89. package/dist/__generated__/productState_fontPack.graphql.d.ts +111 -0
  90. package/dist/__generated__/productState_fontPack.graphql.js +117 -0
  91. package/dist/__generated__/storefrontEventsTrackStorefrontEventMutation.graphql.d.ts +41 -0
  92. package/dist/__generated__/storefrontEventsTrackStorefrontEventMutation.graphql.js +65 -0
  93. package/dist/__tests__/bundleSelectionOrder.test.js +94 -0
  94. package/dist/__tests__/collectionBundleSelection.test.js +443 -1446
  95. package/dist/__tests__/fontPackSelection.test.js +435 -0
  96. package/dist/__tests__/infiniteScroll.test.js +180 -0
  97. package/dist/__tests__/licenseExclusions.test.js +4 -1
  98. package/dist/__tests__/storeModalPackButton.test.js +102 -0
  99. package/dist/__tests__/storeModalPackUnlisted.test.js +112 -0
  100. package/dist/__tests__/storeModalRoutes.test.js +9 -0
  101. package/dist/__tests__/storefrontEvents.test.js +167 -0
  102. package/dist/__tests__/typeTesterAutofitFeatures.test.js +138 -0
  103. package/dist/components/AddToCartBanner/index.js +1 -1
  104. package/dist/components/BuyButton/index.d.ts +29 -9
  105. package/dist/components/BuyButton/index.js +123 -26
  106. package/dist/components/BuyButton/index.server.d.ts +4 -2
  107. package/dist/components/BuyButton/index.server.js +24 -3
  108. package/dist/components/Cart/CartItem/CartItemProduct.js +3 -3
  109. package/dist/components/Cart/Checkout.js +11 -1
  110. package/dist/components/Cart/storefrontEvents.d.ts +34 -0
  111. package/dist/components/Cart/storefrontEvents.js +138 -0
  112. package/dist/components/CollectionAa/index.js +2 -2
  113. package/dist/components/FontFamilies/index.js +4 -3
  114. package/dist/components/NodePasswordForm/NodePasswordFormElement.d.ts +1 -1
  115. package/dist/components/NodePasswordForm/NodePasswordFormElement.js +45 -21
  116. package/dist/components/NodePasswordForm/index.d.ts +7 -4
  117. package/dist/components/NodePasswordForm/index.js +6 -6
  118. package/dist/components/Precart/index.js +6 -0
  119. package/dist/components/PriceBar/index.js +5 -3
  120. package/dist/components/Root/productState.d.ts +2 -8
  121. package/dist/components/Root/productState.js +100 -61
  122. package/dist/components/SKUPrice/index.js +7 -8
  123. package/dist/components/StoreModal/StoreModalFamily.d.ts +7 -0
  124. package/dist/components/StoreModal/StoreModalFamily.js +11 -7
  125. package/dist/components/StoreModal/StoreModalIndex.js +58 -7
  126. package/dist/components/StoreModal/StoreModalIndexEntry.d.ts +13 -0
  127. package/dist/components/StoreModal/StoreModalIndexEntry.js +43 -0
  128. package/dist/components/StoreModal/StoreModalIndexItem.js +7 -30
  129. package/dist/components/StoreModal/StoreModalIndexPackItem.d.ts +7 -0
  130. package/dist/components/StoreModal/StoreModalIndexPackItem.js +21 -0
  131. package/dist/components/StoreModal/StoreModalPack.d.ts +9 -0
  132. package/dist/components/StoreModal/StoreModalPack.js +79 -0
  133. package/dist/components/StoreModal/StoreModalPackButton.d.ts +8 -0
  134. package/dist/components/StoreModal/StoreModalPackButton.js +53 -0
  135. package/dist/components/StoreModal/StoreModalProduct.js +49 -19
  136. package/dist/components/StoreModalProductLicenseSelection/StoreModalProductLicense.d.ts +2 -1
  137. package/dist/components/StoreModalProductLicenseSelection/StoreModalProductLicense.js +8 -3
  138. package/dist/components/StoreModalProductLicenseSelection/index.d.ts +3 -3
  139. package/dist/components/StoreModalProductLicenseSelection/index.js +7 -6
  140. package/dist/components/StoreModalProductSummary/index.js +1 -1
  141. package/dist/components/TypeTester/useTypeTesterStyler.js +1 -0
  142. package/dist/components/elements/StoreModalContainer/StoreModalContainerContext.d.ts +1 -0
  143. package/dist/components/elements/StoreModalContainer/index.js +9 -4
  144. package/dist/components/elements/StoreModalFamily/index.d.ts +1 -1
  145. package/dist/components/elements/StoreModalFamily/index.js +1 -1
  146. package/dist/components/elements/StoreModalIndexLoadMore/index.d.ts +8 -0
  147. package/dist/components/elements/StoreModalIndexLoadMore/index.js +21 -0
  148. package/dist/components/elements/StoreModalUnifiedCheckout.js +12 -0
  149. package/dist/components/useInfiniteScroll.d.ts +28 -0
  150. package/dist/components/useInfiniteScroll.js +50 -0
  151. package/dist/fontdue.css +39 -0
  152. package/dist/hooks/useAutofit.d.ts +2 -1
  153. package/dist/hooks/useAutofit.js +10 -4
  154. package/dist/reducer.d.ts +12 -5
  155. package/dist/reducer.js +18 -87
  156. package/dist/selection.d.ts +44 -0
  157. package/dist/selection.js +539 -0
  158. package/dist/storeModalRoutes.js +5 -4
  159. package/dist/utils.d.ts +1 -12
  160. package/dist/utils.js +1 -36
  161. package/dist/version.js +1 -1
  162. package/package.json +1 -1
  163. package/dist/__generated__/BuyButton_collection.graphql.d.ts +0 -25
  164. package/dist/__generated__/BuyButton_collection.graphql.js +0 -49
  165. package/dist/__generated__/StoreModalProductLicenseSelection_collection.graphql.js +0 -40
  166. package/fontdue.css +0 -2733
  167. 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 !== "93905c08206c30255368dbb06f59ccec" && 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;
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
- collectionId: string;
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 {};