diff --git a/modules/common.js b/modules/common.js index dbdbf92..3bd621e 100644 --- a/modules/common.js +++ b/modules/common.js @@ -64,3 +64,24 @@ export const getType = o => Object.prototype.toString.call(o).slice(TYPE_FROM, T * @returns {boolean} - result */ export const isString = o => typeof o === 'string' || o instanceof String; + +/** + * sleep + * + * @param {number} msec - millisecond + * @param {boolean} doReject - reject instead of resolve + * @returns {?Function} - resolve / reject + */ +export const sleep = (msec = 0, doReject = false) => { + let func; + if (Number.isInteger(msec) && msec >= 0) { + func = new Promise((resolve, reject) => { + if (doReject) { + setTimeout(reject, msec); + } else { + setTimeout(resolve, msec); + } + }); + } + return func || null; +}; diff --git a/src/mjs/file-reader.js b/src/mjs/file-reader.js new file mode 100644 index 0000000..7606f76 --- /dev/null +++ b/src/mjs/file-reader.js @@ -0,0 +1,277 @@ +/** + * file-reader.js + * implement HTML5 FileReader API + */ + +/* shared */ +import { Blob, resolveObjectURL } from 'node:buffer'; +import { getType, isString } from './common.js'; +import textChars from '../lib/file/text-chars.json' assert { type: 'json' }; + +/* constants */ +const DONE = 2; +const EMPTY = 0; +const LOADING = 1; +const REG_CHARSET = /^charset=([\da-z\-_]+)$/i; +const REG_MIME_DOM = + /^(?:text\/(?:ht|x)ml|application\/(?:xhtml\+)?xml|image\/svg\+xml);?/; +const REG_MIME_TEXT = /^text\/[\da-z][\da-z\-.][\da-z]+;?/i; +/** + * file reader + * + */ +export class FileReader extends EventTarget { + /* private fields */ + #error; + #state; + #result; + #terminate; + + /** + * construct + */ + constructor() { + super(); + this.EMPTY = EMPTY; + this.LOADING = LOADING; + this.DONE = DONE; + this.#error = null; + this.#state = this.EMPTY; + this.#result = null; + this.#terminate = false; + } + + /* getter */ + get error() { + return this.#error; + } + + get readyState() { + return this.#state; + } + + get result() { + return this.#result; + } + + /** + * dispatch progress event + * + * @param {string} type - event type + * @returns {Function} - super.dispatchEvent() + */ + _dispatchProgressEvent(type) { + if (isString(type)) { + type = type.trim(); + if (!/abort|error|load(?:end|start)?|progress/.test(type)) { + this.#error = new DOMException('Invalid state.', 'InvalidStateError'); + this._dispatchProgressEvent('error'); + throw this.#error; + } + } else { + this.#error = new TypeError(`Expected String but got ${getType(type)}.`); + this._dispatchProgressEvent('error'); + throw this.#error; + } + if (type === 'error' && !this.#error) { + this.#error = new Error('Unknown error.'); + } + const evt = new Event(type, { + bubbles: false, + cancelable: false + }); + return super.dispatchEvent(evt); + } + + /** + * abort + * + * @returns {void} + */ + abort() { + this.#state = this.DONE; + this.#result = null; + this.#terminate = true; + this._dispatchProgressEvent('abort'); + this._dispatchProgressEvent('loadend'); + } + + /** + * read blob + * + * @param {object|string} blob - blob data or blob URL + * @param {string} format - format to read as + * @param {string} encoding - character encoding + * @returns {void} + */ + async _read(blob, format, encoding = '') { + if (!((blob instanceof Blob || isString(blob)) && isString(format) && + isString(encoding))) { + this.abort(); + } + if (!this.#terminate) { + if (this.#state === this.LOADING) { + this.#error = new DOMException('Invalid state.', 'InvalidStateError'); + this._dispatchProgressEvent('error'); + throw this.#error; + } + this.#state = this.LOADING; + this.#result = null; + this.#error = null; + let data; + if (blob instanceof Blob) { + data = blob; + } else if (isString(blob)) { + try { + const { protocol } = new URL(blob); + if (protocol === 'blob:') { + data = resolveObjectURL(blob); + } + } catch (e) { + // fall through + } + } + if (data) { + this._dispatchProgressEvent('loadstart'); + let res; + try { + const { type } = data; + const buffer = await data.arrayBuffer(); + const uint8arr = new Uint8Array(buffer); + const header = type ? type.split(';') : []; + const binary = String.fromCharCode(...uint8arr); + switch (format) { + case 'arrayBuffer': + case 'buffer': + res = buffer; + break; + case 'binary': + case 'binaryString': + res = binary; + break; + case 'data': + case 'dataURL': { + if (!header.length || header[header.length - 1] !== 'base64') { + header.push('base64'); + } + res = `data:${header.join(';')},${btoa(binary)}`; + break; + } + // NOTE: read only if encoding matches + case 'text': { + const textCharCodes = new Set(textChars); + if (uint8arr.every(c => textCharCodes.has(c))) { + let charset; + for (const head of header) { + if (REG_CHARSET.test(head)) { + [, charset] = REG_CHARSET.exec(head); + if (charset) { + if (/utf-?8/i.test(charset)) { + charset = 'utf8'; + } else { + charset = charset.toLowerCase(); + } + break; + } + } + } + if (encoding) { + if (/utf-?8/i.test(encoding)) { + encoding = 'utf8'; + } else { + encoding = encoding.toLowerCase(); + } + } + if (REG_MIME_DOM.test(type)) { + if ((encoding && charset && encoding === charset) || + (!(encoding || charset)) || + (!encoding && charset === 'utf8') || + (encoding === 'utf8' && !charset)) { + res = binary; + } + } else if (REG_MIME_TEXT.test(type)) { + if ((encoding && charset && encoding === charset) || + (!(encoding || charset)) || + (!encoding && charset === 'utf8') || + (encoding === 'utf8' && charset === 'us-ascii')) { + res = binary; + } + } + } + break; + } + default: + } + } catch (e) { + if (!this.#terminate) { + this.#error = e; + this.#state = this.DONE; + this._dispatchProgressEvent('error'); + this._dispatchProgressEvent('loadend'); + } + } + if (!(this.#terminate || this.#error)) { + if (res) { + this.#result = res; + this.#state = this.DONE; + this._dispatchProgressEvent('load'); + this._dispatchProgressEvent('loadend'); + } else { + this.abort(); + } + } + } else { + this.abort(); + } + } + } + + /** + * read as arrayBuffer + * + * @param {object|string} blob - blob data or blob URL + * @returns {void} + */ + async readAsArrayBuffer(blob) { + await this._read(blob, 'arrayBuffer'); + } + + /** + * read as binary string + * + * @param {object|string} blob - blob data or blob URL + * @returns {void} + */ + async readAsBinaryString(blob) { + await this._read(blob, 'binaryString'); + } + + /** + * read as data URL + * + * @param {object|string} blob - blob data or blob URL + * @returns {void} + */ + async readAsDataURL(blob) { + await this._read(blob, 'dataURL'); + } + + /** + * read as text + * + * @param {object|string} blob - blob data or blob URL + * @param {string} encoding - encoding + * @returns {void} + */ + async readAsText(blob, encoding) { + await this._read(blob, 'text', encoding); + } +} + +/* instance */ +const fileReader = new FileReader(); + +/* export */ +export { + fileReader as default +}; diff --git a/test/common.test.js b/test/common.test.js index 8675f01..9ef0905 100644 --- a/test/common.test.js +++ b/test/common.test.js @@ -5,7 +5,7 @@ import sinon from 'sinon'; /* test */ import { - getType, isString, logErr, logMsg, logWarn, throwErr + getType, isString, logErr, logMsg, logWarn, sleep, throwErr } from '../modules/common.js'; describe('getType', () => { @@ -102,3 +102,39 @@ describe('throwErr', () => { assert.throws(() => throwErr(e)); }); }); + +describe('sleep', () => { + it('should resolve even if no argument given', async () => { + const fake = sinon.fake(); + const fake2 = sinon.fake(); + await sleep().then(fake).catch(fake2); + assert.strictEqual(fake.callCount, 1); + assert.strictEqual(fake2.callCount, 0); + }); + + it('should get null if 1st argument is not integer', async () => { + const res = await sleep('foo'); + assert.isNull(res); + }); + + it('should get null if 1st argument is not positive integer', async () => { + const res = await sleep(-1); + assert.isNull(res); + }); + + it('should resolve', async () => { + const fake = sinon.fake(); + const fake2 = sinon.fake(); + await sleep(1).then(fake).catch(fake2); + assert.strictEqual(fake.callCount, 1); + assert.strictEqual(fake2.callCount, 0); + }); + + it('should reject', async () => { + const fake = sinon.fake(); + const fake2 = sinon.fake(); + await sleep(1, true).then(fake).catch(fake2); + assert.strictEqual(fake.callCount, 0); + assert.strictEqual(fake2.callCount, 1); + }); +}); diff --git a/test/file-reader.test.js b/test/file-reader.test.js new file mode 100644 index 0000000..88dbae6 --- /dev/null +++ b/test/file-reader.test.js @@ -0,0 +1,687 @@ +/** + * file-reader.test.js + */ + +/* api */ +import { assert } from 'chai'; +import { describe, it } from 'mocha'; +import { sleep } from '../modules/common.js'; +import sinon from 'sinon'; + +/* test */ +import fileReader, { FileReader } from '../src/mjs/file-reader.js'; + +describe('file-reader', () => { + describe('default', () => { + it('should be instance of FileReader', () => { + assert.instanceOf(fileReader, EventTarget, 'instance'); + assert.instanceOf(fileReader, FileReader, 'instance'); + }); + }); + + describe('File reader', () => { + it('should create instance', () => { + const reader = new FileReader(); + assert.instanceOf(reader, EventTarget, 'instance'); + assert.instanceOf(reader, FileReader, 'instance'); + }); + + describe('getter', () => { + it('should get value', () => { + const reader = new FileReader(); + const res = reader.error; + assert.isNull(res, 'result'); + }); + + it('should get value', () => { + const reader = new FileReader(); + const res = reader.readyState; + assert.strictEqual(res, 0, 'result'); + }); + + it('should get value', () => { + const reader = new FileReader(); + const res = reader.result; + assert.isNull(res, 'result'); + }); + }); + + describe('dispatch progress event', () => { + it('should throw', () => { + const reader = new FileReader(); + assert.throws(() => reader._dispatchProgressEvent(), + 'Expected String but got Undefined.'); + assert.instanceOf(reader.error, TypeError, 'error'); + assert.strictEqual(reader.error.message, + 'Expected String but got Undefined.', 'message'); + }); + + it('should throw', () => { + const reader = new FileReader(); + assert.throws(() => reader._dispatchProgressEvent('foo'), + 'Invalid state.'); + assert.instanceOf(reader.error, DOMException, 'error'); + assert.strictEqual(reader.error.message, + 'Invalid state.', 'message'); + }); + + it('should dispatch event', () => { + const reader = new FileReader(); + const res = reader._dispatchProgressEvent('abort'); + assert.isTrue(res, 'result'); + }); + + it('should dispatch event', () => { + const reader = new FileReader(); + const res = reader._dispatchProgressEvent('error'); + assert.instanceOf(reader.error, Error, 'instance'); + assert.strictEqual(reader.error.message, 'Unknown error.', 'message'); + assert.isTrue(res, 'result'); + }); + + it('should dispatch event', () => { + const reader = new FileReader(); + const res = reader._dispatchProgressEvent('load'); + assert.isTrue(res, 'result'); + }); + + it('should dispatch event', () => { + const reader = new FileReader(); + const res = reader._dispatchProgressEvent('loadend'); + assert.isTrue(res, 'result'); + }); + + it('should dispatch event', () => { + const reader = new FileReader(); + const res = reader._dispatchProgressEvent('loadstart'); + assert.isTrue(res, 'result'); + }); + + it('should dispatch event', () => { + const reader = new FileReader(); + const res = reader._dispatchProgressEvent('progress'); + assert.isTrue(res, 'result'); + }); + }); + + describe('abort', () => { + it('should call function twice', () => { + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + reader.abort(); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 2, 'called'); + }); + }); + + describe('read blob', () => { + it('should abort', async () => { + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader._read(); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 2, 'called'); + }); + + it('should get result', async () => { + const blob = new Blob(['Hello, world!'], { + type: 'text/plain' + }); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader._read(blob); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 2, 'called'); + }); + + it('should abort', async () => { + const blob = new Blob(['Hello, world!'], { + type: 'text/plain' + }); + const url = URL.createObjectURL(blob); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader._read(url); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 2, 'called'); + }); + + it('should abort', async () => { + const blob = new Blob(['Hello, world!'], { + type: 'text/plain' + }); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader._read(blob, 'foo'); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 3, 'called'); + }); + + it('should abort', async () => { + const blob = new Blob(['Hello, world!'], { + type: 'text/plain' + }); + const url = URL.createObjectURL(blob); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader._read(url, 'foo'); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 3, 'called'); + }); + + it('should throw', async () => { + const blob = new Blob(['Hello, world!'], { + type: 'text/plain' + }); + const stubFunc = sinon.stub(blob, 'arrayBuffer').callsFake(async () => { + await sleep(1000); + }); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await Promise.all([ + reader._read(blob, 'buffer'), + sleep(100).then(() => reader._read(blob, 'binary')) + ]).catch(e => { + assert.instanceOf(e, DOMException, 'error'); + assert.strictEqual(e.message, 'Invalid state.', 'message'); + }); + assert.strictEqual(stubFunc.callCount, 1, 'called'); + assert.strictEqual(reader.readyState, 1, 'state'); + assert.strictEqual(spyFunc.callCount, i + 2, 'called'); + assert.instanceOf(reader.error, DOMException, 'error'); + assert.strictEqual(reader.error.message, 'Invalid state.'); + }); + + it('should abort', async () => { + const blob = 'Hello, world!'; + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader._read(blob, 'buffer'); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 2, 'called'); + }); + + it('should get result', async () => { + const blob = new Blob(['Hello, world!'], { + type: 'text/plain' + }); + const buffer = await blob.arrayBuffer(); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader._read(blob, 'buffer'); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 3, 'called'); + assert.deepEqual(reader.result, buffer, 'result'); + }); + + it('should get result', async () => { + const blob = new Blob(['Hello, world!'], { + type: 'text/plain' + }); + const buffer = await blob.arrayBuffer(); + const url = URL.createObjectURL(blob); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader._read(url, 'buffer'); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 3, 'called'); + assert.deepEqual(reader.result, buffer, 'result'); + }); + + it('should get result', async () => { + const blob = new Blob(['Hello, world!'], { + type: 'text/plain' + }); + const buffer = await blob.arrayBuffer(); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader._read(blob, 'arrayBuffer'); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 3, 'called'); + assert.deepEqual(reader.result, buffer, 'result'); + }); + + it('should get result', async () => { + const blob = new Blob(['Hello, world!'], { + type: 'text/plain' + }); + const buffer = await blob.arrayBuffer(); + const url = URL.createObjectURL(blob); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader._read(url, 'arrayBuffer'); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 3, 'called'); + assert.deepEqual(reader.result, buffer, 'result'); + }); + + it('should get result', async () => { + const blob = new Blob(['Hello, world!'], { + type: 'text/plain' + }); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader._read(blob, 'binary'); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 3, 'called'); + assert.strictEqual(reader.result, 'Hello, world!', 'result'); + }); + + it('should get result', async () => { + const blob = new Blob(['Hello, world!'], { + type: 'text/plain' + }); + const url = URL.createObjectURL(blob); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader._read(url, 'binary'); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 3, 'called'); + assert.strictEqual(reader.result, 'Hello, world!', 'result'); + }); + + it('should get result', async () => { + const blob = new Blob(['Hello, world!'], { + type: 'text/plain' + }); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader._read(blob, 'binaryString'); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 3, 'called'); + assert.strictEqual(reader.result, 'Hello, world!', 'result'); + }); + + it('should get result', async () => { + const blob = new Blob(['Hello, world!'], { + type: 'text/plain' + }); + const url = URL.createObjectURL(blob); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader._read(url, 'binaryString'); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 3, 'called'); + assert.strictEqual(reader.result, 'Hello, world!', 'result'); + }); + + it('should get result', async () => { + const pngBase64 = 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='; + const pngBin = atob(pngBase64); + const pngUint8 = + Uint8Array.from([...pngBin].map(c => c.charCodeAt(0))); + const blob = new Blob([pngUint8], { + type: 'image/png' + }); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader._read(blob, 'binary'); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 3, 'called'); + assert.deepEqual(reader.result, pngBin, 'result'); + }); + + it('should get result', async () => { + const blob = new Blob(['Hello, world!']); + const base64 = btoa('Hello, world!'); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader._read(blob, 'data'); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 3, 'called'); + assert.strictEqual(reader.result, `data:base64,${base64}`, 'result'); + }); + + it('should get result', async () => { + const blob = new Blob(['Hello, world!'], { + type: 'text/plain' + }); + const base64 = btoa('Hello, world!'); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader._read(blob, 'data'); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 3, 'called'); + assert.strictEqual(reader.result, `data:text/plain;base64,${base64}`, + 'result'); + }); + + it('should get result', async () => { + const blob = new Blob(['Hello, world!'], { + type: 'text/plain' + }); + const base64 = btoa('Hello, world!'); + const url = URL.createObjectURL(blob); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader._read(url, 'data'); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 3, 'called'); + assert.strictEqual(reader.result, `data:text/plain;base64,${base64}`, + 'result'); + }); + + it('should get result', async () => { + const blob = new Blob(['Hello, world!'], { + type: 'text/plain' + }); + const base64 = btoa('Hello, world!'); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader._read(blob, 'dataURL'); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 3, 'called'); + assert.strictEqual(reader.result, `data:text/plain;base64,${base64}`, + 'result'); + }); + + it('should get result', async () => { + const blob = new Blob(['Hello, world!'], { + type: 'text/plain' + }); + const base64 = btoa('Hello, world!'); + const url = URL.createObjectURL(blob); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader._read(url, 'dataURL'); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 3, 'called'); + assert.strictEqual(reader.result, `data:text/plain;base64,${base64}`, + 'result'); + }); + + it('should get result', async () => { + const blob = new Blob(['Hello, world!'], { + type: 'text/plain;charset=UTF-8' + }); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader._read(blob, 'text'); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 3, 'called'); + assert.strictEqual(reader.result, 'Hello, world!', 'result'); + }); + + it('should get result', async () => { + const blob = new Blob(['Hello, world!'], { + type: 'text/plain;charset=UTF-8' + }); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader._read(blob, 'text', 'Shift_JIS'); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 3, 'called'); + assert.isNull(reader.result, 'result'); + }); + + it('should get result', async () => { + const blob = new Blob(['Hello, world!'], { + type: 'text/plain;charset=UTF-8' + }); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader._read(blob, 'text', 'UTF-8'); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 3, 'called'); + assert.strictEqual(reader.result, 'Hello, world!', 'result'); + }); + + it('should get result', async () => { + const blob = new Blob(['Hello, world!'], { + type: 'text/plain;charset=US-ASCII' + }); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader._read(blob, 'text', 'UTF-8'); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 3, 'called'); + assert.strictEqual(reader.result, 'Hello, world!', 'result'); + }); + + it('should get null', async () => { + const blob = new Blob(['Hello, world!'], { + type: 'text/plain' + }); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader._read(blob, 'text', 'US-ASCII'); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 3, 'called'); + assert.isNull(reader.result, 'result'); + }); + + it('should get null', async () => { + const pngBase64 = 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='; + const pngBin = atob(pngBase64); + const pngUint8 = + Uint8Array.from([...pngBin].map(c => c.charCodeAt(0))); + const blob = new Blob([...pngUint8], { + type: 'image/png' + }); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader._read(blob, 'text'); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 3, 'called'); + assert.isNull(reader.result, 'result'); + }); + + it('should get result', async () => { + const blob = new Blob(['

Hello, world!

'], { + type: 'text/html' + }); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader._read(blob, 'text'); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 3, 'called'); + assert.strictEqual(reader.result, '

Hello, world!

', 'result'); + }); + + it('should get result', async () => { + const blob = new Blob(['

Hello, world!

'], { + type: 'text/html;charset=UTF-8' + }); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader._read(blob, 'text', 'utf8'); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 3, 'called'); + assert.strictEqual(reader.result, '

Hello, world!

', 'result'); + }); + + it('should get result', async () => { + const blob = new Blob(['

Hello, world!

'], { + type: 'text/html;charset=UTF-8' + }); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader._read(blob, 'text'); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 3, 'called'); + assert.strictEqual(reader.result, '

Hello, world!

', 'result'); + }); + + it('should get result', async () => { + const blob = new Blob(['

Hello, world!

'], { + type: 'text/html' + }); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader._read(blob, 'text', 'utf8'); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 3, 'called'); + assert.strictEqual(reader.result, '

Hello, world!

', 'result'); + }); + + it('should get null', async () => { + const blob = new Blob(['

Hello, world♥

'], { + type: 'text/html' + }); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader._read(blob, 'text', 'US-ASCII'); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 3, 'called'); + assert.isNull(reader.result, 'result'); + }); + + it('should get null', async () => { + const blob = new Blob(['

Hello, world!

'], { + type: 'text/html' + }); + const e = new Error('error'); + const stubFunc = sinon.stub(blob, 'arrayBuffer').rejects(e); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader._read(blob, 'text', 'utf8'); + stubFunc.restore(); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 3, 'called'); + assert.deepEqual(reader.error, e, 'error'); + assert.strictEqual(reader.error.message, 'error', 'message'); + assert.isNull(reader.result, 'result'); + }); + }); + + describe('read as arrayBuffer', () => { + it('should get result', async () => { + const blob = new Blob(['Hello, world!'], { + type: 'text/plain' + }); + const buffer = await blob.arrayBuffer(); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader.readAsArrayBuffer(blob); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 3, 'called'); + assert.deepEqual(reader.result, buffer, 'result'); + }); + + it('should get result', async () => { + const blob = new Blob(['Hello, world!'], { + type: 'text/plain' + }); + const buffer = await blob.arrayBuffer(); + const url = URL.createObjectURL(blob); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader.readAsArrayBuffer(url); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 3, 'called'); + assert.deepEqual(reader.result, buffer, 'result'); + }); + }); + + describe('read as binary string', () => { + it('should get result', async () => { + const blob = new Blob(['Hello, world!'], { + type: 'text/plain' + }); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader.readAsBinaryString(blob); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 3, 'called'); + assert.strictEqual(reader.result, 'Hello, world!', 'result'); + }); + + it('should get result', async () => { + const blob = new Blob(['Hello, world!'], { + type: 'text/plain' + }); + const url = URL.createObjectURL(blob); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader.readAsBinaryString(url); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 3, 'called'); + assert.strictEqual(reader.result, 'Hello, world!', 'result'); + }); + }); + + describe('read as data URL', () => { + it('should get result', async () => { + const blob = new Blob(['

Hello, world!

'], { + type: 'text/html' + }); + const base64 = btoa('

Hello, world!

'); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader.readAsDataURL(blob); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 3, 'called'); + assert.strictEqual(reader.result, `data:text/html;base64,${base64}`, + 'result'); + }); + + it('should get result', async () => { + const blob = new Blob(['

Hello, world!

'], { + type: 'text/html' + }); + const base64 = btoa('

Hello, world!

'); + const url = URL.createObjectURL(blob); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader.readAsDataURL(url); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 3, 'called'); + assert.strictEqual(reader.result, `data:text/html;base64,${base64}`, + 'result'); + }); + + it('should get result', async () => { + const blob = new Blob(['

Hello, world!

'], { + type: 'text/html' + }); + const reader = new FileReader(); + const spyFunc = sinon.spy(reader, '_dispatchProgressEvent'); + const i = spyFunc.callCount; + await reader.readAsText(blob); + assert.strictEqual(reader.readyState, 2, 'state'); + assert.strictEqual(spyFunc.callCount, i + 3, 'called'); + assert.strictEqual(reader.result, '

Hello, world!

', 'result'); + }); + }); + }); +});