Skip to content

Commit 2ed155b

Browse files
committed
try using playwright from vitest
1 parent ebe8bbd commit 2ed155b

9 files changed

Lines changed: 406 additions & 347 deletions

File tree

.github/workflows/ci.yml

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@ jobs:
2727
- run: npm i
2828
- run: npm run typecheck
2929

30-
test-node:
31-
runs-on: ubuntu-latest
32-
steps:
33-
- uses: actions/checkout@v4
34-
- run: npm i
35-
- run: npm run test:node
30+
# test-node:
31+
# runs-on: ubuntu-latest
32+
# steps:
33+
# - uses: actions/checkout@v4
34+
# - run: npm i
35+
# - run: npm run test
3636

3737
test-browsers:
3838
timeout-minutes: 60
@@ -46,11 +46,5 @@ jobs:
4646
run: npm ci
4747
- name: Install Playwright Browsers
4848
run: npx playwright install --with-deps
49-
- name: Run Playwright tests
50-
run: npx playwright test
51-
- uses: actions/upload-artifact@v4
52-
if: ${{ !cancelled() }}
53-
with:
54-
name: playwright-report
55-
path: playwright-report/
56-
retention-days: 30
49+
- name: Run tests
50+
run: npm test

package-lock.json

Lines changed: 287 additions & 167 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,23 +27,21 @@
2727
"build": "tsdown",
2828
"dev": "tsdown --watch",
2929
"lint": "eslint .",
30-
"test:node": "vitest run ./tests/node",
31-
"test:browser": "playwright test",
32-
"test": "npm run test:node && npm run test:browser",
30+
"test": "vitest",
3331
"typecheck": "tsc --noEmit"
3432
},
3533
"devDependencies": {
3634
"@eslint/compat": "^1.4.0",
3735
"@eslint/js": "^9.38.0",
38-
"@playwright/test": "^1.56.1",
3936
"@stylistic/eslint-plugin": "^5.5.0",
4037
"@types/node": "^24.9.1",
38+
"@vitest/browser-playwright": "^4.0.7",
4139
"bumpp": "^10.3.1",
4240
"eslint": "^9.38.0",
4341
"globals": "^16.4.0",
4442
"tsdown": "^0.15.9",
4543
"typescript": "^5.9.3",
4644
"typescript-eslint": "^8.46.2",
47-
"vitest": "^4.0.1"
45+
"vitest": "^4.0.7"
4846
}
4947
}

playwright.config.js

Lines changed: 0 additions & 79 deletions
This file was deleted.

src/index.ts

Lines changed: 75 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,92 @@
11
const defaultChunkSize = 1024 * 1024 // 1MB
22

