text: /**
   * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
   * under one or more contributor license agreements. See the NOTICE file
   * distributed with this work for additional information regarding copyright
   * ownership.
   *
   * Camunda licenses this file to you under the MIT; you may not use this file
   * except in compliance with the MIT License.
   */
  
  /* global sinon */
  
  import React from 'react';
  
  import { mount } from 'enzyme';
  
  import {
    Cache,
    WithCachedState
  } from '../../../cached';
  
  import {
    CmmnEditor
  } from '../CmmnEditor';
  
  import CmmnModeler from 'test/mocks/cmmn-js/Modeler';
  
  import {
    getCanvasEntries,
    getCopyCutPasteEntries,
    getDiagramFindEntries,
    getSelectionEntries,
    getToolEntries,
    getUndoRedoEntries
  } from '../../getEditMenu';
  
  import diagramXML from './diagram.cmmn';
  
  const { spy } = sinon;
  
  
  describe('<CmmnEditor>', function() {
  
    it('should render', function() {
      const {
        instance
      } = renderEditor(diagramXML);
  
      expect(instance).to.exist;
    });
  
  
    describe('caching behavior', function() {
  
      let createSpy;
  
      beforeEach(function() {
        createSpy = sinon.spy(CmmnEditor, 'createCachedState');
      });
  
      afterEach(function() {
        createSpy.restore();
      });
  
  
      it('should create modeler if not cached', function() {
  
        // when
        const {
          instance
        } = renderEditor(diagramXML);
  
        // then
        const {
          modeler
        } = instance.getCached();
  
        expect(modeler).to.exist;
        expect(createSpy).to.have.been.calledOnce;
      });
  
  
      it('should use cached modeler', function() {
  
        // given
        const cache = new Cache();
  
        cache.add('editor', {
          cached: {
            modeler: new CmmnModeler()
          },
          __destroy: () => {}
        });
  
        // when
        renderEditor(diagramXML, {
          id: 'editor',
          cache
        });
  
        // then
        expect(createSpy).not.to.have.been.called;
      });
  
    });
  
  
    it('#getModeler', async function() {
  
      // given
      const { instance } = await renderEditor(diagramXML);
  
      // when
      const modeler = instance.getModeler();
  
      // then
      expect(modeler).to.exist;
    });
  
  
    it('#getXML', async function() {
      const {
        instance
      } = renderEditor(diagramXML);
  
      const xml = await instance.getXML();
  
      expect(xml).to.exist;
      expect(xml).to.eql(diagramXML);
    });
  
  
    describe('#exportAs', function() {
  
      // increase test time-outs, as exporting takes a
      // long certain underpowered CI systems (AppVeyor, wink, wink)
      this.timeout(5000);
  
      let instance;
  
      beforeEach(function() {
        instance = renderEditor(diagramXML).instance;
      });
  
  
      it('svg', async function() {
        const contents = await instance.exportAs('svg');
  
        expect(contents).to.exist;
        expect(contents).to.equal('<svg />');
      });
  
  
      it('png', async function() {
        const contents = await instance.exportAs('png');
  
        expect(contents).to.exist;
        expect(contents).to.contain('data:image/png');
      });
  
  
      it('jpeg', async function() {
        const contents = await instance.exportAs('jpeg');
  
        expect(contents).to.exist;
        expect(contents).to.contain('data:image/jpeg');
      });
  
    });
  
  
    describe('#listen', function() {
  
      function expectHandleChanged(event) {
        return async function() {
          const modeler = new CmmnModeler();
  
          const cache = new Cache();
  
          cache.add('editor', {
            cached: {
              modeler
            },
            __destroy: () => {}
          });
  
          const changedSpy = spy();
  
          await renderEditor(diagramXML, {
            id: 'editor',
            cache,
            onChanged: changedSpy
          });
  
          modeler._emit(event);
  
          expect(changedSpy).to.have.been.called;
        };
      }
  
  
      it('import.done', expectHandleChanged('import.done'));
  
  
      it('saveXML.done', expectHandleChanged('saveXML.done'));
  
  
      it('commandStack.changed', expectHandleChanged('commandStack.changed'));
  
  
      it('selection.changed', expectHandleChanged('selection.changed'));
  
  
      it('attach', expectHandleChanged('attach'));
  
  
      it('propertiesPanel.focusin', expectHandleChanged('propertiesPanel.focusin'));
  
  
      it('propertiesPanel.focusout', expectHandleChanged('propertiesPanel.focusout'));
  
  
      it('directEditing.activate', expectHandleChanged('directEditing.activate'));
  
  
      it('directEditing.deactivate', expectHandleChanged('directEditing.deactivate'));
  
  
      it('searchPad.opened', expectHandleChanged('searchPad.opened'));
  
  
      it('searchPad.closed', expectHandleChanged('searchPad.closed'));
  
    });
  
  
    describe('#handleChanged', function() {
  
      it('should notify about changes', function() {
  
        // given
        const changedSpy = sinon.spy();
  
        const modeler = new CmmnModeler({
          modules: {
            commandStack: {
              canRedo: () => true,
              canUndo: () => true,
              _stackIdx: 1
            },
            selection: {
              get: () => []
            }
          }
        });
  
        const cache = new Cache();
  
        cache.add('editor', {
          cached: {
            lastXML: diagramXML,
            modeler,
            stackIdx: 1
          },
          __destroy: () => {}
        });
  
        const { instance } = renderEditor(diagramXML, {
          id: 'editor',
          cache,
          onChanged: changedSpy
        });
  
        changedSpy.resetHistory();
  
        // when
        modeler.get('commandStack')._stackIdx = 5;
  
        instance.handleChanged();
  
        expect(changedSpy).to.be.calledOnce;
        expect(changedSpy.firstCall.args[0]).to.include({
          defaultCopyCutPaste: false,
          defaultUndoRedo: false,
          dirty: true,
          editLabel: false,
          find: true,
          globalConnectTool: true,
          handTool: true,
          inputActive: false,
          lassoTool: true,
          moveCanvas: true,
          redo: true,
          removeSelected: false,
          spaceTool: true,
          undo: true
        });
      });
  
  
      it('should notify about plugin related changes', async function() {
  
        // given
        const changedSpy = sinon.spy();
  
        const { instance } = await renderEditor(diagramXML, {
          id: 'editor',
          onChanged: changedSpy
        });
  
        changedSpy.resetHistory();
  
        // when
        instance.handleChanged();
  
        // then
        expect(changedSpy).to.be.calledOnce;
  
        const state = changedSpy.firstCall.args[0];
  
        expect(state).to.have.property('cmmn');
        expect(state).to.have.property('editable');
        expect(state).to.have.property('elementsSelected');
        expect(state).to.have.property('inactiveInput');
      });
  
  
      describe('edit menu', function() {
  
        it('should provide undo/redo entries', async function() {
  
          // given
          const changedSpy = (state) => {
  
            const editMenuEntries = getUndoRedoEntries(state);
  
            // then
            expect(state.editMenu).to.deep.include(editMenuEntries);
  
          };
  
          const { instance } = await renderEditor(diagramXML, {
            onChanged: changedSpy
          });
  
          // when
          instance.handleChanged();
        });
  
  
        it('should provide copy/paste entries', async function() {
  
          // given
          const changedSpy = (state) => {
  
            const editMenuEntries = getCopyCutPasteEntries(state);
  
            // then
            expect(state.editMenu).to.deep.include(editMenuEntries);
  
          };
  
          const { instance } = await renderEditor(diagramXML, {
            onChanged: changedSpy
          });
  
          // when
          instance.handleChanged();
        });
  
  
        it('should provide tool entries', async function() {
  
          // given
          const changedSpy = (state) => {
  
            const editMenuEntries = getToolEntries(state);
  
            // then
            expect(state.editMenu).to.deep.include(editMenuEntries);
  
          };
  
          const { instance } = await renderEditor(diagramXML, {
            onChanged: changedSpy
          });
  
          // when
          instance.handleChanged();
        });
  
  
        it('should provide find entries', async function() {
  
          // given
          const changedSpy = (state) => {
  
            const editMenuEntries = getDiagramFindEntries(state);
  
            // then
            expect(state.editMenu).to.deep.include(editMenuEntries);
  
          };
  
          const { instance } = await renderEditor(diagramXML, {
            onChanged: changedSpy
          });
  
          // when
          instance.handleChanged();
        });
  
  
        it('should provide selection + canvas entries', async function() {
  
          // given
          const changedSpy = (state) => {
  
            const editMenuEntries = [
              ...getCanvasEntries(state),
              ...getSelectionEntries(state)
            ];
  
            // then
            expect(state.editMenu).to.deep.include(editMenuEntries);
  
          };
  
          const { instance } = await renderEditor(diagramXML, {
            onChanged: changedSpy
          });
  
          // when
          instance.handleChanged();
        });
      });
  
    });
  
  
    describe('#triggerAction', function() {
  
      it('should return value of editor action', async function() {
  
        // given
        const editorActions = {
          trigger(action, context) {
            if (action === 'foo') {
              return 'bar';
            }
          }
        };
  
        const cache = new Cache();
  
        cache.add('editor', {
          cached: {
            modeler: new CmmnModeler({
              modules: {
                editorActions
              }
            })
          }
        });
  
        // when
        const { instance } = await renderEditor(diagramXML, { cache });
  
        // when
        const returnValue = instance.triggerAction('foo');
  
        // then
        expect(returnValue).to.equal('bar');
      });
  
    });
  
  
    describe('layout', function() {
  
      it('should open properties panel', function() {
  
        // given
        let layout = {
          propertiesPanel: {
            open: false
          }
        };
  
        function onLayoutChanged(newLayout) {
          layout = newLayout;
        }
  
        const {
          wrapper
        } = renderEditor(diagramXML, {
          layout,
          onLayoutChanged
        });
  
        const toggle = wrapper.find('.toggle');
  
        // when
        toggle.simulate('click');
  
        // then
        expect(layout.propertiesPanel.open).to.be.true;
      });
  
  
      it('should close properties panel', function() {
  
        // given
        let layout = {
          propertiesPanel: {
            open: true
          }
        };
  
        function onLayoutChanged(newLayout) {
          layout = newLayout;
        }
  
        const {
          wrapper
        } = renderEditor(diagramXML, {
          layout,
          onLayoutChanged
        });
  
        const toggle = wrapper.find('.toggle');
  
        // when
        toggle.simulate('click');
  
        // then
        expect(layout.propertiesPanel.open).to.be.false;
      });
  
  
      it('should handle missing layout', function() {
  
        // given
        let layout = { };
  
        // then
        renderEditor(diagramXML, {
          layout
        });
  
      });
  
    });
  
  
    describe('errors', function() {
  
      it('should handle XML export error', async function() {
  
        // given
        const errorSpy = spy();
  
        const {
          instance
        } = renderEditor('export-error', {
          onError: errorSpy
        });
  
        // make sure editor is dirty
        const commandStack = instance.getModeler().get('commandStack');
  
        commandStack.execute(1);
  
        let err;
  
        // when
        try {
          await instance.getXML();
        } catch (e) {
          err = e;
        }
  
        // then
        expect(err).to.exist;
        expect(errorSpy).to.have.been.calledOnce;
      });
  
    });
  
  
    describe('import', function() {
  
      afterEach(sinon.restore);
  
  
      it('should import without errors and warnings', function(done) {
  
        // when
        renderEditor(diagramXML, {
          onImport
        });
  
        // then
        function onImport(error, warnings) {
          try {
            expect(error).to.not.exist;
            expect(warnings).to.have.length(0);
  
            done();
          } catch (error) {
            done(error);
          }
        }
      });
  
  
      it('should import with warnings', function(done) {
  
        // given
        const warningInducingFakeXML = 'import-warnings';
  
        // when
        renderEditor(warningInducingFakeXML, {
          onImport
        });
  
        // then
        function onImport(error, warnings) {
          try {
            expect(error).to.not.exist;
            expect(warnings).to.have.length(1);
  
            done();
          } catch (error) {
            done(error);
          }
        }
      });
  
  
      it('should import with error', function(done) {
  
        // given
        const errorInducingFakeXML = 'import-error';
  
        // when
        renderEditor(errorInducingFakeXML, {
          onImport
        });
  
        // then
        function onImport(error, warnings) {
          try {
            expect(error).to.exist;
            expect(warnings).to.have.length(0);
  
            done();
          } catch (error) {
            done(error);
          }
        }
      });
  
  
      it('should not import when provided xml is the same as the cached one', async function() {
  
        // given
        const isImportNeededSpy = sinon.spy(CmmnEditor.prototype, 'isImportNeeded');
        const cache = new Cache();
  
        cache.add('editor', {
          cached: {
            lastXML: diagramXML,
            modeler: new CmmnModeler()
          }
        });
  
        await renderEditor(diagramXML, {
          cache
        });
  
        // then
        expect(isImportNeededSpy).to.be.called;
        expect(isImportNeededSpy).to.have.always.returned(false);
      });
  
  
      it('should not import when props did not changed', async function() {
  
        // given
        const {
          instance
        } = await renderEditor(diagramXML);
  
        const isImportNeededSpy = sinon.spy(instance, 'isImportNeeded');
  
        // when
        await instance.componentDidUpdate({
          xml: diagramXML
        });
  
        // then
        expect(isImportNeededSpy).to.be.called;
        expect(isImportNeededSpy).to.have.always.returned(false);
  
      });
  
  
      it('should unset lastXML on import error', async function() {
  
        // given
        const { instance } = await renderEditor(diagramXML, {
          onImport
        });
  
        async function onImport() {
  
          // assume
          expect(instance.getCached().lastXML).to.equal(diagramXML);
  
          // when
          await instance.handleError(new Error(), []);
  
          // then
          expect(instance.getCached().lastXML).to.be.null;
        }
      });
  
    });
  
  
    describe('dirty state', function() {
  
      let instance;
  
      beforeEach(function() {
        instance = renderEditor(diagramXML).instance;
      });
  
  
      it('should NOT be dirty initially', function() {
  
        // then
        const dirty = instance.isDirty();
  
        expect(dirty).to.be.false;
      });
  
  
      it('should be dirty after modeling', function() {
  
        // given
        const { modeler } = instance.getCached();
  
        // when
        // execute 1 command
        modeler.get('commandStack').execute(1);
  
        // then
        const dirty = instance.isDirty();
  
        expect(dirty).to.be.true;
      });
  
  
      it('should NOT be dirty after modeling -> undo', function() {
  
        // given
        const { modeler } = instance.getCached();
  
        modeler.get('commandStack').execute(1);
  
        // when
        modeler.get('commandStack').undo();
  
        // then
        const dirty = instance.isDirty();
  
        expect(dirty).to.be.false;
      });
  
  
      it('should NOT be dirty after save', async function() {
  
        // given
        const { modeler } = instance.getCached();
  
        // execute 1 command
        modeler.get('commandStack').execute(1);
  
        // when
        await instance.getXML();
  
        // then
        const dirty = instance.isDirty();
  
        expect(dirty).to.be.false;
      });
  
    });
  
  
    describe('properties panel actions', function() {
  
      it('should toggle properties panel', async function() {
  
        // given
        const onLayoutChangedSpy = sinon.spy();
        const {
          instance
        } = await renderEditor(diagramXML, {
          layout: {
            propertiesPanel: {
              open: false,
            }
          },
          onLayoutChanged: onLayoutChangedSpy
        });
  
        // when
        instance.triggerAction('toggleProperties');
  
        // then
        expect(onLayoutChangedSpy).to.be.calledOnceWith({
          propertiesPanel: {
            open: true,
          }
        });
      });
  
  
      it('should reset properties panel', async function() {
  
        // given
        const onLayoutChangedSpy = sinon.spy();
        const {
          instance
        } = await renderEditor(diagramXML, {
          onLayoutChanged: onLayoutChangedSpy
        });
  
        // when
        instance.triggerAction('resetProperties');
  
        // then
        expect(onLayoutChangedSpy).to.be.calledOnceWith({
          propertiesPanel: {
            open: true,
            width: 280
          }
        });
      });
  
    });
  
  
    describe('zoom actions', function() {
  
      let editorActionsStub,
          instance;
  
      beforeEach(async function() {
  
        // given
        editorActionsStub = sinon.stub({ trigger() {} });
  
        const cache = new Cache();
  
        cache.add('editor', {
          cached: {
            modeler: new CmmnModeler({
              modules: {
                editorActions: editorActionsStub
              }
            })
          }
        });
  
        instance = (await renderEditor(diagramXML, { cache })).instance;
      });
  
  
      afterEach(sinon.restore);
  
  
      it('should zoom in', function() {
  
        // when
        instance.triggerAction('zoomIn');
  
        // then
        expect(editorActionsStub.trigger).to.be.calledOnceWith('stepZoom', {
          value: 1
        });
      });
  
  
      it('should zoom out', function() {
  
        // when
        instance.triggerAction('zoomOut');
  
        // then
        expect(editorActionsStub.trigger).to.be.calledOnceWith('stepZoom', {
          value: -1
        });
      });
  
  
      it('should zoom to fit diagram', function() {
  
        // when
        instance.triggerAction('zoomFit');
  
        // then
        expect(editorActionsStub.trigger).to.be.calledOnceWith('zoom', {
          value: 'fit-viewport'
        });
      });
  
  
      it('should reset zoom', async function() {
  
        // when
        instance.triggerAction('resetZoom');
  
        // then
        expect(editorActionsStub.trigger).to.be.calledOnceWith('zoom', {
          value: 1
        });
      });
  
    });
  
  
    it('should notify when modeler was created', async function() {
  
      // given
      const emittedEvents = [];
      const recordActions = (action, options) => {
        emittedEvents.push(options);
      };
  
      const {
        instance
      } = await renderEditor(diagramXML, {
        onAction: recordActions
      });
  
      // when
      const modeler = instance.getModeler();
  
      // then
      expect(emittedEvents.length).to.eql(1);
  
      const { type, payload } = emittedEvents[0];
  
      expect(type).to.eql('cmmn.modeler.created');
      expect(payload.modeler).to.equal(modeler);
    });
  });
  
  
  // helpers //////////
  
  function noop() {}
  
  const TestEditor = WithCachedState(CmmnEditor);
  
  function renderEditor(xml, options = {}) {
    const {
      onAction,
      layout,
      onChanged,
      onError,
      onImport,
      onLayoutChanged
    } = options;
  
    const wrapper = mount(
      <TestEditor
        id={ options.id || 'editor' }
        xml={ xml }
        activeSheet={ options.activeSheet || { id: 'cmmn' } }
        onAction={ onAction || noop }
        onChanged={ onChanged || noop }
        onError={ onError || noop }
        onImport={ onImport || noop }
        onLayoutChanged={ onLayoutChanged || noop }
        cache={ options.cache || new Cache() }
        layout={ layout || {
          minimap: {
            open: false
          },
          propertiesPanel: {
            open: true
          }
        } }
      />
    );
  
    const instance = wrapper.find(CmmnEditor).instance();
  
    return {
      instance,
      wrapper
    };
  }
  