├── .github └── workflows │ └── CI.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── eslint.config.mjs ├── index.js ├── karma.conf.js ├── lib ├── Cli.js ├── Initializer.js ├── commands │ ├── append.js │ ├── color.js │ ├── connect.js │ ├── create.js │ ├── element.js │ ├── elements.js │ ├── index.js │ ├── move.js │ ├── redo.js │ ├── remove.js │ ├── removeConnection.js │ ├── removeShape.js │ ├── save.js │ ├── set-label.js │ ├── set-root.js │ └── undo.js ├── index.js └── parsers │ ├── element.js │ ├── elements.js │ ├── index.js │ ├── point.js │ ├── shape.js │ └── shapes.js ├── package-lock.json ├── package.json ├── renovate.json ├── resources ├── collapsed.bpmn ├── demo.js ├── screencast.gif ├── simple.bpmn └── start.bpmn └── test ├── TestHelper.js ├── spec └── CliSpec.js └── suite.js /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [ push, pull_request ] 3 | jobs: 4 | Build: 5 | 6 | strategy: 7 | matrix: 8 | os: [ ubuntu-latest ] 9 | node-version: [ 20 ] 10 | integration-deps: 11 | - diagram-js@7.x bpmn-js@8.x 12 | - "" # as defined in package.json 13 | 14 | runs-on: ${{ matrix.os }} 15 | 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | - name: Use Node.js 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | cache: 'npm' 24 | - name: Install dependencies 25 | run: npm ci 26 | - name: Setup project 27 | uses: bpmn-io/actions/setup@latest 28 | - name: Install dependencies for integration test 29 | if: ${{ matrix.integration-deps != '' }} 30 | run: npm install ${{ matrix.integration-deps }} 31 | - name: Build 32 | run: npm run all 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .idea/ 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to [bpmn-js-cli](https://github.com/bpmn-io/bpmn-js-cli) are documented here. We use [semantic versioning](http://semver.org/) for releases. 4 | 5 | ## Unreleased 6 | 7 | ___Note:__ Yet to be released changes appear here._ 8 | 9 | # 2.4.0 10 | 11 | * `DEPS`: dependency updates 12 | * `CHORE`: consistently use ES modules internally 13 | 14 | # 2.3.0 15 | 16 | * `DEPS`: widen `bpmn-js` peerDependency range ([e71a09f](https://github.com/bpmn-io/bpmn-js-cli/commit/e71a09f)) 17 | 18 | ## 2.2.1 19 | 20 | * `DEPS`: support `bpmn-js@10` 21 | 22 | ## 2.2.0 23 | 24 | * `FEAT`: add `setRoot` command ([#42](https://github.com/bpmn-io/bpmn-js-cli/pull/42)) 25 | 26 | ## 2.1.0 27 | 28 | * `FEAT`: add `color` command 29 | * `FEAT`: add generic `remove` command 30 | * `FEAT`: add `elements` param 31 | 32 | ## 2.0.2 33 | 34 | * `CHORE`: clean up published files 35 | 36 | ## 2.0.1 37 | 38 | * `CHORE`: mark as `bpmn-js@8` compatible 39 | 40 | ## 2.0.0 41 | 42 | ### Breaking Changes 43 | 44 | * The toolkit now requires the ES6 `Promise` to be present. To support IE11 you must polyfill it. 45 | 46 | ## 1.4.1 47 | 48 | * `CHORE`: fix CHANGELOG 49 | 50 | ## 1.4.0 51 | 52 | _Republish of `v1.2.0`._ 53 | 54 | ## 1.2.0 55 | 56 | * `CHORE`: support `bpmn-js@6` 57 | 58 | ## 1.1.0 59 | 60 | * `CHORE`: support `bpmn-js@4` and `bpmn-js@5` 61 | 62 | ## 1.0.0 63 | 64 | * `FEAT`: expose ES modules 65 | * `FEAT`: mark as `bpmn-js@3` compatible 66 | * `CHORE`: drop `bpmn-js@0.x` compatibility 67 | 68 | ### Breaking Changes 69 | 70 | * You must now use an ES module aware bundler such as Webpack or Rollup to consume this library. 71 | 72 | ## ... 73 | 74 | Check `git log` for earlier history. 75 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Nico Rehwaldt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > As of version `1.0.0` this library exposes [ES modules](http://exploringjs.com/es6/ch_modules.html#sec_basics-of-es6-modules). Use an ES module aware bundler such as [Webpack](https://webpack.js.org) or [Rollup](https://rollupjs.org) to bundle it for the browser. 2 | 3 | # bpmn-js-cli 4 | 5 | [![CI](https://github.com/bpmn-io/bpmn-js-cli/workflows/CI/badge.svg)](https://github.com/bpmn-io/bpmn-js-cli/actions?query=workflow%3ACI) 6 | 7 | An extensible command line interface for [bpmn-js](https://github.com/bpmn-io/bpmn-js). 8 | 9 | 10 | ## Demo 11 | 12 | 13 | 14 | Checkout [`demo.js`](./resources/demo.js) for the commands powering this demo. 15 | 16 | 17 | ## Features 18 | 19 | * Model BPMN 2.0 diagrams in the browser, without a mouse 20 | * Full undo and redo functionality 21 | * Extensible through your own commands 22 | * [Numerous built-in commands](#built-in-commands) 23 | 24 | 25 | ## Built in Commands 26 | 27 | Out of the box, the cli supports the following commands: 28 | 29 | * `append source type [deltaPos]` 30 | * `connect source target type` 31 | * `create type position parent` 32 | * `element id` 33 | * `elements` 34 | * `move shape delta [newParentId]` 35 | * `undo` 36 | * `redo` 37 | * `save svg|bpmn` 38 | * `setLabel element label` 39 | * `setRoot element|elementId` 40 | * `removeShape shape|elementId` 41 | * `removeConnection connection|connectionId` 42 | 43 | 44 | ## Quickstart 45 | 46 | Get the list of available commands: 47 | 48 | ``` 49 | cli.help(); 50 | ``` 51 | 52 | Get the list of elements: 53 | 54 | ``` 55 | cli.elements(); 56 | ``` 57 | 58 | Export SVG or BPMN 2.0 xml 59 | 60 | ``` 61 | cli.save('svg' || 'bpmn'); 62 | ``` 63 | 64 | 65 | ## Usage 66 | 67 | Deploy the cli with [bpmn-js](https://github.com/bpmn-io/bpmn-js): 68 | 69 | ``` 70 | var BpmnModeler = require('bpmn-js/lib/Modeler'), 71 | CliModule = require('bpmn-js-cli'); 72 | 73 | var modeler = new BpmnModeler({ 74 | container: document.body, 75 | additionalModules: [ 76 | CliModule 77 | ], 78 | cli: { 79 | bindTo: 'cli' 80 | } 81 | }); 82 | 83 | modeler.importXML('some-bpmn-xml') 84 | .then(({ warnings }) => { 85 | // ... 86 | }) 87 | .catch(err => { 88 | console.error(err); 89 | }); 90 | ``` 91 | 92 | Access the cli as `cli` in your developer console (open via `F12` in most browsers). 93 | 94 | Use the cli to model BPMN 2.0 diagrams in your browser. Pain free. 95 | 96 | 97 | ## License 98 | 99 | MIT 100 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import bpmnIoPlugin from 'eslint-plugin-bpmn-io'; 2 | 3 | export default [ 4 | ...bpmnIoPlugin.configs.browser, 5 | ...bpmnIoPlugin.configs.node.map(config => { 6 | return { 7 | ...config, 8 | files: [ 9 | 'karma.conf.js', 10 | '**/test/**/*.js' 11 | ] 12 | }; 13 | }), 14 | ...bpmnIoPlugin.configs.mocha.map(config => { 15 | return { 16 | ...config, 17 | files: [ 18 | '**/test/**/*.js' 19 | ] 20 | }; 21 | }) 22 | ]; 23 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | export { 2 | default 3 | } from './lib'; -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 3 | var path = require('path'); 4 | 5 | var singleStart = process.env.SINGLE_START; 6 | 7 | // configures browsers to run test against 8 | // any of [ 'ChromeHeadless', 'Chrome', 'Firefox' ] 9 | var browsers = (process.env.TEST_BROWSERS || 'ChromeHeadless').split(','); 10 | 11 | // use puppeteer provided Chrome for testing 12 | process.env.CHROME_BIN = require('puppeteer').executablePath(); 13 | 14 | module.exports = function(karma) { 15 | var config = { 16 | 17 | frameworks: [ 18 | 'mocha', 19 | 'chai', 20 | 'webpack' 21 | ], 22 | 23 | files: [ 24 | 'test/suite.js' 25 | ], 26 | 27 | preprocessors: { 28 | 'test/suite.js': [ 'webpack', 'env' ] 29 | }, 30 | 31 | reporters: [ 'dots' ], 32 | 33 | browsers, 34 | 35 | browserNoActivityTimeout: 30000, 36 | 37 | singleRun: true, 38 | autoWatch: false, 39 | 40 | webpack: { 41 | mode: 'development', 42 | module: { 43 | rules: [ 44 | { 45 | test: /(\.css|\.bpmn)$/, 46 | type: 'asset/source' 47 | } 48 | ] 49 | }, 50 | resolve: { 51 | modules: [ 52 | 'node_modules', 53 | path.resolve(__dirname) 54 | ] 55 | } 56 | } 57 | }; 58 | 59 | if (singleStart) { 60 | config.browsers = [].concat(config.browsers, 'Debug'); 61 | config.envPreprocessor = [].concat(config.envPreprocessor || [], 'SINGLE_START'); 62 | } 63 | 64 | karma.set(config); 65 | }; 66 | -------------------------------------------------------------------------------- /lib/Cli.js: -------------------------------------------------------------------------------- 1 | import { 2 | isArray, 3 | isNumber, 4 | isString, 5 | isFunction, 6 | forEach 7 | } from 'min-dash'; 8 | 9 | function asArray(args) { 10 | return Array.prototype.slice.call(args); 11 | } 12 | 13 | function asParam(parse) { 14 | return function(name, options) { 15 | return { 16 | name: name, 17 | parse: function(val) { 18 | return parse(val, options || {}); 19 | } 20 | }; 21 | }; 22 | } 23 | 24 | function StringParser() { 25 | return function(arg, options) { 26 | 27 | // support variable arguments 28 | if (isArray(arg)) { 29 | arg = arg.join(' '); 30 | } 31 | 32 | if (arg !== '' && !arg) { 33 | if (options.defaultValue) { 34 | return options.defaultValue; 35 | } else { 36 | throw new Error('no value given'); 37 | } 38 | } else { 39 | return arg; 40 | } 41 | }; 42 | } 43 | 44 | function BooleanParser() { 45 | return function(arg, options) { 46 | if (!arg) { 47 | if (options.defaultValue) { 48 | return options.defaultValue; 49 | } 50 | 51 | if (options.optional) { 52 | return undefined; 53 | } 54 | 55 | throw new Error('no value given'); 56 | } else { 57 | return arg && arg !== 'false'; 58 | } 59 | }; 60 | } 61 | 62 | function NumberParser() { 63 | return function(arg, options) { 64 | if (arg !== 0 && !arg) { 65 | if (options.defaultValue) { 66 | return options.defaultValue; 67 | } else { 68 | throw new Error('no value given'); 69 | } 70 | } else { 71 | return isNumber(arg) ? arg : parseFloat(arg, 10); 72 | } 73 | }; 74 | } 75 | 76 | /** 77 | * The CLI. 78 | * 79 | * @param {Object} config 80 | * @param {didi.Injector} injector 81 | */ 82 | export default function Cli(config, injector) { 83 | 84 | this._commands = {}; 85 | this._params = {}; 86 | 87 | this._injector = injector; 88 | 89 | this._registerParsers(); 90 | this._registerCommands(); 91 | 92 | this._bind(config); 93 | } 94 | 95 | Cli.$inject = [ 96 | 'config', 97 | 'injector' 98 | ]; 99 | 100 | 101 | // reset prototype (ain't gonna inherit from object) 102 | 103 | Cli.prototype = {}; 104 | 105 | 106 | // helpers ////////////////////////// 107 | 108 | Cli.prototype._bind = function(config) { 109 | if (config.cli && config.cli.bindTo) { 110 | console.info('bpmn-js-cli is available via window.' + config.cli.bindTo); 111 | window[config.cli.bindTo] = this; 112 | } 113 | }; 114 | 115 | Cli.prototype._registerParser = function(name, Parser) { 116 | var parser = this._injector.invoke(Parser); 117 | 118 | // must return a function(val, options) -> result 119 | if (!isFunction(parser)) { 120 | throw new Error( 121 | 'parser must be a Function -> Object' 122 | ); 123 | } 124 | 125 | this._params[name] = asParam(parser); 126 | }; 127 | 128 | Cli.prototype._registerCommand = function(name, Command) { 129 | 130 | var command = ( 131 | isFunction(Command) ? 132 | this._injector.invoke(Command) : 133 | Command 134 | ); 135 | 136 | command.args = command.args || []; 137 | 138 | this._commands[name] = command; 139 | 140 | var self = this; 141 | 142 | this[name] = function() { 143 | var args = asArray(arguments); 144 | args.unshift(name); 145 | 146 | return self.exec.apply(self, args); 147 | }; 148 | }; 149 | 150 | Cli.prototype._registerParsers = function() { 151 | this._registerParser('string', StringParser); 152 | this._registerParser('number', NumberParser); 153 | this._registerParser('bool', BooleanParser); 154 | }; 155 | 156 | Cli.prototype._registerCommands = function() { 157 | 158 | var self = this; 159 | 160 | // special command 161 | this._registerCommand('help', { 162 | exec: function() { 163 | var help = 'available commands:\n'; 164 | 165 | forEach(self._commands, function(c, name) { 166 | help += '\n\t' + name; 167 | }); 168 | 169 | return help; 170 | } 171 | }); 172 | }; 173 | 174 | Cli.prototype.parseArguments = function(args, command) { 175 | 176 | var results = []; 177 | 178 | var last = command.args.length - 1; 179 | 180 | forEach(command.args, function(c, i) { 181 | 182 | var val; 183 | 184 | // last arg receives array of all remaining parameters 185 | if (i === last && args.length > command.args.length) { 186 | val = args.slice(i); 187 | } else { 188 | val = args[i]; 189 | } 190 | 191 | try { 192 | results.push(c.parse(val)); 193 | } catch (e) { 194 | throw new Error('could not parse <' + c.name + '>: ' + e.message); 195 | } 196 | }); 197 | 198 | return results; 199 | }; 200 | 201 | Cli.prototype.exec = function() { 202 | 203 | var args = []; 204 | 205 | // convert mixed whitespace separated string / object assignments in args list 206 | // to correct argument representation 207 | asArray(arguments).forEach(function(arg) { 208 | if (isString(arg)) { 209 | args = args.concat(arg.split(/\s+/)); 210 | } else { 211 | args.push(arg); 212 | } 213 | }); 214 | 215 | var name = args.shift(); 216 | 217 | var command = this._commands[name]; 218 | if (!command) { 219 | throw new Error( 220 | 'no command <' + name + '>, ' + 221 | 'execute to get a list of available commands' 222 | ); 223 | } 224 | 225 | var values, result; 226 | 227 | try { 228 | values = this.parseArguments(args, command); 229 | result = command.exec.apply(this, values); 230 | } catch (e) { 231 | throw new Error( 232 | 'failed to execute <' + name + '> ' + 233 | 'with args <[' + args.join(', ') + ']> : ' + 234 | e.stack 235 | ); 236 | } 237 | 238 | return result; 239 | }; -------------------------------------------------------------------------------- /lib/Initializer.js: -------------------------------------------------------------------------------- 1 | import { 2 | PointParser, 3 | ElementParser, 4 | ElementsParser, 5 | ShapeParser, 6 | ShapesParser 7 | } from './parsers'; 8 | 9 | import { 10 | AppendCommand, 11 | ConnectCommand, 12 | ColorCommand, 13 | CreateCommand, 14 | ElementCommand, 15 | ElementsCommand, 16 | MoveCommand, 17 | RedoCommand, 18 | SaveCommand, 19 | SetLabelCommand, 20 | SetRootCommand, 21 | UndoCommand, 22 | RemoveShapeCommand, 23 | RemoveConnectionCommand, 24 | RemoveCommand 25 | } from './commands'; 26 | 27 | /** 28 | * The default CLI initializer that sets up available 29 | * parsers and commands. 30 | * 31 | * @param {Cli} cli 32 | */ 33 | export default function Initializer(cli) { 34 | 35 | // parsers 36 | cli._registerParser('point', PointParser); 37 | cli._registerParser('element', ElementParser); 38 | cli._registerParser('elements', ElementsParser); 39 | cli._registerParser('shape', ShapeParser); 40 | cli._registerParser('shapes', ShapesParser); 41 | 42 | // commands 43 | cli._registerCommand('append', AppendCommand); 44 | cli._registerCommand('connect', ConnectCommand); 45 | cli._registerCommand('color', ColorCommand); 46 | cli._registerCommand('create', CreateCommand); 47 | cli._registerCommand('element', ElementCommand); 48 | cli._registerCommand('elements', ElementsCommand); 49 | cli._registerCommand('move', MoveCommand); 50 | cli._registerCommand('redo', RedoCommand); 51 | cli._registerCommand('save', SaveCommand); 52 | cli._registerCommand('setLabel', SetLabelCommand); 53 | cli._registerCommand('setRoot', SetRootCommand); 54 | cli._registerCommand('undo', UndoCommand); 55 | cli._registerCommand('removeShape', RemoveShapeCommand); 56 | cli._registerCommand('removeConnection', RemoveConnectionCommand); 57 | cli._registerCommand('remove', RemoveCommand); 58 | } 59 | 60 | Initializer.$inject = [ 'cli' ]; -------------------------------------------------------------------------------- /lib/commands/append.js: -------------------------------------------------------------------------------- 1 | export default function AppendCommand(params, modeling) { 2 | 3 | return { 4 | args: [ 5 | params.shape('source'), 6 | params.string('type'), 7 | params.point('delta', { defaultValue: { x: 200, y: 0 } }) 8 | ], 9 | exec: function(source, type, delta) { 10 | var newPosition = { 11 | x: source.x + source.width / 2 + delta.x, 12 | y: source.y + source.height / 2 + delta.y 13 | }; 14 | 15 | return modeling.appendShape(source, { type: type }, newPosition).id; 16 | } 17 | }; 18 | } 19 | 20 | AppendCommand.$inject = [ 'cli._params', 'modeling' ]; -------------------------------------------------------------------------------- /lib/commands/color.js: -------------------------------------------------------------------------------- 1 | import { 2 | isArray, 3 | isObject 4 | } from 'min-dash'; 5 | 6 | 7 | /** 8 | * Colors a number of elements 9 | */ 10 | export default function ColorCommand(params, modeling) { 11 | 12 | function parseColors(colors) { 13 | 14 | // directly pass colors = { fill?, stroke? } 15 | if (isObject(colors)) { 16 | return colors; 17 | } 18 | 19 | var parts = isArray(colors) ? colors : colors.split(','); 20 | 21 | var opts = {}; 22 | 23 | [ 'fill', 'stroke' ].forEach(function(part, idx) { 24 | 25 | var color = parts[idx]; 26 | 27 | if (color) { 28 | opts[part] = color === 'unset' ? undefined : color; 29 | } 30 | }); 31 | 32 | return opts; 33 | } 34 | 35 | return { 36 | args: [ 37 | params.elements('elements'), 38 | params.string('colors') 39 | ], 40 | exec: function(elements, colors) { 41 | modeling.setColor(elements, parseColors(colors)); 42 | } 43 | }; 44 | } 45 | 46 | ColorCommand.$inject = [ 'cli._params', 'modeling' ]; -------------------------------------------------------------------------------- /lib/commands/connect.js: -------------------------------------------------------------------------------- 1 | export default function ConnectCommand(params, modeling) { 2 | 3 | return { 4 | args: [ 5 | params.shape('source'), 6 | params.shape('target'), 7 | params.string('type'), 8 | params.shape('parent', { optional: true }), 9 | ], 10 | exec: function(source, target, type, parent) { 11 | return modeling.createConnection(source, target, { 12 | type: type, 13 | }, parent || source.parent).id; 14 | } 15 | }; 16 | } 17 | 18 | ConnectCommand.$inject = [ 'cli._params', 'modeling' ]; -------------------------------------------------------------------------------- /lib/commands/create.js: -------------------------------------------------------------------------------- 1 | export default function CreateCommand(params, modeling) { 2 | 3 | return { 4 | args: [ 5 | params.string('type'), 6 | params.point('position'), 7 | params.shape('parent'), 8 | params.bool('isAttach', { optional: true }) 9 | ], 10 | exec: function(type, position, parent, isAttach) { 11 | 12 | var hints; 13 | 14 | if (isAttach) { 15 | hints = { 16 | attach: true 17 | }; 18 | } 19 | 20 | return modeling.createShape({ type: type }, position, parent, hints).id; 21 | } 22 | }; 23 | } 24 | 25 | CreateCommand.$inject = [ 'cli._params', 'modeling' ]; -------------------------------------------------------------------------------- /lib/commands/element.js: -------------------------------------------------------------------------------- 1 | export default function ElementCommand(params) { 2 | 3 | return { 4 | args: [ params.element('element') ], 5 | exec: function(element) { 6 | return element; 7 | } 8 | }; 9 | } 10 | 11 | ElementCommand.$inject = [ 'cli._params' ]; -------------------------------------------------------------------------------- /lib/commands/elements.js: -------------------------------------------------------------------------------- 1 | export default function ElementsCommand(params, elementRegistry) { 2 | 3 | return { 4 | exec: function() { 5 | function all() { 6 | return true; 7 | } 8 | 9 | function ids(e) { 10 | return e.id; 11 | } 12 | 13 | return elementRegistry.filter(all).map(ids); 14 | } 15 | }; 16 | } 17 | 18 | ElementsCommand.$inject = [ 'cli._params', 'elementRegistry' ]; -------------------------------------------------------------------------------- /lib/commands/index.js: -------------------------------------------------------------------------------- 1 | export { default as AppendCommand } from './append'; 2 | export { default as ColorCommand } from './color'; 3 | export { default as ConnectCommand } from './connect'; 4 | export { default as CreateCommand } from './create'; 5 | export { default as ElementCommand } from './element'; 6 | export { default as ElementsCommand } from './elements'; 7 | export { default as MoveCommand } from './move'; 8 | export { default as RedoCommand } from './redo'; 9 | export { default as SaveCommand } from './save'; 10 | export { default as SetLabelCommand } from './set-label'; 11 | export { default as SetRootCommand } from './set-root'; 12 | export { default as UndoCommand } from './undo'; 13 | export { default as RemoveShapeCommand } from './removeShape'; 14 | export { default as RemoveConnectionCommand } from './removeConnection'; 15 | export { default as RemoveCommand } from './remove'; -------------------------------------------------------------------------------- /lib/commands/move.js: -------------------------------------------------------------------------------- 1 | export default function MoveCommand(params, modeling) { 2 | 3 | return { 4 | args: [ 5 | params.shapes('shapes'), 6 | params.point('delta'), 7 | params.shape('newParent', { optional: true }), 8 | params.bool('isAttach', { optional: true }) 9 | ], 10 | exec: function(shapes, delta, newParent, isAttach) { 11 | 12 | var hints; 13 | 14 | if (isAttach) { 15 | hints = { 16 | attach: true 17 | }; 18 | } 19 | 20 | modeling.moveElements(shapes, delta, newParent, hints); 21 | return shapes; 22 | } 23 | }; 24 | } 25 | 26 | MoveCommand.$inject = [ 'cli._params', 'modeling' ]; -------------------------------------------------------------------------------- /lib/commands/redo.js: -------------------------------------------------------------------------------- 1 | export default function RedoCommand(commandStack) { 2 | 3 | return { 4 | exec: function() { 5 | commandStack.redo(); 6 | } 7 | }; 8 | } 9 | 10 | RedoCommand.$inject = [ 'commandStack' ]; -------------------------------------------------------------------------------- /lib/commands/remove.js: -------------------------------------------------------------------------------- 1 | export default function RemoveCommand(params, modeling) { 2 | 3 | return { 4 | args: [ 5 | params.elements('elements') 6 | ], 7 | exec: function(elements) { 8 | return modeling.removeElements(elements); 9 | } 10 | }; 11 | } 12 | 13 | RemoveCommand.$inject = [ 'cli._params', 'modeling' ]; -------------------------------------------------------------------------------- /lib/commands/removeConnection.js: -------------------------------------------------------------------------------- 1 | export default function RemoveConnectionCommand(params, modeling) { 2 | 3 | return { 4 | args: [ 5 | params.element('connection') 6 | ], 7 | exec: function(connection) { 8 | return modeling.removeConnection(connection); 9 | } 10 | }; 11 | } 12 | 13 | RemoveConnectionCommand.$inject = [ 'cli._params', 'modeling' ]; -------------------------------------------------------------------------------- /lib/commands/removeShape.js: -------------------------------------------------------------------------------- 1 | export default function RemoveShapeCommand(params, modeling) { 2 | 3 | return { 4 | args: [ 5 | params.shape('shape') 6 | ], 7 | exec: function(shape) { 8 | return modeling.removeShape(shape); 9 | } 10 | }; 11 | } 12 | 13 | RemoveShapeCommand.$inject = [ 'cli._params', 'modeling' ]; -------------------------------------------------------------------------------- /lib/commands/save.js: -------------------------------------------------------------------------------- 1 | export default function SaveCommand(params, bpmnjs) { 2 | 3 | return { 4 | args: [ params.string('format') ], 5 | exec: function(format) { 6 | if (format === 'svg') { 7 | bpmnjs.saveSVG() 8 | .then(({ svg }) => { 9 | console.info(svg); 10 | }) 11 | .catch(err => { 12 | console.error(err); 13 | }); 14 | } else if (format === 'bpmn') { 15 | bpmnjs.saveXML() 16 | .then(({ xml }) => { 17 | console.info(xml); 18 | }) 19 | .catch(err => { 20 | console.error(err); 21 | }); 22 | } else { 23 | throw new Error('unknown format, and are available'); 24 | } 25 | } 26 | }; 27 | } 28 | 29 | SaveCommand.$inject = [ 'cli._params', 'bpmnjs' ]; -------------------------------------------------------------------------------- /lib/commands/set-label.js: -------------------------------------------------------------------------------- 1 | export default function SetLabelCommand(params, modeling) { 2 | 3 | return { 4 | args: [ params.element('element'), params.string('newLabel') ], 5 | exec: function(element, newLabel) { 6 | modeling.updateLabel(element, newLabel); 7 | return element; 8 | } 9 | }; 10 | } 11 | 12 | SetLabelCommand.$inject = [ 'cli._params', 'modeling' ]; -------------------------------------------------------------------------------- /lib/commands/set-root.js: -------------------------------------------------------------------------------- 1 | export default function SetRootCommand(params, canvas) { 2 | 3 | return { 4 | args: [ params.element('element') ], 5 | exec: function(element) { 6 | canvas.setRootElement(element); 7 | return element; 8 | } 9 | }; 10 | } 11 | 12 | SetRootCommand.$inject = [ 'cli._params', 'canvas' ]; -------------------------------------------------------------------------------- /lib/commands/undo.js: -------------------------------------------------------------------------------- 1 | export default function UndoCommand(commandStack) { 2 | 3 | return { 4 | exec: function() { 5 | commandStack.undo(); 6 | } 7 | }; 8 | } 9 | 10 | UndoCommand.$inject = [ 'commandStack' ]; -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | import CliInitializer from './Initializer'; 2 | import Cli from './Cli'; 3 | 4 | export default { 5 | __init__: [ 'cliInitializer' ], 6 | cli: [ 'type', Cli ], 7 | cliInitializer: [ 'type', CliInitializer ] 8 | }; -------------------------------------------------------------------------------- /lib/parsers/element.js: -------------------------------------------------------------------------------- 1 | import { 2 | isObject 3 | } from 'min-dash'; 4 | 5 | 6 | export default function ElementParser(elementRegistry) { 7 | 8 | return function(arg, options) { 9 | 10 | // assume element passed is shape already 11 | if (isObject(arg)) { 12 | return arg; 13 | } 14 | 15 | var e = elementRegistry.get(arg); 16 | if (!e) { 17 | if (options.optional) { 18 | return null; 19 | } else { 20 | if (arg) { 21 | throw new Error('element with id <' + arg + '> does not exist'); 22 | } else { 23 | throw new Error('argument required'); 24 | } 25 | } 26 | } 27 | 28 | return e; 29 | }; 30 | } 31 | 32 | ElementParser.$inject = [ 'elementRegistry' ]; -------------------------------------------------------------------------------- /lib/parsers/elements.js: -------------------------------------------------------------------------------- 1 | import { 2 | isObject, 3 | isString, 4 | isArray 5 | } from 'min-dash'; 6 | 7 | /** 8 | * Parses a list of elements from a list of 9 | * objects or a comma-separated string 10 | */ 11 | export default function ElementsParser(elementRegistry) { 12 | 13 | return function(args, options) { 14 | 15 | if (isString(args)) { 16 | args = args.split(','); 17 | } else 18 | if (!isArray(args)) { 19 | args = [ args ]; 20 | } 21 | 22 | return args.map(function(arg) { 23 | 24 | // assume element passed is shape already 25 | if (isObject(arg)) { 26 | return arg; 27 | } 28 | 29 | var e = elementRegistry.get(arg); 30 | if (!e) { 31 | if (options.optional) { 32 | return null; 33 | } else { 34 | if (arg) { 35 | throw new Error('element with id <' + arg + '> does not exist'); 36 | } else { 37 | throw new Error('argument required'); 38 | } 39 | } 40 | } 41 | 42 | return e; 43 | }).filter(function(e) { return e; }); 44 | }; 45 | } 46 | 47 | ElementsParser.$inject = [ 'elementRegistry' ]; -------------------------------------------------------------------------------- /lib/parsers/index.js: -------------------------------------------------------------------------------- 1 | export { default as PointParser } from './point'; 2 | export { default as ElementParser } from './element'; 3 | export { default as ElementsParser } from './elements'; 4 | export { default as ShapeParser } from './shape'; 5 | export { default as ShapesParser } from './shapes'; -------------------------------------------------------------------------------- /lib/parsers/point.js: -------------------------------------------------------------------------------- 1 | import { 2 | isObject 3 | } from 'min-dash'; 4 | 5 | 6 | /** 7 | * Parses 12,12 to { x: 12, y: 12 }. 8 | * Allows nulls, i.e ,12 -> { x: 0, y: 12 } 9 | */ 10 | export default function PointParser() { 11 | 12 | return function(arg, options) { 13 | 14 | // assume element passed is delta already 15 | if (isObject(arg)) { 16 | return arg; 17 | } 18 | 19 | if (!arg && options.defaultValue) { 20 | return options.defaultValue; 21 | } 22 | 23 | var parts = arg.split(/,/); 24 | 25 | if (parts.length !== 2) { 26 | throw new Error('expected delta to match (\\d*,\\d*)'); 27 | } 28 | 29 | return { 30 | x: parseInt(parts[0], 10) || 0, 31 | y: parseInt(parts[1], 10) || 0 32 | }; 33 | }; 34 | } -------------------------------------------------------------------------------- /lib/parsers/shape.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Parses a single shape from an object or string 3 | */ 4 | export default function ShapeParser(params) { 5 | 6 | return function(arg, options) { 7 | 8 | const element = params.element(arg, options).parse(arg); 9 | 10 | if (element && element.waypoints) { 11 | throw new Error('element <' + arg + '> is a connection'); 12 | } 13 | 14 | return element; 15 | }; 16 | } 17 | 18 | ShapeParser.$inject = [ 'cli._params' ]; -------------------------------------------------------------------------------- /lib/parsers/shapes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Parses a list of shapes from a list of 3 | * objects or a comma-separated string 4 | */ 5 | export default function ShapesParser(params) { 6 | 7 | return function(args, options) { 8 | 9 | return params.elements(args, options).parse(args).filter(function(element) { 10 | if (element.waypoints) { 11 | throw new Error('element <' + element.id + '> is a connection'); 12 | } 13 | 14 | return true; 15 | }); 16 | }; 17 | } 18 | 19 | ShapesParser.$inject = [ 'cli._params' ]; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bpmn-js-cli", 3 | "version": "2.4.0", 4 | "description": "A command-line interface for bpmn-js", 5 | "scripts": { 6 | "all": "run-s lint test", 7 | "lint": "eslint .", 8 | "start": "SINGLE_START=1 npm run dev", 9 | "dev": "npm test -- --auto-watch --no-single-run", 10 | "test": "karma start" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/bpmn-io/bpmn-js-cli" 15 | }, 16 | "keywords": [ 17 | "bpmn-js", 18 | "bpmn-js-plugin" 19 | ], 20 | "author": "Nico Rehwaldt", 21 | "license": "MIT", 22 | "devDependencies": { 23 | "bpmn-js": "^18.0.0", 24 | "chai": "^4.4.1", 25 | "eslint": "^9.0.0", 26 | "eslint-plugin-bpmn-io": "^2.0.1", 27 | "karma": "^6.4.2", 28 | "karma-chai": "^0.1.0", 29 | "karma-chrome-launcher": "^3.2.0", 30 | "karma-debug-launcher": "^0.0.5", 31 | "karma-env-preprocessor": "^0.1.1", 32 | "karma-firefox-launcher": "^2.1.2", 33 | "karma-mocha": "^2.0.1", 34 | "karma-webpack": "^5.0.1", 35 | "mocha": "^10.2.0", 36 | "mocha-test-container-support": "^0.2.0", 37 | "npm-run-all2": "^8.0.0", 38 | "puppeteer": "^24.0.0", 39 | "semver": "^7.5.4", 40 | "webpack": "^5.90.1" 41 | }, 42 | "dependencies": { 43 | "min-dash": "^4.1.1" 44 | }, 45 | "peerDependencies": { 46 | "bpmn-js": "*" 47 | }, 48 | "files": [ 49 | "lib", 50 | "index.js" 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "github>bpmn-io/renovate-config:recommended" 5 | ] 6 | } -------------------------------------------------------------------------------- /resources/collapsed.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flow_0yhvali 6 | 7 | 8 | 9 | Flow_1r6lyrj 10 | 11 | 12 | 13 | Flow_0yhvali 14 | Flow_1r6lyrj 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /resources/demo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Commands running the demo shown in ./screencast.gif 3 | */ 4 | 5 | var cli = window.cli; 6 | 7 | cli.elements(); 8 | 9 | var gateway = cli.append( 10 | 'StartEvent_1', 11 | 'bpmn:ExclusiveGateway', 12 | '150,0' 13 | ); 14 | 15 | var serviceTask = cli.append( 16 | gateway, 17 | 'bpmn:ServiceTask', 18 | '150,0' 19 | ); 20 | 21 | var callActivity = cli.append( 22 | gateway, 23 | 'bpmn:CallActivity', 24 | '150,90' 25 | ); 26 | 27 | cli.undo(); 28 | 29 | callActivity = cli.append( 30 | gateway, 31 | 'bpmn:CallActivity', 32 | '150,120' 33 | ); 34 | 35 | var endEvent = cli.append( 36 | serviceTask, 37 | 'bpmn:EndEvent', 38 | '150,0' 39 | ); 40 | 41 | cli.connect( 42 | callActivity, 43 | endEvent, 44 | 'bpmn:SequenceFlow' 45 | ); 46 | 47 | cli.setLabel(callActivity, 'CallActivity'); 48 | 49 | var gatewayShape = cli.element(gateway); 50 | 51 | var textAnnotation = cli.create( 52 | 'bpmn:TextAnnotation', 53 | { 54 | x: gatewayShape.x - 50, 55 | y: gatewayShape.y + 150 56 | }, 57 | gatewayShape.parent 58 | ); 59 | 60 | cli.setLabel(textAnnotation, 'i-am-text'); 61 | 62 | cli.setLabel(gateway, 'ExclusiveGateway'); 63 | 64 | cli.move(callActivity, { x: 20, y: 30 }); 65 | 66 | cli.undo(); 67 | 68 | cli.undo(); 69 | 70 | cli.undo(); 71 | 72 | cli.redo(); 73 | 74 | cli.redo(); 75 | 76 | cli.redo(); 77 | 78 | cli.save('bpmn'); 79 | -------------------------------------------------------------------------------- /resources/screencast.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpmn-io/bpmn-js-cli/265b3f0c51fe1da210d443af4de6476f761a8c1c/resources/screencast.gif -------------------------------------------------------------------------------- /resources/simple.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ID_CONNECTION_1 16 | 17 | 18 | ID_CONNECTION_1 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /resources/start.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /test/TestHelper.js: -------------------------------------------------------------------------------- 1 | export * from 'bpmn-js/test/helper'; 2 | 3 | import semver from 'semver'; 4 | 5 | import { 6 | insertCSS 7 | } from 'bpmn-js/test/helper'; 8 | 9 | 10 | // insert diagram.css 11 | insertCSS( 12 | 'diagram-js.css', 13 | require('diagram-js/assets/diagram-js.css') 14 | ); 15 | 16 | if (bpmnJsSatisfies('>=9')) { 17 | insertCSS( 18 | 'bpmn-js.css', 19 | require('bpmn-js/dist/assets/bpmn-js.css').default 20 | ); 21 | } 22 | 23 | /** 24 | * Execute test only if currently installed bpmn-js is of given version. 25 | * 26 | * @param {string} versionRange 27 | * @param {boolean} only 28 | */ 29 | export function withBpmnJs(versionRange, only = false) { 30 | if (bpmnJsSatisfies(versionRange)) { 31 | return only ? it.only : it; 32 | } else { 33 | return it.skip; 34 | } 35 | } 36 | 37 | function bpmnJsSatisfies(versionRange) { 38 | const bpmnJsVersion = require('bpmn-js/package.json').version; 39 | 40 | return semver.satisfies(bpmnJsVersion, versionRange, { includePrerelease: true }); 41 | } 42 | -------------------------------------------------------------------------------- /test/spec/CliSpec.js: -------------------------------------------------------------------------------- 1 | import { 2 | bootstrapModeler, 3 | inject, 4 | withBpmnJs 5 | } from '../TestHelper'; 6 | 7 | import { 8 | pick 9 | } from 'min-dash'; 10 | 11 | import modelingModule from 'bpmn-js/lib/features/modeling'; 12 | import coreModule from 'bpmn-js/lib/core'; 13 | import cliModule from '../../'; 14 | 15 | var singleStart = !!(window.__env__ && window.__env__.SINGLE_START); 16 | 17 | 18 | describe('cli', function() { 19 | 20 | var startDiagramXML = require('resources/start.bpmn'); 21 | var simpleDiagramXML = require('resources/simple.bpmn'); 22 | var collapsedDiagramXML = require('resources/collapsed.bpmn'); 23 | 24 | var testModules = [ 25 | coreModule, 26 | cliModule, 27 | modelingModule 28 | ]; 29 | 30 | 31 | describe('bootstrap', function() { 32 | 33 | beforeEach(bootstrapModeler(startDiagramXML, { 34 | modules: testModules, 35 | cli: { 36 | bindTo: 'cli' 37 | } 38 | })); 39 | 40 | 41 | (singleStart ? it.only : it)('should bind to window', inject(function(cli) { 42 | expect(window.cli).to.equal(cli); 43 | })); 44 | 45 | }); 46 | 47 | 48 | describe('model', function() { 49 | 50 | beforeEach(bootstrapModeler(startDiagramXML, { 51 | modules: testModules, 52 | cli: { 53 | bindTo: 'cli' 54 | } 55 | })); 56 | 57 | 58 | it('should model process', inject(function(cli) { 59 | 60 | // elements 61 | var elements = cli.elements(); 62 | 63 | // then 64 | expect(elements).to.eql([ 65 | 'Process_1', 66 | 'StartEvent_1' 67 | ]); 68 | 69 | 70 | // element 71 | var startEventElement = cli.element('StartEvent_1'); 72 | 73 | // then 74 | expect(startEventElement.id).to.equal('StartEvent_1'); 75 | 76 | 77 | // move 78 | var startEventPos = pick(startEventElement, [ 'x', 'y' ]); 79 | cli.move('StartEvent_1', '0,100'); 80 | 81 | // then 82 | expect(startEventElement.x).to.equal(startEventPos.x); 83 | expect(startEventElement.y).to.equal(startEventPos.y + 100); 84 | 85 | 86 | // append 87 | var orGateway = cli.append( 88 | 'StartEvent_1', 89 | 'bpmn:InclusiveGateway', 90 | { x: 150, y: 0 } 91 | ); 92 | 93 | // then 94 | expect(orGateway).to.exist; 95 | 96 | 97 | // set label 98 | cli.setLabel(orGateway, 'You have to decide'); 99 | 100 | // then 101 | expect(cli.element(orGateway).businessObject.name).to.equal('You have to decide'); 102 | 103 | 104 | // append user task 105 | var userTask = cli.append(orGateway, 'bpmn:UserTask 150,0'); 106 | 107 | // then 108 | expect(userTask).to.exist; 109 | 110 | 111 | // continue modeling 112 | 113 | // append gateway 114 | var gateway = cli.append(userTask, 'bpmn:ExclusiveGateway'); 115 | 116 | // append manual task 117 | var manualTask = cli.append( 118 | gateway, 119 | 'bpmn:ManualTask', 120 | { x: 150, y: -70 } 121 | ); 122 | 123 | // append intermediate catch event 124 | var intermediateCatchEvent = cli.append( 125 | gateway, 126 | 'bpmn:IntermediateCatchEvent', 127 | { x: 150, y: 70 } 128 | ); 129 | 130 | // append joining gateway 131 | var joiningGateway = cli.append( 132 | manualTask, 133 | 'bpmn:ExclusiveGateway', 134 | '150,70' 135 | ); 136 | 137 | // connect event -> gateway 138 | cli.connect( 139 | intermediateCatchEvent, 140 | joiningGateway, 141 | 'bpmn:SequenceFlow' 142 | ); 143 | 144 | 145 | // create text-annotation next to orGateway 146 | var orGatewayShape = cli.element(orGateway); 147 | 148 | 149 | var textAnnotation = cli.create( 150 | 'bpmn:TextAnnotation', 151 | { 152 | x: orGatewayShape.x + 100, 153 | y: orGatewayShape.y - 100 154 | }, 155 | orGatewayShape.parent 156 | ); 157 | 158 | cli.setLabel(textAnnotation, 'What do you choose, yes or no?'); 159 | 160 | 161 | // create association 162 | cli.connect(orGateway, textAnnotation, 'bpmn:Association'); 163 | 164 | 165 | // move some nodes 166 | cli.move([ 167 | joiningGateway, 168 | orGateway, 169 | intermediateCatchEvent 170 | ], '0,30'); 171 | 172 | // var 173 | // export as svg 174 | cli.save('svg'); 175 | 176 | 177 | // export as bpmn 178 | cli.save('bpmn'); 179 | 180 | })); 181 | 182 | }); 183 | 184 | 185 | describe('set label', function() { 186 | 187 | // given that 188 | beforeEach(bootstrapModeler(simpleDiagramXML, { 189 | modules: testModules 190 | })); 191 | 192 | 193 | it('should set TextAnnotation label', inject(function(cli) { 194 | 195 | // given 196 | var textAnnotation = cli.create( 197 | 'bpmn:TextAnnotation', 198 | { 199 | x: 100, 200 | y: 100 201 | }, 202 | 'ID_PROCESS_1' 203 | ); 204 | 205 | var text = 'What do you choose, yes or no?'; 206 | 207 | // when 208 | cli.setLabel(textAnnotation, text); 209 | 210 | // then 211 | var shape = cli.element(textAnnotation); 212 | 213 | expect(shape.businessObject.text).to.eql(text); 214 | })); 215 | 216 | }); 217 | 218 | 219 | describe('set root', function() { 220 | 221 | // given that 222 | beforeEach(bootstrapModeler(collapsedDiagramXML, { 223 | modules: testModules 224 | })); 225 | 226 | 227 | withBpmnJs('>=9')('should set Root Element', inject(function(cli, canvas) { 228 | 229 | // given 230 | var rootElement = cli.element('SubProcess_1_plane'); 231 | 232 | // when 233 | cli.setRoot(rootElement); 234 | 235 | // then 236 | expect(canvas.getRootElement()).to.eql(rootElement); 237 | })); 238 | 239 | 240 | withBpmnJs('>=9')('should set Root Element using IDs', inject(function(cli, canvas) { 241 | 242 | // given 243 | var rootElementId = 'SubProcess_1_plane'; 244 | 245 | // when 246 | cli.setRoot(rootElementId); 247 | 248 | // then 249 | expect(canvas.getRootElement().id).to.eql(rootElementId); 250 | })); 251 | 252 | }); 253 | 254 | 255 | describe('help', function() { 256 | 257 | // given that 258 | beforeEach(bootstrapModeler(simpleDiagramXML, { 259 | modules: testModules 260 | })); 261 | 262 | 263 | it('should show available commands', inject(function(cli) { 264 | 265 | // when 266 | var helpText = cli.help(); 267 | 268 | // then 269 | expect(helpText).to.match(/available commands:/); 270 | 271 | expect(helpText).to.contain('undo'); 272 | expect(helpText).to.contain('removeShape'); 273 | expect(helpText).to.contain('append'); 274 | })); 275 | 276 | }); 277 | 278 | 279 | describe('remove', function() { 280 | 281 | // given that 282 | beforeEach(bootstrapModeler(simpleDiagramXML, { 283 | modules: testModules 284 | })); 285 | 286 | 287 | it('should remove shape', inject(function(cli) { 288 | 289 | // when 290 | cli.removeShape('ID_TASK_1'); 291 | 292 | // then 293 | var elements = cli.elements(); 294 | 295 | expect(elements).to.eql([ 296 | 'ID_PROCESS_1', 297 | 'ID_TASK_2' 298 | ]); 299 | })); 300 | 301 | 302 | it('should remove elements', inject(function(cli) { 303 | 304 | // when 305 | cli.remove('ID_TASK_1,ID_TASK_2'); 306 | 307 | // then 308 | var elements = cli.elements(); 309 | 310 | expect(elements).to.eql([ 311 | 'ID_PROCESS_1' 312 | ]); 313 | })); 314 | 315 | 316 | it('should remove connection', inject(function(cli) { 317 | 318 | // when 319 | cli.removeConnection('ID_CONNECTION_1'); 320 | 321 | // then 322 | var elements = cli.elements(); 323 | expect(elements).to.eql([ 324 | 'ID_PROCESS_1', 325 | 'ID_TASK_1', 326 | 'ID_TASK_2' 327 | ]); 328 | })); 329 | 330 | }); 331 | 332 | 333 | describe('color', function() { 334 | 335 | // given that 336 | beforeEach(bootstrapModeler(simpleDiagramXML, { 337 | modules: testModules 338 | })); 339 | 340 | 341 | it('should color shape', inject(function(cli) { 342 | 343 | // when 344 | cli.color('ID_TASK_1', 'fuchsia,fuchsia'); 345 | 346 | // then 347 | var task = cli.element('ID_TASK_1'); 348 | 349 | expect(getDi(task).get('fill')).to.exist; 350 | expect(getDi(task).get('stroke')).to.exist; 351 | })); 352 | 353 | 354 | it('should color shapes', inject(function(cli) { 355 | 356 | // when 357 | cli.color('ID_TASK_1,ID_TASK_2', 'fuchsia,fuchsia'); 358 | 359 | // then 360 | expect( 361 | getDi(cli.element('ID_TASK_2')).get('fill') 362 | ).to.eql( 363 | getDi(cli.element('ID_TASK_1')).get('fill') 364 | ); 365 | })); 366 | 367 | 368 | it('should reset shape color', inject(function(cli) { 369 | 370 | // given 371 | cli.color('ID_TASK_1', 'fuchsia,fuchsia'); 372 | 373 | // when 374 | cli.color('ID_TASK_1', 'unset,unset'); 375 | 376 | var task = cli.element('ID_TASK_1'); 377 | 378 | expect(getDi(task).get('fill')).not.to.exist; 379 | expect(getDi(task).get('stroke')).not.to.exist; 380 | })); 381 | 382 | }); 383 | 384 | 385 | describe('attach', function() { 386 | 387 | beforeEach(bootstrapModeler(simpleDiagramXML, { 388 | modules: testModules, 389 | })); 390 | 391 | 392 | it('should create', inject(function(cli) { 393 | 394 | // when 395 | var newElement = cli.create( 396 | 'bpmn:BoundaryEvent', 397 | '400,150', 398 | 'ID_TASK_2', 399 | true 400 | ); 401 | 402 | var connectedTask = cli.append( 403 | newElement, 404 | 'bpmn:ExclusiveGateway', 405 | '50,50' 406 | ); 407 | 408 | // then 409 | var elements = cli.elements(); 410 | 411 | expect(elements).to.contain(newElement); 412 | expect(elements).to.contain(connectedTask); 413 | })); 414 | 415 | 416 | it('should move', inject(function(cli) { 417 | 418 | // given 419 | var newElement = cli.create( 420 | 'bpmn:BoundaryEvent', 421 | '400,150', 422 | 'ID_TASK_2', 423 | true 424 | ); 425 | 426 | cli.append(newElement, 'bpmn:ExclusiveGateway', '50,50'); 427 | 428 | // when 429 | cli.move(newElement, '-200,0', 'ID_TASK_1', true); 430 | 431 | // then 432 | var taskElement = cli.element('ID_TASK_1'); 433 | var boundaryElement = cli.element(newElement); 434 | 435 | expect(boundaryElement.host).to.eql(taskElement); 436 | })); 437 | 438 | }); 439 | 440 | }); 441 | 442 | 443 | // helpers //////////// 444 | 445 | function getDi(element) { 446 | return element.di || element.businessObject.di; 447 | } -------------------------------------------------------------------------------- /test/suite.js: -------------------------------------------------------------------------------- 1 | var allTests = require.context('.', true, /Spec\.js$/); 2 | 3 | allTests.keys().forEach(allTests); --------------------------------------------------------------------------------