Initial commit of 001code-html Scratch frontend project.

Includes scratch-gui, scratch-vm, scratch-blocks, scratch-render, scratch-l10n, and deployment config.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-16 15:37:45 +08:00
commit 6e0a1fbcbb
11350 changed files with 965674 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Unit Tests for Blockly Blocks</title>
<script src="../../blockly_compressed.js"></script>
<script src="../../msg/js/en.js"></script> <!-- TODO: Test messages in all languages. -->
<script src="../../blocks/colour.js"></script>
<script src="../../blocks/lists.js"></script>
<script src="../../blocks/logic.js"></script>
<script src="../../blocks/loops.js"></script>
<script src="../../blocks/math.js"></script>
<script src="../../blocks/procedures.js"></script>
<script src="../../blocks/text.js"></script>
<script src="../../blocks/variables.js"></script>
<script>goog.require('goog.testing.jsunit');</script>
</head>
<body>
<script src="logic_ternary_test.js"></script>
</body>
</html>

View File

@@ -0,0 +1,316 @@
/**
* @license
* Blockly Tests
*
* Copyright 2017 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
function test_logic_ternary_structure() {
var workspace = new Blockly.Workspace();
try {
var block = workspace.newBlock('logic_ternary');
assertEquals(3, block.inputList && block.inputList.length);
assertEquals(1, block.getInput('IF').connection.check_.length);
assertEquals('Boolean', block.getInput('IF').connection.check_[0]);
assertTrue(!!block.onchangeWrapper_); // Has onchange handler
} finally {
workspace.dispose();
}
}
function test_logic_ternary_attachSameTypeCheckInThenAndElseWithoutParent() {
var workspace = new Blockly.Workspace();
try {
var block = workspace.newBlock('logic_ternary');
var string1 = workspace.newBlock('text');
var string2 = workspace.newBlock('text_charAt');
block.getInput('THEN').connection.connect(string1.outputConnection);
Blockly.Events.fireNow_(); // Force synchronous onchange() call.
assertEquals(block, string1.getRootBlock());
block.getInput('ELSE').connection.connect(string2.outputConnection);
Blockly.Events.fireNow_(); // Force synchronous onchange() call.
assertEquals(block, string1.getRootBlock()); // Still connected.
assertEquals(block, string2.getRootBlock());
} finally {
workspace.dispose();
}
}
function test_logic_ternary_attachDifferectTypeChecksInThenAndElseWithoutParent() {
var workspace = new Blockly.Workspace();
try {
var block = workspace.newBlock('logic_ternary');
var string = workspace.newBlock('text');
var number = workspace.newBlock('math_number');
block.getInput('THEN').connection.connect(string.outputConnection);
Blockly.Events.fireNow_(); // Force synchronous onchange() call.
assertEquals(block, string.getRootBlock());
block.getInput('ELSE').connection.connect(number.outputConnection);
Blockly.Events.fireNow_(); // Force synchronous onchange() call.
assertEquals(block, string.getRootBlock()); // Input THEN still connected.
assertEquals(block, number.getRootBlock());
} finally {
workspace.dispose();
}
}
function test_logic_ternary_attachSameTypeCheckInThenAndElseWithMatchingParent() {
var workspace = new Blockly.Workspace();
try {
var block = workspace.newBlock('logic_ternary');
var parent = workspace.newBlock('text_trim');
parent.getInput('TEXT').connection.connect(block.outputConnection);
assertEquals(parent, block.getRootBlock());
var string1 = workspace.newBlock('text');
var string2 = workspace.newBlock('text_charAt');
block.getInput('THEN').connection.connect(string1.outputConnection);
Blockly.Events.fireNow_(); // Force synchronous onchange() call.
assertEquals(parent, block.getRootBlock()); // Still connected to parent.
assertEquals(parent, string1.getRootBlock());
block.getInput('ELSE').connection.connect(string2.outputConnection);
Blockly.Events.fireNow_(); // Force synchronous onchange() call.
assertEquals(parent, block.getRootBlock()); // Still connected to parent.
assertEquals(parent, string1.getRootBlock()); // Input THEN still connected.
assertEquals(parent, string2.getRootBlock());
} finally {
workspace.dispose();
}
}
function test_logic_ternary_attachDifferectTypeChecksInThenAndElseWithUncheckedParent() {
var workspace = new Blockly.Workspace();
try {
var block = workspace.newBlock('logic_ternary');
var parent = workspace.newBlock('text_print');
parent.getInput('TEXT').connection.connect(block.outputConnection);
assertEquals(parent, block.parentBlock_);
var string = workspace.newBlock('text');
var number = workspace.newBlock('math_number');
block.getInput('THEN').connection.connect(string.outputConnection);
Blockly.Events.fireNow_(); // Force synchronous onchange() call.
assertEquals(parent, block.getRootBlock()); // Still connected to parent.
assertEquals(parent, string.getRootBlock());
block.getInput('ELSE').connection.connect(number.outputConnection);
Blockly.Events.fireNow_(); // Force synchronous onchange() call.
assertEquals(parent, block.getRootBlock()); // Still connected to parent.
assertEquals(parent, string.getRootBlock()); // Input THEN still connected.
assertEquals(parent, number.getRootBlock());
} finally {
workspace.dispose();
}
}
function test_logic_ternary_attachDifferectTypeChecksInThenAndElseWithPermissiveParent() {
var workspace = new Blockly.Workspace();
try {
var block = workspace.newBlock('logic_ternary');
var parent = workspace.newBlock('text_length'); // Allows String or Array
parent.getInput('VALUE').connection.connect(block.outputConnection);
assertEquals(parent, block.parentBlock_);
var string = workspace.newBlock('text');
var array = workspace.newBlock('lists_create_empty');
block.getInput('THEN').connection.connect(string.outputConnection);
Blockly.Events.fireNow_(); // Force synchronous onchange() call.
assertEquals(parent, block.getRootBlock()); // Still connected to parent.
assertEquals(parent, string.getRootBlock());
block.getInput('ELSE').connection.connect(array.outputConnection);
Blockly.Events.fireNow_(); // Force synchronous onchange() call.
assertEquals(parent, block.getRootBlock()); // Still connected to parent.
assertEquals(parent, string.getRootBlock()); // Input THEN still connected.
assertEquals(parent, array.getRootBlock());
} finally {
workspace.dispose();
}
}
function test_logic_ternary_attachMismatchTypeToThen_breakWithParent() {
var workspace = new Blockly.Workspace();
try {
var block = workspace.newBlock('logic_ternary');
var parent = workspace.newBlock('text_length'); // Allows String or Array
parent.getInput('VALUE').connection.connect(block.outputConnection);
Blockly.Events.fireNow_(); // Force synchronous onchange() call.
assertEquals(parent, block.parentBlock_);
var string = workspace.newBlock('text');
var number = workspace.newBlock('math_number');
block.getInput('ELSE').connection.connect(string.outputConnection);
Blockly.Events.fireNow_(); // Force synchronous onchange() call.
assertEquals(parent, block.getRootBlock()); // Still connected to parent.
assertEquals(parent, string.getRootBlock());
// Adding mismatching number.
block.getInput('THEN').connection.connect(number.outputConnection);
Blockly.Events.fireNow_(); // Force synchronous onchange() call.
assertEquals(block, block.getRootBlock()); // Disconnected from parent.
assertEquals(block, number.getRootBlock());
assertEquals(block, string.getRootBlock()); // ELSE string still connected.
} finally {
workspace.dispose();
}
}
function test_logic_ternary_attachMismatchTypeToElse_breakWithParent() {
var workspace = new Blockly.Workspace();
try {
var block = workspace.newBlock('logic_ternary');
var parent = workspace.newBlock('text_length'); // Allows String or Array
parent.getInput('VALUE').connection.connect(block.outputConnection);
Blockly.Events.fireNow_(); // Force synchronous onchange() call.
assertEquals(parent, block.parentBlock_);
var string = workspace.newBlock('text');
var number = workspace.newBlock('math_number');
block.getInput('THEN').connection.connect(string.outputConnection);
Blockly.Events.fireNow_(); // Force synchronous onchange() call.
assertEquals(parent, block.getRootBlock()); // Still connected to parent.
assertEquals(parent, string.getRootBlock());
// Adding mismatching number.
block.getInput('ELSE').connection.connect(number.outputConnection);
Blockly.Events.fireNow_(); // Force synchronous onchange() call.
assertEquals(block, block.getRootBlock()); // Disconnected from parent.
assertEquals(block, number.getRootBlock());
assertEquals(block, string.getRootBlock()); // THEN string still connected.
} finally {
workspace.dispose();
}
}
function test_logic_ternary_attachToUncheckedParentWithDifferentTypes() {
var workspace = new Blockly.Workspace();
try {
var block = workspace.newBlock('logic_ternary');
var string = workspace.newBlock('text');
var number = workspace.newBlock('math_number');
block.getInput('THEN').connection.connect(string.outputConnection);
Blockly.Events.fireNow_(); // Force synchronous onchange() call.
assertEquals(block, string.getRootBlock());
block.getInput('ELSE').connection.connect(number.outputConnection);
Blockly.Events.fireNow_(); // Force synchronous onchange() call.
assertEquals(block, string.getRootBlock()); // Input THEN still connected.
assertEquals(block, number.getRootBlock());
// Attaching to parent.
var parent = workspace.newBlock('text_print');
parent.getInput('TEXT').connection.connect(block.outputConnection);
assertEquals(parent, block.getRootBlock());
assertEquals(parent, string.getRootBlock()); // Input THEN still connected.
assertEquals(parent, number.getRootBlock()); // Input ELSE still connected.
} finally {
workspace.dispose();
}
}
function test_logic_ternary_attachToPermissiveParentWithDifferentTypes() {
var workspace = new Blockly.Workspace();
try {
var block = workspace.newBlock('logic_ternary');
var string = workspace.newBlock('text');
var array = workspace.newBlock('lists_create_empty');
block.getInput('THEN').connection.connect(string.outputConnection);
Blockly.Events.fireNow_(); // Force synchronous onchange() call.
assertEquals(block, string.getRootBlock());
block.getInput('ELSE').connection.connect(array.outputConnection);
Blockly.Events.fireNow_(); // Force synchronous onchange() call.
assertEquals(block, string.getRootBlock()); // Input THEN still connected.
assertEquals(block, array.getRootBlock());
// Attaching to parent.
var parent = workspace.newBlock('text_print');
parent.getInput('TEXT').connection.connect(block.outputConnection);
Blockly.Events.fireNow_(); // Force synchronous onchange() call.
assertEquals(parent, block.getRootBlock());
assertEquals(parent, string.getRootBlock()); // Input THEN still connected.
assertEquals(parent, array.getRootBlock()); // Input ELSE still connected.
} finally {
workspace.dispose();
}
}
function test_logic_ternary_attachToParentWithMismatchingThen_disconnectThen() {
var workspace = new Blockly.Workspace();
try {
var block = workspace.newBlock('logic_ternary');
var number = workspace.newBlock('math_number');
var string = workspace.newBlock('text');
block.getInput('THEN').connection.connect(number.outputConnection);
Blockly.Events.fireNow_(); // Force synchronous onchange() call.
assertEquals(block, number.getRootBlock());
block.getInput('ELSE').connection.connect(string.outputConnection);
Blockly.Events.fireNow_(); // Force synchronous onchange() call.
assertEquals(block, number.getRootBlock()); // Input THEN still connected.
assertEquals(block, string.getRootBlock());
// Attaching to parent.
var parent = workspace.newBlock('text_trim');
parent.getInput('TEXT').connection.connect(block.outputConnection);
Blockly.Events.fireNow_(); // Force synchronous onchange() call.
assertEquals(parent, block.getRootBlock()); // Successful connection to parent.
assertEquals(parent, string.getRootBlock()); // Input ELSE still connected.
assertEquals(number, number.getRootBlock()); // Input THEN disconnected.
} finally {
workspace.dispose();
}
}
function test_logic_ternary_attachToParentWithMismatchingElse_disconnectElse() {
var workspace = new Blockly.Workspace();
try {
var block = workspace.newBlock('logic_ternary');
var string = workspace.newBlock('text');
var number = workspace.newBlock('math_number');
block.getInput('THEN').connection.connect(string.outputConnection);
Blockly.Events.fireNow_(); // Force synchronous onchange() call.
assertEquals(block, string.getRootBlock());
block.getInput('ELSE').connection.connect(number.outputConnection);
Blockly.Events.fireNow_(); // Force synchronous onchange() call.
assertEquals(block, string.getRootBlock()); // Input THEN still connected.
assertEquals(block, number.getRootBlock());
// Attaching to parent.
var parent = workspace.newBlock('text_trim');
parent.getInput('TEXT').connection.connect(block.outputConnection);
Blockly.Events.fireNow_(); // Force synchronous onchange() call.
assertEquals(parent, block.getRootBlock()); // Successful connection to parent.
assertEquals(parent, string.getRootBlock()); // Input THEN still connected.
assertEquals(number, number.getRootBlock()); // Input ELSE disconnected.
} finally {
workspace.dispose();
}
}

View File

@@ -0,0 +1,239 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Custom Procedure Playground</title>
<script src="../blockly_uncompressed_vertical.js"></script>
<script src="../msg/messages.js"></script>
<script src="../blocks_vertical/vertical_extensions.js"></script>
<script src="../blocks_common/math.js"></script>
<script src="../blocks_common/text.js"></script>
<script src="../blocks_common/colour.js"></script>
<script src="../blocks_vertical/control.js"></script>
<script src="../blocks_vertical/event.js"></script>
<script src="../blocks_vertical/motion.js"></script>
<script src="../blocks_vertical/looks.js"></script>
<script src="../blocks_vertical/procedures.js"></script>
<script src="../blocks_vertical/operators.js"></script>
<script src="../blocks_vertical/pen.js"></script>
<script src="../blocks_vertical/sound.js"></script>
<script src="../blocks_vertical/sensing.js"></script>
<script src="../blocks_vertical/data.js"></script>
<style>
body {
background-color: #fff;
font-family: sans-serif;
}
h1 {
font-weight: normal;
font-size: 140%;
}
</style>
</head>
<body>
<table width="100%">
<tr>
<td>
<div id="secondaryDiv" style="height: 480px; width: 600px;"></div>
</td>
<td>
<div id="primaryDiv" style="height: 480px; width: 600px;"></div>
</td>
</tr>
<tr>
<td></td>
<td id="editor-actions">
<button id="text_number" onclick="addTextNumber()">Add text/number input</button>
<button id="boolean" onclick="addBoolean()">Add boolean input</button>
<button id="label" onclick="addLabel()">Add label</button>
<button id="cancelButton" onclick="cancel()">cancel</button>
<button id="okButton" onclick="applyMutation()">ok</button>
<button id="rndButton" onclick="removeRandomInput()">remove random input</button>
<button id="addRndButton" onclick="addRandomInput()">add random input</button>
</td>
</tr>
</table>
<xml id="mutator_blocks" style="display:none">
<block type="procedures_declaration" x="25" y="25" deletable="false">
<mutation proccode="say %s %n times if %b" argumentnames="[&quot;something&quot;,&quot;this many&quot;,&quot;this is true&quot;]" argumentdefaults="[&quot;&quot;,1,false]" warp="false" argumentids="[&quot;a42&quot;, &quot;b23&quot;, &quot;c99&quot;]"/>
</block>
</xml>
<xml id="main_ws_blocks" style="display:none">
<block id="]){{Y!7N9ezN+j@Vr`8p" type="procedures_definition" x="25" y="25">
<statement name="custom_block">
<shadow type="procedures_prototype" id="caller_internal">
<mutation proccode="say %s %n times if %b" argumentnames="[&quot;something&quot;,&quot;this many&quot;,&quot;this is true&quot;]" argumentdefaults="[&quot;&quot;,1,false]" warp="false" argumentids="[&quot;a42&quot;, &quot;b23&quot;, &quot;c99&quot;]"/>
</shadow>
</statement>
<next>
<block id="caller_external" type="procedures_call">
<mutation proccode="say %s %n times if %b" argumentnames="[&quot;something&quot;,&quot;this many&quot;,&quot;this is true&quot;]" argumentdefaults="[&quot;&quot;,1,false]" warp="false" argumentids="[&quot;a42&quot;, &quot;b23&quot;, &quot;c99&quot;]"/>
<value name="input0">
<shadow id="V0~M:TxRk0Ua%osjGzh," type="text">
<field name="TEXT"/></field>
</shadow>
</value>
<value name="input1">
<shadow id="uPx3si(KkbL1)-XpbXoQ" type="math_number">
<field name="NUM">1</field>
</shadow>
</value>
</block>
</next>
</block>
</xml>
<xml id="main_ws_blocks_simpler" style="display:none">
<block id="]){{Y!7N9ezN+j@Vr`8p" type="procedures_definition" x="25" y="25">
<statement name="custom_block">
<shadow type="procedures_prototype" id="caller_internal">
<mutation proccode="say %s %n times if %b" argumentnames="[&quot;something&quot;,&quot;this many&quot;,&quot;this is true&quot;]" argumentdefaults="[&quot;&quot;,1,false]" warp="false" argumentids="[&quot;a42&quot;, &quot;b23&quot;, &quot;c99&quot;]"/>
</shadow>
</statement>
<next>
<block id="caller_external" type="procedures_call">
</block>
</next>
</block>
</xml>
<xml id="main_ws_blocks_simplest" style="display:none">
<block id="]){{Y!7N9ezN+j@Vr`8p" type="procedures_definition" x="25" y="25">
<statement name="custom_block">
<shadow type="procedures_prototype" id="caller_internal">
<mutation proccode="my first function" argumentnames="[]" argumentdefaults="[]" warp="false" argumentids="[]"/>
</shadow>
</statement>
<next>
<block id="caller_external" type="procedures_call">
<mutation proccode="my first function" argumentnames="[]" argumentdefaults="[]" warp="false" argumentids="[]"/>
</block>
</next>
</block>
</xml>
<xml id="mutator_blocks_simplest" style="display:none">
<block type="procedures_declaration" x="25" y="25" deletable="false">
<mutation proccode="my first function" argumentnames="[]" argumentdefaults="[]" warp="false" argumentids="[]"/>
</block>
</xml>
<xml id="toolbox" style="display:none">
<category name="More" colour="#FF6680" secondaryColour="#FF4D6A" custom="PROCEDURE">
</category>
<category name="Values">
<block type="operator_round">
<value name="NUM">
<shadow type="math_number">
<field name="NUM"></field>
</shadow>
</value>
</block>
</category>
<category name="Statements">
<block type="control_forever"></block>
<block type="sound_stopallsounds"></block>
<block type="control_start_as_clone"></block>
<block type="control_stop">
<mutation hasnext="false"></mutation>
<field name="STOP_OPTION">all</field>
</block>
</category>
</xml>
<script>
var editorActions = document.getElementById('editor-actions');
editorActions.style.visibility = 'hidden';
var callback = null;
var mutationRoot = null;
var declarationWorkspace = Blockly.inject('primaryDiv',
{media: '../media/'});
declarationWorkspace.addChangeListener(function() {
if (mutationRoot) {
mutationRoot.onChangeFn();
}
});
var definitionWorkspace = Blockly.inject('secondaryDiv', {
media: '../media/',
toolbox: document.getElementById('toolbox'),
zoom: {
startScale: 0.75
}
});
Blockly.Xml.clearWorkspaceAndLoadFromXml(document.getElementById('main_ws_blocks_simplest'),
definitionWorkspace);
Blockly.Procedures.externalProcedureDefCallback = function (mutation, cb) {
editorActions.style.visibility = 'visible';
callback = cb;
declarationWorkspace.clear();
mutationRoot = declarationWorkspace.newBlock('procedures_declaration');
mutationRoot.domToMutation(mutation);
mutationRoot.initSvg();
mutationRoot.render(false);
}
function applyMutation() {
var mutation = mutationRoot.mutationToDom(/* opt_generateShadows */ true)
console.log(mutation);
callback(mutation);
callback = null;
mutationRoot = null;
declarationWorkspace.clear();
definitionWorkspace.refreshToolboxSelection_()
editorActions.style.visibility = 'hidden';
}
function addLabel() {
mutationRoot.addLabelExternal();
}
function addBoolean() {
mutationRoot.addBooleanExternal();
}
function addTextNumber() {
mutationRoot.addStringNumberExternal();
}
function removeRandomInput() {
var rnd = Math.floor(Math.random() * mutationRoot.inputList.length);
mutationRoot.removeInput(mutationRoot.inputList[rnd].name);
mutationRoot.onChangeFn();
mutationRoot.updateDisplay_();
}
function addRandomInput() {
var rnd = Math.floor(Math.random() * 3);
switch (rnd) {
case 0:
addTextNumber();
break;
case 1:
addLabel();
break;
case 2:
addBoolean();
break;
}
}
function cancel() {
callback = null;
mutationRoot = null;
declarationWorkspace.clear();
definitionWorkspace.refreshToolboxSelection_()
editorActions.style.visibility = 'hidden';
}
</script>
</body>
</html>

View File

@@ -0,0 +1,421 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Horizontal Playground</title>
<script src="../blockly_uncompressed_horizontal.js"></script>
<script src="../msg/messages.js"></script>
<script src="../blocks_common/math.js"></script>
<script src="../blocks_common/text.js"></script>
<script src="../blocks_horizontal/control.js"></script>
<script src="../blocks_horizontal/event.js"></script>
<script src="../blocks_horizontal/wedo.js"></script>
<script src="../blocks_horizontal/default_toolbox.js"></script>
<script>
'use strict';
var fakeDragStack = [];
var workspace = null;
function start() {
var soundsEnabled = null;
if (sessionStorage) {
// Restore sounds state.
soundsEnabled = sessionStorage.getItem('soundsEnabled');
if (soundsEnabled === null) {
soundsEnabled = true;
} else {
soundsEnabled = (soundsEnabled === 'true');
}
} else {
soundsEnabled = true;
}
setSoundsEnabled(soundsEnabled);
// Setup blocks
// Parse the URL arguments.
var match = location.search.match(/dir=([^&]+)/);
var rtl = match && match[1] == 'rtl';
document.forms.options.elements.dir.selectedIndex = Number(rtl);
var toolbox = getToolboxElement();
document.forms.options.elements.toolbox.selectedIndex =
toolbox ? 1: 0;
match = location.search.match(/side=([^&]+)/);
var side = match ? match[1] : 'start';
document.forms.options.elements.side.value = side;
workspace = Blockly.inject('blocklyDiv', {
comments: false,
disable: false,
collapse: false,
media: '../media/',
readOnly: false,
rtl: rtl,
scrollbars: true,
toolbox: toolbox,
trashcan: true,
horizontalLayout: side == 'top' || side == 'bottom',
toolboxPosition: side == 'top' || side == 'start' ? 'start' : 'end',
sounds: soundsEnabled,
grid: {spacing: 16,
length: 1,
colour: '#2C344A',
snap: false
},
zoom: {
controls: true,
wheel: true,
startScale: 1.0,
maxScale: 4,
minScale: 0.25,
scaleSpeed: 1.1
},
colours: {
fieldShadow: 'rgba(255, 255, 255, 0.3)',
dragShadowOpacity: 0.6
}
});
if (sessionStorage) {
// Restore previously displayed text.
var text = sessionStorage.getItem('textarea');
if (text) {
document.getElementById('importExport').value = text;
}
taChange();
// Restore event logging state.
var state = sessionStorage.getItem('logEvents');
logEvents(Boolean(state));
// Restore flyout event logging state.
state = sessionStorage.getItem('logFlyoutEvents');
logFlyoutEvents(Boolean(state));
}
}
function getToolboxElement() {
var match = location.search.match(/toolbox=([^&]+)/);
return document.getElementById('toolbox-' + (match ? match[1] : 'categories'));
}
function toXml() {
var output = document.getElementById('importExport');
var xml = Blockly.Xml.workspaceToDom(workspace);
output.value = Blockly.Xml.domToPrettyText(xml);
output.focus();
output.select();
taChange();
}
function fromXml() {
var input = document.getElementById('importExport');
var xml = Blockly.Xml.textToDom(input.value);
Blockly.Xml.domToWorkspace(workspace, xml);
taChange();
}
// Disable the "Import from XML" button if the XML is invalid.
// Preserve text between page reloads.
function taChange() {
var textarea = document.getElementById('importExport');
if (sessionStorage) {
sessionStorage.setItem('textarea', textarea.value);
}
var valid = true;
try {
Blockly.Xml.textToDom(textarea.value);
} catch (e) {
valid = false;
}
document.getElementById('import').disabled = !valid;
}
function logEvents(state) {
var checkbox = document.getElementById('logCheck');
checkbox.checked = state;
if (sessionStorage) {
sessionStorage.setItem('logEvents', state ? 'checked' : '');
}
if (state) {
workspace.addChangeListener(logger);
} else {
workspace.removeChangeListener(logger);
}
}
function logFlyoutEvents(state) {
var checkbox = document.getElementById('logFlyoutCheck');
checkbox.checked = state;
if (sessionStorage) {
sessionStorage.setItem('logFlyoutEvents', state ? 'checked' : '');
}
var flyoutWorkspace = (workspace.flyout_) ? workspace.flyout_.workspace_ :
workspace.toolbox_.flyout_.workspace_;
if (state) {
flyoutWorkspace.addChangeListener(logger);
} else {
flyoutWorkspace.removeChangeListener(logger);
}
}
function logger(e) {
console.log(e);
}
function glowBlock() {
if (Blockly.selected) {
workspace.glowBlock(Blockly.selected.id, true);
}
}
function unglowBlock() {
if (Blockly.selected) {
workspace.glowBlock(Blockly.selected.id, false);
}
}
function glowStack() {
if (Blockly.selected) {
workspace.glowStack(Blockly.selected.id, true);
}
}
function unglowStack() {
if (Blockly.selected) {
workspace.glowStack(Blockly.selected.id, false);
}
}
function sprinkles(n) {
var prototypes = [];
var toolbox = workspace.options.languageTree;
if (!toolbox) {
console.error('Toolbox not found; add a toolbox element to the DOM.');
return;
}
var blocks = toolbox.getElementsByTagName('block');
for (var i = 0; i < n; i++) {
var blockXML = blocks[Math.floor(Math.random() * blocks.length)];
var block = Blockly.Xml.domToBlock(blockXML, workspace);
block.initSvg();
block.moveBy(
Math.round(Math.random() * 450 + 40),
Math.round(Math.random() * 600 + 40)
);
}
}
function spaghetti(n) {
var xml = spaghettiXml;
for(var i = 0; i < n; i++) {
xml = xml.replace(/(<(statement|next)( name="SUBSTACK")?>)<\//g,
'$1' + spaghettiXml + '</');
}
xml = '<xml xmlns="http://www.w3.org/1999/xhtml">' + xml + '</xml>';
var dom = Blockly.Xml.textToDom(xml);
console.time('Spaghetti domToWorkspace');
Blockly.Xml.domToWorkspace(workspace, dom);
console.timeEnd('Spaghetti domToWorkspace');
}
var spaghettiXml = [
' <block type="control_repeat">',
' <value name="TIMES">',
' <shadow type="math_whole_number">',
' <field name="NUM">10</field>',
' </shadow>',
' </value>',
' <statement name="SUBSTACK"></statement>',
' <next></next>',
' </block>'
].join('\n');
function setSoundsEnabled(state) {
var checkbox = document.getElementById('soundsEnabled');
checkbox.checked = (state) ? 'checked' : '';
if (sessionStorage) {
sessionStorage.setItem('soundsEnabled', state);
}
}
function fakeDrag(id, dx, dy, opt_workspace) {
var ws = opt_workspace || Blockly.getMainWorkspace();
var blockToDrag = ws.getBlockById(id);
if (!blockToDrag) {
fakeDragWrapper();
return;
}
var blockTop = blockToDrag.svgGroup_.getBoundingClientRect().top;
var blockLeft = blockToDrag.svgGroup_.getBoundingClientRect().left;
// Click somewhere on the block.
var mouseDownEvent = new MouseEvent('mousedown',
{clientX: blockLeft + 5, clientY: blockTop + 5});
blockToDrag.onMouseDown_(mouseDownEvent);
// Throw in a move for good measure.
setTimeout(
function() {
var mouseMoveEvent = new MouseEvent('mousemove',
{clientX: blockLeft + dx,
clientY: blockTop + dy});
blockToDrag.onMouseMove_(mouseMoveEvent);
// Drop at dx, dy.
setTimeout(
function() {
var mouseUpEvent = new MouseEvent('mouseup',
{clientX: blockLeft + dx,
clientY: blockTop + dy});
blockToDrag.onMouseUp_(mouseUpEvent);
setTimeout(fakeDragWrapper(), 100);
}, 30);
}, 30);
};
function fakeDragWrapper() {
var dragInfo = fakeDragStack.pop();
if (dragInfo) {
fakeDrag(dragInfo.id, dragInfo.dx, dragInfo.dy, dragInfo.workspace);
}
}
function fakeManyDrags() {
var blockList = workspace.getAllBlocks();
for (var i = 0; i < 2 * blockList.length; i++) {
fakeDragStack.push(
{
id: blockList[Math.round(Math.random() * (blockList.length - 1))].id,
// Move some blocks up and to the left, but mostly down and to the right.
dx: Math.round((Math.random() - 0.25) * 200),
dy: Math.round((Math.random() - 0.25) * 200),
workspace: workspace
});
}
fakeDragWrapper();
}
</script>
<style>
html, body {
height: 100%;
}
body {
background-color: #fff;
font-family: sans-serif;
overflow: hidden;
}
h1 {
font-weight: normal;
font-size: 140%;
}
#blocklyDiv {
float: right;
height: 95%;
width: 70%;
}
#collaborators {
float: right;
width: 30px;
margin-left: 10px;
}
#collaborators > img {
margin-right: 5px;
height: 30px;
padding-bottom: 5px;
width: 30px;
border-radius: 3px;
}
#importExport {
font-family: monospace;
}
</style>
</head>
<body onload="start()">
<div id="collaborators"></div>
<div id="blocklyDiv"></div>
<!-- Sidebar -->
<h1>Horizontal Blocks</h1>
<p>
<a href="javascript:void(workspace.setVisible(true))">Show</a>
- <a href="javascript:void(workspace.setVisible(false))">Hide</a>
</p>
<form id="options">
<select name="dir" onchange="document.forms.options.submit()">
<option value="ltr">LTR</option>
<option value="rtl">RTL</option>
</select>
<select name="toolbox" onchange="document.forms.options.submit()">
<option value="categories">Categories</option>
<option value="simple">Simple</option>
</select>
<select name="side" onchange="document.forms.options.submit()">
<option value="start">Start</option>
<option value="end">End</option>
<option value="top">Top</option>
<option value="bottom">Bottom</option>
</select>
</form>
<p>
<input type="button" value="Export to XML" onclick="toXml()">
&nbsp;
<input type="button" value="Import from XML" onclick="fromXml()" id="import">
<br>
<textarea id="importExport" style="width: 26%; height: 12em"
onchange="taChange();" onkeyup="taChange()"></textarea>
</p>
<p>
Log events: &nbsp;
<input type="checkbox" onclick="logEvents(this.checked)" id="logCheck">
</p>
<p>
Log flyout events: &nbsp;
<input type="checkbox" onclick="logFlyoutEvents(this.checked)" id="logFlyoutCheck">
</p>
<p>
Enable sounds (after refresh): &nbsp;
<input type="checkbox" onclick="setSoundsEnabled(this.checked)" id="soundsEnabled">
</p>
<p>
Stress test: &nbsp;
<input type="button" value="Sprinkles!" onclick="sprinkles(100)">
<input type="button" value="Spaghetti!" onclick="spaghetti(10)">
<input type="button" value="Fake some drags!" onclick="fakeManyDrags()">
</p>
<p>
Glows: &nbsp;
<input type="button" value="Glow last clicked block" onclick="glowBlock()" />
<input type="button" value="Unglow last clicked block" onclick="unglowBlock()" />
<input type="button" value="Stack glow last clicked block" onclick="glowStack()" />
<input type="button" value="Stack unglow last clicked block" onclick="unglowStack()" />
</p>
<p>
<input type="button" value="Undo" onclick="workspace.undo()" />
<input type="button" value="Redo" onclick="workspace.undo(true)" />
</p>
</body>
</html>

View File

