@stone-js/starters 0.8.8 → 0.8.10
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 +2 -2
- package/basic-react-declarative/app/Application.tsx +0 -2
- package/basic-react-declarative/package.json +7 -7
- package/basic-react-declarative/public/favicon.svg +16 -0
- package/basic-react-declarative/tests/Application.spec.ts +25 -100
- package/basic-react-imperative/app/Application.tsx +2 -17
- package/basic-react-imperative/package.json +7 -7
- package/basic-react-imperative/public/favicon.svg +16 -0
- package/basic-react-imperative/tests/Application.spec.ts +25 -86
- package/basic-react-native-declarative/App.tsx +192 -0
- package/basic-react-native-declarative/LICENSE +21 -0
- package/basic-react-native-declarative/README.md +35 -0
- package/basic-react-native-declarative/adapter/NativeAdapter.ts +108 -0
- package/basic-react-native-declarative/adapter/NativeErrorHandler.ts +53 -0
- package/basic-react-native-declarative/adapter/NativeEventSource.ts +58 -0
- package/basic-react-native-declarative/adapter/RawResponseWrapper.ts +30 -0
- package/basic-react-native-declarative/adapter/declarations.ts +40 -0
- package/basic-react-native-declarative/adapter/middleware.ts +73 -0
- package/basic-react-native-declarative/adapter/nativeAdapterBlueprint.ts +51 -0
- package/basic-react-native-declarative/adapter/renderSink.ts +52 -0
- package/basic-react-native-declarative/app/Application.ts +17 -0
- package/basic-react-native-declarative/app/WelcomeController.ts +66 -0
- package/basic-react-native-declarative/app.json +25 -0
- package/basic-react-native-declarative/assets/android-icon-background.png +0 -0
- package/basic-react-native-declarative/assets/android-icon-foreground.png +0 -0
- package/basic-react-native-declarative/assets/android-icon-monochrome.png +0 -0
- package/basic-react-native-declarative/assets/favicon.png +0 -0
- package/basic-react-native-declarative/assets/icon.png +0 -0
- package/basic-react-native-declarative/assets/splash-icon.png +0 -0
- package/basic-react-native-declarative/babel.config.js +16 -0
- package/basic-react-native-declarative/index.ts +22 -0
- package/basic-react-native-declarative/metro.config.js +7 -0
- package/basic-react-native-declarative/package.json +41 -0
- package/basic-react-native-declarative/tests/Application.spec.ts +65 -0
- package/basic-react-native-declarative/tsconfig.json +16 -0
- package/basic-react-native-declarative/vitest.config.ts +27 -0
- package/basic-service-declarative/app/Application.ts +0 -2
- package/basic-service-declarative/package.json +6 -6
- package/basic-service-declarative/tests/Application.spec.ts +20 -53
- package/basic-service-imperative/app/Application.ts +2 -14
- package/basic-service-imperative/package.json +6 -6
- package/basic-service-imperative/tests/Application.spec.ts +19 -44
- package/continuum-showcase/package.json +11 -10
- package/continuum-showcase/public/favicon.svg +16 -0
- package/continuum-showcase/stone.config.mjs +2 -2
- package/continuum-showcase/tests/application.spec.ts +30 -0
- package/full-react-declarative/app/Application.ts +0 -4
- package/full-react-declarative/package.json +12 -12
- package/full-react-declarative/public/favicon.svg +16 -0
- package/full-react-declarative/stone.config.mjs +2 -2
- package/full-react-declarative/tests/application.spec.ts +30 -0
- package/full-react-imperative/app/Application.ts +1 -3
- package/full-react-imperative/package.json +12 -12
- package/full-react-imperative/public/favicon.svg +16 -0
- package/full-react-imperative/stone.config.mjs +2 -2
- package/full-react-imperative/tests/application.spec.ts +30 -0
- package/full-service-declarative/app/Application.ts +5 -2
- package/full-service-declarative/app/configurations/AppConfiguration.ts +1 -3
- package/full-service-declarative/package.json +13 -12
- package/full-service-declarative/stone.config.mjs +2 -2
- package/full-service-declarative/tests/application.spec.ts +30 -0
- package/full-service-imperative/app/Application.ts +8 -2
- package/full-service-imperative/app/configurations/AppConfiguration.ts +1 -3
- package/full-service-imperative/package.json +13 -12
- package/full-service-imperative/stone.config.mjs +2 -2
- package/full-service-imperative/tests/application.spec.ts +30 -0
- package/package.json +15 -1
- package/standard-react-declarative/package.json +11 -10
- package/standard-react-declarative/public/favicon.svg +16 -0
- package/standard-react-declarative/tests/application.spec.ts +30 -0
- package/standard-react-imperative/package.json +11 -10
- package/standard-react-imperative/public/favicon.svg +16 -0
- package/standard-react-imperative/tests/application.spec.ts +30 -0
- package/standard-service-declarative/package.json +9 -8
- package/standard-service-declarative/tests/Application.spec.ts +22 -94
- package/standard-service-imperative/package.json +9 -8
- package/standard-service-imperative/tests/Application.spec.ts +22 -78
- package/basic-react-declarative/vitest.config.ts +0 -24
- package/basic-react-imperative/vitest.config.ts +0 -24
- package/basic-service-declarative/vitest.config.ts +0 -24
- package/basic-service-imperative/vitest.config.ts +0 -24
- package/continuum-showcase/tests/HomePage.spec.tsx +0 -41
- package/continuum-showcase/vitest.config.ts +0 -31
- package/full-react-declarative/tests/AppLayout.spec.ts +0 -51
- package/full-react-declarative/tests/HomePage.spec.ts +0 -38
- package/full-react-declarative/vitest.config.ts +0 -24
- package/full-react-imperative/tests/HomePage.spec.ts +0 -45
- package/full-react-imperative/tests/LayoutHeader.spec.ts +0 -39
- package/full-react-imperative/vitest.config.ts +0 -24
- package/full-service-declarative/tests/PostService.spec.ts +0 -56
- package/full-service-declarative/tests/WelcomeEventHandler.spec.ts +0 -54
- package/full-service-declarative/vitest.config.ts +0 -24
- package/full-service-imperative/tests/PostService.spec.ts +0 -47
- package/full-service-imperative/tests/WelcomeEventHandler.spec.ts +0 -31
- package/full-service-imperative/vitest.config.ts +0 -24
- package/standard-react-declarative/tests/WelcomePage.spec.ts +0 -71
- package/standard-react-declarative/vitest.config.ts +0 -24
- package/standard-react-imperative/tests/WelcomePage.spec.ts +0 -53
- package/standard-react-imperative/vitest.config.ts +0 -24
- package/standard-service-declarative/vitest.config.ts +0 -24
- package/standard-service-imperative/vitest.config.ts +0 -24
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "basic-react-native-declarative",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "Stone.js's basic starter to create a React Native (Expo) app using the declarative API.",
|
|
6
|
+
"author": "Mr. Stone <evensstone@gmail.com>",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"main": "index.ts",
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=20.0.0"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"start": "expo start",
|
|
14
|
+
"android": "expo start --android",
|
|
15
|
+
"ios": "expo start --ios",
|
|
16
|
+
"web": "expo start --web",
|
|
17
|
+
"export": "expo export",
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"test": "vitest run",
|
|
20
|
+
"test:cvg": "npm run test -- --coverage"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@stone-js/browser-core": "^0.8.10",
|
|
24
|
+
"@stone-js/core": "^0.8.10",
|
|
25
|
+
"@stone-js/router": "^0.8.10",
|
|
26
|
+
"expo": "~57.0.14",
|
|
27
|
+
"expo-status-bar": "~57.0.1",
|
|
28
|
+
"fast-text-encoding": "^1.0.6",
|
|
29
|
+
"react": "19.2.3",
|
|
30
|
+
"react-native": "0.86.2",
|
|
31
|
+
"react-native-url-polyfill": "^3.0.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@babel/plugin-proposal-decorators": "^7.28.0",
|
|
35
|
+
"@types/react": "~19.2.2",
|
|
36
|
+
"@vitest/coverage-v8": "^3.0.9",
|
|
37
|
+
"babel-preset-expo": "^57.0.7",
|
|
38
|
+
"typescript": "~6.0.3",
|
|
39
|
+
"vitest": "^3.0.9"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { stoneApp } from '@stone-js/core'
|
|
2
|
+
import { describe, it, expect, beforeAll } from 'vitest'
|
|
3
|
+
import { Application } from '../app/Application'
|
|
4
|
+
import { OutgoingBrowserResponse } from '@stone-js/browser-core'
|
|
5
|
+
import { nativeEventSource } from '../adapter/NativeEventSource'
|
|
6
|
+
import { WelcomeData, WelcomeController } from '../app/WelcomeController'
|
|
7
|
+
import { onNativeError, onNativeRender } from '../adapter/renderSink'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Behavioral test of the full continuum chain: the exact modules the native
|
|
11
|
+
* application boots (domain + router + native adapter) are booted here under
|
|
12
|
+
* Node, navigation intents are emitted, and the rendered payloads asserted.
|
|
13
|
+
*
|
|
14
|
+
* The adapter is pure JavaScript, so what passes here is what runs on device;
|
|
15
|
+
* only the platform checks (URL polyfill, Hermes) need the real application.
|
|
16
|
+
*
|
|
17
|
+
* The application boots ONCE for the whole suite, like on a device: default
|
|
18
|
+
* blueprints are shared module-level objects, so booting the same modules
|
|
19
|
+
* twice in one process would accumulate route definitions.
|
|
20
|
+
*/
|
|
21
|
+
describe('Stone.js native proof of concept', () => {
|
|
22
|
+
const rendered: OutgoingBrowserResponse[] = []
|
|
23
|
+
const errors: Error[] = []
|
|
24
|
+
|
|
25
|
+
beforeAll(async () => {
|
|
26
|
+
onNativeRender((response) => rendered.push(response))
|
|
27
|
+
onNativeError((error) => errors.push(error))
|
|
28
|
+
await stoneApp({ modules: [Application, WelcomeController] }).run()
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('boots and renders the landing route on startup', () => {
|
|
32
|
+
expect(errors).toHaveLength(0)
|
|
33
|
+
expect(rendered).toHaveLength(1)
|
|
34
|
+
|
|
35
|
+
const payload = rendered[0].content as WelcomeData
|
|
36
|
+
expect(payload.route).toBe('/')
|
|
37
|
+
expect(payload.message).toContain('Welcome to Stone.js on React Native!')
|
|
38
|
+
expect(payload.framework.name).toBe('Stone.js')
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('routes a navigation intent with a parameter to the same domain', async () => {
|
|
42
|
+
nativeEventSource.navigate('stone://app/hello/Noowow')
|
|
43
|
+
await new Promise((resolve) => setImmediate(resolve))
|
|
44
|
+
|
|
45
|
+
const payload = rendered.at(-1)?.content as WelcomeData
|
|
46
|
+
expect(payload.route).toBe('/hello/Noowow')
|
|
47
|
+
expect(payload.message).toContain('Hello Noowow!')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('decodes encoded route parameters', async () => {
|
|
51
|
+
nativeEventSource.navigate('stone://app/hello/Mr.%20Stone')
|
|
52
|
+
await new Promise((resolve) => setImmediate(resolve))
|
|
53
|
+
|
|
54
|
+
const payload = rendered.at(-1)?.content as WelcomeData
|
|
55
|
+
expect(payload.message).toContain('Hello Mr. Stone!')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('surfaces unknown routes through the error chain instead of crashing', async () => {
|
|
59
|
+
nativeEventSource.navigate('stone://app/nowhere')
|
|
60
|
+
await new Promise((resolve) => setImmediate(resolve))
|
|
61
|
+
|
|
62
|
+
const lastPayload = rendered.at(-1)?.content as WelcomeData | undefined
|
|
63
|
+
expect(lastPayload?.route === '/nowhere').toBe(false)
|
|
64
|
+
})
|
|
65
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "expo/tsconfig.base",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"strict": true,
|
|
5
|
+
"experimentalDecorators": true,
|
|
6
|
+
"types": ["vitest/globals"]
|
|
7
|
+
},
|
|
8
|
+
"include": [
|
|
9
|
+
"app",
|
|
10
|
+
"adapter",
|
|
11
|
+
"tests",
|
|
12
|
+
"App.tsx",
|
|
13
|
+
"index.ts",
|
|
14
|
+
"vitest.config.ts"
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config'
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
// `experimentalDecorators` is enabled in tsconfig.json for tsc TYPING only
|
|
5
|
+
// (the framework types its decorators with the legacy signatures). At
|
|
6
|
+
// RUNTIME Stone.js requires standard 2023-11 decorators, so esbuild must
|
|
7
|
+
// ignore that flag and emit the standard semantics, `Symbol.metadata`
|
|
8
|
+
// included (esbuild 0.25+ implements them correctly on its own).
|
|
9
|
+
esbuild: {
|
|
10
|
+
tsconfigRaw: {
|
|
11
|
+
compilerOptions: {
|
|
12
|
+
experimentalDecorators: false
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
test: {
|
|
17
|
+
globals: true,
|
|
18
|
+
environment: 'node',
|
|
19
|
+
include: ['./tests/**/*.{test,spec}.?(c|m)[jt]s?(x)'],
|
|
20
|
+
coverage: {
|
|
21
|
+
provider: 'v8',
|
|
22
|
+
include: ['app/**/*.ts', 'adapter/**/*.ts'],
|
|
23
|
+
reporter: ['text', 'html'],
|
|
24
|
+
reportsDirectory: './coverage'
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
})
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { NodeHttp } from '@stone-js/node-http-adapter'
|
|
2
|
-
import { NodeConsole } from '@stone-js/node-cli-adapter'
|
|
3
2
|
import { IncomingEvent, IEventHandler, ILogger, LogLevel, StoneApp } from '@stone-js/core'
|
|
4
3
|
|
|
5
4
|
/**
|
|
@@ -10,7 +9,6 @@ import { IncomingEvent, IEventHandler, ILogger, LogLevel, StoneApp } from '@ston
|
|
|
10
9
|
* @NodeHttp() is used to enable the Node HTTP adapter.
|
|
11
10
|
* @StoneApp() is used to enable the Stone application, it is required.
|
|
12
11
|
*/
|
|
13
|
-
@NodeConsole()
|
|
14
12
|
@NodeHttp({ default: true })
|
|
15
13
|
@StoneApp({ logger: { level: LogLevel.INFO } })
|
|
16
14
|
export class Application implements IEventHandler<IncomingEvent> {
|
|
@@ -14,19 +14,19 @@
|
|
|
14
14
|
"build": "stone build",
|
|
15
15
|
"preview": "stone preview dist/server.mjs",
|
|
16
16
|
"typings": "stone typings",
|
|
17
|
-
"test": "
|
|
18
|
-
"test:cvg": "
|
|
17
|
+
"test": "stone test",
|
|
18
|
+
"test:cvg": "stone test --coverage"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@stone-js/core": "^0.8.
|
|
22
|
-
"@stone-js/node-
|
|
23
|
-
"@stone-js/node-http-adapter": "^0.8.8"
|
|
21
|
+
"@stone-js/core": "^0.8.10",
|
|
22
|
+
"@stone-js/node-http-adapter": "^0.8.10"
|
|
24
23
|
},
|
|
25
24
|
"devDependencies": {
|
|
26
25
|
"@babel/plugin-proposal-decorators": "^7.25.9",
|
|
27
26
|
"@babel/preset-env": "^7.26.9",
|
|
28
27
|
"@babel/preset-typescript": "^7.27.0",
|
|
29
|
-
"@stone-js/cli": "^0.8.
|
|
28
|
+
"@stone-js/cli": "^0.8.10",
|
|
29
|
+
"@stone-js/testing": "^0.8.10",
|
|
30
30
|
"@types/node": "^24.0.7",
|
|
31
31
|
"@vitest/coverage-v8": "^3.0.9",
|
|
32
32
|
"vitest": "^3.0.9"
|
|
@@ -1,61 +1,28 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
// We must mock decorators to lighten the test environment
|
|
14
|
-
vi.mock(import("@stone-js/node-cli-adapter"), async (importOriginal) => {
|
|
15
|
-
const actual = await importOriginal()
|
|
16
|
-
return {
|
|
17
|
-
...actual,
|
|
18
|
-
NodeConsole: vi.fn(() => vi.fn()),
|
|
19
|
-
}
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
// We must mock decorators to lighten the test environment
|
|
23
|
-
vi.mock(import("@stone-js/node-http-adapter"), async (importOriginal) => {
|
|
24
|
-
const actual = await importOriginal()
|
|
25
|
-
return {
|
|
26
|
-
...actual,
|
|
27
|
-
NodeHttp: vi.fn(() => vi.fn()),
|
|
28
|
-
}
|
|
29
|
-
})
|
|
30
|
-
|
|
1
|
+
import { createTestApp, makeIncomingHttpEvent } from '@stone-js/testing'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The whole application, booted in memory and asked a real question.
|
|
5
|
+
*
|
|
6
|
+
* Nothing is mocked and nothing is listed: `createTestApp()` discovers `app/**`, and the event goes
|
|
7
|
+
* through the same kernel production runs. If this passes the application works; if it fails the
|
|
8
|
+
* application is broken. That is the only kind of test worth shipping in a starter — the previous one
|
|
9
|
+
* stubbed out the framework's own decorators, which meant it could pass while nothing worked.
|
|
10
|
+
*/
|
|
31
11
|
describe('Application', () => {
|
|
32
|
-
|
|
33
|
-
|
|
12
|
+
it('answers the caller', async () => {
|
|
13
|
+
const app = await createTestApp()
|
|
34
14
|
|
|
35
|
-
|
|
36
|
-
mockedLogger = {
|
|
37
|
-
info: vi.fn(),
|
|
38
|
-
} as unknown as ILogger
|
|
15
|
+
const response = await app.send(makeIncomingHttpEvent({ method: 'GET', url: '/?name=Ada' }))
|
|
39
16
|
|
|
40
|
-
|
|
17
|
+
expect(response.statusCode).toBe(200)
|
|
18
|
+
expect(response.json()).toMatchObject({ message: expect.stringContaining('Ada') })
|
|
41
19
|
})
|
|
42
20
|
|
|
43
|
-
it('
|
|
44
|
-
|
|
45
|
-
expect(app).toBeInstanceOf(Application)
|
|
46
|
-
})
|
|
21
|
+
it('answers when nobody says who they are', async () => {
|
|
22
|
+
const app = await createTestApp()
|
|
47
23
|
|
|
48
|
-
|
|
49
|
-
// Arrange
|
|
50
|
-
const expectedMessage = 'Hello World! Welcome to Stone.js.'
|
|
51
|
-
const event = { get: () => 'World' } as unknown as IncomingEvent
|
|
24
|
+
const response = await app.send(makeIncomingHttpEvent({ method: 'GET', url: '/' }))
|
|
52
25
|
|
|
53
|
-
|
|
54
|
-
const response = app.handle(event)
|
|
55
|
-
|
|
56
|
-
// Assert
|
|
57
|
-
expect(response.message).toBe(expectedMessage)
|
|
58
|
-
expect(response.framework.name).toBe('Stone.js')
|
|
59
|
-
expect(mockedLogger.info).toHaveBeenCalledWith(expectedMessage)
|
|
26
|
+
expect(response.statusCode).toBe(200)
|
|
60
27
|
})
|
|
61
|
-
})
|
|
28
|
+
})
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { NODE_HTTP_PLATFORM, nodeHttpAdapterBlueprint } from '@stone-js/node-http-adapter'
|
|
2
|
-
import {
|
|
3
|
-
import { defineStoneApp, defineConfig, IBlueprint, ILogger, IncomingEvent, FunctionalEventHandler } from '@stone-js/core'
|
|
2
|
+
import { defineStoneApp, ILogger, IncomingEvent, FunctionalEventHandler } from '@stone-js/core'
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* Create an handler using the factory handler.
|
|
@@ -40,20 +39,9 @@ export const Application = defineStoneApp(
|
|
|
40
39
|
isFactory: true,
|
|
41
40
|
adapter: { platform: NODE_HTTP_PLATFORM }
|
|
42
41
|
},
|
|
43
|
-
[nodeHttpAdapterBlueprint
|
|
42
|
+
[nodeHttpAdapterBlueprint]
|
|
44
43
|
)
|
|
45
44
|
|
|
46
|
-
/**
|
|
47
|
-
* Application configuration.
|
|
48
|
-
*/
|
|
49
|
-
export const AppConfig = defineConfig({
|
|
50
|
-
afterConfigure (blueprint: IBlueprint) {
|
|
51
|
-
if (blueprint.is('stone.adapter.platform', NODE_CONSOLE_PLATFORM)) {
|
|
52
|
-
blueprint.set(defineCommand(FactoryHandler, { name: '*', isFactory: true }))
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
})
|
|
56
|
-
|
|
57
45
|
/**
|
|
58
46
|
* Application options.
|
|
59
47
|
*/
|
|
@@ -14,19 +14,19 @@
|
|
|
14
14
|
"build": "stone build",
|
|
15
15
|
"preview": "stone preview dist/server.mjs",
|
|
16
16
|
"typings": "stone typings",
|
|
17
|
-
"test": "
|
|
18
|
-
"test:cvg": "
|
|
17
|
+
"test": "stone test",
|
|
18
|
+
"test:cvg": "stone test --coverage"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@stone-js/core": "^0.8.
|
|
22
|
-
"@stone-js/node-
|
|
23
|
-
"@stone-js/node-http-adapter": "^0.8.8"
|
|
21
|
+
"@stone-js/core": "^0.8.10",
|
|
22
|
+
"@stone-js/node-http-adapter": "^0.8.10"
|
|
24
23
|
},
|
|
25
24
|
"devDependencies": {
|
|
26
25
|
"@babel/plugin-proposal-decorators": "^7.25.9",
|
|
27
26
|
"@babel/preset-env": "^7.26.9",
|
|
28
27
|
"@babel/preset-typescript": "^7.27.0",
|
|
29
|
-
"@stone-js/cli": "^0.8.
|
|
28
|
+
"@stone-js/cli": "^0.8.10",
|
|
29
|
+
"@stone-js/testing": "^0.8.10",
|
|
30
30
|
"@types/node": "^24.0.7",
|
|
31
31
|
"@vitest/coverage-v8": "^3.0.9",
|
|
32
32
|
"vitest": "^3.0.9"
|
|
@@ -1,53 +1,28 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
})
|
|
13
|
-
|
|
1
|
+
import { createTestApp, makeIncomingHttpEvent } from '@stone-js/testing'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The whole application, booted in memory and asked a real question.
|
|
5
|
+
*
|
|
6
|
+
* Nothing is mocked and nothing is listed: `createTestApp()` discovers `app/**`, and the event goes
|
|
7
|
+
* through the same kernel production runs. If this passes the application works; if it fails the
|
|
8
|
+
* application is broken. That is the only kind of test worth shipping in a starter — the previous one
|
|
9
|
+
* stubbed out the framework's own decorators, which meant it could pass while nothing worked.
|
|
10
|
+
*/
|
|
14
11
|
describe('Application', () => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
mockedLogger = {
|
|
20
|
-
info: vi.fn(),
|
|
21
|
-
} as unknown as ILogger
|
|
12
|
+
it('answers the caller', async () => {
|
|
13
|
+
const app = await createTestApp()
|
|
22
14
|
|
|
23
|
-
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
it('should create an event handler instance', () => {
|
|
27
|
-
// Assert
|
|
28
|
-
expect(app).toBeTypeOf('function')
|
|
29
|
-
})
|
|
15
|
+
const response = await app.send(makeIncomingHttpEvent({ method: 'GET', url: '/?name=Ada' }))
|
|
30
16
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const event = { get: () => 'World' } as unknown as IncomingHttpEvent
|
|
34
|
-
// Act
|
|
35
|
-
const response = await app(event)
|
|
36
|
-
// Assert
|
|
37
|
-
expect(response.message).toBe('Hello World! Welcome to Stone.js.')
|
|
38
|
-
expect(response.framework.name).toBe('Stone.js')
|
|
39
|
-
expect(mockedLogger.info).toHaveBeenCalledWith('Hello World! Welcome to Stone.js.')
|
|
17
|
+
expect(response.statusCode).toBe(200)
|
|
18
|
+
expect(response.json()).toMatchObject({ message: expect.stringContaining('Ada') })
|
|
40
19
|
})
|
|
41
20
|
|
|
42
|
-
it('
|
|
43
|
-
|
|
44
|
-
const config: any = AppConfig
|
|
45
|
-
const blueprint: any = { is: () => true, set: vi.fn() }
|
|
21
|
+
it('answers when nobody says who they are', async () => {
|
|
22
|
+
const app = await createTestApp()
|
|
46
23
|
|
|
47
|
-
|
|
48
|
-
config.afterConfigure(blueprint)
|
|
24
|
+
const response = await app.send(makeIncomingHttpEvent({ method: 'GET', url: '/' }))
|
|
49
25
|
|
|
50
|
-
|
|
51
|
-
expect(blueprint.set).toHaveBeenCalled()
|
|
26
|
+
expect(response.statusCode).toBe(200)
|
|
52
27
|
})
|
|
53
28
|
})
|
|
@@ -9,25 +9,26 @@
|
|
|
9
9
|
"build": "stone build",
|
|
10
10
|
"build:ssr": "stone build --ssr",
|
|
11
11
|
"preview": "stone preview",
|
|
12
|
-
"test": "
|
|
13
|
-
"test:cvg": "
|
|
12
|
+
"test": "stone test",
|
|
13
|
+
"test:cvg": "stone test --coverage"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"react": "^19.0.0",
|
|
17
17
|
"react-dom": "^19.0.0",
|
|
18
|
-
"@stone-js/core": "^0.8.
|
|
19
|
-
"@stone-js/router": "^0.8.
|
|
20
|
-
"@stone-js/use-react": "^0.8.
|
|
21
|
-
"@stone-js/http-core": "^0.8.
|
|
22
|
-
"@stone-js/pipeline": "^0.8.
|
|
23
|
-
"@stone-js/browser-adapter": "^0.8.
|
|
24
|
-
"@stone-js/node-http-adapter": "^0.8.
|
|
18
|
+
"@stone-js/core": "^0.8.10",
|
|
19
|
+
"@stone-js/router": "^0.8.10",
|
|
20
|
+
"@stone-js/use-react": "^0.8.10",
|
|
21
|
+
"@stone-js/http-core": "^0.8.10",
|
|
22
|
+
"@stone-js/pipeline": "^0.8.10",
|
|
23
|
+
"@stone-js/browser-adapter": "^0.8.10",
|
|
24
|
+
"@stone-js/node-http-adapter": "^0.8.10"
|
|
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.
|
|
30
|
+
"@stone-js/cli": "^0.8.10",
|
|
31
|
+
"@stone-js/testing": "^0.8.10",
|
|
31
32
|
"@types/node": "^24.0.7",
|
|
32
33
|
"@types/react": "^19.0.7",
|
|
33
34
|
"@types/react-dom": "^19.0.3",
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96" width="96" height="96" role="img" aria-label="Stone.js">
|
|
2
|
+
<title>Stone.js</title>
|
|
3
|
+
<style>
|
|
4
|
+
.ink { stroke: #1B1613; }
|
|
5
|
+
@media (prefers-color-scheme: dark) { .ink { stroke: #EFE8DC; } }
|
|
6
|
+
</style>
|
|
7
|
+
<defs>
|
|
8
|
+
<linearGradient id="braise" gradientUnits="userSpaceOnUse" x1="22" y1="14" x2="74" y2="26.1">
|
|
9
|
+
<stop offset="0" stop-color="#FFC46B" />
|
|
10
|
+
<stop offset="1" stop-color="#FF5A1F" />
|
|
11
|
+
</linearGradient>
|
|
12
|
+
</defs>
|
|
13
|
+
<path class="ink" d="M 79.96 36.4 A 34 34 0 0 1 53.9 81.5" fill="none" stroke-width="11" stroke-linecap="round" opacity=".85" />
|
|
14
|
+
<path class="ink" d="M 42.1 81.5 A 34 34 0 0 1 16.05 36.4" fill="none" stroke-width="11" stroke-linecap="round" opacity=".85" />
|
|
15
|
+
<path d="M 22 26.1 A 34 34 0 0 1 74 26.1" fill="none" stroke="url(#braise)" stroke-width="11" stroke-linecap="round" />
|
|
16
|
+
</svg>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineBuilderConfig } from '@stone-js/cli'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Build configuration for the showcase.
|
|
@@ -6,7 +6,7 @@ import { defineConfig } from '@stone-js/cli'
|
|
|
6
6
|
* - `target: 'react'` + `rendering: 'ssr'` exercises the server-rendered head pipeline.
|
|
7
7
|
* - `assets` enables the `@img` / `@css` / `@assets` import aliases used in components.
|
|
8
8
|
*/
|
|
9
|
-
export default
|
|
9
|
+
export default defineBuilderConfig({
|
|
10
10
|
target: 'react',
|
|
11
11
|
rendering: 'ssr',
|
|
12
12
|
assets: {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createTestApp, makeIncomingHttpEvent } from '@stone-js/testing'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The whole application, booted in memory and asked a real question.
|
|
5
|
+
*
|
|
6
|
+
* Nothing is mocked and nothing is listed: `createTestApp()` discovers `app/**`, and the event goes
|
|
7
|
+
* through the same kernel production runs. If this passes the application works; if it fails the
|
|
8
|
+
* application is broken. That is the only kind of test worth shipping in a starter — the previous one
|
|
9
|
+
* stubbed out the framework's own decorators, which meant it could pass while nothing worked.
|
|
10
|
+
*/
|
|
11
|
+
describe('Pages', () => {
|
|
12
|
+
it('renders the home page, data included', async () => {
|
|
13
|
+
const app = await createTestApp()
|
|
14
|
+
|
|
15
|
+
const response = await app.send(makeIncomingHttpEvent({ method: 'GET', url: '/?name=Ada' }))
|
|
16
|
+
|
|
17
|
+
expect(response.statusCode).toBe(200)
|
|
18
|
+
// A page is a handler, so a rendered page is a response whose body is HTML. Query that HTML with
|
|
19
|
+
// whatever you already use (happy-dom, jsdom, Testing Library): none is bundled here.
|
|
20
|
+
expect(response.html()).toContain('Ada')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('renders without a name', async () => {
|
|
24
|
+
const app = await createTestApp()
|
|
25
|
+
|
|
26
|
+
const response = await app.send(makeIncomingHttpEvent({ method: 'GET', url: '/' }))
|
|
27
|
+
|
|
28
|
+
expect(response.statusCode).toBe(200)
|
|
29
|
+
})
|
|
30
|
+
})
|
|
@@ -2,7 +2,6 @@ import { Routing } from '@stone-js/router'
|
|
|
2
2
|
import { Browser } from '@stone-js/browser-adapter'
|
|
3
3
|
import { Hook, UseReact } from '@stone-js/use-react'
|
|
4
4
|
import { NodeHttp } from '@stone-js/node-http-adapter'
|
|
5
|
-
import { NodeConsole } from '@stone-js/node-cli-adapter'
|
|
6
5
|
import { IBlueprint, IContainer, ILogger, LogLevel, Promiseable, StoneApp } from '@stone-js/core'
|
|
7
6
|
|
|
8
7
|
/**
|
|
@@ -13,8 +12,6 @@ import { IBlueprint, IContainer, ILogger, LogLevel, Promiseable, StoneApp } from
|
|
|
13
12
|
* @StoneApp() is used to enable the Stone application, it is mandatory.
|
|
14
13
|
* @Routing() is used to enable the routing feature.
|
|
15
14
|
* @NodeHttp() is used to enable the Node HTTP adapter.
|
|
16
|
-
* @NodeConsole() is used to enable the Node Console adapter.
|
|
17
|
-
* @NodeConsole() requires the incoming event type.
|
|
18
15
|
* BodyEventMiddleware is used to parse the incoming event body.
|
|
19
16
|
* BodyEventMiddleware is imported because it is not installed by default.
|
|
20
17
|
*
|
|
@@ -25,7 +22,6 @@ import { IBlueprint, IContainer, ILogger, LogLevel, Promiseable, StoneApp } from
|
|
|
25
22
|
@Routing()
|
|
26
23
|
@Browser()
|
|
27
24
|
@UseReact()
|
|
28
|
-
@NodeConsole()
|
|
29
25
|
@NodeHttp({ default: true, url: 'http://localhost:3100' })
|
|
30
26
|
@StoneApp({ name: 'My Stone', logger: { level: LogLevel.DEBUG } })
|
|
31
27
|
export class Application {
|
|
@@ -14,19 +14,18 @@
|
|
|
14
14
|
"build": "stone build",
|
|
15
15
|
"preview": "stone preview",
|
|
16
16
|
"build:ssr": "stone build --ssr",
|
|
17
|
-
"test": "
|
|
18
|
-
"test:cvg": "
|
|
17
|
+
"test": "stone test",
|
|
18
|
+
"test:cvg": "stone test --coverage"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@stone-js/env": "^0.8.
|
|
22
|
-
"@stone-js/http-core": "^0.8.
|
|
23
|
-
"@stone-js/node-http-adapter": "^0.8.
|
|
24
|
-
"@stone-js/router": "^0.8.
|
|
25
|
-
"@stone-js/filesystem": "^0.8.
|
|
26
|
-
"@stone-js/core": "^0.8.
|
|
27
|
-
"@stone-js/use-react": "^0.8.
|
|
28
|
-
"@stone-js/browser-adapter": "^0.8.
|
|
29
|
-
"@stone-js/node-cli-adapter": "^0.8.8",
|
|
21
|
+
"@stone-js/env": "^0.8.10",
|
|
22
|
+
"@stone-js/http-core": "^0.8.10",
|
|
23
|
+
"@stone-js/node-http-adapter": "^0.8.10",
|
|
24
|
+
"@stone-js/router": "^0.8.10",
|
|
25
|
+
"@stone-js/filesystem": "^0.8.10",
|
|
26
|
+
"@stone-js/core": "^0.8.10",
|
|
27
|
+
"@stone-js/use-react": "^0.8.10",
|
|
28
|
+
"@stone-js/browser-adapter": "^0.8.10",
|
|
30
29
|
"axios": "^1.7.9",
|
|
31
30
|
"react": "^19.0.0",
|
|
32
31
|
"react-dom": "^19.0.0"
|
|
@@ -35,7 +34,8 @@
|
|
|
35
34
|
"@babel/plugin-proposal-decorators": "^7.25.9",
|
|
36
35
|
"@babel/preset-env": "^7.26.9",
|
|
37
36
|
"@babel/preset-typescript": "^7.27.0",
|
|
38
|
-
"@stone-js/cli": "^0.8.
|
|
37
|
+
"@stone-js/cli": "^0.8.10",
|
|
38
|
+
"@stone-js/testing": "^0.8.10",
|
|
39
39
|
"@types/axios": "^0.14.4",
|
|
40
40
|
"@types/node": "^24.0.7",
|
|
41
41
|
"@types/react": "^19.0.7",
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96" width="96" height="96" role="img" aria-label="Stone.js">
|
|
2
|
+
<title>Stone.js</title>
|
|
3
|
+
<style>
|
|
4
|
+
.ink { stroke: #1B1613; }
|
|
5
|
+
@media (prefers-color-scheme: dark) { .ink { stroke: #EFE8DC; } }
|
|
6
|
+
</style>
|
|
7
|
+
<defs>
|
|
8
|
+
<linearGradient id="braise" gradientUnits="userSpaceOnUse" x1="22" y1="14" x2="74" y2="26.1">
|
|
9
|
+
<stop offset="0" stop-color="#FFC46B" />
|
|
10
|
+
<stop offset="1" stop-color="#FF5A1F" />
|
|
11
|
+
</linearGradient>
|
|
12
|
+
</defs>
|
|
13
|
+
<path class="ink" d="M 79.96 36.4 A 34 34 0 0 1 53.9 81.5" fill="none" stroke-width="11" stroke-linecap="round" opacity=".85" />
|
|
14
|
+
<path class="ink" d="M 42.1 81.5 A 34 34 0 0 1 16.05 36.4" fill="none" stroke-width="11" stroke-linecap="round" opacity=".85" />
|
|
15
|
+
<path d="M 22 26.1 A 34 34 0 0 1 74 26.1" fill="none" stroke="url(#braise)" stroke-width="11" stroke-linecap="round" />
|
|
16
|
+
</svg>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineBuilderConfig } from '@stone-js/cli'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Stone build configuration.
|
|
5
5
|
*/
|
|
6
|
-
export default
|
|
6
|
+
export default defineBuilderConfig({
|
|
7
7
|
dotenv: {
|
|
8
8
|
private: {
|
|
9
9
|
path: ['.env', '.env.public'],
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createTestApp, makeIncomingHttpEvent } from '@stone-js/testing'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The whole application, booted in memory and asked a real question.
|
|
5
|
+
*
|
|
6
|
+
* Nothing is mocked and nothing is listed: `createTestApp()` discovers `app/**`, and the event goes
|
|
7
|
+
* through the same kernel production runs. If this passes the application works; if it fails the
|
|
8
|
+
* application is broken. That is the only kind of test worth shipping in a starter — the previous one
|
|
9
|
+
* stubbed out the framework's own decorators, which meant it could pass while nothing worked.
|
|
10
|
+
*/
|
|
11
|
+
describe('Pages', () => {
|
|
12
|
+
it('renders the home page, data included', async () => {
|
|
13
|
+
const app = await createTestApp()
|
|
14
|
+
|
|
15
|
+
const response = await app.send(makeIncomingHttpEvent({ method: 'GET', url: '/?name=Ada' }))
|
|
16
|
+
|
|
17
|
+
expect(response.statusCode).toBe(200)
|
|
18
|
+
// A page is a handler, so a rendered page is a response whose body is HTML. Query that HTML with
|
|
19
|
+
// whatever you already use (happy-dom, jsdom, Testing Library): none is bundled here.
|
|
20
|
+
expect(response.html()).toContain('Ada')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('renders without a name', async () => {
|
|
24
|
+
const app = await createTestApp()
|
|
25
|
+
|
|
26
|
+
const response = await app.send(makeIncomingHttpEvent({ method: 'GET', url: '/' }))
|
|
27
|
+
|
|
28
|
+
expect(response.statusCode).toBe(200)
|
|
29
|
+
})
|
|
30
|
+
})
|