Use a WeakMap for private properties (#42)
* options.buffer isn't a valid option * truly private internal properties * 100% test coverage
This commit is contained in:
@@ -3,66 +3,56 @@
|
||||
|
||||
const {Readable: ReadableStream} = require('stream');
|
||||
|
||||
const BUFFER = Symbol('buffer');
|
||||
const TYPE = Symbol('type');
|
||||
const wm = new WeakMap();
|
||||
|
||||
class Blob {
|
||||
constructor(...args) {
|
||||
this[TYPE] = '';
|
||||
|
||||
const blobParts = args[0];
|
||||
const options = args[1];
|
||||
|
||||
constructor(blobParts = [], options = {type: ''}) {
|
||||
const buffers = [];
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
let size = 0;
|
||||
|
||||
if (blobParts) {
|
||||
blobParts.forEach(element => {
|
||||
let buffer;
|
||||
if (element instanceof Buffer) {
|
||||
buffer = element;
|
||||
} else if (ArrayBuffer.isView(element)) {
|
||||
buffer = Buffer.from(element.buffer, element.byteOffset, element.byteLength);
|
||||
} else if (element instanceof ArrayBuffer) {
|
||||
buffer = Buffer.from(element);
|
||||
} else if (element instanceof Blob) {
|
||||
buffer = element[BUFFER];
|
||||
} else {
|
||||
buffer = Buffer.from(typeof element === 'string' ? element : String(element));
|
||||
}
|
||||
blobParts.forEach(element => {
|
||||
let buffer;
|
||||
if (element instanceof Buffer) {
|
||||
buffer = element;
|
||||
} else if (ArrayBuffer.isView(element)) {
|
||||
buffer = Buffer.from(element.buffer, element.byteOffset, element.byteLength);
|
||||
} else if (element instanceof ArrayBuffer) {
|
||||
buffer = Buffer.from(element);
|
||||
} else if (element instanceof Blob) {
|
||||
buffer = wm.get(element).buffer;
|
||||
} else {
|
||||
buffer = Buffer.from(typeof element === 'string' ? element : String(element));
|
||||
}
|
||||
|
||||
size += buffer.length;
|
||||
buffers.push(buffer);
|
||||
});
|
||||
}
|
||||
size += buffer.length;
|
||||
buffers.push(buffer);
|
||||
});
|
||||
|
||||
this[BUFFER] = Buffer.concat(buffers);
|
||||
const buffer = Buffer.concat(buffers, size);
|
||||
|
||||
const type = options && options.type !== undefined && String(options.type).toLowerCase();
|
||||
if (type && !/[^\u0020-\u007E]/.test(type)) {
|
||||
this[TYPE] = type;
|
||||
}
|
||||
const type = options.type === undefined ? '' : String(options.type).toLowerCase();
|
||||
|
||||
if (options && Buffer.isBuffer(options.buffer)) {
|
||||
this[BUFFER] = options.buffer;
|
||||
}
|
||||
wm.set(this, {
|
||||
type: /[^\u0020-\u007E]/.test(type) ? '' : type,
|
||||
size,
|
||||
buffer
|
||||
});
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this[BUFFER].length;
|
||||
return wm.get(this).size;
|
||||
}
|
||||
|
||||
get type() {
|
||||
return this[TYPE];
|
||||
return wm.get(this).type;
|
||||
}
|
||||
|
||||
text() {
|
||||
return Promise.resolve(this[BUFFER].toString());
|
||||
return Promise.resolve(wm.get(this).buffer.toString());
|
||||
}
|
||||
|
||||
arrayBuffer() {
|
||||
const buf = this[BUFFER];
|
||||
const buf = wm.get(this).buffer;
|
||||
const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
||||
return Promise.resolve(ab);
|
||||
}
|
||||
@@ -70,7 +60,7 @@ class Blob {
|
||||
stream() {
|
||||
const readable = new ReadableStream();
|
||||
readable._read = () => { };
|
||||
readable.push(this[BUFFER]);
|
||||
readable.push(wm.get(this).buffer);
|
||||
readable.push(null);
|
||||
return readable;
|
||||
}
|
||||
@@ -88,30 +78,29 @@ class Blob {
|
||||
let relativeEnd;
|
||||
|
||||
if (start === undefined) {
|
||||
relativeStart = 0;
|
||||
relativeStart = 0; //
|
||||
} else if (start < 0) {
|
||||
relativeStart = Math.max(size + start, 0);
|
||||
relativeStart = Math.max(size + start, 0); //
|
||||
} else {
|
||||
relativeStart = Math.min(start, size);
|
||||
}
|
||||
|
||||
if (end === undefined) {
|
||||
relativeEnd = size;
|
||||
relativeEnd = size; //
|
||||
} else if (end < 0) {
|
||||
relativeEnd = Math.max(size + end, 0);
|
||||
relativeEnd = Math.max(size + end, 0); //
|
||||
} else {
|
||||
relativeEnd = Math.min(end, size);
|
||||
}
|
||||
|
||||
const span = Math.max(relativeEnd - relativeStart, 0);
|
||||
|
||||
const buffer = this[BUFFER];
|
||||
const slicedBuffer = buffer.slice(
|
||||
const slicedBuffer = wm.get(this).buffer.slice(
|
||||
relativeStart,
|
||||
relativeStart + span
|
||||
);
|
||||
const blob = new Blob([], {type: args[2]});
|
||||
blob[BUFFER] = slicedBuffer;
|
||||
const _ = wm.get(blob);
|
||||
_.buffer = slicedBuffer;
|
||||
return blob;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,32 @@ const Blob = require('.');
|
||||
const getStream = require('get-stream');
|
||||
const {Response} = require('node-fetch');
|
||||
|
||||
test('Blob ctor', t => {
|
||||
test('new Blob()', t => {
|
||||
const blob = new Blob(); // eslint-disable-line no-unused-vars
|
||||
t.pass();
|
||||
});
|
||||
|
||||
test('new Blob(parts)', t => {
|
||||
const data = 'a=1';
|
||||
const blob = new Blob([data]); // eslint-disable-line no-unused-vars
|
||||
t.pass();
|
||||
});
|
||||
|
||||
test('Blob ctor parts', async t => {
|
||||
const parts = [
|
||||
'a',
|
||||
new Uint8Array([98]),
|
||||
new Uint16Array([25699]),
|
||||
new Uint8Array([101]).buffer,
|
||||
Buffer.from('f'),
|
||||
new Blob(['g']),
|
||||
{}
|
||||
];
|
||||
|
||||
const blob = new Blob(parts);
|
||||
t.is(await blob.text(), 'abcdefg[object Object]');
|
||||
});
|
||||
|
||||
test('Blob size', t => {
|
||||
const data = 'a=1';
|
||||
const blob = new Blob([data]);
|
||||
@@ -16,12 +36,27 @@ test('Blob size', t => {
|
||||
});
|
||||
|
||||
test('Blob type', t => {
|
||||
const data = 'a=1';
|
||||
const type = 'text/plain';
|
||||
const blob = new Blob([data], {type});
|
||||
const blob = new Blob([], {type});
|
||||
t.is(blob.type, type);
|
||||
});
|
||||
|
||||
test('Blob slice type', t => {
|
||||
const type = 'text/plain';
|
||||
const blob = new Blob().slice(0, 0, type);
|
||||
t.is(blob.type, type);
|
||||
});
|
||||
|
||||
test('invalid Blob type', t => {
|
||||
const blob = new Blob([], {type: '\u001Ftext/plain'});
|
||||
t.is(blob.type, '');
|
||||
});
|
||||
|
||||
test('invalid Blob slice type', t => {
|
||||
const blob = new Blob().slice(0, 0, '\u001Ftext/plain');
|
||||
t.is(blob.type, '');
|
||||
});
|
||||
|
||||
test('Blob text()', async t => {
|
||||
const data = 'a=1';
|
||||
const type = 'text/plain';
|
||||
@@ -55,11 +90,27 @@ test('Blob toString()', t => {
|
||||
});
|
||||
|
||||
test('Blob slice()', async t => {
|
||||
const data = 'a=1';
|
||||
const type = 'text/plain';
|
||||
const blob = new Blob([data], {type});
|
||||
const blob2 = blob.slice(0, 1);
|
||||
t.is(await blob2.text(), data.slice(0, 1));
|
||||
const data = 'abcdefgh';
|
||||
const blob = new Blob([data]).slice();
|
||||
t.is(await blob.text(), data);
|
||||
});
|
||||
|
||||
test('Blob slice(0, 1)', async t => {
|
||||
const data = 'abcdefgh';
|
||||
const blob = new Blob([data]).slice(0, 1);
|
||||
t.is(await blob.text(), 'a');
|
||||
});
|
||||
|
||||
test('Blob slice(-1)', async t => {
|
||||
const data = 'abcdefgh';
|
||||
const blob = new Blob([data]).slice(-1);
|
||||
t.is(await blob.text(), 'h');
|
||||
});
|
||||
|
||||
test('Blob slice(0, -1)', async t => {
|
||||
const data = 'abcdefgh';
|
||||
const blob = new Blob([data]).slice(0, -1);
|
||||
t.is(await blob.text(), 'abcdefg');
|
||||
});
|
||||
|
||||
test('Blob works with node-fetch Response.blob()', async t => {
|
||||
|
||||
Reference in New Issue
Block a user