@@ -0,0 +1,106 @@
/**
* @license
* Blockly Tests
*
* Copyright 2016 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
function test_appendField_FieldIconMenu() {
var workspace = new Blockly.Workspace();
var block_name = 'test_jsonInit_FieldIconMenu';
var field_name = 'TEST_FIELD';
var dropdown_options = [{
value: 'VALUE'
}];
Blockly.Blocks[block_name] = {
init: function() {
this.appendDummyInput()
.appendField(new Blockly.FieldIconMenu(dropdown_options),
field_name);
this.setOutput(true);
}
};
var block = workspace.newBlock(block_name);
assertTrue('IconMenu field not added to block by appendField',
block.getField(field_name) instanceof Blockly.FieldIconMenu);
}
function test_jsonInit_FieldIconMenu() {
var workspace = new Blockly.Workspace();
var block_name = 'test_jsonInit_FieldIconMenu';
var field_name = 'TEST_FIELD';
var dropdown_options = [{
value: 'VALUE'
}];
Blockly.Blocks[block_name] = {
init: function() {
this.jsonInit({
message0: '%1',
args0: [{
type: 'field_iconmenu',
name: field_name,
options: dropdown_options
}],
output: null
});
}
};
var block = workspace.newBlock(block_name);
assertTrue('IconMenu field not added to block by jsonInit',
block.getField(field_name) instanceof Blockly.FieldIconMenu);
}
function test_jsonInit_colors() {
var workspace = new Blockly.Workspace();
var block_name = 'test_jsonInit_FieldDropdown_colors';
var field_name = 'TEST_FIELD';
var dropdown_options = [
['value', 'VALUE']
];
Blockly.Blocks[block_name] = {
init: function() {
this.jsonInit({
message0: '%1',
args0: [{
type: 'field_dropdown',
name: field_name,
options: dropdown_options
}],
output: null,
colour: '#111111',
colourSecondary: '#222222',
colourTertiary: '#333333',
colourQuaternary: '#444444'
});
}
};
var block = workspace.newBlock(block_name);
var field = block.getField(field_name);
assertEquals('Block primary colour not set', block.getColour(), '#111111');
assertEquals('Block secondary colour not set', block.getColourSecondary(), '#222222');
assertEquals('Block tertiary colour not set', block.getColourTertiary(), '#333333');
assertEquals('Block quaternary colour not set', block.getColourQuaternary(), '#444444');
assertEquals('Source block is not correct', field.sourceBlock_, block);
}

View File

@@ -0,0 +1,310 @@
/**
* @license
* Blockly Tests
*
* Copyright 2015 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
function verify_DB_(msg, expected, db) {
var equal = (expected.length == db.connections_.length);
if (equal) {
for (var i = 0; i < expected.length; i++) {
if (expected[i] != db.connections_[i]) {
equal = false;
break;
}
}
}
if (equal) {
assertTrue(msg, true);
} else {
assertEquals(msg, expected, db.connections_);
}
}
function test_DB_addConnection() {
var db = new Blockly.ConnectionDB();
var o2 = {y_: 2, sourceBlock_: {},
getSourceBlock: Blockly.Connection.prototype.getSourceBlock};
db.addConnection(o2);
verify_DB_('Adding connection #2', [o2], db);
var o4 = {y_: 4, sourceBlock_: {},
getSourceBlock: Blockly.Connection.prototype.getSourceBlock};
db.addConnection(o4);
verify_DB_('Adding connection #4', [o2, o4], db);
var o1 = {y_: 1, sourceBlock_: {},
getSourceBlock: Blockly.Connection.prototype.getSourceBlock};
db.addConnection(o1);
verify_DB_('Adding connection #1', [o1, o2, o4], db);
var o3a = {y_: 3, sourceBlock_: {},
getSourceBlock: Blockly.Connection.prototype.getSourceBlock};
db.addConnection(o3a);
verify_DB_('Adding connection #3a', [o1, o2, o3a, o4], db);
var o3b = {y_: 3, sourceBlock_: {},
getSourceBlock: Blockly.Connection.prototype.getSourceBlock};
db.addConnection(o3b);
verify_DB_('Adding connection #3b', [o1, o2, o3b, o3a, o4], db);
}
function test_DB_removeConnection() {
var db = new Blockly.ConnectionDB();
var o1 = {y_: 1, sourceBlock_: {},
getSourceBlock: Blockly.Connection.prototype.getSourceBlock};
var o2 = {y_: 2, sourceBlock_: {},
getSourceBlock: Blockly.Connection.prototype.getSourceBlock};
var o3a = {y_: 3, sourceBlock_: {},
getSourceBlock: Blockly.Connection.prototype.getSourceBlock};
var o3b = {y_: 3, sourceBlock_: {},
getSourceBlock: Blockly.Connection.prototype.getSourceBlock};
var o3c = {y_: 3, sourceBlock_: {},
getSourceBlock: Blockly.Connection.prototype.getSourceBlock};
var o4 = {y_: 4, sourceBlock_: {},
getSourceBlock: Blockly.Connection.prototype.getSourceBlock};
db.addConnection(o1);
db.addConnection(o2);
db.addConnection(o3c);
db.addConnection(o3b);
db.addConnection(o3a);
db.addConnection(o4);
verify_DB_('Adding connections 1-4', [o1, o2, o3a, o3b, o3c, o4], db);
db.removeConnection_(o2);
verify_DB_('Removing connection #2', [o1, o3a, o3b, o3c, o4], db);
db.removeConnection_(o4);
verify_DB_('Removing connection #4', [o1, o3a, o3b, o3c], db);
db.removeConnection_(o1);
verify_DB_('Removing connection #1', [o3a, o3b, o3c], db);
db.removeConnection_(o3a);
verify_DB_('Removing connection #3a', [o3b, o3c], db);
db.removeConnection_(o3c);
verify_DB_('Removing connection #3c', [o3b], db);
db.removeConnection_(o3b);
verify_DB_('Removing connection #3b', [], db);
}
function test_DB_getNeighbours() {
var db = new Blockly.ConnectionDB();
// Search an empty list.
assertEquals(helper_getNeighbours(db,
10 /* x */, 10 /* y */, 100 /* radius */).length, 0);
// Set up some connections.
for (var i = 0; i < 10; i++) {
db.addConnection(helper_createConnection(0, i,
Blockly.PREVIOUS_STATEMENT, null, true));
}
// Test block belongs at beginning.
var result = helper_getNeighbours(db, 0, 0, 4);
assertEquals(5, result.length);
for (i = 0; i < result.length; i++) {
assertNotEquals(result.indexOf(db.connections_[i]), -1); // contains
}
// Test block belongs at middle.
result = helper_getNeighbours(db, 0, 4, 2);
assertEquals(5, result.length);
for (i = 0; i < result.length; i++) {
assertNotEquals(result.indexOf(db.connections_[i + 2]), -1); // contains
}
// Test block belongs at end.
result = helper_getNeighbours(db, 0, 9, 4);
assertEquals(5, result.length);
for (i = 0; i < result.length; i++) {
assertNotEquals(result.indexOf(db.connections_[i + 5]), -1); // contains
}
// Test block has no neighbours due to being out of range in the x direction.
result = helper_getNeighbours(db, 10, 9, 4);
assertEquals(result.length, 0);
// Test block has no neighbours due to being out of range in the y direction.
result = helper_getNeighbours(db, 0, 19, 4);
assertEquals(result.length, 0);
// Test block has no neighbours due to being out of range diagonally.
result = helper_getNeighbours(db, -2, -2, 2);
assertEquals(result.length, 0);
}
function test_DB_findPositionForConnection() {
var db = new Blockly.ConnectionDB();
db.addConnection(helper_createConnection(0, 0, Blockly.PREVIOUS_STATEMENT,
null, true));
db.addConnection(helper_createConnection(0, 1, Blockly.PREVIOUS_STATEMENT,
null, true));
db.addConnection(helper_createConnection(0, 2, Blockly.PREVIOUS_STATEMENT,
null, true));
db.addConnection(helper_createConnection(0, 4, Blockly.PREVIOUS_STATEMENT,
null, true));
db.addConnection(helper_createConnection(0, 5, Blockly.PREVIOUS_STATEMENT,
null, true));
assertEquals(5, db.connections_.length);
var conn = helper_createConnection(0, 3, Blockly.PREVIOUS_STATEMENT, null,
true);
assertEquals(3, db.findPositionForConnection_(conn));
}
function test_DB_findConnection() {
var db = new Blockly.ConnectionDB();
for (var i = 0; i < 10; i++) {
db.addConnection(helper_createConnection(i, 0,
Blockly.PREVIOUS_STATEMENT, null, true));
db.addConnection(helper_createConnection(0, i,
Blockly.PREVIOUS_STATEMENT, null, true));
}
var conn = helper_createConnection(3, 3, Blockly.PREVIOUS_STATEMENT, null,
true);
db.addConnection(conn);
assertEquals(conn, db.connections_[db.findConnection(conn)]);
conn = helper_createConnection(3, 3, Blockly.PREVIOUS_STATEMENT, null, true);
assertEquals(-1, db.findConnection(conn));
}
function test_DB_ordering() {
var db = new Blockly.ConnectionDB();
for (var i = 0; i < 10; i++) {
db.addConnection(helper_createConnection(0, 9 - i,
Blockly.PREVIOUS_STATEMENT), null, true);
}
for (i = 0; i < 10; i++) {
assertEquals(i, db.connections_[i].y_);
}
// quasi-random
var xCoords = [-29, -47, -77, 2, 43, 34, -59, -52, -90, -36, -91, 38, 87, -20,
60, 4, -57, 65, -37, -81, 57, 58, -96, 1, 67, -79, 34, 93, -90, -99, -62,
4, 11, -36, -51, -72, 3, -50, -24, -45, -92, -38, 37, 24, -47, -73, 79,
-20, 99, 43, -10, -87, 19, 35, -62, -36, 49, 86, -24, -47, -89, 33, -44,
25, -73, -91, 85, 6, 0, 89, -94, 36, -35, 84, -9, 96, -21, 52, 10, -95, 7,
-67, -70, 62, 9, -40, -95, -9, -94, 55, 57, -96, 55, 8, -48, -57, -87, 81,
23, 65];
var yCoords = [-81, 82, 5, 47, 30, 57, -12, 28, 38, 92, -25, -20, 23, -51, 73,
-90, 8, 28, -51, -15, 81, -60, -6, -16, 77, -62, -42, -24, 35, 95, -46,
-7, 61, -16, 14, 91, 57, -38, 27, -39, 92, 47, -98, 11, -33, -72, 64, 38,
-64, -88, -35, -59, -76, -94, 45, -25, -100, -95, 63, -97, 45, 98, 99, 34,
27, 52, -18, -45, 66, -32, -38, 70, -73, -23, 5, -2, -13, -9, 48, 74, -97,
-11, 35, -79, -16, -77, 83, -57, -53, 35, -44, 100, -27, -15, 5, 39, 33,
-19, -20, -95];
for (i = 0; i < xCoords.length; i++) {
db.addConnection(helper_createConnection(xCoords[i], yCoords[i],
Blockly.PREVIOUS_STATEMENT), null, true);
}
for (i = 1; i < xCoords.length; i++) {
assertTrue(db.connections_[i].y_ >= db.connections_[i - 1].y_);
}
}
function test_SearchForClosest() {
var db = new Blockly.ConnectionDB();
var sharedWorkspace = {id: "Shared workspace"};
// Search an empty list.
assertEquals(null, helper_searchDB(db, 10 /* x */, 10 /* y */,
100 /* radius */));
db.addConnection(helper_createConnection(100, 0, Blockly.PREVIOUS_STATEMENT,
sharedWorkspace, true));
assertEquals(null, helper_searchDB(db, 0, 0, 5, sharedWorkspace));
db = new Blockly.ConnectionDB();
for (var i = 0; i < 10; i++) {
var tempConn = helper_createConnection(0, i, Blockly.PREVIOUS_STATEMENT,
sharedWorkspace, true);
tempConn.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
db.addConnection(tempConn);
}
// Should be at 0, 9.
var last = db.connections_[db.connections_.length - 1];
// Correct connection is last in db; many connections in radius.
assertEquals(last, helper_searchDB(db, 0, 10, 15, sharedWorkspace));
// Nothing nearby.
assertEquals(null, helper_searchDB(db, 100, 100, 3, sharedWorkspace));
// First in db, exact match.
assertEquals(db.connections_[0], helper_searchDB(db, 0, 0, 0, sharedWorkspace));
tempConn = helper_createConnection(6, 6, Blockly.PREVIOUS_STATEMENT,
sharedWorkspace, true);
tempConn.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
db.addConnection(tempConn);
tempConn = helper_createConnection(5, 5, Blockly.PREVIOUS_STATEMENT,
sharedWorkspace, true);
tempConn.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
db.addConnection(tempConn);
var result = helper_searchDB(db, 4, 6, 3, sharedWorkspace);
assertEquals(5, result.x_);
assertEquals(5, result.y_);
}
function helper_getNeighbours(db, x, y, radius) {
return db.getNeighbours(helper_createConnection(x, y, Blockly.NEXT_STATEMENT,
null, true),
radius);
}
function helper_searchDB(db, x, y, radius, shared_workspace) {
var tempConn = helper_createConnection(x, y,
Blockly.NEXT_STATEMENT, shared_workspace, true);
tempConn.sourceBlock_ = helper_makeSourceBlock(shared_workspace);
tempConn.sourceBlock_.nextConnection = tempConn;
var closest = db.searchForClosest(tempConn, radius, {x: 0, y: 0});
return closest.connection;
}
function helper_makeSourceBlock(sharedWorkspace) {
return {workspace: sharedWorkspace,
parentBlock_: null,
getParent: function() { return null; },
movable_: true,
isMovable: function() { return true; },
isShadow: function() { return false; },
isInsertionMarker: function() { return false; },
getFirstStatementConnection: function() { return null; }
};
}
function helper_createConnection(x, y, type, opt_shared_workspace,
opt_rendered) {
var workspace = opt_shared_workspace ? opt_shared_workspace : {};
if (opt_rendered) {
var conn = new Blockly.RenderedConnection({workspace: workspace}, type);
} else {
var conn = new Blockly.Connection({workspace: workspace}, type);
}
conn.x_ = x;
conn.y_ = y;
return conn;
}

View File

@@ -0,0 +1,404 @@
/**
* @license
* Blockly Tests
*
* Copyright 2016 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Tests for connection logic.
* @author fenichel@google.com (Rachel Fenichel)
*/
'use strict';
var input;
var output;
var previous;
var next;
var dummyWorkspace;
function connectionTest_setUp() {
dummyWorkspace = {};
function createDummyBlock() {
return {
workspace: dummyWorkspace,
isShadow: function() {return false;}
};
}
input = new Blockly.Connection(createDummyBlock(),
Blockly.INPUT_VALUE);
output = new Blockly.Connection(createDummyBlock(),
Blockly.OUTPUT_VALUE);
previous = new Blockly.Connection(createDummyBlock(),
Blockly.PREVIOUS_STATEMENT);
next = new Blockly.Connection(createDummyBlock(),
Blockly.NEXT_STATEMENT);
}
function connectionTest_tearDown() {
input = null;
output = null;
previous = null;
next = null;
dummyWorkspace = null;
}
var isMovableFn = function() { return true; };
/**
* These tests check that the reasons for failures to connect are consistent
* (internal view of error states).
*/
function testCanConnectWithReason_TargetNull() {
connectionTest_setUp();
assertEquals(Blockly.Connection.REASON_TARGET_NULL,
input.canConnectWithReason_(null));
connectionTest_tearDown();
}
function testCanConnectWithReason_Disconnect() {
connectionTest_setUp();
var tempConnection = new Blockly.Connection({workspace: dummyWorkspace, isMovable: isMovableFn},
Blockly.OUTPUT_VALUE);
Blockly.Connection.connectReciprocally_(input, tempConnection);
assertEquals(Blockly.Connection.CAN_CONNECT,
input.canConnectWithReason_(output));
connectionTest_tearDown();
}
function testCanConnectWithReason_DifferentWorkspaces() {
connectionTest_setUp();
input = new Blockly.Connection({workspace: {}}, Blockly.INPUT_VALUE);
output = new Blockly.Connection({workspace: dummyWorkspace},
Blockly.OUTPUT_VALUE);
assertEquals(Blockly.Connection.REASON_DIFFERENT_WORKSPACES,
input.canConnectWithReason_(output));
connectionTest_tearDown();
}
function testCanConnectWithReason_Self() {
connectionTest_setUp();
var block = {type_: "test block"};
input.sourceBlock_ = block;
assertEquals(Blockly.Connection.REASON_SELF_CONNECTION,
input.canConnectWithReason_(input));
connectionTest_tearDown();
}
function testCanConnectWithReason_Type() {
connectionTest_setUp();
assertEquals(Blockly.Connection.REASON_WRONG_TYPE,
input.canConnectWithReason_(previous));
assertEquals(Blockly.Connection.REASON_WRONG_TYPE,
input.canConnectWithReason_(next));
assertEquals(Blockly.Connection.REASON_WRONG_TYPE,
output.canConnectWithReason_(previous));
assertEquals(Blockly.Connection.REASON_WRONG_TYPE,
output.canConnectWithReason_(next));
assertEquals(Blockly.Connection.REASON_WRONG_TYPE,
previous.canConnectWithReason_(input));
assertEquals(Blockly.Connection.REASON_WRONG_TYPE,
previous.canConnectWithReason_(output));
assertEquals(Blockly.Connection.REASON_WRONG_TYPE,
next.canConnectWithReason_(input));
assertEquals(Blockly.Connection.REASON_WRONG_TYPE,
next.canConnectWithReason_(output));
connectionTest_tearDown();
}
function testCanConnectWithReason_CanConnect() {
connectionTest_setUp();
assertEquals(Blockly.Connection.CAN_CONNECT,
previous.canConnectWithReason_(next));
assertEquals(Blockly.Connection.CAN_CONNECT,
next.canConnectWithReason_(previous));
assertEquals(Blockly.Connection.CAN_CONNECT,
input.canConnectWithReason_(output));
assertEquals(Blockly.Connection.CAN_CONNECT,
output.canConnectWithReason_(input));
connectionTest_tearDown();
}
/**
* The next set of tests checks that exceptions are being thrown at the correct
* times (external view of errors).
*/
function testCheckConnection_Self() {
connectionTest_setUp();
var block = {type_: "test block"};
input.sourceBlock_ = block;
try {
input.checkConnection_(input);
fail();
} catch (e) {
// expected
}
connectionTest_tearDown();
}
function testCheckConnection_TypeInputPrev() {
connectionTest_setUp();
try {
input.checkConnection_(previous);
fail();
} catch (e) {
// expected
}
connectionTest_tearDown();
}
function testCheckConnection_TypeInputNext() {
connectionTest_setUp();
try {
input.checkConnection_(next);
fail();
} catch (e) {
// expected
}
connectionTest_tearDown();
}
function testCheckConnection_TypeOutputPrev() {
connectionTest_setUp();
try {
output.checkConnection_(previous);
fail();
} catch (e) {
// expected
}
connectionTest_tearDown();
}
function testCheckConnection_TypePrevInput() {
connectionTest_setUp();
try {
previous.checkConnection_(input);
fail();
} catch (e) {
// expected
}
connectionTest_tearDown();
}
function testCheckConnection_TypePrevOutput() {
connectionTest_setUp();
try {
previous.checkConnection_(output);
fail();
} catch (e) {
// expected
}
connectionTest_tearDown();
}
function testCheckConnection_TypeNextInput() {
connectionTest_setUp();
try {
next.checkConnection_(input);
fail();
} catch (e) {
// expected
}
connectionTest_tearDown();
}
function testCheckConnection_TypeNextOutput() {
connectionTest_setUp();
try {
next.checkConnection_(output);
fail();
} catch (e) {
// expected
}
connectionTest_tearDown();
}
function test_isConnectionAllowed_Distance() {
var sharedWorkspace = {};
// Two connections of opposite types near each other.
var one = helper_createConnection(5 /* x */, 10 /* y */,
Blockly.INPUT_VALUE, null, true);
one.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
var two = helper_createConnection(10 /* x */, 15 /* y */,
Blockly.OUTPUT_VALUE, null, true);
two.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
assertTrue(two.isConnectionAllowed(one, 20.0));
// Move connections farther apart.
two.x_ = 100;
two.y_ = 100;
assertFalse(two.isConnectionAllowed(one, 20.0));
}
function test_isConnectionAllowed_Unrendered() {
var sharedWorkspace = {};
var one = helper_createConnection(5 /* x */, 10 /* y */,
Blockly.INPUT_VALUE);
one.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
// Don't offer to connect a left (male) value plug to
// an available right (female) value plug.
// Unlike in Blockly, you can't do this even if the left value plug isn't
// already connected.
var two = helper_createConnection(0, 0, Blockly.OUTPUT_VALUE);
two.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
assertFalse(one.isConnectionAllowed(two));
var three = helper_createConnection(0, 0, Blockly.INPUT_VALUE);
three.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
Blockly.Connection.connectReciprocally_(two, three);
assertFalse(one.isConnectionAllowed(two));
// Don't connect two connections on the same block.
two.sourceBlock_ = one.sourceBlock_;
assertFalse(one.isConnectionAllowed(two));
}
function test_isConnectionAllowed_NoNext() {
var sharedWorkspace = {};
var one = helper_createConnection(0, 0, Blockly.NEXT_STATEMENT);
one.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
one.sourceBlock_.nextConnection = one;
var two = helper_createConnection(0, 0, Blockly.PREVIOUS_STATEMENT);
two.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
two.sourceBlock_.previousConnection = two;
assertTrue(two.isConnectionAllowed(one));
var three = helper_createConnection(0, 0, Blockly.PREVIOUS_STATEMENT);
three.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
three.sourceBlock_.previousConnection = three;
Blockly.Connection.connectReciprocally_(one, three);
// A terminal block is allowed to replace another terminal block.
assertTrue(two.isConnectionAllowed(one));
}
function test_isConnectionAllowed_InsertionMarker() {
var sharedWorkspace = {};
// Two connections of opposite types near each other.
var one = helper_createConnection(5 /* x */, 10 /* y */,
Blockly.INPUT_VALUE);
one.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
// The second one is an insertion marker.
var two = helper_createConnection(10 /* x */, 15 /* y */,
Blockly.OUTPUT_VALUE);
two.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
two.sourceBlock_.isInsertionMarker = function() {
return true;
};
assertFalse(one.isConnectionAllowed(two, 20.0));
}
function testCheckConnection_Okay() {
connectionTest_setUp();
previous.checkConnection_(next);
next.checkConnection_(previous);
input.checkConnection_(output);
output.checkConnection_(input);
connectionTest_tearDown();
}
function test_canConnectWithReason_Procedures_WrongBlockType() {
var sharedWorkspace = {};
var one = helper_createConnection(0, 0, Blockly.NEXT_STATEMENT);
one.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
one.sourceBlock_.type = Blockly.PROCEDURES_DEFINITION_BLOCK_TYPE;
// Make one be the connection on its source block's input.
one.sourceBlock_.getInput = function() {
return {
connection: one
};
};
var two = helper_createConnection(0, 0, Blockly.PREVIOUS_STATEMENT);
two.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
// Fail because two's source block is the wrong type.
two.sourceBlock_.type = 'wrong_type';
assertEquals(Blockly.Connection.REASON_CUSTOM_PROCEDURE,
one.canConnectWithReason_(two));
}
function test_canConnectWithReason_Procedures_Pass() {
var sharedWorkspace = {};
var one = helper_createConnection(0, 0, Blockly.NEXT_STATEMENT);
one.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
one.sourceBlock_.type = Blockly.PROCEDURES_DEFINITION_BLOCK_TYPE;
// Make one be the connection on its source block's input.
one.sourceBlock_.getInput = function() {
return {
connection: one
};
};
var two = helper_createConnection(0, 0, Blockly.PREVIOUS_STATEMENT);
two.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
two.sourceBlock_.type = Blockly.PROCEDURES_PROTOTYPE_BLOCK_TYPE;
assertEquals(Blockly.Connection.CAN_CONNECT,
one.canConnectWithReason_(two));
}
function test_canConnectWithReason_Procedures_NextConnection() {
var sharedWorkspace = {};
var one = helper_createConnection(0, 0, Blockly.NEXT_STATEMENT);
one.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
one.sourceBlock_.type = Blockly.PROCEDURES_DEFINITION_BLOCK_TYPE;
// One is the next connection, not an input connection
one.sourceBlock_.nextConnection = one;
one.sourceBlock_.getInput = function() {
return {
connection: null
};
};
var two = helper_createConnection(0, 0, Blockly.PREVIOUS_STATEMENT);
two.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
// It should be okay, even if two's source block has the wrong type, because
// it's not trying to connect to the input.
two.sourceBlock_.type = 'wrong_type';
assertEquals(Blockly.Connection.CAN_CONNECT,
one.canConnectWithReason_(two));
}

View File

@@ -0,0 +1,76 @@
/**
* @license
* Blockly Tests
*
* Copyright 2016 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
function test_DB_getNeighbours() {
var db = new Blockly.ConnectionDB();
// Search an empty list.
assertEquals(helper_getNeighbours(db, 10 /* x */, 10 /* y */, 100 /* radius */).length, 0);
// Set up some connections.
for (var i = 0; i < 10; i++) {
db.addConnection_(helper_createConnection(0, i, Blockly.PREVIOUS_STATEMENT));
}
// Test block belongs at beginning
var result = helper_getNeighbours(db, 0, 0, 4);
assertEquals(5, result.length);
for (i = 0; i < result.length; i++) {
assertNotEquals(result.indexOf(db[i]), -1); // contains
}
// Test block belongs at middle
result = helper_getNeighbours(db, 0, 4, 2);
assertEquals(5, result.length);
for (i = 0; i < result.length; i++) {
assertNotEquals(result.indexOf(db[i + 2]), -1); // contains
}
// Test block belongs at end
result = helper_getNeighbours(db, 0, 9, 4);
assertEquals(5, result.length);
for (i = 0; i < result.length; i++) {
assertNotEquals(result.indexOf(db[i + 5]), -1); // contains
}
// Test block has no neighbours due to being out of range in the x direction
result = helper_getNeighbours(db, 10, 9, 4);
assertEquals(result.length, 0);
// Test block has no neighbours due to being out of range in the y direction
result = helper_getNeighbours(db, 0, 19, 4);
assertEquals(result.length, 0);
// Test block has no neighbours due to being out of range diagonally
result = helper_getNeighbours(db, -2, -2, 2);
assertEquals(result.length, 0);
}
function helper_getNeighbours(db, x, y, radius) {
return db.getNeighbours(helper_createConnection(x, y, Blockly.NEXT_STATEMENT), radius);
}
function helper_createConnection(x, y, type) {
var conn = new Blockly.Connection({workspace: {}}, type);
conn.x_ = x;
conn.y_ = y;
return conn;
}

View File

