create-foldkit-app 0.33.0 → 0.34.0-canary.4e90e99d9ad3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/index.js +1 -1
- package/dist/rendering.js +1 -1
- package/dist/templates/examples/api-cache/src/{main.fixtures.ts → main.fixture.ts} +24 -23
- package/dist/templates/examples/api-cache/src/scene.test.ts +1 -1
- package/dist/templates/examples/api-cache/src/story.test.ts +26 -20
- package/dist/templates/examples/auth/src/page/loggedOut/page/login.scene.test.ts +5 -12
- package/dist/templates/examples/auth/src/page/loggedOut/page/login.story.test.ts +4 -5
- package/dist/templates/examples/auth/src/page/loggedOut/page/login.ts +1 -0
- package/dist/templates/examples/auth/src/scene.test.ts +4 -5
- package/dist/templates/examples/canvas-art/src/scene.test.ts +9 -10
- package/dist/templates/examples/canvas-art/src/story.test.ts +9 -10
- package/dist/templates/examples/charting/src/{main.fixtures.ts → main.fixture.ts} +4 -4
- package/dist/templates/examples/charting/src/scene.test.ts +13 -9
- package/dist/templates/examples/charting/src/story.test.ts +8 -5
- package/dist/templates/examples/charting/src/telemetry.test.ts +1 -1
- package/dist/templates/examples/embedding/src/scene.test.ts +2 -1
- package/dist/templates/examples/embedding/src/story.test.ts +2 -1
- package/dist/templates/examples/form/src/main.ts +2 -0
- package/dist/templates/examples/form/src/scene.test.ts +16 -19
- package/dist/templates/examples/form/src/story.test.ts +11 -13
- package/dist/templates/examples/generative-art/src/scene.test.ts +9 -7
- package/dist/templates/examples/generative-art/src/story.test.ts +16 -10
- package/dist/templates/examples/job-application/src/scene.test.ts +44 -42
- package/dist/templates/examples/job-application/src/step/personalInfo/story.test.ts +7 -5
- package/dist/templates/examples/job-application/src/story.test.ts +26 -23
- package/dist/templates/examples/job-application/src/view/field.ts +1 -0
- package/dist/templates/examples/managed-resource-layer/src/story.test.ts +3 -2
- package/dist/templates/examples/map/src/{main.fixtures.ts → main.fixture.ts} +4 -4
- package/dist/templates/examples/map/src/scene.test.ts +24 -18
- package/dist/templates/examples/map/src/story.test.ts +17 -13
- package/dist/templates/examples/pixel-art/src/comparison.bench.test.ts +2 -1
- package/dist/templates/examples/pixel-art/src/main.bench.ts +3 -2
- package/dist/templates/examples/pixel-art/src/scene.test.ts +35 -33
- package/dist/templates/examples/pixel-art/src/story.test.ts +20 -20
- package/dist/templates/examples/pixel-art/src/view/dialog.ts +2 -0
- package/dist/templates/examples/query-sync/src/scene.test.ts +26 -20
- package/dist/templates/examples/query-sync/src/story.test.ts +11 -8
- package/dist/templates/examples/routing/src/page/people.ts +1 -2
- package/dist/templates/examples/shopping-cart/src/scene.test.ts +22 -28
- package/dist/templates/examples/shopping-cart/src/story.test.ts +35 -28
- package/dist/templates/examples/slow-warnings/src/handleSlow.test.ts +4 -4
- package/dist/templates/examples/slow-warnings/src/scene.test.ts +15 -13
- package/dist/templates/examples/slow-warnings/src/story.test.ts +16 -14
- package/dist/templates/examples/snake/src/scene.test.ts +11 -9
- package/dist/templates/examples/snake/src/story.test.ts +10 -11
- package/dist/templates/examples/ssr/package.json +2 -1
- package/dist/templates/examples/state-machine/src/view.ts +3 -0
- package/dist/templates/examples/stopwatch/src/story.test.ts +2 -1
- package/dist/templates/examples/todo/src/scene.test.ts +8 -9
- package/dist/templates/examples/todo/src/story.test.ts +37 -42
- package/dist/templates/examples/ui-showcase/src/story.test.ts +10 -10
- package/dist/templates/examples/ui-showcase/src/ui/view/checkbox.ts +1 -0
- package/dist/templates/examples/ui-showcase/src/ui/view/dialog.ts +5 -0
- package/dist/templates/examples/ui-showcase/src/ui/view/fieldset.ts +5 -0
- package/dist/templates/examples/ui-showcase/src/ui/view/input.ts +2 -0
- package/dist/templates/examples/ui-showcase/src/ui/view/radioGroup.ts +2 -0
- package/dist/templates/examples/ui-showcase/src/ui/view/select.ts +2 -0
- package/dist/templates/examples/ui-showcase/src/ui/view/switch.ts +1 -0
- package/dist/templates/examples/ui-showcase/src/ui/view/textarea.ts +2 -0
- package/dist/templates/examples/weather/src/scene.test.ts +1 -1
- package/dist/templates/examples/weather/src/story.test.ts +1 -1
- package/dist/templates/examples/websocket-chat/src/scene.test.ts +20 -25
- package/dist/templates/examples/websocket-chat/src/story.test.ts +19 -20
- package/dist/templates/package-managers/pnpm/pnpm-workspace.yaml +0 -1
- package/dist/templates/release.json +14 -14
- package/dist/templates/rendering/ssr/package.json +2 -2
- package/dist/templates/rendering/ssr/scripts/serve.ts +155 -0
- package/dist/templates/rendering/ssr/tsconfig.json +1 -1
- package/dist/templates/rendering/ssr/vite.config.ts +1 -1
- package/dist/utils/packages.js +1 -1
- package/package.json +1 -1
- package/dist/templates/rendering/ssr/server/main.ts +0 -205
- /package/dist/templates/examples/weather/src/{main.fixtures.ts → main.fixture.ts} +0 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# create-foldkit-app
|
|
2
2
|
|
|
3
|
-
Scaffolding CLI for new Foldkit applications. Creates a ready-to-run project with Vite, Tailwind CSS, TypeScript, [`@foldkit/vite-plugin`](https://www.npmjs.com/package/@foldkit/vite-plugin) for
|
|
3
|
+
Scaffolding CLI for new Foldkit applications. Creates a ready-to-run project with Vite, Tailwind CSS, TypeScript, [`@foldkit/vite-plugin`](https://www.npmjs.com/package/@foldkit/vite-plugin) for state-preserving live reload, your choice of rendering mode, and a starter example for SPA scaffolds.
|
|
4
4
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
|
@@ -18,11 +18,11 @@ The CLI prompts you for a project name, rendering mode, starter example, and pac
|
|
|
18
18
|
|
|
19
19
|
## Rendering
|
|
20
20
|
|
|
21
|
-
| Mode | Description
|
|
22
|
-
| ----- |
|
|
23
|
-
| `spa` | Render entirely in the browser. Starts from your choice of starter example below.
|
|
24
|
-
| `ssg` | Prerender routes to static HTML at build time, then hydrate in the browser. Scaffolds a small routed app with a server entry
|
|
25
|
-
| `ssr` | Render each request
|
|
21
|
+
| Mode | Description |
|
|
22
|
+
| ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
23
|
+
| `spa` | Render entirely in the browser. Starts from your choice of starter example below. |
|
|
24
|
+
| `ssg` | Prerender routes to static HTML at build time, then hydrate in the browser. Scaffolds a small routed app with a server entry. One `vite build` writes every route. |
|
|
25
|
+
| `ssr` | Render each request, then hydrate in the browser. Scaffolds a counter whose Flags come from the request. One `vite build` emits a Web `fetch` handler; `start` runs `node scripts/serve.ts`. |
|
|
26
26
|
|
|
27
27
|
The starter examples apply to `spa` rendering. The `ssg` and `ssr` modes scaffold their own starter apps.
|
|
28
28
|
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ const nameSchema = Schema.String.pipe(Schema.check(Schema.makeFilter(value => Op
|
|
|
14
14
|
onSome: message => message,
|
|
15
15
|
}))));
|
|
16
16
|
const name = Flag.string('name').pipe(Flag.withAlias('n'), Flag.withDescription('The name of the project to create'), Flag.withSchema(nameSchema), Flag.optional);
|
|
17
|
-
const rendering = Flag.choice('rendering', RENDERING_VALUES).pipe(Flag.withAlias('r'), Flag.withDescription('How the application renders: spa renders entirely in the browser, ssg prerenders routes to static HTML at build time, ssr renders each request
|
|
17
|
+
const rendering = Flag.choice('rendering', RENDERING_VALUES).pipe(Flag.withAlias('r'), Flag.withDescription('How the application renders: spa renders entirely in the browser, ssg prerenders routes to static HTML at build time, ssr renders each request then hydrates'), Flag.optional);
|
|
18
18
|
const example = Flag.choice('example', EXAMPLE_VALUES).pipe(Flag.withAlias('e'), Flag.withDescription("The example application to start from with spa rendering. Run with no flags for an interactive picker that shows each example's description."), Flag.optional);
|
|
19
19
|
const packageManager = Flag.choice('package-manager', [
|
|
20
20
|
'pnpm',
|
package/dist/rendering.js
CHANGED
|
@@ -14,7 +14,7 @@ export const renderings = [
|
|
|
14
14
|
{
|
|
15
15
|
value: 'ssr',
|
|
16
16
|
title: 'SSR',
|
|
17
|
-
description: 'Render each request
|
|
17
|
+
description: 'Render each request, then hydrate in the browser',
|
|
18
18
|
},
|
|
19
19
|
];
|
|
20
20
|
export const Scaffold = Data.taggedEnum();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { HashMap, Option } from 'effect'
|
|
2
|
+
import { evo } from 'foldkit/struct'
|
|
2
3
|
|
|
3
4
|
import { Tabs } from '@foldkit/ui'
|
|
4
5
|
|
|
@@ -43,28 +44,28 @@ export const loadingPostsModel: Model = {
|
|
|
43
44
|
stats: StatsData.Idle(),
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
export const loadedPostsModel: Model = {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}),
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export const cachedFirstPostModel: Model = {
|
|
54
|
-
...loadedPostsModel,
|
|
55
|
-
postDetailById: HashMap.set(
|
|
56
|
-
HashMap.empty(),
|
|
57
|
-
'first-post',
|
|
58
|
-
PostDetailData.Success({
|
|
59
|
-
data: { detail: firstPostDetail, fetchedAt: FETCHED_AT },
|
|
47
|
+
export const loadedPostsModel: Model = evo(loadingPostsModel, {
|
|
48
|
+
posts: () =>
|
|
49
|
+
PostsData.Success({
|
|
50
|
+
data: { posts: fixturePosts, fetchedAt: FETCHED_AT },
|
|
60
51
|
}),
|
|
61
|
-
|
|
62
|
-
}
|
|
52
|
+
})
|
|
63
53
|
|
|
64
|
-
export const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
54
|
+
export const cachedFirstPostModel: Model = evo(loadedPostsModel, {
|
|
55
|
+
postDetailById: () =>
|
|
56
|
+
HashMap.set(
|
|
57
|
+
HashMap.empty(),
|
|
58
|
+
'first-post',
|
|
59
|
+
PostDetailData.Success({
|
|
60
|
+
data: { detail: firstPostDetail, fetchedAt: FETCHED_AT },
|
|
61
|
+
}),
|
|
62
|
+
),
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
export const loadedStatsModel: Model = evo(loadedPostsModel, {
|
|
66
|
+
activeTab: () => 'Stats',
|
|
67
|
+
stats: () =>
|
|
68
|
+
StatsData.Success({
|
|
69
|
+
data: { stats: fixtureStats, fetchedAt: FETCHED_AT },
|
|
70
|
+
}),
|
|
71
|
+
})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { HashMap, Option, Result } from 'effect'
|
|
2
2
|
import { Command, given, message, model, story } from 'foldkit/story'
|
|
3
|
+
import { evo } from 'foldkit/struct'
|
|
3
4
|
import { expect, test } from 'vitest'
|
|
4
5
|
|
|
5
6
|
import { Tabs } from '@foldkit/ui'
|
|
@@ -22,7 +23,7 @@ import {
|
|
|
22
23
|
fixtureStats,
|
|
23
24
|
loadedPostsModel,
|
|
24
25
|
loadedStatsModel,
|
|
25
|
-
} from './main.
|
|
26
|
+
} from './main.fixture'
|
|
26
27
|
|
|
27
28
|
const postDetailTag = (model: Model, postId: string): string =>
|
|
28
29
|
HashMap.get(model.postDetailById, postId).pipe(
|
|
@@ -96,7 +97,7 @@ test('a revalidation tick keeps stale stats on screen while refetching', () => {
|
|
|
96
97
|
FetchStats,
|
|
97
98
|
Message.SettledFetchStats({
|
|
98
99
|
result: Result.succeed({
|
|
99
|
-
stats:
|
|
100
|
+
stats: evo(fixtureStats, { activeUsers: () => 99 }),
|
|
100
101
|
fetchedAt: FETCHED_AT + 5000,
|
|
101
102
|
}),
|
|
102
103
|
}),
|
|
@@ -134,7 +135,7 @@ test('a failed refresh keeps the stale stats on screen with the error', () => {
|
|
|
134
135
|
test('refresh clicks during an in-flight fetch are deduplicated', () => {
|
|
135
136
|
story(
|
|
136
137
|
update,
|
|
137
|
-
given(
|
|
138
|
+
given(evo(loadedStatsModel, { stats: () => StatsData.Loading() })),
|
|
138
139
|
message(Message.ClickedRefreshStats()),
|
|
139
140
|
Command.expectNone(),
|
|
140
141
|
)
|
|
@@ -143,12 +144,14 @@ test('refresh clicks during an in-flight fetch are deduplicated', () => {
|
|
|
143
144
|
test('a revalidation tick during a refresh is deduplicated', () => {
|
|
144
145
|
story(
|
|
145
146
|
update,
|
|
146
|
-
given(
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
147
|
+
given(
|
|
148
|
+
evo(loadedStatsModel, {
|
|
149
|
+
stats: () =>
|
|
150
|
+
StatsData.Refreshing({
|
|
151
|
+
data: { stats: fixtureStats, fetchedAt: FETCHED_AT },
|
|
152
|
+
}),
|
|
150
153
|
}),
|
|
151
|
-
|
|
154
|
+
),
|
|
152
155
|
message(Message.TickedRevalidateStats()),
|
|
153
156
|
Command.expectNone(),
|
|
154
157
|
)
|
|
@@ -183,10 +186,11 @@ test('invalidating posts refetches while keeping the current list', () => {
|
|
|
183
186
|
test('retrying failed posts shows the loading state and refetches', () => {
|
|
184
187
|
story(
|
|
185
188
|
update,
|
|
186
|
-
given(
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
189
|
+
given(
|
|
190
|
+
evo(loadedPostsModel, {
|
|
191
|
+
posts: () => PostsData.Failure({ error: 'The server is down.' }),
|
|
192
|
+
}),
|
|
193
|
+
),
|
|
190
194
|
message(Message.ClickedRetryPosts()),
|
|
191
195
|
model(model => {
|
|
192
196
|
expect(model.posts._tag).toBe('Loading')
|
|
@@ -269,14 +273,16 @@ test('a failed post detail fetch lands in Failure and retry refetches', () => {
|
|
|
269
273
|
test('revisiting a post with a cached failure shows it without refetching', () => {
|
|
270
274
|
story(
|
|
271
275
|
update,
|
|
272
|
-
given(
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
276
|
+
given(
|
|
277
|
+
evo(loadedPostsModel, {
|
|
278
|
+
postDetailById: () =>
|
|
279
|
+
HashMap.set(
|
|
280
|
+
HashMap.empty(),
|
|
281
|
+
'first-post',
|
|
282
|
+
PostDetailData.Failure({ error: 'The connection dropped.' }),
|
|
283
|
+
),
|
|
284
|
+
}),
|
|
285
|
+
),
|
|
280
286
|
message(Message.ClickedPost({ postId: 'first-post' })),
|
|
281
287
|
Command.expectNone(),
|
|
282
288
|
model(model => {
|
|
@@ -11,21 +11,14 @@ import {
|
|
|
11
11
|
type,
|
|
12
12
|
within,
|
|
13
13
|
} from 'foldkit/scene'
|
|
14
|
+
import { evo } from 'foldkit/struct'
|
|
14
15
|
import { describe, test } from 'vitest'
|
|
15
16
|
|
|
16
|
-
import {
|
|
17
|
-
Message,
|
|
18
|
-
Model,
|
|
19
|
-
SimulateAuthRequest,
|
|
20
|
-
initModel,
|
|
21
|
-
update,
|
|
22
|
-
view,
|
|
23
|
-
} from './login'
|
|
17
|
+
import { Message, SimulateAuthRequest, initModel, update, view } from './login'
|
|
24
18
|
|
|
25
|
-
const validModel =
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
password: Valid({ value: 'password' }),
|
|
19
|
+
const validModel = evo(initModel(), {
|
|
20
|
+
email: () => Valid({ value: 'alice@example.com' }),
|
|
21
|
+
password: () => Valid({ value: 'password' }),
|
|
29
22
|
})
|
|
30
23
|
|
|
31
24
|
const heading = role('heading', { name: 'Sign In' })
|
|
@@ -8,21 +8,20 @@ import {
|
|
|
8
8
|
model,
|
|
9
9
|
story,
|
|
10
10
|
} from 'foldkit/story'
|
|
11
|
+
import { evo } from 'foldkit/struct'
|
|
11
12
|
import { describe, expect, test } from 'vitest'
|
|
12
13
|
|
|
13
14
|
import {
|
|
14
15
|
Message,
|
|
15
|
-
Model,
|
|
16
16
|
OutMessage,
|
|
17
17
|
SimulateAuthRequest,
|
|
18
18
|
initModel,
|
|
19
19
|
update,
|
|
20
20
|
} from './login'
|
|
21
21
|
|
|
22
|
-
const validModel =
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
password: Valid({ value: 'password' }),
|
|
22
|
+
const validModel = evo(initModel(), {
|
|
23
|
+
email: () => Valid({ value: 'alice@example.com' }),
|
|
24
|
+
password: () => Valid({ value: 'password' }),
|
|
26
25
|
})
|
|
27
26
|
|
|
28
27
|
const aliceSession = { userId: '1', email: 'alice@example.com', name: 'alice' }
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
submit,
|
|
9
9
|
text,
|
|
10
10
|
} from 'foldkit/scene'
|
|
11
|
+
import { evo } from 'foldkit/struct'
|
|
11
12
|
import { describe, test } from 'vitest'
|
|
12
13
|
|
|
13
14
|
import { SaveSession } from './command'
|
|
@@ -16,7 +17,6 @@ import { Message } from './message'
|
|
|
16
17
|
import { LoggedOut } from './model'
|
|
17
18
|
import {
|
|
18
19
|
Message as LoginMessage,
|
|
19
|
-
Model as LoginModel,
|
|
20
20
|
SimulateAuthRequest,
|
|
21
21
|
initModel as initLoginModel,
|
|
22
22
|
} from './page/loggedOut/page/login'
|
|
@@ -26,10 +26,9 @@ import { view } from './view'
|
|
|
26
26
|
|
|
27
27
|
const validModel = LoggedOut.Model({
|
|
28
28
|
route: AppRoute.Login(),
|
|
29
|
-
loginModel:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
password: Valid({ value: 'password' }),
|
|
29
|
+
loginModel: evo(initLoginModel(), {
|
|
30
|
+
email: () => Valid({ value: 'alice@example.com' }),
|
|
31
|
+
password: () => Valid({ value: 'password' }),
|
|
33
32
|
}),
|
|
34
33
|
})
|
|
35
34
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { click, expect, given, role, scene, text } from 'foldkit/scene'
|
|
2
|
+
import { evo } from 'foldkit/struct'
|
|
2
3
|
import { describe, test } from 'vitest'
|
|
3
4
|
|
|
4
5
|
import { type Model, update, view } from './main'
|
|
@@ -33,15 +34,14 @@ describe('view', () => {
|
|
|
33
34
|
})
|
|
34
35
|
|
|
35
36
|
test('the ball count reflects spawned balls in the Model', () => {
|
|
36
|
-
const populatedModel: Model = {
|
|
37
|
-
|
|
38
|
-
balls: [
|
|
37
|
+
const populatedModel: Model = evo(emptyModel, {
|
|
38
|
+
balls: () => [
|
|
39
39
|
{ id: 0, x: 10, y: 20, vx: 1, vy: 1, radius: 8, color: '#ff2d55' },
|
|
40
40
|
{ id: 1, x: 30, y: 40, vx: -1, vy: 1, radius: 12, color: '#5ac8fa' },
|
|
41
41
|
{ id: 2, x: 50, y: 60, vx: 1, vy: -1, radius: 16, color: '#34c759' },
|
|
42
42
|
],
|
|
43
|
-
nextId: 3,
|
|
44
|
-
}
|
|
43
|
+
nextId: () => 3,
|
|
44
|
+
})
|
|
45
45
|
|
|
46
46
|
scene(
|
|
47
47
|
{ update, view },
|
|
@@ -51,13 +51,12 @@ describe('view', () => {
|
|
|
51
51
|
})
|
|
52
52
|
|
|
53
53
|
test('Clear empties the rendered ball count', () => {
|
|
54
|
-
const populatedModel: Model = {
|
|
55
|
-
|
|
56
|
-
balls: [
|
|
54
|
+
const populatedModel: Model = evo(emptyModel, {
|
|
55
|
+
balls: () => [
|
|
57
56
|
{ id: 0, x: 10, y: 20, vx: 1, vy: 1, radius: 8, color: '#ff2d55' },
|
|
58
57
|
],
|
|
59
|
-
nextId: 1,
|
|
60
|
-
}
|
|
58
|
+
nextId: () => 1,
|
|
59
|
+
})
|
|
61
60
|
|
|
62
61
|
scene(
|
|
63
62
|
{ update, view },
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Command, given, message, model, story } from 'foldkit/story'
|
|
2
|
+
import { evo } from 'foldkit/struct'
|
|
2
3
|
import { describe, expect, test } from 'vitest'
|
|
3
4
|
|
|
4
5
|
import { GenerateBall, Message, type Model, update } from './main'
|
|
@@ -9,14 +10,13 @@ const emptyModel: Model = {
|
|
|
9
10
|
isRunning: true,
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
const populatedModel: Model = {
|
|
13
|
-
|
|
14
|
-
balls: [
|
|
13
|
+
const populatedModel: Model = evo(emptyModel, {
|
|
14
|
+
balls: () => [
|
|
15
15
|
{ id: 0, x: 100, y: 100, vx: 50, vy: 50, radius: 10, color: '#ff2d55' },
|
|
16
16
|
{ id: 1, x: 200, y: 200, vx: -50, vy: -50, radius: 15, color: '#5ac8fa' },
|
|
17
17
|
],
|
|
18
|
-
nextId: 2,
|
|
19
|
-
}
|
|
18
|
+
nextId: () => 2,
|
|
19
|
+
})
|
|
20
20
|
|
|
21
21
|
describe('update', () => {
|
|
22
22
|
describe('spawning balls', () => {
|
|
@@ -97,9 +97,8 @@ describe('update', () => {
|
|
|
97
97
|
})
|
|
98
98
|
|
|
99
99
|
test('bounces a ball off the canvas edges, flipping its velocity', () => {
|
|
100
|
-
const movingRightModel: Model = {
|
|
101
|
-
|
|
102
|
-
balls: [
|
|
100
|
+
const movingRightModel: Model = evo(emptyModel, {
|
|
101
|
+
balls: () => [
|
|
103
102
|
{
|
|
104
103
|
id: 0,
|
|
105
104
|
x: 595,
|
|
@@ -110,8 +109,8 @@ describe('update', () => {
|
|
|
110
109
|
color: '#fff',
|
|
111
110
|
},
|
|
112
111
|
],
|
|
113
|
-
nextId: 1,
|
|
114
|
-
}
|
|
112
|
+
nextId: () => 1,
|
|
113
|
+
})
|
|
115
114
|
|
|
116
115
|
story(
|
|
117
116
|
update,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Option } from 'effect'
|
|
2
|
+
import { evo } from 'foldkit/struct'
|
|
2
3
|
|
|
3
4
|
import { RadioGroup } from '@foldkit/ui'
|
|
4
5
|
|
|
@@ -119,10 +120,9 @@ export const loadingModel = Model.make({
|
|
|
119
120
|
maybeSelectedDatumId: Option.none(),
|
|
120
121
|
})
|
|
121
122
|
|
|
122
|
-
export const readyModel =
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
maybeChartHostId: Option.some('test-chart-host'),
|
|
123
|
+
export const readyModel = evo(loadingModel, {
|
|
124
|
+
telemetry: () => TelemetryAsyncData.Success({ data: sampleTelemetry }),
|
|
125
|
+
maybeChartHostId: () => Option.some('test-chart-host'),
|
|
126
126
|
})
|
|
127
127
|
|
|
128
128
|
export const mockGitHubRepository = {
|
|
@@ -9,12 +9,13 @@ import {
|
|
|
9
9
|
scene,
|
|
10
10
|
text,
|
|
11
11
|
} from 'foldkit/scene'
|
|
12
|
+
import { evo } from 'foldkit/struct'
|
|
12
13
|
import { describe, test } from 'vitest'
|
|
13
14
|
|
|
14
15
|
import { RadioGroup } from '@foldkit/ui'
|
|
15
16
|
|
|
16
17
|
import { SyncChart } from './command'
|
|
17
|
-
import { loadingModel, readyModel, sampleTelemetry } from './main.
|
|
18
|
+
import { loadingModel, readyModel, sampleTelemetry } from './main.fixture'
|
|
18
19
|
import { Message } from './message'
|
|
19
20
|
import { TelemetryAsyncData } from './model'
|
|
20
21
|
import { update } from './update'
|
|
@@ -77,10 +78,12 @@ describe('view', () => {
|
|
|
77
78
|
test('refreshing state keeps the dashboard visible', () => {
|
|
78
79
|
scene(
|
|
79
80
|
{ update, view },
|
|
80
|
-
given(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
given(
|
|
82
|
+
evo(readyModel, {
|
|
83
|
+
telemetry: () =>
|
|
84
|
+
TelemetryAsyncData.Refreshing({ data: sampleTelemetry }),
|
|
85
|
+
}),
|
|
86
|
+
),
|
|
84
87
|
acknowledgeChartMount,
|
|
85
88
|
acknowledgeChartSync,
|
|
86
89
|
expect(text('Refreshing public data')).toExist(),
|
|
@@ -91,10 +94,11 @@ describe('view', () => {
|
|
|
91
94
|
test('failure without stale data shows retry', () => {
|
|
92
95
|
scene(
|
|
93
96
|
{ update, view },
|
|
94
|
-
given(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
given(
|
|
98
|
+
evo(loadingModel, {
|
|
99
|
+
telemetry: () => TelemetryAsyncData.Failure({ error: 'offline' }),
|
|
100
|
+
}),
|
|
101
|
+
),
|
|
98
102
|
expect(label('Telemetry failed')).toExist(),
|
|
99
103
|
expect(role('button', { name: 'Retry' })).toExist(),
|
|
100
104
|
)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Command, given, message, model, story } from 'foldkit/story'
|
|
2
|
+
import { evo } from 'foldkit/struct'
|
|
2
3
|
import { expect, test } from 'vitest'
|
|
3
4
|
|
|
4
5
|
import { RadioGroup } from '@foldkit/ui'
|
|
@@ -10,7 +11,7 @@ import {
|
|
|
10
11
|
chartModes,
|
|
11
12
|
packageIds,
|
|
12
13
|
} from './domain'
|
|
13
|
-
import { loadingModel, readyModel, sampleTelemetry } from './main.
|
|
14
|
+
import { loadingModel, readyModel, sampleTelemetry } from './main.fixture'
|
|
14
15
|
import { Message } from './message'
|
|
15
16
|
import { TelemetryAsyncData } from './model'
|
|
16
17
|
import { update } from './update'
|
|
@@ -101,10 +102,12 @@ test('refreshing with data keeps the old dashboard while fetching', () => {
|
|
|
101
102
|
test('a failed refresh preserves stale data in the stale state', () => {
|
|
102
103
|
story(
|
|
103
104
|
update,
|
|
104
|
-
given(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
given(
|
|
106
|
+
evo(readyModel, {
|
|
107
|
+
telemetry: () =>
|
|
108
|
+
TelemetryAsyncData.Refreshing({ data: sampleTelemetry }),
|
|
109
|
+
}),
|
|
110
|
+
),
|
|
108
111
|
message(Message.FailedFetchTelemetry({ error: 'rate limited' })),
|
|
109
112
|
model(model => {
|
|
110
113
|
expect(model.telemetry._tag).toBe('Stale')
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Command, click, expect, given, role, scene, text } from 'foldkit/scene'
|
|
2
|
+
import { evo } from 'foldkit/struct'
|
|
2
3
|
import { describe, test } from 'vitest'
|
|
3
4
|
|
|
4
5
|
import { Message, type Model, ReportCount, update, view } from './main'
|
|
@@ -19,7 +20,7 @@ describe('view', () => {
|
|
|
19
20
|
test('clicking the advance button moves the count by the step', () => {
|
|
20
21
|
scene(
|
|
21
22
|
{ update, view },
|
|
22
|
-
given(
|
|
23
|
+
given(evo(initialModel, { step: () => 4 })),
|
|
23
24
|
click(role('button', { name: 'Advance by 4' })),
|
|
24
25
|
Command.resolve(ReportCount, Message.CompletedReportCount()),
|
|
25
26
|
expect(text('14')).toExist(),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Command, given, message, model, story } from 'foldkit/story'
|
|
2
|
+
import { evo } from 'foldkit/struct'
|
|
2
3
|
import { describe, expect, test } from 'vitest'
|
|
3
4
|
|
|
4
5
|
import { Message, type Model, ReportCount, update } from './main'
|
|
@@ -10,7 +11,7 @@ describe('update', () => {
|
|
|
10
11
|
test('Ticked advances the count by the step and reports it on the outbound port', () => {
|
|
11
12
|
story(
|
|
12
13
|
update,
|
|
13
|
-
given(
|
|
14
|
+
given(evo(initialModel, { step: () => 3 })),
|
|
14
15
|
message(Message.Ticked()),
|
|
15
16
|
model(model => {
|
|
16
17
|
expect(model.count).toBe(13)
|
|
@@ -299,6 +299,7 @@ const inputFieldView = (
|
|
|
299
299
|
value: field.value,
|
|
300
300
|
onInput: onUpdate,
|
|
301
301
|
isInvalid: field._tag === 'Invalid',
|
|
302
|
+
hasDescription: field._tag === 'Validating' || field._tag === 'Invalid',
|
|
302
303
|
type,
|
|
303
304
|
toView: attributes =>
|
|
304
305
|
h.div(
|
|
@@ -335,6 +336,7 @@ const textareaFieldView = (
|
|
|
335
336
|
value: field.value,
|
|
336
337
|
onInput: onUpdate,
|
|
337
338
|
isInvalid: field._tag === 'Invalid',
|
|
339
|
+
hasDescription: field._tag === 'Validating' || field._tag === 'Invalid',
|
|
338
340
|
toView: attributes =>
|
|
339
341
|
h.div(
|
|
340
342
|
[h.Class('mb-4')],
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
submit,
|
|
11
11
|
type,
|
|
12
12
|
} from 'foldkit/scene'
|
|
13
|
+
import { evo } from 'foldkit/struct'
|
|
13
14
|
import { describe, test } from 'vitest'
|
|
14
15
|
|
|
15
16
|
import {
|
|
@@ -57,10 +58,9 @@ describe('view', () => {
|
|
|
57
58
|
})
|
|
58
59
|
|
|
59
60
|
test('typing a well-formed email triggers async validation', () => {
|
|
60
|
-
const modelWithValidName = {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
61
|
+
const modelWithValidName = evo(initialModel, {
|
|
62
|
+
name: () => FieldValidation.Valid({ value: 'Alice' }),
|
|
63
|
+
})
|
|
64
64
|
|
|
65
65
|
scene(
|
|
66
66
|
{ update, view },
|
|
@@ -101,11 +101,10 @@ describe('view', () => {
|
|
|
101
101
|
})
|
|
102
102
|
|
|
103
103
|
test('submit becomes enabled once name and email are valid', () => {
|
|
104
|
-
const validModel = {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
104
|
+
const validModel = evo(initialModel, {
|
|
105
|
+
name: () => FieldValidation.Valid({ value: 'Alice' }),
|
|
106
|
+
email: () => FieldValidation.Valid({ value: 'alice@example.com' }),
|
|
107
|
+
})
|
|
109
108
|
|
|
110
109
|
scene(
|
|
111
110
|
{ update, view },
|
|
@@ -115,11 +114,10 @@ describe('view', () => {
|
|
|
115
114
|
})
|
|
116
115
|
|
|
117
116
|
test('submitting a valid form shows the loading label then a success banner', () => {
|
|
118
|
-
const validModel = {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
117
|
+
const validModel = evo(initialModel, {
|
|
118
|
+
name: () => FieldValidation.Valid({ value: 'Alice' }),
|
|
119
|
+
email: () => FieldValidation.Valid({ value: 'alice@example.com' }),
|
|
120
|
+
})
|
|
123
121
|
|
|
124
122
|
scene(
|
|
125
123
|
{ update, view },
|
|
@@ -137,11 +135,10 @@ describe('view', () => {
|
|
|
137
135
|
})
|
|
138
136
|
|
|
139
137
|
test('a failed submission renders an error banner', () => {
|
|
140
|
-
const validModel = {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
138
|
+
const validModel = evo(initialModel, {
|
|
139
|
+
name: () => FieldValidation.Valid({ value: 'Alice' }),
|
|
140
|
+
email: () => FieldValidation.Valid({ value: 'alice@example.com' }),
|
|
141
|
+
})
|
|
145
142
|
|
|
146
143
|
scene(
|
|
147
144
|
{ update, view },
|