@jseeio/jsee 0.4.2 → 0.8.1

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