@typed/navigation 0.5.3 → 0.6.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.
Files changed (186) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +5 -0
  3. package/dist/cjs/Blocking.js +53 -0
  4. package/dist/cjs/Blocking.js.map +1 -0
  5. package/dist/cjs/Layer.js +27 -0
  6. package/dist/cjs/Layer.js.map +1 -0
  7. package/dist/cjs/Navigation.js +184 -62
  8. package/dist/cjs/Navigation.js.map +1 -1
  9. package/dist/cjs/index.js +36 -17
  10. package/dist/cjs/index.js.map +1 -1
  11. package/dist/cjs/internal/fromWindow.js +376 -0
  12. package/dist/cjs/internal/fromWindow.js.map +1 -0
  13. package/dist/cjs/internal/memory.js +67 -0
  14. package/dist/cjs/internal/memory.js.map +1 -0
  15. package/dist/cjs/internal/shared.js +403 -0
  16. package/dist/cjs/internal/shared.js.map +1 -0
  17. package/dist/dts/Blocking.d.ts +33 -0
  18. package/dist/dts/Blocking.d.ts.map +1 -0
  19. package/dist/dts/Layer.d.ts +44 -0
  20. package/dist/dts/Layer.d.ts.map +1 -0
  21. package/dist/dts/Navigation.d.ts +377 -0
  22. package/dist/dts/Navigation.d.ts.map +1 -0
  23. package/dist/dts/index.d.ts +17 -0
  24. package/dist/dts/index.d.ts.map +1 -0
  25. package/dist/dts/internal/fromWindow.d.ts +12 -0
  26. package/dist/dts/internal/fromWindow.d.ts.map +1 -0
  27. package/dist/dts/internal/memory.d.ts +6 -0
  28. package/dist/dts/internal/memory.d.ts.map +1 -0
  29. package/dist/dts/internal/shared.d.ts +139 -0
  30. package/dist/dts/internal/shared.d.ts.map +1 -0
  31. package/dist/esm/Blocking.js +39 -0
  32. package/dist/esm/Blocking.js.map +1 -0
  33. package/dist/esm/Layer.js +18 -0
  34. package/dist/esm/Layer.js.map +1 -0
  35. package/dist/esm/Navigation.js +166 -0
  36. package/dist/esm/Navigation.js.map +1 -0
  37. package/dist/esm/index.js +17 -0
  38. package/dist/esm/index.js.map +1 -0
  39. package/dist/esm/internal/fromWindow.js +273 -0
  40. package/dist/esm/internal/fromWindow.js.map +1 -0
  41. package/dist/esm/internal/memory.js +54 -0
  42. package/dist/esm/internal/memory.js.map +1 -0
  43. package/dist/esm/internal/shared.js +336 -0
  44. package/dist/esm/internal/shared.js.map +1 -0
  45. package/dist/esm/package.json +4 -0
  46. package/package.json +52 -32
  47. package/src/Blocking.ts +102 -0
  48. package/src/Layer.ts +53 -0
  49. package/src/Navigation.ts +342 -159
  50. package/src/index.ts +17 -3
  51. package/src/internal/fromWindow.ts +421 -0
  52. package/src/internal/memory.ts +79 -0
  53. package/src/internal/shared.ts +514 -0
  54. package/dist/DOM.d.ts +0 -12
  55. package/dist/DOM.d.ts.map +0 -1
  56. package/dist/DOM.js +0 -87
  57. package/dist/DOM.js.map +0 -1
  58. package/dist/Memory.d.ts +0 -10
  59. package/dist/Memory.d.ts.map +0 -1
  60. package/dist/Memory.js +0 -55
  61. package/dist/Memory.js.map +0 -1
  62. package/dist/Navigation.d.ts +0 -116
  63. package/dist/Navigation.d.ts.map +0 -1
  64. package/dist/Navigation.js +0 -36
  65. package/dist/Navigation.js.map +0 -1
  66. package/dist/_makeServerWindow.d.ts +0 -16
  67. package/dist/_makeServerWindow.d.ts.map +0 -1
  68. package/dist/_makeServerWindow.js +0 -9
  69. package/dist/_makeServerWindow.js.map +0 -1
  70. package/dist/cjs/DOM.d.ts +0 -12
  71. package/dist/cjs/DOM.d.ts.map +0 -1
  72. package/dist/cjs/DOM.js +0 -116
  73. package/dist/cjs/DOM.js.map +0 -1
  74. package/dist/cjs/Memory.d.ts +0 -10
  75. package/dist/cjs/Memory.d.ts.map +0 -1
  76. package/dist/cjs/Memory.js +0 -82
  77. package/dist/cjs/Memory.js.map +0 -1
  78. package/dist/cjs/Navigation.d.ts +0 -116
  79. package/dist/cjs/Navigation.d.ts.map +0 -1
  80. package/dist/cjs/_makeServerWindow.d.ts +0 -16
  81. package/dist/cjs/_makeServerWindow.d.ts.map +0 -1
  82. package/dist/cjs/_makeServerWindow.js +0 -36
  83. package/dist/cjs/_makeServerWindow.js.map +0 -1
  84. package/dist/cjs/constant.d.ts +0 -2
  85. package/dist/cjs/constant.d.ts.map +0 -1
  86. package/dist/cjs/constant.js +0 -30
  87. package/dist/cjs/constant.js.map +0 -1
  88. package/dist/cjs/dom-intent.d.ts +0 -28
  89. package/dist/cjs/dom-intent.d.ts.map +0 -1
  90. package/dist/cjs/dom-intent.js +0 -172
  91. package/dist/cjs/dom-intent.js.map +0 -1
  92. package/dist/cjs/history.d.ts +0 -31
  93. package/dist/cjs/history.d.ts.map +0 -1
  94. package/dist/cjs/history.js +0 -131
  95. package/dist/cjs/history.js.map +0 -1
  96. package/dist/cjs/index.d.ts +0 -4
  97. package/dist/cjs/index.d.ts.map +0 -1
  98. package/dist/cjs/json.d.ts +0 -13
  99. package/dist/cjs/json.d.ts.map +0 -1
  100. package/dist/cjs/json.js +0 -24
  101. package/dist/cjs/json.js.map +0 -1
  102. package/dist/cjs/memory-intent.d.ts +0 -27
  103. package/dist/cjs/memory-intent.d.ts.map +0 -1
  104. package/dist/cjs/memory-intent.js +0 -156
  105. package/dist/cjs/memory-intent.js.map +0 -1
  106. package/dist/cjs/model.d.ts +0 -22
  107. package/dist/cjs/model.d.ts.map +0 -1
  108. package/dist/cjs/model.js +0 -48
  109. package/dist/cjs/model.js.map +0 -1
  110. package/dist/cjs/shared-intent.d.ts +0 -14
  111. package/dist/cjs/shared-intent.d.ts.map +0 -1
  112. package/dist/cjs/shared-intent.js +0 -82
  113. package/dist/cjs/shared-intent.js.map +0 -1
  114. package/dist/cjs/storage.d.ts +0 -19
  115. package/dist/cjs/storage.d.ts.map +0 -1
  116. package/dist/cjs/storage.js +0 -101
  117. package/dist/cjs/storage.js.map +0 -1
  118. package/dist/cjs/util.d.ts +0 -5
  119. package/dist/cjs/util.d.ts.map +0 -1
  120. package/dist/cjs/util.js +0 -39
  121. package/dist/cjs/util.js.map +0 -1
  122. package/dist/constant.d.ts +0 -2
  123. package/dist/constant.d.ts.map +0 -1
  124. package/dist/constant.js +0 -4
  125. package/dist/constant.js.map +0 -1
  126. package/dist/dom-intent.d.ts +0 -28
  127. package/dist/dom-intent.d.ts.map +0 -1
  128. package/dist/dom-intent.js +0 -140
  129. package/dist/dom-intent.js.map +0 -1
  130. package/dist/history.d.ts +0 -31
  131. package/dist/history.d.ts.map +0 -1
  132. package/dist/history.js +0 -104
  133. package/dist/history.js.map +0 -1
  134. package/dist/index.d.ts +0 -4
  135. package/dist/index.d.ts.map +0 -1
  136. package/dist/index.js +0 -4
  137. package/dist/index.js.map +0 -1
  138. package/dist/intent.d.ts +0 -31
  139. package/dist/intent.d.ts.map +0 -1
  140. package/dist/intent.js +0 -157
  141. package/dist/intent.js.map +0 -1
  142. package/dist/json.d.ts +0 -13
  143. package/dist/json.d.ts.map +0 -1
  144. package/dist/json.js +0 -17
  145. package/dist/json.js.map +0 -1
  146. package/dist/memory-intent.d.ts +0 -27
  147. package/dist/memory-intent.d.ts.map +0 -1
  148. package/dist/memory-intent.js +0 -124
  149. package/dist/memory-intent.js.map +0 -1
  150. package/dist/model.d.ts +0 -22
  151. package/dist/model.d.ts.map +0 -1
  152. package/dist/model.js +0 -21
  153. package/dist/model.js.map +0 -1
  154. package/dist/shared-intent.d.ts +0 -14
  155. package/dist/shared-intent.d.ts.map +0 -1
  156. package/dist/shared-intent.js +0 -51
  157. package/dist/shared-intent.js.map +0 -1
  158. package/dist/storage.d.ts +0 -19
  159. package/dist/storage.d.ts.map +0 -1
  160. package/dist/storage.js +0 -73
  161. package/dist/storage.js.map +0 -1
  162. package/dist/tsconfig.cjs.build.tsbuildinfo +0 -1
  163. package/dist/util.d.ts +0 -5
  164. package/dist/util.d.ts.map +0 -1
  165. package/dist/util.js +0 -12
  166. package/dist/util.js.map +0 -1
  167. package/eslintrc.json +0 -3
  168. package/project.json +0 -46
  169. package/src/DOM.test.ts +0 -699
  170. package/src/DOM.ts +0 -163
  171. package/src/Memory.test.ts +0 -464
  172. package/src/Memory.ts +0 -102
  173. package/src/_makeServerWindow.ts +0 -28
  174. package/src/dom-intent.ts +0 -268
  175. package/src/history.ts +0 -165
  176. package/src/json.ts +0 -31
  177. package/src/memory-intent.ts +0 -224
  178. package/src/model.ts +0 -54
  179. package/src/shared-intent.ts +0 -117
  180. package/src/storage.ts +0 -101
  181. package/src/util.ts +0 -20
  182. package/tsconfig.build.json +0 -4
  183. package/tsconfig.build.tsbuildinfo +0 -1
  184. package/tsconfig.cjs.build.json +0 -22
  185. package/tsconfig.json +0 -27
  186. package/vite.config.mjs +0 -3