@@ -0,0 +1,854 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2017 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Tests for Blockly.Events
* @author marisaleung@google.com (Marisa Leung)
*/
'use strict';
goog.require('goog.testing');
goog.require('goog.testing.MockControl');
var mockControl_;
var workspace;
function eventTest_setUp() {
workspace = new Blockly.Workspace();
mockControl_ = new goog.testing.MockControl();
}
function eventTest_setUpWithMockBlocks() {
eventTest_setUp();
// TODO: Replace with defineGetVarBlock();
Blockly.defineBlocksWithJsonArray([{
'type': 'field_variable_test_block',
'message0': '%1',
'args0': [
{
'type': 'field_variable',
'name': 'VAR',
'variable': 'item'
}
],
},
{
'type': 'simple_test_block',
'message0': 'simple test block'
}]);
}
function eventTest_tearDown() {
delete Blockly.Blocks['field_variable_test_block'];
delete Blockly.Blocks['simple_test_block'];
mockControl_.$tearDown();
workspace.dispose();
}
function eventTest_tearDownWithMockBlocks() {
eventTest_tearDown();
delete Blockly.Blocks.field_variable_test_block;
}
function test_block_base_constructor() {
eventTest_setUpWithMockBlocks();
setUpMockMethod(mockControl_, Blockly.utils, 'genUid', null, '1');
try {
var block = createSimpleTestBlock(workspace);
// Here's the event we care about.
var event = new Blockly.Events.BlockBase(block);
assertUndefined(event.varId);
checkExactEventValues(event, {'blockId': '1', 'workspaceId': workspace.id,
'group': '', 'recordUndo': true});
} finally {
eventTest_tearDownWithMockBlocks();
}
}
function test_var_base_constructor() {
eventTest_setUpWithMockBlocks();
setUpMockMethod(mockControl_, Blockly.utils, 'genUid', null, '1');
try {
var variable = workspace.createVariable('name1', 'type1', 'id1');
var event = new Blockly.Events.VarBase(variable);
assertUndefined(event.blockId);
checkExactEventValues(event, {'varId': 'id1',
'workspaceId': workspace.id, 'group': '', 'recordUndo': true});
} finally {
eventTest_tearDownWithMockBlocks();
}
}
function test_abstract_constructor() {
eventTest_setUpWithMockBlocks();
try {
var event = new Blockly.Events.Abstract();
assertUndefined(event.blockId);
assertUndefined(event.workspaceId);
assertUndefined(event.varId);
checkExactEventValues(event, {'group': '', 'recordUndo': true});
} finally {
eventTest_tearDownWithMockBlocks();
}
}
// Test util
function checkCreateEventValues(event, block, ids, type) {
var expected_xml = Blockly.Xml.domToText(Blockly.Xml.blockToDom(block));
var result_xml = Blockly.Xml.domToText(event.xml);
assertEquals(expected_xml, result_xml);
isEqualArrays(ids, event.ids);
assertEquals(type, event.type);
}
// Test util
function checkDeleteEventValues(event, block, ids, type) {
var expected_xml = Blockly.Xml.domToText(Blockly.Xml.blockToDom(block));
var result_xml = Blockly.Xml.domToText(event.oldXml);
assertEquals(expected_xml, result_xml);
isEqualArrays(ids, event.ids);
assertEquals(type, event.type);
}
// Test util
function checkExactEventValues(event, values) {
var keys = Object.keys(values);
for (var i = 0, field; field = keys[i]; i++) {
assertEquals(values[field], event[field]);
}
}
// Test util
function createSimpleTestBlock(workspace) {
// Disable events while constructing the block: this is a test of the
// Blockly.Event constructors, not the block constructor.
Blockly.Events.disable();
var block = new Blockly.Block(workspace, 'simple_test_block');
Blockly.Events.enable();
return block;
}
function test_create_constructor() {
eventTest_setUpWithMockBlocks();
setUpMockMethod(mockControl_, Blockly.utils, 'genUid', null, ['1']);
try {
var block = createSimpleTestBlock(workspace);
var event = new Blockly.Events.Create(block);
checkCreateEventValues(event, block, ['1'], 'create');
} finally {
eventTest_tearDownWithMockBlocks();
}
}
function test_blockCreate_constructor() {
// expect that blockCreate behaves the same as create.
eventTest_setUpWithMockBlocks();
setUpMockMethod(mockControl_, Blockly.utils, 'genUid', null, ['1']);
try {
var block = createSimpleTestBlock(workspace);
var event = new Blockly.Events.BlockCreate(block);
checkCreateEventValues(event, block, ['1'], 'create');
} finally {
eventTest_tearDownWithMockBlocks();
}
}
function test_delete_constructor() {
eventTest_setUpWithMockBlocks();
setUpMockMethod(mockControl_, Blockly.utils, 'genUid', null, ['1']);
try {
var block = createSimpleTestBlock(workspace);
var event = new Blockly.Events.Delete(block);
checkDeleteEventValues(event, block, ['1'], 'delete');
} finally {
eventTest_tearDownWithMockBlocks();
}
}
function test_blockDelete_constructor() {
eventTest_setUpWithMockBlocks();
setUpMockMethod(mockControl_, Blockly.utils, 'genUid', null, ['1']);
try {
var block = createSimpleTestBlock(workspace);
var event = new Blockly.Events.BlockDelete(block);
checkDeleteEventValues(event, block, ['1'], 'delete');
} finally {
eventTest_tearDownWithMockBlocks();
}
}
function test_change_constructor() {
eventTest_setUpWithMockBlocks();
setUpMockMethod(mockControl_, Blockly.utils, 'genUid', null, ['1']);
try {
Blockly.Events.disable();
var block = new Blockly.Block(workspace, 'field_variable_test_block');
Blockly.Events.enable();
var event = new Blockly.Events.Change(block, 'field', 'VAR', 'id1', 'id2');
checkExactEventValues(event, {'element': 'field', 'name': 'VAR',
'oldValue': 'id1', 'newValue': 'id2', 'type': 'change'});
} finally {
eventTest_tearDownWithMockBlocks();
}
}
function test_blockChange_constructor() {
eventTest_setUpWithMockBlocks();
setUpMockMethod(mockControl_, Blockly.utils, 'genUid', null, ['1']);
try {
Blockly.Events.disable();
var block = new Blockly.Block(workspace, 'field_variable_test_block');
Blockly.Events.enable();
var event = new Blockly.Events.BlockChange(block, 'field', 'VAR', 'id1',
'id2');
checkExactEventValues(event, {'element': 'field', 'name': 'VAR',
'oldValue': 'id1', 'newValue': 'id2', 'type': 'change'});
} finally {
eventTest_tearDownWithMockBlocks();
}
}
function test_move_constructorCoordinate() {
// Expect the oldCoordinate to be set.
eventTest_setUpWithMockBlocks();
setUpMockMethod(mockControl_, Blockly.utils, 'genUid', null, ['1', '2']);
try {
var block1 = createSimpleTestBlock(workspace);
var coordinate = new goog.math.Coordinate(3,4);
block1.xy_ = coordinate;
var event = new Blockly.Events.Move(block1);
// Need to check for individual equality of the coordinate values since
// the move event creates a new goog.math.Coordinate object
assertEquals(event.oldCoordinate.x, coordinate.x);
assertEquals(event.oldCoordinate.y, coordinate.y);
assertEquals(event.type, 'move');
} finally {
eventTest_tearDownWithMockBlocks();
}
}
function test_move_constructoroldParentId() {
// Expect the oldParentId to be set but not the oldCoordinate to be set.
eventTest_setUpWithMockBlocks();
setUpMockMethod(mockControl_, Blockly.utils, 'genUid', null, ['1', '2']);
try {
var block1 = createSimpleTestBlock(workspace);
var block2 = createSimpleTestBlock(workspace);
block1.parentBlock_ = block2;
block1.xy_ = new goog.math.Coordinate(3,4);
var event = new Blockly.Events.Move(block1);
checkExactEventValues(event, {'oldCoordinate': undefined,
'oldParentId': '2', 'type': 'move'});
block1.parentBlock_ = null;
} finally {
eventTest_tearDownWithMockBlocks();
}
}
function test_blockMove_constructorCoordinate() {
// Expect the oldCoordinate to be set.
eventTest_setUpWithMockBlocks();
setUpMockMethod(mockControl_, Blockly.utils, 'genUid', null, ['1', '2']);
try {
var block1 = createSimpleTestBlock(workspace);
var coordinate = new goog.math.Coordinate(3,4);
block1.xy_ = coordinate;
var event = new Blockly.Events.BlockMove(block1);
// Need to check for individual equality of the coordinate values since
// the move event creates a new goog.math.Coordinate object
assertEquals(event.oldCoordinate.x, coordinate.x);
assertEquals(event.oldCoordinate.y, coordinate.y);
assertEquals(event.type, 'move');
} finally {
eventTest_tearDownWithMockBlocks();
}
}
function test_blockMove_constructoroldParentId() {
// Expect the oldParentId to be set but not the oldCoordinate to be set.
eventTest_setUpWithMockBlocks();
setUpMockMethod(mockControl_, Blockly.utils, 'genUid', null, ['1', '2']);
try {
var block1 = createSimpleTestBlock(workspace);
var block2 = createSimpleTestBlock(workspace);
block1.parentBlock_ = block2;
block1.xy_ = new goog.math.Coordinate(3,4);
var event = new Blockly.Events.BlockMove(block1);
checkExactEventValues(event, {'oldCoordinate': undefined,
'oldParentId': '2', 'type': 'move'});
block1.parentBlock_ = null;
} finally {
eventTest_tearDownWithMockBlocks();
}
}
function test_uiEvent_constructor_null() {
try {
Blockly.Events.setGroup('testGroup');
var event = new Blockly.Events.Ui(null, 'foo', 'bar', 'baz');
checkExactEventValues(event,
{
'blockId': null,
'workspaceId': null,
'type': 'ui',
'oldValue': 'bar',
'newValue': 'baz',
'element': 'foo',
'recordUndo': false,
'group': 'testGroup'
}
);
} finally {
Blockly.Events.setGroup(false);
}
}
function test_uiEvent_constructor_block() {
eventTest_setUpWithMockBlocks();
setUpMockMethod(mockControl_, Blockly.utils, 'genUid', null, ['1']);
try {
var block1 = createSimpleTestBlock(workspace);
Blockly.Events.setGroup('testGroup');
var event = new Blockly.Events.Ui(block1, 'foo', 'bar', 'baz');
checkExactEventValues(event,
{
'blockId': '1',
'workspaceId': workspace.id,
'type': 'ui',
'oldValue': 'bar',
'newValue': 'baz',
'element': 'foo',
'recordUndo': false,
'group': 'testGroup'
}
);
} finally {
Blockly.Events.setGroup(false);
eventTest_tearDownWithMockBlocks();
}
}
function test_varCreate_constructor() {
eventTest_setUp();
try {
var variable = workspace.createVariable('name1', 'type1', 'id1');
var event = new Blockly.Events.VarCreate(variable);
checkExactEventValues(event, {'varName': 'name1', 'varType': 'type1',
'type': 'var_create'});
} finally {
eventTest_tearDown();
}
}
function test_varCreate_toJson() {
eventTest_setUp();
try {
var variable = workspace.createVariable('name1', 'type1', 'id1');
var event = new Blockly.Events.VarCreate(variable);
var json = event.toJson();
var expectedJson = ({type: "var_create", varId: "id1", varType: "type1",
varName: "name1", isLocal: false, isCloud: false});
assertEquals(JSON.stringify(expectedJson), JSON.stringify(json));
var localVariable = workspace.createVariable('name2', 'type2', 'id2', true);
var event2 = new Blockly.Events.VarCreate(localVariable);
var json2 = event2.toJson();
var expectedJson2 = ({type: "var_create", varId: "id2", varType: "type2",
varName: "name2", isLocal: true, isCloud: false});
assertEquals(JSON.stringify(expectedJson2), JSON.stringify(json2));
var cloudVariable = workspace.createVariable('name3', 'type3', 'id3', false, true);
var event3 = new Blockly.Events.VarCreate(cloudVariable);
var json3 = event3.toJson();
var expectedJson3 = ({type: "var_create", varId: "id3", varType: "type3",
varName: "name3", isLocal: false, isCloud: true});
assertEquals(JSON.stringify(expectedJson3), JSON.stringify(json3));
} finally {
eventTest_tearDown();
}
}
function test_varCreate_fromJson() {
eventTest_setUp();
try {
var variable = workspace.createVariable('name1', 'type1', 'id1');
var event = new Blockly.Events.VarCreate(variable);
var event2 = new Blockly.Events.VarCreate(null);
var json = event.toJson();
event2.fromJson(json);
assertEquals(JSON.stringify(json), JSON.stringify(event2.toJson()));
} finally {
eventTest_tearDown();
}
}
function test_varCreate_runForward() {
eventTest_setUp();
var json = {type: "var_create", varId: "id1", varType: "type1",
varName: "name1"};
var event = Blockly.Events.fromJson(json, workspace);
assertNull(workspace.getVariableById('id1'));
event.run(true);
checkVariableValues(workspace, 'name1', 'type1', 'id1');
eventTest_tearDown();
}
function test_varCreate_runBackwards() {
eventTest_setUp();
var variable = workspace.createVariable('name1', 'type1', 'id1');
var event = new Blockly.Events.VarCreate(variable);
assertNotNull(workspace.getVariableById('id1'));
event.run(false);
assertNull(workspace.getVariableById('id1'));
eventTest_tearDown();
}
function test_varDelete_constructor() {
eventTest_setUp();
var variable = workspace.createVariable('name1', 'type1', 'id1');
var event = new Blockly.Events.VarDelete(variable);
checkExactEventValues(event, {'varName': 'name1', 'varType': 'type1',
'varId':'id1', 'type': 'var_delete'});
eventTest_tearDown();
}
function test_varDelete_toJson() {
eventTest_setUp();
var variable = workspace.createVariable('name1', 'type1', 'id1');
var event = new Blockly.Events.VarDelete(variable);
var json = event.toJson();
var expectedJson = ({type: "var_delete", varId: "id1", varType: "type1",
varName: "name1", isLocal: false, isCloud: false});
assertEquals(JSON.stringify(expectedJson), JSON.stringify(json));
var localVariable = workspace.createVariable('name2', 'type2', 'id2', true);
var event2 = new Blockly.Events.VarDelete(localVariable);
var json2 = event2.toJson();
var expectedJson2 = ({type: "var_delete", varId: "id2", varType: "type2",
varName: "name2", isLocal: true, isCloud: false});
assertEquals(JSON.stringify(expectedJson2), JSON.stringify(json2));
var cloudVariable = workspace.createVariable('name3', 'type3', 'id3', false, true);
var event3 = new Blockly.Events.VarDelete(cloudVariable);
var json3 = event3.toJson();
var expectedJson3 = ({type: "var_delete", varId: "id3", varType: "type3",
varName: "name3", isLocal: false, isCloud: true});
assertEquals(JSON.stringify(expectedJson2), JSON.stringify(json2));
eventTest_tearDown();
}
function test_varDelete_fromJson() {
eventTest_setUp();
var variable = workspace.createVariable('name1', 'type1', 'id1');
var event = new Blockly.Events.VarDelete(variable);
var event2 = new Blockly.Events.VarDelete(null);
var json = event.toJson();
event2.fromJson(json);
assertEquals(JSON.stringify(json), JSON.stringify(event2.toJson()));
eventTest_tearDown();
}
function test_varDelete_runForwards() {
eventTest_setUp();
var variable = workspace.createVariable('name1', 'type1', 'id1');
var event = new Blockly.Events.VarDelete(variable);
assertNotNull(workspace.getVariableById('id1'));
event.run(true);
assertNull(workspace.getVariableById('id1'));
eventTest_tearDown();
}
function test_varDelete_runBackwards() {
eventTest_setUp();
var json = {type: "var_delete", varId: "id1", varType: "type1",
varName: "name1"};
var event = Blockly.Events.fromJson(json, workspace);
assertNull(workspace.getVariableById('id1'));
event.run(false);
checkVariableValues(workspace, 'name1', 'type1', 'id1');
eventTest_tearDown();
}
function test_varRename_constructor() {
eventTest_setUp();
var variable = workspace.createVariable('name1', 'type1', 'id1');
var event = new Blockly.Events.VarRename(variable, 'name2');
checkExactEventValues(event, {'varId': 'id1', 'oldName': 'name1',
'newName': 'name2', 'type': 'var_rename'});
eventTest_tearDown();
}
function test_varRename_toJson() {
eventTest_setUp();
var variable = workspace.createVariable('name1', 'type1', 'id1');
var event = new Blockly.Events.VarRename(variable, 'name2');
var json = event.toJson();
var expectedJson = ({type: "var_rename", varId: "id1", oldName: "name1",
newName: "name2"});
assertEquals(JSON.stringify(expectedJson), JSON.stringify(json));
eventTest_tearDown();
}
function test_varRename_fromJson() {
eventTest_setUp();
var variable = workspace.createVariable('name1', 'type1', 'id1');
var event = new Blockly.Events.VarRename(variable, '');
var event2 = new Blockly.Events.VarRename(null);
var json = event.toJson();
event2.fromJson(json);
assertEquals(JSON.stringify(json), JSON.stringify(event2.toJson()));
eventTest_tearDown();
}
function test_varRename_runForward() {
eventTest_setUp();
var variable = workspace.createVariable('name1', 'type1', 'id1');
var event = new Blockly.Events.VarRename(variable, 'name2');
event.run(true);
assertNull(workspace.getVariable('name1'));
checkVariableValues(workspace, 'name2', 'type1', 'id1');
eventTest_tearDown();
}
function test_varBackard_runForward() {
eventTest_setUp();
var variable = workspace.createVariable('name1', 'type1', 'id1');
var event = new Blockly.Events.VarRename(variable, 'name2');
event.run(false);
assertNull(workspace.getVariable('name2'));
checkVariableValues(workspace, 'name1', 'type1', 'id1');
eventTest_tearDown();
}
function test_events_filter() {
eventTest_setUpWithMockBlocks();
try {
var block1 = workspace.newBlock('field_variable_test_block', '1');
var events = [
new Blockly.Events.BlockCreate(block1),
new Blockly.Events.BlockMove(block1),
new Blockly.Events.BlockChange(block1, 'field', 'VAR', 'id1', 'id2'),
new Blockly.Events.Ui(block1, 'click')
];
var filteredEvents = Blockly.Events.filter(events, true);
assertEquals(4, filteredEvents.length); // no event should have been removed.
// test that the order hasn't changed
assertTrue(filteredEvents[0] instanceof Blockly.Events.BlockCreate);
assertTrue(filteredEvents[1] instanceof Blockly.Events.BlockMove);
assertTrue(filteredEvents[2] instanceof Blockly.Events.BlockChange);
assertTrue(filteredEvents[3] instanceof Blockly.Events.Ui);
} finally {
eventTest_tearDownWithMockBlocks();
}
}
function test_events_filterForward() {
eventTest_setUpWithMockBlocks();
try {
var block1 = workspace.newBlock('field_variable_test_block', '1');
var events = [
new Blockly.Events.BlockCreate(block1),
];
helper_addMoveEvent(events, block1, 1, 1);
helper_addMoveEvent(events, block1, 2, 2);
helper_addMoveEvent(events, block1, 3, 3);
var filteredEvents = Blockly.Events.filter(events, true);
assertEquals(2, filteredEvents.length); // duplicate moves should have been removed.
// test that the order hasn't changed
assertTrue(filteredEvents[0] instanceof Blockly.Events.BlockCreate);
assertTrue(filteredEvents[1] instanceof Blockly.Events.BlockMove);
assertEquals(3, filteredEvents[1].newCoordinate.x);
assertEquals(3, filteredEvents[1].newCoordinate.y);
} finally {
eventTest_tearDownWithMockBlocks();
}
}
function test_events_filterBackward() {
eventTest_setUpWithMockBlocks();
try {
var block1 = workspace.newBlock('field_variable_test_block', '1');
var events = [
new Blockly.Events.BlockCreate(block1),
];
helper_addMoveEvent(events, block1, 1, 1);
helper_addMoveEvent(events, block1, 2, 2);
helper_addMoveEvent(events, block1, 3, 3);
var filteredEvents = Blockly.Events.filter(events, false);
assertEquals(2, filteredEvents.length); // duplicate event should have been removed.
// test that the order hasn't changed
assertTrue(filteredEvents[0] instanceof Blockly.Events.BlockCreate);
assertTrue(filteredEvents[1] instanceof Blockly.Events.BlockMove);
assertEquals(1, filteredEvents[1].newCoordinate.x);
assertEquals(1, filteredEvents[1].newCoordinate.y);
} finally {
eventTest_tearDownWithMockBlocks();
}
}
function test_events_filterDifferentBlocks() {
eventTest_setUpWithMockBlocks();
var block1 = workspace.newBlock('field_variable_test_block', '1');
var block2 = workspace.newBlock('field_variable_test_block', '2');
var events = [
new Blockly.Events.BlockCreate(block1),
new Blockly.Events.BlockMove(block1),
new Blockly.Events.BlockCreate(block2),
new Blockly.Events.BlockMove(block2)
];
var filteredEvents = Blockly.Events.filter(events, true);
assertEquals(4, filteredEvents.length); // no event should have been removed.
eventTest_tearDownWithMockBlocks();
}
function test_events_mergeMove() {
eventTest_setUpWithMockBlocks();
var block1 = workspace.newBlock('field_variable_test_block', '1');
var events = [];
helper_addMoveEvent(events, block1, 0, 0);
helper_addMoveEvent(events, block1, 1, 1);
var filteredEvents = Blockly.Events.filter(events, true);
assertEquals(1, filteredEvents.length); // second move event merged into first
assertEquals(1, filteredEvents[0].newCoordinate.x);
assertEquals(1, filteredEvents[0].newCoordinate.y);
eventTest_tearDownWithMockBlocks();
}
function test_events_mergeChange() {
eventTest_setUpWithMockBlocks();
var block1 = workspace.newBlock('field_variable_test_block', '1');
var events = [
new Blockly.Events.Change(block1, 'field', 'VAR', 'item', 'item1'),
new Blockly.Events.Change(block1, 'field', 'VAR', 'item1', 'item2')
];
var filteredEvents = Blockly.Events.filter(events, true);
assertEquals(1, filteredEvents.length); // second change event merged into first
assertEquals('item', filteredEvents[0].oldValue);
assertEquals('item2', filteredEvents[0].newValue);
eventTest_tearDownWithMockBlocks();
}
function test_events_mergeUi() {
eventTest_setUpWithMockBlocks();
var block1 = workspace.newBlock('field_variable_test_block', '1');
var block2 = workspace.newBlock('field_variable_test_block', '2');
var block3 = workspace.newBlock('field_variable_test_block', '3');
var events = [
new Blockly.Events.Ui(block1, 'commentOpen', 'false', 'true'),
new Blockly.Events.Ui(block1, 'click', 'false', 'true'),
new Blockly.Events.Ui(block2, 'mutatorOpen', 'false', 'true'),
new Blockly.Events.Ui(block2, 'click', 'false', 'true'),
new Blockly.Events.Ui(block3, 'warningOpen', 'false', 'true'),
new Blockly.Events.Ui(block3, 'click', 'false', 'true')
];
var filteredEvents = Blockly.Events.filter(events, true);
assertEquals(3, filteredEvents.length); // click event merged into corresponding *Open event
assertEquals('commentOpen', filteredEvents[0].element);
assertEquals('mutatorOpen', filteredEvents[1].element);
assertEquals('warningOpen', filteredEvents[2].element);
eventTest_tearDownWithMockBlocks();
}
/**
* Tests that events that collide on a (event, block, workspace) tuple
* but cannot be merged do not get dropped during filtering.
*/
function test_events_stackclick() {
eventTest_setUpWithMockBlocks();
var block = workspace.newBlock('field_variable_test_block', '1');
var events = [
new Blockly.Events.Ui(block, 'click', undefined, undefined),
new Blockly.Events.Ui(block, 'stackclick', undefined, undefined)
];
var filteredEvents = Blockly.Events.filter(events, true);
// click and stackclick should both exist
assertEquals(2, filteredEvents.length);
assertEquals('click', filteredEvents[0].element);
assertEquals('stackclick', filteredEvents[1].element);
eventTest_tearDownWithMockBlocks();
}
/**
* Mutator composition could result in move events for blocks
* connected to the mutated block that were null operations. This
* leads to events in the undo/redo queue that do nothing, requiring
* an extra undo/redo to proceed to the next event. This test ensures
* that two move events that do get merged (disconnecting and
* reconnecting a block in response to a mutator change) are filtered
* from the queue.
*/
function test_events_filteraftermerge() {
eventTest_setUpWithMockBlocks();
var block = workspace.newBlock('field_variable_test_block', '1');
block.setParent(null);
var events = [];
helper_addMoveEventParent(events, block, null);
helper_addMoveEventParent(events, block, null);
var filteredEvents = Blockly.Events.filter(events, true);
// The two events should be merged, but because nothing has changed
// they will be filtered out.
assertEquals(0, filteredEvents.length);
eventTest_tearDownWithMockBlocks();
}
/**
* Helper function to simulate block move events.
*
* @param {!Array.<Blockly.Events.Abstract>} events a queue of events.
* @param {!Blockly.Block} block the block to be moved
* @param {number} newX new X coordinate of the block
* @param {number} newY new Y coordinate of the block
*/
function helper_addMoveEvent(events, block, newX, newY) {
events.push(new Blockly.Events.BlockMove(block));
block.xy_ = new goog.math.Coordinate(newX, newY);
events[events.length-1].recordNew();
}
function helper_addMoveEventParent(events, block, parent) {
events.push(new Blockly.Events.BlockMove(block));
block.setParent(parent);
events[events.length-1].recordNew();
}
function test_events_newblock_newvar() {
eventTest_setUpWithMockBlocks();
Blockly.Events.fire = temporary_fireEvent;
temporary_fireEvent.firedEvents_ = [];
// Expect three calls to genUid: one to set the block's ID, one for the event
// group's id, and one for the variable's ID.
setUpMockMethod(mockControl_, Blockly.utils, 'genUid', null, ['1', '2', '3']);
try {
var block = workspace.newBlock('field_variable_test_block');
var firedEvents = workspace.undoStack_;
// Expect two events: varCreate and block create.
assertEquals(2, firedEvents.length);
var event0 = firedEvents[0];
var event1 = firedEvents[1];
assertEquals('var_create', event0.type);
assertEquals('create', event1.type);
// Expect the events to have the same group ID.
assertEquals(event0.group, event1.group);
// Expect the group ID to be the result of the second call to genUid.
assertEquals('2', event0.group);
// Expect the workspace to have a variable with ID '3'.
assertNotNull(workspace.getVariableById('3'));
assertEquals('3', event0.varId);
} finally {
eventTest_tearDownWithMockBlocks();
Blockly.Events.fire = savedFireFunc;
}
}
// The sequence of events should be the same whether the block was created from
// XML or directly.
function test_events_newblock_newvar_xml() {
eventTest_setUpWithMockBlocks();
Blockly.Events.fire = temporary_fireEvent;
temporary_fireEvent.firedEvents_ = [];
try {
var dom = Blockly.Xml.textToDom(
'<xml>' +
' <block type="field_variable_test_block" id="block1">' +
' <field name="VAR" id="id1" variabletype="">name1</field>' +
' </block>' +
'</xml>');
Blockly.Xml.domToWorkspace(dom, workspace);
var firedEvents = workspace.undoStack_;
// Expect two events: varCreate and block create.
// TODO: scratch-blocks also has move and delete events.
// assertEquals(2, firedEvents.length);
var event0 = firedEvents[0];
var event1 = firedEvents[1];
assertEquals('var_create', event0.type);
assertEquals('create', event1.type);
// Expect the events to have the same group ID.
assertEquals(event0.group, event1.group);
// Expect the workspace to have a variable with ID 'id1'.
assertNotNull(workspace.getVariableById('id1'));
assertEquals('id1', event0.varId);
} finally {
eventTest_tearDownWithMockBlocks();
Blockly.Events.fire = savedFireFunc;
}
}
function test_events_filter_nomerge_move() {
// Move events should only merge if they refer to the same block and are
// consecutive.
// See github.com/google/blockly/pull/1892 for a worked example showing
// how merging non-consecutive events can fail when replacing a shadow
// block.
eventTest_setUpWithMockBlocks();
try {
var block1 = createSimpleTestBlock(workspace);
var block2 = createSimpleTestBlock(workspace);
var events = [];
helper_addMoveEvent(events, block1, 1, 1);
helper_addMoveEvent(events, block2, 1, 1);
events.push(new Blockly.Events.BlockDelete(block2));
helper_addMoveEvent(events, block1, 2, 2);
var filteredEvents = Blockly.Events.filter(events, true);
// Nothing should have merged.
assertEquals(4, filteredEvents.length);
// test that the order hasn't changed
assertTrue(filteredEvents[0] instanceof Blockly.Events.BlockMove);
assertTrue(filteredEvents[1] instanceof Blockly.Events.BlockMove);
assertTrue(filteredEvents[2] instanceof Blockly.Events.BlockDelete);
assertTrue(filteredEvents[3] instanceof Blockly.Events.BlockMove);
} finally {
eventTest_tearDownWithMockBlocks();
}
}

View File

@@ -0,0 +1,659 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2017 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Tests for Blockly.Extensions
* @author Anm@anm.me (Andrew n marshall)
*/
'use strict';
function test_extension() {
var workspace = new Blockly.Workspace();
var block;
try {
assertUndefined(Blockly.Extensions.ALL_['extensions_test']);
var numCallsToBefore = 0;
var numCallsToAfter = 0;
// Extension defined before the block type is defined.
Blockly.Extensions.register('extensions_test_before', function () {
numCallsToBefore++;
this.extendedWithBefore = true;
});
Blockly.defineBlocksWithJsonArray([{
"type": "extension_test_block",
"message0": "extension_test_block",
"extensions": ["extensions_test_before", "extensions_test_after"]
}]);
// Extension defined after the block type (but before instantiation).
Blockly.Extensions.register('extensions_test_after', function () {
numCallsToAfter++;
this.extendedWithAfter = true;
});
assert(goog.isFunction(Blockly.Extensions.ALL_['extensions_test_before']));
assert(goog.isFunction(Blockly.Extensions.ALL_['extensions_test_after']));
assertEquals(0, numCallsToBefore);
assertEquals(0, numCallsToAfter);
block = new Blockly.Block(workspace, 'extension_test_block');
assertEquals(1, numCallsToBefore);
assertEquals(1, numCallsToAfter);
assert(block.extendedWithBefore);
assert(block.extendedWithAfter);
} finally {
block && block.dispose();
workspace.dispose();
delete Blockly.Extensions.ALL_['extensions_test_before'];
delete Blockly.Extensions.ALL_['extensions_test_after'];
delete Blockly.Blocks['extension_test_block'];
}
}
function test_extension_missing() {
var workspace = new Blockly.Workspace();
var block;
var exceptionWasThrown = false;
try {
assertUndefined(Blockly.Extensions.ALL_['missing_extension']);
Blockly.defineBlocksWithJsonArray([{
"type": "missing_extension_block",
"message0": "missing_extension_block",
"extensions": ["missing_extension"]
}]);
block = new Blockly.Block(workspace, 'missing_extension_block');
} catch (e) {
// Expected.
exceptionWasThrown = true;
} finally {
block && block.dispose();
workspace.dispose();
delete Blockly.Blocks['missing_extension_block'];
}
assert(exceptionWasThrown);
}
function test_extension_not_a_function() {
var exceptionWasThrown = false;
try {
assertUndefined(Blockly.Extensions.ALL_['extension_just_a_string']);
Blockly.Extensions.register('extension_just_a_string', 'extension_just_a_string');
} catch (e) {
// Expected.
exceptionWasThrown = true;
} finally {
delete Blockly.Extensions.ALL_['extension_just_a_string'];
}
assert(exceptionWasThrown);
var exceptionWasThrown = false;
try {
assertUndefined(Blockly.Extensions.ALL_['extension_is_null']);
Blockly.Extensions.register('extension_is_null', null);
} catch (e) {
// Expected.
exceptionWasThrown = true;
} finally {
delete Blockly.Extensions.ALL_['extension_is_null'];
}
assert(exceptionWasThrown);
var exceptionWasThrown = false;
try {
assertUndefined(Blockly.Extensions.ALL_['extension_is_undefined']);
Blockly.Extensions.register('extension_is_undefined');
} catch (e) {
// Expected.
exceptionWasThrown = true;
} finally {
delete Blockly.Extensions.ALL_['extension_is_undefined'];
}
assert(exceptionWasThrown);
}
function test_parent_tooltip_when_inline() {
var defaultTooltip = "defaultTooltip";
var parentTooltip = "parentTooltip";
var workspace = new Blockly.Workspace();
var block;
try {
Blockly.defineBlocksWithJsonArray([
{
"type": "test_parent_tooltip_when_inline",
"message0": "test_parent_tooltip_when_inline",
"output": true,
"tooltip": defaultTooltip,
"extensions": ["parent_tooltip_when_inline"]
},
{
"type": "test_parent",
"message0": "%1",
"args0": [
{
"type": "input_value",
"name": "INPUT"
}
],
"tooltip": parentTooltip
}
]);
block = new Blockly.Block(workspace, 'test_parent_tooltip_when_inline');
// Tooltip is dynamic after extension initialization.
assert(goog.isFunction(block.tooltip));
assertEquals(block.tooltip(), defaultTooltip);
// Tooltip is normal before connected to parent.
var parent = new Blockly.Block(workspace, 'test_parent');
// Inputs default to inline in scratch-blocks.
parent.setInputsInline(false);
assertEquals(parent.tooltip, parentTooltip);
assertFalse(!!parent.inputsInline);
// Tooltip is normal when parent is not inline.
parent.getInput('INPUT').connection.connect(block.outputConnection);
assertEquals(block.getParent(), parent);
assertEquals(block.tooltip(), defaultTooltip);
// Tooltip is parent's when parent is inline.
parent.setInputsInline(true);
assertEquals(block.tooltip(), parentTooltip);
// Tooltip revert when disconnected.
parent.getInput('INPUT').connection.disconnect();
assert(!block.getParent());
assertEquals(block.tooltip(), defaultTooltip);
} finally {
block && block.dispose();
workspace.dispose();
delete Blockly.Blocks['test_parent_tooltip_when_inline'];
delete Blockly.Blocks['test_parent'];
}
}
function test_mixin_extension() {
var TEST_MIXIN = {
field: 'FIELD',
method: function() {
console.log('TEXT_MIXIN method()');
}
};
var workspace = new Blockly.Workspace();
var block;
try {
assertUndefined(Blockly.Extensions.ALL_['mixin_test']);
// Extension defined before the block type is defined.
Blockly.Extensions.registerMixin('mixin_test', TEST_MIXIN);
assert(goog.isFunction(Blockly.Extensions.ALL_['mixin_test']));
Blockly.defineBlocksWithJsonArray([{
"type": "test_block_mixin",
"message0": "test_block_mixin",
"extensions": ["mixin_test"]
}]);
block = new Blockly.Block(workspace, 'test_block_mixin');
assertEquals(TEST_MIXIN.field, block.field);
assertEquals(TEST_MIXIN.method, block.method);
} finally {
block && block.dispose();
workspace.dispose();
delete Blockly.Extensions.ALL_['mixin_test'];
delete Blockly.Blocks['test_block_mixin'];
}
}
function test_bad_mixin_overwrites_local_value() {
var TEST_MIXIN_BAD_INPUTLIST = {
inputList: 'bad inputList' // Defined in constructor
};
var workspace = new Blockly.Workspace();
var block;
try {
assertUndefined(Blockly.Extensions.ALL_['mixin_bad_inputList']);
// Extension defined before the block type is defined.
Blockly.Extensions.registerMixin('mixin_bad_inputList', TEST_MIXIN_BAD_INPUTLIST);
assert(goog.isFunction(Blockly.Extensions.ALL_['mixin_bad_inputList']));
Blockly.defineBlocksWithJsonArray([{
"type": "test_block_bad_inputList",
"message0": "test_block_bad_inputList",
"extensions": ["mixin_bad_inputList"]
}]);
try {
block = new Blockly.Block(workspace, 'test_block_bad_inputList');
} catch (e) {
// Expected Error
assert(e.message.indexOf('inputList') >= 0); // Reference the conflict
return;
}
fail('Expected error when constructing block');
} finally {
block && block.dispose();
workspace.dispose();
delete Blockly.Extensions.ALL_['mixin_bad_inputList'];
delete Blockly.Blocks['test_block_bad_inputList'];
}
}
function test_bad_mixin_overwrites_prototype() {
var TEST_MIXIN_BAD_COLOUR = {
colour_: 'bad colour_' // Defined on prototype
};
var workspace = new Blockly.Workspace();
var block;
try {
assertUndefined(Blockly.Extensions.ALL_['mixin_bad_colour_']);
// Extension defined before the block type is defined.
Blockly.Extensions.registerMixin('mixin_bad_colour_', TEST_MIXIN_BAD_COLOUR);
assert(goog.isFunction(Blockly.Extensions.ALL_['mixin_bad_colour_']));
Blockly.defineBlocksWithJsonArray([{
"type": "test_block_bad_colour",
"message0": "test_block_bad_colour",
"extensions": ["mixin_bad_colour_"]
}]);
try {
block = new Blockly.Block(workspace, 'test_block_bad_colour');
} catch (e) {
// Expected Error
assert(e.message.indexOf('colour_') >= 0); // Reference the conflict
return;
}
fail('Expected error when constructing block');
} finally {
block && block.dispose();
workspace.dispose();
delete Blockly.Extensions.ALL_['mixin_bad_colour_'];
delete Blockly.Blocks['test_block_bad_colour'];
}
}
function test_mutator_mixin() {
var workspace = new Blockly.Workspace();
var block;
try {
Blockly.defineBlocksWithJsonArray([{
"type": "mutator_test_block",
"message0": "mutator_test_block",
"mutator": "mutator_test"
}]);
// Events code calls mutationToDom and expects it to give back a meaningful
// value.
Blockly.Events.disable();
Blockly.Extensions.registerMutator('mutator_test',
{
domToMutation: function() {
return 'domToMutationFn';
},
mutationToDom: function() {
return 'mutationToDomFn';
},
compose: function() {
return 'composeFn';
},
decompose: function() {
return 'decomposeFn';
}
});
block = new Blockly.Block(workspace, 'mutator_test_block');
// Make sure all of the functions were installed correctly.
assertEquals(block.domToMutation(), 'domToMutationFn');
assertEquals(block.mutationToDom(), 'mutationToDomFn');
assertEquals(block.compose(), 'composeFn');
assertEquals(block.decompose(), 'decomposeFn');
} finally {
if (block) {
block.dispose();
}
workspace.dispose();
Blockly.Events.enable();
delete Blockly.Extensions.ALL_['mutator_test'];
}
}
function test_mutator_mixin_no_dialog() {
var workspace = new Blockly.Workspace();
var block;
try {
Blockly.defineBlocksWithJsonArray([{
"type": "mutator_test_block",
"message0": "mutator_test_block",
"mutator": "mutator_test"
}]);
// Events code calls mutationToDom and expects it to give back a meaningful
// value.
Blockly.Events.disable();
assertUndefined(Blockly.Extensions.ALL_['mutator_test']);
Blockly.Extensions.registerMutator('mutator_test',
{
domToMutation: function() {
return 'domToMutationFn';
},
mutationToDom: function() {
return 'mutationToDomFn';
}
});
block = new Blockly.Block(workspace, 'mutator_test_block');
// Make sure all of the functions were installed correctly.
assertEquals(block.domToMutation(), 'domToMutationFn');
assertEquals(block.mutationToDom(), 'mutationToDomFn');
assertFalse(block.hasOwnProperty('compose'));
assertFalse(block.hasOwnProperty('decompose'));
} finally {
if (block) {
block.dispose();
}
workspace.dispose();
Blockly.Events.enable();
delete Blockly.Extensions.ALL_['mutator_test'];
}
}
// Explicitly check all four things that could be missing.
function test_mutator_mixin_no_decompose_fails() {
var exceptionWasThrown = false;
try {
Blockly.Extensions.registerMutator('mutator_test',
{
domToMutation: function() {
return 'domToMutationFn';
},
mutationToDom: function() {
return 'mutationToDomFn';
},
compose: function() {
return 'composeFn';
}
});
} catch (e) {
// Expected.
exceptionWasThrown = true;
} finally {
delete Blockly.Extensions.ALL_['mutator_test'];
}
assertTrue(exceptionWasThrown);
}
function test_mutator_mixin_no_compose_fails() {
var exceptionWasThrown = false;
try {
Blockly.Extensions.registerMutator('mutator_test',
{
domToMutation: function() {
return 'domToMutationFn';
},
mutationToDom: function() {
return 'mutationToDomFn';
},
decompose: function() {
return 'decomposeFn';
}
});
} catch (e) {
// Expected.
exceptionWasThrown = true;
} finally {
delete Blockly.Extensions.ALL_['mutator_test'];
}
assertTrue(exceptionWasThrown);
}
function test_mutator_mixin_no_domToMutation_fails() {
var exceptionWasThrown = false;
try {
Blockly.Extensions.registerMutator('mutator_test',
{
mutationToDom: function() {
return 'mutationToDomFn';
},
compose: function() {
return 'composeFn';
},
decompose: function() {
return 'decomposeFn';
}
});
} catch (e) {
// Expected.
exceptionWasThrown = true;
} finally {
delete Blockly.Extensions.ALL_['mutator_test'];
}
assertTrue(exceptionWasThrown);
}
function test_mutator_mixin_no_mutationToDom_fails() {
var exceptionWasThrown = false;
try {
Blockly.Extensions.registerMutator('mutator_test',
{
domToMutation: function() {
return 'domToMutationFn';
},
compose: function() {
return 'composeFn';
},
decompose: function() {
return 'decomposeFn';
}
});
} catch (e) {
// Expected.
exceptionWasThrown = true;
} finally {
delete Blockly.Extensions.ALL_['mutator_test'];
}
assertTrue(exceptionWasThrown);
}
function test_use_mutator_as_extension_fails() {
var workspace = new Blockly.Workspace();
var block;
var exceptionWasThrown = false;
try {
Blockly.defineBlocksWithJsonArray([{
"type": "mutator_test_block",
"message0": "mutator_test_block",
"extensions": ["mutator_test"]
}]);
Blockly.Events.disable();
assertUndefined(Blockly.Extensions.ALL_['mutator_test']);
Blockly.Extensions.registerMutator('mutator_test',
{
domToMutation: function() {
return 'domToMutationFn';
},
mutationToDom: function() {
return 'mutationToDomFn';
}
});
// Events code calls mutationToDom and expects it to give back a meaningful
// value.
block = new Blockly.Block(workspace, 'mutator_test_block');
} catch (e) {
// Expected
exceptionWasThrown = true;
// Should have failed on apply, not on register.
assertNotNull(Blockly.Extensions.ALL_['mutator_test']);
} finally {
if (block) {
block.dispose();
}
workspace.dispose();
Blockly.Events.enable();
delete Blockly.Extensions.ALL_['mutator_test'];
}
assertTrue(exceptionWasThrown);
}
function test_use_mutator_mixin_as_extension_fails() {
var workspace = new Blockly.Workspace();
var block;
var exceptionWasThrown = false;
try {
Blockly.defineBlocksWithJsonArray([{
"type": "mutator_test_block",
"message0": "mutator_test_block",
"extensions": ["mutator_test"]
}]);
// Events code calls mutationToDom and expects it to give back a meaningful
// value.
Blockly.Events.disable();
assertUndefined(Blockly.Extensions.ALL_['mutator_test']);
Blockly.Extensions.registerMixin('mutator_test',
{
domToMutation: function() {
return 'domToMutationFn';
},
mutationToDom: function() {
return 'mutationToDomFn';
}
});
block = new Blockly.Block(workspace, 'mutator_test_block');
} catch (e) {
// Expected
exceptionWasThrown = true;
// Should have failed on apply, not on register.
assertNotNull(Blockly.Extensions.ALL_['mutator_test']);
} finally {
if (block) {
block.dispose();
}
workspace.dispose();
Blockly.Events.enable();
delete Blockly.Extensions.ALL_['mutator_test'];
}
assertTrue(exceptionWasThrown);
}
function test_use_extension_as_mutator_fails() {
var workspace = new Blockly.Workspace();
var block;
var exceptionWasThrown = false;
try {
Blockly.defineBlocksWithJsonArray([{
"type": "mutator_test_block",
"message0": "mutator_test_block",
"mutator": ["extensions_test"]
}]);
// Events code calls mutationToDom and expects it to give back a meaningful
// value.
Blockly.Events.disable();
assertUndefined(Blockly.Extensions.ALL_['extensions_test']);
Blockly.Extensions.register('extensions_test', function() {
return 'extensions_test_fn';
});
block = new Blockly.Block(workspace, 'mutator_test_block');
} catch (e) {
// Expected
exceptionWasThrown = true;
// Should have failed on apply, not on register.
assertNotNull(Blockly.Extensions.ALL_['extensions_test']);
} finally {
if (block) {
block.dispose();
}
workspace.dispose();
Blockly.Events.enable();
delete Blockly.Extensions.ALL_['extensions_test'];
}
assertTrue(exceptionWasThrown);
}
function test_mutator_mixin_plus_function() {
var workspace = new Blockly.Workspace();
var block;
var fnWasCalled = false;
try {
Blockly.defineBlocksWithJsonArray([{
"type": "mutator_test_block",
"message0": "mutator_test_block",
"mutator": ["extensions_test"]
}]);
Blockly.Events.disable();
assertUndefined(Blockly.Extensions.ALL_['extensions_test']);
Blockly.Extensions.registerMutator('extensions_test',
{
domToMutation: function() {
return 'domToMutationFn';
},
mutationToDom: function() {
return 'mutationToDomFn';
}
},
function() {
fnWasCalled = true;
}
);
// Events code calls mutationToDom and expects it to give back a meaningful
// value.
block = new Blockly.Block(workspace, 'mutator_test_block');
} finally {
if (block) {
block.dispose();
}
workspace.dispose();
Blockly.Events.enable();
delete Blockly.Extensions.ALL_['extensions_test'];
}
assertTrue(fnWasCalled);
}

