├── .github └── workflows │ └── npm-publish.yml ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── bin └── snap2js ├── index.js ├── lib └── snap │ ├── CREDITS.txt │ ├── node.js │ └── xml.js ├── package-lock.json ├── package.json ├── src ├── ast.js ├── backend │ ├── javascript-helpers.js │ └── javascript.js ├── basic.js.ejs ├── context │ ├── basic.js │ ├── cli.js │ └── nop.js └── utils.js └── test ├── change-x-10x.spec.js ├── compilation.spec.js ├── concurrency.spec.js ├── control.spec.js ├── custom-blocks.spec.js ├── do-report-fns.spec.js ├── errors.spec.js ├── functions.spec.js ├── hello-if.spec.js ├── hello-joined-world.spec.js ├── hello-world.spec.js ├── lists.spec.js ├── looks.spec.js ├── misc.spec.js ├── motion.spec.js ├── new-blocks-1320.spec.js ├── operators.spec.js ├── pen.spec.js ├── run-test-cases.js ├── sanitize.spec.js ├── say-and-thinking.spec.js ├── sensing.spec.js ├── sounds.spec.js ├── sprite.spec.js ├── test-cases ├── contexts │ ├── args-global-custom.xml │ ├── args-global-recursive.xml │ ├── args-local-custom.xml │ ├── args-local-recursive.xml │ ├── args-simple-fn.xml │ ├── back-tick-var.xml │ ├── block-child-null.xml │ ├── build-list.xml │ ├── empty-receiver.xml │ ├── fib.xml │ ├── forever-timeout.xml │ ├── get-field-context.xml │ ├── get-random-numbers.xml │ ├── global-custom.xml │ ├── global-recursive.xml │ ├── global-vars-with-ctx.xml │ ├── hello-world.xml │ ├── hoist-sprite.xml │ ├── list-err-forever.xml │ ├── list-err-loop.xml │ ├── list-err-until.xml │ ├── list-err-warp.xml │ ├── list-err.xml │ ├── local-custom.xml │ ├── local-recursive.xml │ ├── outercontext-var.xml │ ├── quote-var-val.xml │ ├── recursive-data.xml │ ├── rename-upvar.xml │ ├── repeat-timeout.xml │ ├── repeat-until-timeout.xml │ ├── report-monadic.xml │ ├── rpc-example.xml │ ├── simple-fn.xml │ ├── transform.xml │ ├── undef-var-err.xml │ ├── upvar-custom-block.xml │ └── warp-forever-timeout.xml └── projects │ ├── aliased-async-fn.xml │ ├── all-control.xml │ ├── all-controlv2.xml │ ├── all-looks.xml │ ├── all-looksv2.xml │ ├── all-motion.xml │ ├── all-pen.xml │ ├── all-penv2.xml │ ├── all-sensing.xml │ ├── all-sensingv2.xml │ ├── all-sounds.xml │ ├── all-soundsv2.xml │ ├── all-variables.xml │ ├── all-variablesv2.xml │ ├── basic-math.xml │ ├── boolean.xml │ ├── broadcast-and-wait-any-msg.xml │ ├── broadcast-any-msg.xml │ ├── broadcast-wait.xml │ ├── broadcast.xml │ ├── change-x-10x.xml │ ├── clone-other.xml │ ├── cloning.xml │ ├── cmd-ring.xml │ ├── comments.xml │ ├── comparisons.xml │ ├── cons-cdr.xml │ ├── costume-size.xml │ ├── create-lists.xml │ ├── custom-block-cslot.xml │ ├── custom-block-inputs.xml │ ├── custom-block.xml │ ├── date.xml │ ├── do-if-false-async-repeat-until.xml │ ├── do-if-false-async.xml │ ├── do-if-false-sync.xml │ ├── do-if-true-async-repeat-until.xml │ ├── do-if-true-async.xml │ ├── do-if-true-sync.xml │ ├── do-report-anon-pred.xml │ ├── do-report-custom-reporter.xml │ ├── do-report-nop.xml │ ├── do-report-reify-script-async.xml │ ├── do-report-reify-script.xml │ ├── do-report-simple.xml │ ├── doFor.xml │ ├── doForEach.xml │ ├── doReport-custom-blocks.xml │ ├── draggable.xml │ ├── empty-if-cond.xml │ ├── fake-implicit-inputs.xml │ ├── fn-names.xml │ ├── forever-timeout.xml │ ├── fork.xml │ ├── forward-angle.xml │ ├── hello-joined-world.xml │ ├── hello-world.xml │ ├── if-statement.xml │ ├── implicit-inputs.xml │ ├── initial-set-change-pos.xml │ ├── initial-var-fn.xml │ ├── initial-variables.xml │ ├── js-fn.xml │ ├── lists.xml │ ├── local-custom-sum.xml │ ├── loops-and-waits.xml │ ├── nested-implicit-inputs.xml │ ├── nested-lists.xml │ ├── new-blocks-1320.xml │ ├── predicate-ring.xml │ ├── random.xml │ ├── recursive-and-iterative-custom-blocks.xml │ ├── repeat-timeout.xml │ ├── repeat-until-timeout.xml │ ├── repeat-until.xml │ ├── report-list-item.xml │ ├── report-monadic.xml │ ├── reporter-ring.xml │ ├── reverseDoFor.xml │ ├── sanitize-inputs.xml │ ├── say-and-think.xml │ ├── stage-custom-sum.xml │ ├── string-ops.xml │ ├── timer.xml │ ├── variables.xml │ └── warp-forever-timeout.xml ├── timeout.spec.js ├── utils.js └── variables.spec.js /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to NPM 2 | 3 | on: 4 | release: 5 | types: 6 | - published 7 | 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/setup-node@v1 13 | with: 14 | node-version: 12 15 | registry-url: https://registry.npmjs.org/ 16 | - run: npm ci 17 | - run: npm publish 18 | env: 19 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | notes.md 3 | tmp/ 4 | notes/ 5 | coverage/ 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | tmp/ 3 | coverage/ 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: 3 | directories: 4 | - node_modules 5 | node_js: 6 | - '10.16.3' 7 | install: 8 | - npm install 9 | before_script: 10 | - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter 11 | - chmod +x ./cc-test-reporter 12 | - ./cc-test-reporter before-build 13 | 14 | after_script: 15 | - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/NetsBlox/Snap2Js.svg?branch=master)](https://travis-ci.org/NetsBlox/Snap2Js) 2 | [![Test Coverage](https://api.codeclimate.com/v1/badges/10eb358dc112069c5263/test_coverage)](https://codeclimate.com/github/NetsBlox/Snap2Js/test_coverage) 3 | # Snap2Js 4 | As the name suggests, Snap2Js is a compiler from [Snap!](http://snap.berkeley.edu) to JavaScript. 5 | 6 | ## Quick Start 7 | Snap2Js requires [NodeJS](https://nodejs.org) LTS. Snap2JS can be installed using `npm` as follows: 8 | 9 | ``` 10 | npm install -g snap2js 11 | ``` 12 | 13 | Next, you can compile your favorite Snap! application by first exporting it to xml then using snap2js to compile it to a js file! A "hello world" example project is provided [here](https://github.com/NetsBlox/Snap2Js/blob/master/test/test-cases/projects/hello-world.xml). 14 | 15 | ``` 16 | snap2js hello-world.xml 17 | node hello-world.js 18 | ``` 19 | -------------------------------------------------------------------------------- /bin/snap2js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const prog = require('caporal'); 4 | const version = require('../package.json').version; 5 | const snap2js = require('..'); 6 | const path = require('path'); 7 | const fs = require('fs'); 8 | const exists = require('exists-file'); 9 | const browserify = require('browserify'); 10 | const BROWSERIFY_OPTS = { 11 | noParse: [ undefined ], 12 | extensions: [], 13 | ignoreTransform: [], 14 | fullPaths: false, 15 | builtins: false, 16 | commondir: false, 17 | bundleExternal: true, 18 | basedir: undefined, 19 | browserField: false, 20 | transformKey: undefined, 21 | dedupe: true, 22 | detectGlobals: true, 23 | insertGlobals: false, 24 | insertGlobalVars: 25 | { 26 | process: undefined, 27 | global: undefined, 28 | 'Buffer.isBuffer': undefined, 29 | Buffer: undefined 30 | }, 31 | ignoreMissing: false, 32 | debug: false, 33 | standalone: undefined 34 | }; 35 | const {promisify} = require('util'); 36 | const readFile = promisify(fs.readFile); 37 | const writeFile = promisify(fs.writeFile); 38 | 39 | prog 40 | .version(version) 41 | .description('A tool to compile Snap! xml files to JavaScript') 42 | .argument('', 'Snap xml files to compile') 43 | .option('-i,--interpret') 44 | .option( 45 | '--args [inputs]', 46 | 'Comma separated list of arguments (if interpreting a serialized function)' 47 | ) 48 | .option('--output ', 'Output directory', fileExists, '.') 49 | .option('--context ', 'Context to use', isContext, 'basic') 50 | .action((args, opts, logger) => { 51 | if (opts.interpret) { 52 | if (args.files.length > 1) logger.warn(`Only interpretting ${args.files[0]}`) 53 | return interpretFile(args.files[0], opts, logger); 54 | } else { 55 | return Promise.all(args.files.map(filename => saveToFile(filename, opts, logger))); 56 | } 57 | }); 58 | 59 | async function compileFile(filename, opts, logger) { 60 | const contents = await readFile(filename) 61 | return snap2js.transpile(contents); 62 | } 63 | 64 | async function saveToFile(filename, opts, logger) { 65 | const outfile = path.join(opts.output, path.basename(filename).replace(/\.[a-zA-Z0-9]+$/, '.js')); 66 | const tmpfile = outfile + '.tmp'; 67 | const context = opts.context; 68 | 69 | logger.debug('compiling ' + filename + ' to ' + outfile); 70 | const fn = await compileFile(filename, opts, logger); 71 | const relPath = path.resolve(`${path.dirname(__dirname)}/src/context/${context}`); 72 | const contextPath = path.relative(path.resolve(opts.output), relPath); 73 | const code = `(${fn.toString()})(require('./${contextPath}'))`; 74 | await writeFile(tmpfile, code); 75 | 76 | const b = browserify([tmpfile], BROWSERIFY_OPTS); 77 | const outputStream = fs.createWriteStream(outfile); 78 | 79 | b.bundle().pipe(outputStream); 80 | outputStream.on('error', err => logger.warn(err)); 81 | 82 | outputStream.on('close', () => { 83 | logger.debug('finished compiling ' + outfile); 84 | logger.debug('removing ' + tmpfile); 85 | fs.unlinkSync(tmpfile); 86 | }); 87 | } 88 | 89 | async function interpretFile(filename, opts, logger) { 90 | const context = opts.context; 91 | const contents = await readFile(filename, 'utf8'); 92 | const fn = await snap2js.compile(contents); 93 | const env = require(`../src/context/${context}`); 94 | 95 | const result = await fn(env); 96 | if (isFunction(contents)) { 97 | const args = opts.args ? parseFnArgs(opts.args) : []; 98 | try { 99 | const fnOutput = await result.apply(null, args); 100 | console.log(fnOutput); 101 | return fnOutput; 102 | } catch (err) { 103 | console.log(err.stack); 104 | process.exit(1); 105 | } 106 | } 107 | return result; 108 | } 109 | 110 | function parseFnArgs(argString) { 111 | try { 112 | return JSON.parse(argString); 113 | } catch (err) { 114 | return argString.split(','); 115 | } 116 | } 117 | 118 | function isFunction(data) { 119 | return data.trim().startsWith(' name.replace(/\.js$/, '')); 133 | 134 | var isValid = contexts.find(filename => filename === name.toLowerCase()); 135 | 136 | if (!isValid) { 137 | throw new Error(`Invalid context: ${dir}. Expected ${contexts.join(', ')}`); 138 | } 139 | return name.toLowerCase(); 140 | } 141 | 142 | prog.parse(process.argv); 143 | -------------------------------------------------------------------------------- /lib/snap/CREDITS.txt: -------------------------------------------------------------------------------- 1 | These files have been borrowed from the Snap! project with minimal modifications: 2 | - node.js contains only the Node definition from morphic 3 | - addition of `module.exports` to the end for use in nodejs 4 | -------------------------------------------------------------------------------- /lib/snap/node.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | morphic.js 4 | 5 | a lively Web-GUI 6 | inspired by Squeak 7 | 8 | written by Jens Mönig 9 | jens@moenig.org 10 | 11 | Copyright (C) 2017 by Jens Mönig 12 | 13 | This file is part of Snap!. 14 | 15 | Snap! is free software: you can redistribute it and/or modify 16 | it under the terms of the GNU Affero General Public License as 17 | published by the Free Software Foundation, either version 3 of 18 | the License, or (at your option) any later version. 19 | 20 | This program is distributed in the hope that it will be useful, 21 | but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | GNU Affero General Public License for more details. 24 | 25 | You should have received a copy of the GNU Affero General Public License 26 | along with this program. If not, see . 27 | 28 | 29 | This contains the Node definition from the morphic framework (as the xml.js) 30 | file depends on it 31 | */ 32 | function Node(parent, childrenArray) { 33 | this.init(parent || null, childrenArray || []); 34 | } 35 | 36 | Node.prototype.init = function (parent, childrenArray) { 37 | this.parent = parent || null; 38 | this.children = childrenArray || []; 39 | }; 40 | 41 | // Node string representation: e.g. 'a Node[3]' 42 | 43 | Node.prototype.toString = function () { 44 | return 'a Node' + '[' + this.children.length.toString() + ']'; 45 | }; 46 | 47 | // Node accessing: 48 | 49 | Node.prototype.addChild = function (aNode) { 50 | this.children.push(aNode); 51 | aNode.parent = this; 52 | }; 53 | 54 | Node.prototype.addChildFirst = function (aNode) { 55 | this.children.splice(0, null, aNode); 56 | aNode.parent = this; 57 | }; 58 | 59 | Node.prototype.removeChild = function (aNode) { 60 | var idx = this.children.indexOf(aNode); 61 | if (idx !== -1) { 62 | this.children.splice(idx, 1); 63 | } 64 | }; 65 | 66 | // Node functions: 67 | 68 | Node.prototype.root = function () { 69 | if (this.parent === null) { 70 | return this; 71 | } 72 | return this.parent.root(); 73 | }; 74 | 75 | Node.prototype.depth = function () { 76 | if (this.parent === null) { 77 | return 0; 78 | } 79 | return this.parent.depth() + 1; 80 | }; 81 | 82 | Node.prototype.allChildren = function () { 83 | // includes myself 84 | var result = [this]; 85 | this.children.forEach(function (child) { 86 | result = result.concat(child.allChildren()); 87 | }); 88 | return result; 89 | }; 90 | 91 | Node.prototype.forAllChildren = function (aFunction) { 92 | if (this.children.length > 0) { 93 | this.children.forEach(function (child) { 94 | child.forAllChildren(aFunction); 95 | }); 96 | } 97 | aFunction.call(null, this); 98 | }; 99 | 100 | Node.prototype.anyChild = function (aPredicate) { 101 | // includes myself 102 | var i; 103 | if (aPredicate.call(null, this)) { 104 | return true; 105 | } 106 | for (i = 0; i < this.children.length; i += 1) { 107 | if (this.children[i].anyChild(aPredicate)) { 108 | return true; 109 | } 110 | } 111 | return false; 112 | }; 113 | 114 | Node.prototype.allLeafs = function () { 115 | var result = []; 116 | this.allChildren().forEach(function (element) { 117 | if (element.children.length === 0) { 118 | result.push(element); 119 | } 120 | }); 121 | return result; 122 | }; 123 | 124 | Node.prototype.allParents = function () { 125 | // includes myself 126 | var result = [this]; 127 | if (this.parent !== null) { 128 | result = result.concat(this.parent.allParents()); 129 | } 130 | return result; 131 | }; 132 | 133 | Node.prototype.siblings = function () { 134 | var myself = this; 135 | if (this.parent === null) { 136 | return []; 137 | } 138 | return this.parent.children.filter(function (child) { 139 | return child !== myself; 140 | }); 141 | }; 142 | 143 | Node.prototype.parentThatIsA = function (constructor) { 144 | // including myself 145 | if (this instanceof constructor) { 146 | return this; 147 | } 148 | if (!this.parent) { 149 | return null; 150 | } 151 | return this.parent.parentThatIsA(constructor); 152 | }; 153 | 154 | Node.prototype.parentThatIsAnyOf = function (constructors) { 155 | // including myself 156 | var yup = false, 157 | myself = this; 158 | constructors.forEach(function (each) { 159 | if (myself.constructor === each) { 160 | yup = true; 161 | return; 162 | } 163 | }); 164 | if (yup) { 165 | return this; 166 | } 167 | if (!this.parent) { 168 | return null; 169 | } 170 | return this.parent.parentThatIsAnyOf(constructors); 171 | }; 172 | 173 | module.exports = Node; 174 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "snap2js", 3 | "version": "1.7.0", 4 | "description": "Cross compiler from Snap! to JavaScript", 5 | "main": "index.js", 6 | "bin": { 7 | "snap2js": "./bin/snap2js" 8 | }, 9 | "scripts": { 10 | "test-quick": "mocha --recursive test/", 11 | "test": "istanbul --hook-run-in-context cover -x \"lib/**\" node_modules/mocha/bin/_mocha -- -R spec test/**.js" 12 | }, 13 | "author": "Brian Broll", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "mocha": "^7.0.1" 17 | }, 18 | "dependencies": { 19 | "browserify": "^14.3.0", 20 | "caporal": "^0.5.0", 21 | "exists-file": "^3.0.0", 22 | "istanbul": "^0.4.5", 23 | "keypress": "^0.2.1", 24 | "lodash": "^4.17.4", 25 | "prettier": "^1.1.0", 26 | "readline-sync": "^1.4.7" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/backend/javascript-helpers.js: -------------------------------------------------------------------------------- 1 | const callRawFnWithArgs = function(fn) { 2 | const inputs = Array.prototype.slice.call(arguments, 1); 3 | if (inputs.length) { 4 | return `${fn}.call(self, ${inputs.join(', ')}, DEFAULT_CONTEXT)`; 5 | } 6 | return `${fn}.call(self, DEFAULT_CONTEXT)`; 7 | }; 8 | 9 | const callFnWithArgs = function(fn) { 10 | arguments[0] = `__ENV.${fn}`; 11 | return callRawFnWithArgs.apply(null, arguments); 12 | }; 13 | 14 | const callStatementWithArgs = function() { 15 | return callFnWithArgs.apply(null, arguments) + '\n'; 16 | }; 17 | 18 | module.exports = { 19 | callRawFnWithArgs, 20 | callFnWithArgs, 21 | callStatementWithArgs, 22 | }; 23 | -------------------------------------------------------------------------------- /src/basic.js.ejs: -------------------------------------------------------------------------------- 1 | class Variable { 2 | constructor(name, value) { 3 | this.name = name; 4 | this.value = value; 5 | } 6 | } 7 | 8 | class VariableFrame { 9 | constructor(parent) { 10 | this.parent = parent; 11 | this.vars = {}; 12 | } 13 | 14 | get(name, silent) { 15 | let result = null; 16 | if (this.vars[name]) { 17 | result = this.vars[name]; 18 | } else if (this.parent) { 19 | result = this.parent.get(name, silent); 20 | } 21 | if (result === null && !silent) throw new Error('a variable of name \''+name+'\' does not exist in this context'); 22 | return result; 23 | } 24 | 25 | set(name, value) { 26 | this.vars[name] = new Variable(name, value); 27 | } 28 | } 29 | 30 | class Stage { 31 | constructor(name, variables, customBlocks) { 32 | this.name = name; 33 | this.variables = new VariableFrame(variables); 34 | this.customBlocks = new VariableFrame(project.customBlocks); 35 | this.isSprite = false; 36 | this.variables.set('__SELF', this); 37 | this.project = project; 38 | } 39 | 40 | onFlagPressed() { 41 | } 42 | 43 | onUserEventReceived (event) { 44 | } 45 | 46 | emit(event, wait) { 47 | if (wait) { 48 | return project.sprites.concat([project.stage]) 49 | .map(obj => obj.onUserEventReceived(event)); 50 | } else { 51 | setTimeout(() => { 52 | project.sprites.concat([project.stage]) 53 | .forEach(obj => obj.onUserEventReceived(event)); 54 | }, 0); 55 | } 56 | } 57 | 58 | getTimerStart() { 59 | return project.timerStart; 60 | } 61 | 62 | resetTimer() { 63 | project.timerStart = Date.now(); 64 | } 65 | 66 | getTempo() { 67 | return project.tempo; 68 | } 69 | 70 | setTempo(bpm) { 71 | return project.tempo = Math.max(20, (+bpm || 0)); 72 | } 73 | } 74 | 75 | class Sprite extends Stage { 76 | constructor(name, variables, customBlocks) { 77 | super(name, variables, customBlocks); 78 | this.clones = []; 79 | this.isSprite = true; 80 | this.xPosition = 0; 81 | this.yPosition = 0; 82 | this.direction = 90; 83 | this.costume = 0; 84 | this.size = 100; 85 | } 86 | 87 | clone() { 88 | let clone = Object.create(this); 89 | this.clones.push(clone); 90 | clone.onCloneStart(); 91 | } 92 | } 93 | 94 | __ENV = __ENV || this; 95 | var project = { 96 | variables: new VariableFrame(), 97 | customBlocks: new VariableFrame(), 98 | timerStart: null, 99 | tempo: <%= tempo %>, 100 | sprites: [] 101 | }; 102 | project.stage = new Stage(<%= stage.name %>, project.variables, project.customBlocks); 103 | let DEFAULT_CONTEXT = new VariableFrame(project.stage.variables); 104 | 105 | var sprite; 106 | <% sprites.forEach(function(rawSprite) { %> 107 | sprite = new Sprite(<%= rawSprite.name %>, project.variables, project.customBlocks); 108 | sprite.xPosition = <%= rawSprite.position.x %>; 109 | sprite.yPosition = <%= rawSprite.position.y %>; 110 | sprite.direction = <%= rawSprite.direction %>; 111 | sprite.draggable = <%= rawSprite.draggable %>; 112 | sprite.rotation = <%= rawSprite.rotation %>; 113 | sprite.size = <%= rawSprite.size %>; 114 | sprite.costumeIdx = <%= rawSprite.costumeIdx %>; 115 | project.sprites.push(sprite); 116 | <% }); %> 117 | 118 | <% stage.customBlocks.forEach(function(def) { %> 119 | project.stage.customBlocks.set(<%= def.name %>, <%= def.code %>); 120 | <% }) %> 121 | 122 | project.stage.onFlagPressed = async function() { 123 | var self = this; 124 | <% 125 | (stage.scripts.receiveGo || []) 126 | .forEach(function(code, i) { 127 | %> 128 | const handler_<%= i %> = <%= code %> 129 | <% 130 | }); 131 | %> 132 | 133 | return Promise.all([ 134 | <% 135 | (stage.scripts.receiveGo || []) 136 | .forEach(function(_, i) { 137 | %> 138 | handler_<%=i%>, 139 | <% 140 | }); 141 | %> ]); 142 | }; 143 | 144 | project.stage.onUserEventReceived = function(event) { 145 | var self = this; 146 | 147 | <% 148 | (stage.scripts.receiveMessage || []) 149 | .forEach(function(code, i) { 150 | %> 151 | const handler_<%= i %> = <%= code %> 152 | <% 153 | }); 154 | %> 155 | return Promise.all([ 156 | <% 157 | (stage.scripts.receiveMessage || []) 158 | .forEach(function(_, i) { 159 | %> 160 | handler_<%=i%>, 161 | <% 162 | }); 163 | %> ]); 164 | }; 165 | 166 | project.stage.onKeyPressed = function(key) { 167 | var self = this; 168 | <% 169 | (stage.scripts.receiveKey || []) 170 | .forEach(function(code) { 171 | %> 172 | <%= code %> 173 | 174 | <% 175 | }); 176 | %> 177 | }; 178 | // Initialize content references 179 | var self = project; 180 | SNAP2JS_REFERENCES = []; 181 | project.variables.set('SNAP2JS_REFERENCES', SNAP2JS_REFERENCES); 182 | <%= initRefs %> 183 | 184 | // for each sprite... 185 | var sprite; 186 | <% sprites.forEach(function(rawSprite, i) { %> 187 | sprite = project.sprites[<%= i %>]; 188 | <% rawSprite.customBlocks.forEach(function(def) { %> 189 | sprite.customBlocks.set(<%= def.name %>, <%= def.code %>); 190 | <% }) %> 191 | <% Object.keys(rawSprite.variables).forEach(function(variable) { %> 192 | sprite.variables.set(<%= variable %>, <%= rawSprite.variables[variable] %>);<% }) %> 193 | 194 | sprite.onFlagPressed = function() { 195 | var self = this; 196 | <% 197 | (rawSprite.scripts.receiveGo || []) 198 | .forEach(function(code, i) { 199 | %> 200 | const handler_<%= i %> = <%= code %> 201 | <% 202 | }); 203 | %> 204 | return Promise.all([ 205 | <% 206 | (rawSprite.scripts.receiveGo || []) 207 | .forEach(function(_, i) { 208 | %>handler_<%=i%>, 209 | <% 210 | }); 211 | %> ]); 212 | }; 213 | 214 | sprite.onKeyPressed = function(key) { 215 | var self = this; 216 | <% 217 | (rawSprite.scripts.receiveKey || []) 218 | .forEach(function(code) { 219 | %> 220 | <%= code %> 221 | 222 | <% 223 | }); 224 | %> 225 | }; 226 | 227 | sprite.onUserEventReceived = function(event) { 228 | var self = this; 229 | 230 | <% 231 | (rawSprite.scripts.receiveMessage || []) 232 | .forEach(function(code, i) { 233 | %> 234 | const handler_<%= i %> = <%= code %> 235 | <% 236 | }); 237 | %> 238 | return Promise.all([ 239 | <% 240 | (rawSprite.scripts.receiveMessage || []) 241 | .forEach(function(_, i) { 242 | %>handler_<%=i%>, 243 | <% 244 | }); 245 | %> ]); 246 | }; 247 | 248 | sprite.onCloneStart = function(event) { 249 | var self = this; 250 | <% 251 | (rawSprite.scripts.receiveOnClone || []) 252 | .forEach(function(code) { 253 | %> 254 | <%= code %> 255 | 256 | <% 257 | }); 258 | %> 259 | }; 260 | 261 | sprite.onEventReceived = function(event) { 262 | var self = this; 263 | 264 | if (event === 'clicked') { 265 | // Add code for the given event... 266 | // TODO 267 | } 268 | }; 269 | 270 | sprite.checkConditions = function() { 271 | var self = this; 272 | 273 | // TODO: add arbitrary hat block code here 274 | }; 275 | 276 | <% }) %> 277 | project.timerStart = Date.now(); 278 | __ENV.__start(project, __ENV); 279 | 280 | <% Object.keys(variables).forEach(function(variable) { %> 281 | project.variables.set(<%= variable %>, <%= variables[variable] %>);<% }) %> 282 | <% customBlocks.forEach(function(def) { %> 283 | project.customBlocks.set(<%= def.name %>, <%= def.code %>); 284 | <% }) %> 285 | 286 | <%= initCode %> 287 | 288 | <% // This only executes if there is not context...? 289 | // This should be the default fn to call when this runs... 290 | // or should this 291 | if (returnValue) { %> 292 | return <%= returnValue %>; 293 | <% } else { %> 294 | return Promise.all(project.sprites.concat(project.stage).map(sprite => sprite.onFlagPressed())); 295 | <% } %> 296 | -------------------------------------------------------------------------------- /src/context/cli.js: -------------------------------------------------------------------------------- 1 | const base = require('./basic'); 2 | const clone = require('../utils').clone; 3 | const readline = require('readline-sync'); 4 | 5 | var context = clone(base); 6 | var lastAnswer = ''; 7 | 8 | context.doAsk = question => { 9 | lastAnswer = readline.question(question + '\n'); 10 | }; 11 | 12 | context.getLastAnswer = function(node) { 13 | return lastAnswer; 14 | }; 15 | 16 | context.__start = function(project) { 17 | var stdin = process.openStdin(); 18 | stdin.setRawMode(true); 19 | stdin.resume(); 20 | stdin.setEncoding('utf8'); 21 | 22 | stdin.on('data', (key) => { 23 | if (key === '\u0003') process.exit(); 24 | 25 | switch (key) { 26 | case '\u001b[A': 27 | key = 'up arrow'; 28 | 29 | case '\u001b[B': 30 | key = 'down arrow'; 31 | 32 | case '\u001b[C': 33 | key = 'right arrow'; 34 | 35 | case '\u001b[C': 36 | key = 'left arrow'; 37 | 38 | case ' ': 39 | key = 'space'; 40 | } 41 | 42 | project.sprites.concat([project.stage]) 43 | .forEach(obj => obj.onKeyPressed(key)); 44 | }); 45 | }; 46 | 47 | module.exports = context; 48 | -------------------------------------------------------------------------------- /src/context/nop.js: -------------------------------------------------------------------------------- 1 | // nop everything 2 | const backend = require('../backend/javascript'); 3 | const nop = () => {}; 4 | 5 | var context = {}; 6 | Object.keys(backend).forEach(key => context[key] = nop); 7 | 8 | // special cases 9 | context.doYield = nop; 10 | context.__start = nop; 11 | 12 | module.exports = context; 13 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | const parseSpec = function (spec) { 2 | var parts = [], word = '', i, quoted = false, c; 3 | for (i = 0; i < spec.length; i += 1) { 4 | c = spec[i]; 5 | if (c === "'") { 6 | quoted = !quoted; 7 | } else if (c === ' ' && !quoted) { 8 | parts.push(word); 9 | word = ''; 10 | } else { 11 | word = word.concat(c); 12 | } 13 | } 14 | parts.push(word); 15 | return parts; 16 | }; 17 | const inputNames = function (spec) { 18 | var vNames = [], 19 | parts = parseSpec(spec); 20 | 21 | parts.forEach(function (part) { 22 | if (part[0] === '%' && part.length > 1) { 23 | vNames.push(part.slice(1)); 24 | } 25 | }); 26 | return vNames; 27 | }; 28 | 29 | const indent = lines => ' ' + lines.replace(/\n/g, ' '); 30 | const clone = obj => { 31 | var newObj = {}, 32 | keys = Object.keys(obj); 33 | 34 | for (var i = keys.length; i--;) { 35 | newObj[keys[i]] = obj[keys[i]]; 36 | } 37 | return newObj; 38 | }; 39 | 40 | const sanitize = function(text) { 41 | if (typeof text === 'string') { 42 | return `unescape('${escape(text)}')`; 43 | } else if (text instanceof Array){ 44 | return '[' + text.map(val => sanitize(val)).join(',') + ']'; 45 | } 46 | return text; 47 | }; 48 | 49 | const defer = function () { 50 | const deferred = {resolve: null, reject: null}; 51 | deferred.promise = new Promise((resolve, reject) => { 52 | deferred.resolve = resolve; 53 | deferred.reject = reject; 54 | }); 55 | return deferred; 56 | }; 57 | 58 | module.exports = { 59 | indent: indent, 60 | clone: clone, 61 | parseSpec: parseSpec, 62 | inputNames: inputNames, 63 | sanitize, 64 | defer, 65 | }; 66 | -------------------------------------------------------------------------------- /test/change-x-10x.spec.js: -------------------------------------------------------------------------------- 1 | describe('change x 10x', function() { 2 | let snap2js = require('..'), 3 | assert = require('assert'), 4 | utils = require('./utils'), 5 | content; 6 | 7 | before(function(){ 8 | content = utils.getProjectXml('change-x-10x'); 9 | }); 10 | 11 | describe('transpile', function() { 12 | var code; 13 | 14 | before(function() { 15 | code = snap2js.transpile(content); 16 | }); 17 | 18 | it('should contain "setXPosition"', function() { 19 | assert(/\bsetXPosition\b/.test(code)); 20 | }); 21 | }); 22 | 23 | describe('compile', function() { 24 | var bin, 25 | cxt, 26 | xVal = 0; 27 | 28 | before(function() { 29 | cxt = snap2js.newContext(); 30 | cxt['setXPosition'] = v => xVal = (+v); 31 | 32 | bin = snap2js.compile(content); 33 | }); 34 | 35 | it('should finish with x == 100', function(done) { 36 | cxt['changeXPosition'] = v => { 37 | xVal += +v; 38 | if (xVal === 100) done(); 39 | }; 40 | bin(cxt); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /test/compilation.spec.js: -------------------------------------------------------------------------------- 1 | describe('compilation', function() { 2 | const fs = require('fs'); 3 | const snap2js = require('..'); 4 | const path = require('path'); 5 | const utils = require('./utils'); 6 | let context; 7 | 8 | before(function() { 9 | context = snap2js.newContext(snap2js.CONTEXT.NOP); 10 | }); 11 | 12 | utils.getProjectPaths() 13 | .filter(filename => !filename.includes('all-control')) 14 | .forEach(filename => { 15 | it(`should be able to compile ${filename}`, function() { 16 | const content = fs.readFileSync(filename, 'utf8'); 17 | const bin = snap2js.compile(content) 18 | }); 19 | 20 | }); 21 | 22 | describe('custom blocks', function() { 23 | it('should be able to compile custom blocks w/o a def', function() { 24 | const content = utils.getContextXml('block-child-null'); 25 | snap2js.compile(content) 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/concurrency.spec.js: -------------------------------------------------------------------------------- 1 | describe('concurrency', function() { 2 | let snap2js = require('..'), 3 | assert = require('assert'), 4 | utils = require('./utils'), 5 | content; 6 | 7 | before(function(){ 8 | content = utils.getProjectXml('loops-and-waits'); 9 | }); 10 | 11 | describe('transpile', function() { 12 | let code; 13 | 14 | before(function() { 15 | code = snap2js.transpile(content); 16 | }); 17 | 18 | it('should not contain "doWarp"', function() { 19 | assert(!/\bdoWarp\b/.test(code)); 20 | }); 21 | 22 | it('should contain "for"', function() { 23 | assert(/for\s*\(/.test(code)); 24 | }); 25 | }); 26 | 27 | describe('compile', function() { 28 | var bin, 29 | cxt, 30 | values = [], 31 | totalOrder; 32 | 33 | before(function(done) { 34 | this.timeout(5000); 35 | cxt = snap2js.newContext(); 36 | cxt['bubble'] = val => { 37 | values.push(val); 38 | if (values.length === 2) { 39 | totalOrder = values[1]; 40 | done(); 41 | } 42 | }; 43 | bin = snap2js.compile(content); 44 | bin(cxt); 45 | }); 46 | 47 | // Things to check: 48 | // - wait block yields 49 | // - end of loop yields 50 | 51 | it('should yield after wait block', function() { 52 | assert.notEqual(totalOrder[1], '2'); 53 | assert.notEqual(totalOrder[1], '22'); 54 | }); 55 | 56 | it('should yield after doSayFor', function() { 57 | assert(!utils.isRightAfter(totalOrder, '13', '14')) 58 | }); 59 | 60 | it('should yield after repeat loop', function() { 61 | assert(!utils.isRightAfter(totalOrder, '15', '16')) 62 | }); 63 | 64 | it('should not yield after repeat in warp', function() { 65 | assert(utils.isRightBefore(totalOrder, '27.5', '28')); 66 | }); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /test/control.spec.js: -------------------------------------------------------------------------------- 1 | describe('control', function() { 2 | const utils = require('./utils'); 3 | const assert = require('assert'); 4 | const snap2js = require('..'); 5 | 6 | describe('all blocks', function() { 7 | // It's not really safe to run the generated code... 8 | it('should compile all blocks', function() { 9 | bin = utils.getCompiledVersionOf('all-control'); 10 | assert.equal(typeof bin, 'function'); 11 | }); 12 | 13 | it('should compile v6 blocks', function() { 14 | bin = utils.getCompiledVersionOf('all-controlv2'); 15 | assert.equal(typeof bin, 'function'); 16 | }); 17 | }); 18 | 19 | describe('doFor', function() { 20 | it('should loop until iterator == 11', async function() { 21 | const count = await utils.compileAndRun('doFor'); 22 | assert.equal(count, 11); 23 | }); 24 | 25 | it('should loop until correctly', async function() { 26 | const count = await utils.compileAndRun('reverseDoFor'); 27 | assert.deepEqual(count, [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]); 28 | }); 29 | }); 30 | 31 | describe('doForEach', function() { 32 | it('should loop until correctly', async function() { 33 | const count = await utils.compileAndRun('doForEach'); 34 | assert.deepEqual(count, [10, 9, 8, 7, 6, 5, 4, 3, 2, 1].reverse()); 35 | }); 36 | }); 37 | 38 | describe('doUntil', function() { 39 | it('should loop until count == 61', async function() { 40 | const count = await utils.compileAndRun('repeat-until'); 41 | assert.equal(count, 61); 42 | }); 43 | }); 44 | 45 | describe('broadcast', function() { 46 | 47 | it('should trigger given hat block', async function() { 48 | const result = await utils.compileAndRun('broadcast-and-wait-any-msg'); 49 | assert.equal(result, 'success!'); 50 | }); 51 | 52 | it('should trigger given hat block w/ any-msg', async function() { 53 | const result = await utils.compileAndRunUntilReport('broadcast-any-msg'); 54 | assert.equal(result, 'success!'); 55 | }); 56 | 57 | it('should support broadcast and wait', async function() { 58 | const list = await utils.compileAndRunUntilReport('broadcast-wait'); 59 | list.forEach((el, i) => assert.equal(i+1, el)); 60 | }); 61 | }); 62 | 63 | describe('cloning', function() { 64 | it('should support cloning self', async function() { 65 | const trace = await utils.compileAndRunUntilReport('cloning'); 66 | assert.deepEqual(trace, ['1', '3', '2']); 67 | }); 68 | 69 | it('should support cloning others', async function() { 70 | const trace = await utils.compileAndRun('clone-other'); 71 | assert.equal(trace, 'success'); 72 | }); 73 | }); 74 | 75 | describe('forking', function() { 76 | it('should support forking', async function() { 77 | const trace = await utils.compileAndRunUntilReport('fork'); 78 | assert.deepEqual(trace, ['1', '3', '2']); 79 | }); 80 | }); 81 | 82 | describe('doIf', function() { 83 | 84 | describe('basic compilation', function() { 85 | it('should correctly detect the body', async function() { 86 | const result = await utils.compileAndRun('empty-if-cond'); 87 | }); 88 | }); 89 | 90 | describe('repeat loop', function() { 91 | [true, false].forEach(cond => { 92 | const COND_FN = '2'; 93 | const COND_FN_LOOP = '2.5'; 94 | const IF_BODY = '3'; 95 | const AFTER_IF = '4'; 96 | const CONC_CODE = '1'; 97 | 98 | describe(`async conditions (${cond})`, function() { 99 | let trace; 100 | before(async () => { 101 | trace = await utils.compileAndRun(`do-if-${cond}-async`); 102 | }); 103 | 104 | it('should evaluate the condition once', function() { 105 | const evalCount = trace.filter(i => i === COND_FN).length; 106 | assert.equal(evalCount, 1); 107 | }); 108 | 109 | it('should evaluate loop 5 times', function() { 110 | const evalCount = trace.filter(i => i === COND_FN_LOOP).length; 111 | assert.equal(evalCount, 5); 112 | }); 113 | 114 | it('should alternate btwn loops', function() { 115 | trace.forEach((value, index) => { 116 | if (value === COND_FN_LOOP) { 117 | assert.notEqual(COND_FN_LOOP, trace[index + 1]) 118 | } 119 | }); 120 | }); 121 | 122 | it(`should ${cond ? 'not ' : ''}evaluate after if`, function() { 123 | assert.equal(!trace.find(i => i == AFTER_IF), cond); 124 | }); 125 | 126 | it(`should ${cond ? '': 'not '}evaluate body of if statement`, function() { 127 | assert.equal(!!trace.find(i => i == IF_BODY), cond); 128 | }); 129 | 130 | }); 131 | 132 | describe(`sync conditions (${cond})`, function() { 133 | let trace; 134 | before(async () => { 135 | trace = await utils.compileAndRun(`do-if-${cond}-sync`); 136 | }); 137 | 138 | it('should evaluate the condition once', function() { 139 | const evalCount = trace.filter(i => i === COND_FN).length; 140 | assert.equal(evalCount, 1); 141 | }); 142 | 143 | it('should evaluate condition before loop runs twice', function() { 144 | assert.notDeepEqual(trace.slice(0, 2), ['1', '1']); 145 | }); 146 | 147 | it(`should ${cond ? 'not ' : ''}evaluate after if`, function() { 148 | assert.equal(!trace.find(i => i == AFTER_IF), cond); 149 | }); 150 | 151 | it(`should ${cond ? '': 'not '}evaluate body of if statement`, function() { 152 | assert.equal(!!trace.find(i => i == IF_BODY), cond); 153 | }); 154 | }); 155 | }); 156 | }); 157 | 158 | describe('repeatUntil', function() { 159 | it('should support async conditions (true)', async function() { 160 | const result = await utils.compileAndRun('do-if-true-async-repeat-until') 161 | assert.equal(result, true); 162 | }); 163 | 164 | it('should support async conditions (false)', async function() { 165 | const result = await utils.compileAndRun('do-if-false-async-repeat-until') 166 | assert.equal(result, false); 167 | }); 168 | }); 169 | }); 170 | }); 171 | -------------------------------------------------------------------------------- /test/do-report-fns.spec.js: -------------------------------------------------------------------------------- 1 | describe('doReport', function() { 2 | const snap2js = require('..'); 3 | const assert = require('assert'); 4 | const utils = require('./utils'); 5 | const env = snap2js.newContext(); 6 | 7 | it('should return the result of reifyReporter', async () => { 8 | let content = utils.getProjectXml('do-report-simple'); 9 | let fn = snap2js.compile(content); 10 | 11 | // Run the script and check the value of 'result' (should be true) 12 | env.bubble = result => { 13 | assert.equal(result, true); 14 | }; 15 | 16 | await fn(env); 17 | }); 18 | 19 | it('should return the result in reifyScript', async () => { 20 | let content = utils.getProjectXml('do-report-reify-script'); 21 | let fn = snap2js.compile(content); 22 | 23 | // Run the script and check the value of 'result' (should be true) 24 | env.bubble = result => { 25 | assert.equal(result, true); 26 | }; 27 | 28 | await fn(env); 29 | }); 30 | 31 | it('should return the result in (async) reifyScript', async () => { 32 | let content = utils.getProjectXml('do-report-reify-script-async'); 33 | let fn = snap2js.compile(content); 34 | env.bubble = result => { 35 | assert.equal(result, true); 36 | }; 37 | await fn(env); 38 | }); 39 | 40 | it('should ignore the doReport in custom command blocks', async () => { 41 | let content = utils.getProjectXml('do-report-nop'); 42 | let fn = snap2js.compile(content); 43 | 44 | // Run the script and check the value of 'result' (should be true) 45 | env.bubble = result => { 46 | assert.equal(result, true); 47 | }; 48 | 49 | await fn(env); 50 | }); 51 | 52 | it('should return result in custom reporter blocks', done => { 53 | let content = utils.getProjectXml('do-report-custom-reporter'); 54 | let fn = snap2js.compile(content); 55 | 56 | // Run the script and check the value of 'result' (should be true) 57 | env.bubble = result => { 58 | assert.equal(result, true); 59 | done(); 60 | }; 61 | 62 | fn(env); 63 | }); 64 | 65 | it('should return result in custom predicate blocks', done => { 66 | let content = utils.getProjectXml('do-report-anon-pred'); 67 | let fn = snap2js.compile(content); 68 | 69 | // Run the script and check the value of 'result' (should be true) 70 | env.bubble = result => { 71 | assert.equal(result, true); 72 | done(); 73 | }; 74 | 75 | fn(env); 76 | }); 77 | 78 | }); 79 | -------------------------------------------------------------------------------- /test/errors.spec.js: -------------------------------------------------------------------------------- 1 | describe('errors', function() { 2 | const snap2js = require('..'); 3 | const assert = require('assert'); 4 | const utils = require('./utils'); 5 | 6 | describe('basic', function() { 7 | it('should be able to catch error', async function(done) { 8 | let cxt = snap2js.newContext(); 9 | let content = utils.getContextXml('list-err'); 10 | let bin = snap2js.compile(content); 11 | let fn = bin(cxt); 12 | try { 13 | await fn(); 14 | } catch (err) { 15 | if (err instanceof TypeError) { 16 | done(); 17 | } else { 18 | done(err); 19 | } 20 | } 21 | }); 22 | 23 | it.skip('should return the failing block id', async function() { 24 | let cxt = snap2js.newContext(); 25 | 26 | let content = utils.getContextXml('list-err'); 27 | console.log(snap2js.transpile(content)); 28 | 29 | let bin = snap2js.compile(content); 30 | let fn = bin(cxt); 31 | console.log(fn.toString()); 32 | try { 33 | await fn(); 34 | console.log('no error'); 35 | } catch (e) { 36 | console.log('err', e); 37 | } 38 | }); 39 | }); 40 | 41 | describe('in repeat loop', function() { 42 | it('should be able to catch error', function(done) { 43 | let cxt = snap2js.newContext(); 44 | let content = utils.getContextXml('list-err-loop'); 45 | let bin = snap2js.compile(content); 46 | let fn = bin(cxt); 47 | fn().catch(err => done()) 48 | }); 49 | }); 50 | 51 | describe('in repeat until', function() { 52 | it('should be able to catch error', function(done) { 53 | let cxt = snap2js.newContext(); 54 | let content = utils.getContextXml('list-err-until'); 55 | let bin = snap2js.compile(content); 56 | let fn = bin(cxt); 57 | fn().catch(err => done()) 58 | }); 59 | }); 60 | 61 | describe('in forever', function() { 62 | it('should be able to catch error', function(done) { 63 | let cxt = snap2js.newContext(); 64 | let content = utils.getContextXml('list-err-forever'); 65 | let bin = snap2js.compile(content); 66 | let fn = bin(cxt); 67 | fn().catch(err => done()) 68 | }); 69 | }); 70 | 71 | describe('in warp', function() { 72 | it('should be able to catch error', async function(done) { 73 | let cxt = snap2js.newContext(); 74 | let content = utils.getContextXml('list-err-warp'); 75 | let bin = snap2js.compile(content); 76 | let fn = bin(cxt); 77 | try { 78 | await fn(); 79 | } catch (err) { 80 | if (err instanceof TypeError) { 81 | done(); 82 | } 83 | } 84 | }); 85 | }); 86 | 87 | describe('undef var', function() { 88 | it('should be able to catch error', async function(done) { 89 | let cxt = snap2js.newContext(); 90 | let content = utils.getContextXml('undef-var-err'); 91 | let bin = snap2js.compile(content); 92 | let fn = bin(cxt); 93 | try { 94 | await fn(); 95 | } catch (err) { 96 | assert(err instanceof Error); 97 | done(); 98 | } 99 | }); 100 | }); 101 | }); 102 | -------------------------------------------------------------------------------- /test/functions.spec.js: -------------------------------------------------------------------------------- 1 | // Test that we can compile functions from snap into callable JS functions 2 | describe('functions', function() { 3 | const _ = require('lodash'); 4 | const snap2js = require('..'); 5 | const assert = require('assert'); 6 | const env = snap2js.newContext(); 7 | const path = require('path'); 8 | const utils = require('./utils'); 9 | 10 | // Create a test for each of the test cases that we have 11 | const INPUT_OUTPUTS = {}; 12 | INPUT_OUTPUTS['args-local-recursive'] = 13 | INPUT_OUTPUTS['args-global-recursive'] = { 14 | input: [6], 15 | output: 720 16 | }; 17 | INPUT_OUTPUTS['local-recursive'] = 18 | INPUT_OUTPUTS['global-recursive'] = { 19 | output: 120 20 | }; 21 | INPUT_OUTPUTS['fib'] = { 22 | output: 1 23 | }; 24 | INPUT_OUTPUTS['args-local-custom'] = 25 | INPUT_OUTPUTS['args-global-custom'] = 26 | INPUT_OUTPUTS['global-custom'] = 27 | INPUT_OUTPUTS['local-custom'] = 28 | INPUT_OUTPUTS['simple-fn'] = { 29 | output: 'szia világ' 30 | }; 31 | INPUT_OUTPUTS['args-simple-fn'] = { 32 | input: ['TEST '], 33 | output: 'TEST világ' 34 | }; 35 | INPUT_OUTPUTS['outercontext-var'] = { 36 | output: ['a', 'b', 'c'] 37 | }; 38 | 39 | utils.getContextNames().forEach((name, i) => { 40 | describe(name, () => { 41 | let content = null; 42 | 43 | before(() => content = utils.getContextXml(name)); 44 | 45 | it(`should be able to compile code`, function() { 46 | let code = snap2js.transpile(content); 47 | }); 48 | 49 | it(`should be able to compile code w/o warp`, function() { 50 | snap2js.transpile(content, false, {allowWarp: false}); 51 | }); 52 | 53 | it(`should create a js function`, function() { 54 | let fn = snap2js.compile(content); 55 | assert.equal(typeof fn, 'function'); 56 | }); 57 | 58 | it(`should return a callable js fn`, function() { 59 | let fn = snap2js.compile(content); 60 | assert.equal(typeof fn(env), 'function'); 61 | }); 62 | 63 | if (INPUT_OUTPUTS[name]) { 64 | let input = INPUT_OUTPUTS[name].input; 65 | let output = INPUT_OUTPUTS[name].output; 66 | it(`should return fn where ${input || '()'}->${output}`, async function() { 67 | const factory = snap2js.compile(content); 68 | const fn = factory(env); 69 | if (input) { 70 | assert.deepEqual(await fn.apply(null, input), output); 71 | } else { 72 | assert.deepEqual(await fn(), output); 73 | } 74 | }); 75 | } 76 | }); 77 | }); 78 | 79 | describe('anon statement fn', () => { 80 | before(() => content = utils.getContextXml('build-list')); 81 | 82 | it(`should return list 1-100`, async function() { 83 | let factory = snap2js.compile(content); 84 | let env = snap2js.newContext(); 85 | let fn = factory(env); 86 | const result = await fn(); 87 | result.forEach((n, i) => assert.equal(n, i+1)); 88 | }); 89 | }); 90 | 91 | describe('implicit arguments', () => { 92 | it('should default to empty slots as inputs', async function() { 93 | const result = await utils.compileAndRun('implicit-inputs'); 94 | assert.equal(result, 9); 95 | }); 96 | 97 | it('should not treat empty slots as inputs if specified', async function() { 98 | const result = await utils.compileAndRun('fake-implicit-inputs'); 99 | assert.equal(result, 1); 100 | }); 101 | 102 | it('should not set inputs inside other rings', async function() { 103 | const result = await utils.compileAndRun('nested-implicit-inputs'); 104 | assert.equal(result, 16); 105 | }); 106 | }); 107 | 108 | describe.skip('closures', () => { 109 | before(() => content = utils.getContextXml('fib')) 110 | 111 | it(`should maintain project state between calls`, function() { 112 | let fn = snap2js.compile(content); 113 | let cxt = snap2js.newContext(); 114 | let fib = fn(cxt); 115 | let results = [1, 2, 3, 5, 8, 13]; 116 | results.forEach(num => assert.equal(num, fib())); 117 | }); 118 | 119 | // This is more of a problem with the snap serialization... 120 | it(`should capture global variables w/ primitive fns`, function() { 121 | content = utils.getContextXml('hello-world'); 122 | let factory = snap2js.compile(content); 123 | let cxt = snap2js.newContext(); 124 | let hello = factory(cxt); 125 | assert.equal(hello(), 'I am running on the server'); 126 | }); 127 | 128 | // It should capture the variables in the scope... 129 | it(`should retrieve script variables`, function() { 130 | // TODO 131 | }); 132 | }); 133 | 134 | describe('async fns in variables', function() { 135 | it('should resolve async fn as variable', async function() { 136 | const result = await utils.compileAndRun('aliased-async-fn'); 137 | assert.equal(result, true); 138 | }); 139 | }); 140 | }); 141 | -------------------------------------------------------------------------------- /test/hello-if.spec.js: -------------------------------------------------------------------------------- 1 | describe('hello if-else', function() { 2 | let snap2js = require('..'), 3 | assert = require('assert'), 4 | utils = require('./utils'), 5 | content; 6 | 7 | before(function(){ 8 | content = utils.getProjectXml('if-statement'); 9 | }); 10 | 11 | describe('transpile', function() { 12 | var code; 13 | 14 | before(function() { 15 | code = snap2js.transpile(content); 16 | }); 17 | 18 | it('should contain "bubble" block selector w/ nested fn', function() { 19 | assert(/\bbubble\b/.test(code)); 20 | }); 21 | 22 | }); 23 | 24 | describe('compile', function() { 25 | var bin; 26 | 27 | before(function() { 28 | bin = snap2js.compile(content); 29 | }); 30 | 31 | it('should call "bubble" if "getScale" != 50', function(done) { 32 | var cxt = snap2js.newContext(); 33 | cxt['getScale'] = () => 49; 34 | cxt['bubble'] = function(str) { 35 | assert.equal(str, 'hello world!'); 36 | done(); 37 | }; 38 | bin(cxt); 39 | }); 40 | 41 | it('should call "doSayFor" if "getScale" == 50', function(done) { 42 | var cxt = snap2js.newContext(), 43 | said = false; 44 | 45 | cxt['getScale'] = () => 50; 46 | cxt['bubble'] = function(str) { 47 | assert(false); 48 | }; 49 | cxt['doSayFor'] = async function(str, time) { 50 | assert.equal(time, 2); 51 | assert.equal(str, 'I am small'); 52 | said = true; 53 | }; 54 | cxt['forward'] = function(d) { 55 | assert.equal(d, 10); 56 | assert(said); 57 | done(); 58 | }; 59 | bin(cxt); 60 | }); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /test/hello-joined-world.spec.js: -------------------------------------------------------------------------------- 1 | describe('hello (joined) world', function() { 2 | let snap2js = require('..'), 3 | assert = require('assert'), 4 | utils = require('./utils'), 5 | content; 6 | 7 | before(function(){ 8 | content = utils.getProjectXml('hello-joined-world'); 9 | }); 10 | 11 | describe('transpile', function() { 12 | var code; 13 | 14 | before(function() { 15 | code = snap2js.transpile(content); 16 | }); 17 | 18 | it('should contain "bubble" block selector w/ nested fn', function() { 19 | assert(/\bbubble\b/.test(code)); 20 | }); 21 | 22 | }); 23 | 24 | it('should call "bubble" with "Hello world!"', function(done) { 25 | var cxt = snap2js.newContext(); 26 | 27 | bin = snap2js.compile(content); 28 | cxt['bubble'] = function(str) { 29 | assert.equal(str, 'hello world!'); 30 | done(); 31 | }; 32 | bin(cxt); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /test/hello-world.spec.js: -------------------------------------------------------------------------------- 1 | describe('hello world', function() { 2 | let snap2js = require('..'), 3 | utils = require('./utils'), 4 | assert = require('assert'), 5 | content; 6 | 7 | before(function(){ 8 | content = utils.getProjectXml('hello-world'); 9 | }); 10 | 11 | describe('transpile', function() { 12 | var code; 13 | 14 | before(function() { 15 | code = snap2js.transpile(content); 16 | }); 17 | 18 | it('should generate js code', function() { 19 | assert(code); 20 | }); 21 | 22 | it('should contain "bubble" block selector (fn)', function() { 23 | assert(code.includes('bubble')); 24 | }); 25 | 26 | }); 27 | 28 | it('should call "bubble" with "Hello world!"', function(done) { 29 | var cxt = snap2js.newContext(); 30 | var bin = snap2js.compile(content); 31 | cxt['bubble'] = function(str) { 32 | assert.equal(str, 'Hello world!'); 33 | done(); 34 | }; 35 | bin(cxt); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test/lists.spec.js: -------------------------------------------------------------------------------- 1 | describe('lists', function() { 2 | let snap2js = require('..'), 3 | utils = require('./utils'), 4 | assert = require('assert'), 5 | content; 6 | 7 | before(function(){ 8 | content = utils.getProjectXml('lists'); 9 | }); 10 | 11 | describe('transpile', function() { 12 | var code; 13 | 14 | before(function() { 15 | code = snap2js.transpile(content); 16 | }); 17 | 18 | it('should contain "reportListLength"', function() { 19 | assert(/\breportListLength\b/.test(code)); 20 | }); 21 | 22 | it('should contain doAddToList', function() { 23 | assert(/\bdoAddToList\b/.test(code)); 24 | }); 25 | }); 26 | 27 | describe('compile', function() { 28 | var bin, 29 | cxt, 30 | vals = []; 31 | 32 | // check the 'doSayFor' blocks 33 | // TODO 34 | 35 | // should report length of 2 36 | // should report 'hello' 37 | // should report ['world'] 38 | // should report true 39 | // should report true 40 | before(function() { 41 | cxt = snap2js.newContext(); 42 | cxt['bubble'] = v => vals.push(v); 43 | 44 | bin = snap2js.compile(content); 45 | bin(cxt); 46 | }); 47 | 48 | it('should report length of 2', function() { 49 | assert.equal(vals[0], 2) 50 | }); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /test/looks.spec.js: -------------------------------------------------------------------------------- 1 | describe('looks', function() { 2 | let snap2js = require('..'), 3 | assert = require('assert'), 4 | utils = require('./utils'), 5 | content; 6 | 7 | describe('initial values', function() { 8 | var result; 9 | 10 | before(function() { 11 | content = utils.getProjectXml('costume-size'); 12 | cxt = snap2js.newContext(); 13 | cxt['doReport'] = val => result = val; 14 | bin = snap2js.compile(content); 15 | bin(cxt); 16 | }); 17 | 18 | it('should set costume number to 3', function() { 19 | assert.equal(result[1], 3) 20 | }); 21 | 22 | it('should set size to 66', function() { 23 | assert.equal(result[0], 66) 24 | }); 25 | }); 26 | 27 | describe('all look blocks', function() { 28 | var bin, 29 | cxt; 30 | 31 | before(function() { 32 | content = utils.getProjectXml('all-looks'); 33 | bin = snap2js.compile(content); 34 | }); 35 | 36 | it('should switch to Turtle costume', function(done) { 37 | cxt = snap2js.newContext(); 38 | cxt['doSwitchToCostume'] = costume => { 39 | assert.equal(costume, 'Turtle'); 40 | done(); 41 | }; 42 | bin(cxt); 43 | }); 44 | 45 | it('should change fisheye effect', function(done) { 46 | cxt = snap2js.newContext(); 47 | cxt['changeEffect'] = (effect, amount) => { 48 | assert.equal(effect, 'fisheye'); 49 | assert.equal(amount, 25); 50 | done(); 51 | }; 52 | bin(cxt); 53 | }); 54 | 55 | it('should change scale by 10', function(done) { 56 | cxt = snap2js.newContext(); 57 | cxt['changeScale'] = amount => { 58 | assert.equal(amount, 10); 59 | done(); 60 | }; 61 | bin(cxt); 62 | }); 63 | 64 | it('should go back 1 layer', function(done) { 65 | cxt = snap2js.newContext(); 66 | cxt['goBack'] = amount => { 67 | assert.equal(amount, 1); 68 | done(); 69 | }; 70 | bin(cxt); 71 | }); 72 | 73 | it('should be able to compile v6 blocks', function() { 74 | const content = utils.getProjectXml('all-looksv2'); 75 | snap2js.compile(content); 76 | }) 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /test/misc.spec.js: -------------------------------------------------------------------------------- 1 | describe('misc', function() { 2 | let snap2js = require('..'), 3 | assert = require('assert'), 4 | utils = require('./utils'); 5 | 6 | it('should reference "project" from the stage', function(done) { 7 | const content = utils.getProjectXml('change-x-10x'); 8 | const fn = snap2js.compile(content); 9 | 10 | const env = snap2js.newContext(); 11 | env.__start = project => { 12 | const stage = project.stage; 13 | assert.equal(stage.project, project); 14 | done(); 15 | }; 16 | fn(env) 17 | }); 18 | 19 | it('should compile w/ empty receiver elements', function() { 20 | const content = utils.getContextXml('empty-receiver'); 21 | const fn = snap2js.compile(content); 22 | }); 23 | 24 | it('should hoist sprite declaration before stage logic', function() { 25 | const queryXml = utils.getContextXml('hoist-sprite'); 26 | const factory = snap2js.compile(queryXml); 27 | const env = snap2js.newContext(); 28 | const fn = factory(env); // throws an error if undefined sprites 29 | }); 30 | 31 | it('should ignore comments', function() { 32 | const content = utils.getProjectXml('comments'); 33 | const fn = snap2js.compile(content); 34 | const env = snap2js.newContext(); 35 | fn(env); 36 | }); 37 | 38 | it('should get random numbers', async function() { 39 | const queryXml = utils.getContextXml('get-random-numbers'); 40 | const factory = snap2js.compile(queryXml); 41 | const env = snap2js.newContext(); 42 | const fn = factory(env); 43 | const randoms = await fn([1,2]); 44 | assert.equal(randoms.length, 2); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /test/motion.spec.js: -------------------------------------------------------------------------------- 1 | describe('motion', function() { 2 | let snap2js = require('..'), 3 | assert = require('assert'), 4 | utils = require('./utils'), 5 | content; 6 | 7 | describe('position', function() { 8 | var bin, 9 | cxt, 10 | result; 11 | 12 | before(function() { 13 | content = utils.getProjectXml('initial-set-change-pos'); 14 | cxt = snap2js.newContext(); 15 | cxt['doReport'] = val => result = val; 16 | bin = snap2js.compile(content); 17 | bin(cxt); 18 | }); 19 | 20 | it('should have x position of 100', function() { 21 | assert.equal(result[0], 100); 22 | }); 23 | 24 | it('should have y position of 320', function() { 25 | assert.equal(result[1], 320); 26 | }); 27 | 28 | it('should have direction of 58', function() { 29 | assert.equal(result[2], 58); 30 | }); 31 | }); 32 | 33 | describe('turn-forward', function() { 34 | var bin, 35 | cxt, 36 | result; 37 | 38 | before(function() { 39 | content = utils.getProjectXml('forward-angle'); 40 | cxt = snap2js.newContext(); 41 | cxt['doReport'] = val => result = val; 42 | bin = snap2js.compile(content); 43 | bin(cxt); 44 | }); 45 | 46 | it('should update x,y after angled move forward', function() { 47 | assert.equal(Math.floor(result[0]), 168); 48 | assert.equal(Math.floor(result[1]), 545); 49 | }); 50 | }); 51 | 52 | describe('all motion blocks', function() { 53 | before(function() { 54 | content = utils.getProjectXml('all-motion'); 55 | }); 56 | 57 | it('should compile without error', function() { 58 | snap2js.compile(content); 59 | }); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /test/new-blocks-1320.spec.js: -------------------------------------------------------------------------------- 1 | describe('new-blocks-1320', function() { 2 | let result; 3 | const utils = require('./utils'); 4 | const snap2js = require('..'); 5 | const assert = require('assert'); 6 | const createTest = (pair, index) => { 7 | it('should ' + pair[0] + ' correctly', function() { 8 | assert.equal(result[index], pair[1]); 9 | }); 10 | }; 11 | 12 | describe('new blocks 1320', function() { 13 | before(async function() { 14 | result = await utils.compileAndRun('new-blocks-1320'); 15 | }); 16 | 17 | it('should support variadic sum', function() { 18 | assert.deepStrictEqual(result[0], [14, 21, 0]); 19 | }); 20 | it('should support variadic product', function() { 21 | assert.deepStrictEqual(result[1], [126, 1680, 1]); 22 | }); 23 | it('should support variadic min', function() { 24 | assert.deepStrictEqual(result[2], [1, 2, Infinity]); 25 | }); 26 | it('should support variadic max', function() { 27 | assert.deepStrictEqual(result[3], [10, 12, -Infinity]); 28 | }); 29 | it('should support atan2', function() { 30 | assert(Math.abs(result[4][0] - 53.13010235) <= 1e-5); 31 | assert(Math.abs(result[4][1] + 53.13010235) <= 1e-5); 32 | assert(Math.abs(result[4][2] - 126.8698976) <= 1e-5); 33 | assert(Math.abs(result[4][3] + 126.8698976) <= 1e-5); 34 | }); 35 | it('should support <=', function() { 36 | assert.deepStrictEqual(result[5], [true, true, false]); 37 | }); 38 | it('should support >=', function() { 39 | assert.deepStrictEqual(result[6], [false, true, true]); 40 | }); 41 | it('should support !=', function() { 42 | assert.deepStrictEqual(result[7], [true, false, true]); 43 | }); 44 | it('should support length list attribute', function() { 45 | assert.deepStrictEqual(result[8], [10, 4, 3]); 46 | }); 47 | it('should support rank list attribute', function() { 48 | assert.deepStrictEqual(result[9], [1, 2, 3]); 49 | }); 50 | it('should support dimensions list attribute', function() { 51 | assert.deepStrictEqual(result[10], [[10], [4, 10], [3, 2, 10]]); 52 | }); 53 | it('should support flatten list attribute', function() { 54 | assert.deepStrictEqual(result[11], [ 55 | [1,2,3,4,5,6,7,8,9,10], 56 | [1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10], 57 | ['1',1,2,3,4,5,6,7,8,9,10,'3'], 58 | ]); 59 | }); 60 | it('should support columns list attribute', function() { 61 | assert.deepStrictEqual(result[12], [ 62 | [[1,2,3,4,5,6,7,8,9,10]], 63 | [[1,1,1,1], [2,2,2,2], [3,3,3,3], [4,4,4,4], [5,5,5,5], [6,6,6,6], [7,7,7,7], [8,8,8,8], [9,9,9,9], [10,10,10,10]], 64 | [['1','3',''], [[1,2,3,4,5,6,7,8,9,10], '3', '']], 65 | ]); 66 | }); 67 | it('should support reverse list attribute', function() { 68 | assert.deepStrictEqual(result[13], [ 69 | [10,9,8,7,6,5,4,3,2,1], 70 | [[1,2,3,4,5,6,7,8,9,10],[1,2,3,4,5,6,7,8,9,10],[1,2,3,4,5,6,7,8,9,10],[1,2,3,4,5,6,7,8,9,10]], 71 | [[], '3', ['1', [1,2,3,4,5,6,7,8,9,10]]], 72 | ]); 73 | }); 74 | it('should support lines list attribute', function() { 75 | assert.deepStrictEqual(result[14], '1\n2\n3\n4\n5\n6\n7\n8\n9\n10'); 76 | }); 77 | it('should support csv list attribute', function() { 78 | assert.deepStrictEqual(result[15], [ 79 | '1,2,3,4,5,6,7,8,9,10', 80 | '1,2,3,4,5,6,7,8,9,10\n1,2,3,4,5,6,7,8,9,10\n1,2,3,4,5,6,7,8,9,10\n1,2,3,4,5,6,7,8,9,10', 81 | 'test world,"anoth, test","ag""ain",ano\'ther,"""test"""', 82 | ]); 83 | }); 84 | it('should support json list attribute', function() { 85 | assert.deepStrictEqual(result[16], [ 86 | '[1,2,3,4,5,6,7,8,9,10]', 87 | '[[1,2,3,4,5,6,7,8,9,10],[1,2,3,4,5,6,7,8,9,10],[1,2,3,4,5,6,7,8,9,10],[1,2,3,4,5,6,7,8,9,10]]', 88 | '[["1",[1,2,3,4,5,6,7,8,9,10]],"3",[]]', 89 | ]); 90 | }); 91 | it('should support reshape list', function() { 92 | assert.deepStrictEqual(result[17], [ 93 | [[1,2],[3,4],[5,6],[7,8],[9,10]], 94 | [ 95 | [[[1,2,3,4,5],[6,7,8,9,10]], [[1,2,3,4,5],[6,7,8,9,10]]], 96 | [[[1,2,3,4,5],[6,7,8,9,10]], [[1,2,3,4,5],[6,7,8,9,10]]], 97 | [[[1,2,3,4,5],[6,7,8,9,10]], [[1,2,3,4,5],[6,7,8,9,10]]], 98 | [[[1,2,3,4,5],[6,7,8,9,10]], [[1,2,3,4,5],[6,7,8,9,10]]], 99 | [[[1,2,3,4,5],[6,7,8,9,10]], [[1,2,3,4,5],[6,7,8,9,10]]], 100 | ], 101 | [[[1,2,3,4,5]], [[6,7,8,9,10]]], 102 | ]); 103 | }); 104 | it('should support combinations (really cartesian product) of lists', function() { 105 | assert.deepStrictEqual(result[18], [ 106 | [1,7], [1,8], [1,9], [1,10], [1,11], 107 | [2,7], [2,8], [2,9], [2,10], [2,11], 108 | [3,7], [3,8], [3,9], [3,10], [3,11], 109 | [4,7], [4,8], [4,9], [4,10], [4,11], 110 | ]); 111 | assert.deepStrictEqual(result[19], [ 112 | [1,7,13], [1,7,14], 113 | [1,8,13], [1,8,14], 114 | [1,9,13], [1,9,14], 115 | [1,10,13], [1,10,14], 116 | [1,11,13], [1,11,14], 117 | 118 | [2,7,13], [2,7,14], 119 | [2,8,13], [2,8,14], 120 | [2,9,13], [2,9,14], 121 | [2,10,13], [2,10,14], 122 | [2,11,13], [2,11,14], 123 | 124 | [3,7,13], [3,7,14], 125 | [3,8,13], [3,8,14], 126 | [3,9,13], [3,9,14], 127 | [3,10,13], [3,10,14], 128 | [3,11,13], [3,11,14], 129 | 130 | [4,7,13], [4,7,14], 131 | [4,8,13], [4,8,14], 132 | [4,9,13], [4,9,14], 133 | [4,10,13], [4,10,14], 134 | [4,11,13], [4,11,14], 135 | ]); 136 | }); 137 | }); 138 | }); 139 | -------------------------------------------------------------------------------- /test/operators.spec.js: -------------------------------------------------------------------------------- 1 | describe('operators', function() { 2 | let result; 3 | const utils = require('./utils'); 4 | const snap2js = require('..'); 5 | const assert = require('assert'); 6 | const createTest = (pair, index) => { 7 | it('should ' + pair[0] + ' correctly', function() { 8 | assert.equal(result[index], pair[1]); 9 | }); 10 | }; 11 | 12 | describe('basic math', function() { 13 | before(async function() { 14 | result = await utils.compileAndRun('basic-math'); 15 | }); 16 | 17 | it('should add numbers', function() { 18 | assert.equal(result[0], 6); 19 | }); 20 | 21 | it('should subtract numbers', function() { 22 | assert.equal(result[1], -2); 23 | }); 24 | 25 | it('should multiply numbers', function() { 26 | assert.equal(result[2], 8); 27 | }); 28 | 29 | it('should divide numbers', function() { 30 | assert.equal(result[3], 17); 31 | }); 32 | 33 | it('should mod numbers', function() { 34 | assert.equal(result[4], 3); 35 | }); 36 | 37 | it('should round numbers', function() { 38 | assert.equal(result[5], 2); 39 | }); 40 | }); 41 | 42 | describe('reportMonadic', function() { 43 | it('should support computing the sqrt from context', function() { 44 | const content = utils.getContextXml('report-monadic'); 45 | const fn = snap2js.compile(content)(snap2js.newContext()); 46 | const result = fn(); 47 | assert.equal(+result, 3); 48 | }); 49 | 50 | describe('all ops', function() { 51 | let fns = null; 52 | before(async () => { 53 | fns = await utils.compileAndRun('report-monadic'); 54 | }); 55 | 56 | const threeRads = 3*Math.PI/180; 57 | [ 58 | ['sqrt'], 59 | ['abs'], 60 | ['ceiling'], 61 | ['floor'], 62 | ['sin', () => Math.sin(threeRads)], 63 | ['cos', () => Math.cos(threeRads)], 64 | ['tan', () => Math.tan(threeRads)], 65 | ['asin', () => 180/Math.PI*Math.asin(0.5)], 66 | ['acos', () => 180/Math.PI*Math.acos(0.5)], 67 | ['atan', x => 180/Math.PI*Math.atan(x)], 68 | ['ln', Math.log], 69 | ['log', x => Math.log(x) / Math.LN10], 70 | ['e^', Math.exp], 71 | ['10^', Math.pow.bind(null, 10)], 72 | ['undefined', () => 0] 73 | ].forEach((pair, i) => { 74 | const [name, grader] = pair; 75 | it(`should compute ${name}`, function() { 76 | const blockFn = fns[i]; 77 | const result = blockFn(); 78 | const expected = grader ? grader(3) : 3; 79 | assert.equal(result, expected); 80 | }); 81 | }); 82 | }); 83 | }); 84 | 85 | describe('random', function() { 86 | before(async function() { 87 | result = await utils.compileAndRun('random'); 88 | }); 89 | 90 | it('should not generate equal random numbers', function() { 91 | var areSame = !result.find(v => v !== result[0]); 92 | assert(result[0] !== undefined); 93 | assert(!areSame); 94 | }); 95 | 96 | }); 97 | 98 | describe('comparisons', function() { 99 | before(async function() { 100 | result = await utils.compileAndRun('comparisons'); 101 | }); 102 | 103 | [ 104 | ['2 < 3', true], 105 | ['11 < 3', false], 106 | ['2 > 3', false], 107 | ['2 > 1', true], 108 | ['3 == 11', false], 109 | ['11 == 11.0', true] 110 | ].forEach(createTest); 111 | }); 112 | 113 | describe('text', function() { 114 | before(async function() { 115 | result = await utils.compileAndRun('string-ops'); 116 | }); 117 | 118 | it('should join words "hello", "world", "there"', function() { 119 | assert.equal(result[0], 'hello world there'); 120 | }); 121 | 122 | it('should split words "hello world"', function() { 123 | assert.equal(result[1][0], 'hello'); 124 | assert.equal(result[1][1], 'world'); 125 | }); 126 | 127 | it('should get the length of "world"', function() { 128 | assert.equal(result[2], 5); 129 | }); 130 | 131 | }); 132 | 133 | describe('boolean', function() { 134 | before(async function() { 135 | result = await utils.compileAndRun('boolean'); 136 | }); 137 | 138 | it('should support AND', function() { 139 | assert.equal(result[0], true); 140 | }); 141 | 142 | it('should support OR', function() { 143 | assert.equal(result[1], false); 144 | }); 145 | 146 | it('should support NOT', function() { 147 | assert.equal(result[2], false); 148 | }); 149 | 150 | it('should support nested NOT', function() { 151 | assert.equal(result[3], true); 152 | }); 153 | 154 | it('should support "identical to"', function() { 155 | assert.equal(result[4], false); 156 | }); 157 | 158 | it('should support "is a number"', function() { 159 | assert.equal(result[6], true); 160 | }); 161 | 162 | it('should support creating js fns', function() { 163 | assert.equal(typeof result[7], 'function'); 164 | }); 165 | 166 | }); 167 | 168 | describe('functions', function() { 169 | 170 | describe('js', function() { 171 | before(async function() { 172 | result = await utils.compileAndRun('js-fn'); 173 | }); 174 | 175 | it('should return a fn', function() { 176 | assert.equal(typeof result, 'function'); 177 | }); 178 | 179 | it('should eval correctly (add 5 to input)', function() { 180 | assert.equal(result(3), 8); 181 | }); 182 | }); 183 | 184 | describe('invalid js names', function() { 185 | before(async function() { 186 | result = await utils.compileAndRun('fn-names'); 187 | }); 188 | 189 | it('should return a fn', function() { 190 | assert.equal(typeof result, 'function'); 191 | }); 192 | 193 | it('should eval correctly (and+and+not)', function() { 194 | }); 195 | }); 196 | 197 | describe('predicate-ring', function() { 198 | before(async function() { 199 | result = await utils.compileAndRun('predicate-ring'); 200 | }); 201 | 202 | it('should return a fn', function() { 203 | assert.equal(typeof result, 'function'); 204 | }); 205 | 206 | it('should eval true correctly (and+and+not)', function() { 207 | const value = result(true, true, false); 208 | assert.equal(value, true); 209 | }); 210 | 211 | it('should eval false correctly (and+and+not)', function() { 212 | const value = result(true, true, true) 213 | assert.equal(value, false); 214 | }); 215 | }); 216 | 217 | describe('reporter-ring', function() { 218 | before(async function() { 219 | result = await utils.compileAndRun('reporter-ring'); 220 | }); 221 | 222 | it('should return a fn', function() { 223 | assert.equal(typeof result, 'function'); 224 | }); 225 | 226 | it('should eval correctly (sum inputs)', function() { 227 | const value = result(3, 5) 228 | assert.equal(value, 8); 229 | }); 230 | }); 231 | 232 | describe('cmd-ring', function() { 233 | let bin; 234 | 235 | before(function() { 236 | bin = utils.getCompiledVersionOf('cmd-ring'); 237 | }); 238 | 239 | it('should return a fn', async function() { 240 | const result = await utils.compileAndRun('cmd-ring'); 241 | assert.equal(typeof result, 'function', result.toString()); 242 | }); 243 | 244 | it('should move forward by 100', function(done) { 245 | var cxt = snap2js.newContext(); 246 | cxt['forward'] = value => { 247 | assert.equal(value, 100); 248 | done(); 249 | }; 250 | bin(cxt); 251 | }); 252 | 253 | it('should turn 45 degrees', function(done) { 254 | var cxt = snap2js.newContext(); 255 | cxt['turn'] = value => { 256 | assert.equal(value, 45); 257 | done(); 258 | }; 259 | bin(cxt); 260 | }); 261 | }); 262 | }); 263 | 264 | }); 265 | -------------------------------------------------------------------------------- /test/pen.spec.js: -------------------------------------------------------------------------------- 1 | describe('pen', function() { 2 | let snap2js = require('..'), 3 | assert = require('assert'), 4 | utils = require('./utils'), 5 | checkBlockValue = require('./utils').checkBlockValue, 6 | content; 7 | 8 | describe('all blocks (old)', function() { 9 | var bin, 10 | cxt; 11 | 12 | before(function() { 13 | content = utils.getProjectXml('all-pen'); 14 | bin = snap2js.compile(content); 15 | }); 16 | 17 | it('should set pen color to reddish', function(done) { 18 | checkBlockValue(bin, 'setColor', '145,26,68,1', done); 19 | }); 20 | 21 | it('should change hue by 10', function(done) { 22 | checkBlockValue(bin, 'changeHue', 10, done); 23 | }); 24 | 25 | it('should set hue to 0', function(done) { 26 | checkBlockValue(bin, 'setHue', 0, done); 27 | }); 28 | 29 | it('should set brightness to 100', function(done) { 30 | checkBlockValue(bin, 'setBrightness', 100, done); 31 | }); 32 | 33 | it('should change brightness by 10', function(done) { 34 | checkBlockValue(bin, 'changeBrightness', 10, done); 35 | }); 36 | 37 | it('should change size by 1', function(done) { 38 | checkBlockValue(bin, 'changeSize', 1, done); 39 | }); 40 | 41 | it('should set size to 1', function(done) { 42 | checkBlockValue(bin, 'setSize', 1, done); 43 | }); 44 | 45 | it('should compile v6 blocks', function() { 46 | content = utils.getProjectXml('all-penv2'); 47 | bin = snap2js.compile(content); 48 | }); 49 | }); 50 | 51 | }); 52 | 53 | -------------------------------------------------------------------------------- /test/run-test-cases.js: -------------------------------------------------------------------------------- 1 | describe.skip('test cases', function() { 2 | let fs = require('fs'), 3 | path = require('path'), 4 | TEST_CASE_DIR = path.join(__dirname, 'test-cases'); 5 | 6 | //fs.readdirSync(TEST_CASE_DIR) 7 | //.forEach(filename => { 8 | //}); 9 | }); 10 | -------------------------------------------------------------------------------- /test/sanitize.spec.js: -------------------------------------------------------------------------------- 1 | describe('sanitize', function() { 2 | const utils = require('./utils'); 3 | const snap2jsUtils = require('../src/utils'); 4 | const snap2js = require('..'); 5 | const assert = require('assert'); 6 | 7 | it('should compile with custom blocks containing `', async function() { 8 | const content = utils.getContextXml('back-tick-var'); 9 | const context = snap2js.newContext(); 10 | 11 | const fn = snap2js.compile(content)(context); 12 | await fn(); 13 | }); 14 | 15 | it('should compile with invalid var,sprite names, values', function() { 16 | const content = utils.getProjectXml('sanitize-inputs'); 17 | const context = snap2js.newContext(); 18 | 19 | const fn = snap2js.compile(content); 20 | fn(context); 21 | }); 22 | 23 | it('should be able to sanitize arrays', function() { 24 | const original = ['hey', '-world']; 25 | const safeList = snap2jsUtils.sanitize(original); 26 | const list = eval(safeList); 27 | assert.deepEqual(list, original); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test/say-and-thinking.spec.js: -------------------------------------------------------------------------------- 1 | describe('say and think', function() { 2 | const snap2js = require('..'); 3 | const assert = require('assert'); 4 | const utils = require('./utils'); 5 | const isRightAfter = utils.isRightAfter; 6 | 7 | describe('compile', function() { 8 | let totalOrder; 9 | 10 | before(async function() { 11 | totalOrder = await utils.compileAndRunUntilReport('say-and-think'); 12 | console.log(totalOrder); 13 | }); 14 | 15 | it('should not yield w/ doSay', function() { 16 | assert(isRightAfter(totalOrder, '1', '2')); 17 | }); 18 | 19 | it('should yield w/ doSayFor', function() { 20 | assert(!isRightAfter(totalOrder, '2', '3')); 21 | }); 22 | 23 | it('should not yield w/ doThink', function() { 24 | assert(isRightAfter(totalOrder, '3', '4')); 25 | }); 26 | 27 | it('should yield w/ doThinkFor', function() { 28 | assert(!isRightAfter(totalOrder, '4', '5')); 29 | }); 30 | 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /test/sensing.spec.js: -------------------------------------------------------------------------------- 1 | describe('sensing', function() { 2 | const snap2js = require('..'); 3 | const assert = require('assert'); 4 | const utils = require('./utils'); 5 | let content; 6 | 7 | describe('all blocks', function() { 8 | var bin, 9 | cxt; 10 | 11 | before(function() { 12 | const content = utils.getProjectXml('all-sensing'); 13 | bin = snap2js.compile(content); 14 | }); 15 | 16 | it('should ask "what\'s your name?"', function(done) { 17 | cxt = snap2js.newContext(); 18 | cxt['doAsk'] = msg => { 19 | assert.equal(msg, 'what\'s your name?'); 20 | done(); 21 | }; 22 | bin(cxt); 23 | }); 24 | 25 | it('should set turbo mode on', function(done) { 26 | checkBlockValue(bin, 'doSetFastTracking', true, done); 27 | }); 28 | 29 | it('should check space key pressed', function(done) { 30 | checkBlockValue(bin, 'reportKeyPressed', 'space', done); 31 | }); 32 | 33 | it('should get distance to mouse-pointer', function(done) { 34 | checkBlockValue(bin, 'reportDistanceTo', 'mouse-pointer', done); 35 | }); 36 | 37 | it('should get direction of Sprite', function(done) { 38 | cxt = snap2js.newContext('nop'); 39 | cxt['reportAttributeOf'] = (attr, obj) => { 40 | assert.equal(attr, 'direction'); 41 | assert.equal(obj, 'Sprite'); 42 | done(); 43 | }; 44 | bin(cxt); 45 | }); 46 | 47 | it('should get neighbors', function(done) { 48 | checkBlockValue(bin, 'reportGet', 'neighbors', done); 49 | }); 50 | 51 | it('should get snap url', function(done) { 52 | checkBlockValue(bin, 'reportURL', 'snap.berkeley.edu', done); 53 | }); 54 | 55 | it('should get date', function(done) { 56 | checkBlockValue(bin, 'reportDate', 'date', done); 57 | }); 58 | 59 | function checkBlockValue(bin, fn, val, done) { 60 | var cxt = snap2js.newContext('nop'); 61 | cxt[fn] = arg => { 62 | assert.equal(arg, val); 63 | done(); 64 | }; 65 | bin(cxt); 66 | }; 67 | 68 | it('should compile v6 blocks', function() { 69 | const content = utils.getProjectXml('all-sensingv2'); 70 | snap2js.compile(content); 71 | }); 72 | 73 | }); 74 | 75 | describe('timer', function() { 76 | before(function() { 77 | content = utils.getProjectXml('timer'); 78 | }); 79 | 80 | it('should have a timer value of 0.1', function(done) { 81 | var cxt = snap2js.newContext(); 82 | cxt['doReport'] = time => { 83 | console.log(time); 84 | assert(time <= 0.15); 85 | assert(time >= 0.05); 86 | done(); 87 | }; 88 | bin = snap2js.compile(content); 89 | bin(cxt); 90 | }); 91 | 92 | }); 93 | 94 | describe('date', function() { 95 | var result, 96 | startTime, 97 | endTime; 98 | 99 | before(function(done) { 100 | content = utils.getProjectXml('date'); 101 | var cxt = snap2js.newContext(); 102 | cxt['doReport'] = res => { 103 | result = res; 104 | console.log(result); 105 | endTime = new Date(); 106 | done(); 107 | }; 108 | bin = snap2js.compile(content); 109 | startTime = new Date(); 110 | bin(cxt); 111 | }); 112 | 113 | it('should return the correct date', function() { 114 | checkDate(0, 'getDate'); 115 | }); 116 | 117 | it('should return the correct year', function() { 118 | checkDate(1, 'getFullYear'); 119 | }); 120 | 121 | it('should return the correct month', function() { 122 | checkDate(2, 'getMonth'); 123 | }); 124 | 125 | it('should return the correct day of week', function() { 126 | checkDate(3, 'getDay'); 127 | }); 128 | 129 | it('should return the correct hour', function() { 130 | checkDate(4, 'getHours'); 131 | }); 132 | 133 | it('should return the correct minutes', function() { 134 | checkDate(5, 'getMinutes'); 135 | }); 136 | 137 | it('should return the correct seconds', function() { 138 | checkDate(6, 'getSeconds'); 139 | }); 140 | 141 | it('should return the correct time', function() { 142 | checkDate(7, 'getTime'); 143 | }); 144 | 145 | function checkDate(index, fn) { 146 | assert(startTime[fn]() <= result[index], 147 | `expected ${startTime[fn]()} <= ${result[index]}`); 148 | assert(endTime[fn]() >= result[index], 149 | `expected ${startTime[fn]()} >= ${result[index]}`); 150 | } 151 | 152 | }); 153 | }); 154 | 155 | -------------------------------------------------------------------------------- /test/sounds.spec.js: -------------------------------------------------------------------------------- 1 | describe('sounds', function() { 2 | let snap2js = require('..'), 3 | assert = require('assert'), 4 | checkBlockValue = require('./utils').checkBlockValue, 5 | utils = require('./utils'), 6 | content; 7 | 8 | describe('all blocks', function() { 9 | var bin, 10 | cxt; 11 | 12 | before(function() { 13 | content = utils.getProjectXml('all-sounds'); 14 | bin = snap2js.compile(content); 15 | }); 16 | 17 | it('should play "cat" sound', function(done) { 18 | checkBlockValue(bin, 'playSound', 'Cat', done); 19 | }); 20 | 21 | it('should play "cat" sound until done', function(done) { 22 | checkBlockValue(bin, 'doPlaySoundUntilDone', 'Cat', done); 23 | }); 24 | 25 | it('should rest for 0.2 beats', function(done) { 26 | checkBlockValue(bin, 'doRest', 0.2, done); 27 | }); 28 | 29 | it('should play 60 for 0.5 beats', function(done) { 30 | cxt = snap2js.newContext('nop'); 31 | cxt['doPlayNote'] = (note, duration) => { 32 | assert.equal(note, 60); 33 | assert.equal(duration, 0.5); 34 | done(); 35 | }; 36 | bin(cxt); 37 | }); 38 | 39 | it('should set tempo to 60', function(done) { 40 | checkBlockValue(bin, 'doSetTempo', 60, done); 41 | }); 42 | 43 | it('should change tempo by 20', function(done) { 44 | checkBlockValue(bin, 'doChangeTempo', 20, done); 45 | }); 46 | 47 | it('should have a tempo of 80', function(done) { 48 | cxt = snap2js.newContext(); 49 | cxt['doReport'] = tempo => { 50 | assert.equal(tempo, 80); 51 | done(); 52 | }; 53 | bin(cxt); 54 | }); 55 | 56 | it('should compile v6 blocks', function() { 57 | const content = utils.getProjectXml('all-soundsv2'); 58 | snap2js.compile(content); 59 | }); 60 | }); 61 | }); 62 | 63 | -------------------------------------------------------------------------------- /test/sprite.spec.js: -------------------------------------------------------------------------------- 1 | describe('sprite', function() { 2 | const utils = require('./utils'); 3 | const assert = require('assert'); 4 | const snap2js = require('..'); 5 | 6 | it('should record draggable', function() { 7 | let bin = utils.getCompiledVersionOf('draggable'); 8 | let cxt = snap2js.newContext(); 9 | cxt['doReport'] = function() { 10 | assert.equal(this.draggable, true); 11 | }; 12 | bin(cxt); 13 | }); 14 | 15 | it('should record rotation style', function() { 16 | let bin = utils.getCompiledVersionOf('draggable'); 17 | let cxt = snap2js.newContext(); 18 | cxt['doReport'] = function() { 19 | assert.equal(this.rotation, 1); 20 | }; 21 | bin(cxt); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/test-cases/contexts/back-tick-var.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/test-cases/contexts/build-list.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 83 | 86 | 105 | 118 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | -------------------------------------------------------------------------------- /test/test-cases/contexts/empty-receiver.xml: -------------------------------------------------------------------------------- 1 | #1 2 | -------------------------------------------------------------------------------- /test/test-cases/contexts/forever-timeout.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/test-cases/contexts/get-field-context.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | row 4 | 5 | 6 | 7 | 1 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/test-cases/contexts/hello-world.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | I am running on the 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/test-cases/contexts/list-err-forever.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/test-cases/contexts/list-err-loop.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/test-cases/contexts/list-err-until.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/test-cases/contexts/list-err-warp.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/test-cases/contexts/list-err.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/test-cases/contexts/outercontext-var.xml: -------------------------------------------------------------------------------- 1 | abc 2 | -------------------------------------------------------------------------------- /test/test-cases/contexts/recursive-data.xml: -------------------------------------------------------------------------------- 1 | I am a sprite variable!11 2 | messagemsgI am a global variable!12 3 | -------------------------------------------------------------------------------- /test/test-cases/contexts/rename-upvar.xml: -------------------------------------------------------------------------------- 1 | datamessagemsg
datehome_teamaway_teamhome_scoreaway_scorecitycountry1872-11-30ScotlandEngland00GlasgowScotland1873-03-08EnglandScotland42LondonEngland1874-03-07ScotlandEngland21GlasgowScotland1875-03-06EnglandScotland22LondonEngland1876-03-04ScotlandEngland30GlasgowScotland1876-03-25ScotlandWales40GlasgowScotland1877-03-03EnglandScotland13LondonEngland1877-03-05WalesScotland02WrexhamWales1878-03-02ScotlandEngland72GlasgowScotland
2 | -------------------------------------------------------------------------------- /test/test-cases/contexts/repeat-timeout.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/test-cases/contexts/repeat-until-timeout.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/test-cases/contexts/report-monadic.xml: -------------------------------------------------------------------------------- 1 | 9 -------------------------------------------------------------------------------- /test/test-cases/contexts/transform.xml: -------------------------------------------------------------------------------- 1 | row2 2 | -------------------------------------------------------------------------------- /test/test-cases/contexts/undef-var-err.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/test-cases/contexts/upvar-custom-block.xml: -------------------------------------------------------------------------------- 1 | messagemsg
110i
2 | -------------------------------------------------------------------------------- /test/test-cases/contexts/warp-forever-timeout.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/test-cases/projects/all-controlv2.xml: -------------------------------------------------------------------------------- 1 | messagemsgi110100 2 | -------------------------------------------------------------------------------- /test/test-cases/projects/all-looksv2.xml: -------------------------------------------------------------------------------- 1 | messagemsg 2 | -------------------------------------------------------------------------------- /test/test-cases/projects/all-penv2.xml: -------------------------------------------------------------------------------- 1 | messagemsg 2 | -------------------------------------------------------------------------------- /test/test-cases/projects/all-sensingv2.xml: -------------------------------------------------------------------------------- 1 | messagemsg 2 | -------------------------------------------------------------------------------- /test/test-cases/projects/all-soundsv2.xml: -------------------------------------------------------------------------------- 1 | messagemsg 2 | -------------------------------------------------------------------------------- /test/test-cases/projects/all-variablesv2.xml: -------------------------------------------------------------------------------- 1 | messagemsgtest6110test123110test110test201100 2 | -------------------------------------------------------------------------------- /test/test-cases/projects/comments.xml: -------------------------------------------------------------------------------- 1 | messagemsgadd comment here...<script><block collabId="item_0" s="receiveGo"></block></script>item_-15012<_0>item_0<script><block collabId="item_1" s="forward"><l>10</l></block></script>271176item_0bottomblock<_0>item_1<comment collabId="item_2" x="154" y="20" w="90" collapsed="false">add comment here...</comment>item_0<_0>item_2<comment collabId="item_3" x="219" y="179" w="90" collapsed="false">add comment here...</comment>item_-1219179<_0>item_3 2 | -------------------------------------------------------------------------------- /test/test-cases/projects/do-if-false-async-repeat-until.xml: -------------------------------------------------------------------------------- 1 | data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKAAAAB4CAYAAAB1ovlvAAACpklEQVR4Xu3XQaoaQQBF0ep96FyXo+CGnLggxdXoRnTQASFk8POTkEFfaI7jhkfdOtg6zfM8Dx8FogITgFF5s58CAIKQFgAwzW8cQAbSAgCm+Y0DyEBaAMA0v3EAGUgLAJjmNw4gA2kBANP8xgFkIC0AYJrfOIAMpAUATPMbB5CBtACAaX7jADKQFgAwzW8cQAbSAgCm+Y0DyEBaAMA0v3EAGUgLAJjmNw4gA2kBANP8xgFkIC0AYJrfOIAMpAUATPMbB5CBtACAaX7jADKQFgAwzW8cQAbSAgCm+Y0DyEBaAMA0v3EAGUgLAJjmNw4gA2kBANP8xgFkIC0AYJrfOIAMpAUATPMbB5CBtACAaX7jADKQFgAwzW8cQAbSAgCm+Y0DyEBaAMA0v3EAGUgLAPhN/v1+P87n8zidTukFrX0cwD/c8PF4HPM8f554PB7jfr+P7Xa7dhOLng/Av+Q+HA5fnpimabzf73G9Xhe9rDWOAfifAF+v17jdbms0seiZAPzHV/Dz+fy8gjebzaIXtPYxAL+54d1uNy6Xy/jdK3jtKJY8H4BL1rb19ff0/PNvnjgKBAV8AwbRTf4qACANaQEA0/zGAWQgLQBgmt84gAykBQBM8xsHkIG0AIBpfuMAMpAWADDNbxxABtICAKb5jQPIQFoAwDS/cQAZSAsAmOY3DiADaQEA0/zGAWQgLQBgmt84gAykBQBM8xsHkIG0AIBpfuMAMpAWADDNbxxABtICAKb5jQPIQFoAwDS/cQAZSAsAmOY3DiADaQEA0/zGAWQgLQBgmt84gAykBQBM8xsHkIG0AIBpfuMAMpAWADDNbxxABtICAKb5jQPIQFoAwDS/cQAZSAsAmOY3DiADaQEA0/zGAWQgLQBgmt84gAykBQBM8xv/ASteArcSbq6lAAAAAElFTkSuQmCCdata:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAeAAAAFoCAYAAACPNyggAAAOhUlEQVR4Xu3VwQkAAAjEMN1/abewn7jAQRC64wgQIECAAIF3gX1fNEiAAAECBAiMAHsCAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQIHLFxAWmhEwHPAAAAAElFTkSuQmCCfalse -------------------------------------------------------------------------------- /test/test-cases/projects/do-if-true-async-repeat-until.xml: -------------------------------------------------------------------------------- 1 | data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKAAAAB4CAYAAAB1ovlvAAACpklEQVR4Xu3XQaoaQQBF0ep96FyXo+CGnLggxdXoRnTQASFk8POTkEFfaI7jhkfdOtg6zfM8Dx8FogITgFF5s58CAIKQFgAwzW8cQAbSAgCm+Y0DyEBaAMA0v3EAGUgLAJjmNw4gA2kBANP8xgFkIC0AYJrfOIAMpAUATPMbB5CBtACAaX7jADKQFgAwzW8cQAbSAgCm+Y0DyEBaAMA0v3EAGUgLAJjmNw4gA2kBANP8xgFkIC0AYJrfOIAMpAUATPMbB5CBtACAaX7jADKQFgAwzW8cQAbSAgCm+Y0DyEBaAMA0v3EAGUgLAJjmNw4gA2kBANP8xgFkIC0AYJrfOIAMpAUATPMbB5CBtACAaX7jADKQFgAwzW8cQAbSAgCm+Y0DyEBaAMA0v3EAGUgLAPhN/v1+P87n8zidTukFrX0cwD/c8PF4HPM8f554PB7jfr+P7Xa7dhOLng/Av+Q+HA5fnpimabzf73G9Xhe9rDWOAfifAF+v17jdbms0seiZAPzHV/Dz+fy8gjebzaIXtPYxAL+54d1uNy6Xy/jdK3jtKJY8H4BL1rb19ff0/PNvnjgKBAV8AwbRTf4qACANaQEA0/zGAWQgLQBgmt84gAykBQBM8xsHkIG0AIBpfuMAMpAWADDNbxxABtICAKb5jQPIQFoAwDS/cQAZSAsAmOY3DiADaQEA0/zGAWQgLQBgmt84gAykBQBM8xsHkIG0AIBpfuMAMpAWADDNbxxABtICAKb5jQPIQFoAwDS/cQAZSAsAmOY3DiADaQEA0/zGAWQgLQBgmt84gAykBQBM8xsHkIG0AIBpfuMAMpAWADDNbxxABtICAKb5jQPIQFoAwDS/cQAZSAsAmOY3DiADaQEA0/zGAWQgLQBgmt84gAykBQBM8xv/ASteArcSbq6lAAAAAElFTkSuQmCCdata:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAeAAAAFoCAYAAACPNyggAAAOhUlEQVR4Xu3VwQkAAAjEMN1/abewn7jAQRC64wgQIECAAIF3gX1fNEiAAAECBAiMAHsCAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQIHLFxAWmhEwHPAAAAAElFTkSuQmCCfalse -------------------------------------------------------------------------------- /test/test-cases/projects/do-report-reify-script.xml: -------------------------------------------------------------------------------- 1 | data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKAAAAB4CAYAAAB1ovlvAAAGyklEQVR4Xu2dfUhVdxjHvzeUTaVbw7RWKixI0pbYWGA2KyiQVlRedY0NB5vsj8nYGMRomyMmMRdCc64kYoOo2BuDFZr9I7bmWCzXmrnRMqtp1F5vabNQr3rHOeLdri8p5e152PM9/6j3nvN7vi8fzj3n+Mf1BIPBILgxAaEEPARQKHmOdRMggARBNIExAWxpaUF9fb2osJHDExISsGHDBni9XlW6KObuEhgF4OHDh5GRkQHn0tDj8Yz782pNJW6c+BgD7hWk5+5UjHn0v5emOe+dgv9mwNXT0NCA4uLiCMzjkhIJhAF4/PhxzJ07d0L4HDj/fHsFkjMew5GY1ejt7UV7ezv81/yIuT9mUsePhDslJQXx8fGjMij6pRzwePBrcX1o3QULFkhkxZkRSCAMwO3bt8Pn8932zDd8ZhyoXI3GzDdx68Z1RD9+Bs2d9ej1T8PV74LwzgPiHx7/5nqwP4jr5z3oah9ESvZ9iPIGXGu3OgawsvMtBKdFh6x2d3fjBf8udDxzNKQrPT09AlFwSYkEwgDctm0bCgoKxj2DdXR0wDlTORBO270WjZml8Pu/wKWV3+PyNwHsKTyN+fPnh3wUN84a09PsbzejtKQCsbGx7vs/dzWi4kye+3vmyVRExRahpKQE1dXV7mtF7e/g0tO1IV2LFy+WyIozI5BAGIClpaXIz893xzQ3N2PNmjXo7+/H/v374ZReVVWFnTt3uu9H71nvAhhccQKnrtVhXVc1fOufCJP4xqks/HarDb//MIDCR1/BV/1V7vsvz2hwrzP/uw3DeuVkHzbG7nABXLZsGYqKilwAz28+FNp9yZIlEYiCS0okEAbg1q1bQwCeO3cOiYmJ6OzsxMWLF90z3969e1FRUeHqjPlgkwtg70MfoSXuLJbPfhLPpe4K8/D814kYDA66r61NfglHLw8B+GHOXyO8BlHcmOC+FlUfg8w5r+PAgQMufMNnwLP5n4eOWbp0qURWnBmBBMIA3LJlSwjAiWZN35fvXgN6+/xoeKTS3d2B8NnU9/F3wI99519Es3/8RzmvZhxC6ozl+KnzS7zbUjB0Jx0ENra+hiuBoY/m4c05A7Zs/CT0t3Nm5Pb/SCAMQOfslpWVNSlnDxzcjB9X7MD1G93w9v6Bm2nH8GnT6UkdO9ZO6YGZyE0qweX+8Od8m9p2YHp0EKfXHQwdlpOTc8dzeKCuBMIAdO44m5qaJnUX/OBnT2He9ChcTczC7vY56OnpmdRxEz1fHH4/Fj0om3lsKC2PB025+9z1nUc+ubm5ulKkmjtOYNSD6MrKSixatGjCBbOzsxEXFzfhflOxg/OMsbW1FV1dXVi1ahVmzRr77noqZnGNe5sA/xd8b/PmtBEJEEAiIZoAARSNn8MJIBkQTYAAisbP4QSQDIgmQABF4+dwAkgGRBMggKLxczgBJAOiCRBA0fg5nACSAdEECKBo/BxOAMmAaAIEUDR+DieAZEA0AQIoGj+HE0AyIJoAARSNn8MJIBkQTYAAisbP4QSQDIgmQABF4+dwAkgGRBMggKLxczgBJAOiCRBA0fg5nACSAdEECKBo/BxOAMmAaAIEUDR+DieAZEA0AQIoGj+HE0AyIJoAARSNn8MJIBkQTYAAisbP4QSQDIgmQABF4+dwAkgGRBMggKLxczgBJAOiCRBA0fg5nACOw0BaWhrKyspQWFhISiKYAAG8Tbg+nw/Ot3c6W1tbG+rq6pCcnBzBOuwtTQAn6DwvL2/UHs7XxgYCAdTU1NgjZoodE8A7BLCvrw+1tbVTXIe95QjgJD+CL1y44H4EJyUl2aMkgo4J4DjhLly4EOXl5RjrIziCfZhbmgCaq1yXYQKoqw9zagigucp1GSaAuvowp4YAmqtcl2ECqKsPc2oIoLnKdRkmgLr6MKeGAJqrXJdhAqirD3NqCKC5ynUZJoC6+jCnhgCaq1yXYQKoqw9zagigucp1GSaAuvowp4YAmqtcl2ECqKsPc2oIoLnKdRkmgLr6MKeGAJqrXJdhAqirD3NqCKC5ynUZJoC6+jCnhgCaq1yXYQKoqw9zagigucp1GSaAuvowp4YAmqtcl2ECqKsPc2oIoLnKdRkmgLr6MKeGAJqrXJdhAqirD3NqCKC5ynUZJoC6+jCnhgCaq1yXYQKoqw9zagigucp1GSaAuvowp4YAmqtcl2ECqKsPc2oIoLnKdRkmgLr6MKeGAJqrXJdhAqirD3NqCKC5ynUZJoC6+jCnhgCaq1yXYQKoqw9zagigucp1GSaAuvowp4YAmqtcl2ECqKsPc2oIoLnKdRkmgLr6MKeGAJqrXJfhfwAHtaDGAB8grgAAAABJRU5ErkJggg==data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAeAAAAFoCAYAAACPNyggAAAOhUlEQVR4Xu3VwQkAAAjEMN1/abewn7jAQRC64wgQIECAAIF3gX1fNEiAAAECBAiMAHsCAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQIHLFxAWmhEwHPAAAAAElFTkSuQmCC#14 -------------------------------------------------------------------------------- /test/test-cases/projects/draggable.xml: -------------------------------------------------------------------------------- 1 | data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKAAAAB4CAYAAAB1ovlvAAADCElEQVR4Xu3VMW5aARRE0e992L1ZDgUbcuMNeT14IRRJ5CJy4yQaRhnJOr+F9544XMHDj1/P4SEwEngQ4Eje2Q8BAQphKiDAKb/jAtTAVECAU37HBaiBqYAAp/yOC1ADUwEBTvkdF6AGpgICnPI7LkANTAUEOOV3XIAamAoIcMrvuAA1MBUQ4JTfcQFqYCogwCm/4wLUwFRAgFN+xwWogamAAKf8jgtQA1MBAU75HRegBqYCApzyOy5ADUwFBDjld1yAGpgKCHDK77gANTAVEOCU33EBamAqIMApv+MC1MBUQIBTfscFqIGpgACn/I4LUANTAQFO+R0XoAamAgKc8jsuQA1MBQQ45XdcgBqYCghwyu+4ADUwFRDglN9xAWpgKiDAKb/jAtTAVECAU37HBaiBqYAAp/yOC1ADUwEBfsF/Op2Ol5eX43K5TL+g735cgH/4hs/n8+9Xr9fr8fb2djw9PX33Jv7r5xPgX7g/R/j5rbfb7SNIz30CAhTgfQXdOS3Af/wLfn9///jFe3x8vJPc+GcBAX7Rw/Pz8/H6+np89Rcso46AADuOtoQCAgzhjHUEBNhxtCUUEGAIZ6wjIMCOoy2hgABDOGMdAQF2HG0JBQQYwhnrCAiw42hLKCDAEM5YR0CAHUdbQgEBhnDGOgIC7DjaEgoIMIQz1hEQYMfRllBAgCGcsY6AADuOtoQCAgzhjHUEBNhxtCUUEGAIZ6wjIMCOoy2hgABDOGMdAQF2HG0JBQQYwhnrCAiw42hLKCDAEM5YR0CAHUdbQgEBhnDGOgIC7DjaEgoIMIQz1hEQYMfRllBAgCGcsY6AADuOtoQCAgzhjHUEBNhxtCUUEGAIZ6wjIMCOoy2hgABDOGMdAQF2HG0JBQQYwhnrCAiw42hLKCDAEM5YR0CAHUdbQgEBhnDGOgIC7DjaEgoIMIQz1hEQYMfRllBAgCGcsY6AADuOtoQCAgzhjHUEBNhxtCUUEGAIZ6wjIMCOoy2hgABDOGMdAQF2HG0JBQQYwhnrCAiw42hLKPAT12n8qPmYkCQAAAAASUVORK5CYII=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAeAAAAFoCAYAAACPNyggAAAOhUlEQVR4Xu3VwQkAAAjEMN1/abewn7jAQRC64wgQIECAAIF3gX1fNEiAAAECBAiMAHsCAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQIHLFxAWmhEwHPAAAAAElFTkSuQmCC]]>item_0283203<_0>item_0_2item_0_2203283item_0]]>]]>item_08348<_0>item_2]]>304193item_2bottomblock<_0>item_3]]>item_0283203<_0>item_0_2item_0_2203283item_0]]>]]>item_08348<_0>item_2]]>304193item_2bottomblock<_0>item_3 -------------------------------------------------------------------------------- /test/test-cases/projects/empty-if-cond.xml: -------------------------------------------------------------------------------- 1 | messagemsg<script><block collabId="item_0" s="receiveGo"></block></script>item_-15012<_0>item_0<script><block collabId="item_1" s="doIf"><l/><script></script></block></script>271176item_0bottomblock<_0>item_1<script><block collabId="item_2" s="forward"><l>10</l></block></script>278.000001195.000001item_1/1/bottomslot<_0>item_2 2 | -------------------------------------------------------------------------------- /test/test-cases/projects/fork.xml: -------------------------------------------------------------------------------- 1 | messagemsg#123132 2 | -------------------------------------------------------------------------------- /test/test-cases/projects/hello-world.xml: -------------------------------------------------------------------------------- 1 | data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKAAAAB4CAYAAAB1ovlvAAADCElEQVR4Xu3VMW5aARRE0e992L1ZDgUbcuMNeT14IRRJ5CJy4yQaRhnJOr+F9544XMHDj1/P4SEwEngQ4Eje2Q8BAQphKiDAKb/jAtTAVECAU37HBaiBqYAAp/yOC1ADUwEBTvkdF6AGpgICnPI7LkANTAUEOOV3XIAamAoIcMrvuAA1MBUQ4JTfcQFqYCogwCm/4wLUwFRAgFN+xwWogamAAKf8jgtQA1MBAU75HRegBqYCApzyOy5ADUwFBDjld1yAGpgKCHDK77gANTAVEOCU33EBamAqIMApv+MC1MBUQIBTfscFqIGpgACn/I4LUANTAQFO+R0XoAamAgKc8jsuQA1MBQQ45XdcgBqYCghwyu+4ADUwFRDglN9xAWpgKiDAKb/jAtTAVECAU37HBaiBqYAAp/yOC1ADUwEBfsF/Op2Ol5eX43K5TL+g735cgH/4hs/n8+9Xr9fr8fb2djw9PX33Jv7r5xPgX7g/R/j5rbfb7SNIz30CAhTgfQXdOS3Af/wLfn9///jFe3x8vJPc+GcBAX7Rw/Pz8/H6+np89Rcso46AADuOtoQCAgzhjHUEBNhxtCUUEGAIZ6wjIMCOoy2hgABDOGMdAQF2HG0JBQQYwhnrCAiw42hLKCDAEM5YR0CAHUdbQgEBhnDGOgIC7DjaEgoIMIQz1hEQYMfRllBAgCGcsY6AADuOtoQCAgzhjHUEBNhxtCUUEGAIZ6wjIMCOoy2hgABDOGMdAQF2HG0JBQQYwhnrCAiw42hLKCDAEM5YR0CAHUdbQgEBhnDGOgIC7DjaEgoIMIQz1hEQYMfRllBAgCGcsY6AADuOtoQCAgzhjHUEBNhxtCUUEGAIZ6wjIMCOoy2hgABDOGMdAQF2HG0JBQQYwhnrCAiw42hLKCDAEM5YR0CAHUdbQgEBhnDGOgIC7DjaEgoIMIQz1hEQYMfRllBAgCGcsY6AADuOtoQCAgzhjHUEBNhxtCUUEGAIZ6wjIMCOoy2hgABDOGMdAQF2HG0JBQQYwhnrCAiw42hLKPAT12n8qPmYkCQAAAAASUVORK5CYII=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAeAAAAFoCAYAAACPNyggAAAOhUlEQVR4Xu3VwQkAAAjEMN1/abewn7jAQRC64wgQIECAAIF3gX1fNEiAAAECBAiMAHsCAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQIHLFxAWmhEwHPAAAAAElFTkSuQmCC 2 | -------------------------------------------------------------------------------- /test/test-cases/projects/lists.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/test-cases/projects/nested-lists.xml: -------------------------------------------------------------------------------- 1 | data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKAAAAB4CAYAAAB1ovlvAAACpklEQVR4Xu3XQaoaQQBF0ep96FyXo+CGnLggxdXoRnTQASFk8POTkEFfaI7jhkfdOtg6zfM8Dx8FogITgFF5s58CAIKQFgAwzW8cQAbSAgCm+Y0DyEBaAMA0v3EAGUgLAJjmNw4gA2kBANP8xgFkIC0AYJrfOIAMpAUATPMbB5CBtACAaX7jADKQFgAwzW8cQAbSAgCm+Y0DyEBaAMA0v3EAGUgLAJjmNw4gA2kBANP8xgFkIC0AYJrfOIAMpAUATPMbB5CBtACAaX7jADKQFgAwzW8cQAbSAgCm+Y0DyEBaAMA0v3EAGUgLAJjmNw4gA2kBANP8xgFkIC0AYJrfOIAMpAUATPMbB5CBtACAaX7jADKQFgAwzW8cQAbSAgCm+Y0DyEBaAMA0v3EAGUgLAPhN/v1+P87n8zidTukFrX0cwD/c8PF4HPM8f554PB7jfr+P7Xa7dhOLng/Av+Q+HA5fnpimabzf73G9Xhe9rDWOAfifAF+v17jdbms0seiZAPzHV/Dz+fy8gjebzaIXtPYxAL+54d1uNy6Xy/jdK3jtKJY8H4BL1rb19ff0/PNvnjgKBAV8AwbRTf4qACANaQEA0/zGAWQgLQBgmt84gAykBQBM8xsHkIG0AIBpfuMAMpAWADDNbxxABtICAKb5jQPIQFoAwDS/cQAZSAsAmOY3DiADaQEA0/zGAWQgLQBgmt84gAykBQBM8xsHkIG0AIBpfuMAMpAWADDNbxxABtICAKb5jQPIQFoAwDS/cQAZSAsAmOY3DiADaQEA0/zGAWQgLQBgmt84gAykBQBM8xsHkIG0AIBpfuMAMpAWADDNbxxABtICAKb5jQPIQFoAwDS/cQAZSAsAmOY3DiADaQEA0/zGAWQgLQBgmt84gAykBQBM8xv/ASteArcSbq6lAAAAAElFTkSuQmCCdata:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAeAAAAFoCAYAAACPNyggAAAOhUlEQVR4Xu3VwQkAAAjEMN1/abewn7jAQRC64wgQIECAAIF3gX1fNEiAAAECBAiMAHsCAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQICLAfIECAAAECgYAAB+gmCRAgQICAAPsBAgQIECAQCAhwgG6SAAECBAgIsB8gQIAAAQKBgAAH6CYJECBAgIAA+wECBAgQIBAICHCAbpIAAQIECAiwHyBAgAABAoGAAAfoJgkQIECAgAD7AQIECBAgEAgIcIBukgABAgQIHLFxAWmhEwHPAAAAAElFTkSuQmCC -------------------------------------------------------------------------------- /test/timeout.spec.js: -------------------------------------------------------------------------------- 1 | describe('callMaybeAsync', function() { 2 | const utils = require('./utils'); 3 | const assert = require('assert'); 4 | const snap2js = require('..'); 5 | 6 | const context = snap2js.newContext(); 7 | const TIMEOUT = 50; 8 | let lastMsg = null; 9 | 10 | context.bubble = res => { 11 | lastMsg = res; 12 | }; 13 | context.__start = function(project) { 14 | project.startTime = Date.now(); 15 | }; 16 | 17 | context.doYield = function() { 18 | // Wait for any args to resolve 19 | if (Date.now() > (this.project.startTime + TIMEOUT)) { 20 | throw new Error('Timeout Exceeded'); 21 | } 22 | }; 23 | 24 | [ 25 | 'forever-timeout', 26 | 'warp-forever-timeout', 27 | 'repeat-timeout', 28 | 'repeat-until-timeout', 29 | ].forEach(filename => { 30 | describe(filename, function() { 31 | it('should return the timeout error', async function() { 32 | const content = utils.getContextXml(filename); 33 | const fn = snap2js.compile(content, {allowWarp: false})(context); 34 | try { 35 | await fn(); 36 | } catch (err) { 37 | assert(err.message.includes('Timeout Exceeded'), err.message); 38 | } 39 | }); 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | const snap2js = require('..'); 2 | const utils = require('../src/utils'); 3 | const assert = require('assert'); 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const TEST_CASE_DIR = path.join(__dirname, 'test-cases'); 7 | 8 | // Helpers 9 | function isRightAfter(list, a, b) { 10 | var i = list.indexOf(a); 11 | return list[i+1] === b; 12 | } 13 | 14 | function isRightBefore(list, a, b) { 15 | var i = list.indexOf(b); 16 | return list[i-1] === a; 17 | } 18 | 19 | function checkBlockValue(bin, fn, val, done) { 20 | var cxt = snap2js.newContext('nop'); 21 | cxt[fn] = arg => { 22 | assert.equal(arg, val); 23 | done(); 24 | }; 25 | bin(cxt); 26 | } 27 | 28 | function getCompiledVersionOf(projectName) { 29 | var content = getProjectXml(projectName); 30 | return snap2js.compile(content); 31 | } 32 | 33 | async function compileAndRun(projectName) { 34 | let lastReportedValue = null; 35 | const cxt = snap2js.newContext(); 36 | cxt['doReport'] = val => { 37 | lastReportedValue = val; 38 | return val; 39 | }; 40 | 41 | const bin = getCompiledVersionOf(projectName); 42 | await bin(cxt); 43 | return lastReportedValue; 44 | } 45 | 46 | function compileAndRunUntilReport(projectName) { 47 | const deferred = utils.defer(); 48 | const cxt = snap2js.newContext(); 49 | cxt['doReport'] = val => { 50 | deferred.resolve(val); 51 | return val; 52 | }; 53 | 54 | const bin = getCompiledVersionOf(projectName); 55 | bin(cxt); 56 | return deferred.promise; 57 | } 58 | 59 | function getProjectPaths() { 60 | return fs.readdirSync(path.join(TEST_CASE_DIR, 'projects')) 61 | .map(name => path.join(TEST_CASE_DIR, 'projects', name)); 62 | } 63 | 64 | function getContextNames() { 65 | return fs.readdirSync(path.join(TEST_CASE_DIR, 'contexts')) 66 | .map(name => name.replace(/\.xml$/, '')); 67 | } 68 | 69 | function getProjectXml(projectName) { 70 | return fs.readFileSync(path.join(TEST_CASE_DIR, 'projects', projectName + '.xml')); 71 | } 72 | 73 | function getContextXml(name) { 74 | return fs.readFileSync(path.join(TEST_CASE_DIR, 'contexts', name + '.xml')); 75 | } 76 | 77 | module.exports = { 78 | isRightAfter, 79 | isRightBefore, 80 | getCompiledVersionOf, 81 | compileAndRun, 82 | compileAndRunUntilReport, 83 | checkBlockValue, 84 | 85 | getProjectXml, 86 | getProjectPaths, 87 | getContextXml, 88 | getContextNames 89 | }; 90 | -------------------------------------------------------------------------------- /test/variables.spec.js: -------------------------------------------------------------------------------- 1 | describe('variables', function() { 2 | let snap2js = require('..'), 3 | assert = require('assert'), 4 | utils = require('./utils'), 5 | content; 6 | 7 | describe('initial values', function() { 8 | var bin, 9 | cxt, 10 | values; 11 | 12 | before(function() { 13 | content = utils.getProjectXml('initial-variables'); 14 | cxt = snap2js.newContext(); 15 | 16 | values = []; 17 | cxt['bubble'] = value => values.push(value); 18 | bin = snap2js.compile(content); 19 | bin(cxt); 20 | }); 21 | 22 | it('should first say 14', function() { 23 | assert.equal(values[0], 14); 24 | }); 25 | 26 | it('should second say list 1,2,3', function() { 27 | assert.equal(values[1][0], 1); 28 | assert.equal(values[1][1], 2); 29 | assert.equal(values[1][2], 3); 30 | }); 31 | 32 | it('should load variables with double quotes', function() { 33 | let content = utils.getContextXml('quote-var-val'); 34 | cxt = snap2js.newContext(); 35 | bin = snap2js.compile(content); 36 | // This was throwing an exception before issue #56 37 | bin(cxt); 38 | }); 39 | 40 | it('should load variables with block values', async function() { 41 | await utils.compileAndRun('initial-var-fn') 42 | cxt = snap2js.newContext(); 43 | }); 44 | }); 45 | 46 | describe('basic blocks', function() { 47 | before(function(){ 48 | content = utils.getProjectXml('variables'); 49 | }); 50 | 51 | describe('transpile', function() { 52 | var code; 53 | 54 | before(function() { 55 | code = snap2js.transpile(content); 56 | }); 57 | 58 | it('should contain "doSetVar"', function() { 59 | assert(/\bdoSetVar\b/.test(code)); 60 | }); 61 | 62 | it('should contain "doChangeVar"', function() { 63 | assert(/\bdoChangeVar\b/.test(code)); 64 | }); 65 | }); 66 | 67 | describe('compile', function() { 68 | var bin, 69 | cxt, 70 | xVal = 0; 71 | 72 | before(function() { 73 | cxt = snap2js.newContext(); 74 | cxt['setXPosition'] = v => xVal = v; 75 | cxt['changeXPosition'] = v => xVal += v; 76 | 77 | bin = snap2js.compile(content); 78 | }); 79 | 80 | it('should set a to 12', function(done) { 81 | cxt['doSetVar'] = (name, v) => { 82 | if (name === 'a' && v === '14') done(); 83 | }; 84 | bin(cxt); 85 | }); 86 | }); 87 | }); 88 | 89 | describe('nested lists', function() { 90 | let result; 91 | 92 | before(done => { 93 | content = utils.getProjectXml('nested-lists'); 94 | const cxt = snap2js.newContext(); 95 | const bin = snap2js.compile(content); 96 | cxt['bubble'] = val => { 97 | console.log('value', val); 98 | result = val; 99 | done(); 100 | }; 101 | bin(cxt); 102 | }); 103 | 104 | it('should parse string value', function() { 105 | assert.equal(result[0], '1'); 106 | }); 107 | 108 | it('should parse nested list values', function() { 109 | assert.equal(result[1][0], '2'); 110 | assert.equal(result[1][1], '3'); 111 | }); 112 | 113 | it('should parse nested x2 list values', function() { 114 | assert.equal(result[1][2][0], '4'); 115 | }); 116 | }); 117 | 118 | describe('cons/cdr', function() { 119 | before(function() { 120 | content = utils.getProjectXml('cons-cdr'); 121 | var cxt = snap2js.newContext(); 122 | var bin = snap2js.compile(content); 123 | cxt['doReport'] = val => result = val; 124 | bin(cxt); 125 | }); 126 | 127 | it('should parse cdr', function() { 128 | assert.equal(result[0], '5'); 129 | assert.equal(result[1], '6'); 130 | assert.equal(result[2], '7'); 131 | }); 132 | 133 | }); 134 | 135 | describe('all blocks', function() { 136 | var result, 137 | cxt; 138 | 139 | before(function() { 140 | content = utils.getProjectXml('all-variables'); 141 | var bin = snap2js.compile(content) 142 | cxt = snap2js.newContext(); 143 | cxt['doReport'] = val => result = val; 144 | bin(cxt); 145 | }); 146 | 147 | it('should set cat to 11', function() { 148 | assert.equal(result[5], 11); 149 | }); 150 | 151 | it('should return length of list', function() { 152 | assert.equal(result[4], 4); 153 | }); 154 | 155 | it('should check list containment', function() { 156 | assert.equal(result[3], true); 157 | }); 158 | 159 | it('should get cdr', function() { 160 | var cdr = result[2]; 161 | assert.equal(cdr[0], 4); 162 | assert.equal(cdr[1], 5); 163 | assert.equal(cdr[2], 3); 164 | }); 165 | 166 | it('should item by index', function() { 167 | assert.equal(result[1], 2); 168 | }); 169 | 170 | it('should compile v6 blocks', function() { 171 | const content = utils.getProjectXml('all-variablesv2'); 172 | snap2js.compile(content) 173 | }); 174 | }); 175 | 176 | describe('include global vars in ctx', function() { 177 | let list; 178 | 179 | before(async function() { 180 | const content = utils.getContextXml('global-vars-with-ctx'); 181 | const bin = snap2js.compile(content) 182 | const cxt = snap2js.newContext(); 183 | const fn = bin(cxt); 184 | list = await fn(); 185 | }); 186 | 187 | it('should support script vars', function() { 188 | assert.equal(list[0], 'set a script variable!'); 189 | }); 190 | 191 | it('should support sprite vars', function() { 192 | assert.equal(list[1], 'I am a sprite variable!'); 193 | }); 194 | 195 | it('should support global vars', function() { 196 | assert.equal(list[2], 'I am a global variable!'); 197 | }); 198 | }); 199 | }); 200 | --------------------------------------------------------------------------------