package/src/DOM.ts DELETED
@@ -1,163 +0,0 @@
1
- import { pipe } from '@effect/data/Function'
2
- import * as Option from '@effect/data/Option'
3
- import * as Cause from '@effect/io/Cause'
4
- import * as Effect from '@effect/io/Effect'
5
- import * as Layer from '@effect/io/Layer'
6
- import * as Context from '@typed/context'
7
- import { Location, History, Window, Storage, addWindowListener, Document } from '@typed/dom'
8
- import * as Fx from '@typed/fx'
9
-
10
- import { Destination, DestinationKey, Navigation, NavigationError } from './Navigation.js'
11
- import { makeIntent } from './dom-intent.js'
12
- import { onHistoryEvent, patchHistory } from './history.js'
13
- import { makeModel } from './model.js'
14
- import { getInitialValues } from './storage.js'
15
-
16
- export type NavigationServices = Window | Document | Location | History | Storage
17
-
18
- export interface DomNavigationOptions {
19
- // Defaults to a random value, but you can provide your own
20
- // Navigation keys can be provided to Navigation.navigate along the way as needed.
21
- readonly initialKey?: DestinationKey
22
-
23
- // Defaults to 50
24
- // The maximum number of entries to keep in memory and storage.
25
- readonly maxEntries?: number
26
- }
27
-
28
- export const dom = (
29
- options: DomNavigationOptions = {},
30
- ): Layer.Layer<NavigationServices, never, Navigation> => {
31
- return Navigation.layerScoped(
32
- Effect.gen(function* ($) {
33
- // Get resources
34
- const context = yield* $(Effect.context<NavigationServices>())
35
- const document = Context.get(context, Document)
36
- const base = document.querySelector('base')
37
- const baseHref = base ? getBasePathFromHref(base.href) : '/'
38
-
39
- // Patch History API to enable sending events
40
- const [history, historyEvents] = yield* $(patchHistory)
41
-
42
- // Create model and intent
43
- const [initialEntries, initialIndex] = yield* $(getInitialValues(baseHref, options))
44
-
45
- const model = yield* $(makeModel(initialEntries, initialIndex))
46
- const intent = makeIntent(model, baseHref, history, options)
47
-
48
- // Used to ensure ordering of navigation events
49
- const lock = Effect.unsafeMakeSemaphore(1).withPermits(1)
50
-
51
- const handleNavigationError =
52
- (depth: number) =>
53
- (
54
- error: NavigationError | Cause.NoSuchElementException,
55
- ): Effect.Effect<never, never, Destination> =>
56
- Effect.provideContext(
57
- Effect.gen(function* ($) {
58
- if (depth >= 50) {
59
- throw new Error(
60
- 'Too many redirects. You may have an infinite loop of onNavigation handlers that are redirecting.',
61
- )
62
- }
63
-
64
- switch (error._tag) {
65
- case 'NoSuchElementException':
66
- case 'CancelNavigation':
67
- return yield* $(model.currentEntry.get)
68
- case 'RedirectNavigation':
69
- return yield* $(
70
- Effect.catchAll(
71
- intent.navigate(error.url, error),
72
- handleNavigationError(depth + 1),
73
- ),
74
- )
75
- }
76
- }),
77
- context,
78
- )
79
-
80
- const catchNavigationError = <R, A>(
81
- effect: Effect.Effect<R, NavigationError | Cause.NoSuchElementException, A>,
82
- ) => Effect.catchAll(effect, handleNavigationError(0))
83
-
84
- // Used to provide a locked effect with the current context
85
- const provideLocked = <E, A>(effect: Effect.Effect<NavigationServices, E, A>) =>
86
- Effect.provideContext(lock(effect), context)
87
-
88
- // Constructor our service
89
- const navigation: Navigation = {
90
- back: provideLocked(catchNavigationError(intent.back(false))),
91
- base: baseHref,
92
- canGoBack: model.canGoBack,
93
- canGoForward: model.canGoForward,
94
- currentEntry: model.currentEntry,
95
- entries: model.entries,
96
- forward: provideLocked(catchNavigationError(intent.forward(false))),
97
- goTo: (a) =>
98
- pipe(
99
- a,
100
- intent.goTo,
101
- Effect.catchAll((a) => pipe(a, handleNavigationError(0), Effect.map(Option.some))),
102
- provideLocked,
103
- ),
104
- navigate: (url, options) =>
105
- pipe(intent.navigate(url, options), catchNavigationError, provideLocked),
106
- onNavigation: (handler, options) =>
107
- pipe(intent.onNavigation(handler, options), catchNavigationError, Effect.asUnit),
108
- onNavigationEnd: (handler, options) =>
109
- Effect.asUnit(intent.onNavigationEnd(handler, options)),
110
- reload: provideLocked(catchNavigationError(intent.reload)),
111
- }
112
-
113
- // Listen to various events and update our model
114
- yield* $(
115
- Fx.mergeAll(
116
- // Listen to history events and keep track of entries
117
- pipe(
118
- historyEvents,
119
- Fx.mapEffect((event) => lock(onHistoryEvent(event, intent))),
120
- ),
121
- // Listen to hash changes and push them to the history
122
- pipe(
123
- addWindowListener('hashchange', { capture: true }),
124
- Fx.mapEffect((ev) => lock(intent.push(ev.newURL, { state: history.state }, true))),
125
- ),
126
- // Listen to popstate events and go to the correct entry
127
- pipe(
128
- addWindowListener('popstate'),
129
- Fx.mapEffect(
130
- Effect.unifiedFn((ev) => {
131
- // TODO: Should we throw some kind of error here?
132
- // This should never happen if you are solely using the Navigation Service
133
- if (!ev.state || !ev.state.key) {
134
- return lock(intent.push(location.href, { state: history.state }, true))
135
- }
136
-
137
- return lock(intent.goTo(ev.state.key))
138
- }),
139
- ),
140
- ),
141
- ),
142
- Fx.drain,
143
- Effect.forkScoped,
144
- )
145
-
146
- return navigation
147
- }),
148
- )
149
- }
150
-
151
- export function getBasePathFromHref(href: string) {
152
- try {
153
- const url = new URL(href)
154
-
155
- return getCurrentPathFromLocation(url)
156
- } catch {
157
- return href
158
- }
159
- }
160
-
161
- export function getCurrentPathFromLocation(location: Location | HTMLAnchorElement | URL) {
162
- return location.pathname + location.search + location.hash
163
- }
@@ -1,464 +0,0 @@
1
- import { deepStrictEqual, ok } from 'assert'
2
-
3
- import * as Duration from '@effect/data/Duration'
4
- import * as Option from '@effect/data/Option'
5
- import * as Effect from '@effect/io/Effect'
6
- import * as Fiber from '@effect/io/Fiber'
7
- import * as Fx from '@typed/fx'
8
- import { describe, it } from 'vitest'
9
-
10
- import { MemoryNavigationOptions, memory } from './Memory.js'
11
- import {
12
- Destination,
13
- DestinationKey,
14
- Navigation,
15
- NavigationType,
16
- cancelNavigation,
17
- redirect,
18
- } from './Navigation.js'
19
-
20
- const provide = <R, E, A>(effect: Effect.Effect<R, E, A>, options: MemoryNavigationOptions) =>
21
- Effect.provideSomeLayer(memory(options))(effect)
22
-
23
- const testKey = DestinationKey('keys-are-random-and-not-tested-by-default-assertions')
24
- const testUrl = 'https://example.com'
25
- const testDestination = Destination(DestinationKey('default'), new URL(testUrl))
26
- const testPathname1 = `${testUrl}/1`
27
- const testPathname1Destination = Destination(testKey, new URL(testPathname1))
28
- const testPathname2 = `${testUrl}/2`
29
- const testPathname2Destination = Destination(testKey, new URL(testPathname2))
30
-
31
- const testNavigation = <Y extends Effect.EffectGen<any, any, any>, A>(
32
- f: (adapter: Effect.Adapter, navigation: Navigation) => Generator<Y, A, any>,
33
- options: MemoryNavigationOptions = { initialUrl: new URL(testUrl) },
34
- ) =>
35
- Effect.scoped(
36
- provide(
37
- Effect.tapErrorCause(
38
- Effect.gen(function* ($) {
39
- const navigation = yield* $(Navigation)
40
- const result = yield* f($, navigation)
41
-
42
- return result
43
- }),
44
- Effect.logError,
45
- ),
46
- options,
47
- ),
48
- )
49
-
50
- const fxToFiber = <R, E, A>(fx: Fx.Fx<R, E, A>, take: number) =>
51
- Effect.gen(function* ($) {
52
- const fiber = yield* $(fx, Fx.take(take), Fx.toReadonlyArray, Effect.forkScoped)
53
-
54
- yield* $(Effect.sleep(Duration.millis(0)))
55
- yield* $(Effect.sleep(Duration.millis(0)))
56
-
57
- return fiber
58
- })
59
-
60
- const assertEqualDestination: (a: Destination, b: Destination) => void = (a, b) => {
61
- deepStrictEqual(
62
- a.url.href,
63
- b.url.href,
64
- 'Urls should match. Actual: ' + a.url.href + ' Expected: ' + b.url.href,
65
- )
66
- deepStrictEqual(
67
- a.state,
68
- b.state,
69
- 'State should match. Actual: ' +
70
- JSON.stringify(a.state) +
71
- ' Expeced: ' +
72
- JSON.stringify(b.state),
73
- )
74
- }
75
-
76
- const assertSomeDestination = (a: Option.Option<Destination>, b: Destination) => {
77
- ok(Option.isSome(a))
78
- assertEqualDestination(a.value, b)
79
- }
80
-
81
- const assertEqualDestinations: (a: readonly Destination[], b: readonly Destination[]) => void = (
82
- a,
83
- b,
84
- ) => {
85
- deepStrictEqual(a.length, b.length, 'Destinations should have the same length')
86
-
87
- for (let i = 0; i < a.length; ++i) {
88
- assertEqualDestination(a[i], b[i])
89
- }
90
- }
91
-
92
- describe(import.meta.url, () => {
93
- describe('entries', () => {
94
- it('returns the initial entry immediately', async () => {
95
- const test = testNavigation(function* ($, { entries }) {
96
- const initial = yield* $(entries)
97
-
98
- assertEqualDestinations(initial, [testDestination])
99
- })
100
-
101
- await Effect.runPromise(test)
102
- })
103
-
104
- it('is observerable', async () => {
105
- const test = testNavigation(function* ($, { entries, navigate }) {
106
- const fiber = yield* $(fxToFiber(entries, 2))
107
-
108
- yield* $(navigate(testPathname1))
109
-
110
- const results = yield* $(Fiber.join(fiber))
111
-
112
- deepStrictEqual(results.length, 2)
113
-
114
- assertEqualDestinations(results[0], [testDestination])
115
- assertEqualDestinations(results[1], [testDestination, testPathname1Destination])
116
- })
117
-
118
- await Effect.runPromise(test)
119
- })
120
- })
121
-
122
- describe('currentEntry', () => {
123
- it('returns the initial entry immediately', async () => {
124
- const test = testNavigation(function* ($, { currentEntry }) {
125
- const initial = yield* $(currentEntry)
126
-
127
- assertEqualDestination(initial, testDestination)
128
- })
129
-
130
- await Effect.runPromise(test)
131
- })
132
-
133
- it('is observerable', async () => {
134
- const test = testNavigation(function* ($, { currentEntry, navigate }) {
135
- const fiber = yield* $(fxToFiber(currentEntry, 3))
136
-
137
- yield* $(navigate(testPathname1))
138
- yield* $(navigate(testPathname2))
139
-
140
- const results = yield* $(Fiber.join(fiber))
141
-
142
- assertEqualDestinations(results, [
143
- testDestination,
144
- testPathname1Destination,
145
- testPathname2Destination,
146
- ])
147
- })
148
-
149
- await Effect.runPromise(test)
150
- })
151
- })
152
-
153
- describe('navigate', () => {
154
- it('navigates to a new url', async () => {
155
- const test = testNavigation(function* ($, { currentEntry, navigate }) {
156
- const initial = yield* $(currentEntry)
157
-
158
- assertEqualDestination(initial, testDestination)
159
-
160
- const destination = yield* $(navigate(testPathname1))
161
-
162
- assertEqualDestination(destination, testPathname1Destination)
163
- })
164
-
165
- await Effect.runPromise(test)
166
- })
167
-
168
- it('sets state when provided', async () => {
169
- const test = testNavigation(function* ($, { navigate }) {
170
- const destination = yield* $(navigate(testPathname1, { state: testKey }))
171
-
172
- assertEqualDestination(destination, Destination(testKey, new URL(testPathname1), testKey))
173
- })
174
-
175
- await Effect.runPromise(test)
176
- })
177
-
178
- it('allows replacing the current entry', async () => {
179
- const test = testNavigation(function* ($, { entries, navigate }) {
180
- assertEqualDestinations(yield* $(entries), [testDestination])
181
-
182
- const destination = yield* $(navigate(testPathname1, { history: 'replace' }))
183
-
184
- assertEqualDestinations(yield* $(entries), [destination])
185
- })
186
-
187
- await Effect.runPromise(test)
188
- })
189
- })
190
-
191
- describe('onNavigation', () => {
192
- it('allows subscribing to navigation events', async () => {
193
- const test = testNavigation(function* ($, { navigate, onNavigation }) {
194
- let i = 0
195
- yield* $(
196
- onNavigation((event) => {
197
- if (i === 1) {
198
- deepStrictEqual(event.navigationType, NavigationType.Push)
199
- assertEqualDestination(event.destination, testPathname1Destination)
200
- }
201
-
202
- if (i === 2) {
203
- deepStrictEqual(event.navigationType, NavigationType.Push)
204
- assertEqualDestination(event.destination, testPathname2Destination)
205
- }
206
-
207
- i++
208
-
209
- return Effect.unit
210
- }),
211
- )
212
-
213
- yield* $(navigate(testPathname1))
214
- yield* $(navigate(testPathname2))
215
-
216
- deepStrictEqual(i, 3)
217
- })
218
-
219
- await Effect.runPromise(test)
220
- })
221
-
222
- it('allows canceling the requested navigation', async () => {
223
- const test = testNavigation(function* ($, { navigate, onNavigation }) {
224
- yield* $(onNavigation(() => cancelNavigation))
225
-
226
- const destination = yield* $(navigate(testPathname1))
227
-
228
- assertEqualDestination(destination, testDestination)
229
- })
230
-
231
- await Effect.runPromise(test)
232
- })
233
-
234
- it('allow redirection to a different url', async () => {
235
- const test = testNavigation(function* ($, { navigate, onNavigation }) {
236
- yield* $(
237
- onNavigation(({ destination }) =>
238
- destination.url.href === testPathname1 ? redirect(testPathname2) : Effect.unit,
239
- ),
240
- )
241
-
242
- const destination = yield* $(navigate(testPathname1))
243
-
244
- assertEqualDestination(destination, testPathname2Destination)
245
- })
246
-
247
- await Effect.runPromise(test)
248
- })
249
- })
250
-
251
- describe('canGoBack', () => {
252
- it('returns false when on the first entry', async () => {
253
- const test = testNavigation(function* ($, { canGoBack }) {
254
- deepStrictEqual(yield* $(canGoBack), false)
255
- })
256
-
257
- await Effect.runPromise(test)
258
- })
259
-
260
- it('returns true when there are entries to go back to', async () => {
261
- const test = testNavigation(function* ($, { canGoBack, navigate }) {
262
- yield* $(navigate(testPathname1))
263
-
264
- deepStrictEqual(yield* $(canGoBack), true)
265
- })
266
-
267
- await Effect.runPromise(test)
268
- })
269
-
270
- it('is observable', async () => {
271
- const test = testNavigation(function* ($, { canGoBack, navigate }) {
272
- const fiber = yield* $(fxToFiber(canGoBack, 2))
273
-
274
- yield* $(navigate(testPathname1))
275
- yield* $(navigate(testPathname2))
276
-
277
- const results = yield* $(Fiber.join(fiber))
278
-
279
- deepStrictEqual(results, [false, true])
280
- })
281
-
282
- await Effect.runPromise(test)
283
- })
284
- })
285
-
286
- describe('back', () => {
287
- it('does nothing when there are no entries to go back to', async () => {
288
- const test = testNavigation(function* ($, { back }) {
289
- assertEqualDestination(yield* $(back), testDestination)
290
- })
291
-
292
- await Effect.runPromise(test)
293
- })
294
-
295
- it('goes back to the previous entry', async () => {
296
- const test = testNavigation(function* ($, { back, navigate }) {
297
- yield* $(navigate(testPathname1))
298
-
299
- assertEqualDestination(yield* $(back), testDestination)
300
- })
301
-
302
- await Effect.runPromise(test)
303
- })
304
- })
305
-
306
- describe('canGoForward', () => {
307
- it('returns false when on the last entry', async () => {
308
- const test = testNavigation(function* ($, { canGoForward }) {
309
- deepStrictEqual(yield* $(canGoForward), false)
310
- })
311
-
312
- await Effect.runPromise(test)
313
- })
314
-
315
- it('returns true when there are entries to go forward to', async () => {
316
- const test = testNavigation(function* ($, { canGoForward, back, navigate }) {
317
- yield* $(navigate(testPathname1))
318
- yield* $(navigate(testPathname2))
319
-
320
- deepStrictEqual(yield* $(canGoForward), false)
321
-
322
- yield* $(back)
323
-
324
- deepStrictEqual(yield* $(canGoForward), true)
325
- })
326
-
327
- await Effect.runPromise(test)
328
- })
329
-
330
- it('is observable', async () => {
331
- const test = testNavigation(function* ($, { canGoForward, back, navigate }) {
332
- const fiber = yield* $(fxToFiber(canGoForward, 2))
333
-
334
- yield* $(navigate(testPathname1))
335
- yield* $(back)
336
-
337
- const results = yield* $(Fiber.join(fiber))
338
-
339
- deepStrictEqual(results, [false, true]) // Duplication between first and second entry is skipped
340
- })
341
-
342
- await Effect.runPromise(test)
343
- })
344
- })
345
-
346
- describe('forward', () => {
347
- it('does nothing when there are no entries to go forward to', async () => {
348
- const test = testNavigation(function* ($, { forward }) {
349
- assertEqualDestination(yield* $(forward), testDestination)
350
- })
351
-
352
- await Effect.runPromise(test)
353
- })
354
-
355
- it('goes forward to the next entry', async () => {
356
- const test = testNavigation(function* ($, { forward, back, navigate }) {
357
- yield* $(navigate(testPathname1))
358
- yield* $(navigate(testPathname2))
359
-
360
- assertEqualDestination(yield* $(back), testPathname1Destination)
361
- assertEqualDestination(yield* $(forward), testPathname2Destination)
362
- })
363
-
364
- await Effect.runPromise(test)
365
- })
366
- })
367
-
368
- describe('reload', () => {
369
- it('reloads the current entry', async () => {
370
- const test = testNavigation(function* ($, { reload, navigate }) {
371
- yield* $(navigate(testPathname1))
372
-
373
- assertEqualDestination(yield* $(reload), testPathname1Destination)
374
- })
375
-
376
- await Effect.runPromise(test)
377
- })
378
-
379
- it('sends a reload event to subscribers', async () => {
380
- const test = testNavigation(function* ($, { reload, navigate, onNavigation }) {
381
- let i = 0
382
- yield* $(
383
- onNavigation((event) => {
384
- if (i === 1) {
385
- deepStrictEqual(event.navigationType, NavigationType.Push)
386
- assertEqualDestination(event.destination, testPathname1Destination)
387
- }
388
-
389
- if (i === 2) {
390
- deepStrictEqual(event.navigationType, NavigationType.Reload)
391
- assertEqualDestination(event.destination, testPathname1Destination)
392
- }
393
-
394
- i++
395
-
396
- return Effect.unit
397
- }),
398
- )
399
-
400
- yield* $(navigate(testPathname1))
401
- yield* $(reload)
402
-
403
- deepStrictEqual(i, 3)
404
- })
405
-
406
- await Effect.runPromise(test)
407
- })
408
- })
409
-
410
- describe('goTo', () => {
411
- it('goes to a specific entry', async () => {
412
- const test = testNavigation(function* ($, { currentEntry, goTo, navigate }) {
413
- const d0 = yield* $(currentEntry)
414
- const d1 = yield* $(navigate(testPathname1))
415
- const d2 = yield* $(navigate(testPathname2))
416
-
417
- assertSomeDestination(yield* $(goTo(d0.key)), testDestination)
418
- assertSomeDestination(yield* $(goTo(d1.key)), testPathname1Destination)
419
- assertSomeDestination(yield* $(goTo(d2.key)), testPathname2Destination)
420
- })
421
-
422
- await Effect.runPromise(test)
423
- })
424
-
425
- it('returns None when the entry does not exist', async () => {
426
- const test = testNavigation(function* ($, { goTo }) {
427
- deepStrictEqual(yield* $(goTo(DestinationKey('does-not-exist'))), Option.none())
428
- })
429
-
430
- await Effect.runPromise(test)
431
- })
432
- })
433
-
434
- describe('maxEntries', () => {
435
- it('allows configuring how many entries are stored', async () => {
436
- const test = testNavigation(
437
- function* ($, { currentEntry, entries, navigate }) {
438
- const fiber = yield* $(fxToFiber(currentEntry, 3))
439
-
440
- yield* $(navigate(testPathname1))
441
- yield* $(navigate(testPathname2))
442
-
443
- const results = yield* $(Fiber.join(fiber))
444
-
445
- assertEqualDestinations(results, [
446
- testDestination,
447
- testPathname1Destination,
448
- testPathname2Destination,
449
- ])
450
-
451
- const entriesAfterNavigation = yield* $(entries)
452
-
453
- assertEqualDestinations(entriesAfterNavigation, [
454
- testPathname1Destination,
455
- testPathname2Destination,
456
- ])
457
- },
458
- { initialUrl: new URL(testUrl), maxEntries: 2 },
459
- )
460
-
461
- await Effect.runPromise(test)
462
- })
463
- })
464
- })