Files
blockly/tests/mocha/connection_checker_test.js
dependabot[bot] bfb5b1dd49 chore(deps): Bump chai from 4.3.10 to 5.1.1 (#8092)
* chore(deps): Bump chai from 4.3.10 to 5.1.1

  Bumps [chai](https://github.com/chaijs/chai) from 4.3.10 to 5.1.1.
  - [Release notes](https://github.com/chaijs/chai/releases)
  - [Changelog](https://github.com/chaijs/chai/blob/main/History.md)
  - [Commits](https://github.com/chaijs/chai/compare/v4.3.10...v5.1.1)

  ---
  updated-dependencies:
  - dependency-name: chai
    dependency-type: direct:development
    update-type: version-update:semver-major
  ...

  Signed-off-by: dependabot[bot] <support@github.com>

* fix(tests): Migrate all usage of chai to ESM (#8216)

* fix(tests): Migrate node tests from CJS to ESM

  This allows us to import (rather than require) chai, fixing failures
  caused by that package dropping suppport for CJS in chai v5.0.0.

* fix(tests): Have mocha tests directly import chai

  Previously they relied on obtaining it from the global scope, but it's
  better if imports are explicit.

* fix(tests): Remove broken load of chai as script

  Chai v5.0.0 no longer supports being loaded as a script, so this did
  nothing but emit an syntax error message on the console.

* fix(tests): Migrate browser tests from CJS to ESM

  This allows us to import (rather than require) chai, fixing failures
  caused by chai no longer supporting CJS.

* chore(tests): format

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Christopher Allen <cpcallen+git@google.com>
2024-06-17 16:48:21 +01:00

667 lines
19 KiB
JavaScript

/**
* @license
* Copyright 2020 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {assert} from '../../node_modules/chai/chai.js';
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) {
assert.equal(checker.canConnectWithReason(one, two), reason);
// Order should not matter.
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) {
assert.isTrue(checker.doTypeChecks(one, two));
// Order should not matter.
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');
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.
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.
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.
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.
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.
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;
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;
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;
assert.isTrue(
this.checker.doDragChecks(
inputConnection,
this.blockB.outputConnection,
9000,
),
'Should connect to unconnected unmovable block',
);
});
});
});
});