diff --git a/.eslintignore b/.eslintignore index 22c20bea9..0d60b8df1 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,14 +1,17 @@ *_compressed*.js *_uncompressed*.js +blockly_node_javascript_en.js +gulpfile.js /msg/* /core/css.js /tests/blocks/* /tests/compile/* /tests/jsunit/* /tests/generators/* +/tests/test_runner.js /tests/workspace_svg/* /generators/* /demos/* /accessible/* /appengine/* -/externs/svg-externs.js +/externs/* diff --git a/.eslintrc b/.eslintrc index 8385c0b7b..b20da09f8 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,6 +1,6 @@ { "rules": { - "curly": ["error", "multi-line"], + "curly": ["error"], "eol-last": ["error"], "indent": [ "error", 2, # Blockly/Google use 2-space indents @@ -50,6 +50,7 @@ "quotes": ["off"], # Blockly uses single quotes except for JSON blobs, which must use double quotes. "semi": ["error", "always"], "space-before-function-paren": ["error", "never"], # Blockly doesn't have space before function paren + "space-infix-ops": ["error"], "strict": ["off"], # Blockly uses 'use strict' in files "no-cond-assign": ["off"], # Blockly often uses cond-assignment in loops "no-redeclare": ["off"], # Closure style allows redeclarations diff --git a/.gitignore b/.gitignore index 642605678..3277b7174 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +blockly_node_javascript_en.js node_modules npm-debug.log .DS_Store diff --git a/.travis.yml b/.travis.yml index b370eb809..de6192206 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,21 +13,13 @@ matrix: node_js: stable osx_image: xcode8.3 -before_install: -- npm install google-closure-library -- npm install google-closure-compiler -- npm install webdriverio -# Symlink closure library -- ln -s $(npm root)/google-closure-library ../closure-library - before_script: + # Symlink closure library used by test/jsunit + - ln -s $(npm root)/google-closure-library ../closure-library - export DISPLAY=:99.0 - if [ "${TRAVIS_OS_NAME}" == "linux" ]; then ( tests/scripts/setup_linux_env.sh ) fi - if [ "${TRAVIS_OS_NAME}" == "osx" ]; then ( tests/scripts/setup_osx_env.sh ) fi - sleep 2 script: - - set -x - - npm run lint - - npm test - - cd tests/compile; compile.sh; cd .. + - tests/run_all_tests.sh diff --git a/accessible/block-connection.service.js b/accessible/block-connection.service.js index c1ee03aac..d2c5cd866 100644 --- a/accessible/block-connection.service.js +++ b/accessible/block-connection.service.js @@ -101,7 +101,7 @@ blocklyApp.BlockConnectionService = ng.core.Class({ }, attachToMarkedConnection: function(block) { var xml = Blockly.Xml.blockToDom(block); - var reconstitutedBlock = Blockly.Xml.domToBlock(blocklyApp.workspace, xml); + var reconstitutedBlock = Blockly.Xml.domToBlock(xml, blocklyApp.workspace); var targetConnection = null; if (this.markedConnection_.targetBlock() && diff --git a/accessible/field-segment.component.js b/accessible/field-segment.component.js index 8dda20d71..c68afb455 100644 --- a/accessible/field-segment.component.js +++ b/accessible/field-segment.component.js @@ -182,7 +182,7 @@ blocklyApp.FieldSegmentComponent = ng.core.Component({ }, // Sets the value on a dropdown input. setDropdownValue: function(optionValue) { - this.optionValue = optionValue + this.optionValue = optionValue; if (this.optionValue == 'NO_ACTION') { return; } diff --git a/accessible/media/accessible.css b/accessible/media/accessible.css index 500ee7323..285fe8a95 100644 --- a/accessible/media/accessible.css +++ b/accessible/media/accessible.css @@ -26,7 +26,7 @@ } .blocklySidebarButton[disabled] { border: 1px solid #ccc; - opacity: 0.5; + opacity: .5; } .blocklyAriaLiveStatus { diff --git a/accessible/toolbox-modal.service.js b/accessible/toolbox-modal.service.js index 5d93cea88..70c665c4d 100644 --- a/accessible/toolbox-modal.service.js +++ b/accessible/toolbox-modal.service.js @@ -71,7 +71,7 @@ blocklyApp.ToolboxModalService = ng.core.Class({ this.allToolboxCategories = Array.from(toolboxCategoryElts).map( function(categoryElt) { var tmpWorkspace = new Blockly.Workspace(); - var custom = categoryElt.attributes.custom + var custom = categoryElt.attributes.custom; // TODO (corydiers): Implement custom flyouts once #1153 is solved. if (custom && custom.value == Blockly.VARIABLE_CATEGORY_NAME) { var varBlocks = @@ -95,7 +95,7 @@ blocklyApp.ToolboxModalService = ng.core.Class({ // containing all the top-level blocks. var tmpWorkspace = new Blockly.Workspace(); Array.from(toolboxXmlElt.children).forEach(function(topLevelNode) { - Blockly.Xml.domToBlock(tmpWorkspace, topLevelNode); + Blockly.Xml.domToBlock(topLevelNode, tmpWorkspace); }); that.allToolboxCategories = [{ @@ -214,7 +214,7 @@ blocklyApp.ToolboxModalService = ng.core.Class({ this.showModal_(this.toolboxCategoriesForNewGroup, function(block) { var blockDescription = that.utilsService.getBlockDescription(block); var xml = Blockly.Xml.blockToDom(block); - var newBlockId = Blockly.Xml.domToBlock(blocklyApp.workspace, xml).id; + var newBlockId = Blockly.Xml.domToBlock(xml, blocklyApp.workspace).id; // Invoke a digest cycle, so that the DOM settles. setTimeout(function() { diff --git a/accessible/variable-add-modal.component.js b/accessible/variable-add-modal.component.js index 1965e4e5e..2c3f77bbc 100644 --- a/accessible/variable-add-modal.component.js +++ b/accessible/variable-add-modal.component.js @@ -71,7 +71,7 @@ blocklyApp.VariableAddModalComponent = ng.core.Component({ this.workspace = blocklyApp.workspace; this.variableModalService = variableService; this.audioService = audioService; - this.keyboardInputService = keyboardService + this.keyboardInputService = keyboardService; this.modalIsVisible = false; this.activeButtonIndex = -1; @@ -103,7 +103,7 @@ blocklyApp.VariableAddModalComponent = ng.core.Component({ getInteractiveElements: Blockly.CommonModal.getInteractiveElements, // Gets the container with interactive elements. getInteractiveContainer: function() { - return document.getElementById("varForm"); + return document.getElementById('varForm'); }, // Submits the name change for the variable. submit: function() { diff --git a/accessible/variable-remove-modal.component.js b/accessible/variable-remove-modal.component.js index b542e88a5..ce27f5c25 100644 --- a/accessible/variable-remove-modal.component.js +++ b/accessible/variable-remove-modal.component.js @@ -74,17 +74,17 @@ blocklyApp.VariableRemoveModalComponent = ng.core.Component({ this.treeService = treeService; this.variableModalService = variableService; this.audioService = audioService; - this.keyboardInputService = keyboardService + this.keyboardInputService = keyboardService; this.modalIsVisible = false; this.activeButtonIndex = -1; - this.currentVariableName = ""; + this.currentVariableName = ''; this.count = 0; var that = this; this.variableModalService.registerPreRemoveShowHook( function(name, count) { that.currentVariableName = name; - that.count = count + that.count = count; that.modalIsVisible = true; Blockly.CommonModal.setupKeyboardOverrides(that); @@ -106,7 +106,7 @@ blocklyApp.VariableRemoveModalComponent = ng.core.Component({ getInteractiveElements: Blockly.CommonModal.getInteractiveElements, // Gets the container with interactive elements. getInteractiveContainer: function() { - return document.getElementById("varForm"); + return document.getElementById('varForm'); }, getNumVariables: function() { return this.variableModalService.getNumVariables(this.currentVariableName); diff --git a/accessible/variable-rename-modal.component.js b/accessible/variable-rename-modal.component.js index e276e739c..3bb92e75b 100644 --- a/accessible/variable-rename-modal.component.js +++ b/accessible/variable-rename-modal.component.js @@ -72,10 +72,10 @@ blocklyApp.VariableRenameModalComponent = ng.core.Component({ this.workspace = blocklyApp.workspace; this.variableModalService = variableService; this.audioService = audioService; - this.keyboardInputService = keyboardService + this.keyboardInputService = keyboardService; this.modalIsVisible = false; this.activeButtonIndex = -1; - this.currentVariableName = ""; + this.currentVariableName = ''; var that = this; this.variableModalService.registerPreRenameShowHook( @@ -106,7 +106,7 @@ blocklyApp.VariableRenameModalComponent = ng.core.Component({ getInteractiveElements: Blockly.CommonModal.getInteractiveElements, // Gets the container with interactive elements. getInteractiveContainer: function() { - return document.getElementById("varForm"); + return document.getElementById('varForm'); }, // Submits the name change for the variable. submit: function() { diff --git a/appengine/index_redirect.py b/appengine/index_redirect.py index 286a8e87c..94cc26226 100644 --- a/appengine/index_redirect.py +++ b/appengine/index_redirect.py @@ -1,2 +1,3 @@ -print("Status: 302") +print("Status: 301") print("Location: /static/demos/index.html") +print("") diff --git a/appengine/storage.py b/appengine/storage.py index 4a572073f..122fe364d 100644 --- a/appengine/storage.py +++ b/appengine/storage.py @@ -22,35 +22,32 @@ limitations under the License. __author__ = "q.neutron@gmail.com (Quynh Neutron)" import cgi +import hashlib from random import randint -from google.appengine.ext import db from google.appengine.api import memcache -import logging +from google.appengine.ext import ndb -print "Content-Type: text/plain\n" def keyGen(): # Generate a random string of length KEY_LEN. KEY_LEN = 6 - CHARS = "abcdefghijkmnopqrstuvwxyz23456789" # Exclude l, 0, 1. + CHARS = "abcdefghijkmnopqrstuvwxyz23456789" # Exclude l, 0, 1. max_index = len(CHARS) - 1 return "".join([CHARS[randint(0, max_index)] for x in range(KEY_LEN)]) -class Xml(db.Model): +class Xml(ndb.Model): # A row in the database. - xml_hash = db.IntegerProperty() - xml_content = db.TextProperty() + xml_hash = ndb.IntegerProperty() + xml_content = ndb.TextProperty() -forms = cgi.FieldStorage() -if "xml" in forms: +def xmlToKey(xml_content): # Store XML and return a generated key. - xml_content = forms["xml"].value - xml_hash = hash(xml_content) - lookup_query = db.Query(Xml) - lookup_query.filter("xml_hash =", xml_hash) + xml_hash = long(hashlib.sha1(xml_content).hexdigest(), 16) + xml_hash = int(xml_hash % (2 ** 64) - (2 ** 63)) + lookup_query = Xml.query(Xml.xml_hash == xml_hash) lookup_result = lookup_query.get() if lookup_result: - xml_key = lookup_result.key().name() + xml_key = lookup_result.key.string_id() else: trials = 0 result = True @@ -59,27 +56,32 @@ if "xml" in forms: if trials == 100: raise Exception("Sorry, the generator failed to get a key for you.") xml_key = keyGen() - result = db.get(db.Key.from_path("Xml", xml_key)) - xml = db.Text(xml_content, encoding="utf_8") - row = Xml(key_name = xml_key, xml_hash = xml_hash, xml_content = xml) + result = Xml.get_by_id(xml_key) + row = Xml(id = xml_key, xml_hash = xml_hash, xml_content = xml_content) row.put() - print xml_key + return xml_key -if "key" in forms: +def keyToXml(key_provided): # Retrieve stored XML based on the provided key. - key_provided = forms["key"].value # Normalize the string. key_provided = key_provided.lower().strip() # Check memcache for a quick match. xml = memcache.get("XML_" + key_provided) if xml is None: # Check datastore for a definitive match. - result = db.get(db.Key.from_path("Xml", key_provided)) + result = Xml.get_by_id(key_provided) if not result: xml = "" else: xml = result.xml_content # Save to memcache for next hit. - if not memcache.add("XML_" + key_provided, xml, 3600): - logging.error("Memcache set failed.") - print xml.encode("utf-8") + memcache.add("XML_" + key_provided, xml, 3600) + return xml.encode("utf-8") + +if __name__ == "__main__": + print("Content-Type: text/plain\n") + forms = cgi.FieldStorage() + if "xml" in forms: + print(xmlToKey(forms["xml"].value)) + if "key" in forms: + print(keyToXml(forms["key"].value)) diff --git a/blockly_accessible_compressed.js b/blockly_accessible_compressed.js index ec0572566..22be0080c 100644 --- a/blockly_accessible_compressed.js +++ b/blockly_accessible_compressed.js @@ -4,22 +4,23 @@ 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||"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 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.provide=function(a){if(goog.isInModuleLoader_())throw Error("goog.provide cannot 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.getScriptNonce=function(){null===goog.cspNonce_&&(goog.cspNonce_=goog.getScriptNonce_(goog.global.document)||"");return goog.cspNonce_};goog.NONCE_PATTERN_=/^[\w+/_-]+[=]{0,2}$/;goog.cspNonce_=null;goog.getScriptNonce_=function(a){return(a=a.querySelector("script[nonce]"))&&(a=a.nonce||a.getAttribute("nonce"))&&goog.NONCE_PATTERN_.test(a)?a:null};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}; +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].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(){if(goog.moduleLoaderState_&&goog.moduleLoaderState_.type==goog.ModuleType.ES6)return!0;var a=goog.global.$jscomp;return a?"function"!=typeof a.getCurrentModulePath?!1:!!a.getCurrentModulePath():!1}; 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;cNumber(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("es9",function(){return b("({...rest} = {}), true")});a("es_next",function(){return b("({...rest} = {}), true")});return c},goog.Transpiler.prototype.needsTranspile=function(a,b){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.protectScriptTag_=function(a){return a.replace(/<\/(SCRIPT)/ig,"\\x3c/$1")},goog.DebugLoader_=function(){this.dependencies_={};this.idToPath_={};this.written_={};this.loadingDeps_=[];this.depsToLoad_=[];this.paused_=!1;this.factory_=new goog.DependencyFactory(goog.transpiler_);this.deferredCallbacks_={};this.deferredQueue_=[]},goog.DebugLoader_.prototype.bootstrap=function(a, -b){function c(){d&&(goog.global.setTimeout(d,0),d=null)}var d=b;if(a.length){b=[];for(var e=0;e 1, true")});a("es9",function(){return b("({...rest} = {}), true")});a("es_next",function(){return!1});return c},goog.Transpiler.prototype.needsTranspile=function(a,b){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]?!0:!goog.inHtmlDocument_()||"es6"!=b||"noModule"in goog.global.document.createElement("script")? +!1:!0;throw Error("Unknown language mode: "+a);},goog.Transpiler.prototype.transpile=function(a,b){return goog.transpile_(a,b)},goog.transpiler_=new goog.Transpiler,goog.protectScriptTag_=function(a){return a.replace(/<\/(SCRIPT)/ig,"\\x3c/$1")},goog.DebugLoader_=function(){this.dependencies_={};this.idToPath_={};this.written_={};this.loadingDeps_=[];this.depsToLoad_=[];this.paused_=!1;this.factory_=new goog.DependencyFactory(goog.transpiler_);this.deferredCallbacks_={};this.deferredQueue_=[]},goog.DebugLoader_.prototype.bootstrap= +function(a,b){function c(){d&&(goog.global.setTimeout(d,0),d=null)}var d=b;if(a.length){b=[];for(var e=0;e\x3c/script>")}else{var d=b.createElement("script");d.defer=goog.Dependency.defer_;d.async=!1;d.type="text/javascript";goog.DebugLoader_.IS_OLD_IE_?(a.pause(),d.onreadystatechange=function(){if("loaded"==d.readyState||"complete"==d.readyState)a.loaded(),a.resume()}):d.onload=function(){d.onload= -null;a.loaded()};d.src=this.path;b.head.appendChild(d)}}else goog.logToConsole_("Cannot use default debug loader outside of HTML documents."),"deps.js"==this.relativePath?(goog.logToConsole_("Consider setting CLOSURE_IMPORT_SCRIPT before loading base.js, or seting CLOSURE_NO_DEPS to true."),a.loaded()):a.pause()},goog.Es6ModuleDependency=function(a,b,c,d){goog.Dependency.call(this,a,b,[],c,d)},goog.inherits(goog.Es6ModuleDependency,goog.Dependency),goog.Es6ModuleDependency.prototype.load=function(a){function b(a, -b){b?d.write(' + + diff --git a/tests/jsunit/run_jsunit_tests_in_browser.js b/tests/jsunit/run_jsunit_tests_in_browser.js new file mode 100644 index 000000000..2764dc237 --- /dev/null +++ b/tests/jsunit/run_jsunit_tests_in_browser.js @@ -0,0 +1,108 @@ +/** + * @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. + */ + +/** + * @fileoverview Node.js script to run JsUnit tests in Chrome, via webdriver. + */ +var webdriverio = require('webdriverio'); + +/** + * Runs the JsUnit tests in this directory in Chrome. It uses webdriverio to + * launch Chrome and load index.html. Outputs a summary of the test results + * to the console. + * @return the Thenable managing the processing of the browser tests. + */ +function runJsUnitTestsInBrowser() { + var options = { + desiredCapabilities: { + browserName: 'chrome' + } + }; + + var url = 'file://' + __dirname + '/index.html'; + console.log('Starting webdriverio...'); + return webdriverio + .remote(options) + .init() + .then(function() { + console.log('Initialized.\nLoading url: ' + url); + }) + .url(url) + .then(function() { + console.log('Loaded.\nPausing to allow processing.'); + }) + .pause(5000) //TODO: change pause to waitunitl + .then(function() { + console.log('Retrieving results...'); + }) + .getHTML('#closureTestRunnerLog') + .then(function(result) { + // call js to parse html + var regex = /[\d]+\spassed,\s([\d]+)\sfailed./i; + var numOfFailure = regex.exec(result)[1]; + var regex2 = /Unit Tests for Blockly .*]/; + var testStatus = regex2.exec(result)[0]; + console.log('============Blockly Unit Test Summary================='); + console.log(testStatus); + var regex3 = /\d+ passed,\s\d+ failed/; + var detail = regex3.exec(result)[0]; + console.log(detail); + console.log('============Blockly Unit Test Summary================='); + if (parseInt(numOfFailure) !== 0) { + console.log(result); + process.exit(1); + } + }) + .catch(function(e) { + console.error('Error: ', e); + + if (require.main === module) { + // .catch() doesn't seem to work in the calling code, + // even if the error is rethrown. To ensure the script + // exit code is non-zero, shutdown the process here. + process.exit(1); + } + + // WARNING: Catching this outside of runJsUnitTestsInBrowser() is not + // working. However, killing the process doesn't seem good, either. + throw e; + }); +} + +module.exports = runJsUnitTestsInBrowser; + +if (require.main === module) { + try { + runJsUnitTestsInBrowser() + .catch(function(e) { + // TODO: Never called during errors. Fix. + console.error('Error: ' + e); + process.exit(1); + }) + .endAll() + .then(function() { + console.log('JSUnit tests completed'); + process.exit(0); + }); + } catch(e) { + console.error('Uncaught error: ', e); + process.exit(1); + } +} diff --git a/tests/jsunit/test_runner.js b/tests/jsunit/test_runner.js deleted file mode 100644 index bfd7c0bd3..000000000 --- a/tests/jsunit/test_runner.js +++ /dev/null @@ -1,35 +0,0 @@ -var webdriverio = require('webdriverio'); -var options = { - desiredCapabilities: { - browserName: 'chrome' - } -}; - -var path = process.cwd(); -//TODO: change pause to waitunitl -var browser = webdriverio - .remote(options) - .init() - .url("file://" + path + "/tests/jsunit/index.html").pause(5000); - - -browser -.getHTML('#closureTestRunnerLog') -.then(function(result) { - // call js to parse html - var regex = /[\d]+\spassed,\s([\d]+)\sfailed./i; - var numOfFailure = regex.exec(result)[1]; - var regex2 = /Unit Tests for Blockly .*]/; - var testStatus = regex2.exec(result)[0]; - console.log("============Blockly Unit Test Summary================="); - console.log(testStatus); - var regex3 = /\d+ passed,\s\d+ failed/; - var detail = regex3.exec(result)[0]; - console.log(detail); - console.log("============Blockly Unit Test Summary================="); - if ( parseInt(numOfFailure) !== 0) { - console.log(result); - process.exit(1); - } -}) -.catch(function(err) { console.log(err); process.exit(1); }); diff --git a/tests/jsunit/test_utilities.js b/tests/jsunit/test_utilities.js index 381e9134e..36469ee29 100644 --- a/tests/jsunit/test_utilities.js +++ b/tests/jsunit/test_utilities.js @@ -27,6 +27,23 @@ 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.} array1 The first array. diff --git a/tests/jsunit/workspace_comment_test.js b/tests/jsunit/workspace_comment_test.js new file mode 100644 index 000000000..5327abb91 --- /dev/null +++ b/tests/jsunit/workspace_comment_test.js @@ -0,0 +1,158 @@ +/** + * @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, '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, '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, '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, '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, '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_wsCommentContent() { + workspaceCommentTest_setUp(); + + Blockly.Events.fire = temporary_fireEvent; + temporary_fireEvent.firedEvents_ = []; + try { + var comment = + new Blockly.WorkspaceComment(workspace, 'comment text', 10, 20, 'comment id'); + assertEquals( + 'Check comment text', 'comment text', comment.getContent()); + assertEquals( + 'Workspace undo stack has one event', 1, workspace.undoStack_.length); + + comment.setContent('comment text'); + assertEquals( + 'Comment text has not changed', 'comment text', comment.getContent()); + // Setting the text to the old value does not fire an event. + assertEquals( + 'Workspace undo stack has one event', 1, workspace.undoStack_.length); + + comment.setContent('new comment text'); + assertEquals( + 'Comment text has changed', 'new comment text', comment.getContent()); + assertEquals( + 'Workspace undo stack has two events', 2, workspace.undoStack_.length); + comment.dispose(); + } finally { + workspaceCommentTest_tearDown(); + Blockly.Events.fire = savedFireFunc; + } +} diff --git a/tests/multi_playground.html b/tests/multi_playground.html index a9ad9f834..afbcbf8cf 100644 --- a/tests/multi_playground.html +++ b/tests/multi_playground.html @@ -162,7 +162,7 @@ h1 { diff --git a/tests/playground.html b/tests/playground.html index c91703281..745ba0cab 100644 --- a/tests/playground.html +++ b/tests/playground.html @@ -401,7 +401,7 @@ h1 { Variables category uses untyped variable blocks. See https://developers.google.com/blockly/guides/create-custom-blocks/variables#untyped_variable_blocks for more information. -->