@lightningjs/blits 1.23.2 → 1.25.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/.github/workflows/run-blits-tests.yml +5 -1
- package/CHANGELOG.md +16 -0
- package/docs/plugins/text-to-speech-announcer.md +12 -0
- package/index.d.ts +16 -1
- package/package.json +1 -1
- package/src/announcer/announcer.js +18 -0
- package/src/application.js +2 -0
- package/src/components/FPScounter.js +2 -2
- package/src/engines/L3/element.js +17 -12
- package/src/engines/L3/element.test.js +0 -6
- package/src/lib/codegenerator/generator.js +2 -2
- package/src/lib/codegenerator/generator.test.js +206 -3
- package/src/router/router.js +3 -1
- package/src/router/router.test.js +244 -9
|
@@ -4,6 +4,7 @@ on:
|
|
|
4
4
|
pull_request_target:
|
|
5
5
|
branches:
|
|
6
6
|
- dev
|
|
7
|
+
- master
|
|
7
8
|
|
|
8
9
|
jobs:
|
|
9
10
|
run-blits-tests:
|
|
@@ -12,6 +13,9 @@ jobs:
|
|
|
12
13
|
steps:
|
|
13
14
|
- name: Checkout Code
|
|
14
15
|
uses: actions/checkout@v3
|
|
16
|
+
with:
|
|
17
|
+
ref: ${{ github.event.pull_request.head.ref }}
|
|
18
|
+
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
|
15
19
|
|
|
16
20
|
- name: Set up Node.js
|
|
17
21
|
uses: actions/setup-node@v3
|
|
@@ -42,7 +46,7 @@ jobs:
|
|
|
42
46
|
echo "summary=$summary" >> $GITHUB_OUTPUT
|
|
43
47
|
|
|
44
48
|
# If the number of failed tests is greater than 1, exit with error.
|
|
45
|
-
if [ "$failed" -gt
|
|
49
|
+
if [ "$failed" -gt 0 ]; then
|
|
46
50
|
exit 1
|
|
47
51
|
fi
|
|
48
52
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v1.25.0
|
|
4
|
+
|
|
5
|
+
_28 mar 2025_
|
|
6
|
+
|
|
7
|
+
- Fixed issue when same effect type is used in `effects` attribute
|
|
8
|
+
- Added ability to enable / disable announcer via launch setting as well as runtime
|
|
9
|
+
|
|
10
|
+
## v1.24.0
|
|
11
|
+
|
|
12
|
+
_26 mar 2025_
|
|
13
|
+
|
|
14
|
+
- Added additional tests for router
|
|
15
|
+
- Fixed issues with `:show` attribute not working properly
|
|
16
|
+
- Fixed issue with strings starting with a number being casted to a number
|
|
17
|
+
- Fixed named slots functionality
|
|
18
|
+
|
|
3
19
|
## v1.23.2
|
|
4
20
|
|
|
5
21
|
_18 mar 2025_
|
|
@@ -46,3 +46,15 @@ Blits.Component('MyComponent', {
|
|
|
46
46
|
}
|
|
47
47
|
})
|
|
48
48
|
```
|
|
49
|
+
|
|
50
|
+
## Enabling / disabling the announcer
|
|
51
|
+
|
|
52
|
+
By default the announcer is disabled. This mean that whenever you call `this.$announcer.speak()` there will actually not be anything spoken out via the Text To Speech engine.
|
|
53
|
+
|
|
54
|
+
In order to enable utterances being spoken out, you can set the Blits launch setting `announcer` to `true` in the index.js.
|
|
55
|
+
|
|
56
|
+
Alternatively the announcer can be enabled or disabled run time by using one of the following methods on the Announcer pluging:
|
|
57
|
+
|
|
58
|
+
- `this.$announcer.enable()` - activates the announcer
|
|
59
|
+
- `this.$announcer.disable()` - deactivates the announcer
|
|
60
|
+
- `this.$announcer.disable(true/false)` - turns the announcer or on off
|
package/index.d.ts
CHANGED
|
@@ -929,7 +929,22 @@ declare module '@lightningjs/blits' {
|
|
|
929
929
|
*
|
|
930
930
|
* @important if you dont know what you're doing here, you probably shouldn't be doing it!
|
|
931
931
|
*/
|
|
932
|
-
advanced?: Partial<RendererMainSettings
|
|
932
|
+
advanced?: Partial<RendererMainSettings>,
|
|
933
|
+
/**
|
|
934
|
+
* Whether or not the announcer should be activated on initialization
|
|
935
|
+
*
|
|
936
|
+
* When set to `false` announcements via `this.$annoucer.speak()` will
|
|
937
|
+
* be ignored. When set to `true` announcement will be spoken out via the
|
|
938
|
+
* text to speech API
|
|
939
|
+
*
|
|
940
|
+
* Announcer can be enabled / disabled run time as well via:
|
|
941
|
+
* - this.$announcer.enable()
|
|
942
|
+
* - this.$announcer.disable()
|
|
943
|
+
* - this.$announcer.toggle(true/false)
|
|
944
|
+
*
|
|
945
|
+
* @default false
|
|
946
|
+
*/
|
|
947
|
+
announcer?: boolean
|
|
933
948
|
}
|
|
934
949
|
|
|
935
950
|
interface State {
|
package/package.json
CHANGED
|
@@ -19,7 +19,22 @@ import speechSynthesis from './speechSynthesis.js'
|
|
|
19
19
|
|
|
20
20
|
let debounce
|
|
21
21
|
|
|
22
|
+
let active = false
|
|
23
|
+
|
|
24
|
+
const enable = () => {
|
|
25
|
+
active = true
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const disable = () => {
|
|
29
|
+
active = false
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const toggle = (v) => {
|
|
33
|
+
active = v ? true : false
|
|
34
|
+
}
|
|
35
|
+
|
|
22
36
|
const speak = (message, politeness = 'off') => {
|
|
37
|
+
if (active === false) return
|
|
23
38
|
clearTimeout(debounce)
|
|
24
39
|
speechSynthesis.cancel()
|
|
25
40
|
// assertive messages get spoken immediately
|
|
@@ -45,4 +60,7 @@ export default {
|
|
|
45
60
|
polite,
|
|
46
61
|
assertive,
|
|
47
62
|
stop,
|
|
63
|
+
enable,
|
|
64
|
+
disable,
|
|
65
|
+
toggle,
|
|
48
66
|
}
|
package/src/application.js
CHANGED
|
@@ -54,6 +54,8 @@ const Application = (config) => {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
config.hooks[symbols.init] = function () {
|
|
57
|
+
// set the initial activation state of the announcer based on launch setting
|
|
58
|
+
this.$announcer.toggle(Settings.get('announcer', false))
|
|
57
59
|
const keyMap = { ...defaultKeyMap, ...Settings.get('keymap', {}) }
|
|
58
60
|
|
|
59
61
|
keyDownHandler = async (e) => {
|
|
@@ -736,7 +736,7 @@ export default () =>
|
|
|
736
736
|
)
|
|
737
737
|
|
|
738
738
|
elementConfig23['image'] = component.image
|
|
739
|
-
elementConfig23['x'] = -2
|
|
739
|
+
elementConfig23['x'] = '-2'
|
|
740
740
|
elementConfig23['w'] = 47
|
|
741
741
|
elementConfig23['h'] = 25
|
|
742
742
|
elementConfig23['map'] = component.sprite
|
|
@@ -759,7 +759,7 @@ export default () =>
|
|
|
759
759
|
parent = elms[23]
|
|
760
760
|
const props24 = {}
|
|
761
761
|
props24['image'] = component.image
|
|
762
|
-
props24['x'] = -2
|
|
762
|
+
props24['x'] = '-2'
|
|
763
763
|
props24['w'] = 47
|
|
764
764
|
props24['h'] = 25
|
|
765
765
|
props24['map'] = component.sprite
|
|
@@ -82,6 +82,9 @@ const layoutFn = function (config) {
|
|
|
82
82
|
let otherDimension = 0
|
|
83
83
|
const gap = config.gap || 0
|
|
84
84
|
for (let i = 0; i < childrenLength; i++) {
|
|
85
|
+
if (this.children[i] !== undefined && this.children[i].props.raw.show === false) {
|
|
86
|
+
continue
|
|
87
|
+
}
|
|
85
88
|
const node = children[i]
|
|
86
89
|
node[position] = offset
|
|
87
90
|
node[oppositePosition] = padding.oppositeStart
|
|
@@ -303,12 +306,8 @@ const propsTransformer = {
|
|
|
303
306
|
set show(v) {
|
|
304
307
|
if (v) {
|
|
305
308
|
this.props['alpha'] = this.raw['alpha'] !== undefined ? this.raw['alpha'] : 1
|
|
306
|
-
this.props['width'] = this.raw['w'] || this.raw['width'] || 0
|
|
307
|
-
this.props['height'] = this.raw['h'] || this.raw['height'] || 0
|
|
308
309
|
} else {
|
|
309
310
|
this.props['alpha'] = 0
|
|
310
|
-
this.props['width'] = 0
|
|
311
|
-
this.props['height'] = 0
|
|
312
311
|
}
|
|
313
312
|
},
|
|
314
313
|
set alpha(v) {
|
|
@@ -329,24 +328,29 @@ const propsTransformer = {
|
|
|
329
328
|
v[i].props.color = colors.normalize(v[i].props.color)
|
|
330
329
|
}
|
|
331
330
|
}
|
|
332
|
-
|
|
331
|
+
const effectNames = {}
|
|
333
332
|
if (this.element.node === undefined) {
|
|
333
|
+
this.element.effectNames = []
|
|
334
334
|
this.props['shader'] = renderer.createShader('DynamicShader', {
|
|
335
335
|
effects: v.map((effect) => {
|
|
336
|
+
let name = effect.type
|
|
337
|
+
if (effectNames[name] !== undefined) {
|
|
338
|
+
name += ++effectNames[name]
|
|
339
|
+
} else {
|
|
340
|
+
effectNames[name] = 1
|
|
341
|
+
}
|
|
342
|
+
name += this.element.counter
|
|
343
|
+
this.element.effectNames.push(name)
|
|
336
344
|
// temporary add counter to work around shader caching issues
|
|
337
|
-
return renderer.createEffect(
|
|
338
|
-
effect.type,
|
|
339
|
-
effect.props,
|
|
340
|
-
effect.type + this.element.counter
|
|
341
|
-
)
|
|
345
|
+
return renderer.createEffect(effect.type, effect.props, name)
|
|
342
346
|
}),
|
|
343
347
|
})
|
|
344
348
|
} else {
|
|
345
349
|
for (let i = 0; i < v.length; i++) {
|
|
350
|
+
const name = this.element.effectNames[i]
|
|
346
351
|
// temporary add counter to work around shader caching issues
|
|
347
|
-
const target = this.element.node.shader.props[
|
|
352
|
+
const target = this.element.node.shader.props[name]
|
|
348
353
|
const props = Object.keys(v[i].props)
|
|
349
|
-
|
|
350
354
|
if (target == undefined) continue
|
|
351
355
|
for (let j = 0; j < props.length; j++) {
|
|
352
356
|
target[props[j]] = v[i].props[props[j]]
|
|
@@ -652,6 +656,7 @@ export default (config, component) => {
|
|
|
652
656
|
scheduledTransitions: {},
|
|
653
657
|
config,
|
|
654
658
|
component,
|
|
659
|
+
effectNames: [],
|
|
655
660
|
counter: counter++,
|
|
656
661
|
})
|
|
657
662
|
}
|
|
@@ -551,10 +551,6 @@ test('Element - Set `show` property as false', (assert) => {
|
|
|
551
551
|
|
|
552
552
|
assert.equal(el.node['alpha'], 0, 'Node alpha parameter should set to 0')
|
|
553
553
|
assert.equal(el.props.props['alpha'], 0, 'Props alpha parameter should set')
|
|
554
|
-
assert.equal(el.node['width'], 0, 'Node width parameter should set to 0')
|
|
555
|
-
assert.equal(el.props.props['width'], 0, 'props width parameter should set to 0')
|
|
556
|
-
assert.equal(el.node['height'], 0, 'Node height parameter should set to 0')
|
|
557
|
-
assert.equal(el.props.props['height'], 0, 'props height parameter should set to 0')
|
|
558
554
|
|
|
559
555
|
assert.end()
|
|
560
556
|
})
|
|
@@ -567,9 +563,7 @@ test('Element - Set `show` property as false', (assert) => {
|
|
|
567
563
|
assert.equal(el.node['alpha'], 1, 'Node alpha parameter should be set')
|
|
568
564
|
assert.equal(el.props.props['alpha'], 1, 'Props alpha parameter should be set')
|
|
569
565
|
assert.equal(el.node['width'], 960, 'Node width parameter should be set')
|
|
570
|
-
assert.equal(el.props.props['width'], 960, 'props width parameter should be set')
|
|
571
566
|
assert.equal(el.node['height'], 540, 'Node height parameter should be set')
|
|
572
|
-
assert.equal(el.props.props['height'], 540, 'props height parameter should be set')
|
|
573
567
|
|
|
574
568
|
assert.end()
|
|
575
569
|
})
|
|
@@ -148,7 +148,7 @@ const generateElementCode = function (
|
|
|
148
148
|
Object.keys(templateObject).forEach((key) => {
|
|
149
149
|
if (key === 'slot') {
|
|
150
150
|
renderCode.push(`
|
|
151
|
-
elementConfig${counter}['parent'] =
|
|
151
|
+
elementConfig${counter}['parent'] = slotComponent[Symbol.for('slots')] !== undefined && Array.isArray(slotComponent[Symbol.for('slots')]) === true && slotComponent[Symbol.for('slots')].filter(slot => slot.ref === '${templateObject.slot}').shift() || parent
|
|
152
152
|
`)
|
|
153
153
|
}
|
|
154
154
|
|
|
@@ -691,7 +691,7 @@ const cast = (val = '', key = false, component = 'component.') => {
|
|
|
691
691
|
}
|
|
692
692
|
}
|
|
693
693
|
// numeric
|
|
694
|
-
else if (key !== 'color' && !isNaN(parseFloat(val))) {
|
|
694
|
+
else if (key !== 'color' && /[^0-9%\s.eE]/.test(val) === false && !isNaN(parseFloat(val))) {
|
|
695
695
|
castedValue = parseFloat(val)
|
|
696
696
|
if (val.endsWith('%')) {
|
|
697
697
|
const map = {
|
|
@@ -296,6 +296,71 @@ test('Generate code for a template with a single element with attributes with a
|
|
|
296
296
|
assert.end()
|
|
297
297
|
})
|
|
298
298
|
|
|
299
|
+
test('Generate code for a template with a single element with attributes with numeric & alpha numeric value', (assert) => {
|
|
300
|
+
const templateObject = {
|
|
301
|
+
children: [
|
|
302
|
+
{
|
|
303
|
+
[Symbol.for('componentType')]: 'Element',
|
|
304
|
+
x: '10e2',
|
|
305
|
+
y: '100',
|
|
306
|
+
h: '45%',
|
|
307
|
+
w: '45.45%',
|
|
308
|
+
one: 'Pure String',
|
|
309
|
+
two: '123abc',
|
|
310
|
+
three: '1E',
|
|
311
|
+
four: 'E1',
|
|
312
|
+
},
|
|
313
|
+
],
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const expectedRender = `
|
|
317
|
+
function anonymous(parent, component, context, components, effect, getRaw, Log) {
|
|
318
|
+
const elms = []
|
|
319
|
+
let componentType
|
|
320
|
+
const rootComponent = component
|
|
321
|
+
let propData
|
|
322
|
+
let slotComponent
|
|
323
|
+
let inSlot = false
|
|
324
|
+
let slotChildCounter = 0
|
|
325
|
+
const elementConfig0 = {}
|
|
326
|
+
|
|
327
|
+
elms[0] = this.element(
|
|
328
|
+
{ parent: parent || 'root' },
|
|
329
|
+
inSlot === true ? slotComponent : component
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
elementConfig0['x'] = 1000
|
|
333
|
+
elementConfig0['y'] = 100
|
|
334
|
+
elementConfig0['h'] = parent.node.height * (45 / 100)
|
|
335
|
+
elementConfig0['w'] = parent.node.width * (45.45 / 100)
|
|
336
|
+
elementConfig0['one'] = "Pure String"
|
|
337
|
+
elementConfig0['two'] = "123abc"
|
|
338
|
+
elementConfig0['three'] = 1
|
|
339
|
+
elementConfig0['four'] = "E1"
|
|
340
|
+
|
|
341
|
+
elms[0].populate(elementConfig0)
|
|
342
|
+
|
|
343
|
+
if (inSlot === true) {
|
|
344
|
+
slotChildCounter -= 1
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return elms
|
|
348
|
+
}
|
|
349
|
+
`
|
|
350
|
+
|
|
351
|
+
const actual = generator.call(scope, templateObject)
|
|
352
|
+
assert.equal(
|
|
353
|
+
normalize(actual.render.toString()),
|
|
354
|
+
normalize(expectedRender),
|
|
355
|
+
'Generator should return a render function with the correct code'
|
|
356
|
+
)
|
|
357
|
+
assert.ok(
|
|
358
|
+
Array.isArray(actual.effects) && actual.effects.length === 0,
|
|
359
|
+
'Generator should return an empty effects array'
|
|
360
|
+
)
|
|
361
|
+
assert.end()
|
|
362
|
+
})
|
|
363
|
+
|
|
299
364
|
test('Generate code for a template with attributes and a nested element with attributes', (assert) => {
|
|
300
365
|
const templateObject = {
|
|
301
366
|
children: [
|
|
@@ -1427,6 +1492,144 @@ test('Generate code for a template with custom components with arguments', (asse
|
|
|
1427
1492
|
assert.end()
|
|
1428
1493
|
})
|
|
1429
1494
|
|
|
1495
|
+
test('Generate code for a template with custom components with argument value as numeric & alpha numeric', (assert) => {
|
|
1496
|
+
const templateObject = {
|
|
1497
|
+
children: [
|
|
1498
|
+
{
|
|
1499
|
+
[Symbol.for('componentType')]: 'Element',
|
|
1500
|
+
children: [
|
|
1501
|
+
{
|
|
1502
|
+
[Symbol.for('componentType')]: 'Poster',
|
|
1503
|
+
x: '10e2',
|
|
1504
|
+
y: '100',
|
|
1505
|
+
h: '45%',
|
|
1506
|
+
w: '45.45%',
|
|
1507
|
+
one: 'Pure String',
|
|
1508
|
+
two: '123abc',
|
|
1509
|
+
three: '1E',
|
|
1510
|
+
four: 'E1',
|
|
1511
|
+
},
|
|
1512
|
+
],
|
|
1513
|
+
},
|
|
1514
|
+
],
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
const scope = {
|
|
1518
|
+
components: {
|
|
1519
|
+
Poster: () => {},
|
|
1520
|
+
},
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
const expectedRender = `
|
|
1524
|
+
function anonymous(parent, component, context, components, effect, getRaw, Log) {
|
|
1525
|
+
const elms = []
|
|
1526
|
+
let componentType
|
|
1527
|
+
const rootComponent = component
|
|
1528
|
+
let propData
|
|
1529
|
+
let slotComponent
|
|
1530
|
+
let inSlot = false
|
|
1531
|
+
let slotChildCounter = 0
|
|
1532
|
+
const elementConfig0 = {}
|
|
1533
|
+
|
|
1534
|
+
elms[0] = this.element(
|
|
1535
|
+
{ parent: parent || 'root' },
|
|
1536
|
+
inSlot === true ? slotComponent : component
|
|
1537
|
+
)
|
|
1538
|
+
elms[0].populate(elementConfig0)
|
|
1539
|
+
|
|
1540
|
+
if (inSlot === true) {
|
|
1541
|
+
slotChildCounter -= 1
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
const cmp1 = (context.components && context.components['Poster']) || components['Poster']
|
|
1545
|
+
parent = elms[0]
|
|
1546
|
+
const elementConfig1 = {}
|
|
1547
|
+
|
|
1548
|
+
elms[1] = this.element(
|
|
1549
|
+
{ parent: parent || 'root' },
|
|
1550
|
+
inSlot === true ? slotComponent : component
|
|
1551
|
+
)
|
|
1552
|
+
|
|
1553
|
+
elementConfig1['x'] = 1000
|
|
1554
|
+
elementConfig1['y'] = 100
|
|
1555
|
+
elementConfig1['h'] = parent.node.height * (45 / 100)
|
|
1556
|
+
elementConfig1['w'] = parent.node.width * (45.45 / 100)
|
|
1557
|
+
elementConfig1['one'] = "PureString"
|
|
1558
|
+
elementConfig1['two'] = "123abc"
|
|
1559
|
+
elementConfig1['three'] = 1
|
|
1560
|
+
elementConfig1['four'] = "E1"
|
|
1561
|
+
|
|
1562
|
+
const skip1 = []
|
|
1563
|
+
if (typeof cmp1 !== 'undefined') {
|
|
1564
|
+
for (let key in cmp1.config.props) {
|
|
1565
|
+
delete elementConfig1[cmp1.config.props[key]]
|
|
1566
|
+
skip1.push(cmp1.config.props[key])
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
elms[1].populate(elementConfig1)
|
|
1571
|
+
|
|
1572
|
+
if (inSlot === true) {
|
|
1573
|
+
slotChildCounter -= 1
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
parent = elms[1]
|
|
1577
|
+
const props2 = {}
|
|
1578
|
+
|
|
1579
|
+
props2['x'] = 1000
|
|
1580
|
+
props2['y'] = 100
|
|
1581
|
+
props2['h'] = parent.node.height * (45 / 100)
|
|
1582
|
+
props2['w'] = parent.node.width * (45.45 / 100)
|
|
1583
|
+
props2['one'] = "PureString"
|
|
1584
|
+
props2['two'] = "123abc"
|
|
1585
|
+
props2['three'] = 1
|
|
1586
|
+
props2['four'] = "E1"
|
|
1587
|
+
|
|
1588
|
+
componentType = props2['is'] || 'Poster'
|
|
1589
|
+
let component2
|
|
1590
|
+
|
|
1591
|
+
if (typeof componentType === 'string') {
|
|
1592
|
+
component2 = context.components && context.components[componentType] || components[componentType]
|
|
1593
|
+
if (!component2) {
|
|
1594
|
+
throw new Error('Component "Poster" not found')
|
|
1595
|
+
}
|
|
1596
|
+
} else if (typeof componentType === 'function' && componentType[Symbol.for('isComponent')] === true) {
|
|
1597
|
+
component2 = componentType
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
elms[2] = component2.call(null, { props: props2 }, elms[1], component)
|
|
1601
|
+
|
|
1602
|
+
if (elms[2][Symbol.for('slots')][0]) {
|
|
1603
|
+
parent = elms[2][Symbol.for('slots')][0]
|
|
1604
|
+
slotComponent = elms[2]
|
|
1605
|
+
inSlot = true
|
|
1606
|
+
} else {
|
|
1607
|
+
parent = elms[2][Symbol.for('children')][0]
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
if (inSlot === true && slotChildCounter === 0) {
|
|
1611
|
+
inSlot = false
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
return elms
|
|
1615
|
+
}
|
|
1616
|
+
`
|
|
1617
|
+
|
|
1618
|
+
const actual = generator.call(scope, templateObject)
|
|
1619
|
+
|
|
1620
|
+
assert.equal(
|
|
1621
|
+
normalize(actual.render.toString()),
|
|
1622
|
+
normalize(expectedRender),
|
|
1623
|
+
'Generator should return a render function with the correct code'
|
|
1624
|
+
)
|
|
1625
|
+
assert.ok(
|
|
1626
|
+
Array.isArray(actual.effects) && actual.effects.length === 0,
|
|
1627
|
+
'Generator should return an empty effects array'
|
|
1628
|
+
)
|
|
1629
|
+
|
|
1630
|
+
assert.end()
|
|
1631
|
+
})
|
|
1632
|
+
|
|
1430
1633
|
test('Generate code for a template with custom components with reactive props', (assert) => {
|
|
1431
1634
|
const templateObject = {
|
|
1432
1635
|
children: [
|
|
@@ -1933,9 +2136,9 @@ test('Generate code for a template with slot content, using a named slot', (asse
|
|
|
1933
2136
|
elms[4] = this.element({ parent: parent || 'root' }, inSlot === true ? slotComponent : component)
|
|
1934
2137
|
elementConfig4['x'] = 100
|
|
1935
2138
|
elementConfig4['y'] = component.y
|
|
1936
|
-
elementConfig4['parent'] =
|
|
1937
|
-
Array.isArray(
|
|
1938
|
-
|
|
2139
|
+
elementConfig4['parent'] = slotComponent[Symbol.for('slots')] !== undefined &&
|
|
2140
|
+
Array.isArray(slotComponent[Symbol.for('slots')]) === true &&
|
|
2141
|
+
slotComponent[Symbol.for('slots')].filter(slot => slot.ref === 'mySlot').shift() || parent
|
|
1939
2142
|
elementConfig4['slot'] = "mySlot"
|
|
1940
2143
|
elms[4].populate(elementConfig4)
|
|
1941
2144
|
|
package/src/router/router.js
CHANGED
|
@@ -66,7 +66,7 @@ const isString = (v) => typeof v === 'string'
|
|
|
66
66
|
|
|
67
67
|
export const matchHash = (path, routes = []) => {
|
|
68
68
|
// remove trailing slashes
|
|
69
|
-
const originalPath = path
|
|
69
|
+
const originalPath = path.replace(/^\/+|\/+$/g, '')
|
|
70
70
|
path = normalizePath(path)
|
|
71
71
|
let matchingRoute = false
|
|
72
72
|
let i = 0
|
|
@@ -89,6 +89,8 @@ export const matchHash = (path, routes = []) => {
|
|
|
89
89
|
dynamicRoutePartsRegex.substring(part.index + part[0].length)
|
|
90
90
|
})
|
|
91
91
|
|
|
92
|
+
dynamicRoutePartsRegex = '^' + dynamicRoutePartsRegex
|
|
93
|
+
|
|
92
94
|
// test if the constructed regex matches the path
|
|
93
95
|
const match = originalPath.match(new RegExp(`${dynamicRoutePartsRegex}`, 'i'))
|
|
94
96
|
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import test from 'tape'
|
|
19
|
-
import { matchHash } from './router.js'
|
|
19
|
+
import { matchHash, getHash, to } from './router.js'
|
|
20
20
|
|
|
21
21
|
const routes = [
|
|
22
22
|
{
|
|
@@ -31,6 +31,9 @@ const routes = [
|
|
|
31
31
|
{
|
|
32
32
|
path: '/tv/:show/seasons/:season',
|
|
33
33
|
},
|
|
34
|
+
{
|
|
35
|
+
path: '/movies/special',
|
|
36
|
+
},
|
|
34
37
|
{
|
|
35
38
|
path: '/movies/:name',
|
|
36
39
|
},
|
|
@@ -40,6 +43,9 @@ const routes = [
|
|
|
40
43
|
{
|
|
41
44
|
path: '/route/with/trailing/slash/',
|
|
42
45
|
},
|
|
46
|
+
{
|
|
47
|
+
path: '/dutchmovies/:id',
|
|
48
|
+
},
|
|
43
49
|
{
|
|
44
50
|
path: '*',
|
|
45
51
|
},
|
|
@@ -54,6 +60,7 @@ test('Type of matchHash', (assert) => {
|
|
|
54
60
|
})
|
|
55
61
|
|
|
56
62
|
test('Match paths with static routes that have an exact match', (assert) => {
|
|
63
|
+
assert.equal(matchHash('', routes), routes[0], 'Should return the correct route object')
|
|
57
64
|
assert.equal(matchHash('/', routes), routes[0], 'Should return the correct route object')
|
|
58
65
|
assert.equal(matchHash('/page1', routes), routes[1], 'Should return the correct route object')
|
|
59
66
|
assert.equal(
|
|
@@ -61,6 +68,11 @@ test('Match paths with static routes that have an exact match', (assert) => {
|
|
|
61
68
|
routes[2],
|
|
62
69
|
'Should return the correct route object'
|
|
63
70
|
)
|
|
71
|
+
assert.equal(
|
|
72
|
+
matchHash('/movies/special', routes),
|
|
73
|
+
routes[4],
|
|
74
|
+
'Should return the correct route object'
|
|
75
|
+
)
|
|
64
76
|
assert.end()
|
|
65
77
|
})
|
|
66
78
|
|
|
@@ -71,32 +83,69 @@ test('Match paths with dynamic route parts', (assert) => {
|
|
|
71
83
|
'Should return the correct route object'
|
|
72
84
|
)
|
|
73
85
|
|
|
86
|
+
assert.equal(
|
|
87
|
+
matchHash('/tv/simpsons/season/first', routes),
|
|
88
|
+
routes[9],
|
|
89
|
+
'Should return the correct route object'
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
assert.equal(
|
|
93
|
+
matchHash('/examples/test/seasons/5', routes),
|
|
94
|
+
routes[6],
|
|
95
|
+
'Should return the correct route object'
|
|
96
|
+
)
|
|
97
|
+
|
|
74
98
|
assert.equal(
|
|
75
99
|
matchHash('/movies/avengers-endgame', routes),
|
|
76
|
-
routes[
|
|
100
|
+
routes[5],
|
|
77
101
|
'Should return the correct route object'
|
|
78
102
|
)
|
|
103
|
+
|
|
104
|
+
assert.equal(
|
|
105
|
+
matchHash('/movies/special-1', routes),
|
|
106
|
+
routes[5],
|
|
107
|
+
'Should return the correct route object'
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
assert.equal(
|
|
111
|
+
matchHash('/dutchmovies/123', routes),
|
|
112
|
+
routes[8],
|
|
113
|
+
'Should return the correct route object'
|
|
114
|
+
)
|
|
115
|
+
|
|
79
116
|
assert.end()
|
|
80
117
|
})
|
|
81
118
|
|
|
82
119
|
test('Match paths with a wildcard asterix', (assert) => {
|
|
83
120
|
assert.equal(
|
|
84
121
|
matchHash('/examples/example1', routes),
|
|
85
|
-
routes[
|
|
122
|
+
routes[6],
|
|
86
123
|
'Should return the correct route object'
|
|
87
124
|
)
|
|
88
125
|
|
|
89
126
|
assert.equal(
|
|
90
127
|
matchHash('/examples/example1/subexample/and-another-page', routes),
|
|
91
|
-
routes[
|
|
128
|
+
routes[6],
|
|
92
129
|
'Should return the correct route object'
|
|
93
130
|
)
|
|
94
131
|
|
|
95
|
-
assert.equal(matchHash('/404', routes), routes[
|
|
132
|
+
assert.equal(matchHash('/404', routes), routes[9], 'Should return the correct route object')
|
|
133
|
+
|
|
134
|
+
assert.equal(
|
|
135
|
+
matchHash('/page1/non-existing-path', routes),
|
|
136
|
+
routes[9],
|
|
137
|
+
'Should return the correct route object'
|
|
138
|
+
)
|
|
96
139
|
|
|
97
140
|
assert.equal(
|
|
98
141
|
matchHash('/page1/subpage1/i-dont-exist', routes),
|
|
99
|
-
routes[
|
|
142
|
+
routes[9],
|
|
143
|
+
'Should return the correct route object'
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
assert.equal(
|
|
147
|
+
matchHash('/completely/unexpected/route', routes),
|
|
148
|
+
routes[9],
|
|
100
149
|
'Should return the correct route object'
|
|
101
150
|
)
|
|
102
151
|
|
|
@@ -124,6 +173,15 @@ test('Add params to route for dynamic route matches', (assert) => {
|
|
|
124
173
|
'Should return the correct route object'
|
|
125
174
|
)
|
|
126
175
|
|
|
176
|
+
const match3 = matchHash('/dutchmovies/1', routes)
|
|
177
|
+
assert.deepEqual(
|
|
178
|
+
match3.params,
|
|
179
|
+
{
|
|
180
|
+
id: '1',
|
|
181
|
+
},
|
|
182
|
+
'Should return the correct route object'
|
|
183
|
+
)
|
|
184
|
+
|
|
127
185
|
assert.end()
|
|
128
186
|
})
|
|
129
187
|
|
|
@@ -166,7 +224,13 @@ test('Work with trailing slashes', (assert) => {
|
|
|
166
224
|
|
|
167
225
|
assert.equal(
|
|
168
226
|
matchHash('/route/with/trailing/slash', routes),
|
|
169
|
-
routes[
|
|
227
|
+
routes[7],
|
|
228
|
+
'Should return the correct route object'
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
assert.equal(
|
|
232
|
+
matchHash('/tv/simpsons/seasons/first', routes),
|
|
233
|
+
routes[3],
|
|
170
234
|
'Should return the correct route object'
|
|
171
235
|
)
|
|
172
236
|
|
|
@@ -185,7 +249,13 @@ test('Work with and without leading slashes', (assert) => {
|
|
|
185
249
|
|
|
186
250
|
assert.equal(
|
|
187
251
|
matchHash('route/with/trailing/slash', routes),
|
|
188
|
-
routes[
|
|
252
|
+
routes[7],
|
|
253
|
+
'Should return the correct route object'
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
assert.equal(
|
|
257
|
+
matchHash('//tv/simpsons/seasons/first//', routes),
|
|
258
|
+
routes[3],
|
|
189
259
|
'Should return the correct route object'
|
|
190
260
|
)
|
|
191
261
|
|
|
@@ -208,7 +278,7 @@ test('Match routes case insensitive', (assert) => {
|
|
|
208
278
|
|
|
209
279
|
assert.equal(
|
|
210
280
|
matchHash('/Movies/Avengers-Endgame', routes),
|
|
211
|
-
routes[
|
|
281
|
+
routes[5],
|
|
212
282
|
'Should return the correct route object'
|
|
213
283
|
)
|
|
214
284
|
assert.end()
|
|
@@ -234,5 +304,170 @@ test('Match routes case insensitive, but pass props with original casing', (asse
|
|
|
234
304
|
'Should return the correct params object'
|
|
235
305
|
)
|
|
236
306
|
|
|
307
|
+
const match3 = matchHash('/DutchMovies/ID_123', routes)
|
|
308
|
+
assert.deepEqual(
|
|
309
|
+
match3.params,
|
|
310
|
+
{
|
|
311
|
+
id: 'ID_123',
|
|
312
|
+
},
|
|
313
|
+
'Should return the correct params object'
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
assert.end()
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
test('Match paths with dynamic route parts along with query string params', (assert) => {
|
|
320
|
+
const hash1 = '#/tv/simpsons/seasons/first?token=123&ln=en'
|
|
321
|
+
document.location.hash = hash1
|
|
322
|
+
|
|
323
|
+
const { hash, path, queryParams } = getHash()
|
|
324
|
+
|
|
325
|
+
assert.equal(hash, hash1, 'Should return correct hash')
|
|
326
|
+
assert.equal(path, '/tv/simpsons/seasons/first', 'Should return the correct route path')
|
|
327
|
+
assert.equal(matchHash(path, routes), routes[3], 'Should return the correct route object')
|
|
328
|
+
assert.equal(
|
|
329
|
+
queryParams.get('token'),
|
|
330
|
+
'123',
|
|
331
|
+
'Should contain the correct query parameter key with value'
|
|
332
|
+
)
|
|
333
|
+
assert.equal(
|
|
334
|
+
queryParams.get('ln'),
|
|
335
|
+
'en',
|
|
336
|
+
'Should contain the correct query parameter key with value'
|
|
337
|
+
)
|
|
338
|
+
assert.end()
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
test('Get the hash from the URL', (assert) => {
|
|
342
|
+
const hash = '#/movies/action/the-avengers'
|
|
343
|
+
document.location.hash = hash
|
|
344
|
+
|
|
345
|
+
const result = getHash()
|
|
346
|
+
|
|
347
|
+
assert.equal(
|
|
348
|
+
result.hash,
|
|
349
|
+
hash,
|
|
350
|
+
'The result object should contain a hash key with the correct location hash'
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
assert.equal(
|
|
354
|
+
result.path,
|
|
355
|
+
'/movies/action/the-avengers',
|
|
356
|
+
'The result object should contain a path key with the hash (stripped the # symbol)'
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
assert.end()
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
test('Get the hash from the URL and handle query params', (assert) => {
|
|
363
|
+
const hash = '#/movies/comedy/the-hangover?category=1&item=2'
|
|
364
|
+
document.location.hash = hash
|
|
365
|
+
|
|
366
|
+
const result = getHash()
|
|
367
|
+
|
|
368
|
+
assert.equal(
|
|
369
|
+
result.hash,
|
|
370
|
+
hash,
|
|
371
|
+
'The result object should contain a hash key with the correct location hash without the query params'
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
assert.equal(
|
|
375
|
+
result.path,
|
|
376
|
+
'/movies/comedy/the-hangover',
|
|
377
|
+
'The result object should contain a path key with the hash (stripped the # symbol)'
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
assert.equal(
|
|
381
|
+
result.queryParams instanceof URLSearchParams,
|
|
382
|
+
true,
|
|
383
|
+
'The result object should contain a queryParams key with an URLSearchParams object'
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
assert.equal(
|
|
387
|
+
result.queryParams.get('category'),
|
|
388
|
+
'1',
|
|
389
|
+
'The result object should contain a queryParams key with the correct route query param values'
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
assert.equal(
|
|
393
|
+
result.queryParams.get('item'),
|
|
394
|
+
'2',
|
|
395
|
+
'The result object should contain a queryParams key with the correct route query param values'
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
assert.end()
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
test('Get route object from Match hash when navigating using to() method', (assert) => {
|
|
402
|
+
const hash = '/page1/subpage1'
|
|
403
|
+
|
|
404
|
+
to(hash)
|
|
405
|
+
|
|
406
|
+
const result = matchHash(hash, routes)
|
|
407
|
+
|
|
408
|
+
assert.equal(
|
|
409
|
+
result.path,
|
|
410
|
+
'page1/subpage1',
|
|
411
|
+
'The result object should contain a path key with path hash'
|
|
412
|
+
)
|
|
413
|
+
assert.equal(
|
|
414
|
+
Object.keys(result.params).length,
|
|
415
|
+
0,
|
|
416
|
+
'The results object should contain a params key with zero props'
|
|
417
|
+
)
|
|
418
|
+
assert.equal(
|
|
419
|
+
Object.keys(result.data).length,
|
|
420
|
+
0,
|
|
421
|
+
'The results object should contain a data key with zero props'
|
|
422
|
+
)
|
|
423
|
+
assert.equal(
|
|
424
|
+
Object.keys(result.options).length,
|
|
425
|
+
0,
|
|
426
|
+
'The results object should contain a options key with zero props'
|
|
427
|
+
)
|
|
428
|
+
assert.end()
|
|
429
|
+
})
|
|
430
|
+
|
|
431
|
+
test('Get route object from Match hash when navigating using to() method with options', (assert) => {
|
|
432
|
+
const hash = '/page1/subpage1'
|
|
433
|
+
|
|
434
|
+
to(hash, undefined, { keepAlive: true })
|
|
435
|
+
|
|
436
|
+
const result = matchHash(hash, routes)
|
|
437
|
+
|
|
438
|
+
assert.equal(
|
|
439
|
+
result.path,
|
|
440
|
+
'page1/subpage1',
|
|
441
|
+
'The result object should contain a path key with path hash'
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
assert.equal(
|
|
445
|
+
result.options.keepAlive,
|
|
446
|
+
true,
|
|
447
|
+
'The results object should contain a options key with keep alive as True'
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
assert.end()
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
test('Get Hash from URL when navigating using to() method', (assert) => {
|
|
454
|
+
const hash = '#/movies/action/avengers'
|
|
455
|
+
|
|
456
|
+
to(hash)
|
|
457
|
+
|
|
458
|
+
const result = getHash()
|
|
459
|
+
|
|
460
|
+
assert.equal(
|
|
461
|
+
result.hash,
|
|
462
|
+
hash,
|
|
463
|
+
'The result object key property should contain correct location hash'
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
assert.equal(
|
|
467
|
+
result.path,
|
|
468
|
+
'/movies/action/avengers',
|
|
469
|
+
'The result object should contain a path key with hash without #'
|
|
470
|
+
)
|
|
471
|
+
|
|
237
472
|
assert.end()
|
|
238
473
|
})
|