@akinon/next 1.126.18 → 1.126.19
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 +8 -0
- package/__tests__/collection-widget-data.test.ts +126 -0
- package/data/server/widget.ts +49 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @akinon/next
|
|
2
2
|
|
|
3
|
+
## 1.126.19
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- df46ca2: ZERO-5207: Add getCollectionWidgetData to the v1 server data layer, as on main
|
|
8
|
+
|
|
9
|
+
`getCollectionWidgetData` reads a collection widget (`/product_collection_widgets/<slug>/`) through the core's cache, under `widget_collection:<slug>`, the way `getWidgetData` reads a widget. `@akinon/pz-theme` already looks the function up on `@akinon/next/data/server` and uses it when it is there; on a core without it, pz-theme calls the endpoint itself, without the cache. Like `getWidgetSchemaData` on this line, it skips main's preview-copy lookup, because the theme editor writes no preview copies of collection widgets.
|
|
10
|
+
|
|
3
11
|
## 1.126.18
|
|
4
12
|
|
|
5
13
|
### Patch Changes
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
jest.mock('server-only', () => ({}), { virtual: true });
|
|
2
|
+
|
|
3
|
+
const appFetch = jest.fn();
|
|
4
|
+
|
|
5
|
+
jest.mock('../utils/app-fetch', () => ({
|
|
6
|
+
__esModule: true,
|
|
7
|
+
default: (...args: unknown[]) => appFetch(...args)
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
jest.mock('../utils/server-variables', () => ({
|
|
11
|
+
ServerVariables: { locale: 'tr', currency: 'try' }
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
import * as serverData from '../data/server';
|
|
15
|
+
import { getCollectionWidgetData, getWidgetData } from '../data/server/widget';
|
|
16
|
+
import { Cache, CacheKey } from '../lib/cache';
|
|
17
|
+
|
|
18
|
+
const SLUG = 'home-product-slider';
|
|
19
|
+
|
|
20
|
+
const COLLECTION_WIDGET = {
|
|
21
|
+
pk: 7,
|
|
22
|
+
name: SLUG,
|
|
23
|
+
attributes: {},
|
|
24
|
+
products: [{ pk: 1, name: 'Shirt', absolute_url: '/shirt/' }]
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
describe('getCollectionWidgetData', () => {
|
|
28
|
+
let wrap: jest.SpyInstance;
|
|
29
|
+
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
appFetch.mockReset().mockResolvedValue(COLLECTION_WIDGET);
|
|
32
|
+
wrap = jest
|
|
33
|
+
.spyOn(Cache, 'wrap')
|
|
34
|
+
.mockImplementation(async (_key, _locale, handler) => handler());
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
wrap.mockRestore();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// @akinon/pz-theme reads it off this module object and calls the endpoint
|
|
42
|
+
// itself, uncached, when it is missing.
|
|
43
|
+
it('is exported from the server data layer', () => {
|
|
44
|
+
expect(
|
|
45
|
+
(serverData as unknown as Record<string, unknown>).getCollectionWidgetData
|
|
46
|
+
).toBe(getCollectionWidgetData);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('reads the collection widget endpoint for the request locale and currency', async () => {
|
|
50
|
+
await expect(getCollectionWidgetData({ slug: SLUG })).resolves.toEqual(
|
|
51
|
+
COLLECTION_WIDGET
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
expect(appFetch).toHaveBeenCalledTimes(1);
|
|
55
|
+
expect(appFetch).toHaveBeenCalledWith({
|
|
56
|
+
url: `/product_collection_widgets/${SLUG}/`,
|
|
57
|
+
locale: 'tr',
|
|
58
|
+
currency: 'try',
|
|
59
|
+
init: {}
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('caches compressed, under its own key, per locale', async () => {
|
|
64
|
+
await getCollectionWidgetData({ slug: SLUG });
|
|
65
|
+
|
|
66
|
+
expect(wrap).toHaveBeenCalledWith(
|
|
67
|
+
`widget_collection:${SLUG}`,
|
|
68
|
+
'tr',
|
|
69
|
+
expect.any(Function),
|
|
70
|
+
{ compressed: true }
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('never shares a cache entry with a regular widget of the same slug', () => {
|
|
75
|
+
expect(CacheKey.Widget(`collection:${SLUG}`)).not.toBe(
|
|
76
|
+
CacheKey.Widget(SLUG)
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('honours an explicit locale, currency and cache options', async () => {
|
|
81
|
+
await getCollectionWidgetData({
|
|
82
|
+
slug: SLUG,
|
|
83
|
+
locale: 'en',
|
|
84
|
+
currency: 'usd',
|
|
85
|
+
cacheOptions: { expire: 30 }
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
expect(wrap).toHaveBeenCalledWith(
|
|
89
|
+
`widget_collection:${SLUG}`,
|
|
90
|
+
'en',
|
|
91
|
+
expect.any(Function),
|
|
92
|
+
{ compressed: true, expire: 30 }
|
|
93
|
+
);
|
|
94
|
+
expect(appFetch).toHaveBeenCalledWith({
|
|
95
|
+
url: `/product_collection_widgets/${SLUG}/`,
|
|
96
|
+
locale: 'en',
|
|
97
|
+
currency: 'usd',
|
|
98
|
+
init: {}
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('serves a cached answer without calling the api', async () => {
|
|
103
|
+
wrap.mockResolvedValue(COLLECTION_WIDGET);
|
|
104
|
+
|
|
105
|
+
await expect(getCollectionWidgetData({ slug: SLUG })).resolves.toEqual(
|
|
106
|
+
COLLECTION_WIDGET
|
|
107
|
+
);
|
|
108
|
+
expect(appFetch).not.toHaveBeenCalled();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('answers null for an empty slug without calling the api', async () => {
|
|
112
|
+
await expect(getCollectionWidgetData({ slug: '' })).resolves.toBeNull();
|
|
113
|
+
expect(appFetch).not.toHaveBeenCalled();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('is cached the way the widget answer is', async () => {
|
|
117
|
+
await getWidgetData({ slug: SLUG });
|
|
118
|
+
await getCollectionWidgetData({ slug: SLUG });
|
|
119
|
+
|
|
120
|
+
const [widgetOptions, collectionOptions] = wrap.mock.calls.map(
|
|
121
|
+
(call) => call[3]
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
expect(widgetOptions).toEqual(collectionOptions);
|
|
125
|
+
});
|
|
126
|
+
});
|
package/data/server/widget.ts
CHANGED
|
@@ -265,6 +265,55 @@ export const getWidgetData = async <T>({
|
|
|
265
265
|
);
|
|
266
266
|
};
|
|
267
267
|
|
|
268
|
+
const getCollectionWidgetDataHandler =
|
|
269
|
+
(
|
|
270
|
+
slug: string,
|
|
271
|
+
locale: string,
|
|
272
|
+
currency: string,
|
|
273
|
+
next?: { revalidate: number }
|
|
274
|
+
) =>
|
|
275
|
+
async () => {
|
|
276
|
+
if (!slug) {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return await appFetch({
|
|
281
|
+
url: widgets.getCollectionWidget(slug),
|
|
282
|
+
locale,
|
|
283
|
+
currency,
|
|
284
|
+
init: next ? { next } : {}
|
|
285
|
+
});
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* A collection-backed widget (`product_collection_widgets`): what a theme
|
|
290
|
+
* section bound to a product collection renders, with the collection's live
|
|
291
|
+
* rows. Cached like the widget answer. Like getWidgetSchemaData on this line,
|
|
292
|
+
* it skips main's preview-copy lookup: the theme editor writes no preview
|
|
293
|
+
* copies of collection widgets (see ZERO-5149).
|
|
294
|
+
*/
|
|
295
|
+
export const getCollectionWidgetData = async <T>({
|
|
296
|
+
slug,
|
|
297
|
+
locale = ServerVariables.locale,
|
|
298
|
+
currency = ServerVariables.currency,
|
|
299
|
+
cacheOptions
|
|
300
|
+
}: {
|
|
301
|
+
slug: string;
|
|
302
|
+
locale?: string;
|
|
303
|
+
currency?: string;
|
|
304
|
+
cacheOptions?: CacheOptions;
|
|
305
|
+
}): Promise<WidgetResultType<T>> => {
|
|
306
|
+
return Cache.wrap(
|
|
307
|
+
CacheKey.Widget(`collection:${slug}`),
|
|
308
|
+
locale,
|
|
309
|
+
getCollectionWidgetDataHandler(slug, locale, currency),
|
|
310
|
+
{
|
|
311
|
+
compressed: true,
|
|
312
|
+
...cacheOptions
|
|
313
|
+
}
|
|
314
|
+
);
|
|
315
|
+
};
|
|
316
|
+
|
|
268
317
|
const getWidgetSchemaDataHandler =
|
|
269
318
|
(
|
|
270
319
|
widgetSlug: string,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akinon/next",
|
|
3
3
|
"description": "Core package for Project Zero Next",
|
|
4
|
-
"version": "1.126.
|
|
4
|
+
"version": "1.126.19",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"set-cookie-parser": "2.6.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@akinon/eslint-plugin-projectzero": "1.126.
|
|
38
|
+
"@akinon/eslint-plugin-projectzero": "1.126.19",
|
|
39
39
|
"@babel/core": "7.26.10",
|
|
40
40
|
"@babel/preset-env": "7.26.9",
|
|
41
41
|
"@babel/preset-typescript": "7.27.0",
|