@lightningjs/blits 1.23.2 → 1.24.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.
@@ -12,6 +12,9 @@ jobs:
12
12
  steps:
13
13
  - name: Checkout Code
14
14
  uses: actions/checkout@v3
15
+ with:
16
+ ref: ${{ github.event.pull_request.head.ref }}
17
+ repository: ${{ github.event.pull_request.head.repo.full_name }}
15
18
 
16
19
  - name: Set up Node.js
17
20
  uses: actions/setup-node@v3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## v1.24.0
4
+
5
+ _26 mar 2025_
6
+
7
+ - Added additional tests for router
8
+ - Fixed issues with `:show` attribute not working properly
9
+ - Fixed issue with strings starting with a number being casted to a number
10
+ - Fixed named slots functionality
11
+
3
12
  ## v1.23.2
4
13
 
5
14
  _18 mar 2025_
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightningjs/blits",
3
- "version": "1.23.2",
3
+ "version": "1.24.0",
4
4
  "description": "Blits: The Lightning 3 App Development Framework",
5
5
  "bin": "bin/index.js",
6
6
  "exports": {
@@ -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) {
@@ -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'] = component[Symbol.for('slots')] !== undefined && Array.isArray(component[Symbol.for('slots')]) === true && component[Symbol.for('slots')].filter(slot => slot.ref === '${templateObject.slot}').shift() || 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'] = component[Symbol.for('slots')] !== undefined &&
1937
- Array.isArray(component[Symbol.for('slots')]) === true &&
1938
- component[Symbol.for('slots')].filter(slot => slot.ref === 'mySlot').shift() || parent
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
 
@@ -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[4],
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[5],
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[5],
128
+ routes[6],
92
129
  'Should return the correct route object'
93
130
  )
94
131
 
95
- assert.equal(matchHash('/404', routes), routes[7], 'Should return the correct route object')
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[7],
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[6],
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[6],
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[4],
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
  })