@wavelengthusaf/client-router 0.0.1 → 0.0.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.
Files changed (68) hide show
  1. package/dist/src/elements/RouteElement.d.ts +11 -0
  2. package/dist/src/elements/RouteElement.d.ts.map +1 -0
  3. package/dist/src/elements/RouterElement.d.ts +12 -0
  4. package/dist/src/elements/RouterElement.d.ts.map +1 -0
  5. package/dist/src/index.d.ts +6 -0
  6. package/dist/src/index.d.ts.map +1 -0
  7. package/dist/src/models/CompiledRoute.d.ts +7 -0
  8. package/dist/src/models/CompiledRoute.d.ts.map +1 -0
  9. package/dist/src/models/MatchedRoute.d.ts +7 -0
  10. package/dist/src/models/MatchedRoute.d.ts.map +1 -0
  11. package/dist/src/models/RouteComponent.d.ts +6 -0
  12. package/dist/src/models/RouteComponent.d.ts.map +1 -0
  13. package/dist/src/models/RouteContext.d.ts +7 -0
  14. package/dist/src/models/RouteContext.d.ts.map +1 -0
  15. package/dist/src/models/RouteDefinition.d.ts +6 -0
  16. package/dist/src/models/RouteDefinition.d.ts.map +1 -0
  17. package/dist/src/models/RouteGuard.d.ts +3 -0
  18. package/dist/src/models/RouteGuard.d.ts.map +1 -0
  19. package/dist/src/models/RouteParams.d.ts +4 -0
  20. package/dist/src/models/RouteParams.d.ts.map +1 -0
  21. package/dist/src/models/index.d.ts +9 -0
  22. package/dist/src/models/index.d.ts.map +1 -0
  23. package/dist/src/router/Router.d.ts +20 -0
  24. package/dist/src/router/Router.d.ts.map +1 -0
  25. package/dist/src/utils/compileRoute.d.ts +3 -0
  26. package/dist/src/utils/compileRoute.d.ts.map +1 -0
  27. package/dist/src/utils/index.d.ts +4 -0
  28. package/dist/src/utils/index.d.ts.map +1 -0
  29. package/dist/src/utils/matchRoute.d.ts +3 -0
  30. package/dist/src/utils/matchRoute.d.ts.map +1 -0
  31. package/package.json +7 -1
  32. package/.gitlab-ci.yml +0 -84
  33. package/.husky/pre-commit +0 -38
  34. package/.storybook/main.ts +0 -16
  35. package/.storybook/manager.ts +0 -7
  36. package/.storybook/preview.ts +0 -23
  37. package/.storybook/theme.ts +0 -30
  38. package/.storybook/vitest.setup.ts +0 -7
  39. package/eslint.config.mjs +0 -89
  40. package/index.ts +0 -15
  41. package/prettier.config.ts +0 -30
  42. package/src/elements/RouteElement.ts +0 -67
  43. package/src/elements/RouterElement.ts +0 -97
  44. package/src/elements/__tests__/RouteElement.test.ts +0 -89
  45. package/src/elements/__tests__/RouterElement.test.ts +0 -253
  46. package/src/index.ts +0 -15
  47. package/src/models/CompiledRoute.ts +0 -7
  48. package/src/models/MatchedRoute.ts +0 -7
  49. package/src/models/RouteComponent.ts +0 -6
  50. package/src/models/RouteContext.ts +0 -7
  51. package/src/models/RouteDefinition.ts +0 -6
  52. package/src/models/RouteGuard.ts +0 -6
  53. package/src/models/RouteParams.ts +0 -3
  54. package/src/models/index.ts +0 -17
  55. package/src/router/Router.ts +0 -225
  56. package/src/router/__tests__/Router.test.ts +0 -464
  57. package/src/stories/RouterElement.stories.test.ts +0 -310
  58. package/src/stories/RouterElement.stories.ts +0 -208
  59. package/src/utils/__tests__/compileRoute.test.ts +0 -97
  60. package/src/utils/__tests__/matchRoute.test.ts +0 -180
  61. package/src/utils/compileRoute.ts +0 -37
  62. package/src/utils/index.ts +0 -4
  63. package/src/utils/matchRoute.ts +0 -37
  64. package/tsconfig.build.json +0 -20
  65. package/tsconfig.json +0 -55
  66. package/vitest.config.ts +0 -34
  67. package/vitest.setup.ts +0 -3
  68. package/vitest.shims.d.ts +0 -1
