@stone-js/starters 0.8.3 → 0.8.5

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 (91) hide show
  1. package/basic-react-declarative/app/Application.tsx +22 -6
  2. package/basic-react-declarative/assets/css/index.css +163 -18
  3. package/basic-react-declarative/package.json +8 -5
  4. package/basic-react-declarative/public/logo.svg +16 -0
  5. package/basic-react-declarative/tests/Application.spec.ts +13 -4
  6. package/basic-react-imperative/app/Application.tsx +22 -6
  7. package/basic-react-imperative/assets/css/index.css +163 -18
  8. package/basic-react-imperative/package.json +8 -5
  9. package/basic-react-imperative/public/logo.svg +16 -0
  10. package/basic-react-imperative/tests/Application.spec.ts +15 -4
  11. package/basic-service-declarative/app/Application.ts +18 -3
  12. package/basic-service-declarative/package.json +7 -4
  13. package/basic-service-declarative/tests/Application.spec.ts +3 -2
  14. package/basic-service-imperative/app/Application.ts +18 -3
  15. package/basic-service-imperative/package.json +7 -4
  16. package/basic-service-imperative/tests/Application.spec.ts +3 -2
  17. package/continuum-showcase/LICENSE +21 -0
  18. package/continuum-showcase/app/Application.ts +45 -0
  19. package/continuum-showcase/app/layouts/MainLayout.tsx +13 -8
  20. package/continuum-showcase/app/pages/BlogPostPage.tsx +6 -3
  21. package/continuum-showcase/app/pages/HomePage.tsx +21 -8
  22. package/continuum-showcase/assets/css/index.css +198 -0
  23. package/continuum-showcase/assets/images/logo.svg +16 -0
  24. package/continuum-showcase/package.json +28 -10
  25. package/continuum-showcase/tests/HomePage.spec.tsx +41 -0
  26. package/continuum-showcase/tsconfig.json +33 -0
  27. package/continuum-showcase/vitest.config.ts +31 -0
  28. package/full-react-declarative/app/layout/AppLayout/AppLayout.tsx +1 -1
  29. package/full-react-declarative/app/layout/ErrorLayout/ErrorLayout.tsx +1 -1
  30. package/full-react-declarative/app/layout/SecurityLayout/SecurityLayout.tsx +1 -1
  31. package/full-react-declarative/app/middleware/AuthMiddleware.ts +1 -1
  32. package/full-react-declarative/app/pages/home/HomePage.tsx +22 -21
  33. package/full-react-declarative/assets/css/index.css +167 -0
  34. package/full-react-declarative/package.json +13 -10
  35. package/full-react-declarative/public/logo.svg +16 -0
  36. package/full-react-declarative/tests/AppLayout.spec.ts +51 -0
  37. package/full-react-declarative/tests/HomePage.spec.ts +38 -0
  38. package/full-react-imperative/app/components/LayoutHeader/LayoutHeader.tsx +1 -1
  39. package/full-react-imperative/app/components/PostWidget/PostWidget.tsx +1 -1
  40. package/full-react-imperative/app/components/PullToRefresh/PullToRefresh.css +10 -0
  41. package/full-react-imperative/app/components/PullToRefresh/PullToRefresh.tsx +66 -0
  42. package/full-react-imperative/app/layout/ErrorLayout/ErrorLayout.tsx +1 -1
  43. package/full-react-imperative/app/layout/SecurityLayout/SecurityLayout.tsx +1 -1
  44. package/full-react-imperative/app/middleware/AuthMiddleware.ts +1 -1
  45. package/full-react-imperative/app/pages/home/HomePage.tsx +32 -16
  46. package/full-react-imperative/assets/css/index.css +167 -0
  47. package/full-react-imperative/package.json +14 -12
  48. package/full-react-imperative/public/logo.svg +16 -0
  49. package/full-react-imperative/tests/HomePage.spec.ts +45 -0
  50. package/full-react-imperative/tests/LayoutHeader.spec.ts +39 -0
  51. package/full-service-declarative/app/handlers/WelcomeEventHandler.ts +47 -0
  52. package/full-service-declarative/app/middleware/AuthMiddleware.ts +1 -1
  53. package/full-service-declarative/package.json +12 -9
  54. package/full-service-declarative/tests/PostService.spec.ts +56 -0
  55. package/full-service-declarative/tests/WelcomeEventHandler.spec.ts +54 -0
  56. package/full-service-declarative/vitest.config.ts +1 -1
  57. package/full-service-imperative/app/handlers/WelcomeEventHandler.ts +51 -0
  58. package/full-service-imperative/app/middleware/AuthMiddleware.ts +1 -1
  59. package/full-service-imperative/package.json +12 -9
  60. package/full-service-imperative/tests/PostService.spec.ts +47 -0
  61. package/full-service-imperative/tests/WelcomeEventHandler.spec.ts +31 -0
  62. package/full-service-imperative/vitest.config.ts +1 -1
  63. package/package.json +1 -1
  64. package/standard-react-declarative/app/pages/welcome/WelcomePage.tsx +37 -4
  65. package/standard-react-declarative/assets/css/index.css +163 -18
  66. package/standard-react-declarative/package.json +11 -8
  67. package/standard-react-declarative/public/logo.svg +16 -0
  68. package/standard-react-declarative/tests/WelcomePage.spec.ts +71 -0
  69. package/standard-react-imperative/app/pages/welcome/WelcomePage.tsx +37 -4
  70. package/standard-react-imperative/assets/css/index.css +163 -18
  71. package/standard-react-imperative/package.json +11 -8
  72. package/standard-react-imperative/public/logo.svg +16 -0
  73. package/standard-react-imperative/tests/WelcomePage.spec.ts +53 -0
  74. package/standard-service-declarative/app/handlers/WelcomeEventHandler.ts +2 -2
  75. package/standard-service-declarative/app/services/WelcomeService.ts +28 -2
  76. package/standard-service-declarative/package.json +9 -6
  77. package/standard-service-declarative/tests/Application.spec.ts +100 -0
  78. package/standard-service-declarative/vitest.config.ts +1 -1
  79. package/standard-service-imperative/app/handlers/welcomeEventHandler.ts +2 -4
  80. package/standard-service-imperative/app/services/welcomeService.ts +28 -2
  81. package/standard-service-imperative/package.json +9 -6
  82. package/standard-service-imperative/tests/Application.spec.ts +84 -0
  83. package/standard-service-imperative/vitest.config.ts +1 -1
  84. package/basic-react-declarative/public/logo.png +0 -0
  85. package/basic-react-declarative/tests/__snapshots__/Application.spec.ts.snap +0 -3
  86. package/basic-react-imperative/public/logo.png +0 -0
  87. package/basic-react-imperative/tests/__snapshots__/Application.spec.ts.snap +0 -3
  88. package/full-react-declarative/assets/img/logo.png +0 -0
  89. package/full-react-declarative/public/logo.png +0 -0
  90. package/full-react-imperative/assets/img/logo.png +0 -0
  91. package/full-react-imperative/public/logo.png +0 -0
