es-arraybuffer-base64
Version:
An ES-spec-compliant shim/polyfill/replacement for ArrayBuffer base64 methods that works as far down as ES3
301 lines (253 loc) • 8.99 kB
JavaScript
'use strict';
var availableTypedArrays = require('available-typed-arrays')();
var callBind = require('call-bind');
var defineProperties = require('define-properties');
var DetachArrayBuffer = require('es-abstract/2024/DetachArrayBuffer');
var forEach = require('es-abstract/helpers/forEach');
var isCore = require('is-core-module');
var test = require('tape');
/* globals postMessage: false */
var canDetach = typeof structuredClone === 'function' || typeof postMessage === 'function' || isCore('worker_threads');
var index = require('../Uint8Array.prototype.toBase64');
var impl = require('../Uint8Array.prototype.toBase64/implementation');
var polyfill = require('../Uint8Array.prototype.toBase64/polyfill')();
var isEnumerable = Object.prototype.propertyIsEnumerable;
var methodName = 'toBase64';
var shimName = 'Uint8Array.prototype.' + methodName;
module.exports = {
tests: function (t, method) {
t.test('Uint8Arrays not supported', { skip: typeof Uint8Array === 'function' }, function (st) {
st['throws'](
function () { return method(); },
SyntaxError,
'throws SyntaxError when Uint8Arrays are not supported'
);
st.end();
});
t.test('Uint8Arrays supported', { skip: typeof Uint8Array !== 'function' }, function (st) {
st.deepEqual(method(new Uint8Array([])), '', 'empty array produces empty string');
var array = new Uint8Array([251, 255, 191]);
st.equal(
method(array),
'+/+/',
'no alphabet produces base64 string'
);
st.equal(
method(array, { alphabet: 'base64' }),
'+/+/',
'base64 alphabet produces base64 string'
);
st.equal(
method(array, { alphabet: 'base64url' }),
'-_-_',
'base64url alphabet produces base64url string'
);
st['throws'](
function () { return method(array, { alphabet: 'invalid' }); },
TypeError,
'invalid alphabet throws'
);
st.test('test262: test/built-ins/Uint8Array/prototype/toBase64/alphabet.js', function (s2t) {
s2t.equal(method(new Uint8Array([199, 239, 242])), 'x+/y');
s2t.equal(method(new Uint8Array([199, 239, 242]), { alphabet: 'base64' }), 'x+/y');
s2t.equal(method(new Uint8Array([199, 239, 242]), { alphabet: 'base64url' }), 'x-_y');
s2t['throws'](
function () { method(new Uint8Array([199, 239, 242]), { alphabet: 'other' }); },
TypeError
);
s2t.end();
});
st.test('test262: test/built-ins/Uint8Array/prototype/toBase64/receiver-not-uint8array.js', {
skip: defineProperties.supportsDescriptors
}, function (s2t) {
var options = {};
s2t.intercept(options, 'alphabet', {
get: function () {
throw new EvalError('options.alphabet accessed despite incompatible receiver');
}
});
forEach(availableTypedArrays, function (taName) {
if (taName === 'Uint8Array') { return; }
var TA = global[taName];
var sample = new TA(2);
s2t['throws'](
function () { method(sample, options); },
TypeError,
'throws with ' + taName
);
});
s2t['throws'](
function () { method([], options); },
TypeError
);
s2t['throws'](
function () { method(options); },
TypeError
);
s2t.end();
});
st.test('test262: test/built-ins/Uint8Array/prototype/toBase64/detached-buffer.js', {
skip: !canDetach || !defineProperties.supportsDescriptors
}, function (s2t) {
var arr = new Uint8Array(2);
var receiverDetachingOptions = {};
var results = s2t.intercept(receiverDetachingOptions, 'alphabet', {
get: function () {
DetachArrayBuffer(arr.buffer);
return 'base64';
}
});
s2t['throws'](
function () { method(arr, receiverDetachingOptions); },
TypeError
);
s2t.deepEqual(results(), [
{
type: 'get',
success: true,
value: 'base64',
args: [],
receiver: receiverDetachingOptions
}
]);
var detached = new Uint8Array(2);
DetachArrayBuffer(detached.buffer);
var sideEffectingOptions = {};
var results2 = s2t.intercept(sideEffectingOptions, 'alphabet', {
get: function () {
return 'base64';
}
});
s2t['throws'](
function () { method(detached, sideEffectingOptions); },
TypeError
);
s2t.deepEqual(results2(), [
{
type: 'get',
success: true,
value: 'base64',
args: [],
receiver: sideEffectingOptions
}
]);
s2t.end();
});
st.test('test262: test/built-ins/Uint8Array/prototype/toBase64/option-coercion.js', function (s2t) {
s2t['throws'](
function () {
method(new Uint8Array(2), { alphabet: Object('base64') });
},
TypeError
);
var throwyToString = {};
var results = s2t.intercept(throwyToString, 'toString', {
value: function () {
throw new EvalError('toString called on alphabet value');
}
});
s2t['throws'](
function () { method(new Uint8Array(2), { alphabet: throwyToString }); },
TypeError
);
s2t.deepEqual(results(), []);
var base64UrlOptions = {};
var results2 = s2t.intercept(base64UrlOptions, 'alphabet', {
get: function () {
return 'base64url';
}
});
s2t.equal(method(new Uint8Array([199, 239, 242]), base64UrlOptions), 'x-_y');
s2t.deepEqual(results2(), [
{
type: 'get',
success: true,
value: 'base64url',
args: [],
receiver: base64UrlOptions
}
]);
// side-effects from the getter on the receiver are reflected in the result
var arr = new Uint8Array([0]);
var receiverMutatingOptions = {};
var results3 = s2t.intercept(receiverMutatingOptions, 'alphabet', {
get: function () {
arr[0] = 255;
return 'base64';
}
});
var result = method(arr, receiverMutatingOptions);
s2t.equal(result, '/w==');
s2t.equal(arr[0], 255);
s2t.deepEqual(results3(), [
{
type: 'get',
success: true,
value: 'base64',
args: [],
receiver: receiverMutatingOptions
}
]);
s2t.end();
});
st.test('test262: test/built-ins/Uint8Array/prototype/toBase64/omit-padding', function (s2t) {
s2t.equal(method(new Uint8Array([199, 239])), 'x+8=');
s2t.equal(method(new Uint8Array([199, 239]), { omitPadding: false }), 'x+8=');
s2t.equal(method(new Uint8Array([199, 239]), { omitPadding: true }), 'x+8');
s2t.equal(method(new Uint8Array([255]), { omitPadding: true }), '/w');
// works with base64url alphabet
s2t.equal(method(new Uint8Array([199, 239]), { alphabet: 'base64url' }), 'x-8=');
s2t.equal(method(new Uint8Array([199, 239]), { alphabet: 'base64url', omitPadding: false }), 'x-8=');
s2t.equal(method(new Uint8Array([199, 239]), { alphabet: 'base64url', omitPadding: true }), 'x-8');
s2t.equal(method(new Uint8Array([255]), { alphabet: 'base64url', omitPadding: true }), '_w');
// performs ToBoolean on the argument
s2t.equal(method(new Uint8Array([255]), { omitPadding: 0 }), '/w==');
s2t.equal(method(new Uint8Array([255]), { omitPadding: 1 }), '/w');
s2t.end();
});
// standard test vectors from https://datatracker.ietf.org/doc/html/rfc4648#section-10
st.equal(method(new Uint8Array([])), '');
st.equal(method(new Uint8Array([102])), 'Zg==');
st.equal(method(new Uint8Array([102]), { omitPadding: true }), 'Zg');
st.equal(method(new Uint8Array([102, 111])), 'Zm8=');
st.equal(method(new Uint8Array([102, 111]), { omitPadding: true }), 'Zm8');
st.equal(method(new Uint8Array([102, 111, 111])), 'Zm9v');
st.equal(method(new Uint8Array([102, 111, 111, 98])), 'Zm9vYg==');
st.equal(method(new Uint8Array([102, 111, 111, 98]), { omitPadding: true }), 'Zm9vYg');
st.equal(method(new Uint8Array([102, 111, 111, 98, 97])), 'Zm9vYmE=');
st.equal(method(new Uint8Array([102, 111, 111, 98, 97]), { omitPadding: true }), 'Zm9vYmE');
st.equal(method(new Uint8Array([102, 111, 111, 98, 97, 114])), 'Zm9vYmFy');
st.end();
});
},
index: function () {
test(shimName + ': index', function (t) {
t.notEqual(index, polyfill, 'index !== polyfill');
t.equal(typeof index, 'function', 'index is a function');
t['throws'](
function () { return new index(); }, // eslint-disable-line new-cap
TypeError,
'index throws when Construct-ed'
);
module.exports.tests(t, index);
t.end();
});
},
implementation: function () {
test(shimName + ': implementation', function (t) {
t.equal(impl, polyfill, 'implementation is polyfill itself');
module.exports.tests(t, callBind(impl));
t.end();
});
},
shimmed: function () {
test(shimName + ': shimmed', function (t) {
t.test('enumerability', { skip: !defineProperties.supportsDescriptors }, function (et) {
et.equal(false, isEnumerable.call(Uint8Array.prototype, methodName), shimName + ' is not enumerable');
et.end();
});
module.exports.tests(t, callBind(Uint8Array.prototype[methodName]));
t.end();
});
}
};