Skip to content

Commit 0619d1d

Browse files
committed
test(tests): add Helper and Index tests and extend Constant Create Render Validator 🎲
- Add Constant tests for allowedNodeKeys, allowedPropsKeys, containerTags, tagAliases and svgNamespace - Add Create tests for void tag with empty children, freeze attrs, reject root not array, definition not object - Add Helper.test.ts for el.root, el.div, el.node, void tags, and edge cases - Add Index.test.ts for default export, named create/render/el, and create error paths - Add Render mockDocument and tests for createElementForNode and render smoke - Add Validator tests for empty root, void tag with empty children, id not string, void br with children - Rename test variables to validatedNode, validatedDefinition, recordedSetPropertyCalls, expectedVoidTags
1 parent f54694a commit 0619d1d

File tree

6 files changed

+438
-41
lines changed

6 files changed

+438
-41
lines changed

tests/Constant.test.ts

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,51 @@
11
import { assertEquals } from '@std/assert'
22
import Constant from '@app/Constant.ts'
33

4-
Deno.test('Constant.svgNs is SVG namespace string', () => {
5-
assertEquals(Constant.svgNs, 'http://www.w3.org/2000/svg')
4+
Deno.test('Constant.allowedNodeKeys contains all required keys', () => {
5+
const requiredKeys = [
6+
'type',
7+
'id',
8+
'layout',
9+
'style',
10+
'attrs',
11+
'content',
12+
'src',
13+
'alt',
14+
'children'
15+
]
16+
assertEquals(Constant.allowedNodeKeys.size, requiredKeys.length)
17+
for (const key of requiredKeys) {
18+
assertEquals(Constant.allowedNodeKeys.has(key), true)
19+
}
20+
})
21+
22+
Deno.test('Constant.allowedPropsKeys excludes type and children', () => {
23+
assertEquals(Constant.allowedPropsKeys.has('type'), false)
24+
assertEquals(Constant.allowedPropsKeys.has('children'), false)
25+
assertEquals(Constant.allowedPropsKeys.has('id'), true)
26+
assertEquals(Constant.allowedPropsKeys.size, Constant.allowedNodeKeys.size - 2)
27+
})
28+
29+
Deno.test('Constant.containerTags does not include void tags', () => {
30+
for (const tag of Constant.voidTags) {
31+
assertEquals(Constant.containerTags.includes(tag), false)
32+
}
33+
})
34+
35+
Deno.test('Constant.svgNamespace is SVG namespace string', () => {
36+
assertEquals(Constant.svgNamespace, 'http://www.w3.org/2000/svg')
37+
})
38+
39+
Deno.test('Constant.tagAliases equals containerTags plus voidTags', () => {
40+
const combinedTagList = [...Constant.containerTags, ...Constant.voidTags]
41+
assertEquals(Constant.tagAliases.length, combinedTagList.length)
42+
for (let i = 0; i < combinedTagList.length; i++) {
43+
assertEquals(Constant.tagAliases[i], combinedTagList[i])
44+
}
645
})
746

847
Deno.test('Constant.voidTags contains all expected void tags', () => {
9-
const expected = [
48+
const expectedVoidTags = [
1049
'area',
1150
'base',
1251
'br',
@@ -22,8 +61,8 @@ Deno.test('Constant.voidTags contains all expected void tags', () => {
2261
'track',
2362
'wbr'
2463
]
25-
assertEquals(Constant.voidTags.size, expected.length)
26-
for (const tag of expected) {
64+
assertEquals(Constant.voidTags.size, expectedVoidTags.length)
65+
for (const tag of expectedVoidTags) {
2766
assertEquals(Constant.voidTags.has(tag), true)
2867
}
2968
})

tests/Create.test.ts

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,28 @@ Deno.test('create() accepts empty root array', () => {
77
assertEquals(Object.isFrozen(schema), true)
88
})
99

10+
Deno.test('create() accepts void tag with empty children array', () => {
11+
const schema = Create.create({ root: [{ type: 'img', children: [] }] })
12+
assertEquals(schema.root[0]?.type, 'img')
13+
})
14+
15+
Deno.test('create() freezes attrs object', () => {
16+
const schema = Create.create({
17+
root: [{ type: 'div', attrs: { 'data-x': 'y' } }]
18+
})
19+
const node = schema.root[0]
20+
assertEquals(Object.isFrozen(node?.attrs), true)
21+
})
22+
23+
Deno.test('create() freezes layout and style objects', () => {
24+
const schema = Create.create({
25+
root: [{ type: 'div', layout: { width: 1 }, style: { fill: '#f00' } }]
26+
})
27+
const node = schema.root[0]
28+
assertEquals(Object.isFrozen(node?.layout), true)
29+
assertEquals(Object.isFrozen(node?.style), true)
30+
})
31+
1032
Deno.test('create() freezes nested children', () => {
1133
const schema = Create.create({
1234
root: [
@@ -26,15 +48,6 @@ Deno.test('create() freezes nested children', () => {
2648
assertEquals(div?.children?.[0]?.content, 'a')
2749
})
2850

29-
Deno.test('create() freezes layout and style objects', () => {
30-
const schema = Create.create({
31-
root: [{ type: 'div', layout: { width: 1 }, style: { fill: '#f00' } }]
32-
})
33-
const node = schema.root[0]
34-
assertEquals(Object.isFrozen(node?.layout), true)
35-
assertEquals(Object.isFrozen(node?.style), true)
36-
})
37-
3851
Deno.test('create() normalises tag to lowercase', () => {
3952
const schema = Create.create({ root: [{ type: 'DIV' }] })
4053
assertEquals(schema.root[0]?.type, 'div')
@@ -60,6 +73,11 @@ Deno.test('create() preserves layout style attrs id src alt', () => {
6073
assertEquals(node?.attrs?.['data-test'], 'ok')
6174
})
6275

76+
Deno.test('create() rejects definition with root not array', () => {
77+
assertThrows(() => Create.create({ root: null }), Error, 'definition.root must be an array')
78+
assertThrows(() => Create.create({ root: {} }), Error, 'definition.root must be an array')
79+
})
80+
6381
Deno.test('create() returns frozen schema with root', () => {
6482
const schema = Create.create({
6583
root: [{ type: 'div', content: 'hello' }]
@@ -71,6 +89,13 @@ Deno.test('create() returns frozen schema with root', () => {
7189
assertEquals(Object.isFrozen(schema.root), true)
7290
})
7391

92+
Deno.test('create() throws when definition is null or not object', () => {
93+
assertThrows(() => Create.create(null), Error, 'definition must be an object')
94+
assertThrows(() => Create.create(undefined), Error, 'definition must be an object')
95+
assertThrows(() => Create.create(1), Error, 'definition must be an object')
96+
assertThrows(() => Create.create(''), Error, 'definition must be an object')
97+
})
98+
7499
Deno.test('create() throws when root is missing', () => {
75100
assertThrows(() => Create.create({}), Error, 'definition.root must be an array')
76101
})

tests/Helper.test.ts

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
import { assertEquals, assertThrows } from '@std/assert'
2+
import Helper from '@app/Helper.ts'
3+
import Create from '@app/Create.ts'
4+
5+
const el = Helper.el
6+
7+
Deno.test('create(el.root(...)) produces valid schema', () => {
8+
const schema = Create.create(
9+
el.root(
10+
el.header(
11+
{ id: 'header', layout: { width: '100%', height: 56 }, style: { fill: '#1a1a2e' } },
12+
el.h1({ id: 'title', style: { font: '20px sans-serif', fill: '#eee' } }, 'Hello')
13+
),
14+
el.main({ layout: { width: '100%', flex: 1 } }, el.a({ attrs: { href: '/' } }, 'Home'))
15+
)
16+
)
17+
assertEquals(schema.root.length, 2)
18+
assertEquals(schema.root[0]?.type, 'header')
19+
assertEquals(schema.root[0]?.children?.[0]?.type, 'h1')
20+
assertEquals(schema.root[0]?.children?.[0]?.content, 'Hello')
21+
assertEquals(schema.root[1]?.type, 'main')
22+
assertEquals(schema.root[1]?.children?.[0]?.type, 'a')
23+
assertEquals(schema.root[1]?.children?.[0]?.content, 'Home')
24+
})
25+
26+
Deno.test('el.div with children returns children array', () => {
27+
const node = el.div(el.span('a'), el.span('b'))
28+
assertEquals(node.type, 'div')
29+
assertEquals(node.children?.length, 2)
30+
assertEquals(node.children?.[0]?.content, 'a')
31+
assertEquals(node.children?.[1]?.content, 'b')
32+
})
33+
34+
Deno.test('el.div with props and children', () => {
35+
const node = el.div({ id: 'box' }, el.span('a'), el.span('b'))
36+
assertEquals(node.type, 'div')
37+
assertEquals(node.id, 'box')
38+
assertEquals(node.children?.length, 2)
39+
})
40+
41+
Deno.test('el.div with props and string returns content', () => {
42+
const node = el.div({ id: 'x' }, 'hello')
43+
assertEquals(node.type, 'div')
44+
assertEquals(node.id, 'x')
45+
assertEquals(node.content, 'hello')
46+
})
47+
48+
Deno.test('el.div with props returns node with id layout style attrs', () => {
49+
const node = el.div({
50+
id: 'x',
51+
layout: { width: 100 },
52+
style: { fill: '#f00' },
53+
attrs: { 'data-test': 'ok' }
54+
})
55+
assertEquals(node.type, 'div')
56+
assertEquals(node.id, 'x')
57+
assertEquals(node.layout?.width, 100)
58+
assertEquals(node.style?.fill, '#f00')
59+
assertEquals(node.attrs?.['data-test'], 'ok')
60+
})
61+
62+
Deno.test('el.div with string returns content', () => {
63+
const node = el.div('hello')
64+
assertEquals(node.type, 'div')
65+
assertEquals(node.content, 'hello')
66+
})
67+
68+
Deno.test('el.div() returns node with type div', () => {
69+
const node = el.div()
70+
assertEquals(node.type, 'div')
71+
})
72+
73+
Deno.test('el.img with no args returns img node', () => {
74+
const node = el.img()
75+
assertEquals(node.type, 'img')
76+
})
77+
78+
Deno.test('el.img with content throws', () => {
79+
assertThrows(
80+
() => el.img('text' as unknown as Record<string, unknown>),
81+
Error,
82+
'void tag "img" must not have content or children'
83+
)
84+
})
85+
86+
Deno.test('el.img with props returns img with src alt', () => {
87+
const node = el.img({ src: '/x.png', alt: 'image' })
88+
assertEquals(node.type, 'img')
89+
assertEquals(node.src, '/x.png')
90+
assertEquals(node.alt, 'image')
91+
})
92+
93+
Deno.test('el.node container with string and children throws', () => {
94+
assertThrows(
95+
() => el.node('div', 'text', el.span('x')),
96+
Error,
97+
'content (string) and children cannot be mixed'
98+
)
99+
})
100+
101+
Deno.test('el.node normalises tag to lowercase', () => {
102+
const node = el.node('DIV')
103+
assertEquals(node.type, 'div')
104+
})
105+
106+
Deno.test('el.node void tag with children throws', () => {
107+
assertThrows(
108+
() => el.node('img', el.span('x')),
109+
Error,
110+
'void tag "img" must not have content or children'
111+
)
112+
})
113+
114+
Deno.test('el.node void tag with only props does not throw', () => {
115+
const node = el.input({ id: 'field', attrs: { placeholder: 'Enter' } })
116+
assertEquals(node.type, 'input')
117+
assertEquals(node.id, 'field')
118+
assertEquals(node.attrs?.['placeholder'], 'Enter')
119+
})
120+
121+
Deno.test('el.node with first child then array of children', () => {
122+
const node = el.node('div', el.span('a'), [el.span('b'), el.span('c')])
123+
assertEquals(node.type, 'div')
124+
assertEquals(node.children?.length, 3)
125+
assertEquals(node.children?.[0]?.content, 'a')
126+
assertEquals(node.children?.[1]?.content, 'b')
127+
assertEquals(node.children?.[2]?.content, 'c')
128+
})
129+
130+
Deno.test('el.root with single node returns definition with one node', () => {
131+
const rootDefinition = el.root(el.p('only'))
132+
assertEquals(rootDefinition.root.length, 1)
133+
assertEquals(rootDefinition.root[0]?.type, 'p')
134+
assertEquals(rootDefinition.root[0]?.content, 'only')
135+
})
136+
137+
Deno.test('el.root() returns empty root', () => {
138+
const rootDefinition = el.root()
139+
assertEquals(rootDefinition.root.length, 0)
140+
})
141+
142+
Deno.test('el.root([n1, n2]) accepts single array', () => {
143+
const rootDefinition = el.root([el.div(), el.span()])
144+
assertEquals(rootDefinition.root.length, 2)
145+
assertEquals(rootDefinition.root[0]?.type, 'div')
146+
assertEquals(rootDefinition.root[1]?.type, 'span')
147+
})
148+
149+
Deno.test('el.root(n1, n2) returns definition with two nodes', () => {
150+
const rootDefinition = el.root(el.div(), el.span())
151+
assertEquals(rootDefinition.root.length, 2)
152+
assertEquals(rootDefinition.root[0]?.type, 'div')
153+
assertEquals(rootDefinition.root[1]?.type, 'span')
154+
})
155+
156+
Deno.test('el.section exists and returns node with type section', () => {
157+
const node = el.section(el.header('Header'))
158+
assertEquals(node.type, 'section')
159+
assertEquals(node.children?.length, 1)
160+
assertEquals(node.children?.[0]?.type, 'header')
161+
})
162+
163+
Deno.test('Helper.node() static equals Helper.el.node()', () => {
164+
const resultA = Helper.node('span', 'text')
165+
const resultB = Helper.el.node('span', 'text')
166+
assertEquals(resultA.type, resultB.type)
167+
assertEquals(resultA.content, resultB.content)
168+
})
169+
170+
Deno.test('Helper.node() static equals el.node()', () => {
171+
const resultA = Helper.node('div', { id: 'x' })
172+
const resultB = el.node('div', { id: 'x' })
173+
assertEquals(resultA.type, resultB.type)
174+
assertEquals(resultA.id, resultB.id)
175+
})
176+
177+
Deno.test('Helper.root() and el.root() are same', () => {
178+
const resultA = Helper.root(el.div())
179+
const resultB = el.root(el.div())
180+
assertEquals(resultA.root.length, resultB.root.length)
181+
assertEquals(resultA.root[0]?.type, resultB.root[0]?.type)
182+
})
183+
184+
Deno.test('Helper.root() static equals el.root()', () => {
185+
const resultA = Helper.root(el.div(), el.span())
186+
const resultB = el.root(el.div(), el.span())
187+
assertEquals(resultA.root.length, resultB.root.length)
188+
assertEquals(resultA.root[0]?.type, resultB.root[0]?.type)
189+
assertEquals(resultA.root[1]?.type, resultB.root[1]?.type)
190+
})
191+
192+
Deno.test('node strips unknown keys from props', () => {
193+
const node = el.node('div', { id: 'x', foo: 'bar' } as unknown as Record<string, unknown>)
194+
assertEquals(node.type, 'div')
195+
assertEquals(node.id, 'x')
196+
assertEquals('foo' in node, false)
197+
})

tests/Index.test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { assertEquals, assertThrows } from '@std/assert'
2+
import Schema2UI, { create, el, render } from '@app/index.ts'
3+
import Create from '@app/Create.ts'
4+
import Helper from '@app/Helper.ts'
5+
6+
Deno.test('create() throws when definition is not object', () => {
7+
assertThrows(() => create(null), Error, 'definition must be an object')
8+
assertThrows(() => create(undefined), Error, 'definition must be an object')
9+
assertThrows(() => create(''), Error, 'definition must be an object')
10+
})
11+
12+
Deno.test('create() throws when root is not array', () => {
13+
assertThrows(() => create({}), Error, 'definition.root must be an array')
14+
assertThrows(() => create({ root: null }), Error, 'definition.root must be an array')
15+
})
16+
17+
Deno.test('default export call returns object with create, render, el', () => {
18+
const api = Schema2UI()
19+
assertEquals(typeof api.create, 'function')
20+
assertEquals(typeof api.render, 'function')
21+
assertEquals(api.el != null, true)
22+
assertEquals(api.el.root != null, true)
23+
assertEquals(api.el.node != null, true)
24+
})
25+
26+
Deno.test('named create() equals Schema2UI.create()', () => {
27+
const definition = { root: [{ type: 'span' }] }
28+
const createdA = create(definition)
29+
const createdB = Schema2UI.create(definition)
30+
assertEquals(createdA.root.length, createdB.root.length)
31+
assertEquals(createdA.root[0]?.type, createdB.root[0]?.type)
32+
})
33+
34+
Deno.test('named el equals Schema2UI.el', () => {
35+
assertEquals(el, Schema2UI.el)
36+
assertEquals(el.root, Schema2UI.el.root)
37+
})
38+
39+
Deno.test('render is a function', () => {
40+
assertEquals(typeof render, 'function')
41+
})
42+
43+
Deno.test('Schema2UI.create equals Create.create', () => {
44+
const definition = { root: [{ type: 'div', content: 'x' }] }
45+
const createdA = Schema2UI.create(definition)
46+
const createdB = Create.create(definition)
47+
assertEquals(createdA.root.length, createdB.root.length)
48+
assertEquals(createdA.root[0]?.type, createdB.root[0]?.type)
49+
assertEquals(createdA.root[0]?.content, createdB.root[0]?.content)
50+
})
51+
52+
Deno.test('Schema2UI.el equals Helper.el', () => {
53+
assertEquals(Schema2UI.el, Helper.el)
54+
assertEquals(Schema2UI.el.root, Helper.el.root)
55+
assertEquals(Schema2UI.el.node, Helper.el.node)
56+
})

0 commit comments

Comments
 (0)