@@ -22,14 +22,14 @@
22
22
  },
23
23
  "dependencies": {
24
24
  "@libsql/client": "^0.14.0",
25
- "@stone-js/core": "^0.8.3",
26
- "@stone-js/env": "^0.8.3",
27
- "@stone-js/filesystem": "^0.8.3",
28
- "@stone-js/http-core": "^0.8.3",
29
- "@stone-js/node-cli-adapter": "^0.8.3",
30
- "@stone-js/node-http-adapter": "^0.8.3",
31
- "@stone-js/pipeline": "^0.8.3",
32
- "@stone-js/router": "^0.8.3",
25
+ "@stone-js/core": "^0.8.5",
26
+ "@stone-js/env": "^0.8.5",
27
+ "@stone-js/filesystem": "^0.8.5",
28
+ "@stone-js/http-core": "^0.8.5",
29
+ "@stone-js/node-cli-adapter": "^0.8.5",
30
+ "@stone-js/node-http-adapter": "^0.8.5",
31
+ "@stone-js/pipeline": "^0.8.5",
32
+ "@stone-js/router": "^0.8.5",
33
33
  "bcryptjs": "^3.0.2",
34
34
  "drizzle-orm": "^0.44.2",
35
35
  "jsonwebtoken": "^9.0.2"
@@ -38,11 +38,14 @@
38
38
  "@babel/plugin-proposal-decorators": "^7.25.9",
39
39
  "@babel/preset-env": "^7.26.9",
40
40
  "@babel/preset-typescript": "^7.27.0",
41
- "@stone-js/cli": "^0.8.3",
41
+ "@stone-js/cli": "^0.8.5",
42
42
  "@types/jsonwebtoken": "^9.0.8",
43
43
  "@types/node": "^24.0.3",
44
44
  "@vitest/coverage-v8": "^3.0.9",
45
45
  "drizzle-kit": "^0.31.1",
46
46
  "vitest": "^3.0.9"
47
+ },
48
+ "overrides": {
49
+ "glob": "^11.0.0"
47
50
  }