View File

@@ -0,0 +1,44 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2017 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Tests for Blockly.FieldAngle
* @author Anm@anm.me (Andrew n marshall)
*/
'use strict';
function test_fieldangle_constructor() {
assertEquals(new Blockly.FieldAngle().getValue(), '0');
assertEquals(new Blockly.FieldAngle(null).getValue(), '0');
assertEquals(new Blockly.FieldAngle(undefined).getValue(), '0');
assertEquals(new Blockly.FieldAngle(1).getValue(), '1');
assertEquals(new Blockly.FieldAngle(1.5).getValue(), '1.5');
assertEquals(new Blockly.FieldAngle('2').getValue(), '2');
assertEquals(new Blockly.FieldAngle('2.5').getValue(), '2.5');
// Bad values
assertEquals(new Blockly.FieldAngle('bad').getValue(), '0');
assertEquals(new Blockly.FieldAngle(NaN).getValue(), '0');
}
function test_fieldangle_fromJson() {
assertEquals(Blockly.FieldAngle.fromJson({}).getValue(), '0');
assertEquals(Blockly.FieldAngle.fromJson({ angle: 1 }).getValue(), '1');
}

View File

@@ -0,0 +1,82 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2017 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Tests for Blockly.FieldNumber
* @author Anm@anm.me (Andrew n marshall)
*/
'use strict';
function test_fieldnumber_constructor() {
// No arguments
var field = new Blockly.FieldNumber();
assertEquals(field.getValue(), '0');
// Unlike blockly, scratch-blocks doesn't store min, max, and precision.
// TODO: Update this to check the restrictor, based on min, max, and precision.
assertEquals(field.min_, undefined);
assertEquals(field.max_, undefined);
assertEquals(field.precision_, undefined);
// Numeric values
field = new Blockly.FieldNumber(1);
assertEquals(field.getValue(), '1');
field = new Blockly.FieldNumber(1.5);
assertEquals(field.getValue(), '1.5');
// String value
field = new Blockly.FieldNumber('2');
assertEquals(field.getValue(), '2');
field = new Blockly.FieldNumber('2.5');
assertEquals(field.getValue(), '2.5');
// All values
field = new Blockly.FieldNumber(
/* value */ 0,
/* min */ -128,
/* max */ 127,
/* precision */ 1);
// Unlike blockly, scratch-blocks doesn't store min, max, and precision.
assertEquals(field.getValue(), '0');
assertEquals(field.min_, undefined);
assertEquals(field.max_, undefined);
assertEquals(field.precision_, undefined);
// Bad value defaults to '0'
field = new Blockly.FieldNumber('bad');
assertEquals(field.getValue(), '0');
field = new Blockly.FieldNumber(NaN);
assertEquals(field.getValue(), '0');
}
function test_fieldnumber_fromJson() {
assertEquals(Blockly.FieldNumber.fromJson({}).getValue(), '0');
assertEquals(Blockly.FieldNumber.fromJson({ value: 1 }).getValue(), '1');
// All options, but scratch-blocks parses min/max/precision differently from
// Blockly. See notes in field_number.js.
var field = Blockly.FieldNumber.fromJson({
value: 0,
min: -128,
max: 127,
precision: 1
});
assertEquals(field.getValue(), '0');
}

View File

@@ -0,0 +1,126 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2017 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Tests for Blockly.Field
* @author fenichel@google.com (Rachel Fenichel)
*/
'use strict';
function test_field_isEditable_simple() {
var field = new Blockly.Field("Dummy text");
// EDITABLE is true by default, but without a source block a field can't be
// edited.
assertFalse('Field without a block is not editable',
field.isCurrentlyEditable());
}
function test_field_isEditable_false() {
// Setting EDITABLE to false doesn't matter.
var field = new Blockly.Field("Dummy text");
field.EDITABLE = false;
assertFalse('Field without a block is not editable',
field.isCurrentlyEditable());
}
function test_field_isEditable_editableBlock() {
var editableBlock = {
isEditable: function() {
return true;
}
};
var field = new Blockly.Field("Dummy text");
field.sourceBlock_ = editableBlock;
assertTrue('Editable field with editable block is editable',
field.isCurrentlyEditable());
}
function test_field_isEditable_editableBlock_false() {
var editableBlock = {
isEditable: function() {
return true;
}
};
var field = new Blockly.Field("Dummy text");
field.sourceBlock_ = editableBlock;
field.EDITABLE = false;
assertFalse('Non-editable field with editable block is not editable',
field.isCurrentlyEditable());
}
function test_field_isEditable_nonEditableBlock() {
var nonEditableBlock = {
isEditable: function() {
return false;
}
};
var field = new Blockly.Field("Dummy text");
field.sourceBlock_ = nonEditableBlock;
assertFalse('Editable field with non-editable block is not editable',
field.isCurrentlyEditable());
}
function test_field_isEditable_nonEditableBlock_false() {
var nonEditableBlock = {
isEditable: function() {
return false;
}
};
var field = new Blockly.Field("Dummy text");
field.sourceBlock_ = nonEditableBlock;
field.EDITABLE = false;
assertFalse('Non-editable field with non-editable block is not editable',
field.isCurrentlyEditable());
}
function test_field_register_with_custom_field() {
var CustomFieldType = function(value) {
CustomFieldType.superClass_.constructor.call(this, value);
};
goog.inherits(CustomFieldType, Blockly.Field);
CustomFieldType.fromJson = function(options) {
return new CustomFieldType(options['value']);
};
var json = {
type: 'field_custom_test',
value: 'ok'
};
// before registering
var field = Blockly.Field.fromJson(json);
assertNull(field);
Blockly.Field.register('field_custom_test', CustomFieldType);
// after registering
field = Blockly.Field.fromJson(json);
assertNotNull(field);
assertEquals(field.getValue(), 'ok');
}

View File

@@ -0,0 +1,63 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2017 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Tests for Blockly.FieldVariableGetter
* @author fenichel@google.com (Rachel Fenichel)
*/
'use strict';
function test_fieldvariablegetter_constructor() {
var field = new Blockly.FieldVariableGetter('text', 'name');
// The field does not have a variable until after init() is called.
assertEquals(field.getText(), '');
assertEquals(field.getValue(), '');
}
function test_fieldvariablegetter_isEditable() {
var field = new Blockly.FieldVariableGetter('text', 'name');
// EDITABLE is true by default, but without a source block a field can't be
// edited.
assertFalse('Field without a block is not editable',
field.isCurrentlyEditable());
}
function test_fieldvariablegetter_isEditableBlock() {
var field = new Blockly.FieldVariableGetter('text', 'name');
var editableBlock = {
isEditable: function() {
return true;
}
};
field.sourceBlock_ = editableBlock;
// Variable getter fields aren't user editable.
assertFalse('Variable getter field should not be editable',
field.isCurrentlyEditable());
}
function test_fieldvariablegetter_isSerializable() {
var field = new Blockly.FieldVariableGetter('text', 'name');
// Variable getter fields are serializable by default.
assertTrue('Variable getter field should be serializable',
field.SERIALIZABLE);
}

View File

@@ -0,0 +1,202 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2017 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Tests for Blockly.FieldVariable
* @author marisaleung@google.com (Marisa Leung)
*/
'use strict';
goog.require('goog.testing');
goog.require('goog.testing.MockControl');
var workspace;
var mockControl_;
function fieldVariableTestWithMocks_setUp() {
workspace = new Blockly.Workspace();
mockControl_ = new goog.testing.MockControl();
}
function fieldVariableTestWithMocks_tearDown() {
mockControl_.$tearDown();
workspace.dispose();
}
function fieldVariable_mockBlock(workspace) {
return {'workspace': workspace, 'isShadow': function(){return false;}};
}
function fieldVariable_createAndInitField(workspace) {
var fieldVariable = new Blockly.FieldVariable('name1');
var mockBlock = fieldVariable_mockBlock(workspace);
fieldVariable.setSourceBlock(mockBlock);
// No view to initialize, but the model still needs work.
fieldVariable.initModel();
return fieldVariable;
}
function test_fieldVariable_Constructor() {
workspace = new Blockly.Workspace();
var fieldVariable = new Blockly.FieldVariable('name1');
// The field does not have a variable until after init() is called.
assertEquals('', fieldVariable.getText());
workspace.dispose();
}
function test_fieldVariable_setValueMatchId() {
// Expect the fieldVariable value to be set to variable name
fieldVariableTestWithMocks_setUp();
workspace.createVariable('name2', null, 'id2');
var fieldVariable = fieldVariable_createAndInitField(workspace);
var oldId = fieldVariable.getValue();
var event = new Blockly.Events.BlockChange(
fieldVariable.sourceBlock_, 'field', undefined, oldId, 'id2');
setUpMockMethod(mockControl_, Blockly.Events, 'fire', [event], null);
fieldVariable.setValue('id2');
assertEquals('name2', fieldVariable.getText());
assertEquals('id2', fieldVariable.getValue());
fieldVariableTestWithMocks_tearDown();
}
function test_fieldVariable_setValueNoVariable() {
fieldVariableTestWithMocks_setUp();
var fieldVariable = fieldVariable_createAndInitField(workspace);
var mockBlock = fieldVariable.sourceBlock_;
mockBlock.isShadow = function() {
return false;
};
try {
fieldVariable.setValue('id1');
// Calling setValue with a variable that doesn't exist throws an error.
fail();
} catch (e) {
// expected
} finally {
fieldVariableTestWithMocks_tearDown();
}
}
function test_fieldVariable_dropdownCreateVariablesExist() {
// Expect that the dropdown options will contain the variables that exist.
workspace = new Blockly.Workspace();
workspace.createVariable('name1', '', 'id1');
workspace.createVariable('name2', '', 'id2');
var fieldVariable = fieldVariable_createAndInitField(workspace);
var result_options = Blockly.FieldVariable.dropdownCreate.call(
fieldVariable);
assertEquals(result_options.length, 3);
isEqualArrays(result_options[0], ['name1', 'id1']);
isEqualArrays(result_options[1], ['name2', 'id2']);
workspace.dispose();
}
function test_fieldVariable_setValueNull() {
// This should no longer create a variable for the selected option.
fieldVariableTestWithMocks_setUp();
setUpMockMethod(mockControl_, Blockly.utils, 'genUid', null, ['id1', null]);
var fieldVariable = fieldVariable_createAndInitField(workspace);
try {
fieldVariable.setValue(null);
fail();
} catch (e) {
// expected
} finally {
fieldVariableTestWithMocks_tearDown();
}
}
function test_fieldVariable_getVariableTypes_undefinedVariableTypes() {
// Expect that since variableTypes is undefined, only type empty string
// will be returned (regardless of what types are available on the workspace).
workspace = new Blockly.Workspace();
workspace.createVariable('name1', 'type1');
workspace.createVariable('name2', 'type2');
var fieldVariable = new Blockly.FieldVariable('name1');
var resultTypes = fieldVariable.getVariableTypes_();
isEqualArrays(resultTypes, ['']);
workspace.dispose();
}
function test_fieldVariable_getVariableTypes_givenVariableTypes() {
// Expect that since variableTypes is defined, it will be the return value,
// regardless of what types are available on the workspace.
workspace = new Blockly.Workspace();
workspace.createVariable('name1', 'type1');
workspace.createVariable('name2', 'type2');
var fieldVariable = new Blockly.FieldVariable(
'name1', null, ['type1', 'type2']);
var resultTypes = fieldVariable.getVariableTypes_();
isEqualArrays(resultTypes, ['type1', 'type2']);
workspace.dispose();
}
function test_fieldVariable_getVariableTypes_nullVariableTypes() {
// Expect all variable types to be returned.
// The variable does not need to be initialized to do this--it just needs a
// pointer to the workspace.
workspace = new Blockly.Workspace();
workspace.createVariable('name1', 'type1');
workspace.createVariable('name2', 'type2');
var fieldVariable = new Blockly.FieldVariable('name1');
var mockBlock = fieldVariable_mockBlock(workspace);
fieldVariable.setSourceBlock(mockBlock);
fieldVariable.variableTypes = null;
var resultTypes = fieldVariable.getVariableTypes_();
// The empty string is always one of the options.
isEqualArrays(resultTypes, ['type1', 'type2', '']);
workspace.dispose();
}
function test_fieldVariable_getVariableTypes_emptyListVariableTypes() {
// Expect an error to be thrown.
workspace = new Blockly.Workspace();
workspace.createVariable('name1', 'type1');
workspace.createVariable('name2', 'type2');
var fieldVariable = new Blockly.FieldVariable('name1');
var mockBlock = fieldVariable_mockBlock(workspace);
fieldVariable.setSourceBlock(mockBlock);
fieldVariable.variableTypes = [];
try {
fieldVariable.getVariableTypes_();
fail();
} catch (e) {
//expected
} finally {
workspace.dispose();
}
}

View File

@@ -0,0 +1,28 @@
/**
* @license
* Blockly Tests
*
* Copyright 2012 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
function test_prefix() {
var generator = new Blockly.Generator('INTERCAL');
assertEquals('Prefix nothing.', '', generator.prefixLines('', ''));
assertEquals('Prefix a word.', '@Hello', generator.prefixLines('Hello', '@'));
assertEquals('Prefix one line.', '12Hello\n', generator.prefixLines('Hello\n', '12'));
assertEquals('Prefix two lines.', '***Hello\n***World\n', generator.prefixLines('Hello\nWorld\n', '***'));
}

View File

@@ -0,0 +1,90 @@
/**
* @license
* Blockly Tests
*
* Copyright 2017 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Tests for gesture.
* @author marisaleung@google.com (Marisa Leung)
*/
'use strict';
var e;
var workspace;
function gestureTest_setUp() {
workspace = new Blockly.Workspace();
e = {};
}
function gestureTest_tearDown() {
e = null;
workspace.dispose();
}
function test_gestureConstructor() {
var gesture = new Blockly.Gesture(e, workspace);
assertEquals(gesture.mostRecentEvent_, e);
assertEquals(gesture.creatorWorkspace_, workspace);
}
function test_gestureIsField_ClickInWorkspace() {
gestureTest_setUp();
var block = new Blockly.Block(workspace);
var field = new Blockly.Field();
field.setSourceBlock(block);
var gesture = new Blockly.Gesture(e, workspace);
gesture.setStartField(field);
var isFieldClick = gesture.isFieldClick_();
assertEquals(isFieldClick, true);
gestureTest_tearDown();
}
function gestureIsFieldClick_InFlyoutHelper(flyout, expectedResult){
// Assign workspace flyout
workspace.flyout_ = flyout;
// Create a Field inside of a Block
var block = new Blockly.Block(workspace);
var field = new Blockly.Field();
field.setSourceBlock(block);
// Create gesture from the flyout
var gesture = new Blockly.Gesture(e, workspace.flyout_);
// Populate gesture with click start information
gesture.setStartField(field);
gesture.setStartFlyout_(workspace.flyout_);
var isFieldClick = gesture.isFieldClick_();
assertEquals(isFieldClick, expectedResult);
}
function test_gestureIsFieldClick_AutoCloseFlyout() {
gestureTest_setUp();
var flyout = new Blockly.VerticalFlyout({});
gestureIsFieldClick_InFlyoutHelper(flyout, false);
gestureTest_tearDown();
}
function test_gestureIsFieldClick_AlwaysOpenFlyout() {
gestureTest_setUp();
var flyout = new Blockly.VerticalFlyout({});
flyout.autoClose = false;
gestureIsFieldClick_InFlyoutHelper(flyout, true);
gestureTest_tearDown();
}

View File

@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Unit Tests for Horizontal Scratch-Blockly</title>
<script src="../../blockly_uncompressed_horizontal.js"></script>
<script>goog.require('goog.testing.jsunit');</script>
</head>
<body>
<script src="test_utilities.js"></script>
<script src="block_test.js"></script>
<script src="connection_test.js"></script>
<script src="connection_db_test.js"></script>
<script src="extensions_test.js"></script>
<script src="field_test.js"></script>
<script src="field_angle_test.js"></script>
<script src="field_number_test.js"></script>
<script src="generator_test.js"></script>
<script src="input_test.js"></script>
<script src="json_test.js"></script>
<script src="names_test.js"></script>
<script src="procedure_test.js"></script>
<script src="svg_test.js"></script>
<script src="utils_test.js"></script>
<script src="workspace_comment_test.js"></script>
<script src="workspace_test.js"></script>
<script src="xml_test.js"></script>
<script src="widget_div_test.js"></script>
<div id="blocklyDiv" style="display: none; height: 480px; width: 600px;"></div>
<xml id="toolbox" style="display: none"></xml>
</body>
</html>

View File

@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script src="test_utilities.js"></script>
<script src="utils_test.js"></script>
<script src="connection_test.js"></script>
<script src="connection_db_test.js"></script>
<script src="extensions_test.js"></script>
<script src="event_test.js"></script>
<script src="field_test.js"></script>
<script src="field_angle_test.js"></script>
<script src="field_number_test.js"></script>
<script src="field_variable_test.js"></script>
<script src="generator_test.js"></script>
<script src="gesture_test.js"></script>
<script src="input_test.js"></script>
<script src="names_test.js"></script>
<script src="workspace_test.js"></script>
<script src="workspace_undo_redo_test.js"></script>
<script src="xml_test.js"></script>
<script src="json_test.js"></script>
<script src="variable_model_test.js"></script>
<script src="variable_map_test.js"></script>
<script src="widget_div_test.js"></script>
</body>
</html>

View File

@@ -0,0 +1,202 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2017 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Tests for Blockly.Input
*/
'use strict';
function test_appendField_simple() {
var ws = new Blockly.Workspace();
var block = new Blockly.Block(ws);
var input = new Blockly.Input(Blockly.DUMMY_INPUT, 'INPUT', block);
var field1 = new Blockly.FieldLabel('#1');
var field2 = new Blockly.FieldLabel('#2');
// Preconditions
assertEquals(0, input.fieldRow.length);
// Actual Tests
input.appendField(field1, 'first');
assertEquals(1, input.fieldRow.length);
assertEquals(field1, input.fieldRow[0]);
assertEquals('first', input.fieldRow[0].name);
assertEquals(block, field1.sourceBlock_);
input.appendField(field2, 'second');
assertEquals(2, input.fieldRow.length);
assertEquals(field2, input.fieldRow[1]);
assertEquals('second', input.fieldRow[1].name);
assertEquals(block, field2.sourceBlock_);
}
function test_appendField_string() {
var ws = new Blockly.Workspace();
var block = new Blockly.Block(ws);
var input = new Blockly.Input(Blockly.DUMMY_INPUT, 'INPUT', block);
var labelText = 'label';
// Preconditions
assertEquals(0, input.fieldRow.length);
// Actual Tests
input.appendField(labelText, 'name');
assertEquals(1, input.fieldRow.length);
assertEquals(Blockly.FieldLabel, input.fieldRow[0].constructor);
assertEquals(labelText, input.fieldRow[0].getValue());
assertEquals('name', input.fieldRow[0].name);
}
function test_appendField_prefix() {
var ws = new Blockly.Workspace();
var block = new Blockly.Block(ws);
var input = new Blockly.Input(Blockly.DUMMY_INPUT, 'INPUT', block);
var prefix = new Blockly.FieldLabel('prefix');
var field = new Blockly.FieldLabel('field');
field.prefixField = prefix;
// Preconditions
assertEquals(0, input.fieldRow.length);
// Actual Tests
input.appendField(field);
assertEquals(2, input.fieldRow.length);
assertEquals(prefix, input.fieldRow[0]);
assertEquals(field, input.fieldRow[1]);
}
function test_appendField_suffix() {
var ws = new Blockly.Workspace();
var block = new Blockly.Block(ws);
var input = new Blockly.Input(Blockly.DUMMY_INPUT, 'INPUT', block);
var suffix = new Blockly.FieldLabel('suffix');
var field = new Blockly.FieldLabel('field');
field.suffixField = suffix;
// Preconditions
assertEquals(0, input.fieldRow.length);
// Actual Tests
input.appendField(field);
assertEquals(2, input.fieldRow.length);
assertEquals(field, input.fieldRow[0]);
assertEquals(suffix, input.fieldRow[1]);
}
function test_insertFieldAt_simple() {
var ws = new Blockly.Workspace();
var block = new Blockly.Block(ws);
var input = new Blockly.Input(Blockly.DUMMY_INPUT, 'INPUT', block);
var before = new Blockly.FieldLabel('before');
var after = new Blockly.FieldLabel('after');
var between = new Blockly.FieldLabel('between');
input.appendField(before);
input.appendField(after);
// Preconditions
assertEquals(2, input.fieldRow.length);
assertEquals(before, input.fieldRow[0]);
assertEquals(after, input.fieldRow[1]);
// Actual Tests
input.insertFieldAt(1, between, 'name');
assertEquals(3, input.fieldRow.length);
assertEquals(before, input.fieldRow[0]);
assertEquals(between, input.fieldRow[1]);
assertEquals('name', input.fieldRow[1].name);
assertEquals(after, input.fieldRow[2]);
}
function test_insertFieldAt_string() {
var ws = new Blockly.Workspace();
var block = new Blockly.Block(ws);
var input = new Blockly.Input(Blockly.DUMMY_INPUT, 'INPUT', block);
var before = new Blockly.FieldLabel('before');
var after = new Blockly.FieldLabel('after');
var labelText = 'label';
input.appendField(before);
input.appendField(after);
// Preconditions
assertEquals(2, input.fieldRow.length);
assertEquals(before, input.fieldRow[0]);
assertEquals(after, input.fieldRow[1]);
// Actual Tests
input.insertFieldAt(1, labelText, 'name');
assertEquals(3, input.fieldRow.length);
assertEquals(before, input.fieldRow[0]);
assertEquals(Blockly.FieldLabel, input.fieldRow[1].constructor);
assertEquals(labelText, input.fieldRow[1].getValue());
assertEquals('name', input.fieldRow[1].name);
assertEquals(after, input.fieldRow[2]);
}
function test_insertFieldAt_prefix() {
var ws = new Blockly.Workspace();
var block = new Blockly.Block(ws);
var input = new Blockly.Input(Blockly.DUMMY_INPUT, 'INPUT', block);
var before = new Blockly.FieldLabel('before');
var after = new Blockly.FieldLabel('after');
var prefix = new Blockly.FieldLabel('prefix');
var between = new Blockly.FieldLabel('between');
between.prefixField = prefix
input.appendField(before);
input.appendField(after);
// Preconditions
assertEquals(2, input.fieldRow.length);
assertEquals(before, input.fieldRow[0]);
assertEquals(after, input.fieldRow[1]);
// Actual Tests
input.insertFieldAt(1, between);
assertEquals(4, input.fieldRow.length);
assertEquals(before, input.fieldRow[0]);
assertEquals(prefix, input.fieldRow[1]);
assertEquals(between, input.fieldRow[2]);
assertEquals(after, input.fieldRow[3]);
}
function test_insertFieldAt_prefix() {
var ws = new Blockly.Workspace();
var block = new Blockly.Block(ws);
var input = new Blockly.Input(Blockly.DUMMY_INPUT, 'INPUT', block);
var before = new Blockly.FieldLabel('before');
var after = new Blockly.FieldLabel('after');
var suffix = new Blockly.FieldLabel('suffix');
var between = new Blockly.FieldLabel('between');
between.suffixField = suffix
input.appendField(before);
input.appendField(after);
// Preconditions
assertEquals(2, input.fieldRow.length);
assertEquals(before, input.fieldRow[0]);
assertEquals(after, input.fieldRow[1]);
// Actual Tests
input.insertFieldAt(1, between);
assertEquals(4, input.fieldRow.length);
assertEquals(before, input.fieldRow[0]);
assertEquals(between, input.fieldRow[1]);
assertEquals(suffix, input.fieldRow[2]);
assertEquals(after, input.fieldRow[3]);
}

View File

@@ -0,0 +1,260 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2017 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
/** Ensure a block can be instantiated from a JSON definition. */
function test_json_minimal() {
var BLOCK_TYPE = 'test_json_minimal';
var workspace = new Blockly.Workspace();
var block;
try {
Blockly.defineBlocksWithJsonArray([{
"type": BLOCK_TYPE
}]);
block = new Blockly.Block(workspace, BLOCK_TYPE);
assertEquals(BLOCK_TYPE, block.type);
// TODO: asserts
} finally {
block.dispose();
workspace.dispose();
delete Blockly.Blocks[BLOCK_TYPE];
}
}
/** Ensure message0 creates an input. */
function test_json_message0() {
var BLOCK_TYPE = 'test_json_message0';
var MESSAGE0 = 'message0';
var workspace = new Blockly.Workspace();
var block;
try {
Blockly.defineBlocksWithJsonArray([{
"type": BLOCK_TYPE,
"message0": MESSAGE0
}]);
block = new Blockly.Block(workspace, BLOCK_TYPE);
assertEquals(1, block.inputList.length);
assertEquals(1, block.inputList[0].fieldRow.length);
var textField = block.inputList[0].fieldRow[0];
assertEquals(Blockly.FieldLabel, textField.constructor);
assertEquals(MESSAGE0, textField.getText());
} finally {
block && block.dispose();
workspace.dispose();
delete Blockly.Blocks[BLOCK_TYPE];
}
}
/** Ensure message1 creates a new input. */
function test_json_message1() {
var BLOCK_TYPE = 'test_json_message1';
var MESSAGE0 = 'message0';
var MESSAGE1 = 'message1';
var workspace = new Blockly.Workspace();
var block;
try {
Blockly.defineBlocksWithJsonArray([{
"type": BLOCK_TYPE,
"message0": MESSAGE0,
"message1": MESSAGE1
}]);
block = new Blockly.Block(workspace, BLOCK_TYPE);
assertEquals(2, block.inputList.length);
assertEquals(1, block.inputList[0].fieldRow.length);
var textField = block.inputList[0].fieldRow[0];
assertEquals(Blockly.FieldLabel, textField.constructor);
assertEquals(MESSAGE0, textField.getText());
assertEquals(1, block.inputList[1].fieldRow.length);
var textField = block.inputList[1].fieldRow[0];
assertEquals(Blockly.FieldLabel, textField.constructor);
assertEquals(MESSAGE1, textField.getText());
} finally {
block && block.dispose();
workspace.dispose();
delete Blockly.Blocks[BLOCK_TYPE];
}
}
/** Ensure message string is dereferenced. */
function test_json_message0_i18n() {
var BLOCK_TYPE = 'test_json_message0_i18n';
var MESSAGE0 = '%{BKY_MESSAGE}';
var MESSAGE = 'message';
Blockly.Msg['MESSAGE'] = MESSAGE;
var workspace = new Blockly.Workspace();
var block;
try {
Blockly.defineBlocksWithJsonArray([{
"type": BLOCK_TYPE,
"message0": MESSAGE0
}]);
block = new Blockly.Block(workspace, BLOCK_TYPE);
assertEquals(1, block.inputList.length);
assertEquals(1, block.inputList[0].fieldRow.length);
var textField = block.inputList[0].fieldRow[0];
assertEquals(Blockly.FieldLabel, textField.constructor);
assertEquals(MESSAGE, textField.getText());
} finally {
block && block.dispose(); // Disposes of textField, too.
workspace.dispose();
delete Blockly.Blocks[BLOCK_TYPE];
delete Blockly.Msg['MESSAGE'];
}
}
function test_json_dropdown() {
var BLOCK_TYPE = 'test_json_dropdown';
var FIELD_NAME = 'FIELD_NAME';
var LABEL0 = 'LABEL0';
var VALUE0 = 'VALUE0';
var LABEL1 = 'LABEL1';
var VALUE1 = 'VALUE1';
var workspace = new Blockly.Workspace();
var block;
try {
Blockly.defineBlocksWithJsonArray([{
"type": BLOCK_TYPE,
"message0": "%1",
"args0": [
{
"type": "field_dropdown",
"name": FIELD_NAME,
"options": [
[LABEL0, VALUE0],
[LABEL1, VALUE1]
]
}
]
}]);
block = new Blockly.Block(workspace, BLOCK_TYPE);
assertEquals(1, block.inputList.length);
assertEquals(1, block.inputList[0].fieldRow.length);
var dropdown = block.inputList[0].fieldRow[0];
assertEquals(dropdown, block.getField(FIELD_NAME));
assertEquals(Blockly.FieldDropdown, dropdown.constructor);
assertEquals(VALUE0, dropdown.getValue());
var options = dropdown.getOptions();
assertEquals(LABEL0, options[0][0]);
assertEquals(VALUE0, options[0][1]);
assertEquals(LABEL1, options[1][0]);
assertEquals(VALUE1, options[1][1]);
} finally {
block && block.dispose(); // Disposes of dropdown, too.
workspace.dispose();
delete Blockly.Blocks[BLOCK_TYPE];
}
}
function test_json_dropdown_image() {
var BLOCK_TYPE = 'test_json_dropdown';
var FIELD_NAME = 'FIELD_NAME';
var IMAGE1_ALT_TEXT = 'Localized message.';
Blockly.Msg['ALT_TEXT'] = IMAGE1_ALT_TEXT;
var IMAGE0 = {
'width': 12,
'height': 34,
'src': 'http://image0.src',
'alt': 'IMAGE0 alt text'
};
var VALUE0 = 'VALUE0';
var IMAGE1 = {
'width': 56,
'height': 78,
'src': 'http://image1.src',
'alt': '%{BKY_ALT_TEXT}'
};
var VALUE1 = 'VALUE1';
var IMAGE2 = {
'width': 90,
'height': 123,
'src': 'http://image2.src'
};
var VALUE2 = 'VALUE2';
var workspace = new Blockly.Workspace();
var block;
try {
Blockly.defineBlocksWithJsonArray([{
"type": BLOCK_TYPE,
"message0": "%1",
"args0": [
{
"type": "field_dropdown",
"name": FIELD_NAME,
"options": [
[IMAGE0, VALUE0],
[IMAGE1, VALUE1],
[IMAGE2, VALUE2]
]
}
]
}]);
block = new Blockly.Block(workspace, BLOCK_TYPE);
assertEquals(1, block.inputList.length);
assertEquals(1, block.inputList[0].fieldRow.length);
var dropdown = block.inputList[0].fieldRow[0];
assertEquals(dropdown, block.getField(FIELD_NAME));
assertEquals(Blockly.FieldDropdown, dropdown.constructor);
assertEquals(VALUE0, dropdown.getValue());
var options = dropdown.getOptions();
var image0 = options[0][0];
assertEquals(IMAGE0.width, image0.width);
assertEquals(IMAGE0.height, image0.height);
assertEquals(IMAGE0.src, image0.src);
assertEquals(IMAGE0.alt, image0.alt);
assertEquals(VALUE0, options[0][1]);
var image1 = options[1][0];
assertEquals(IMAGE1.width, image1.width);
assertEquals(IMAGE1.height, image1.height);
assertEquals(IMAGE1.src, image1.src);
assertEquals(IMAGE1.alt, IMAGE1_ALT_TEXT); // Via Msg reference
assertEquals(VALUE1, options[1][1]);
var image2 = options[2][0];
assertEquals(IMAGE2.width, image2.width);
assertEquals(IMAGE2.height, image2.height);
assertEquals(IMAGE2.src, image2.src);
assert(image2.alt == null); // No alt specified.
assertEquals(VALUE2, options[2][1]);
} finally {
block && block.dispose(); // Disposes of dropdown, too.
workspace.dispose();
delete Blockly.Blocks[BLOCK_TYPE];
delete Blockly.Msg['ALTTEXT'];
}
}

View File

