@jseeio/jsee 0.4.1 → 0.8.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.
Files changed (60) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/LICENSE +21 -0
  3. package/README.md +583 -55
  4. package/dist/2b3e1faf89f94a483539.png +0 -0
  5. package/dist/416d91365b44e4b4f477.png +0 -0
  6. package/dist/8f2c4d11474275fbc161.png +0 -0
  7. package/dist/jsee.core.js +1 -0
  8. package/dist/jsee.full.js +1 -0
  9. package/dist/jsee.runtime.js +2 -1
  10. package/package.json +84 -18
  11. package/src/app.js +130 -33
  12. package/src/cli.js +474 -64
  13. package/src/extended-imports.js +11 -0
  14. package/src/main.js +264 -45
  15. package/src/overlay.js +26 -1
  16. package/src/utils.js +390 -12
  17. package/templates/common-inputs.js +88 -0
  18. package/templates/common-outputs.js +340 -4
  19. package/templates/minimal-app.vue +367 -0
  20. package/templates/minimal-input.vue +573 -0
  21. package/templates/minimal-output.vue +426 -0
  22. package/templates/virtual-table.vue +194 -0
  23. package/.claude/settings.local.json +0 -13
  24. package/.eslintrc.js +0 -38
  25. package/AGENTS.md +0 -65
  26. package/CLAUDE.md +0 -5
  27. package/CNAME +0 -1
  28. package/_config.yml +0 -26
  29. package/dist/jsee.js +0 -1
  30. package/jest-puppeteer.config.js +0 -14
  31. package/jest.config.js +0 -8
  32. package/jest.unit.config.js +0 -8
  33. package/load/index.html +0 -52
  34. package/templates/bulma-app.vue +0 -242
  35. package/templates/bulma-input.vue +0 -125
  36. package/templates/bulma-output.vue +0 -101
  37. package/test/class.html +0 -22
  38. package/test/code.html +0 -25
  39. package/test/codew.html +0 -25
  40. package/test/example-class.js +0 -8
  41. package/test/example-sum.js +0 -3
  42. package/test/fixtures/lodash-like.js +0 -15
  43. package/test/fixtures/upload-sample.csv +0 -3
  44. package/test/importw.html +0 -28
  45. package/test/minimal.html +0 -14
  46. package/test/minimal1.html +0 -13
  47. package/test/minimal2.html +0 -15
  48. package/test/minimal3.html +0 -10
  49. package/test/minimal4.html +0 -22
  50. package/test/pipeline.html +0 -52
  51. package/test/python.html +0 -41
  52. package/test/string.html +0 -26
  53. package/test/stringw.html +0 -29
  54. package/test/sum.schema.json +0 -17
  55. package/test/sumw.schema.json +0 -15
  56. package/test/test-basic.test.js +0 -603
  57. package/test/test-python.test.js +0 -23
  58. package/test/unit/cli-fetch.test.js +0 -229
  59. package/test/unit/utils.test.js +0 -888
  60. package/webpack.config.js +0 -101
