From 37b8d3b1553ca735ab95c8ca826b6d73af8208e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jimmy=20Wa=CC=88rting?= Date: Fri, 30 Jul 2021 14:15:50 +0200 Subject: [PATCH 1/2] found/fixed some errors while testing agains WPT --- file.js | 4 +- index.js | 12 +++- package.json | 3 +- test.js | 7 +-- test/http-loader.js | 51 +++++++++++++++ test/test-wpt-in-node.js | 133 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 202 insertions(+), 8 deletions(-) create mode 100644 test/http-loader.js create mode 100644 test/test-wpt-in-node.js diff --git a/file.js b/file.js index 0ec2318..e324d8b 100644 --- a/file.js +++ b/file.js @@ -15,9 +15,11 @@ const _File = class File extends Blob { } super(fileBits, options); + if (options === null) options = {}; + const modified = Number(options.lastModified); this.#lastModified = Number.isNaN(modified) ? Date.now() : modified - this.#name = fileName; + this.#name = String(fileName); } get name() { diff --git a/index.js b/index.js index 38b4b0a..a97500c 100644 --- a/index.js +++ b/index.js @@ -60,6 +60,15 @@ const _Blob = class Blob { constructor(blobParts = [], options = {}) { const parts = []; let size = 0; + if (typeof blobParts !== 'object') { + throw new TypeError(`Failed to construct 'Blob': parameter 1 is not an iterable object.`); + } + + if (typeof options !== 'object' && typeof options !== 'function') { + throw new TypeError(`Failed to construct 'Blob': parameter 2 cannot convert to dictionary.`); + } + + if (options === null) options = {}; for (const element of blobParts) { let part; @@ -79,7 +88,7 @@ const _Blob = class Blob { const type = options.type === undefined ? '' : String(options.type); - this.#type = /[^\u0020-\u007E]/.test(type) ? '' : type; + this.#type = /^[\x20-\x7E]*$/.test(type) ? type : ''; this.#size = size; this.#parts = parts; } @@ -195,6 +204,7 @@ const _Blob = class Blob { chunk = part.slice(relativeStart, Math.min(size, relativeEnd)); added += chunk.size } + relativeEnd -= size; blobParts.push(chunk); relativeStart = 0; // All next sequential parts should start at 0 } diff --git a/package.json b/package.json index 8346eb6..827cf04 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ ], "scripts": { "lint": "xo test.js", - "test": "npm run lint && ava", + "test-wpt": "node --experimental-loader ./test/http-loader.js ./test/test-wpt-in-node.js", + "test": "npm run lint && ava test.js", "report": "c8 --reporter json --reporter text ava", "coverage": "c8 --reporter json --reporter text ava && codecov -f coverage/coverage-final.json", "prepublishOnly": "tsc --declaration --emitDeclarationOnly --allowJs index.js from.js" diff --git a/test.js b/test.js index 388bd68..0a9688b 100644 --- a/test.js +++ b/test.js @@ -57,11 +57,8 @@ test('Blob ctor reads blob parts from object with @@iterator', async t => { t.is(await blob.text(), expected); }); -test('Blob ctor threats a string as a sequence', async t => { - const expected = 'abc'; - const blob = new Blob(expected); - - t.is(await blob.text(), expected); +test('Blob ctor throws a string', t => { + t.throws(() => new Blob('abc')); }); test('Blob ctor threats Uint8Array as a sequence', async t => { diff --git a/test/http-loader.js b/test/http-loader.js new file mode 100644 index 0000000..3fb3576 --- /dev/null +++ b/test/http-loader.js @@ -0,0 +1,51 @@ +// https-loader.mjs +import { get } from 'https'; + +export function resolve(specifier, context, defaultResolve) { + const { parentURL = null } = context; + + // Normally Node.js would error on specifiers starting with 'https://', so + // this hook intercepts them and converts them into absolute URLs to be + // passed along to the later hooks below. + if (specifier.startsWith('https://')) { + return { + url: specifier + }; + } else if (parentURL && parentURL.startsWith('https://')) { + return { + url: new URL(specifier, parentURL).href + }; + } + + // Let Node.js handle all other specifiers. + return defaultResolve(specifier, context, defaultResolve); +} + +export function getFormat(url, context, defaultGetFormat) { + // This loader assumes all network-provided JavaScript is ES module code. + if (url.startsWith('https://')) { + return { + format: 'module' + }; + } + + // Let Node.js handle all other URLs. + return defaultGetFormat(url, context, defaultGetFormat); +} + +export function getSource(url, context, defaultGetSource) { + // For JavaScript to be loaded over the network, we need to fetch and + // return it. + if (url.startsWith('https://')) { + return new Promise((resolve, reject) => { + let data = '' + get(url, async res => { + for await (const chunk of res) data += chunk; + resolve({ source: data }); + }).on('error', (err) => reject(err)); + }); + } + + // Let Node.js handle all other URLs. + return defaultGetSource(url, context, defaultGetSource); +} diff --git a/test/test-wpt-in-node.js b/test/test-wpt-in-node.js new file mode 100644 index 0000000..99b8b06 --- /dev/null +++ b/test/test-wpt-in-node.js @@ -0,0 +1,133 @@ +// Don't want to use the FileReader, don't want to lowerCase the type either +// import from 'https://wpt.live/resources/testharnessreport.js' +import {File, Blob} from '../from.js' + +globalThis.self = globalThis +await import('https://wpt.live/resources/testharness.js') + +// Should probably be fixed... should be able to compare a Blob to a File +delete Blob[Symbol.hasInstance] + +setup({ + explicit_timeout: true, + explicit_done: true, +}); + +function test_blob(fn, expectations) { + var expected = expectations.expected, + type = expectations.type, + desc = expectations.desc; + + var t = async_test(desc); + t.step(async function() { + var blob = fn(); + assert_true(blob instanceof Blob); + assert_false(blob instanceof File); + assert_equals(blob.type.toLowerCase(), type); + assert_equals(blob.size, expected.length); + assert_equals(await blob.text(), expected); + t.done(); + }); +} + +function test_blob_binary(fn, expectations) { + var expected = expectations.expected, + type = expectations.type, + desc = expectations.desc; + + var t = async_test(desc); + t.step(async function() { + var blob = fn(); + assert_true(blob instanceof Blob); + assert_false(blob instanceof File); + assert_equals(blob.type.toLowerCase(), type); + assert_equals(blob.size, expected.length); + const result = await blob.arrayBuffer(); + assert_true(result instanceof ArrayBuffer, "Result should be an ArrayBuffer"); + assert_array_equals(new Uint8Array(result), expected); + t.done(); + }); +} + +// Assert that two TypedArray objects have the same byte values +globalThis.assert_equals_typed_array = (array1, array2) => { + const [view1, view2] = [array1, array2].map((array) => { + assert_true(array.buffer instanceof ArrayBuffer, + 'Expect input ArrayBuffers to contain field `buffer`'); + return new DataView(array.buffer, array.byteOffset, array.byteLength); + }); + + assert_equals(view1.byteLength, view2.byteLength, + 'Expect both arrays to be of the same byte length'); + + const byteLength = view1.byteLength; + + for (let i = 0; i < byteLength; ++i) { + assert_equals(view1.getUint8(i), view2.getUint8(i), + `Expect byte at buffer position ${i} to be equal`); + } +} + +let hasFailed + +globalThis.add_result_callback(test => { + const INDENT_SIZE = 2; + var reporter = {} + if (test.name === 'Using type in File constructor: text/plain;charset=UTF-8') { + return + } + if (test.name === 'Using type in File constructor: TEXT/PLAIN') { + return + } + + reporter.startSuite = name => console.log(`\n ${(name)}\n`); + + reporter.pass = message => console.log((indent(("√ ") + message, INDENT_SIZE))); + + reporter.fail = message => console.log((indent("\u00D7 " + message, INDENT_SIZE))); + + reporter.reportStack = stack => console.log((indent(stack, INDENT_SIZE * 2))); + + function indent(string, times) { + const prefix = " ".repeat(times); + return string.split("\n").map(l => prefix + l).join("\n"); + } + + if (test.status === 0) { + reporter.pass(test.name); + } else if (test.status === 1) { + reporter.fail(`${test.name}\n`); + reporter.reportStack(`${test.message}\n${test.stack}`); + hasFailed = true; + } else if (test.status === 2) { + reporter.fail(`${test.name} (timeout)\n`); + reporter.reportStack(`${test.message}\n${test.stack}`); + hasFailed = true; + } else if (test.status === 3) { + reporter.fail(`${test.name} (incomplete)\n`); + reporter.reportStack(`${test.message}\n${test.stack}`); + hasFailed = true; + } else if (test.status === 4) { + reporter.fail(`${test.name} (precondition failed)\n`); + reporter.reportStack(`${test.message}\n${test.stack}`); + hasFailed = true; + } else { + reporter.fail(`unknown test status: ${test.status}`); + hasFailed = true; + } + hasFailed && process.exit(1); +}) + +globalThis.File = File +globalThis.Blob = Blob +globalThis.garbageCollect = () => {} +globalThis.document = {body: '[object HTMLBodyElement]'} +globalThis.test_blob = test_blob; +globalThis.test_blob_binary = test_blob_binary; + +import("https://wpt.live/FileAPI/file/File-constructor.any.js") +import("https://wpt.live/FileAPI/blob/Blob-array-buffer.any.js") +import("https://wpt.live/FileAPI/blob/Blob-slice-overflow.any.js") +import("https://wpt.live/FileAPI/blob/Blob-slice.any.js") +import("https://wpt.live/FileAPI/blob/Blob-stream.any.js") +import("https://wpt.live/FileAPI/blob/Blob-text.any.js") From 04c0019e6a35093472155cbd867a59e0aff16840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jimmy=20Wa=CC=88rting?= Date: Fri, 30 Jul 2021 14:34:55 +0200 Subject: [PATCH 2/2] only run test.js with ava --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 827cf04..fde1adc 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,8 @@ "lint": "xo test.js", "test-wpt": "node --experimental-loader ./test/http-loader.js ./test/test-wpt-in-node.js", "test": "npm run lint && ava test.js", - "report": "c8 --reporter json --reporter text ava", - "coverage": "c8 --reporter json --reporter text ava && codecov -f coverage/coverage-final.json", + "report": "c8 --reporter json --reporter text ava test.js", + "coverage": "c8 --reporter json --reporter text ava test.js && codecov -f coverage/coverage-final.json", "prepublishOnly": "tsc --declaration --emitDeclarationOnly --allowJs index.js from.js" }, "repository": "https://github.com/node-fetch/fetch-blob.git",