@@ -0,0 +1,62 @@
/**
* @license
* Blockly Tests
*
* Copyright 2012 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
function test_safeName() {
var varDB = new Blockly.Names('window,door');
assertEquals('SafeName empty.', 'unnamed', varDB.safeName_(''));
assertEquals('SafeName ok.', 'foobar', varDB.safeName_('foobar'));
assertEquals('SafeName number start.', 'my_9lives',
varDB.safeName_('9lives'));
assertEquals('SafeName number end.', 'lives9', varDB.safeName_('lives9'));
assertEquals('SafeName special chars.', '____', varDB.safeName_('!@#$'));
assertEquals('SafeName reserved.', 'door', varDB.safeName_('door'));
}
function test_getName() {
var varDB = new Blockly.Names('window,door');
assertEquals('Name add #1.', 'Foo_bar', varDB.getName('Foo.bar', 'var'));
assertEquals('Name get #1.', 'Foo_bar', varDB.getName('Foo.bar', 'var'));
assertEquals('Name add #2.', 'Foo_bar2', varDB.getName('Foo bar', 'var'));
assertEquals('Name get #2.', 'Foo_bar2', varDB.getName('foo BAR', 'var'));
assertEquals('Name add #3.', 'door2', varDB.getName('door', 'var'));
assertEquals('Name add #4.', 'Foo_bar3', varDB.getName('Foo.bar', 'proc'));
assertEquals('Name get #1b.', 'Foo_bar', varDB.getName('Foo.bar', 'var'));
assertEquals('Name get #4.', 'Foo_bar3', varDB.getName('Foo.bar', 'proc'));
}
function test_getDistinctName() {
var varDB = new Blockly.Names('window,door');
assertEquals('Name distinct #1.', 'Foo_bar',
varDB.getDistinctName('Foo.bar', 'var'));
assertEquals('Name distinct #2.', 'Foo_bar2',
varDB.getDistinctName('Foo.bar', 'var'));
assertEquals('Name distinct #3.', 'Foo_bar3',
varDB.getDistinctName('Foo.bar', 'proc'));
varDB.reset();
assertEquals('Name distinct #4.', 'Foo_bar',
varDB.getDistinctName('Foo.bar', 'var'));
}
function test_nameEquals() {
assertTrue('Name equals #1.', Blockly.Names.equals('Foo.bar', 'Foo.bar'));
assertFalse('Name equals #2.', Blockly.Names.equals('Foo.bar', 'Foo_bar'));
assertTrue('Name equals #3.', Blockly.Names.equals('Foo.bar', 'FOO.BAR'));
}

View File

@@ -0,0 +1,341 @@
/**
* @license
* Blockly Tests
*
* Copyright 2017 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
goog.require('goog.testing');
goog.require('goog.testing.MockControl');
var workspace;
//var mockControl_;
function procedureTest_setUp() {
Blockly.Blocks[Blockly.PROCEDURES_CALL_BLOCK_TYPE] = {
init: function() {
this.procCode_ = '';
this.setPreviousStatement(true);
this.setNextStatement(true);
},
getProcCode: function() {
return this.procCode_;
}
};
Blockly.Blocks['foo'] = {
init: function() {
this.jsonInit({
"message0": "foo",
"previousStatement": null,
"nextStatement": null
});
this.setNextStatement(true);
}
};
Blockly.Blocks['loop'] = {
init: function() {
this.jsonInit({ message0: 'forever %1',
"args0": [
{
"type": "input_statement",
"name": "SUBSTACK"
}
]});
}
};
workspace = new Blockly.Workspace();
//mockControl_ = new goog.testing.MockControl();
}
function procedureTest_tearDown() {
delete Blockly.Blocks[Blockly.PROCEDURES_CALL_BLOCK_TYPE];
delete Blockly.Blocks['foo'];
delete Blockly.Blocks['loop'];
//mockControl_.$tearDown();
workspace.dispose();
}
function test_findCallers_simple_oneCaller() {
var xml = '<xml xmlns="http://www.w3.org/1999/xhtml">' +
'<variables></variables>' +
'<block type="' + Blockly.PROCEDURES_CALL_BLOCK_TYPE +
'" id="test_1" x="301" y="516">' +
'</block>' +
'</xml>';
procedureTest_setUp();
try {
Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(xml), workspace);
workspace.getBlockById('test_1').procCode_ = 'test_procedure';
var callers = Blockly.Procedures.getCallers('test_procedure', workspace,
{id: ''}, false);
assertEquals(1, callers.length);
assertEquals('test_1', callers[0].id);
}
finally {
procedureTest_tearDown();
}
}
function test_findCallers_noRecursion() {
var xml = '<xml xmlns="http://www.w3.org/1999/xhtml">' +
'<variables></variables>' +
'<block type="' + Blockly.PROCEDURES_CALL_BLOCK_TYPE +
'" id="test_1" x="301" y="516">' +
'</block>' +
'</xml>';
procedureTest_setUp();
try {
Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(xml), workspace);
workspace.getBlockById('test_1').procCode_ = 'test_procedure';
var rootBlock = workspace.getBlockById('test_1');
var callers = Blockly.Procedures.getCallers('test_procedure', workspace,
rootBlock, false /* allowRecursion */);
// There was a single call to this function, but it was in a stack that
// should be ignored.
assertEquals(0, callers.length);
}
finally {
procedureTest_tearDown();
}
}
function test_findCallers_allowRecursion() {
var xml = '<xml xmlns="http://www.w3.org/1999/xhtml">' +
'<variables></variables>' +
'<block type="' + Blockly.PROCEDURES_CALL_BLOCK_TYPE +
'" id="test_1" x="301" y="516">' +
'</block>' +
'</xml>';
procedureTest_setUp();
try {
Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(xml), workspace);
workspace.getBlockById('test_1').procCode_ = 'test_procedure';
var rootBlock = workspace.getBlockById('test_1');
var callers = Blockly.Procedures.getCallers('test_procedure', workspace,
rootBlock, true /* allowRecursion */);
// There was a single call to this function, in the same stack as the
// definition root. Recursion is allowed, so it should be found.
assertEquals(1, callers.length);
assertEquals('test_1', callers[0].id);
}
finally {
procedureTest_tearDown();
}
}
function test_findCallers_simple_noCallers() {
var xml = '<xml xmlns="http://www.w3.org/1999/xhtml">' +
'<variables></variables>' +
'<block type="foo" id="test_1" x="301" y="516">' +
'</block>' +
'</xml>';
procedureTest_setUp();
try {
Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(xml), workspace);
var callers = Blockly.Procedures.getCallers('test_procedure', workspace,
{id: ''}, false);
// There weren't even blocks of type procedures_callnoreturn.
assertEquals(0, callers.length);
}
finally {
procedureTest_tearDown();
}
}
function test_findCallers_wrongProcCode() {
var xml = '<xml xmlns="http://www.w3.org/1999/xhtml">' +
'<variables></variables>' +
'<block type="' + Blockly.PROCEDURES_CALL_BLOCK_TYPE +
'" id="test_1" x="301" y="516">' +
'</block>' +
'</xml>';
procedureTest_setUp();
try {
Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(xml), workspace);
workspace.getBlockById('test_1').procCode_ = 'wrong procedure';
var callers = Blockly.Procedures.getCallers('test_procedure', workspace,
{id: ''}, false);
// There was a procedure_callnoreturn call, but it had the wrong procCode.
assertEquals(0, callers.length);
}
finally {
procedureTest_tearDown();
}
}
function test_findCallers_onStatementInput() {
var xml = '<xml xmlns="http://www.w3.org/1999/xhtml">' +
'<block type="loop" id="test_1">' +
'<statement name="SUBSTACK">' +
'<block type="foo" id="test_2">' +
'<next>' +
'<block type="' + Blockly.PROCEDURES_CALL_BLOCK_TYPE +
'" id="test_3"></block>' +
'</next></block>' +
'</statement>' +
'</block>' +
'</xml>';
procedureTest_setUp();
try {
Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(xml), workspace);
workspace.getBlockById('test_3').procCode_ = 'test_procedure';
var callers = Blockly.Procedures.getCallers('test_procedure', workspace,
{id: ''}, false);
// There should be one caller, connected to a stack on a statement input.
assertEquals(1, callers.length);
assertEquals('test_3', callers[0].id);
}
finally {
procedureTest_tearDown();
}
}
function test_findCallers_multipleStacks() {
var xml = '<xml xmlns="http://www.w3.org/1999/xhtml">' +
'<block type="foo" id="test_1"></block>' +
'<block type="' + Blockly.PROCEDURES_CALL_BLOCK_TYPE +
'" id="test_2"></block>'+
'<block type="foo" id="test_1"></block>' +
'</xml>';
procedureTest_setUp();
try {
Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(xml), workspace);
workspace.getBlockById('test_2').procCode_ = 'test_procedure';
var callers = Blockly.Procedures.getCallers('test_procedure', workspace,
{id: ''}, false);
// There should be one caller, but multiple stacks in the workspace.
assertEquals(1, callers.length);
assertEquals('test_2', callers[0].id);
}
finally {
procedureTest_tearDown();
}
}
function test_findCallers_multipleCallers() {
var xml = '<xml xmlns="http://www.w3.org/1999/xhtml">' +
'<block type="' + Blockly.PROCEDURES_CALL_BLOCK_TYPE +
'" id="test_1"></block>' +
'<block type="' + Blockly.PROCEDURES_CALL_BLOCK_TYPE +
'" id="test_2"></block>'+
'</xml>';
procedureTest_setUp();
try {
Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(xml), workspace);
workspace.getBlockById('test_2').procCode_ = 'test_procedure';
workspace.getBlockById('test_1').procCode_ = 'test_procedure';
var callers = Blockly.Procedures.getCallers('test_procedure', workspace,
{id: ''}, false);
// There should be two callers, on two different stacks.
assertEquals(2, callers.length);
// Order doesn't matter.
assertTrue(callers[0].id == 'test_1' || callers[0].id == 'test_2');
assertTrue(callers[1].id == 'test_1' || callers[1].id == 'test_2');
}
finally {
procedureTest_tearDown();
}
}
function test_deleteProcedure_noCallers() {
// If there are no callers, the stack should be deleted.
procedureTest_setUp();
var xml = '<xml xmlns="http://www.w3.org/1999/xhtml">' +
'<block type="' + Blockly.PROCEDURES_CALL_BLOCK_TYPE +
'" id="test_1" x="301" y="516"></block>' +
'<block type="foo" id="test_2"></block>' +
'<block type="foo" id="test_3"></block>' +
'</block>' +
'</xml>';
try {
Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(xml), workspace);
workspace.getBlockById('test_1').procCode_ = 'test_procedure';
var rootBlock = workspace.getBlockById('test_1');
assertTrue(Blockly.Procedures.deleteProcedureDefCallback('test_procedure',
rootBlock));
// The other two blocks should stick around.
assertEquals(2, workspace.getTopBlocks().length);
}
finally {
procedureTest_tearDown();
}
}
function test_deleteProcedure_recursiveCaller() {
// If there is a caller but it's a part of stack starting with definitionRoot,
// the stack should be deleted.
procedureTest_setUp();
var xml = '<xml xmlns="http://www.w3.org/1999/xhtml">' +
'<block type="loop" id="test_1">' +
'<statement name="SUBSTACK">' +
'<block type="foo" id="test_2">' +
'<next>' +
'<block type="' + Blockly.PROCEDURES_CALL_BLOCK_TYPE +
'" id="test_3"></block>' +
'</next></block>' +
'</statement>' +
'</block>' +
'</block>' +
'</xml>';
try {
Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(xml), workspace);
workspace.getBlockById('test_3').procCode_ = 'test_procedure';
var rootBlock = workspace.getBlockById('test_1');
assertTrue(Blockly.Procedures.deleteProcedureDefCallback('test_procedure',
rootBlock));
assertEquals(0, workspace.getTopBlocks().length);
}
finally {
procedureTest_tearDown();
}
}
function test_deleteProcedure_nonRecursiveCaller() {
// If there is a caller and it's not part of the procedure definition, the
// stack should not be deleted.
procedureTest_setUp();
var xml = '<xml xmlns="http://www.w3.org/1999/xhtml">' +
'<block type="' + Blockly.PROCEDURES_CALL_BLOCK_TYPE +
'" id="test_1" x="301" y="516"></block>' +
'<block type="foo" id="test_2"></block>' +
'<block type="foo" id="test_3"></block>' +
'</xml>';
try {
Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(xml), workspace);
workspace.getBlockById('test_1').procCode_ = 'test_procedure';
var rootBlock = workspace.getBlockById('test_2');
assertFalse(Blockly.Procedures.deleteProcedureDefCallback('test_procedure',
rootBlock));
// All blocks should stay on the workspace.
assertEquals(3, workspace.getTopBlocks().length);
}
finally {
procedureTest_tearDown();
}
}

View File

@@ -0,0 +1,177 @@
/**
* @license
* Blockly Tests
*
* Copyright 2017 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
goog.require('goog.testing');
var block;
var workspace;
var originalCreateIcon = Blockly.ScratchBlockComment.createIcon;
function scratchBlockCommentTest_setUp() {
workspace = new Blockly.Workspace();
// Mock the resizeContents function on the workspace
workspace.resizeContents = function () {};
var BLOCK_TYPE = 'test_json_minimal';
Blockly.defineBlocksWithJsonArray([{
"type": BLOCK_TYPE
}]);
block = new Blockly.BlockSvg(workspace, BLOCK_TYPE);
// Mock createIcon function since it is not necessary for these tests
Blockly.ScratchBlockComment.prototype.createIcon = function () {};
}
function scratchBlockCommentTest_tearDown() {
workspace.dispose();
block.dispose();
// Restore original createIcon function
Blockly.ScratchBlockComment.prototype.createIcon = originalCreateIcon;
}
function test_blockWithNoBlockComments() {
scratchBlockCommentTest_setUp();
try {
assertEquals('Workspace has a block.', 1, workspace.getTopBlocks(false).length);
assertEquals('Workspace does not have a comment.', 0, workspace.getTopComments(false).length);
assertEquals('Block does not have a comment', null, block.comment);
assertEquals('Block does not have comment text', '', block.getCommentText());
} finally {
scratchBlockCommentTest_tearDown();
}
}
function test_createBlockCommentMinimalArguments() {
scratchBlockCommentTest_setUp();
try {
var comment = new Blockly.ScratchBlockComment(block, 'Some comment text');
assertEquals('Workspace has a block.', 1, workspace.getTopBlocks(false).length);
assertEquals('Workspace has a comment.', 1, workspace.getTopComments(false).length);
assertEquals('Comment knows about workspace.', workspace, comment.workspace);
} finally {
scratchBlockCommentTest_tearDown();
}
}
function test_createBlockCommentAllArguments() {
scratchBlockCommentTest_setUp();
try {
var comment = new Blockly.ScratchBlockComment(block, 'Some comment text', 'aMockComment', 10, 20, true);
assertEquals('Workspace has a block.', 1, workspace.getTopBlocks(false).length);
assertEquals('Workspace has a comment.', 1, workspace.getTopComments(false).length);
assertEquals('Comment knows about workspace.', workspace, comment.workspace);
} finally {
scratchBlockCommentTest_tearDown();
}
}
function test_addCommentToBlock() {
scratchBlockCommentTest_setUp();
try {
block.setCommentText('Some comment text', 'aMockComment');
assertEquals('Workspace has a block.', 1, workspace.getTopBlocks(false).length);
assertEquals('Workspace has a comment.', 1, workspace.getTopComments(false).length);
assertNotEquals('Block has a comment', null, block.comment);
assertEquals('Block has comment text', 'Some comment text', block.getCommentText());
} finally {
scratchBlockCommentTest_tearDown();
}
}
function test_blockCommentXYWhenPositionProvided() {
scratchBlockCommentTest_setUp();
try {
var comment = new Blockly.ScratchBlockComment(block, 'Some comment text', 'aMockComment', 10, 20);
var commentXY = comment.getXY();
var commentX = commentXY.x;
var commentY = commentXY.y;
assertEquals('Comment x position is type number', 'number', typeof commentX);
assertEquals('Comment y position is type number', 'number', typeof commentY);
assertEquals('Comment x position is what was provided', 10, commentX);
assertEquals('Comment y position is what was provided', 20, commentY);
} finally {
scratchBlockCommentTest_tearDown();
}
}
function test_blockCommentXYWhenPositionNotProvided() {
scratchBlockCommentTest_setUp();
try {
var comment = new Blockly.ScratchBlockComment(
block, 'Some comment text', 'aMockComment');
var commentXY = comment.getXY();
var commentX = commentXY.x;
var commentY = commentXY.y;
console.log("COMMENT X: " + commentX);
console.log("COMMENT Y: " + commentY);
assertEquals('Comment x position is type number', 'number', typeof commentX);
assertEquals('Comment y position is type number', 'number', typeof commentY);
assertFalse('Comment x position is not NaN', isNaN(commentX));
assertFalse('Comment y position is not NaN', isNaN(commentY));
} finally {
scratchBlockCommentTest_tearDown();
}
}
function test_blockCommentXYNaNPositionProvided() {
scratchBlockCommentTest_setUp();
try {
var comment = new Blockly.ScratchBlockComment(
block, 'Some comment text', 'aMockComment', NaN, NaN);
var commentXY = comment.getXY();
var commentX = commentXY.x;
var commentY = commentXY.y;
assertEquals('Comment x position is type number', 'number', typeof commentX);
assertEquals('Comment y position is type number', 'number', typeof commentY);
assertFalse('Comment x position is not NaN', isNaN(commentX));
assertFalse('Comment y position is not NaN', isNaN(commentY));
} finally {
scratchBlockCommentTest_tearDown();
}
}
function test_blockCommentXYNullPositionProvided() {
scratchBlockCommentTest_setUp();
try {
var comment = new Blockly.ScratchBlockComment(
block, 'Some comment text', 'aMockComment', null, null);
var commentXY = comment.getXY();
var commentX = commentXY.x;
var commentY = commentXY.y;
assertEquals('Comment x position is type number', 'number', typeof commentX);
assertEquals('Comment y position is type number', 'number', typeof commentY);
assertFalse('Comment x position is not NaN', isNaN(commentX));
assertFalse('Comment y position is not NaN', isNaN(commentY));
} finally {
scratchBlockCommentTest_tearDown();
}
}

View File

@@ -0,0 +1,82 @@
/**
* @license
* Blockly Tests
*
* Copyright 2016 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
var svgTest_workspace;
function svgTest_setUp() {
svgTest_workspace = Blockly.inject('blocklyDiv',
{toolbox: document.getElementById('toolbox')});
}
function svgTest_tearDown() {
svgTest_workspace.dispose();
svgTest_workspace = null;
}
/**
* Create a block with one field. Must be called after svgTest_setUp().
* @return {!Blockly.Block} The new block with one field.
*/
function svgTest_newOneFieldBlock() {
Blockly.Blocks['one_field_block'] = {
init: function() {
this.jsonInit({
'message0': '%1',
'args0': [
{
'type': 'field_input',
'name': 'FIELD'
}
]
});
}
};
var block = svgTest_workspace.newBlock('one_field_block');
block.initSvg();
block.render(false);
return block;
}
/**
* Create a block with two fields. Must be called after svgTest_setUp().
* @return {!Blockly.Block} The new block with two fields.
*/
function svgTest_newTwoFieldBlock() {
Blockly.Blocks['two_field_block'] = {
init: function() {
this.jsonInit({
'message0': 'text_field %1',
'args0': [
{
'type': 'field_input',
'name': 'FIELD'
}
]
});
}
};
var block = svgTest_workspace.newBlock('two_field_block');
block.initSvg();
block.render(false);
return block;
}

View File

@@ -0,0 +1,60 @@
require('chromedriver');
var webdriver = require('selenium-webdriver');
var chrome = require('selenium-webdriver/chrome');
var builder = new webdriver.Builder().forBrowser('chrome');
if (process.env.CI) {
const options = new chrome.Options().headless();
if (process.platform === 'linux') {
options.addArguments('no-sandbox');
}
builder.setChromeOptions(options);
}
var browser = builder.build();
// Parse jsunit html report, exit(1) if there are any failures.
var testHtml = function (htmlString) {
var regex = /[\d]+\spassed,\s([\d]+)\sfailed./i;
var numOfFailure = regex.exec(htmlString)[1];
var regex2 = /Unit Tests for .*]/;
var testStatus = regex2.exec(htmlString)[0];
console.log("============Unit Test Summary=================");
console.log(testStatus);
var regex3 = /\d+ passed,\s\d+ failed/;
var detail = regex3.exec(htmlString)[0];
console.log(detail);
console.log("============Unit Test Summary=================");
if (parseInt(numOfFailure) !== 0) {
console.log(htmlString);
process.exit(1);
}
};
var path = process.cwd();
var runTests = async function () {
try {
var element, text;
await browser.get("file://" + path + "/tests/jsunit/vertical_tests.html");
await browser.sleep(5000);
element = await browser.findElement({id: "closureTestRunnerLog"});
text = await element.getText();
testHtml(text);
await browser.get("file://" + path + "/tests/jsunit/horizontal_tests.html");
await browser.sleep(5000);
element = await browser.findElement({id: "closureTestRunnerLog"});
text = await element.getText();
testHtml(text);
}
finally {
await browser.quit();
}
};
runTests().catch(e => {
console.error(e);
process.exit(1);
});

View File

@@ -0,0 +1,159 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2017 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Test utilities.
* @author marisaleung@google.com (Marisa Leung)
*/
'use strict';
goog.require('goog.testing');
/**
* The normal blockly event fire function. We sometimes override this. This
* handle lets us reset after an override.
*/
var savedFireFunc = Blockly.Events.fire;
/**
* A helper function to replace Blockly.Events.fire in tests.
*/
function temporary_fireEvent(event) {
if (!Blockly.Events.isEnabled()) {
return;
}
Blockly.Events.FIRE_QUEUE_.push(event);
Blockly.Events.fireNow_();
}
/**
* Check that two arrays have the same content.
* @param {!Array.<string>} array1 The first array.
* @param {!Array.<string>} array2 The second array.
*/
function isEqualArrays(array1, array2) {
assertEquals(array1.length, array2.length);
for (var i = 0; i < array1.length; i++) {
assertEquals(array1[i], array2[i]);
}
}
/**
* Creates a controlled MethodMock. Sets the expected return values and
* the parameters if any exist. Sets the method to replay.
* @param {!goog.testing.MockControl} mockControl Object that holds a set
* of mocks for this test.
* @param {!Object} scope The scope of the method to be mocked out.
* @param {!string} funcName The name of the function we're going to mock.
* @param {Array<Object>} parameters The parameters to call the mock with.
* @param {Array<!Object>} return_values The values to return when called.
* @return {!goog.testing.MockInterface} The mocked method.
*/
function setUpMockMethod(mockControl, scope, funcName, parameters,
return_values) {
var mockMethod = mockControl.createMethodMock(scope, funcName);
if (return_values) {
for (var i = 0, return_value; return_value = return_values[i]; i++) {
if (parameters && i < parameters.length) {
mockMethod(parameters[i]).$returns(return_value);
}
else {
mockMethod().$returns(return_value);
}
}
}
// If there are no return values but there are parameters, we are only
// recording specific method calls.
else if (parameters) {
for (var i = 0; i < parameters.length; i++) {
mockMethod(parameters[i]);
}
}
mockMethod.$replay();
return mockMethod;
}
/**
* 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 checkVariableValues(container, name, type, id) {
var variable = container.getVariableById(id);
assertNotUndefined(variable);
assertEquals(name, variable.name);
assertEquals(type, variable.type);
assertEquals(id, variable.getId());
}
/**
* Create a test get_var_block.
* Will fail if get_var_block isn't defined.
* TODO (fenichel): Rename to createMockVarBlock.
* @param {!string} variable_id The id of the variable to reference.
* @return {!Blockly.Block} The created block.
*/
function createMockBlock(variable_id) {
if (!Blockly.Blocks['get_var_block']) {
fail();
}
// Turn off events to avoid testing XML at the same time.
Blockly.Events.disable();
var block = new Blockly.Block(workspace, 'get_var_block');
block.inputList[0].fieldRow[0].setValue(variable_id);
Blockly.Events.enable();
return block;
}
function createTwoVariablesAndBlocks(workspace) {
// Create two variables of different types.
workspace.createVariable('name1', 'type1', 'id1');
workspace.createVariable('name2', 'type2', 'id2');
// Create blocks to refer to both of them.
createMockBlock('id1');
createMockBlock('id2');
}
function createVariableAndBlock(workspace) {
workspace.createVariable('name1', 'type1', 'id1');
createMockBlock('id1');
}
function defineGetVarBlock() {
Blockly.defineBlocksWithJsonArray([{
"type": "get_var_block",
"message0": "%1",
"args0": [
{
"type": "field_variable",
"name": "VAR",
"variableTypes": ["", "type1", "type2"]
}
]
}]);
}
function undefineGetVarBlock() {
delete Blockly.Blocks['get_var_block'];
}

View File

@@ -0,0 +1,165 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2011 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
function test_genUid() {
var uuids = {};
for (var i = 0; i < 1000; i++) {
var uuid = Blockly.utils.genUid();
assertFalse('UUID different: ' + uuid, uuid in uuids);
uuids[uuid] = true;
}
}
function test_addClass() {
var p = document.createElement('p');
Blockly.utils.addClass(p, 'one');
assertEquals('Adding "one"', 'one', p.className);
Blockly.utils.addClass(p, 'one');
assertEquals('Adding duplicate "one"', 'one', p.className);
Blockly.utils.addClass(p, 'two');
assertEquals('Adding "two"', 'one two', p.className);
Blockly.utils.addClass(p, 'two');
assertEquals('Adding duplicate "two"', 'one two', p.className);
Blockly.utils.addClass(p, 'three');
assertEquals('Adding "three"', 'one two three', p.className);
}
function test_hasClass() {
var p = document.createElement('p');
p.className = ' one three two three ';
assertTrue('Has "one"', Blockly.utils.hasClass(p, 'one'));
assertTrue('Has "two"', Blockly.utils.hasClass(p, 'two'));
assertTrue('Has "three"', Blockly.utils.hasClass(p, 'three'));
assertFalse('Has no "four"', Blockly.utils.hasClass(p, 'four'));
assertFalse('Has no "t"', Blockly.utils.hasClass(p, 't'));
}
function test_removeClass() {
var p = document.createElement('p');
p.className = ' one three two three ';
Blockly.utils.removeClass(p, 'two');
assertEquals('Removing "two"', 'one three three', p.className);
Blockly.utils.removeClass(p, 'four');
assertEquals('Removing "four"', 'one three three', p.className);
Blockly.utils.removeClass(p, 'three');
assertEquals('Removing "three"', 'one', p.className);
Blockly.utils.removeClass(p, 'ne');
assertEquals('Removing "ne"', 'one', p.className);
Blockly.utils.removeClass(p, 'one');
assertEquals('Removing "one"', '', p.className);
Blockly.utils.removeClass(p, 'zero');
assertEquals('Removing "zero"', '', p.className);
}
function test_tokenizeInterpolation() {
var tokens = Blockly.utils.tokenizeInterpolation('');
assertArrayEquals('Null interpolation', [], tokens);
tokens = Blockly.utils.tokenizeInterpolation('Hello');
assertArrayEquals('No interpolation', ['Hello'], tokens);
tokens = Blockly.utils.tokenizeInterpolation('Hello%World');
assertArrayEquals('Unescaped %.', ['Hello%World'], tokens);
tokens = Blockly.utils.tokenizeInterpolation('Hello%%World');
assertArrayEquals('Escaped %.', ['Hello%World'], tokens);
tokens = Blockly.utils.tokenizeInterpolation('Hello %1 World');
assertArrayEquals('Interpolation.', ['Hello ', 1, ' World'], tokens);
tokens = Blockly.utils.tokenizeInterpolation('%123Hello%456World%789');
assertArrayEquals('Interpolations.', [123, 'Hello', 456, 'World', 789], tokens);
tokens = Blockly.utils.tokenizeInterpolation('%%%x%%0%00%01%');
assertArrayEquals('Torture interpolations.', ['%%x%0', 0, 1, '%'], tokens);
Blockly.Msg = Blockly.Msg || {};
Blockly.Msg.STRING_REF = 'test string';
tokens = Blockly.utils.tokenizeInterpolation('%{bky_string_ref}');
assertArrayEquals('String table reference, lowercase', ['test string'], tokens);
tokens = Blockly.utils.tokenizeInterpolation('%{BKY_STRING_REF}');
assertArrayEquals('String table reference, uppercase', ['test string'], tokens);
Blockly.Msg.WITH_PARAM = 'before %1 after';
tokens = Blockly.utils.tokenizeInterpolation('%{bky_with_param}');
assertArrayEquals('String table reference, with parameter', ['before ', 1, ' after'], tokens);
Blockly.Msg.RECURSE = 'before %{bky_string_ref} after';
tokens = Blockly.utils.tokenizeInterpolation('%{bky_recurse}');
assertArrayEquals('String table reference, with subreference', ['before test string after'], tokens);
// Error cases...
tokens = Blockly.utils.tokenizeInterpolation('%{bky_undefined}');
assertArrayEquals('Undefined string table reference', ['%{bky_undefined}'], tokens);
Blockly.Msg['1'] = 'Will not match';
tokens = Blockly.utils.tokenizeInterpolation('before %{1} after');
assertArrayEquals('Invalid initial digit in string table reference', ['before %{1} after'], tokens);
Blockly.Msg['TWO WORDS'] = 'Will not match';
tokens = Blockly.utils.tokenizeInterpolation('before %{two words} after');
assertArrayEquals('Invalid character in string table reference: space', ['before %{two words} after'], tokens);
Blockly.Msg['TWO-WORDS'] = 'Will not match';
tokens = Blockly.utils.tokenizeInterpolation('before %{two-words} after');
assertArrayEquals('Invalid character in string table reference: dash', ['before %{two-words} after'], tokens);
Blockly.Msg['TWO.WORDS'] = 'Will not match';
tokens = Blockly.utils.tokenizeInterpolation('before %{two.words} after');
assertArrayEquals('Invalid character in string table reference: period', ['before %{two.words} after'], tokens);
Blockly.Msg['AB&C'] = 'Will not match';
tokens = Blockly.utils.tokenizeInterpolation('before %{ab&c} after');
assertArrayEquals('Invalid character in string table reference: &', ['before %{ab&c} after'], tokens);
Blockly.Msg['UNCLOSED'] = 'Will not match';
tokens = Blockly.utils.tokenizeInterpolation('before %{unclosed');
assertArrayEquals('String table reference, with parameter', ['before %{unclosed'], tokens);
}
function test_replaceMessageReferences() {
Blockly.Msg = Blockly.Msg || {};
Blockly.Msg.STRING_REF = 'test string';
var resultString = Blockly.utils.replaceMessageReferences('');
assertEquals('Empty string produces empty string', '', resultString);
resultString = Blockly.utils.replaceMessageReferences('%{bky_string_ref}');
assertEquals('Message ref dereferenced.', 'test string', resultString);
resultString = Blockly.utils.replaceMessageReferences('before %{bky_string_ref} after');
assertEquals('Message ref dereferenced.', 'before test string after', resultString);
resultString = Blockly.utils.replaceMessageReferences('%1');
assertEquals('Interpolation tokens ignored.', '%1', resultString);
resultString = Blockly.utils.replaceMessageReferences('%1 %2');
assertEquals('Interpolation tokens ignored.', '%1 %2', resultString);
resultString = Blockly.utils.replaceMessageReferences('before %1 after');
assertEquals('Interpolation tokens ignored.', 'before %1 after', resultString);
resultString = Blockly.utils.replaceMessageReferences('%%');
assertEquals('Escaped %', '%', resultString);
resultString = Blockly.utils.replaceMessageReferences('%%{bky_string_ref}');
assertEquals('Escaped %', '%{bky_string_ref}', resultString);
resultString = Blockly.utils.replaceMessageReferences('%a');
assertEquals('Unrecognized % escape code treated as literal', '%a', resultString);
}

View File

@@ -0,0 +1,302 @@
/**
* @license
* Blockly Tests
*
* Copyright 2017 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Tests for variable map.
* @author marisaleung@google.com (Marisa Leung)
*/
'use strict';
goog.require('goog.testing');
goog.require('goog.testing.MockControl');
var variable_map;
var mockControl_;
var workspace;
function variableMapTest_setUp() {
workspace = new Blockly.Workspace();
variable_map = new Blockly.VariableMap(workspace);
mockControl_ = new goog.testing.MockControl();
}
function variableMapTest_tearDown() {
workspace.dispose();
mockControl_.$tearDown();
variable_map = null;
}
function test_getVariable_ByNameAndType() {
variableMapTest_setUp();
var var_1 = variable_map.createVariable('name1', 'type1', 'id1');
var var_2 = variable_map.createVariable('name2', 'type1', 'id2');
var var_3 = variable_map.createVariable('name3', 'type2', 'id3');
var result_1 = variable_map.getVariable('name1', 'type1');
var result_2 = variable_map.getVariable('name2', 'type1');
var result_3 = variable_map.getVariable('name3', 'type2');
// Searching by name + type is correct.
assertEquals(var_1, result_1);
assertEquals(var_2, result_2);
assertEquals(var_3, result_3);
// Searching only by name defaults to the '' type.
assertNull(variable_map.getVariable('name1'));
assertNull(variable_map.getVariable('name2'));
assertNull(variable_map.getVariable('name3'));
variableMapTest_tearDown();
}
function test_getVariable_NotFound() {
variableMapTest_setUp();
var result = variable_map.getVariable('name1');
assertNull(result);
variableMapTest_tearDown();
}
function test_getVariableById_Trivial() {
variableMapTest_setUp();
var var_1 = variable_map.createVariable('name1', 'type1', 'id1');
var var_2 = variable_map.createVariable('name2', 'type1', 'id2');
var var_3 = variable_map.createVariable('name3', 'type2', 'id3');
var result_1 = variable_map.getVariableById('id1');
var result_2 = variable_map.getVariableById('id2');
var result_3 = variable_map.getVariableById('id3');
assertEquals(var_1, result_1);
assertEquals(var_2, result_2);
assertEquals(var_3, result_3);
variableMapTest_tearDown();
}
function test_getVariableById_NotFound() {
variableMapTest_setUp();
var result = variable_map.getVariableById('id1');
assertNull(result);
variableMapTest_tearDown();
}
function test_createVariableTrivial() {
variableMapTest_setUp();
variable_map.createVariable('name1', 'type1', 'id1');
checkVariableValues(variable_map, 'name1', 'type1', 'id1');
variableMapTest_tearDown();
}
function test_createVariableAlreadyExists() {
// Expect that when the variable already exists, the variableMap_ is unchanged.
variableMapTest_setUp();
variable_map.createVariable('name1', 'type1', 'id1');
// Assert there is only one variable in the variable_map.
var keys = Object.keys(variable_map.variableMap_);
assertEquals(1, keys.length);
var varMapLength = variable_map.variableMap_[keys[0]].length;
assertEquals(1, varMapLength);
variable_map.createVariable('name1', 'type1');
checkVariableValues(variable_map, 'name1', 'type1', 'id1');
// Check that the size of the variableMap_ did not change.
keys = Object.keys(variable_map.variableMap_);
assertEquals(1, keys.length);
varMapLength = variable_map.variableMap_[keys[0]].length;
assertEquals(1, varMapLength);
variableMapTest_tearDown();
}
function test_createVariableNameAlreadyExists() {
// Expect that when a variable with the same name but a different type already
// exists, the new variable is created.
variableMapTest_setUp();
variable_map.createVariable('name1', 'type1', 'id1');
// Assert there is only one variable in the variable_map.
var keys = Object.keys(variable_map.variableMap_);
assertEquals(1, keys.length);
var varMapLength = variable_map.variableMap_[keys[0]].length;
assertEquals(1, varMapLength);
variable_map.createVariable('name1', 'type2', 'id2');
checkVariableValues(variable_map, 'name1', 'type1', 'id1');
checkVariableValues(variable_map, 'name1', 'type2', 'id2');
// Check that the size of the variableMap_ did change.
keys = Object.keys(variable_map.variableMap_);
assertEquals(2, keys.length);
variableMapTest_tearDown();
}
function test_createVariableNullAndUndefinedType() {
variableMapTest_setUp();
variable_map.createVariable('name1', null, 'id1');
variable_map.createVariable('name2', undefined, 'id2');
checkVariableValues(variable_map, 'name1', '', 'id1');
checkVariableValues(variable_map, 'name2', '', 'id2');
variableMapTest_tearDown();
}
function test_createVariableNullId() {
variableMapTest_setUp();
setUpMockMethod(mockControl_, Blockly.utils, 'genUid', null, ['1', '2']);
try {
variable_map.createVariable('name1', 'type1', null);
checkVariableValues(variable_map, 'name1', 'type1', '1');
}
finally {
variableMapTest_tearDown();
}
}
function test_createVariableUndefinedId() {
variableMapTest_setUp();
setUpMockMethod(mockControl_, Blockly.utils, 'genUid', null, ['1', '2']);
try {
variable_map.createVariable('name1', 'type1', undefined);
checkVariableValues(variable_map, 'name1', 'type1', '1');
}
finally {
variableMapTest_tearDown();
}
}
function test_createVariableIdAlreadyExists() {
variableMapTest_setUp();
variable_map.createVariable('name1', 'type1', 'id1');
try {
variable_map.createVariable('name2', 'type2', 'id1');
fail();
} catch (e) {
// expected
}
variableMapTest_tearDown();
}
function test_createVariableMismatchedIdAndType() {
variableMapTest_setUp();
variable_map.createVariable('name1', 'type1', 'id1');
try {
variable_map.createVariable('name1', 'type2', 'id1');
fail();
} catch (e) {
// expected
}
try {
variable_map.createVariable('name1', 'type1', 'id2');
fail();
} catch (e) {
// expected
}
variableMapTest_tearDown();
}
function test_createVariableTwoSameTypes() {
variableMapTest_setUp();
variable_map.createVariable('name1', 'type1', 'id1');
variable_map.createVariable('name2', 'type1', 'id2');
checkVariableValues(variable_map, 'name1', 'type1', 'id1');
checkVariableValues(variable_map, 'name2', 'type1', 'id2');
variableMapTest_tearDown();
}
function test_getVariablesOfType_Trivial() {
variableMapTest_setUp();
var var_1 = variable_map.createVariable('name1', 'type1', 'id1');
var var_2 = variable_map.createVariable('name2', 'type1', 'id2');
variable_map.createVariable('name3', 'type2', 'id3');
variable_map.createVariable('name4', 'type3', 'id4');
var result_array_1 = variable_map.getVariablesOfType('type1');
var result_array_2 = variable_map.getVariablesOfType('type5');
isEqualArrays([var_1, var_2], result_array_1);
isEqualArrays([], result_array_2);
variableMapTest_tearDown();
}
function test_getVariablesOfType_Null() {
variableMapTest_setUp();
var var_1 = variable_map.createVariable('name1', '', 'id1');
var var_2 = variable_map.createVariable('name2', '', 'id2');
var var_3 = variable_map.createVariable('name3', '', 'id3');
variable_map.createVariable('name4', 'type1', 'id4');
var result_array = variable_map.getVariablesOfType(null);
isEqualArrays([var_1, var_2, var_3], result_array);
variableMapTest_tearDown();
}
function test_getVariablesOfType_EmptyString() {
variableMapTest_setUp();
var var_1 = variable_map.createVariable('name1', null, 'id1');
var var_2 = variable_map.createVariable('name2', null, 'id2');
var result_array = variable_map.getVariablesOfType('');
isEqualArrays([var_1, var_2], result_array);
variableMapTest_tearDown();
}
function test_getVariablesOfType_Deleted() {
variableMapTest_setUp();
var variable = variable_map.createVariable('name1', null, 'id1');
variable_map.deleteVariable(variable);
var result_array = variable_map.getVariablesOfType('');
isEqualArrays([], result_array);
variableMapTest_tearDown();
}
function test_getVariablesOfType_DoesNotExist() {
variableMapTest_setUp();
var result_array = variable_map.getVariablesOfType('type1');
isEqualArrays([], result_array);
variableMapTest_tearDown();
}
function test_getVariableTypes_Trivial() {
variableMapTest_setUp();
variable_map.createVariable('name1', 'type1', 'id1');
variable_map.createVariable('name2', 'type1', 'id2');
variable_map.createVariable('name3', 'type2', 'id3');
variable_map.createVariable('name4', 'type3', 'id4');
var result_array = variable_map.getVariableTypes();
// The empty string is always an option.
isEqualArrays(['type1', 'type2', 'type3', ''], result_array);
variableMapTest_tearDown();
}
function test_getVariableTypes_None() {
variableMapTest_setUp();
// The empty string is always an option.
var result_array = variable_map.getVariableTypes();
isEqualArrays([''], result_array);
variableMapTest_tearDown();
}
function test_getAllVariables_Trivial() {
variableMapTest_setUp();
var var_1 = variable_map.createVariable('name1', 'type1', 'id1');
var var_2 = variable_map.createVariable('name2', 'type1', 'id2');
var var_3 = variable_map.createVariable('name3', 'type2', 'id3');
var result_array = variable_map.getAllVariables();
isEqualArrays([var_1, var_2, var_3], result_array);
variableMapTest_tearDown();
}
function test_getAllVariables_None() {
variableMapTest_setUp();
var result_array = variable_map.getAllVariables();
isEqualArrays([], result_array);
variableMapTest_tearDown();
}

