@furystack/shades 14.0.0 → 15.0.0
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/CHANGELOG.md +13 -0
- package/esm/component-factory.spec.d.ts.map +1 -1
- package/esm/component-factory.spec.js +2 -2
- package/esm/component-factory.spec.js.map +1 -1
- package/esm/components/nested-hooks.js +2 -2
- package/esm/components/nested-hooks.js.map +1 -1
- package/esm/components/nested-hooks.spec.js +14 -14
- package/esm/components/nested-hooks.spec.js.map +1 -1
- package/esm/components/nested-navigate.js +1 -1
- package/esm/components/nested-navigate.js.map +1 -1
- package/esm/components/nested-navigate.spec.js +16 -16
- package/esm/components/nested-navigate.spec.js.map +1 -1
- package/esm/components/nested-replace.js +1 -1
- package/esm/components/nested-replace.js.map +1 -1
- package/esm/components/nested-replace.spec.js +6 -6
- package/esm/components/nested-replace.spec.js.map +1 -1
- package/esm/components/nested-route-link.js +1 -1
- package/esm/components/nested-route-link.js.map +1 -1
- package/esm/components/nested-route-link.spec.js +8 -8
- package/esm/components/nested-route-link.spec.js.map +1 -1
- package/esm/components/nested-router.js +3 -3
- package/esm/components/nested-router.js.map +1 -1
- package/esm/components/nested-router.spec.js +24 -24
- package/esm/components/nested-router.spec.js.map +1 -1
- package/esm/services/location-service.d.ts +39 -42
- package/esm/services/location-service.d.ts.map +1 -1
- package/esm/services/location-service.js +115 -134
- package/esm/services/location-service.js.map +1 -1
- package/esm/services/location-service.spec.js +32 -30
- package/esm/services/location-service.spec.js.map +1 -1
- package/esm/services/route-match-service.d.ts +3 -2
- package/esm/services/route-match-service.d.ts.map +1 -1
- package/esm/services/route-match-service.js +13 -22
- package/esm/services/route-match-service.js.map +1 -1
- package/esm/services/route-match-service.spec.js +40 -51
- package/esm/services/route-match-service.spec.js.map +1 -1
- package/esm/services/route-meta-utils.spec.js +3 -3
- package/esm/services/route-meta-utils.spec.js.map +1 -1
- package/esm/services/screen-service.d.ts +13 -72
- package/esm/services/screen-service.d.ts.map +1 -1
- package/esm/services/screen-service.js +45 -137
- package/esm/services/screen-service.js.map +1 -1
- package/esm/services/screen-service.spec.js +19 -18
- package/esm/services/screen-service.spec.js.map +1 -1
- package/esm/services/spatial-navigation-service.d.ts +17 -39
- package/esm/services/spatial-navigation-service.d.ts.map +1 -1
- package/esm/services/spatial-navigation-service.js +280 -313
- package/esm/services/spatial-navigation-service.js.map +1 -1
- package/esm/services/spatial-navigation-service.spec.js +163 -158
- package/esm/services/spatial-navigation-service.spec.js.map +1 -1
- package/esm/shade.d.ts +1 -1
- package/esm/shade.d.ts.map +1 -1
- package/esm/shade.js +10 -7
- package/esm/shade.js.map +1 -1
- package/package.json +5 -4
- package/src/component-factory.spec.tsx +3 -2
- package/src/components/nested-hooks.spec.ts +14 -14
- package/src/components/nested-hooks.ts +2 -2
- package/src/components/nested-navigate.spec.ts +17 -16
- package/src/components/nested-navigate.ts +1 -1
- package/src/components/nested-replace.spec.ts +7 -6
- package/src/components/nested-replace.ts +1 -1
- package/src/components/nested-route-link.spec.tsx +8 -8
- package/src/components/nested-route-link.tsx +1 -1
- package/src/components/nested-router.spec.tsx +24 -24
- package/src/components/nested-router.tsx +3 -3
- package/src/services/location-service.spec.ts +35 -31
- package/src/services/location-service.tsx +156 -122
- package/src/services/route-match-service.spec.ts +41 -32
- package/src/services/route-match-service.ts +17 -8
- package/src/services/route-meta-utils.spec.ts +3 -3
- package/src/services/screen-service.spec.ts +19 -18
- package/src/services/screen-service.ts +65 -110
- package/src/services/spatial-navigation-service.spec.ts +165 -160
- package/src/services/spatial-navigation-service.ts +299 -287
- package/src/shade.ts +11 -8
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { Injector } from '@furystack/inject'
|
|
1
|
+
import type { Injector } from '@furystack/inject'
|
|
2
|
+
import { createInjector } from '@furystack/inject'
|
|
2
3
|
import { usingAsync } from '@furystack/utils'
|
|
3
4
|
import { describe, expect, expectTypeOf, it, vi } from 'vitest'
|
|
4
5
|
import { LocationService } from '../services/location-service.js'
|
|
@@ -10,8 +11,8 @@ type TestRoute = Pick<NestedRoute<unknown, any, any>, 'component'>
|
|
|
10
11
|
|
|
11
12
|
describe('nestedNavigate', () => {
|
|
12
13
|
it('Should navigate to a simple path', async () => {
|
|
13
|
-
await usingAsync(
|
|
14
|
-
const locationService = injector.
|
|
14
|
+
await usingAsync(createInjector(), async (injector) => {
|
|
15
|
+
const locationService = injector.get(LocationService)
|
|
15
16
|
const spy = vi.spyOn(locationService, 'navigate')
|
|
16
17
|
|
|
17
18
|
nestedNavigate(injector, { path: '/buttons' })
|
|
@@ -21,8 +22,8 @@ describe('nestedNavigate', () => {
|
|
|
21
22
|
})
|
|
22
23
|
|
|
23
24
|
it('Should compile params into the path', async () => {
|
|
24
|
-
await usingAsync(
|
|
25
|
-
const locationService = injector.
|
|
25
|
+
await usingAsync(createInjector(), async (injector) => {
|
|
26
|
+
const locationService = injector.get(LocationService)
|
|
26
27
|
const spy = vi.spyOn(locationService, 'navigate')
|
|
27
28
|
|
|
28
29
|
nestedNavigate(injector, { path: '/users/:id', params: { id: '42' } })
|
|
@@ -32,8 +33,8 @@ describe('nestedNavigate', () => {
|
|
|
32
33
|
})
|
|
33
34
|
|
|
34
35
|
it('Should compile multiple params into the path', async () => {
|
|
35
|
-
await usingAsync(
|
|
36
|
-
const locationService = injector.
|
|
36
|
+
await usingAsync(createInjector(), async (injector) => {
|
|
37
|
+
const locationService = injector.get(LocationService)
|
|
37
38
|
const spy = vi.spyOn(locationService, 'navigate')
|
|
38
39
|
|
|
39
40
|
nestedNavigate(injector, {
|
|
@@ -46,8 +47,8 @@ describe('nestedNavigate', () => {
|
|
|
46
47
|
})
|
|
47
48
|
|
|
48
49
|
it('Should append serialized query string when provided', async () => {
|
|
49
|
-
await usingAsync(
|
|
50
|
-
const locationService = injector.
|
|
50
|
+
await usingAsync(createInjector(), async (injector) => {
|
|
51
|
+
const locationService = injector.get(LocationService)
|
|
51
52
|
const spy = vi.spyOn(locationService, 'navigate')
|
|
52
53
|
|
|
53
54
|
nestedNavigate(injector, { path: '/buttons', query: { page: 2 } })
|
|
@@ -62,8 +63,8 @@ describe('nestedNavigate', () => {
|
|
|
62
63
|
})
|
|
63
64
|
|
|
64
65
|
it('Should omit the query string when no keys are provided', async () => {
|
|
65
|
-
await usingAsync(
|
|
66
|
-
const locationService = injector.
|
|
66
|
+
await usingAsync(createInjector(), async (injector) => {
|
|
67
|
+
const locationService = injector.get(LocationService)
|
|
67
68
|
const spy = vi.spyOn(locationService, 'navigate')
|
|
68
69
|
|
|
69
70
|
nestedNavigate(injector, { path: '/buttons', query: {} })
|
|
@@ -73,8 +74,8 @@ describe('nestedNavigate', () => {
|
|
|
73
74
|
})
|
|
74
75
|
|
|
75
76
|
it('Should append the hash segment when provided', async () => {
|
|
76
|
-
await usingAsync(
|
|
77
|
-
const locationService = injector.
|
|
77
|
+
await usingAsync(createInjector(), async (injector) => {
|
|
78
|
+
const locationService = injector.get(LocationService)
|
|
78
79
|
const spy = vi.spyOn(locationService, 'navigate')
|
|
79
80
|
|
|
80
81
|
nestedNavigate(injector, { path: '/buttons', hash: 'overview' })
|
|
@@ -84,8 +85,8 @@ describe('nestedNavigate', () => {
|
|
|
84
85
|
})
|
|
85
86
|
|
|
86
87
|
it('Should combine params, query and hash in the correct order', async () => {
|
|
87
|
-
await usingAsync(
|
|
88
|
-
const locationService = injector.
|
|
88
|
+
await usingAsync(createInjector(), async (injector) => {
|
|
89
|
+
const locationService = injector.get(LocationService)
|
|
89
90
|
const spy = vi.spyOn(locationService, 'navigate')
|
|
90
91
|
|
|
91
92
|
nestedNavigate(injector, {
|
|
@@ -197,7 +198,7 @@ describe('Type utilities', () => {
|
|
|
197
198
|
|
|
198
199
|
const appNavigate = createNestedNavigate<Routes>()
|
|
199
200
|
// @ts-expect-error -- '/nonexistent' is not a valid route path
|
|
200
|
-
appNavigate(
|
|
201
|
+
appNavigate(createInjector(), { path: '/nonexistent' })
|
|
201
202
|
})
|
|
202
203
|
|
|
203
204
|
it('Should accept routes with typed match parameters (NestedRoute<T>)', () => {
|
|
@@ -71,7 +71,7 @@ export const buildNestedNavigateUrl = (args: NestedNavigateArgs): string => {
|
|
|
71
71
|
* @param args - The navigation arguments
|
|
72
72
|
*/
|
|
73
73
|
export const nestedNavigate = (injector: Injector, args: NestedNavigateArgs): void => {
|
|
74
|
-
injector.
|
|
74
|
+
injector.get(LocationService).navigate(buildNestedNavigateUrl(args))
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { Injector } from '@furystack/inject'
|
|
1
|
+
import type { Injector } from '@furystack/inject'
|
|
2
|
+
import { createInjector } from '@furystack/inject'
|
|
2
3
|
import { usingAsync } from '@furystack/utils'
|
|
3
4
|
import { describe, expect, expectTypeOf, it, vi } from 'vitest'
|
|
4
5
|
import { LocationService } from '../services/location-service.js'
|
|
@@ -9,8 +10,8 @@ type TestRoute = Pick<NestedRoute<unknown, any, any>, 'component'>
|
|
|
9
10
|
|
|
10
11
|
describe('nestedReplace', () => {
|
|
11
12
|
it('Should replace the current history entry with a simple path', async () => {
|
|
12
|
-
await usingAsync(
|
|
13
|
-
const locationService = injector.
|
|
13
|
+
await usingAsync(createInjector(), async (injector) => {
|
|
14
|
+
const locationService = injector.get(LocationService)
|
|
14
15
|
const navigateSpy = vi.spyOn(locationService, 'navigate')
|
|
15
16
|
const replaceSpy = vi.spyOn(locationService, 'replace')
|
|
16
17
|
|
|
@@ -22,8 +23,8 @@ describe('nestedReplace', () => {
|
|
|
22
23
|
})
|
|
23
24
|
|
|
24
25
|
it('Should compile params, query and hash for a replace call', async () => {
|
|
25
|
-
await usingAsync(
|
|
26
|
-
const locationService = injector.
|
|
26
|
+
await usingAsync(createInjector(), async (injector) => {
|
|
27
|
+
const locationService = injector.get(LocationService)
|
|
27
28
|
const replaceSpy = vi.spyOn(locationService, 'replace')
|
|
28
29
|
|
|
29
30
|
nestedReplace(injector, {
|
|
@@ -69,7 +70,7 @@ describe('createNestedReplace', () => {
|
|
|
69
70
|
|
|
70
71
|
const appReplace = createNestedReplace<Routes>()
|
|
71
72
|
// @ts-expect-error -- '/nonexistent' is not a valid route path
|
|
72
|
-
appReplace(
|
|
73
|
+
appReplace(createInjector(), { path: '/nonexistent' })
|
|
73
74
|
})
|
|
74
75
|
|
|
75
76
|
it('Should enforce a declared required query shape', () => {
|
|
@@ -18,7 +18,7 @@ import type { NestedRoute } from './nested-router.js'
|
|
|
18
18
|
* @param args - The navigation arguments
|
|
19
19
|
*/
|
|
20
20
|
export const nestedReplace = (injector: Injector, args: NestedNavigateArgs): void => {
|
|
21
|
-
injector.
|
|
21
|
+
injector.get(LocationService).replace(buildNestedNavigateUrl(args))
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createInjector } from '@furystack/inject'
|
|
2
2
|
import { usingAsync } from '@furystack/utils'
|
|
3
3
|
import { afterEach, beforeEach, describe, expect, expectTypeOf, it, vi } from 'vitest'
|
|
4
4
|
import { initializeShadeRoot } from '../initialize.js'
|
|
@@ -24,7 +24,7 @@ describe('NestedRouteLink', () => {
|
|
|
24
24
|
})
|
|
25
25
|
|
|
26
26
|
it('Should render a link with the correct href', async () => {
|
|
27
|
-
await usingAsync(
|
|
27
|
+
await usingAsync(createInjector(), async (injector) => {
|
|
28
28
|
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
29
29
|
|
|
30
30
|
initializeShadeRoot({
|
|
@@ -44,11 +44,11 @@ describe('NestedRouteLink', () => {
|
|
|
44
44
|
})
|
|
45
45
|
|
|
46
46
|
it('Should trigger SPA navigation on click', async () => {
|
|
47
|
-
await usingAsync(
|
|
47
|
+
await usingAsync(createInjector(), async (injector) => {
|
|
48
48
|
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
49
49
|
const onRouteChange = vi.fn()
|
|
50
50
|
|
|
51
|
-
injector.
|
|
51
|
+
injector.get(LocationService).onLocationPathChanged.subscribe(onRouteChange)
|
|
52
52
|
|
|
53
53
|
initializeShadeRoot({
|
|
54
54
|
injector,
|
|
@@ -68,7 +68,7 @@ describe('NestedRouteLink', () => {
|
|
|
68
68
|
})
|
|
69
69
|
|
|
70
70
|
it('Should compile route params in the href', async () => {
|
|
71
|
-
await usingAsync(
|
|
71
|
+
await usingAsync(createInjector(), async (injector) => {
|
|
72
72
|
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
73
73
|
|
|
74
74
|
initializeShadeRoot({
|
|
@@ -88,7 +88,7 @@ describe('NestedRouteLink', () => {
|
|
|
88
88
|
})
|
|
89
89
|
|
|
90
90
|
it('Should compile route params with multiple segments', async () => {
|
|
91
|
-
await usingAsync(
|
|
91
|
+
await usingAsync(createInjector(), async (injector) => {
|
|
92
92
|
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
93
93
|
|
|
94
94
|
initializeShadeRoot({
|
|
@@ -108,7 +108,7 @@ describe('NestedRouteLink', () => {
|
|
|
108
108
|
})
|
|
109
109
|
|
|
110
110
|
it('Should append a serialized query string to the rendered href', async () => {
|
|
111
|
-
await usingAsync(
|
|
111
|
+
await usingAsync(createInjector(), async (injector) => {
|
|
112
112
|
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
113
113
|
|
|
114
114
|
initializeShadeRoot({
|
|
@@ -128,7 +128,7 @@ describe('NestedRouteLink', () => {
|
|
|
128
128
|
})
|
|
129
129
|
|
|
130
130
|
it('Should append the hash segment to the rendered href', async () => {
|
|
131
|
-
await usingAsync(
|
|
131
|
+
await usingAsync(createInjector(), async (injector) => {
|
|
132
132
|
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
133
133
|
|
|
134
134
|
initializeShadeRoot({
|
|
@@ -73,7 +73,7 @@ const _NestedRouteLink = Shade<NestedRouteLinkProps>({
|
|
|
73
73
|
ev.preventDefault()
|
|
74
74
|
// eslint-disable-next-line furystack/prefer-location-service -- This IS the SPA link component; it must call pushState directly.
|
|
75
75
|
history.pushState('', props.title || '', resolvedUrl)
|
|
76
|
-
injector.
|
|
76
|
+
injector.get(LocationService).updateState()
|
|
77
77
|
},
|
|
78
78
|
})
|
|
79
79
|
return <>{children}</>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createInjector } from '@furystack/inject'
|
|
2
2
|
import { serializeValue } from '@furystack/rest'
|
|
3
3
|
import { sleepAsync, usingAsync } from '@furystack/utils'
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
@@ -438,7 +438,7 @@ describe('NestedRouter lifecycle hooks', () => {
|
|
|
438
438
|
callOrder.push('leave-other')
|
|
439
439
|
})
|
|
440
440
|
|
|
441
|
-
await usingAsync(
|
|
441
|
+
await usingAsync(createInjector(), async (injector) => {
|
|
442
442
|
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
443
443
|
|
|
444
444
|
initializeShadeRoot({
|
|
@@ -556,7 +556,7 @@ describe('NestedRouter query / hash re-render', () => {
|
|
|
556
556
|
const onLeave = vi.fn(async () => {})
|
|
557
557
|
const componentFn = vi.fn(({ hash }: { hash: string | undefined }) => <div id="content">hash={hash ?? 'none'}</div>)
|
|
558
558
|
|
|
559
|
-
await usingAsync(
|
|
559
|
+
await usingAsync(createInjector(), async (injector) => {
|
|
560
560
|
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
561
561
|
|
|
562
562
|
initializeShadeRoot({
|
|
@@ -581,7 +581,7 @@ describe('NestedRouter query / hash re-render', () => {
|
|
|
581
581
|
expect(onVisit).toBeCalledTimes(1)
|
|
582
582
|
expect(onLeave).not.toBeCalled()
|
|
583
583
|
|
|
584
|
-
const locationService = injector.
|
|
584
|
+
const locationService = injector.get(LocationService)
|
|
585
585
|
locationService.navigate('/tabs#a')
|
|
586
586
|
await flushUpdates()
|
|
587
587
|
await flushUpdates()
|
|
@@ -609,7 +609,7 @@ describe('NestedRouter query / hash re-render', () => {
|
|
|
609
609
|
<div id="content">page={query?.page ?? 'none'}</div>
|
|
610
610
|
))
|
|
611
611
|
|
|
612
|
-
await usingAsync(
|
|
612
|
+
await usingAsync(createInjector(), async (injector) => {
|
|
613
613
|
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
614
614
|
|
|
615
615
|
initializeShadeRoot({
|
|
@@ -633,7 +633,7 @@ describe('NestedRouter query / hash re-render', () => {
|
|
|
633
633
|
expect(document.getElementById('content')?.innerHTML).toBe('page=none')
|
|
634
634
|
expect(onVisit).toBeCalledTimes(1)
|
|
635
635
|
|
|
636
|
-
const locationService = injector.
|
|
636
|
+
const locationService = injector.get(LocationService)
|
|
637
637
|
locationService.navigate(`/list?page=${serializeValue(2)}`)
|
|
638
638
|
await flushUpdates()
|
|
639
639
|
await flushUpdates()
|
|
@@ -662,7 +662,7 @@ describe('NestedRouter query / hash re-render', () => {
|
|
|
662
662
|
</div>
|
|
663
663
|
))
|
|
664
664
|
|
|
665
|
-
await usingAsync(
|
|
665
|
+
await usingAsync(createInjector(), async (injector) => {
|
|
666
666
|
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
667
667
|
|
|
668
668
|
initializeShadeRoot({
|
|
@@ -686,7 +686,7 @@ describe('NestedRouter query / hash re-render', () => {
|
|
|
686
686
|
expect(document.getElementById('content')?.innerHTML).toBe('a')
|
|
687
687
|
bComponent.mockClear()
|
|
688
688
|
|
|
689
|
-
injector.
|
|
689
|
+
injector.get(LocationService).navigate(`/b?page=${serializeValue(7)}#x`)
|
|
690
690
|
await flushUpdates()
|
|
691
691
|
await flushUpdates()
|
|
692
692
|
|
|
@@ -731,7 +731,7 @@ describe('NestedRouter latest-wins on rapid navigation', () => {
|
|
|
731
731
|
callOrder.push('leave-c')
|
|
732
732
|
})
|
|
733
733
|
|
|
734
|
-
await usingAsync(
|
|
734
|
+
await usingAsync(createInjector(), async (injector) => {
|
|
735
735
|
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
736
736
|
|
|
737
737
|
initializeShadeRoot({
|
|
@@ -813,7 +813,7 @@ describe('NestedRouter lifecycle element scope', () => {
|
|
|
813
813
|
const visitElements: Array<{ route: string; element: JSX.Element }> = []
|
|
814
814
|
const leaveElements: Array<{ route: string; element: JSX.Element }> = []
|
|
815
815
|
|
|
816
|
-
await usingAsync(
|
|
816
|
+
await usingAsync(createInjector(), async (injector) => {
|
|
817
817
|
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
818
818
|
|
|
819
819
|
initializeShadeRoot({
|
|
@@ -906,7 +906,7 @@ describe('NestedRouter flat routes', () => {
|
|
|
906
906
|
it('should render and navigate between flat (non-nested) Record routes', async () => {
|
|
907
907
|
history.pushState(null, '', '/')
|
|
908
908
|
|
|
909
|
-
await usingAsync(
|
|
909
|
+
await usingAsync(createInjector(), async (injector) => {
|
|
910
910
|
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
911
911
|
|
|
912
912
|
initializeShadeRoot({
|
|
@@ -967,7 +967,7 @@ describe('NestedRouter outlet composition', () => {
|
|
|
967
967
|
it('should compose parent layout wrapping child content via outlet', async () => {
|
|
968
968
|
history.pushState(null, '', '/dashboard/settings')
|
|
969
969
|
|
|
970
|
-
await usingAsync(
|
|
970
|
+
await usingAsync(createInjector(), async (injector) => {
|
|
971
971
|
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
972
972
|
|
|
973
973
|
initializeShadeRoot({
|
|
@@ -1012,7 +1012,7 @@ describe('NestedRouter outlet composition', () => {
|
|
|
1012
1012
|
it('should render parent with fallback when navigating to parent URL without a child match', async () => {
|
|
1013
1013
|
history.pushState(null, '', '/dashboard')
|
|
1014
1014
|
|
|
1015
|
-
await usingAsync(
|
|
1015
|
+
await usingAsync(createInjector(), async (injector) => {
|
|
1016
1016
|
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
1017
1017
|
|
|
1018
1018
|
initializeShadeRoot({
|
|
@@ -1065,7 +1065,7 @@ describe('NestedRouter route param changes', () => {
|
|
|
1065
1065
|
callOrder.push('leave-user')
|
|
1066
1066
|
})
|
|
1067
1067
|
|
|
1068
|
-
await usingAsync(
|
|
1068
|
+
await usingAsync(createInjector(), async (injector) => {
|
|
1069
1069
|
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
1070
1070
|
|
|
1071
1071
|
initializeShadeRoot({
|
|
@@ -1141,7 +1141,7 @@ describe('NestedRouter route param changes', () => {
|
|
|
1141
1141
|
const onVisitDash = vi.fn()
|
|
1142
1142
|
const onLeaveDash = vi.fn()
|
|
1143
1143
|
|
|
1144
|
-
await usingAsync(
|
|
1144
|
+
await usingAsync(createInjector(), async (injector) => {
|
|
1145
1145
|
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
1146
1146
|
|
|
1147
1147
|
initializeShadeRoot({
|
|
@@ -1214,9 +1214,9 @@ describe('NestedRouter + RouteMatchService integration', () => {
|
|
|
1214
1214
|
it('should update RouteMatchService with the current match chain on navigation', async () => {
|
|
1215
1215
|
history.pushState(null, '', '/parent/child-a')
|
|
1216
1216
|
|
|
1217
|
-
await usingAsync(
|
|
1217
|
+
await usingAsync(createInjector(), async (injector) => {
|
|
1218
1218
|
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
1219
|
-
const routeMatchService = injector.
|
|
1219
|
+
const routeMatchService = injector.get(RouteMatchService)
|
|
1220
1220
|
|
|
1221
1221
|
const parentRoute: NestedRoute = {
|
|
1222
1222
|
meta: { title: 'Parent' },
|
|
@@ -1278,9 +1278,9 @@ describe('NestedRouter + RouteMatchService integration', () => {
|
|
|
1278
1278
|
it('should expose match params through RouteMatchService', async () => {
|
|
1279
1279
|
history.pushState(null, '', '/users/42')
|
|
1280
1280
|
|
|
1281
|
-
await usingAsync(
|
|
1281
|
+
await usingAsync(createInjector(), async (injector) => {
|
|
1282
1282
|
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
1283
|
-
const routeMatchService = injector.
|
|
1283
|
+
const routeMatchService = injector.get(RouteMatchService)
|
|
1284
1284
|
|
|
1285
1285
|
initializeShadeRoot({
|
|
1286
1286
|
injector,
|
|
@@ -1378,7 +1378,7 @@ describe('NestedRouter view transitions', () => {
|
|
|
1378
1378
|
it('should call startViewTransition when viewTransition is enabled', async () => {
|
|
1379
1379
|
history.pushState(null, '', '/')
|
|
1380
1380
|
|
|
1381
|
-
await usingAsync(
|
|
1381
|
+
await usingAsync(createInjector(), async (injector) => {
|
|
1382
1382
|
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
1383
1383
|
|
|
1384
1384
|
initializeShadeRoot({
|
|
@@ -1414,7 +1414,7 @@ describe('NestedRouter view transitions', () => {
|
|
|
1414
1414
|
it('should not call startViewTransition when viewTransition is not set', async () => {
|
|
1415
1415
|
history.pushState(null, '', '/')
|
|
1416
1416
|
|
|
1417
|
-
await usingAsync(
|
|
1417
|
+
await usingAsync(createInjector(), async (injector) => {
|
|
1418
1418
|
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
1419
1419
|
|
|
1420
1420
|
initializeShadeRoot({
|
|
@@ -1449,7 +1449,7 @@ describe('NestedRouter view transitions', () => {
|
|
|
1449
1449
|
it('should pass types to startViewTransition when configured', async () => {
|
|
1450
1450
|
history.pushState(null, '', '/')
|
|
1451
1451
|
|
|
1452
|
-
await usingAsync(
|
|
1452
|
+
await usingAsync(createInjector(), async (injector) => {
|
|
1453
1453
|
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
1454
1454
|
|
|
1455
1455
|
initializeShadeRoot({
|
|
@@ -1484,7 +1484,7 @@ describe('NestedRouter view transitions', () => {
|
|
|
1484
1484
|
it('should respect per-route viewTransition: false override', async () => {
|
|
1485
1485
|
history.pushState(null, '', '/')
|
|
1486
1486
|
|
|
1487
|
-
await usingAsync(
|
|
1487
|
+
await usingAsync(createInjector(), async (injector) => {
|
|
1488
1488
|
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
1489
1489
|
|
|
1490
1490
|
initializeShadeRoot({
|
|
@@ -1525,7 +1525,7 @@ describe('NestedRouter view transitions', () => {
|
|
|
1525
1525
|
|
|
1526
1526
|
history.pushState(null, '', '/')
|
|
1527
1527
|
|
|
1528
|
-
await usingAsync(
|
|
1528
|
+
await usingAsync(createInjector(), async (injector) => {
|
|
1529
1529
|
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
1530
1530
|
|
|
1531
1531
|
initializeShadeRoot({
|
|
@@ -386,7 +386,7 @@ export const NestedRouter = Shade<NestedRouterProps>({
|
|
|
386
386
|
chainElements: [],
|
|
387
387
|
})
|
|
388
388
|
|
|
389
|
-
const locationService = injector.
|
|
389
|
+
const locationService = injector.get(LocationService)
|
|
390
390
|
|
|
391
391
|
const updateUrl = async (currentUrl: string) => {
|
|
392
392
|
const [lastState] = useState<NestedRouterState>('routerState', state)
|
|
@@ -426,7 +426,7 @@ export const NestedRouter = Shade<NestedRouterProps>({
|
|
|
426
426
|
|
|
427
427
|
const applyUpdate = () => {
|
|
428
428
|
setState({ matchChain: newChain, jsx: newResult.jsx, chainElements: newResult.chainElements })
|
|
429
|
-
injector.
|
|
429
|
+
injector.get(RouteMatchService).currentMatchChain.setValue(newChain)
|
|
430
430
|
}
|
|
431
431
|
|
|
432
432
|
const vtConfig = resolveViewTransition(options.props.viewTransition, newChain)
|
|
@@ -451,7 +451,7 @@ export const NestedRouter = Shade<NestedRouterProps>({
|
|
|
451
451
|
jsx: options.props.notFound || <div />,
|
|
452
452
|
chainElements: [],
|
|
453
453
|
})
|
|
454
|
-
injector.
|
|
454
|
+
injector.get(RouteMatchService).currentMatchChain.setValue([])
|
|
455
455
|
}
|
|
456
456
|
|
|
457
457
|
await maybeViewTransition(options.props.viewTransition, applyNotFound)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createInjector } from '@furystack/inject'
|
|
2
2
|
import { deserializeQueryString, serializeToQueryString, serializeValue } from '@furystack/rest'
|
|
3
3
|
import { usingAsync } from '@furystack/utils'
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
@@ -13,16 +13,17 @@ describe('LocationService', () => {
|
|
|
13
13
|
})
|
|
14
14
|
|
|
15
15
|
it('Shuld be constructed', async () => {
|
|
16
|
-
await usingAsync(
|
|
17
|
-
const s = i.
|
|
18
|
-
expect(s).
|
|
16
|
+
await usingAsync(createInjector(), async (i) => {
|
|
17
|
+
const s = i.get(LocationService)
|
|
18
|
+
expect(s).toBeDefined()
|
|
19
|
+
expect(typeof s.navigate).toBe('function')
|
|
19
20
|
})
|
|
20
21
|
})
|
|
21
22
|
|
|
22
23
|
it('Shuld update state on events', async () => {
|
|
23
|
-
await usingAsync(
|
|
24
|
+
await usingAsync(createInjector(), async (i) => {
|
|
24
25
|
const onLocaionChanged = vi.fn()
|
|
25
|
-
const s = i.
|
|
26
|
+
const s = i.get(LocationService)
|
|
26
27
|
s.onLocationPathChanged.subscribe(onLocaionChanged)
|
|
27
28
|
expect(onLocaionChanged).toBeCalledTimes(0)
|
|
28
29
|
history.pushState(null, '', '/loc1')
|
|
@@ -39,9 +40,9 @@ describe('LocationService', () => {
|
|
|
39
40
|
})
|
|
40
41
|
|
|
41
42
|
it('Should update location path when navigate is called', async () => {
|
|
42
|
-
await usingAsync(
|
|
43
|
+
await usingAsync(createInjector(), async (i) => {
|
|
43
44
|
const onLocationChanged = vi.fn()
|
|
44
|
-
const s = i.
|
|
45
|
+
const s = i.get(LocationService)
|
|
45
46
|
s.onLocationPathChanged.subscribe(onLocationChanged)
|
|
46
47
|
s.navigate('/dashboard')
|
|
47
48
|
expect(s.onLocationPathChanged.getValue()).toBe('/dashboard')
|
|
@@ -51,8 +52,8 @@ describe('LocationService', () => {
|
|
|
51
52
|
|
|
52
53
|
describe('replace', () => {
|
|
53
54
|
it('Should update the observable path without pushing a new history entry', async () => {
|
|
54
|
-
await usingAsync(
|
|
55
|
-
const s = i.
|
|
55
|
+
await usingAsync(createInjector(), async (i) => {
|
|
56
|
+
const s = i.get(LocationService)
|
|
56
57
|
const lengthBefore = history.length
|
|
57
58
|
s.replace('/replaced')
|
|
58
59
|
expect(s.onLocationPathChanged.getValue()).toBe('/replaced')
|
|
@@ -61,8 +62,8 @@ describe('LocationService', () => {
|
|
|
61
62
|
})
|
|
62
63
|
|
|
63
64
|
it('Should call history.replaceState rather than pushState', async () => {
|
|
64
|
-
await usingAsync(
|
|
65
|
-
const s = i.
|
|
65
|
+
await usingAsync(createInjector(), async (i) => {
|
|
66
|
+
const s = i.get(LocationService)
|
|
66
67
|
const pushSpy = vi.spyOn(history, 'pushState')
|
|
67
68
|
const replaceSpy = vi.spyOn(history, 'replaceState')
|
|
68
69
|
|
|
@@ -78,8 +79,8 @@ describe('LocationService', () => {
|
|
|
78
79
|
})
|
|
79
80
|
|
|
80
81
|
it('Should notify path subscribers after replace', async () => {
|
|
81
|
-
await usingAsync(
|
|
82
|
-
const s = i.
|
|
82
|
+
await usingAsync(createInjector(), async (i) => {
|
|
83
|
+
const s = i.get(LocationService)
|
|
83
84
|
const onLocationChanged = vi.fn()
|
|
84
85
|
s.onLocationPathChanged.subscribe(onLocationChanged)
|
|
85
86
|
s.replace('/notify')
|
|
@@ -90,8 +91,8 @@ describe('LocationService', () => {
|
|
|
90
91
|
|
|
91
92
|
describe('useSearchParam', () => {
|
|
92
93
|
it('Should create observables lazily', async () => {
|
|
93
|
-
await usingAsync(
|
|
94
|
-
const service = i.
|
|
94
|
+
await usingAsync(createInjector(), async (i) => {
|
|
95
|
+
const service = i.get(LocationService)
|
|
95
96
|
const observables = service.searchParamObservables
|
|
96
97
|
|
|
97
98
|
const testSearchParam = service.useSearchParam('test', null)
|
|
@@ -110,16 +111,16 @@ describe('LocationService', () => {
|
|
|
110
111
|
})
|
|
111
112
|
|
|
112
113
|
it('Should return the default value, if not present in the query string', async () => {
|
|
113
|
-
await usingAsync(
|
|
114
|
-
const service = i.
|
|
114
|
+
await usingAsync(createInjector(), async (i) => {
|
|
115
|
+
const service = i.get(LocationService)
|
|
115
116
|
const testSearchParam = service.useSearchParam('test', { value: 'foo' })
|
|
116
117
|
expect(testSearchParam.getValue()).toEqual({ value: 'foo' })
|
|
117
118
|
})
|
|
118
119
|
})
|
|
119
120
|
|
|
120
121
|
it('Should return the value from the query string', async () => {
|
|
121
|
-
await usingAsync(
|
|
122
|
-
const service = i.
|
|
122
|
+
await usingAsync(createInjector(), async (i) => {
|
|
123
|
+
const service = i.get(LocationService)
|
|
123
124
|
history.pushState(null, '', `/loc1?test=${serializeValue(1)}`)
|
|
124
125
|
const testSearchParam = service.useSearchParam('test', 123)
|
|
125
126
|
expect(testSearchParam.getValue()).toBe(1)
|
|
@@ -127,8 +128,8 @@ describe('LocationService', () => {
|
|
|
127
128
|
})
|
|
128
129
|
|
|
129
130
|
it('should update the observable value on push / replace states', async () => {
|
|
130
|
-
await usingAsync(
|
|
131
|
-
const service = i.
|
|
131
|
+
await usingAsync(createInjector(), async (i) => {
|
|
132
|
+
const service = i.get(LocationService)
|
|
132
133
|
history.pushState(null, '', `/loc1?test=${serializeValue(1)}`)
|
|
133
134
|
const testSearchParam = service.useSearchParam('test', 234)
|
|
134
135
|
expect(testSearchParam.getValue()).toBe(1)
|
|
@@ -138,8 +139,8 @@ describe('LocationService', () => {
|
|
|
138
139
|
})
|
|
139
140
|
|
|
140
141
|
it('Should update the URL based on search value change', async () => {
|
|
141
|
-
await usingAsync(
|
|
142
|
-
const service = i.
|
|
142
|
+
await usingAsync(createInjector(), async (i) => {
|
|
143
|
+
const service = i.get(LocationService)
|
|
143
144
|
history.pushState(null, '', `/loc1?test=${serializeValue('2')}`)
|
|
144
145
|
const testSearchParam = service.useSearchParam('test', '')
|
|
145
146
|
testSearchParam.setValue('2')
|
|
@@ -147,25 +148,28 @@ describe('LocationService', () => {
|
|
|
147
148
|
})
|
|
148
149
|
})
|
|
149
150
|
|
|
150
|
-
it('Should throw
|
|
151
|
-
await usingAsync(
|
|
151
|
+
it('Should throw when called after LocationService has been resolved', async () => {
|
|
152
|
+
await usingAsync(createInjector(), async (i) => {
|
|
152
153
|
const customSerializer = vi.fn((value: any) => serializeToQueryString(value))
|
|
153
154
|
const customDeserializer = vi.fn((value: string) => deserializeQueryString(value))
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
|
|
156
|
+
// Eagerly resolve once so the service patches history / adds listeners.
|
|
157
|
+
i.get(LocationService)
|
|
158
|
+
|
|
159
|
+
expect(() => useCustomSearchStateSerializer(i, customSerializer, customDeserializer)).toThrow(
|
|
160
|
+
/before LocationService is resolved/,
|
|
157
161
|
)
|
|
158
162
|
})
|
|
159
163
|
})
|
|
160
164
|
|
|
161
165
|
it('Should use custom serializer and deserializer', async () => {
|
|
162
|
-
await usingAsync(
|
|
166
|
+
await usingAsync(createInjector(), async (i) => {
|
|
163
167
|
const customSerializer = vi.fn((value: any) => serializeToQueryString(value))
|
|
164
168
|
const customDeserializer = vi.fn((value: string) => deserializeQueryString(value))
|
|
165
169
|
|
|
166
170
|
useCustomSearchStateSerializer(i, customSerializer, customDeserializer)
|
|
167
171
|
|
|
168
|
-
const locationService = i.
|
|
172
|
+
const locationService = i.get(LocationService)
|
|
169
173
|
const testSearchParam = locationService.useSearchParam('test', { value: 'foo' })
|
|
170
174
|
|
|
171
175
|
testSearchParam.setValue({ value: 'bar' })
|