48
51
  }
@@ -0,0 +1,47 @@
1
+ import { NotFoundError } from '@stone-js/http-core'
2
+ import { factoryPostService } from '../app/services/PostService'
3
+ import { IPostService } from '../app/services/contracts/IPostService'
4
+ import { IPostRepository } from '../app/repositories/contracts/IPostRepository'
5
+ import { Post } from '../app/models/Post'
6
+
7
+ describe('factoryPostService', () => {
8
+ let service: IPostService
9
+ let postRepository: IPostRepository
10
+
11
+ beforeEach(() => {
12
+ postRepository = {
13
+ list: vi.fn(),
14
+ findById: vi.fn(),
15
+ } as unknown as IPostRepository
16
+
17
+ service = factoryPostService({ postRepository })
18
+ })
19
+
20
+ it('should delegate list() to the repository with the given limit', async () => {
21
+ // Arrange
22
+ const posts = [{ id: 1, title: 'Hello' }] as unknown as Post[]
23
+ ;(postRepository.list as any).mockResolvedValue(posts)
24
+
25
+ // Act
26
+ const result = await service.list(5)
27
+
28
+ // Assert
29
+ expect(result).toBe(posts)
30
+ expect(postRepository.list).toHaveBeenCalledWith({ limit: 5 })
31
+ })
32
+
33
+ it('should return the post when found and throw NotFoundError otherwise', async () => {
34
+ // Arrange
35
+ const post = { id: 42, title: 'Answer' } as unknown as Post
36
+ ;(postRepository.findById as any).mockResolvedValueOnce(post)
37
+
38
+ // Act & Assert (found)
39
+ await expect(service.findById(42)).resolves.toBe(post)
40
+
41
+ // Arrange (not found)
42
+ ;(postRepository.findById as any).mockResolvedValueOnce(undefined)
43
+
44
+ // Act & Assert (not found)
45
+ await expect(service.findById(99)).rejects.toBeInstanceOf(NotFoundError)
46
+ })
47
+ })
@@ -0,0 +1,31 @@
1
+ import { IncomingHttpEvent } from '@stone-js/http-core'
2
+ import { factoryWelcomeEventHandler, WelcomePayload } from '../app/handlers/WelcomeEventHandler'
3
+
4
+ describe('factoryWelcomeEventHandler', () => {
5
+ const makeEvent = (name?: string): IncomingHttpEvent => ({
6
+ get: vi.fn((_key: string, fallback: string) => name ?? fallback),
7
+ }) as unknown as IncomingHttpEvent
8
+
9
+ it('should return a branded Stone.js payload greeting the given name', () => {
10
+ // Act
11
+ const handler = factoryWelcomeEventHandler()
12
+ const payload = handler(makeEvent('Ada')) as unknown as WelcomePayload
13
+
14
+ // Assert
15
+ expect(payload.message).toBe('Hello Ada! Welcome to Stone.js.')
16
+ expect(payload.framework.name).toBe('Stone.js')
17
+ expect(payload.framework.tagline).toBe('The continuum framework')
18
+ expect(payload.framework.docs).toBe('https://stonejs.dev')
19
+ expect(payload.framework.github).toBe('https://github.com/stone-foundation/stone-js-framework')
20
+ })
21
+
22
+ it('should fall back to the default name when none is provided', () => {
23
+ // Act
24
+ const handler = factoryWelcomeEventHandler()
25
+ const payload = handler(makeEvent()) as unknown as WelcomePayload
26
+
27
+ // Assert
28
+ expect(payload.message).toBe('Hello Stone! Welcome to Stone.js.')
29
+ expect(payload.framework.name).toBe('Stone.js')
30
+ })
31
+ })
@@ -1,4 +1,4 @@
1
- import { defineConfig } from 'vitest/config.js'
1
+ import { defineConfig } from 'vitest/config'
2
2
 