@@ -1,97 +0,0 @@
1
- import type { RouteDefinition, RouteGuard } from '../models/index.ts';
2
- import { Router } from '../router/Router.ts';
3
- import type { RouteElement } from './RouteElement.ts';
4
-
5
- export class RouterElement extends HTMLElement {
6
- public static readonly tag = 'wavelength-router';
7
-
8
- #router?: Router;
9
-
10
- public static get observedAttributes(): string[] {
11
- return ['hash-mode', 'guard'];
12
- }
13
-
14
- public connectedCallback(): void {
15
- this.#bootstrap();
16
- }
17
-
18
- public disconnectedCallback(): void {
19
- this.#teardown();
20
- }
21
-
22
- public attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {
23
- if (!this.isConnected || oldValue === newValue) return;
24
- this.#bootstrap();
25
- }
26
-
27
- public navigate(path: string): void {
28
- this.#router?.navigate(path);
29
- }
30
-
31
- public replace(path: string): void {
32
- this.#router?.replace(path);
33
- }
34
-
35
- public back(): void {
36
- this.#router?.back();
37
- }
38
-
39
- #bootstrap(): void {
40
- this.#teardown();
41
-
42
- const routes: RouteDefinition[] = this.#collectRoutes();
43
-
44
- if (routes.length === 0) {
45
- console.warn('[wavelength-router] No <wavelength-route> children found.');
46
- return;
47
- }
48
-
49
- const guard: RouteGuard | undefined =this.#resolveGuard();
50
- this.#router = new Router(
51
- routes,
52
- {
53
- outlet: this,
54
- hashMode: this.hasAttribute('hash-mode'),
55
- ...(guard ? { beforeEach: guard } : {}),
56
- },
57
- {
58
- window: window,
59
- document: document,
60
- },
61
- );
62
-
63
- this.#router.start();
64
- }
65
-
66
- #teardown(): void {
67
- this.#router?.destroy();
68
- }
69
-
70
- #collectRoutes(): RouteDefinition[] {
71
- const routeElements: RouteElement[] = Array.from(
72
- this.querySelectorAll<RouteElement>('wavelength-route'),
73
- );
74
-
75
- return routeElements
76
- .map((routeElement: RouteElement) => routeElement.toDefinition({ document }))
77
- .filter((routeDefinition: RouteDefinition | null): routeDefinition is RouteDefinition => routeDefinition !== null);
78
- }
79
-
80
- #resolveGuard(): RouteGuard | undefined {
81
- const guardName: string | null = this.getAttribute('guard');
82
- if (!guardName) return undefined;
83
-
84
- const fn: unknown = (window as unknown as Record<string, unknown>)[guardName];
85
-
86
- if (typeof fn !== 'function') {
87
- console.warn(`[wavelength-router] Guard "${guardName}" not found on window.`);
88
- return undefined;
89
- }
90
-
91
- return fn as RouteGuard;
92
- }
93
- }
94
-
95
- if (!customElements.get(RouterElement.tag)) {
96
- customElements.define(RouterElement.tag, RouterElement);
97
- }
@@ -1,89 +0,0 @@
1
- import { describe, expect, it, vi, beforeEach } from 'vitest';
2
- import { RouteElement } from '../RouteElement.ts';
3
-
4
- /* eslint-disable @typescript-eslint/no-explicit-any */
5
- describe('RouteElement', () => {
6
- beforeEach(() => {
7
- if (!customElements.get(RouteElement.tag)) {
8
- customElements.define(RouteElement.tag, RouteElement);
9
- }
10
- document.body.innerHTML = '';
11
- });
12
-
13
- describe('Attribute Accessors & toDefinition Logic', () => {
14
- it.each([
15
- {
16
- it: 'returns default path "*" if attribute is missing',
17
- html: `<wavelength-route component="my-view"></wavelength-route>`,
18
- expectedPath: '*',
19
- expectedTag: 'my-view'
20
- },
21
- {
22
- it: 'extracts specified path and component tag',
23
- html: `<wavelength-route path="/home" component="home-view"></wavelength-route>`,
24
- expectedPath: '/home',
25
- expectedTag: 'home-view'
26
- },
27
- {
28
- it: 'returns null and warns if component attribute is missing',
29
- html: `<wavelength-route path="/error"></wavelength-route>`,
30
- expectedPath: '/error',
31
- expectedTag: '',
32
- shouldBeNull: true
33
- }
34
- ])('$it', ({ html, expectedPath, expectedTag, shouldBeNull }) => {
35
- document.body.innerHTML = html;
36
- const el = document.querySelector<RouteElement>('wavelength-route')!;
37
- const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
38
-
39
- expect(el.path).toBe(expectedPath);
40
- expect(el.componentTag).toBe(expectedTag);
41
-
42
- const def = el.toDefinition({ document });
43
-
44
- if (shouldBeNull) {
45
- expect(def).toBeNull();
46
- expect(warnSpy).toHaveBeenCalled();
47
- } else {
48
- expect(def?.path).toBe(expectedPath);
49
- expect(def?.routeComponent).toBeDefined();
50
- }
51
-
52
- warnSpy.mockRestore();
53
- });
54
- });
55
-
56
- describe('SSR / Environment Resilience', () => {
57
- it('returns a no-op component if document is missing', () => {
58
- const el = document.createElement('wavelength-route') as RouteElement;
59
- el.setAttribute('component', 'test-view');
60
-
61
- const def = el.toDefinition({});
62
-
63
- expect(def?.path).toBe('*');
64
- expect(def?.routeComponent.mount).toBeInstanceOf(Function);
65
- expect(() => def?.routeComponent.mount({} as any, {} as any)).not.toThrow();
66
- expect(() => def?.routeComponent.unmount?.()).not.toThrow();
67
- });
68
- });
69
-
70
- describe('RouteComponent Mounting Logic', () => {
71
- it('properly creates element and injects context on mount', () => {
72
- const el = document.createElement('wavelength-route') as RouteElement;
73
- el.setAttribute('component', 'user-profile');
74
-
75
- const def = el.toDefinition({ document });
76
- const container = document.createElement('div');
77
- const mockContext = { path: '/user/1', routeParams: { id: '1' }, query: new URLSearchParams() };
78
-
79
- def?.routeComponent.mount(container, mockContext as any);
80
-
81
- const renderedElement = container.querySelector('user-profile') as any;
82
-
83
- expect(renderedElement).toBeDefined();
84
- expect(renderedElement.routeContext).toEqual(mockContext);
85
- expect(def?.routeComponent.unmount).toBeDefined();
86
- def?.routeComponent.unmount?.();
87
- });
88
- });
89
- });
@@ -1,253 +0,0 @@
1
- import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
2
- import { RouterElement } from '../RouterElement.ts';
3
- import type { RouteElement } from '../RouteElement.ts';
4
-
5
- /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-member-accessibility */
6
- const routerInstances: any[] = [];
7
- const routerArgs: any[] = [];
8
-
9
- vi.mock('../../router/Router.ts', () => {
10
- return {
11
- Router: class {
12
- start = vi.fn();
13
- destroy = vi.fn();
14
- navigate = vi.fn();
15
- replace = vi.fn();
16
- back = vi.fn();
17
-
18
- constructor(...args: any[]) {
19
- routerInstances.push(this);
20
- routerArgs.push(args);
21
- }
22
- },
23
- };
24
- });
25
-
26
- const createFakeRoute = (def: any | null): RouteElement => {
27
- const el = document.createElement('wavelength-route') as unknown as RouteElement;
28
- el.toDefinition = vi.fn().mockReturnValue(def);
29
- return el;
30
- };
31
-
32
- const setWindowProp = (key: string, value: any) => {
33
- (window as any)[key] = value;
34
- return () => delete (window as any)[key];
35
- };
36
-
37
- describe('RouterElement', () => {
38
- let container: HTMLDivElement;
39
- let warnSpy: ReturnType<typeof vi.spyOn>;
40
-
41
- beforeEach(() => {
42
- container = document.createElement('div');
43
- document.body.appendChild(container);
44
-
45
- warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
46
-
47
- routerInstances.length = 0;
48
- routerArgs.length = 0;
49
-
50
- vi.clearAllMocks();
51
- });
52
-
53
- afterEach(() => {
54
- document.body.innerHTML = '';
55
- warnSpy.mockRestore();
56
- });
57
-
58
- describe('Lifecycle & Bootstrapping', () => {
59
- it('warns and aborts if no route children are found', () => {
60
- const el = document.createElement(RouterElement.tag) as RouterElement;
61
-
62
- container.appendChild(el);
63
-
64
- expect(warnSpy).toHaveBeenCalledWith(
65
- '[wavelength-router] No <wavelength-route> children found.',
66
- );
67
- expect(routerInstances).toHaveLength(0);
68
- });
69
-
70
- it('filters out invalid route definitions', () => {
71
- const el = document.createElement(RouterElement.tag) as RouterElement;
72
-
73
- el.appendChild(createFakeRoute({ path: '/valid', routeComponent: {} }));
74
- el.appendChild(createFakeRoute(null));
75
-
76
- container.appendChild(el);
77
-
78
- expect(routerInstances).toHaveLength(1);
79
-
80
- const [routes] = routerArgs[0];
81
- expect(routes).toHaveLength(1);
82
- expect(routes[0]!.path).toBe('/valid');
83
- });
84
-
85
- it('starts the router after bootstrap', () => {
86
- const el = document.createElement(RouterElement.tag) as RouterElement;
87
- el.appendChild(createFakeRoute({ path: '/', routeComponent: {} }));
88
-
89
- container.appendChild(el);
90
-
91
- expect(routerInstances[0].start).toHaveBeenCalled();
92
- });
93
-
94
- it('tears down the router on disconnectedCallback', () => {
95
- const el = document.createElement(RouterElement.tag) as RouterElement;
96
- el.appendChild(createFakeRoute({ path: '/', routeComponent: {} }));
97
-
98
- container.appendChild(el);
99
-
100
- const instance = routerInstances[0];
101
-
102
- container.removeChild(el);
103
-
104
- expect(instance.destroy).toHaveBeenCalled();
105
- });
106
-
107
- it('destroys existing router if routes become empty on re-bootstrap', () => {
108
- const el = document.createElement(RouterElement.tag) as RouterElement;
109
- el.appendChild(createFakeRoute({ path: '/', routeComponent: {} }));
110
-
111
- container.appendChild(el);
112
-
113
- const first = routerInstances[0];
114
-
115
- el.innerHTML = '';
116
- el.setAttribute('hash-mode', '');
117
-
118
- expect(first.destroy).toHaveBeenCalled();
119
- expect(routerInstances).toHaveLength(1);
120
- });
121
- });
122
-
123
- describe('Attribute Reactions & Re-bootstrapping', () => {
124
- it('ignores attribute changes if not connected to the DOM', () => {
125
- const el = document.createElement(RouterElement.tag) as RouterElement;
126
-
127
- el.attributeChangedCallback('hash-mode', null, '');
128
-
129
- expect(routerInstances).toHaveLength(0);
130
- });
131
-
132
- it('re-bootstraps when observed attributes change', () => {
133
- const el = document.createElement(RouterElement.tag) as RouterElement;
134
- el.appendChild(createFakeRoute({ path: '/', routeComponent: {} }));
135
-
136
- container.appendChild(el);
137
-
138
- const first = routerInstances[0];
139
-
140
- el.setAttribute('hash-mode', '');
141
-
142
- expect(first.destroy).toHaveBeenCalled();
143
- expect(routerInstances.length).toBeGreaterThan(1);
144
-
145
- const [, options] = routerArgs[1];
146
- expect(options.hashMode).toBe(true);
147
- });
148
- });
149
-
150
- describe('Guard Resolution Table', () => {
151
- it.each([
152
- {
153
- it: 'returns undefined if guard attribute is missing',
154
- setup: () => {},
155
- guardAttr: null,
156
- expectedWarn: false,
157
- expectGuard: false,
158
- },
159
- {
160
- it: 'warns if guard is not found on window',
161
- setup: () => {},
162
- guardAttr: 'missingGuard',
163
- expectedWarn: true,
164
- expectGuard: false,
165
- },
166
- {
167
- it: 'warns if guard is not a function',
168
- setup: () => setWindowProp('notAFunc', 'string'),
169
- guardAttr: 'notAFunc',
170
- expectedWarn: true,
171
- expectGuard: false,
172
- },
173
- {
174
- it: 'resolves and passes valid guard',
175
- setup: () => setWindowProp('validGuard', vi.fn()),
176
- guardAttr: 'validGuard',
177
- expectedWarn: false,
178
- expectGuard: true,
179
- },
180
- ])('$it', ({ setup, guardAttr, expectedWarn, expectGuard }) => {
181
- const cleanup = setup?.();
182
-
183
- const el = document.createElement(RouterElement.tag) as RouterElement;
184
-
185
- if (guardAttr) el.setAttribute('guard', guardAttr);
186
- el.appendChild(createFakeRoute({ path: '/', routeComponent: {} }));
187
-
188
- container.appendChild(el);
189
-
190
- if (expectedWarn) {
191
- expect(warnSpy).toHaveBeenCalledWith(
192
- expect.stringContaining('[wavelength-router] Guard'),
193
- );
194
- } else {
195
- expect(warnSpy).not.toHaveBeenCalled();
196
- }
197
-
198
- const [, options] = routerArgs[0];
199
-
200
- if (expectGuard) {
201
- expect(options.beforeEach).toBeDefined();
202
- } else {
203
- expect(options.beforeEach).toBeUndefined();
204
- }
205
-
206
- // @ts-expect-error (passing wrong parameters)
207
- cleanup?.();
208
- });
209
- });
210
-
211
- describe('Delegated Router Methods Table', () => {
212
- it.each([
213
- { method: 'navigate', args: ['/path'] },
214
- { method: 'replace', args: ['/path'] },
215
- { method: 'back', args: [] },
216
- ] as const)(
217
- 'delegates $method to router safely',
218
- ({ method, args }) => {
219
- const el = document.createElement(RouterElement.tag) as RouterElement;
220
-
221
- expect(() => (el[method] as any)(...args)).not.toThrow();
222
-
223
- el.appendChild(createFakeRoute({ path: '/', routeComponent: {} }));
224
- container.appendChild(el);
225
-
226
- const instance = routerInstances[0];
227
-
228
- (el[method] as any)(...args);
229
-
230
- expect(instance[method]).toHaveBeenCalledWith(...args);
231
- },
232
- );
233
- });
234
-
235
- describe('Router constructor contract', () => {
236
- it('passes correct options and environment', () => {
237
- const el = document.createElement(RouterElement.tag) as RouterElement;
238
-
239
- el.setAttribute('hash-mode', '');
240
- el.appendChild(createFakeRoute({ path: '/', routeComponent: {} }));
241
-
242
- container.appendChild(el);
243
-
244
- const [, options, env] = routerArgs[0];
245
-
246
- expect(options.outlet).toBe(el);
247
- expect(options.hashMode).toBe(true);
248
-
249
- expect(env.window).toBe(window);
250
- expect(env.document).toBe(document);
251
- });
252
- });
253
- });
package/src/index.ts DELETED
@@ -1,15 +0,0 @@
1
- export { Router } from './router/Router.ts';
2
- export { RouterElement } from './elements/RouterElement.ts';
3
- export { RouteElement } from './elements/RouteElement.ts';
4
-
5
- export type {
6
- RouteComponent,
7
- RouteContext,
8
- RouteDefinition,
9
- RouteGuard,
10
- RouteParams,
11
- CompiledRoute,
12
- MatchedRoute,
13
- } from './models/index.ts';
14
-
15
- export { compileRoute, matchRoute } from './utils/index.ts';
@@ -1,7 +0,0 @@
1
- import type { RouteDefinition } from './RouteDefinition.js';
2
-
3
- export interface CompiledRoute {
4
- routeDefinition: RouteDefinition;
5
- pattern: RegExp;
6
- paramNames: string[];
7
- }
@@ -1,7 +0,0 @@
1
- import type { RouteDefinition } from './RouteDefinition.ts';
2
- import type { RouteParams } from './RouteParams.ts';
3
-
4
- export interface MatchedRoute {
5
- routeDefinition: RouteDefinition;
6
- routeParams: RouteParams;
7
- }
@@ -1,6 +0,0 @@
1
- import type { RouteContext } from './RouteContext.ts';
2
-
3
- export interface RouteComponent {
4
- mount(container: HTMLElement, context: RouteContext): void;
5
- unmount?(): void;
6
- }
@@ -1,7 +0,0 @@
1
- import type { RouteParams } from './RouteParams.ts';
2
-
3
- export interface RouteContext {
4
- path: string;
5
- routeParams: RouteParams;
6
- query: URLSearchParams;
7
- }
@@ -1,6 +0,0 @@
1
- import type { RouteComponent } from './RouteComponent.ts';
2
-
3
- export interface RouteDefinition {
4
- path: string;
5
- routeComponent: RouteComponent;
6
- }
@@ -1,6 +0,0 @@
1
- import type { RouteContext } from './RouteContext.ts';
2
-
3
- export type RouteGuard = (
4
- to: RouteContext,
5
- next: (redirect?: string) => void,
6
- ) => void;
@@ -1,3 +0,0 @@
1
- export interface RouteParams {
2
- [key: string]: string;
3
- }
@@ -1,17 +0,0 @@
1
- import type { CompiledRoute } from './CompiledRoute.ts';
2
- import type { MatchedRoute } from './MatchedRoute.ts';
3
- import type { RouteComponent } from './RouteComponent.ts';
4
- import type { RouteContext } from './RouteContext.ts';
5
- import type { RouteDefinition } from './RouteDefinition.ts';
6
- import type { RouteGuard } from './RouteGuard.ts';
7
- import type { RouteParams } from './RouteParams.ts';
8
-
9
- export type {
10
- CompiledRoute,
11
- MatchedRoute,
12
- RouteComponent,
13
- RouteContext,
14
- RouteDefinition,
15
- RouteGuard,
16
- RouteParams,
17
- };