mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-23 14:32:59 -05:00
Merge branch 'develop' into immutableState
This commit is contained in:
commit
809760b954
7 changed files with 390 additions and 34 deletions
|
@ -34,6 +34,7 @@
|
||||||
"eslint-config-scratch": "^3.1.0",
|
"eslint-config-scratch": "^3.1.0",
|
||||||
"expose-loader": "0.7.3",
|
"expose-loader": "0.7.3",
|
||||||
"gh-pages": "^0.12.0",
|
"gh-pages": "^0.12.0",
|
||||||
|
"got": "5.7.1",
|
||||||
"highlightjs": "^9.8.0",
|
"highlightjs": "^9.8.0",
|
||||||
"htmlparser2": "3.9.2",
|
"htmlparser2": "3.9.2",
|
||||||
"immutable": "3.8.1",
|
"immutable": "3.8.1",
|
||||||
|
@ -46,6 +47,7 @@
|
||||||
"scratch-render": "^0.1.0-prerelease.0",
|
"scratch-render": "^0.1.0-prerelease.0",
|
||||||
"scratch-storage": "^0.1.0",
|
"scratch-storage": "^0.1.0",
|
||||||
"script-loader": "0.7.0",
|
"script-loader": "0.7.0",
|
||||||
|
"socket.io-client": "1.7.3",
|
||||||
"stats.js": "^0.17.0",
|
"stats.js": "^0.17.0",
|
||||||
"tap": "^10.2.0",
|
"tap": "^10.2.0",
|
||||||
"travis-after-all": "^1.4.4",
|
"travis-after-all": "^1.4.4",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const adapter = require('./adapter');
|
const adapter = require('./adapter');
|
||||||
const mutationAdapter = require('./mutation-adapter');
|
const mutationAdapter = require('./mutation-adapter');
|
||||||
const xmlEscape = require('../util/xml-escape');
|
const xmlEscape = require('../util/xml-escape');
|
||||||
const MonitorRecord = require('./records');
|
const MonitorRecord = require('./monitor-record');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @fileoverview
|
* @fileoverview
|
||||||
|
@ -279,7 +279,7 @@ class Blocks {
|
||||||
const block = this._blocks[args.id];
|
const block = this._blocks[args.id];
|
||||||
if (typeof block === 'undefined') return;
|
if (typeof block === 'undefined') return;
|
||||||
|
|
||||||
let wasMonitored = block.isMonitored;
|
const wasMonitored = block.isMonitored;
|
||||||
switch (args.element) {
|
switch (args.element) {
|
||||||
case 'field':
|
case 'field':
|
||||||
// Update block value
|
// Update block value
|
||||||
|
@ -297,15 +297,10 @@ class Blocks {
|
||||||
optRuntime.requestAddMonitor(MonitorRecord({
|
optRuntime.requestAddMonitor(MonitorRecord({
|
||||||
// @todo(vm#564) this will collide if multiple sprites use same block
|
// @todo(vm#564) this will collide if multiple sprites use same block
|
||||||
id: block.id,
|
id: block.id,
|
||||||
category: 'data',
|
opcode: block.opcode,
|
||||||
// @todo(vm#565) how to handle translation here?
|
params: this._getBlockParams(block),
|
||||||
label: block.opcode,
|
|
||||||
// @todo(vm#565) for numerical values with decimals, some countries use comma
|
// @todo(vm#565) for numerical values with decimals, some countries use comma
|
||||||
value: '', // Ensure that value is not undefined, since React requires it
|
value: ''
|
||||||
x: 0,
|
|
||||||
// @todo(vm#566) Don't require sending x and y when instantiating a
|
|
||||||
// monitor. If it's not preset the GUI should decide.
|
|
||||||
y: 0
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -512,6 +507,24 @@ class Blocks {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------
|
// ---------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* Helper to serialize block fields and input fields for reporting new monitors
|
||||||
|
* @param {!object} block Block to be paramified.
|
||||||
|
* @return {!object} object of param key/values.
|
||||||
|
*/
|
||||||
|
_getBlockParams (block) {
|
||||||
|
const params = {};
|
||||||
|
for (const key in block.fields) {
|
||||||
|
params[key] = block.fields[key].value;
|
||||||
|
}
|
||||||
|
for (const inputKey in block.inputs) {
|
||||||
|
const inputBlock = this._blocks[block.inputs[inputKey].block];
|
||||||
|
for (const key in inputBlock.fields) {
|
||||||
|
params[key] = inputBlock.fields[key].value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to add a stack to `this._scripts`.
|
* Helper to add a stack to `this._scripts`.
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
const {Record} = require('immutable');
|
|
||||||
|
|
||||||
const MonitorRecord = Record({
|
|
||||||
id: null,
|
|
||||||
category: 'data',
|
|
||||||
label: null,
|
|
||||||
value: null,
|
|
||||||
x: null,
|
|
||||||
y: null
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = MonitorRecord;
|
|
|
@ -6,6 +6,7 @@ const {OrderedMap} = require('immutable');
|
||||||
|
|
||||||
// Virtual I/O devices.
|
// Virtual I/O devices.
|
||||||
const Clock = require('../io/clock');
|
const Clock = require('../io/clock');
|
||||||
|
const DeviceManager = require('../io/deviceManager');
|
||||||
const Keyboard = require('../io/keyboard');
|
const Keyboard = require('../io/keyboard');
|
||||||
const Mouse = require('../io/mouse');
|
const Mouse = require('../io/mouse');
|
||||||
|
|
||||||
|
@ -166,6 +167,7 @@ class Runtime extends EventEmitter {
|
||||||
/** @type {Object.<string, Object>} */
|
/** @type {Object.<string, Object>} */
|
||||||
this.ioDevices = {
|
this.ioDevices = {
|
||||||
clock: new Clock(),
|
clock: new Clock(),
|
||||||
|
deviceManager: new DeviceManager(),
|
||||||
keyboard: new Keyboard(this),
|
keyboard: new Keyboard(this),
|
||||||
mouse: new Mouse(this)
|
mouse: new Mouse(this)
|
||||||
};
|
};
|
||||||
|
|
333
src/io/deviceManager.js
Normal file
333
src/io/deviceManager.js
Normal file
|
@ -0,0 +1,333 @@
|
||||||
|
const got = require('got');
|
||||||
|
const io = require('socket.io-client/dist/socket.io');
|
||||||
|
const querystring = require('querystring');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal class used by the Device Manager client to manage making a connection to a particular device.
|
||||||
|
*/
|
||||||
|
class DeviceOpener {
|
||||||
|
/**
|
||||||
|
* @return {number} - The number of milliseconds to allow before deciding a connection attempt has timed out.
|
||||||
|
*/
|
||||||
|
static get CONNECTION_TIMEOUT_MS () {
|
||||||
|
return 10 * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a DeviceOpener to help connect to a particular device.
|
||||||
|
* @param {DeviceManager} deviceManager - the Device Manager client which instigated this action.
|
||||||
|
* @param {function} resolve - callback to be called if the device is successfully found, connected, and opened.
|
||||||
|
* @param {function} reject - callback to be called if an error or timeout is encountered.
|
||||||
|
*/
|
||||||
|
constructor (deviceManager, resolve, reject) {
|
||||||
|
/**
|
||||||
|
* The DeviceManager client which wants to open a device.
|
||||||
|
* @type {DeviceManager}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this._deviceManager = deviceManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback to be called if the device is successfully found, connected, and opened.
|
||||||
|
* @type {Function}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this._resolve = resolve;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback to be called if an error or timeout is encountered.
|
||||||
|
* @type {Function}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this._reject = reject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The socket for the device being opened.
|
||||||
|
* @type {Socket}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this._socket = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this timeout expires before a successful connection, the connection attempt will be canceled.
|
||||||
|
* @type {Object}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this._connectionTimeout = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to open a particular device. This will cause `resolve` or `reject` to be called.
|
||||||
|
* Note that in some cases it's possible that both `resolve` and `reject` will be called. In that event, ignore all
|
||||||
|
* calls after the first. If `resolve` and `reject` are from a Promise, then the Promise will do this for you.
|
||||||
|
* @param {string} extensionName - human-readable name of the extension requesting the device
|
||||||
|
* @param {string} deviceType - the type of device to open, such as 'wedo2'
|
||||||
|
* @param {string} deviceId - the ID of the particular device to open, usually from list results
|
||||||
|
*/
|
||||||
|
open (extensionName, deviceType, deviceId) {
|
||||||
|
this._socket = io(`${this._deviceManager._serverURL}/${deviceType}`);
|
||||||
|
|
||||||
|
this._socket.on('deviceWasOpened', () => this.onDeviceWasOpened());
|
||||||
|
this._socket.on('disconnect', () => this.onDisconnect());
|
||||||
|
this._connectionTimeout = setTimeout(() => this.onTimeout(), DeviceOpener.CONNECTION_TIMEOUT_MS);
|
||||||
|
|
||||||
|
this._socket.emit('open', {deviceId: deviceId, name: extensionName});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* React to a 'deviceWasOpened' message from the Device Manager application.
|
||||||
|
*/
|
||||||
|
onDeviceWasOpened () {
|
||||||
|
this.clearConnectionTimeout();
|
||||||
|
this._resolve(this._socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* React to the socket becoming disconnected.
|
||||||
|
*/
|
||||||
|
onDisconnect () {
|
||||||
|
this.clearConnectionTimeout();
|
||||||
|
this._reject('device disconnected');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* React to the connection timeout expiring. This could mean that the socket itself timed out, or that the Device
|
||||||
|
* Manager took too long to send a 'deviceWasOpened' message back.
|
||||||
|
*/
|
||||||
|
onTimeout () {
|
||||||
|
this.clearConnectionTimeout();
|
||||||
|
|
||||||
|
// `socket.disconnect()` triggers `onDisconnect` only for connected sockets
|
||||||
|
if (this._socket.connected) {
|
||||||
|
this._socket.disconnect();
|
||||||
|
} else {
|
||||||
|
this._reject('connection attempt timed out');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel the connection timeout.
|
||||||
|
*/
|
||||||
|
clearConnectionTimeout () {
|
||||||
|
if (this._connectionTimeout !== null) {
|
||||||
|
clearTimeout(this._connectionTimeout);
|
||||||
|
this._connectionTimeout = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A DeviceFinder implements the Device Manager client's `searchAndConnect` functionality.
|
||||||
|
* Use the `promise` property to access a promise for a device socket.
|
||||||
|
* Call `cancel()` to cancel the search. Once the search finds a device it cannot be canceled.
|
||||||
|
*/
|
||||||
|
class DeviceFinder {
|
||||||
|
/**
|
||||||
|
* @return {number} - the number of milliseconds to wait between search attempts (calls to 'list')
|
||||||
|
*/
|
||||||
|
static get SEARCH_RETRY_MS () {
|
||||||
|
return 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a DeviceFinder to help find and connect to a device satisfying specific conditions.
|
||||||
|
* @param {DeviceManager} deviceManager - the Device Manager client which instigated this action.
|
||||||
|
* @param {string} extensionName - human-readable name of the extension requesting the search
|
||||||
|
* @param {string} deviceType - the type of device to find, such as 'wedo2'.
|
||||||
|
* @param {object} [deviceSpec] - optional additional information about the specific devices to list
|
||||||
|
*/
|
||||||
|
constructor (deviceManager, extensionName, deviceType, deviceSpec) {
|
||||||
|
/**
|
||||||
|
* The Device Manager client which wants to find a device.
|
||||||
|
* @type {DeviceManager}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this._deviceManager = deviceManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The human-readable name of the extension requesting the search.
|
||||||
|
* @type {string}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this._extensionName = extensionName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of device to find, such as 'wedo2'.
|
||||||
|
* @type {string}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this._deviceType = deviceType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional additional information about the specific devices to list.
|
||||||
|
* @type {Object}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this._deviceSpec = deviceSpec;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag indicating that the search should be canceled.
|
||||||
|
* @type {boolean}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this._cancel = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The promise representing this search's results.
|
||||||
|
* @type {Promise}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this._promise = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The fulfillment function for `this._promise`.
|
||||||
|
* @type {Function}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this._fulfill = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {Promise} - A promise for a device socket.
|
||||||
|
*/
|
||||||
|
get promise () {
|
||||||
|
return this._promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start searching for a device.
|
||||||
|
*/
|
||||||
|
start () {
|
||||||
|
this._promise = new Promise((fulfill, reject) => {
|
||||||
|
this._fulfill = fulfill;
|
||||||
|
this._reject = reject;
|
||||||
|
this._getList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel the search for a device. Effective only before the promise resolves.
|
||||||
|
*/
|
||||||
|
cancel () {
|
||||||
|
this._cancel = true;
|
||||||
|
this._reject('canceled');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the list of devices matching the parameters provided in the constructor.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_getList () {
|
||||||
|
this._deviceManager
|
||||||
|
.list(this._extensionName, this._deviceType, this._deviceSpec)
|
||||||
|
.then(
|
||||||
|
listResult => this._listResultHandler(listResult),
|
||||||
|
() => this._listResultHandler(null)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the list of devices returned by the Device Manager.
|
||||||
|
* @param {Array} listResult - an array of device information objects.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_listResultHandler (listResult) {
|
||||||
|
if (this._cancel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listResult && listResult.length > 0) {
|
||||||
|
for (const deviceInfo of listResult) {
|
||||||
|
if (!deviceInfo.connected) {
|
||||||
|
this._fulfill(this._deviceManager.open(this._extensionName, this._deviceType, deviceInfo.id));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => this._getList(), DeviceFinder.SEARCH_RETRY_MS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Scratch 3.0 "I/O Device" representing a client for the Scratch Device Manager.
|
||||||
|
*/
|
||||||
|
class DeviceManager {
|
||||||
|
/**
|
||||||
|
* @return {string} - The default Scratch Device Manager connection URL.
|
||||||
|
*/
|
||||||
|
static get DEFAULT_SERVER_URL () {
|
||||||
|
return 'https://device-manager.scratch.mit.edu:3030';
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor () {
|
||||||
|
/**
|
||||||
|
* The URL this client will use for Device Manager communication both HTTP(S) and WS(S).
|
||||||
|
* @type {string}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this._serverURL = DeviceManager.DEFAULT_SERVER_URL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if there is no known problem connecting to the Scratch Device Manager, false otherwise.
|
||||||
|
* @type {boolean}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this._isConnected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {boolean} - True if there is no known problem connecting to the Scratch Device Manager, false otherwise.
|
||||||
|
*/
|
||||||
|
get isConnected () {
|
||||||
|
return this._isConnected;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* High-level request to find and connect to a device satisfying the specified characteristics.
|
||||||
|
* This function will repeatedly call list() until the list is non-empty, then it will open() the first suitable
|
||||||
|
* item in the list and provide the socket for that device.
|
||||||
|
* @todo Offer a way to filter results. See the Scratch 2.0 PicoBoard extension for details on why that's important.
|
||||||
|
* @param {string} extensionName - human-readable name of the extension requesting the search
|
||||||
|
* @param {string} deviceType - the type of device to list, such as 'wedo2'
|
||||||
|
* @param {object} [deviceSpec] - optional additional information about the specific devices to list
|
||||||
|
* @return {DeviceFinder} - An object providing a Promise for an opened device and a way to cancel the search.
|
||||||
|
*/
|
||||||
|
searchAndConnect (extensionName, deviceType, deviceSpec) {
|
||||||
|
const finder = new DeviceFinder(this, extensionName, deviceType, deviceSpec);
|
||||||
|
finder.start();
|
||||||
|
return finder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request a list of available devices.
|
||||||
|
* @param {string} extensionName - human-readable name of the extension requesting the list
|
||||||
|
* @param {string} deviceType - the type of device to list, such as 'wedo2'
|
||||||
|
* @param {object} [deviceSpec] - optional additional information about the specific devices to list
|
||||||
|
* @return {Promise} - A Promise for an Array of available devices.
|
||||||
|
*/
|
||||||
|
list (extensionName, deviceType, deviceSpec) {
|
||||||
|
const queryObject = {
|
||||||
|
name: extensionName
|
||||||
|
};
|
||||||
|
if (deviceSpec) queryObject.spec = deviceSpec;
|
||||||
|
const url = `${this._serverURL}/${encodeURIComponent(deviceType)}/list?${querystring.stringify(queryObject)}`;
|
||||||
|
return got(url).then(response => JSON.parse(response.body));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to open a particular device.
|
||||||
|
* @param {string} extensionName - human-readable name of the extension requesting the device
|
||||||
|
* @param {string} deviceType - the type of device to open, such as 'wedo2'
|
||||||
|
* @param {string} deviceId - the ID of the particular device to open, usually from list results
|
||||||
|
* @return {Promise} - A Promise for a Socket which can be used to communicate with the device
|
||||||
|
*/
|
||||||
|
open (extensionName, deviceType, deviceId) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const opener = new DeviceOpener(this, resolve, reject);
|
||||||
|
opener.open(extensionName, deviceType, deviceId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = DeviceManager;
|
|
@ -18,11 +18,8 @@ test('monitorStateEquals', t => {
|
||||||
const id = 'xklj4#!';
|
const id = 'xklj4#!';
|
||||||
const prevMonitorState = MonitorRecord({
|
const prevMonitorState = MonitorRecord({
|
||||||
id,
|
id,
|
||||||
category: 'data',
|
opcode: 'turtle whereabouts',
|
||||||
label: 'turtle whereabouts',
|
value: '25'
|
||||||
value: '25',
|
|
||||||
x: 0,
|
|
||||||
y: 0
|
|
||||||
});
|
});
|
||||||
const newMonitorDelta = Map({
|
const newMonitorDelta = Map({
|
||||||
id,
|
id,
|
||||||
|
@ -31,7 +28,7 @@ test('monitorStateEquals', t => {
|
||||||
r.requestAddMonitor(prevMonitorState);
|
r.requestAddMonitor(prevMonitorState);
|
||||||
r.requestUpdateMonitor(newMonitorDelta);
|
r.requestUpdateMonitor(newMonitorDelta);
|
||||||
|
|
||||||
t.equals(true, prevMonitorState.equals(r._monitorState.get(id)));
|
t.equals(true, prevMonitorState === r._monitorState.get(id));
|
||||||
t.equals(String(25), r._monitorState.get(id).get('value'));
|
t.equals(String(25), r._monitorState.get(id).get('value'));
|
||||||
t.end();
|
t.end();
|
||||||
});
|
});
|
||||||
|
@ -39,10 +36,10 @@ test('monitorStateEquals', t => {
|
||||||
test('monitorStateDoesNotEqual', t => {
|
test('monitorStateDoesNotEqual', t => {
|
||||||
const r = new Runtime();
|
const r = new Runtime();
|
||||||
const id = 'xklj4#!';
|
const id = 'xklj4#!';
|
||||||
|
const params = {seven: 7};
|
||||||
const prevMonitorState = MonitorRecord({
|
const prevMonitorState = MonitorRecord({
|
||||||
id,
|
id,
|
||||||
category: 'data',
|
opcode: 'turtle whereabouts',
|
||||||
label: 'turtle whereabouts',
|
|
||||||
value: '25'
|
value: '25'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -60,13 +57,13 @@ test('monitorStateDoesNotEqual', t => {
|
||||||
// Prop change
|
// Prop change
|
||||||
newMonitorDelta = Map({
|
newMonitorDelta = Map({
|
||||||
id: 'xklj4#!',
|
id: 'xklj4#!',
|
||||||
x: 7
|
params: params
|
||||||
});
|
});
|
||||||
r.requestUpdateMonitor(newMonitorDelta);
|
r.requestUpdateMonitor(newMonitorDelta);
|
||||||
|
|
||||||
t.equals(false, prevMonitorState.equals(r._monitorState.get(id)));
|
t.equals(false, prevMonitorState.equals(r._monitorState.get(id)));
|
||||||
t.equals(String(24), r._monitorState.get(id).get('value'));
|
t.equals(String(24), r._monitorState.get(id).value);
|
||||||
t.equals(7, r._monitorState.get(id).get('x'));
|
t.equals(params, r._monitorState.get(id).params);
|
||||||
|
|
||||||
t.end();
|
t.end();
|
||||||
});
|
});
|
||||||
|
|
21
test/unit/io_deviceManager.js
Normal file
21
test/unit/io_deviceManager.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
const test = require('tap').test;
|
||||||
|
const DeviceManager = require('../../src/io/deviceManager');
|
||||||
|
|
||||||
|
test('spec', t => {
|
||||||
|
const deviceManager = new DeviceManager();
|
||||||
|
|
||||||
|
t.type(DeviceManager, 'function');
|
||||||
|
t.type(deviceManager, 'object');
|
||||||
|
t.type(deviceManager.list, 'function');
|
||||||
|
t.type(deviceManager.open, 'function');
|
||||||
|
t.type(deviceManager.searchAndConnect, 'function');
|
||||||
|
t.type(deviceManager.isConnected, 'boolean');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('default connected', t => {
|
||||||
|
const deviceManager = new DeviceManager();
|
||||||
|
|
||||||
|
t.strictEqual(deviceManager.isConnected, true);
|
||||||
|
t.end();
|
||||||
|
});
|
Loading…
Reference in a new issue