create-foldkit-app 0.33.0 → 0.34.0-canary.63fa8e97d24a
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/commands/create.js +4 -4
- package/dist/index.js +7 -7
- package/dist/rendering.js +1 -1
- package/dist/templates/examples/api-cache/package.json +3 -3
- 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/package.json +3 -3
- 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/package.json +3 -3
- 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/package.json +3 -3
- 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/counter/package.json +3 -3
- package/dist/templates/examples/counters/package.json +3 -3
- package/dist/templates/examples/crash-view/package.json +3 -3
- package/dist/templates/examples/embedding/package.json +3 -3
- 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/package.json +3 -3
- 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/package.json +3 -3
- 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/interrupting-commands/package.json +3 -3
- package/dist/templates/examples/job-application/package.json +3 -3
- 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/kanban/package.json +3 -3
- package/dist/templates/examples/managed-resource-layer/package.json +3 -3
- package/dist/templates/examples/managed-resource-layer/src/story.test.ts +3 -2
- package/dist/templates/examples/map/package.json +3 -3
- 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/package.json +3 -3
- package/dist/templates/examples/pixel-art/src/comparison.bench.test.ts +2 -1
- package/dist/templates/examples/pixel-art/src/main.bench.ts +71 -54
- 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/package.json +3 -3
- 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/route-transitions/package.json +3 -3
- package/dist/templates/examples/routing/package.json +3 -3
- package/dist/templates/examples/routing/src/page/people.ts +1 -2
- package/dist/templates/examples/shopping-cart/package.json +3 -3
- 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/package.json +3 -3
- 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/package.json +3 -3
- 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/ssg/package.json +2 -2
- package/dist/templates/examples/ssr/package.json +6 -5
- package/dist/templates/examples/state-machine/package.json +3 -3
- package/dist/templates/examples/state-machine/src/view.ts +3 -0
- package/dist/templates/examples/stopwatch/package.json +3 -3
- package/dist/templates/examples/stopwatch/src/story.test.ts +2 -1
- package/dist/templates/examples/todo/package.json +3 -3
- 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/package.json +3 -3
- 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/view-transitions/package.json +3 -3
- package/dist/templates/examples/weather/package.json +3 -3
- 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/web-components/package.json +3 -3
- package/dist/templates/examples/websocket-chat/package.json +3 -3
- 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 +15 -15
- 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 +6 -6
- 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/commands/create.js
CHANGED
|
@@ -8,14 +8,14 @@ import { createProject } from '../utils/files.js';
|
|
|
8
8
|
import { devCommand, installDependencies, readFoldkitSubtreeRef, } from '../utils/packages.js';
|
|
9
9
|
import { validateProjectName } from '../validateName.js';
|
|
10
10
|
const isWindows = process.platform === 'win32';
|
|
11
|
-
const promptForName = Prompt.
|
|
11
|
+
const promptForName = Prompt.String({
|
|
12
12
|
message: 'Give your project a name',
|
|
13
13
|
validate: value => Option.match(validateProjectName(value), {
|
|
14
14
|
onNone: () => Effect.succeed(value),
|
|
15
15
|
onSome: message => Effect.fail(message),
|
|
16
16
|
}),
|
|
17
17
|
});
|
|
18
|
-
const promptForRendering = Prompt.
|
|
18
|
+
const promptForRendering = Prompt.Select({
|
|
19
19
|
message: 'Pick a rendering mode',
|
|
20
20
|
choices: renderings.map(({ value, title, description }) => ({
|
|
21
21
|
value,
|
|
@@ -23,7 +23,7 @@ const promptForRendering = Prompt.select({
|
|
|
23
23
|
description,
|
|
24
24
|
})),
|
|
25
25
|
});
|
|
26
|
-
const promptForExample = Prompt.
|
|
26
|
+
const promptForExample = Prompt.AutoComplete({
|
|
27
27
|
message: 'Pick a starting example',
|
|
28
28
|
choices: examples.map(({ value, title, description }) => ({
|
|
29
29
|
value,
|
|
@@ -31,7 +31,7 @@ const promptForExample = Prompt.autoComplete({
|
|
|
31
31
|
description,
|
|
32
32
|
})),
|
|
33
33
|
});
|
|
34
|
-
const promptForPackageManager = Prompt.
|
|
34
|
+
const promptForPackageManager = Prompt.Select({
|
|
35
35
|
message: 'Pick a package manager',
|
|
36
36
|
choices: [
|
|
37
37
|
{ value: 'pnpm', title: 'pnpm' },
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { Effect,
|
|
2
|
+
import { Effect, Option, Schema } from 'effect';
|
|
3
3
|
import { Command, Flag } from 'effect/unstable/cli';
|
|
4
4
|
import { createRequire } from 'node:module';
|
|
5
|
-
import { NodeRuntime, NodeServices
|
|
5
|
+
import { NodeRuntime, NodeServices } from '@effect/platform-node';
|
|
6
6
|
import { create as create_ } from './commands/create.js';
|
|
7
7
|
import { EXAMPLE_VALUES } from './examples.js';
|
|
8
8
|
import { RENDERING_VALUES } from './rendering.js';
|
|
@@ -13,10 +13,10 @@ const nameSchema = Schema.String.pipe(Schema.check(Schema.makeFilter(value => Op
|
|
|
13
13
|
onNone: () => true,
|
|
14
14
|
onSome: message => message,
|
|
15
15
|
}))));
|
|
16
|
-
const name = Flag.
|
|
17
|
-
const rendering = Flag.
|
|
18
|
-
const example = Flag.
|
|
19
|
-
const packageManager = Flag.
|
|
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.Literals('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
|
+
const example = Flag.Literals('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
|
+
const packageManager = Flag.Literals('package-manager', [
|
|
20
20
|
'pnpm',
|
|
21
21
|
'npm',
|
|
22
22
|
'yarn',
|
|
@@ -32,4 +32,4 @@ const create = Command.make('create', {
|
|
|
32
32
|
const cli = Command.run(create, {
|
|
33
33
|
version: packageJson.version,
|
|
34
34
|
});
|
|
35
|
-
cli.pipe(Effect.provide(
|
|
35
|
+
cli.pipe(Effect.provide(NodeServices.layer), NodeRuntime.runMain);
|
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();
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
"test": "vitest run"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@effect/platform-browser": "4.0.0-rc.
|
|
12
|
+
"@effect/platform-browser": "4.0.0-rc.115",
|
|
13
13
|
"@foldkit/ui": "workspace:*",
|
|
14
|
-
"effect": "4.0.0-rc.
|
|
14
|
+
"effect": "4.0.0-rc.115",
|
|
15
15
|
"foldkit": "workspace:*"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
@@ -22,6 +22,6 @@
|
|
|
22
22
|
"tailwindcss": "^4.3.3",
|
|
23
23
|
"typescript": "^7.0.2",
|
|
24
24
|
"vite": "^8.2.2",
|
|
25
|
-
"vitest": "^
|
|
25
|
+
"vitest": "^5.0.0"
|
|
26
26
|
}
|
|
27
27
|
}
|
|
@@ -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 => {
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
"test": "vitest run"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@effect/platform-browser": "4.0.0-rc.
|
|
12
|
+
"@effect/platform-browser": "4.0.0-rc.115",
|
|
13
13
|
"@foldkit/ui": "workspace:*",
|
|
14
14
|
"clsx": "^2.1.1",
|
|
15
|
-
"effect": "4.0.0-rc.
|
|
15
|
+
"effect": "4.0.0-rc.115",
|
|
16
16
|
"foldkit": "workspace:*"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
@@ -23,6 +23,6 @@
|
|
|
23
23
|
"tailwindcss": "^4.3.3",
|
|
24
24
|
"typescript": "^7.0.2",
|
|
25
25
|
"vite": "^8.2.2",
|
|
26
|
-
"vitest": "^
|
|
26
|
+
"vitest": "^5.0.0"
|
|
27
27
|
}
|
|
28
28
|
}
|
|
@@ -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
|
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
"test": "vitest run"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@effect/platform-browser": "4.0.0-rc.
|
|
12
|
+
"@effect/platform-browser": "4.0.0-rc.115",
|
|
13
13
|
"@foldkit/ui": "workspace:*",
|
|
14
|
-
"effect": "4.0.0-rc.
|
|
14
|
+
"effect": "4.0.0-rc.115",
|
|
15
15
|
"foldkit": "workspace:*"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
@@ -22,6 +22,6 @@
|
|
|
22
22
|
"tailwindcss": "^4.3.3",
|
|
23
23
|
"typescript": "^7.0.2",
|
|
24
24
|
"vite": "^8.2.2",
|
|
25
|
-
"vitest": "^
|
|
25
|
+
"vitest": "^5.0.0"
|
|
26
26
|
}
|
|
27
27
|
}
|
|
@@ -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,
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
"test": "vitest run"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@effect/platform-browser": "4.0.0-rc.
|
|
12
|
+
"@effect/platform-browser": "4.0.0-rc.115",
|
|
13
13
|
"@foldkit/ui": "workspace:*",
|
|
14
14
|
"clsx": "^2.1.1",
|
|
15
15
|
"echarts": "^6.1.0",
|
|
16
|
-
"effect": "4.0.0-rc.
|
|
16
|
+
"effect": "4.0.0-rc.115",
|
|
17
17
|
"foldkit": "workspace:*"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
@@ -24,6 +24,6 @@
|
|
|
24
24
|
"tailwindcss": "^4.3.3",
|
|
25
25
|
"typescript": "^7.0.2",
|
|
26
26
|
"vite": "^8.2.2",
|
|
27
|
-
"vitest": "^
|
|
27
|
+
"vitest": "^5.0.0"
|
|
28
28
|
}
|
|
29
29
|
}
|
|
@@ -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')
|