@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 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/.gitignore'),
185
+ path.join(fixturesBase, 'common/git/gitignore'),
186
186
  path.join(cwd, '.gitignore')
187
187
  )
188
188
  })
@@ -0,0 +1,5 @@
1
+ .DS_Store
2
+ node_modules
3
+ build
4
+ dist
5
+ env
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightningjs/blits",
3
- "version": "0.6.11",
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",
@@ -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
  */
@@ -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
- const route = routes
41
- .filter((r) => {
42
- return r.path === path
43
- })
44
- .pop()
45
- if (route) {
46
- route.options = { ...route.options, ...overrideOptions }
47
- currentRoute = route
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
- return route
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
- view = await route.component(this[symbols.props], holder, this)
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(this[symbols.props], holder, this)
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(this[symbols.props], holder, this)
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
- to(route.path)
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
+ })