View File

@@ -0,0 +1,91 @@
/**
* @license
* Blockly Tests
*
* Copyright 2017 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Tests for variable model.
* @author marisaleung@google.com (Marisa Leung)
*/
'use strict';
var variable;
var workspace;
function variableModelTest_setUp() {
workspace = new Blockly.Workspace();
}
function variableModelTest_tearDown() {
workspace.dispose();
variable = null;
}
/**
* These tests check the constructor of the variable model.
*/
function testInit_Trivial() {
variableModelTest_setUp();
variable = new Blockly.VariableModel(workspace, 'test', 'test_type',
'test_id');
assertEquals('test', variable.name);
assertEquals('test_type', variable.type);
assertEquals('test_id', variable.id_);
variableModelTest_tearDown();
}
function testInit_NullType() {
variableModelTest_setUp();
variable = new Blockly.VariableModel(workspace, 'test', null, 'test_id');
assertEquals('', variable.type);
variableModelTest_tearDown();
}
function testInit_UndefinedType() {
variableModelTest_setUp();
variable = new Blockly.VariableModel(workspace, 'test', undefined, 'test_id');
assertEquals('', variable.type);
variableModelTest_tearDown();
}
function testInit_NullId() {
variableModelTest_setUp();
variable = new Blockly.VariableModel(workspace, 'test', 'test_type', null);
assertEquals('test', variable.name);
assertEquals('test_type', variable.type);
assertNotNull(variable.id_);
variableModelTest_tearDown();
}
function testInit_UndefinedId() {
variableModelTest_setUp();
variable = new Blockly.VariableModel(workspace, 'test', 'test_type', undefined);
assertEquals('test', variable.name);
assertEquals('test_type', variable.type);
assertNotNull(variable.id_);
variableModelTest_tearDown();
}
function testInit_OnlyNameProvided() {
variableModelTest_setUp();
variable = new Blockly.VariableModel(workspace, 'test');
assertEquals('test', variable.name);
assertEquals('', variable.type);
assertNotNull(variable.id_);
variableModelTest_tearDown();
}

View File

@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Unit Tests for Vertical Scratch-Blockly</title>
<script src="../../blockly_uncompressed_vertical.js"></script>
<script>goog.require('goog.testing.jsunit');</script>
</head>
<body>
<script src="test_utilities.js"></script>
<script src="block_test.js"></script>
<script src="connection_db_test.js"></script>
<script src="connection_test.js"></script>
<script src="event_test.js"></script>
<script src="extensions_test.js"></script>
<script src="field_test.js"></script>
<script src="field_angle_test.js"></script>
<script src="field_number_test.js"></script>
<script src="field_variable_getter_test.js"></script>
<script src="generator_test.js"></script>
<script src="input_test.js"></script>
<script src="json_test.js"></script>
<script src="names_test.js"></script>
<script src="procedure_test.js"></script>
<script src="scratch_block_comment_test.js"></script>
<script src="svg_test.js"></script>
<script src="utils_test.js"></script>
<script src="widget_div_test.js"></script>
<script src="workspace_comment_test.js"></script>
<script src="workspace_test.js"></script>
<script src="xml_test.js"></script>
<div id="blocklyDiv" style="display: none; height: 480px; width: 600px;"></div>
<xml id="toolbox" style="display: none"></xml>
</body>
</html>

View File

@@ -0,0 +1,154 @@
/**
* @license
* Blockly Tests
*
* Copyright 2017 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
goog.require('goog.testing');
function widgetdiv_testHelper_makeBBox(left, top, width, height) {
return {
left: left,
right: left + width,
top: top,
bottom: top + height
};
}
function widgetdiv_testHelper_makeSize(width, height) {
return {
width: width,
height: height
};
}
var widgetDiv_test_viewport = widgetdiv_testHelper_makeBBox(0, 0, 1000, 1000);
var widgetDiv_test_widgetSize = widgetdiv_testHelper_makeSize(100, 100);
// Anchor is always 90 px wide and 90 px tall for this test.
var widgetDiv_test_anchorSize = 90;
function widgetdiv_testHelper_makeAnchor(left, top) {
return {
left: left,
right: left + widgetDiv_test_anchorSize,
top: top,
bottom: top + widgetDiv_test_anchorSize
};
}
function test_widgetDiv_topConflict() {
var anchorTop = 50;
// Anchor placed close to the top.
var anchorBBox = widgetdiv_testHelper_makeAnchor(500, anchorTop);
// The widget div should be placed just below the anchor.
var calculated = Blockly.WidgetDiv.calculateY_(widgetDiv_test_viewport,
anchorBBox, widgetDiv_test_widgetSize);
assertEquals(anchorTop + widgetDiv_test_anchorSize, calculated);
}
function test_widgetDiv_bottomConflict() {
var anchorTop = 900;
// Anchor placed close to the bottom.
var anchorBBox = widgetdiv_testHelper_makeAnchor(500, anchorTop);
// The widget div should be placed just above the anchor.
var calculated = Blockly.WidgetDiv.calculateY_(widgetDiv_test_viewport,
anchorBBox, widgetDiv_test_widgetSize);
assertEquals(anchorTop - widgetDiv_test_widgetSize.height, calculated);
}
function test_widgetDiv_noYConflict() {
var anchorTop = 500;
// Anchor placed in the middle.
var anchorBBox = widgetdiv_testHelper_makeAnchor(500, anchorTop);
// The widget div should be placed just below the anchor.
var calculated = Blockly.WidgetDiv.calculateY_(widgetDiv_test_viewport,
anchorBBox, widgetDiv_test_widgetSize);
assertEquals(anchorTop + widgetDiv_test_anchorSize, calculated);
}
function test_widgetDiv_leftConflict_LTR() {
var anchorLeft = 50;
// Anchor placed close to the left side.
var anchorBBox = widgetdiv_testHelper_makeAnchor(anchorLeft, 500);
// The widget div should be placed at the anchor.
var calculated = Blockly.WidgetDiv.calculateX_(widgetDiv_test_viewport,
anchorBBox, widgetDiv_test_widgetSize, false /* rtl */);
assertEquals(anchorLeft, calculated);
}
function test_widgetDiv_rightConflict_LTR() {
var anchorLeft = 950;
// Anchor placed close to the right side.
var anchorBBox = widgetdiv_testHelper_makeAnchor(anchorLeft, 500);
// The widget div should be placed as far right as possible--at the edge of
// the screen.
var calculated = Blockly.WidgetDiv.calculateX_(widgetDiv_test_viewport,
anchorBBox, widgetDiv_test_widgetSize, false /* rtl */);
assertEquals(1000 - widgetDiv_test_widgetSize.width, calculated);
}
function test_widgetDiv_noXConflict_LTR() {
var anchorLeft = 500;
// Anchor in the middle
var anchorBBox = widgetdiv_testHelper_makeAnchor(anchorLeft, 500);
// The widget div should be placed just at the left side of the anchor.
var calculated = Blockly.WidgetDiv.calculateX_(widgetDiv_test_viewport,
anchorBBox, widgetDiv_test_widgetSize, false /* rtl */);
assertEquals(anchorLeft, calculated);
}
function test_widgetDiv_leftConflict_RTL() {
var anchorLeft = 10;
// Anchor placed close to the left side.
var anchorBBox = widgetdiv_testHelper_makeAnchor(anchorLeft, 500);
// The widget div should be placed as far left as possible--at the edge of
// the screen.
var calculated = Blockly.WidgetDiv.calculateX_(widgetDiv_test_viewport,
anchorBBox, widgetDiv_test_widgetSize, true /* rtl */);
assertEquals(0, calculated);
}
function test_widgetDiv_rightConflict_RTL() {
var anchorLeft = 950;
// Anchor placed close to the right side.
var anchorBBox = widgetdiv_testHelper_makeAnchor(anchorLeft, 500);
// The widget div should be placed as far right as possible--at the edge of
// the screen.
var calculated = Blockly.WidgetDiv.calculateX_(widgetDiv_test_viewport,
anchorBBox, widgetDiv_test_widgetSize, true /* rtl */);
assertEquals(1000 - widgetDiv_test_widgetSize.width, calculated);
}
function test_widgetDiv_noXConflict_RTL() {
var anchorLeft = 500;
// anchor placed in the middle
var anchorBBox = widgetdiv_testHelper_makeAnchor(anchorLeft, 500);
// The widget div should be placed at the right side of the anchor.
var calculated = Blockly.WidgetDiv.calculateX_(widgetDiv_test_viewport,
anchorBBox, widgetDiv_test_widgetSize, true /* rtl */);
assertEquals(anchorBBox.right - widgetDiv_test_widgetSize.width, calculated);
}

View File

@@ -0,0 +1,186 @@
/**
* @license
* Blockly Tests
*
* Copyright 2017 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
goog.require('goog.testing');
var workspace;
function workspaceCommentTest_setUp() {
workspace = new Blockly.Workspace();
}
function workspaceCommentTest_tearDown() {
workspace.dispose();
}
function test_noWorkspaceComments() {
workspaceCommentTest_setUp();
try {
assertEquals('Empty workspace: no comments (1).', 0, workspace.getTopComments(true).length);
assertEquals('Empty workspace: no comments (2).', 0, workspace.getTopComments(false).length);
workspace.clear();
assertEquals('Empty workspace: no comments (3).', 0, workspace.getTopComments(true).length);
assertEquals('Empty workspace: no comments (4).', 0, workspace.getTopComments(false).length);
} finally {
workspaceCommentTest_tearDown();
}
}
function test_oneWorkspaceComment() {
workspaceCommentTest_setUp();
try {
var comment = new Blockly.WorkspaceComment(workspace, 'comment text', 0, 0, false, 'comment id');
assertEquals('One comment on workspace (1).', 1, workspace.getTopComments(true).length);
assertEquals('One comment on workspace (2).', 1, workspace.getTopComments(false).length);
assertEquals('Comment db contains this comment.', comment, workspace.commentDB_['comment id']);
workspace.clear();
assertEquals('Cleared workspace: no comments (3).', 0, workspace.getTopComments(true).length);
assertEquals('Cleared workspace: no comments (4).', 0, workspace.getTopComments(false).length);
assertFalse('Comment DB does not contain this comment.', 'comment id' in workspace.commentDB_);
} finally {
workspaceCommentTest_tearDown();
}
}
function test_getWorkspaceCommentById() {
workspaceCommentTest_setUp();
try {
var comment = new Blockly.WorkspaceComment(workspace, 'comment text', 0, 0, false, 'comment id');
assertEquals('Getting a comment by id.', comment, workspace.getCommentById('comment id'));
assertEquals('No comment found.', null, workspace.getCommentById('not a comment'));
comment.dispose();
assertEquals('Can\'t find the comment.', null, workspace.getCommentById('comment id'));
} finally {
workspaceCommentTest_tearDown();
}
}
function test_disposeWsCommentTwice() {
workspaceCommentTest_setUp();
try {
var comment = new Blockly.WorkspaceComment(workspace, 'comment text', 0, 0, false, 'comment id');
comment.dispose();
// Nothing should go wrong the second time dispose is called.
comment.dispose();
}finally {
workspaceCommentTest_tearDown();
}
}
function test_wsCommentHeightWidth() {
workspaceCommentTest_setUp();
try {
var comment =
new Blockly.WorkspaceComment(workspace, 'comment text', 10, 20, false, 'comment id');
assertEquals('Initial width', 20, comment.getWidth());
assertEquals('Initial height', 10, comment.getHeight());
comment.setWidth(30);
assertEquals('New width should be different', 30, comment.getWidth());
assertEquals('New height should not be different', 10, comment.getHeight());
comment.setHeight(40);
assertEquals('New width should not be different', 30, comment.getWidth());
assertEquals('New height should be different', 40, comment.getHeight());
comment.dispose();
} finally {
workspaceCommentTest_tearDown();
}
}
function test_wsCommentXY() {
workspaceCommentTest_setUp();
try {
var comment =
new Blockly.WorkspaceComment(workspace, 'comment text', 10, 20, false, 'comment id');
var xy = comment.getXY();
assertEquals('Initial X position', 0, xy.x);
assertEquals('Initial Y position', 0, xy.y);
comment.moveBy(10, 100);
xy = comment.getXY();
assertEquals('New X position', 10, xy.x);
assertEquals('New Y position', 100, xy.y);
comment.dispose();
} finally {
workspaceCommentTest_tearDown();
}
}
function test_wsCommentText() {
workspaceCommentTest_setUp();
Blockly.Events.fire = temporary_fireEvent;
temporary_fireEvent.firedEvents_ = [];
try {
var comment =
new Blockly.WorkspaceComment(workspace, 'comment text', 10, 20, false, 'comment id');
assertEquals(
'Check comment text', 'comment text', comment.getText());
assertEquals(
'Workspace undo stack has one event', 1, workspace.undoStack_.length);
comment.setText('comment text');
assertEquals(
'Comment text has not changed', 'comment text', comment.getText());
// Setting the text to the old value does not fire an event.
assertEquals(
'Workspace undo stack has one event', 1, workspace.undoStack_.length);
comment.setText('new comment text');
assertEquals(
'Comment text has changed', 'new comment text', comment.getText());
assertEquals(
'Workspace undo stack has two events', 2, workspace.undoStack_.length);
comment.dispose();
} finally {
workspaceCommentTest_tearDown();
Blockly.Events.fire = savedFireFunc;
}
}
function test_workspaceCommentMinimized() {
workspaceCommentTest_setUp();
try {
var comment = new Blockly.WorkspaceComment(workspace, 'comment text', 0, 0, true, 'comment id');
assertEquals('Comment is minimized', true, comment.isMinimized());
} finally {
workspaceCommentTest_tearDown();
}
}
function test_workspaceCommentMinimizedFromXml() {
workspaceCommentTest_setUp();
try {
var comment = new Blockly.WorkspaceComment(workspace, 'comment text', 0, 0, true, 'comment id');
var commentXml = comment.toXml();
var xml = goog.dom.createDom('xml');
xml.appendChild(commentXml);
comment.dispose();
assertEquals('Comment is no longer on workspace', null, workspace.getCommentById('comment id'));
Blockly.Xml.domToWorkspace(xml, workspace);
var importedComment = workspace.getCommentById('comment id');
assertNotEquals('Comment loaded from xml is on workspace', null, importedComment)
assertEquals('Imported comment is minimized', true, importedComment.isMinimized());
} finally {
workspaceCommentTest_tearDown();
}
}

View File

@@ -0,0 +1,371 @@
/**
* @license
* Blockly Tests
*
* Copyright 2012 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
goog.require('goog.testing');
goog.require('goog.testing.MockControl');
var workspace;
var mockControl_;
function workspaceTest_setUp() {
defineGetVarBlock();
workspace = new Blockly.Workspace();
mockControl_ = new goog.testing.MockControl();
}
function workspaceTest_tearDown() {
undefineGetVarBlock();
mockControl_.$tearDown();
workspace.dispose();
}
function test_emptyWorkspace() {
workspaceTest_setUp();
try {
assertEquals('Empty workspace (1).', 0, workspace.getTopBlocks(true).length);
assertEquals('Empty workspace (2).', 0, workspace.getTopBlocks(false).length);
assertEquals('Empty workspace (3).', 0, workspace.getAllBlocks().length);
workspace.clear();
assertEquals('Empty workspace (4).', 0, workspace.getTopBlocks(true).length);
assertEquals('Empty workspace (5).', 0, workspace.getTopBlocks(false).length);
assertEquals('Empty workspace (6).', 0, workspace.getAllBlocks().length);
}
finally {
workspaceTest_tearDown();
}
}
function test_flatWorkspace() {
workspaceTest_setUp();
try {
var blockA = workspace.newBlock('');
assertEquals('One block workspace (1).', 1, workspace.getTopBlocks(true).length);
assertEquals('One block workspace (2).', 1, workspace.getTopBlocks(false).length);
assertEquals('One block workspace (3).', 1, workspace.getAllBlocks().length);
var blockB = workspace.newBlock('');
assertEquals('Two block workspace (1).', 2, workspace.getTopBlocks(true).length);
assertEquals('Two block workspace (2).', 2, workspace.getTopBlocks(false).length);
assertEquals('Two block workspace (3).', 2, workspace.getAllBlocks().length);
blockA.dispose();
assertEquals('One block workspace (4).', 1, workspace.getTopBlocks(true).length);
assertEquals('One block workspace (5).', 1, workspace.getTopBlocks(false).length);
assertEquals('One block workspace (6).', 1, workspace.getAllBlocks().length);
workspace.clear();
assertEquals('Cleared workspace (1).', 0, workspace.getTopBlocks(true).length);
assertEquals('Cleared workspace (2).', 0, workspace.getTopBlocks(false).length);
assertEquals('Cleared workspace (3).', 0, workspace.getAllBlocks().length);
} finally {
workspaceTest_tearDown();
}
}
function test_getWorkspaceById() {
var workspaceA = new Blockly.Workspace();
var workspaceB = new Blockly.Workspace();
try {
assertEquals('Find workspaceA.', workspaceA,
Blockly.Workspace.getById(workspaceA.id));
assertEquals('Find workspaceB.', workspaceB,
Blockly.Workspace.getById(workspaceB.id));
assertEquals('No workspace found.', null,
Blockly.Workspace.getById('I do not exist.'));
workspaceA.dispose();
assertEquals('Can\'t find workspaceA.', null,
Blockly.Workspace.getById(workspaceA.id));
assertEquals('WorkspaceB exists.', workspaceB,
Blockly.Workspace.getById(workspaceB.id));
} finally {
workspaceB.dispose();
workspaceA.dispose();
}
}
function test_getBlockById() {
workspaceTest_setUp();
try {
var blockA = workspace.newBlock('');
var blockB = workspace.newBlock('');
assertEquals('Find blockA.', blockA, workspace.getBlockById(blockA.id));
assertEquals('Find blockB.', blockB, workspace.getBlockById(blockB.id));
assertEquals('No block found.', null,
workspace.getBlockById('I do not exist.'));
blockA.dispose();
assertEquals('Can\'t find blockA.', null, workspace.getBlockById(blockA.id));
assertEquals('BlockB exists.', blockB, workspace.getBlockById(blockB.id));
workspace.clear();
assertEquals('Can\'t find blockB.', null, workspace.getBlockById(blockB.id));
} finally {
workspaceTest_tearDown();
}
}
function test_deleteVariable_InternalTrivial() {
workspaceTest_setUp();
var var_1 = workspace.createVariable('name1', 'type1', 'id1');
workspace.createVariable('name2', 'type2', 'id2');
createMockBlock('id1');
createMockBlock('id1');
createMockBlock('id2');
var uses = workspace.getVariableUsesById(var_1.getId());
workspace.deleteVariableInternal_(var_1, uses);
var variable = workspace.getVariableById('id1');
var block_var_name = workspace.topBlocks_[0].getVarModels()[0].name;
assertNull(variable);
checkVariableValues(workspace, 'name2', 'type2', 'id2');
assertEquals('name2', block_var_name);
workspaceTest_tearDown();
}
// TODO(marisaleung): Test the alert for deleting a variable that is a procedure.
function test_addTopBlock_TrivialFlyoutIsTrue() {
workspaceTest_setUp();
var targetWorkspace = new Blockly.Workspace();
workspace.isFlyout = true;
workspace.targetWorkspace = targetWorkspace;
targetWorkspace.createVariable('name1', '', '1');
// Flyout.init usually does this binding.
workspace.variableMap_ = targetWorkspace.getVariableMap();
try {
var block = createMockBlock('1');
workspace.removeTopBlock(block);
workspace.addTopBlock(block);
checkVariableValues(workspace, 'name1', '', '1');
} finally {
workspaceTest_tearDown();
// Have to dispose of the main workspace after the flyout workspace, because
// it holds the variable map.
// Normally the main workspace disposes of the flyout workspace.
targetWorkspace.dispose();
}
}
function test_clear_Trivial() {
workspaceTest_setUp();
workspace.createVariable('name1', 'type1', 'id1');
workspace.createVariable('name2', 'type2', 'id2');
setUpMockMethod(mockControl_, Blockly.Events, 'setGroup', [true, false],
null);
try {
workspace.clear();
var topBlocks_length = workspace.topBlocks_.length;
var varMapLength = Object.keys(workspace.variableMap_.variableMap_).length;
assertEquals(0, topBlocks_length);
assertEquals(0, varMapLength);
}
finally {
workspaceTest_tearDown();
}
}
function test_clear_NoVariables() {
workspaceTest_setUp();
setUpMockMethod(mockControl_, Blockly.Events, 'setGroup', [true, false],
null);
try {
workspace.clear();
var topBlocks_length = workspace.topBlocks_.length;
var varMapLength = Object.keys(workspace.variableMap_.variableMap_).length;
assertEquals(0, topBlocks_length);
assertEquals(0, varMapLength);
}
finally {
workspaceTest_tearDown();
}
}
function test_renameVariable_NoReference() {
// Test renaming a variable in the simplest case: when no blocks refer to it.
workspaceTest_setUp();
var id = 'id1';
var type = 'type1';
var oldName = 'name1';
var newName = 'name2';
workspace.createVariable(oldName, type, id);
try {
workspace.renameVariableById(id, newName);
checkVariableValues(workspace, newName, type, id);
// Renaming should not have created a new variable.
assertEquals(1, workspace.getAllVariables().length);
} finally {
workspaceTest_tearDown();
}
}
function test_renameVariable_ReferenceExists() {
// Test renaming a variable when a reference to it exists.
// Expect 'renameVariable' to change oldName variable name to newName.
workspaceTest_setUp();
var newName = 'name2';
createVariableAndBlock(workspace);
workspace.renameVariableById('id1', newName);
checkVariableValues(workspace, newName, 'type1', 'id1');
// Renaming should not have created a new variable.
assertEquals(1, workspace.getAllVariables().length);
var block_var_name = workspace.topBlocks_[0].getVarModels()[0].name;
assertEquals(newName, block_var_name);
workspaceTest_tearDown();
}
function test_renameVariable_TwoVariablesSameType() {
// Cannot rename variable to a name that already exists
// for a variable of the same type.
// Note: this behavior is different from that of blockly which allows
// renaming variables to a name that already exists if the variables have the
// same type.
workspaceTest_setUp();
var id1 = 'id1';
var id2 = 'id2';
var type = 'type1';
var oldName = 'name1';
var newName = 'name2';
// Create two variables of the same type.
workspace.createVariable(oldName, type, id1);
workspace.createVariable(newName, type, id2);
// Create blocks to refer to both of them.
createMockBlock(id1);
createMockBlock(id2);
workspace.renameVariableById(id1, newName);
// Both variables should retain the same names/ids as before.
checkVariableValues(workspace, oldName, type, id1);
checkVariableValues(workspace, newName, type, id2);
// Both variables should remain on the workspace.
assertEquals(2, workspace.getAllVariables().length);
// References should have the correct names.
var block_var_name_1 = workspace.topBlocks_[0].getVarModels()[0].name;
var block_var_name_2 = workspace.topBlocks_[1].getVarModels()[0].name;
assertEquals(oldName, block_var_name_1);
assertEquals(newName, block_var_name_2);
workspaceTest_tearDown();
}
function test_renameVariable_TwoVariablesDifferentType() {
// Expect the rename to succeed, because variables with different types are
// allowed to have the same name.
workspaceTest_setUp();
createTwoVariablesAndBlocks(workspace);
var newName = 'name2';
workspace.renameVariableById('id1', newName);
checkVariableValues(workspace, newName, 'type1', 'id1');
checkVariableValues(workspace, newName, 'type2', 'id2');
// References shoul have the correct names.
var block_var_name_1 = workspace.topBlocks_[0].getVarModels()[0].name;
var block_var_name_2 = workspace.topBlocks_[1].getVarModels()[0].name;
assertEquals(newName, block_var_name_1);
assertEquals(newName, block_var_name_2);
workspaceTest_tearDown();
}
function test_renameVariable_OldCase() {
// Rename a variable with a single reference. Update only the capitalization.
workspaceTest_setUp();
var newName = 'Name1';
createVariableAndBlock(workspace);
workspace.renameVariableById('id1', newName);
checkVariableValues(workspace, newName, 'type1', 'id1');
var variable = workspace.getVariableById('id1');
assertNotEquals('name1', variable.name);
workspaceTest_tearDown();
}
function test_renameVariable_TwoVariablesAndOldCase() {
// Test renaming a variable to an in-use name, but with different
// capitalization. Two variables with different capitalizations should
// co-exist.
// Note: this behavior is different from that of blockly which does not allow
// two variables of the same name to exist with different capitalization. In
// blockly, variable names are case insensitive, so different capitalizations
// of a variable name are treated as the same name.
workspaceTest_setUp();
var oldName = 'name1';
var oldCase = 'Name2';
var newName = 'name2';
var id1 = 'id1';
var id2 = 'id2';
var type = 'type1';
workspace.createVariable(oldName, type, id1);
workspace.createVariable(oldCase, type, id2);
createMockBlock(id1);
createMockBlock(id2);
// Blocks should have the correct variable names
var old_block_var_name_1 = workspace.topBlocks_[0].getVarModels()[0].name;
var old_block_var_name_2 = workspace.topBlocks_[1].getVarModels()[0].name;
assertEquals(oldName, old_block_var_name_1);
assertEquals(oldCase, old_block_var_name_2);
workspace.renameVariableById(id1, newName);
// The old variable gets properly renamed to the new name,
// since variables are case sensitive.
checkVariableValues(workspace, newName, type, id1);
// Both variables should still exist
assertEquals(2, workspace.getAllVariables().length);
// Block which had oldName should have been updated to use newName, while
// block with oldCase should still have the same name.
var block_var_name_1 = workspace.topBlocks_[0].getVarModels()[0].name;
var block_var_name_2 = workspace.topBlocks_[1].getVarModels()[0].name;
assertEquals(newName, block_var_name_1);
assertNotEquals(old_block_var_name_1, block_var_name_1);
assertEquals(oldCase, block_var_name_2);
assertEquals(old_block_var_name_2, block_var_name_2);
workspaceTest_tearDown();
}
function test_deleteVariableById_Trivial() {
workspaceTest_setUp();
createTwoVariablesAndBlocks(workspace);
workspace.deleteVariableById('id1');
checkVariableValues(workspace, 'name2', 'type2', 'id2');
var variable = workspace.getVariableById('id1');
var block_var_name = workspace.topBlocks_[0].getVarModels()[0].name;
assertNull(variable);
assertEquals('name2', block_var_name);
workspaceTest_tearDown();
}

View File

@@ -0,0 +1,381 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2017 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Tests for Blockly.Workspace.undo.
* @author marisaleung@google.com (Marisa Leung)
*/
'use strict';
goog.require('goog.events.EventHandler');
goog.require('goog.testing');
goog.require('goog.testing.events');
goog.require('goog.testing.MockControl');
var workspace;
var mockControl_;
var savedFireFunc = Blockly.Events.fire;
function temporary_fireEvent(event) {
if (!Blockly.Events.isEnabled()) {
return;
}
Blockly.Events.FIRE_QUEUE_.push(event);
Blockly.Events.fireNow_();
}
function undoRedoTest_setUp() {
defineGetVarBlock();
workspace = new Blockly.Workspace();
mockControl_ = new goog.testing.MockControl();
Blockly.Events.fire = temporary_fireEvent;
}
function undoRedoTest_tearDown() {
undefineGetVarBlock();
mockControl_.$tearDown();
workspace.dispose();
Blockly.Events.fire = savedFireFunc;
}
/**
* Check that the top block with the given index contains a variable with
* the given name.
* @param {number} blockIndex The index of the top block.
* @param {string} name The expected name of the variable in the block.
*/
function undoRedoTest_checkBlockVariableName(blockIndex, name) {
var blockVarName = workspace.topBlocks_[blockIndex].getVarModels()[0].name;
assertEquals(name, blockVarName);
}
function createTwoVarsEmptyType() {
workspace.createVariable('name1', '', 'id1');
workspace.createVariable('name2', '', 'id2');
}
function createTwoVarsDifferentTypes() {
workspace.createVariable('name1', 'type1', 'id1');
workspace.createVariable('name2', 'type2', 'id2');
}
function test_undoCreateVariable_Trivial() {
undoRedoTest_setUp();
createTwoVarsDifferentTypes();
workspace.undo();
checkVariableValues(workspace, 'name1', 'type1', 'id1');
assertNull(workspace.getVariableById('id2'));
workspace.undo();
assertNull(workspace.getVariableById('id1'));
assertNull(workspace.getVariableById('id2'));
undoRedoTest_tearDown();
}
function test_redoAndUndoCreateVariable_Trivial() {
undoRedoTest_setUp();
createTwoVarsDifferentTypes();
workspace.undo();
workspace.undo(true);
// Expect that variable 'id2' is recreated
checkVariableValues(workspace, 'name1', 'type1', 'id1');
checkVariableValues(workspace, 'name2', 'type2', 'id2');
workspace.undo();
workspace.undo();
workspace.undo(true);
// Expect that variable 'id1' is recreated
checkVariableValues(workspace, 'name1', 'type1', 'id1');
assertNull(workspace.getVariableById('id2'));
undoRedoTest_tearDown();
}
function test_undoDeleteVariable_NoBlocks() {
undoRedoTest_setUp();
createTwoVarsDifferentTypes();
workspace.deleteVariableById('id1');
workspace.deleteVariableById('id2');
workspace.undo();
assertNull(workspace.getVariableById('id1'));
checkVariableValues(workspace, 'name2', 'type2', 'id2');
workspace.undo();
checkVariableValues(workspace, 'name1', 'type1', 'id1');
checkVariableValues(workspace, 'name2', 'type2', 'id2');
undoRedoTest_tearDown();
}
function test_undoDeleteVariable_WithBlocks() {
undoRedoTest_setUp();
createTwoVariablesAndBlocks(workspace);
workspace.deleteVariableById('id1');
workspace.deleteVariableById('id2');
workspace.undo();
undoRedoTest_checkBlockVariableName(0, 'name2');
assertNull(workspace.getVariableById('id1'));
checkVariableValues(workspace, 'name2', 'type2', 'id2');
workspace.undo();
undoRedoTest_checkBlockVariableName(0, 'name2');
undoRedoTest_checkBlockVariableName(1, 'name1');
checkVariableValues(workspace, 'name1', 'type1', 'id1');
checkVariableValues(workspace, 'name2', 'type2', 'id2');
undoRedoTest_tearDown();
}
function test_redoAndUndoDeleteVariable_NoBlocks() {
undoRedoTest_setUp();
createTwoVarsDifferentTypes();
workspace.deleteVariableById('id1');
workspace.deleteVariableById('id2');
workspace.undo();
workspace.undo(true);
// Expect that both variables are deleted
assertNull(workspace.getVariableById('id1'));
assertNull(workspace.getVariableById('id2'));
workspace.undo();
workspace.undo();
workspace.undo(true);
// Expect that variable 'id2' is recreated
assertNull(workspace.getVariableById('id1'));
checkVariableValues(workspace, 'name2', 'type2', 'id2');
undoRedoTest_tearDown();
}
function test_redoAndUndoDeleteVariable_WithBlocks() {
undoRedoTest_setUp();
createTwoVariablesAndBlocks(workspace);
workspace.deleteVariableById('id1');
workspace.deleteVariableById('id2');
workspace.undo();
workspace.undo(true);
// Expect that both variables are deleted
assertEquals(0, workspace.topBlocks_.length);
assertNull(workspace.getVariableById('id1'));
assertNull(workspace.getVariableById('id2'));
workspace.undo();
workspace.undo();
workspace.undo(true);
// Expect that variable 'id2' is recreated
undoRedoTest_checkBlockVariableName(0, 'name2');
assertNull(workspace.getVariableById('id1'));
checkVariableValues(workspace, 'name2', 'type2', 'id2');
undoRedoTest_tearDown();
}
function test_redoAndUndoDeleteVariableTwice_NoBlocks() {
undoRedoTest_setUp();
workspace.createVariable('name1', 'type1', 'id1');
workspace.deleteVariableById('id1');
workspace.deleteVariableById('id1');
// Check the undoStack only recorded one delete event.
var undoStack = workspace.undoStack_;
assertEquals('var_delete', undoStack[undoStack.length-1].type);
assertNotEquals('var_delete', undoStack[undoStack.length-2].type);
// undo delete
workspace.undo();
checkVariableValues(workspace, 'name1', 'type1', 'id1');
// redo delete
workspace.undo(true);
assertNull(workspace.getVariableById('id1'));
// redo delete, nothing should happen
workspace.undo(true);
assertNull(workspace.getVariableById('id1'));
undoRedoTest_tearDown();
}
function test_redoAndUndoDeleteVariableTwice_WithBlocks() {
undoRedoTest_setUp();
var id = 'id1';
workspace.createVariable('name1', 'type1', id);
createMockBlock(id);
workspace.deleteVariableById(id);
workspace.deleteVariableById(id);
// Check the undoStack only recorded one delete event.
var undoStack = workspace.undoStack_;
assertEquals('var_delete', undoStack[undoStack.length-1].type);
assertEquals('delete', undoStack[undoStack.length-2].type);
assertNotEquals('var_delete', undoStack[undoStack.length-3].type);
// undo delete
workspace.undo();
undoRedoTest_checkBlockVariableName(0, 'name1');
checkVariableValues(workspace, 'name1', 'type1', id);
// redo delete
workspace.undo(true);
assertEquals(0, workspace.topBlocks_.length);
assertNull(workspace.getVariableById(id));
// redo delete, nothing should happen
workspace.undo(true);
assertEquals(0, workspace.topBlocks_.length);
assertNull(workspace.getVariableById(id));
undoRedoTest_tearDown();
}
function test_undoRedoRenameVariable_OneExists_NoBlocks() {
undoRedoTest_setUp();
workspace.createVariable('name1', '', 'id1');
workspace.renameVariableById('id1', 'name2');
workspace.undo();
checkVariableValues(workspace, 'name1', '', 'id1');
workspace.undo(true);
checkVariableValues(workspace, 'name2', '', 'id1');
undoRedoTest_tearDown();
}
function test_undoRedoRenameVariable_OneExists_WithBlocks() {
undoRedoTest_setUp();
workspace.createVariable('name1', '', 'id1');
createMockBlock('id1');
workspace.renameVariableById('id1', 'name2');
workspace.undo();
undoRedoTest_checkBlockVariableName(0, 'name1');
checkVariableValues(workspace, 'name1', '', 'id1');
workspace.undo(true);
checkVariableValues(workspace, 'name2', '', 'id1');
undoRedoTest_checkBlockVariableName(0, 'name2');
undoRedoTest_tearDown();
}
function test_undoRedoRenameVariable_BothExist_NoBlocks() {
undoRedoTest_setUp();
createTwoVarsEmptyType();
workspace.renameVariableById('id1', 'name2');
workspace.undo();
checkVariableValues(workspace, 'name1', '', 'id1');
checkVariableValues(workspace, 'name2', '', 'id2');
workspace.undo(true);
checkVariableValues(workspace, 'name2', '', 'id2');
assertNull(workspace.getVariableById('id1'));
undoRedoTest_tearDown();
}
function test_undoRedoRenameVariable_BothExist_WithBlocks() {
undoRedoTest_setUp();
createTwoVarsEmptyType();
createMockBlock('id1');
createMockBlock('id2');
workspace.renameVariableById('id1', 'name2');
workspace.undo();
undoRedoTest_checkBlockVariableName(0, 'name1');
undoRedoTest_checkBlockVariableName(1, 'name2');
checkVariableValues(workspace, 'name1', '', 'id1');
checkVariableValues(workspace, 'name2', '', 'id2');
workspace.undo(true);
undoRedoTest_checkBlockVariableName(0, 'name2');
undoRedoTest_checkBlockVariableName(1, 'name2');
undoRedoTest_tearDown();
}
function test_undoRedoRenameVariable_BothExistCaseChange_NoBlocks() {
undoRedoTest_setUp();
createTwoVarsEmptyType();
workspace.renameVariableById('id1', 'Name2');
workspace.undo();
checkVariableValues(workspace, 'name1', '', 'id1');
checkVariableValues(workspace, 'name2', '', 'id2');
workspace.undo(true);
checkVariableValues(workspace, 'Name2', '', 'id2');
assertNull(workspace.getVariable('name1'));
undoRedoTest_tearDown();
}
function test_undoRedoRenameVariable_BothExistCaseChange_WithBlocks() {
undoRedoTest_setUp();
createTwoVarsEmptyType();
createMockBlock('id1');
createMockBlock('id2');
workspace.renameVariableById('id1', 'Name2');
workspace.undo();
undoRedoTest_checkBlockVariableName(0, 'name1');
undoRedoTest_checkBlockVariableName(1, 'name2');
checkVariableValues(workspace, 'name1', '', 'id1');
checkVariableValues(workspace, 'name2', '', 'id2');
workspace.undo(true);
checkVariableValues(workspace, 'Name2', '', 'id2');
assertNull(workspace.getVariableById('id1'));
undoRedoTest_checkBlockVariableName(0, 'Name2');
undoRedoTest_checkBlockVariableName(1, 'Name2');
undoRedoTest_tearDown();
}
function test_undoRedoRenameVariable_OnlyCaseChange_NoBlocks() {
undoRedoTest_setUp();
workspace.createVariable('name1', '', 'id1');
workspace.renameVariableById('id1', 'Name1');
workspace.undo();
checkVariableValues(workspace, 'name1', '', 'id1');
workspace.undo(true);
checkVariableValues(workspace, 'Name1', '', 'id1');
undoRedoTest_tearDown();
}
function test_undoRedoRenameVariable_OnlyCaseChange_WithBlocks() {
undoRedoTest_setUp();
workspace.createVariable('name1', '', 'id1');
createMockBlock('id1');
workspace.renameVariableById('id1', 'Name1');
workspace.undo();
undoRedoTest_checkBlockVariableName(0, 'name1');
checkVariableValues(workspace, 'name1', '', 'id1');
workspace.undo(true);
checkVariableValues(workspace, 'Name1', '', 'id1');
undoRedoTest_checkBlockVariableName(0, 'Name1');
undoRedoTest_tearDown();
}

