mirror of
https://github.com/google/blockly.git
synced 2026-02-01 13:10:22 +01:00
301 lines
10 KiB
JavaScript
301 lines
10 KiB
JavaScript
/**
|
|
* @license
|
|
* Copyright 2019 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* Check if a variable with the given values exists.
|
|
* @param {Blockly.Workspace|Blockly.VariableMap} container The workspace or
|
|
* variableMap the checked variable belongs to.
|
|
* @param {!string} name The expected name of the variable.
|
|
* @param {!string} type The expected type of the variable.
|
|
* @param {!string} id The expected id of the variable.
|
|
*/
|
|
function assertVariableValues(container, name, type, id) {
|
|
var variable = container.getVariableById(id);
|
|
chai.assert.isDefined(variable);
|
|
chai.assert.equal(variable.name, name);
|
|
chai.assert.equal(variable.type, type);
|
|
chai.assert.equal(variable.getId(), id);
|
|
}
|
|
|
|
/**
|
|
* Captures the strings sent to console.warn() when calling a function.
|
|
* @param {function} innerFunc The function where warnings may called.
|
|
* @return {string[]} The warning messages (only the first arguments).
|
|
*/
|
|
function captureWarnings(innerFunc) {
|
|
var msgs = [];
|
|
var nativeConsoleWarn = console.warn;
|
|
try {
|
|
console.warn = function(msg) {
|
|
msgs.push(msg);
|
|
};
|
|
innerFunc();
|
|
} finally {
|
|
console.warn = nativeConsoleWarn;
|
|
}
|
|
return msgs;
|
|
}
|
|
|
|
/**
|
|
* Safely disposes of Blockly workspace, logging any errors.
|
|
* Assumes that sharedTestSetup has also been called. This should be called
|
|
* using workspaceTeardown.call(this).
|
|
* @param {!Blockly.Workspace} workspace The workspace to dispose.
|
|
*/
|
|
function workspaceTeardown(workspace) {
|
|
try {
|
|
this.clock.runAll(); // Run all queued setTimeout calls.
|
|
workspace.dispose();
|
|
this.clock.runAll(); // Run all remaining queued setTimeout calls.
|
|
} catch (e) {
|
|
console.error(this.currentTest.fullTitle() + '\n', e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates stub for Blockly.Events.fire that advances the clock forward after
|
|
* the event fires so it is processed immediately instead of on a timeout.
|
|
* @param {!SinonClock} clock The sinon clock.
|
|
* @return {!SinonStub} The created stub.
|
|
* @private
|
|
*/
|
|
function createEventsFireStubFireImmediately_(clock) {
|
|
var stub = sinon.stub(Blockly.Events, 'fire');
|
|
stub.callsFake(function(event) {
|
|
// TODO(#4070) Replace the fake function content with the following code
|
|
// that uses wrappedMethod after cleanup is added to ALL tests.
|
|
// Calling the original method will not consistently work if other tests
|
|
// add things to the event queue because the setTimeout call will not be
|
|
// added to the stubbed clock (so runAll cannot be used to control).
|
|
// // Call original method.
|
|
// stub.wrappedMethod.call(this, ...arguments);
|
|
// // Advance clock forward to run any queued events.
|
|
// clock.runAll();
|
|
if (!Blockly.Events.isEnabled()) {
|
|
return;
|
|
}
|
|
Blockly.Events.FIRE_QUEUE_.push(event);
|
|
Blockly.Events.fireNow_();
|
|
});
|
|
return stub;
|
|
}
|
|
|
|
/**
|
|
* Shared setup method that sets up fake timer for clock so that pending
|
|
* setTimeout calls can be cleared in test teardown along with other common
|
|
* stubs. Should be called in setup of outermost suite using
|
|
* sharedTestSetup.call(this).
|
|
* @param {Object<string, boolean>} options Options to enable/disable setup
|
|
* of certain stubs.
|
|
*/
|
|
function sharedTestSetup(options = {}) {
|
|
this.sharedSetupCalled_ = true;
|
|
// Sandbox created for greater control when certain stubs are cleared.
|
|
this.sharedSetupSandbox_ = sinon.createSandbox();
|
|
this.clock = this.sharedSetupSandbox_.useFakeTimers();
|
|
if (options['fireEventsNow'] === undefined || options['fireEventsNow']) {
|
|
// Stubs event firing unless passed option "fireEventsNow: false"
|
|
this.eventsFireStub = createEventsFireStubFireImmediately_(this.clock);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Shared cleanup method that clears up pending setTimeout calls, disposes of
|
|
* workspace, and resets global variables. Should be called in setup of
|
|
* outermost suite using sharedTestTeardown.call(this).
|
|
*/
|
|
function sharedTestTeardown() {
|
|
if (!this.sharedSetupCalled_) {
|
|
console.error('"' + this.currentTest.fullTitle() +
|
|
'" did not call sharedTestSetup');
|
|
}
|
|
|
|
try {
|
|
if (this.workspace) {
|
|
workspaceTeardown.call(this, this.workspace);
|
|
this.workspace = null;
|
|
} else {
|
|
this.clock.runAll(); // Run all queued setTimeout calls.
|
|
}
|
|
} catch (e) {
|
|
console.error(this.currentTest.fullTitle() + '\n', e);
|
|
} finally {
|
|
// Clear Blockly.Event state.
|
|
Blockly.Events.setGroup(false);
|
|
Blockly.Events.disabled_ = 0;
|
|
if (Blockly.Events.FIRE_QUEUE_.length) {
|
|
// If this happens, it may mean that some previous test is missing cleanup
|
|
// (i.e. a previous test added an event to the queue on a timeout that
|
|
// did not use a stubbed clock).
|
|
Blockly.Events.FIRE_QUEUE_.length = 0;
|
|
console.warn(this.currentTest.fullTitle() +
|
|
'" needed cleanup of Blockly.Events.FIRE_QUEUE_. This may indicate ' +
|
|
'leakage from an earlier test');
|
|
}
|
|
|
|
// Restore all stubbed methods.
|
|
this.sharedSetupSandbox_.restore();
|
|
sinon.restore();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates stub for Blockly.utils.genUid that returns the provided id or ids.
|
|
* Recommended to also assert that the stub is called the expected number of
|
|
* times.
|
|
* @param {string|!Array<string>} returnIds The return values to use for the
|
|
* created stub. If a single value is passed, then the stub always returns
|
|
* that value.
|
|
* @return {!SinonStub} The created stub.
|
|
*/
|
|
function createGenUidStubWithReturns(returnIds) {
|
|
var stub = sinon.stub(Blockly.utils, "genUid");
|
|
if (Array.isArray(returnIds)) {
|
|
for (var i = 0; i < returnIds.length; i++) {
|
|
stub.onCall(i).returns(returnIds[i]);
|
|
}
|
|
} else {
|
|
stub.returns(returnIds);
|
|
}
|
|
return stub;
|
|
}
|
|
|
|
/**
|
|
* Creates spy for workspace fireChangeListener
|
|
* @param {!Blockly.Workspace} workspace The workspace to spy fireChangeListener
|
|
* calls on.
|
|
* @return {!SinonSpy} The created spy.
|
|
*/
|
|
function createFireChangeListenerSpy(workspace) {
|
|
return sinon.spy(workspace, 'fireChangeListener');
|
|
}
|
|
|
|
/**
|
|
* Asserts that the given event has the expected values.
|
|
* @param {!Blockly.Event.Abstract} event The event to check.
|
|
* @param {string} expectedType Expected type of event fired.
|
|
* @param {string} expectedWorkspaceId Expected workspace id of event fired.
|
|
* @param {string} expectedBlockId Expected block id of event fired.
|
|
* @param {!Object<string, *>} expectedProperties Map of of additional expected
|
|
* properties to check on fired event.
|
|
* @param {string=} message Optional message to prepend assert messages.
|
|
* @private
|
|
*/
|
|
function assertEventEquals(event, expectedType,
|
|
expectedWorkspaceId, expectedBlockId, expectedProperties, message) {
|
|
var prependMessage = message ? message + ' ' : '';
|
|
chai.assert.equal(event.type, expectedType,
|
|
prependMessage + 'Event fired type');
|
|
chai.assert.equal(event.workspaceId, expectedWorkspaceId,
|
|
prependMessage + 'Event fired workspace id');
|
|
chai.assert.equal(event.blockId, expectedBlockId,
|
|
prependMessage + 'Event fired block id');
|
|
Object.keys(expectedProperties).map((key) => {
|
|
var value = event[key];
|
|
var expectedValue = expectedProperties[key];
|
|
if (key.endsWith('Xml')) {
|
|
value = Blockly.Xml.domToText(value);
|
|
expectedValue = Blockly.Xml.domToText(expectedValue);
|
|
}
|
|
chai.assert.equal(value, expectedValue, prependMessage + 'Event fired ' + key);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Asserts that the event passed to the last call of the given spy has the
|
|
* expected values. Assumes that the event is passed as the first argument.
|
|
* @param {!SinonSpy} spy The spy to use.
|
|
* @param {string} expectedType Expected type of event fired.
|
|
* @param {string} expectedWorkspaceId Expected workspace id of event fired.
|
|
* @param {string} expectedBlockId Expected block id of event fired.
|
|
* @param {!Object<string, *>} expectedProperties Map of of expected properties
|
|
* to check on fired event.
|
|
* @param {string=} message Optional message to prepend assert messages.
|
|
*/
|
|
function assertLastCallEventArgEquals(spy, expectedType,
|
|
expectedWorkspaceId, expectedBlockId, expectedProperties, message) {
|
|
var event = spy.lastCall.firstArg;
|
|
assertEventEquals(event, expectedType, expectedWorkspaceId, expectedBlockId,
|
|
expectedProperties, message);
|
|
}
|
|
|
|
/**
|
|
* Asserts that the event passed to the nth call of the given spy has the
|
|
* expected values. Assumes that the event is passed as the first argument.
|
|
* @param {!SinonSpy} spy The spy to use.
|
|
* @param {number} n Which call to check.
|
|
* @param {string} expectedType Expected type of event fired.
|
|
* @param {string} expectedWorkspaceId Expected workspace id of event fired.
|
|
* @param {string} expectedBlockId Expected block id of event fired.
|
|
* @param {Object<string, *>} expectedProperties Map of of expected properties
|
|
* to check on fired event.
|
|
* @param {string=} message Optional message to prepend assert messages.
|
|
*/
|
|
function assertNthCallEventArgEquals(spy, n, expectedType,
|
|
expectedWorkspaceId, expectedBlockId, expectedProperties, message) {
|
|
var event = spy.getCall(n).firstArg;
|
|
assertEventEquals(event, expectedType, expectedWorkspaceId, expectedBlockId,
|
|
expectedProperties, message);
|
|
}
|
|
|
|
function defineStackBlock() {
|
|
Blockly.defineBlocksWithJsonArray([{
|
|
"type": "stack_block",
|
|
"message0": "",
|
|
"previousStatement": null,
|
|
"nextStatement": null
|
|
}]);
|
|
}
|
|
|
|
function defineRowBlock() {
|
|
Blockly.defineBlocksWithJsonArray([{
|
|
"type": "row_block",
|
|
"message0": "%1",
|
|
"args0": [
|
|
{
|
|
"type": "input_value",
|
|
"name": "INPUT"
|
|
}
|
|
],
|
|
"output": null
|
|
}]);
|
|
}
|
|
|
|
function defineStatementBlock() {
|
|
Blockly.defineBlocksWithJsonArray([{
|
|
"type": "statement_block",
|
|
"message0": "%1",
|
|
"args0": [
|
|
{
|
|
"type": "input_statement",
|
|
"name": "NAME"
|
|
}
|
|
],
|
|
"previousStatement": null,
|
|
"nextStatement": null,
|
|
"colour": 230,
|
|
"tooltip": "",
|
|
"helpUrl": ""
|
|
}]);
|
|
}
|
|
|
|
function createTestBlock() {
|
|
return {
|
|
id: 'test',
|
|
rendered: false,
|
|
workspace: {
|
|
rendered: false
|
|
}
|
|
};
|
|
}
|
|
|
|
function createRenderedBlock(workspaceSvg, type) {
|
|
var block = workspaceSvg.newBlock(type);
|
|
block.initSvg();
|
|
block.render();
|
|
return block;
|
|
}
|