@lightningjs/blits 0.6.11 → 0.6.12
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 +8 -0
- package/bin/helpers.js +1 -1
- package/boilerplate/common/git/gitignore +5 -0
- package/package.json +4 -2
- package/src/component.d.ts +11 -1
- package/src/router/router.js +72 -13
- package/src/router/router.test.js +238 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
# v0.6.12
|
|
4
|
+
|
|
5
|
+
_9 feb 2024_
|
|
6
|
+
|
|
7
|
+
- Added type hinting for `this.$clearInterval` and `this.$clearTimeout`
|
|
8
|
+
- Fixed issue with `.gitignore` in app create flow
|
|
9
|
+
- Added support for dynamic route parts in the router (i.e `/tv/:series/episodes/:episode`)
|
|
10
|
+
|
|
3
11
|
# v0.6.11
|
|
4
12
|
|
|
5
13
|
_7 feb 2024_
|
package/bin/helpers.js
CHANGED
|
@@ -182,7 +182,7 @@ export const gitInit = (cwd, fixturesBase) => {
|
|
|
182
182
|
.then(({ stdout }) => (msg = stdout))
|
|
183
183
|
.then(() => {
|
|
184
184
|
return fs.copyFileSync(
|
|
185
|
-
path.join(fixturesBase, 'common/git
|
|
185
|
+
path.join(fixturesBase, 'common/git/gitignore'),
|
|
186
186
|
path.join(cwd, '.gitignore')
|
|
187
187
|
)
|
|
188
188
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightningjs/blits",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.12",
|
|
4
4
|
"description": "Blits: The Lightning 3 App Development Framework",
|
|
5
5
|
"bin": "bin/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
13
|
"test": "c8 npm run test:run",
|
|
14
|
-
"test:run": "tape '{,!(node_modules)/**/}*.test.js' | tap-diff",
|
|
14
|
+
"test:run": "node -r 'global-jsdom/register' ./node_modules/.bin/tape '{,!(node_modules)/**/}*.test.js' | tap-diff",
|
|
15
15
|
"lint": "eslint '**/*.js'",
|
|
16
16
|
"lint:fix": "eslint '**/*.js' --fix",
|
|
17
17
|
"prepublishOnly": "node scripts/prepublishOnly.js",
|
|
@@ -36,7 +36,9 @@
|
|
|
36
36
|
"eslint": "^8.8.0",
|
|
37
37
|
"eslint-config-prettier": "^8.3.0",
|
|
38
38
|
"eslint-plugin-prettier": "^4.0.0",
|
|
39
|
+
"global-jsdom": "24.0.0",
|
|
39
40
|
"husky": "^7.0.4",
|
|
41
|
+
"jsdom": "24.0.0",
|
|
40
42
|
"lint-staged": "^12.3.3",
|
|
41
43
|
"prettier": "^2.5.1",
|
|
42
44
|
"tap-diff": "^0.1.1",
|
package/src/component.d.ts
CHANGED
|
@@ -142,12 +142,22 @@ declare namespace Component {
|
|
|
142
142
|
* Set a timeout that is automatically cleaned upon component destroy
|
|
143
143
|
*/
|
|
144
144
|
$setTimeout: (callback: (args: any) => void, ms?: number | undefined) => ReturnType<typeof setTimeout>
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Clear a timeout
|
|
148
|
+
*/
|
|
149
|
+
$clearTimeout: (id: ReturnType<typeof setTimeout>) => void
|
|
145
150
|
|
|
146
151
|
/**
|
|
147
152
|
* Set an interval that is automatically cleaned upon component destroy
|
|
148
153
|
*/
|
|
149
154
|
$setInterval: (callback: (args: any) => void, ms?: number | undefined) => ReturnType<typeof setInterval>
|
|
150
|
-
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Clear a interval
|
|
158
|
+
*/
|
|
159
|
+
$clearInterval: (id: ReturnType<typeof setInterval>) => void
|
|
160
|
+
|
|
151
161
|
/**
|
|
152
162
|
* Log to the console with prettier output and configurable debug levels in Settings
|
|
153
163
|
*/
|
package/src/router/router.js
CHANGED
|
@@ -36,17 +36,68 @@ export const getHash = () => {
|
|
|
36
36
|
return (document.location.hash || '/').replace(/^#/, '')
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
const normalizePath = (path) => {
|
|
40
|
+
return (
|
|
41
|
+
path
|
|
42
|
+
// remove leading and trailing slashes
|
|
43
|
+
.replace(/^\/+|\/+$/g, '')
|
|
44
|
+
.toLowerCase()
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
39
48
|
export const matchHash = (path, routes = []) => {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
route
|
|
47
|
-
|
|
49
|
+
// remove trailing slashes
|
|
50
|
+
const originalPath = path
|
|
51
|
+
path = normalizePath(path)
|
|
52
|
+
let matchingRoute = false
|
|
53
|
+
let i = 0
|
|
54
|
+
while (!matchingRoute && i < routes.length) {
|
|
55
|
+
const route = routes[i]
|
|
56
|
+
route.path = normalizePath(route.path)
|
|
57
|
+
if (route.path === path) {
|
|
58
|
+
matchingRoute = route
|
|
59
|
+
} else if (route.path.indexOf(':') > -1) {
|
|
60
|
+
// match dynamic route parts
|
|
61
|
+
const dynamicRouteParts = [...route.path.matchAll(/:([^\s/]+)/gi)]
|
|
62
|
+
|
|
63
|
+
// construct a regex for the route with dynamic parts
|
|
64
|
+
let dynamicRoutePartsRegex = route.path
|
|
65
|
+
dynamicRouteParts.reverse().forEach((part) => {
|
|
66
|
+
dynamicRoutePartsRegex =
|
|
67
|
+
dynamicRoutePartsRegex.substring(0, part.index) +
|
|
68
|
+
'([^\\s/]+)' +
|
|
69
|
+
dynamicRoutePartsRegex.substring(part.index + part[0].length)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
// test if the constructed regex matches the path
|
|
73
|
+
const match = originalPath.match(new RegExp(`${dynamicRoutePartsRegex}`, 'i'))
|
|
74
|
+
|
|
75
|
+
if (match) {
|
|
76
|
+
// map the route params to a params object
|
|
77
|
+
route.params = dynamicRouteParts.reverse().reduce((acc, part, index) => {
|
|
78
|
+
acc[part[1]] = match[index + 1]
|
|
79
|
+
return acc
|
|
80
|
+
}, {})
|
|
81
|
+
matchingRoute = route
|
|
82
|
+
}
|
|
83
|
+
} else if (route.path.endsWith('*')) {
|
|
84
|
+
const regex = new RegExp(route.path.replace(/\/?\*/, '/?([^\\s]*)'), 'i')
|
|
85
|
+
const match = path.match(regex)
|
|
86
|
+
|
|
87
|
+
if (match) {
|
|
88
|
+
if (match[1]) route.params = { path: match[1] }
|
|
89
|
+
matchingRoute = route
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
i++
|
|
48
93
|
}
|
|
49
|
-
|
|
94
|
+
|
|
95
|
+
if (matchingRoute) {
|
|
96
|
+
matchingRoute.options = { ...matchingRoute.options, ...overrideOptions }
|
|
97
|
+
currentRoute = matchingRoute
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return matchingRoute
|
|
50
101
|
}
|
|
51
102
|
|
|
52
103
|
export const navigate = async function () {
|
|
@@ -81,16 +132,18 @@ export const navigate = async function () {
|
|
|
81
132
|
holder.populate({})
|
|
82
133
|
holder.set('w', '100%')
|
|
83
134
|
holder.set('h', '100%')
|
|
84
|
-
|
|
135
|
+
// merge props with potential route params to be injected into the component instance
|
|
136
|
+
const props = { ...this[symbols.props], ...route.params }
|
|
137
|
+
view = await route.component({ props }, holder, this)
|
|
85
138
|
if (view[Symbol.toStringTag] === 'Module') {
|
|
86
139
|
if (view.default && typeof view.default === 'function') {
|
|
87
|
-
view = view.default(
|
|
140
|
+
view = view.default({ props }, holder, this)
|
|
88
141
|
} else {
|
|
89
142
|
Log.error("Dynamic import doesn't have a default export or default is not a function")
|
|
90
143
|
}
|
|
91
144
|
}
|
|
92
145
|
if (typeof view === 'function') {
|
|
93
|
-
view = view(
|
|
146
|
+
view = view({ props }, holder, this)
|
|
94
147
|
}
|
|
95
148
|
} else {
|
|
96
149
|
holder = view[symbols.wrapper]
|
|
@@ -207,7 +260,13 @@ export const back = () => {
|
|
|
207
260
|
if (route) {
|
|
208
261
|
// set indicator that we are navigating back (to prevent adding page to history stack)
|
|
209
262
|
navigatingBack = true
|
|
210
|
-
|
|
263
|
+
let targetRoutePath = route.path
|
|
264
|
+
if (targetRoutePath.indexOf(':') > -1) {
|
|
265
|
+
Object.keys(route.params).forEach((item) => {
|
|
266
|
+
targetRoutePath = targetRoutePath.replace(`:${item}`, route.params[item])
|
|
267
|
+
})
|
|
268
|
+
}
|
|
269
|
+
to(targetRoutePath)
|
|
211
270
|
return true
|
|
212
271
|
} else {
|
|
213
272
|
return false
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 Comcast Cable Communications Management, LLC
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License.
|
|
5
|
+
* You may obtain a copy of the License at
|
|
6
|
+
*
|
|
7
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
*
|
|
9
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
* See the License for the specific language governing permissions and
|
|
13
|
+
* limitations under the License.
|
|
14
|
+
*
|
|
15
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import test from 'tape'
|
|
19
|
+
import { matchHash } from './router.js'
|
|
20
|
+
|
|
21
|
+
const routes = [
|
|
22
|
+
{
|
|
23
|
+
path: '/',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
path: '/page1',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
path: '/page1/subpage1',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
path: '/tv/:show/seasons/:season',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
path: '/movies/:name',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
path: '/examples/*',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
path: '/route/with/trailing/slash/',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
path: '*',
|
|
45
|
+
},
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
test('Type of matchHash', (assert) => {
|
|
49
|
+
const expected = 'function'
|
|
50
|
+
const actual = typeof matchHash
|
|
51
|
+
|
|
52
|
+
assert.equal(actual, expected, 'matchhash should be a function')
|
|
53
|
+
assert.end()
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('Match paths with static routes that have an exact match', (assert) => {
|
|
57
|
+
assert.equal(matchHash('/', routes), routes[0], 'Should return the correct route object')
|
|
58
|
+
assert.equal(matchHash('/page1', routes), routes[1], 'Should return the correct route object')
|
|
59
|
+
assert.equal(
|
|
60
|
+
matchHash('/page1/subpage1', routes),
|
|
61
|
+
routes[2],
|
|
62
|
+
'Should return the correct route object'
|
|
63
|
+
)
|
|
64
|
+
assert.end()
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('Match paths with dynamic route parts', (assert) => {
|
|
68
|
+
assert.equal(
|
|
69
|
+
matchHash('/tv/simpsons/seasons/first', routes),
|
|
70
|
+
routes[3],
|
|
71
|
+
'Should return the correct route object'
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
assert.equal(
|
|
75
|
+
matchHash('/movies/avengers-endgame', routes),
|
|
76
|
+
routes[4],
|
|
77
|
+
'Should return the correct route object'
|
|
78
|
+
)
|
|
79
|
+
assert.end()
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test('Match paths with a wildcard asterix', (assert) => {
|
|
83
|
+
assert.equal(
|
|
84
|
+
matchHash('/examples/example1', routes),
|
|
85
|
+
routes[5],
|
|
86
|
+
'Should return the correct route object'
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
assert.equal(
|
|
90
|
+
matchHash('/examples/example1/subexample/and-another-page', routes),
|
|
91
|
+
routes[5],
|
|
92
|
+
'Should return the correct route object'
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
assert.equal(matchHash('/404', routes), routes[7], 'Should return the correct route object')
|
|
96
|
+
|
|
97
|
+
assert.equal(
|
|
98
|
+
matchHash('/page1/subpage1/i-dont-exist', routes),
|
|
99
|
+
routes[7],
|
|
100
|
+
'Should return the correct route object'
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
assert.end()
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test('Add params to route for dynamic route matches', (assert) => {
|
|
107
|
+
const match1 = matchHash('/tv/simpsons/seasons/first', routes)
|
|
108
|
+
|
|
109
|
+
assert.deepEqual(
|
|
110
|
+
match1.params,
|
|
111
|
+
{
|
|
112
|
+
show: 'simpsons',
|
|
113
|
+
season: 'first',
|
|
114
|
+
},
|
|
115
|
+
'Should return the correct params object'
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
const match2 = matchHash('/movies/avengers-endgame', routes)
|
|
119
|
+
assert.deepEqual(
|
|
120
|
+
match2.params,
|
|
121
|
+
{
|
|
122
|
+
name: 'avengers-endgame',
|
|
123
|
+
},
|
|
124
|
+
'Should return the correct route object'
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
assert.end()
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
test('Add remaining path as param for wild card routes', (assert) => {
|
|
131
|
+
const match1 = matchHash('/examples/example1', routes)
|
|
132
|
+
assert.deepEqual(match1.params, { path: 'example1' }, 'Should return the correct route object')
|
|
133
|
+
|
|
134
|
+
const match2 = matchHash('/examples/example1/subexample/and-another-page', routes)
|
|
135
|
+
assert.deepEqual(
|
|
136
|
+
match2.params,
|
|
137
|
+
{ path: 'example1/subexample/and-another-page' },
|
|
138
|
+
'Should return the correct params object'
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
const match3 = matchHash('/404', routes)
|
|
142
|
+
assert.deepEqual(match3.params, { path: '404' }, 'Should return the correct params object')
|
|
143
|
+
|
|
144
|
+
const match4 = matchHash('/page1/subpage1/i-dont-exist', routes)
|
|
145
|
+
assert.deepEqual(
|
|
146
|
+
match4.params,
|
|
147
|
+
{ path: 'page1/subpage1/i-dont-exist' },
|
|
148
|
+
'Should return the correct params object'
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
assert.end()
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
test('Work with trailing slashes', (assert) => {
|
|
155
|
+
assert.equal(matchHash('/page1/', routes), routes[1], 'Should return the correct route object')
|
|
156
|
+
assert.equal(
|
|
157
|
+
matchHash('/page1//////', routes),
|
|
158
|
+
routes[1],
|
|
159
|
+
'Should return the correct route object'
|
|
160
|
+
)
|
|
161
|
+
assert.equal(
|
|
162
|
+
matchHash('/page1/subpage1/', routes),
|
|
163
|
+
routes[2],
|
|
164
|
+
'Should return the correct route object'
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
assert.equal(
|
|
168
|
+
matchHash('/route/with/trailing/slash', routes),
|
|
169
|
+
routes[6],
|
|
170
|
+
'Should return the correct route object'
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
assert.end()
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
test('Work with and without leading slashes', (assert) => {
|
|
177
|
+
assert.equal(matchHash('page1/', routes), routes[1], 'Should return the correct route object')
|
|
178
|
+
|
|
179
|
+
assert.equal(matchHash('////page1', routes), routes[1], 'Should return the correct route object')
|
|
180
|
+
assert.equal(
|
|
181
|
+
matchHash('page1/subpage1/', routes),
|
|
182
|
+
routes[2],
|
|
183
|
+
'Should return the correct route object'
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
assert.equal(
|
|
187
|
+
matchHash('route/with/trailing/slash', routes),
|
|
188
|
+
routes[6],
|
|
189
|
+
'Should return the correct route object'
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
assert.end()
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
test('Match routes case insensitive', (assert) => {
|
|
196
|
+
assert.equal(matchHash('/', routes), routes[0], 'Should return the correct route object')
|
|
197
|
+
assert.equal(matchHash('/Page1', routes), routes[1], 'Should return the correct route object')
|
|
198
|
+
assert.equal(
|
|
199
|
+
matchHash('/pagE1/SUBpage1', routes),
|
|
200
|
+
routes[2],
|
|
201
|
+
'Should return the correct route object'
|
|
202
|
+
)
|
|
203
|
+
assert.equal(
|
|
204
|
+
matchHash('/Tv/SimpsonS/Seasons/First', routes),
|
|
205
|
+
routes[3],
|
|
206
|
+
'Should return the correct route object'
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
assert.equal(
|
|
210
|
+
matchHash('/Movies/Avengers-Endgame', routes),
|
|
211
|
+
routes[4],
|
|
212
|
+
'Should return the correct route object'
|
|
213
|
+
)
|
|
214
|
+
assert.end()
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
test('Match routes case insensitive, but pass props with original casing', (assert) => {
|
|
218
|
+
const match1 = matchHash('/Tv/SimpsonS/Seasons/First', routes)
|
|
219
|
+
assert.deepEqual(
|
|
220
|
+
match1.params,
|
|
221
|
+
{
|
|
222
|
+
show: 'SimpsonS',
|
|
223
|
+
season: 'First',
|
|
224
|
+
},
|
|
225
|
+
'Should return the correct params object'
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
const match2 = matchHash('/Movies/Avengers-Endgame', routes)
|
|
229
|
+
assert.deepEqual(
|
|
230
|
+
match2.params,
|
|
231
|
+
{
|
|
232
|
+
name: 'Avengers-Endgame',
|
|
233
|
+
},
|
|
234
|
+
'Should return the correct params object'
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
assert.end()
|
|
238
|
+
})
|