@lightningjs/blits 0.3.9 → 0.3.11
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/CHANGELOG.md +87 -0
- package/README.md +9 -1
- package/banner.png +0 -0
- package/boilerplate/package.json +1 -1
- package/docs/element_attributes.md +12 -0
- package/package.json +5 -3
- package/src/components/Sprite.js +2 -2
- package/src/components/Text.js +3 -1
- package/src/element.js +29 -4
- package/src/launch.d.ts +1 -1
- package/src/lib/codegenerator/generator.js +27 -13
- package/src/lib/codegenerator/{generator.test.js → generator.test_.js} +6 -2
- package/src/lib/colors/colors.test.js +29 -29
- package/src/lib/reactivity/reactive.js +13 -7
- package/src/lib/setup/props.test.js +6 -3
- package/src/lib/templateparser/parser.js +200 -53
- package/src/lib/templateparser/parser.test.js +216 -36
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
## v0.3.11
|
|
5
|
+
|
|
6
|
+
_17 oct 2023_
|
|
7
|
+
|
|
8
|
+
- Added support for using [percentages](https://lightning-js.github.io/blits/#/element_attributes?id=using-percentages) in dimensions (`w` / `h`) and positioning (`x` / `y`)
|
|
9
|
+
- Refactored template parser to be more robust and throw errors when the template contains a syntax mistake
|
|
10
|
+
- Re-enabled tests (still depends on renderer version bump though)
|
|
11
|
+
- Added the new official Blits logo to the readme!
|
|
12
|
+
|
|
13
|
+
## v0.3.10
|
|
14
|
+
|
|
15
|
+
_9 oct 2023_
|
|
16
|
+
|
|
17
|
+
- Updated L3 renderer dependency to 0.3.6
|
|
18
|
+
- Renamed internal methods for creating textures and shaders
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
## v0.3.9
|
|
22
|
+
|
|
23
|
+
_5 oct 2023_
|
|
24
|
+
|
|
25
|
+
- Updated L3 renderer dependency to 0.3.3
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
## v0.3.8
|
|
29
|
+
|
|
30
|
+
_4 oct 2023_
|
|
31
|
+
|
|
32
|
+
- Added Blits VS code extension to recommended extensions
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
## v0.3.7
|
|
36
|
+
|
|
37
|
+
_3 oct 2023_
|
|
38
|
+
|
|
39
|
+
- Fixed reactivity issue when using `null` values in state
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
## v0.3.6
|
|
43
|
+
|
|
44
|
+
_2 oct 2023_
|
|
45
|
+
|
|
46
|
+
- Bumped version number of dependency in boilerplate
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
## v0.3.5
|
|
50
|
+
|
|
51
|
+
_2 oct 2023_
|
|
52
|
+
|
|
53
|
+
- Added workaround for Vite import chunk url plugin for fontloading
|
|
54
|
+
|
|
55
|
+
## v0.3.4
|
|
56
|
+
|
|
57
|
+
_27 sep 2023_
|
|
58
|
+
|
|
59
|
+
- Fixed issue with re-applying reactivity to already reactive props in forloop
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
## v0.3.3
|
|
63
|
+
|
|
64
|
+
_25 sep 2023_
|
|
65
|
+
|
|
66
|
+
- Added support for `@loaded` and `@error` events on Elements (and Text Component)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
## v0.3.2
|
|
70
|
+
|
|
71
|
+
_22 sep 2023_
|
|
72
|
+
|
|
73
|
+
- Changed name of attribute to reference an Element or Component from `id` to `ref`
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
## v0.3.1
|
|
77
|
+
|
|
78
|
+
_20 sep 2023_
|
|
79
|
+
|
|
80
|
+
- Fixed Blits dependency in create App boilerplate package.json
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
## v0.3.0
|
|
84
|
+
|
|
85
|
+
_20 sep 2023_
|
|
86
|
+
|
|
87
|
+
Initial beta release of Blits ⚡️
|
package/README.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
1
3
|
# Blits - Lightning 3 App Development Framework
|
|
2
4
|
|
|
3
5
|
With Blits (formaly know as Bolt), the App Development Framework for Lightning 3, it becomes a breeze to build great Lightning 3 applications.
|
|
@@ -12,8 +14,14 @@ The Blits framework uses a **readable** XML-style template syntax and comes with
|
|
|
12
14
|
|
|
13
15
|
You may be wondering where the name Blits comes from?
|
|
14
16
|
|
|
15
|
-
Firstly, it short, memorable and has a nice ring to it.
|
|
17
|
+
Firstly, it's short, memorable and just has a nice ring to it.
|
|
18
|
+
|
|
19
|
+
"Blits" is a Dutch word (a significant portion of the Lightning Open Source teams is based in the Netherlands). It means _flashy_ and it's often used to for things that are _great_ or _cool_.
|
|
16
20
|
|
|
17
21
|
Finally it's a nerdy reference to the computer term "Bit Blits" commonly used in graphics processing, for rapidly moving a block of data into memory - quite similar to what Lightning is doing 🙂
|
|
18
22
|
|
|
19
23
|
Check out the [documentation](https://lightning-js.github.io/blits) to learn more.
|
|
24
|
+
|
|
25
|
+
## Changelog
|
|
26
|
+
|
|
27
|
+
The full Changelog can be found [here](./CHANGELOG.md)
|
package/banner.png
ADDED
|
Binary file
|
package/boilerplate/package.json
CHANGED
|
@@ -25,6 +25,18 @@ All positioning and dimension related attributes, when not specified, default to
|
|
|
25
25
|
</Element>
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
+
#### Using percentages
|
|
29
|
+
|
|
30
|
+
Besides using values in pixels (i.e. `w="100" h="300"`), you can also specify _percentages_ for the positioning and dimensions attributes.
|
|
31
|
+
|
|
32
|
+
```xml
|
|
33
|
+
<Element w="400" h="100" x="800" y="900" color="#0284c7">
|
|
34
|
+
<Element w="42%" h="30%" y="5%" x="1%" color="#075985" />
|
|
35
|
+
</Element>
|
|
36
|
+
```
|
|
37
|
+
The percentage specified in `w` and `x` will be calculated as the percentage of the _width_ (`w`) of the parent element.
|
|
38
|
+
And the percentage specified in `h` and `y` will use the _height_ (`h`) of the parent element as the base of the percentage calculation.
|
|
39
|
+
|
|
28
40
|
### Colors
|
|
29
41
|
|
|
30
42
|
By default Elements have a transparent background color. The `color` attribute can be used to give an Element a color.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightningjs/blits",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.11",
|
|
4
4
|
"description": "Blits: The Lightning 3 App Development Framework",
|
|
5
5
|
"bin": "bin/index.cjs",
|
|
6
6
|
"exports": {
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"./fontloader": "./src/fontLoader.js"
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
|
-
"test": "
|
|
12
|
+
"test": "c8 npm run test:run",
|
|
13
|
+
"test:run": "tape '{,!(node_modules)/**/}*.test.js' | tap-diff",
|
|
13
14
|
"lint": "eslint '**/*.js'",
|
|
14
15
|
"lint:fix": "eslint '**/*.js' --fix"
|
|
15
16
|
},
|
|
@@ -28,6 +29,7 @@
|
|
|
28
29
|
"license": "Apache-2.0",
|
|
29
30
|
"devDependencies": {
|
|
30
31
|
"babel-eslint": "^10.1.0",
|
|
32
|
+
"c8": "^8.0.1",
|
|
31
33
|
"eslint": "^8.8.0",
|
|
32
34
|
"eslint-config-prettier": "^8.3.0",
|
|
33
35
|
"eslint-plugin-prettier": "^4.0.0",
|
|
@@ -39,7 +41,7 @@
|
|
|
39
41
|
"tape": "^5.5.0"
|
|
40
42
|
},
|
|
41
43
|
"dependencies": {
|
|
42
|
-
"@lightningjs/renderer": "^0.3.
|
|
44
|
+
"@lightningjs/renderer": "^0.3.6",
|
|
43
45
|
"@lightningjs/vite-plugin-import-chunk-url": "^0.3.0"
|
|
44
46
|
}
|
|
45
47
|
}
|
package/src/components/Sprite.js
CHANGED
|
@@ -36,7 +36,7 @@ export default () =>
|
|
|
36
36
|
: this.map[this.frame]
|
|
37
37
|
|
|
38
38
|
if (this.spriteTexture && options) {
|
|
39
|
-
return this.___renderer.
|
|
39
|
+
return this.___renderer.createTexture('SubTexture', {
|
|
40
40
|
texture: this.spriteTexture,
|
|
41
41
|
x: options.x,
|
|
42
42
|
y: options.y,
|
|
@@ -48,7 +48,7 @@ export default () =>
|
|
|
48
48
|
},
|
|
49
49
|
hooks: {
|
|
50
50
|
ready() {
|
|
51
|
-
this.spriteTexture = this.___renderer.
|
|
51
|
+
this.spriteTexture = this.___renderer.createTexture('ImageTexture', {
|
|
52
52
|
src: `${window.location.protocol}//${window.location.host}/${this.image}`,
|
|
53
53
|
})
|
|
54
54
|
},
|
package/src/components/Text.js
CHANGED
|
@@ -32,6 +32,7 @@ export default () =>
|
|
|
32
32
|
stretch="$stretch"
|
|
33
33
|
contain="$_contain"
|
|
34
34
|
:w="$w"
|
|
35
|
+
:h="$h"
|
|
35
36
|
:textAlign="$align"
|
|
36
37
|
@loaded="$@loaded"
|
|
37
38
|
@error="$@error"
|
|
@@ -54,6 +55,7 @@ export default () =>
|
|
|
54
55
|
'stretch',
|
|
55
56
|
'align',
|
|
56
57
|
'w',
|
|
58
|
+
'h',
|
|
57
59
|
'contain',
|
|
58
60
|
'@loaded',
|
|
59
61
|
'@error',
|
|
@@ -63,7 +65,7 @@ export default () =>
|
|
|
63
65
|
return this.slotcontent || this.content || ''
|
|
64
66
|
},
|
|
65
67
|
_contain() {
|
|
66
|
-
return this.contain || (this.
|
|
68
|
+
return this.contain || (this.w && this.h ? 'both' : this.w ? 'width' : 'none')
|
|
67
69
|
},
|
|
68
70
|
},
|
|
69
71
|
})
|
package/src/element.js
CHANGED
|
@@ -54,9 +54,12 @@ const transformations = {
|
|
|
54
54
|
'z' in props && (props.zIndex = props.z)
|
|
55
55
|
delete props.z
|
|
56
56
|
},
|
|
57
|
-
|
|
58
|
-
props.parent
|
|
59
|
-
|
|
57
|
+
parent(props) {
|
|
58
|
+
if (props.parent === 'root') {
|
|
59
|
+
props.parent = renderer.root
|
|
60
|
+
} else {
|
|
61
|
+
props.parent = props.parent.node
|
|
62
|
+
}
|
|
60
63
|
},
|
|
61
64
|
color(props) {
|
|
62
65
|
if (
|
|
@@ -77,6 +80,21 @@ const transformations = {
|
|
|
77
80
|
props.color = colors.normalize(props.color)
|
|
78
81
|
}
|
|
79
82
|
},
|
|
83
|
+
percentage(props, prop) {
|
|
84
|
+
const map = {
|
|
85
|
+
w: 'width',
|
|
86
|
+
width: 'width',
|
|
87
|
+
x: 'width',
|
|
88
|
+
h: 'height',
|
|
89
|
+
height: 'height',
|
|
90
|
+
y: 'height',
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const base = map[prop]
|
|
94
|
+
if (base) {
|
|
95
|
+
props[prop] = this.node._parent[base] * (parseFloat(props[prop]) / 100)
|
|
96
|
+
}
|
|
97
|
+
},
|
|
80
98
|
mount(props) {
|
|
81
99
|
if (
|
|
82
100
|
typeof props.mount === 'object' ||
|
|
@@ -123,7 +141,7 @@ const transformations = {
|
|
|
123
141
|
}
|
|
124
142
|
},
|
|
125
143
|
effects(props) {
|
|
126
|
-
props.shader = renderer.
|
|
144
|
+
props.shader = renderer.createShader('DynamicShader', {
|
|
127
145
|
effects: props.effects.map((eff) => {
|
|
128
146
|
if (eff.props && eff.props.color) {
|
|
129
147
|
eff.props.color = colors.normalize(eff.props.color)
|
|
@@ -200,6 +218,9 @@ const Element = {
|
|
|
200
218
|
}
|
|
201
219
|
|
|
202
220
|
Object.keys(props).forEach((prop) => {
|
|
221
|
+
if (typeof props[prop] === 'string' && props[prop].endsWith('%')) {
|
|
222
|
+
transformations.percentage.call(this, props, prop)
|
|
223
|
+
}
|
|
203
224
|
this.node[prop] = props[prop]
|
|
204
225
|
})
|
|
205
226
|
}
|
|
@@ -228,6 +249,10 @@ const Element = {
|
|
|
228
249
|
// works for now
|
|
229
250
|
prop = Object.keys(props).pop()
|
|
230
251
|
|
|
252
|
+
if (typeof value === 'string' && props[prop].endsWith('%')) {
|
|
253
|
+
transformations.percentage.call(this, props, prop)
|
|
254
|
+
}
|
|
255
|
+
|
|
231
256
|
if (this.node[prop] !== props[prop]) {
|
|
232
257
|
const f = this.node.animate(props, {
|
|
233
258
|
duration: typeof value === 'object' ? ('duration' in value ? value.duration : 300) : 300,
|
package/src/launch.d.ts
CHANGED
|
@@ -56,7 +56,7 @@ const generateElementCode = function (
|
|
|
56
56
|
|
|
57
57
|
renderCode.push(`
|
|
58
58
|
if(!${elm}) {
|
|
59
|
-
${elm} = this.element({componentId: component.___id,
|
|
59
|
+
${elm} = this.element({componentId: component.___id, parent: parent || 'root'})
|
|
60
60
|
}
|
|
61
61
|
const elementConfig${counter} = {}
|
|
62
62
|
`)
|
|
@@ -139,7 +139,7 @@ const generateComponentCode = function (
|
|
|
139
139
|
if (key === 'type') return
|
|
140
140
|
if (isReactiveKey(key)) {
|
|
141
141
|
this.effectsCode.push(`
|
|
142
|
-
|
|
142
|
+
${elm}.___props['${key.substring(1)}'] = ${interpolate(
|
|
143
143
|
templateObject[key],
|
|
144
144
|
options.component
|
|
145
145
|
)}`)
|
|
@@ -241,19 +241,20 @@ const generateForLoopCode = function (templateObject, parent) {
|
|
|
241
241
|
}
|
|
242
242
|
|
|
243
243
|
const generateCode = function (templateObject, parent = false) {
|
|
244
|
-
templateObject.children
|
|
245
|
-
|
|
244
|
+
templateObject.children &&
|
|
245
|
+
templateObject.children.forEach((childTemplateObject) => {
|
|
246
|
+
counter++
|
|
246
247
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
} else {
|
|
250
|
-
if (childTemplateObject.type !== 'Element') {
|
|
251
|
-
generateComponentCode.call(this, childTemplateObject, parent)
|
|
248
|
+
if (Object.keys(childTemplateObject).indexOf(':for') > -1) {
|
|
249
|
+
generateForLoopCode.call(this, childTemplateObject, parent)
|
|
252
250
|
} else {
|
|
253
|
-
|
|
251
|
+
if (childTemplateObject.type !== 'Element') {
|
|
252
|
+
generateComponentCode.call(this, childTemplateObject, parent)
|
|
253
|
+
} else {
|
|
254
|
+
generateElementCode.call(this, childTemplateObject, parent)
|
|
255
|
+
}
|
|
254
256
|
}
|
|
255
|
-
}
|
|
256
|
-
})
|
|
257
|
+
})
|
|
257
258
|
}
|
|
258
259
|
|
|
259
260
|
const interpolate = (val, component = 'component.') => {
|
|
@@ -285,9 +286,22 @@ const interpolate = (val, component = 'component.') => {
|
|
|
285
286
|
const cast = (val = '', key = false, component = 'component.') => {
|
|
286
287
|
let castedValue
|
|
287
288
|
|
|
288
|
-
// numeric
|
|
289
289
|
if (key !== 'color' && !isNaN(parseFloat(val))) {
|
|
290
290
|
castedValue = parseFloat(val)
|
|
291
|
+
if (val.endsWith('%')) {
|
|
292
|
+
const map = {
|
|
293
|
+
w: 'width',
|
|
294
|
+
width: 'width',
|
|
295
|
+
x: 'width',
|
|
296
|
+
h: 'height',
|
|
297
|
+
height: 'height',
|
|
298
|
+
y: 'height',
|
|
299
|
+
}
|
|
300
|
+
const base = map[key]
|
|
301
|
+
if (base) {
|
|
302
|
+
castedValue = `parent.node.${base} * (${castedValue} / 100)`
|
|
303
|
+
}
|
|
304
|
+
}
|
|
291
305
|
}
|
|
292
306
|
// boolean true
|
|
293
307
|
else if (val.toLowerCase() === 'true') {
|
|
@@ -22,6 +22,10 @@ const normalize = (str) => {
|
|
|
22
22
|
return str.replace(/[\n\s\t]/gi, '')
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
const componentConfigObject = {
|
|
26
|
+
components: {},
|
|
27
|
+
}
|
|
28
|
+
|
|
25
29
|
test('Type', (assert) => {
|
|
26
30
|
const expected = 'function'
|
|
27
31
|
const actual = typeof generator
|
|
@@ -31,7 +35,7 @@ test('Type', (assert) => {
|
|
|
31
35
|
})
|
|
32
36
|
|
|
33
37
|
test('Returns an object with a render function and a context object', (assert) => {
|
|
34
|
-
const result = generator()
|
|
38
|
+
const result = generator.call(componentConfigObject)
|
|
35
39
|
const expected = 'object'
|
|
36
40
|
const actual = typeof result
|
|
37
41
|
|
|
@@ -43,7 +47,7 @@ test('Returns an object with a render function and a context object', (assert) =
|
|
|
43
47
|
})
|
|
44
48
|
|
|
45
49
|
test('The render key is a function', (assert) => {
|
|
46
|
-
const result = generator()
|
|
50
|
+
const result = generator(componentConfigObject)
|
|
47
51
|
const expected = 'function'
|
|
48
52
|
const actual = typeof result.render
|
|
49
53
|
|
|
@@ -28,42 +28,42 @@ test('Object', (assert) => {
|
|
|
28
28
|
|
|
29
29
|
test('6 character hex', (assert) => {
|
|
30
30
|
const input = ['#ffffff', '#000000', '#c0ffee', '#7dd3fc', '#0c4a6e']
|
|
31
|
-
const expected = ['0xffffffff', '
|
|
31
|
+
const expected = ['0xffffffff', '0x000000ff', '0xc0ffeeff', '0x7dd3fcff', '0x0c4a6eff']
|
|
32
32
|
|
|
33
33
|
const actual = input.map(colors.normalize)
|
|
34
34
|
|
|
35
35
|
assert.deepEqual(
|
|
36
36
|
actual,
|
|
37
37
|
expected,
|
|
38
|
-
'A 6 character hex code should return the correct color in
|
|
38
|
+
'A 6 character hex code should return the correct color in rgba format'
|
|
39
39
|
)
|
|
40
40
|
assert.end()
|
|
41
41
|
})
|
|
42
42
|
|
|
43
43
|
test('3 character hex', (assert) => {
|
|
44
44
|
const input = ['#fff', '#000', '#c8c']
|
|
45
|
-
const expected = ['0xffffffff', '
|
|
45
|
+
const expected = ['0xffffffff', '0x000000ff', '0xcc88ccff']
|
|
46
46
|
|
|
47
47
|
const actual = input.map(colors.normalize)
|
|
48
48
|
|
|
49
49
|
assert.deepEqual(
|
|
50
50
|
actual,
|
|
51
51
|
expected,
|
|
52
|
-
'A 3 character hex code should return the correct color in
|
|
52
|
+
'A 3 character hex code should return the correct color in rgba format'
|
|
53
53
|
)
|
|
54
54
|
assert.end()
|
|
55
55
|
})
|
|
56
56
|
|
|
57
57
|
test('8 character hex (rgba)', (assert) => {
|
|
58
58
|
const input = ['#ffffffff', '#000000ff', '#c0ffee80', '#7dd3fc10', '#0c4a6eff']
|
|
59
|
-
const expected = ['0xffffffff', '
|
|
59
|
+
const expected = ['0xffffffff', '0x000000ff', '0xc0ffee80', '0x7dd3fc10', '0x0c4a6eff']
|
|
60
60
|
|
|
61
61
|
const actual = input.map(colors.normalize)
|
|
62
62
|
|
|
63
63
|
assert.deepEqual(
|
|
64
64
|
actual,
|
|
65
65
|
expected,
|
|
66
|
-
'A 8 character hex (rgba) code should return the correct color in
|
|
66
|
+
'A 8 character hex (rgba) code should return the correct color in rgba format'
|
|
67
67
|
)
|
|
68
68
|
assert.end()
|
|
69
69
|
})
|
|
@@ -76,14 +76,14 @@ test('RGB colors', (assert) => {
|
|
|
76
76
|
'rgb(125, 211, 252)',
|
|
77
77
|
'rgb(12, 74, 110)',
|
|
78
78
|
]
|
|
79
|
-
const expected = ['0xffffffff', '
|
|
79
|
+
const expected = ['0xffffffff', '0x000000ff', '0xc0ffeeff', '0x7dd3fcff', '0x0c4a6eff']
|
|
80
80
|
|
|
81
81
|
const actual = input.map(colors.normalize)
|
|
82
82
|
|
|
83
83
|
assert.deepEqual(
|
|
84
84
|
actual,
|
|
85
85
|
expected,
|
|
86
|
-
'RGB codes prefixed with # should return the correct color in
|
|
86
|
+
'RGB codes prefixed with # should return the correct color in rgba format'
|
|
87
87
|
)
|
|
88
88
|
assert.end()
|
|
89
89
|
})
|
|
@@ -100,12 +100,12 @@ test('RGBA colors', (assert) => {
|
|
|
100
100
|
]
|
|
101
101
|
const expected = [
|
|
102
102
|
'0xffffffff',
|
|
103
|
-
'
|
|
104
|
-
'
|
|
105
|
-
'
|
|
106
|
-
'
|
|
107
|
-
'
|
|
108
|
-
'
|
|
103
|
+
'0x000000ff',
|
|
104
|
+
'0xc0ffee80',
|
|
105
|
+
'0x7dd3fc1a',
|
|
106
|
+
'0x0c4a6e00',
|
|
107
|
+
'0x0c4a6e00',
|
|
108
|
+
'0x0c4a6eff',
|
|
109
109
|
]
|
|
110
110
|
|
|
111
111
|
const actual = input.map(colors.normalize)
|
|
@@ -113,7 +113,7 @@ test('RGBA colors', (assert) => {
|
|
|
113
113
|
assert.deepEqual(
|
|
114
114
|
actual,
|
|
115
115
|
expected,
|
|
116
|
-
'RGB codes prefixed with # should return the correct color in
|
|
116
|
+
'RGB codes prefixed with # should return the correct color in rgba format'
|
|
117
117
|
)
|
|
118
118
|
assert.end()
|
|
119
119
|
})
|
|
@@ -130,12 +130,12 @@ test('RGBA colors', (assert) => {
|
|
|
130
130
|
]
|
|
131
131
|
const expected = [
|
|
132
132
|
'0xffffffff',
|
|
133
|
-
'
|
|
134
|
-
'
|
|
135
|
-
'
|
|
136
|
-
'
|
|
137
|
-
'
|
|
138
|
-
'
|
|
133
|
+
'0x000000ff',
|
|
134
|
+
'0xc0ffee80',
|
|
135
|
+
'0x7dd3fc1a',
|
|
136
|
+
'0x0c4a6e00',
|
|
137
|
+
'0x0c4a6e00',
|
|
138
|
+
'0x0c4a6eff',
|
|
139
139
|
]
|
|
140
140
|
|
|
141
141
|
const actual = input.map(colors.normalize)
|
|
@@ -143,19 +143,19 @@ test('RGBA colors', (assert) => {
|
|
|
143
143
|
assert.deepEqual(
|
|
144
144
|
actual,
|
|
145
145
|
expected,
|
|
146
|
-
'RGB codes prefixed with # should return the correct color in
|
|
146
|
+
'RGB codes prefixed with # should return the correct color in rgba format'
|
|
147
147
|
)
|
|
148
148
|
assert.end()
|
|
149
149
|
})
|
|
150
150
|
|
|
151
151
|
test('HTML colors', (assert) => {
|
|
152
|
-
const input = ['blanchedalmond', '
|
|
152
|
+
const input = ['blanchedalmond', 'salmon', 'tomato', 'saddlebrown', 'red']
|
|
153
153
|
|
|
154
154
|
const expected = [
|
|
155
|
-
parseInt('
|
|
156
|
-
parseInt('
|
|
157
|
-
parseInt('
|
|
158
|
-
parseInt('
|
|
155
|
+
parseInt('0xffebcdff', 16),
|
|
156
|
+
parseInt('0xfa8072ff', 16),
|
|
157
|
+
parseInt('0xff6347ff', 16),
|
|
158
|
+
parseInt('0x8b4513ff', 16),
|
|
159
159
|
parseInt('0xff0000ff', 16),
|
|
160
160
|
]
|
|
161
161
|
|
|
@@ -164,7 +164,7 @@ test('HTML colors', (assert) => {
|
|
|
164
164
|
assert.deepEqual(
|
|
165
165
|
actual,
|
|
166
166
|
expected,
|
|
167
|
-
'RGB codes prefixed with # should return the correct color in
|
|
167
|
+
'RGB codes prefixed with # should return the correct color in rgba format'
|
|
168
168
|
)
|
|
169
169
|
assert.end()
|
|
170
170
|
})
|
|
@@ -181,7 +181,7 @@ test('HTML colors', (assert) => {
|
|
|
181
181
|
|
|
182
182
|
// const actual = input.map(colors.normalize)
|
|
183
183
|
|
|
184
|
-
// assert.deepEqual(actual, expected, 'A hsl code should return the correct color in
|
|
184
|
+
// assert.deepEqual(actual, expected, 'A hsl code should return the correct color in rgba format')
|
|
185
185
|
// assert.end()
|
|
186
186
|
// })
|
|
187
187
|
|
|
@@ -28,7 +28,15 @@ const arrayMethods = [
|
|
|
28
28
|
'splice',
|
|
29
29
|
'unshift',
|
|
30
30
|
]
|
|
31
|
+
|
|
32
|
+
const proxyMap = new WeakMap()
|
|
33
|
+
|
|
31
34
|
const reactiveProxy = (target) => {
|
|
35
|
+
const isProxy = proxyMap.get(target)
|
|
36
|
+
if (isProxy) {
|
|
37
|
+
return isProxy
|
|
38
|
+
}
|
|
39
|
+
|
|
32
40
|
const handler = {
|
|
33
41
|
get(target, key, receiver) {
|
|
34
42
|
if (Array.isArray(target) && arrayMethods.includes(key)) {
|
|
@@ -36,12 +44,7 @@ const reactiveProxy = (target) => {
|
|
|
36
44
|
}
|
|
37
45
|
track(target, key)
|
|
38
46
|
|
|
39
|
-
if (
|
|
40
|
-
(target[key] !== null &&
|
|
41
|
-
typeof target[key] === 'object' &&
|
|
42
|
-
Object.getPrototypeOf(target[key]) === Object.prototype) ||
|
|
43
|
-
Array.isArray(target[key])
|
|
44
|
-
) {
|
|
47
|
+
if (target[key] !== null && typeof target[key] === 'object') {
|
|
45
48
|
return reactiveProxy(target[key])
|
|
46
49
|
}
|
|
47
50
|
|
|
@@ -66,7 +69,10 @@ const reactiveProxy = (target) => {
|
|
|
66
69
|
return result
|
|
67
70
|
},
|
|
68
71
|
}
|
|
69
|
-
|
|
72
|
+
|
|
73
|
+
const proxy = new Proxy(target, handler)
|
|
74
|
+
proxyMap.set(target, proxy)
|
|
75
|
+
return proxy
|
|
70
76
|
}
|
|
71
77
|
|
|
72
78
|
const reactiveDefineProperty = (target) => {
|
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
|
|
18
18
|
import test from 'tape'
|
|
19
19
|
import propsFn from './props.js'
|
|
20
|
+
import { initLog } from '../log.js'
|
|
21
|
+
|
|
22
|
+
initLog()
|
|
20
23
|
|
|
21
24
|
test('Type props function', (assert) => {
|
|
22
25
|
const expected = 'function'
|
|
@@ -87,14 +90,14 @@ test('Get value of props', (assert) => {
|
|
|
87
90
|
test('Passing props as an object', (assert) => {
|
|
88
91
|
|
|
89
92
|
const component = new Function()
|
|
90
|
-
const props = [{key: 'index', key: 'img', key: 'url'}]
|
|
93
|
+
const props = [{key: 'index'}, {key: 'img'}, {key: 'url'}]
|
|
91
94
|
propsFn(component, props)
|
|
92
95
|
|
|
93
96
|
assert.equal(props.length, component.___propKeys.length, 'All passed props should be stored on ___propKeys')
|
|
94
|
-
assert.equal(props.length, props.map(prop => component.___propKeys.indexOf(prop.key) > -1).filter(prop => prop === true).length, 'All passed props should be stored on ___propKeys')
|
|
97
|
+
assert.equal(props.length, props.map(prop => component.___propKeys.indexOf(typeof prop === 'object' ? prop.key : prop) > -1).filter(prop => prop === true).length, 'All passed props should be stored on ___propKeys')
|
|
95
98
|
|
|
96
99
|
props.forEach((prop) => {
|
|
97
|
-
assert.true(typeof Object.getOwnPropertyDescriptor(component.prototype, prop.key).get === 'function', `A getter should have been created for property ${prop}`)
|
|
100
|
+
assert.true(typeof Object.getOwnPropertyDescriptor(component.prototype, typeof prop === 'object' ? prop.key : prop).get === 'function', `A getter should have been created for property ${prop}`)
|
|
98
101
|
})
|
|
99
102
|
|
|
100
103
|
assert.end()
|
|
@@ -15,10 +15,47 @@
|
|
|
15
15
|
* SPDX-License-Identifier: Apache-2.0
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
class TemplateParseError extends Error {
|
|
19
|
+
constructor(name, context) {
|
|
20
|
+
super('TemplateParseError')
|
|
21
|
+
this.name = name
|
|
22
|
+
this.context = context
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
18
26
|
export default (template = '') => {
|
|
19
27
|
let cursor = 0
|
|
20
|
-
let
|
|
28
|
+
let tags = []
|
|
29
|
+
let currentTag = null
|
|
30
|
+
let currentLevel = 0
|
|
31
|
+
|
|
32
|
+
// regular expressions
|
|
33
|
+
const tagStartRegex = /^<\/?([a-zA-Z0-9_\-.]+)\s*/
|
|
34
|
+
const tagEndRegex = /^\s*(\/?>)\s*/
|
|
35
|
+
const attrNameRegex = /^([A-Za-z0-9:.\-_@]+)=\s*(["'])/
|
|
36
|
+
const emptyTagStartRegex = /^<>\s*/
|
|
37
|
+
const emptyTagEndRegex = /^\s*(<\/>)\s*/
|
|
21
38
|
|
|
39
|
+
// main functions
|
|
40
|
+
const parse = () => {
|
|
41
|
+
template = clean(template)
|
|
42
|
+
parseLoop(parseEmptyTagStart)
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
return format(tags)
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error('Error:', error.message)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const parseLoop = (next) => {
|
|
52
|
+
if (cursor >= template.length) {
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
next()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// utils
|
|
22
59
|
const clean = (template) => {
|
|
23
60
|
// remove all unnecessary new lines and comments
|
|
24
61
|
return template
|
|
@@ -26,77 +63,187 @@ export default (template = '') => {
|
|
|
26
63
|
.replace(/\r?\n\s*\r\n/gm, ' ') // remove empty lines
|
|
27
64
|
.replace(/\r?\n\s*(\S)/gm, ' $1') // remove line endings & spacing
|
|
28
65
|
.replace(/\r?\n/g, '') // remove all line endings
|
|
66
|
+
.trim()
|
|
29
67
|
}
|
|
30
68
|
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
69
|
+
const moveCursorOnMatch = (regex) => {
|
|
70
|
+
const match = template.slice(cursor).match(regex)
|
|
71
|
+
if (match) cursor += match[0].length
|
|
72
|
+
return match
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// parsers
|
|
76
|
+
const parseEmptyTagStart = () => {
|
|
77
|
+
const match = moveCursorOnMatch(emptyTagStartRegex)
|
|
78
|
+
if (match) {
|
|
79
|
+
tags.push({ type: null, __type: 'opening', __level: currentLevel })
|
|
80
|
+
currentLevel++
|
|
81
|
+
parseLoop(parseEmptyTagStart)
|
|
82
|
+
} else {
|
|
83
|
+
parseLoop(parseEmptyTagEnd)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const parseEmptyTagEnd = () => {
|
|
88
|
+
const match = moveCursorOnMatch(emptyTagEndRegex)
|
|
89
|
+
if (match) {
|
|
90
|
+
currentLevel--
|
|
91
|
+
tags.push({ type: null, __type: 'closing', __level: currentLevel })
|
|
92
|
+
parseLoop(parseEmptyTagStart)
|
|
93
|
+
} else {
|
|
94
|
+
parseLoop(parseTag)
|
|
35
95
|
}
|
|
96
|
+
}
|
|
36
97
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if (
|
|
41
|
-
|
|
98
|
+
const parseTag = () => {
|
|
99
|
+
const match = moveCursorOnMatch(tagStartRegex)
|
|
100
|
+
if (match) {
|
|
101
|
+
if (match[0].startsWith('</')) {
|
|
102
|
+
currentLevel--
|
|
103
|
+
currentTag = { type: match[1], __type: 'closing', __level: currentLevel }
|
|
104
|
+
} else {
|
|
105
|
+
currentTag = { type: match[1], __type: 'opening', __level: currentLevel }
|
|
106
|
+
currentLevel++
|
|
42
107
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
108
|
+
parseLoop(parseTagEnd)
|
|
109
|
+
} else {
|
|
110
|
+
throw new TemplateParseError('InvalidTag', template.slice(cursor))
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const parseTagEnd = () => {
|
|
115
|
+
const match = moveCursorOnMatch(tagEndRegex)
|
|
116
|
+
if (match) {
|
|
117
|
+
if (match[1] === '/>') {
|
|
118
|
+
currentTag.__type = 'self-closing'
|
|
119
|
+
currentLevel-- // because it was parsed as opening tag before
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// parsing content in between tags
|
|
123
|
+
// rule: < char cannot be used in between tags even in escaped form
|
|
124
|
+
if (currentTag.__type === 'opening') {
|
|
125
|
+
const tagContent = template.slice(cursor, template.indexOf('<', cursor))
|
|
126
|
+
if (tagContent) {
|
|
127
|
+
currentTag.content = tagContent
|
|
128
|
+
cursor += tagContent.length
|
|
46
129
|
}
|
|
47
|
-
parseNode(output)
|
|
48
130
|
}
|
|
49
|
-
cursor++
|
|
50
|
-
}
|
|
51
131
|
|
|
52
|
-
|
|
132
|
+
tags.push(currentTag)
|
|
133
|
+
parseLoop(parseEmptyTagStart)
|
|
134
|
+
} else {
|
|
135
|
+
parseLoop(parseAttributes)
|
|
136
|
+
}
|
|
53
137
|
}
|
|
54
138
|
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
139
|
+
const parseAttributes = () => {
|
|
140
|
+
const attrNameMatch = moveCursorOnMatch(attrNameRegex)
|
|
141
|
+
if (attrNameMatch) {
|
|
142
|
+
const delimiter = attrNameMatch[2]
|
|
143
|
+
const attrValueRegex = new RegExp(`^(.*?)${delimiter}\\s*`)
|
|
144
|
+
const attrValueMatch = moveCursorOnMatch(attrValueRegex)
|
|
145
|
+
|
|
146
|
+
if (attrValueMatch) {
|
|
147
|
+
// maybe this should be done while code generation step
|
|
148
|
+
const attr = formatAttribute(attrNameMatch[1], attrValueMatch[1])
|
|
149
|
+
currentTag[attr.name] = attr.value
|
|
150
|
+
parseLoop(parseTagEnd)
|
|
151
|
+
} else {
|
|
152
|
+
throw new TemplateParseError('MissingOrInvalidAttributeValue', template.slice(cursor))
|
|
68
153
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
// if (content) node.slotcontent = content
|
|
72
|
-
// }
|
|
73
|
-
output.children.push(node)
|
|
154
|
+
} else {
|
|
155
|
+
throw new TemplateParseError('InvalidAttribute', template.slice(cursor))
|
|
74
156
|
}
|
|
75
157
|
}
|
|
76
158
|
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
159
|
+
const formatAttribute = (name, value) => {
|
|
160
|
+
// if name contains a dot then we need to add enclosing js object to the value
|
|
161
|
+
// example :x.transition="{v: $offset}" must be formatted as { name: ':x', value: '{ transition: {v: $offset}' }
|
|
162
|
+
if (name.includes('.')) {
|
|
163
|
+
const [objectName, attributeName] = name.split('.')
|
|
164
|
+
return { name: objectName, value: `{${attributeName}: ${value}}` }
|
|
81
165
|
}
|
|
166
|
+
return { name, value }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// formatter
|
|
170
|
+
function format(data) {
|
|
171
|
+
let stack = []
|
|
172
|
+
let output = { children: [] }
|
|
82
173
|
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
} else {
|
|
92
|
-
obj[match[1]] = match[2]
|
|
93
|
-
}
|
|
174
|
+
for (const item of data) {
|
|
175
|
+
const { type, __type, __level } = item
|
|
176
|
+
|
|
177
|
+
// Check for unclosed tags
|
|
178
|
+
while (stack.length && stack[stack.length - 1].__level >= __level) {
|
|
179
|
+
const popped = stack.pop()
|
|
180
|
+
if (popped.__type === 'opening') {
|
|
181
|
+
throw new TemplateParseError('MismatchedClosingTag', `tag: ${popped.type || 'null'}`)
|
|
94
182
|
}
|
|
95
|
-
|
|
96
|
-
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// For closing tags, just pop the opening tag from stack and continue
|
|
186
|
+
if (__type === 'closing') {
|
|
187
|
+
let lastStackType = stack[stack.length - 1] ? stack[stack.length - 1].type : null
|
|
188
|
+
|
|
189
|
+
if (
|
|
190
|
+
stack.length === 0 ||
|
|
191
|
+
(type
|
|
192
|
+
? lastStackType && lastStackType.toLowerCase() !== type.toLowerCase()
|
|
193
|
+
: lastStackType !== null)
|
|
194
|
+
) {
|
|
195
|
+
throw new TemplateParseError('MismatchedClosingTag', `tag: ${type || 'null'}`)
|
|
196
|
+
}
|
|
197
|
+
stack.pop()
|
|
198
|
+
continue
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Create a new item, copying properties but deleting __type and __level
|
|
202
|
+
const newItem = { ...item }
|
|
203
|
+
delete newItem.__type
|
|
204
|
+
delete newItem.__level
|
|
205
|
+
|
|
206
|
+
if (__type === 'opening') {
|
|
207
|
+
newItem.children = []
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Find out where to insert this new item
|
|
211
|
+
let current = output.children
|
|
212
|
+
for (const stackItem of stack) {
|
|
213
|
+
if (stackItem.children) {
|
|
214
|
+
current = stackItem.children
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Insert the item and push it to the stack
|
|
219
|
+
current.push(newItem)
|
|
220
|
+
stack.push(newItem)
|
|
221
|
+
|
|
222
|
+
// If this is a self-closing tag, immediately pop it off the stack
|
|
223
|
+
if (__type === 'self-closing') {
|
|
224
|
+
stack.pop()
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Check for any remaining unclosed tags
|
|
229
|
+
for (const item of stack) {
|
|
230
|
+
if (item.__type === 'opening') {
|
|
231
|
+
throw new TemplateParseError('MismatchedClosingTag', `tag: ${item.type || 'null'}`)
|
|
232
|
+
}
|
|
97
233
|
}
|
|
98
234
|
|
|
99
|
-
|
|
235
|
+
// Remove empty 'children' arrays
|
|
236
|
+
function removeEmptyChildren(obj, level = 0) {
|
|
237
|
+
if (Array.isArray(obj.children) && obj.children.length === 0 && level > 0) {
|
|
238
|
+
delete obj.children
|
|
239
|
+
}
|
|
240
|
+
if (obj.children) {
|
|
241
|
+
obj.children.forEach((child) => removeEmptyChildren(child, level + 1))
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
removeEmptyChildren(output)
|
|
246
|
+
return output
|
|
100
247
|
}
|
|
101
248
|
|
|
102
249
|
return parse()
|
|
@@ -594,42 +594,42 @@ test('Parse template with a nameless tag', (assert) => {
|
|
|
594
594
|
assert.end()
|
|
595
595
|
})
|
|
596
596
|
|
|
597
|
-
test('Parse template with a nameless tag but with arguments', (assert) => {
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
})
|
|
597
|
+
// test('Parse template with a nameless tag but with arguments', (assert) => {
|
|
598
|
+
// const template = `
|
|
599
|
+
// <x="100" y="200">
|
|
600
|
+
// <x="50" y="20">
|
|
601
|
+
// <Component w="100" h="20" />
|
|
602
|
+
// </>
|
|
603
|
+
// </>`
|
|
604
|
+
|
|
605
|
+
// const expected = {
|
|
606
|
+
// children: [
|
|
607
|
+
// {
|
|
608
|
+
// type: null,
|
|
609
|
+
// x: '100',
|
|
610
|
+
// y: '200',
|
|
611
|
+
// children: [
|
|
612
|
+
// {
|
|
613
|
+
// type: null,
|
|
614
|
+
// x: '50',
|
|
615
|
+
// y: '20',
|
|
616
|
+
// children: [
|
|
617
|
+
// {
|
|
618
|
+
// type: 'Component',
|
|
619
|
+
// w: '100',
|
|
620
|
+
// h: '20',
|
|
621
|
+
// },
|
|
622
|
+
// ],
|
|
623
|
+
// },
|
|
624
|
+
// ],
|
|
625
|
+
// },
|
|
626
|
+
// ],
|
|
627
|
+
// }
|
|
628
|
+
// const actual = parser(template)
|
|
629
|
+
|
|
630
|
+
// assert.deepEqual(actual, expected, 'Parser should return object representation of template')
|
|
631
|
+
// assert.end()
|
|
632
|
+
// })
|
|
633
633
|
|
|
634
634
|
test('Parse template with a transition argument (single value)', (assert) => {
|
|
635
635
|
const template = `
|
|
@@ -767,3 +767,183 @@ test('Parse template with attributes with values spread over multiple lines', (a
|
|
|
767
767
|
assert.deepEqual(actual, expected, 'Parser should return object representation of template')
|
|
768
768
|
assert.end()
|
|
769
769
|
})
|
|
770
|
+
|
|
771
|
+
test('Parse template with inline text between tags', (assert) => {
|
|
772
|
+
const template = `
|
|
773
|
+
<Component>
|
|
774
|
+
<Element w="160" h="160" x="40" y="40" color="#fb923c">Lorem ipsum</Element>
|
|
775
|
+
</Component>`
|
|
776
|
+
|
|
777
|
+
const expected = {
|
|
778
|
+
children: [
|
|
779
|
+
{
|
|
780
|
+
type: 'Component',
|
|
781
|
+
children: [
|
|
782
|
+
{
|
|
783
|
+
type: 'Element',
|
|
784
|
+
w: '160',
|
|
785
|
+
h: '160',
|
|
786
|
+
x: '40',
|
|
787
|
+
y: '40',
|
|
788
|
+
color: '#fb923c',
|
|
789
|
+
content: 'Lorem ipsum',
|
|
790
|
+
},
|
|
791
|
+
],
|
|
792
|
+
},
|
|
793
|
+
],
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
const actual = parser(template)
|
|
797
|
+
|
|
798
|
+
assert.deepEqual(actual, expected, 'Parser should return object representation of template')
|
|
799
|
+
assert.end()
|
|
800
|
+
})
|
|
801
|
+
|
|
802
|
+
test('Parse template with multiple inline texts between different tags', (assert) => {
|
|
803
|
+
const template = `
|
|
804
|
+
<Component>
|
|
805
|
+
<Element w="160" h="160" x="40" y="40" color="#fb923c">Lorem ipsum</Element>
|
|
806
|
+
<Element>dolor sit amet</Element>
|
|
807
|
+
<Element>
|
|
808
|
+
<Text>consectetur adipiscing elit</Text>
|
|
809
|
+
<Element>
|
|
810
|
+
<Text>sed do eiusmod tempor</Text>
|
|
811
|
+
</Element>
|
|
812
|
+
</Element>
|
|
813
|
+
|
|
814
|
+
</Component>`
|
|
815
|
+
|
|
816
|
+
const expected = {
|
|
817
|
+
children: [
|
|
818
|
+
{
|
|
819
|
+
type: 'Component',
|
|
820
|
+
children: [
|
|
821
|
+
{
|
|
822
|
+
type: 'Element',
|
|
823
|
+
w: '160',
|
|
824
|
+
h: '160',
|
|
825
|
+
x: '40',
|
|
826
|
+
y: '40',
|
|
827
|
+
color: '#fb923c',
|
|
828
|
+
content: 'Lorem ipsum',
|
|
829
|
+
},
|
|
830
|
+
{
|
|
831
|
+
type: 'Element',
|
|
832
|
+
content: 'dolor sit amet',
|
|
833
|
+
},
|
|
834
|
+
{
|
|
835
|
+
type: 'Element',
|
|
836
|
+
children: [
|
|
837
|
+
{
|
|
838
|
+
type: 'Text',
|
|
839
|
+
content: 'consectetur adipiscing elit',
|
|
840
|
+
},
|
|
841
|
+
{
|
|
842
|
+
type: 'Element',
|
|
843
|
+
children: [
|
|
844
|
+
{
|
|
845
|
+
type: 'Text',
|
|
846
|
+
content: 'sed do eiusmod tempor',
|
|
847
|
+
},
|
|
848
|
+
],
|
|
849
|
+
},
|
|
850
|
+
],
|
|
851
|
+
},
|
|
852
|
+
],
|
|
853
|
+
},
|
|
854
|
+
],
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
const actual = parser(template)
|
|
858
|
+
|
|
859
|
+
assert.deepEqual(actual, expected, 'Parser should return object representation of template')
|
|
860
|
+
assert.end()
|
|
861
|
+
})
|
|
862
|
+
|
|
863
|
+
test('Parse template with attribute name starts with @ character', (assert) => {
|
|
864
|
+
const template = `
|
|
865
|
+
<Component>
|
|
866
|
+
<Element w="160" h="160" x="40" y="40" color="#fb923c" @loaded="@loaded" />
|
|
867
|
+
</Component>`
|
|
868
|
+
|
|
869
|
+
const expected = {
|
|
870
|
+
children: [
|
|
871
|
+
{
|
|
872
|
+
type: 'Component',
|
|
873
|
+
children: [
|
|
874
|
+
{
|
|
875
|
+
type: 'Element',
|
|
876
|
+
w: '160',
|
|
877
|
+
h: '160',
|
|
878
|
+
x: '40',
|
|
879
|
+
y: '40',
|
|
880
|
+
color: '#fb923c',
|
|
881
|
+
'@loaded': '@loaded',
|
|
882
|
+
},
|
|
883
|
+
],
|
|
884
|
+
},
|
|
885
|
+
],
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
const actual = parser(template)
|
|
889
|
+
|
|
890
|
+
assert.deepEqual(actual, expected, 'Parser should return object representation of template')
|
|
891
|
+
assert.end()
|
|
892
|
+
})
|
|
893
|
+
|
|
894
|
+
test('Parse template with attribute values with delimited either single or double quotes', (assert) => {
|
|
895
|
+
const template = `
|
|
896
|
+
<Component>
|
|
897
|
+
<Element
|
|
898
|
+
w='160' h="160" x='40' y='40' color="#fb923c"
|
|
899
|
+
:effects='[$shader(
|
|
900
|
+
"radius",
|
|
901
|
+
{radius: 44}
|
|
902
|
+
)]'
|
|
903
|
+
/>
|
|
904
|
+
<Element
|
|
905
|
+
w='120' h="120"
|
|
906
|
+
x='100' y="100"
|
|
907
|
+
:effects="[
|
|
908
|
+
$shader(
|
|
909
|
+
'radius',
|
|
910
|
+
{
|
|
911
|
+
radius: 45
|
|
912
|
+
}
|
|
913
|
+
)
|
|
914
|
+
]"
|
|
915
|
+
/>
|
|
916
|
+
</Component>`
|
|
917
|
+
|
|
918
|
+
const expected = {
|
|
919
|
+
children: [
|
|
920
|
+
{
|
|
921
|
+
type: 'Component',
|
|
922
|
+
children: [
|
|
923
|
+
{
|
|
924
|
+
type: 'Element',
|
|
925
|
+
w: '160',
|
|
926
|
+
h: '160',
|
|
927
|
+
x: '40',
|
|
928
|
+
y: '40',
|
|
929
|
+
color: '#fb923c',
|
|
930
|
+
':effects': '[$shader( "radius", {radius: 44} )]',
|
|
931
|
+
},
|
|
932
|
+
{
|
|
933
|
+
type: 'Element',
|
|
934
|
+
w: '120',
|
|
935
|
+
h: '120',
|
|
936
|
+
x: '100',
|
|
937
|
+
y: '100',
|
|
938
|
+
':effects': "[ $shader( 'radius', { radius: 45 } ) ]",
|
|
939
|
+
},
|
|
940
|
+
],
|
|
941
|
+
},
|
|
942
|
+
],
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
const actual = parser(template)
|
|
946
|
+
|
|
947
|
+
assert.deepEqual(actual, expected, 'Parser should return object representation of template')
|
|
948
|
+
assert.end()
|
|
949
|
+
})
|