diff --git a/.eslintrc b/.eslintrc index 9530b7666..8385c0b7b 100644 --- a/.eslintrc +++ b/.eslintrc @@ -24,10 +24,28 @@ "ignoredNodes": ["ConditionalExpression"] } ], + "keyword-spacing": ["error"], "linebreak-style": ["error", "unix"], - "max-len": ["error", 120, 4], + "max-len": [ + "error", + { + "code": 100, + "tabWidth": 4, + "ignoreStrings": true, + "ignoreRegExpLiterals": true + } + ], "no-trailing-spaces": ["error", { "skipBlankLines": true }], - "no-unused-vars": ["error", {"args": "after-used", "varsIgnorePattern": "^_"}], + "no-unused-vars": [ + "error", + { + "args": "after-used", + # Ignore vars starting with an underscore. + "varsIgnorePattern": "^_", + # Ignore arguments starting with an underscore. + "argsIgnorePattern": "^_" + } + ], "no-use-before-define": ["error"], "quotes": ["off"], # Blockly uses single quotes except for JSON blobs, which must use double quotes. "semi": ["error", "always"], diff --git a/.gitignore b/.gitignore index 0dce6535c..642605678 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ npm-debug.log tests/compile/main_compressed.js tests/compile/*compiler*.jar +local_build/*compiler*.jar +local_build/local_blockly_compressed.js diff --git a/appengine/storage.js b/appengine/storage.js index 8141806d5..4132472ae 100644 --- a/appengine/storage.js +++ b/appengine/storage.js @@ -70,7 +70,16 @@ BlocklyStorage.restoreBlocks = function(opt_workspace) { */ BlocklyStorage.link = function(opt_workspace) { var workspace = opt_workspace || Blockly.getMainWorkspace(); - var xml = Blockly.Xml.workspaceToDom(workspace); + var xml = Blockly.Xml.workspaceToDom(workspace, true); + // Remove x/y coordinates from XML if there's only one block stack. + // There's no reason to store this, removing it helps with anonymity. + if (workspace.getTopBlocks(false).length == 1 && xml.querySelector) { + var block = xml.querySelector('block'); + if (block) { + block.removeAttribute('x'); + block.removeAttribute('y'); + } + } var data = Blockly.Xml.domToText(xml); BlocklyStorage.makeRequest_('/storage', 'xml', data, workspace); }; diff --git a/blockly_accessible_compressed.js b/blockly_accessible_compressed.js index 135a8e64d..ec0572566 100644 --- a/blockly_accessible_compressed.js +++ b/blockly_accessible_compressed.js @@ -2,27 +2,30 @@ 'use strict'; var $jscomp=$jscomp||{};$jscomp.scope={};var COMPILED=!0,goog=goog||{};goog.global=this;goog.isDef=function(a){return void 0!==a};goog.isString=function(a){return"string"==typeof a};goog.isBoolean=function(a){return"boolean"==typeof a};goog.isNumber=function(a){return"number"==typeof a}; -goog.exportPath_=function(a,b,c){a=a.split(".");c=c||goog.global;a[0]in c||!c.execScript||c.execScript("var "+a[0]);for(var d;a.length&&(d=a.shift());)!a.length&&goog.isDef(b)?c[d]=b:c=c[d]&&c[d]!==Object.prototype[d]?c[d]:c[d]={}}; +goog.exportPath_=function(a,b,c){a=a.split(".");c=c||goog.global;a[0]in c||"undefined"==typeof c.execScript||c.execScript("var "+a[0]);for(var d;a.length&&(d=a.shift());)!a.length&&goog.isDef(b)?c[d]=b:c=c[d]&&c[d]!==Object.prototype[d]?c[d]:c[d]={}}; goog.define=function(a,b){if(!COMPILED){var c=goog.global.CLOSURE_UNCOMPILED_DEFINES,d=goog.global.CLOSURE_DEFINES;c&&void 0===c.nodeType&&Object.prototype.hasOwnProperty.call(c,a)?b=c[a]:d&&void 0===d.nodeType&&Object.prototype.hasOwnProperty.call(d,a)&&(b=d[a])}goog.exportPath_(a,b)};goog.DEBUG=!1;goog.LOCALE="en";goog.TRUSTED_SITE=!0;goog.STRICT_MODE_COMPATIBLE=!1;goog.DISALLOW_TEST_ONLY_CODE=COMPILED&&!goog.DEBUG;goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING=!1; -goog.provide=function(a){if(goog.isInModuleLoader_())throw Error("goog.provide can not be used within a goog.module.");if(!COMPILED&&goog.isProvided_(a))throw Error('Namespace "'+a+'" already declared.');goog.constructNamespace_(a)};goog.constructNamespace_=function(a,b){if(!COMPILED){delete goog.implicitNamespaces_[a];for(var c=a;(c=c.substring(0,c.lastIndexOf(".")))&&!goog.getObjectByName(c);)goog.implicitNamespaces_[c]=!0}goog.exportPath_(a,b)};goog.VALID_MODULE_RE_=/^[a-zA-Z_$][a-zA-Z0-9._$]*$/; -goog.module=function(a){if(!goog.isString(a)||!a||-1==a.search(goog.VALID_MODULE_RE_))throw Error("Invalid module identifier");if(!goog.isInModuleLoader_())throw Error("Module "+a+" has been loaded incorrectly. Note, modules cannot be loaded as normal scripts. They require some kind of pre-processing step. You're likely trying to load a module via a script tag or as a part of a concatenated bundle without rewriting the module. For more info see: https://github.com/google/closure-library/wiki/goog.module:-an-ES6-module-like-alternative-to-goog.provide."); -if(goog.moduleLoaderState_.moduleName)throw Error("goog.module may only be called once per module.");goog.moduleLoaderState_.moduleName=a;if(!COMPILED){if(goog.isProvided_(a))throw Error('Namespace "'+a+'" already declared.');delete goog.implicitNamespaces_[a]}};goog.module.get=function(a){return goog.module.getInternal_(a)}; -goog.module.getInternal_=function(a){if(!COMPILED){if(a in goog.loadedModules_)return goog.loadedModules_[a];if(!goog.implicitNamespaces_[a])return a=goog.getObjectByName(a),null!=a?a:null}return null};goog.moduleLoaderState_=null;goog.isInModuleLoader_=function(){return null!=goog.moduleLoaderState_}; -goog.module.declareLegacyNamespace=function(){if(!COMPILED&&!goog.isInModuleLoader_())throw Error("goog.module.declareLegacyNamespace must be called from within a goog.module");if(!COMPILED&&!goog.moduleLoaderState_.moduleName)throw Error("goog.module must be called prior to goog.module.declareLegacyNamespace.");goog.moduleLoaderState_.declareLegacyNamespace=!0}; +goog.provide=function(a){if(goog.isInModuleLoader_())throw Error("goog.provide can not be used within a module.");if(!COMPILED&&goog.isProvided_(a))throw Error('Namespace "'+a+'" already declared.');goog.constructNamespace_(a)};goog.constructNamespace_=function(a,b){if(!COMPILED){delete goog.implicitNamespaces_[a];for(var c=a;(c=c.substring(0,c.lastIndexOf(".")))&&!goog.getObjectByName(c);)goog.implicitNamespaces_[c]=!0}goog.exportPath_(a,b)};goog.VALID_MODULE_RE_=/^[a-zA-Z_$][a-zA-Z0-9._$]*$/; +goog.module=function(a){if(!goog.isString(a)||!a||-1==a.search(goog.VALID_MODULE_RE_))throw Error("Invalid module identifier");if(!goog.isInGoogModuleLoader_())throw Error("Module "+a+" has been loaded incorrectly. Note, modules cannot be loaded as normal scripts. They require some kind of pre-processing step. You're likely trying to load a module via a script tag or as a part of a concatenated bundle without rewriting the module. For more info see: https://github.com/google/closure-library/wiki/goog.module:-an-ES6-module-like-alternative-to-goog.provide."); +if(goog.moduleLoaderState_.moduleName)throw Error("goog.module may only be called once per module.");goog.moduleLoaderState_.moduleName=a;if(!COMPILED){if(goog.isProvided_(a))throw Error('Namespace "'+a+'" already declared.');delete goog.implicitNamespaces_[a]}}; +goog.module.get=function(a){if(!COMPILED&&a in goog.loadedModules_){if(goog.loadedModules_[a].type!=goog.ModuleType.GOOG)throw Error("Can only goog.module.get for goog.modules.");if(goog.loadedModules_[a].moduleId!=a)throw Error("Cannot goog.module.get by path.");}return goog.module.getInternal_(a)};goog.module.getInternal_=function(a){if(!COMPILED){if(a in goog.loadedModules_)return goog.loadedModules_[a].exports;if(!goog.implicitNamespaces_[a])return a=goog.getObjectByName(a),null!=a?a:null}return null}; +goog.ModuleType={ES6:"es6",GOOG:"goog"};goog.moduleLoaderState_=null;goog.isInModuleLoader_=function(){return goog.isInGoogModuleLoader_()||goog.isInEs6ModuleLoader_()};goog.isInGoogModuleLoader_=function(){return!!goog.moduleLoaderState_&&goog.moduleLoaderState_.type==goog.ModuleType.GOOG};goog.isInEs6ModuleLoader_=function(){return!!goog.moduleLoaderState_&&goog.moduleLoaderState_.type==goog.ModuleType.ES6};goog.getModulePath_=function(){return goog.moduleLoaderState_&&goog.moduleLoaderState_.path}; +goog.module.declareLegacyNamespace=function(){if(!COMPILED&&!goog.isInGoogModuleLoader_())throw Error("goog.module.declareLegacyNamespace must be called from within a goog.module");if(!COMPILED&&!goog.moduleLoaderState_.moduleName)throw Error("goog.module must be called prior to goog.module.declareLegacyNamespace.");goog.moduleLoaderState_.declareLegacyNamespace=!0}; goog.setTestOnly=function(a){if(goog.DISALLOW_TEST_ONLY_CODE)throw a=a||"",Error("Importing test-only code into non-debug environment"+(a?": "+a:"."));};goog.forwardDeclare=function(a){};COMPILED||(goog.isProvided_=function(a){return a in goog.loadedModules_||!goog.implicitNamespaces_[a]&&goog.isDefAndNotNull(goog.getObjectByName(a))},goog.implicitNamespaces_={"goog.module":!0}); -goog.getObjectByName=function(a,b){a=a.split(".");b=b||goog.global;for(var c=0;c>>0);goog.uidCounter_=0;goog.getHashCode=goog.getUid; -goog.removeHashCode=goog.removeUid;goog.cloneObject=function(a){var b=goog.typeOf(a);if("object"==b||"array"==b){if(a.clone)return a.clone();b="array"==b?[]:{};for(var c in a)b[c]=goog.cloneObject(a[c]);return b}return a};goog.bindNative_=function(a,b,c){return a.call.apply(a.bind,arguments)}; +goog.removeHashCode=goog.removeUid;goog.cloneObject=function(a){var b=goog.typeOf(a);if("object"==b||"array"==b){if("function"===typeof a.clone)return a.clone();b="array"==b?[]:{};for(var c in a)b[c]=goog.cloneObject(a[c]);return b}return a};goog.bindNative_=function(a,b,c){return a.call.apply(a.bind,arguments)}; goog.bindJs_=function(a,b,c){if(!a)throw Error();if(2Number(a[1])?!1:b('(()=>{"use strict";class X{constructor(){if(new.target!=String)throw 1;this.x=42}}let q=Reflect.construct(X,[],String);if(q.x!=42||!(q instanceof String))throw 1;for(const a of[2,3]){if(a==2)continue;function f(z={a}){let a=0;return z.a}{function f(){return 0;}}return f()==3}})()')});a("es6-impl",function(){return!0});a("es7",function(){return b("2 ** 2 == 4")});a("es8",function(){return b("async () => 1, true")});a("es_next",function(){return b("({...rest} = {}), true")}); -return c},goog.Transpiler.prototype.needsTranspile=function(a){if("always"==goog.TRANSPILE)return!0;if("never"==goog.TRANSPILE)return!1;this.requiresTranspilation_||(this.requiresTranspilation_=this.createRequiresTranspilation_());if(a in this.requiresTranspilation_)return this.requiresTranspilation_[a];throw Error("Unknown language mode: "+a);},goog.Transpiler.prototype.transpile=function(a,b){return goog.transpile_(a,b)},goog.transpiler_=new goog.Transpiler,goog.DebugLoader=function(){this.dependencies_= -{loadFlags:{},nameToPath:{},requires:{},visited:{},written:{},deferred:{}};this.oldIeWaiting_=!1;this.queuedModules_=[];this.lastNonModuleScriptIndex_=0},goog.DebugLoader.IS_OLD_IE_=!(goog.global.atob||!goog.global.document||!goog.global.document.all),goog.DebugLoader.prototype.earlyProcessLoad=function(a){goog.DebugLoader.IS_OLD_IE_&&this.maybeProcessDeferredDep_(a)},goog.DebugLoader.prototype.load=function(a){var b=this.getPathFromDeps_(a);if(b){var c=[],d={},e=this.dependencies_,f=this,g=function(a){if(!(a in -e.written||a in e.visited)){e.visited[a]=!0;if(a in e.requires)for(var b in e.requires[a])if(!f.isProvided(b))if(b in e.nameToPath)g(e.nameToPath[b]);else throw Error("Undefined nameToPath for "+b);a in d||(d[a]=!0,c.push(a))}};g(b);for(a=0;a\x3c/script>')},goog.DebugLoader.prototype.appendScriptSrcNode_=function(a){var b=goog.global.document,c=b.createElement("script");c.type="text/javascript";c.src=a;c.defer=!1;c.async=!1;b.head.appendChild(c)},goog.DebugLoader.prototype.writeScriptTag_=function(a,b){if(this.inHtmlDocument()){var c=goog.global.document; -if(!goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING&&"complete"==c.readyState){if(/\bdeps.js$/.test(a))return!1;throw Error('Cannot write "'+a+'" after document load');}void 0===b?goog.DebugLoader.IS_OLD_IE_?(this.oldIeWaiting_=!0,b=" onreadystatechange='goog.debugLoader_.onScriptLoad_(this, "+ ++this.lastNonModuleScriptIndex_+")' ",c.write(' + diff --git a/tests/jsunit/json_test.js b/tests/jsunit/json_test.js index d489022b2..6832e151d 100644 --- a/tests/jsunit/json_test.js +++ b/tests/jsunit/json_test.js @@ -26,12 +26,17 @@ function test_json_minimal() { var workspace = new Blockly.Workspace(); var block; try { - Blockly.defineBlocksWithJsonArray([{ - "type": BLOCK_TYPE - }]); + var warnings = captureWarnings(function() { + Blockly.defineBlocksWithJsonArray([{ + "type": BLOCK_TYPE + }]); + block = new Blockly.Block(workspace, BLOCK_TYPE); + }); - block = new Blockly.Block(workspace, BLOCK_TYPE); assertEquals(BLOCK_TYPE, block.type); + assertEquals( + 'Expecting no warnings when defining and creating a simple block.', + warnings.length, 0); // TODO: asserts } finally { block.dispose(); @@ -40,6 +45,40 @@ function test_json_minimal() { } } +/** + * Ensure a block without a type id fails with a warning, but does not block + * later definitions. + */ +function test_json_nullOrUndefinedBlockTypeId() { + var BLOCK_TYPE1 = 'test_json_before_bad_blocks'; + var BLOCK_TYPE2 = 'test_json_after_bad_blocks'; + + assertUndefined(Blockly.Blocks[BLOCK_TYPE1]); + assertUndefined(Blockly.Blocks[BLOCK_TYPE2]); + var blockTypeCount = Object.keys(Blockly.Blocks).length; + + try { + var warnings = captureWarnings(function() { + Blockly.defineBlocksWithJsonArray([ + {"type": BLOCK_TYPE1}, + {"type": undefined}, + {"type": null}, + {"type": BLOCK_TYPE2}]); + }); + + assertNotNullNorUndefined('Block before bad blocks should be defined.', + Blockly.Blocks[BLOCK_TYPE1]); + assertNotNullNorUndefined('Block after bad blocks should be defined.', + Blockly.Blocks[BLOCK_TYPE2]); + assertEquals(Object.keys(Blockly.Blocks).length, blockTypeCount + 2); + assertEquals( + 'Expecting 2 warnings, one for each bad block.', warnings.length, 2); + } finally { + delete Blockly.Blocks[BLOCK_TYPE1]; + delete Blockly.Blocks[BLOCK_TYPE2]; + } +} + /** Ensure message0 creates an input. */ function test_json_message0() { var BLOCK_TYPE = 'test_json_message0'; @@ -257,3 +296,75 @@ function test_json_dropdown_image() { delete Blockly.Msg['ALTTEXT']; } } + +function test_defineBlocksWithJsonArray_nullItem() { + var BLOCK_TYPE1 = 'test_block_before_null'; + var BLOCK_TYPE2 = 'test_block_after_null'; + + assertUndefined(Blockly.Blocks[BLOCK_TYPE1]); + assertUndefined(Blockly.Blocks[BLOCK_TYPE2]); + var blockTypeCount = Object.keys(Blockly.Blocks).length; + + try { + var warnings = captureWarnings(function() { + Blockly.defineBlocksWithJsonArray([ + { + "type": BLOCK_TYPE1, + "message0": 'before' + }, + null, + { + "type": BLOCK_TYPE2, + "message0": 'after' + }]); + }); + assertNotNullNorUndefined( + 'Block before null in array should be defined.', + Blockly.Blocks[BLOCK_TYPE1]); + assertNotNullNorUndefined( + 'Block after null in array should be defined.', + Blockly.Blocks[BLOCK_TYPE2]); + assertEquals(Object.keys(Blockly.Blocks).length, blockTypeCount + 2); + assertEquals('Expected 1 warning for the bad block.', warnings.length, 1); + } finally { + workspace.dispose(); + delete Blockly.Blocks[BLOCK_TYPE1]; + delete Blockly.Blocks[BLOCK_TYPE2]; + } +} + +function test_defineBlocksWithJsonArray_undefinedItem() { + var BLOCK_TYPE1 = 'test_block_before_undefined'; + var BLOCK_TYPE2 = 'test_block_after_undefined'; + + assertUndefined(Blockly.Blocks[BLOCK_TYPE1]); + assertUndefined(Blockly.Blocks[BLOCK_TYPE2]); + var blockTypeCount = Object.keys(Blockly.Blocks).length; + + try { + var warnings = captureWarnings(function() { + Blockly.defineBlocksWithJsonArray([ + { + "type": BLOCK_TYPE1, + "message0": 'before' + }, + undefined, + { + "type": BLOCK_TYPE2, + "message0": 'after' + }]); + }); + assertNotNullNorUndefined( + 'Block before undefined in array should be defined.', + Blockly.Blocks[BLOCK_TYPE1]); + assertNotNullNorUndefined( + 'Block after undefined in array should be defined.', + Blockly.Blocks[BLOCK_TYPE2]); + assertEquals(Object.keys(Blockly.Blocks).length, blockTypeCount + 2); + assertEquals('Expected 1 warning for the bad block.', warnings.length, 1); + } finally { + workspace.dispose(); + delete Blockly.Blocks[BLOCK_TYPE1]; + delete Blockly.Blocks[BLOCK_TYPE2]; + } +} diff --git a/tests/jsunit/metrics_test.js b/tests/jsunit/metrics_test.js new file mode 100644 index 000000000..30cd11af3 --- /dev/null +++ b/tests/jsunit/metrics_test.js @@ -0,0 +1,132 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2018 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 assertDimensionsMatch(toCheck, left, top, width, height) { + assertEquals('Top did not match.', top, toCheck.top); + assertEquals('Left did not match.', left, toCheck.left); + assertEquals('Width did not match.', width, toCheck.width); + assertEquals('Height did not match.', height, toCheck.height); +} + +/** + * Make a mock workspace object with two properties: getBlocksBoundingBox and + * scale. + */ +function makeMockWs(scale, x, y, width, height) { + return { + getBlocksBoundingBox: function() { + return { + width: width, + height: height, + x: x, + y: y + } + }, + scale: scale + }; +} + +// Empty workspace. +var test_GetContentDimensionsExact_empty = function() { + var ws = makeMockWs(1, 0, 0, 0, 0) + var defaultZoom = Blockly.WorkspaceSvg.getContentDimensionsExact_(ws); + assertDimensionsMatch(defaultZoom, 0, 0, 0, 0); +} + +var test_GetContentDimensionsExact_emptyZoomIn = function() { + var ws = makeMockWs(2, 0, 0, 0, 0) + var zoomIn = Blockly.WorkspaceSvg.getContentDimensionsExact_(ws); + assertDimensionsMatch(zoomIn, 0, 0, 0, 0); +} + +var test_GetContentDimensionsExact_emptyZoomOut = function() { + var ws = makeMockWs(.5, 0, 0, 0, 0) + var zoomOut = Blockly.WorkspaceSvg.getContentDimensionsExact_(ws); + assertDimensionsMatch(zoomOut, 0, 0, 0, 0); +} + +// Non-empty workspace, with top-left corner at ws origin. +var test_GetContentDimensionsExact_nonEmptyAtOrigin = function() { + var ws = makeMockWs(1, 0, 0, 100, 100) + var defaultZoom = Blockly.WorkspaceSvg.getContentDimensionsExact_(ws); + // Pixel and ws units are the same at default zoom. + assertDimensionsMatch(defaultZoom, 0, 0, 100, 100); +} + +var test_GetContentDimensionsExact_nonEmptyAtOriginZoomIn = function() { + var ws = makeMockWs(2, 0, 0, 100, 100) + var zoomIn = Blockly.WorkspaceSvg.getContentDimensionsExact_(ws); + // 1 ws unit = 2 pixels at this zoom level. + assertDimensionsMatch(zoomIn, 0, 0, 200, 200); +} + +var test_GetContentDimensionsExact_nonEmptyAtOriginZoomOut = function() { + var ws = makeMockWs(.5, 0, 0, 100, 100) + var zoomOut = Blockly.WorkspaceSvg.getContentDimensionsExact_(ws); + // 1 ws unit = 0.5 pixels at this zoom level. + assertDimensionsMatch(zoomOut, 0, 0, 50, 50); +} + +// Non-empty workspace, with top-left corner in positive ws coordinates. +var test_GetContentDimensionsExact_nonEmptyPositiveOrigin = function() { + var ws = makeMockWs(1, 10, 10, 100, 100) + var defaultZoom = Blockly.WorkspaceSvg.getContentDimensionsExact_(ws); + // Pixel and ws units are the same at default zoom. + assertDimensionsMatch(defaultZoom, 10, 10, 100, 100); +} + +// Changing zoom will change both width/height and origin location in pixels. +var test_GetContentDimensionsExact_nonEmptyPositiveOriginZoomIn = function() { + var ws = makeMockWs(2, 10, 10, 100, 100) + var zoomIn = Blockly.WorkspaceSvg.getContentDimensionsExact_(ws); + // 1 ws unit = 2 pixels at this zoom level. + assertDimensionsMatch(zoomIn, 20, 20, 200, 200); +} + +var test_GetContentDimensionsExact_nonEmptyPositiveOriginZoomOut = function() { + var ws = makeMockWs(.5, 10, 10, 100, 100) + var zoomOut = Blockly.WorkspaceSvg.getContentDimensionsExact_(ws); + // 1 ws unit = 0.5 pixels at this zoom level. + assertDimensionsMatch(zoomOut, 5, 5, 50, 50); +} + +// Non-empty workspace, with top-left corner in negative ws coordinates. +var test_GetContentDimensionsExact_nonEmptyNegativeOrigin = function() { + var ws = makeMockWs(1, -10, -10, 100, 100) + var defaultZoom = Blockly.WorkspaceSvg.getContentDimensionsExact_(ws); + // Pixel and ws units are the same at default zoom. + assertDimensionsMatch(defaultZoom, -10, -10, 100, 100); +} + +// Changing zoom will change both width/height and origin location in pixels. +var test_GetContentDimensionsExact_nonEmptyNegativeOriginZoomIn = function() { + var ws = makeMockWs(2, -10, -10, 100, 100) + var zoomIn = Blockly.WorkspaceSvg.getContentDimensionsExact_(ws); + // 1 ws unit = 2 pixels at this zoom level. + assertDimensionsMatch(zoomIn, -20, -20, 200, 200); +} + +var test_GetContentDimensionsExact_nonEmptyNegativeOriginZoomOut = function() { + var ws = makeMockWs(.5, -10, -10, 100, 100) + var zoomOut = Blockly.WorkspaceSvg.getContentDimensionsExact_(ws); + // 1 ws unit = 0.5 pixels at this zoom level. + assertDimensionsMatch(zoomOut, -5, -5, 50, 50); +} diff --git a/tests/jsunit/test_utilities.js b/tests/jsunit/test_utilities.js index 0c256e6f7..381e9134e 100644 --- a/tests/jsunit/test_utilities.js +++ b/tests/jsunit/test_utilities.js @@ -141,3 +141,23 @@ function defineGetVarBlock() { function undefineGetVarBlock() { delete Blockly.Blocks['get_var_block']; } + +/** + * Capture the strings sent to console.warn() when calling a function. + * @param {function} innerFunc The function where warnings may called. + * @return {string[]} The warning messages (only the first arguments). + */ +function captureWarnings(innerFunc) { + var msgs = []; + var nativeConsoleWarn = console.warn; + try { + console.warn = function(msg) { + msgs.push(msg); + nativeConsoleWarn.apply(console, arguments); + }; + innerFunc(); + } finally { + console.warn = nativeConsoleWarn; + } + return msgs; +} diff --git a/tests/jsunit/xml_test.js b/tests/jsunit/xml_test.js index 9c3da790b..527854052 100644 --- a/tests/jsunit/xml_test.js +++ b/tests/jsunit/xml_test.js @@ -392,7 +392,7 @@ function test_variableFieldXml_caseSensitive() { }; var generatedXml = - Blockly.Variables.generateVariableFieldXml_(mockVariableModel); + Blockly.Variables.generateVariableFieldXmlString(mockVariableModel); var goldenXml = ' - + + 123 + diff --git a/tests/playground.html b/tests/playground.html index 596e8f373..c91703281 100644 --- a/tests/playground.html +++ b/tests/playground.html @@ -64,6 +64,7 @@ + + + +

Outer Frame

+