create-foldkit-app 0.32.1 → 0.33.0-canary.40b6ef2babe9
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 +5 -5
- package/dist/index.js +1 -1
- package/dist/rendering.js +1 -1
- package/dist/templates/base/.oxfmtrc.json +28 -0
- package/dist/templates/base/.oxlintrc.json +4 -0
- package/dist/templates/base/FOLDKIT.md +1 -1
- package/dist/templates/base/package.json +1 -1
- package/dist/templates/examples/api-cache/package.json +0 -2
- 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 +2 -3
- package/dist/templates/examples/api-cache/src/story.test.ts +29 -24
- package/dist/templates/examples/auth/package.json +0 -2
- 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/scene.test.ts +4 -5
- package/dist/templates/examples/canvas-art/package.json +0 -2
- 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 +0 -2
- package/dist/templates/examples/charting/src/{main.fixtures.ts → main.fixture.ts} +4 -4
- package/dist/templates/examples/charting/src/scene.test.ts +14 -11
- package/dist/templates/examples/charting/src/story.test.ts +11 -9
- package/dist/templates/examples/charting/src/telemetry.test.ts +1 -1
- package/dist/templates/examples/counter/package.json +0 -2
- package/dist/templates/examples/counters/package.json +0 -2
- package/dist/templates/examples/crash-view/package.json +0 -2
- package/dist/templates/examples/embedding/package.json +0 -2
- 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 +0 -2
- 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 +0 -2
- 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 +0 -2
- package/dist/templates/examples/job-application/package.json +0 -2
- package/dist/templates/examples/job-application/src/main.fixture.ts +69 -0
- package/dist/templates/examples/job-application/src/main.ts +1 -1
- package/dist/templates/examples/job-application/src/scene.test.ts +50 -108
- package/dist/templates/examples/job-application/src/step/attachments/index.ts +2 -0
- package/dist/templates/examples/job-application/src/step/attachments/scene.test.ts +19 -0
- package/dist/templates/examples/job-application/src/step/attachments/story.test.ts +46 -0
- package/dist/templates/examples/job-application/src/{view/attachments.ts → step/attachments/view.ts} +4 -11
- package/dist/templates/examples/job-application/src/{view → step/coverLetter}/coverLetter.ts +36 -7
- package/dist/templates/examples/job-application/src/step/coverLetter/index.ts +1 -0
- package/dist/templates/examples/job-application/src/step/coverLetter/scene.test.ts +24 -0
- package/dist/templates/examples/job-application/src/step/coverLetter/story.test.ts +19 -0
- package/dist/templates/examples/job-application/src/step/education/{entry.ts → entry/entry.ts} +2 -2
- package/dist/templates/examples/job-application/src/step/education/entry/index.ts +11 -0
- package/dist/templates/examples/job-application/src/step/education/entry/story.test.ts +39 -0
- package/dist/templates/examples/job-application/src/step/education/entry/view.ts +160 -0
- package/dist/templates/examples/job-application/src/step/education/index.ts +11 -1
- package/dist/templates/examples/job-application/src/step/education/scene.test.ts +20 -0
- package/dist/templates/examples/job-application/src/step/education/story.test.ts +87 -0
- package/dist/templates/examples/job-application/src/{view/education.ts → step/education/view.ts} +6 -10
- package/dist/templates/examples/job-application/src/step/personalInfo/index.ts +11 -0
- package/dist/templates/examples/job-application/src/step/{personalInfo.ts → personalInfo/personalInfo.ts} +2 -2
- package/dist/templates/examples/job-application/src/step/personalInfo/scene.test.ts +38 -0
- package/dist/templates/examples/job-application/src/step/personalInfo/story.test.ts +89 -0
- package/dist/templates/examples/job-application/src/{view/personalInfo.ts → step/personalInfo/view.ts} +24 -34
- package/dist/templates/examples/job-application/src/step/skills/{entry.ts → entry/entry.ts} +2 -2
- package/dist/templates/examples/job-application/src/step/skills/entry/index.ts +11 -0
- package/dist/templates/examples/job-application/src/step/skills/entry/story.test.ts +27 -0
- package/dist/templates/examples/job-application/src/{view/skillEntry.ts → step/skills/entry/view.ts} +9 -12
- package/dist/templates/examples/job-application/src/step/skills/index.ts +11 -1
- package/dist/templates/examples/job-application/src/step/skills/scene.test.ts +16 -0
- package/dist/templates/examples/job-application/src/step/skills/story.test.ts +85 -0
- package/dist/templates/examples/job-application/src/step/skills/view.ts +47 -0
- package/dist/templates/examples/job-application/src/step/workHistory/{entry.ts → entry/entry.ts} +1 -1
- package/dist/templates/examples/job-application/src/step/workHistory/entry/index.ts +11 -0
- package/dist/templates/examples/job-application/src/step/workHistory/entry/story.test.ts +42 -0
- package/dist/templates/examples/job-application/src/{view/workEntry.ts → step/workHistory/entry/view.ts} +27 -42
- package/dist/templates/examples/job-application/src/step/workHistory/index.ts +11 -1
- package/dist/templates/examples/job-application/src/step/workHistory/scene.test.ts +20 -0
- package/dist/templates/examples/job-application/src/step/workHistory/story.test.ts +87 -0
- package/dist/templates/examples/job-application/src/{view/workHistory.ts → step/workHistory/view.ts} +4 -8
- package/dist/templates/examples/job-application/src/story.test.ts +54 -442
- package/dist/templates/examples/job-application/src/view/datePicker.ts +4 -4
- package/dist/templates/examples/job-application/src/view/field.ts +3 -3
- package/dist/templates/examples/job-application/src/view/index.ts +3 -358
- package/dist/templates/examples/job-application/src/view/stepNav.ts +2 -2
- package/dist/templates/examples/job-application/src/view/view.ts +359 -0
- package/dist/templates/examples/kanban/package.json +0 -2
- package/dist/templates/examples/kanban/src/scene.test.ts +1 -1
- package/dist/templates/examples/kanban/src/story.test.ts +22 -23
- package/dist/templates/examples/kanban/src/view/index.ts +1 -71
- package/dist/templates/examples/kanban/src/view/view.ts +71 -0
- package/dist/templates/examples/managed-resource-layer/package.json +0 -2
- package/dist/templates/examples/managed-resource-layer/src/story.test.ts +3 -2
- package/dist/templates/examples/map/package.json +0 -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/package.json +0 -2
- 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 +51 -42
- package/dist/templates/examples/pixel-art/src/story.test.ts +32 -27
- package/dist/templates/examples/query-sync/package.json +0 -2
- package/dist/templates/examples/query-sync/src/scene.test.ts +26 -20
- package/dist/templates/examples/query-sync/src/story.test.ts +15 -13
- package/dist/templates/examples/route-transitions/package.json +0 -2
- package/dist/templates/examples/routing/package.json +0 -2
- package/dist/templates/examples/routing/src/page/people.scene.test.ts +41 -0
- package/dist/templates/examples/routing/src/page/people.story.test.ts +73 -0
- package/dist/templates/examples/routing/src/scene.test.ts +2 -17
- package/dist/templates/examples/routing/src/story.test.ts +4 -34
- package/dist/templates/examples/shopping-cart/package.json +0 -2
- package/dist/templates/examples/shopping-cart/src/page/products.scene.test.ts +20 -0
- package/dist/templates/examples/shopping-cart/src/page/products.story.test.ts +56 -0
- package/dist/templates/examples/shopping-cart/src/page/products.ts +1 -1
- package/dist/templates/examples/shopping-cart/src/scene.test.ts +23 -32
- package/dist/templates/examples/shopping-cart/src/story.test.ts +38 -32
- package/dist/templates/examples/slow-warnings/package.json +0 -2
- 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 +0 -2
- 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 +0 -2
- package/dist/templates/examples/ssr/package.json +1 -3
- package/dist/templates/examples/state-machine/package.json +0 -2
- package/dist/templates/examples/state-machine/src/main.ts +79 -87
- package/dist/templates/examples/state-machine/src/scene.test.ts +1 -2
- package/dist/templates/examples/stopwatch/package.json +0 -2
- package/dist/templates/examples/stopwatch/src/story.test.ts +2 -1
- package/dist/templates/examples/todo/package.json +0 -2
- 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 +0 -2
- package/dist/templates/examples/ui-showcase/src/story.test.ts +11 -12
- package/dist/templates/examples/view-transitions/package.json +0 -2
- package/dist/templates/examples/view-transitions/src/styles.css +3 -3
- package/dist/templates/examples/weather/package.json +0 -2
- 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 +0 -2
- package/dist/templates/examples/websocket-chat/package.json +0 -2
- 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/release.json +15 -16
- package/dist/templates/rendering/ssg/package.json +1 -1
- package/dist/templates/rendering/ssr/package.json +3 -3
- 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 +2 -3
- package/package.json +3 -3
- package/dist/templates/base/.prettierignore +0 -5
- package/dist/templates/base/.prettierrc +0 -13
- package/dist/templates/examples/job-application/src/step/coverLetter.ts +0 -34
- package/dist/templates/examples/job-application/src/view/educationEntry.ts +0 -165
- package/dist/templates/examples/job-application/src/view/skills.ts +0 -49
- package/dist/templates/rendering/ssr/server/main.ts +0 -205
- /package/dist/templates/examples/job-application/src/step/{attachments.ts → attachments/attachments.ts} +0 -0
- /package/dist/templates/examples/weather/src/{main.fixtures.ts → main.fixture.ts} +0 -0
package/README.md
CHANGED
|
@@ -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();
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/oxfmt/configuration_schema.json",
|
|
3
|
+
"semi": false,
|
|
4
|
+
"singleQuote": true,
|
|
5
|
+
"arrowParens": "avoid",
|
|
6
|
+
"trailingComma": "all",
|
|
7
|
+
"printWidth": 80,
|
|
8
|
+
"sortImports": {
|
|
9
|
+
"ignoreCase": false,
|
|
10
|
+
"customGroups": [
|
|
11
|
+
{ "groupName": "scoped", "elementNamePattern": ["@*", "@*/**"] }
|
|
12
|
+
],
|
|
13
|
+
"groups": [
|
|
14
|
+
["builtin", "external"],
|
|
15
|
+
"scoped",
|
|
16
|
+
["parent", "sibling", "index"],
|
|
17
|
+
"unknown"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
"sortPackageJson": false,
|
|
21
|
+
"ignorePatterns": [
|
|
22
|
+
"node_modules",
|
|
23
|
+
"dist",
|
|
24
|
+
"repos",
|
|
25
|
+
"*.tsbuildinfo",
|
|
26
|
+
"pnpm-lock.yaml"
|
|
27
|
+
]
|
|
28
|
+
}
|
|
@@ -149,7 +149,7 @@ type Message = typeof Message.Type
|
|
|
149
149
|
|
|
150
150
|
Keep the `defineMessageUnion()` declaration and `type Message` alias adjacent. Construct values through the namespace (`Message.ClickedSubmit()`) and handle the union with `Message.match`. Never destructure constructors from `Message` or `OutMessage`; the owning namespace stays visible at every call site.
|
|
151
151
|
|
|
152
|
-
Keep each case's payload object on one line when it fits. Let
|
|
152
|
+
Keep each case's payload object on one line when it fits. Let Oxfmt wrap payloads that need more space, so the declaration remains easy to scan as one variant per line.
|
|
153
153
|
|
|
154
154
|
Messages are verb-first past-tense. Common prefixes: `Clicked*`, `Updated*` (input changes and external state updates), `Submitted*`, `Pressed*`, `Selected*`, `Succeeded*` / `Failed*` (paired async results), `Completed*` (every other Command result), `Got*` (child OutMessage in the Submodel pattern).
|
|
155
155
|
|
|
@@ -18,9 +18,7 @@
|
|
|
18
18
|
"@foldkit/devtools": "workspace:*",
|
|
19
19
|
"@foldkit/vite-plugin": "workspace:*",
|
|
20
20
|
"@tailwindcss/vite": "^4.3.3",
|
|
21
|
-
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
|
|
22
21
|
"happy-dom": "^20.11.13",
|
|
23
|
-
"prettier": "^3.9.6",
|
|
24
22
|
"tailwindcss": "^4.3.3",
|
|
25
23
|
"typescript": "^7.0.2",
|
|
26
24
|
"vite": "^8.2.2",
|
|
@@ -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
|
+
})
|
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
import { describe, test } from 'vitest'
|
|
13
13
|
|
|
14
14
|
import { Tabs } from '@foldkit/ui'
|
|
15
|
-
import { Message as TabsMessage } from '@foldkit/ui/tabs'
|
|
16
15
|
|
|
17
16
|
import { FetchPostDetail, FetchStats, Message, update, view } from './main'
|
|
18
17
|
import {
|
|
@@ -22,11 +21,11 @@ import {
|
|
|
22
21
|
fixtureStats,
|
|
23
22
|
loadedPostsModel,
|
|
24
23
|
loadingPostsModel,
|
|
25
|
-
} from './main.
|
|
24
|
+
} from './main.fixture'
|
|
26
25
|
|
|
27
26
|
const resolveFocusTab = Command.resolve(
|
|
28
27
|
Tabs.FocusTab,
|
|
29
|
-
|
|
28
|
+
Tabs.Message.CompletedFocusTab(),
|
|
30
29
|
)
|
|
31
30
|
|
|
32
31
|
describe('view', () => {
|
|
@@ -1,9 +1,9 @@
|
|
|
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'
|
|
6
|
-
import { Message as TabsMessage } from '@foldkit/ui/tabs'
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
9
|
FetchPostDetail,
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
fixtureStats,
|
|
24
24
|
loadedPostsModel,
|
|
25
25
|
loadedStatsModel,
|
|
26
|
-
} from './main.
|
|
26
|
+
} from './main.fixture'
|
|
27
27
|
|
|
28
28
|
const postDetailTag = (model: Model, postId: string): string =>
|
|
29
29
|
HashMap.get(model.postDetailById, postId).pipe(
|
|
@@ -32,16 +32,16 @@ const postDetailTag = (model: Model, postId: string): string =>
|
|
|
32
32
|
)
|
|
33
33
|
|
|
34
34
|
const selectedPostsTab = Message.GotTabsMessage({
|
|
35
|
-
message:
|
|
35
|
+
message: Tabs.Message.SelectedTab({ index: 0, value: 'Posts' }),
|
|
36
36
|
})
|
|
37
37
|
|
|
38
38
|
const selectedStatsTab = Message.GotTabsMessage({
|
|
39
|
-
message:
|
|
39
|
+
message: Tabs.Message.SelectedTab({ index: 1, value: 'Stats' }),
|
|
40
40
|
})
|
|
41
41
|
|
|
42
42
|
const resolveFocusTab = Command.resolve(
|
|
43
43
|
Tabs.FocusTab,
|
|
44
|
-
|
|
44
|
+
Tabs.Message.CompletedFocusTab(),
|
|
45
45
|
)
|
|
46
46
|
|
|
47
47
|
test('first visit to the Stats tab fetches stats', () => {
|
|
@@ -97,7 +97,7 @@ test('a revalidation tick keeps stale stats on screen while refetching', () => {
|
|
|
97
97
|
FetchStats,
|
|
98
98
|
Message.SettledFetchStats({
|
|
99
99
|
result: Result.succeed({
|
|
100
|
-
stats:
|
|
100
|
+
stats: evo(fixtureStats, { activeUsers: () => 99 }),
|
|
101
101
|
fetchedAt: FETCHED_AT + 5000,
|
|
102
102
|
}),
|
|
103
103
|
}),
|
|
@@ -135,7 +135,7 @@ test('a failed refresh keeps the stale stats on screen with the error', () => {
|
|
|
135
135
|
test('refresh clicks during an in-flight fetch are deduplicated', () => {
|
|
136
136
|
story(
|
|
137
137
|
update,
|
|
138
|
-
given(
|
|
138
|
+
given(evo(loadedStatsModel, { stats: () => StatsData.Loading() })),
|
|
139
139
|
message(Message.ClickedRefreshStats()),
|
|
140
140
|
Command.expectNone(),
|
|
141
141
|
)
|
|
@@ -144,12 +144,14 @@ test('refresh clicks during an in-flight fetch are deduplicated', () => {
|
|
|
144
144
|
test('a revalidation tick during a refresh is deduplicated', () => {
|
|
145
145
|
story(
|
|
146
146
|
update,
|
|
147
|
-
given(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
147
|
+
given(
|
|
148
|
+
evo(loadedStatsModel, {
|
|
149
|
+
stats: () =>
|
|
150
|
+
StatsData.Refreshing({
|
|
151
|
+
data: { stats: fixtureStats, fetchedAt: FETCHED_AT },
|
|
152
|
+
}),
|
|
151
153
|
}),
|
|
152
|
-
|
|
154
|
+
),
|
|
153
155
|
message(Message.TickedRevalidateStats()),
|
|
154
156
|
Command.expectNone(),
|
|
155
157
|
)
|
|
@@ -184,10 +186,11 @@ test('invalidating posts refetches while keeping the current list', () => {
|
|
|
184
186
|
test('retrying failed posts shows the loading state and refetches', () => {
|
|
185
187
|
story(
|
|
186
188
|
update,
|
|
187
|
-
given(
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
189
|
+
given(
|
|
190
|
+
evo(loadedPostsModel, {
|
|
191
|
+
posts: () => PostsData.Failure({ error: 'The server is down.' }),
|
|
192
|
+
}),
|
|
193
|
+
),
|
|
191
194
|
message(Message.ClickedRetryPosts()),
|
|
192
195
|
model(model => {
|
|
193
196
|
expect(model.posts._tag).toBe('Loading')
|
|
@@ -270,14 +273,16 @@ test('a failed post detail fetch lands in Failure and retry refetches', () => {
|
|
|
270
273
|
test('revisiting a post with a cached failure shows it without refetching', () => {
|
|
271
274
|
story(
|
|
272
275
|
update,
|
|
273
|
-
given(
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
+
),
|
|
281
286
|
message(Message.ClickedPost({ postId: 'first-post' })),
|
|
282
287
|
Command.expectNone(),
|
|
283
288
|
model(model => {
|
|
@@ -19,9 +19,7 @@
|
|
|
19
19
|
"@foldkit/devtools": "workspace:*",
|
|
20
20
|
"@foldkit/vite-plugin": "workspace:*",
|
|
21
21
|
"@tailwindcss/vite": "^4.3.3",
|
|
22
|
-
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
|
|
23
22
|
"happy-dom": "^20.11.13",
|
|
24
|
-
"prettier": "^3.9.6",
|
|
25
23
|
"tailwindcss": "^4.3.3",
|
|
26
24
|
"typescript": "^7.0.2",
|
|
27
25
|
"vite": "^8.2.2",
|
|
@@ -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
|
|
|
@@ -18,9 +18,7 @@
|
|
|
18
18
|
"@foldkit/devtools": "workspace:*",
|
|
19
19
|
"@foldkit/vite-plugin": "workspace:*",
|
|
20
20
|
"@tailwindcss/vite": "^4.3.3",
|
|
21
|
-
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
|
|
22
21
|
"happy-dom": "^20.11.13",
|
|
23
|
-
"prettier": "^3.9.6",
|
|
24
22
|
"tailwindcss": "^4.3.3",
|
|
25
23
|
"typescript": "^7.0.2",
|
|
26
24
|
"vite": "^8.2.2",
|
|
@@ -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,
|
|
@@ -20,9 +20,7 @@
|
|
|
20
20
|
"@foldkit/devtools": "workspace:*",
|
|
21
21
|
"@foldkit/vite-plugin": "workspace:*",
|
|
22
22
|
"@tailwindcss/vite": "^4.3.3",
|
|
23
|
-
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
|
|
24
23
|
"happy-dom": "^20.11.13",
|
|
25
|
-
"prettier": "^3.9.6",
|
|
26
24
|
"tailwindcss": "^4.3.3",
|
|
27
25
|
"typescript": "^7.0.2",
|
|
28
26
|
"vite": "^8.2.2",
|
|
@@ -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,13 +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
|
-
import { Message as RadioGroupMessage } from '@foldkit/ui/radioGroup'
|
|
16
16
|
|
|
17
17
|
import { SyncChart } from './command'
|
|
18
|
-
import { loadingModel, readyModel, sampleTelemetry } from './main.
|
|
18
|
+
import { loadingModel, readyModel, sampleTelemetry } from './main.fixture'
|
|
19
19
|
import { Message } from './message'
|
|
20
20
|
import { TelemetryAsyncData } from './model'
|
|
21
21
|
import { update } from './update'
|
|
@@ -34,7 +34,7 @@ const acknowledgeChartSync = Command.resolve(
|
|
|
34
34
|
|
|
35
35
|
const resolveFocusOption = Command.resolve(
|
|
36
36
|
RadioGroup.FocusOption,
|
|
37
|
-
|
|
37
|
+
RadioGroup.Message.CompletedFocusOption(),
|
|
38
38
|
)
|
|
39
39
|
|
|
40
40
|
describe('view', () => {
|
|
@@ -78,10 +78,12 @@ describe('view', () => {
|
|
|
78
78
|
test('refreshing state keeps the dashboard visible', () => {
|
|
79
79
|
scene(
|
|
80
80
|
{ update, view },
|
|
81
|
-
given(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
given(
|
|
82
|
+
evo(readyModel, {
|
|
83
|
+
telemetry: () =>
|
|
84
|
+
TelemetryAsyncData.Refreshing({ data: sampleTelemetry }),
|
|
85
|
+
}),
|
|
86
|
+
),
|
|
85
87
|
acknowledgeChartMount,
|
|
86
88
|
acknowledgeChartSync,
|
|
87
89
|
expect(text('Refreshing public data')).toExist(),
|
|
@@ -92,10 +94,11 @@ describe('view', () => {
|
|
|
92
94
|
test('failure without stale data shows retry', () => {
|
|
93
95
|
scene(
|
|
94
96
|
{ update, view },
|
|
95
|
-
given(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
given(
|
|
98
|
+
evo(loadingModel, {
|
|
99
|
+
telemetry: () => TelemetryAsyncData.Failure({ error: 'offline' }),
|
|
100
|
+
}),
|
|
101
|
+
),
|
|
99
102
|
expect(label('Telemetry failed')).toExist(),
|
|
100
103
|
expect(role('button', { name: 'Retry' })).toExist(),
|
|
101
104
|
)
|
|
@@ -1,8 +1,8 @@
|
|
|
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'
|
|
5
|
-
import { Message as RadioGroupMessage } from '@foldkit/ui/radioGroup'
|
|
6
6
|
|
|
7
7
|
import { FetchTelemetry, SyncChart } from './command'
|
|
8
8
|
import {
|
|
@@ -11,14 +11,14 @@ import {
|
|
|
11
11
|
chartModes,
|
|
12
12
|
packageIds,
|
|
13
13
|
} from './domain'
|
|
14
|
-
import { loadingModel, readyModel, sampleTelemetry } from './main.
|
|
14
|
+
import { loadingModel, readyModel, sampleTelemetry } from './main.fixture'
|
|
15
15
|
import { Message } from './message'
|
|
16
16
|
import { TelemetryAsyncData } from './model'
|
|
17
17
|
import { update } from './update'
|
|
18
18
|
|
|
19
19
|
const selectedChartMode = (chartMode: ChartMode) =>
|
|
20
20
|
Message.GotChartModeRadioGroupMessage({
|
|
21
|
-
message:
|
|
21
|
+
message: RadioGroup.Message.SelectedOption({
|
|
22
22
|
index: chartModes.indexOf(chartMode),
|
|
23
23
|
value: chartMode,
|
|
24
24
|
}),
|
|
@@ -26,7 +26,7 @@ const selectedChartMode = (chartMode: ChartMode) =>
|
|
|
26
26
|
|
|
27
27
|
const selectedPackage = (packageId: PackageId) =>
|
|
28
28
|
Message.GotPackageRadioGroupMessage({
|
|
29
|
-
message:
|
|
29
|
+
message: RadioGroup.Message.SelectedOption({
|
|
30
30
|
index: packageIds.indexOf(packageId),
|
|
31
31
|
value: packageId,
|
|
32
32
|
}),
|
|
@@ -34,7 +34,7 @@ const selectedPackage = (packageId: PackageId) =>
|
|
|
34
34
|
|
|
35
35
|
const resolveChartModeFocus = Command.resolve(
|
|
36
36
|
RadioGroup.FocusOption,
|
|
37
|
-
|
|
37
|
+
RadioGroup.Message.CompletedFocusOption(),
|
|
38
38
|
)
|
|
39
39
|
|
|
40
40
|
test('mounting the chart syncs current telemetry into ECharts', () => {
|
|
@@ -102,10 +102,12 @@ test('refreshing with data keeps the old dashboard while fetching', () => {
|
|
|
102
102
|
test('a failed refresh preserves stale data in the stale state', () => {
|
|
103
103
|
story(
|
|
104
104
|
update,
|
|
105
|
-
given(
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
105
|
+
given(
|
|
106
|
+
evo(readyModel, {
|
|
107
|
+
telemetry: () =>
|
|
108
|
+
TelemetryAsyncData.Refreshing({ data: sampleTelemetry }),
|
|
109
|
+
}),
|
|
110
|
+
),
|
|
109
111
|
message(Message.FailedFetchTelemetry({ error: 'rate limited' })),
|
|
110
112
|
model(model => {
|
|
111
113
|
expect(model.telemetry._tag).toBe('Stale')
|