View File

@@ -0,0 +1,443 @@
/**
* @license
* Blockly Tests
*
* Copyright 2014 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
goog.require('goog.testing');
goog.require('goog.testing.MockControl');
var mockControl_;
var workspace;
var XML_TEXT = ['<xml xmlns="http://www.w3.org/1999/xhtml">',
' <block type="controls_repeat_ext" inline="true" x="21" y="23">',
' <value name="TIMES">',
' <block type="math_number">',
' <field name="NUM">10</field>',
' </block>',
' </value>',
' <statement name="DO">',
' <block type="variables_set" inline="true">',
' <field name="VAR">item</field>',
' <value name="VALUE">',
' <block type="lists_create_empty"></block>',
' </value>',
' <next>',
' <block type="text_print" inline="false">',
' <value name="TEXT">',
' <block type="text">',
' <field name="TEXT">Hello</field>',
' </block>',
' </value>',
' </block>',
' </next>',
' </block>',
' </statement>',
' </block>',
'</xml>'].join('\n');
function xmlTest_setUp() {
workspace = new Blockly.Workspace();
mockControl_ = new goog.testing.MockControl();
}
function xmlTest_setUpWithMockBlocks() {
xmlTest_setUp();
Blockly.defineBlocksWithJsonArray([{
'type': 'field_variable_test_block',
'message0': '%1',
'args0': [
{
'type': 'field_variable',
'name': 'VAR',
'variable': 'item'
}
]
},
{
'type': 'field_serializable_test_block',
'message0': '%1 %2',
'args0': [
{
'type': 'field_label_serializable',
'name': 'FIELD'
},
{
"type": "field_input",
"name": "TEXTINPUT",
"text": "default"
}
]
}]);
}
function xmlTest_tearDown() {
mockControl_.$tearDown();
workspace.dispose();
}
function xmlTest_tearDownWithMockBlocks() {
xmlTest_tearDown();
delete Blockly.Blocks.field_variable_test_block;
}
/**
* Check the values of the non variable field dom.
* @param {!Element} fieldDom The xml dom of the non variable field.
* @param {!string} name The expected name of the variable.
* @param {!string} text The expected text of the variable.
*/
function xmlTest_checkNonVariableField(fieldDom, name, text) {
assertEquals(text, fieldDom.textContent);
assertEquals(name, fieldDom.getAttribute('name'));
assertNull(fieldDom.getAttribute('id'));
assertNull(fieldDom.getAttribute('variabletype'));
}
/**
* Check the values of the variable field DOM.
* @param {!Element} fieldDom The xml dom of the variable field.
* @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.
* @param {!string} text The expected text of the variable.
*/
function xmlTest_checkVariableFieldDomValues(fieldDom, name, type, id, text) {
assertEquals(name, fieldDom.getAttribute('name'));
assertEquals(type, fieldDom.getAttribute('variabletype'));
assertEquals(id, fieldDom.getAttribute('id'));
assertEquals(text, fieldDom.textContent);
}
/**
* Check the values of the variable DOM.
* @param {!Element} variableDom The xml dom of the variable.
* @param {!string} type The expected type of the variable.
* @param {!string} id The expected id of the variable.
* @param {!string} text The expected text of the variable.
*/
function xmlTest_checkVariableDomValues(variableDom, type, id, text) {
assertEquals(type, variableDom.getAttribute('type'));
assertEquals(id, variableDom.getAttribute('id'));
assertEquals(text, variableDom.textContent);
}
function test_textToDom() {
var dom = Blockly.Xml.textToDom(XML_TEXT);
assertEquals('XML tag', 'xml', dom.nodeName);
assertEquals('Block tags', 6, dom.getElementsByTagName('block').length);
}
function test_domToText() {
var dom = Blockly.Xml.textToDom(XML_TEXT);
var text = Blockly.Xml.domToText(dom);
assertEquals('Round trip', XML_TEXT.replace(/\s+/g, ''),
text.replace(/\s+/g, ''));
}
function test_domToWorkspace_BackwardCompatibility() {
// Expect that workspace still loads without serialized variables.
xmlTest_setUpWithMockBlocks();
setUpMockMethod(mockControl_, Blockly.utils, 'genUid', null, ['1', '1']);
try {
var dom = Blockly.Xml.textToDom(
'<xml>' +
' <block type="field_variable_test_block" id="block_id">' +
' <field name="VAR">name1</field>' +
' </block>' +
'</xml>');
Blockly.Xml.domToWorkspace(dom, workspace);
assertEquals('Block count', 1, workspace.getAllBlocks().length);
checkVariableValues(workspace, 'name1', '', '1');
} finally {
xmlTest_tearDownWithMockBlocks();
}
}
function test_domToWorkspace_VariablesAtTop() {
// Expect that unused variables are preserved.
xmlTest_setUpWithMockBlocks();
try {
var dom = Blockly.Xml.textToDom(
'<xml>' +
' <variables>' +
' <variable type="type1" id="id1">name1</variable>' +
' <variable type="type2" id="id2">name2</variable>' +
' <variable type="" id="id3">name3</variable>' +
' </variables>' +
' <block type="field_variable_test_block">' +
' <field name="VAR" id="id3" variabletype="">name3</field>' +
' </block>' +
'</xml>');
Blockly.Xml.domToWorkspace(dom, workspace);
assertEquals('Block count', 1, workspace.getAllBlocks().length);
checkVariableValues(workspace, 'name1', 'type1', 'id1');
checkVariableValues(workspace, 'name2', 'type2', 'id2');
checkVariableValues(workspace, 'name3', '', 'id3');
} finally {
xmlTest_tearDownWithMockBlocks();
}
}
function test_domToWorkspace_VariablesAtTop_DuplicateVariablesTag() {
// Expect thrown Error because of duplicate 'variables' tag
xmlTest_setUpWithMockBlocks();
try {
var dom = Blockly.Xml.textToDom(
'<xml>' +
' <variables>' +
' </variables>' +
' <variables>' +
' </variables>' +
'</xml>');
Blockly.Xml.domToWorkspace(dom, workspace);
fail();
}
catch (e) {
// expected
} finally {
xmlTest_tearDownWithMockBlocks();
}
}
function test_domToWorkspace_VariablesAtTop_MissingType() {
// Expect thrown error when a variable tag is missing the type attribute.
workspace = new Blockly.Workspace();
try {
var dom = Blockly.Xml.textToDom(
'<xml>' +
' <variables>' +
' <variable id="id1">name1</variable>' +
' </variables>' +
' <block type="field_variable_test_block">' +
' <field name="VAR" id="id1" variabletype="">name3</field>' +
' </block>' +
'</xml>');
Blockly.Xml.domToWorkspace(dom, workspace);
fail();
} catch (e) {
// expected
} finally {
workspace.dispose();
}
}
function test_domToWorkspace_VariablesAtTop_MismatchBlockType() {
// Expect thrown error when the serialized type of a variable does not match
// the type of a variable field that references it.
xmlTest_setUpWithMockBlocks();
try {
var dom = Blockly.Xml.textToDom(
'<xml>' +
' <variables>' +
' <variable type="type1" id="id1">name1</variable>' +
' </variables>' +
' <block type="field_variable_test_block">' +
' <field name="VAR" id="id1" variabletype="">name1</field>' +
' </block>' +
'</xml>');
Blockly.Xml.domToWorkspace(dom, workspace);
fail();
} catch (e) {
// expected
} finally {
xmlTest_tearDownWithMockBlocks();
}
}
function test_domToPrettyText() {
var dom = Blockly.Xml.textToDom(XML_TEXT);
var text = Blockly.Xml.domToPrettyText(dom);
assertEquals('Round trip', XML_TEXT.replace(/\s+/g, ''),
text.replace(/\s+/g, ''));
}
/**
* Tests the that appendDomToWorkspace works in a headless mode.
* Also see test_appendDomToWorkspace() in workspace_svg_test.js.
*/
function test_appendDomToWorkspace() {
Blockly.Blocks.test_block = {
init: function() {
this.jsonInit({
message0: 'test',
});
}
};
var workspace = new Blockly.Workspace();
try {
var dom = Blockly.Xml.textToDom(
'<xml xmlns="http://www.w3.org/1999/xhtml">' +
' <block type="test_block" inline="true" x="21" y="23">' +
' </block>' +
'</xml>');
workspace = new Blockly.Workspace();
Blockly.Xml.appendDomToWorkspace(dom, workspace);
assertEquals('Block count', 1, workspace.getAllBlocks().length);
var newBlockIds = Blockly.Xml.appendDomToWorkspace(dom, workspace);
assertEquals('Block count', 2, workspace.getAllBlocks().length);
assertEquals('Number of new block ids',1,newBlockIds.length);
} finally {
delete Blockly.Blocks.test_block;
workspace.dispose();
}
}
function test_blockToDom_fieldToDom_trivial() {
xmlTest_setUpWithMockBlocks();
// TODO (#1199): make a similar test where the variable is given a non-empty
// type.f
workspace.createVariable('name1', '', 'id1');
var block = new Blockly.Block(workspace, 'field_variable_test_block');
block.inputList[0].fieldRow[0].setValue('id1');
var resultFieldDom = Blockly.Xml.blockToDom(block).childNodes[0];
xmlTest_checkVariableFieldDomValues(resultFieldDom, 'VAR', '', 'id1',
'name1');
xmlTest_tearDownWithMockBlocks();
}
function test_blockToDom_fieldToDom_defaultCase() {
xmlTest_setUpWithMockBlocks();
setUpMockMethod(mockControl_, Blockly.utils, 'genUid', null, ['1', '1']);
try {
workspace.createVariable('name1');
Blockly.Events.disable();
var block = new Blockly.Block(workspace, 'field_variable_test_block');
block.inputList[0].fieldRow[0].setValue('1');
Blockly.Events.enable();
var resultFieldDom = Blockly.Xml.blockToDom(block).childNodes[0];
// Expect type is '' and id is '1' since we don't specify type and id.
xmlTest_checkVariableFieldDomValues(resultFieldDom, 'VAR', '', '1', 'name1');
} finally {
xmlTest_tearDownWithMockBlocks();
}
}
function test_blockToDom_fieldToDom_notAFieldVariable() {
Blockly.defineBlocksWithJsonArray([{
"type": "field_angle_test_block",
"message0": "%1",
"args0": [
{
"type": "field_angle",
"name": "VAR",
"angle": 90
}
],
}]);
xmlTest_setUpWithMockBlocks();
var block = new Blockly.Block(workspace, 'field_angle_test_block');
var resultFieldDom = Blockly.Xml.blockToDom(block).childNodes[0];
xmlTest_checkNonVariableField(resultFieldDom, 'VAR', '90');
delete Blockly.Blocks.field_angle_block;
xmlTest_tearDownWithMockBlocks();
}
function test_variablesToDom_oneVariable() {
xmlTest_setUp();
setUpMockMethod(mockControl_, Blockly.utils, 'genUid', null, ['1']);
workspace.createVariable('name1');
var resultDom = Blockly.Xml.variablesToDom(workspace.getAllVariables());
assertEquals(1, resultDom.children.length);
var resultVariableDom = resultDom.children[0];
assertEquals('name1', resultVariableDom.textContent);
assertEquals('', resultVariableDom.getAttribute('type'));
assertEquals('1', resultVariableDom.getAttribute('id'));
xmlTest_tearDown();
}
function test_variablesToDom_twoVariables_oneBlock() {
xmlTest_setUpWithMockBlocks();
workspace.createVariable('name1', '', 'id1');
workspace.createVariable('name2', 'type2', 'id2');
// If events are enabled during block construction, it will create a default
// variable.
Blockly.Events.disable();
var block = new Blockly.Block(workspace, 'field_variable_test_block');
block.inputList[0].fieldRow[0].setValue('id1');
Blockly.Events.enable();
var resultDom = Blockly.Xml.variablesToDom(workspace.getAllVariables());
assertEquals(2, resultDom.children.length);
xmlTest_checkVariableDomValues(resultDom.children[0], '', 'id1',
'name1');
xmlTest_checkVariableDomValues(resultDom.children[1], 'type2', 'id2',
'name2');
xmlTest_tearDownWithMockBlocks();
}
function test_variablesToDom_noVariables() {
xmlTest_setUp();
workspace.createVariable('name1');
var resultDom = Blockly.Xml.variablesToDom(workspace.getAllVariables());
assertEquals(1, resultDom.children.length);
xmlTest_tearDown();
}
function test_fieldIsSerialized() {
xmlTest_setUpWithMockBlocks();
var block = new Blockly.Block(workspace, 'field_serializable_test_block');
block.getField('FIELD').setValue('serialized');
var resultDom = Blockly.Xml.blockToDom(block).childNodes[0];
assertEquals('serialized', resultDom.textContent);
assertEquals('FIELD', resultDom.getAttribute('name'));
xmlTest_tearDownWithMockBlocks();
}
function test_fieldIsNotSerialized() {
xmlTest_setUpWithMockBlocks();
var block = new Blockly.Block(workspace, 'field_serializable_test_block');
block.getField('FIELD').SERIALIZABLE = false;
block.getField('FIELD').setValue('serialized');
var resultDom = Blockly.Xml.blockToDom(block).childNodes[0];
assertEquals('default', resultDom.textContent);
assertEquals('TEXTINPUT', resultDom.getAttribute('name'));
xmlTest_tearDownWithMockBlocks();
}
function test_variableFieldXml_caseSensitive() {
var id = 'testId';
var type = 'testType';
var name = 'testName';
var mockVariableModel = {
type: type,
name: name,
getId: function() {
return id;
}
};
var generatedXml =
Blockly.Variables.generateVariableFieldXml_(mockVariableModel);
var goldenXml =
'<field name="VARIABLE"' +
' id="' + id + '"' +
' variabletype="' + type + '"' +
'>' + name + '</field>';
assertEquals(goldenXml, generatedXml);
}

View File

@@ -0,0 +1,262 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Multi-toolbox Playground</title>
<script src="../blockly_uncompressed_horizontal.js"></script>
<script src="../msg/messages.js"></script>
<script src="../blocks_common/math.js"></script>
<script src="../blocks_common/text.js"></script>
<script src="../blocks_horizontal/control.js"></script>
<script src="../blocks_horizontal/event.js"></script>
<script src="../blocks_horizontal/wedo.js"></script>
<script>
'use strict';
var options = {
comments: true,
collapse: true,
media: '../media/',
oneBasedIndex: true,
readOnly: false,
//rtl: false,
scrollbars: true,
trashcan: true,
//toolbox: null,
//horizontalLayout: false,
//toolboxPosition: 'start',
zoom: {
controls: true,
wheel: true,
startScale: 1.0,
maxScale: 4,
minScale: 0.25,
scaleSpeed: 1.1
}
};
function start() {
var match = location.search.match(/toolbox=([^&]+)/);
var toolbox =
document.getElementById('toolbox-' + (match ? match[1] : 'categories'));
document.forms.options.elements.toolbox.selectedIndex =
Number(toolbox.getElementsByTagName('category').length == 0);
startBlocklyInstance('VertStartLTR', false, false, 'start', toolbox);
startBlocklyInstance('VertStartRTL', true, false, 'start', toolbox);
startBlocklyInstance('VertEndLTR', false, false, 'end', toolbox);
startBlocklyInstance('VertEndRTL', true, false, 'end', toolbox);
startBlocklyInstance('HorizontalStartLTR', false, true, 'start', toolbox);
startBlocklyInstance('HorizontalStartRTL', true, true, 'start', toolbox);
startBlocklyInstance('HorizontalEndLTR', false, true, 'end', toolbox);
startBlocklyInstance('HorizontalEndRTL', true, true, 'end', toolbox);
}
function startBlocklyInstance(suffix, rtl, horizontalLayout, position,
toolbox) {
options.rtl = rtl;
options.toolbox = toolbox;
options.horizontalLayout = horizontalLayout;
options.toolboxPosition = position;
Blockly.inject('blocklyDiv' + suffix, options);
}
</script>
<style>
html, body {
height: 100%;
}
body {
background-color: #fff;
font-family: sans-serif;
}
h1 {
font-weight: normal;
font-size: 140%;
}
#octaweb {
width: 100%;
}
#octaweb th {
padding-top: 1em;
width: 50%;
}
#octaweb td {
width: 50%;
}
#octaweb td >div {
height: 480px;
width: 100%;
}
</style>
</head>
<body onload="start()">
<h1>Blockly Multi Playground</h1>
<form id="options">
<select name="toolbox" onchange="document.forms.options.submit()">
<option value="categories">Categories</option>
<option value="simple">Simple</option>
</select>
</form>
<table id="octaweb">
<tr>
<th>LTR, Vertical, Start</th>
<th>RTL, Vertical, Start</th>
</tr>
<tr>
<td><div id="blocklyDivVertStartLTR"></div></td>
<td><div id="blocklyDivVertStartRTL"></div></td>
</tr>
<tr>
<th>LTR, Vertical, End</th>
<th>RTL, Vertical, End</th>
</tr>
<tr>
<td><div id="blocklyDivVertEndLTR"></div></td>
<td><div id="blocklyDivVertEndRTL"></div></td>
</tr>
<tr>
<th>LTR, Horizontal, Start</th>
<th>RTL, Horizontal, Start</th>
</tr>
<tr>
<td><div id="blocklyDivHorizontalStartLTR"></div></td>
<td><div id="blocklyDivHorizontalStartRTL"></div></td>
</tr>
<tr>
<th>LTR, Horizontal, End</th>
<th>RTL, Horizontal, End</th>
</tr>
<tr>
<td><div id="blocklyDivHorizontalEndLTR"></div></td>
<td><div id="blocklyDivHorizontalEndRTL"></div></td>
</tr>
</table>
<xml id="toolbox-simple" style="display: none">
<block type="event_whenflagclicked"></block>
<block type="wedo_motorclockwise"></block>
<!-- <block type="control_repeat"></block> -->
<block type="control_forever"></block>
<block type="control_repeat">
<value name="TIMES">
<shadow type="math_number">
<field name="NUM">10</field>
</shadow>
</value>
</block>
</xml>
<xml id="toolbox-categories" style="display: none">
<category name="Events" colour="210" secondaryColour="0">
<block type="event_whenflagclicked"></block>
<block type="event_whenflagclicked"></block>
<block type="event_whenflagclicked"></block>
<block type="event_whenflagclicked"></block>
<block type="event_whenflagclicked"></block>
<block type="event_whenflagclicked"></block>
<block type="event_whenflagclicked"></block>
<block type="event_whenflagclicked"></block>
<block type="event_whenflagclicked"></block>
<block type="event_whenflagclicked"></block>
<block type="event_whenflagclicked"></block>
<block type="event_whenflagclicked"></block>
<block type="event_whenflagclicked"></block>
</category>
<sep></sep>
<!-- <block type="control_repeat"></block> -->
<category name="Pants" secondaryColour="0">
<block type="control_forever"></block>
<block type="control_repeat">
<value name="TIMES">
<shadow type="math_number">
<field name="NUM">10</field>
</shadow>
</value>
</block>
</category>
</xml>
<xml id="toolbox_categories" style="display: none">
<category name="Events" secondaryColour="0">
<block type="event_whenflagclicked"></block>
<block type="event_whenbroadcastreceived">
<value name="CHOICE">
<shadow type="dropdown_whenbroadcast">
<field name="CHOICE">blue</field>
</shadow>
</value>
</block>
<block type="event_broadcast">
<value name="CHOICE">
<shadow type="dropdown_broadcast">
<field name="CHOICE">blue</field>
</shadow>
</value>
</block>
</category>
<category name="Control" secondaryColour="0">
<block type="control_forever"></block>
<block type="control_repeat">
<value name="TIMES">
<shadow type="math_number">
<field name="NUM">4</field>
</shadow>
</value>
</block>
<block type="control_stop"></block>
<block type="control_wait">
<value name="DURATION">
<shadow type="math_number">
<field name="NUM">1</field>
</shadow>
</value>
</block>
</category>
<category name="Wedo" secondaryColour="0">
<block type="wedo_setcolor">
<value name="CHOICE">
<shadow type="dropdown_wedo_setcolor">
<field name="CHOICE">mystery</field>
</shadow>
</value>
</block>
<block type="wedo_motorclockwise">
<value name="DURATION">
<shadow type="math_number">
<field name="NUM">1</field>
</shadow>
</value>
</block>
<block type="wedo_motorcounterclockwise">
<value name="DURATION">
<shadow type="math_number">
<field name="NUM">1</field>
</shadow>
</value>
</block>
<block type="wedo_motorspeed">
<value name="CHOICE">
<shadow type="dropdown_wedo_motorspeed">
<field name="CHOICE">fast</field>
</shadow>
</value>
</block>
<block type="wedo_whentilt">
<value name="CHOICE">
<shadow type="dropdown_wedo_whentilt">
<field name="CHOICE">forward</field>
</shadow>
</value>
</block>
<block type="wedo_whendistanceclose"></block>
</category>
</xml>
</body>
</html>

View File

