Files
blockly/tests/mocha/connection_checker_test.js
Christopher Allen 6f20ac290d refactor(tests): Use import instead of goog.bootstrap to load Blockly in mocha tests (#7406)
* fix(build): Have buildShims clean up up after itself

  We need to create a build/package.json file to allow node.js to
  load build/src/core/blockly.js and the other chunk entry points
  as ES modules (it forcibly assumes .js means CJS even if one is
  trying to import, unless package.json says {"type": "module"}),
  but this interferes with scripts/migration/js2ts doing a
  require('build/deps.js'), which is _not_ an ES module.

  Specific error message was:

  /Users/cpcallen/src/blockly/scripts/migration/js2ts:56
  require(path.resolve(__dirname, '../../build/deps.js'));
  ^

  Error [ERR_REQUIRE_ESM]: require() of ES Module
  /Users/cpcallen/src/blockly/build/deps.js from /Users/cpcallen/src/blockly/scripts/migration/js2ts
  not supported.
  deps.js is treated as an ES module file as it is a .js file whose
  nearest parent package.json contains "type": "module" which
  declares all .js files in that package scope as ES modules.
  Instead rename deps.js to end in .cjs, change the requiring code
  to use dynamic import() which is available in all CommonJS
  modules, or change "type": "module" to "type": "commonjs" in
  /Users/cpcallen/src/blockly/build/package.json to treat all .js
  files as CommonJS (using .mjs for all ES modules instead).

      at Object.<anonymous> (/Users/cpcallen/src/blockly/scripts/migration/js2ts:56:1) {
    code: 'ERR_REQUIRE_ESM'
  }

* chore(tests): Reorder to put interesting script nearer top of file

* chore(tests): Add missing imports of closure/goog/goog.js

  These modules were depending on being loaded via the
  debug module loader, which cannot be used without first loading
  base.js as a script, and thereby defining goog.declareModuleId
  as a side effect—but if they are to be loaded via direct import
  statements then they need to actually import their own
  dependencies.

  This is a temporary measure as soon the goog.declareMouleId
  calls can themselves be deleted.

* refactor(tests): Use import instead of bootstrap to load Blockly

* chores(build): Stop generating deps.mocha.js

  This file was only needed by tests/mocha/index.html's use of
  the debug module loader (via bootstrap.js), which has now been
  removed.

* chore(tests): Remove unneeded goog.declareModuleId calls

  These were only needed because these modules were previously
  being loaded by goog.require and/or goog.bootstrap.

* chores(tests): Remove dead code

  We are fully committed to proper modules now.
2023-08-18 18:06:52 +01:00

666 lines
19 KiB
JavaScript

/**
* @license
* Copyright 2020 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {ConnectionType} from '../../build/src/core/connection_type.js';
import {
sharedTestSetup,
sharedTestTeardown,
} from './test_helpers/setup_teardown.js';
suite('Connection checker', function () {
setup(function () {
sharedTestSetup.call(this);
});
teardown(function () {
sharedTestTeardown.call(this);
});
suiteSetup(function () {
this.checker = new Blockly.ConnectionChecker();
});
suite('Safety checks', function () {
function assertReasonHelper(checker, one, two, reason) {
chai.assert.equal(checker.canConnectWithReason(one, two), reason);
// Order should not matter.
chai.assert.equal(checker.canConnectWithReason(two, one), reason);
}
test('Target Null', function () {
const connection = new Blockly.Connection({}, ConnectionType.INPUT_VALUE);
assertReasonHelper(
this.checker,
connection,
null,
Blockly.Connection.REASON_TARGET_NULL,
);
});
test('Target Self', function () {
const block = {workspace: 1};
const connection1 = new Blockly.Connection(
block,
ConnectionType.INPUT_VALUE,
);
const connection2 = new Blockly.Connection(
block,
ConnectionType.OUTPUT_VALUE,
);
assertReasonHelper(
this.checker,
connection1,
connection2,
Blockly.Connection.REASON_SELF_CONNECTION,
);
});
test('Different Workspaces', function () {
const connection1 = new Blockly.Connection(
{workspace: 1},
ConnectionType.INPUT_VALUE,
);
const connection2 = new Blockly.Connection(
{workspace: 2},
ConnectionType.OUTPUT_VALUE,
);
assertReasonHelper(
this.checker,
connection1,
connection2,
Blockly.Connection.REASON_DIFFERENT_WORKSPACES,
);
});
suite('Types', function () {
setup(function () {
// We have to declare each separately so that the connections belong
// on different blocks.
const prevBlock = {isShadow: function () {}};
const nextBlock = {isShadow: function () {}};
const outBlock = {isShadow: function () {}};
const inBlock = {isShadow: function () {}};
this.previous = new Blockly.Connection(
prevBlock,
ConnectionType.PREVIOUS_STATEMENT,
);
this.next = new Blockly.Connection(
nextBlock,
ConnectionType.NEXT_STATEMENT,
);
this.output = new Blockly.Connection(
outBlock,
ConnectionType.OUTPUT_VALUE,
);
this.input = new Blockly.Connection(
inBlock,
ConnectionType.INPUT_VALUE,
);
});
test('Previous, Next', function () {
assertReasonHelper(
this.checker,
this.previous,
this.next,
Blockly.Connection.CAN_CONNECT,
);
});
test('Previous, Output', function () {
assertReasonHelper(
this.checker,
this.previous,
this.output,
Blockly.Connection.REASON_WRONG_TYPE,
);
});
test('Previous, Input', function () {
assertReasonHelper(
this.checker,
this.previous,
this.input,
Blockly.Connection.REASON_WRONG_TYPE,
);
});
test('Next, Previous', function () {
assertReasonHelper(
this.checker,
this.next,
this.previous,
Blockly.Connection.CAN_CONNECT,
);
});
test('Next, Output', function () {
assertReasonHelper(
this.checker,
this.next,
this.output,
Blockly.Connection.REASON_WRONG_TYPE,
);
});
test('Next, Input', function () {
assertReasonHelper(
this.checker,
this.next,
this.input,
Blockly.Connection.REASON_WRONG_TYPE,
);
});
test('Output, Previous', function () {
assertReasonHelper(
this.checker,
this.previous,
this.output,
Blockly.Connection.REASON_WRONG_TYPE,
);
});
test('Output, Next', function () {
assertReasonHelper(
this.checker,
this.output,
this.next,
Blockly.Connection.REASON_WRONG_TYPE,
);
});
test('Output, Input', function () {
assertReasonHelper(
this.checker,
this.output,
this.input,
Blockly.Connection.CAN_CONNECT,
);
});
test('Input, Previous', function () {
assertReasonHelper(
this.checker,
this.previous,
this.input,
Blockly.Connection.REASON_WRONG_TYPE,
);
});
test('Input, Next', function () {
assertReasonHelper(
this.checker,
this.input,
this.next,
Blockly.Connection.REASON_WRONG_TYPE,
);
});
test('Input, Output', function () {
assertReasonHelper(
this.checker,
this.input,
this.output,
Blockly.Connection.CAN_CONNECT,
);
});
});
suite('Shadows', function () {
test('Previous Shadow', function () {
const prevBlock = {
isShadow: function () {
return true;
},
};
const nextBlock = {
isShadow: function () {
return false;
},
};
const prev = new Blockly.Connection(
prevBlock,
ConnectionType.PREVIOUS_STATEMENT,
);
const next = new Blockly.Connection(
nextBlock,
ConnectionType.NEXT_STATEMENT,
);
assertReasonHelper(
this.checker,
prev,
next,
Blockly.Connection.CAN_CONNECT,
);
});
test('Next Shadow', function () {
const prevBlock = {
isShadow: function () {
return false;
},
};
const nextBlock = {
isShadow: function () {
return true;
},
};
const prev = new Blockly.Connection(
prevBlock,
ConnectionType.PREVIOUS_STATEMENT,
);
const next = new Blockly.Connection(
nextBlock,
ConnectionType.NEXT_STATEMENT,
);
assertReasonHelper(
this.checker,
prev,
next,
Blockly.Connection.REASON_SHADOW_PARENT,
);
});
test('Prev and Next Shadow', function () {
const prevBlock = {
isShadow: function () {
return true;
},
};
const nextBlock = {
isShadow: function () {
return true;
},
};
const prev = new Blockly.Connection(
prevBlock,
ConnectionType.PREVIOUS_STATEMENT,
);
const next = new Blockly.Connection(
nextBlock,
ConnectionType.NEXT_STATEMENT,
);
assertReasonHelper(
this.checker,
prev,
next,
Blockly.Connection.CAN_CONNECT,
);
});
test('Output Shadow', function () {
const outBlock = {
isShadow: function () {
return true;
},
};
const inBlock = {
isShadow: function () {
return false;
},
};
const outCon = new Blockly.Connection(
outBlock,
ConnectionType.OUTPUT_VALUE,
);
const inCon = new Blockly.Connection(
inBlock,
ConnectionType.INPUT_VALUE,
);
assertReasonHelper(
this.checker,
outCon,
inCon,
Blockly.Connection.CAN_CONNECT,
);
});
test('Input Shadow', function () {
const outBlock = {
isShadow: function () {
return false;
},
};
const inBlock = {
isShadow: function () {
return true;
},
};
const outCon = new Blockly.Connection(
outBlock,
ConnectionType.OUTPUT_VALUE,
);
const inCon = new Blockly.Connection(
inBlock,
ConnectionType.INPUT_VALUE,
);
assertReasonHelper(
this.checker,
outCon,
inCon,
Blockly.Connection.REASON_SHADOW_PARENT,
);
});
test('Output and Input Shadow', function () {
const outBlock = {
isShadow: function () {
return true;
},
};
const inBlock = {
isShadow: function () {
return true;
},
};
const outCon = new Blockly.Connection(
outBlock,
ConnectionType.OUTPUT_VALUE,
);
const inCon = new Blockly.Connection(
inBlock,
ConnectionType.INPUT_VALUE,
);
assertReasonHelper(
this.checker,
outCon,
inCon,
Blockly.Connection.CAN_CONNECT,
);
});
});
suite('Output and Previous', function () {
/**
* Update two connections to target each other.
* @param {Connection} first The first connection to update.
* @param {Connection} second The second connection to update.
*/
const connectReciprocally = function (first, second) {
if (!first || !second) {
throw Error('Cannot connect null connections.');
}
first.targetConnection = second;
second.targetConnection = first;
};
test('Output connected, adding previous', function () {
const outBlock = {
isShadow: function () {},
};
const inBlock = {
isShadow: function () {},
};
const outCon = new Blockly.Connection(
outBlock,
ConnectionType.OUTPUT_VALUE,
);
const inCon = new Blockly.Connection(
inBlock,
ConnectionType.INPUT_VALUE,
);
outBlock.outputConnection = outCon;
inBlock.inputConnection = inCon;
connectReciprocally(inCon, outCon);
const prevCon = new Blockly.Connection(
outBlock,
ConnectionType.PREVIOUS_STATEMENT,
);
const nextBlock = {
isShadow: function () {},
};
const nextCon = new Blockly.Connection(
nextBlock,
ConnectionType.NEXT_STATEMENT,
);
assertReasonHelper(
this.checker,
prevCon,
nextCon,
Blockly.Connection.REASON_PREVIOUS_AND_OUTPUT,
);
});
test('Previous connected, adding output', function () {
const prevBlock = {
isShadow: function () {},
};
const nextBlock = {
isShadow: function () {},
};
const prevCon = new Blockly.Connection(
prevBlock,
ConnectionType.PREVIOUS_STATEMENT,
);
const nextCon = new Blockly.Connection(
nextBlock,
ConnectionType.NEXT_STATEMENT,
);
prevBlock.previousConnection = prevCon;
nextBlock.nextConnection = nextCon;
connectReciprocally(prevCon, nextCon);
const outCon = new Blockly.Connection(
prevBlock,
ConnectionType.OUTPUT_VALUE,
);
const inBlock = {
isShadow: function () {},
};
const inCon = new Blockly.Connection(
inBlock,
ConnectionType.INPUT_VALUE,
);
assertReasonHelper(
this.checker,
outCon,
inCon,
Blockly.Connection.REASON_PREVIOUS_AND_OUTPUT,
);
});
});
});
suite('Check Types', function () {
setup(function () {
this.con1 = new Blockly.Connection({}, ConnectionType.PREVIOUS_STATEMENT);
this.con2 = new Blockly.Connection({}, ConnectionType.NEXT_STATEMENT);
});
function assertCheckTypes(checker, one, two) {
chai.assert.isTrue(checker.doTypeChecks(one, two));
// Order should not matter.
chai.assert.isTrue(checker.doTypeChecks(one, two));
}
test('No Types', function () {
assertCheckTypes(this.checker, this.con1, this.con2);
});
test('Same Type', function () {
this.con1.setCheck('type1');
this.con2.setCheck('type1');
assertCheckTypes(this.checker, this.con1, this.con2);
});
test('Same Types', function () {
this.con1.setCheck(['type1', 'type2']);
this.con2.setCheck(['type1', 'type2']);
assertCheckTypes(this.checker, this.con1, this.con2);
});
test('Single Same Type', function () {
this.con1.setCheck(['type1', 'type2']);
this.con2.setCheck(['type1', 'type3']);
assertCheckTypes(this.checker, this.con1, this.con2);
});
test('One Typed, One Promiscuous', function () {
this.con1.setCheck('type1');
assertCheckTypes(this.checker, this.con1, this.con2);
});
test('No Compatible Types', function () {
this.con1.setCheck('type1');
this.con2.setCheck('type2');
chai.assert.isFalse(this.checker.doTypeChecks(this.con1, this.con2));
});
});
suite('Dragging Checks', function () {
suite('Stacks', function () {
setup(function () {
this.workspace = Blockly.inject('blocklyDiv');
// Load in three blocks: A and B are connected (next/prev); B is unmovable.
Blockly.Xml.domToWorkspace(
Blockly.utils.xml
.textToDom(`<xml xmlns="https://developers.google.com/blockly/xml">
<block type="text_print" id="A" x="-76" y="-112">
<next>
<block type="text_print" id="B" movable="false">
</block>
</next>
</block>
<block type="text_print" id="C" x="47" y="-118"/>
</xml>`),
this.workspace,
);
[this.blockA, this.blockB, this.blockC] =
this.workspace.getAllBlocks(true);
this.checker = this.workspace.connectionChecker;
});
test('Connect a stack', function () {
// block C is not connected to block A; both are movable.
chai.assert.isTrue(
this.checker.doDragChecks(
this.blockC.nextConnection,
this.blockA.previousConnection,
9000,
),
'Should connect two compatible stack blocks',
);
});
test('Connect to unmovable shadow block', function () {
// Remove original test blocks.
this.workspace.clear();
// Add the same test blocks, but this time block B is a shadow block.
Blockly.Xml.domToWorkspace(
Blockly.utils.xml
.textToDom(`<xml xmlns="https://developers.google.com/blockly/xml">
<block type="text_print" id="A" x="-76" y="-112">
<next>
<shadow type="text_print" id="B" movable="false">
</shadow>
</next>
</block>
<block type="text_print" id="C" x="47" y="-118"/>
</xml>`),
this.workspace,
);
[this.blockA, this.blockB, this.blockC] =
this.workspace.getAllBlocks(true);
// Try to connect blockC into the input connection of blockA, replacing blockB.
// This is allowed because shadow blocks can always be replaced, even though
// they are unmovable.
chai.assert.isTrue(
this.checker.doDragChecks(
this.blockC.previousConnection,
this.blockA.nextConnection,
9000,
),
'Should connect in place of a shadow block',
);
});
test('Do not splice into unmovable stack', function () {
// Try to connect blockC above blockB. It shouldn't work because B is not movable
// and is already connected to A's nextConnection.
chai.assert.isFalse(
this.checker.doDragChecks(
this.blockC.previousConnection,
this.blockA.nextConnection,
9000,
),
'Should not splice in a block above an unmovable block',
);
});
test('Connect to bottom of unmovable stack', function () {
// Try to connect blockC below blockB.
// This is allowed even though B is not movable because it is on B's nextConnection.
chai.assert.isTrue(
this.checker.doDragChecks(
this.blockC.previousConnection,
this.blockB.nextConnection,
9000,
),
'Should connect below an unmovable stack block',
);
});
test('Connect to unconnected unmovable block', function () {
this.blockB.previousConnection.disconnect();
this.blockA.dispose();
// Try to connect blockC above blockB.
// This is allowed because we're not splicing into a stack.
chai.assert.isTrue(
this.checker.doDragChecks(
this.blockC.nextConnection,
this.blockB.previousConnection,
9000,
),
'Should connect above an unconnected unmovable block',
);
});
});
suite('Rows', function () {
setup(function () {
this.workspace = Blockly.inject('blocklyDiv');
// Load 3 blocks: A and B are connected (input/output); B is unmovable.
Blockly.Xml.domToWorkspace(
Blockly.utils.xml
.textToDom(`<xml xmlns="https://developers.google.com/blockly/xml">
<block type="test_basic_row" id="A" x="38" y="37">
<value name="INPUT">
<block type="test_basic_row" id="B" movable="false"></block>
</value>
</block>
<block type="test_basic_row" id="C" x="38" y="87"></block>
</xml>`),
this.workspace,
);
[this.blockA, this.blockB, this.blockC] =
this.workspace.getAllBlocks(true);
this.checker = this.workspace.connectionChecker;
});
test('Do not splice into unmovable block row', function () {
// Try to connect C's output to A's input. Should fail because
// A is already connected to B, which is unmovable.
const inputConnection = this.blockA.inputList[0].connection;
chai.assert.isFalse(
this.checker.doDragChecks(
this.blockC.outputConnection,
inputConnection,
9000,
),
'Should not splice in a block before an unmovable block',
);
});
test('Connect to end of unmovable block', function () {
// Make blockC unmovable
this.blockC.setMovable(false);
// Try to connect A's output to C's input. This is allowed.
const inputConnection = this.blockC.inputList[0].connection;
chai.assert.isTrue(
this.checker.doDragChecks(
this.blockA.outputConnection,
inputConnection,
9000,
),
'Should connect to end of unmovable block',
);
});
test('Connect to unconnected unmovable block', function () {
this.blockB.outputConnection.disconnect();
this.blockA.dispose();
// Try to connect C's input to B's output. Allowed because B is now unconnected.
const inputConnection = this.blockC.inputList[0].connection;
chai.assert.isTrue(
this.checker.doDragChecks(
inputConnection,
this.blockB.outputConnection,
9000,
),
'Should connect to unconnected unmovable block',
);
});
});
});
});