import TestContainer from 'mocha-test-container-support';
import Diagram from 'diagram-js/lib/Diagram';
import ViewerDefaultExport from '../../';
import Viewer from 'lib/Viewer';
import inherits from 'inherits';
import {
createViewer
} from 'test/TestHelper';
var singleStart = window.__env__ && window.__env__.SINGLE_START === 'viewer';
describe('Viewer', function() {
var container;
beforeEach(function() {
container = TestContainer.get(this);
});
(singleStart ? it.only : it)('should import simple process', function() {
var xml = require('../fixtures/bpmn/simple.bpmn');
// when
return createViewer(container, Viewer, xml).then(function(result) {
var err = result.error;
var warnings = result.warnings;
var viewer = result.viewer;
// then
expect(err).not.to.exist;
expect(warnings).to.be.empty;
var definitions = viewer.getDefinitions();
expect(definitions).to.exist;
expect(definitions).to.eql(viewer._definitions);
});
});
it('should re-import simple process', function() {
var xml = require('../fixtures/bpmn/simple.bpmn');
// given
return createViewer(container, Viewer, xml).then(function(result) {
var viewer = result.viewer;
// when
// mimic re-import of same diagram
return viewer.importXML(xml).then(function(result) {
// then
expect(result.warnings).to.be.empty;
});
});
});
it('should be instance of Diagram', function() {
// when
var viewer = new Viewer({ container: container });
// then
expect(viewer).to.be.instanceof(Diagram);
});
describe('overlay support', function() {
it('should allow to add overlays', function() {
var xml = require('../fixtures/bpmn/simple.bpmn');
return createViewer(container, Viewer, xml).then(function(result) {
var err = result.error;
var viewer = result.viewer;
expect(err).not.to.exist;
// when
var overlays = viewer.get('overlays'),
elementRegistry = viewer.get('elementRegistry');
// then
expect(overlays).to.exist;
expect(elementRegistry).to.exist;
// when
overlays.add('SubProcess_1', {
position: {
bottom: 0,
right: 0
},
html: '
YUP GREAT STUFF!
'
});
// then
expect(overlays.get({ element: 'SubProcess_1' }).length).to.equal(1);
});
});
});
describe('editor actions support', function() {
it('should not ship per default', function() {
// given
var viewer = new Viewer();
// when
var editorActions = viewer.get('editorActions', false);
// then
expect(editorActions).not.to.exist;
});
});
describe('error handling', function() {
function expectMessage(e, expectedMessage) {
expect(e).to.exist;
if (expectedMessage instanceof RegExp) {
expect(e.message).to.match(expectedMessage);
} else {
expect(e.message).to.equal(expectedMessage);
}
}
function expectWarnings(warnings, expected) {
expect(warnings.length).to.equal(expected.length);
warnings.forEach(function(w, idx) {
expectMessage(w, expected[idx]);
});
}
it('should handle non-bpmn input', function() {
var xml = 'invalid stuff';
return createViewer(container, Viewer, xml).then(function(result) {
var err = result.error;
expect(err).to.exist;
expectMessage(err, /missing start tag/);
});
});
it('should handle invalid BPMNPlane#bpmnElement', function() {
var xml = require('../fixtures/bpmn/error/di-plane-no-bpmn-element.bpmn');
// when
return createViewer(container, Viewer, xml).then(function(result) {
var err = result.error;
var warnings = result.warnings;
// then
expect(err).not.to.exist;
expectWarnings(warnings, [
'unresolved reference ',
'no bpmnElement referenced in ',
'correcting missing bpmnElement ' +
'on ' +
'to '
]);
});
});
it('should handle invalid namespaced element', function() {
var xml = require('../fixtures/bpmn/error/categoryValue.bpmn');
// when
return createViewer(container, Viewer, xml).then(function(result) {
var err = result.error;
var warnings = result.warnings;
// then
expect(err).not.to.exist;
expectWarnings(warnings, [
/unparsable content detected/,
'unresolved reference '
]);
});
});
it('should handle missing namespace', function() {
var xml = require('../fixtures/bpmn/error/missing-namespace.bpmn');
// when
return createViewer(container, Viewer, xml).then(function(result) {
var err = result.error;
var warnings = result.warnings;
// then
expect(err).to.exist;
expect(err.message).to.eql('failed to parse document as ');
expect(warnings).to.have.length(1);
expect(warnings[0].message).to.match(/unparsable content detected/);
});
});
it('should handle duplicate ids', function() {
var xml = require('../fixtures/bpmn/error/duplicate-ids.bpmn');
// when
return createViewer(container, Viewer, xml).then(function(result) {
var err = result.error;
var warnings = result.warnings;
// then
expect(err).not.to.exist;
expectWarnings(warnings, [
/duplicate ID /
]);
});
});
it('should throw error due to missing diagram', function() {
var xml = require('../fixtures/bpmn/empty-definitions.bpmn');
// when
return createViewer(container, Viewer, xml).then(function(result) {
var err = result.error;
// then
expect(err.message).to.eql('no diagram to display');
});
});
it('should handle missing process/collaboration', function() {
var xml = require('../fixtures/bpmn/error/no-process-collaboration.bpmn');
// when
return createViewer(container, Viewer, xml).then(function(result) {
var err = result.error;
// then
expect(err.message).to.eql('no process or collaboration to display');
});
});
});
describe('dependency injection', function() {
it('should provide self as ', function() {
var xml = require('../fixtures/bpmn/simple.bpmn');
return createViewer(container, Viewer, xml).then(function(result) {
var viewer = result.viewer;
var err = result.error;
expect(viewer.get('bpmnjs')).to.equal(viewer);
expect(err).not.to.exist;
});
});
it('should allow Diagram#get before import', function() {
// when
var viewer = new Viewer({ container: container });
// then
var eventBus = viewer.get('eventBus');
expect(eventBus).to.exist;
});
it('should keep references to services across re-import', function() {
// given
var someXML = require('../fixtures/bpmn/simple.bpmn'),
otherXML = require('../fixtures/bpmn/basic.bpmn');
var viewer = new Viewer({ container: container });
var eventBus = viewer.get('eventBus'),
canvas = viewer.get('canvas');
// when
return viewer.importXML(someXML).then(function() {
// then
expect(viewer.get('canvas')).to.equal(canvas);
expect(viewer.get('eventBus')).to.equal(eventBus);
return viewer.importXML(otherXML);
}).then(function() {
// then
expect(viewer.get('canvas')).to.equal(canvas);
expect(viewer.get('eventBus')).to.equal(eventBus);
});
});
});
describe('creation', function() {
var testModules = [
{ logger: [ 'type', function() { this.called = true; } ] }
];
// given
var xml = require('../fixtures/bpmn/simple.bpmn');
var viewer;
afterEach(function() {
viewer.destroy();
});
it('should override default modules', function() {
// given
viewer = new Viewer({ container: container, modules: testModules });
// when
return viewer.importXML(xml).catch(function(err) {
// then
expect(err.message).to.equal('No provider for "bpmnImporter"! (Resolving: bpmnImporter)');
});
});
it('should add module to default modules', function() {
// given
viewer = new Viewer({ container: container, additionalModules: testModules });
// when
return viewer.importXML(xml).then(function(result) {
// then
var logger = viewer.get('logger');
expect(logger.called).to.be.true;
});
});
it('should use custom size and position', function() {
// when
viewer = new Viewer({
container: container,
width: 200,
height: 100,
position: 'fixed'
});
// then
expect(viewer._container.style.position).to.equal('fixed');
expect(viewer._container.style.width).to.equal('200px');
expect(viewer._container.style.height).to.equal('100px');
});
var camundaPackage = require('../fixtures/json/model/camunda');
it('should provide custom moddle extensions', function() {
var xml = require('../fixtures/bpmn/extension/camunda.bpmn');
// given
viewer = new Viewer({
container: container,
moddleExtensions: {
camunda: camundaPackage
}
});
// when
return viewer.importXML(xml).then(function(result) {
var elementRegistry = viewer.get('elementRegistry');
var taskShape = elementRegistry.get('send'),
sendTask = taskShape.businessObject;
// then
expect(sendTask).to.exist;
var extensionElements = sendTask.extensionElements;
// receive task should be moddle extended
expect(sendTask.$instanceOf('camunda:ServiceTaskLike')).to.be.true;
// extension elements should provide typed element
expect(extensionElements).to.exist;
expect(extensionElements.values).to.exist;
expect(extensionElements.values).to.have.length(1);
expect(extensionElements.values[0].$instanceOf('camunda:InputOutput')).to.be.true;
});
});
it('should allow to add default custom moddle extensions', function() {
// given
var xml = require('../fixtures/bpmn/extension/custom.bpmn'),
additionalModdleDescriptors = {
custom: require('../fixtures/json/model/custom')
};
function CustomViewer(options) {
Viewer.call(this, options);
}
inherits(CustomViewer, Viewer);
CustomViewer.prototype._moddleExtensions = additionalModdleDescriptors;
viewer = new CustomViewer({
container: container,
moddleExtensions: {
camunda: camundaPackage
}
});
// when
return viewer.importXML(xml).then(function(result) {
var elementRegistry = viewer.get('elementRegistry');
var taskShape = elementRegistry.get('send'),
sendTask = taskShape.businessObject;
// then
expect(sendTask).to.exist;
var extensionElements = sendTask.extensionElements;
// receive task should be moddle extended
expect(sendTask.$instanceOf('camunda:ServiceTaskLike')).to.be.true;
expect(sendTask.$instanceOf('custom:ServiceTaskGroup')).to.be.true;
// extension elements should provide typed element
expect(extensionElements).to.exist;
expect(extensionElements.values.length).to.equal(2);
expect(extensionElements.values[0].$instanceOf('camunda:InputOutput')).to.be.true;
expect(extensionElements.values[1].$instanceOf('custom:CustomSendElement')).to.be.true;
});
});
it('should allow user to override default custom moddle extensions', function() {
// given
var xml = require('../fixtures/bpmn/extension/custom-override.bpmn');
var additionalModdleDescriptors = {
custom: require('../fixtures/json/model/custom')
};
var customOverride = require('../fixtures/json/model/custom-override');
function CustomViewer(options) {
Viewer.call(this, options);
}
inherits(CustomViewer, Viewer);
CustomViewer.prototype._moddleExtensions = additionalModdleDescriptors;
viewer = new CustomViewer({
container: container,
moddleExtensions: {
camunda: camundaPackage,
custom : customOverride
}
});
// when
return viewer.importXML(xml).then(function(result) {
var elementRegistry = viewer.get('elementRegistry');
var taskShape = elementRegistry.get('send'),
sendTask = taskShape.businessObject;
// then
expect(sendTask).to.exist;
var extensionElements = sendTask.extensionElements;
// receive task should be moddle extended
expect(sendTask.$instanceOf('camunda:ServiceTaskLike')).to.be.true;
expect(sendTask.$instanceOf('custom:ServiceTaskGroupOverride')).to.be.true;
// extension elements should provide typed element
expect(extensionElements).to.exist;
expect(extensionElements.values.length).to.equal(2);
expect(extensionElements.values[0].$instanceOf('camunda:InputOutput')).to.be.true;
expect(extensionElements.values[1].$instanceOf('custom:CustomSendElementOverride')).to.be.true;
});
});
});
describe('configuration', function() {
var xml = require('../fixtures/bpmn/simple.bpmn');
it('should configure Canvas', function() {
// given
var viewer = new Viewer({
container: container,
canvas: {
deferUpdate: true
}
});
// when
return viewer.importXML(xml).then(function(result) {
var canvasConfig = viewer.get('config.canvas');
// then
expect(canvasConfig.deferUpdate).to.be.true;
});
});
describe('container', function() {
it('should attach if provided', function() {
var xml = require('../fixtures/bpmn/simple.bpmn');
var viewer = new Viewer({ container: container });
return viewer.importXML(xml).then(function(result) {
expect(viewer._container.parentNode).to.equal(container);
});
});
it('should not attach if absent', function() {
var xml = require('../fixtures/bpmn/simple.bpmn');
var viewer = new Viewer();
return viewer.importXML(xml).then(function(result) {
expect(viewer._container.parentNode).to.equal(null);
});
});
});
});
describe('#importXML', function() {
it('should emit events', function() {
// given
var viewer = new Viewer({ container: container });
var xml = require('../fixtures/bpmn/simple.bpmn');
var events = [];
viewer.on([
'import.parse.start',
'import.parse.complete',
'import.render.start',
'import.render.complete',
'import.done'
], function(e) {
// log event type + event arguments
events.push([
e.type,
Object.keys(e).filter(function(key) {
return key !== 'type';
})
]);
});
// when
return viewer.importXML(xml).then(function(result) {
// then
expect(events).to.eql([
[ 'import.parse.start', [ 'xml' ] ],
[ 'import.parse.complete', [ 'error', 'definitions', 'elementsById', 'references', 'warnings', 'context' ] ],
[ 'import.render.start', [ 'definitions' ] ],
[ 'import.render.complete', [ 'error', 'warnings' ] ],
[ 'import.done', [ 'error', 'warnings' ] ]
]);
});
});
it('should work without callback', function(done) {
// given
var viewer = new Viewer({ container: container });
var xml = require('../fixtures/bpmn/simple.bpmn');
// when
viewer.importXML(xml);
// then
viewer.on('import.done', function(event) {
done();
});
});
describe('multiple BPMNDiagram elements', function() {
var multipleXML = require('../fixtures/bpmn/multiple-diagrams.bpmn');
it('should import default without bpmnDiagram specified', function() {
// when
return createViewer(container, Viewer, multipleXML).then(function(result) {
var err = result.error;
// then
expect(err).not.to.exist;
});
});
it('should import bpmnDiagram specified by id', function() {
// when
return createViewer(container, Viewer, multipleXML, 'BpmnDiagram_2').then(function(result) {
var err = result.error;
// then
expect(err).not.to.exist;
});
});
it('should handle diagram not found', function() {
// given
var xml = require('../fixtures/bpmn/multiple-diagrams.bpmn');
// when
return createViewer(container, Viewer, xml, 'Diagram_IDontExist').then(function(result) {
var err = result.error;
// then
expect(err).to.exist;
expect(err.message).to.eql('BPMNDiagram not found');
});
});
describe('without callback', function() {
it('should open default', function(done) {
// given
var viewer = new Viewer({ container: container });
// when
viewer.importXML(multipleXML);
// then
viewer.on('import.done', function(event) {
done(event.error);
});
});
it('should open specified BPMNDiagram', function(done) {
// given
var viewer = new Viewer({ container: container });
// when
viewer.importXML(multipleXML, 'BpmnDiagram_2');
// then
viewer.on('import.done', function(event) {
done(event.error);
});
});
});
});
});
describe('#importDefinitions', function() {
describe('single diagram', function() {
var xml = require('../fixtures/bpmn/simple.bpmn'),
viewer,
definitions;
beforeEach(function() {
return createViewer(container, Viewer, xml, null).then(function(result) {
var error = result.error;
var tmpViewer = result.viewer;
if (error) {
throw error;
}
definitions = tmpViewer.getDefinitions();
tmpViewer.destroy();
});
});
beforeEach(function() {
viewer = new Viewer({ container: container });
});
afterEach(function() {
viewer.destroy();
});
it('should emit events', function() {
// given
var events = [];
viewer.on([
'import.parse.start',
'import.parse.complete',
'import.render.start',
'import.render.complete',
'import.done'
], function(e) {
// log event type + event arguments
events.push([
e.type,
Object.keys(e).filter(function(key) {
return key !== 'type';
})
]);
});
// when
return viewer.importDefinitions(definitions).then(function() {
// then
expect(events).to.eql([
[ 'import.render.start', [ 'definitions' ] ],
[ 'import.render.complete', [ 'error', 'warnings' ] ]
]);
});
});
it('should work without callback', function(done) {
// given
viewer.on('import.render.complete', function(context) {
// then
done(context.error);
});
// when
viewer.importDefinitions(definitions);
});
});
describe('multiple BPMNDiagram elements', function() {
var multipleXML = require('../fixtures/bpmn/multiple-diagrams.bpmn'),
viewer,
definitions;
beforeEach(function() {
return createViewer(container, Viewer, multipleXML).then(function(result) {
var error = result.error;
var tmpViewer = result.viewer;
if (error) {
throw error;
}
definitions = tmpViewer.getDefinitions();
tmpViewer.destroy();
});
});
beforeEach(function() {
viewer = new Viewer({ container: container });
});
afterEach(function() {
viewer.destroy();
});
it('should import default without bpmnDiagram specified', function() {
// when
return viewer.importDefinitions(definitions);
});
it('should import bpmnDiagram specified by id', function() {
// when
return viewer.importDefinitions(definitions, 'BpmnDiagram_2');
});
it('should handle diagram not found', function() {
// when
return viewer.importDefinitions(definitions, 'Diagram_IDontExist').catch(function(err) {
// then
expect(err).to.exist;
expect(err.message).to.eql('BPMNDiagram not found');
});
});
describe('without callback', function() {
it('should open default', function(done) {
// given
viewer.on('import.render.complete', function(event) {
// then
done(event.error);
});
// when
viewer.importDefinitions(definitions);
});
it('should open specified BPMNDiagram', function(done) {
// given
viewer.on('import.render.complete', function(event) {
// then
done(event.error);
});
// when
viewer.importDefinitions(definitions, 'BpmnDiagram_2');
});
});
});
});
describe('#open', function() {
var multipleXMLSimple = require('../fixtures/bpmn/multiple-diagrams.bpmn'),
multipleXMLOverlappingDI = require('../fixtures/bpmn/multiple-diagrams-overlapping-di.bpmn'),
multipleXMLWithLaneSet = require('../fixtures/bpmn/multiple-diagrams-lanesets.bpmn'),
diagram1 = 'BpmnDiagram_1',
diagram2 = 'BpmnDiagram_2';
it('should open the first diagram if id was not provided', function() {
var viewer, renderedDiagram;
// when
return createViewer(container, Viewer, multipleXMLSimple, diagram1).then(function(result) {
var err = result.error;
viewer = result.viewer;
expect(err).not.to.exist;
renderedDiagram = viewer.get('canvas').getRootElement().businessObject.di;
return viewer.open();
}).then(function() {
// then
expect(viewer.get('canvas').getRootElement().businessObject.di).to.equal(renderedDiagram);
});
});
it('should switch between diagrams', function() {
var viewer, definitions;
// when
return createViewer(container, Viewer, multipleXMLSimple, diagram1).then(function(result) {
var err = result.error;
var warnings = result.warnings;
viewer = result.viewer;
// then
expect(err).not.to.exist;
expect(warnings).to.be.empty;
definitions = viewer.getDefinitions();
expect(definitions).to.exist;
return viewer.open(diagram2);
}).then(function(result) {
// then
var warnings = result.warnings;
expect(warnings).to.be.empty;
expect(definitions).to.equal(viewer.getDefinitions());
var elementRegistry = viewer.get('elementRegistry');
expect(elementRegistry.get('Task_A')).to.not.exist;
expect(elementRegistry.get('Task_B')).to.exist;
});
});
it('should switch between diagrams with overlapping DI', function() {
var viewer, definitions;
// when
return createViewer(container, Viewer, multipleXMLOverlappingDI, diagram1).then(function(result) {
var err = result.error;
var warnings = result.warnings;
viewer = result.viewer;
// then
expect(err).not.to.exist;
expect(warnings).to.be.empty;
definitions = viewer.getDefinitions();
expect(definitions).to.exist;
return viewer.open(diagram2);
}).then(function(result) {
var warnings = result.warnings;
expect(warnings).to.be.empty;
expect(definitions).to.equal(viewer.getDefinitions());
});
});
it('should switch between diagrams with laneSets', function() {
var viewer, definitions;
// when
return createViewer(container, Viewer, multipleXMLWithLaneSet, diagram2).then(function(result) {
var err = result.error;
var warnings = result.warnings;
viewer = result.viewer;
// then
expect(err).not.to.exist;
expect(warnings).to.be.empty;
definitions = viewer.getDefinitions();
expect(definitions).to.exist;
return viewer.open(diagram1);
}).then(function(result) {
// then
var warnings = result.warnings;
expect(warnings).to.be.empty;
expect(definitions).to.equal(viewer.getDefinitions());
var elementRegistry = viewer.get('elementRegistry');
expect(elementRegistry.get('Task_A')).to.exist;
expect(elementRegistry.get('Task_B')).to.not.exist;
});
});
it('should complete with error if xml was not imported', function() {
// given
var viewer = new Viewer();
// when
return viewer.open().catch(function(err) {
// then
expect(err).to.exist;
expect(err.message).to.eql('no XML imported');
var definitions = viewer.getDefinitions();
expect(definitions).to.not.exist;
});
});
it('should open with error if diagram does not exist', function() {
var viewer, definitions;
// when
return createViewer(container, Viewer, multipleXMLSimple, diagram1).then(function(result) {
var err = result.error;
var warnings = result.warnings;
viewer = result.viewer;
// then
expect(err).not.to.exist;
expect(warnings).to.be.empty;
definitions = viewer.getDefinitions();
expect(definitions).to.exist;
return viewer.open('Diagram_IDontExist');
}).catch(function(err) {
// then
expect(err).to.exist;
expect(err.message).to.eql('BPMNDiagram not found');
// definitions stay the same
expect(viewer.getDefinitions()).to.eql(definitions);
});
});
it('should emit events', function() {
var viewer = new Viewer({ container: container });
var events = [];
return viewer.importXML(multipleXMLSimple, diagram1).then(function(result) {
// given
viewer.on([
'import.parse.start',
'import.parse.complete',
'import.render.start',
'import.render.complete',
'import.done'
], function(e) {
// log event type + event arguments
events.push([
e.type,
Object.keys(e).filter(function(key) {
return key !== 'type';
})
]);
});
// when
return viewer.open(diagram2);
}).then(function() {
// then
expect(events).to.eql([
[ 'import.render.start', [ 'definitions' ] ],
[ 'import.render.complete', [ 'error', 'warnings' ] ]
]);
});
});
});
describe('#saveXML', function() {
it('should export XML', function() {
// given
var xml = require('../fixtures/bpmn/simple.bpmn');
return createViewer(container, Viewer, xml).then(function(result) {
var err = result.error;
var viewer = result.viewer;
expect(err).not.to.exist;
// when
return viewer.saveXML({ format: true });
}).then(function(result) {
var xml = result.xml;
// then
expect(xml).to.contain('');
expect(xml).to.contain(' events', function() {
var xml = require('../fixtures/bpmn/simple.bpmn');
var viewer;
var events = [];
return createViewer(container, Viewer, xml).then(function(result) {
var err = result.error;
viewer = result.viewer;
expect(err).not.to.exist;
viewer.on([
'saveXML.start',
'saveXML.serialized',
'saveXML.done'
], function(e) {
// log event type + event arguments
events.push([
e.type,
Object.keys(e).filter(function(key) {
return key !== 'type';
})
]);
});
return viewer.importXML(xml);
}).then(function(result) {
// when
return viewer.saveXML();
}).then(function() {
// then
expect(events).to.eql([
[ 'saveXML.start', [ 'definitions' ] ],
[ 'saveXML.serialized', [ 'xml' ] ],
[ 'saveXML.done', [ 'xml' ] ]
]);
});
});
it('should emit on error', function() {
var xml = require('../fixtures/bpmn/simple.bpmn');
var viewer;
var events = [];
return createViewer(container, Viewer, xml).then(function(result) {
var err = result.error;
viewer = result.viewer;
expect(err).not.to.exist;
// when
viewer.on('saveXML.start', 250, function() {
throw new Error('failing pre-save listener');
});
viewer.on([
'saveXML.start',
'saveXML.serialized',
'saveXML.done'
], function(e) {
// log event type + event arguments
events.push([
e.type,
Object.keys(e).filter(function(key) {
return key !== 'type';
})
]);
});
return viewer.importXML(xml);
}).then(function(result) {
// when
return viewer.saveXML();
}).catch(function(error) {
events.push([ 'error' ]);
}).finally(function() {
// then
expect(events).to.eql([
[ 'saveXML.start', [ 'definitions' ] ],
[ 'saveXML.done', [ 'error' ] ],
[ 'error' ]
]);
});
});
it('should emit on no definitions loaded', function() {
var events = [];
var viewer = new Viewer({
container: container
});
viewer.on([
'saveXML.start',
'saveXML.serialized',
'saveXML.done'
], function(e) {
// log event type + event arguments
events.push([
e.type,
Object.keys(e).filter(function(key) {
return key !== 'type';
})
]);
});
return viewer.saveXML().catch(function(error) {
events.push([ 'error' ]);
}).finally(function() {
// then
expect(events).to.eql([
[ 'saveXML.done', [ 'error' ] ],
[ 'error' ]
]);
});
});
});
describe('#saveSVG', function() {
function currentTime() {
return new Date().getTime();
}
function validSVG(svg) {
var expectedStart = '';
var expectedEnd = '';
expect(svg.indexOf(expectedStart)).to.equal(0);
expect(svg.indexOf(expectedEnd)).to.equal(svg.length - expectedEnd.length);
// ensure correct rendering of SVG contents
expect(svg.indexOf('undefined')).to.equal(-1);
// expect header to be written only once
expect(svg.indexOf('';
expect(svg.indexOf(expectedStart)).to.equal(0);
expect(svg.indexOf(expectedEnd)).to.equal(svg.length - expectedEnd.length);
// ensure correct rendering of SVG contents
expect(svg.indexOf('undefined')).to.equal(-1);
// expect header to be written only once
expect(svg.indexOf('