@stone-js/starters 0.8.13 → 0.8.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/basic-react-declarative/package.json +6 -5
  2. package/basic-react-declarative/tests/Application.spec.ts +4 -3
  3. package/basic-react-imperative/package.json +6 -5
  4. package/basic-react-imperative/tests/Application.spec.ts +4 -3
  5. package/basic-react-native-declarative/README.md +15 -22
  6. package/basic-react-native-declarative/package.json +7 -7
  7. package/basic-react-native-declarative/tests/HomeScreen.spec.ts +36 -0
  8. package/basic-react-native-declarative/tests/{Application.spec.ts → navigation.spec.ts} +10 -7
  9. package/basic-react-native-declarative/vitest.config.ts +6 -0
  10. package/basic-react-native-imperative/README.md +15 -22
  11. package/basic-react-native-imperative/package.json +7 -7
  12. package/basic-react-native-imperative/tests/HomeScreen.spec.ts +36 -0
  13. package/basic-react-native-imperative/tests/{Application.spec.ts → navigation.spec.ts} +10 -7
  14. package/basic-react-native-imperative/vitest.config.ts +6 -0
  15. package/basic-service-declarative/package.json +5 -4
  16. package/basic-service-declarative/tests/Application.spec.ts +2 -1
  17. package/basic-service-imperative/package.json +5 -4
  18. package/basic-service-imperative/tests/Application.spec.ts +2 -1
  19. package/continuum-showcase/package.json +9 -9
  20. package/continuum-showcase/tests/application.spec.ts +2 -1
  21. package/full-react-declarative/package.json +10 -10
  22. package/full-react-declarative/tests/application.spec.ts +2 -1
  23. package/full-react-imperative/package.json +10 -10
  24. package/full-react-imperative/tests/application.spec.ts +2 -1
  25. package/full-service-declarative/package.json +10 -10
  26. package/full-service-declarative/tests/application.spec.ts +2 -1
  27. package/full-service-imperative/package.json +10 -10
  28. package/full-service-imperative/tests/application.spec.ts +2 -1
  29. package/monorepo-declarative/LICENSE +21 -0
  30. package/monorepo-declarative/README.md +91 -0
  31. package/monorepo-declarative/apps/mobile/App.tsx +26 -0
  32. package/monorepo-declarative/apps/mobile/LICENSE +21 -0
  33. package/monorepo-declarative/apps/mobile/app/Application.ts +24 -0
  34. package/monorepo-declarative/apps/mobile/app/PortalMark.tsx +32 -0
  35. package/monorepo-declarative/apps/mobile/app/TaskListScreen.tsx +67 -0
  36. package/monorepo-declarative/apps/mobile/app/TaskListView.tsx +98 -0
  37. package/monorepo-declarative/apps/mobile/app/theme.ts +66 -0
  38. package/monorepo-declarative/apps/mobile/app.json +32 -0
  39. package/monorepo-declarative/apps/mobile/assets/android-icon-background.png +0 -0
  40. package/monorepo-declarative/apps/mobile/assets/android-icon-foreground.png +0 -0
  41. package/monorepo-declarative/apps/mobile/assets/android-icon-monochrome.png +0 -0
  42. package/monorepo-declarative/apps/mobile/assets/favicon.png +0 -0
  43. package/monorepo-declarative/apps/mobile/assets/icon.png +0 -0
  44. package/monorepo-declarative/apps/mobile/assets/logo-dark.png +0 -0
  45. package/monorepo-declarative/apps/mobile/assets/logo-light.png +0 -0
  46. package/monorepo-declarative/apps/mobile/assets/splash-icon.png +0 -0
  47. package/monorepo-declarative/apps/mobile/babel.config.js +16 -0
  48. package/monorepo-declarative/apps/mobile/index.ts +30 -0
  49. package/monorepo-declarative/apps/mobile/metro.config.js +13 -0
  50. package/monorepo-declarative/apps/mobile/package.json +47 -0
  51. package/monorepo-declarative/apps/mobile/tests/TaskListScreen.spec.ts +54 -0
  52. package/monorepo-declarative/apps/mobile/tests/stubs/react-native.ts +42 -0
  53. package/monorepo-declarative/apps/mobile/tsconfig.json +15 -0
  54. package/monorepo-declarative/apps/mobile/vitest.config.ts +35 -0
  55. package/monorepo-declarative/apps/web/LICENSE +21 -0
  56. package/monorepo-declarative/apps/web/app/Application.ts +23 -0
  57. package/monorepo-declarative/apps/web/app/TaskListPage.tsx +95 -0
  58. package/monorepo-declarative/apps/web/assets/css/index.css +220 -0
  59. package/monorepo-declarative/apps/web/package.json +40 -0
  60. package/monorepo-declarative/apps/web/public/favicon.svg +16 -0
  61. package/monorepo-declarative/apps/web/public/logo.svg +16 -0
  62. package/monorepo-declarative/apps/web/tests/TaskListPage.spec.ts +37 -0
  63. package/monorepo-declarative/apps/web/tsconfig.json +33 -0
  64. package/monorepo-declarative/package.json +28 -0
  65. package/monorepo-declarative/packages/domain/LICENSE +21 -0
  66. package/monorepo-declarative/packages/domain/package.json +32 -0
  67. package/monorepo-declarative/packages/domain/src/Task.ts +16 -0
  68. package/monorepo-declarative/packages/domain/src/TaskService.ts +75 -0
  69. package/monorepo-declarative/packages/domain/src/index.ts +33 -0
  70. package/monorepo-declarative/packages/domain/tests/TaskService.spec.ts +73 -0
  71. package/monorepo-declarative/packages/domain/tsconfig.build.json +4 -0
  72. package/monorepo-declarative/packages/domain/tsconfig.json +16 -0
  73. package/monorepo-declarative/packages/domain/vitest.config.ts +15 -0
  74. package/monorepo-declarative/pnpm-lock.yaml +7613 -0
  75. package/monorepo-declarative/pnpm-workspace.yaml +4 -0
  76. package/package.json +16 -1
  77. package/standard-react-declarative/package.json +9 -9
  78. package/standard-react-declarative/tests/application.spec.ts +2 -1
  79. package/standard-react-imperative/package.json +9 -9
  80. package/standard-react-imperative/tests/application.spec.ts +2 -1
  81. package/standard-service-declarative/package.json +7 -7
  82. package/standard-service-declarative/tests/Application.spec.ts +2 -1
  83. package/standard-service-imperative/package.json +7 -7
  84. package/standard-service-imperative/tests/Application.spec.ts +2 -1
