spiegel-keyman/common/predictive-text/unit_tests/headless/worker-initialization.js
2018-11-29 23:35:37 +07:00

186 lines
6 KiB
JavaScript

var assert = require('chai').assert;
var sinon = require('sinon');
let LMLayerWorker = require('../../build/intermediate');
// Unit tests for instantiating and initializing the LMLayer Worker in isolation.
//
// Although the LMLayerWorker expected to be used inside a DedicatedWorkerGlobalScope,
// these unit tests DO NOT run inside a Worker, and instead use Sinon fakes to assert
// behavior.
describe('LMLayerWorker', function() {
describe('#constructor()', function() {
it('should allow for the mocking of postMessage()', function () {
var fakePostMessage = sinon.fake();
var worker = new LMLayerWorker({ postMessage: fakePostMessage });
// Sending it the initialize it should notify us that it's initialized!
worker.onMessage(createMessageEventWithData({
message: 'initialize',
model: dummyModelCode()
}));
assert(fakePostMessage.calledOnce);
});
});
describe('#onMessage()', function() {
it('should fail if not given the `message` attribute', function () {
var worker = new LMLayerWorker({
postMessage: sinon.fake(), // required, but ignored in this test case
});
// Every message is a discriminated union with the tag being `message`.
// If it doesn't see 'message', something is deeply wrong,
// and it should loudly let us know.
assert.throws(function () {
worker.onMessage(createMessageEventWithData({
model: 'dummy attribute'
}));
});
});
});
describe('.install()', function () {
it('should create a new instance, installed on our global object', function () {
var fakeWorkerGlobal = {
onmessage: undefined,
postMessage: new sinon.fake(),
};
// Instantiate and install a worker on our global object.
var worker = LMLayerWorker.install(fakeWorkerGlobal);
assert.instanceOf(worker, LMLayerWorker);
// It should have installed a callback.
assert.isFunction(fakeWorkerGlobal.onmessage);
// Send a message; we should get something back.
worker.onMessage(createMessageEventWithData({
message: 'initialize',
model: dummyModelCode()
}));
// It called the postMessage() in its global scope.
assert.isTrue(fakeWorkerGlobal.postMessage.calledOnce)
});
});
describe('Message: initialize', function () {
it('should disallow any other message', function () {
var worker = new LMLayerWorker({
postMessage: sinon.fake(), // required, but ignored
});
// It should not respond to 'predict'
assert.throws(function () {
worker.onMessage(createMessageEventWithData({
message: 'predict',
}));
}, /invalid message/i);
});
it('should send back a "ready" message', function () {
var fakePostMessage = sinon.fake();
var worker = new LMLayerWorker({ postMessage: fakePostMessage });
worker.onMessage(createMessageEventWithData({
message: 'initialize',
model: dummyModelCode()
}));
assert(fakePostMessage.calledOnceWith(sinon.match({
message: 'ready'
})));
});
it('should send back configuration', function () {
var fakePostMessage = sinon.fake();
var worker = new LMLayerWorker({ postMessage: fakePostMessage });
var maxCodeUnits = 64;
worker.onMessage(createMessageEventWithData({
message: 'initialize',
model: dummyModelCode(),
configuration: {
maxLeftContextCodeUnits: maxCodeUnits,
}
}));
assert(fakePostMessage.calledOnceWith(sinon.match({
message: 'ready',
configuration: {
leftContextCodeUnits: maxCodeUnits,
rightContextCodeUnits: 0,
}
})), lastMessageAsString(fakePostMessage));
});
it('should run the code for the model', function () {
var fakePostMessage;
var worker = new LMLayerWorker({
postMessage: fakePostMessage = sinon.fake(), // required, but ignored in this test case
});
// Create some values that the function can't fake:
var maxCodeUnit = 64;
var fakeLeft = Math.round(Math.random() * maxCodeUnit);
var fakeRight = Math.round(Math.random() * maxCodeUnit);
worker.onMessage(createMessageEventWithData({
message: 'initialize',
configuration: {
supportsRightContexts: true,
maxRightContextCodeUnits: maxCodeUnit
},
// We can only send source code through the Structure Clones algorithm,
// (we can't send mocks :c) so, send something that cannot
// be easily faked, and we can determine if it's correct.
model: `
return {
model: {},
configuration: {
leftContextCodeUnits: ${fakeLeft},
rightContextCodeUnits: ${fakeRight}
}
};
`
}));
assert(fakePostMessage.calledOnceWith(sinon.match({
message: 'ready',
configuration: {
leftContextCodeUnits: fakeLeft,
rightContextCodeUnits: fakeRight,
}
})), lastMessageAsString(fakePostMessage));
});
});
// TODO: move these tests to a different file.
describe('Message: predict', function () {
it.skip('should predict from a local model', function () {
// will need import scripts figured out
});
});
/**
* Creates a MessageEvent (for inter-worker communication), with the given data payload.
* @param {*} data
*/
function createMessageEventWithData(data) {
return { data };
}
/**
* Deprecation warning: Soon, models will not be requierd to be passed as source code;
* when this happens, tests should refrain from sending source code for the model
* parameter.
*/
function dummyModelCode() {
return 'return {model: {}, configuration: {}}';
}
/**
* Returns the last message received in a pretty string format.
* @param {sinon.SinonFake} fakePostMessage
* @returns {string}
*/
function lastMessageAsString(fakePostMessage) {
return JSON.stringify(fakePostMessage.lastCall.args[0], null, 2);
}
});