Spaces:
Running
Running
const tap = require('tap'); | |
const path = require('path'); | |
const readFileToBuffer = require('../fixtures/readProjectFile').readFileToBuffer; | |
const VirtualMachine = require('../../src/virtual-machine'); | |
const Runtime = require('../../src/engine/runtime'); | |
const MonitorRecord = require('../../src/engine/monitor-record'); | |
const {Map} = require('immutable'); | |
tap.tearDown(() => process.nextTick(process.exit)); | |
const test = tap.test; | |
test('spec', t => { | |
const r = new Runtime(); | |
t.type(Runtime, 'function'); | |
t.type(r, 'object'); | |
// Test types of cloud data managing functions | |
t.type(r.hasCloudData, 'function'); | |
t.type(r.canAddCloudVariable, 'function'); | |
t.type(r.addCloudVariable, 'function'); | |
t.type(r.removeCloudVariable, 'function'); | |
t.ok(r instanceof Runtime); | |
t.end(); | |
}); | |
test('monitorStateEquals', t => { | |
const r = new Runtime(); | |
const id = 'xklj4#!'; | |
const prevMonitorState = MonitorRecord({ | |
id, | |
opcode: 'turtle whereabouts', | |
value: '25' | |
}); | |
const newMonitorDelta = Map({ | |
id, | |
value: String(25) | |
}); | |
r.requestAddMonitor(prevMonitorState); | |
r.requestUpdateMonitor(newMonitorDelta); | |
t.equals(true, prevMonitorState === r._monitorState.get(id)); | |
t.equals(String(25), r._monitorState.get(id).get('value')); | |
t.end(); | |
}); | |
test('monitorStateDoesNotEqual', t => { | |
const r = new Runtime(); | |
const id = 'xklj4#!'; | |
const params = {seven: 7}; | |
const prevMonitorState = MonitorRecord({ | |
id, | |
opcode: 'turtle whereabouts', | |
value: '25' | |
}); | |
// Value change | |
let newMonitorDelta = Map({ | |
id, | |
value: String(24) | |
}); | |
r.requestAddMonitor(prevMonitorState); | |
r.requestUpdateMonitor(newMonitorDelta); | |
t.equals(false, prevMonitorState.equals(r._monitorState.get(id))); | |
t.equals(String(24), r._monitorState.get(id).get('value')); | |
// Prop change | |
newMonitorDelta = Map({ | |
id: 'xklj4#!', | |
params: params | |
}); | |
r.requestUpdateMonitor(newMonitorDelta); | |
t.equals(false, prevMonitorState.equals(r._monitorState.get(id))); | |
t.equals(String(24), r._monitorState.get(id).value); | |
t.equals(params, r._monitorState.get(id).params); | |
t.end(); | |
}); | |
test('getLabelForOpcode', t => { | |
const r = new Runtime(); | |
const fakeExtension = { | |
id: 'fakeExtension', | |
name: 'Fake Extension', | |
blocks: [ | |
{ | |
info: { | |
opcode: 'foo', | |
json: {}, | |
text: 'Foo', | |
xml: '' | |
} | |
}, | |
{ | |
info: { | |
opcode: 'foo_2', | |
json: {}, | |
text: 'Foo 2', | |
xml: '' | |
} | |
} | |
] | |
}; | |
r._blockInfo.push(fakeExtension); | |
const result1 = r.getLabelForOpcode('fakeExtension_foo'); | |
t.type(result1.category, 'string'); | |
t.type(result1.label, 'string'); | |
t.equals(result1.label, 'Fake Extension: Foo'); | |
const result2 = r.getLabelForOpcode('fakeExtension_foo_2'); | |
t.type(result2.category, 'string'); | |
t.type(result2.label, 'string'); | |
t.equals(result2.label, 'Fake Extension: Foo 2'); | |
t.end(); | |
}); | |
test('Project loaded emits runtime event', t => { | |
const vm = new VirtualMachine(); | |
const projectUri = path.resolve(__dirname, '../fixtures/default.sb2'); | |
const project = readFileToBuffer(projectUri); | |
let projectLoaded = false; | |
vm.runtime.addListener('PROJECT_LOADED', () => { | |
projectLoaded = true; | |
}); | |
vm.loadProject(project).then(() => { | |
t.equal(projectLoaded, true, 'Project load event emitted'); | |
t.end(); | |
}); | |
}); | |
test('Cloud variable limit allows only 10 cloud variables', t => { | |
// This is a test of just the cloud variable limit mechanism | |
// The functions being tested below need to be used when | |
// creating and deleting cloud variables in the runtime. | |
const rt = new Runtime(); | |
t.equal(rt.hasCloudData(), false); | |
for (let i = 0; i < 10; i++) { | |
t.equal(rt.canAddCloudVariable(), true); | |
rt.addCloudVariable(); | |
// Adding a cloud variable should change the | |
// result of the hasCloudData check | |
t.equal(rt.hasCloudData(), true); | |
} | |
// We should be at the cloud variable limit now | |
t.equal(rt.canAddCloudVariable(), false); | |
// Removing a cloud variable should allow the addition of exactly one more | |
// when we are at the cloud variable limit | |
rt.removeCloudVariable(); | |
t.equal(rt.canAddCloudVariable(), true); | |
rt.addCloudVariable(); | |
t.equal(rt.canAddCloudVariable(), false); | |
// Disposing of the runtime should reset the cloud variable limitations | |
rt.dispose(); | |
t.equal(rt.hasCloudData(), false); | |
for (let i = 0; i < 10; i++) { | |
t.equal(rt.canAddCloudVariable(), true); | |
rt.addCloudVariable(); | |
t.equal(rt.hasCloudData(), true); | |
} | |
// We should be at the cloud variable limit now | |
t.equal(rt.canAddCloudVariable(), false); | |
t.end(); | |
}); | |
test('Starting the runtime emits an event', t => { | |
let started = false; | |
const rt = new Runtime(); | |
rt.addListener('RUNTIME_STARTED', () => { | |
started = true; | |
}); | |
rt.start(); | |
t.equal(started, true); | |
t.end(); | |
}); | |
test('Runtime cannot be started while already running', t => { | |
const rt = new Runtime(); | |
rt.start(); // Start the first time | |
// Set up a flag/listener to check if it can be started again | |
let started = false; | |
rt.addListener('RUNTIME_STARTED', () => { | |
started = true; | |
}); | |
// Starting again should not emit another event | |
rt.start(); | |
t.equal(started, false); | |
t.end(); | |
}); | |
test('setCompatibilityMode restarts if it was already running', t => { | |
const rt = new Runtime(); | |
rt.start(); // Start the first time | |
// Set up a flag/listener to check if it gets started again | |
let started = false; | |
rt.addListener('RUNTIME_STARTED', () => { | |
started = true; | |
}); | |
rt.setCompatibilityMode(true); | |
// TW: We make an intentional API change here. Changing compatibility mode won't emit a RUNTIME_STARTED | |
// if the runtime is already running. | |
t.equal(started, false); | |
t.end(); | |
}); | |
test('setCompatibilityMode does not restart if it was not running', t => { | |
const rt = new Runtime(); | |
let started = false; | |
rt.addListener('RUNTIME_STARTED', () => { | |
started = true; | |
}); | |
rt.setCompatibilityMode(true); | |
t.equal(started, false); | |
t.end(); | |
}); | |
test('Disposing the runtime emits an event', t => { | |
let disposed = false; | |
const rt = new Runtime(); | |
rt.addListener('RUNTIME_DISPOSED', () => { | |
disposed = true; | |
}); | |
rt.dispose(); | |
t.equal(disposed, true); | |
t.end(); | |
}); | |
test('Clock is reset on runtime dispose', t => { | |
const rt = new Runtime(); | |
const c = rt.ioDevices.clock; | |
let simulatedTime = 0; | |
c._projectTimer = { | |
timeElapsed: () => simulatedTime, | |
start: () => { | |
simulatedTime = 0; | |
} | |
}; | |
t.ok(c.projectTimer() === 0); | |
simulatedTime += 1000; | |
t.ok(c.projectTimer() === 1); | |
rt.dispose(); | |
// When the runtime is disposed, the clock should be reset | |
t.ok(c.projectTimer() === 0); | |
t.end(); | |
}); | |