swup 3.1.1 → 4.0.0-rc.20

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 (79) hide show
  1. package/README.md +94 -0
  2. package/dist/Swup.cjs +1 -1
  3. package/dist/Swup.cjs.map +1 -1
  4. package/dist/Swup.modern.js +1 -1
  5. package/dist/Swup.modern.js.map +1 -1
  6. package/dist/Swup.module.js +1 -1
  7. package/dist/Swup.module.js.map +1 -1
  8. package/dist/Swup.umd.js +1 -1
  9. package/dist/Swup.umd.js.map +1 -1
  10. package/dist/types/Swup.d.ts +53 -45
  11. package/dist/types/helpers/Location.d.ts +10 -7
  12. package/dist/types/helpers/delegateEvent.d.ts +2 -2
  13. package/dist/types/helpers/matchPath.d.ts +3 -0
  14. package/dist/types/helpers.d.ts +1 -4
  15. package/dist/types/index.d.ts +7 -4
  16. package/dist/types/modules/Cache.d.ts +14 -14
  17. package/dist/types/modules/Classes.d.ts +13 -0
  18. package/dist/types/modules/Context.d.ts +73 -0
  19. package/dist/types/modules/Hooks.d.ts +241 -0
  20. package/dist/types/modules/__test__/cache.test.d.ts +1 -0
  21. package/dist/types/modules/__test__/hooks.test.d.ts +1 -0
  22. package/dist/types/modules/__test__/replaceContent.test.d.ts +1 -0
  23. package/dist/types/modules/awaitAnimations.d.ts +21 -0
  24. package/dist/types/modules/enterPage.d.ts +5 -2
  25. package/dist/types/modules/fetchPage.d.ts +23 -3
  26. package/dist/types/modules/getAnchorElement.d.ts +2 -1
  27. package/dist/types/modules/leavePage.d.ts +5 -2
  28. package/dist/types/modules/plugins.d.ts +7 -0
  29. package/dist/types/modules/renderPage.d.ts +6 -6
  30. package/dist/types/modules/replaceContent.d.ts +8 -11
  31. package/dist/types/modules/visit.d.ts +33 -0
  32. package/dist/types/utils/index.d.ts +3 -1
  33. package/package.json +13 -9
  34. package/src/Swup.ts +172 -182
  35. package/src/__test__/index.test.ts +8 -3
  36. package/src/helpers/Location.ts +12 -9
  37. package/src/helpers/__test__/matchPath.test.ts +54 -0
  38. package/src/helpers/delegateEvent.ts +3 -2
  39. package/src/helpers/matchPath.ts +22 -0
  40. package/src/helpers.ts +2 -5
  41. package/src/index.ts +36 -4
  42. package/src/modules/Cache.ts +43 -33
  43. package/src/modules/Classes.ts +48 -0
  44. package/src/modules/Context.ts +121 -0
  45. package/src/modules/Hooks.ts +413 -0
  46. package/src/modules/__test__/cache.test.ts +142 -0
  47. package/src/modules/__test__/hooks.test.ts +263 -0
  48. package/src/modules/__test__/replaceContent.test.ts +92 -0
  49. package/src/modules/awaitAnimations.ts +169 -0
  50. package/src/modules/enterPage.ts +23 -17
  51. package/src/modules/fetchPage.ts +74 -29
  52. package/src/modules/getAnchorElement.ts +2 -1
  53. package/src/modules/leavePage.ts +26 -20
  54. package/src/modules/plugins.ts +7 -2
  55. package/src/modules/renderPage.ts +52 -33
  56. package/src/modules/replaceContent.ts +33 -16
  57. package/src/modules/visit.ts +143 -0
  58. package/src/utils/index.ts +25 -5
  59. package/dist/types/helpers/cleanupAnimationClasses.d.ts +0 -2
  60. package/dist/types/helpers/fetch.d.ts +0 -5
  61. package/dist/types/helpers/getDataFromHtml.d.ts +0 -7
  62. package/dist/types/helpers/markSwupElements.d.ts +0 -1
  63. package/dist/types/modules/events.d.ts +0 -33
  64. package/dist/types/modules/getAnimationPromises.d.ts +0 -7
  65. package/dist/types/modules/getPageData.d.ts +0 -6
  66. package/dist/types/modules/loadPage.d.ts +0 -15
  67. package/dist/types/modules/transitions.d.ts +0 -6
  68. package/readme.md +0 -60
  69. package/src/helpers/cleanupAnimationClasses.ts +0 -8
  70. package/src/helpers/fetch.ts +0 -33
  71. package/src/helpers/getDataFromHtml.ts +0 -39
  72. package/src/helpers/markSwupElements.ts +0 -16
  73. package/src/modules/__test__/events.test.ts +0 -72
  74. package/src/modules/events.ts +0 -92
  75. package/src/modules/getAnimationPromises.ts +0 -183
  76. package/src/modules/getPageData.ts +0 -24
  77. package/src/modules/loadPage.ts +0 -81
  78. package/src/modules/transitions.ts +0 -10
  79. /package/dist/types/{modules/__test__/events.test.d.ts → helpers/__test__/matchPath.test.d.ts} +0 -0
