fontdue-js 3.2.4 → 3.2.6

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 CHANGED
@@ -1,3 +1,11 @@
1
+ ## 3.2.6
2
+
3
+ - **Fixed `TypeTesters` crashing when `tags` or `excludeTags` is set on a server-rendered page.** In a React Server Component the server entry preloaded the filtered testers but rendered the client component as though no filter had been given, so the refetch that runs once the page hydrates pulled in the collection's *whole* tester list. Rendering a tester the filter had excluded then threw `Cannot destructure property 'content' of … as it is undefined` and took the page down with it. Both filters now reach the client component on the preloaded path, as they already did when the component fetches its own query in the browser. No public API change.
4
+
5
+ ## 3.2.5
6
+
7
+ - **`CartButton` is back in the server-rendered HTML.** Since 3.0.0 it rendered nothing at all until its cart query resolved in the browser, so the button was absent from the initial HTML and only appeared after hydration and a network round-trip — missing for crawlers, and a visible pop-in for everyone else. It now server-renders its empty state (`data-count="0"`) and fills in the count once the cart loads, as it did on 2.x. No public API change.
8
+
1
9
  ## 3.2.4
2
10
 
3
11
  - **The `StoreModal` checkout no longer advances past the customer step when the customer fails to save.** A rejected save — an invalid email, a duplicate record — previously moved the buyer on to payment with their details unsaved. The step now holds and shows the field errors. No public API change.
