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