123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855 |
- /* global sinon */
- import {
- bootstrapDiagram,
- inject
- } from 'test/TestHelper';
- import cmdModule from 'lib/command';
- // example commands
- function TracableCommand(id) {
- this.execute = function(ctx) {
- ctx.element.trace.push(id);
- };
- this.revert = function(ctx) {
- expect(ctx.element.trace.pop()).to.equal(id);
- };
- }
- function SimpleCommand() {
- TracableCommand.call(this, 'simple-command');
- }
- function ComplexCommand(commandStack) {
- TracableCommand.call(this, 'complex-command');
- this.preExecute = function(ctx) {
- commandStack.execute('pre-command', { element: ctx.element });
- };
- this.postExecute = function(ctx) {
- commandStack.execute('post-command', { element: ctx.element });
- };
- }
- function PreCommand() {
- TracableCommand.call(this, 'pre-command');
- }
- function PostCommand() {
- TracableCommand.call(this, 'post-command');
- }
- describe('command/CommandStack', function() {
- beforeEach(bootstrapDiagram({ modules: [ cmdModule ] }));
- describe('#register', function() {
- var handler = {
- execute: function(ctx) {
- ctx.heho = 'HE';
- },
- revert: function(ctx) {
- ctx.heho = 'HO';
- }
- };
- it('should register handler instance', inject(function(commandStack) {
- // when
- commandStack.register('heho', handler);
- // then
- expect(commandStack._getHandler('heho')).to.equal(handler);
- }));
- });
- describe('#registerHandler', function() {
- var Handler = function(eventBus) {
- this.execute = function(ctx) {
- expect(eventBus).to.be.an('object');
- ctx.heho = 'HE';
- };
- this.revert = function(ctx) {
- ctx.heho = 'HO';
- expect(eventBus).to.be.an('object');
- };
- };
- it('should register handler class', inject(function(commandStack) {
- // when
- commandStack.registerHandler('heho', Handler);
- // then
- expect(commandStack._getHandler('heho') instanceof Handler).to.eql(true);
- }));
- it('should inject handler instance', inject(function(commandStack) {
- // given
- commandStack.registerHandler('heho', Handler);
- var context = {};
- // when
- commandStack.execute('heho', context);
- // then
- expect(context.heho).to.equal('HE');
- }));
- });
- describe('#execute', function() {
- it('should throw error on no command', inject(function(commandStack) {
- expect(function() {
- commandStack.execute();
- }).to.throw();
- }));
- it('should throw error on no handler', inject(function(commandStack) {
- expect(function() {
- commandStack.execute('non-existing-command');
- }).to.throw();
- }));
- it('should execute command', inject(function(commandStack) {
- // given
- commandStack.registerHandler('simple-command', SimpleCommand);
- var context = { element: { trace: [] } };
- // when
- commandStack.execute('simple-command', context);
- // then
- expect(context.element.trace).to.eql([ 'simple-command' ]);
- expect(commandStack._stack.length).to.equal(1);
- expect(commandStack._stackIdx).to.equal(0);
- }));
- });
- describe('#canExecute', function() {
- it('should reject unhandled', inject(function(commandStack) {
- // when
- var canExecute = commandStack.canExecute('non-existing-command');
- // then
- expect(canExecute).to.be.false;
- }));
- it('should still allow unregistered commands on interceptor response', inject(function(eventBus, commandStack) {
- eventBus.on('commandStack.foo.canExecute', function() {
- return true;
- });
- // when
- var canExecute = commandStack.canExecute('foo');
- // then
- expect(canExecute).to.be.true;
- }));
- describe('should forward to handler', function() {
- function testCanExecute(commandStack, accept) {
- // given
- commandStack.register('command', {
- canExecute: function(context) {
- return (context.canExecute = accept);
- }
- });
- var context = { };
- // when
- var canExecute = commandStack.canExecute('command', context);
- // then
- expect(canExecute).to.equal(accept);
- expect(context.canExecute).to.equal(accept);
- }
- it('accepting', inject(function(commandStack) {
- testCanExecute(commandStack, true);
- }));
- it('rejecting', inject(function(commandStack) {
- testCanExecute(commandStack, false);
- }));
- });
- describe('should integrate with eventBus', function() {
- it('rejecting in listener', inject(function(eventBus, commandStack) {
- // given
- eventBus.on('commandStack.command.canExecute', function(event) {
- return (event.context.listenerCanExecute = false);
- });
- commandStack.register('command', {
- canExecute: function(context) {
- return (context.commandCanExecute = true);
- }
- });
- var context = { };
- // when
- var canExecute = commandStack.canExecute('command', context);
- // then
- expect(canExecute).to.be.false;
- expect(context.listenerCanExecute).to.be.false;
- expect(context.commandCanExecute).to.be.undefined;
- }));
- it('allowing in listener', inject(function(eventBus, commandStack) {
- // given
- eventBus.on('commandStack.command.canExecute', function(event) {
- return (event.context.listenerCanExecute = true);
- });
- commandStack.register('command', {
- canExecute: function(context) {
- return (context.commandCanExecute = false);
- }
- });
- var context = { };
- // when
- var canExecute = commandStack.canExecute('command', context);
- // then
- expect(canExecute).to.be.true;
- expect(context.listenerCanExecute).to.be.true;
- expect(context.commandCanExecute).to.be.undefined;
- }));
- it('rejecting in command', inject(function(eventBus, commandStack) {
- // given
- eventBus.on('commandStack.command.canExecute', function(event) {
- // do nothing, just chill
- });
- commandStack.register('command', {
- canExecute: function(context) {
- return (context.commandCanExecute = false);
- }
- });
- var context = { };
- // when
- var canExecute = commandStack.canExecute('command', context);
- // then
- expect(canExecute).to.be.false;
- expect(context.commandCanExecute).to.be.false;
- }));
- });
- });
- describe('#undo', function() {
- it('should not fail if nothing to undo', inject(function(commandStack) {
- expect(function() {
- commandStack.undo();
- }).not.to.throw();
- }));
- it('should undo command', inject(function(commandStack) {
- // given
- commandStack.registerHandler('simple-command', SimpleCommand);
- var context = { element: { trace: [] } };
- commandStack.execute('simple-command', context);
- // when
- commandStack.undo();
- // then
- expect(context.element.trace).to.eql([]);
- expect(commandStack._stack.length).to.equal(1);
- expect(commandStack._stackIdx).to.equal(-1);
- }));
- });
- describe('#redo', function() {
- it('should not fail if nothing to redo', inject(function(commandStack) {
- expect(function() {
- commandStack.redo();
- }).not.to.throw();
- }));
- it('should redo command', inject(function(commandStack) {
- // given
- commandStack.registerHandler('simple-command', SimpleCommand);
- var context = { element: { trace: [] } };
- commandStack.execute('simple-command', context);
- commandStack.undo();
- // when
- commandStack.redo();
- // then
- expect(context.element.trace).to.eql([ 'simple-command' ]);
- expect(commandStack._stack.length).to.equal(1);
- expect(commandStack._stackIdx).to.equal(0);
- }));
- });
- describe('command context', function() {
- it('should pass command context to handler', inject(function(commandStack) {
- // given
- var context = {};
- commandStack.register('command', {
- execute: function(ctx) {
- expect(ctx).to.equal(context);
- },
- revert: function(ctx) {
- expect(ctx).to.equal(context);
- }
- });
- // then
- // expect not to fail
- commandStack.execute('command', context);
- commandStack.undo();
- commandStack.redo('command', context);
- }));
- });
- describe('#preExecute / #postExecute support', function() {
- var element, context;
- beforeEach(inject(function(commandStack) {
- element = { trace: [] };
- context = {
- element: element
- };
- commandStack.registerHandler('complex-command', ComplexCommand);
- commandStack.registerHandler('pre-command', PreCommand);
- commandStack.registerHandler('post-command', PostCommand);
- }));
- it('should invoke #preExecute and #postExecute in order', inject(function(commandStack) {
- // when
- commandStack.execute('complex-command', context);
- // then
- expect(element.trace).to.eql([
- 'pre-command',
- 'complex-command',
- 'post-command'
- ]);
- }));
- it('should group {pre,actual,post} commands on commandStack', inject(function(commandStack) {
- // when
- commandStack.execute('complex-command', context);
- var stack = commandStack._stack,
- stackIdx = commandStack._stackIdx;
- // then
- expect(stack.length).to.equal(3);
- expect(stackIdx).to.equal(2);
- // expect same id(s)
- expect(stack[0].id).to.equal(stack[1].id);
- expect(stack[2].id).to.equal(stack[1].id);
- }));
- it('should undo {pre,actual,post} commands', inject(function(commandStack) {
- // when
- commandStack.execute('complex-command', context);
- commandStack.undo();
- var stack = commandStack._stack,
- stackIdx = commandStack._stackIdx;
- // then
- expect(stack.length).to.equal(3);
- expect(stackIdx).to.equal(-1);
- expect(element.trace).eql([]);
- }));
- it('should redo pre/post commands', inject(function(commandStack) {
- // when
- commandStack.execute('complex-command', context);
- commandStack.undo();
- commandStack.redo();
- var stack = commandStack._stack,
- stackIdx = commandStack._stackIdx;
- // then
- expect(stack.length).to.equal(3);
- expect(stackIdx).to.equal(2);
- expect(element.trace).eql([
- 'pre-command',
- 'complex-command',
- 'post-command'
- ]);
- }));
- describe('event integration', function() {
- it('should emit #preExecute and #postExecute events', inject(function(commandStack, eventBus) {
- // given
- var events = [];
- function logEvent(e) {
- events.push(e.type + ' ' + e.command);
- }
- eventBus.on([
- 'commandStack.preExecute',
- 'commandStack.preExecuted',
- 'commandStack.execute',
- 'commandStack.postExecute',
- 'commandStack.postExecuted'
- ], logEvent);
- // when
- commandStack.execute('complex-command', context);
- // then
- expect(events).eql([
- 'commandStack.preExecute complex-command',
- 'commandStack.preExecute pre-command',
- 'commandStack.preExecuted pre-command',
- 'commandStack.execute pre-command',
- 'commandStack.postExecute pre-command',
- 'commandStack.postExecuted pre-command',
- 'commandStack.preExecuted complex-command',
- 'commandStack.execute complex-command',
- 'commandStack.postExecute complex-command',
- 'commandStack.preExecute post-command',
- 'commandStack.preExecuted post-command',
- 'commandStack.execute post-command',
- 'commandStack.postExecute post-command',
- 'commandStack.postExecuted post-command',
- 'commandStack.postExecuted complex-command'
- ]);
- }));
- it('should emit execute* events', inject(function(commandStack, eventBus) {
- // given
- var events = [];
- function logEvent(e) {
- events.push((e && e.command) || 'changed');
- }
- eventBus.on([ 'commandStack.execute', 'commandStack.changed' ], logEvent);
- // when
- commandStack.execute('complex-command', context);
- // then
- expect(events).to.eql([
- 'pre-command',
- 'complex-command',
- 'post-command',
- 'changed'
- ]);
- }));
- it('should emit revert* events', inject(function(commandStack, eventBus) {
- // given
- var events = [];
- function logEvent(e) {
- events.push((e && e.command) || 'changed');
- }
- commandStack.execute('complex-command', context);
- eventBus.on([ 'commandStack.revert', 'commandStack.changed' ], logEvent);
- // when
- commandStack.undo();
- // then
- expect(events).to.eql([
- 'post-command',
- 'complex-command',
- 'pre-command',
- 'changed'
- ]);
- }));
- });
- });
- describe('missing handler #execute / #revert', function() {
- function JustPrePostCommand() {
- var id = 'just-pre-post-command';
- this.preExecute = function(ctx) {
- ctx.element.trace.push(id + '-pre-execute');
- };
- this.postExecute = function(ctx) {
- ctx.element.trace.push(id + '-post-execute');
- };
- }
- it('should execute anyway', inject(function(commandStack) {
- // given
- var element = { trace: [] };
- commandStack.registerHandler('just-pre-post-command', JustPrePostCommand);
- // when
- commandStack.execute('just-pre-post-command', { element: element });
- // then
- expect(element.trace).to.eql([
- 'just-pre-post-command-pre-execute',
- 'just-pre-post-command-post-execute'
- ]);
- }));
- it('should undo anyway', inject(function(commandStack) {
- // given
- var element = { trace: [] };
- commandStack.registerHandler('just-pre-post-command', JustPrePostCommand);
- commandStack.execute('just-pre-post-command', { element: element });
- // then
- expect(function() {
- commandStack.undo();
- }).not.to.throw;
- }));
- });
- describe('dirty handling', function() {
- var OuterHandler = function(commandStack) {
- this.execute = function(context) {
- return context.s1;
- };
- this.revert = function(context) {
- return context.s1;
- };
- this.postExecute = function(context) {
- commandStack.execute('inner-command', context);
- };
- };
- var InnerHandler = function() {
- this.execute = function(context) {
- return [ context.s1, context.s2 ];
- };
- this.revert = function(context) {
- return [ context.s1, context.s2 ];
- };
- };
- it('should update dirty shapes after change', inject(function(commandStack, eventBus) {
- // given
- commandStack.registerHandler('outer-command', OuterHandler);
- commandStack.registerHandler('inner-command', InnerHandler);
- var s1 = { id: 1 }, s2 = { id: 2 }, context = { s1: s1, s2: s2 };
- var events = [];
- function logEvent(e) {
- events.push(e.elements);
- }
- eventBus.on('elements.changed', logEvent);
- // when
- commandStack.execute('outer-command', context);
- // then
- expect(events).to.eql([ [ s1, s2 ] ]);
- }));
- });
- describe('stack information', function() {
- var Handler = function(eventBus) {
- this.execute = function(ctx) {
- expect(eventBus).to.be.an('object');
- ctx.heho = 'HE';
- };
- this.revert = function(ctx) {
- ctx.heho = 'HO';
- expect(eventBus).to.be.an('object');
- };
- };
- describe('stack information #canUndo', function() {
- it('should return true', inject(function(commandStack) {
- // given
- commandStack.registerHandler('heho', Handler);
- var context = {};
- // when
- commandStack.execute('heho', context);
- // then
- expect(commandStack.canUndo()).to.be.true;
- }));
- it('should return false', inject(function(commandStack) {
- // then
- expect(commandStack.canUndo()).to.be.false;
- }));
- });
- describe('stack information #canRedo', function() {
- it('should return true', inject(function(commandStack) {
- // given
- commandStack.registerHandler('heho', Handler);
- var context = {};
- // when
- commandStack.execute('heho', context);
- commandStack.undo();
- // then
- expect(commandStack.canRedo()).to.be.true;
- }));
- it('should return false', inject(function(commandStack) {
- // given
- commandStack.registerHandler('heho', Handler);
- var context = {};
- // when
- commandStack.execute('heho', context);
- // then
- expect(commandStack.canRedo()).to.be.false;
- }));
- });
- });
- describe('diagram life-cycle integration', function() {
- function verifyReset(eventName) {
- return function(eventBus, commandStack) {
- // given
- commandStack._stack.push('FOO');
- commandStack._stackIdx = 10;
- var changedSpy = sinon.spy(function() {});
- eventBus.on('commandStack.reset', changedSpy);
- // when
- eventBus.fire(eventName);
- // then
- expect(commandStack._stack).to.be.empty;
- expect(commandStack._stackIdx).to.eql(-1);
- expect(changedSpy).not.to.have.been.called;
- };
- }
- it('should clear on diagram.destroy', inject(verifyReset('diagram.destroy')));
- it('should clear on diagram.clear', inject(verifyReset('diagram.clear')));
- });
- describe('atomic commands', function() {
- var ERROR_MESSAGE = 'illegal invocation in <execute> or <revert> phase (action: simple-command)';
- describe('should protect against illegal invocation', function() {
- it('during <execute>', inject(function(eventBus, commandStack) {
- // given
- var invokeNested = sinon.spy(function invokeNested(event) {
- // then
- expect(function() {
- commandStack.execute('simple-command', {});
- }).to.throw(ERROR_MESSAGE);
- });
- commandStack.registerHandler('simple-command', SimpleCommand);
- eventBus.on('commandStack.execute', invokeNested);
- // when
- commandStack.execute('simple-command', { element: { trace: [] } });
- // then
- expect(invokeNested).to.have.been.called;
- }));
- it('during <revert>', inject(function(eventBus, commandStack) {
- // given
- var invokeNested = sinon.spy(function invokeNested(event) {
- // then
- expect(function() {
- commandStack.execute('simple-command', {});
- }).to.throw(ERROR_MESSAGE);
- });
- commandStack.registerHandler('simple-command', SimpleCommand);
- eventBus.on('commandStack.revert', invokeNested);
- commandStack.execute('simple-command', { element: { trace: [] } });
- // when
- commandStack.undo();
- // then
- expect(invokeNested).to.have.been.called;
- }));
- });
- });
- });
|