create-foldkit-app 0.30.1 → 0.31.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/dist/templates/base/FOLDKIT.md +3 -1
- package/dist/templates/examples/auth/src/main.ts +3 -3
- package/dist/templates/examples/auth/src/page/loggedIn/model.ts +2 -2
- package/dist/templates/examples/auth/src/page/loggedOut/model.ts +2 -2
- package/dist/templates/examples/auth/src/route.ts +19 -29
- package/dist/templates/examples/auth/src/scene.test.ts +2 -2
- package/dist/templates/examples/auth/src/update.ts +3 -4
- package/dist/templates/examples/form/src/main.ts +12 -21
- package/dist/templates/examples/interrupting-commands/src/scene.test.ts +2 -2
- package/dist/templates/examples/interrupting-commands/src/story.test.ts +7 -7
- package/dist/templates/examples/job-application/src/main.ts +2 -2
- package/dist/templates/examples/job-application/src/model.ts +7 -12
- package/dist/templates/examples/job-application/src/scene.test.ts +5 -11
- package/dist/templates/examples/job-application/src/story.test.ts +4 -4
- package/dist/templates/examples/job-application/src/update.ts +8 -4
- package/dist/templates/examples/job-application/src/view/review.ts +92 -94
- package/dist/templates/examples/managed-resource-layer/src/main.ts +23 -27
- package/dist/templates/examples/managed-resource-layer/src/scene.test.ts +3 -4
- package/dist/templates/examples/managed-resource-layer/src/story.test.ts +11 -20
- package/dist/templates/examples/map/src/main.fixtures.ts +2 -2
- package/dist/templates/examples/map/src/main.ts +23 -21
- package/dist/templates/examples/map/src/scene.test.ts +4 -2
- package/dist/templates/examples/map/src/story.test.ts +9 -10
- package/dist/templates/examples/query-sync/src/main.ts +30 -29
- package/dist/templates/examples/query-sync/src/scene.test.ts +8 -15
- package/dist/templates/examples/query-sync/src/story.test.ts +21 -8
- package/dist/templates/examples/route-transitions/src/main.ts +15 -25
- package/dist/templates/examples/route-transitions/src/route.ts +21 -26
- package/dist/templates/examples/route-transitions/src/story.test.ts +20 -26
- package/dist/templates/examples/routing/src/fileTree.ts +3 -3
- package/dist/templates/examples/routing/src/main.ts +2 -12
- package/dist/templates/examples/routing/src/page/people.ts +18 -16
- package/dist/templates/examples/routing/src/route.ts +18 -32
- package/dist/templates/examples/routing/src/scene.test.ts +12 -20
- package/dist/templates/examples/routing/src/story.test.ts +9 -6
- package/dist/templates/examples/shopping-cart/src/route.ts +11 -19
- package/dist/templates/examples/shopping-cart/src/scene.test.ts +10 -8
- package/dist/templates/examples/shopping-cart/src/story.test.ts +2 -2
- package/dist/templates/examples/ssg/src/route.ts +9 -8
- package/dist/templates/examples/state-machine/src/main.ts +68 -63
- package/dist/templates/examples/state-machine/src/story.test.ts +8 -7
- package/dist/templates/examples/state-machine/src/view.ts +7 -13
- package/dist/templates/examples/todo/src/main.ts +11 -17
- package/dist/templates/examples/todo/src/scene.test.ts +2 -2
- package/dist/templates/examples/todo/src/story.test.ts +11 -12
- package/dist/templates/examples/ui-showcase/src/main.ts +64 -85
- package/dist/templates/examples/ui-showcase/src/scene.test.ts +14 -30
- package/dist/templates/examples/ui-showcase/src/story.test.ts +2 -2
- package/dist/templates/examples/ui-showcase/src/ui/message.ts +0 -70
- package/dist/templates/examples/view-transitions/src/main.ts +1 -1
- package/dist/templates/examples/view-transitions/src/route.ts +9 -12
- package/dist/templates/examples/view-transitions/src/scene.test.ts +5 -14
- package/dist/templates/examples/websocket-chat/src/main.ts +29 -37
- package/dist/templates/examples/websocket-chat/src/scene.test.ts +18 -10
- package/dist/templates/examples/websocket-chat/src/story.test.ts +19 -15
- package/dist/templates/release.json +13 -13
- package/dist/templates/rendering/ssg/src/route.ts +9 -8
- package/dist/templates/rendering/ssg/src/scene.test.ts +2 -2
- package/package.json +1 -1
|
@@ -12,12 +12,17 @@ import { Command, Submodel, type Update } from 'foldkit'
|
|
|
12
12
|
import { Html, HtmlBuilder } from 'foldkit/html'
|
|
13
13
|
import { defineMessageUnion } from 'foldkit/message'
|
|
14
14
|
import { pushUrl } from 'foldkit/navigation'
|
|
15
|
-
import {
|
|
15
|
+
import { defineTaggedUnion } from 'foldkit/schema'
|
|
16
16
|
import { evo } from 'foldkit/struct'
|
|
17
17
|
|
|
18
18
|
import { Button, Input } from '@foldkit/ui'
|
|
19
19
|
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
AppRoute,
|
|
22
|
+
type PeopleRoute,
|
|
23
|
+
peopleRouter,
|
|
24
|
+
personRouter,
|
|
25
|
+
} from '../route'
|
|
21
26
|
|
|
22
27
|
// DOMAIN
|
|
23
28
|
|
|
@@ -77,13 +82,10 @@ export const findPerson = (id: number) =>
|
|
|
77
82
|
|
|
78
83
|
// MODEL
|
|
79
84
|
|
|
80
|
-
export const
|
|
81
|
-
|
|
82
|
-
query: S.String,
|
|
83
|
-
people: S.Array(Person),
|
|
85
|
+
export const SearchResults = defineTaggedUnion({
|
|
86
|
+
Loading: {},
|
|
87
|
+
Loaded: { query: S.String, people: S.Array(Person) },
|
|
84
88
|
})
|
|
85
|
-
|
|
86
|
-
export const SearchResults = S.Union([SearchLoading, SearchLoaded])
|
|
87
89
|
export type SearchResults = typeof SearchResults.Type
|
|
88
90
|
|
|
89
91
|
export const Model = S.Struct({
|
|
@@ -98,7 +100,7 @@ export type Model = typeof Model.Type
|
|
|
98
100
|
export const Message = defineMessageUnion({
|
|
99
101
|
ChangedSearchInput: { value: S.String },
|
|
100
102
|
SubmittedSearch: {},
|
|
101
|
-
ChangedRoute: { route:
|
|
103
|
+
ChangedRoute: { route: AppRoute.People },
|
|
102
104
|
SucceededFetchPeople: {
|
|
103
105
|
query: S.String,
|
|
104
106
|
people: S.Array(Person),
|
|
@@ -118,7 +120,7 @@ export const init = (route: PeopleRoute): InitReturn => {
|
|
|
118
120
|
model: {
|
|
119
121
|
searchInput: searchText,
|
|
120
122
|
searchHistory: addSearchToHistory([], searchText),
|
|
121
|
-
results:
|
|
123
|
+
results: SearchResults.Loading(),
|
|
122
124
|
},
|
|
123
125
|
commands: [FetchPeople({ searchText })],
|
|
124
126
|
}
|
|
@@ -175,7 +177,7 @@ export const update = (model: Model, message: Message) =>
|
|
|
175
177
|
searchInput: () => searchText,
|
|
176
178
|
searchHistory: searchHistory =>
|
|
177
179
|
addSearchToHistory(searchHistory, searchText),
|
|
178
|
-
results: () =>
|
|
180
|
+
results: () => SearchResults.Loading(),
|
|
179
181
|
}),
|
|
180
182
|
commands: [FetchPeople({ searchText })],
|
|
181
183
|
}
|
|
@@ -183,7 +185,7 @@ export const update = (model: Model, message: Message) =>
|
|
|
183
185
|
|
|
184
186
|
SucceededFetchPeople: ({ query, people: fetchedPeople }) => ({
|
|
185
187
|
model: evo(model, {
|
|
186
|
-
results: () =>
|
|
188
|
+
results: () => SearchResults.Loaded({ query, people: fetchedPeople }),
|
|
187
189
|
}),
|
|
188
190
|
}),
|
|
189
191
|
|
|
@@ -203,8 +205,8 @@ export const informRouteChanged = (model: Model, route: PeopleRoute) =>
|
|
|
203
205
|
const statusText = (results: SearchResults): string =>
|
|
204
206
|
M.value(results).pipe(
|
|
205
207
|
M.withReturnType<string>(),
|
|
206
|
-
M.tag('
|
|
207
|
-
M.tag('
|
|
208
|
+
M.tag('Loading', () => 'Searching…'),
|
|
209
|
+
M.tag('Loaded', ({ query, people: found }) => {
|
|
208
210
|
if (String.isEmpty(query)) {
|
|
209
211
|
return 'Click on any person to view their details:'
|
|
210
212
|
}
|
|
@@ -330,8 +332,8 @@ export const view = Submodel.defineView<Model, Message>(
|
|
|
330
332
|
),
|
|
331
333
|
|
|
332
334
|
M.value(model.results).pipe(
|
|
333
|
-
M.tag('
|
|
334
|
-
M.tag('
|
|
335
|
+
M.tag('Loading', () => h.empty),
|
|
336
|
+
M.tag('Loaded', ({ people: results }) =>
|
|
335
337
|
h.ul(
|
|
336
338
|
[h.Class('space-y-3')],
|
|
337
339
|
Array.map(results, person => personListItemView(person, h)),
|
|
@@ -1,35 +1,21 @@
|
|
|
1
1
|
import { Schema as S, pipe } from 'effect'
|
|
2
2
|
import { Route } from 'foldkit'
|
|
3
|
-
import { int, literal,
|
|
3
|
+
import { defineRouteUnion, int, literal, rest, slash } from 'foldkit/route'
|
|
4
4
|
|
|
5
|
-
export const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
export const AppRoute = defineRouteUnion({
|
|
6
|
+
Home: {},
|
|
7
|
+
Nested: {},
|
|
8
|
+
People: { searchText: S.Option(S.String) },
|
|
9
|
+
Person: { personId: S.Number },
|
|
10
|
+
FilesIndex: {},
|
|
11
|
+
Files: { path: S.NonEmptyArray(S.String) },
|
|
12
|
+
NotFound: { path: S.String },
|
|
13
|
+
})
|
|
12
14
|
|
|
13
|
-
export const AppRoute = S.Union([
|
|
14
|
-
HomeRoute,
|
|
15
|
-
NestedRoute,
|
|
16
|
-
PeopleRoute,
|
|
17
|
-
PersonRoute,
|
|
18
|
-
FilesIndexRoute,
|
|
19
|
-
FilesRoute,
|
|
20
|
-
NotFoundRoute,
|
|
21
|
-
])
|
|
22
|
-
|
|
23
|
-
export type HomeRoute = typeof HomeRoute.Type
|
|
24
|
-
export type NestedRoute = typeof NestedRoute.Type
|
|
25
|
-
export type PeopleRoute = typeof PeopleRoute.Type
|
|
26
|
-
export type PersonRoute = typeof PersonRoute.Type
|
|
27
|
-
export type FilesIndexRoute = typeof FilesIndexRoute.Type
|
|
28
|
-
export type FilesRoute = typeof FilesRoute.Type
|
|
29
|
-
export type NotFoundRoute = typeof NotFoundRoute.Type
|
|
30
15
|
export type AppRoute = typeof AppRoute.Type
|
|
16
|
+
export type PeopleRoute = typeof AppRoute.People.Type
|
|
31
17
|
|
|
32
|
-
export const homeRouter = pipe(Route.root, Route.mapTo(
|
|
18
|
+
export const homeRouter = pipe(Route.root, Route.mapTo(AppRoute.Home))
|
|
33
19
|
|
|
34
20
|
export const nestedRouter = pipe(
|
|
35
21
|
literal('nested'),
|
|
@@ -37,7 +23,7 @@ export const nestedRouter = pipe(
|
|
|
37
23
|
slash(literal('is')),
|
|
38
24
|
slash(literal('very')),
|
|
39
25
|
slash(literal('nested')),
|
|
40
|
-
Route.mapTo(
|
|
26
|
+
Route.mapTo(AppRoute.Nested),
|
|
41
27
|
)
|
|
42
28
|
|
|
43
29
|
export const peopleRouter = pipe(
|
|
@@ -47,24 +33,24 @@ export const peopleRouter = pipe(
|
|
|
47
33
|
searchText: S.OptionFromOptional(S.String),
|
|
48
34
|
}),
|
|
49
35
|
),
|
|
50
|
-
Route.mapTo(
|
|
36
|
+
Route.mapTo(AppRoute.People),
|
|
51
37
|
)
|
|
52
38
|
|
|
53
39
|
export const personRouter = pipe(
|
|
54
40
|
literal('people'),
|
|
55
41
|
slash(int('personId')),
|
|
56
|
-
Route.mapTo(
|
|
42
|
+
Route.mapTo(AppRoute.Person),
|
|
57
43
|
)
|
|
58
44
|
|
|
59
45
|
export const filesIndexRouter = pipe(
|
|
60
46
|
literal('files'),
|
|
61
|
-
Route.mapTo(
|
|
47
|
+
Route.mapTo(AppRoute.FilesIndex),
|
|
62
48
|
)
|
|
63
49
|
|
|
64
50
|
export const filesRouter = pipe(
|
|
65
51
|
literal('files'),
|
|
66
52
|
slash(rest('path')),
|
|
67
|
-
Route.mapTo(
|
|
53
|
+
Route.mapTo(AppRoute.Files),
|
|
68
54
|
)
|
|
69
55
|
|
|
70
56
|
const routeParser = Route.oneOf(
|
|
@@ -78,5 +64,5 @@ const routeParser = Route.oneOf(
|
|
|
78
64
|
|
|
79
65
|
export const urlToAppRoute = Route.parseUrlWithFallback(
|
|
80
66
|
routeParser,
|
|
81
|
-
|
|
67
|
+
AppRoute.NotFound,
|
|
82
68
|
)
|
|
@@ -2,25 +2,14 @@ import { Array, Option, String } from 'effect'
|
|
|
2
2
|
import { expect, given, role, scene, text } from 'foldkit/scene'
|
|
3
3
|
import { describe, test } from 'vitest'
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
FilesIndexRoute,
|
|
7
|
-
FilesRoute,
|
|
8
|
-
HomeRoute,
|
|
9
|
-
Model,
|
|
10
|
-
NestedRoute,
|
|
11
|
-
NotFoundRoute,
|
|
12
|
-
PeopleRoute,
|
|
13
|
-
PersonRoute,
|
|
14
|
-
update,
|
|
15
|
-
view,
|
|
16
|
-
} from './main'
|
|
5
|
+
import { AppRoute, Model, update, view } from './main'
|
|
17
6
|
import { People } from './page'
|
|
18
7
|
|
|
19
8
|
const peoplePageWith = (searchInput: string) =>
|
|
20
9
|
People.Model.make({
|
|
21
10
|
searchInput,
|
|
22
11
|
searchHistory: Array.liftPredicate(String.isNonEmpty)(searchInput),
|
|
23
|
-
results: People.
|
|
12
|
+
results: People.SearchResults.Loaded({
|
|
24
13
|
query: searchInput,
|
|
25
14
|
people: People.searchPeople(searchInput),
|
|
26
15
|
}),
|
|
@@ -28,35 +17,38 @@ const peoplePageWith = (searchInput: string) =>
|
|
|
28
17
|
|
|
29
18
|
const initialPeoplePage = peoplePageWith('')
|
|
30
19
|
|
|
31
|
-
const home = Model.make({
|
|
20
|
+
const home = Model.make({
|
|
21
|
+
route: AppRoute.Home(),
|
|
22
|
+
peoplePage: initialPeoplePage,
|
|
23
|
+
})
|
|
32
24
|
const people = (searchInput: string) =>
|
|
33
25
|
Model.make({
|
|
34
|
-
route:
|
|
26
|
+
route: AppRoute.People({
|
|
35
27
|
searchText: Option.liftPredicate(String.isNonEmpty)(searchInput),
|
|
36
28
|
}),
|
|
37
29
|
peoplePage: peoplePageWith(searchInput),
|
|
38
30
|
})
|
|
39
31
|
const person = (personId: number) =>
|
|
40
32
|
Model.make({
|
|
41
|
-
route:
|
|
33
|
+
route: AppRoute.Person({ personId }),
|
|
42
34
|
peoplePage: initialPeoplePage,
|
|
43
35
|
})
|
|
44
36
|
const nested = Model.make({
|
|
45
|
-
route:
|
|
37
|
+
route: AppRoute.Nested(),
|
|
46
38
|
peoplePage: initialPeoplePage,
|
|
47
39
|
})
|
|
48
40
|
const filesIndex = Model.make({
|
|
49
|
-
route:
|
|
41
|
+
route: AppRoute.FilesIndex(),
|
|
50
42
|
peoplePage: initialPeoplePage,
|
|
51
43
|
})
|
|
52
44
|
const files = (path: Array.NonEmptyReadonlyArray<string>) =>
|
|
53
45
|
Model.make({
|
|
54
|
-
route:
|
|
46
|
+
route: AppRoute.Files({ path }),
|
|
55
47
|
peoplePage: initialPeoplePage,
|
|
56
48
|
})
|
|
57
49
|
const notFound = (path: string) =>
|
|
58
50
|
Model.make({
|
|
59
|
-
route:
|
|
51
|
+
route: AppRoute.NotFound({ path }),
|
|
60
52
|
peoplePage: initialPeoplePage,
|
|
61
53
|
})
|
|
62
54
|
|
|
@@ -3,7 +3,7 @@ import { Command, given, message, model, story } from 'foldkit/story'
|
|
|
3
3
|
import { fromString } from 'foldkit/url'
|
|
4
4
|
import { describe, expect, test } from 'vitest'
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { AppRoute, Message, Model, update } from './main'
|
|
7
7
|
import { People } from './page'
|
|
8
8
|
import { Message as PeopleMessage } from './page/people'
|
|
9
9
|
|
|
@@ -11,7 +11,7 @@ const peoplePageWith = (searchInput: string) =>
|
|
|
11
11
|
People.Model.make({
|
|
12
12
|
searchInput,
|
|
13
13
|
searchHistory: Array.liftPredicate(String.isNonEmpty)(searchInput),
|
|
14
|
-
results: People.
|
|
14
|
+
results: People.SearchResults.Loaded({
|
|
15
15
|
query: searchInput,
|
|
16
16
|
people: People.searchPeople(searchInput),
|
|
17
17
|
}),
|
|
@@ -19,11 +19,14 @@ const peoplePageWith = (searchInput: string) =>
|
|
|
19
19
|
|
|
20
20
|
const initialPeoplePage = peoplePageWith('')
|
|
21
21
|
|
|
22
|
-
const home = Model.make({
|
|
22
|
+
const home = Model.make({
|
|
23
|
+
route: AppRoute.Home(),
|
|
24
|
+
peoplePage: initialPeoplePage,
|
|
25
|
+
})
|
|
23
26
|
|
|
24
27
|
const onPeople = (searchInput: string) =>
|
|
25
28
|
Model.make({
|
|
26
|
-
route:
|
|
29
|
+
route: AppRoute.People({
|
|
27
30
|
searchText: Option.liftPredicate(String.isNonEmpty)(searchInput),
|
|
28
31
|
}),
|
|
29
32
|
peoplePage: peoplePageWith(searchInput),
|
|
@@ -177,12 +180,12 @@ describe('update', () => {
|
|
|
177
180
|
model(model => {
|
|
178
181
|
expect(model.peoplePage.searchInput).toBe('designer')
|
|
179
182
|
expect(model.peoplePage.searchHistory).toStrictEqual(['designer'])
|
|
180
|
-
expect(model.peoplePage.results._tag).toBe('
|
|
183
|
+
expect(model.peoplePage.results._tag).toBe('Loading')
|
|
181
184
|
}),
|
|
182
185
|
Command.expectHas(People.FetchPeople),
|
|
183
186
|
resolveFetch('designer'),
|
|
184
187
|
model(model => {
|
|
185
|
-
if (model.peoplePage.results._tag === '
|
|
188
|
+
if (model.peoplePage.results._tag === 'Loaded') {
|
|
186
189
|
expect(
|
|
187
190
|
model.peoplePage.results.people.map(person => person.name),
|
|
188
191
|
).toStrictEqual(['Alice Johnson', 'Eva Brown'])
|
|
@@ -1,24 +1,16 @@
|
|
|
1
1
|
import { Schema as S, pipe } from 'effect'
|
|
2
2
|
import { Route } from 'foldkit'
|
|
3
|
-
import {
|
|
3
|
+
import { defineRouteUnion, literal } from 'foldkit/route'
|
|
4
4
|
|
|
5
5
|
// ROUTE
|
|
6
6
|
|
|
7
|
-
export const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
CartRoute,
|
|
14
|
-
CheckoutRoute,
|
|
15
|
-
NotFoundRoute,
|
|
16
|
-
])
|
|
7
|
+
export const AppRoute = defineRouteUnion({
|
|
8
|
+
Products: { searchText: S.Option(S.String) },
|
|
9
|
+
Cart: {},
|
|
10
|
+
Checkout: {},
|
|
11
|
+
NotFound: { path: S.String },
|
|
12
|
+
})
|
|
17
13
|
|
|
18
|
-
export type ProductsRoute = typeof ProductsRoute.Type
|
|
19
|
-
export type CartRoute = typeof CartRoute.Type
|
|
20
|
-
export type CheckoutRoute = typeof CheckoutRoute.Type
|
|
21
|
-
export type NotFoundRoute = typeof NotFoundRoute.Type
|
|
22
14
|
export type AppRoute = typeof AppRoute.Type
|
|
23
15
|
|
|
24
16
|
// ROUTERS
|
|
@@ -26,12 +18,12 @@ export type AppRoute = typeof AppRoute.Type
|
|
|
26
18
|
export const productsRouter = pipe(
|
|
27
19
|
Route.root,
|
|
28
20
|
Route.query(S.Struct({ searchText: S.OptionFromOptional(S.String) })),
|
|
29
|
-
Route.mapTo(
|
|
21
|
+
Route.mapTo(AppRoute.Products),
|
|
30
22
|
)
|
|
31
|
-
export const cartRouter = pipe(literal('cart'), Route.mapTo(
|
|
23
|
+
export const cartRouter = pipe(literal('cart'), Route.mapTo(AppRoute.Cart))
|
|
32
24
|
export const checkoutRouter = pipe(
|
|
33
25
|
literal('checkout'),
|
|
34
|
-
Route.mapTo(
|
|
26
|
+
Route.mapTo(AppRoute.Checkout),
|
|
35
27
|
)
|
|
36
28
|
|
|
37
29
|
// PARSER
|
|
@@ -39,5 +31,5 @@ export const checkoutRouter = pipe(
|
|
|
39
31
|
const routeParser = Route.oneOf(checkoutRouter, cartRouter, productsRouter)
|
|
40
32
|
export const urlToAppRoute = Route.parseUrlWithFallback(
|
|
41
33
|
routeParser,
|
|
42
|
-
|
|
34
|
+
AppRoute.NotFound,
|
|
43
35
|
)
|
|
@@ -6,13 +6,13 @@ import { products } from './data/products'
|
|
|
6
6
|
import { Cart } from './domain'
|
|
7
7
|
import { type Model, update, view } from './main'
|
|
8
8
|
import { Products } from './page'
|
|
9
|
-
import {
|
|
9
|
+
import { AppRoute } from './route'
|
|
10
10
|
|
|
11
11
|
const apple = { id: '1', name: 'Apple', price: 1.5 }
|
|
12
12
|
const banana = { id: '2', name: 'Banana', price: 0.75 }
|
|
13
13
|
|
|
14
14
|
const baseModel: Model = {
|
|
15
|
-
route:
|
|
15
|
+
route: AppRoute.Products({ searchText: Option.none() }),
|
|
16
16
|
cart: [],
|
|
17
17
|
deliveryInstructions: '',
|
|
18
18
|
orderPlaced: false,
|
|
@@ -65,7 +65,7 @@ describe('view', () => {
|
|
|
65
65
|
{ update, view },
|
|
66
66
|
given({
|
|
67
67
|
...baseModel,
|
|
68
|
-
route:
|
|
68
|
+
route: AppRoute.Cart(),
|
|
69
69
|
}),
|
|
70
70
|
expect(role('heading', { name: 'Shopping Cart' })).toExist(),
|
|
71
71
|
expect(text('Your cart is empty')).toExist(),
|
|
@@ -75,7 +75,9 @@ describe('view', () => {
|
|
|
75
75
|
test('the Cart route renders items and the running total', () => {
|
|
76
76
|
scene(
|
|
77
77
|
{ update, view },
|
|
78
|
-
given(
|
|
78
|
+
given(
|
|
79
|
+
withCart([{ item: apple, quantity: 2 }], { route: AppRoute.Cart() }),
|
|
80
|
+
),
|
|
79
81
|
expect(text('Apple')).toExist(),
|
|
80
82
|
expect(text('$1.50 each')).toExist(),
|
|
81
83
|
expect(text('$3.00')).toExist(),
|
|
@@ -89,7 +91,7 @@ describe('view', () => {
|
|
|
89
91
|
{ update, view },
|
|
90
92
|
given({
|
|
91
93
|
...baseModel,
|
|
92
|
-
route:
|
|
94
|
+
route: AppRoute.Checkout(),
|
|
93
95
|
}),
|
|
94
96
|
expect(role('heading', { name: 'Checkout' })).toExist(),
|
|
95
97
|
expect(text('Your cart is empty')).toExist(),
|
|
@@ -101,7 +103,7 @@ describe('view', () => {
|
|
|
101
103
|
{ update, view },
|
|
102
104
|
given(
|
|
103
105
|
withCart([{ item: apple, quantity: 2 }], {
|
|
104
|
-
route:
|
|
106
|
+
route: AppRoute.Checkout(),
|
|
105
107
|
}),
|
|
106
108
|
),
|
|
107
109
|
expect(role('heading', { name: 'Order Summary' })).toExist(),
|
|
@@ -116,7 +118,7 @@ describe('view', () => {
|
|
|
116
118
|
{ update, view },
|
|
117
119
|
given(
|
|
118
120
|
withCart([{ item: apple, quantity: 1 }], {
|
|
119
|
-
route:
|
|
121
|
+
route: AppRoute.Checkout(),
|
|
120
122
|
}),
|
|
121
123
|
),
|
|
122
124
|
click(role('button', { name: 'Place Order' })),
|
|
@@ -129,7 +131,7 @@ describe('view', () => {
|
|
|
129
131
|
{ update, view },
|
|
130
132
|
given({
|
|
131
133
|
...baseModel,
|
|
132
|
-
route:
|
|
134
|
+
route: AppRoute.NotFound({ path: '/oops' }),
|
|
133
135
|
}),
|
|
134
136
|
expect(role('heading', { name: '404 - Page Not Found' })).toExist(),
|
|
135
137
|
expect(text('The path "/oops" was not found.')).toExist(),
|
|
@@ -7,13 +7,13 @@ import { products } from './data/products'
|
|
|
7
7
|
import { Message, type Model, update } from './main'
|
|
8
8
|
import { Products } from './page'
|
|
9
9
|
import { Message as ProductsMessage } from './page/products'
|
|
10
|
-
import {
|
|
10
|
+
import { AppRoute } from './route'
|
|
11
11
|
|
|
12
12
|
const apple = { id: '1', name: 'Apple', price: 1.5 }
|
|
13
13
|
const banana = { id: '2', name: 'Banana', price: 0.75 }
|
|
14
14
|
|
|
15
15
|
const baseModel: Model = {
|
|
16
|
-
route:
|
|
16
|
+
route: AppRoute.Products({ searchText: Option.none() }),
|
|
17
17
|
cart: [],
|
|
18
18
|
deliveryInstructions: '',
|
|
19
19
|
orderPlaced: false,
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import { Schema as S, pipe } from 'effect'
|
|
2
2
|
import { Route } from 'foldkit'
|
|
3
|
-
import {
|
|
3
|
+
import { defineRouteUnion, literal } from 'foldkit/route'
|
|
4
4
|
|
|
5
|
-
export const
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
export const AppRoute = defineRouteUnion({
|
|
6
|
+
Home: {},
|
|
7
|
+
About: {},
|
|
8
|
+
NotFound: { path: S.String },
|
|
9
|
+
})
|
|
8
10
|
|
|
9
|
-
export const AppRoute = S.Union([HomeRoute, AboutRoute, NotFoundRoute])
|
|
10
11
|
export type AppRoute = typeof AppRoute.Type
|
|
11
12
|
|
|
12
|
-
export const homeRouter = pipe(Route.root, Route.mapTo(
|
|
13
|
-
export const aboutRouter = pipe(literal('about'), Route.mapTo(
|
|
13
|
+
export const homeRouter = pipe(Route.root, Route.mapTo(AppRoute.Home))
|
|
14
|
+
export const aboutRouter = pipe(literal('about'), Route.mapTo(AppRoute.About))
|
|
14
15
|
|
|
15
16
|
const routeParser = Route.oneOf(aboutRouter, homeRouter)
|
|
16
17
|
|
|
17
18
|
export const urlToAppRoute = Route.parseUrlWithFallback(
|
|
18
19
|
routeParser,
|
|
19
|
-
|
|
20
|
+
AppRoute.NotFound,
|
|
20
21
|
)
|