digital-products 2.0.1
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/.turbo/turbo-build.log +5 -0
- package/CHANGELOG.md +9 -0
- package/README.md +535 -0
- package/dist/api.d.ts +99 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +129 -0
- package/dist/api.js.map +1 -0
- package/dist/app.d.ts +79 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +107 -0
- package/dist/app.js.map +1 -0
- package/dist/content.d.ts +58 -0
- package/dist/content.d.ts.map +1 -0
- package/dist/content.js +78 -0
- package/dist/content.js.map +1 -0
- package/dist/data.d.ts +67 -0
- package/dist/data.d.ts.map +1 -0
- package/dist/data.js +107 -0
- package/dist/data.js.map +1 -0
- package/dist/dataset.d.ts +32 -0
- package/dist/dataset.d.ts.map +1 -0
- package/dist/dataset.js +50 -0
- package/dist/dataset.js.map +1 -0
- package/dist/entities/ai.d.ts +53 -0
- package/dist/entities/ai.d.ts.map +1 -0
- package/dist/entities/ai.js +859 -0
- package/dist/entities/ai.js.map +1 -0
- package/dist/entities/content.d.ts +52 -0
- package/dist/entities/content.d.ts.map +1 -0
- package/dist/entities/content.js +784 -0
- package/dist/entities/content.js.map +1 -0
- package/dist/entities/index.d.ts +112 -0
- package/dist/entities/index.d.ts.map +1 -0
- package/dist/entities/index.js +89 -0
- package/dist/entities/index.js.map +1 -0
- package/dist/entities/interfaces.d.ts +67 -0
- package/dist/entities/interfaces.d.ts.map +1 -0
- package/dist/entities/interfaces.js +930 -0
- package/dist/entities/interfaces.js.map +1 -0
- package/dist/entities/lifecycle.d.ts +51 -0
- package/dist/entities/lifecycle.d.ts.map +1 -0
- package/dist/entities/lifecycle.js +804 -0
- package/dist/entities/lifecycle.js.map +1 -0
- package/dist/entities/products.d.ts +53 -0
- package/dist/entities/products.d.ts.map +1 -0
- package/dist/entities/products.js +798 -0
- package/dist/entities/products.js.map +1 -0
- package/dist/entities/web.d.ts +44 -0
- package/dist/entities/web.d.ts.map +1 -0
- package/dist/entities/web.js +658 -0
- package/dist/entities/web.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp.d.ts +101 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +140 -0
- package/dist/mcp.js.map +1 -0
- package/dist/product.d.ts +37 -0
- package/dist/product.d.ts.map +1 -0
- package/dist/product.js +54 -0
- package/dist/product.js.map +1 -0
- package/dist/registry.d.ts +9 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +32 -0
- package/dist/registry.js.map +1 -0
- package/dist/sdk.d.ts +99 -0
- package/dist/sdk.d.ts.map +1 -0
- package/dist/sdk.js +128 -0
- package/dist/sdk.js.map +1 -0
- package/dist/site.d.ts +85 -0
- package/dist/site.d.ts.map +1 -0
- package/dist/site.js +113 -0
- package/dist/site.js.map +1 -0
- package/dist/types.d.ts +528 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/example.ts +236 -0
- package/package.json +35 -0
- package/src/api.ts +140 -0
- package/src/app.ts +117 -0
- package/src/content.ts +82 -0
- package/src/data.ts +129 -0
- package/src/dataset.ts +53 -0
- package/src/entities/ai.ts +932 -0
- package/src/entities/content.ts +851 -0
- package/src/entities/index.ts +156 -0
- package/src/entities/interfaces.ts +1017 -0
- package/src/entities/lifecycle.ts +872 -0
- package/src/entities/products.ts +867 -0
- package/src/entities/web.ts +719 -0
- package/src/index.ts +55 -0
- package/src/mcp.ts +163 -0
- package/src/product.ts +59 -0
- package/src/registry.ts +41 -0
- package/src/sdk.ts +148 -0
- package/src/site.ts +127 -0
- package/src/types.ts +558 -0
- package/test/api.test.ts +247 -0
- package/test/app.test.ts +220 -0
- package/test/content.test.ts +171 -0
- package/test/data.test.ts +201 -0
- package/test/dataset.test.ts +181 -0
- package/test/mcp.test.ts +230 -0
- package/test/product.test.ts +200 -0
- package/test/sdk.test.ts +236 -0
- package/test/site.test.ts +245 -0
- package/tsconfig.json +9 -0
package/test/api.test.ts
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for API functionality
|
|
3
|
+
*
|
|
4
|
+
* Covers API creation and helper functions.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, beforeEach } from 'vitest'
|
|
8
|
+
import { API, Endpoint, APIAuth, RateLimit, registry } from '../src/index.js'
|
|
9
|
+
|
|
10
|
+
describe('API', () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
registry.clear()
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
describe('API creation', () => {
|
|
16
|
+
it('creates an API with basic config', () => {
|
|
17
|
+
const api = API({
|
|
18
|
+
id: 'my-api',
|
|
19
|
+
name: 'My API',
|
|
20
|
+
description: 'A RESTful API',
|
|
21
|
+
version: '1.0.0',
|
|
22
|
+
baseUrl: 'https://api.example.com',
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
expect(api.id).toBe('my-api')
|
|
26
|
+
expect(api.name).toBe('My API')
|
|
27
|
+
expect(api.type).toBe('api')
|
|
28
|
+
expect(api.baseUrl).toBe('https://api.example.com')
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('defaults style to rest', () => {
|
|
32
|
+
const api = API({
|
|
33
|
+
id: 'rest-api',
|
|
34
|
+
name: 'REST API',
|
|
35
|
+
description: 'A REST API',
|
|
36
|
+
version: '1.0.0',
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
expect(api.style).toBe('rest')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('supports graphql style', () => {
|
|
43
|
+
const api = API({
|
|
44
|
+
id: 'graphql-api',
|
|
45
|
+
name: 'GraphQL API',
|
|
46
|
+
description: 'A GraphQL API',
|
|
47
|
+
version: '1.0.0',
|
|
48
|
+
style: 'graphql',
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
expect(api.style).toBe('graphql')
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('creates an API with endpoints', () => {
|
|
55
|
+
const api = API({
|
|
56
|
+
id: 'endpoint-api',
|
|
57
|
+
name: 'Endpoint API',
|
|
58
|
+
description: 'API with endpoints',
|
|
59
|
+
version: '1.0.0',
|
|
60
|
+
endpoints: [
|
|
61
|
+
Endpoint('GET', '/users', 'List users'),
|
|
62
|
+
Endpoint('POST', '/users', 'Create user'),
|
|
63
|
+
],
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
expect(api.endpoints).toHaveLength(2)
|
|
67
|
+
expect(api.endpoints?.[0]?.method).toBe('GET')
|
|
68
|
+
expect(api.endpoints?.[1]?.method).toBe('POST')
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('creates an API with auth', () => {
|
|
72
|
+
const api = API({
|
|
73
|
+
id: 'auth-api',
|
|
74
|
+
name: 'Auth API',
|
|
75
|
+
description: 'API with auth',
|
|
76
|
+
version: '1.0.0',
|
|
77
|
+
auth: {
|
|
78
|
+
type: 'bearer',
|
|
79
|
+
header: 'Authorization',
|
|
80
|
+
},
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
expect(api.auth?.type).toBe('bearer')
|
|
84
|
+
expect(api.auth?.header).toBe('Authorization')
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('creates an API with rate limiting', () => {
|
|
88
|
+
const api = API({
|
|
89
|
+
id: 'rate-limited-api',
|
|
90
|
+
name: 'Rate Limited API',
|
|
91
|
+
description: 'API with rate limiting',
|
|
92
|
+
version: '1.0.0',
|
|
93
|
+
rateLimit: {
|
|
94
|
+
requests: 100,
|
|
95
|
+
window: 60,
|
|
96
|
+
},
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
expect(api.rateLimit?.requests).toBe(100)
|
|
100
|
+
expect(api.rateLimit?.window).toBe(60)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('registers the API automatically', () => {
|
|
104
|
+
API({
|
|
105
|
+
id: 'auto-registered',
|
|
106
|
+
name: 'Auto Registered',
|
|
107
|
+
description: 'Automatically registered',
|
|
108
|
+
version: '1.0.0',
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
expect(registry.get('auto-registered')).toBeDefined()
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('supports docsUrl', () => {
|
|
115
|
+
const api = API({
|
|
116
|
+
id: 'documented-api',
|
|
117
|
+
name: 'Documented API',
|
|
118
|
+
description: 'API with docs',
|
|
119
|
+
version: '1.0.0',
|
|
120
|
+
docsUrl: 'https://docs.example.com/api',
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
expect(api.docsUrl).toBe('https://docs.example.com/api')
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('supports openapi spec reference', () => {
|
|
127
|
+
const api = API({
|
|
128
|
+
id: 'openapi-api',
|
|
129
|
+
name: 'OpenAPI API',
|
|
130
|
+
description: 'API with OpenAPI',
|
|
131
|
+
version: '1.0.0',
|
|
132
|
+
openapi: 'https://api.example.com/openapi.json',
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
expect(api.openapi).toBe('https://api.example.com/openapi.json')
|
|
136
|
+
})
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
describe('Endpoint helper', () => {
|
|
140
|
+
it('creates a basic endpoint', () => {
|
|
141
|
+
const endpoint = Endpoint('GET', '/users', 'List all users')
|
|
142
|
+
|
|
143
|
+
expect(endpoint.method).toBe('GET')
|
|
144
|
+
expect(endpoint.path).toBe('/users')
|
|
145
|
+
expect(endpoint.description).toBe('List all users')
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('creates an endpoint with params', () => {
|
|
149
|
+
const endpoint = Endpoint('GET', '/users/:id', 'Get user by ID', {
|
|
150
|
+
params: { id: 'User ID' },
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
expect(endpoint.params).toEqual({ id: 'User ID' })
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it('creates an endpoint with request and response schemas', () => {
|
|
157
|
+
const endpoint = Endpoint('POST', '/users', 'Create user', {
|
|
158
|
+
request: {
|
|
159
|
+
name: 'User name',
|
|
160
|
+
email: 'User email',
|
|
161
|
+
},
|
|
162
|
+
response: {
|
|
163
|
+
id: 'User ID',
|
|
164
|
+
name: 'User name',
|
|
165
|
+
email: 'User email',
|
|
166
|
+
},
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
expect(endpoint.request).toEqual({
|
|
170
|
+
name: 'User name',
|
|
171
|
+
email: 'User email',
|
|
172
|
+
})
|
|
173
|
+
expect(endpoint.response).toEqual({
|
|
174
|
+
id: 'User ID',
|
|
175
|
+
name: 'User name',
|
|
176
|
+
email: 'User email',
|
|
177
|
+
})
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it('creates an endpoint with auth requirement', () => {
|
|
181
|
+
const endpoint = Endpoint('DELETE', '/users/:id', 'Delete user', {
|
|
182
|
+
auth: true,
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
expect(endpoint.auth).toBe(true)
|
|
186
|
+
})
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
describe('APIAuth helper', () => {
|
|
190
|
+
it('creates bearer auth config', () => {
|
|
191
|
+
const auth = APIAuth({
|
|
192
|
+
type: 'bearer',
|
|
193
|
+
header: 'Authorization',
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
expect(auth.type).toBe('bearer')
|
|
197
|
+
expect(auth.header).toBe('Authorization')
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
it('creates API key auth config', () => {
|
|
201
|
+
const auth = APIAuth({
|
|
202
|
+
type: 'apiKey',
|
|
203
|
+
header: 'X-API-Key',
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
expect(auth.type).toBe('apiKey')
|
|
207
|
+
expect(auth.header).toBe('X-API-Key')
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
it('creates OAuth2 auth config', () => {
|
|
211
|
+
const auth = APIAuth({
|
|
212
|
+
type: 'oauth2',
|
|
213
|
+
oauth2: {
|
|
214
|
+
authUrl: 'https://auth.example.com/authorize',
|
|
215
|
+
tokenUrl: 'https://auth.example.com/token',
|
|
216
|
+
scopes: ['read', 'write'],
|
|
217
|
+
},
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
expect(auth.type).toBe('oauth2')
|
|
221
|
+
expect(auth.oauth2?.authUrl).toBe('https://auth.example.com/authorize')
|
|
222
|
+
expect(auth.oauth2?.scopes).toEqual(['read', 'write'])
|
|
223
|
+
})
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
describe('RateLimit helper', () => {
|
|
227
|
+
it('creates basic rate limit config', () => {
|
|
228
|
+
const rateLimit = RateLimit({
|
|
229
|
+
requests: 100,
|
|
230
|
+
window: 60,
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
expect(rateLimit.requests).toBe(100)
|
|
234
|
+
expect(rateLimit.window).toBe(60)
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
it('supports onExceeded action', () => {
|
|
238
|
+
const rateLimit = RateLimit({
|
|
239
|
+
requests: 100,
|
|
240
|
+
window: 60,
|
|
241
|
+
onExceeded: 'reject',
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
expect(rateLimit.onExceeded).toBe('reject')
|
|
245
|
+
})
|
|
246
|
+
})
|
|
247
|
+
})
|
package/test/app.test.ts
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for App functionality
|
|
3
|
+
*
|
|
4
|
+
* Covers application creation and helper functions.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, beforeEach } from 'vitest'
|
|
8
|
+
import { App, Route, State, Auth, registry } from '../src/index.js'
|
|
9
|
+
|
|
10
|
+
describe('App', () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
registry.clear()
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
describe('App creation', () => {
|
|
16
|
+
it('creates an app with basic config', () => {
|
|
17
|
+
const app = App({
|
|
18
|
+
id: 'my-app',
|
|
19
|
+
name: 'My App',
|
|
20
|
+
description: 'A web application',
|
|
21
|
+
version: '1.0.0',
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
expect(app.id).toBe('my-app')
|
|
25
|
+
expect(app.name).toBe('My App')
|
|
26
|
+
expect(app.type).toBe('app')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('defaults framework to react', () => {
|
|
30
|
+
const app = App({
|
|
31
|
+
id: 'react-app',
|
|
32
|
+
name: 'React App',
|
|
33
|
+
description: 'Default framework',
|
|
34
|
+
version: '1.0.0',
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
expect(app.framework).toBe('react')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('supports custom frameworks', () => {
|
|
41
|
+
const app = App({
|
|
42
|
+
id: 'vue-app',
|
|
43
|
+
name: 'Vue App',
|
|
44
|
+
description: 'Vue application',
|
|
45
|
+
version: '1.0.0',
|
|
46
|
+
framework: 'vue',
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
expect(app.framework).toBe('vue')
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('creates an app with routes', () => {
|
|
53
|
+
const app = App({
|
|
54
|
+
id: 'routed-app',
|
|
55
|
+
name: 'Routed App',
|
|
56
|
+
description: 'App with routes',
|
|
57
|
+
version: '1.0.0',
|
|
58
|
+
routes: [
|
|
59
|
+
Route('/', 'Home'),
|
|
60
|
+
Route('/about', 'About'),
|
|
61
|
+
Route('/users/:id', 'UserDetail'),
|
|
62
|
+
],
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
expect(app.routes).toHaveLength(3)
|
|
66
|
+
expect(app.routes?.[0]?.path).toBe('/')
|
|
67
|
+
expect(app.routes?.[2]?.path).toBe('/users/:id')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('creates an app with state config', () => {
|
|
71
|
+
const app = App({
|
|
72
|
+
id: 'stateful-app',
|
|
73
|
+
name: 'Stateful App',
|
|
74
|
+
description: 'App with state',
|
|
75
|
+
version: '1.0.0',
|
|
76
|
+
state: {
|
|
77
|
+
library: 'zustand',
|
|
78
|
+
schema: {
|
|
79
|
+
user: 'Current user object',
|
|
80
|
+
settings: 'App settings object',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
expect(app.state?.library).toBe('zustand')
|
|
86
|
+
expect(app.state?.schema?.user).toBe('Current user object')
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('creates an app with auth config', () => {
|
|
90
|
+
const app = App({
|
|
91
|
+
id: 'authed-app',
|
|
92
|
+
name: 'Authed App',
|
|
93
|
+
description: 'App with auth',
|
|
94
|
+
version: '1.0.0',
|
|
95
|
+
auth: {
|
|
96
|
+
provider: 'clerk',
|
|
97
|
+
protectedRoutes: ['/dashboard', '/profile'],
|
|
98
|
+
},
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
expect(app.auth?.provider).toBe('clerk')
|
|
102
|
+
expect(app.auth?.protectedRoutes).toContain('/dashboard')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('registers the app automatically', () => {
|
|
106
|
+
App({
|
|
107
|
+
id: 'auto-registered',
|
|
108
|
+
name: 'Auto Registered',
|
|
109
|
+
description: 'Automatically registered',
|
|
110
|
+
version: '1.0.0',
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
expect(registry.get('auto-registered')).toBeDefined()
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('supports deployments config', () => {
|
|
117
|
+
const app = App({
|
|
118
|
+
id: 'deployed-app',
|
|
119
|
+
name: 'Deployed App',
|
|
120
|
+
description: 'App with deployments',
|
|
121
|
+
version: '1.0.0',
|
|
122
|
+
deployments: {
|
|
123
|
+
production: 'https://app.example.com',
|
|
124
|
+
staging: 'https://staging.example.com',
|
|
125
|
+
},
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
expect(app.deployments?.production).toBe('https://app.example.com')
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
describe('Route helper', () => {
|
|
133
|
+
it('creates a basic route', () => {
|
|
134
|
+
const route = Route('/', 'Home')
|
|
135
|
+
|
|
136
|
+
expect(route.path).toBe('/')
|
|
137
|
+
expect(route.component).toBe('Home')
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('creates a route with meta', () => {
|
|
141
|
+
const route = Route('/about', 'About', {
|
|
142
|
+
meta: { title: 'About Us' },
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
expect(route.meta).toEqual({ title: 'About Us' })
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('creates a route with children', () => {
|
|
149
|
+
const route = Route('/dashboard', 'Dashboard', {
|
|
150
|
+
children: [
|
|
151
|
+
Route('/dashboard/stats', 'Stats'),
|
|
152
|
+
Route('/dashboard/settings', 'Settings'),
|
|
153
|
+
],
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
expect(route.children).toHaveLength(2)
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it('creates a protected route', () => {
|
|
160
|
+
const route = Route('/admin', 'Admin', {
|
|
161
|
+
protected: true,
|
|
162
|
+
roles: ['admin'],
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
expect(route.protected).toBe(true)
|
|
166
|
+
expect(route.roles).toContain('admin')
|
|
167
|
+
})
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
describe('State helper', () => {
|
|
171
|
+
it('creates a state config', () => {
|
|
172
|
+
const state = State({
|
|
173
|
+
library: 'zustand',
|
|
174
|
+
schema: {
|
|
175
|
+
user: 'Current user',
|
|
176
|
+
settings: 'User settings',
|
|
177
|
+
},
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
expect(state.library).toBe('zustand')
|
|
181
|
+
expect(state.schema).toHaveProperty('user')
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
it('supports persistence config', () => {
|
|
185
|
+
const state = State({
|
|
186
|
+
library: 'zustand',
|
|
187
|
+
schema: {},
|
|
188
|
+
persistence: {
|
|
189
|
+
type: 'local',
|
|
190
|
+
key: 'app-state',
|
|
191
|
+
},
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
expect(state.persistence?.type).toBe('local')
|
|
195
|
+
expect(state.persistence?.key).toBe('app-state')
|
|
196
|
+
})
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
describe('Auth helper', () => {
|
|
200
|
+
it('creates an auth config', () => {
|
|
201
|
+
const auth = Auth({
|
|
202
|
+
provider: 'clerk',
|
|
203
|
+
protectedRoutes: ['/dashboard', '/profile'],
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
expect(auth.provider).toBe('clerk')
|
|
207
|
+
expect(auth.protectedRoutes).toHaveLength(2)
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
it('supports roles', () => {
|
|
211
|
+
const auth = Auth({
|
|
212
|
+
provider: 'auth0',
|
|
213
|
+
protectedRoutes: ['/admin'],
|
|
214
|
+
roles: ['admin', 'user'],
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
expect(auth.roles).toContain('admin')
|
|
218
|
+
})
|
|
219
|
+
})
|
|
220
|
+
})
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Content functionality
|
|
3
|
+
*
|
|
4
|
+
* Covers content creation and workflow helper.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, beforeEach } from 'vitest'
|
|
8
|
+
import { Content, Workflow, registry } from '../src/index.js'
|
|
9
|
+
|
|
10
|
+
describe('Content', () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
registry.clear()
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
describe('Content creation', () => {
|
|
16
|
+
it('creates content with basic config', () => {
|
|
17
|
+
const content = Content({
|
|
18
|
+
id: 'blog',
|
|
19
|
+
name: 'Blog Posts',
|
|
20
|
+
description: 'Blog content',
|
|
21
|
+
version: '1.0.0',
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
expect(content.id).toBe('blog')
|
|
25
|
+
expect(content.name).toBe('Blog Posts')
|
|
26
|
+
expect(content.type).toBe('content')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('defaults format to markdown', () => {
|
|
30
|
+
const content = Content({
|
|
31
|
+
id: 'docs',
|
|
32
|
+
name: 'Documentation',
|
|
33
|
+
description: 'Docs content',
|
|
34
|
+
version: '1.0.0',
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
expect(content.format).toBe('markdown')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('supports mdx format', () => {
|
|
41
|
+
const content = Content({
|
|
42
|
+
id: 'interactive-docs',
|
|
43
|
+
name: 'Interactive Docs',
|
|
44
|
+
description: 'MDX content',
|
|
45
|
+
version: '1.0.0',
|
|
46
|
+
format: 'mdx',
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
expect(content.format).toBe('mdx')
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('creates content with source', () => {
|
|
53
|
+
const content = Content({
|
|
54
|
+
id: 'blog',
|
|
55
|
+
name: 'Blog Posts',
|
|
56
|
+
description: 'Blog content',
|
|
57
|
+
version: '1.0.0',
|
|
58
|
+
source: './content/blog',
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
expect(content.source).toBe('./content/blog')
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('creates content with frontmatter schema', () => {
|
|
65
|
+
const content = Content({
|
|
66
|
+
id: 'blog',
|
|
67
|
+
name: 'Blog Posts',
|
|
68
|
+
description: 'Blog content',
|
|
69
|
+
version: '1.0.0',
|
|
70
|
+
frontmatter: {
|
|
71
|
+
title: 'Post title',
|
|
72
|
+
author: 'Author name',
|
|
73
|
+
date: 'Publication date (date)',
|
|
74
|
+
tags: ['Array of tags'],
|
|
75
|
+
},
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
expect(content.frontmatter?.title).toBe('Post title')
|
|
79
|
+
expect(content.frontmatter?.author).toBe('Author name')
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('creates content with categories', () => {
|
|
83
|
+
const content = Content({
|
|
84
|
+
id: 'blog',
|
|
85
|
+
name: 'Blog Posts',
|
|
86
|
+
description: 'Blog content',
|
|
87
|
+
version: '1.0.0',
|
|
88
|
+
categories: ['Technology', 'Business', 'Design'],
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
expect(content.categories).toHaveLength(3)
|
|
92
|
+
expect(content.categories).toContain('Technology')
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('creates content with workflow', () => {
|
|
96
|
+
const content = Content({
|
|
97
|
+
id: 'blog',
|
|
98
|
+
name: 'Blog Posts',
|
|
99
|
+
description: 'Blog content',
|
|
100
|
+
version: '1.0.0',
|
|
101
|
+
workflow: Workflow({
|
|
102
|
+
states: ['draft', 'review', 'published'],
|
|
103
|
+
initialState: 'draft',
|
|
104
|
+
transitions: [
|
|
105
|
+
{ from: 'draft', to: 'review', action: 'submit' },
|
|
106
|
+
{ from: 'review', to: 'published', action: 'approve' },
|
|
107
|
+
],
|
|
108
|
+
}),
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
expect(content.workflow?.states).toHaveLength(3)
|
|
112
|
+
expect(content.workflow?.initialState).toBe('draft')
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('registers content automatically', () => {
|
|
116
|
+
Content({
|
|
117
|
+
id: 'auto-registered',
|
|
118
|
+
name: 'Auto Registered',
|
|
119
|
+
description: 'Automatically registered',
|
|
120
|
+
version: '1.0.0',
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
expect(registry.get('auto-registered')).toBeDefined()
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
describe('Workflow helper', () => {
|
|
128
|
+
it('creates a workflow with states', () => {
|
|
129
|
+
const workflow = Workflow({
|
|
130
|
+
states: ['draft', 'review', 'published', 'archived'],
|
|
131
|
+
initialState: 'draft',
|
|
132
|
+
transitions: [],
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
expect(workflow.states).toHaveLength(4)
|
|
136
|
+
expect(workflow.initialState).toBe('draft')
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it('creates a workflow with transitions', () => {
|
|
140
|
+
const workflow = Workflow({
|
|
141
|
+
states: ['draft', 'review', 'published'],
|
|
142
|
+
initialState: 'draft',
|
|
143
|
+
transitions: [
|
|
144
|
+
{ from: 'draft', to: 'review', action: 'submit' },
|
|
145
|
+
{ from: 'review', to: 'published', action: 'approve' },
|
|
146
|
+
{ from: 'review', to: 'draft', action: 'reject' },
|
|
147
|
+
],
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
expect(workflow.transitions).toHaveLength(3)
|
|
151
|
+
expect(workflow.transitions?.[0]?.from).toBe('draft')
|
|
152
|
+
expect(workflow.transitions?.[0]?.to).toBe('review')
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('supports approvals config', () => {
|
|
156
|
+
const workflow = Workflow({
|
|
157
|
+
states: ['draft', 'review', 'published'],
|
|
158
|
+
initialState: 'draft',
|
|
159
|
+
transitions: [
|
|
160
|
+
{ from: 'draft', to: 'review', action: 'submit' },
|
|
161
|
+
],
|
|
162
|
+
approvals: [
|
|
163
|
+
{ state: 'review', roles: ['editor', 'admin'] },
|
|
164
|
+
],
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
expect(workflow.approvals).toHaveLength(1)
|
|
168
|
+
expect(workflow.approvals?.[0]?.roles).toContain('editor')
|
|
169
|
+
})
|
|
170
|
+
})
|
|
171
|
+
})
|