@@ -0,0 +1,98 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ // The <TypeTesters> react-server entrypoint's whole job is to run the preload
4
+ // on the server and hand *both* the response and the caller's props to the
5
+ // client renderer. Stub the network preload, and the client module with it —
6
+ // it's a 'use client' module that pulls in Relay and Redux — so these tests
7
+ // observe exactly that handoff.
8
+ const loadSerializableQuery = vi.fn(async (query, variables) => ({
9
+ params: query.params,
10
+ variables,
11
+ response: {
12
+ data: {}
13
+ }
14
+ }));
15
+ vi.mock('../relay/loadSerializableQuery.js', () => ({
16
+ default: function () {
17
+ return loadSerializableQuery(...arguments);
18
+ }
19
+ }));
20
+ vi.mock('../components/TypeTesters/index.js', () => ({
21
+ TypeTestersPreloadedIDQueryRenderer: function TypeTestersPreloadedIDQueryRenderer() {
22
+ return null;
23
+ },
24
+ TypeTestersPreloadedSlugQueryRenderer: function TypeTestersPreloadedSlugQueryRenderer() {
25
+ return null;
26
+ }
27
+ }));
28
+ import TypeTestersServer from '../components/TypeTesters/index.server.js';
29
+ import { TypeTestersPreloadedIDQueryRenderer, TypeTestersPreloadedSlugQueryRenderer } from '../components/TypeTesters/index.js';
30
+ const render = props => TypeTestersServer(props);
31
+ describe('TypeTesters react-server entrypoint', () => {
32
+ beforeEach(() => {
33
+ loadSerializableQuery.mockClear();
34
+ });
35
+ it('forwards tags and excludeTags to the preloaded ID renderer', async () => {
36
+ const element = await render({
37
+ collectionId: 'collection-1',
38
+ excludeTags: ['homepage', 'specimen'],
39
+ autofit: true
40
+ });
41
+ expect(element === null || element === void 0 ? void 0 : element.type).toBe(TypeTestersPreloadedIDQueryRenderer);
42
+ // The client component needs these to rebuild the refetch variables it
43
+ // hands to useRefetchOnLicenseChanges. Dropping them made a licence-change
44
+ // refetch return the *unfiltered* tester list, which the tester state was
45
+ // never seeded with.
46
+ expect(element === null || element === void 0 ? void 0 : element.props.excludeTags).toEqual(['homepage', 'specimen']);
47
+ expect(element === null || element === void 0 ? void 0 : element.props.tags).toBeNull();
48
+ // Everything else still rides along.
49
+ expect(element === null || element === void 0 ? void 0 : element.props.autofit).toBe(true);
50
+ expect(element === null || element === void 0 ? void 0 : element.props.preloadedQuery).toBeDefined();
51
+ });
52
+ it('forwards tags and excludeTags to the preloaded slug renderer', async () => {
53
+ const element = await render({
54
+ collectionSlug: 'my-collection',
55
+ tags: ['specimen'],
56
+ excludeTags: ['homepage']
57
+ });
58
+ expect(element === null || element === void 0 ? void 0 : element.type).toBe(TypeTestersPreloadedSlugQueryRenderer);
59
+ expect(element === null || element === void 0 ? void 0 : element.props.tags).toEqual(['specimen']);
60
+ expect(element === null || element === void 0 ? void 0 : element.props.excludeTags).toEqual(['homepage']);
61
+ expect(element === null || element === void 0 ? void 0 : element.props.preloadedQuery).toBeDefined();
62
+ });
63
+ it('preloads with the same tag filters it forwards', async () => {
64
+ const element = await render({
65
+ collectionId: 'collection-1',
66
+ tags: ['specimen'],
67
+ excludeTags: ['homepage']
68
+ });
69
+ expect(loadSerializableQuery).toHaveBeenCalledTimes(1);
70
+ const [, variables] = loadSerializableQuery.mock.calls[0];
71
+ expect(variables).toEqual({
72
+ collectionId: 'collection-1',
73
+ tags: ['specimen'],
74
+ excludeTags: ['homepage']
75
+ });
76
+ expect(element === null || element === void 0 ? void 0 : element.props.tags).toEqual(variables.tags);
77
+ expect(element === null || element === void 0 ? void 0 : element.props.excludeTags).toEqual(variables.excludeTags);
78
+ });
79
+ it('defaults both filters to null when the caller passes neither', async () => {
80
+ const element = await render({
81
+ collectionId: 'collection-1'
82
+ });
83
+
84
+ // Explicit nulls, not undefined: the preload normalizes optional variables
85
+ // to null so the server and client Relay cache keys match, and the
86
+ // forwarded props have to agree with the variables the payload was
87
+ // fetched under.
88
+ const [, variables] = loadSerializableQuery.mock.calls[0];
89
+ expect(variables.tags).toBeNull();
90
+ expect(variables.excludeTags).toBeNull();
91
+ expect(element === null || element === void 0 ? void 0 : element.props.tags).toBeNull();
92
+ expect(element === null || element === void 0 ? void 0 : element.props.excludeTags).toBeNull();
93
+ });
94
+ it('renders nothing without a collection id or slug', async () => {
95
+ expect(await render({})).toBeNull();
96
+ expect(loadSerializableQuery).not.toHaveBeenCalled();
97
+ });
98
+ });
@@ -22,12 +22,14 @@ const openCart = () => ({
22
22
  type: 'OPEN_CART'
23
23
  });