@@ -1,603 +0,0 @@
1
- require('expect-puppeteer')
2
- const path = require('path')
3
-
4
- page.setDefaultTimeout(10000)
5
-
6
- // Server on port 8484 is auto-started by jest-puppeteer (see jest-puppeteer.config.js)
7
-
8
- const port = 8484
9
- const urlSchema = (name) => `http://localhost:${port}/load/?s=/test/${name}.schema.json`
10
- const urlHTML = (name) => `http://localhost:${port}/test/${name}.html`
11
- const urlQuery = (schema) => `http://localhost:${port}/load/?s=${JSON.stringify(schema)}`
12
- const urlQueryEscaped = (schema) => `http://localhost:${port}/load/?s=${encodeURIComponent(JSON.stringify(schema))}`
13
- const uploadFixture = path.resolve(__dirname, 'fixtures', 'upload-sample.csv')
14
-
15
- describe('Initial test', () => {
16
- beforeAll(async () => {
17
- await page.goto(urlSchema('sum'))
18
- })
19
- test('Title', async () => {
20
- await expect(page).toMatchTextContent('title')
21
- })
22
- test('Description', async () => {
23
- await expect(page).toMatchTextContent('description')
24
- })
25
- test('Run button is active', async () => {
26
- await expect(page).toClick('button', { text: 'Run' })
27
- })
28
- test('Default result is right', async () => {
29
- await expect(page).toMatchTextContent('142')
30
- })
31
- test('Changing inputs', async () => {
32
- await expect(page).toFill('#a', '200')
33
- await expect(page).toClick('button', { text: 'Run' })
34
- await expect(page).toMatchTextContent('242')
35
- // await jestPuppeteer.debug()
36
- })
37
- })
38
-
39
-
40
- describe('Initial test (worker)', () => {
41
- beforeAll(async () => {
42
- await page.goto(urlSchema('sumw'))
43
- })
44
- test('Result is right', async () => {
45
- await expect(page).toFill('#a', '8')
46
- await expect(page).toFill('#b', '7')
47
- await expect(page).toClick('button', { text: 'Run' })
48
- await expect(page).toMatchTextContent('15')
49
- })
50
- })
51
-
52
- describe('Minimal examples', () => {
53
- const schema = {
54
- 'model': {
55
- 'code': 'function (a, b) { return a / b }',
56
- }
57
- }
58
- test('Code only (text) (main window)', async () => {
59
- schema.model.worker = false
60
- await page.goto(urlQueryEscaped(schema))
61
- await expect(page).toFill('#a', '100')
62
- await expect(page).toFill('#b', '4')
63
- await expect(page).toClick('button', { text: 'Run' })
64
- await expect(page).toMatchTextContent('25')
65
- })
66
- test('Code instead of schema (function)', async () => {
67
- await page.goto(urlHTML('minimal1'))
68
- await expect(page).toFill('#a', '100')
69
- await expect(page).toFill('#b', '4')
70
- await expect(page).toClick('button', { text: 'Run' })
71
- await expect(page).toMatchTextContent('400')
72
- })
73
- test('Code instead of model (function)', async () => {
74
- await page.goto(urlHTML('minimal2'))
75
- await expect(page).toFill('#a', '100')
76
- await expect(page).toFill('#b', '4')
77
- await expect(page).toClick('button', { text: 'Run' })
78
- await expect(page).toMatchTextContent('400')
79
- })
80
- test('Code instead of schema (anonymous function)', async () => {
81
- await page.goto(urlHTML('minimal3'))
82
- await expect(page).toFill('#a', '100')
83
- await expect(page).toFill('#b', '4')
84
- await expect(page).toClick('button', { text: 'Run' })
85
- await expect(page).toMatchTextContent('400')
86
- })
87
- })
88
-
89
- describe('Load code directly', () => {
90
- test('Window', async () => {
91
- await page.goto(urlHTML('code'))
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('Window (string with eval)', async () => {
98
- await page.goto(urlHTML('string'))
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
- test('Worker', async () => {
105
- await page.goto(urlHTML('codew'))
106
- await expect(page).toFill('#a', '8')
107
- await expect(page).toFill('#b', '7')
108
- await expect(page).toClick('button', { text: 'Run' })
109
- await expect(page).toMatchTextContent('15')
110
- })
111
- test('Worker (string)', async () => {
112
- await page.goto(urlHTML('stringw'))
113
- await expect(page).toFill('#a', '8')
114
- await expect(page).toFill('#b', '7')
115
- await expect(page).toClick('button', { text: 'Run' })
116
- await expect(page).toMatchTextContent('15')
117
- })
118
-
119
- test('Window infers function type for URL-loaded JS when type is omitted', async () => {
120
- const schema = {
121
- model: {
122
- name: 'sum',
123
- container: 'args',
124
- url: '/test/example-sum.js',
125
- worker: false
126
- },
127
- inputs: [
128
- { name: 'a', type: 'int', default: 8 },
129
- { name: 'b', type: 'int', default: 7 }
130
- ]
131
- }
132
- await page.goto(urlQueryEscaped(schema))
133
- await expect(page).toClick('button', { text: 'Run' })
134
- await expect(page).toMatchTextContent('15')
135
- })
136
-
137
- test('Worker infers function type for URL-loaded JS when type is omitted', async () => {
138
- const schema = {
139
- model: {
140
- name: 'sum',
141
- container: 'args',
142
- url: '/test/example-sum.js',
143
- worker: true
144
- },
145
- inputs: [
146
- { name: 'a', type: 'int', default: 8 },
147
- { name: 'b', type: 'int', default: 7 }
148
- ]
149
- }
150
- await page.goto(urlQueryEscaped(schema))
151
- await expect(page).toClick('button', { text: 'Run' })
152
- await expect(page).toMatchTextContent('15')
153
- })
154
- })
155
-
156
- describe('Classes', () => {
157
- const schema = {
158
- 'model': {
159
- 'name': 'Doubler',
160
- 'method': 'double',
161
- 'type': 'class',
162
- 'container': 'args',
163
- 'url': '/test/example-class.js',
164
- },
165
- 'inputs': [
166
- { 'name': 'b', 'type': 'int', 'default': 100 }
167
- ]
168
- }
169
-
170
- test('Window', async () => {
171
- schema.model.worker = false
172
- await page.goto(urlQueryEscaped(schema))
173
- await expect(page).toClick('button', { text: 'Run' })
174
- await expect(page).toMatchTextContent('200')
175
- })
176
-
177
- test('Worker', async () => {
178
- schema.model.worker = true
179
- await page.goto(urlQueryEscaped(schema))
180
- await expect(page).toClick('button', { text: 'Run' })
181
- await expect(page).toMatchTextContent('200')
182
- })
183
- })
184
-
185
- describe('Some edge cases', () => {
186
- test('Result is zero', async () => {
187
- const schema = {
188
- 'model': {
189
- 'code': 'function mul (a, b) { return a * b }',
190
- }
191
- }
192
- schema.model.worker = false
193
- await page.goto(urlQueryEscaped(schema))
194
- await expect(page).toFill('#a', '0')
195
- await expect(page).toFill('#b', '0')
196
- await expect(page).toClick('button', { text: 'Run' })
197
- await expect(page).toMatchTextContent('Copy')
198
- })
199
- })
200
-
201
- describe('Imports', () => {
202
- const schema = {
203
- 'model': {
204
- 'name': 'kebab',
205
- 'type': 'function',
206
- 'container': 'args',
207
- 'code': `
208
- function kebab (str) {
209
- return _.kebabCase(str)
210
- }
211
- `
212
- },
213
- 'imports': `http://localhost:${port}/test/fixtures/lodash-like.js`,
214
- 'inputs': [
215
- { 'name': 'str', 'type': 'string', 'default': 'FooBar' },
216
- ]
217
- }
218
- test('Window', async () => {
219
- schema.model.worker = false
220
- await page.goto(urlQueryEscaped(schema))
221
- await expect(page).toClick('button', { text: 'Run' })
222
- await expect(page).toMatchTextContent('foo-bar')
223
- })
224
- test('Worker', async () => {
225
- schema.model.worker = true
226
- await page.goto(urlQueryEscaped(schema))
227
- const runUntilReady = async (attempts=3) => {
228
- if (attempts <= 0) {
229
- return false
230
- }
231
- await expect(page).toClick('button', { text: 'Run' })
232
- try {
233
- await page.waitForFunction(() => document.body.innerText.includes('foo-bar'), { timeout: 5000 })
234
- return true
235
- } catch (error) {
236
- return runUntilReady(attempts - 1)
237
- }
238
- }
239
- const ready = await runUntilReady()
240
- expect(ready).toBe(true)
241
- await expect(page).toMatchTextContent('foo-bar')
242
- // await (new Promise(resolve => setTimeout(resolve, 1000)))
243
- })
244
- })
245
-
246
- describe('Buttons, button titles and caller', () => {
247
- const schema = {
248
- 'model': {
249
- 'name': 'callerRepeater',
250
- 'type': 'function',
251
- 'code': `function callerRepeater (inputs) {
252
- return inputs.caller
253
- }`
254
- },
255
- 'inputs': [
256
- { 'name': 'test_button', 'type': 'button', 'title': 'Test Button' },
257
- ]
258
- }
259
- test('Window', async () => {
260
- schema.model.worker = false
261
- await page.goto(urlQueryEscaped(schema))
262
- await expect(page).toMatchTextContent('Test Button')
263
- await expect(page).toClick('button', { text: 'Test Button' })
264
- await expect(page).toMatchTextContent('test_button')
265
- })
266
- test('Worker', async () => {
267
- schema.model.worker = true
268
- await page.goto(urlQueryEscaped(schema))
269
- await expect(page).toClick('button', { text: 'Test Button' })
270
- await expect(page).toMatchTextContent('test_button')
271
- })
272
- })
273
-
274
- describe('Pipeline', () => {
275
- test('Multiple models', async () => {
276
- let a = 3
277
- let b = 4
278
- await page.goto(urlHTML('pipeline'))
279
- await expect(page).toFill('#a', a.toString())
280
- await expect(page).toFill('#b', b.toString())
281
- await expect(page).toClick('button', { text: 'Run' })
282
- await expect(page).toMatchTextContent((Math.pow((a + b), 2) + 1).toString())
283
- await expect(page).toClick('button', { text: 'Run' })
284
- await expect(page).toMatchTextContent((Math.pow((a + b), 2) + 2).toString())
285
- })
286
- })
287
-
288
- describe('File uploads', () => {
289
- test('default file input reads uploaded text', async () => {
290
- const schema = {
291
- model: {
292
- worker: false,
293
- code: `function filePreview (inputs) {
294
- return {
295
- preview: inputs.file.split('\\n')[0]
296
- }
297
- }`
298
- },
299
- inputs: [
300
- { name: 'file', type: 'file' }
301
- ]
302
- }
303
- await page.goto(urlQueryEscaped(schema))
304
- await page.waitForSelector('#vfp-filePicker')
305
- const fileInput = await page.$('#vfp-filePicker')
306
- await fileInput.uploadFile(uploadFixture)
307
- await page.waitForFunction(() => document.body.innerText.includes('Selected 1 file(s)'))
308
- await expect(page).toClick('button', { text: 'Run' })
309
- await expect(page).toMatchTextContent('name,age')
310
- })
311
-
312
- test('raw file input passes File object (not text content)', async () => {
313
- const schema = {
314
- model: {
315
- worker: false,
316
- code: `function fileRawMeta (inputs) {
317
- const isFileObject = !!inputs.file
318
- && (typeof inputs.file === 'object')
319
- && (typeof inputs.file.name === 'string')
320
- && (typeof inputs.file.text === 'function')
321
- return {
322
- is_file_object: isFileObject,
323
- is_string: typeof inputs.file === 'string',
324
- file_name: inputs.file && inputs.file.name ? inputs.file.name : '',
325
- content_prefix: typeof inputs.file === 'string' ? inputs.file.slice(0, 10) : 'NONE'
326
- }
327
- }`
328
- },
329
- inputs: [
330
- { name: 'file', type: 'file', raw: true }
331
- ]
332
- }
333
- await page.goto(urlQueryEscaped(schema))
334
- await page.waitForSelector('#vfp-filePicker')
335
- const fileInput = await page.$('#vfp-filePicker')
336
- await fileInput.uploadFile(uploadFixture)
337
- await page.waitForFunction(() => document.body.innerText.includes('Selected 1 file(s)'))
338
- await expect(page).toClick('button', { text: 'Run' })
339
- await expect(page).toMatchTextContent('is_file_object')
340
- await expect(page).toMatchTextContent('true')
341
- await expect(page).toMatchTextContent('file_name')
342
- await expect(page).toMatchTextContent('upload-sample.csv')
343
- await expect(page).toMatchTextContent('content_prefix')
344
- await expect(page).toMatchTextContent('NONE')
345
- })
346
-
347
- test('raw url input passes URL handle (not fetched text content)', async () => {
348
- const schema = {
349
- model: {
350
- worker: false,
351
- code: `function fileRawUrlMeta (inputs) {
352
- const isUrlHandle = !!inputs.file
353
- && (typeof inputs.file === 'object')
354
- && (inputs.file.kind === 'url')
355
- && (typeof inputs.file.url === 'string')
356
- return {
357
- is_url_handle: isUrlHandle,
358
- is_string: typeof inputs.file === 'string',
359
- url_value: isUrlHandle ? inputs.file.url : '',
360
- content_prefix: typeof inputs.file === 'string' ? inputs.file.slice(0, 10) : 'NONE'
361
- }
362
- }`
363
- },
364
- inputs: [
365
- { name: 'file', type: 'file', raw: true }
366
- ]
367
- }
368
- const rawUrl = 'http://127.0.0.1:9999/nope.csv'
369
- await page.goto(urlQueryEscaped(schema))
370
- await expect(page).toClick('button', { text: 'From URL' })
371
- await page.waitForSelector('input.vfp-urlInput[type="text"]')
372
- await page.click('input.vfp-urlInput[type="text"]', { clickCount: 3 })
373
- await page.type('input.vfp-urlInput[type="text"]', rawUrl)
374
- await page.evaluate(() => {
375
- const loadButton = Array.from(document.querySelectorAll('button'))
376
- .find(btn => btn.textContent.trim() === 'Load')
377
- if (!loadButton) throw new Error('Load button not found')
378
- loadButton.click()
379
- })
380
- await expect(page).toClick('button', { text: 'Run' })
381
- await page.waitForFunction((expectedUrl) => {
382
- return document.querySelector('#outputs').innerText.includes(expectedUrl)
383
- }, {}, rawUrl)
384
- await expect(page).toMatchTextContent('is_url_handle')
385
- await expect(page).toMatchTextContent('true')
386
- await expect(page).toMatchTextContent('url_value')
387
- await expect(page).toMatchTextContent(rawUrl)
388
- await expect(page).toMatchTextContent('content_prefix')
389
- await expect(page).toMatchTextContent('NONE')
390
- })
391
-
392
- test('file query param auto-loads URL without clicking Load', async () => {
393
- const schema = {
394
- model: {
395
- worker: false,
396
- code: `function filePreviewFromQuery (inputs) {
397
- return {
398
- preview: inputs.file.split('\\n')[0]
399
- }
400
- }`
401
- },
402
- inputs: [
403
- { name: 'file', type: 'file' }
404
- ]
405
- }
406
- const fileUrl = `http://localhost:${port}/test/fixtures/upload-sample.csv`
407
- await page.goto(`${urlQueryEscaped(schema)}&file=${encodeURIComponent(fileUrl)}`)
408
- await page.waitForFunction(() => document.body.innerText.includes('Loaded from URL:'), { timeout: 5000 })
409
- await expect(page).toClick('button', { text: 'Run' })
410
- await expect(page).toMatchTextContent('preview')
411
- await expect(page).toMatchTextContent('name,age')
412
- })
413
- })
414
-
415
- describe('Streamed file inputs', () => {
416
- test('main thread receives uploaded file as async iterable stream', async () => {
417
- const schema = {
418
- model: {
419
- worker: false,
420
- code: `async function streamMainFile (inputs) {
421
- const reader = inputs.file
422
- const isIterable = !!reader
423
- && typeof reader[Symbol.asyncIterator] === 'function'
424
- && typeof reader.text === 'function'
425
- && typeof reader.bytes === 'function'
426
- && typeof reader.lines === 'function'
427
- if (!isIterable) {
428
- return { is_iterable: false, header: 'NONE' }
429
- }
430
- const text = await reader.text()
431
- return {
432
- is_iterable: true,
433
- header: text.split('\\n')[0]
434
- }
435
- }`
436
- },
437
- inputs: [
438
- { name: 'file', type: 'file', raw: true, stream: true }
439
- ]
440
- }
441
- await page.goto(urlQueryEscaped(schema))
442
- await page.waitForSelector('#vfp-filePicker')
443
- const fileInput = await page.$('#vfp-filePicker')
444
- await fileInput.uploadFile(uploadFixture)
445
- await expect(page).toClick('button', { text: 'Run' })
446
- await expect(page).toMatchTextContent('is_iterable')
447
- await expect(page).toMatchTextContent('true')
448
- await expect(page).toMatchTextContent('header')
449
- await expect(page).toMatchTextContent('name,age')
450
- })
451
-
452
- test('stream metadata is preserved for downstream pipeline models', async () => {
453
- const schema = {
454
- model: [
455
- {
456
- worker: false,
457
- code: `function streamStageOne (inputs) {
458
- return {
459
- stage1_name: inputs.file && inputs.file.name ? inputs.file.name : 'NONE'
460
- }
461
- }`
462
- },
463
- {
464
- worker: false,
465
- code: `function streamStageTwo (inputs) {
466
- return {
467
- stage2_name: inputs.file && inputs.file.name ? inputs.file.name : 'NONE',
468
- stage2_size: inputs.file && typeof inputs.file.size === 'number' ? inputs.file.size : -1
469
- }
470
- }`
471
- }
472
- ],
473
- inputs: [
474
- { name: 'file', type: 'file', raw: true, stream: true }
475
- ]
476
- }
477
- await page.goto(urlQueryEscaped(schema))
478
- await page.waitForSelector('#vfp-filePicker')
479
- const fileInput = await page.$('#vfp-filePicker')
480
- await fileInput.uploadFile(uploadFixture)
481
- await expect(page).toClick('button', { text: 'Run' })
482
- await expect(page).toMatchTextContent('stage1_name')
483
- await expect(page).toMatchTextContent('stage2_name')
484
- await expect(page).toMatchTextContent('upload-sample.csv')
485
- await expect(page).toMatchTextContent('stage2_size')
486
- })
487
-
488
- test('main thread receives URL source as async iterable stream', async () => {
489
- const schema = {
490
- model: {
491
- worker: false,
492
- code: `async function streamMainUrl (inputs) {
493
- const reader = inputs.file
494
- const text = await reader.text()
495
- return {
496
- is_iterable: typeof reader[Symbol.asyncIterator] === 'function',
497
- header: text.split('\\n')[0]
498
- }
499
- }`
500
- },
501
- inputs: [
502
- { name: 'file', type: 'file', raw: true, stream: true }
503
- ]
504
- }
505
- const sampleUrl = `http://localhost:${port}/test/fixtures/upload-sample.csv`
506
- await page.goto(urlQueryEscaped(schema))
507
- await expect(page).toClick('button', { text: 'From URL' })
508
- await page.waitForSelector('input.vfp-urlInput[type="text"]')
509
- await page.click('input.vfp-urlInput[type="text"]', { clickCount: 3 })
510
- await page.type('input.vfp-urlInput[type="text"]', sampleUrl)
511
- await page.evaluate(() => {
512
- const loadButton = Array.from(document.querySelectorAll('button'))
513
- .find(btn => btn.textContent.trim() === 'Load')
514
- if (!loadButton) throw new Error('Load button not found')
515
- loadButton.click()
516
- })
517
- await expect(page).toClick('button', { text: 'Run' })
518
- await page.waitForFunction(() => {
519
- const outputs = document.querySelector('#outputs')
520
- return outputs && outputs.innerText.includes('header')
521
- }, { timeout: 5000 })
522
- await expect(page).toMatchTextContent('is_iterable')
523
- await expect(page).toMatchTextContent('true')
524
- await expect(page).toMatchTextContent('header')
525
- await expect(page).toMatchTextContent('name,age')
526
- })
527
-
528
- test('worker receives uploaded file as async iterable stream', async () => {
529
- const schema = {
530
- model: {
531
- worker: true,
532
- code: `async function streamWorkerFile (inputs) {
533
- const reader = inputs.file
534
- const lines = []
535
- for await (const line of reader.lines()) {
536
- lines.push(line)
537
- }
538
- return {
539
- is_iterable: typeof reader.text === 'function',
540
- header: lines[0] || 'NONE'
541
- }
542
- }`
543
- },
544
- inputs: [
545
- { name: 'file', type: 'file', raw: true, stream: true }
546
- ]
547
- }
548
- await page.goto(urlQueryEscaped(schema))
549
- await page.waitForSelector('#vfp-filePicker')
550
- const fileInput = await page.$('#vfp-filePicker')
551
- await fileInput.uploadFile(uploadFixture)
552
- await expect(page).toClick('button', { text: 'Run' })
553
- await expect(page).toMatchTextContent('is_iterable')
554
- await expect(page).toMatchTextContent('true')
555
- await expect(page).toMatchTextContent('header')
556
- await expect(page).toMatchTextContent('name,age')
557
- })
558
-
559
- test('worker receives URL source as async iterable stream', async () => {
560
- const schema = {
561
- model: {
562
- worker: true,
563
- code: `async function streamWorkerUrl (inputs) {
564
- const reader = inputs.file
565
- const decoder = new TextDecoder()
566
- let text = ''
567
- for await (const chunk of reader) {
568
- text += decoder.decode(chunk, { stream: true })
569
- }
570
- text += decoder.decode()
571
- return {
572
- is_iterable: typeof reader.bytes === 'function',
573
- header: text.split('\\n')[0]
574
- }
575
- }`
576
- },
577
- inputs: [
578
- { name: 'file', type: 'file', raw: true, stream: true }
579
- ]
580
- }
581
- const sampleUrl = `http://localhost:${port}/test/fixtures/upload-sample.csv`
582
- await page.goto(urlQueryEscaped(schema))
583
- await expect(page).toClick('button', { text: 'From URL' })
584
- await page.waitForSelector('input.vfp-urlInput[type="text"]')
585
- await page.click('input.vfp-urlInput[type="text"]', { clickCount: 3 })
586
- await page.type('input.vfp-urlInput[type="text"]', sampleUrl)
587
- await page.evaluate(() => {
588
- const loadButton = Array.from(document.querySelectorAll('button'))
589
- .find(btn => btn.textContent.trim() === 'Load')
590
- if (!loadButton) throw new Error('Load button not found')
591
- loadButton.click()
592
- })
593
- await expect(page).toClick('button', { text: 'Run' })
594
- await page.waitForFunction(() => {
595
- const outputs = document.querySelector('#outputs')
596
- return outputs && outputs.innerText.includes('header')
597
- }, { timeout: 5000 })
598
- await expect(page).toMatchTextContent('is_iterable')
599
- await expect(page).toMatchTextContent('true')
600
- await expect(page).toMatchTextContent('header')
601
- await expect(page).toMatchTextContent('name,age')
602
- })
603
- })
@@ -1,23 +0,0 @@
1
- require('expect-puppeteer')
2
-
3
- page.setDefaultTimeout(30000)
4
-
5
- // Server on port 8484 is auto-started by jest-puppeteer (see jest-puppeteer.config.js)
6
-
7
- const port = 8484
8
- const urlSchema = (name) => `http://localhost:${port}/load/?s=/test/${name}.schema.json`
9
- const urlHTML = (name) => `http://localhost:${port}/test/${name}.html`
10
- const urlQuery = (schema) => `http://localhost:${port}/load/?s=${JSON.stringify(schema)}`
11
-
12
- describe('Python', () => {
13
- test('Window', async () => {
14
- await page.goto(urlHTML('python'))
15
- // Wait for pyodide to load
16
- await (new Promise(resolve => setTimeout(resolve, 10000)))
17
- await expect(page).toFill('#a', '4')
18
- await expect(page).toFill('#b', '2')
19
- await expect(page).toClick('button', { text: 'Run' })
20
- await (new Promise(resolve => setTimeout(resolve, 5000)))
21
- await expect(page).toMatchTextContent('3')
22
- })
23
- })