@@ -20,16 +20,17 @@
20
20
  "dependencies": {
21
21
  "react": "^19.0.0",
22
22
  "react-dom": "^19.0.0",
23
- "@stone-js/core": "^0.8.13",
24
- "@stone-js/use-react": "^0.8.13",
25
- "@stone-js/browser-adapter": "^0.8.13"
23
+ "@stone-js/core": "^0.8.14",
24
+ "@stone-js/use-react": "^0.8.14",
25
+ "@stone-js/browser-adapter": "^0.8.14"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@babel/plugin-proposal-decorators": "^7.25.9",
29
29
  "@babel/preset-env": "^7.26.9",
30
30
  "@babel/preset-typescript": "^7.27.0",
31
- "@stone-js/cli": "^0.8.13",
32
- "@stone-js/testing": "^0.8.13",
31
+ "@stone-js/browser-core": "^0.8.14",
32
+ "@stone-js/cli": "^0.8.14",
33
+ "@stone-js/testing": "^0.8.14",
33
34
  "@types/node": "^24.0.7",
34
35
  "@types/react": "^19.0.7",
35
36
  "@types/react-dom": "^19.0.3",
@@ -1,4 +1,5 @@
1
- import { createTestApp, makeIncomingHttpEvent } from '@stone-js/testing'
1
+ import { createTestApp } from '@stone-js/testing'
2
+ import { makeIncomingBrowserEvent } from '@stone-js/testing/browser'
2
3
 
3
4
  /**
4
5
  * The whole application, booted in memory and asked a real question.
@@ -12,7 +13,7 @@ describe('Pages', () => {
12
13
  it('renders the home page, data included', async () => {
13
14
  const app = await createTestApp()
14
15
 
15
- const response = await app.send(makeIncomingHttpEvent({ method: 'GET', url: '/?name=Ada' }))
16
+ const response = await app.send(makeIncomingBrowserEvent({ url: '/?name=Ada' }))
16
17
 
17
18
  expect(response.statusCode).toBe(200)
18
19
  // A page is a handler, so a rendered page is a response whose body is HTML. Query that HTML with
@@ -23,7 +24,7 @@ describe('Pages', () => {
23
24
  it('renders without a name', async () => {
24
25
  const app = await createTestApp()
25
26
 
26
- const response = await app.send(makeIncomingHttpEvent({ method: 'GET', url: '/' }))
27
+ const response = await app.send(makeIncomingBrowserEvent({ url: '/' }))
27
28
 
28
29
  expect(response.statusCode).toBe(200)
29
30
  })
@@ -20,16 +20,17 @@
20
20
  "dependencies": {
21
21
  "react": "^19.0.0",
22
22
  "react-dom": "^19.0.0",
23
- "@stone-js/core": "^0.8.13",
24
- "@stone-js/use-react": "^0.8.13",
25
- "@stone-js/browser-adapter": "^0.8.13"
23
+ "@stone-js/core": "^0.8.14",
24
+ "@stone-js/use-react": "^0.8.14",
25
+ "@stone-js/browser-adapter": "^0.8.14"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@babel/plugin-proposal-decorators": "^7.25.9",
29
29
  "@babel/preset-env": "^7.26.9",
30
30
  "@babel/preset-typescript": "^7.27.0",
31
- "@stone-js/cli": "^0.8.13",
32
- "@stone-js/testing": "^0.8.13",
31
+ "@stone-js/browser-core": "^0.8.14",
32
+ "@stone-js/cli": "^0.8.14",
33
+ "@stone-js/testing": "^0.8.14",
33
34
  "@types/node": "^24.0.7",
34
35
  "@types/react": "^19.0.7",
35
36
  "@types/react-dom": "^19.0.3",
@@ -1,4 +1,5 @@
1
- import { createTestApp, makeIncomingHttpEvent } from '@stone-js/testing'
1
+ import { createTestApp } from '@stone-js/testing'
2
+ import { makeIncomingBrowserEvent } from '@stone-js/testing/browser'
2
3
 
3
4
  /**
4
5
  * The whole application, booted in memory and asked a real question.
@@ -12,7 +13,7 @@ describe('Pages', () => {
12
13
  it('renders the home page, data included', async () => {
13
14
  const app = await createTestApp()
14
15
 
15
- const response = await app.send(makeIncomingHttpEvent({ method: 'GET', url: '/?name=Ada' }))
16
+ const response = await app.send(makeIncomingBrowserEvent({ url: '/?name=Ada' }))
16
17
 
17
18
  expect(response.statusCode).toBe(200)
18
19
  // A page is a handler, so a rendered page is a response whose body is HTML. Query that HTML with
@@ -23,7 +24,7 @@ describe('Pages', () => {
23
24
  it('renders without a name', async () => {
24
25
  const app = await createTestApp()
25
26
 
26
- const response = await app.send(makeIncomingHttpEvent({ method: 'GET', url: '/' }))
27
+ const response = await app.send(makeIncomingBrowserEvent({ url: '/' }))
27
28
 
28
29
  expect(response.statusCode).toBe(200)
29
30
  })
@@ -106,36 +106,29 @@ the page that owns `/`, with `name` readable through `event.get('name')`. Change
106
106
 
107
107
  `App.tsx` renders `StoneNativeApp`, which shows the screen on top of the stack. That is the floor,
108
108
  not the ceiling: it is what makes the first run work with nothing installed. The platform's
109
- transitions, the swipe-back gesture, and a screen keeping its own state while another covers it are
110
- things only a native navigator gives you.
109
+ transitions, the swipe-back gesture, the hardware back button, and a screen keeping its own state
110
+ while another covers it are things only a native navigator gives you.
111
111
 
112
- The stack is public state, so a navigator drives itself from it:
112
+ Two commands and one import away:
113
113
 
114
- ```tsx
115
- import { useScreens } from '@stone-js/use-react-native'
116
- import { NavigationContainer } from '@react-navigation/native'
117
- import { createNativeStackNavigator } from '@react-navigation/native-stack'
114
+ ```sh
115
+ npx expo install @react-navigation/native @react-navigation/native-stack \
116
+ react-native-screens react-native-safe-area-context
117
+ ```
118
118
 
119
- const Stack = createNativeStackNavigator()
119
+ ```tsx
120
+ // App.tsx
121
+ import { StoneNativeStack } from '@stone-js/use-react-native/navigation'
120
122
 
121
123
  export default function App () {
122
- const screens = useScreens()
123
-
124
- return (
125
- <NavigationContainer>
126
- <Stack.Navigator>
127
- {screens.map((screen) => (
128
- <Stack.Screen key={screen.key} name={screen.key} options={{ title: screen.title }}>
129
- {() => screen.element}
130
- </Stack.Screen>
131
- ))}
132
- </Stack.Navigator>
133
- </NavigationContainer>
134
- )
124
+ return <StoneNativeStack screenOptions={{ headerShown: true }} />
135
125
  }
136
126
  ```
137
127
 
138
- Nothing about your pages changes.
128
+ Nothing about your screens changes. Each becomes a native one, keyed so the navigator keeps its
129
+ state, and titled from the page's `head`. See the
130
+ [renderer's README](https://www.npmjs.com/package/@stone-js/use-react-native) for how the two stacks
131
+ stay in agreement, and for writing your own navigator instead.
139
132
 
140
133
  ## The imperative twin
141
134
 
@@ -21,11 +21,11 @@
21
21
  "test:cvg": "npm run test -- --coverage"
22
22
  },
23
23
  "dependencies": {
24
- "@stone-js/browser-core": "^0.8.13",
25
- "@stone-js/core": "^0.8.13",
26
- "@stone-js/react-native-adapter": "^0.8.13",
27
- "@stone-js/router": "^0.8.13",
28
- "@stone-js/use-react-native": "^0.8.13",
24
+ "@stone-js/browser-core": "^0.8.14",
25
+ "@stone-js/core": "^0.8.14",
26
+ "@stone-js/react-native-adapter": "^0.8.14",
27
+ "@stone-js/router": "^0.8.14",
28
+ "@stone-js/use-react-native": "^0.8.14",
29
29
  "expo": "~57.0.14",
30
30
  "expo-status-bar": "~57.0.1",
31
31
  "fast-text-encoding": "^1.0.6",
@@ -35,8 +35,8 @@
35
35
  },
36
36
  "devDependencies": {
37
37
  "@babel/plugin-proposal-decorators": "^7.28.0",
38
- "@stone-js/cli": "^0.8.13",
39
- "@stone-js/testing": "^0.8.13",
38
+ "@stone-js/cli": "^0.8.14",
39
+ "@stone-js/testing": "^0.8.14",
40
40
  "@types/react": "~19.2.2",
41
41
  "@vitest/coverage-v8": "^3.0.9",
42
42
  "babel-preset-expo": "^57.0.7",
@@ -0,0 +1,36 @@
1
+ import { createTestApp } from '@stone-js/testing'
2
+ import { makeIncomingBrowserEvent } from '@stone-js/testing/browser'
3
+ import { REACT_NATIVE_PLATFORM } from '@stone-js/react-native-adapter'
4
+
5
+ /**
6
+ * The whole application, booted in memory and asked a real question.
7
+ *
8
+ * Nothing is mocked and nothing is listed: `createTestApp()` discovers `app/**`, and the event goes
9
+ * through the same kernel a phone runs. This is the same test the web starters ship, with one
10
+ * difference: the platform is named, and the event is the one a native application receives, which
11
+ * is how a deep link arrives.
12
+ *
13
+ * What it substitutes is the adapter, and that is the architecture rather than a concession to
14
+ * testing: the domain is written once and the context is chosen at run time, so under Node the
15
+ * context is a test adapter instead of React Native.
16
+ */
17
+ describe('HomeScreen', () => {
18
+ it('resolves the home screen, data included', async () => {
19
+ const app = await createTestApp({ platform: REACT_NATIVE_PLATFORM })
20
+
21
+ const response = await app.send(makeIncomingBrowserEvent({ url: 'stone://app/?name=Ada' }))
22
+
23
+ expect(response.statusCode).toBe(200)
24
+ // The head is what a navigator shows in its header, and it proves the loader ran with the deep
25
+ // link's parameter.
26
+ expect((response.content as any).head.title).toBe('Ada · Welcome to Stone.js')
27
+ })
28
+
29
+ it('resolves without a name', async () => {
30
+ const app = await createTestApp({ platform: REACT_NATIVE_PLATFORM })
31
+
32
+ const response = await app.send(makeIncomingBrowserEvent({ url: 'stone://app/' }))
33
+
34
+ expect(response.statusCode).toBe(200)
35
+ })
36
+ })
@@ -5,18 +5,21 @@ import { ScreenStack } from '@stone-js/use-react-native'
5
5
  import { NavigationSource } from '@stone-js/react-native-adapter'
6
6
 
7
7
  /**
8
- * The whole application, booted under Node and asked real questions.
8
+ * Navigation, with the real adapter this time.
9
9
  *
10
- * Nothing is mocked. The kernel, the router, the adapter and the renderer are the ones a phone
11
- * runs; what a device adds is a screen to draw on, and this asserts what the framework resolved
12
- * before drawing. Two things are supplied rather than discovered, both through documented
13
- * configuration: the navigation source, so the test can send a deep link, and the screen stack, so
14
- * it can read what landed on it.
10
+ * `HomeScreen.spec.ts` asks what a route resolves to, which is the same question on every platform
11
+ * and needs no adapter. This asks the native question instead: what lands on the navigation stack,
12
+ * and in what order. So nothing is substituted here. The kernel, the router, the React Native adapter
13
+ * and the renderer are the ones a phone runs; what a device adds is a screen to draw on.
14
+ *
15
+ * Two things are supplied rather than discovered, both through documented configuration: the
16
+ * navigation source, so the test can send a deep link, and the screen stack, so it can read what
17
+ * landed on it.
15
18
  *
16
19
  * Screens are listed here because the generated manifest (`.stone/modules.ts`) is Metro's, written
17
20
  * when Metro starts. Under a test runner the modules are imported directly, which is the same set.
18
21
  */
19
- describe('Screens', () => {
22
+ describe('Navigation', () => {
20
23
  const screens = ScreenStack.create()
21
24
  const navigation = NavigationSource.create({ baseUrl: 'stone://app' })
22
25
 
@@ -22,6 +22,12 @@ export default defineConfig({
22
22
  }
23
23
  },
24
24
  test: {
25
+ // `createTestApp()` discovers the application's modules by importing them at run time. Vitest
26
+ // externalises dependencies from `node_modules`, and an externalised module's `import()` is
27
+ // Node's, which cannot load a `.ts` file. Inlining the package puts those imports back through
28
+ // Vite's transform. `stone test` does this for you; a project running Vitest directly, as an
29
+ // Expo project does, states it here.
30
+ server: { deps: { inline: ['@stone-js/testing'] } },
25
31
  globals: true,
26
32
  environment: 'node',
27
33
  include: ['./tests/**/*.{test,spec}.?(c|m)[jt]s?(x)'],
@@ -106,36 +106,29 @@ the page that owns `/`, with `name` readable through `event.get('name')`. Change
106
106
 
107
107
  `App.tsx` renders `StoneNativeApp`, which shows the screen on top of the stack. That is the floor,
108
108
  not the ceiling: it is what makes the first run work with nothing installed. The platform's
109
- transitions, the swipe-back gesture, and a screen keeping its own state while another covers it are
110
- things only a native navigator gives you.
109
+ transitions, the swipe-back gesture, the hardware back button, and a screen keeping its own state
110
+ while another covers it are things only a native navigator gives you.
111
111
 
112
- The stack is public state, so a navigator drives itself from it:
112
+ Two commands and one import away:
113
113
 
114
- ```tsx
115
- import { useScreens } from '@stone-js/use-react-native'
116
- import { NavigationContainer } from '@react-navigation/native'
117
- import { createNativeStackNavigator } from '@react-navigation/native-stack'
114
+ ```sh
115
+ npx expo install @react-navigation/native @react-navigation/native-stack \
116
+ react-native-screens react-native-safe-area-context
117
+ ```
118
118
 
119
- const Stack = createNativeStackNavigator()
119
+ ```tsx
120
+ // App.tsx
121
+ import { StoneNativeStack } from '@stone-js/use-react-native/navigation'
120
122
 
121
123
  export default function App () {
122
- const screens = useScreens()
123
-
124
- return (
125
- <NavigationContainer>
126
- <Stack.Navigator>
127
- {screens.map((screen) => (
128
- <Stack.Screen key={screen.key} name={screen.key} options={{ title: screen.title }}>
129
- {() => screen.element}
130
- </Stack.Screen>
131
- ))}
132
- </Stack.Navigator>
133
- </NavigationContainer>
134
- )
124
+ return <StoneNativeStack screenOptions={{ headerShown: true }} />
135
125
  }
136
126
  ```
137
127
 
138
- Nothing about your pages changes.
128
+ Nothing about your screens changes. Each becomes a native one, keyed so the navigator keeps its
129
+ state, and titled from the page's `head`. See the
130
+ [renderer's README](https://www.npmjs.com/package/@stone-js/use-react-native) for how the two stacks
131
+ stay in agreement, and for writing your own navigator instead.
139
132
 
140
133
  ## The declarative twin
141
134
 
@@ -21,11 +21,11 @@
21
21
  "test:cvg": "npm run test -- --coverage"
22
22
  },
23
23
  "dependencies": {
24
- "@stone-js/browser-core": "^0.8.13",
25
- "@stone-js/core": "^0.8.13",
26
- "@stone-js/react-native-adapter": "^0.8.13",
27
- "@stone-js/router": "^0.8.13",
28
- "@stone-js/use-react-native": "^0.8.13",
24
+ "@stone-js/browser-core": "^0.8.14",
25
+ "@stone-js/core": "^0.8.14",
26
+ "@stone-js/react-native-adapter": "^0.8.14",
27
+ "@stone-js/router": "^0.8.14",
28
+ "@stone-js/use-react-native": "^0.8.14",
29
29
  "expo": "~57.0.14",
30
30
  "expo-status-bar": "~57.0.1",
31
31
  "fast-text-encoding": "^1.0.6",
@@ -35,8 +35,8 @@
35
35
  },
36
36
  "devDependencies": {
37
37
  "@babel/plugin-proposal-decorators": "^7.28.0",
38
- "@stone-js/cli": "^0.8.13",
39
- "@stone-js/testing": "^0.8.13",
38
+ "@stone-js/cli": "^0.8.14",
39
+ "@stone-js/testing": "^0.8.14",
40
40
  "@types/react": "~19.2.2",
41
41
  "@vitest/coverage-v8": "^3.0.9",
42
42
  "babel-preset-expo": "^57.0.7",
@@ -0,0 +1,36 @@
1
+ import { createTestApp } from '@stone-js/testing'
2
+ import { makeIncomingBrowserEvent } from '@stone-js/testing/browser'
3
+ import { REACT_NATIVE_PLATFORM } from '@stone-js/react-native-adapter'
4
+
5
+ /**
6
+ * The whole application, booted in memory and asked a real question.
7
+ *
8
+ * Nothing is mocked and nothing is listed: `createTestApp()` discovers `app/**`, and the event goes
9
+ * through the same kernel a phone runs. This is the same test the web starters ship, with one
10
+ * difference: the platform is named, and the event is the one a native application receives, which
11
+ * is how a deep link arrives.
12
+ *
13
+ * What it substitutes is the adapter, and that is the architecture rather than a concession to
14
+ * testing: the domain is written once and the context is chosen at run time, so under Node the
15
+ * context is a test adapter instead of React Native.
16
+ */
17
+ describe('HomeScreen', () => {
18
+ it('resolves the home screen, data included', async () => {
19
+ const app = await createTestApp({ platform: REACT_NATIVE_PLATFORM })
20
+
21
+ const response = await app.send(makeIncomingBrowserEvent({ url: 'stone://app/?name=Ada' }))
22
+
23
+ expect(response.statusCode).toBe(200)
24
+ // The head is what a navigator shows in its header, and it proves the loader ran with the deep
25
+ // link's parameter.
26
+ expect((response.content as any).head.title).toBe('Ada · Welcome to Stone.js')
27
+ })
28
+
29
+ it('resolves without a name', async () => {
30
+ const app = await createTestApp({ platform: REACT_NATIVE_PLATFORM })
31
+
32
+ const response = await app.send(makeIncomingBrowserEvent({ url: 'stone://app/' }))
33
+
34
+ expect(response.statusCode).toBe(200)
35
+ })
36
+ })
@@ -5,18 +5,21 @@ import { ScreenStack } from '@stone-js/use-react-native'
5
5
  import { NavigationSource } from '@stone-js/react-native-adapter'
6
6
 
7
7
  /**
8
- * The whole application, booted under Node and asked real questions.
8
+ * Navigation, with the real adapter this time.
9
9
  *
10
- * Nothing is mocked. The kernel, the router, the adapter and the renderer are the ones a phone
11
- * runs; what a device adds is a screen to draw on, and this asserts what the framework resolved
12
- * before drawing. Two things are supplied rather than discovered, both through documented
13
- * configuration: the navigation source, so the test can send a deep link, and the screen stack, so
14
- * it can read what landed on it.
10
+ * `HomeScreen.spec.ts` asks what a route resolves to, which is the same question on every platform
11
+ * and needs no adapter. This asks the native question instead: what lands on the navigation stack,
12
+ * and in what order. So nothing is substituted here. The kernel, the router, the React Native adapter
13
+ * and the renderer are the ones a phone runs; what a device adds is a screen to draw on.
14
+ *
15
+ * Two things are supplied rather than discovered, both through documented configuration: the
16
+ * navigation source, so the test can send a deep link, and the screen stack, so it can read what
17
+ * landed on it.
15
18
  *
16
19
  * Screens are listed here because the generated manifest (`.stone/modules.ts`) is Metro's, written
17
20
  * when Metro starts. Under a test runner the modules are imported directly, which is the same set.
18
21
  */
19
- describe('Screens', () => {
22
+ describe('Navigation', () => {
20
23
  const screens = ScreenStack.create()
21
24
  const navigation = NavigationSource.create({ baseUrl: 'stone://app' })
22
25
 
@@ -22,6 +22,12 @@ export default defineConfig({
22
22
  }
23
23
  },
24
24
  test: {
25
+ // `createTestApp()` discovers the application's modules by importing them at run time. Vitest
26
+ // externalises dependencies from `node_modules`, and an externalised module's `import()` is
27
+ // Node's, which cannot load a `.ts` file. Inlining the package puts those imports back through
28
+ // Vite's transform. `stone test` does this for you; a project running Vitest directly, as an
29
+ // Expo project does, states it here.
30
+ server: { deps: { inline: ['@stone-js/testing'] } },
25
31
  globals: true,
26
32
  environment: 'node',
27
33
  include: ['./tests/**/*.{test,spec}.?(c|m)[jt]s?(x)'],
@@ -18,15 +18,16 @@
18
18
  "test:cvg": "stone test --coverage"
19
19
  },
20
20
  "dependencies": {
21
- "@stone-js/core": "^0.8.13",
22
- "@stone-js/node-http-adapter": "^0.8.13"
21
+ "@stone-js/core": "^0.8.14",
22
+ "@stone-js/node-http-adapter": "^0.8.14"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@babel/plugin-proposal-decorators": "^7.25.9",
26
26
  "@babel/preset-env": "^7.26.9",
27
27
  "@babel/preset-typescript": "^7.27.0",
28
- "@stone-js/cli": "^0.8.13",
29
- "@stone-js/testing": "^0.8.13",
28
+ "@stone-js/cli": "^0.8.14",
29
+ "@stone-js/http-core": "^0.8.14",
30
+ "@stone-js/testing": "^0.8.14",
30
31
  "@types/node": "^24.0.7",
31
32
  "@vitest/coverage-v8": "^3.0.9",
32
33
  "vitest": "^3.0.9"
@@ -1,4 +1,5 @@
1
- import { createTestApp, makeIncomingHttpEvent } from '@stone-js/testing'
1
+ import { createTestApp } from '@stone-js/testing'
2
+ import { makeIncomingHttpEvent } from '@stone-js/testing/http'
2
3
 
3
4
  /**
4
5
  * The whole application, booted in memory and asked a real question.
@@ -18,15 +18,16 @@
18
18
  "test:cvg": "stone test --coverage"
19
19
  },
20
20
  "dependencies": {
21
- "@stone-js/core": "^0.8.13",
22
- "@stone-js/node-http-adapter": "^0.8.13"
21
+ "@stone-js/core": "^0.8.14",
22
+ "@stone-js/node-http-adapter": "^0.8.14"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@babel/plugin-proposal-decorators": "^7.25.9",
26
26
  "@babel/preset-env": "^7.26.9",
27
27
  "@babel/preset-typescript": "^7.27.0",
28
- "@stone-js/cli": "^0.8.13",
29
- "@stone-js/testing": "^0.8.13",
28
+ "@stone-js/cli": "^0.8.14",
29
+ "@stone-js/http-core": "^0.8.14",
30
+ "@stone-js/testing": "^0.8.14",
30
31
  "@types/node": "^24.0.7",
31
32
  "@vitest/coverage-v8": "^3.0.9",
32
33
  "vitest": "^3.0.9"
@@ -1,4 +1,5 @@
1
- import { createTestApp, makeIncomingHttpEvent } from '@stone-js/testing'
1
+ import { createTestApp } from '@stone-js/testing'
2
+ import { makeIncomingHttpEvent } from '@stone-js/testing/http'
2
3
 
3
4
  /**
4
5
  * The whole application, booted in memory and asked a real question.
@@ -15,20 +15,20 @@
15
15
  "dependencies": {
16
16
  "react": "^19.0.0",
17
17
  "react-dom": "^19.0.0",
18
- "@stone-js/core": "^0.8.13",
19
- "@stone-js/router": "^0.8.13",
20
- "@stone-js/use-react": "^0.8.13",
21
- "@stone-js/http-core": "^0.8.13",
22
- "@stone-js/pipeline": "^0.8.13",
23
- "@stone-js/browser-adapter": "^0.8.13",
24
- "@stone-js/node-http-adapter": "^0.8.13"
18
+ "@stone-js/core": "^0.8.14",
19
+ "@stone-js/router": "^0.8.14",
20
+ "@stone-js/use-react": "^0.8.14",
21
+ "@stone-js/http-core": "^0.8.14",
22
+ "@stone-js/pipeline": "^0.8.14",
23
+ "@stone-js/browser-adapter": "^0.8.14",
24
+ "@stone-js/node-http-adapter": "^0.8.14"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@babel/plugin-proposal-decorators": "^7.25.9",
28
28
  "@babel/preset-env": "^7.26.9",
29
29
  "@babel/preset-typescript": "^7.27.0",
30
- "@stone-js/cli": "^0.8.13",
31
- "@stone-js/testing": "^0.8.13",
30
+ "@stone-js/cli": "^0.8.14",
31
+ "@stone-js/testing": "^0.8.14",
32
32
  "@types/node": "^24.0.7",
33
33
  "@types/react": "^19.0.7",
34
34
  "@types/react-dom": "^19.0.3",
@@ -1,4 +1,5 @@
1
- import { createTestApp, makeIncomingHttpEvent } from '@stone-js/testing'
1
+ import { createTestApp } from '@stone-js/testing'
2
+ import { makeIncomingHttpEvent } from '@stone-js/testing/http'
2
3
 
3
4
  /**
4
5
  * The whole application, booted in memory and asked a real question.
@@ -18,14 +18,14 @@
18
18
  "test:cvg": "stone test --coverage"
19
19
  },
20
20
  "dependencies": {
21
- "@stone-js/env": "^0.8.13",
22
- "@stone-js/http-core": "^0.8.13",
23
- "@stone-js/node-http-adapter": "^0.8.13",
24
- "@stone-js/router": "^0.8.13",
25
- "@stone-js/filesystem": "^0.8.13",
26
- "@stone-js/core": "^0.8.13",
27
- "@stone-js/use-react": "^0.8.13",
28
- "@stone-js/browser-adapter": "^0.8.13",
21
+ "@stone-js/env": "^0.8.14",
22
+ "@stone-js/http-core": "^0.8.14",
23
+ "@stone-js/node-http-adapter": "^0.8.14",
24
+ "@stone-js/router": "^0.8.14",
25
+ "@stone-js/filesystem": "^0.8.14",
26
+ "@stone-js/core": "^0.8.14",
27
+ "@stone-js/use-react": "^0.8.14",
28
+ "@stone-js/browser-adapter": "^0.8.14",
29
29
  "axios": "^1.7.9",
30
30
  "react": "^19.0.0",
31
31
  "react-dom": "^19.0.0"
@@ -34,8 +34,8 @@
34
34
  "@babel/plugin-proposal-decorators": "^7.25.9",
35
35
  "@babel/preset-env": "^7.26.9",
36
36
  "@babel/preset-typescript": "^7.27.0",
37
- "@stone-js/cli": "^0.8.13",
38
- "@stone-js/testing": "^0.8.13",
37
+ "@stone-js/cli": "^0.8.14",
38
+ "@stone-js/testing": "^0.8.14",
39
39
  "@types/axios": "^0.14.4",
40
40
  "@types/node": "^24.0.7",
41
41
  "@types/react": "^19.0.7",
@@ -1,4 +1,5 @@
1
- import { createTestApp, makeIncomingHttpEvent } from '@stone-js/testing'
1
+ import { createTestApp } from '@stone-js/testing'
2
+ import { makeIncomingHttpEvent } from '@stone-js/testing/http'
2
3
 
3
4
  /**
4
5
  * The whole application, booted in memory and asked a real question.
@@ -18,14 +18,14 @@
18
18
  "test:cvg": "stone test --coverage"
19
19
  },
20
20
  "dependencies": {
21
- "@stone-js/env": "^0.8.13",
22
- "@stone-js/http-core": "^0.8.13",
23
- "@stone-js/node-http-adapter": "^0.8.13",
24
- "@stone-js/router": "^0.8.13",
25
- "@stone-js/filesystem": "^0.8.13",
26
- "@stone-js/core": "^0.8.13",
27
- "@stone-js/use-react": "^0.8.13",
28
- "@stone-js/browser-adapter": "^0.8.13",
21
+ "@stone-js/env": "^0.8.14",
22
+ "@stone-js/http-core": "^0.8.14",
23
+ "@stone-js/node-http-adapter": "^0.8.14",
24
+ "@stone-js/router": "^0.8.14",
25
+ "@stone-js/filesystem": "^0.8.14",
26
+ "@stone-js/core": "^0.8.14",
27
+ "@stone-js/use-react": "^0.8.14",
28
+ "@stone-js/browser-adapter": "^0.8.14",
29
29
  "axios": "^1.7.9",
30
30
  "dayjs": "^1.11.13",
31
31
  "react": "^19.0.0",
@@ -35,8 +35,8 @@
35
35
  "@babel/plugin-proposal-decorators": "^7.25.9",
36
36
  "@babel/preset-env": "^7.26.9",
37
37
  "@babel/preset-typescript": "^7.27.0",
38
- "@stone-js/cli": "^0.8.13",
39
- "@stone-js/testing": "^0.8.13",
38
+ "@stone-js/cli": "^0.8.14",
39
+ "@stone-js/testing": "^0.8.14",
40
40
  "@types/axios": "^0.14.4",
41
41
  "@types/node": "^24.0.7",
42
42
  "@types/react": "^19.0.7",
@@ -1,4 +1,5 @@
1
- import { createTestApp, makeIncomingHttpEvent } from '@stone-js/testing'
1
+ import { createTestApp } from '@stone-js/testing'
2
+ import { makeIncomingHttpEvent } from '@stone-js/testing/http'
2
3
 
3
4
  /**
4
5
  * The whole application, booted in memory and asked a real question.