Initial project import
This commit is contained in:
327
node_modules/troika-worker-utils/__tests__/WorkerModules.test.js
generated
vendored
Normal file
327
node_modules/troika-worker-utils/__tests__/WorkerModules.test.js
generated
vendored
Normal file
@@ -0,0 +1,327 @@
|
||||
/*global require,global,_testDataBucket*/
|
||||
|
||||
require('./_jsdom-worker.js')
|
||||
const {defineWorkerModule} = require('../src/WorkerModules.js')
|
||||
|
||||
beforeEach(() => {
|
||||
// Functions that are run in the worker can place breadcrumbs in this object that
|
||||
// `expect` matchers can then retrieve and test later on.
|
||||
global._testDataBucket = {}
|
||||
})
|
||||
|
||||
|
||||
|
||||
test('requires an `options` argument', () => {
|
||||
expect(() => {
|
||||
defineWorkerModule()
|
||||
}).toThrow()
|
||||
})
|
||||
|
||||
test('requires an `init` function option', () => {
|
||||
expect(() => {
|
||||
defineWorkerModule({notInit: 'blerp'})
|
||||
}).toThrow()
|
||||
expect(() => {
|
||||
defineWorkerModule({init: 'not_a_function'})
|
||||
}).toThrow()
|
||||
expect(() => {
|
||||
defineWorkerModule({init: () => {}})
|
||||
}).not.toThrow()
|
||||
})
|
||||
|
||||
test('returns a function', () => {
|
||||
expect(typeof defineWorkerModule({init: () => {}})).toBe('function')
|
||||
})
|
||||
|
||||
describe('calling the module function', () => {
|
||||
test('throws if init did not return a function', () => {
|
||||
const moduleFn = defineWorkerModule({
|
||||
init: function() {
|
||||
return 'not a function'
|
||||
}
|
||||
})
|
||||
return expect(moduleFn()).rejects.toThrow()
|
||||
})
|
||||
|
||||
test('does not throw if init returns a function', () => {
|
||||
const moduleFn = defineWorkerModule({
|
||||
init: function() {
|
||||
return function() {}
|
||||
}
|
||||
})
|
||||
return expect(moduleFn()).resolves.toBe(undefined)
|
||||
})
|
||||
|
||||
test('returns a thenable', () => {
|
||||
const moduleFn = defineWorkerModule({
|
||||
init: function() {return function() {}}
|
||||
})
|
||||
expect(moduleFn()).toEqual(expect.objectContaining({then: expect.any(Function)}))
|
||||
})
|
||||
|
||||
test('invokes (a clone of) the init function', function() {
|
||||
const moduleFn = defineWorkerModule({
|
||||
init: function() {
|
||||
_testDataBucket.initWasCalled = true
|
||||
return function() {}
|
||||
}
|
||||
})
|
||||
return moduleFn().then(() => {
|
||||
expect(_testDataBucket.initWasCalled).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
test('invokes a function returned by the init function and resolves the thenable with its return value', () => {
|
||||
const moduleFn = defineWorkerModule({
|
||||
init: function() {
|
||||
return function() {
|
||||
_testDataBucket.initReturnFnWasCalled = true
|
||||
return 'this is the return value'
|
||||
}
|
||||
}
|
||||
})
|
||||
return moduleFn().then((result) => {
|
||||
expect(_testDataBucket.initReturnFnWasCalled).toBe(true)
|
||||
expect(result).toEqual('this is the return value')
|
||||
})
|
||||
})
|
||||
|
||||
test('waits for a thenable returned by the init function', () => {
|
||||
const moduleFn = defineWorkerModule({
|
||||
init: function() {
|
||||
return function() {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve('promise resolution value')
|
||||
}, 50)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
return moduleFn().then((result) => {
|
||||
expect(result).toEqual('promise resolution value')
|
||||
})
|
||||
})
|
||||
|
||||
test('throws if the init function throws', () => {
|
||||
const moduleFn = defineWorkerModule({
|
||||
init: function() {
|
||||
const err = new Error('blerp')
|
||||
err.noLog = true
|
||||
throw err
|
||||
}
|
||||
})
|
||||
return expect(moduleFn()).rejects.toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
describe('dependencies', () => {
|
||||
test('are passed to the init function', () => {
|
||||
const moduleFn = defineWorkerModule({
|
||||
dependencies: [
|
||||
1,
|
||||
true,
|
||||
'three'
|
||||
],
|
||||
init: function(one, two, three) {
|
||||
expect(one).toBe(1)
|
||||
expect(two).toBe(true)
|
||||
expect(three).toBe('three')
|
||||
return function() {}
|
||||
}
|
||||
})
|
||||
return moduleFn()
|
||||
})
|
||||
|
||||
test('can be functions', () => {
|
||||
const moduleFn = defineWorkerModule({
|
||||
dependencies: [
|
||||
function() {
|
||||
_testDataBucket.firstDepFnCalled = true
|
||||
},
|
||||
function(arg1, arg2) {
|
||||
_testDataBucket.secondDepFnArgs = [arg1, arg2]
|
||||
}
|
||||
],
|
||||
init: function(dep1, dep2) {
|
||||
expect(typeof dep1).toBe('function')
|
||||
expect(typeof dep2).toBe('function')
|
||||
dep1()
|
||||
dep2('hey', 'jude')
|
||||
return function() {}
|
||||
}
|
||||
})
|
||||
return moduleFn().then(() => {
|
||||
expect(_testDataBucket.firstDepFnCalled).toBe(true)
|
||||
expect(_testDataBucket.secondDepFnArgs).toEqual(['hey', 'jude'])
|
||||
})
|
||||
})
|
||||
|
||||
test('can be other worker modules', () => {
|
||||
const depMod1 = defineWorkerModule({
|
||||
dependencies: [123, 456],
|
||||
init: function(n1, n2) {
|
||||
return n1 + n2
|
||||
}
|
||||
})
|
||||
const depMod2 = defineWorkerModule({
|
||||
dependencies: ['abc', 'xyz'],
|
||||
init: function(s1, s2) {
|
||||
return s2 + s1
|
||||
}
|
||||
})
|
||||
const depMod3 = defineWorkerModule({
|
||||
dependencies: [depMod1, depMod2],
|
||||
init: function(d1, d2) {
|
||||
return d1 + d2
|
||||
}
|
||||
})
|
||||
|
||||
const moduleFn = defineWorkerModule({
|
||||
dependencies: [
|
||||
depMod1,
|
||||
depMod2,
|
||||
depMod3
|
||||
],
|
||||
init: function(dep1, dep2, dep3) {
|
||||
expect(dep1).toEqual(579)
|
||||
expect(dep2).toEqual('xyzabc')
|
||||
expect(dep3).toEqual('579xyzabc')
|
||||
return function() {
|
||||
return [dep1, dep2, dep3]
|
||||
}
|
||||
}
|
||||
})
|
||||
return moduleFn().then((result) => {
|
||||
expect(result).toEqual([579, 'xyzabc', '579xyzabc'])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
describe('getTransferables', () => {
|
||||
test('is called before result is sent to main thread', () => {
|
||||
_testDataBucket.callOrder = []
|
||||
|
||||
const moduleFn = defineWorkerModule({
|
||||
init: function() {
|
||||
_testDataBucket.callOrder.push('init')
|
||||
return function() {
|
||||
_testDataBucket.callOrder.push('module')
|
||||
return {data: new Uint8Array(32)}
|
||||
}
|
||||
},
|
||||
getTransferables: function (result) {
|
||||
_testDataBucket.callOrder.push('getTransferables')
|
||||
return result.data
|
||||
}
|
||||
})
|
||||
|
||||
return moduleFn().then(() => {
|
||||
_testDataBucket.callOrder.push('response')
|
||||
}).then(() => {
|
||||
expect(_testDataBucket.callOrder).toEqual(['init', 'module', 'getTransferables', 'response'])
|
||||
})
|
||||
})
|
||||
|
||||
test('is called with the module return value as param', () => {
|
||||
const moduleFn = defineWorkerModule({
|
||||
init: function() {
|
||||
return function() {
|
||||
return {data: new Uint8Array(32)}
|
||||
}
|
||||
},
|
||||
getTransferables: function (result) {
|
||||
_testDataBucket.getTransferablesParam = result
|
||||
return result.data
|
||||
}
|
||||
})
|
||||
|
||||
return moduleFn().then(() => {
|
||||
expect(_testDataBucket.getTransferablesParam).toEqual({data: expect.any(Uint8Array)})
|
||||
})
|
||||
})
|
||||
|
||||
// TODO not working yet, see comment below
|
||||
test.skip('its return value is used as the `transferables` in postMessage', () => {
|
||||
const moduleFn = defineWorkerModule({
|
||||
init: function() {
|
||||
// Hack: override the worker's postMessage with one we can spy on
|
||||
// TODO this doesn't work, because `init` is invoked in the worker's global scope but
|
||||
// the WorkerModule framework code (that calls postMessage) is wrapped in a narrower
|
||||
// scope by the jsdom-worker polyfill. Will need to find a way around that before this
|
||||
// test can work.
|
||||
const _origPostMessage = postMessage
|
||||
// eslint-disable-next-line no-global-assign
|
||||
postMessage = function(e, transferList) {
|
||||
_testDataBucket.postMessageTransferList = transferList
|
||||
_origPostMessage(e)
|
||||
}
|
||||
|
||||
return function() {
|
||||
return {data: new Uint8Array(32)}
|
||||
}
|
||||
},
|
||||
getTransferables: function (result) {
|
||||
return [result.data]
|
||||
}
|
||||
})
|
||||
|
||||
return moduleFn().then(() => {
|
||||
expect(_testDataBucket.postMessageTransferList).toEqual([expect.any(Uint8Array)])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('multiple workers', () => {
|
||||
test('different `workerId`s execute in different workers', () => {
|
||||
|
||||
const commonDep = defineWorkerModule({
|
||||
init: function() {
|
||||
return Math.random() //give a unique value per worker
|
||||
}
|
||||
})
|
||||
|
||||
// 1a and 1b share a worker so their dependency should init with the same value;
|
||||
// 2 is a different worker so the dependency should init with a different value.
|
||||
const module1a = defineWorkerModule({
|
||||
workerId: 'worker1',
|
||||
dependencies: [commonDep],
|
||||
init: function(depVal) {
|
||||
return () => depVal
|
||||
}
|
||||
})
|
||||
const module1b = defineWorkerModule({
|
||||
workerId: 'worker1',
|
||||
dependencies: [commonDep],
|
||||
init: function(depVal) {
|
||||
return () => depVal
|
||||
}
|
||||
})
|
||||
const module2 = defineWorkerModule({
|
||||
workerId: 'worker2',
|
||||
dependencies: [commonDep],
|
||||
init: function(depVal) {
|
||||
return () => depVal
|
||||
}
|
||||
})
|
||||
const module3 = defineWorkerModule({
|
||||
workerId: null, //default worker
|
||||
dependencies: [commonDep],
|
||||
init: function(depVal) {
|
||||
return () => depVal
|
||||
}
|
||||
})
|
||||
|
||||
return Promise.all([module1a(), module1b(), module2(), module3()]).then(([result1a, result1b, result2, result3]) => {
|
||||
expect(typeof result1a).toEqual('number')
|
||||
expect(typeof result1b).toEqual('number')
|
||||
expect(typeof result2).toEqual('number')
|
||||
expect(typeof result3).toEqual('number')
|
||||
expect(result1a).toEqual(result1b)
|
||||
expect(result2).not.toEqual(result1a)
|
||||
expect(result3).not.toEqual(result1a)
|
||||
expect(result3).not.toEqual(result2)
|
||||
})
|
||||
})
|
||||
})
|
||||
135
node_modules/troika-worker-utils/__tests__/_jsdom-worker.js
generated
vendored
Normal file
135
node_modules/troika-worker-utils/__tests__/_jsdom-worker.js
generated
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
/*global global*/
|
||||
|
||||
/**
|
||||
* This is a forked version of the MIT-licensed jsdom-worker polyfill, taken from
|
||||
* https://github.com/developit/jsdom-worker/commit/4a400f72ec901fc926f0301ab4680f9f2c318439
|
||||
*
|
||||
* We keep a local forked copy because:
|
||||
* 1) That repo appears to be unmaintained and some of its commits have not made it to npm
|
||||
* 2) We need to patch in some extra support for things like importScripts.
|
||||
*
|
||||
* All edits are marked with [TROIKA EDIT] below.
|
||||
*/
|
||||
|
||||
import mitt from 'mitt';
|
||||
import uuid from 'uuid-v4';
|
||||
import fetch, { Response } from 'node-fetch';
|
||||
|
||||
if (!global.URL) global.URL = {};
|
||||
if (!global.URL.$$objects) {
|
||||
global.URL.$$objects = new Map();
|
||||
global.URL.createObjectURL = blob => {
|
||||
let id = uuid();
|
||||
global.URL.$$objects[id] = blob;
|
||||
return `blob:http://localhost/${id}`;
|
||||
};
|
||||
|
||||
// [TROIKA EDIT]: add revokeObjectURL as no-op:
|
||||
global.URL.revokeObjectURL = function() {}
|
||||
}
|
||||
|
||||
// [TROIKA EDIT]: `fetch` is overwritten with jsdom's version on each suite run, but
|
||||
// URL.$$objects hangs around between runs, so it wouldn't be re-patched. We move it
|
||||
// to its own conditional.
|
||||
if (!global.fetch || !global.fetch._patched) {
|
||||
let oldFetch = global.fetch || fetch;
|
||||
global.fetch = function(url, opts) {
|
||||
if (url.match(/^blob:/)) {
|
||||
return new Promise( (resolve, reject) => {
|
||||
let fr = new FileReader();
|
||||
fr.onload = () => {
|
||||
let Res = global.Response || Response;
|
||||
resolve(new Res(fr.result, { status: 200, statusText: 'OK' }));
|
||||
};
|
||||
fr.onerror = () => {
|
||||
reject(fr.error);
|
||||
};
|
||||
let id = url.match(/[^/]+$/)[0];
|
||||
fr.readAsText(global.URL.$$objects[id]);
|
||||
});
|
||||
}
|
||||
return oldFetch.call(this, url, opts);
|
||||
};
|
||||
global.fetch._patched = true
|
||||
}
|
||||
|
||||
// [TROIKA EDIT]: To enable a synchronous Blob read without jsdom support for FileReaderSync,
|
||||
// we override the Blob constructor to save a reference to its original text input.
|
||||
const blobContents = new WeakMap()
|
||||
const _Blob = global.Blob;
|
||||
global.Blob = function(...args) {
|
||||
let b = new _Blob(...args)
|
||||
blobContents.set(b, args[0])
|
||||
return b
|
||||
}
|
||||
|
||||
|
||||
if (!global.document) {
|
||||
global.document = {};
|
||||
}
|
||||
|
||||
function Event(type) { this.type = type; }
|
||||
Event.prototype.initEvent = Object;
|
||||
if (!global.document.createEvent) {
|
||||
global.document.createEvent = function(type) {
|
||||
let Ctor = global[type] || Event;
|
||||
return new Ctor(type);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
global.Worker = function Worker(url) {
|
||||
let messageQueue = [],
|
||||
inside = mitt(),
|
||||
outside = mitt(),
|
||||
scope = {
|
||||
onmessage: null,
|
||||
dispatchEvent: inside.emit,
|
||||
addEventListener: inside.on,
|
||||
removeEventListener: inside.off,
|
||||
postMessage(data) {
|
||||
outside.emit('message', { data });
|
||||
},
|
||||
fetch: global.fetch,
|
||||
importScripts(...urls) {
|
||||
// [TROIKA EDIT]: Implement importScripts, for text-based blob urls only for now:
|
||||
urls.forEach(url => {
|
||||
if (url.match(/^blob:/)) {
|
||||
let id = url.match(/[^/]+$/)[0];
|
||||
let code = blobContents.get(global.URL.$$objects[id]).join('');
|
||||
let vars = 'var self=this,global=self';
|
||||
for (let k in scope) vars += `,${k}=self.${k}`;
|
||||
new Function(vars+';\n'+code).call(scope);
|
||||
} else {
|
||||
throw Error('importScripts only supports blob urls for now.')
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
getScopeVar;
|
||||
inside.on('message', e => { let f = getScopeVar('onmessage'); if (f) f.call(scope, e); });
|
||||
this.addEventListener = outside.on;
|
||||
this.removeEventListener = outside.off;
|
||||
this.dispatchEvent = outside.emit;
|
||||
outside.on('message', e => { this.onmessage && this.onmessage(e); });
|
||||
this.postMessage = data => {
|
||||
if (messageQueue!=null) messageQueue.push(data);
|
||||
else inside.emit('message', { data });
|
||||
};
|
||||
this.terminate = () => {
|
||||
// [TROIKA EDIT]: don't throw.
|
||||
// throw Error('Not Supported');
|
||||
};
|
||||
global.fetch(url)
|
||||
.then( r => r.text() )
|
||||
.then( code => {
|
||||
let vars = 'var self=this,global=self';
|
||||
for (let k in scope) vars += `,${k}=self.${k}`;
|
||||
getScopeVar = eval('(function() {'+vars+';\n'+code+'\nreturn function(__){return eval(__)}})').call(scope);
|
||||
let q = messageQueue;
|
||||
messageQueue = null;
|
||||
q.forEach(this.postMessage);
|
||||
})
|
||||
.catch( e => { outside.emit('error', e); console.error(e); });
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user