swup 4.3.4 → 4.4.1

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 (99) hide show
  1. package/dist/Swup.cjs +1 -1
  2. package/dist/Swup.cjs.map +1 -1
  3. package/dist/Swup.modern.js +1 -1
  4. package/dist/Swup.modern.js.map +1 -1
  5. package/dist/Swup.module.js +1 -1
  6. package/dist/Swup.module.js.map +1 -1
  7. package/dist/Swup.umd.js +1 -1
  8. package/dist/Swup.umd.js.map +1 -1
  9. package/dist/types/Swup.d.ts +10 -5
  10. package/dist/types/Swup.d.ts.map +1 -0
  11. package/dist/types/config/version.d.ts +1 -0
  12. package/dist/types/config/version.d.ts.map +1 -0
  13. package/dist/types/helpers/Location.d.ts +1 -0
  14. package/dist/types/helpers/Location.d.ts.map +1 -0
  15. package/dist/types/helpers/classify.d.ts +1 -0
  16. package/dist/types/helpers/classify.d.ts.map +1 -0
  17. package/dist/types/helpers/createHistoryRecord.d.ts +1 -0
  18. package/dist/types/helpers/createHistoryRecord.d.ts.map +1 -0
  19. package/dist/types/helpers/delegateEvent.d.ts +3 -2
  20. package/dist/types/helpers/delegateEvent.d.ts.map +1 -0
  21. package/dist/types/helpers/getCurrentUrl.d.ts +1 -0
  22. package/dist/types/helpers/getCurrentUrl.d.ts.map +1 -0
  23. package/dist/types/helpers/matchPath.d.ts +2 -1
  24. package/dist/types/helpers/matchPath.d.ts.map +1 -0
  25. package/dist/types/helpers/updateHistoryRecord.d.ts +1 -0
  26. package/dist/types/helpers/updateHistoryRecord.d.ts.map +1 -0
  27. package/dist/types/helpers.d.ts +1 -0
  28. package/dist/types/helpers.d.ts.map +1 -0
  29. package/dist/types/index.d.ts +1 -0
  30. package/dist/types/index.d.ts.map +1 -0
  31. package/dist/types/modules/Cache.d.ts +3 -2
  32. package/dist/types/modules/Cache.d.ts.map +1 -0
  33. package/dist/types/modules/Classes.d.ts +2 -1
  34. package/dist/types/modules/Classes.d.ts.map +1 -0
  35. package/dist/types/modules/Hooks.d.ts +10 -4
  36. package/dist/types/modules/Hooks.d.ts.map +1 -0
  37. package/dist/types/modules/Visit.d.ts +4 -2
  38. package/dist/types/modules/Visit.d.ts.map +1 -0
  39. package/dist/types/modules/animatePageIn.d.ts +2 -1
  40. package/dist/types/modules/animatePageIn.d.ts.map +1 -0
  41. package/dist/types/modules/animatePageOut.d.ts +2 -1
  42. package/dist/types/modules/animatePageOut.d.ts.map +1 -0
  43. package/dist/types/modules/awaitAnimations.d.ts +3 -1
  44. package/dist/types/modules/awaitAnimations.d.ts.map +1 -0
  45. package/dist/types/modules/fetchPage.d.ts +10 -3
  46. package/dist/types/modules/fetchPage.d.ts.map +1 -0
  47. package/dist/types/modules/getAnchorElement.d.ts +1 -0
  48. package/dist/types/modules/getAnchorElement.d.ts.map +1 -0
  49. package/dist/types/modules/navigate.d.ts +4 -3
  50. package/dist/types/modules/navigate.d.ts.map +1 -0
  51. package/dist/types/modules/plugins.d.ts +2 -1
  52. package/dist/types/modules/plugins.d.ts.map +1 -0
  53. package/dist/types/modules/renderPage.d.ts +3 -2
  54. package/dist/types/modules/renderPage.d.ts.map +1 -0
  55. package/dist/types/modules/replaceContent.d.ts +4 -2
  56. package/dist/types/modules/replaceContent.d.ts.map +1 -0
  57. package/dist/types/modules/resolveUrl.d.ts +2 -1
  58. package/dist/types/modules/resolveUrl.d.ts.map +1 -0
  59. package/dist/types/modules/scrollToContent.d.ts +2 -1
  60. package/dist/types/modules/scrollToContent.d.ts.map +1 -0
  61. package/dist/types/utils/index.d.ts +1 -0
  62. package/dist/types/utils/index.d.ts.map +1 -0
  63. package/dist/types/utils.d.ts +1 -0
  64. package/dist/types/utils.d.ts.map +1 -0
  65. package/package.json +13 -17
  66. package/src/Swup.ts +18 -7
  67. package/src/helpers/delegateEvent.ts +6 -2
  68. package/src/helpers/matchPath.ts +1 -1
  69. package/src/helpers/updateHistoryRecord.ts +1 -1
  70. package/src/modules/Cache.ts +2 -2
  71. package/src/modules/Classes.ts +1 -1
  72. package/src/modules/Hooks.ts +9 -4
  73. package/src/modules/Visit.ts +3 -2
  74. package/src/modules/animatePageIn.ts +1 -1
  75. package/src/modules/animatePageOut.ts +1 -1
  76. package/src/modules/awaitAnimations.ts +2 -1
  77. package/src/modules/fetchPage.ts +48 -9
  78. package/src/modules/navigate.ts +26 -17
  79. package/src/modules/plugins.ts +1 -1
  80. package/src/modules/renderPage.ts +2 -2
  81. package/src/modules/replaceContent.ts +3 -2
  82. package/src/modules/resolveUrl.ts +1 -1
  83. package/src/modules/scrollToContent.ts +1 -1
  84. package/dist/types/__test__/index.test.d.ts +0 -1
  85. package/dist/types/helpers/__test__/matchPath.test.d.ts +0 -1
  86. package/dist/types/modules/__test__/cache.test.d.ts +0 -1
  87. package/dist/types/modules/__test__/delegateEvent.test.d.ts +0 -1
  88. package/dist/types/modules/__test__/hooks.test.d.ts +0 -1
  89. package/dist/types/modules/__test__/plugins.test.d.ts +0 -1
  90. package/dist/types/modules/__test__/replaceContent.test.d.ts +0 -1
  91. package/dist/types/modules/__test__/visit.test.d.ts +0 -1
  92. package/src/__test__/index.test.ts +0 -83
  93. package/src/helpers/__test__/matchPath.test.ts +0 -54
  94. package/src/modules/__test__/cache.test.ts +0 -159
  95. package/src/modules/__test__/delegateEvent.test.ts +0 -36
  96. package/src/modules/__test__/hooks.test.ts +0 -319
  97. package/src/modules/__test__/plugins.test.ts +0 -89
  98. package/src/modules/__test__/replaceContent.test.ts +0 -91
  99. package/src/modules/__test__/visit.test.ts +0 -92
