camunda-modeler
Version:
Camunda Modeler for BPMN, DMN and CMMN, based on bpmn.io
2,117 lines (1,368 loc) • 46.2 kB
JavaScript
;
var Config = require('test/helper/mock/config'),
Dialog = require('test/helper/mock/dialog'),
Events = require('base/events'),
FileSystem = require('test/helper/mock/file-system'),
Workspace = require('test/helper/mock/workspace'),
Logger = require('base/logger');
var App = require('app');
var select = require('test/helper/vdom').select,
render = require('test/helper/vdom').render,
simulateEvent = require('test/helper/vdom').simulateEvent;
var assign = require('lodash/object/assign'),
find = require('lodash/collection/find');
var arg = require('test/helper/util/arg'),
spy = require('test/helper/util/spy');
var bpmnXML = require('app/tabs/bpmn/initial.bpmn'),
activitiXML = require('test/fixtures/activiti.xml'),
dmnXML = require('app/tabs/dmn/initial.dmn');
var inherits = require('inherits');
var MultiEditorTab = require('app/tabs/multi-editor-tab');
var BaseEditor = require('app/editor/base-editor');
var Tab = require('base/components/tab');
function createBpmnFile(xml, overrides) {
return assign({
name: 'diagram_1.bpmn',
path: 'diagram_1.bpmn',
contents: xml,
fileType: 'bpmn',
lastModified: new Date().getTime()
}, overrides);
}
function createBpmnActivityFile(overrides) {
return assign({
name: 'activiti.xml',
path: 'activiti.xml',
contents: activitiXML,
lastModified: new Date().getTime()
}, overrides);
}
function createDmnFile(xml, overrides) {
return assign({
name: 'diagram_1.dmn',
path: 'diagram_1.dmn',
contents: xml,
fileType: 'dmn',
lastModified: new Date().getTime()
}, overrides);
}
var UNSAVED_FILE = { path: '[unsaved]' };
describe('App', function() {
var app, config, dialog,
events, fileSystem, logger,
workspace;
beforeEach(function() {
config = new Config();
events = new Events();
dialog = new Dialog(events);
fileSystem = new FileSystem();
logger = new Logger();
workspace = new Workspace();
app = new App({
config: config,
dialog: dialog,
events: events,
fileSystem: fileSystem,
logger: logger,
workspace: workspace,
metaData: {}
});
});
describe('cycle through tabs', function() {
var tabs, emptyTabIndex, emptyTab;
beforeEach(function() {
var file1 = createBpmnFile(bpmnXML);
var file2 = createDmnFile(dmnXML);
var file3 = createDmnFile(dmnXML, UNSAVED_FILE);
tabs = app.openTabs([ file1, file2, file3 ]);
emptyTabIndex = app.tabs.length - 1;
emptyTab = app.tabs[emptyTabIndex];
});
it('should select next tab', function() {
// given
app.selectTab(tabs[0]);
// when
app.triggerAction('select-tab', 'next');
// then
expect(app.activeTab).to.eql(tabs[1]);
});
it('should select previous tab', function() {
// given
app.selectTab(tabs[2]);
// when
app.triggerAction('select-tab', 'previous');
// then
expect(app.activeTab).to.eql(tabs[1]);
});
it('should not select empty tab on next', function() {
// given
app.selectTab(tabs[emptyTabIndex - 1]);
// when
app.triggerAction('select-tab', 'next');
// then
expect(app.activeTab).to.not.eql(emptyTab);
expect(app.activeTab).to.eql(tabs[0]);
});
it('should not select empty tab on previous', function() {
// given
var emptyTabIndex = app.tabs.length - 1;
var emptyTab = app.tabs[emptyTabIndex];
app.selectTab(tabs[0]);
// when
app.triggerAction('select-tab', 'previous');
// then
expect(app.activeTab).to.not.eql(emptyTab);
expect(app.activeTab).to.eql(tabs[emptyTabIndex - 1]);
});
});
describe('open last tab', function() {
it('should open last closed file', function() {
// given
var bpmnFile = createBpmnFile(bpmnXML),
openTab = app.openTab(bpmnFile);
app.closeTab(openTab);
var openFiles = spy(app, 'openFiles');
// when
app.reopenLastTab();
// then
expect(openFiles).calledWith([ bpmnFile ]);
expect(app.activeTab.file).to.eql(bpmnFile);
});
it('should not open any files if history is empty', function() {
// given
var bpmnFile = createBpmnFile(bpmnXML);
app.openTab(bpmnFile);
var openFiles = spy(app, 'openFiles');
// when
app.reopenLastTab();
// then
expect(openFiles).notCalled;
expect(app.fileHistory).to.have.length(0);
});
it('should remove opened file from history', function() {
// given
var bpmnFile = createBpmnFile(bpmnXML),
openTab = app.openTab(bpmnFile);
app.closeTab(openTab);
// when
app.reopenLastTab();
// then
expect(app.fileHistory).to.have.length(0);
});
});
describe('dialog overlay', function() {
it('should open overlay when dialog is called', function(done) {
// given
var openFile = createBpmnFile(bpmnXML);
dialog.setResponse('open', [ openFile ]);
// when
app.on('dialog-overlay:toggle', function(isOpened) {
expect(isOpened).to.be.true;
done();
});
app.openDiagram();
});
it('should close overlay when dialog is closed', function() {
// given
var openFile = createBpmnFile(bpmnXML),
closeOverlay = spy(dialog, '_closeOverlay');
dialog.setResponse('open', [ openFile ]);
// when
app.openDiagram();
// then
expect(closeOverlay).to.have.been.called;
});
});
describe('check external file modifications', function() {
var openFile;
beforeEach(function() {
openFile = createBpmnFile(bpmnXML);
});
describe('given file has been modified', function() {
var tab;
var statsFile;
var newFile;
beforeEach(function() {
// given
statsFile = assign({}, openFile, {
lastModified: new Date().getTime() + 2000
});
app.fileSystem.setStatsFile(statsFile);
newFile = assign({}, statsFile, {
content: 'NEW CONTENT'
});
app.fileSystem.setFile(newFile);
tab = app._createTab(openFile);
});
it('should call file reload dialog', function() {
// when
app.recheckTabContent(tab);
// then
expect(dialog.contentChanged).to.have.been.calledWith(arg.any);
});
it('should set new file on tab if reload accepted', function() {
// given
dialog.setResponse('contentChanged', 'ok');
// when
app.recheckTabContent(tab);
// then
expect(tab.file).to.eql(newFile);
});
it('should update "lastModified" flag and not change content on cancel', function() {
// given
dialog.setResponse('contentChanged', 'cancel');
// when
app.recheckTabContent(tab);
// then
expect(tab.file).to.eql(assign({}, openFile, { lastModified: statsFile.lastModified }));
expect(tab.file).to.not.eql(newFile);
});
});
describe('given file has NOT been modified', function() {
var tab;
beforeEach(function() {
// given
app.fileSystem.setStatsFile(assign({}, openFile));
tab = app._createTab(openFile);
});
it('should NOT call file reload dialog', function() {
// when
app.recheckTabContent(tab);
// then
expect(dialog.contentChanged).to.have.not.been.calledWith(arg.any);
});
it('tab should keep old file', function() {
// when
app.recheckTabContent(tab);
// then
expect(tab.file).to.equal(openFile);
});
});
describe('should be called', function() {
var recheckTabContent;
beforeEach(function() {
recheckTabContent = spy(app, 'recheckTabContent');
app.fileSystem.setFile(assign({}, openFile));
app.fileSystem.setStatsFile(assign({}, openFile));
});
it('on opening tab', function() {
// when
var tab = app.openTab(openFile);
// then
expect(recheckTabContent).to.have.been.calledWith(tab);
});
it('on selecting tab', function() {
// given
var tab = app._createTab(openFile);
// when
app.selectTab(tab);
// then
expect(recheckTabContent).to.have.been.calledWith(tab);
});
});
});
describe('run', function() {
it('should emit "ready" event', function(done) {
// then
app.on('ready', done);
// when
app.run();
});
});
describe('quit', function() {
var file, SomeTab;
beforeEach(function() {
file = createBpmnFile(bpmnXML);
SomeTab = function SomeTab(dirty) {
this.dirty = dirty;
this.on('focus', () => {
this.events.emit('tools:state-changed', this, {
dirty: this.dirty
});
});
Tab.call(this, {
events: events
});
};
inherits(SomeTab, Tab);
SomeTab.prototype.save = function(done) {
done(null, file);
};
SomeTab.prototype.setFile = function() {};
app.tabs = [];
});
it('should emit "quitting" event and close all dirty tabs on successful exit', function(done) {
// given
dialog.setResponse('close', file);
app._addTab(new SomeTab(false));
app._addTab(new SomeTab(true));
app._addTab(new SomeTab(false));
app._addTab(new SomeTab(true));
app.on('quitting', function() {
// then
expect(app.tabs).to.have.length(2);
done();
});
// when
app.triggerAction('quit');
});
it('should emit "quit-aborted" event when closing tab results in error', function(done) {
// given
dialog.setResponse('close', userCanceled());
app._addTab(new SomeTab(false));
app._addTab(new SomeTab(true));
app._addTab(new SomeTab(true));
app.on('quit-aborted', function() {
// then
expect(app.tabs).to.have.length(3);
done();
});
// when
app.triggerAction('quit');
});
it('should emit "quit-aborted" event when closing tab is being canceled', function(done) {
// given
dialog.setResponse('close', 'cancel');
app._addTab(new SomeTab(true));
app._addTab(new SomeTab(false));
app._addTab(new SomeTab(true));
app.on('quit-aborted', function() {
// then
expect(app.tabs).to.have.length(3);
done();
});
// when
app.triggerAction('quit');
});
});
it('should render', function() {
// when
var tree = render(app);
// then
expect(select('.footer', tree)).to.exist;
expect(select('.tabbed.main', tree)).to.exist;
expect(select('.menu-bar', tree)).to.exist;
});
describe('bpmn support', function() {
it('should create new BPMN tab', function() {
// when
app.createDiagram('bpmn');
var tree = render(app);
// then
// expect BPMN tab with editor to be shown
expect(select('.bpmn-editor', tree)).to.exist;
});
it('should open passed BPMN diagram file', function() {
// given
var openFile = createBpmnFile(bpmnXML);
// when
app.openTabs([ openFile ]);
// then
expect(app.activeTab.file).to.eql(openFile);
// and rendered ...
var tree = render(app);
// then
// expect BPMN tab with editor to be shown
expect(select('.bpmn-editor', tree)).to.exist;
});
});
describe('dmn support', function() {
it('should create new DMN tab', function() {
// when
app.createDiagram('dmn');
var tree = render(app);
// then
// expect DMN tab with editor to be shown
expect(select('.dmn-editor', tree)).to.exist;
});
it('should open passed DMN diagram file', function() {
// given
var openFile = createDmnFile(dmnXML);
// when
app.openTabs([ openFile ]);
// then
expect(app.activeTab.file).to.eql(openFile);
// and rendered ...
var tree = render(app);
// then
// expect BPMN tab with editor to be shown
expect(select('.dmn-editor', tree)).to.exist;
});
});
describe('xml support', function() {
it('should render xml-view', function() {
// given
var openFile = createBpmnFile(bpmnXML),
activeTab;
// when
app.openTabs([ openFile ]);
activeTab = app.activeTab;
activeTab.activeEditor = activeTab.getEditor('xml');
var tree = render(app);
// then
// expect BPMN tab with editor to be shown
expect(select('.xml-editor', tree)).to.exist;
});
});
describe('file open', function() {
it('should open suitable files', function() {
// given
var validFile = createBpmnFile(bpmnXML);
var invalidFile = createBpmnFile('FOO BAR', {
name: 'text.txt',
path: '[unsaved]'
});
var droppedFiles = [ validFile, invalidFile ];
// when
app.openFiles(droppedFiles);
// then
// only one file got added
expect(app.tabs.length).to.eql(2);
// valid diagram got opened
expect(app.activeTab.file).to.eql(assign({}, validFile));
expect(dialog.unrecognizedFileError).to.have.been.calledWith(invalidFile, arg.any);
});
});
describe('diagram opening', function() {
it('should open BPMN file', function() {
// given
var openFile = createBpmnFile(bpmnXML);
var expectedFile = assign({ fileType: 'bpmn' }, openFile);
dialog.setResponse('open', [ openFile ]);
// when
app.openDiagram();
// then
expect(app.activeTab.file).to.eql(expectedFile);
});
it('should open DMN file', function() {
// given
var openFile = createDmnFile(dmnXML);
var expectedFile = assign({ fileType: 'dmn' }, openFile);
dialog.setResponse('open', [ openFile ]);
// when
app.openDiagram();
// then
expect(app.activeTab.file).to.eql(expectedFile);
});
it('should fail on Error', function() {
// given
var lastTab = app.activeTab,
openError = new Error('foo');
dialog.setResponse('open', openError);
// when
app.openDiagram();
// then
expect(dialog.openError).to.have.been.called;
// still displaying last tab
expect(app.activeTab).to.eql(lastTab);
});
it('should fail on unrecognized file format', function() {
// given
var lastTab = app.activeTab,
openFile = createBpmnFile(bpmnXML, {
contents: require('./no-bpmn.bpmn')
});
dialog.setResponse('open', [openFile]);
// when
app.openDiagram();
// then
expect(dialog.unrecognizedFileError).to.have.been.called;
// still displaying last tab
expect(app.activeTab).to.eql(lastTab);
});
it('should open multiple files', function() {
var bpmnTab, dmnTab;
// given
var bpmnFile = createBpmnFile(bpmnXML);
var dmnFile = createDmnFile(dmnXML);
var expectedBpmnFile = assign({ fileType: 'bpmn' }, bpmnFile),
expectedDmnFile = assign({ fileType: 'dmn' }, dmnFile);
dialog.setResponse('open', [ bpmnFile, dmnFile ]);
// when
app.openDiagram();
bpmnTab = app.tabs[0];
dmnTab = app.tabs[1];
// then
expect(bpmnTab.file).to.eql(expectedBpmnFile);
expect(dmnTab.file).to.eql(expectedDmnFile);
});
it('should not open new tab for the same file', function() {
var bpmnTab, dmnTab;
// given
var bpmnFile = createBpmnFile(bpmnXML);
var dmnFile = createDmnFile(dmnXML);
var expectedBpmnFile = assign({ fileType: 'bpmn' }, bpmnFile),
expectedDmnFile = assign({ fileType: 'dmn' }, dmnFile);
dialog.setResponse('open', [ bpmnFile, dmnFile ]);
app.tabs = [];
// when
app.openDiagram();
app.openDiagram();
app.openDiagram();
app.openDiagram();
app.openDiagram();
dmnTab = app.tabs[0];
bpmnTab = app.tabs[1];
// then
expect(bpmnTab.file).to.eql(expectedBpmnFile);
expect(dmnTab.file).to.eql(expectedDmnFile);
expect(app.tabs).length.to.be(2);
});
it('should open bpmn file and NOT activiti file', function() {
// given
var bpmnFile = createBpmnFile(bpmnXML);
var activitiFile = createBpmnActivityFile();
var expectedBpmnFile = assign({ fileType: 'bpmn' }, bpmnFile);
dialog.setResponse('open', [ bpmnFile, activitiFile ]);
dialog.setResponse('namespace', 'cancel');
// when
app.openDiagram();
// then
expect(dialog.convertNamespace).to.have.been.called;
expect(app.activeTab.file).to.eql(expectedBpmnFile);
expect(app.tabs).to.have.length(2);
});
it('should open activiti file with convertion', function() {
// given
var activitiFile = createBpmnActivityFile();
var expectedActivitiFile = assign({}, activitiFile, { fileType: 'bpmn' });
dialog.setResponse('open', [ activitiFile ]);
dialog.setResponse('namespace', 'yes');
// when
app.openDiagram();
// then
expect(app.activeTab.file.name).to.eql('activiti.xml');
expect(app.activeTab.file).to.not.eql(expectedActivitiFile);
});
it('should open activiti file without convertion', function() {
// given
var activitiFile = createBpmnActivityFile();
var expectedActivitiFile = assign({}, activitiFile, { fileType: 'bpmn' });
dialog.setResponse('open', [ activitiFile ]);
dialog.setResponse('namespace', 'no');
// when
app.openDiagram();
// then
expect(app.activeTab.file).to.eql(expectedActivitiFile);
});
});
describe('diagram saving', function() {
it('should save BPMN file', function() {
// given
var file = createBpmnFile(bpmnXML),
tab = app.openTab(file);
patchSave(tab, file);
// when
app.triggerAction('save');
// then
expect(fileSystem.writeFile).to.have.been.calledWith(file, arg.any);
});
it('should save-as BPMN file', function() {
// given
var file = createBpmnFile(bpmnXML),
tab = app.openTab(file);
var expectedFile = assign({}, file, { path: '/foo/bar', name: 'bar' });
dialog.setResponse('saveAs', expectedFile);
patchSave(tab);
// when
app.triggerAction('save-as');
// then
expect(fileSystem.writeFile).to.have.been.calledWith(expectedFile, arg.any);
// expect tab got updated
expect(app.activeTab.label).to.eql(expectedFile.name);
expect(app.activeTab.title).to.eql(expectedFile.path);
});
it('should fail on saving denied error and cancel interaction', function() {
// given
var file = createBpmnFile(bpmnXML),
tab = app.openTab(file);
var saveFileSpy = spy(app, 'saveFile');
patchSave(tab, file);
var savingDenied = new Error('saving-denied');
fileSystem.setResponse('writeFile', savingDenied);
dialog.setResponse('savingDenied', 'cancel');
// when
app.triggerAction('save');
// then
expect(dialog.savingDenied).to.have.been.called;
expect(saveFileSpy).to.have.been.calledOnce;
});
it('should fail on saving denied error and trigger save-as', function() {
// given
var file = createBpmnFile(bpmnXML),
tab = app.openTab(file);
var saveFileSpy = spy(app, 'saveFile');
patchSave(tab, file);
var savingDenied = new Error('saving-denied');
fileSystem.setResponse('writeFile', savingDenied);
dialog.setResponse('savingDenied', 'save-as');
// when
app.triggerAction('save');
// then
expect(dialog.savingDenied).to.have.been.called;
expect(saveFileSpy).to.have.been.calledTwice;
});
it('should fail on Error', function() {
// given
var file = createBpmnFile(bpmnXML);
var tab = app.openTab(file);
var saveError = new Error('something went wrong');
patchSave(tab, saveError);
// when
app.triggerAction('save');
// then
expect(dialog.saveError).to.have.been.calledWith(saveError, arg.any);
});
describe('save all', function() {
it('should reset dirty state', function() {
// given
var saveTab = spy(app, 'saveTab');
var bpmnFile = createBpmnFile(bpmnXML, UNSAVED_FILE);
var dmnFile = createDmnFile(dmnXML, UNSAVED_FILE);
var tabs = app.openTabs([ bpmnFile, dmnFile ]);
patchSave(tabs);
dialog.setResponse('saveAs', { path: bpmnFile.name });
// when
app.triggerAction('save-all');
// then
expect(saveTab).to.have.been.calledTwice;
tabs.forEach(function(tab) {
expect(tab.dirty).to.be.false;
});
});
it('should abort when canceled', function() {
// given
var saveTab = spy(app, 'saveTab');
var bpmnFile = createBpmnFile(bpmnXML, UNSAVED_FILE);
var dmnFile = createDmnFile(dmnXML, UNSAVED_FILE);
var tabs = app.openTabs([ bpmnFile, dmnFile ]);
var bpmnTab = tabs[0],
dmnTab = tabs[1];
patchSave(bpmnTab);
// when
app.triggerAction('save-all');
// then
expect(saveTab).to.have.been.calledOnce;
expect(bpmnTab.dirty).to.be.true;
expect(dmnTab.dirty).to.be.true;
});
it('should abort on export error', function() {
// given
var saveTab = spy(app, 'saveTab');
var bpmnFile = createBpmnFile(bpmnXML, UNSAVED_FILE);
var dmnFile = createDmnFile(dmnXML, UNSAVED_FILE);
var tabs = app.openTabs([ bpmnFile, dmnFile ]);
var bpmnTab = tabs[0],
dmnTab = tabs[1];
// fail exporting the first tab already
patchSave(bpmnTab, new Error('failed to save diagram'));
// when
app.triggerAction('save-all');
// then
expect(saveTab).to.have.been.calledOnce;
expect(bpmnTab.dirty).to.be.true;
expect(dmnTab.dirty).to.be.true;
});
it('should save dirty diagrams only', function() {
// given
var saveTab = spy(app, 'saveTab');
var bpmnFile = createBpmnFile(bpmnXML);
var dmnFile = createDmnFile(dmnXML, UNSAVED_FILE);
var tabs = app.openTabs([ bpmnFile, dmnFile ]);
var dmnTab = tabs[1];
patchSave(dmnTab, function(done) {
done(null, dmnFile);
});
// when
app.triggerAction('save-all');
// then
expect(saveTab).to.have.been.calledOnce;
expect(saveTab).to.have.been.calledWith(dmnTab, arg.any);
});
// TODO(nikku): needs to be implemented properly
it('should select tab before saving', function() {
// given
var tabs = app.openTabs([
createBpmnFile(bpmnXML, UNSAVED_FILE),
createBpmnFile(bpmnXML, UNSAVED_FILE),
createBpmnFile(bpmnXML, UNSAVED_FILE)
]);
var activeTab = app.activeTab;
var savingTab = tabs[1];
patchSave(tabs);
patchSave(savingTab, function(done) {
expect(app.activeTab).to.eql(savingTab);
done(null, savingTab.file);
});
// when
app.saveTab(savingTab);
// then
expect(app.activeTab).to.eql(activeTab);
});
});
});
describe('tab dragging', function() {
var tabs;
beforeEach(function() {
var bpmnFile = createBpmnFile(bpmnXML),
dmnFile = createDmnFile(dmnXML);
app.openTabs([ bpmnFile, dmnFile ]);
tabs = app.tabs;
});
it('should drag tab to new position', function() {
// given
var dragTab = app.tabs[0],
targetTab = app.tabs[1];
// when
app.shiftTab(dragTab, 1);
// then
expect(tabs[0]).to.equal(targetTab);
expect(tabs[1]).to.equal(dragTab);
});
it('should not drag tab when target and drag tab are the same', function() {
// given
var dragTab = app.tabs[0];
var selectTab = spy(app, 'selectTab');
// when
app.shiftTab(dragTab, 0);
// then
expect(tabs[0]).to.equal(dragTab);
expect(selectTab).to.not.have.been.called;
});
it('should not shift tabs when no drag tab is provided', function() {
// given
var dragTab = null;
var selectTab = spy(app, 'selectTab');
// when
app.shiftTab(dragTab, 1);
// then
expect(selectTab).to.not.have.been.called;
});
});
describe('tab closing', function() {
it('should keep history of closed file', function() {
// given
var bpmnFile = createBpmnFile(bpmnXML),
openTab = app.openTab(bpmnFile);
// when
app.closeTab(openTab);
// then
expect(app.fileHistory).to.have.length(1);
expect(app.fileHistory[0]).to.eql(bpmnFile);
});
it('should not track unsaved files', function() {
// given
var bpmnFile = createBpmnFile(bpmnXML, UNSAVED_FILE),
openTab = app.openTab(bpmnFile);
// when
app.closeTab(openTab);
// then
expect(app.fileHistory).to.have.length(0);
});
it('should close showing close dialog on dirty tab', function() {
// given
var bpmnFile = createBpmnFile(bpmnXML, UNSAVED_FILE),
openTab = app.openTab(bpmnFile);
// when
app.closeTab(openTab);
// then
expect(dialog.close).to.have.been.called;
});
it('should close without close dialog with clean tab', function() {
// given
var file = createBpmnFile(bpmnXML),
openTab = app.openTab(file);
// when
app.closeTab(openTab);
// then
expect(dialog.close).to.not.have.been.called;
});
it('should save dirty file', function(done) {
// given
var file = createBpmnFile(bpmnXML, UNSAVED_FILE),
openTab = app.openTab(file);
var expectedFile = assign({}, file, { path: '/foo/bar', name: 'bar' });
app.saveTab = function(tab, cb) {
tab.setFile(expectedFile);
cb(null, expectedFile);
};
// when
dialog.setResponse('close', 'save');
app.closeTab(openTab, function(err) {
// then
expect(app.tabs).to.not.contain(openTab);
expect(dialog.close).to.have.been.called;
done();
});
});
it('should discard tab without saving', function(done) {
// given
var file = createBpmnFile(bpmnXML, UNSAVED_FILE),
openTab = app.openTab(file);
var expectedFile = assign({}, file, { path: '/foo/bar', name: 'bar' });
app.saveTab = function(tab, cb) {
tab.setFile(expectedFile);
cb(null, expectedFile);
};
var saveTab = spy(app, 'saveTab');
// when
dialog.setResponse('close', 'discard');
app.closeTab(openTab, function(err) {
// then
expect(app.tabs).to.not.contain(openTab);
expect(dialog.close).to.have.been.called;
expect(saveTab).to.have.not.been.called;
done();
});
});
it('should cancel tab closing', function(done) {
// given
var file = createBpmnFile(bpmnXML, UNSAVED_FILE),
openTab = app.openTab(file);
// when
dialog.setResponse('close', userCanceled());
app.closeTab(openTab, function(err) {
// then
expect(err).to.eql(userCanceled());
expect(app.tabs).to.contain(openTab);
expect(dialog.close).to.have.been.called;
done();
});
});
it('should emit "destroy" on tab closing', function(done) {
// given
var file = createBpmnFile(bpmnXML),
openTab = app.openTab(file);
var tabDestroyListener = spy(function() {});
var listenerRemoveSpy = spy(app.events, 'removeListener');
openTab.on('destroy', tabDestroyListener);
var editorSpies = openTab.editors.map(function(editor) {
return spy(editor, 'destroy');
});
// when
app.closeTab(openTab, function(err) {
// then
expect(tabDestroyListener).to.have.been.called;
editorSpies.forEach(function(spy) {
expect(spy).to.have.been.called;
});
// make sure we remove global listeners
expect(listenerRemoveSpy.callCount).to.eql(4);
done();
});
});
// TODO(nikku): needs to be implemented properly
it.skip('should select tab before closing', function() {
// given
var tabs = app.openTabs([
createBpmnFile(bpmnXML, UNSAVED_FILE),
createBpmnFile(bpmnXML, UNSAVED_FILE),
createBpmnFile(bpmnXML, UNSAVED_FILE)
]);
var activeTab = app.activeTab;
var closingTab = tabs[1];
app.dialog.close = function(file, done) {
expect(app.activeTab).to.eql(closingTab);
done(null, null);
};
// when
app.closeTab(closingTab);
// then
expect(app.activeTab).to.eql(activeTab);
});
it('should close all tabs', function() {
// given
var bpmnFile = createBpmnFile(bpmnXML);
var dmnFile = createDmnFile(dmnXML);
var tabs = app.openTabs([ bpmnFile, dmnFile ]);
// when
app.closeAllTabs();
// then
tabs.forEach(function(tab) {
expect(app.tabs).to.not.contain(tab);
});
});
it('should close all tabs and prompt on unsaved', function() {
// given
var bpmnFile = createBpmnFile(bpmnXML, UNSAVED_FILE);
var dmnFile = createDmnFile(dmnXML);
var tabs = app.openTabs([ bpmnFile, dmnFile ]);
dialog.setResponse('close', 'discard');
// when
app.closeAllTabs();
// then
expect(dialog.close).to.have.been.calledOnce;
tabs.forEach(function(tab) {
expect(app.tabs).to.not.contain(tab);
});
});
it('should abort closing all tabs on cancel', function() {
// given
var bpmnFile = createBpmnFile(bpmnXML);
var dmnFile = createDmnFile(dmnXML, UNSAVED_FILE);
var tabs = app.openTabs([ bpmnFile, dmnFile ]);
dialog.setResponse('close', 'cancel');
// when
app.closeAllTabs();
// then
expect(dialog.close).to.have.been.calledOnce;
expect(app.tabs).to.not.contain(tabs[0]);
expect(app.tabs).to.contain(tabs[1]);
});
});
describe('menu-bar', function() {
var tree;
beforeEach(function() {
tree = render(app);
});
it('should bind create-bpmn-diagram', function() {
// given
var element = select('.menu-bar [ref=create-bpmn-diagram]', tree);
var createDiagram = spy(app, 'createDiagram');
// when
simulateEvent(element, 'mouseup');
// then
expect(createDiagram).to.have.been.calledWith('bpmn');
});
it('should bind create-dmn-diagram', function() {
// given
var element = select('.menu-bar [ref=create-dmn-diagram]', tree);
var createDiagram = spy(app, 'createDiagram');
// when
simulateEvent(element, 'mouseup');
// then
expect(createDiagram).to.have.been.calledWith('dmn');
});
it('should bind open', function() {
// given
var element = select('.menu-bar [ref=open]', tree);
var openDiagram = spy(app, 'openDiagram');
// when
simulateEvent(element, 'click');
// then
expect(openDiagram).to.have.been.called;
});
it('should bind save');
it('should bind save-as');
it('should bind undo');
it('should bind redo');
it('should bind export-png');
});
describe('tabs', function() {
var tree;
beforeEach(function() {
tree = render(app);
});
it('should bind + tab, creating new diagram', function() {
// given
var element = select('.tabbed [ref=empty-tab]', tree);
var createDiagram = spy(app, 'createDiagram');
// when
simulateEvent(element, 'mousedown');
// then
expect(createDiagram).to.have.been.calledWith('bpmn');
});
});
describe('workspace', function() {
describe('api', function() {
describe('#persistWorkspace', function() {
it('should persist empty', function(done) {
// when
app.persistWorkspace(function(err, workspaceConfig) {
// then
expect(err).not.to.exist;
expect(workspaceConfig).to.have.keys([
'tabs',
'activeTab',
'layout'
]);
expect(workspaceConfig.tabs).to.have.length(0);
expect(workspaceConfig.activeTab).to.eql(-1);
done();
});
});
it('should persist tabs', function(done) {
// given
var bpmnFile = createBpmnFile(bpmnXML),
dmnFile = createDmnFile(dmnXML);
app.openTabs([ bpmnFile, dmnFile ]);
app.selectTab(app.tabs[0]);
// when
app.persistWorkspace(function(err, workspaceConfig) {
expect(err).not.to.exist;
expect(workspaceConfig).to.have.keys([
'tabs',
'activeTab',
'layout'
]);
expect(workspaceConfig.tabs).to.eql([ bpmnFile, dmnFile ]);
expect(workspaceConfig.activeTab).to.eql(0);
done();
});
});
});
describe('#restoreWorkspace', function() {
it('should restore saved', function(done) {
// given
var bpmnFile = createBpmnFile(bpmnXML),
dmnFile = createDmnFile(dmnXML);
var layout = {
propertiesPanel: {
open: false,
width: 250
},
log: {
open: false,
height: 150
}
};
workspace.setSaved({
tabs: [ bpmnFile, dmnFile ],
activeTab: 1,
layout: layout
});
// when
app.restoreWorkspace(function(err) {
// then
expect(err).not.to.exist;
// two tabs + empty tab are open
expect(app.tabs).to.have.length(3);
expect(app.activeTab).to.eql(app.tabs[1]);
expect(app.layout).to.eql(layout);
done();
});
});
it('should restore default', function(done) {
// given
workspace.setSaved(null);
// when
app.restoreWorkspace(function(err) {
// then
expect(err).not.to.exist;
// empty tab is open
expect(app.tabs).to.have.length(1);
// empty tab is selected, too
expect(app.tabs[0]).to.eql(app.activeTab);
// empty tab is selected, too
expect(app.activeTab).to.exist;
done();
});
});
});
});
describe('persist behavior', function() {
it('should save on new tab', function(done) {
// given
var bpmnFile = createBpmnFile(bpmnXML);
// when
app.openTabs([ bpmnFile ]);
// then
app.on('workspace:persisted', function(err, workspaceConfig) {
expect(err).not.to.exists;
expect(workspaceConfig.tabs).to.have.length(1);
expect(workspaceConfig.activeTab).to.eql(0);
done();
});
});
it('should save on tab change', function(done) {
// given
var bpmnFile = createBpmnFile(bpmnXML),
dmnFile = createDmnFile(dmnXML);
// when
app.openTabs([ bpmnFile, dmnFile ]);
app.selectTab(app.tabs[1]);
// then
app.on('workspace:persisted', function(err, workspaceConfig) {
expect(err).not.to.exists;
expect(workspaceConfig.tabs).to.have.length(2);
expect(workspaceConfig.activeTab).to.eql(1);
done();
});
});
it('should save on tab close', function(done) {
// given
var bpmnFile = createBpmnFile(bpmnXML),
dmnFile = createDmnFile(dmnXML);
// when
app.openTabs([ bpmnFile, dmnFile ]);
app.closeTab(app.tabs[1]);
// then
app.on('workspace:persisted', function(err, workspaceConfig) {
expect(err).not.to.exists;
expect(workspaceConfig.tabs).to.have.length(1);
expect(workspaceConfig.activeTab).to.eql(0);
done();
});
});
it('should not save unsaved tabs', function(done) {
// when
app.createDiagram('bpmn');
// then
app.on('workspace:persisted', function(err, workspaceConfig) {
expect(err).not.to.exist;
expect(workspaceConfig.tabs).to.have.length(0);
done();
});
});
});
describe('restore behavior', function() {
it('should restore on run', function() {
// given
var restoreWorkspace = spy(app, 'restoreWorkspace');
// when
app.run();
// then
expect(restoreWorkspace).to.have.been.called;
});
});
});
describe('config', function() {
it('should provide entries after load', function() {
// given
config.setLoadResult({ foo: 'bar' });
// when
app.loadConfig(function(err) {
// then
expect(app.config._entries).to.eql({ foo: 'bar' });
});
});
describe('load behavior', function() {
it('should load on run', function() {
// given
var loadConfig = spy(app, 'loadConfig');
// when
app.run();
// then
expect(loadConfig).to.have.been.called;
});
});
});
describe('event emitter', function() {
var tab;
beforeEach(function() {
function SomeEditor() {
BaseEditor.call(this, {});
}
inherits(SomeEditor, BaseEditor);
SomeEditor.prototype.update = function() {
this.emit('state-updated', { editorStateProperty: 'smth' });
};
tab = new MultiEditorTab({
editorDefinitions: [
{ id: 'someEditor', label: 'SomeEditor', component: SomeEditor }
],
id: 'someId',
events: events,
dialog: dialog
});
});
describe('focus', function() {
it('should be emitted on the active tab once selected', function(done) {
// when
app._addTab(tab);
// assume
expect(app.activeTab).not.to.eql(tab);
tab.on('focus', function() {
// then
expect(app.activeTab).to.eql(tab);
done();
});
// when
app.selectTab(tab);
});
});
describe('tools:state-changed', function() {
it('should emit on application start', function(done) {
// given
app.once('tools:state-changed', function(tab, state) {
// then
expect(state).to.eql({});
done();
});
// when
app.run();
});
it('should emit on editor "state-updated" event', function(done) {
// given
app._addTab(tab);
app.on('tools:state-changed', function(tab, state) {
// then
expect(state).to.have.property('save', true);
expect(state).to.have.property('editorStateProperty', 'smth');
done();
});
// when
app.selectTab(tab);
});
});
});
describe('export', function() {
describe('api', function() {
function createTab(file) {
app.openTabs([ file ]);
return app.tabs[0];
}
it('should export image', function(done) {
// given
var tab = createTab(createBpmnFile(bpmnXML)),
exportedFile = {
name: 'diagram_1.png',
path: 'diagram_1.png',
contents: 'foo',
fileType: 'png'
};
tab.activeEditor.exportAs = function(type, callback) {
callback(null, { contents: 'foo' });
};
dialog.setResponse('saveAs', exportedFile);
// when
app.exportTab(tab, 'png', function(err, file) {
// then
expect(file.name).to.equal('diagram_1.png');
expect(file.path).to.equal('diagram_1.png');
expect(file.contents).to.equal('foo');
expect(file.fileType).to.equal('png');
expect(dialog.saveAs).to.have.been.calledWith(exportedFile);
done();
});
});
it('should not export on error', function(done) {
// given
var tab = createTab(createBpmnFile(bpmnXML)),
exportError = new Error('export failed');
tab.activeEditor.exportAs = function(type, callback) {
callback(exportError);
};
// when
app.exportTab(tab, 'svg', function(err, svg) {
// then
expect(err).to.equal(exportError);
expect(dialog.saveAs).to.not.have.been.called;
done();
});
});
it('should not export with DMN', function(done) {
// given
var tab = createTab(createDmnFile(dmnXML));
// when
app.exportTab(tab, 'svg', function(err, svg) {
// then
expect(err.message).to.equal('<exportAs> not supported for the current tab');
expect(dialog.saveAs).to.not.have.been.called;
done();
});
});
});
describe('menu-bar', function() {
it('should be enabled when exporting is allowed', function(done) {
// given
var bpmnFile = createBpmnFile(bpmnXML),
exportButton = find(app.menuEntries.modeler.buttons, { id: 'export-as' }),
activeEditor;
// when
app.openTabs([ bpmnFile ]);
activeEditor = app.activeTab.activeEditor;
app.once('tools:state-changed', function() {
// then
expect(exportButton.disabled).to.be.false;
done();
});
activeEditor.mountEditor(document.createElement('div'));
});
it('should show export as "jpeg" and "svg"', function(done) {
// given
var bpmnFile = createBpmnFile(bpmnXML),
exportButton = find(app.menuEntries.modeler.buttons, { id: 'export-as' }),
bpmnTab;
app.openTabs([ bpmnFile ]);
bpmnTab = app.activeTab;
app.once('tools:state-changed', function() {
// then
expect(exportButton.choices).to.have.length(2);
expect(exportButton.choices[0].id).to.equal('jpeg');
expect(exportButton.choices[1].id).to.equal('svg');
done();
});
// when
app.emit('tools:state-changed', bpmnTab, { exportAs: [ 'jpeg', 'svg' ] });
});
describe('should update export button state', function() {
it('when there are no open tabs', function() {
// given
var exportButton = find(app.menuEntries.modeler.buttons, { id: 'export-as' });
// then
expect(exportButton.disabled).to.be.true;
});
it('when closing a tab where it was enabled', function() {
// given
var bpmnFile = createBpmnFile(bpmnXML),
exportButton;
app.openTabs([ bpmnFile ]);
app.closeTab(app.activeTab);
exportButton = find(app.menuEntries.modeler.buttons, { id: 'export-as' });
// then
expect(exportButton.disabled).to.be.true;
});
it('when switching tabs', function(done) {
// given
var bpmnFile = createBpmnFile(bpmnXML),
dmnFile = createDmnFile(dmnXML),
exportButton = find(app.menuEntries.modeler.buttons, { id: 'export-as' }),
bpmnTab,
activeEditor;
app.openTabs([ bpmnFile, dmnFile ]);
bpmnTab = app.tabs[0];
activeEditor = bpmnTab.activeEditor;
activeEditor.mountEditor(document.createElement('div'));
app.once('tools:state-changed', function() {
// then
expect(exportButton.disabled).to.be.false;
done();
});
// when -> selecting bpmn tab
app.selectTab(bpmnTab);
});
it('when switching editor views', function(done) {
// given
var bpmnFile = createBpmnFile(bpmnXML),
exportButton = find(app.menuEntries.modeler.buttons, { id: 'export-as' }),
activeTab, xmlEditor;
app.openTabs([ bpmnFile ]);
activeTab = app.activeTab;
xmlEditor = activeTab.getEditor('xml');
xmlEditor.mountEditor(document.createElement('div'));
app.once('tools:state-changed', function() {
// then
expect(exportButton.disabled).to.be.true;
done();
});
// when -> on xml view
activeTab.setEditor(xmlEditor);
});
});
});
});
});
/**
* Patch save on a tab or a list of tabs.
*
* @param {Tab|Array<Tab>} tabs
* @param {Error|FileDescriptor|Function} answer
*/
function patchSave(tabs, answer) {
if (!('length' in tabs)) {
tabs = [ tabs ];
}
tabs.forEach(function(tab) {
var fn = typeof answer === 'function' ? answer : function(done) {
if (answer instanceof Error) {
return done(answer);
}
return done(null, answer || tab.file);
};
tab.save = fn;
});
}
function userCanceled() {
return new Error('user canceled');
}