adapt-schemas 1.0.0
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 +292 -0
- package/index.js +7 -0
- package/lib/Keywords.js +98 -0
- package/lib/Schema.js +389 -0
- package/lib/Schemas.js +370 -0
- package/lib/XSSDefaults.js +145 -0
- package/package.json +33 -0
- package/schema/base.schema.json +13 -0
- package/test.js +289 -0
package/test.js
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test script for the Adapt Schema Library
|
|
3
|
+
*/
|
|
4
|
+
import Schemas from './index.js'
|
|
5
|
+
import path from 'path'
|
|
6
|
+
import { fileURLToPath } from 'url'
|
|
7
|
+
import fs from 'fs/promises'
|
|
8
|
+
|
|
9
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
10
|
+
const hasSpecifiedPath = Boolean(process.argv[2])
|
|
11
|
+
|
|
12
|
+
async function setupTestSchemas() {
|
|
13
|
+
if (hasSpecifiedPath) return
|
|
14
|
+
// Create test schema directory
|
|
15
|
+
const testSchemaDir = path.join(__dirname, 'test-schemas')
|
|
16
|
+
await fs.mkdir(testSchemaDir, { recursive: true })
|
|
17
|
+
|
|
18
|
+
// Create a course schema with _globals
|
|
19
|
+
const courseSchema = {
|
|
20
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
21
|
+
"$anchor": "course",
|
|
22
|
+
"$merge": {
|
|
23
|
+
"source": { "$ref": "base" },
|
|
24
|
+
"with": {
|
|
25
|
+
"properties": {
|
|
26
|
+
"title": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"description": "Course title",
|
|
29
|
+
"default": "Untitled Course"
|
|
30
|
+
},
|
|
31
|
+
"description": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"description": "Course description",
|
|
34
|
+
"default": ""
|
|
35
|
+
},
|
|
36
|
+
"_globals": {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"description": "Global settings",
|
|
39
|
+
"properties": {
|
|
40
|
+
"_accessibility": {
|
|
41
|
+
"type": "object",
|
|
42
|
+
"properties": {
|
|
43
|
+
"_isEnabled": {
|
|
44
|
+
"type": "boolean",
|
|
45
|
+
"default": true
|
|
46
|
+
},
|
|
47
|
+
"skipNavigationText": {
|
|
48
|
+
"type": "string",
|
|
49
|
+
"default": "Skip navigation"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"required": [
|
|
53
|
+
"skipNavigationText"
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
"_extensions": {
|
|
57
|
+
"type": "object",
|
|
58
|
+
"properties": {
|
|
59
|
+
"_trickle": {
|
|
60
|
+
"type": "object",
|
|
61
|
+
"properties": {
|
|
62
|
+
"incompleteContent": {
|
|
63
|
+
"type": "string",
|
|
64
|
+
"default": "There is incomplete content above"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"required": ["title"]
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Create a content schema
|
|
79
|
+
const contentSchema = {
|
|
80
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
81
|
+
"$anchor": "content",
|
|
82
|
+
"$merge": {
|
|
83
|
+
"source": { "$ref": "base" },
|
|
84
|
+
"with": {
|
|
85
|
+
"properties": {
|
|
86
|
+
"_type": {
|
|
87
|
+
"type": "string",
|
|
88
|
+
"description": "Content type"
|
|
89
|
+
},
|
|
90
|
+
"body": {
|
|
91
|
+
"type": "string",
|
|
92
|
+
"description": "Content body",
|
|
93
|
+
"default": ""
|
|
94
|
+
},
|
|
95
|
+
"_isOptional": {
|
|
96
|
+
"type": "boolean",
|
|
97
|
+
"default": false
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Create a component schema that extends content
|
|
105
|
+
const componentSchema = {
|
|
106
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
107
|
+
"$anchor": "component",
|
|
108
|
+
"$merge": {
|
|
109
|
+
"source": { "$ref": "content" },
|
|
110
|
+
"with": {
|
|
111
|
+
"properties": {
|
|
112
|
+
"_component": {
|
|
113
|
+
"type": "string",
|
|
114
|
+
"description": "Component type"
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
"required": ["_component"]
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Create a patch schema that extends course
|
|
123
|
+
const coursePatchSchema = {
|
|
124
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
125
|
+
"$anchor": "course-extension",
|
|
126
|
+
"$patch": {
|
|
127
|
+
"source": { "$ref": "course" },
|
|
128
|
+
"with": {
|
|
129
|
+
"properties": {
|
|
130
|
+
"_globals": {
|
|
131
|
+
"type": "object",
|
|
132
|
+
"properties": {
|
|
133
|
+
"_myPlugin": {
|
|
134
|
+
"type": "object",
|
|
135
|
+
"properties": {
|
|
136
|
+
"buttonLabel": {
|
|
137
|
+
"type": "string",
|
|
138
|
+
"default": "Click me"
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
await fs.writeFile(
|
|
150
|
+
path.join(testSchemaDir, 'course.schema.json'),
|
|
151
|
+
JSON.stringify(courseSchema, null, 2)
|
|
152
|
+
)
|
|
153
|
+
await fs.writeFile(
|
|
154
|
+
path.join(testSchemaDir, 'content.schema.json'),
|
|
155
|
+
JSON.stringify(contentSchema, null, 2)
|
|
156
|
+
)
|
|
157
|
+
await fs.writeFile(
|
|
158
|
+
path.join(testSchemaDir, 'component.schema.json'),
|
|
159
|
+
JSON.stringify(componentSchema, null, 2)
|
|
160
|
+
)
|
|
161
|
+
await fs.writeFile(
|
|
162
|
+
path.join(testSchemaDir, 'course-extension.schema.json'),
|
|
163
|
+
JSON.stringify(coursePatchSchema, null, 2)
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
return testSchemaDir
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function runTests() {
|
|
170
|
+
console.log('=== Adapt Schema Library Tests ===\n')
|
|
171
|
+
|
|
172
|
+
const testSchemaDir = hasSpecifiedPath
|
|
173
|
+
? path.join(__dirname, process.argv[2])
|
|
174
|
+
: await setupTestSchemas()
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
// Test 1: Initialize library
|
|
178
|
+
console.log('Test 1: Initialize library')
|
|
179
|
+
const library = new Schemas({
|
|
180
|
+
enableCache: true,
|
|
181
|
+
directoryReplacements: {
|
|
182
|
+
'$ROOT': process.cwd()
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
await library.init()
|
|
186
|
+
console.log(' ✓ Library initialized\n')
|
|
187
|
+
|
|
188
|
+
// Test 2: Load schemas with glob
|
|
189
|
+
console.log('Test 2: Load schemas with glob patterns')
|
|
190
|
+
await library.loadSchemas('**/*.schema.json', {
|
|
191
|
+
cwd: testSchemaDir,
|
|
192
|
+
ignore: ['**/excluded/**']
|
|
193
|
+
})
|
|
194
|
+
const schemaNames = library.getSchemaNames()
|
|
195
|
+
console.log(` ✓ Loaded schemas: ${schemaNames.join(', ')}\n`)
|
|
196
|
+
|
|
197
|
+
// Test 3: Get schema
|
|
198
|
+
console.log('Test 3: Get built schema')
|
|
199
|
+
const courseBuilt = await library.getBuiltSchema('course')
|
|
200
|
+
console.log(` ✓ Course schema has properties: ${Object.keys(courseBuilt.properties).join(', ')}\n`)
|
|
201
|
+
|
|
202
|
+
// Test 4: Get defaults
|
|
203
|
+
console.log('Test 4: Get schema defaults')
|
|
204
|
+
const courseDefaults = await library.getSchemaDefaults('course')
|
|
205
|
+
console.log(` ✓ Course defaults:`, JSON.stringify(courseDefaults, null, 4).split('\n').map(l => ' ' + l).join('\n'), '\n')
|
|
206
|
+
|
|
207
|
+
// Test 5: Get _globals defaults
|
|
208
|
+
console.log('Test 5: Get _globals defaults')
|
|
209
|
+
const globalsDefaults = await library.getGlobalsDefaults('course')
|
|
210
|
+
console.log(` ✓ _globals defaults:`, JSON.stringify(globalsDefaults, null, 4).split('\n').map(l => ' ' + l).join('\n'), '\n')
|
|
211
|
+
|
|
212
|
+
// Test 6: Validate data
|
|
213
|
+
console.log('Test 6: Validate data')
|
|
214
|
+
const validData = await library.validate('course', {
|
|
215
|
+
title: 'My Course'
|
|
216
|
+
})
|
|
217
|
+
console.log(` ✓ Validated data has title: "${validData.title}"`)
|
|
218
|
+
console.log(` ✓ Default description applied: "${validData.description}"\n`)
|
|
219
|
+
|
|
220
|
+
// Test 7: Validate with error (missing required field without defaults)
|
|
221
|
+
console.log('Test 7: Validation error handling')
|
|
222
|
+
try {
|
|
223
|
+
await library.validate('course', {
|
|
224
|
+
// Missing required title - disable defaults to trigger required error
|
|
225
|
+
_globals: { _accessibility: {} }
|
|
226
|
+
}, { useDefaults: false, ignoreRequired: false })
|
|
227
|
+
console.log(' ✗ Should have thrown validation error\n')
|
|
228
|
+
} catch (e) {
|
|
229
|
+
console.log(` ✓ Caught validation error: ${e.code}`)
|
|
230
|
+
console.log(` ✓ Error message: ${e.message}\n`)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Test 7b: Validate with type error
|
|
234
|
+
console.log('Test 7b: Type validation error')
|
|
235
|
+
try {
|
|
236
|
+
await library.validate('course', {
|
|
237
|
+
title: 12345 // Should be string, not number
|
|
238
|
+
})
|
|
239
|
+
console.log(' ✗ Should have thrown validation error\n')
|
|
240
|
+
} catch (e) {
|
|
241
|
+
console.log(` ✓ Caught type validation error: ${e.code}\n`)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Test 8: Schema inheritance
|
|
245
|
+
console.log('Test 8: Schema inheritance')
|
|
246
|
+
const componentBuilt = await library.getBuiltSchema('component')
|
|
247
|
+
const hasInheritedBody = componentBuilt.properties.body !== undefined
|
|
248
|
+
const hasOwnComponent = componentBuilt.properties._component !== undefined
|
|
249
|
+
console.log(` ✓ Component has inherited 'body' property: ${hasInheritedBody}`)
|
|
250
|
+
console.log(` ✓ Component has own '_component' property: ${hasOwnComponent}\n`)
|
|
251
|
+
|
|
252
|
+
// Test 9: Schema info
|
|
253
|
+
console.log('Test 9: Schema info')
|
|
254
|
+
const info = library.getSchemaInfo()
|
|
255
|
+
console.log(' ✓ Schema info:')
|
|
256
|
+
Object.entries(info).forEach(([name, details]) => {
|
|
257
|
+
console.log(` - ${name}: extensions=[${details.extensions.join(', ')}], isPatch=${details.isPatch}`)
|
|
258
|
+
})
|
|
259
|
+
console.log('')
|
|
260
|
+
|
|
261
|
+
// Test 10: Events
|
|
262
|
+
console.log('Test 10: Event handling')
|
|
263
|
+
library.on('schemaRegistered', (name) => {
|
|
264
|
+
console.log(` ✓ Event received: schemaRegistered (${name})`)
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
// Create another test schema to trigger the event
|
|
268
|
+
const newSchemaPath = path.join(testSchemaDir, 'test-event.schema.json')
|
|
269
|
+
await fs.writeFile(newSchemaPath, JSON.stringify({
|
|
270
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
271
|
+
"$anchor": "test-event",
|
|
272
|
+
"$merge": {
|
|
273
|
+
"source": { "$ref": "base" },
|
|
274
|
+
"with": { "properties": { "test": { "type": "string" } } }
|
|
275
|
+
}
|
|
276
|
+
}))
|
|
277
|
+
await library.registerSchema(newSchemaPath)
|
|
278
|
+
console.log('')
|
|
279
|
+
|
|
280
|
+
console.log('=== All tests passed! ===')
|
|
281
|
+
|
|
282
|
+
} finally {
|
|
283
|
+
if (hasSpecifiedPath) return
|
|
284
|
+
// Cleanup
|
|
285
|
+
await fs.rm(testSchemaDir, { recursive: true, force: true })
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
runTests().catch(console.error)
|