Files
blockly/tests/browser/test/clipboard_test.mjs
Maribeth Moffatt 62f3b8914a chore: add tests for clipboard (#9254)
* chore: add tests for clipboard

* chore: clean up
2025-08-06 14:01:59 -07:00

612 lines
18 KiB
JavaScript

/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import * as chai from 'chai';
import {Key} from 'webdriverio';
import {
PAUSE_TIME,
clickWorkspace,
focusOnBlock,
getAllBlocks,
getBlockTypeFromWorkspace,
getCategory,
getSelectedBlockId,
getSelectedBlockType,
openMutatorForBlock,
testFileLocations,
testSetup,
} from './test_setup.mjs';
const testBlockJson = {
'blocks': {
'languageVersion': 0,
'blocks': [
{
'type': 'controls_repeat_ext',
'id': 'controls_repeat_1',
'x': 88,
'y': 88,
'inputs': {
'TIMES': {
'shadow': {
'type': 'math_number',
'id': 'math_number_shadow_1',
'fields': {
'NUM': 10,
},
},
},
'DO': {
'block': {
'type': 'controls_if',
'id': 'controls_if_1',
'inputs': {
'IF0': {
'block': {
'type': 'logic_boolean',
'id': 'logic_boolean_1',
'fields': {
'BOOL': 'TRUE',
},
},
},
'DO0': {
'block': {
'type': 'text_print',
'id': 'text_print_1',
'inputs': {
'TEXT': {
'shadow': {
'type': 'text',
'id': 'text_shadow_1',
'fields': {
'TEXT': 'abc',
},
},
},
},
},
},
},
},
},
},
},
],
},
};
async function loadStartBlocks(browser) {
await browser.execute((stringifiedJson) => {
// Hangs forever if the json isn't stringified ¯\_(ツ)_/¯
const testBlockJson = JSON.parse(stringifiedJson);
const workspace = Blockly.common.getMainWorkspace();
Blockly.serialization.workspaces.load(testBlockJson, workspace);
}, JSON.stringify(testBlockJson));
await browser.pause(PAUSE_TIME);
}
suite('Clipboard test', async function () {
// Setting timeout to unlimited as these tests take longer time to run
this.timeout(0);
// Clear the workspace and load start blocks
setup(async function () {
this.browser = await testSetup(testFileLocations.PLAYGROUND);
await this.browser.pause(PAUSE_TIME);
});
test('Paste block to/from main workspace', async function () {
await loadStartBlocks(this.browser);
// Select and copy the "true" block
await focusOnBlock(this.browser, 'logic_boolean_1');
await this.browser.pause(PAUSE_TIME);
await this.browser.keys([Key.Ctrl, 'c']);
await this.browser.pause(PAUSE_TIME);
// Check how many blocks there are before pasting
const allBlocksBeforePaste = await getAllBlocks(this.browser);
// Paste the block while still in the main workspace
await this.browser.keys([Key.Ctrl, 'v']);
await this.browser.pause(PAUSE_TIME);
// Check result
const allBlocksAfterPaste = await getAllBlocks(this.browser);
chai.assert.equal(
allBlocksAfterPaste.length,
allBlocksBeforePaste.length + 1,
'Expected there to be one additional block after paste',
);
const focusedBlockId = await getSelectedBlockId(this.browser);
chai.assert.notEqual(
focusedBlockId,
'logic_boolean_1',
'Newly pasted block should be selected',
);
const focusedBlockType = await getSelectedBlockType(this.browser);
chai.assert.equal(
focusedBlockType,
'logic_boolean',
'Newly pasted block should be selected',
);
});
test('Copying a block also copies and pastes its children', async function () {
await loadStartBlocks(this.browser);
// Select and copy the "if/else" block which has children
await focusOnBlock(this.browser, 'controls_if_1');
await this.browser.pause(PAUSE_TIME);
await this.browser.keys([Key.Ctrl, 'c']);
await this.browser.pause(PAUSE_TIME);
// Check how many blocks there are before pasting
const allBlocksBeforePaste = await getAllBlocks(this.browser);
// Paste the block while still in the main workspace
await this.browser.keys([Key.Ctrl, 'v']);
await this.browser.pause(PAUSE_TIME);
// Check result
const allBlocksAfterPaste = await getAllBlocks(this.browser);
chai.assert.equal(
allBlocksAfterPaste.length,
allBlocksBeforePaste.length + 4,
'Expected there to be four additional blocks after paste',
);
});
test('Paste shadow block to/from main workspace', async function () {
await loadStartBlocks(this.browser);
// Select and copy the shadow number block
await focusOnBlock(this.browser, 'math_number_shadow_1');
await this.browser.pause(PAUSE_TIME);
await this.browser.keys([Key.Ctrl, 'c']);
await this.browser.pause(PAUSE_TIME);
// Check how many blocks there are before pasting
const allBlocksBeforePaste = await getAllBlocks(this.browser);
// Paste the block while still in the main workspace
await this.browser.keys([Key.Ctrl, 'v']);
await this.browser.pause(PAUSE_TIME);
// Check result
const allBlocksAfterPaste = await getAllBlocks(this.browser);
chai.assert.equal(
allBlocksAfterPaste.length,
allBlocksBeforePaste.length + 1,
'Expected there to be one additional block after paste',
);
const focusedBlockId = await getSelectedBlockId(this.browser);
chai.assert.notEqual(
focusedBlockId,
'math_number_shadow_1',
'Newly pasted block should be selected',
);
const focusedBlockType = await getSelectedBlockType(this.browser);
chai.assert.equal(
focusedBlockType,
'math_number',
'Newly pasted block should be selected',
);
const focusedBlockIsShadow = await this.browser.execute(() => {
return Blockly.common.getSelected().isShadow();
});
chai.assert.isFalse(
focusedBlockIsShadow,
'Expected the pasted version of the block to not be a shadow block',
);
});
test('Copy block from flyout, paste to main workspace', async function () {
// Open flyout
await getCategory(this.browser, 'Logic').then((category) =>
category.click(),
);
// Focus on first block in flyout
await this.browser.execute(() => {
const ws = Blockly.getMainWorkspace().getFlyout().getWorkspace();
const block = ws.getBlocksByType('controls_if')[0];
Blockly.getFocusManager().focusNode(block);
});
await this.browser.pause(PAUSE_TIME);
// Copy
await this.browser.keys([Key.Ctrl, 'c']);
await this.browser.pause(PAUSE_TIME);
// Select the main workspace
await clickWorkspace(this.browser);
await this.browser.pause(PAUSE_TIME);
// Paste
await this.browser.keys([Key.Ctrl, 'v']);
await this.browser.pause(PAUSE_TIME);
// Check that the block is now on the workspace and selected
const allBlocks = await getAllBlocks(this.browser);
chai.assert.equal(
allBlocks.length,
1,
'Expected there to be one block on main workspace after paste from flyout',
);
const focusedBlockType = await getSelectedBlockType(this.browser);
chai.assert.equal(
focusedBlockType,
'controls_if',
'Newly pasted block should be selected',
);
});
test('Copy block from flyout, paste while flyout focused', async function () {
// Open flyout
await getCategory(this.browser, 'Logic').then((category) =>
category.click(),
);
// Focus on first block in flyout
await this.browser.execute(() => {
const ws = Blockly.getMainWorkspace().getFlyout().getWorkspace();
const block = ws.getBlocksByType('controls_if')[0];
Blockly.getFocusManager().focusNode(block);
});
await this.browser.pause(PAUSE_TIME);
// Copy
await this.browser.keys([Key.Ctrl, 'c']);
await this.browser.pause(PAUSE_TIME);
// Paste
await this.browser.keys([Key.Ctrl, 'v']);
await this.browser.pause(PAUSE_TIME);
// Check that the flyout is closed
const flyoutIsVisible = await this.browser
.$('.blocklyToolboxFlyout')
.then((elem) => elem.isDisplayed());
chai.assert.isFalse(flyoutIsVisible, 'Expected flyout to not be open');
// Check that the block is now on the main workspace and selected
const allBlocks = await getAllBlocks(this.browser);
chai.assert.equal(
allBlocks.length,
1,
'Expected there to be one block on main workspace after paste from flyout',
);
const focusedBlockType = await getSelectedBlockType(this.browser);
chai.assert.equal(
focusedBlockType,
'controls_if',
'Newly pasted block should be selected',
);
});
test('Copy block from mutator flyout, paste to mutator workspace', async function () {
// Load the start blocks
await loadStartBlocks(this.browser);
// Open the controls_if mutator
const block = await getBlockTypeFromWorkspace(
this.browser,
'controls_if',
0,
);
await openMutatorForBlock(this.browser, block);
// Select the first block in the mutator flyout
await this.browser.execute(
(blockId, mutatorBlockType) => {
const flyoutBlock = Blockly.getMainWorkspace()
.getBlockById(blockId)
.mutator.getWorkspace()
.getFlyout()
.getWorkspace()
.getBlocksByType(mutatorBlockType)[0];
Blockly.getFocusManager().focusNode(flyoutBlock);
},
'controls_if_1',
'controls_if_elseif',
);
await this.browser.pause(PAUSE_TIME);
// Copy
await this.browser.keys([Key.Ctrl, 'c']);
await this.browser.pause(PAUSE_TIME);
// Paste
await this.browser.keys([Key.Ctrl, 'v']);
await this.browser.pause(PAUSE_TIME);
// Check that the block is now in the mutator workspace and selected
const numberOfIfElseBlocks = await this.browser.execute(
(blockId, mutatorBlockType) => {
return Blockly.getMainWorkspace()
.getBlockById(blockId)
.mutator.getWorkspace()
.getBlocksByType(mutatorBlockType).length;
},
'controls_if_1',
'controls_if_elseif',
);
chai.assert.equal(
numberOfIfElseBlocks,
1,
'Expected there to be one if_else block in mutator workspace',
);
const focusedBlockType = await getSelectedBlockType(this.browser);
chai.assert.equal(
focusedBlockType,
'controls_if_elseif',
'Newly pasted block should be selected',
);
});
test('Copy block from mutator flyout, paste to main workspace while mutator open', async function () {
// Load the start blocks
await loadStartBlocks(this.browser);
// Open the controls_if mutator
const block = await getBlockTypeFromWorkspace(
this.browser,
'controls_if',
0,
);
await openMutatorForBlock(this.browser, block);
// Select the first block in the mutator flyout
await this.browser.execute(
(blockId, mutatorBlockType) => {
const flyoutBlock = Blockly.getMainWorkspace()
.getBlockById(blockId)
.mutator.getWorkspace()
.getFlyout()
.getWorkspace()
.getBlocksByType(mutatorBlockType)[0];
Blockly.getFocusManager().focusNode(flyoutBlock);
},
'controls_if_1',
'controls_if_elseif',
);
await this.browser.pause(PAUSE_TIME);
// Copy
await this.browser.keys([Key.Ctrl, 'c']);
await this.browser.pause(PAUSE_TIME);
// Click the main workspace
await clickWorkspace(this.browser);
// Paste
await this.browser.keys([Key.Ctrl, 'v']);
await this.browser.pause(PAUSE_TIME);
// Check that the block is now in the mutator workspace and selected
const numberOfIfElseBlocks = await this.browser.execute(
(blockId, mutatorBlockType) => {
return Blockly.getMainWorkspace()
.getBlockById(blockId)
.mutator.getWorkspace()
.getBlocksByType(mutatorBlockType).length;
},
'controls_if_1',
'controls_if_elseif',
);
chai.assert.equal(
numberOfIfElseBlocks,
1,
'Expected there to be one if_else block in mutator workspace',
);
const focusedBlockType = await getSelectedBlockType(this.browser);
chai.assert.equal(
focusedBlockType,
'controls_if_elseif',
'Newly pasted block should be selected',
);
// Check that there are no new blocks on the main workspace
const numberOfIfElseBlocksOnMainWorkspace = await this.browser.execute(
(mutatorBlockType) => {
return Blockly.getMainWorkspace().getBlocksByType(mutatorBlockType)
.length;
},
'controls_if_elseif',
);
chai.assert.equal(
numberOfIfElseBlocksOnMainWorkspace,
0,
'Mutator blocks should not appear on main workspace',
);
});
test('Copy block from mutator flyout, paste to main workspace while mutator closed', async function () {
// Load the start blocks
await loadStartBlocks(this.browser);
// Open the controls_if mutator
const block = await getBlockTypeFromWorkspace(
this.browser,
'controls_if',
0,
);
await openMutatorForBlock(this.browser, block);
// Select the first block in the mutator flyout
await this.browser.execute(
(blockId, mutatorBlockType) => {
const flyoutBlock = Blockly.getMainWorkspace()
.getBlockById(blockId)
.mutator.getWorkspace()
.getFlyout()
.getWorkspace()
.getBlocksByType(mutatorBlockType)[0];
Blockly.getFocusManager().focusNode(flyoutBlock);
},
'controls_if_1',
'controls_if_elseif',
);
await this.browser.pause(PAUSE_TIME);
// Copy
await this.browser.keys([Key.Ctrl, 'c']);
await this.browser.pause(PAUSE_TIME);
// Close the mutator flyout (calling this method on open mutator closes it)
await openMutatorForBlock(this.browser, block);
// Click the main workspace
await clickWorkspace(this.browser);
// Paste
await this.browser.keys([Key.Ctrl, 'v']);
await this.browser.pause(PAUSE_TIME);
// Check that there are no new blocks on the main workspace
const numberOfIfElseBlocksOnMainWorkspace = await this.browser.execute(
(mutatorBlockType) => {
return Blockly.getMainWorkspace().getBlocksByType(mutatorBlockType)
.length;
},
'controls_if_elseif',
);
chai.assert.equal(
numberOfIfElseBlocksOnMainWorkspace,
0,
'Mutator blocks should not appear on main workspace',
);
});
test('Copy workspace comment, paste to main workspace', async function () {
// Add a workspace comment to the workspace
await this.browser.execute(() => {
const workspace = Blockly.getMainWorkspace();
const json = {
'workspaceComments': [
{
'height': 100,
'width': 120,
'id': 'workspace_comment_1',
'x': 13,
'y': -12,
'text': 'This is a comment',
},
],
};
Blockly.serialization.workspaces.load(json, workspace);
});
await this.browser.pause(PAUSE_TIME);
// Select the workspace comment
await this.browser.execute(() => {
const comment = Blockly.getMainWorkspace().getCommentById(
'workspace_comment_1',
);
Blockly.getFocusManager().focusNode(comment);
});
await this.browser.pause(PAUSE_TIME);
// Copy
await this.browser.keys([Key.Ctrl, 'c']);
await this.browser.pause(PAUSE_TIME);
// Click the main workspace
await clickWorkspace(this.browser);
// Paste
await this.browser.keys([Key.Ctrl, 'v']);
await this.browser.pause(PAUSE_TIME);
// Check that there are 2 comments on the workspace
const numberOfComments = await this.browser.execute(() => {
return Blockly.getMainWorkspace().getTopComments().length;
});
chai.assert.equal(
numberOfComments,
2,
'Expected 2 workspace comments after pasting',
);
});
test('Cut block from main workspace, paste to main workspace', async function () {
await loadStartBlocks(this.browser);
// Select and cut the "true" block
await focusOnBlock(this.browser, 'logic_boolean_1');
await this.browser.pause(PAUSE_TIME);
await this.browser.keys([Key.Ctrl, 'x']);
await this.browser.pause(PAUSE_TIME);
// Check that the "true" block was deleted
const trueBlock = await this.browser.execute(() => {
return Blockly.getMainWorkspace().getBlockById('logic_boolean_1') ?? null;
});
chai.assert.isNull(trueBlock);
// Check how many blocks there are before pasting
const allBlocksBeforePaste = await getAllBlocks(this.browser);
// Paste the block while still in the main workspace
await this.browser.keys([Key.Ctrl, 'v']);
await this.browser.pause(PAUSE_TIME);
// Check result
const allBlocksAfterPaste = await getAllBlocks(this.browser);
chai.assert.equal(
allBlocksAfterPaste.length,
allBlocksBeforePaste.length + 1,
'Expected there to be one additional block after paste',
);
});
test('Cannot cut block from flyout', async function () {
// Open flyout
await getCategory(this.browser, 'Logic').then((category) =>
category.click(),
);
// Focus on first block in flyout
await this.browser.execute(() => {
const ws = Blockly.getMainWorkspace().getFlyout().getWorkspace();
const block = ws.getBlocksByType('controls_if')[0];
Blockly.getFocusManager().focusNode(block);
});
await this.browser.pause(PAUSE_TIME);
// Cut
await this.browser.keys([Key.Ctrl, 'x']);
await this.browser.pause(PAUSE_TIME);
// Select the main workspace
await clickWorkspace(this.browser);
await this.browser.pause(PAUSE_TIME);
// Paste
await this.browser.keys([Key.Ctrl, 'v']);
await this.browser.pause(PAUSE_TIME);
// Check that no block was pasted
const allBlocks = await getAllBlocks(this.browser);
chai.assert.equal(
allBlocks.length,
0,
'Expected no blocks in the workspace because nothing to paste',
);
});
});