3+
export function toUrl(text: string): {
4+
url: string
5+
fileSize: number
6+
revoke: () => void
7+
} {
8+
// add an extra space to fix https://github.com/nodejs/node/issues/60382
9+
const blob = new Blob([text + ' '])
10+
const url = URL.createObjectURL(blob)
11+
return {
12+
url,
13+
fileSize: blob.size - 1, // subtract the extra space
14+
revoke: () => {
15+
URL.revokeObjectURL(url)
16+
},
17+
}
18+
}
19+
320
export async function* parse(
421
url: string,
522
options: {
623
chunkSize?: number
7-
isUrl?: boolean
24+
fileSize?: number
825
},
9-
) {
26+
): AsyncGenerator<string> {
1027
const chunkSize = options.chunkSize ?? defaultChunkSize
11-
const isUrl = options.isUrl ?? true
12-
// See https://github.com/nodejs/node/issues/60382
13-
let objectURLWorkaround = false
14-
if (!isUrl) {
15-
// transform to a URL object
16-
// See https://github.com/nodejs/node/issues/60382
17-
objectURLWorkaround = true
18-
url = URL.createObjectURL(new Blob([url + ' '], { type: 'text/plain' }))
19-
}
20-
28+
let fileSize = options.fileSize
2129
const decoder = new TextDecoder('utf-8')
30+
2231
let rangeStart = 0
23-
let fileSize = Infinity
24-
while (rangeStart < fileSize) {
25-
const rangeEnd = rangeStart + chunkSize - 1 + (objectURLWorkaround ? 1 : 0)
26-
const response = await fetch(url, {
27-
headers: {
28-
Range: `bytes=${rangeStart}-${rangeEnd}`,
29-
},
30-
})
31-
if (response.status === 416) {
32-
// Requested Range Not Satisfiable
33-
throw new Error(
34-
`Requested range not satisfiable: ${rangeStart}-${rangeEnd}`,
35-
)
36-
}
37-
if (response.status === 200) {
38-
// Server ignored Range header
39-
throw new Error(`Server did not support range requests.`)
40-
}
41-
if (response.status !== 206) {
42-
throw new Error(
43-
`Failed to fetch chunk: ${response.status} ${response.statusText}`,
44-
)
45-
}
46-
// Check the content-range header
47-
const contentRange = response.headers.get('content-range')
48-
const contentLength = response.headers.get('content-length')
49-
if (!contentRange || !contentLength) {
50-
throw new Error(`Missing content-range or content-length header.`)
51-
}
52-
const last = contentRange.split('/')[1]
53-
if (last === undefined) {
54-
throw new Error(`Invalid content-range header: ${contentRange}`)
55-
}
56-
fileSize = parseInt(last)
32+
while (rangeStart < (fileSize ?? Number.POSITIVE_INFINITY)) {
33+
// Always request one extra byte, due to https://github.com/nodejs/node/issues/60382
34+
const extraByte = 1
35+
const rangeEnd = rangeStart + chunkSize - 1 + extraByte
36+
37+
const result = await fetchChunk({ url, rangeStart, rangeEnd })
5738

58-
// Decode exactly chunkSize bytes or less if it's the last chunk
59-
const bytes = await response.bytes()
39+
fileSize ??= result.fileSize
6040
const bytesToDecode = Math.min(chunkSize, fileSize - rangeStart)
61-
const chunk = decoder.decode(bytes.subarray(0, bytesToDecode))
41+
const chunk = decoder.decode(result.bytes.subarray(0, bytesToDecode))
42+
6243
yield chunk
6344

6445
rangeStart += chunkSize
6546
}
6647
}
48+
49+
async function fetchChunk({ url, rangeStart, rangeEnd }: { url: string, rangeStart: number, rangeEnd: number }): Promise<{
50+
bytes: Uint8Array
51+
fileSize: number
52+
}> {
53+
const response = await fetch(url, {
54+
headers: {
55+
Range: `bytes=${rangeStart}-${rangeEnd}`,
56+
},
57+
})
58+
if (response.status === 416) {
59+
// Requested Range Not Satisfiable
60+
throw new Error(
61+
`Requested range not satisfiable: ${rangeStart}-${rangeEnd}`,
62+
)
63+
}
64+
if (response.status === 200) {
65+
// Server ignored Range header
66+
throw new Error(`Server did not support range requests.`)
67+
}
68+
if (response.status !== 206) {
69+
throw new Error(
70+
`Failed to fetch chunk: ${response.status} ${response.statusText}`,
71+
)
72+
}
73+
// Check the content-range header
74+
const contentRange = response.headers.get('content-range')
75+
const contentLength = response.headers.get('content-length')
76+
if (!contentRange || !contentLength) {
77+
throw new Error(`Missing content-range or content-length header.`)
78+
}
79+
const last = contentRange.split('/')[1]
80+
if (last === undefined) {
81+
throw new Error(`Invalid content-range header: ${contentRange}`)
82+
}
83+
const fileSize = parseInt(last)
84+
if (isNaN(fileSize)) {
85+
throw new Error(`Invalid file size in content-range header: ${last}`)
86+
}
87+
88+
// Decode exactly chunkSize bytes or less if it's the last chunk
89+
const bytes = await response.bytes()
90+
91+
return { bytes, fileSize }
92+
}

tests/browser/index.test.ts

Lines changed: 0 additions & 9 deletions
This file was deleted.

tests/common/index.test.ts

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
1-
import { parse } from '../../src'
1+
import { parse, toUrl } from '../../src'
2+
import { describe, expect, test } from 'vitest'
23

3-
export const tests = [
4-
{
5-
name: 'parse',
6-
prepare: async () => {
7-
const text = 'hello, csvremote!!!'
8-
let result = ''
9-
for await (const chunk of parse(text, { chunkSize: 5, isUrl: false })) {
10-
result += chunk
11-
}
12-
return result
13-
},
14-
expected: 'hello, csvremote!!!',
15-
},
16-
]
4+
describe('parse', () => {
5+
test.each([1, 5, 10, 20])('parses text in chunks of size %d', async (chunkSize) => {
6+
const text = 'hello, csvremote!!!'
7+
const { url, fileSize, revoke } = toUrl(text)
8+
let result = ''
9+
for await (const chunk of parse(url, { chunkSize, fileSize })) {
10+
result += chunk
11+
}
12+
revoke()
13+
expect(result).toBe(text)
14+
})
15+
})

tests/node/index.test.ts

Lines changed: 0 additions & 9 deletions
This file was deleted.

vitest.config.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { defineConfig } from 'vitest/config'
2+
import { playwright } from '@vitest/browser-playwright'
3+
4+
export default defineConfig({
5+
test: {
6+
browser: {
7+
enabled: true,
8+
provider: playwright(),
9+
headless: true,
10+
// https://vitest.dev/guide/browser/playwright
11+
instances: [
12+
{ browser: 'chromium' },
13+
{ browser: 'firefox' },
14+
// webkit is not working on my machine
15+
// { browser: 'webkit' },
16+
],
17+
},
18+
},
19+
})

0 commit comments

Comments
 (0)