@@ -1,6 +1,6 @@
1
- import Swup from '../Swup.js';
1
+ import type Swup from '../Swup.js';
2
2
  import { Location } from '../helpers.js';
3
- import { PageData } from './fetchPage.js';
3
+ import { type PageData } from './fetchPage.js';
4
4
 
5
5
  export interface CacheData extends PageData {}
6
6
 
@@ -1,4 +1,4 @@
1
- import Swup from '../Swup.js';
1
+ import type Swup from '../Swup.js';
2
2
  import { queryAll } from '../utils.js';
3
3
 
4
4
  export class Classes {
@@ -1,9 +1,9 @@
1
- import { DelegateEvent } from 'delegate-it';
1
+ import type { DelegateEvent } from 'delegate-it';
2
2
 
3
- import Swup from '../Swup.js';
3
+ import type Swup from '../Swup.js';
4
4
  import { isPromise, runAsPromise } from '../utils.js';
5
- import { Visit } from './Visit.js';
6
- import { FetchOptions, PageData } from './fetchPage.js';
5
+ import type { Visit } from './Visit.js';
6
+ import type { FetchOptions, PageData } from './fetchPage.js';
7
7
 
8
8
  export interface HookDefinitions {
9
9
  'animation:out:start': undefined;
@@ -21,6 +21,7 @@ export interface HookDefinitions {
21
21
  'disable': undefined;
22
22
  'fetch:request': { url: string; options: FetchOptions };
23
23
  'fetch:error': { url: string; status: number; response: Response };
24
+ 'fetch:timeout': { url: string };
24
25
  'history:popstate': { event: PopStateEvent };
25
26
  'link:click': { el: HTMLAnchorElement; event: DelegateEvent<MouseEvent> };
26
27
  'link:self': undefined;
@@ -31,6 +32,7 @@ export interface HookDefinitions {
31
32
  'scroll:top': { options: ScrollIntoViewOptions };
32
33
  'scroll:anchor': { hash: string; options: ScrollIntoViewOptions };
33
34
  'visit:start': undefined;
35
+ 'visit:transition': undefined;
34
36
  'visit:end': undefined;
35
37
  }
36
38
 
@@ -40,6 +42,7 @@ export interface HookReturnValues {
40
42
  'page:load': Promise<PageData>;
41
43
  'scroll:top': boolean;
42
44
  'scroll:anchor': boolean;
45
+ 'visit:transition': Promise<boolean>;
43
46
  }
44
47
 
45
48
  export type HookArguments<T extends HookName> = HookDefinitions[T];
@@ -131,6 +134,7 @@ export class Hooks {
131
134
  'disable',
132
135
  'fetch:request',
133
136
  'fetch:error',
137
+ 'fetch:timeout',
134
138
  'history:popstate',
135
139
  'link:click',
136
140
  'link:self',
@@ -141,6 +145,7 @@ export class Hooks {
141
145
  'scroll:top',
142
146
  'scroll:anchor',
143
147
  'visit:start',
148
+ 'visit:transition',
144
149
  'visit:end'
145
150
  ];
146
151
 
@@ -1,5 +1,6 @@
1
- import Swup, { Options } from '../Swup.js';
2
- import { HistoryAction, HistoryDirection } from './navigate.js';
1
+ import type Swup from '../Swup.js';
2
+ import type { Options } from '../Swup.js';
3
+ import type { HistoryAction, HistoryDirection } from './navigate.js';
3
4
 
4
5
  /** An object holding details about the current visit. */
5
6
  export interface Visit {
@@ -1,4 +1,4 @@
1
- import Swup from '../Swup.js';
1
+ import type Swup from '../Swup.js';
2
2
  import { nextTick } from '../utils.js';
3
3
 
4
4
  /**
@@ -1,4 +1,4 @@
1
- import Swup from '../Swup.js';
1
+ import type Swup from '../Swup.js';
2
2
  import { classify } from '../helpers.js';
3
3
 
4
4
  /**
@@ -1,5 +1,6 @@
1
1
  import { queryAll, toMs } from '../utils.js';
2
- import Swup, { Options } from '../Swup.js';
2
+ import type Swup from '../Swup.js';
3
+ import type { Options } from '../Swup.js';
3
4
 
4
5
  const TRANSITION = 'transition';
5
6
  const ANIMATION = 'animation';
@@ -1,4 +1,4 @@
1
- import Swup from '../Swup.js';
1
+ import type Swup from '../Swup.js';
2
2
  import { Location } from '../helpers.js';
3
3
 
4
4
  /** A page object as used by swup and its cache. */
@@ -15,16 +15,25 @@ export interface FetchOptions extends Omit<RequestInit, 'cache'> {
15
15
  method?: 'GET' | 'POST';
16
16
  /** The body of the request: raw string, form data object or URL params. */
17
17
  body?: string | FormData | URLSearchParams;
18
+ /** The request timeout in milliseconds. */
19
+ timeout?: number;
18
20
  }
19
21
 
20
22
  export class FetchError extends Error {
21
23
  url: string;
22
- status: number;
23
- constructor(message: string, details: { url: string; status: number }) {
24
+ status?: number;
25
+ aborted: boolean;
26
+ timedOut: boolean;
27
+ constructor(
28
+ message: string,
29
+ details: { url: string; status?: number; aborted?: boolean; timedOut?: boolean }
30
+ ) {
24
31
  super(message);
25
32
  this.name = 'FetchError';
26
33
  this.url = details.url;
27
34
  this.status = details.status;
35
+ this.aborted = details.aborted || false;
36
+ this.timedOut = details.timedOut || false;
28
37
  }
29
38
  }
30
39
 
@@ -39,14 +48,44 @@ export async function fetchPage(
39
48
  url = Location.fromUrl(url).url;
40
49
 
41
50
  const headers = { ...this.options.requestHeaders, ...options.headers };
42
- options = { ...options, headers };
51
+ const timeout = options.timeout ?? this.options.timeout;
52
+ const controller = new AbortController();
53
+ const { signal } = controller;
54
+ options = { ...options, headers, signal };
55
+
56
+ let timedOut = false;
57
+ let timeoutId: ReturnType<typeof setTimeout> | null = null;
58
+ if (timeout && timeout > 0) {
59
+ timeoutId = setTimeout(() => {
60
+ timedOut = true;
61
+ controller.abort('timeout');
62
+ }, timeout);
63
+ }
43
64
 
44
65
  // Allow hooking before this and returning a custom response-like object (e.g. custom fetch implementation)
45
- const response: Response = await this.hooks.call(
46
- 'fetch:request',
47
- { url, options },
48
- (visit, { url, options }) => fetch(url, options)
49
- );
66
+ let response: Response;
67
+ try {
68
+ response = await this.hooks.call(
69
+ 'fetch:request',
70
+ { url, options },
71
+ (visit, { url, options }) => fetch(url, options)
72
+ );
73
+ if (timeoutId) {
74
+ clearTimeout(timeoutId);
75
+ }
76
+ } catch (error) {
77
+ if (timedOut) {
78
+ this.hooks.call('fetch:timeout', { url });
79
+ throw new FetchError(`Request timed out: ${url}`, { url, timedOut });
80
+ }
81
+ if ((error as Error)?.name === 'AbortError' || signal.aborted) {
82
+ throw new FetchError(`Request aborted: ${url}`, {
83
+ url: url,
84
+ aborted: true
85
+ });
86
+ }
87
+ throw error;
88
+ }
50
89
 
51
90
  const { status, url: responseUrl } = response;
52
91
  const html = await response.text();
@@ -1,7 +1,7 @@
1
- import Swup from '../Swup.js';
1
+ import type Swup from '../Swup.js';
2
2
  import { createHistoryRecord, updateHistoryRecord, getCurrentUrl, Location } from '../helpers.js';
3
- import { FetchOptions, PageData } from './fetchPage.js';
4
- import { VisitInitOptions } from './Visit.js';
3
+ import { FetchError, type FetchOptions, type PageData } from './fetchPage.js';
4
+ import type { VisitInitOptions } from './Visit.js';
5
5
 
6
6
  export type HistoryAction = 'push' | 'replace';
7
7
  export type HistoryDirection = 'forwards' | 'backwards';
@@ -62,6 +62,7 @@ export async function performNavigation(
62
62
  this: Swup,
63
63
  options: NavigationOptions & FetchOptions = {}
64
64
  ): Promise<void> {
65
+ this.navigating = true;
65
66
  // Save this localy to a) allow ignoring the visit if a new one was started in the meantime
66
67
  // and b) avoid unintended modifications to any newer visits
67
68
  const visit = this.visit;
@@ -137,20 +138,27 @@ export async function performNavigation(
137
138
  visit.to.html = html;
138
139
  }
139
140
 
140
- // Wait for page to load and leave animation to finish
141
- const animationPromise = this.animatePageOut();
142
- const [page] = await Promise.all([pagePromise, animationPromise]);
141
+ // perform the actual transition: animate and replace content
142
+ await this.hooks.call('visit:transition', undefined, async (visit) => {
143
+ // Start leave animation
144
+ const animationPromise = this.animatePageOut();
143
145
 
144
- // Abort if another visit was started in the meantime
145
- if (visit.id !== this.visit.id) {
146
- return;
147
- }
146
+ // Wait for page to load and leave animation to finish
147
+ const [page] = await Promise.all([pagePromise, animationPromise]);
148
+
149
+ // Abort if another visit was started in the meantime
150
+ if (visit.id !== this.visit.id) {
151
+ return false;
152
+ }
148
153
 
149
- // Render page: replace content and scroll to top/fragment
150
- await this.renderPage(page);
154
+ // Render page: replace content and scroll to top/fragment
155
+ await this.renderPage(page);
151
156
 
152
- // Wait for enter animation
153
- await this.animatePageIn();
157
+ // Wait for enter animation
158
+ await this.animatePageIn();
159
+
160
+ return true;
161
+ });
154
162
 
155
163
  // Finalize visit
156
164
  await this.hooks.call('visit:end', undefined, () => this.classes.clear());
@@ -159,9 +167,10 @@ export async function performNavigation(
159
167
  // if (visit.to && this.isSameResolvedUrl(visit.to.url, requestedUrl)) {
160
168
  // this.visit = this.createVisit({ to: undefined });
161
169
  // }
162
- } catch (error: unknown) {
163
- // Return early if error is undefined (probably aborted preload request)
164
- if (!error) {
170
+ this.navigating = false;
171
+ } catch (error) {
172
+ // Return early if error is undefined or signals an aborted request
173
+ if (!error || (error as FetchError)?.aborted) {
165
174
  return;
166
175
  }
167
176
 
@@ -1,4 +1,4 @@
1
- import Swup from '../Swup.js';
1
+ import type Swup from '../Swup.js';
2
2
 
3
3
  export type Plugin = {
4
4
  /** Identify as a swup plugin */
@@ -1,6 +1,6 @@
1
1
  import { updateHistoryRecord, getCurrentUrl, classify } from '../helpers.js';
2
- import Swup from '../Swup.js';
3
- import { PageData } from './fetchPage.js';
2
+ import type Swup from '../Swup.js';
3
+ import type { PageData } from './fetchPage.js';
4
4
 
5
5
  /**
6
6
  * Render the next page: replace the content and update scroll position.
@@ -1,6 +1,7 @@
1
- import Swup, { Options } from '../Swup.js';
1
+ import type Swup from '../Swup.js';
2
+ import type { Options } from '../Swup.js';
2
3
  import { query, queryAll } from '../utils.js';
3
- import { PageData } from './fetchPage.js';
4
+ import type { PageData } from './fetchPage.js';
4
5
 
5
6
  /**
6
7
  * Perform the replacement of content after loading a page.
@@ -1,4 +1,4 @@
1
- import Swup from '../Swup.js';
1
+ import type Swup from '../Swup.js';
2
2
 
3
3
  /**
4
4
  * Utility function to validate and run the global option 'resolveUrl'
@@ -1,4 +1,4 @@
1
- import Swup from '../Swup.js';
1
+ import type Swup from '../Swup.js';
2
2
 
3
3
  /**
4
4
  * Update the scroll position after page render.
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1,83 +0,0 @@
1
- import { DelegateEvent } from 'delegate-it';
2
- import { describe, expect, it, vi } from 'vitest';
3
-
4
- import pckg from '../../package.json';
5
- import Swup, { Options, Plugin } from '../index.js';
6
- import * as SwupTS from '../Swup.js';
7
-
8
- const baseUrl = window.location.origin;
9
-
10
- describe('Exports', () => {
11
- it('should export Swup and Options/Plugin types', () => {
12
- class SwupPlugin implements Plugin {
13
- name = 'SwupPlugin';
14
- isSwupPlugin = true as const;
15
- mount = () => {};
16
- unmount = () => {};
17
- }
18
-
19
- const options: Partial<Options> = {
20
- animateHistoryBrowsing: false,
21
- animationSelector: '[class*="transition-"]',
22
- cache: true,
23
- containers: ['#swup'],
24
- ignoreVisit: (url, { el } = {}) => !!el?.closest('[data-no-swup]'),
25
- linkSelector: 'a[href]',
26
- plugins: [new SwupPlugin()],
27
- resolveUrl: (url) => url,
28
- requestHeaders: {
29
- 'X-Requested-With': 'swup',
30
- 'Accept': 'text/html, application/xhtml+xml'
31
- },
32
- skipPopStateHandling: (event) => event.state?.source !== 'swup'
33
- };
34
-
35
- const swup = new Swup(options);
36
- expect(swup).toBeInstanceOf(Swup);
37
- });
38
-
39
- it('should define a version', () => {
40
- const swup = new Swup();
41
- expect(swup.version).not.toBeUndefined();
42
- expect(swup.version).toEqual(pckg.version);
43
- });
44
-
45
- it('UMD compatibility: Swup.ts should only have a default export', () => {
46
- expect(Object.keys(SwupTS)).toEqual(['default']);
47
- });
48
- });
49
-
50
- describe('ignoreVisit', () => {
51
- it('should be called with relative URL', () => {
52
- const ignoreVisit = vi.fn(() => true);
53
- const swup = new Swup({ ignoreVisit });
54
- swup.shouldIgnoreVisit(`${baseUrl}/path/?query#hash`);
55
-
56
- expect(ignoreVisit.mock.calls).toHaveLength(1);
57
- expect((ignoreVisit.mock.lastCall as any)[0]).toEqual('/path/?query#hash');
58
- });
59
-
60
- it('should have access to element and event params', () => {
61
- const el = document.createElement('a');
62
- el.href = `${baseUrl}/path/?query#hash`;
63
- const event = new MouseEvent('click') as DelegateEvent<MouseEvent>;
64
- event.delegateTarget = el;
65
-
66
- const ignoreVisit = vi.fn(() => true);
67
- const swup = new Swup({ ignoreVisit });
68
- swup.navigate(el.href, {}, { el, event });
69
-
70
- expect(ignoreVisit.mock.calls).toHaveLength(1);
71
- expect((ignoreVisit.mock.lastCall as any)[1]).toEqual(
72
- expect.objectContaining({ el, event })
73
- );
74
- });
75
-
76
- it('should be called from visit method', () => {
77
- const ignoreVisit = vi.fn(() => true);
78
- const swup = new Swup({ ignoreVisit });
79
- swup.navigate('/path/');
80
-
81
- expect(ignoreVisit.mock.calls).toHaveLength(1);
82
- });
83
- });
@@ -1,54 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { matchPath } from '../../index.js';
3
- import { pathToRegexp } from 'path-to-regexp';
4
-
5
- describe('matchPath', () => {
6
- it('should return false if not matching', () => {
7
- const urlMatch = matchPath('/users/:user');
8
- const match = urlMatch('/posts/');
9
- expect(match).toBe(false);
10
- });
11
-
12
- it('should return an object if matching', () => {
13
- const urlMatch = matchPath('/users/:user');
14
- const match = urlMatch('/users/bob');
15
- expect(match).toEqual({
16
- path: '/users/bob',
17
- index: 0,
18
- params: { user: 'bob' }
19
- });
20
- });
21
-
22
- it('should work with primitive strings', () => {
23
- const urlMatch = matchPath<{ user: string }>('/users/:user');
24
- const match = urlMatch('/users/bob');
25
- const params = !match ? false : match.params;
26
- expect(params).toEqual({ user: 'bob' });
27
- });
28
-
29
- it('should work with an array of paths', () => {
30
- const urlMatch = matchPath<{ user: string }>(['/users/', '/users/:user']);
31
-
32
- const { params: withParams } = urlMatch('/users/bob') || {};
33
- expect(withParams).toEqual({ user: 'bob' });
34
-
35
- const { params: withoutParams } = urlMatch('/users/') || {};
36
- expect(withoutParams).toEqual({});
37
- });
38
-
39
- /**
40
- * When passing a regex to `match`, the params in the response are sorted by appearance.
41
- * Only helpful for falsy/truthy detection
42
- */
43
- it('should work with regex', () => {
44
- const re = pathToRegexp('/users/:user');
45
- const urlMatch = matchPath(re);
46
- const { params } = urlMatch('/users/bob') || {};
47
- expect(params).toEqual({ '0': 'bob' });
48
- });
49
-
50
- it('should throw with malformed paths', () => {
51
- // prettier-ignore
52
- expect(() => matchPath('/\?user=:user')).toThrowError('[swup] Error parsing path');
53
- });
54
- });
@@ -1,159 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from 'vitest';
2
- import Swup from '../../Swup.js';
3
- import { Cache, CacheData } from '../Cache.js';
4
- import { Visit } from '../Visit.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 visit = swup.visit;
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(visit, { 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);
94
- });
95
-
96
- cache.set('/page', { url: '/page', html: '' });
97
-
98
- const page = cache.get('/page');
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 });
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
- it('should return a copy from cache.get()', () => {
121
- cache.set(page1.url, page1);
122
- const page = cache.get(page1.url);
123
- page!.html = 'new';
124
- expect(cache.get(page1.url)?.html).toEqual(page1.html);
125
- });
126
-
127
- it('should return a new Map with shallow copies from cache.all', () => {
128
- cache.set(page1.url, page1);
129
- cache.set(page2.url, page2);
130
-
131
- const all = cache.all;
132
- all.get(page1.url)!.html = 'new';
133
-
134
- expect(cache.get(page1.url)?.html).toEqual(page1.html);
135
- });
136
- });
137
-
138
- describe('Types', () => {
139
- it('error when necessary', async () => {
140
- const swup = new Swup();
141
- const cache = new Cache(swup);
142
-
143
- // @ts-expect-no-error
144
- swup.hooks.on('history:popstate', (visit: Visit, { event: PopStateEvent }) => {});
145
- // @ts-expect-no-error
146
- await swup.hooks.call('history:popstate', { event: new PopStateEvent('') });
147
-
148
- try {
149
- // @ts-expect-error
150
- cache.set();
151
- // @ts-expect-error
152
- cache.set(url);
153
- // @ts-expect-error
154
- cache.set(url, {});
155
- // @ts-expect-error
156
- cache.set({ url: '/test' });
157
- } catch (error) {}
158
- });
159
- });
@@ -1,36 +0,0 @@
1
- import { DelegateEvent } from 'delegate-it';
2
- import { describe, it } from 'vitest';
3
-
4
- import { delegateEvent } from '../../helpers/delegateEvent.js';
5
-
6
- describe('delegateEvent', () => {
7
- it('should return correct types', () => {
8
- delegateEvent('form', 'submit', (event) => {});
9
-
10
- // @ts-expect-no-error
11
- delegateEvent('form', 'submit', (event: SubmitEvent) => {});
12
- // @ts-expect-error
13
- delegateEvent('form', 'submit', (event: MouseEvent) => {});
14
-
15
- // @ts-expect-no-error
16
- delegateEvent('form', 'submit', (event: DelegateEvent<SubmitEvent>) => {});
17
- // @ts-expect-error
18
- delegateEvent('form', 'submit', (event: DelegateEvent<MouseEvent>) => {});
19
-
20
- // @ts-expect-no-error
21
- delegateEvent('form', 'submit', (event: DelegateEvent<SubmitEvent, HTMLFormElement>) => {});
22
- delegateEvent(
23
- 'form',
24
- 'submit',
25
- // @ts-expect-error
26
- (event: DelegateEvent<MouseEvent, HTMLAnchorElement>) => {}
27
- );
28
-
29
- delegateEvent('form', 'submit', (event) => {
30
- // @ts-expect-no-error
31
- const el: HTMLFormElement = event.delegateTarget;
32
- });
33
- // @ts-expect-error
34
- delegateEvent('form', 'submit', (event: MouseEvent) => {});
35
- });
36
- });