@@ -0,0 +1,413 @@
1
+ import { DelegateEvent } from 'delegate-it';
2
+
3
+ import Swup, { Options } from '../Swup.js';
4
+ import { isPromise, runAsPromise } from '../utils.js';
5
+ import { Context } from './Context.js';
6
+ import { FetchOptions, PageData } from './fetchPage.js';
7
+ import { AnimationDirection } from './awaitAnimations.js';
8
+
9
+ export interface HookDefinitions {
10
+ 'animation:out:start': undefined;
11
+ 'animation:out:end': undefined;
12
+ 'animation:in:start': undefined;
13
+ 'animation:in:end': undefined;
14
+ 'animation:skip': undefined;
15
+ 'animation:await': { direction: AnimationDirection };
16
+ 'cache:clear': undefined;
17
+ 'cache:set': { page: PageData };
18
+ 'content:replace': { page: PageData };
19
+ 'content:scroll': { options: ScrollIntoViewOptions };
20
+ 'enable': undefined;
21
+ 'disable': undefined;
22
+ 'fetch:request': { url: string; options: FetchOptions };
23
+ 'fetch:error': { url: string; status: number; response: Response };
24
+ 'history:popstate': { event: PopStateEvent };
25
+ 'link:click': { el: HTMLAnchorElement; event: DelegateEvent<MouseEvent> };
26
+ 'link:self': undefined;
27
+ 'link:anchor': { hash: string; options: ScrollIntoViewOptions };
28
+ 'link:newtab': { href: string };
29
+ 'page:request': { url: string; options: FetchOptions };
30
+ 'page:load': { page: PageData; cache?: boolean };
31
+ 'page:view': { url: string; title: string };
32
+ 'visit:start': undefined;
33
+ 'visit:end': undefined;
34
+ }
35
+
36
+ export type HookArguments<T extends HookName> = HookDefinitions[T];
37
+
38
+ export type HookName = keyof HookDefinitions;
39
+
40
+ export type Handler<T extends HookName> = (
41
+ /** The global context object for the current visit */
42
+ context: Context,
43
+ /** The local arguments passed into the handler */
44
+ args: HookArguments<T>,
45
+ /** The default handler to be executed, available if replacing an internal hook handler */
46
+ defaultHandler?: Handler<T>
47
+ ) => Promise<any> | void;
48
+
49
+ export type Handlers = {
50
+ [K in HookName]: Handler<K>[];
51
+ };
52
+
53
+ export type HookUnregister = () => void;
54
+
55
+ export type HookOptions = {
56
+ /** Execute the hook once, then remove the handler */
57
+ once?: boolean;
58
+ /** Execute the hook before the internal default handler */
59
+ before?: boolean;
60
+ /** Set a priority for when to execute this hook. Lower numbers execute first. Default: `0` */
61
+ priority?: number;
62
+ /** Replace the internal default handler with this hook handler */
63
+ replace?: boolean;
64
+ };
65
+
66
+ export type HookRegistration<T extends HookName> = {
67
+ id: number;
68
+ hook: T;
69
+ handler: Handler<T>;
70
+ } & HookOptions;
71
+
72
+ type HookLedger<T extends HookName> = Map<Handler<T>, HookRegistration<T>>;
73
+
74
+ interface HookRegistry extends Map<HookName, HookLedger<HookName>> {
75
+ get<K extends HookName>(key: K): HookLedger<K> | undefined;
76
+ set<K extends HookName>(key: K, value: HookLedger<K>): this;
77
+ }
78
+
79
+ /**
80
+ * Hook registry.
81
+ *
82
+ * Create, trigger and handle hooks.
83
+ *
84
+ */
85
+ export class Hooks {
86
+ protected swup: Swup;
87
+ protected registry: HookRegistry = new Map();
88
+
89
+ // Can we deduplicate this somehow? Or make it error when not in sync with HookDefinitions?
90
+ // https://stackoverflow.com/questions/53387838/how-to-ensure-an-arrays-values-the-keys-of-a-typescript-interface/53395649
91
+ readonly hooks: HookName[] = [
92
+ 'animation:out:start',
93
+ 'animation:out:end',
94
+ 'animation:in:start',
95
+ 'animation:in:end',
96
+ 'animation:skip',
97
+ 'animation:await',
98
+ 'cache:clear',
99
+ 'cache:set',
100
+ 'content:replace',
101
+ 'content:scroll',
102
+ 'enable',
103
+ 'disable',
104
+ 'fetch:request',
105
+ 'fetch:error',
106
+ 'history:popstate',
107
+ 'link:click',
108
+ 'link:self',
109
+ 'link:anchor',
110
+ 'link:newtab',
111
+ 'page:request',
112
+ 'page:load',
113
+ 'page:view',
114
+ 'visit:start',
115
+ 'visit:end'
116
+ ];
117
+
118
+ constructor(swup: Swup) {
119
+ this.swup = swup;
120
+ this.init();
121
+ }
122
+
123
+ /**
124
+ * Create ledgers for all core hooks.
125
+ */
126
+ protected init() {
127
+ this.hooks.forEach((hook) => this.create(hook));
128
+ }
129
+
130
+ /**
131
+ * Register a new hook.
132
+ */
133
+ create(hook: HookName) {
134
+ if (!this.registry.has(hook)) {
135
+ this.registry.set(hook, new Map());
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Check if a hook is registered.
141
+ */
142
+ has(hook: HookName): boolean {
143
+ return this.registry.has(hook);
144
+ }
145
+
146
+ /**
147
+ * Get the ledger with all registrations for a hook.
148
+ */
149
+ protected get<T extends HookName>(hook: T): HookLedger<T> | undefined {
150
+ const ledger = this.registry.get(hook);
151
+ if (ledger) {
152
+ return ledger;
153
+ }
154
+ console.error(`Unknown hook '${hook}'`);
155
+ }
156
+
157
+ /**
158
+ * Remove all handlers of all hooks.
159
+ */
160
+ clear() {
161
+ this.registry.forEach((ledger) => ledger.clear());
162
+ }
163
+
164
+ /**
165
+ * Register a new hook handler.
166
+ * @param hook Name of the hook to listen for
167
+ * @param handler The handler function to execute
168
+ * @param options Object to specify how and when the handler is executed
169
+ * Available options:
170
+ * - `once`: Only execute the handler once
171
+ * - `before`: Execute the handler before the default handler
172
+ * - `priority`: Specify the order in which the handlers are executed
173
+ * - `replace`: Replace the default handler with this handler
174
+ * @returns A function to unregister the handler
175
+ */
176
+ on<T extends HookName>(hook: T, handler: Handler<T>): HookUnregister;
177
+ on<T extends HookName>(hook: T, handler: Handler<T>, options: HookOptions): HookUnregister;
178
+ on<T extends HookName>(
179
+ hook: T,
180
+ handler: Handler<T>,
181
+ options: HookOptions = {}
182
+ ): HookUnregister {
183
+ const ledger = this.get(hook);
184
+ if (!ledger) {
185
+ console.warn(`Hook '${hook}' not found.`);
186
+ return () => {};
187
+ }
188
+
189
+ const id = ledger.size + 1;
190
+ const registration: HookRegistration<T> = { ...options, id, hook, handler };
191
+ ledger.set(handler, registration);
192
+
193
+ return () => this.off(hook, handler);
194
+ }
195
+
196
+ /**
197
+ * Register a new hook handler to run before the default handler.
198
+ * Shortcut for `hooks.on(hook, handler, { before: true })`.
199
+ * @param hook Name of the hook to listen for
200
+ * @param handler The handler function to execute
201
+ * @param options Any other event options (see `hooks.on()` for details)
202
+ * @returns A function to unregister the handler
203
+ * @see on
204
+ */
205
+ before<T extends HookName>(hook: T, handler: Handler<T>): HookUnregister;
206
+ before<T extends HookName>(hook: T, handler: Handler<T>, options: HookOptions): HookUnregister;
207
+ before<T extends HookName>(
208
+ hook: T,
209
+ handler: Handler<T>,
210
+ options: HookOptions = {}
211
+ ): HookUnregister {
212
+ return this.on(hook, handler, { ...options, before: true });
213
+ }
214
+
215
+ /**
216
+ * Register a new hook handler to replace the default handler.
217
+ * Shortcut for `hooks.on(hook, handler, { replace: true })`.
218
+ * @param hook Name of the hook to listen for
219
+ * @param handler The handler function to execute instead of the default handler
220
+ * @param options Any other event options (see `hooks.on()` for details)
221
+ * @returns A function to unregister the handler
222
+ * @see on
223
+ */
224
+ replace<T extends HookName>(hook: T, handler: Handler<T>): HookUnregister;
225
+ replace<T extends HookName>(hook: T, handler: Handler<T>, options: HookOptions): HookUnregister;
226
+ replace<T extends HookName>(
227
+ hook: T,
228
+ handler: Handler<T>,
229
+ options: HookOptions = {}
230
+ ): HookUnregister {
231
+ return this.on(hook, handler, { ...options, replace: true });
232
+ }
233
+
234
+ /**
235
+ * Register a new hook handler to run once.
236
+ * Shortcut for `hooks.on(hook, handler, { once: true })`.
237
+ * @param hook Name of the hook to listen for
238
+ * @param handler The handler function to execute
239
+ * @param options Any other event options (see `hooks.on()` for details)
240
+ * @see on
241
+ */
242
+ once<T extends HookName>(hook: T, handler: Handler<T>): HookUnregister;
243
+ once<T extends HookName>(hook: T, handler: Handler<T>, options: HookOptions): HookUnregister;
244
+ once<T extends HookName>(
245
+ hook: T,
246
+ handler: Handler<T>,
247
+ options: HookOptions = {}
248
+ ): HookUnregister {
249
+ return this.on(hook, handler, { ...options, once: true });
250
+ }
251
+
252
+ /**
253
+ * Unregister a hook handler.
254
+ * @param hook Name of the hook the handler is registered for
255
+ * @param handler The handler function that was registered.
256
+ * If omitted, all handlers for the hook will be removed.
257
+ */
258
+ off<T extends HookName>(hook: T): void;
259
+ off<T extends HookName>(hook: T, handler: Handler<T>): void;
260
+ off<T extends HookName>(hook: T, handler?: Handler<T>): void {
261
+ const ledger = this.get(hook);
262
+ if (ledger && handler) {
263
+ const deleted = ledger.delete(handler);
264
+ if (!deleted) {
265
+ console.warn(`Handler for hook '${hook}' not found.`);
266
+ }
267
+ } else if (ledger) {
268
+ ledger.clear();
269
+ }
270
+ }
271
+
272
+ /**
273
+ * Trigger a hook asynchronously, executing its default handler and all registered handlers.
274
+ * Will execute all handlers in order and `await` any `Promise`s they return.
275
+ * @param hook Name of the hook to trigger
276
+ * @param args Arguments to pass to the handler
277
+ * @param defaultHandler A default implementation of this hook to execute
278
+ * @returns The resolved return value of the executed default handler
279
+ */
280
+ async trigger<T extends HookName>(
281
+ hook: T,
282
+ args?: HookArguments<T>,
283
+ defaultHandler?: Handler<T>
284
+ ): Promise<any> {
285
+ const { before, handler, after, replaced } = this.getHandlers(hook, defaultHandler);
286
+ await this.execute(before, args);
287
+ const [result] = await this.execute(handler, args, replaced ? defaultHandler : undefined);
288
+ await this.execute(after, args);
289
+ this.dispatchDomEvent(hook, args);
290
+ return result;
291
+ }
292
+
293
+ /**
294
+ * Trigger a hook synchronously, executing its default handler and all registered handlers.
295
+ * Will execute all handlers in order, but will **not** `await` any `Promise`s they return.
296
+ * @param hook Name of the hook to trigger
297
+ * @param args Arguments to pass to the handler
298
+ * @param defaultHandler A default implementation of this hook to execute
299
+ * @returns The (possibly unresolved) return value of the executed default handler
300
+ */
301
+ triggerSync<T extends HookName>(
302
+ hook: T,
303
+ args?: HookArguments<T>,
304
+ defaultHandler?: Handler<T>
305
+ ): any {
306
+ const { before, after, handler, replaced } = this.getHandlers(hook, defaultHandler);
307
+ this.executeSync(before, args);
308
+ const [result] = this.executeSync(handler, args, replaced ? defaultHandler : undefined);
309
+ this.executeSync(after, args);
310
+ this.dispatchDomEvent(hook, args);
311
+ return result;
312
+ }
313
+
314
+ /**
315
+ * Execute the handlers for a hook, in order, as `Promise`s that will be `await`ed.
316
+ * @param registrations The registrations (handler + options) to execute
317
+ * @param args Arguments to pass to the handler
318
+ */
319
+ async execute<T extends HookName>(
320
+ registrations: HookRegistration<T>[],
321
+ args?: HookArguments<T>,
322
+ defaultHandler?: Handler<T>
323
+ ): Promise<any> {
324
+ const results = [];
325
+ for (const { hook, handler, once } of registrations) {
326
+ const result = await runAsPromise(handler, [this.swup.context, args, defaultHandler]);
327
+ results.push(result);
328
+ if (once) {
329
+ this.off(hook, handler);
330
+ }
331
+ }
332
+ return results;
333
+ }
334
+
335
+ /**
336
+ * Execute the handlers for a hook, in order, without `await`ing any returned `Promise`s.
337
+ * @param registrations The registrations (handler + options) to execute
338
+ * @param args Arguments to pass to the handler
339
+ */
340
+ executeSync<T extends HookName>(
341
+ registrations: HookRegistration<T>[],
342
+ args?: HookArguments<T>,
343
+ defaultHandler?: Handler<T>
344
+ ): any[] {
345
+ const results = [];
346
+ for (const { hook, handler, once } of registrations) {
347
+ const result = handler(this.swup.context, args as HookArguments<T>, defaultHandler);
348
+ results.push(result);
349
+ if (isPromise(result)) {
350
+ console.warn(
351
+ `Promise returned from handler for synchronous hook '${hook}'.` +
352
+ `Swup will not wait for it to resolve.`
353
+ );
354
+ }
355
+ if (once) {
356
+ this.off(hook, handler);
357
+ }
358
+ }
359
+ return results;
360
+ }
361
+
362
+ /**
363
+ * Get all registered handlers for a hook, sorted by priority and registration order.
364
+ * @param hook Name of the hook
365
+ * @param defaultHandler The optional default handler of this hook
366
+ * @returns An object with the handlers sorted into `before` and `after` arrays,
367
+ * as well as a flag indicating if the original handler was replaced
368
+ */
369
+ getHandlers<T extends HookName>(hook: T, defaultHandler?: Handler<T>) {
370
+ const ledger = this.get(hook);
371
+ if (!ledger) {
372
+ return { found: false, before: [], handler: [], after: [], replaced: false };
373
+ }
374
+
375
+ const sort = this.sortRegistrations;
376
+ const registrations = Array.from(ledger.values());
377
+
378
+ const before = registrations.filter(({ before, replace }) => before && !replace).sort(sort);
379
+ const replace = registrations.filter(({ replace }) => replace).sort(sort);
380
+ const after = registrations.filter(({ before, replace }) => !before && !replace).sort(sort);
381
+ const replaced = replace.length > 0;
382
+
383
+ let handler: HookRegistration<T>[] = [];
384
+ if (replaced) {
385
+ handler = [{ id: 0, hook, handler: replace[0].handler }];
386
+ } else if (defaultHandler) {
387
+ handler = [{ id: 0, hook, handler: defaultHandler }];
388
+ }
389
+
390
+ return { found: true, before, handler, after, replaced };
391
+ }
392
+
393
+ /**
394
+ * Sort two hook registrations by priority and registration order.
395
+ * @param a The registration object to compare
396
+ * @param b The other registration object to compare with
397
+ * @returns The sort direction
398
+ */
399
+ sortRegistrations<T extends HookName>(a: HookRegistration<T>, b: HookRegistration<T>): number {
400
+ const priority = (a.priority ?? 0) - (b.priority ?? 0);
401
+ const id = a.id - b.id;
402
+ return priority || id || 0;
403
+ }
404
+
405
+ /**
406
+ * Trigger a custom event on the `document`. Prefixed with `swup:`
407
+ * @param hook Name of the hook to trigger.
408
+ */
409
+ dispatchDomEvent<T extends HookName>(hook: T, args?: HookArguments<T>): void {
410
+ const detail = { hook, args, context: this.swup.context };
411
+ document.dispatchEvent(new CustomEvent(`swup:${hook}`, { detail }));
412
+ }
413
+ }
@@ -0,0 +1,142 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import Swup from '../../Swup.js';
3
+ import { Cache, CacheData } from '../Cache.js';
4
+ import { Context } from '../Context.js';
5
+
6
+ interface CacheTtlData {
7
+ ttl: number;
8
+ created: number;
9
+ }
10
+
11
+ interface CacheIndexData {
12
+ index: number;
13
+ }
14
+
15
+ interface AugmentedCacheData extends CacheData, CacheTtlData, CacheIndexData {}
16
+
17
+ const swup = new Swup();
18
+ const ctx = swup.context;
19
+ const cache = new Cache(swup);
20
+
21
+ const page1 = { url: '/page-1', html: '1' };
22
+ const page2 = { url: '/page-2', html: '2' };
23
+ const page3 = { url: '/page-3', html: '3' };
24
+
25
+ describe('Cache', () => {
26
+ beforeEach(() => {
27
+ cache.clear();
28
+ });
29
+
30
+ it('should be empty', () => {
31
+ expect(cache.size).toBe(0);
32
+ });
33
+
34
+ it('should append pages', () => {
35
+ cache.set(page1.url, page1);
36
+ expect(cache.size).toBe(1);
37
+ });
38
+
39
+ it('should have pages', () => {
40
+ cache.set(page1.url, page1);
41
+ expect(cache.has(page1.url)).toBe(true);
42
+ });
43
+
44
+ it('should get pages', () => {
45
+ cache.set(page1.url, page1);
46
+ expect(cache.get(page1.url)).toEqual(page1);
47
+ });
48
+
49
+ it('should delete pages', () => {
50
+ cache.set(page1.url, page1);
51
+ expect(cache.has(page1.url)).toBe(true);
52
+ cache.delete(page1.url);
53
+ expect(cache.has(page1.url)).toBe(false);
54
+ });
55
+
56
+ it('should clear', () => {
57
+ cache.set(page1.url, page1);
58
+ expect(cache.size).toBe(1);
59
+ cache.clear();
60
+ expect(cache.size).toBe(0);
61
+ });
62
+
63
+ it('should overwrite identical pages', () => {
64
+ cache.set(page1.url, page1);
65
+ expect(cache.size).toBe(1);
66
+ cache.set(page1.url, page1);
67
+ expect(cache.size).toBe(1);
68
+ });
69
+
70
+ it('should not overwrite different pages', () => {
71
+ cache.set(page1.url, page1);
72
+ expect(cache.size).toBe(1);
73
+ cache.set(page2.url, page2);
74
+ expect(cache.size).toBe(2);
75
+ });
76
+
77
+ it('should trigger a hook on set', () => {
78
+ const handler = vi.fn();
79
+
80
+ swup.hooks.on('cache:set', handler);
81
+
82
+ cache.set(page1.url, page1);
83
+
84
+ expect(handler).toBeCalledTimes(1);
85
+ expect(handler).toBeCalledWith(ctx, { page: page1 }, undefined);
86
+ });
87
+
88
+ it('should allow augmenting cache entries on save', () => {
89
+ const now = Date.now();
90
+
91
+ swup.hooks.on('cache:set', (_, { page }) => {
92
+ const ttl: CacheTtlData = { ttl: 1000, created: now };
93
+ cache.update(page.url, ttl as AugmentedCacheData);
94
+ });
95
+
96
+ cache.set('/page', { url: '/page', html: '' });
97
+
98
+ const page = cache.get('/page') as AugmentedCacheData;
99
+
100
+ expect(page).toEqual({ url: '/page', html: '', ttl: 1000, created: now });
101
+ });
102
+
103
+ it('should allow manual pruning', () => {
104
+ swup.hooks.on('cache:set', (_, { page }) => {
105
+ cache.update(page.url, { index: cache.size } as AugmentedCacheData);
106
+ });
107
+
108
+ cache.set(page1.url, page1);
109
+ cache.set(page2.url, page2);
110
+ cache.set(page3.url, page3);
111
+
112
+ cache.prune((url, page) => (page as AugmentedCacheData).index > 2);
113
+
114
+ expect(cache.size).toBe(2);
115
+ expect(cache.has(page1.url)).toBe(true);
116
+ expect(cache.has(page2.url)).toBe(true);
117
+ expect(cache.has(page3.url)).toBe(false);
118
+ });
119
+ });
120
+
121
+ describe('Types', () => {
122
+ it('error when necessary', async () => {
123
+ const swup = new Swup();
124
+ const cache = new Cache(swup);
125
+
126
+ // @ts-expect-no-error
127
+ swup.hooks.on('history:popstate', (ctx: Context, { event: PopStateEvent }) => {});
128
+ // @ts-expect-no-error
129
+ await swup.hooks.trigger('history:popstate', { event: new PopStateEvent('') });
130
+
131
+ try {
132
+ // @ts-expect-error
133
+ cache.set();
134
+ // @ts-expect-error
135
+ cache.set(url);
136
+ // @ts-expect-error
137
+ cache.set(url, {});
138
+ // @ts-expect-error
139
+ cache.set({ url: '/test' });
140
+ } catch (error) {}
141
+ });
142
+ });