@jseeio/jsee 0.4.0 → 0.4.2
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/.claude/settings.local.json +3 -1
- package/CHANGELOG.md +2 -1
- package/README.md +122 -88
- package/dist/jsee.js +1 -1
- package/dist/jsee.runtime.js +1 -1
- package/dump.sh +23 -0
- package/jsee.dump.txt +5459 -0
- package/package.json +1 -1
- package/src/app.js +3 -1
- package/src/cli.js +45 -12
- package/src/main.js +37 -3
- package/src/utils.js +9 -1
- package/test/arrow-main.html +18 -0
- package/test/arrow-worker.html +18 -0
- package/test/runtime-arrow.html +18 -0
- package/test/test-basic.test.js +27 -0
- package/test/unit/cli-fetch.test.js +112 -23
- package/test/unit/utils.test.js +20 -0
package/package.json
CHANGED
package/src/app.js
CHANGED
|
@@ -231,7 +231,9 @@ function createVueApp (env, mountedCallback, logMain) {
|
|
|
231
231
|
app.use(window[framework])
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
-
|
|
234
|
+
const component = app.mount(container) // After app.mount() it's not the same app
|
|
235
|
+
component.__vueApp = app
|
|
236
|
+
return component
|
|
235
237
|
}
|
|
236
238
|
|
|
237
239
|
export { createVueApp }
|
package/src/cli.js
CHANGED
|
@@ -80,26 +80,58 @@ function getImportUrlValue (importValue) {
|
|
|
80
80
|
return null
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
// Resolve an import path to a local file by checking candidate locations on disk.
|
|
84
|
+
// Returns the absolute file path if found, null otherwise.
|
|
85
|
+
// This replaces heuristic prefix detection (isLocalJsImport/isLocalCssImport) with
|
|
86
|
+
// a definitive filesystem check — no heuristic can reliably distinguish bare-relative
|
|
87
|
+
// local paths like "dist/core.js" from npm package subpaths like "chart.js/dist/chart.min.js".
|
|
88
|
+
function resolveLocalImportFile (importUrlValue, modelUrl, cwd) {
|
|
89
|
+
if (!importUrlValue || typeof importUrlValue !== 'string') return null
|
|
90
|
+
if (isHttpUrl(importUrlValue)) return null
|
|
91
|
+
|
|
92
|
+
const modelDir = modelUrl && !isHttpUrl(modelUrl) ? path.dirname(modelUrl) : '.'
|
|
93
|
+
const candidates = []
|
|
94
|
+
|
|
95
|
+
if (importUrlValue.startsWith('./') || importUrlValue.startsWith('../')) {
|
|
96
|
+
// Explicit relative: prefer model-relative (colocated helpers), fallback to cwd
|
|
97
|
+
candidates.push(path.resolve(cwd, path.join(modelDir, importUrlValue)))
|
|
98
|
+
candidates.push(path.resolve(cwd, importUrlValue))
|
|
99
|
+
} else if (importUrlValue.startsWith('/')) {
|
|
100
|
+
// Absolute from project root
|
|
101
|
+
candidates.push(path.resolve(cwd, importUrlValue.slice(1)))
|
|
102
|
+
} else {
|
|
103
|
+
// Bare relative (dist/core.js): prefer cwd (schema-relative), fallback to model-relative
|
|
104
|
+
candidates.push(path.resolve(cwd, importUrlValue))
|
|
105
|
+
candidates.push(path.resolve(cwd, path.join(modelDir, importUrlValue)))
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
for (const fp of candidates) {
|
|
109
|
+
if (fs.existsSync(fp) && fs.statSync(fp).isFile()) return fp
|
|
110
|
+
}
|
|
111
|
+
return null
|
|
112
|
+
}
|
|
113
|
+
|
|
83
114
|
function resolveFetchImport (importValue, modelUrl, cwd) {
|
|
84
115
|
const importUrlValue = getImportUrlValue(importValue)
|
|
85
|
-
if (!importUrlValue)
|
|
86
|
-
return null
|
|
87
|
-
}
|
|
116
|
+
if (!importUrlValue) return null
|
|
88
117
|
const importIsObject = importValue && typeof importValue === 'object'
|
|
89
118
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
119
|
+
const localFilePath = resolveLocalImportFile(importUrlValue, modelUrl, cwd)
|
|
120
|
+
|
|
121
|
+
if (localFilePath) {
|
|
122
|
+
// Keep the raw import string as the lookup key (importUrl) so that
|
|
123
|
+
// data-src in the bundled HTML matches what the runtime's loadFromDOM
|
|
124
|
+
// will look up before getUrl transforms the path.
|
|
94
125
|
return {
|
|
95
|
-
schemaImport:
|
|
96
|
-
schemaEntry: importIsObject ? { ...importValue, url:
|
|
97
|
-
importUrl:
|
|
98
|
-
localFilePath
|
|
126
|
+
schemaImport: importUrlValue,
|
|
127
|
+
schemaEntry: importIsObject ? { ...importValue, url: importUrlValue } : importUrlValue,
|
|
128
|
+
importUrl: importUrlValue,
|
|
129
|
+
localFilePath,
|
|
99
130
|
remoteUrl: null
|
|
100
131
|
}
|
|
101
132
|
}
|
|
102
133
|
|
|
134
|
+
// Remote: npm package or explicit HTTP URL
|
|
103
135
|
const remoteUrl = isHttpUrl(importUrlValue)
|
|
104
136
|
? importUrlValue
|
|
105
137
|
: `https://cdn.jsdelivr.net/npm/${importUrlValue}`
|
|
@@ -108,7 +140,7 @@ function resolveFetchImport (importValue, modelUrl, cwd) {
|
|
|
108
140
|
schemaEntry: importIsObject ? { ...importValue, url: importUrlValue } : importUrlValue,
|
|
109
141
|
importUrl: toRuntimeUrl(importUrlValue),
|
|
110
142
|
localFilePath: null,
|
|
111
|
-
remoteUrl
|
|
143
|
+
remoteUrl
|
|
112
144
|
}
|
|
113
145
|
}
|
|
114
146
|
|
|
@@ -1053,6 +1085,7 @@ Documentation: https://jsee.org
|
|
|
1053
1085
|
|
|
1054
1086
|
module.exports = gen
|
|
1055
1087
|
module.exports.collectFetchBundleBlocks = collectFetchBundleBlocks
|
|
1088
|
+
module.exports.resolveLocalImportFile = resolveLocalImportFile
|
|
1056
1089
|
module.exports.resolveFetchImport = resolveFetchImport
|
|
1057
1090
|
module.exports.resolveRuntimeMode = resolveRuntimeMode
|
|
1058
1091
|
module.exports.resolveOutputPath = resolveOutputPath
|
package/src/main.js
CHANGED
|
@@ -152,6 +152,7 @@ export default class JSEE {
|
|
|
152
152
|
this.__version__ = VERSION
|
|
153
153
|
this.cancelled = false
|
|
154
154
|
this._cancelWorkerRun = null
|
|
155
|
+
this._workers = []
|
|
155
156
|
|
|
156
157
|
// Check if schema is provided
|
|
157
158
|
if (typeof this.schema === 'undefined') {
|
|
@@ -171,7 +172,7 @@ export default class JSEE {
|
|
|
171
172
|
}
|
|
172
173
|
}
|
|
173
174
|
|
|
174
|
-
this.init()
|
|
175
|
+
this._initPromise = this.init()
|
|
175
176
|
}
|
|
176
177
|
|
|
177
178
|
log (...args) {
|
|
@@ -194,6 +195,35 @@ export default class JSEE {
|
|
|
194
195
|
return this.cancelled === true
|
|
195
196
|
}
|
|
196
197
|
|
|
198
|
+
destroy () {
|
|
199
|
+
log('Destroying JSEE instance')
|
|
200
|
+
// Cancel any running computation
|
|
201
|
+
this.cancelCurrentRun()
|
|
202
|
+
// Terminate all workers
|
|
203
|
+
this._workers.forEach(w => {
|
|
204
|
+
try { w.terminate() } catch (e) { /* ignore */ }
|
|
205
|
+
})
|
|
206
|
+
this._workers = []
|
|
207
|
+
// Unmount Vue app
|
|
208
|
+
if (this.app && this.app.__vueApp) {
|
|
209
|
+
this.app.__vueApp.unmount()
|
|
210
|
+
}
|
|
211
|
+
// Clean up overlay
|
|
212
|
+
if (this.overlay) {
|
|
213
|
+
this.overlay.hide()
|
|
214
|
+
}
|
|
215
|
+
// Remove progress bar
|
|
216
|
+
const progress = document.querySelector('#progress')
|
|
217
|
+
if (progress) progress.remove()
|
|
218
|
+
// Null out references
|
|
219
|
+
this.app = null
|
|
220
|
+
this.data = null
|
|
221
|
+
this.pipeline = null
|
|
222
|
+
this.model = null
|
|
223
|
+
this.schema = null
|
|
224
|
+
this._cancelWorkerRun = null
|
|
225
|
+
}
|
|
226
|
+
|
|
197
227
|
progress (i) {
|
|
198
228
|
const progressState = utils.getProgressState(i)
|
|
199
229
|
if (!progressState) {
|
|
@@ -413,8 +443,11 @@ export default class JSEE {
|
|
|
413
443
|
imp = m.imports[i]
|
|
414
444
|
}
|
|
415
445
|
if (!m.type.includes('py')) {
|
|
416
|
-
imp.
|
|
417
|
-
imp.
|
|
446
|
+
imp.code = utils.loadFromDOM(imp.url) // Try raw path first (matches --fetch data-src)
|
|
447
|
+
imp.url = utils.getUrl(imp.url) // Resolve to absolute URL for network/worker
|
|
448
|
+
if (!imp.code) {
|
|
449
|
+
imp.code = utils.loadFromDOM(imp.url) // Fallback: resolved URL (legacy compat)
|
|
450
|
+
}
|
|
418
451
|
}
|
|
419
452
|
}
|
|
420
453
|
}
|
|
@@ -578,6 +611,7 @@ export default class JSEE {
|
|
|
578
611
|
async initWorker (model) {
|
|
579
612
|
// Init worker
|
|
580
613
|
const worker = new Worker()
|
|
614
|
+
this._workers.push(worker)
|
|
581
615
|
|
|
582
616
|
// Init worker with the model
|
|
583
617
|
if (typeof model.code === 'function') {
|
package/src/utils.js
CHANGED
|
@@ -775,7 +775,15 @@ function debounce (fn, ms) {
|
|
|
775
775
|
function getName (code) {
|
|
776
776
|
switch (typeof code) {
|
|
777
777
|
case 'function':
|
|
778
|
-
|
|
778
|
+
if (!code.name) return undefined
|
|
779
|
+
// Arrow functions get an inferred .name from property assignment
|
|
780
|
+
// (e.g. { code: (a) => a } → code.name === "code") which is misleading.
|
|
781
|
+
// Only trust .name when toString() confirms a real named declaration.
|
|
782
|
+
const src = code.toString().trimStart()
|
|
783
|
+
if (src.startsWith('function') || src.startsWith('async function')) {
|
|
784
|
+
return code.name
|
|
785
|
+
}
|
|
786
|
+
return undefined
|
|
779
787
|
case 'string':
|
|
780
788
|
const words = code.split(' ')
|
|
781
789
|
const functionIndex = words.findIndex((word) => word === 'function')
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<html>
|
|
2
|
+
<div id="jsee-container">
|
|
3
|
+
<script src="/dist/jsee.js"></script>
|
|
4
|
+
<script>
|
|
5
|
+
new JSEE({
|
|
6
|
+
schema: {
|
|
7
|
+
model: {
|
|
8
|
+
worker: false,
|
|
9
|
+
code: ({ a, b }) => ({ sum: Number(a) + Number(b) })
|
|
10
|
+
},
|
|
11
|
+
inputs: [
|
|
12
|
+
{ name: 'a', type: 'int', default: 100 },
|
|
13
|
+
{ name: 'b', type: 'int', default: 42 }
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
</script>
|
|
18
|
+
</html>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<html>
|
|
2
|
+
<div id="jsee-container">
|
|
3
|
+
<script src="/dist/jsee.js"></script>
|
|
4
|
+
<script>
|
|
5
|
+
new JSEE({
|
|
6
|
+
schema: {
|
|
7
|
+
model: {
|
|
8
|
+
worker: true,
|
|
9
|
+
code: ({ a, b }) => ({ sum: Number(a) + Number(b) })
|
|
10
|
+
},
|
|
11
|
+
inputs: [
|
|
12
|
+
{ name: 'a', type: 'int', default: 100 },
|
|
13
|
+
{ name: 'b', type: 'int', default: 42 }
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
</script>
|
|
18
|
+
</html>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<html>
|
|
2
|
+
<div id="jsee-container">
|
|
3
|
+
<script src="/dist/jsee.runtime.js"></script>
|
|
4
|
+
<script>
|
|
5
|
+
new JSEE({
|
|
6
|
+
schema: {
|
|
7
|
+
model: {
|
|
8
|
+
worker: true,
|
|
9
|
+
code: ({ a, b }) => ({ sum: Number(a) + Number(b) })
|
|
10
|
+
},
|
|
11
|
+
inputs: [
|
|
12
|
+
{ name: 'a', type: 'int', default: 100 },
|
|
13
|
+
{ name: 'b', type: 'int', default: 42 }
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
</script>
|
|
18
|
+
</html>
|
package/test/test-basic.test.js
CHANGED
|
@@ -86,6 +86,33 @@ describe('Minimal examples', () => {
|
|
|
86
86
|
})
|
|
87
87
|
})
|
|
88
88
|
|
|
89
|
+
describe('Arrow function as model.code', () => {
|
|
90
|
+
test('Main thread', async () => {
|
|
91
|
+
await page.goto(urlHTML('arrow-main'))
|
|
92
|
+
await expect(page).toFill('#a', '8')
|
|
93
|
+
await expect(page).toFill('#b', '7')
|
|
94
|
+
await expect(page).toClick('button', { text: 'Run' })
|
|
95
|
+
await expect(page).toMatchTextContent('15')
|
|
96
|
+
})
|
|
97
|
+
test('Worker', async () => {
|
|
98
|
+
await page.goto(urlHTML('arrow-worker'))
|
|
99
|
+
await expect(page).toFill('#a', '8')
|
|
100
|
+
await expect(page).toFill('#b', '7')
|
|
101
|
+
await expect(page).toClick('button', { text: 'Run' })
|
|
102
|
+
await expect(page).toMatchTextContent('15')
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
describe('Runtime build (jsee.runtime.js)', () => {
|
|
107
|
+
test('Arrow function in worker', async () => {
|
|
108
|
+
await page.goto(urlHTML('runtime-arrow'))
|
|
109
|
+
await expect(page).toFill('#a', '8')
|
|
110
|
+
await expect(page).toFill('#b', '7')
|
|
111
|
+
await expect(page).toClick('button', { text: 'Run' })
|
|
112
|
+
await expect(page).toMatchTextContent('15')
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
|
|
89
116
|
describe('Load code directly', () => {
|
|
90
117
|
test('Window', async () => {
|
|
91
118
|
await page.goto(urlHTML('code'))
|
|
@@ -2,7 +2,7 @@ const fs = require('fs')
|
|
|
2
2
|
const os = require('os')
|
|
3
3
|
const path = require('path')
|
|
4
4
|
const gen = require('../../src/cli')
|
|
5
|
-
const { collectFetchBundleBlocks, resolveFetchImport, resolveRuntimeMode } = gen
|
|
5
|
+
const { collectFetchBundleBlocks, resolveLocalImportFile, resolveFetchImport, resolveRuntimeMode } = gen
|
|
6
6
|
|
|
7
7
|
describe('collectFetchBundleBlocks', () => {
|
|
8
8
|
test('collects model, view and render blocks', () => {
|
|
@@ -32,19 +32,107 @@ describe('collectFetchBundleBlocks', () => {
|
|
|
32
32
|
})
|
|
33
33
|
})
|
|
34
34
|
|
|
35
|
+
describe('resolveLocalImportFile', () => {
|
|
36
|
+
let tmpDir
|
|
37
|
+
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'jsee-resolve-'))
|
|
40
|
+
// Create a project layout:
|
|
41
|
+
// dist/a.js
|
|
42
|
+
// css/x.css
|
|
43
|
+
// src/model.js
|
|
44
|
+
// src/helper.js
|
|
45
|
+
fs.mkdirSync(path.join(tmpDir, 'dist'))
|
|
46
|
+
fs.mkdirSync(path.join(tmpDir, 'css'))
|
|
47
|
+
fs.mkdirSync(path.join(tmpDir, 'src'))
|
|
48
|
+
fs.writeFileSync(path.join(tmpDir, 'dist', 'a.js'), '// a')
|
|
49
|
+
fs.writeFileSync(path.join(tmpDir, 'css', 'x.css'), '/* x */')
|
|
50
|
+
fs.writeFileSync(path.join(tmpDir, 'src', 'model.js'), '// model')
|
|
51
|
+
fs.writeFileSync(path.join(tmpDir, 'src', 'helper.js'), '// helper')
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
afterEach(() => {
|
|
55
|
+
fs.rmSync(tmpDir, { recursive: true, force: true })
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test('resolves bare-relative JS path from cwd', () => {
|
|
59
|
+
const result = resolveLocalImportFile('dist/a.js', 'src/model.js', tmpDir)
|
|
60
|
+
expect(result).toBe(path.join(tmpDir, 'dist', 'a.js'))
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('resolves bare-relative CSS path from cwd', () => {
|
|
64
|
+
const result = resolveLocalImportFile('css/x.css', 'src/model.js', tmpDir)
|
|
65
|
+
expect(result).toBe(path.join(tmpDir, 'css', 'x.css'))
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test('resolves explicit-relative path against model directory', () => {
|
|
69
|
+
const result = resolveLocalImportFile('./helper.js', 'src/model.js', tmpDir)
|
|
70
|
+
expect(result).toBe(path.join(tmpDir, 'src', 'helper.js'))
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test('returns null for nonexistent file', () => {
|
|
74
|
+
const result = resolveLocalImportFile('dist/nope.js', 'src/model.js', tmpDir)
|
|
75
|
+
expect(result).toBeNull()
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
test('returns null for HTTP URLs', () => {
|
|
79
|
+
const result = resolveLocalImportFile('https://cdn.example.com/lib.js', 'src/model.js', tmpDir)
|
|
80
|
+
expect(result).toBeNull()
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test('returns null for npm package names', () => {
|
|
84
|
+
const result = resolveLocalImportFile('lodash', 'src/model.js', tmpDir)
|
|
85
|
+
expect(result).toBeNull()
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
|
|
35
89
|
describe('resolveFetchImport', () => {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
90
|
+
let tmpDir
|
|
91
|
+
|
|
92
|
+
beforeEach(() => {
|
|
93
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'jsee-resolve-'))
|
|
94
|
+
fs.mkdirSync(path.join(tmpDir, 'dist'))
|
|
95
|
+
fs.mkdirSync(path.join(tmpDir, 'css'))
|
|
96
|
+
fs.mkdirSync(path.join(tmpDir, 'src'))
|
|
97
|
+
fs.writeFileSync(path.join(tmpDir, 'dist', 'core.js'), '// core')
|
|
98
|
+
fs.writeFileSync(path.join(tmpDir, 'css', 'app.css'), '/* app */')
|
|
99
|
+
fs.writeFileSync(path.join(tmpDir, 'src', 'model.js'), '// model')
|
|
100
|
+
fs.writeFileSync(path.join(tmpDir, 'src', 'helper.js'), '// helper')
|
|
101
|
+
})
|
|
39
102
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
103
|
+
afterEach(() => {
|
|
104
|
+
fs.rmSync(tmpDir, { recursive: true, force: true })
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test('resolves bare-relative local JS with raw importUrl', () => {
|
|
108
|
+
const result = resolveFetchImport('dist/core.js', 'src/model.js', tmpDir)
|
|
109
|
+
|
|
110
|
+
expect(result.schemaImport).toBe('dist/core.js')
|
|
111
|
+
expect(result.importUrl).toBe('dist/core.js')
|
|
112
|
+
expect(result.localFilePath).toBe(path.join(tmpDir, 'dist', 'core.js'))
|
|
113
|
+
expect(result.remoteUrl).toBeNull()
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test('resolves bare-relative local CSS with raw importUrl', () => {
|
|
117
|
+
const result = resolveFetchImport('css/app.css', 'src/model.js', tmpDir)
|
|
118
|
+
|
|
119
|
+
expect(result.schemaImport).toBe('css/app.css')
|
|
120
|
+
expect(result.importUrl).toBe('css/app.css')
|
|
121
|
+
expect(result.localFilePath).toBe(path.join(tmpDir, 'css', 'app.css'))
|
|
122
|
+
expect(result.remoteUrl).toBeNull()
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
test('resolves explicit-relative local import against model dir', () => {
|
|
126
|
+
const result = resolveFetchImport('./helper.js', 'src/model.js', tmpDir)
|
|
127
|
+
|
|
128
|
+
expect(result.schemaImport).toBe('./helper.js')
|
|
129
|
+
expect(result.importUrl).toBe('./helper.js')
|
|
130
|
+
expect(result.localFilePath).toBe(path.join(tmpDir, 'src', 'helper.js'))
|
|
43
131
|
expect(result.remoteUrl).toBeNull()
|
|
44
132
|
})
|
|
45
133
|
|
|
46
134
|
test('keeps package imports as remote URLs', () => {
|
|
47
|
-
const result = resolveFetchImport('chart.js', '
|
|
135
|
+
const result = resolveFetchImport('chart.js', 'src/model.js', tmpDir)
|
|
48
136
|
|
|
49
137
|
expect(result.schemaImport).toBe('chart.js')
|
|
50
138
|
expect(result.importUrl).toBe('https://cdn.jsdelivr.net/npm/chart.js')
|
|
@@ -52,32 +140,33 @@ describe('resolveFetchImport', () => {
|
|
|
52
140
|
expect(result.remoteUrl).toBe('https://cdn.jsdelivr.net/npm/chart.js')
|
|
53
141
|
})
|
|
54
142
|
|
|
143
|
+
test('keeps remote HTTP URLs unchanged', () => {
|
|
144
|
+
const result = resolveFetchImport('https://cdn.example.com/lib.css', 'model.js', tmpDir)
|
|
145
|
+
expect(result.remoteUrl).toBe('https://cdn.example.com/lib.css')
|
|
146
|
+
expect(result.localFilePath).toBeNull()
|
|
147
|
+
})
|
|
148
|
+
|
|
55
149
|
test('supports object imports and preserves extra fields', () => {
|
|
56
150
|
const result = resolveFetchImport(
|
|
57
|
-
{ url: './
|
|
58
|
-
'
|
|
59
|
-
|
|
151
|
+
{ url: './helper.js', integrity: 'sha-123' },
|
|
152
|
+
'src/model.js',
|
|
153
|
+
tmpDir
|
|
60
154
|
)
|
|
61
155
|
|
|
62
156
|
expect(result.schemaEntry).toEqual({
|
|
63
|
-
url: '
|
|
157
|
+
url: './helper.js',
|
|
64
158
|
integrity: 'sha-123'
|
|
65
159
|
})
|
|
66
|
-
expect(result.importUrl).toBe('
|
|
67
|
-
expect(result.localFilePath).toBe(path.join('
|
|
68
|
-
expect(result.remoteUrl).toBeNull()
|
|
69
|
-
})
|
|
70
|
-
test('resolves local CSS import paths', () => {
|
|
71
|
-
const result = resolveFetchImport('./styles/app.css', 'apps/demo/model.js', '/tmp')
|
|
72
|
-
expect(result.schemaImport).toBe('apps/demo/styles/app.css')
|
|
73
|
-
expect(result.localFilePath).toBe(path.join('/tmp', 'apps/demo/styles/app.css'))
|
|
160
|
+
expect(result.importUrl).toBe('./helper.js')
|
|
161
|
+
expect(result.localFilePath).toBe(path.join(tmpDir, 'src', 'helper.js'))
|
|
74
162
|
expect(result.remoteUrl).toBeNull()
|
|
75
163
|
})
|
|
76
164
|
|
|
77
|
-
test('
|
|
78
|
-
const result = resolveFetchImport('
|
|
79
|
-
|
|
165
|
+
test('nonexistent file falls through to remote/CDN', () => {
|
|
166
|
+
const result = resolveFetchImport('dist/nope.js', 'src/model.js', tmpDir)
|
|
167
|
+
|
|
80
168
|
expect(result.localFilePath).toBeNull()
|
|
169
|
+
expect(result.remoteUrl).toBe('https://cdn.jsdelivr.net/npm/dist/nope.js')
|
|
81
170
|
})
|
|
82
171
|
})
|
|
83
172
|
|
package/test/unit/utils.test.js
CHANGED
|
@@ -464,6 +464,26 @@ describe('getName', () => {
|
|
|
464
464
|
function myFunc () {}
|
|
465
465
|
expect(getName(myFunc)).toBe('myFunc')
|
|
466
466
|
})
|
|
467
|
+
test('arrow function reference returns undefined (not inferred property name)', () => {
|
|
468
|
+
const obj = { code: (a, b) => a + b }
|
|
469
|
+
expect(getName(obj.code)).toBeUndefined()
|
|
470
|
+
})
|
|
471
|
+
test('anonymous function reference returns undefined', () => {
|
|
472
|
+
const fn = function () {}
|
|
473
|
+
// When assigned to a variable, .name is inferred, but toString() doesn't
|
|
474
|
+
// start with 'function <name>' — it starts with 'function ()'
|
|
475
|
+
// For this case getName should still work because fn.name = 'fn'
|
|
476
|
+
// and fn.toString() starts with 'function'
|
|
477
|
+
expect(getName(fn)).toBe('fn')
|
|
478
|
+
})
|
|
479
|
+
test('async function reference', () => {
|
|
480
|
+
async function fetchData () {}
|
|
481
|
+
expect(getName(fetchData)).toBe('fetchData')
|
|
482
|
+
})
|
|
483
|
+
test('async arrow function returns undefined', () => {
|
|
484
|
+
const obj = { code: async (a) => a }
|
|
485
|
+
expect(getName(obj.code)).toBeUndefined()
|
|
486
|
+
})
|
|
467
487
|
test('non-string non-function returns undefined', () => {
|
|
468
488
|
expect(getName(42)).toBeUndefined()
|
|
469
489
|
expect(getName(null)).toBeUndefined()
|