fix: add test helpers to core and convert to goog modules

This commit is contained in:
Beka Westberg
2022-02-28 22:30:26 +00:00
parent 99af0643ff
commit eff84af8ee
4 changed files with 657 additions and 0 deletions

View File

@@ -44,6 +44,9 @@ goog.addDependency('../../tests/mocha/registry_test.js', ['Blockly.test.registry
goog.addDependency('../../tests/mocha/run_mocha_tests_in_browser.js', [], [], {'lang': 'es8'});
goog.addDependency('../../tests/mocha/serializer_test.js', ['Blockly.test.serialization'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../tests/mocha/shortcut_registry_test.js', ['Blockly.test.shortcutRegistry'], ['Blockly.test.helpers'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../tests/mocha/test_helpers/block_test_helpers.mocha.js', ['Blockly.test.blockHelpers'], ['Blockly.test.commonHelpers'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../tests/mocha/test_helpers/common_test_helpers.mocha.js', ['Blockly.test.commonHelpers'], [], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../tests/mocha/test_helpers/field_test_helpers.mocha.js', ['Blockly.test.fieldHelpers'], ['Blockly.test.commonHelpers'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../tests/mocha/test_helpers/procedures_test_helpers.js', ['Blockly.test.procedureHelpers'], ['Blockly.ConnectionType'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../tests/mocha/test_helpers/test_helpers.js', ['Blockly.test.helpers'], ['Blockly.Events.utils', 'Blockly.blocks', 'Blockly.utils.KeyCodes'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../tests/mocha/test_helpers/toolbox_helper.js', ['Blockly.test.toolboxHelpers'], [], {'lang': 'es6', 'module': 'goog'});

View File

@@ -0,0 +1,240 @@
/**
* @license
* Copyright 2020 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
goog.module('Blockly.test.blockHelpers');
const {runTestCases, runTestSuites, TestCase, TestSuite} = goog.require('Blockly.test.commonHelpers');
/**
* Code generation test case configuration.
* @implements {TestCase}
* @record
*/
class CodeGenerationTestCase {
/**
* Class for a code generation test case.
*/
constructor() {
/**
* @type {string} The expected code.
*/
this.expectedCode;
/**
* @type {boolean|undefined} Whether to use workspaceToCode instead of
* blockToCode for test.
*/
this.useWorkspaceToCode;
/**
* @type {number|undefined} The expected inner order.
*/
this.expectedInnerOrder;
}
/**
* Creates the block to use for this test case.
* @param {!Blockly.Workspace} workspace The workspace context for this
* test.
* @return {!Blockly.Block} The block to use for the test case.
*/
createBlock(workspace) {}
}
exports.CodeGenerationTestCase = CodeGenerationTestCase;
/**
* Code generation test suite.
* @extends {TestSuite<CodeGenerationTestCase, CodeGenerationTestSuite>}
* @record
*/
class CodeGenerationTestSuite {
/**
* Class for a code generation test suite.
*/
constructor() {
/**
* @type {!Blockly.Generator} The generator to use for running test cases.
*/
this.generator;
}
}
exports.CodeGenerationTestSuite = CodeGenerationTestSuite;
/**
* Serialization test case.
* @implements {TestCase}
* @record
*/
class SerializationTestCase {
/**
* Class for a block serialization test case.
*/
constructor() {
/**
* @type {string} The block xml to use for test. Do not provide if json is
* provided.
*/
this.xml;
/**
* @type {string|undefined} The expected xml after round trip. Provided if
* it different from xml that was passed in.
*/
this.expectedXml;
/**
* @type {string} The block json to use for test. Do not provide if xml is
* provided.
*/
this.json;
/**
* @type {string|undefined} The expected json after round trip. Provided if
* it is different from json that was passed in.
*/
this.expectedJson;
}
/**
* Asserts that the block created from xml has the expected structure.
* @param {!Blockly.Block} block The block to check.
*/
assertBlockStructure(block) {}
}
exports.SerializationTestCase = SerializationTestCase;
/**
* Returns mocha test callback for code generation based on provided
* generator.
* @param {!Blockly.Generator} generator The generator to use in test.
* @return {function(!CodeGenerationTestCase):!Function} Function that
* returns mocha test callback based on test case.
* @private
*/
const createCodeGenerationTestFn_ = (generator) => {
return (testCase) => {
return function() {
const block = testCase.createBlock(this.workspace);
let code;
let innerOrder;
if (testCase.useWorkspaceToCode) {
code = generator.workspaceToCode(this.workspace);
} else {
generator.init(this.workspace);
code = generator.blockToCode(block);
if (Array.isArray(code)) {
innerOrder = code[1];
code = code[0];
}
}
const assertFunc = (typeof testCase.expectedCode === 'string') ?
assert.equal : assert.match;
assertFunc(code, testCase.expectedCode);
if (!testCase.useWorkspaceToCode &&
testCase.expectedInnerOrder !== undefined) {
assert.equal(innerOrder, testCase.expectedInnerOrder);
}
};
};
};
/**
* Runs blockToCode test suites.
* @param {!Array<!CodeGenerationTestSuite>} testSuites The test suites to run.
*/
const runCodeGenerationTestSuites = (testSuites) => {
/**
* Creates function used to generate mocha test callback.
* @param {!CodeGenerationTestSuite} suiteInfo The test suite information.
* @return {function(!CodeGenerationTestCase):!Function} Function that
* creates mocha test callback.
*/
const createTestFn = (suiteInfo) => {
return createCodeGenerationTestFn_(suiteInfo.generator);
};
runTestSuites(testSuites, createTestFn);
};
exports.runCodeGenerationTestSuites = runCodeGenerationTestSuites;
/**
* Runs serialization test suite.
* @param {!Array<!SerializationTestCase>} testCases The test cases to run.
*/
const runSerializationTestSuite = (testCases) => {
/**
* Creates test callback for xmlToBlock test.
* @param {!SerializationTestCase} testCase The test case information.
* @return {!Function} The test callback.
*/
const createSerializedDataToBlockTestCallback = (testCase) => {
return function() {
let block;
if (testCase.json) {
block = Blockly.serialization.blocks.append(
testCase.json, this.workspace);
} else {
block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
testCase.xml), this.workspace);
}
testCase.assertBlockStructure(block);
};
};
/**
* Creates test callback for xml round trip test.
* @param {!SerializationTestCase} testCase The test case information.
* @return {!Function} The test callback.
*/
const createRoundTripTestCallback = (testCase) => {
return function() {
if (testCase.json) {
const block = Blockly.serialization.blocks.append(
testCase.json, this.workspace);
const generatedJson = Blockly.serialization.blocks.save(block);
const expectedJson = testCase.expectedJson || testCase.json;
assert.deepEqual(generatedJson, expectedJson);
} else {
const block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
testCase.xml), this.workspace);
const generatedXml =
Blockly.Xml.domToPrettyText(
Blockly.Xml.blockToDom(block));
const expectedXml = testCase.expectedXml || testCase.xml;
assert.equal(generatedXml, expectedXml);
}
};
};
suite('Serialization', function() {
suite('xmlToBlock', function() {
runTestCases(testCases, createSerializedDataToBlockTestCallback);
});
suite('xml round-trip', function() {
setup(function() {
// The genUid is undergoing change as part of the 2021Q3
// goog.module migration:
//
// - It is being moved from Blockly.utils to
// Blockly.utils.idGenerator (which itself is being renamed
// from IdGenerator).
// - For compatibility with changes to the module system (from
// goog.provide to goog.module and in future to ES modules),
// .genUid is now a wrapper around .TEST_ONLY.genUid, which
// can be safely stubbed by sinon or other similar
// frameworks in a way that will continue to work.
if (Blockly.utils.idGenerator &&
Blockly.utils.idGenerator.TEST_ONLY) {
sinon.stub(Blockly.utils.idGenerator.TEST_ONLY, 'genUid')
.returns('1');
} else {
// Fall back to stubbing original version on Blockly.utils.
sinon.stub(Blockly.utils, 'genUid').returns('1');
}
});
teardown(function() {
sinon.restore();
});
runTestCases(testCases, createRoundTripTestCallback);
});
});
};
exports.runSerializationTestSuite = runSerializationTestSuite;

View File

@@ -0,0 +1,133 @@
/**
* @license
* Copyright 2020 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
goog.module('Blockly.test.commonHelpers');
/**
* Test case configuration.
* @record
*/
class TestCase {
/**
* Class for a test case configuration.
*/
constructor() {
/**
* @type {string} The title for the test case.
*/
this.title;
/**
* @type {boolean|undefined} Whether this test case should be skipped.
* Used to skip buggy test case and should have an associated bug.
*/
this.skip;
/**
* @type {boolean|undefined} Whether this test case should be called as
* only. Used for debugging.
*/
this.only;
}
}
exports.TestCase = TestCase;
/**
* Test suite configuration.
* @record
* @template {TestCase} T
* @template {TestSuite} U
*/
class TestSuite {
/**
* Class for a test suite configuration.
*/
constructor() {
/**
* @type {string} The title for the test case.
*/
this.title;
/**
* @type {?Array<T>} The associated test cases.
*/
this.testCases;
/**
* @type {?Array<U>} List of nested inner test suites.
*/
this.testSuites;
/**
* @type {boolean|undefined} Whether this test suite should be skipped.
* Used to skip buggy test case and should have an associated bug.
*/
this.skip;
/**
* @type {boolean|undefined} Whether this test suite should be called as
* only. Used for debugging.
*/
this.only;
}
}
exports.TestSuite = TestSuite;
/**
* Runs provided test cases.
* @template {TestCase} T
* @param {!Array<T>} testCases The test cases to run.
* @param {function(T):Function} createTestCallback Creates test
* callback using given test case.
*/
function runTestCases(testCases, createTestCallback) {
testCases.forEach((testCase) => {
let testCall = (testCase.skip ? test.skip : test);
testCall = (testCase.only ? test.only : testCall);
testCall(testCase.title, createTestCallback(testCase));
});
}
exports.runTestCases = runTestCases;
/**
* Runs provided test suite.
* @template {TestCase} T
* @template {TestSuite<T, U>} U
* @param {Array<!U>} testSuites The test suites to run.
* @param {function(!U):(function(T):!Function)
* } createTestCaseCallback Creates test case callback using given test
* suite.
*/
function runTestSuites(testSuites, createTestCaseCallback) {
testSuites.forEach((testSuite) => {
let suiteCall = (testSuite.skip ? suite.skip : suite);
suiteCall = (testSuite.only ? suite.only : suiteCall);
suiteCall(testSuite.title, function() {
if (testSuite.testSuites && testSuite.testSuites.length) {
runTestSuites(testSuite.testSuites, createTestCaseCallback);
}
if (testSuite.testCases && testSuite.testCases.length) {
runTestCases(testSuite.testCases, createTestCaseCallback(testSuite));
}
});
});
}
exports.runTestSuites = runTestSuites;
/**
* Captures the strings sent to console.warn() when calling a function.
* Copies from core.
* @param {Function} innerFunc The function where warnings may called.
* @return {Array<string>} The warning messages (only the first arguments).
*/
function captureWarnings(innerFunc) {
const msgs = [];
const nativeConsoleWarn = console.warn;
try {
console.warn = function(msg) {
msgs.push(msg);
};
innerFunc();
} finally {
console.warn = nativeConsoleWarn;
}
return msgs;
}
exports.captureWarnings = captureWarnings;

View File

@@ -0,0 +1,281 @@
/**
* @license
* Copyright 2020 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
goog.module('Blockly.test.fieldHelpers');
const {runTestCases, TestCase} = goog.require('Blockly.test.commonHelpers');
/**
* Field value test case.
* @implements {TestCase}
* @record
*/
class FieldValueTestCase {
/**
* Class for a a field value test case.
*/
constructor() {
/**
* @type {*} The value to use in test.
*/
this.value;
/**
* @type {*} The expected value.
*/
this.expectedValue;
/**
* @type {string|undefined} The expected text value. Provided if different
* from String(expectedValue).
*/
this.expectedText;
/**
* @type {!RegExp|string|undefined} The optional error message matcher.
* Provided if test case is expected to throw.
*/
this.errMsgMatcher;
}
}
exports.FieldValueTestCase = FieldValueTestCase;
/**
* Field creation test case.
* @extends {FieldValueTestCase}
* @record
*/
class FieldCreationTestCase {
/**
* Class for a field creation test case.
*/
constructor() {
/**
* @type {Array<*>} The arguments to pass to field constructor.
*/
this.args;
/**
* @type {string} The json to use in field creation.
*/
this.json;
}
}
exports.FieldCreationTestCase = FieldCreationTestCase;
/**
* Assert a field's value is the same as the expected value.
* @param {!Blockly.Field} field The field.
* @param {*} expectedValue The expected value.
* @param {string=} expectedText The expected text.
*/
function assertFieldValue(field, expectedValue, expectedText = undefined) {
const actualValue = field.getValue();
const actualText = field.getText();
if (expectedText === undefined) {
expectedText = String(expectedValue);
}
assert.equal(actualValue, expectedValue, 'Value');
assert.equal(actualText, expectedText, 'Text');
}
/**
* Runs provided creation test cases.
* @param {!Array<!FieldCreationTestCase>} testCases The test cases to run.
* @param {function(!Blockly.Field, !FieldCreationTestCase)} assertion The
* assertion to use.
* @param {function(new:Blockly.Field,!FieldCreationTestCase):Blockly.Field
* } creation A function that returns an instance of the field based on the
* provided test case.
* @private
*/
function runCreationTests_(testCases, assertion, creation) {
/**
* Creates test callback for creation test.
* @param {FieldCreationTestCase} testCase The test case to use.
* @return {Function} The test callback.
*/
const createTestFn = (testCase) => {
return function() {
const field = creation.call(this, testCase);
assertion(field, testCase);
};
};
runTestCases(testCases, createTestFn);
}
/**
* Runs provided creation test cases.
* @param {!Array<!FieldCreationTestCase>} testCases The test cases to run.
* @param {function(new:Blockly.Field,!FieldCreationTestCase):Blockly.Field
* } creation A function that returns an instance of the field based on the
* provided test case.
* @private
*/
function runCreationTestsAssertThrows_(testCases, creation) {
/**
* Creates test callback for creation test.
* @param {!FieldCreationTestCase} testCase The test case to use.
* @return {!Function} The test callback.
*/
const createTestFn = (testCase) => {
return function() {
assert.throws(function() {
creation.call(this, testCase);
}, testCase.errMsgMatcher);
};
};
runTestCases(testCases, createTestFn);
}
/**
* Runs suite of tests for constructor for the specified field.
* @param {function(new:Blockly.Field, *=)} TestedField The class of the field
* being tested.
* @param {Array<!FieldCreationTestCase>} validValueTestCases Test cases with
* valid values for given field.
* @param {Array<!FieldCreationTestCase>} invalidValueTestCases Test cases with
* invalid values for given field.
* @param {function(!Blockly.Field, !FieldCreationTestCase)
* } validRunAssertField Asserts that field has expected values.
* @param {function(!Blockly.Field)=} assertFieldDefault Asserts that field has
* default values. If undefined, tests will check that field throws when
* invalid value is passed rather than asserting default.
* @param {function(!FieldCreationTestCase=)=} customCreateWithJs Custom
* creation function to use in tests.
*/
function runConstructorSuiteTests(TestedField, validValueTestCases,
invalidValueTestCases, validRunAssertField, assertFieldDefault,
customCreateWithJs) {
suite('Constructor', function() {
if (assertFieldDefault) {
test('Empty', function() {
const field = customCreateWithJs ? customCreateWithJs.call(this) :
new TestedField();
assertFieldDefault(field);
});
} else {
test('Empty', function() {
assert.throws(function() {
customCreateWithJs ? customCreateWithJs.call(this) :
new TestedField();
});
});
}
/**
* Creates a field using its constructor and the provided test case.
* @param {!FieldCreationTestCase} testCase The test case information.
* @return {!Blockly.Field} The instantiated field.
*/
const createWithJs = function(testCase) {
return customCreateWithJs ? customCreateWithJs.call(this, testCase) :
new TestedField(...testCase.args);
};
if (assertFieldDefault) {
runCreationTests_(
invalidValueTestCases, assertFieldDefault, createWithJs);
} else {
runCreationTestsAssertThrows_(invalidValueTestCases, createWithJs);
}
runCreationTests_(validValueTestCases, validRunAssertField, createWithJs);
});
}
exports.runConstructorSuiteTests = runConstructorSuiteTests;
/**
* Runs suite of tests for fromJson creation of specified field.
* @param {function(new:Blockly.Field, *=)} TestedField The class of the field
* being tested.
* @param {!Array<!FieldCreationTestCase>} validValueTestCases Test cases with
* valid values for given field.
* @param {!Array<!FieldCreationTestCase>} invalidValueTestCases Test cases with
* invalid values for given field.
* @param {function(!Blockly.Field, !FieldValueTestCase)
* } validRunAssertField Asserts that field has expected values.
* @param {function(!Blockly.Field)=} assertFieldDefault Asserts that field has
* default values. If undefined, tests will check that field throws when
* invalid value is passed rather than asserting default.
* @param {function(!FieldCreationTestCase=)=} customCreateWithJson Custom
* creation function to use in tests.
*/
function runFromJsonSuiteTests(TestedField, validValueTestCases,
invalidValueTestCases, validRunAssertField, assertFieldDefault,
customCreateWithJson) {
suite('fromJson', function() {
if (assertFieldDefault) {
test('Empty', function() {
const field = customCreateWithJson ? customCreateWithJson.call(this) :
TestedField.fromJson({});
assertFieldDefault(field);
});
} else {
test('Empty', function() {
assert.throws(function() {
customCreateWithJson ? customCreateWithJson.call(this) :
TestedField.fromJson({});
});
});
}
/**
* Creates a field using fromJson and the provided test case.
* @param {!FieldCreationTestCase} testCase The test case information.
* @return {!Blockly.Field} The instantiated field.
*/
const createWithJson = function(testCase) {
return customCreateWithJson ? customCreateWithJson.call(this, testCase) :
TestedField.fromJson(testCase.json);
};
if (assertFieldDefault) {
runCreationTests_(
invalidValueTestCases, assertFieldDefault, createWithJson);
} else {
runCreationTestsAssertThrows_(invalidValueTestCases, createWithJson);
}
runCreationTests_(validValueTestCases, validRunAssertField, createWithJson);
});
}
exports.runFromJsonSuiteTests = runFromJsonSuiteTests;
/**
* Runs tests for setValue calls.
* @param {!Array<!FieldValueTestCase>} validValueTestCases Test cases with
* valid values.
* @param {!Array<!FieldValueTestCase>} invalidValueTestCases Test cases with
* invalid values.
* @param {*} invalidRunExpectedValue Expected value for field after invalid
* call to setValue.
* @param {string=} invalidRunExpectedText Expected text for field after invalid
* call to setValue.
*/
function runSetValueTests(validValueTestCases, invalidValueTestCases,
invalidRunExpectedValue, invalidRunExpectedText) {
/**
* Creates test callback for invalid setValue test.
* @param {!FieldValueTestCase} testCase The test case information.
* @return {!Function} The test callback.
*/
const createInvalidSetValueTestCallback = (testCase) => {
return function() {
this.field.setValue(testCase.value);
assertFieldValue(
this.field, invalidRunExpectedValue, invalidRunExpectedText);
};
};
/**
* Creates test callback for valid setValue test.
* @param {!FieldValueTestCase} testCase The test case information.
* @return {!Function} The test callback.
*/
const createValidSetValueTestCallback = (testCase) => {
return function() {
this.field.setValue(testCase.value);
assertFieldValue(
this.field, testCase.expectedValue, testCase.expectedText);
};
};
runTestCases(invalidValueTestCases, createInvalidSetValueTestCallback);
runTestCases(validValueTestCases, createValidSetValueTestCallback);
}
exports.runSetValueTests = runSetValueTests;