swup 3.0.1 → 3.0.3

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.
@@ -1,3 +1,4 @@
1
+ import pckg from '../../package.json';
1
2
  import Swup, { Options, Plugin } from '../index';
2
3
 
3
4
  console.log = jest.fn();
@@ -6,8 +7,19 @@ console.error = jest.fn();
6
7
 
7
8
  const baseUrl = window.location.origin;
8
9
 
9
- describe('exports', () => {
10
- it('exports Swup, and Options/Plugin types', () => {
10
+ function createPlugin(plugin = {}) {
11
+ return {
12
+ name: 'TestPlugin',
13
+ isSwupPlugin: true as const,
14
+ mount: jest.fn(() => {}),
15
+ unmount: jest.fn(() => {}),
16
+ _checkRequirements: jest.fn(() => true),
17
+ ...plugin
18
+ };
19
+ }
20
+
21
+ describe('Exports', () => {
22
+ it('should export Swup and Options/Plugin types', () => {
11
23
  class SwupPlugin implements Plugin {
12
24
  name = 'SwupPlugin';
13
25
  isSwupPlugin = true as const;
@@ -32,21 +44,92 @@ describe('exports', () => {
32
44
  };
33
45
 
34
46
  const swup = new Swup(options);
47
+ expect(swup).toBeInstanceOf(Swup);
48
+ });
35
49
 
50
+ it('should define a version', () => {
51
+ const swup = new Swup();
36
52
  expect(swup.version).not.toBeUndefined();
53
+ expect(swup.version).toEqual(pckg.version);
37
54
  });
55
+ });
38
56
 
39
- it('passes relative URL to ignoreVisit', () => {
40
- let ignorableUrl = 'nothing';
41
- const swup = new Swup({
42
- ignoreVisit: (url, { el } = {}) => {
43
- ignorableUrl = url;
44
- return false;
45
- }
46
- });
57
+ describe('ignoreVisit', () => {
58
+ it('should be called with relative URL', () => {
59
+ const ignoreVisit = jest.fn(() => true);
60
+ const swup = new Swup({ ignoreVisit });
47
61
 
48
62
  swup.shouldIgnoreVisit(baseUrl + '/path/?query#hash');
49
63
 
50
- expect(ignorableUrl).toEqual('/path/?query#hash');
64
+ expect(ignoreVisit.mock.calls).toHaveLength(1);
65
+ expect(ignoreVisit.mock.lastCall).toBeDefined();
66
+ expect((ignoreVisit.mock.lastCall as any)[0]).toEqual('/path/?query#hash');
67
+ });
68
+
69
+ it('should be called from loadPage method', () => {
70
+ const ignoreVisit = jest.fn(() => true);
71
+ const swup = new Swup({ ignoreVisit });
72
+
73
+ swup.loadPage({ url: '/path/' });
74
+
75
+ expect(ignoreVisit.mock.calls).toHaveLength(1);
76
+ });
77
+ });
78
+
79
+ describe('Plugin module', () => {
80
+ it('should mount and unmount plugins', function () {
81
+ const plugin = createPlugin();
82
+ const swup = new Swup();
83
+ swup.use(plugin);
84
+ swup.unuse(plugin);
85
+
86
+ expect(plugin.mount.mock.calls).toHaveLength(1);
87
+ expect(plugin.unmount.mock.calls).toHaveLength(1);
88
+ });
89
+
90
+ it('should mount plugins from options', function () {
91
+ const plugin = createPlugin();
92
+ const swup = new Swup({ plugins: [plugin] });
93
+ expect(plugin.mount.mock.calls).toHaveLength(1);
94
+ });
95
+
96
+ it('should find a plugin instance by reference', function () {
97
+ const plugin = createPlugin({ name: 'ExamplePlugin' });
98
+ const swup = new Swup({ plugins: [plugin] });
99
+ const instance = swup.findPlugin(plugin);
100
+
101
+ expect(instance).toEqual(expect.objectContaining({ name: 'ExamplePlugin' }));
102
+ });
103
+
104
+ it('should find a plugin instance by name', function () {
105
+ const plugin = createPlugin({ name: 'ExamplePlugin' });
106
+ const swup = new Swup({ plugins: [plugin] });
107
+ const instance = swup.findPlugin('ExamplePlugin');
108
+
109
+ expect(instance).toEqual(expect.objectContaining({ name: 'ExamplePlugin' }));
110
+ });
111
+
112
+ it('should check plugin requirements', function () {
113
+ const plugin = createPlugin();
114
+ const swup = new Swup({ plugins: [plugin] });
115
+ expect(plugin._checkRequirements.mock.calls).toHaveLength(1);
116
+ });
117
+
118
+ it('should reject plugins with unmet requirements', function () {
119
+ const allowedPlugin = createPlugin({
120
+ name: 'AllowedPlugin',
121
+ _checkRequirements: () => true
122
+ });
123
+ const unallowedPlugin = createPlugin({
124
+ name: 'UnallowedPlugin',
125
+ _checkRequirements: () => false
126
+ });
127
+ const swup = new Swup({ plugins: [allowedPlugin, unallowedPlugin] });
128
+
129
+ const allowedInstance = swup.findPlugin(allowedPlugin);
130
+ expect(allowedInstance).toEqual(expect.objectContaining({ name: 'AllowedPlugin' }));
131
+
132
+ const unallowedInstance = swup.findPlugin(unallowedPlugin);
133
+ expect(unallowedInstance).toBeUndefined();
51
134
  });
52
135
  });
@@ -1,12 +1,10 @@
1
1
  import { nextTick } from '../utils';
2
2
  import Swup from '../Swup';
3
+ import { PageRenderOptions } from './renderPage';
3
4
 
4
- export const enterPage = function (
5
- this: Swup,
6
- { popstate, skipTransition }: { popstate?: PopStateEvent; skipTransition?: boolean }
7
- ) {
5
+ export const enterPage = function (this: Swup, { event, skipTransition }: PageRenderOptions = {}) {
8
6
  if (skipTransition) {
9
- this.triggerEvent('transitionEnd', popstate);
7
+ this.triggerEvent('transitionEnd', event);
10
8
  this.cleanupAnimationClasses();
11
9
  return [Promise.resolve()];
12
10
  }
@@ -19,7 +17,7 @@ export const enterPage = function (
19
17
  const animationPromises = this.getAnimationPromises('in');
20
18
  Promise.all(animationPromises).then(() => {
21
19
  this.triggerEvent('animationInDone');
22
- this.triggerEvent('transitionEnd', popstate);
20
+ this.triggerEvent('transitionEnd', event);
23
21
  this.cleanupAnimationClasses();
24
22
  });
25
23
  return animationPromises;
@@ -51,11 +51,7 @@ export function off(this: Swup, event?: EventType, handler?: Handler) {
51
51
  }
52
52
  }
53
53
 
54
- export function triggerEvent(
55
- this: Swup,
56
- eventName: EventType,
57
- originalEvent?: PopStateEvent | MouseEvent
58
- ): void {
54
+ export function triggerEvent(this: Swup, eventName: EventType, originalEvent?: Event): void {
59
55
  // call saved handlers with "on" method and pass originalEvent object if available
60
56
  this._handlers[eventName].forEach((handler) => {
61
57
  try {
@@ -1,13 +1,9 @@
1
1
  import Swup from '../Swup';
2
- import { TransitionOptions } from './loadPage';
2
+ import { PageRenderOptions } from './renderPage';
3
+
4
+ export const leavePage = function (this: Swup, { event, skipTransition }: PageRenderOptions = {}) {
5
+ const isHistoryVisit = event instanceof PopStateEvent;
3
6
 
4
- export const leavePage = function (
5
- this: Swup,
6
- data: TransitionOptions,
7
- { popstate, skipTransition }: { popstate: PopStateEvent | null; skipTransition?: boolean } = {
8
- popstate: null
9
- }
10
- ) {
11
7
  if (skipTransition) {
12
8
  this.triggerEvent('animationSkipped');
13
9
  return [Promise.resolve()];
@@ -17,7 +13,7 @@ export const leavePage = function (
17
13
 
18
14
  // handle classes
19
15
  document.documentElement.classList.add('is-changing', 'is-leaving', 'is-animating');
20
- if (popstate) {
16
+ if (isHistoryVisit) {
21
17
  document.documentElement.classList.add('is-popstate');
22
18
  }
23
19
 
@@ -7,15 +7,30 @@ export type TransitionOptions = {
7
7
  customTransition?: string;
8
8
  };
9
9
 
10
- export const loadPage = function (
11
- this: Swup,
12
- data: TransitionOptions,
13
- popstate: PopStateEvent | null
14
- ) {
15
- const { url, customTransition } = data;
16
- const skipTransition = !!(popstate && !this.options.animateHistoryBrowsing);
10
+ export type PageLoadOptions = {
11
+ url: string;
12
+ event?: Event;
13
+ customTransition?: string;
14
+ };
17
15
 
18
- this.triggerEvent('transitionStart', popstate || undefined);
16
+ export function loadPage(this: Swup, data: TransitionOptions) {
17
+ const { url } = data;
18
+
19
+ // Check if the visit should be ignored
20
+ if (this.shouldIgnoreVisit(url)) {
21
+ window.location.href = url;
22
+ } else {
23
+ this.performPageLoad(data);
24
+ }
25
+ }
26
+
27
+ export function performPageLoad(this: Swup, data: PageLoadOptions) {
28
+ const { url, event, customTransition } = data ?? {};
29
+
30
+ const isHistoryVisit = event instanceof PopStateEvent;
31
+ const skipTransition = this.shouldSkipTransition({ url, event });
32
+
33
+ this.triggerEvent('transitionStart', event);
19
34
 
20
35
  // set transition object
21
36
  this.updateTransition(getCurrentUrl(), url, customTransition);
@@ -24,10 +39,10 @@ export const loadPage = function (
24
39
  }
25
40
 
26
41
  // start/skip animation
27
- const animationPromises = this.leavePage(data, { popstate, skipTransition });
42
+ const animationPromises = this.leavePage({ event, skipTransition });
28
43
 
29
44
  // create history record if this is not a popstate call (with or without anchor)
30
- if (!popstate) {
45
+ if (!isHistoryVisit) {
31
46
  createHistoryRecord(url + (this.scrollToElement || ''));
32
47
  }
33
48
 
@@ -39,7 +54,7 @@ export const loadPage = function (
39
54
  // when everything is ready, render the page
40
55
  Promise.all<PageRecord | void>([fetchPromise, ...animationPromises])
41
56
  .then(([pageData]) => {
42
- this.renderPage(pageData as PageRecord, { popstate, skipTransition });
57
+ this.renderPage(pageData as PageRecord, { event, skipTransition });
43
58
  })
44
59
  .catch((errorUrl) => {
45
60
  // Return early if errorUrl is not defined (probably aborted preload request)
@@ -54,4 +69,4 @@ export const loadPage = function (
54
69
  // Go back to the actual page we're still at
55
70
  history.go(-1);
56
71
  });
57
- };
72
+ }
@@ -2,12 +2,15 @@ import { Location, updateHistoryRecord, getCurrentUrl } from '../helpers';
2
2
  import Swup from '../Swup';
3
3
  import { PageRecord } from './Cache';
4
4
 
5
+ export type PageRenderOptions = {
6
+ event?: Event;
7
+ skipTransition?: boolean;
8
+ };
9
+
5
10
  export const renderPage = function (
6
11
  this: Swup,
7
12
  page: PageRecord,
8
- { popstate, skipTransition }: { popstate: PopStateEvent | null; skipTransition?: boolean } = {
9
- popstate: null
10
- }
13
+ { event, skipTransition }: PageRenderOptions = {}
11
14
  ) {
12
15
  document.documentElement.classList.remove('is-leaving');
13
16
 
@@ -30,11 +33,11 @@ export const renderPage = function (
30
33
  document.documentElement.classList.add('is-rendering');
31
34
  }
32
35
 
33
- this.triggerEvent('willReplaceContent', popstate || undefined);
36
+ this.triggerEvent('willReplaceContent', event);
34
37
 
35
38
  this.replaceContent(page).then(() => {
36
- this.triggerEvent('contentReplaced', popstate || undefined);
37
- this.triggerEvent('pageView', popstate || undefined);
39
+ this.triggerEvent('contentReplaced', event);
40
+ this.triggerEvent('pageView', event);
38
41
 
39
42
  // empty cache if it's disabled (in case preload plugin filled it)
40
43
  if (!this.options.cache) {
@@ -42,7 +45,7 @@ export const renderPage = function (
42
45
  }
43
46
 
44
47
  // Perform in transition
45
- this.enterPage({ popstate: popstate || undefined, skipTransition });
48
+ this.enterPage({ event, skipTransition });
46
49
 
47
50
  // reset scroll-to element
48
51
  this.scrollToElement = null;
@@ -0,0 +1,10 @@
1
+ import Swup from '../Swup';
2
+
3
+ export function updateTransition(this: Swup, from: string, to: string, custom?: string): void {
4
+ this.transition = { from, to, custom };
5
+ }
6
+
7
+ export function shouldSkipTransition(this: Swup, { event }: { url?: string; event?: Event }) {
8
+ const isHistoryVisit = event instanceof PopStateEvent;
9
+ return !!(isHistoryVisit && !this.options.animateHistoryBrowsing);
10
+ }
@@ -1,2 +0,0 @@
1
- import Swup from '../Swup';
2
- export declare const updateTransition: (this: Swup, from: string, to: string, custom?: string) => void;
@@ -1,10 +0,0 @@
1
- import Swup from '../Swup';
2
-
3
- export const updateTransition = function (
4
- this: Swup,
5
- from: string,
6
- to: string,
7
- custom?: string
8
- ): void {
9
- this.transition = { from, to, custom };
10
- };