├── .eslintignore ├── .eslintrc ├── .github └── workflows │ └── CI.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── lib ├── change-handler.js ├── diff.js ├── differ.js ├── filters.js ├── index.js └── util.js ├── package-lock.json ├── package.json ├── rollup.config.js └── test ├── .eslintrc ├── fixtures ├── add │ ├── after.bpmn │ └── before.bpmn ├── change │ ├── after.bpmn │ ├── before.bpmn │ ├── type-change.after.bpmn │ └── type-change.before.bpmn ├── collaboration │ ├── after.bpmn │ ├── before.bpmn │ ├── message-flow-after.bpmn │ └── message-flow-before.bpmn ├── data-objects │ ├── after.bpmn │ └── before.bpmn ├── different-collaboration │ ├── after.bpmn │ └── before.bpmn ├── event-definition │ ├── after.bpmn │ └── before.bpmn ├── extension-elements │ ├── c7.after.bpmn │ ├── c7.before.bpmn │ ├── signavio.after.bpmn │ └── signavio.before.bpmn ├── incoming-outgoing │ ├── add-task.after.bpmn │ ├── add-task.before.bpmn │ ├── reconnect.after.bpmn │ └── reconnect.before.bpmn ├── lanes │ ├── create-laneset-after.bpmn │ └── create-laneset-before.bpmn ├── layout-change │ ├── after.bpmn │ └── before.bpmn ├── pizza-collaboration │ ├── new.bpmn │ ├── old.bpmn │ ├── start-event-new.bpmn │ └── start-event-old.bpmn ├── remove │ ├── after.bpmn │ └── before.bpmn ├── signavio-collapsed │ ├── after.expanded.bpmn │ └── before.collapsed.bpmn └── sub-processes │ ├── after.bpmn │ └── before.bpmn └── spec └── differ.js /.eslintignore: -------------------------------------------------------------------------------- 1 | dist -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "plugin:bpmn-io/recommended" 3 | } -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [ push, pull_request ] 3 | jobs: 4 | Build: 5 | runs-on: ${{ matrix.os }} 6 | 7 | strategy: 8 | matrix: 9 | os: [ubuntu-latest] 10 | node-version: [ 20 ] 11 | 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | - name: Use Node.js ${{matrix.node-version}} 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: ${{matrix.node-version}} 19 | cache: 'npm' 20 | - name: Install dependencies 21 | run: npm ci 22 | - name: Build 23 | run: npm run all -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | tmp/ 3 | dist/ -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to [bpmn-js-differ](https://github.com/bpmn-io/bpmn-js-differ) 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 | ## 3.0.2 10 | 11 | * `FIX`: restore `main` entry point ([#27](https://github.com/bpmn-io/bpmn-js-differ/pull/27)) 12 | 13 | ## 3.0.1 14 | 15 | * `FIX`: detect type change ([#24](https://github.com/bpmn-io/bpmn-js-differ/issues/24)) 16 | 17 | ## 3.0.0 18 | 19 | * `FEAT`: diff based on [moddle](https://github.com/bpmn-io/moddle) infrastructure 20 | * `FEAT`: detect flow source and target changes ([#18](https://github.com/bpmn-io/bpmn-js-differ/issues/18)) 21 | * `CHORE`: turn into ES module 22 | * `CHORE`: drop UMD distribution 23 | * `DEPS`: update to `diffpatch@0.6.0` 24 | 25 | ### Breaking Changes 26 | 27 | * Drop UMD distribution; consume as an ES module. 28 | 29 | ## 2.0.2 30 | 31 | * `FIX`: swap new/old value in collection properties 32 | 33 | ## 2.0.0 34 | 35 | * `FEAT`: track `bpmn:Collaboration` changes 36 | * `FEAT`: track `bpmn:Process` changes 37 | * `FEAT`: detect collection addition 38 | * `FEAT`: mark parent as changed on nested, un-tracked changes 39 | * `FEAT`: track collection property changes with path(s) 40 | 41 | ## 1.2.0 42 | 43 | * `FEAT`: migrate from `jsondiffpatch` to `diffpatch` for improved bundle sizes 44 | 45 | ## 1.1.0 46 | 47 | * `FEAT`: provide pre-built CommonJS and ES module distributions 48 | 49 | ## 1.0.0 50 | 51 | ### Breaking Changes 52 | 53 | * `FEAT`: migrate to ES modules. Use `esm` or a ES module aware transpiler to consume this library 54 | * `CHORE`: expose `{ Differ, diff }` via module 55 | 56 | ### Other Improvements 57 | 58 | * `CHORE`: use [min-dash](https://github.com/bpmn-io/min-dash) as the utility toolbelt 59 | 60 | ## 0.4.0 61 | 62 | * `FIX`: correctly detect data object reference changes 63 | * `FIX`: correctly detect waypoint additions / removals as layout changes 64 | * `CHORE`: bump to `jsondiffpatch@0.3.9` 65 | 66 | ## ... 67 | 68 | Check `git log` for earlier history. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024-present Camunda Services GmbH 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bpmn-js-differ 2 | 3 | [![CI](https://github.com/bpmn-io/bpmn-js-differ/actions/workflows/CI.yml/badge.svg)](https://github.com/bpmn-io/bpmn-js-differ/actions/workflows/CI.yml) 4 | 5 | A semantic diffing utility for BPMN 2.0 files. To be used together with [bpmn-moddle](https://github.com/bpmn-io/bpmn-moddle). 6 | 7 | 8 | ## Usage 9 | 10 | Get the project via [npm](http://npmjs.org): 11 | 12 | ``` 13 | npm install --save bpmn-js-differ 14 | ``` 15 | 16 | Use the differ to compare two BPMN 2.0 documents: 17 | 18 | ```javascript 19 | import { diff } from 'bpmn-js-differ'; 20 | 21 | var oldDefinitions, newDefinitions; // read with bpmn-moddle 22 | 23 | var changes = diff(oldDefinitions, newDefinitions); 24 | ``` 25 | 26 | The diff returns an object with the `_changed`, `_added`, `_removed`, `_layoutChanged` keys containing all differences between the models. 27 | 28 | ```javascript 29 | console.log(changes._changed); 30 | // { 31 | // ServiceTask_1: { 32 | // model: { $type: 'bpmn:ServiceTask', id: 'ServiceTask_1', ... }, 33 | // attrs: { name: { oldValue: '', newValue: 'T' } } 34 | // } 35 | // } 36 | 37 | console.log(changes._removed); 38 | // { 39 | // SequenceFlow_1: { $type: 'bpmn:SequenceFlow', id: 'SequenceFlow_1' } 40 | // } 41 | 42 | console.log(changes._layoutChanged); 43 | // { 44 | // StartEvent_1: { $type: 'bpmn:StartEvent', id: 'StartEvent_1' } 45 | // } 46 | 47 | console.log(changes._added); 48 | // { 49 | // Participant_1: { $type: 'bpmn:Participant', id: 'Participant_1' } 50 | // } 51 | ``` 52 | 53 | ## Reading BPMN 2.0 documents 54 | 55 | Get [bpmn-moddle](https://github.com/bpmn-io/bpmn-moddle) via npm: 56 | 57 | ``` 58 | npm install --save bpmn-moddle 59 | ``` 60 | 61 | Load a diagram definition: 62 | 63 | ```javascript 64 | import BpmnModdle from 'bpmn-moddle'; 65 | 66 | async function loadModel(diagramXML) { 67 | 68 | const bpmnModdle = new BpmnModdle(); 69 | 70 | const { rootElement: definitionsA } = await bpmnModdle.fromXML(diagramXML), 71 | 72 | return rootElement; 73 | } 74 | 75 | const definitionsA = await loadModel(aXML); 76 | 77 | // ... 78 | // go ahead and use the model 79 | ``` 80 | 81 | ## Visual Diffing 82 | 83 | Use [bpmn-js](https://github.com/bpmn-io/bpmn-js) along with [element coloring](https://github.com/bpmn-io/bpmn-js-examples/tree/master/colors) to build your [visual diff tool](https://demo.bpmn.io/diff) on top of this utility. 84 | 85 | 86 | ## License 87 | 88 | MIT 89 | -------------------------------------------------------------------------------- /lib/change-handler.js: -------------------------------------------------------------------------------- 1 | /* eslint no-cond-assign: 0 */ 2 | 3 | import { 4 | is, 5 | isAny 6 | } from './util.js'; 7 | 8 | function isDi(element) { 9 | return isAny(element, [ 10 | 'bpmndi:BPMNEdge', 11 | 'bpmndi:BPMNShape' 12 | ]); 13 | } 14 | 15 | function getTrackedProcessVisual(processElement) { 16 | 17 | var definitions = processElement.$parent; 18 | 19 | var collaboration = definitions.rootElements.find(function(el) { 20 | return is(el, 'bpmn:Collaboration'); 21 | }); 22 | 23 | // we track the process, too 24 | if (!collaboration) { 25 | return { 26 | element: processElement, 27 | property: '' 28 | }; 29 | } 30 | 31 | var participant = collaboration.participants.find(function(el) { 32 | return el.processRef === processElement; 33 | }); 34 | 35 | return participant && { 36 | element: participant, 37 | property: 'processRef.' 38 | }; 39 | } 40 | 41 | function isTracked(element) { 42 | 43 | // a bpmn:FlowElement without visual representation 44 | if (is(element, 'bpmn:DataObject')) { 45 | return false; 46 | } 47 | 48 | // track referencing bpmn:Participant instead of 49 | // bpmn:Process in collaboration diagrams 50 | if (is(element, 'bpmn:Process')) { 51 | return getTrackedProcessVisual(element); 52 | } 53 | 54 | var track = isAny(element, [ 55 | 'bpmn:Participant', 56 | 'bpmn:Collaboration', 57 | 'bpmn:FlowElement', 58 | 'bpmn:SequenceFlow', 59 | 'bpmn:MessageFlow', 60 | 'bpmn:Participant', 61 | 'bpmn:Lane', 62 | 'bpmn:DataAssociation' 63 | ]); 64 | 65 | if (track) { 66 | return { 67 | element: element, 68 | property: '' 69 | }; 70 | } 71 | } 72 | 73 | export default function ChangeHandler() { 74 | this._layoutChanged = {}; 75 | this._changed = {}; 76 | this._removed = {}; 77 | this._added = {}; 78 | } 79 | 80 | 81 | ChangeHandler.prototype.removed = function(model, property, element, idx) { 82 | 83 | var tracked; 84 | 85 | if (tracked = isTracked(element)) { 86 | if (!this._removed[tracked.element.id]) { 87 | this._removed[tracked.element.id] = element; 88 | } 89 | } else if (tracked = isTracked(model)) { 90 | this.changed(tracked.element, tracked.property + property + '[' + idx + ']', null, element); 91 | } else if (isDi(model) && property === 'waypoint') { 92 | this._layoutChanged[model.bpmnElement.id] = model.bpmnElement; 93 | } 94 | }; 95 | 96 | ChangeHandler.prototype.changed = function(model, property, newValue, oldValue) { 97 | 98 | var tracked; 99 | 100 | if (isDi(model)) { 101 | this._layoutChanged[model.bpmnElement.id] = model.bpmnElement; 102 | } else if (tracked = isTracked(model)) { 103 | var changed = this._changed[tracked.element.id]; 104 | 105 | if (!changed) { 106 | changed = this._changed[tracked.element.id] = { model: model, attrs: { } }; 107 | } 108 | 109 | if (oldValue !== undefined || newValue !== undefined) { 110 | changed.attrs[property] = { oldValue: oldValue, newValue: newValue }; 111 | } 112 | } 113 | }; 114 | 115 | ChangeHandler.prototype.added = function(model, property, element, idx) { 116 | 117 | var tracked; 118 | 119 | if (tracked = isTracked(element)) { 120 | if (!this._added[tracked.element.id]) { 121 | this._added[tracked.element.id] = element; 122 | } 123 | } else if (tracked = isTracked(model)) { 124 | this.changed(tracked.element, tracked.property + property + '[' + idx + ']', element, null); 125 | } else if (isDi(model) && property === 'waypoint') { 126 | this._layoutChanged[model.bpmnElement.id] = model.bpmnElement; 127 | } 128 | }; 129 | 130 | ChangeHandler.prototype.moved = function(model, property, oldIndex, newIndex) { 131 | 132 | // noop 133 | }; 134 | -------------------------------------------------------------------------------- /lib/diff.js: -------------------------------------------------------------------------------- 1 | import Differ from './differ.js'; 2 | 3 | export default function diff(a, b, handler) { 4 | return new Differ().diff(a, b, handler); 5 | } -------------------------------------------------------------------------------- /lib/differ.js: -------------------------------------------------------------------------------- 1 | import { 2 | forEach, 3 | reduce, 4 | isArray 5 | } from 'min-dash'; 6 | 7 | import { 8 | DiffPatcher 9 | } from 'diffpatch'; 10 | 11 | import ChangeHandler from './change-handler.js'; 12 | 13 | import { 14 | moddleFilter, 15 | moddleDiffFilter 16 | } from './filters.js'; 17 | 18 | 19 | export default function Differ() { } 20 | 21 | Differ.prototype.createDiff = function(a, b) { 22 | 23 | // create a configured instance, match objects by name 24 | var diffpatcher = new DiffPatcher({ 25 | objectHash: function(obj) { 26 | return obj.id || JSON.stringify(obj); 27 | }, 28 | propertyFilter: function(name, context) { 29 | return name !== '$instanceOf'; 30 | } 31 | }); 32 | 33 | // tag elements as appropriate 34 | diffpatcher.processor.pipe('diff').after('trivial', moddleFilter); 35 | 36 | // handle moddle elements 37 | diffpatcher.processor.pipe('diff').after('objects', moddleDiffFilter); 38 | 39 | return diffpatcher.diff(a, b); 40 | }; 41 | 42 | 43 | Differ.prototype.diff = function(a, b, handler) { 44 | 45 | handler = handler || new ChangeHandler(); 46 | 47 | function walk(diff, model) { 48 | 49 | forEach(diff, function(d, key) { 50 | 51 | if (d._t !== 'a' && isArray(d)) { 52 | 53 | // take into account that collection properties are lazily 54 | // initialized; this means that adding to an empty collection 55 | // looks like setting an undefined variable to [] 56 | // 57 | // ensure we detect this case and change it to an array diff 58 | if (isArray(d[0])) { 59 | 60 | d = reduce(d[0], function(newDelta, element, idx) { 61 | var prefix = d.length === 3 ? '_' : ''; 62 | 63 | newDelta[prefix + idx] = [ element ]; 64 | 65 | return newDelta; 66 | }, { _t: 'a' }); 67 | } 68 | 69 | } 70 | 71 | 72 | // is array 73 | if (d._t === 'a') { 74 | 75 | forEach(d, function(val, idx) { 76 | 77 | if (idx === '_t') { 78 | return; 79 | } 80 | 81 | var removed = /^_/.test(idx), 82 | added = !removed && isArray(val), 83 | moved = removed && val[0] === ''; 84 | 85 | idx = parseInt(removed ? idx.slice(1) : idx, 10); 86 | 87 | if (added || (removed && !moved)) { 88 | handler[removed ? 'removed' : 'added'](model, key, val[0], idx); 89 | } else if (moved) { 90 | handler.moved(model, key, val[1], val[2]); 91 | } else { 92 | walk(val, model[key][idx]); 93 | } 94 | }); 95 | } else { 96 | if (isArray(d)) { 97 | handler.changed(model, key, d[0], d[1]); 98 | } else { 99 | handler.changed(model, key); 100 | walk(d, model[key]); 101 | } 102 | } 103 | }); 104 | } 105 | 106 | var diff = this.createDiff(a, b); 107 | 108 | walk(diff, b, handler); 109 | 110 | return handler; 111 | }; -------------------------------------------------------------------------------- /lib/filters.js: -------------------------------------------------------------------------------- 1 | import DiffContext from 'diffpatch/lib/contexts/diff.js'; 2 | 3 | /** 4 | * A filter that detects and marks `moddle` elements, 5 | * so we can later calculate change sets for them. 6 | * 7 | * @param {any} context 8 | */ 9 | export function moddleFilter(context) { 10 | 11 | if (context.left && context.left.$instanceOf) { 12 | context.leftType = 'moddle'; 13 | } 14 | 15 | if (context.right && context.right.$instanceOf) { 16 | context.rightType = 'moddle'; 17 | } 18 | } 19 | 20 | moddleFilter.filterName = 'moddle'; 21 | 22 | 23 | /** 24 | * A filter that creates a change set for a given context, 25 | * using `moddle` infrastructure to figure which changes 26 | * to actually diff. 27 | * 28 | * It ensures that we traverse all relevant relationships 29 | * and serializes references to prevent endless loops. 30 | * 31 | * This filter assumes we don't ever compare `moddle` elements 32 | * with plain objects. 33 | * 34 | * @param {any} context 35 | */ 36 | export function moddleDiffFilter(context) { 37 | if (context.leftIsArray || context.leftType !== 'moddle') { 38 | return; 39 | } 40 | 41 | const leftProperties = getModdleProperties(context.left); 42 | 43 | let property; 44 | let child; 45 | 46 | const propertyFilter = context.options.propertyFilter; 47 | 48 | if (context.left.$type !== context.right.$type) { 49 | child = new DiffContext(context.left.$type, context.right.$type); 50 | context.push(child, '$type'); 51 | } 52 | 53 | for (property of leftProperties) { 54 | 55 | const { name, isVirtual, isMany, isReference } = property; 56 | 57 | if (isVirtual || (isMany && isReference)) { 58 | continue; 59 | } 60 | 61 | if (propertyFilter && !propertyFilter(name, context)) { 62 | continue; 63 | } 64 | 65 | child = new DiffContext(unref(context.left, name), unref(context.right, name)); 66 | context.push(child, name); 67 | } 68 | 69 | const rightProperties = getModdleProperties(context.right); 70 | 71 | for (property of rightProperties) { 72 | 73 | const { name, isVirtual, isMany, isReference } = property; 74 | 75 | if (isVirtual || (isMany && isReference)) { 76 | continue; 77 | } 78 | 79 | if (propertyFilter && !propertyFilter(name, context)) { 80 | continue; 81 | } 82 | 83 | if (typeof context.left[name] === 'undefined') { 84 | child = new DiffContext(undefined, unref(context.right, name)); 85 | context.push(child, name); 86 | } 87 | } 88 | 89 | if (!context.children || context.children.length === 0) { 90 | context.setResult(undefined).exit(); 91 | return; 92 | } 93 | context.exit(); 94 | } 95 | 96 | moddleDiffFilter.filterName = 'moddleDiff'; 97 | 98 | 99 | /** 100 | * Returns the ID to an external reference, or the actual 101 | * object for containment relationships. 102 | * 103 | * @param {ModdleElement} moddleElement 104 | * @param {string} propertyName 105 | * 106 | * @return {ModdleElement|string|any} 107 | */ 108 | function unref(moddleElement, propertyName) { 109 | 110 | const { 111 | isGeneric, 112 | idProperty, 113 | propertiesByName 114 | } = moddleElement.$descriptor; 115 | 116 | const value = moddleElement[propertyName]; 117 | 118 | if (isGeneric) { 119 | return value; 120 | } 121 | 122 | const property = propertiesByName[propertyName]; 123 | 124 | if (property && !property.isMany && property.isReference) { 125 | return value && idProperty && `#ref:${value.get(idProperty.name)}`; 126 | } 127 | 128 | return value; 129 | } 130 | 131 | /** 132 | * @param {ModdleElement} genericModdleElement 133 | * 134 | * @return {any[]} 135 | */ 136 | function createGenericProperties(genericModdleElement) { 137 | 138 | return Object.keys(genericModdleElement).flatMap(key => { 139 | return key !== '$type' ? { name: key } : []; 140 | }); 141 | } 142 | 143 | /** 144 | * Returns the properties to iterate over when 145 | * diffing a particular moddle element. 146 | * 147 | * @param {ModdleElement} moddleElement 148 | * 149 | * @return {any[]} 150 | */ 151 | function getModdleProperties(moddleElement) { 152 | 153 | const { 154 | properties, 155 | isGeneric 156 | } = moddleElement.$descriptor; 157 | 158 | if (isGeneric) { 159 | return createGenericProperties(moddleElement); 160 | } else { 161 | return properties; 162 | } 163 | } -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | export { 2 | default as Differ 3 | } from './differ.js'; 4 | 5 | export { 6 | default as diff 7 | } from './diff.js'; -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef { any } ModdleElement 3 | */ 4 | 5 | /** 6 | * @param {ModdleElement} element 7 | * @param {string} type 8 | * 9 | * @return {boolean} 10 | */ 11 | export function is(element, type) { 12 | return element.$instanceOf(type); 13 | } 14 | 15 | /** 16 | * @param {ModdleElement} element 17 | * @param {string[]} types 18 | * 19 | * @return {boolean} 20 | */ 21 | export function isAny(element, types) { 22 | return types.some(function(t) { 23 | return is(element, t); 24 | }); 25 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bpmn-js-differ", 3 | "version": "3.0.2", 4 | "description": "A semantic diffing utility for BPMN 2.0 files", 5 | "scripts": { 6 | "all": "run-s lint test", 7 | "lint": "eslint .", 8 | "pretest": "rollup -c --bundleConfigAsCjs", 9 | "test": "mocha test/spec/*.js" 10 | }, 11 | "main": "./dist/index.cjs", 12 | "exports": { 13 | "./package.json": "./package.json", 14 | ".": { 15 | "import": "./dist/index.esm.js", 16 | "node": "./dist/index.cjs" 17 | } 18 | }, 19 | "type": "module", 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/bpmn-io/bpmn-js-differ" 23 | }, 24 | "keywords": [ 25 | "bpmnjs", 26 | "diff" 27 | ], 28 | "author": { 29 | "name": "Nico Rehwaldt", 30 | "url": "https://github.com/Nikku" 31 | }, 32 | "contributors": [ 33 | { 34 | "name": "bpmn.io contributors", 35 | "url": "https://github.com/bpmn-io" 36 | } 37 | ], 38 | "license": "MIT", 39 | "devDependencies": { 40 | "@rollup/plugin-node-resolve": "^15.2.3", 41 | "bpmn-moddle": "^9.0.1", 42 | "chai": "^4.1.2", 43 | "eslint": "^8.57.0", 44 | "eslint-plugin-bpmn-io": "^1.0.1", 45 | "mocha": "^8.4.0", 46 | "npm-run-all": "^4.1.2", 47 | "rollup": "^4.19.0" 48 | }, 49 | "dependencies": { 50 | "diffpatch": "^0.6.0", 51 | "min-dash": "^4.2.1" 52 | }, 53 | "files": [ 54 | "dist" 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import nodeResolve from '@rollup/plugin-node-resolve'; 2 | 3 | import pkg from './package.json'; 4 | 5 | export default [ 6 | { 7 | input: 'lib/index.js', 8 | output: [ 9 | { file: pkg['exports']['.'].node, format: 'cjs' }, 10 | { file: pkg['exports']['.'].import, format: 'es' } 11 | ], 12 | external: [ 'min-dash' ], 13 | plugins: pgl() 14 | } 15 | ]; 16 | 17 | 18 | function pgl(plugins = []) { 19 | 20 | return [ 21 | nodeResolve(), 22 | ...plugins 23 | ]; 24 | } -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "plugin:bpmn-io/mocha" 3 | } -------------------------------------------------------------------------------- /test/fixtures/add/after.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SequenceFlow_1 6 | 7 | 8 | SequenceFlow_1 9 | SequenceFlow_2 10 | 11 | 12 | 13 | SequenceFlow_2 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /test/fixtures/add/before.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SequenceFlow_1 6 | 7 | 8 | SequenceFlow_1 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /test/fixtures/change/after.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SequenceFlow_1 6 | 7 | 8 | SequenceFlow_1 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /test/fixtures/change/before.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SequenceFlow_1 6 | 7 | 8 | SequenceFlow_1 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /test/fixtures/change/type-change.after.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /test/fixtures/change/type-change.before.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /test/fixtures/collaboration/after.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /test/fixtures/collaboration/before.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Task_1 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /test/fixtures/collaboration/message-flow-after.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /test/fixtures/collaboration/message-flow-before.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /test/fixtures/data-objects/after.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | DataObjectReference_B 10 | Property_0icr6ru 11 | 12 | 13 | DataStoreReference_D 14 | 15 | 16 | DataObjectReference_E 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 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /test/fixtures/data-objects/before.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | DataObjectReference_B 11 | Property_0icr6ru 12 | 13 | 14 | DataObjectReference_A 15 | Property_0icr6ru 16 | 17 | 18 | DataStoreReference_C 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 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /test/fixtures/different-collaboration/after.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | SequenceFlow_0v46ced 15 | 16 | 17 | SequenceFlow_11olgcr 18 | 19 | 20 | 21 | 22 | SequenceFlow_11olgcr 23 | 24 | 25 | 26 | 27 | 28 | SequenceFlow_0v46ced 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /test/fixtures/different-collaboration/before.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | StartEvent_1 11 | 12 | 13 | Task_0kdjqv7 14 | 15 | 16 | EndEvent_0fm1cek 17 | 18 | 19 | 20 | 21 | 22 | SequenceFlow_1qq277f 23 | 24 | 25 | 26 | 27 | SequenceFlow_1qq277f 28 | 29 | 30 | SequenceFlow_0hkqeae 31 | 32 | 33 | 34 | 35 | 36 | SequenceFlow_0hkqeae 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /test/fixtures/event-definition/after.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test/fixtures/event-definition/before.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/fixtures/extension-elements/c7.after.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 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 | 43 | 44 | -------------------------------------------------------------------------------- /test/fixtures/extension-elements/c7.before.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /test/fixtures/extension-elements/signavio.after.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /test/fixtures/extension-elements/signavio.before.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /test/fixtures/incoming-outgoing/add-task.after.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | FLOW_1 6 | 7 | 8 | FLOW_2 9 | 10 | 11 | 12 | FLOW_1 13 | FLOW_2 14 | 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 | -------------------------------------------------------------------------------- /test/fixtures/incoming-outgoing/add-task.before.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | FLOW_1 6 | 7 | 8 | FLOW_1 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /test/fixtures/incoming-outgoing/reconnect.after.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | SEQUENCE_FLOW 12 | 13 | 14 | SEQUENCE_FLOW 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 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /test/fixtures/incoming-outgoing/reconnect.before.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | SEQUENCE_FLOW 11 | 12 | 13 | SEQUENCE_FLOW 14 | 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 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /test/fixtures/lanes/create-laneset-after.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /test/fixtures/lanes/create-laneset-before.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /test/fixtures/layout-change/after.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SequenceFlow_1 6 | 7 | 8 | 9 | SequenceFlow_1 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /test/fixtures/layout-change/before.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SequenceFlow_1 6 | 7 | 8 | SequenceFlow_1 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /test/fixtures/pizza-collaboration/new.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | _6-450 9 | _6-652 10 | _6-695 11 | ManualTask_1 12 | 13 | 14 | _6-463 15 | 16 | 17 | _6-514 18 | _6-565 19 | _6-616 20 | 21 | 22 | 23 | _6-630 24 | 25 | 26 | 27 | _6-630 28 | _6-693 29 | 30 | 31 | 32 | _6-693 33 | _6-632 34 | 35 | 36 | _6-632 37 | _6-634 38 | 39 | 40 | _6-634 41 | _6-636 42 | 43 | 44 | _6-636 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | _6-420 59 | 60 | 61 | _6-420 62 | _6-430 63 | _6-422 64 | _6-424 65 | 66 | 67 | _6-422 68 | _6-428 69 | 70 | 71 | 72 | _6-424 73 | _6-426 74 | 75 | 76 | 77 | 78 | 79 | _6-426 80 | _6-430 81 | 82 | 83 | _6-428 84 | _6-434 85 | 86 | 87 | _6-434 88 | _6-436 89 | 90 | 91 | _6-436 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | -------------------------------------------------------------------------------- /test/fixtures/pizza-collaboration/old.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | _6-450 9 | _6-652 10 | _6-674 11 | _6-695 12 | 13 | 14 | _6-463 15 | 16 | 17 | _6-514 18 | _6-565 19 | _6-616 20 | 21 | 22 | 23 | _6-630 24 | 25 | 26 | 27 | _6-630 28 | _6-691 29 | _6-693 30 | 31 | 32 | _6-691 33 | _6-746 34 | _6-748 35 | 36 | 37 | 38 | _6-748 39 | _6-746 40 | 41 | 42 | _6-693 43 | _6-632 44 | 45 | 46 | _6-632 47 | _6-634 48 | 49 | 50 | _6-634 51 | _6-636 52 | 53 | 54 | _6-636 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | _6-125 70 | 71 | 72 | _6-125 73 | _6-178 74 | 75 | 76 | _6-178 77 | _6-420 78 | 79 | 80 | _6-420 81 | _6-430 82 | _6-422 83 | _6-424 84 | 85 | 86 | _6-422 87 | _6-428 88 | 89 | 90 | 91 | _6-424 92 | _6-426 93 | 94 | 95 | 96 | 97 | 98 | _6-426 99 | _6-430 100 | 101 | 102 | _6-428 103 | _6-434 104 | 105 | 106 | _6-434 107 | _6-436 108 | 109 | 110 | _6-436 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | -------------------------------------------------------------------------------- /test/fixtures/pizza-collaboration/start-event-new.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/fixtures/pizza-collaboration/start-event-old.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /test/fixtures/remove/after.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /test/fixtures/remove/before.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SequenceFlow_1 6 | 7 | 8 | SequenceFlow_1 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /test/fixtures/signavio-collapsed/after.expanded.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | SequenceFlow_2 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | SequenceFlow_1 47 | SequenceFlow_2 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | SequenceFlow_1 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /test/fixtures/signavio-collapsed/before.collapsed.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | SequenceFlow_2 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | SequenceFlow_1 47 | SequenceFlow_2 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | SequenceFlow_1 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /test/fixtures/sub-processes/after.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 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 | -------------------------------------------------------------------------------- /test/fixtures/sub-processes/before.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 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 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /test/spec/differ.js: -------------------------------------------------------------------------------- 1 | import { 2 | expect 3 | } from 'chai'; 4 | 5 | import { 6 | readFileSync 7 | } from 'fs'; 8 | 9 | import BpmnModdle from 'bpmn-moddle'; 10 | 11 | import { 12 | Differ, 13 | diff 14 | } from 'bpmn-js-differ'; 15 | 16 | import SimpleChangeHandler from '../../lib/change-handler.js'; 17 | 18 | 19 | describe('diffing', function() { 20 | 21 | describe('diff', function() { 22 | 23 | it('should discover add', async function() { 24 | 25 | var aDiagram = readFileSync('test/fixtures/add/before.bpmn', 'utf-8'); 26 | var bDiagram = readFileSync('test/fixtures/add/after.bpmn', 'utf-8'); 27 | 28 | // when 29 | await testDiff(aDiagram, bDiagram, function(results, aDefinitions, bDefinitions) { 30 | 31 | // then 32 | expect(results._added).to.have.keys([ 'EndEvent_1', 'SequenceFlow_2' ]); 33 | expect(results._removed).to.eql({}); 34 | expect(results._layoutChanged).to.eql({}); 35 | expect(results._changed).to.eql({}); 36 | }); 37 | 38 | }); 39 | 40 | 41 | describe('should discover flow changed', function() { 42 | 43 | it('after reconnect', async function() { 44 | 45 | var aDiagram = readFileSync('test/fixtures/incoming-outgoing/reconnect.before.bpmn', 'utf-8'); 46 | var bDiagram = readFileSync('test/fixtures/incoming-outgoing/reconnect.after.bpmn', 'utf-8'); 47 | 48 | // when 49 | await testDiff(aDiagram, bDiagram, function(results, aDefinitions, bDefinitions) { 50 | 51 | // then 52 | expect(results._added, 'added').to.be.eql({}); 53 | expect(results._removed, 'removed').to.eql({}); 54 | expect(results._layoutChanged, 'layout changed').to.have.keys([ 'SEQUENCE_FLOW', 'MESSAGE_FLOW' ]); 55 | expect(results._changed, 'changed').to.have.keys([ 'SEQUENCE_FLOW', 'MESSAGE_FLOW' ]); 56 | 57 | expect(results._changed['SEQUENCE_FLOW'].attrs).to.deep.eql({ 58 | targetRef: { oldValue: '#ref:TASK_2', newValue: '#ref:TASK_1' } 59 | }); 60 | }); 61 | 62 | }); 63 | 64 | 65 | it('after task insert', async function() { 66 | 67 | var aDiagram = readFileSync('test/fixtures/incoming-outgoing/add-task.before.bpmn', 'utf-8'); 68 | var bDiagram = readFileSync('test/fixtures/incoming-outgoing/add-task.after.bpmn', 'utf-8'); 69 | 70 | // when 71 | await testDiff(aDiagram, bDiagram, function(results, aDefinitions, bDefinitions) { 72 | 73 | // then 74 | expect(results._added, 'added').to.have.keys([ 'TASK', 'FLOW_2' ]); 75 | expect(results._removed, 'removed').to.eql({}); 76 | expect(results._layoutChanged, 'layout changed').to.have.keys([ 'FLOW_1' ]); 77 | expect(results._changed, 'changed').to.have.keys([ 'FLOW_1' ]); 78 | }); 79 | 80 | }); 81 | 82 | }); 83 | 84 | 85 | it('should discover remove', async function() { 86 | 87 | var aDiagram = readFileSync('test/fixtures/remove/before.bpmn', 'utf-8'); 88 | var bDiagram = readFileSync('test/fixtures/remove/after.bpmn', 'utf-8'); 89 | 90 | // when 91 | await testDiff(aDiagram, bDiagram, function(results, aDefinitions, bDefinitions) { 92 | 93 | // then 94 | expect(results._added).to.eql({}); 95 | expect(results._removed).to.have.keys([ 'Task_1', 'SequenceFlow_1' ]); 96 | expect(results._layoutChanged).to.eql({}); 97 | expect(results._changed).to.eql({}); 98 | }); 99 | 100 | }); 101 | 102 | 103 | describe('should discover change', function() { 104 | 105 | it('property', async function() { 106 | 107 | var aDiagram = readFileSync('test/fixtures/change/before.bpmn', 'utf-8'); 108 | var bDiagram = readFileSync('test/fixtures/change/after.bpmn', 'utf-8'); 109 | 110 | // when 111 | await testDiff(aDiagram, bDiagram, function(results, aDefinitions, bDefinitions) { 112 | 113 | // then 114 | expect(results._added).to.eql({}); 115 | expect(results._removed).to.eql({}); 116 | expect(results._layoutChanged).to.eql({}); 117 | expect(results._changed).to.have.keys([ 'Task_1' ]); 118 | 119 | expect(results._changed['Task_1'].attrs).to.deep.eql({ 120 | name: { oldValue: undefined, newValue: 'TASK' } 121 | }); 122 | }); 123 | 124 | }); 125 | 126 | 127 | it('$type', async function() { 128 | 129 | var aDiagram = readFileSync('test/fixtures/change/type-change.before.bpmn', 'utf-8'); 130 | var bDiagram = readFileSync('test/fixtures/change/type-change.after.bpmn', 'utf-8'); 131 | 132 | // when 133 | await testDiff(aDiagram, bDiagram, function(results, aDefinitions, bDefinitions) { 134 | 135 | // then 136 | expect(results._added).to.eql({}); 137 | expect(results._removed).to.eql({}); 138 | expect(results._layoutChanged).to.eql({}); 139 | expect(results._changed).to.have.keys([ 'TASK' ]); 140 | 141 | expect(results._changed['TASK'].attrs).to.deep.eql({ 142 | $type: { oldValue: 'bpmn:ServiceTask', newValue: 'bpmn:Task' } 143 | }); 144 | }); 145 | 146 | }); 147 | 148 | }); 149 | 150 | 151 | it('should discover layout-change', async function() { 152 | 153 | var aDiagram = readFileSync('test/fixtures/layout-change/before.bpmn', 'utf-8'); 154 | var bDiagram = readFileSync('test/fixtures/layout-change/after.bpmn', 'utf-8'); 155 | 156 | // when 157 | await testDiff(aDiagram, bDiagram, function(results, aDefinitions, bDefinitions) { 158 | 159 | // then 160 | expect(results._added).to.eql({}); 161 | expect(results._removed).to.eql({}); 162 | expect(results._layoutChanged).to.have.keys([ 'Task_1', 'SequenceFlow_1' ]); 163 | expect(results._changed).to.eql({}); 164 | }); 165 | 166 | }); 167 | 168 | }); 169 | 170 | 171 | describe('api', function() { 172 | 173 | it('should diff with default handler', async function() { 174 | 175 | var aDiagram = readFileSync('test/fixtures/layout-change/before.bpmn', 'utf-8'); 176 | var bDiagram = readFileSync('test/fixtures/layout-change/after.bpmn', 'utf-8'); 177 | 178 | // when 179 | importDiagrams(aDiagram, bDiagram, function(aDefinitions, bDefinitions) { 180 | 181 | // when 182 | var results = new Differ().diff(aDefinitions, bDefinitions); 183 | 184 | // then 185 | expect(results._added).to.eql({}); 186 | expect(results._removed).to.eql({}); 187 | expect(results._layoutChanged).to.have.keys([ 'Task_1', 'SequenceFlow_1' ]); 188 | expect(results._changed).to.eql({}); 189 | }); 190 | 191 | }); 192 | 193 | 194 | it('should diff via static diff', async function() { 195 | 196 | var aDiagram = readFileSync('test/fixtures/layout-change/before.bpmn', 'utf-8'); 197 | var bDiagram = readFileSync('test/fixtures/layout-change/after.bpmn', 'utf-8'); 198 | 199 | // when 200 | await importDiagrams(aDiagram, bDiagram, function(aDefinitions, bDefinitions) { 201 | 202 | // when 203 | var results = diff(aDefinitions, bDefinitions); 204 | 205 | // then 206 | expect(results._added).to.eql({}); 207 | expect(results._removed).to.eql({}); 208 | expect(results._layoutChanged).to.have.keys([ 'Task_1', 'SequenceFlow_1' ]); 209 | expect(results._changed).to.eql({}); 210 | }); 211 | 212 | }); 213 | 214 | }); 215 | 216 | 217 | describe('should diff scenario', function() { 218 | 219 | 220 | it('collaboration pools / lanes', async function() { 221 | 222 | var aDiagram = readFileSync('test/fixtures/collaboration/before.bpmn', 'utf-8'); 223 | var bDiagram = readFileSync('test/fixtures/collaboration/after.bpmn', 'utf-8'); 224 | 225 | 226 | // when 227 | await testDiff(aDiagram, bDiagram, function(results, aDefinitions, bDefinitions) { 228 | 229 | // then 230 | expect(results._added).to.have.keys([ 'Participant_2' ]); 231 | expect(results._removed).to.have.keys([ 'Participant_1', 'Lane_1', 'Task_1' ]); 232 | expect(results._layoutChanged).to.have.keys([ '_Participant_2', 'Lane_2' ]); 233 | expect(results._changed).to.have.keys([ 'Lane_2' ]); 234 | }); 235 | }); 236 | 237 | 238 | it('lanes create', async function() { 239 | 240 | var aDiagram = readFileSync('test/fixtures/lanes/create-laneset-before.bpmn', 'utf-8'); 241 | var bDiagram = readFileSync('test/fixtures/lanes/create-laneset-after.bpmn', 'utf-8'); 242 | 243 | 244 | // when 245 | await testDiff(aDiagram, bDiagram, function(results, aDefinitions, bDefinitions) { 246 | 247 | // then 248 | expect(results._added).to.be.empty; 249 | expect(results._removed).to.be.empty; 250 | expect(results._layoutChanged).to.be.empty; 251 | expect(results._changed).to.have.keys([ 'Participant_03hz6qm' ]); 252 | 253 | var changed = results._changed['Participant_03hz6qm']; 254 | 255 | expect(changed.attrs).to.have.keys([ 'processRef.laneSets[0]' ]); 256 | 257 | var changedLaneSets = changed.attrs['processRef.laneSets[0]']; 258 | 259 | expect(changedLaneSets.oldValue).not.to.exist; 260 | expect(changedLaneSets.newValue).to.exist; 261 | }); 262 | }); 263 | 264 | 265 | it('lanes remove', async function() { 266 | 267 | var aDiagram = readFileSync('test/fixtures/lanes/create-laneset-after.bpmn', 'utf-8'); 268 | var bDiagram = readFileSync('test/fixtures/lanes/create-laneset-before.bpmn', 'utf-8'); 269 | 270 | 271 | // when 272 | await testDiff(aDiagram, bDiagram, function(results, aDefinitions, bDefinitions) { 273 | 274 | // then 275 | expect(results._added).to.be.empty; 276 | expect(results._removed).to.be.empty; 277 | expect(results._layoutChanged).to.be.empty; 278 | expect(results._changed).to.have.keys([ 'Participant_03hz6qm' ]); 279 | }); 280 | }); 281 | 282 | 283 | it('collaboration message flow', async function() { 284 | 285 | var aDiagram = readFileSync('test/fixtures/collaboration/message-flow-before.bpmn', 'utf-8'); 286 | var bDiagram = readFileSync('test/fixtures/collaboration/message-flow-after.bpmn', 'utf-8'); 287 | 288 | 289 | // when 290 | await testDiff(aDiagram, bDiagram, function(results, aDefinitions, bDefinitions) { 291 | 292 | // then 293 | expect(results._added).to.be.empty; 294 | expect(results._removed).to.have.keys([ 295 | 'Participant_1w6hx42', 296 | 'MessageFlow_1ofxm38' 297 | ]); 298 | expect(results._layoutChanged).to.be.empty; 299 | expect(results._changed).to.be.empty; 300 | }); 301 | 302 | }); 303 | 304 | 305 | describe('extension elements', function() { 306 | 307 | it('c7', async function() { 308 | 309 | var aDiagram = readFileSync('test/fixtures/extension-elements/c7.before.bpmn', 'utf-8'); 310 | var bDiagram = readFileSync('test/fixtures/extension-elements/c7.after.bpmn', 'utf-8'); 311 | 312 | // when 313 | await testDiff(aDiagram, bDiagram, function(results, aDefinitions, bDefinitions) { 314 | 315 | // then 316 | expect(results._added).to.be.empty; 317 | expect(results._removed).to.be.empty; 318 | expect(results._layoutChanged).to.be.empty; 319 | expect(results._changed).to.have.keys([ 'usertask' ]); 320 | }); 321 | }); 322 | 323 | 324 | it('signavio', async function() { 325 | 326 | var aDiagram = readFileSync('test/fixtures/extension-elements/signavio.before.bpmn', 'utf-8'); 327 | var bDiagram = readFileSync('test/fixtures/extension-elements/signavio.after.bpmn', 'utf-8'); 328 | 329 | 330 | // when 331 | await testDiff(aDiagram, bDiagram, function(results, aDefinitions, bDefinitions) { 332 | 333 | // then 334 | expect(results._added).to.be.empty; 335 | expect(results._removed).to.be.empty; 336 | expect(results._layoutChanged).to.be.empty; 337 | expect(results._changed).to.have.keys([ 'Process_1' ]); 338 | }); 339 | }); 340 | 341 | }); 342 | 343 | 344 | it('pizza collaboration StartEvent move', async function() { 345 | 346 | var aDiagram = readFileSync('test/fixtures/pizza-collaboration/start-event-old.bpmn', 'utf-8'); 347 | var bDiagram = readFileSync('test/fixtures/pizza-collaboration/start-event-new.bpmn', 'utf-8'); 348 | 349 | 350 | // when 351 | await testDiff(aDiagram, bDiagram, function(results, aDefinitions, bDefinitions) { 352 | 353 | // then 354 | expect(results._added).to.eql({}); 355 | expect(results._removed).to.eql({}); 356 | expect(results._layoutChanged).to.have.keys([ '_6-61' ]); 357 | expect(results._changed).to.eql({}); 358 | }); 359 | }); 360 | 361 | 362 | it('pizza collaboration', async function() { 363 | 364 | var aDiagram = readFileSync('test/fixtures/pizza-collaboration/old.bpmn', 'utf-8'); 365 | var bDiagram = readFileSync('test/fixtures/pizza-collaboration/new.bpmn', 'utf-8'); 366 | 367 | // when 368 | await testDiff(aDiagram, bDiagram, function(results, aDefinitions, bDefinitions) { 369 | 370 | // then 371 | expect(results._added).to.have.keys([ 372 | 'ManualTask_1', 373 | 'ExclusiveGateway_1' 374 | ]); 375 | 376 | expect(results._removed).to.have.keys([ 377 | '_6-674', '_6-691', '_6-746', '_6-748', '_6-74', '_6-125', '_6-178', '_6-642' 378 | ]); 379 | 380 | expect(results._layoutChanged).to.have.keys([ 381 | '_6-61' 382 | ]); 383 | 384 | expect(results._changed).to.have.keys([ 385 | '_6-127', 386 | '_6-219' 387 | ]); 388 | }); 389 | }); 390 | 391 | 392 | it('data-objects', async function() { 393 | 394 | var aDiagram = readFileSync('test/fixtures/data-objects/before.bpmn', 'utf-8'); 395 | var bDiagram = readFileSync('test/fixtures/data-objects/after.bpmn', 'utf-8'); 396 | 397 | 398 | // when 399 | await testDiff(aDiagram, bDiagram, function(results, aDefinitions, bDefinitions) { 400 | 401 | // then 402 | expect(results._added).to.have.keys([ 403 | 'DataObjectReference_E', 404 | 'DataOutputAssociation_2', 405 | 'DataOutputAssociation_3', 406 | 'DataStoreReference_D' 407 | ]); 408 | 409 | expect(results._removed).to.have.keys([ 410 | 'DataInputAssociation_4', 411 | 'DataOutputAssociation_5', 412 | 'DataStoreReference_C' 413 | ]); 414 | 415 | expect(results._layoutChanged).to.have.keys([ 416 | 417 | // waypoints changed 418 | 'DataInputAssociation_1' 419 | ]); 420 | 421 | // TODO(nikku): detect bpmn:DataObjectReference#dataObject change 422 | expect(results._changed).to.have.keys([ 423 | 'Process_1' 424 | ]); 425 | }); 426 | }); 427 | 428 | 429 | it('event definition', async function() { 430 | 431 | var aDiagram = readFileSync('test/fixtures/event-definition/before.bpmn', 'utf-8'); 432 | var bDiagram = readFileSync('test/fixtures/event-definition/after.bpmn', 'utf-8'); 433 | 434 | 435 | // when 436 | await testDiff(aDiagram, bDiagram, function(results, aDefinitions, bDefinitions) { 437 | 438 | // then 439 | expect(results._added).to.be.empty; 440 | 441 | expect(results._removed).to.be.empty; 442 | 443 | expect(results._layoutChanged).to.be.empty; 444 | 445 | expect(results._changed).to.have.keys([ 446 | 'IntermediateThrowEvent_0mn39ym' 447 | ]); 448 | 449 | var changed = results._changed['IntermediateThrowEvent_0mn39ym']; 450 | 451 | expect(changed.attrs).to.have.keys([ 452 | 'eventDefinitions[0]' 453 | ]); 454 | }); 455 | }); 456 | 457 | 458 | it('sub-processes', async function() { 459 | 460 | var aDiagram = readFileSync('test/fixtures/sub-processes/before.bpmn', 'utf-8'); 461 | var bDiagram = readFileSync('test/fixtures/sub-processes/after.bpmn', 'utf-8'); 462 | 463 | 464 | // when 465 | await testDiff(aDiagram, bDiagram, function(results, aDefinitions, bDefinitions) { 466 | 467 | // then 468 | expect(results._added).to.have.keys([ 469 | 'Task_F', 470 | 'SubProcess_4' 471 | ]); 472 | 473 | expect(results._removed).to.have.keys([ 474 | 'Task_A', 475 | 'Task_B', 476 | 'SubProcess_3' 477 | ]); 478 | 479 | expect(results._layoutChanged).to.have.keys([ 480 | 481 | // sub-process collapsed state changed 482 | 'SubProcess_5' 483 | ]); 484 | 485 | expect(results._changed).to.be.empty; 486 | }); 487 | }); 488 | 489 | 490 | it('signavio-collapsed', async function() { 491 | 492 | var aDiagram = readFileSync('test/fixtures/signavio-collapsed/before.collapsed.bpmn', 'utf-8'); 493 | var bDiagram = readFileSync('test/fixtures/signavio-collapsed/after.expanded.bpmn', 'utf-8'); 494 | 495 | // when 496 | await testDiff(aDiagram, bDiagram, function(results, aDefinitions, bDefinitions) { 497 | 498 | // then 499 | expect(results._added).to.be.empty; 500 | 501 | expect(results._removed).to.be.empty; 502 | 503 | expect(results._layoutChanged).to.have.keys([ 504 | 505 | // sub-process collapsed state changed 506 | 'SubProcess_1' 507 | ]); 508 | 509 | expect(results._changed).to.be.empty; 510 | }); 511 | }); 512 | 513 | 514 | it('different collaborations', async function() { 515 | 516 | const aDiagram = readFileSync('test/fixtures/different-collaboration/before.bpmn', 'utf-8'); 517 | const bDiagram = readFileSync('test/fixtures/different-collaboration/after.bpmn', 'utf-8'); 518 | 519 | // when 520 | await testDiff(aDiagram, bDiagram, function(results, aDefinitions, bDefinitions) { 521 | 522 | // then 523 | expect(results._added).to.have.keys([ 524 | 'Collaboration_108r8n7', 525 | 'Participant_1sdnyht' 526 | ]); 527 | 528 | expect(results._removed).to.have.keys([ 529 | 'Collaboration_1cidyxu', 530 | 'Participant_0px403d' 531 | ]); 532 | 533 | expect(results._layoutChanged).to.be.empty; 534 | 535 | expect(results._changed).to.be.empty; 536 | }); 537 | }); 538 | 539 | }); 540 | 541 | }); 542 | 543 | 544 | // helpers ////////////////// 545 | 546 | async function importDiagrams(a, b, done) { 547 | 548 | const [ 549 | { rootElement: aDefs }, 550 | { rootElement: bDefs } 551 | ] = await Promise.all([ 552 | new BpmnModdle().fromXML(a), 553 | new BpmnModdle().fromXML(b), 554 | ]); 555 | 556 | return done(aDefs, bDefs); 557 | } 558 | 559 | 560 | function testDiff(a, b, done) { 561 | 562 | return importDiagrams(a, b, function(adefs, bdefs) { 563 | 564 | // given 565 | var handler = new SimpleChangeHandler(); 566 | 567 | // when 568 | new Differ().diff(adefs, bdefs, handler); 569 | 570 | return done(handler, adefs, bdefs); 571 | }); 572 | 573 | } 574 | --------------------------------------------------------------------------------