3
3
  export default defineConfig({
4
4
  test: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stone-js/starters",
3
- "version": "0.8.3",
3
+ "version": "0.8.5",
4
4
  "description": "This repository contains official **starter templates** for Stone.js applications.",
5
5
  "author": "Mr. Stone <evensstone@gmail.com>",
6
6
  "license": "MIT",
@@ -1,6 +1,7 @@
1
1
  import { JSX } from 'react'
2
+ import { Promiseable } from '@stone-js/core'
2
3
  import { WelcomeService } from '../../services/WelcomeService'
3
- import { Page, ReactIncomingEvent, PageRenderContext, IPage } from '@stone-js/use-react'
4
+ import { Page, ReactIncomingEvent, PageRenderContext, PageHeadContext, HeadContext, IPage } from '@stone-js/use-react'
4
5
 
5
6
  /**
6
7
  * WelcomePage Options
@@ -42,6 +43,22 @@ export class WelcomePage implements IPage<ReactIncomingEvent> {
42
43
  return this.welcomeService.welcome(event.get<string>('name', 'World'))
43
44
  }
44
45
 
46
+ /**
47
+ * Set the page head tags, like title, meta, link, script, style.
48
+ *
49
+ * @returns The head context.
50
+ */
51
+ head ({ event }: PageHeadContext): Promiseable<HeadContext> {
52
+ return {
53
+ title: `${String(event.get<string>('name', 'World'))} · Welcome to Stone.js`,
54
+ description: 'A universal Stone.js application. Write your domain once; Stone.js is the context that runs it anywhere.',
55
+ metas: [
56
+ { name: 'author', content: 'Stone.js' },
57
+ { name: 'keywords', content: 'stonejs,continuum,react,universal,framework' }
58
+ ]
59
+ }
60
+ }
61
+
45
62
  /**
46
63
  * Render the component.
47
64
  *
@@ -49,9 +66,25 @@ export class WelcomePage implements IPage<ReactIncomingEvent> {
49
66
  */
50
67
  render ({ data }: PageRenderContext<ResponseData>): JSX.Element {
51
68
  return (
52
- <section className='container'>
53
- <h1 className='h1 text-center mt-64'>{data?.message}</h1>
54
- </section>
69
+ <main className='stone-welcome'>
70
+ <div className='glow' aria-hidden='true' />
71
+ <section className='hero'>
72
+ <img className='mark' src='/logo.svg' alt='Stone.js' width={104} height={104} />
73
+ <p className='eyebrow'>Welcome to</p>
74
+ <h1 className='title'>Stone.js</h1>
75
+ <p className='lead'>{data?.message}</p>
76
+ <p className='tagline'>
77
+ Your app is running. Write your domain once, Stone.js is the context that runs it
78
+ anywhere: server, serverless, browser, CLI and the edge.
79
+ </p>
80
+ <nav className='links'>
81
+ <a href='https://stonejs.dev/docs' target='_blank' rel='noreferrer noopener'>Documentation</a>
82
+ <a href='https://github.com/stone-foundation/stone-js-framework' target='_blank' rel='noreferrer noopener'>GitHub</a>
83
+ <span className='edit'>Edit <b>app/pages/welcome/WelcomePage.tsx</b></span>
84
+ </nav>
85
+ </section>
86
+ <footer className='brand'><span className='dot'>●</span> Stone.js — the continuum framework</footer>
87
+ </main>
55
88
  )
56
89
  }
57
90
  }
@@ -1,26 +1,171 @@
1
- body {
2
- margin: 0;
3
- padding: 0;
4
- color: #333;
5
- font-size: 14px;
6
- line-height: 1.5;
7
- background-color: #fff;
8
- font-family: 'Roboto', sans-serif;
1
+ /**
2
+ * Stone.js starter — brand welcome screen.
3
+ * Identity: "Obsidienne & Braise" — obsidian ground, ember (braise) accent, the Portal mark.
4
+ * Theme-aware: dark by default, light under prefers-color-scheme: light.
5
+ */
6
+ :root {
7
+ --ink: #14100E;
8
+ --ink-2: #1E1815;
9
+ --cream: #F4EFE6;
10
+ --cream-dim: #B8AE9E;
11
+ --ember-1: #FFC46B;
12
+ --ember-2: #FF5A1F;
13
+ --line: rgba(244, 239, 230, 0.10);
14
+ --card: rgba(244, 239, 230, 0.04);
15
+ --bg: radial-gradient(1200px 600px at 50% -10%, #241C17 0%, var(--ink) 55%);
9
16
  }
10
- .h1 {
17
+
18
+ @media (prefers-color-scheme: light) {
19
+ :root {
20
+ --ink: #F4EFE6;
21
+ --ink-2: #ffffff;
22
+ --cream: #1B1613;
23
+ --cream-dim: #6B6154;
24
+ --line: rgba(27, 22, 19, 0.10);
25
+ --card: rgba(27, 22, 19, 0.03);
26
+ --bg: radial-gradient(1200px 600px at 50% -10%, #FFFFFF 0%, #F1E9DC 60%);
27
+ }
28
+ }
29
+
30
+ * { box-sizing: border-box; }
31
+
32
+ body {
11
33
  margin: 0;
12
- font-weight: 500;
13
- font-size: 3rem;
14
- font-weight: bold;
34
+ min-height: 100vh;
35
+ color: var(--cream);
36
+ background: var(--ink);
37
+ font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
38
+ -webkit-font-smoothing: antialiased;
15
39
  }
16
- .container {
17
- width: 100vw;
40
+
41
+ .stone-welcome {
42
+ position: relative;
43
+ min-height: 100vh;
18
44
  display: flex;
45
+ flex-direction: column;
46
+ align-items: center;
19
47
  justify-content: center;
48
+ padding: 48px 24px;
49
+ background: var(--bg);
50
+ overflow: hidden;
51
+ }
52
+
53
+ /* Ember glow behind the mark. */
54
+ .stone-welcome .glow {
55
+ position: absolute;
56
+ top: 8%;
57
+ left: 50%;
58
+ width: 520px;
59
+ height: 520px;
60
+ transform: translateX(-50%);
61
+ background: radial-gradient(circle, rgba(255, 90, 31, 0.22) 0%, rgba(255, 90, 31, 0) 68%);
62
+ filter: blur(8px);
63
+ pointer-events: none;
64
+ z-index: 0;
20
65
  }
21
- .text-center {
66
+
67
+ .stone-welcome .hero {
68
+ position: relative;
69
+ z-index: 1;
70
+ width: 100%;
71
+ max-width: 680px;
22
72
  text-align: center;
73
+ animation: rise 0.7s cubic-bezier(0.2, 0.7, 0.2, 1) both;
74
+ }
75
+
76
+ .stone-welcome .mark {
77
+ width: 104px;
78
+ height: 104px;
79
+ filter: drop-shadow(0 8px 28px rgba(255, 90, 31, 0.35));
80
+ }
81
+
82
+ .stone-welcome .eyebrow {
83
+ margin: 22px 0 0;
84
+ font-size: 12px;
85
+ font-weight: 600;
86
+ letter-spacing: 0.28em;
87
+ text-transform: uppercase;
88
+ color: var(--cream-dim);
89
+ }
90
+
91
+ .stone-welcome .title {
92
+ margin: 8px 0 0;
93
+ font-size: clamp(44px, 8vw, 76px);
94
+ font-weight: 800;
95
+ letter-spacing: -0.03em;
96
+ line-height: 1.02;
97
+ background: linear-gradient(92deg, var(--ember-1), var(--ember-2));
98
+ -webkit-background-clip: text;
99
+ background-clip: text;
100
+ color: transparent;
101
+ }
102
+
103
+ .stone-welcome .lead {
104
+ margin: 18px auto 0;
105
+ font-size: 20px;
106
+ font-weight: 600;
107
+ color: var(--cream);
108
+ }
109
+
110
+ .stone-welcome .tagline {
111
+ margin: 10px auto 0;
112
+ max-width: 46ch;
113
+ font-size: 16px;
114
+ line-height: 1.6;
115
+ color: var(--cream-dim);
116
+ }
117
+
118
+ .stone-welcome .links {
119
+ margin-top: 34px;
120
+ display: flex;
121
+ flex-wrap: wrap;
122
+ gap: 12px;
123
+ justify-content: center;
124
+ }
125
+
126
+ .stone-welcome .links a,
127
+ .stone-welcome .links .edit {
128
+ display: inline-flex;
129
+ align-items: center;
130
+ gap: 8px;
131
+ padding: 11px 18px;
132
+ border: 1px solid var(--line);
133
+ border-radius: 12px;
134
+ background: var(--card);
135
+ color: var(--cream);
136
+ font-size: 14px;
137
+ font-weight: 600;
138
+ text-decoration: none;
139
+ transition: border-color 0.2s, transform 0.2s, background 0.2s;
140
+ }
141
+
142
+ .stone-welcome .links a:hover {
143
+ transform: translateY(-2px);
144
+ border-color: rgba(255, 90, 31, 0.6);
145
+ background: rgba(255, 90, 31, 0.08);
146
+ }
147
+
148
+ .stone-welcome .links .edit {
149
+ font-family: ui-monospace, 'SF Mono', Menlo, Monaco, Consolas, monospace;
150
+ color: var(--cream-dim);
151
+ }
152
+ .stone-welcome .links .edit b { color: var(--ember-1); font-weight: 700; }
153
+
154
+ .stone-welcome .brand {
155
+ position: relative;
156
+ z-index: 1;
157
+ margin-top: 56px;
158
+ font-size: 13px;
159
+ letter-spacing: 0.02em;
160
+ color: var(--cream-dim);
161
+ }
162
+ .stone-welcome .brand .dot { color: var(--ember-2); }
163
+
164
+ @keyframes rise {
165
+ from { opacity: 0; transform: translateY(14px); }
166
+ to { opacity: 1; transform: translateY(0); }
167
+ }
168
+
169
+ @media (prefers-reduced-motion: reduce) {
170
+ .stone-welcome .hero { animation: none; }
23
171
  }
24
- .mt-64 {
25
- margin-top: 64px;
26
- }
@@ -20,23 +20,26 @@
20
20
  "dependencies": {
21
21
  "react": "^19.0.0",
22
22
  "react-dom": "^19.0.0",
23
- "@stone-js/use-react": "^0.8.3",
24
- "@stone-js/browser-adapter": "^0.8.3",
25
- "@stone-js/pipeline": "^0.8.3",
26
- "@stone-js/http-core": "^0.8.3",
27
- "@stone-js/node-http-adapter": "^0.8.3",
28
- "@stone-js/router": "^0.8.3",
29
- "@stone-js/core": "^0.8.3"
23
+ "@stone-js/use-react": "^0.8.5",
24
+ "@stone-js/browser-adapter": "^0.8.5",
25
+ "@stone-js/pipeline": "^0.8.5",
26
+ "@stone-js/http-core": "^0.8.5",
27
+ "@stone-js/node-http-adapter": "^0.8.5",
28
+ "@stone-js/router": "^0.8.5",
29
+ "@stone-js/core": "^0.8.5"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@babel/plugin-proposal-decorators": "^7.25.9",
33
33
  "@babel/preset-env": "^7.26.9",
34
34
  "@babel/preset-typescript": "^7.27.0",
35
- "@stone-js/cli": "^0.8.3",
35
+ "@stone-js/cli": "^0.8.5",
36
36
  "@types/node": "^24.0.7",
37
37
  "@types/react": "^19.0.7",
38
38
  "@types/react-dom": "^19.0.3",
39
39
  "@vitest/coverage-v8": "^3.0.9",
40
40
  "vitest": "^3.0.9"
41
+ },
42
+ "overrides": {
43
+ "glob": "^11.0.0"
41
44
  }
42
45
  }
@@ -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>
@@ -0,0 +1,71 @@
1
+ import { ILogger } from '@stone-js/core'
2
+ import { renderToString } from 'react-dom/server'
3
+ import { ReactIncomingEvent } from '@stone-js/use-react'
4
+ import { WelcomePage } from '../app/pages/welcome/WelcomePage'
5
+ import { WelcomeService } from '../app/services/WelcomeService'
6
+
7
+ // We must mock decorators to lighten the test environment
8
+ vi.mock('@stone-js/use-react', async (importOriginal) => {
9
+ const actual: any = await importOriginal()
10
+ return {
11
+ ...actual,
12
+ Page: vi.fn(() => vi.fn()),
13
+ }
14
+ })
15
+
16
+ // We must mock decorators to lighten the test environment
17
+ vi.mock('@stone-js/core', async (importOriginal) => {
18
+ const actual: any = await importOriginal()
19
+ return {
20
+ ...actual,
21
+ Service: vi.fn(() => vi.fn()),
22
+ }
23
+ })
24
+
25
+ describe('WelcomePage', () => {
26
+ let page: WelcomePage
27
+ let mockedLogger: ILogger
28
+
29
+ beforeEach(() => {
30
+ mockedLogger = {
31
+ info: vi.fn(),
32
+ } as unknown as ILogger
33
+
34
+ page = new WelcomePage({ welcomeService: new WelcomeService({ logger: mockedLogger }) })
35
+ })
36
+
37
+ it('should handle incoming events', () => {
38
+ // Arrange
39
+ const expectedMessage = 'Hello World!'
40
+ const event = { get: () => 'World' } as unknown as ReactIncomingEvent
41
+
42
+ // Act
43
+ const response = page.handle(event)
44
+
45
+ // Assert
46
+ expect(response.message).toBe(expectedMessage)
47
+ expect(mockedLogger.info).toHaveBeenCalledWith('Welcome World')
48
+ })
49
+
50
+ it('should render the message', () => {
51
+ // Arrange
52
+ const message = 'Hello World!'
53
+
54
+ // Act
55
+ const response = renderToString(page.render({ data: { message } } as any))
56
+
57
+ // Assert
58
+ expect(response).toContain(message)
59
+ })
60
+
61
+ it('should render the Stone.js logo', () => {
62
+ // Act
63
+ const response = renderToString(page.render({ data: { message: 'Hello World!' } } as any))
64
+
65
+ // Assert
66
+ expect(response).toContain('/logo.svg')
67
+ expect(response).toContain('alt="Stone.js"')
68
+ expect(response).toContain('Welcome')
69
+ expect(response).toContain('Stone.js')
70
+ })
71
+ })
@@ -1,6 +1,7 @@
1
1
  import { JSX } from 'react'
2
+ import { Promiseable } from '@stone-js/core'
2
3
  import { WelcomeService } from '../../services/WelcomeService'
3
- import { definePage, IPage, PageRenderContext, ReactIncomingEvent } from '@stone-js/use-react'
4
+ import { definePage, IPage, PageRenderContext, PageHeadContext, HeadContext, ReactIncomingEvent } from '@stone-js/use-react'
4
5
 
5
6
  /**
6
7
  * WelcomePage Options
@@ -30,6 +31,22 @@ export const WelcomePage = ({ welcomeService }: WelcomePageOptions): IPage<React
30
31
  return welcomeService.welcome(event.get<string>('name', 'World'))
31
32
  },
32
33
 
34
+ /**
35
+ * Set the page head tags, like title, meta, link, script, style.
36
+ *
37
+ * @returns The head context.
38
+ */
39
+ head ({ event }: PageHeadContext): Promiseable<HeadContext> {
40
+ return {
41
+ title: `${String(event.get<string>('name', 'World'))} · Welcome to Stone.js`,
42
+ description: 'A universal Stone.js application. Write your domain once; Stone.js is the context that runs it anywhere.',
43
+ metas: [
44
+ { name: 'author', content: 'Stone.js' },
45
+ { name: 'keywords', content: 'stonejs,continuum,react,universal,framework' }
46
+ ]
47
+ }
48
+ },
49
+
33
50
  /**
34
51
  * Render the component.
35
52
  *
@@ -37,9 +54,25 @@ export const WelcomePage = ({ welcomeService }: WelcomePageOptions): IPage<React
37
54
  */
38
55
  render ({ data }: PageRenderContext<ResponseData>): JSX.Element {
39
56
  return (
40
- <section className='container'>
41
- <h1 className='h1 text-center mt-64'>{data?.message}</h1>
42
- </section>
57
+ <main className='stone-welcome'>
58
+ <div className='glow' aria-hidden='true' />
59
+ <section className='hero'>
60
+ <img className='mark' src='/logo.svg' alt='Stone.js' width={104} height={104} />
61
+ <p className='eyebrow'>Welcome to</p>
62
+ <h1 className='title'>Stone.js</h1>
63
+ <p className='lead'>{data?.message}</p>
64
+ <p className='tagline'>
65
+ Your app is running. Write your domain once, Stone.js is the context that runs it
66
+ anywhere: server, serverless, browser, CLI and the edge.
67
+ </p>
68
+ <nav className='links'>
69
+ <a href='https://stonejs.dev/docs' target='_blank' rel='noreferrer noopener'>Documentation</a>
70
+ <a href='https://github.com/stone-foundation/stone-js-framework' target='_blank' rel='noreferrer noopener'>GitHub</a>
71
+ <span className='edit'>Edit <b>app/pages/welcome/WelcomePage.tsx</b></span>
72
+ </nav>
73
+ </section>
74
+ <footer className='brand'><span className='dot'>●</span> Stone.js — the continuum framework</footer>
75
+ </main>
43
76
  )
44
77
  }
45
78
  })