@next-box/link-extra 0.1.0 → 0.1.2
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/README.md +138 -2
- package/dist/cjs/Link.cjs +3 -0
- package/dist/cjs/Link.cjs.map +1 -0
- package/dist/cjs/LinkProvider.cjs +3 -0
- package/dist/cjs/LinkProvider.cjs.map +1 -0
- package/dist/cjs/helpers.cjs +2 -0
- package/dist/cjs/helpers.cjs.map +1 -0
- package/dist/cjs/index.cjs +1 -1
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/useLinkExtra.cjs +2 -0
- package/dist/cjs/useLinkExtra.cjs.map +1 -0
- package/dist/esm/Link.mjs +3 -0
- package/dist/esm/Link.mjs.map +1 -0
- package/dist/esm/LinkProvider.mjs +3 -0
- package/dist/esm/LinkProvider.mjs.map +1 -0
- package/dist/esm/helpers.mjs +2 -0
- package/dist/esm/helpers.mjs.map +1 -0
- package/dist/esm/index.mjs +1 -1
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/useLinkExtra.mjs +2 -0
- package/dist/esm/useLinkExtra.mjs.map +1 -0
- package/dist/production.analysis.txt +26 -26
- package/dist/types/cjs/Link.d.cts.map +1 -1
- package/dist/types/cjs/LinkProvider.d.cts +2 -3
- package/dist/types/cjs/LinkProvider.d.cts.map +1 -1
- package/dist/types/cjs/helpers.d.cts.map +1 -1
- package/dist/types/esm/Link.d.ts.map +1 -1
- package/dist/types/esm/LinkProvider.d.ts +2 -3
- package/dist/types/esm/LinkProvider.d.ts.map +1 -1
- package/dist/types/esm/helpers.d.ts.map +1 -1
- package/dist/types/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +13 -13
- package/src/Link.tsx +2 -0
- package/src/LinkProvider.tsx +24 -6
- package/src/helpers.ts +18 -19
- package/src/main.test.tsx +159 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/** @jest-environment @repodog/jest-config/FailOnJsdomConsoleEnvironment.mjs */
|
|
2
|
+
|
|
3
|
+
import { afterEach, describe, expect, it, jest } from '@jest/globals';
|
|
4
|
+
import { type ComponentProps, type ComponentType, type ReactElement, act } from 'react';
|
|
5
|
+
import { createRoot } from 'react-dom/client';
|
|
6
|
+
import { Link } from './Link.tsx';
|
|
7
|
+
import { LinkProvider } from './LinkProvider.tsx';
|
|
8
|
+
import { shouldUrlAndLocationMatch } from './helpers.ts';
|
|
9
|
+
import { type NextLinkComponent, type OwnLinkProps } from './types.ts';
|
|
10
|
+
|
|
11
|
+
const NextLinkMock: NextLinkComponent = ({ children, href }) => {
|
|
12
|
+
const nextHref = typeof href === 'string' ? href : (href.pathname ?? '');
|
|
13
|
+
|
|
14
|
+
return <div data-next-href={nextHref}>{children}</div>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const OwnLink: ComponentType<OwnLinkProps> = ({ children, ...props }) => <a {...props}>{children}</a>;
|
|
18
|
+
|
|
19
|
+
const Loading = () => <span data-testid="loading">Loading</span>;
|
|
20
|
+
|
|
21
|
+
Object.defineProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT', { configurable: true, value: true });
|
|
22
|
+
|
|
23
|
+
const mount = (element: ReactElement) => {
|
|
24
|
+
const container = document.createElement('div');
|
|
25
|
+
const root = createRoot(container);
|
|
26
|
+
|
|
27
|
+
act(() => {
|
|
28
|
+
root.render(element);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
container,
|
|
33
|
+
unmount: () => {
|
|
34
|
+
act(() => {
|
|
35
|
+
root.unmount();
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const createLink = (props: ComponentProps<typeof Link>) => (
|
|
42
|
+
<LinkProvider
|
|
43
|
+
LoadingComponent={Loading}
|
|
44
|
+
NextLink={NextLinkMock}
|
|
45
|
+
OwnLink={OwnLink}
|
|
46
|
+
loadingTimeout={100}
|
|
47
|
+
pathname="/current"
|
|
48
|
+
>
|
|
49
|
+
<Link {...props} />
|
|
50
|
+
</LinkProvider>
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
afterEach(() => {
|
|
54
|
+
jest.useRealTimers();
|
|
55
|
+
history.replaceState(undefined, '', '/');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('Link integration', () => {
|
|
59
|
+
it('When a link is clicked and navigation exceeds the timeout, then it displays the loading component', () => {
|
|
60
|
+
// Arrange
|
|
61
|
+
history.replaceState(undefined, '', '/current');
|
|
62
|
+
Object.defineProperty(globalThis, 'scroll', { configurable: true, value: jest.fn() });
|
|
63
|
+
jest.useFakeTimers();
|
|
64
|
+
|
|
65
|
+
const { container, unmount } = mount(createLink({ children: 'Next page', href: '/next' }));
|
|
66
|
+
const anchor = container.querySelector('a');
|
|
67
|
+
|
|
68
|
+
// Act
|
|
69
|
+
act(() => {
|
|
70
|
+
anchor?.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
|
|
71
|
+
jest.advanceTimersByTime(100);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Assert
|
|
75
|
+
expect(container.querySelector('[data-testid="loading"]')?.textContent).toBe('Loading');
|
|
76
|
+
expect(container.querySelector<HTMLElement>('[data-next-href]')?.dataset.nextHref).toMatch(/^\/next\?y=/);
|
|
77
|
+
|
|
78
|
+
unmount();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('When a click is prevented, then it does not display the loading component', () => {
|
|
82
|
+
// Arrange
|
|
83
|
+
history.replaceState(undefined, '', '/current');
|
|
84
|
+
Object.defineProperty(globalThis, 'scroll', { configurable: true, value: jest.fn() });
|
|
85
|
+
jest.useFakeTimers();
|
|
86
|
+
|
|
87
|
+
const { container, unmount } = mount(
|
|
88
|
+
createLink({
|
|
89
|
+
children: 'Prevented navigation',
|
|
90
|
+
href: '/next',
|
|
91
|
+
onClick: event => {
|
|
92
|
+
event.preventDefault();
|
|
93
|
+
},
|
|
94
|
+
}),
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const anchor = container.querySelector('a');
|
|
98
|
+
|
|
99
|
+
// Act
|
|
100
|
+
act(() => {
|
|
101
|
+
anchor?.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
|
|
102
|
+
jest.advanceTimersByTime(100);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Assert
|
|
106
|
+
expect(container.querySelector('[data-testid="loading"]')).toBeNull();
|
|
107
|
+
|
|
108
|
+
unmount();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('When router cache control is disabled, then it passes the original href to NextLink', () => {
|
|
112
|
+
// Arrange
|
|
113
|
+
history.replaceState(undefined, '', '/current');
|
|
114
|
+
Object.defineProperty(globalThis, 'scroll', { configurable: true, value: jest.fn() });
|
|
115
|
+
|
|
116
|
+
const { container, unmount } = mount(
|
|
117
|
+
createLink({ children: 'Current page', disableRouterCache: false, href: '/current' }),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
// Act
|
|
121
|
+
const href = container.querySelector<HTMLElement>('[data-next-href]')?.dataset.nextHref;
|
|
122
|
+
|
|
123
|
+
// Assert
|
|
124
|
+
expect(href).toBe('/current');
|
|
125
|
+
|
|
126
|
+
unmount();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('When a pending navigation timer reaches an unmounted provider, then it has been cleared', () => {
|
|
130
|
+
// Arrange
|
|
131
|
+
history.replaceState(undefined, '', '/current');
|
|
132
|
+
Object.defineProperty(globalThis, 'scroll', { configurable: true, value: jest.fn() });
|
|
133
|
+
jest.useFakeTimers();
|
|
134
|
+
|
|
135
|
+
const { container, unmount } = mount(createLink({ children: 'Next page', href: '/next' }));
|
|
136
|
+
const anchor = container.querySelector('a');
|
|
137
|
+
|
|
138
|
+
act(() => {
|
|
139
|
+
anchor?.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Act
|
|
143
|
+
unmount();
|
|
144
|
+
|
|
145
|
+
// Assert
|
|
146
|
+
expect(jest.getTimerCount()).toBe(0);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('When query parameters are reordered, then the URL matches the current location', () => {
|
|
150
|
+
// Arrange
|
|
151
|
+
history.replaceState(undefined, '', '/current?alpha=one&bravo=two&y=cache');
|
|
152
|
+
|
|
153
|
+
// Act
|
|
154
|
+
const isMatch = shouldUrlAndLocationMatch('/current?bravo=two&alpha=one&y=other');
|
|
155
|
+
|
|
156
|
+
// Assert
|
|
157
|
+
expect(isMatch).toBe(true);
|
|
158
|
+
});
|
|
159
|
+
});
|