@@ -0,0 +1,585 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Vertical Playground</title>
<script src="../blockly_uncompressed_vertical.js"></script>
<script src="../msg/messages.js"></script>
<script src="../msg/scratch_msgs.js"></script>
<script src="../blocks_vertical/vertical_extensions.js"></script>
<script src="../blocks_common/math.js"></script>
<script src="../blocks_common/matrix.js"></script>
<script src="../blocks_common/note.js"></script>
<script src="../blocks_common/text.js"></script>
<script src="../blocks_common/colour.js"></script>
<script src="../blocks_vertical/control.js"></script>
<script src="../blocks_vertical/event.js"></script>
<script src="../blocks_vertical/motion.js"></script>
<script src="../blocks_vertical/looks.js"></script>
<script src="../blocks_vertical/procedures.js"></script>
<script src="../blocks_vertical/operators.js"></script>
<script src="../blocks_vertical/pen.js"></script>
<script src="../blocks_vertical/sound.js"></script>
<script src="../blocks_vertical/sensing.js"></script>
<script src="../blocks_vertical/data.js"></script>
<script src="../blocks_vertical/extensions.js"></script>
<script src="../blocks_vertical/default_toolbox.js"></script>
<script>
'use strict';
window.ScratchBlocks = Blockly;
var workspace = null;
function start() {
var soundsEnabled = null;
if (sessionStorage) {
// Restore sounds state.
soundsEnabled = sessionStorage.getItem('soundsEnabled');
if (soundsEnabled === null) {
soundsEnabled = true;
} else {
soundsEnabled = (soundsEnabled === 'true');
}
} else {
soundsEnabled = true;
}
setSoundsEnabled(soundsEnabled);
// Setup blocks
// Parse the URL arguments.
var match = location.search.match(/dir=([^&]+)/);
var rtl = match && match[1] == 'rtl';
document.forms.options.elements.dir.selectedIndex = Number(rtl);
var toolbox = getToolboxElement();
document.forms.options.elements.toolbox.selectedIndex =
toolbox ? 1: 0;
match = location.search.match(/side=([^&]+)/);
var side = match ? match[1] : 'start';
document.forms.options.elements.side.value = side;
match = location.search.match(/locale=([^&]+)/);
var locale = match ? match[1] : 'en';
Blockly.ScratchMsgs.setLocale(locale);
document.forms.options.elements.locale.value = locale;
// Create main workspace.
workspace = Blockly.inject('blocklyDiv', {
comments: true,
disable: false,
collapse: false,
media: '../media/',
readOnly: false,
rtl: rtl,
scrollbars: true,
toolbox: toolbox,
toolboxPosition: side == 'top' || side == 'start' ? 'start' : 'end',
horizontalLayout: side == 'top' || side == 'bottom',
sounds: soundsEnabled,
zoom: {
controls: true,
wheel: true,
startScale: 0.675,
maxScale: 4,
minScale: 0.25,
scaleSpeed: 1.1
},
colours: {
fieldShadow: 'rgba(255, 255, 255, 0.3)',
dragShadowOpacity: 0.6
}
});
if (sessionStorage) {
// Restore previously displayed text.
var text = sessionStorage.getItem('textarea');
if (text) {
document.getElementById('importExport').value = text;
}
taChange();
}
if (sessionStorage) {
// Restore event logging state.
var state = sessionStorage.getItem('logEvents');
logEvents(Boolean(state));
// Restore flyout event logging state.
state = sessionStorage.getItem('logFlyoutEvents');
logFlyoutEvents(Boolean(state));
}
}
function getToolboxElement() {
var match = location.search.match(/toolbox=([^&]+)/);
return document.getElementById('toolbox-' + (match ? match[1] : 'categories'));
}
function toXml() {
var output = document.getElementById('importExport');
var xml = Blockly.Xml.workspaceToDom(workspace);
output.value = Blockly.Xml.domToPrettyText(xml);
output.focus();
output.select();
taChange();
}
function fromXml() {
var input = document.getElementById('importExport');
var xml = Blockly.Xml.textToDom(input.value);
Blockly.Xml.domToWorkspace(xml, workspace);
workspace.refreshToolboxSelection_();
taChange();
}
// Disable the "Import from XML" button if the XML is invalid.
// Preserve text between page reloads.
function taChange() {
var textarea = document.getElementById('importExport');
if (sessionStorage) {
sessionStorage.setItem('textarea', textarea.value)
}
var valid = true;
try {
Blockly.Xml.textToDom(textarea.value);
} catch (e) {
valid = false;
}
document.getElementById('import').disabled = !valid;
}
function logEvents(state) {
var checkbox = document.getElementById('logCheck');
checkbox.checked = state;
if (sessionStorage) {
sessionStorage.setItem('logEvents', state ? 'checked' : '');
}
if (state) {
workspace.addChangeListener(logger);
} else {
workspace.removeChangeListener(logger);
}
}
function logFlyoutEvents(state) {
var checkbox = document.getElementById('logFlyoutCheck');
checkbox.checked = state;
var soundsEnabled = null;
if (sessionStorage) {
sessionStorage.setItem('logFlyoutEvents', state ? 'checked' : '');
}
var flyoutWorkspace = (workspace.flyout_) ? workspace.flyout_.workspace_ :
workspace.toolbox_.flyout_.workspace_;
if (state) {
flyoutWorkspace.addChangeListener(logger);
} else {
flyoutWorkspace.removeChangeListener(logger);
}
}
function logger(e) {
console.log(e);
}
function glowBlock() {
if (Blockly.selected) {
workspace.glowBlock(Blockly.selected.id, true);
}
}
function unglowBlock() {
if (Blockly.selected) {
workspace.glowBlock(Blockly.selected.id, false);
}
}
function glowStack() {
if (Blockly.selected) {
workspace.glowStack(Blockly.selected.id, true);
}
}
function unglowStack() {
if (Blockly.selected) {
workspace.glowStack(Blockly.selected.id, false);
}
}
function sprinkles(n) {
var prototypes = [];
var toolbox = workspace.options.languageTree;
if (!toolbox) {
console.error('Toolbox not found; add a toolbox element to the DOM.');
return;
}
var blocks = toolbox.getElementsByTagName('block');
for (var i = 0; i < n; i++) {
var blockXML = blocks[Math.floor(Math.random() * blocks.length)];
var block = Blockly.Xml.domToBlock(blockXML, workspace);
block.initSvg();
block.moveBy(
Math.round(Math.random() * 450 + 40),
Math.round(Math.random() * 600 + 40)
);
}
}
var equalsXml = [
' <shadow type="operator_equals">',
' <value name="OPERAND1">',
' <shadow type="text">',
' <field name="TEXT">foo</field>',
' </shadow>',
' </value>',
' <value name="OPERAND2">',
' <shadow type="operator_equals"></shadow>',
' </value>',
' </shadow>'
].join('\n');
var spaghettiXml = [
' <block type="control_if_else">',
' <value name="CONDITION">',
' <shadow type="operator_equals"></shadow>',
' </value>',
' <statement name="SUBSTACK"></statement>',
' <statement name="SUBSTACK2"></statement>',
' <next></next>',
' </block>'
].join('\n');
function spaghetti(n) {
console.log("Starting spaghetti. This may take some time...");
var xml = spaghettiXml;
// Nest if/else statements deeply.
for(var i = 0; i < 2 * n; i++) {
xml = xml.replace(/(<statement name="SUBSTACK2?"?>)<\//g,
'$1' + spaghettiXml + '</');
}
// Stack a bit.
for(var i = 0; i < n; i++) {
xml = xml.replace(/(<next>)<\//g,
'$1' + spaghettiXml + '</');
}
// Nest boolean comparisons.
var equalsBlock = equalsXml;
for (var i = 0; i < n; i++) {
equalsBlock = equalsBlock.replace(
/(<shadow( type="operator_equals")?>)<\/shadow>/g, equalsXml);
}
// Put the nested boolean comparisons into if/else statements.
xml = xml.replace(/(<shadow( type="operator_equals")?>)<\/shadow>/g,
equalsBlock);
xml = '<xml xmlns="http://www.w3.org/1999/xhtml">' + xml + '</xml>';
var dom = Blockly.Xml.textToDom(xml);
console.time('Spaghetti domToWorkspace');
Blockly.Xml.domToWorkspace(dom, workspace);
console.timeEnd('Spaghetti domToWorkspace');
}
function setSoundsEnabled(state) {
var checkbox = document.getElementById('soundsEnabled');
checkbox.checked = (state) ? 'checked' : '';
if (sessionStorage) {
sessionStorage.setItem('soundsEnabled', state);
}
}
function reportDemo() {
if (Blockly.selected) {
workspace.reportValue(
Blockly.selected.id,
document.getElementById('reportValue').value
);
}
}
function setLocale(locale) {
workspace.getFlyout().setRecyclingEnabled(false);
var xml = Blockly.Xml.workspaceToDom(workspace);
Blockly.ScratchMsgs.setLocale(locale);
Blockly.Xml.clearWorkspaceAndLoadFromXml(xml, workspace);
workspace.getFlyout().setRecyclingEnabled(true);
}
</script>
<style>
html, body {
height: 100%;
}
body {
background-color: #fff;
font-family: sans-serif;
overflow: hidden;
}
h1 {
font-weight: normal;
font-size: 140%;
}
#blocklyDiv {
float: right;
height: 95%;
width: 70%;
}
#collaborators {
float: right;
width: 30px;
margin-left: 10px;
}
#collaborators > img {
margin-right: 5px;
height: 30px;
padding-bottom: 5px;
width: 30px;
border-radius: 3px;
}
#importExport {
font-family: monospace;
}
</style>
</head>
<body onload="start()">
<div id="collaborators"></div>
<div id="blocklyDiv"></div>
<!-- Simple toolbox -->
<xml id="toolbox-simple" style="display: none">
<block type="operator_random">
<value name="FROM">
<shadow type="math_number">
<field name="NUM">1</field>
</shadow>
</value>
<value name="TO">
<shadow type="math_number">
<field name="NUM">10</field>
</shadow>
</value>
</block>
<block type="operator_lt">
<value name="OPERAND1">
<shadow type="text">
<field name="TEXT"></field>
</shadow>
</value>
<value name="OPERAND2">
<shadow type="text">
<field name="TEXT"></field>
</shadow>
</value>
</block>
<block type="operator_equals">
<value name="OPERAND1">
<shadow type="text">
<field name="TEXT"></field>
</shadow>
</value>
<value name="OPERAND2">
<shadow type="text">
<field name="TEXT"></field>
</shadow>
</value>
</block>
<block type="operator_gt">
<value name="OPERAND1">
<shadow type="text">
<field name="TEXT"></field>
</shadow>
</value>
<value name="OPERAND2">
<shadow type="text">
<field name="TEXT"></field>
</shadow>
</value>
</block>
<block type="operator_and"></block>
<block type="operator_or"></block>
<block type="operator_not"></block>
<block type="operator_join">
<value name="STRING1">
<shadow type="text">
<field name="TEXT">hello</field>
</shadow>
</value>
<value name="STRING2">
<shadow type="text">
<field name="TEXT">world</field>
</shadow>
</value>
</block>
<block type="operator_letter_of">
<value name="LETTER">
<shadow type="math_whole_number">
<field name="NUM">1</field>
</shadow>
</value>
<value name="STRING">
<shadow type="text">
<field name="TEXT">world</field>
</shadow>
</value>
</block>
<block type="operator_length">
<value name="STRING">
<shadow type="text">
<field name="TEXT">world</field>
</shadow>
</value>
</block>
<block type="operator_mod">
<value name="NUM1">
<shadow type="math_number">
<field name="NUM"></field>
</shadow>
</value>
<value name="NUM2">
<shadow type="math_number">
<field name="NUM"></field>
</shadow>
</value>
</block>
<block type="operator_round">
<value name="NUM">
<shadow type="math_number">
<field name="NUM"></field>
</shadow>
</value>
</block>
<block type="operator_mathop">
<value name="OPERATOR">
<shadow type="operator_mathop_menu"></shadow>
</value>
<value name="NUM">
<shadow type="math_number">
<field name="NUM"></field>
</shadow>
</value>
</block>
</xml>
<!-- Sidebar -->
<h1>Vertical Blocks</h1>
<p>
<a href="javascript:void(workspace.setVisible(true))">Show</a>
- <a href="javascript:void(workspace.setVisible(false))">Hide</a>
</p>
<form id="options">
<select name="dir" onchange="document.forms.options.submit()">
<option value="ltr">LTR</option>
<option value="rtl">RTL</option>
</select>
<select name="toolbox" onchange="document.forms.options.submit()">
<option value="categories">Categories</option>
<option value="simple">Simple</option>
</select>
<select name="side" onchange="document.forms.options.submit()">
<option value="start">Start</option>
<option value="end">End</option>
<option value="top">Top</option>
<option value="bottom">Bottom</option>
</select>
<select name="locale" onchange="setLocale(this.value)">
<option value="en">English</option>
<option value="ca">Catalan</option>
<option value="cs">Czech</option>
<option value="da">Danish</option>
<option value="nl">Dutch</option>
<option value="fi">Finnish</option>
<option value="fr">French</option>
<option value="de">German</option>
<option value="el">Greek</option>
<option value="he">Hebrew</option>
<option value="hu">Hungarian</option>
<option value="id">Indonesian</option>
<option value="ga">Irish Gaelic</option>
<option value="it">Italian</option>
<option value="ja">Japanese</option>
<option value="ja-Hira">Japanese(Hira)</option>
<option value="mi">Maori</option>
<option value="nb">Norwegian (Bokmal)</option>
<option value="pt">Portuguese</option>
<option value="pt-br">Portuguese (Brazil)</option>
<option value="gd">Scottish Gaelic</option>
<option value="sr">Serbian</option>
<option value="sl">Slovenian</option>
<option value="es">Spanish</option>
<option value="es-419">Spanish (Latin America)</option>
<option value="sv">Swedish</option>
<option value="tr">Turkish</option>
<option value="uk">Ukranian</option>
<option value="vi">Vietnamese</option>
<option value="cy">Welsh</option>
<option value="zh-cn">Chinese (China)</option>
<option value="zh-tw">Chinese (TW)</option>
</select>
</form>
<p>
<input type="button" value="Export to XML" onclick="toXml()">
&nbsp;
<input type="button" value="Import from XML" onclick="fromXml()" id="import">
<br>
<textarea id="importExport" style="width: 26%; height: 12em"
onchange="taChange();" onkeyup="taChange()"></textarea>
</p>
<hr>
<p>
Log events: &nbsp;
<input type="checkbox" onclick="logEvents(this.checked)" id="logCheck">
</p>
<p>
Stress test: &nbsp;
<input type="button" value="Sprinkles!" onclick="sprinkles(100)">
<input type="button" value="Spaghetti!" onclick="spaghetti(3)">
</p>
<p>
Glows: &nbsp;
<input type="button" value="Glow last clicked block" onclick="glowBlock()" />
<input type="button" value="Unglow last clicked block" onclick="unglowBlock()" />
<input type="button" value="Stack glow last clicked block" onclick="glowStack()" />
<input type="button" value="Stack unglow last clicked block" onclick="unglowStack()" />
</p>
<p>
Log flyout events: &nbsp;
<input type="checkbox" onclick="logFlyoutEvents(this.checked)" id="logFlyoutCheck">
</p>
<p>
Enable sounds (after refresh): &nbsp;
<input type="checkbox" onclick="setSoundsEnabled(this.checked)" id="soundsEnabled">
</p>
<p>
<input type="button" value="Undo" onclick="workspace.undo()" />
<input type="button" value="Redo" onclick="workspace.undo(true)" />
</p>
<p>
Report:
<input id="reportValue" type="text" value="123" />
<input type="button" value="Report last clicked block" onclick="reportDemo()" />
</p>
</body>
</html>

View File

@@ -0,0 +1,726 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Compressed Vertical Playground</title>
<script src="../blockly_compressed_vertical.js"></script>
<script src="../msg/messages.js"></script>
<script src="../msg/scratch_msgs.js"></script>
<script src="../blocks_compressed.js"></script>
<script src="../blocks_compressed_vertical.js"></script>
<script>
'use strict';
window.ScratchBlocks = Blockly;
var fakeDragStack = [];
var workspace = null;
function start() {
var soundsEnabled = null;
if (sessionStorage) {
// Restore sounds state.
soundsEnabled = sessionStorage.getItem('soundsEnabled');
if (soundsEnabled === null) {
soundsEnabled = true;
} else {
soundsEnabled = (soundsEnabled === 'true');
}
} else {
soundsEnabled = true;
}
setSoundsEnabled(soundsEnabled);
// Setup blocks
// Parse the URL arguments.
var match = location.search.match(/dir=([^&]+)/);
var rtl = match && match[1] == 'rtl';
document.forms.options.elements.dir.selectedIndex = Number(rtl);
var toolbox = getToolboxElement();
document.forms.options.elements.toolbox.selectedIndex =
toolbox ? 1: 0;
match = location.search.match(/side=([^&]+)/);
var side = match ? match[1] : 'start';
document.forms.options.elements.side.value = side;
match = location.search.match(/locale=([^&]+)/);
var locale = match ? match[1] : 'en';
Blockly.ScratchMsgs.setLocale(locale);
document.forms.options.elements.locale.value = locale;
// Create main workspace.
workspace = Blockly.inject('blocklyDiv', {
comments: true,
disable: false,
collapse: false,
media: '../media/',
readOnly: false,
rtl: rtl,
scrollbars: true,
toolbox: toolbox,
toolboxPosition: side == 'top' || side == 'start' ? 'start' : 'end',
horizontalLayout: side == 'top' || side == 'bottom',
trashcan: true,
sounds: soundsEnabled,
zoom: {
controls: true,
wheel: true,
startScale: 0.75,
maxScale: 4,
minScale: 0.25,
scaleSpeed: 1.1
},
colours: {
fieldShadow: 'rgba(255, 255, 255, 0.3)',
dragShadowOpacity: 0.6
}
});
// Restore previously displayed text.
var text = sessionStorage.getItem('textarea');
if (text) {
document.getElementById('importExport').value = text;
}
taChange();
if (sessionStorage) {
// Restore event logging state.
var state = sessionStorage.getItem('logEvents');
logEvents(Boolean(state));
// Restore flyout event logging state.
state = sessionStorage.getItem('logFlyoutEvents');
logFlyoutEvents(Boolean(state));
}
}
function getToolboxElement() {
var match = location.search.match(/toolbox=([^&]+)/);
return document.getElementById('toolbox-' + (match ? match[1] : 'categories'));
}
function toXml() {
var output = document.getElementById('importExport');
var xml = Blockly.Xml.workspaceToDom(workspace);
output.value = Blockly.Xml.domToPrettyText(xml);
output.focus();
output.select();
taChange();
}
function fromXml() {
var input = document.getElementById('importExport');
var xml = Blockly.Xml.textToDom(input.value);
Blockly.Xml.domToWorkspace(xml, workspace);
taChange();
}
function toCode(lang) {
var output = document.getElementById('importExport');
output.value = Blockly[lang].workspaceToCode(workspace);
taChange();
}
// Disable the "Import from XML" button if the XML is invalid.
// Preserve text between page reloads.
function taChange() {
var textarea = document.getElementById('importExport');
if (sessionStorage) {
sessionStorage.setItem('textarea', textarea.value);
}
var valid = true;
try {
Blockly.Xml.textToDom(textarea.value);
} catch (e) {
valid = false;
}
document.getElementById('import').disabled = !valid;
}
function logEvents(state) {
var checkbox = document.getElementById('logCheck');
checkbox.checked = state;
if (sessionStorage) {
sessionStorage.setItem('logEvents', state ? 'checked' : '');
}
if (state) {
workspace.addChangeListener(logger);
} else {
workspace.removeChangeListener(logger);
}
}
function logFlyoutEvents(state) {
var checkbox = document.getElementById('logFlyoutCheck');
checkbox.checked = state;
if (sessionStorage) {
sessionStorage.setItem('logFlyoutEvents', state ? 'checked' : '');
}
var flyoutWorkspace = (workspace.flyout_) ? workspace.flyout_.workspace_ :
workspace.toolbox_.flyout_.workspace_;
if (state) {
flyoutWorkspace.addChangeListener(logger);
} else {
flyoutWorkspace.removeChangeListener(logger);
}
}
function logger(e) {
console.log(e);
}
function glowBlock() {
if (Blockly.selected) {
workspace.glowBlock(Blockly.selected.id, true);
}
}
function unglowBlock() {
if (Blockly.selected) {
workspace.glowBlock(Blockly.selected.id, false);
}
}
function glowStack() {
if (Blockly.selected) {
workspace.glowStack(Blockly.selected.id, true);
}
}
function unglowStack() {
if (Blockly.selected) {
workspace.glowStack(Blockly.selected.id, false);
}
}
function sprinkles(n) {
var prototypes = [];
var toolbox = workspace.options.languageTree;
if (!toolbox) {
console.error('Toolbox not found; add a toolbox element to the DOM.');
return;
}
var blocks = toolbox.getElementsByTagName('block');
for (var i = 0; i < n; i++) {
var blockXML = blocks[Math.floor(Math.random() * blocks.length)];
var block = Blockly.Xml.domToBlock(blockXML, workspace);
block.initSvg();
block.moveBy(
Math.round(Math.random() * 450 + 40),
Math.round(Math.random() * 600 + 40)
);
}
}
var equalsXml = [
' <shadow type="operator_equals">',
' <value name="OPERAND1">',
' <shadow type="text">',
' <field name="TEXT">foo</field>',
' </shadow>',
' </value>',
' <value name="OPERAND2">',
' <shadow type="operator_equals"></shadow>',
' </value>',
' </shadow>'
].join('\n');
var spaghettiXml = [
' <block type="control_if_else">',
' <value name="CONDITION">',
' <shadow type="operator_equals"></shadow>',
' </value>',
' <statement name="SUBSTACK"></statement>',
' <statement name="SUBSTACK2"></statement>',
' <next></next>',
' </block>'
].join('\n');
function spaghetti(n) {
console.log("Starting spaghetti. This may take some time...");
var xml = spaghettiXml;
// Nest if/else statements deeply.
for(var i = 0; i < 2 * n; i++) {
xml = xml.replace(/(<statement name="SUBSTACK2?"?>)<\//g,
'$1' + spaghettiXml + '</');
}
// Stack a bit.
for(var i = 0; i < n; i++) {
xml = xml.replace(/(<next>)<\//g,
'$1' + spaghettiXml + '</');
}
// Nest boolean comparisons.
var equalsBlock = equalsXml;
for (var i = 0; i < n; i++) {
equalsBlock = equalsBlock.replace(
/(<shadow( type="operator_equals")?>)<\/shadow>/g, equalsXml);
}
// Put the nested boolean comparisons into if/else statements.
xml = xml.replace(/(<shadow( type="operator_equals")?>)<\/shadow>/g,
equalsBlock);
xml = '<xml xmlns="http://www.w3.org/1999/xhtml">' + xml + '</xml>';
var dom = Blockly.Xml.textToDom(xml);
console.time('Spaghetti domToWorkspace');
Blockly.Xml.domToWorkspace(dom, workspace);
console.timeEnd('Spaghetti domToWorkspace');
}
function setSoundsEnabled(state) {
var checkbox = document.getElementById('soundsEnabled');
checkbox.checked = (state) ? 'checked' : '';
if (sessionStorage) {
sessionStorage.setItem('soundsEnabled', state);
}
}
function fakeDrag(id, dx, dy, opt_workspace) {
var ws = opt_workspace || Blockly.getMainWorkspace();
var blockToDrag = ws.getBlockById(id);
if (!blockToDrag) {
fakeDragWrapper();
return;
}
var blockTop = blockToDrag.svgGroup_.getBoundingClientRect().top;
var blockLeft = blockToDrag.svgGroup_.getBoundingClientRect().left;
// Click somewhere on the block.
var mouseDownEvent = new MouseEvent('mousedown',
{clientX: blockLeft + 5, clientY: blockTop + 5});
blockToDrag.onMouseDown_(mouseDownEvent);
// Throw in a move for good measure.
setTimeout(
function() {
var mouseMoveEvent = new MouseEvent('mousemove',
{clientX: blockLeft + dx,
clientY: blockTop + dy});
blockToDrag.onMouseMove_(mouseMoveEvent);
// Drop at dx, dy.
setTimeout(
function() {
var mouseUpEvent = new MouseEvent('mouseup',
{clientX: blockLeft + dx,
clientY: blockTop + dy});
blockToDrag.onMouseUp_(mouseUpEvent);
setTimeout(fakeDragWrapper(), 100);
}, 30);
}, 30);
};
function fakeDragWrapper() {
var dragInfo = fakeDragStack.pop();
if (dragInfo) {
fakeDrag(dragInfo.id, dragInfo.dx, dragInfo.dy, dragInfo.workspace);
}
}
function fakeManyDrags() {
var blockList = workspace.getAllBlocks();
for (var i = 0; i < 2 * blockList.length; i++) {
fakeDragStack.push(
{
id: blockList[Math.round(Math.random() * (blockList.length - 1))].id,
// Move some blocks up and to the left, but mostly down and to the right.
dx: Math.round((Math.random() - 0.25) * 200),
dy: Math.round((Math.random() - 0.25) * 200),
workspace: workspace
});
}
fakeDragWrapper();
}
function reportDemo() {
if (Blockly.selected) {
workspace.reportValue(
Blockly.selected.id,
document.getElementById('reportValue').value
);
}
}
function setLocale(locale) {
workspace.getFlyout().setRecyclingEnabled(false);
var xml = Blockly.Xml.workspaceToDom(workspace);
Blockly.ScratchMsgs.setLocale(locale);
Blockly.Xml.clearWorkspaceAndLoadFromXml(xml, workspace);
workspace.getFlyout().setRecyclingEnabled(true);
}
</script>
<style>
html, body {
height: 100%;
}
body {
background-color: #fff;
font-family: sans-serif;
overflow: hidden;
}
h1 {
font-weight: normal;
font-size: 140%;
}
#blocklyDiv {
float: right;
height: 95%;
width: 70%;
}
#collaborators {
float: right;
width: 30px;
margin-left: 10px;
}
#collaborators > img {
margin-right: 5px;
height: 30px;
padding-bottom: 5px;
width: 30px;
border-radius: 3px;
}
#importExport {
font-family: monospace;
}
</style>
</head>
<body onload="start()">
<div id="collaborators"></div>
<div id="blocklyDiv"></div>
<!-- Toolbox -->
<xml id="toolbox-simple" style="display: none">
<block type="event_whenflagclicked"></block>
<block type="event_whenbroadcastreceived">
</block>
<block type="event_broadcast">
<value name="BROADCAST_OPTION">
<shadow type="event_broadcast_menu"></shadow>
</value>
</block>
<block type="event_broadcastandwait">
<value name="BROADCAST_OPTION">
<shadow type="event_broadcast_menu"></shadow>
</value>
</block>
<block type="control_wait">
<value name="DURATION">
<shadow type="math_positive_number">
<field name="NUM">1</field>
</shadow>
</value>
</block>
<block type="control_repeat">
<value name="TIMES">
<shadow type="math_whole_number">
<field name="NUM">10</field>
</shadow>
</value>
</block>
<block type="control_forever"></block>
<block type="control_if"></block>
<block type="control_if_else"></block>
<block type="control_wait_until"></block>
<block type="control_repeat_until"></block>
<block type="control_stop">
<value name="STOP_OPTION">
<shadow type="control_stop_menu"></shadow>
</value>
</block>
<block type="control_start_as_clone"></block>
<block type="control_create_clone_of">
<value name="CLONE_OPTION">
<shadow type="control_create_clone_of_menu"></shadow>
</value>
</block>
<block type="control_delete_this_clone"></block>
<block type="operator_add">
<value name="NUM1">
<shadow type="math_number">
<field name="NUM"></field>
</shadow>
</value>
<value name="NUM2">
<shadow type="math_number">
<field name="NUM"></field>
</shadow>
</value>
</block>
<block type="operator_subtract">
<value name="NUM1">
<shadow type="math_number">
<field name="NUM"></field>
</shadow>
</value>
<value name="NUM2">
<shadow type="math_number">
<field name="NUM"></field>
</shadow>
</value>
</block>
<block type="operator_multiply">
<value name="NUM1">
<shadow type="math_number">
<field name="NUM"></field>
</shadow>
</value>
<value name="NUM2">
<shadow type="math_number">
<field name="NUM"></field>
</shadow>
</value>
</block>
<block type="operator_divide">
<value name="NUM1">
<shadow type="math_number">
<field name="NUM"></field>
</shadow>
</value>
<value name="NUM2">
<shadow type="math_number">
<field name="NUM"></field>
</shadow>
</value>
</block>
<block type="operator_random">
<value name="FROM">
<shadow type="math_number">
<field name="NUM">1</field>
</shadow>
</value>
<value name="TO">
<shadow type="math_number">
<field name="NUM">10</field>
</shadow>
</value>
</block>
<block type="operator_lt">
<value name="OPERAND1">
<shadow type="text">
<field name="TEXT"></field>
</shadow>
</value>
<value name="OPERAND2">
<shadow type="text">
<field name="TEXT"></field>
</shadow>
</value>
</block>
<block type="operator_equals">
<value name="OPERAND1">
<shadow type="text">
<field name="TEXT"></field>
</shadow>
</value>
<value name="OPERAND2">
<shadow type="text">
<field name="TEXT"></field>
</shadow>
</value>
</block>
<block type="operator_gt">
<value name="OPERAND1">
<shadow type="text">
<field name="TEXT"></field>
</shadow>
</value>
<value name="OPERAND2">
<shadow type="text">
<field name="TEXT"></field>
</shadow>
</value>
</block>
<block type="operator_and"></block>
<block type="operator_or"></block>
<block type="operator_not"></block>
<block type="operator_join">
<value name="STRING1">
<shadow type="text">
<field name="TEXT">hello</field>
</shadow>
</value>
<value name="STRING2">
<shadow type="text">
<field name="TEXT">world</field>
</shadow>
</value>
</block>
<block type="operator_letter_of">
<value name="LETTER">
<shadow type="math_whole_number">
<field name="NUM">1</field>
</shadow>
</value>
<value name="STRING">
<shadow type="text">
<field name="TEXT">world</field>
</shadow>
</value>
</block>
<block type="operator_length">
<value name="STRING">
<shadow type="text">
<field name="TEXT">world</field>
</shadow>
</value>
</block>
<block type="operator_mod">
<value name="NUM1">
<shadow type="math_number">
<field name="NUM"></field>
</shadow>
</value>
<value name="NUM2">
<shadow type="math_number">
<field name="NUM"></field>
</shadow>
</value>
</block>
<block type="operator_round">
<value name="NUM">
<shadow type="math_number">
<field name="NUM"></field>
</shadow>
</value>
</block>
<block type="operator_mathop">
<value name="OPERATOR">
<shadow type="operator_mathop_menu"></shadow>
</value>
<value name="NUM">
<shadow type="math_number">
<field name="NUM"></field>
</shadow>
</value>
</block>
</xml>
<!-- Sidebar -->
<h1>Vertical Blocks</h1>
<p>
<a href="javascript:void(workspace.setVisible(true))">Show</a>
- <a href="javascript:void(workspace.setVisible(false))">Hide</a>
</p>
<form id="options">
<select name="dir" onchange="document.forms.options.submit()">
<option value="ltr">LTR</option>
<option value="rtl">RTL</option>
</select>
<select name="toolbox" onchange="document.forms.options.submit()">
<option value="categories">Categories</option>
<option value="simple">Simple</option>
</select>
<select name="side" onchange="document.forms.options.submit()">
<option value="start">Start</option>
<option value="end">End</option>
<option value="top">Top</option>
<option value="bottom">Bottom</option>
</select>
<select name="locale" onchange="setLocale(this.value)">
<option value="en">English</option>
<option value="ca">Catalan</option>
<option value="cs">Czech</option>
<option value="da">Danish</option>
<option value="nl">Dutch</option>
<option value="fi">Finnish</option>
<option value="fr">French</option>
<option value="de">German</option>
<option value="el">Greek</option>
<option value="he">Hebrew</option>
<option value="hu">Hungarian</option>
<option value="id">Indonesian</option>
<option value="ga">Irish Gaelic</option>
<option value="it">Italian</option>
<option value="ja">Japanese</option>
<option value="ja-Hira">Japanese(Hira)</option>
<option value="mi">Maori</option>
<option value="nb">Norwegian (Bokmal)</option>
<option value="pt">Portuguese</option>
<option value="pt-br">Portuguese (Brazil)</option>
<option value="gd">Scottish Gaelic</option>
<option value="sr">Serbian</option>
<option value="sl">Slovenian</option>
<option value="es">Spanish</option>
<option value="es-419">Spanish (Latin America)</option>
<option value="sv">Swedish</option>
<option value="tr">Turkish</option>
<option value="uk">Ukranian</option>
<option value="vi">Vietnamese</option>
<option value="cy">Welsh</option>
<option value="zh-cn">Chinese (China)</option>
<option value="zh-tw">Chinese (TW)</option>
</select>
</form>
<p>
<input type="button" value="Export to XML" onclick="toXml()">
&nbsp;
<input type="button" value="Import from XML" onclick="fromXml()" id="import">
<br>
<input type="button" value="To JavaScript" onclick="toCode('JavaScript')">
<br>
<textarea id="importExport" style="width: 26%; height: 12em"
onchange="taChange();" onkeyup="taChange()"></textarea>
</p>
<hr>
<p>
Log events: &nbsp;
<input type="checkbox" onclick="logEvents(this.checked)" id="logCheck">
</p>
<p>
Stress test: &nbsp;
<input type="button" value="Sprinkles!" onclick="sprinkles(100)">
<input type="button" value="Spaghetti!" onclick="spaghetti(3)">
<input type="button" value="Fake some drags!" onclick="fakeManyDrags()">
</p>
<p>
Glows: &nbsp;
<input type="button" value="Glow last clicked block" onclick="glowBlock()" />
<input type="button" value="Unglow last clicked block" onclick="unglowBlock()" />
<input type="button" value="Stack glow last clicked block" onclick="glowStack()" />
<input type="button" value="Stack unglow last clicked block" onclick="unglowStack()" />
</p>
<p>
Log flyout events: &nbsp;
<input type="checkbox" onclick="logFlyoutEvents(this.checked)" id="logFlyoutCheck">
</p>
<p>
Enable sounds (after refresh): &nbsp;
<input type="checkbox" onclick="setSoundsEnabled(this.checked)" id="soundsEnabled">
</p>
<p>
<input type="button" value="Undo" onclick="workspace.undo()" />
<input type="button" value="Redo" onclick="workspace.undo(true)" />
</p>
<p>
Report:
<input id="reportValue" type="text" value="123" />
<input type="button" value="Report last clicked block" onclick="reportDemo()" />
</p>
</body>
</html>

View File

@@ -0,0 +1,75 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Blockly Workspace SVG testing</title>
<script src="../../blockly_uncompressed_vertical.js"></script>
<script src="../../blocks_vertical/vertical_extensions.js"></script>
<script src="../../blocks_vertical/looks.js"></script>
<script>goog.require('goog.testing.jsunit');</script>
<script src="../../msg/messages.js"></script>
<script>
'use strict';
</script>
<style>
html, body {
height: 100%;
}
body {
background-color: #fff;
font-family: sans-serif;
overflow: hidden;
}
h1 {
font-weight: normal;
font-size: 140%;
}
#blocklyDiv {
float: right;
height: 95%;
width: 70%;
}
#importExport {
font-family: monospace;
}
.ioLabel>.blocklyFlyoutLabelText {
font-style: italic;
}
</style>
</head>
<body>
<div id="blocklyDiv"></div>
<h1>Blockly Workspace testing</h1>
<script src="../jsunit/test_utilities.js"></script>
<script src="workspace_svg_test.js"></script>
<xml id="toolbox-categories" style="display: none">
<category name="Looks" colour="#9966FF" secondaryColour="#774DCB">
<block type="looks_show" id="lABCD"></block>
<block type="looks_hide" id="looks_hide"></block>
<block type="looks_switchcostumeto" id="looks_switchcostumeto">
<value name="COSTUME">
<shadow type="looks_costume"></shadow>
</value>
</block>
<block type="looks_nextcostume" id="looks_nextcostume"></block>
<block type="looks_nextbackdrop" id="looks_nextbackdrop"></block>
<block type="looks_switchbackdropto" id="looks_switchbackdropto">
<value name="BACKDROP">
<shadow type="looks_backdrops"></shadow>
</value>
</block>
<sep></sep>
<category name="Variables" colour="330" custom="VARIABLE"></category>
<category name="Functions" colour="290" custom="PROCEDURE"></category>
</xml>
</body>
</html>

View File

@@ -0,0 +1,135 @@
/**
* @license
* Blockly Tests
*
* Copyright 2017 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
goog.require('goog.testing');
goog.require('goog.testing.MockControl');
function helper_createWorkspaceWithToolbox() {
var toolbox = document.getElementById('toolbox-categories');
return Blockly.inject('blocklyDiv', {toolbox: toolbox});
}
function test_createWorkspace() {
var workspace = helper_createWorkspaceWithToolbox();
workspace.dispose();
}
function test_emptyWorkspace() {
var workspace = helper_createWorkspaceWithToolbox();
try {
assertEquals('Empty workspace (1).', 0, workspace.getTopBlocks(true).length);
assertEquals('Empty workspace (2).', 0, workspace.getTopBlocks(false).length);
assertEquals('Empty workspace (3).', 0, workspace.getAllBlocks().length);
workspace.clear();
assertEquals('Empty workspace (4).', 0, workspace.getTopBlocks(true).length);
assertEquals('Empty workspace (5).', 0, workspace.getTopBlocks(false).length);
assertEquals('Empty workspace (6).', 0, workspace.getAllBlocks().length);
} finally {
workspace.dispose();
}
}
function test_flatWorkspace() {
var workspace = helper_createWorkspaceWithToolbox();
var blockA, blockB;
try {
blockA = workspace.newBlock('');
assertEquals('One block workspace (1).', 1, workspace.getTopBlocks(true).length);
assertEquals('One block workspace (2).', 1, workspace.getTopBlocks(false).length);
assertEquals('One block workspace (3).', 1, workspace.getAllBlocks().length);
blockB = workspace.newBlock('');
assertEquals('Two block workspace (1).', 2, workspace.getTopBlocks(true).length);
assertEquals('Two block workspace (2).', 2, workspace.getTopBlocks(false).length);
assertEquals('Two block workspace (3).', 2, workspace.getAllBlocks().length);
blockA.dispose();
assertEquals('One block workspace (4).', 1, workspace.getTopBlocks(true).length);
assertEquals('One block workspace (5).', 1, workspace.getTopBlocks(false).length);
assertEquals('One block workspace (6).', 1, workspace.getAllBlocks().length);
workspace.clear();
assertEquals('Cleared workspace (1).', 0, workspace.getTopBlocks(true).length);
assertEquals('Cleared workspace (2).', 0, workspace.getTopBlocks(false).length);
assertEquals('Cleared workspace (3).', 0, workspace.getAllBlocks().length);
} finally {
blockB && blockB.dispose();
blockA && blockA.dispose();
workspace.dispose();
}
}
/** Tests the alignment of appendDomToWorkspace with WorkspaceSvg. */
function test_appendDomToWorkspace() {
var workspace = helper_createWorkspaceWithToolbox();
try {
var dom = Blockly.Xml.textToDom(
'<xml xmlns="http://www.w3.org/1999/xhtml">' +
' <block type="looks_show" inline="true" x="21" y="23">' +
' </block>' +
'</xml>');
Blockly.Xml.appendDomToWorkspace(dom, workspace);
assertEquals('Block count', 1, workspace.getAllBlocks().length);
Blockly.Xml.appendDomToWorkspace(dom, workspace);
assertEquals('Block count', 2, workspace.getAllBlocks().length);
var blocks = workspace.getAllBlocks();
assertEquals('Block 1 position x',21,blocks[0].getRelativeToSurfaceXY().x);
assertEquals('Block 1 position y',23,blocks[0].getRelativeToSurfaceXY().y);
assertEquals('Block 2 position x',21,blocks[1].getRelativeToSurfaceXY().x);
assertEquals('Block 2 position y',23 + blocks[0].getHeightWidth().height + Blockly.BlockSvg.SEP_SPACE_Y,blocks[1].getRelativeToSurfaceXY().y);
} finally {
workspace.dispose();
}
}
function test_addNewVariableRefreshToolbox() {
var workspace = helper_createWorkspaceWithToolbox();
var mockControl = new goog.testing.MockControl();
var mockMethod = mockControl.createMethodMock(Blockly.WorkspaceSvg.prototype,
'refreshToolboxSelection_');
try {
mockMethod().$once();
mockControl.$replayAll();
workspace.createVariable('name1', 'type1', 'id1');
mockControl.$verifyAll();
} finally {
workspace.dispose();
}
}
function test_addDuplicateVariableDoNotRefreshToolbox() {
var workspace = helper_createWorkspaceWithToolbox();
var mockControl = new goog.testing.MockControl();
var mockMethod = mockControl.createMethodMock(Blockly.WorkspaceSvg.prototype,
'refreshToolboxSelection_');
try {
mockMethod().$once();
mockControl.$replayAll();
// Create same variable twice. The first should refresh the toolbox,
// the second should not.
workspace.createVariable('name1', 'type1', 'id1');
workspace.createVariable('name1', 'type1', 'id1');
mockControl.$verifyAll();
} finally {
workspace.dispose();
}
}