24
24
  function CartButtonComponent(_ref) {
25
+ var _orderData$stripeChar;
25
26
  let {
26
27
  label,
27
28
  buttonStyle = 'inline',
28
29
  order: orderKey,
29
30
  children,
30
- suffix
31
+ suffix,
32
+ pending = false
31
33
  } = _ref;
32
34
  const orderData = useFragment((_CartButton_order.hash && _CartButton_order.hash !== "26a092d7efb579c98adda18413349b3b" && console.error("The definition of 'CartButton_order' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _CartButton_order), orderKey);
33
35
  const [count, setCount] = useState(0);
@@ -71,8 +73,8 @@ function CartButtonComponent(_ref) {
71
73
  useEffect(() => {
72
74
  setCount(cartButtonCount(orderData));
73
75
  }, [orderData, setCount]);
74
- if (!orderData) return null;
75
- if (orderData.stripeCharge && orderData.stripeCharge.paid) return null;
76
+ if (!orderData && !pending) return null;
77
+ if (orderData !== null && orderData !== void 0 && (_orderData$stripeChar = orderData.stripeCharge) !== null && _orderData$stripeChar !== void 0 && _orderData$stripeChar.paid) return null;
76
78
  const suffixNodes = formatSuffix();
77
79
  return /*#__PURE__*/React.createElement(ComponentsContext.Consumer, null, components => components.CartButton ? /*#__PURE__*/React.createElement(components.CartButton, {
78
80
  count: count,
@@ -118,9 +120,11 @@ export default function CartButton(_ref2) {
118
120
  config: config
119
121
  }, mounted ? /*#__PURE__*/React.createElement(Suspense, {
120
122
  fallback: /*#__PURE__*/React.createElement(CartButtonComponent, _extends({}, props, {
121
- order: null
123
+ order: null,
124
+ pending: true
122
125
  }))
123
126
  }, /*#__PURE__*/React.createElement(CartButtonLazyQueryRenderer, props)) : /*#__PURE__*/React.createElement(CartButtonComponent, _extends({}, props, {
124
- order: null
127
+ order: null,
128
+ pending: true
125
129
  })));
126
130
  }
@@ -14,6 +14,14 @@ export function loadTypeTestersQuery() {
14
14
  // the client `*PreloadedQueryRenderer`. Lets pages just write
15
15
  // `<TypeTesters collectionId={id} />` in an RSC without manually invoking
16
16
  // `loadTypeTestersQuery` + threading `preloadedQuery` through props.
17
+ //
18
+ // `tags`/`excludeTags` are both query variables *and* component props: the
19
+ // client component needs them to build the refetch variables it hands to
20
+ // `useRefetchOnLicenseChanges`. Destructuring them here takes them out of
21
+ // `...rest`, so both branches pass them on explicitly — otherwise a
22
+ // licence-change refetch re-runs the filtered query unfiltered and the
23
+ // tester list no longer matches the state seeded from the preload. The lazy
24
+ // `TypeTesters*QueryRenderer`s do the same for the same reason.
17
25
  export default async function TypeTesters(_ref) {
18
26
  let {
19
27
  collectionId,
@@ -29,7 +37,9 @@ export default async function TypeTesters(_ref) {
29
37
  excludeTags
30
38
  });
31
39
  return /*#__PURE__*/React.createElement(TypeTestersPreloadedIDQueryRenderer, _extends({
32
- preloadedQuery: preloadedQuery
40
+ preloadedQuery: preloadedQuery,
41
+ tags: tags,
42
+ excludeTags: excludeTags
33
43
  }, rest));
34
44
  }
35
45
  if (collectionSlug) {
@@ -39,7 +49,9 @@ export default async function TypeTesters(_ref) {
39
49
  excludeTags
40
50
  });
41
51
  return /*#__PURE__*/React.createElement(TypeTestersPreloadedSlugQueryRenderer, _extends({
42
- preloadedQuery: preloadedQuery
52
+ preloadedQuery: preloadedQuery,
53
+ tags: tags,
54
+ excludeTags: excludeTags
43
55
  }, rest));
44
56
  }
45
57
  return null;
@@ -8,7 +8,7 @@ import { NODE_ACCESS_HEADER } from '../nodeAccess.js';
8
8
  // (defineVersionPlugin in .babelrc.cjs) with the literal package.json#version.
9
9
  // Exported so UI (the admin toolbar) can surface it without re-reading the
10
10
  // build-time global in a 'use client' module.
11
- export const version = "3.2.4";
11
+ export const version = "3.2.6";
12
12
  const IS_SERVER = typeof window === typeof undefined;
13
13
 
14
14
  // Opt server fetches into Next's data cache only in production; dev stays
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fontdue-js",
3
- "version": "3.2.4",
3
+ "version": "3.2.6",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "npm run relay && run-p build-js build-css build-ts",