import { NS } from '../../packages/svgcanvas/core/namespaces.js' import * as utilities from '../../packages/svgcanvas/core/utilities.js' import * as history from '../../packages/svgcanvas/core/history.js' describe('history', function () { // TODO(codedread): Write tests for handling history events. utilities.mock({ getHref () { return '#foo' }, setHref () { /* empty fn */ }, getRotationAngle () { return 0 } }) // const svg = document.createElementNS(NS.SVG, 'svg'); let undoMgr = null let divparent let div1 let div2 let div3 let div4 let div5 let div class MockCommand extends history.Command { constructor (optText) { super() this.text = optText } apply (handler) { super.apply(handler, () => { /* empty fn */ }) } unapply (handler) { super.unapply(handler, () => { /* empty fn */ }) } elements () { return [] } } /* class MockHistoryEventHandler { handleHistoryEvent (eventType, command) {} } */ /** * Set up tests (with undo manager). * @returns {void} */ beforeEach(function () { undoMgr = new history.UndoManager() document.body.textContent = '' divparent = document.createElement('div') divparent.id = 'divparent' divparent.style.visibility = 'hidden' div1 = document.createElement('div'); div1.id = 'div1' div2 = document.createElement('div'); div2.id = 'div2' div3 = document.createElement('div'); div3.id = 'div3' div4 = document.createElement('div'); div4.id = 'div4' div5 = document.createElement('div'); div5.id = 'div5' div = document.createElement('div'); div.id = 'div' divparent.append(div1, div2, div3) div4.style.visibility = 'hidden' div4.append(div5) document.body.append(divparent, div) }) /** * Tear down tests, destroying undo manager. * @returns {void} */ afterEach(() => { undoMgr = null }) it('Test svgedit.history package', function () { assert.ok(history) assert.ok(history.MoveElementCommand) assert.ok(history.InsertElementCommand) assert.ok(history.ChangeElementCommand) assert.ok(history.RemoveElementCommand) assert.ok(history.BatchCommand) assert.ok(history.UndoManager) assert.equal(typeof history.MoveElementCommand, typeof function () { /* empty fn */ }) assert.equal(typeof history.InsertElementCommand, typeof function () { /* empty fn */ }) assert.equal(typeof history.ChangeElementCommand, typeof function () { /* empty fn */ }) assert.equal(typeof history.RemoveElementCommand, typeof function () { /* empty fn */ }) assert.equal(typeof history.BatchCommand, typeof function () { /* empty fn */ }) assert.equal(typeof history.UndoManager, typeof function () { /* empty fn */ }) }) it('Test UndoManager methods', function () { assert.ok(undoMgr) assert.ok(undoMgr.addCommandToHistory) assert.ok(undoMgr.getUndoStackSize) assert.ok(undoMgr.getRedoStackSize) assert.ok(undoMgr.resetUndoStack) assert.ok(undoMgr.getNextUndoCommandText) assert.ok(undoMgr.getNextRedoCommandText) assert.equal(typeof undoMgr, typeof {}) assert.equal(typeof undoMgr.addCommandToHistory, typeof function () { /* empty fn */ }) assert.equal(typeof undoMgr.getUndoStackSize, typeof function () { /* empty fn */ }) assert.equal(typeof undoMgr.getRedoStackSize, typeof function () { /* empty fn */ }) assert.equal(typeof undoMgr.resetUndoStack, typeof function () { /* empty fn */ }) assert.equal(typeof undoMgr.getNextUndoCommandText, typeof function () { /* empty fn */ }) assert.equal(typeof undoMgr.getNextRedoCommandText, typeof function () { /* empty fn */ }) }) it('Test UndoManager.addCommandToHistory() function', function () { assert.equal(undoMgr.getUndoStackSize(), 0) undoMgr.addCommandToHistory(new MockCommand()) assert.equal(undoMgr.getUndoStackSize(), 1) undoMgr.addCommandToHistory(new MockCommand()) assert.equal(undoMgr.getUndoStackSize(), 2) }) it('Test UndoManager.getUndoStackSize() and getRedoStackSize() functions', function () { undoMgr.addCommandToHistory(new MockCommand()) undoMgr.addCommandToHistory(new MockCommand()) undoMgr.addCommandToHistory(new MockCommand()) assert.equal(undoMgr.getUndoStackSize(), 3) assert.equal(undoMgr.getRedoStackSize(), 0) undoMgr.undo() assert.equal(undoMgr.getUndoStackSize(), 2) assert.equal(undoMgr.getRedoStackSize(), 1) undoMgr.undo() assert.equal(undoMgr.getUndoStackSize(), 1) assert.equal(undoMgr.getRedoStackSize(), 2) undoMgr.undo() assert.equal(undoMgr.getUndoStackSize(), 0) assert.equal(undoMgr.getRedoStackSize(), 3) undoMgr.undo() assert.equal(undoMgr.getUndoStackSize(), 0) assert.equal(undoMgr.getRedoStackSize(), 3) undoMgr.redo() assert.equal(undoMgr.getUndoStackSize(), 1) assert.equal(undoMgr.getRedoStackSize(), 2) undoMgr.redo() assert.equal(undoMgr.getUndoStackSize(), 2) assert.equal(undoMgr.getRedoStackSize(), 1) undoMgr.redo() assert.equal(undoMgr.getUndoStackSize(), 3) assert.equal(undoMgr.getRedoStackSize(), 0) undoMgr.redo() assert.equal(undoMgr.getUndoStackSize(), 3) assert.equal(undoMgr.getRedoStackSize(), 0) }) it('Test UndoManager.resetUndoStackSize() function', function () { undoMgr.addCommandToHistory(new MockCommand()) undoMgr.addCommandToHistory(new MockCommand()) undoMgr.addCommandToHistory(new MockCommand()) undoMgr.undo() assert.equal(undoMgr.getUndoStackSize(), 2) assert.equal(undoMgr.getRedoStackSize(), 1) undoMgr.resetUndoStack() assert.equal(undoMgr.getUndoStackSize(), 0) assert.equal(undoMgr.getRedoStackSize(), 0) }) it('Test UndoManager.getNextUndoCommandText() function', function () { assert.equal(undoMgr.getNextUndoCommandText(), '') undoMgr.addCommandToHistory(new MockCommand('First')) undoMgr.addCommandToHistory(new MockCommand('Second')) undoMgr.addCommandToHistory(new MockCommand('Third')) assert.equal(undoMgr.getNextUndoCommandText(), 'Third') undoMgr.undo() assert.equal(undoMgr.getNextUndoCommandText(), 'Second') undoMgr.undo() assert.equal(undoMgr.getNextUndoCommandText(), 'First') undoMgr.undo() assert.equal(undoMgr.getNextUndoCommandText(), '') undoMgr.redo() assert.equal(undoMgr.getNextUndoCommandText(), 'First') undoMgr.redo() assert.equal(undoMgr.getNextUndoCommandText(), 'Second') undoMgr.redo() assert.equal(undoMgr.getNextUndoCommandText(), 'Third') undoMgr.redo() assert.equal(undoMgr.getNextUndoCommandText(), 'Third') }) it('Test UndoManager.getNextRedoCommandText() function', function () { assert.equal(undoMgr.getNextRedoCommandText(), '') undoMgr.addCommandToHistory(new MockCommand('First')) undoMgr.addCommandToHistory(new MockCommand('Second')) undoMgr.addCommandToHistory(new MockCommand('Third')) assert.equal(undoMgr.getNextRedoCommandText(), '') undoMgr.undo() assert.equal(undoMgr.getNextRedoCommandText(), 'Third') undoMgr.undo() assert.equal(undoMgr.getNextRedoCommandText(), 'Second') undoMgr.undo() assert.equal(undoMgr.getNextRedoCommandText(), 'First') undoMgr.redo() assert.equal(undoMgr.getNextRedoCommandText(), 'Second') undoMgr.redo() assert.equal(undoMgr.getNextRedoCommandText(), 'Third') undoMgr.redo() assert.equal(undoMgr.getNextRedoCommandText(), '') }) it('Test UndoManager.undo() and redo() functions', function () { let lastCalled = null const cmd1 = new MockCommand() const cmd2 = new MockCommand() const cmd3 = new MockCommand() cmd1.apply = function () { lastCalled = 'cmd1.apply' } cmd2.apply = function () { lastCalled = 'cmd2.apply' } cmd3.apply = function () { lastCalled = 'cmd3.apply' } cmd1.unapply = function () { lastCalled = 'cmd1.unapply' } cmd2.unapply = function () { lastCalled = 'cmd2.unapply' } cmd3.unapply = function () { lastCalled = 'cmd3.unapply' } undoMgr.addCommandToHistory(cmd1) undoMgr.addCommandToHistory(cmd2) undoMgr.addCommandToHistory(cmd3) assert.ok(!lastCalled) undoMgr.undo() assert.equal(lastCalled, 'cmd3.unapply') undoMgr.redo() assert.equal(lastCalled, 'cmd3.apply') undoMgr.undo() undoMgr.undo() assert.equal(lastCalled, 'cmd2.unapply') undoMgr.undo() assert.equal(lastCalled, 'cmd1.unapply') lastCalled = null undoMgr.undo() assert.ok(!lastCalled) undoMgr.redo() assert.equal(lastCalled, 'cmd1.apply') undoMgr.redo() assert.equal(lastCalled, 'cmd2.apply') undoMgr.redo() assert.equal(lastCalled, 'cmd3.apply') lastCalled = null undoMgr.redo() assert.ok(!lastCalled) }) it('Test MoveElementCommand', function () { let move = new history.MoveElementCommand(div3, div1, divparent) assert.ok(move.unapply) assert.ok(move.apply) assert.equal(typeof move.unapply, typeof function () { /* empty fn */ }) assert.equal(typeof move.apply, typeof function () { /* empty fn */ }) move.unapply() assert.equal(divparent.firstElementChild, div3) assert.equal(divparent.firstElementChild.nextElementSibling, div1) assert.equal(divparent.lastElementChild, div2) move.apply() assert.equal(divparent.firstElementChild, div1) assert.equal(divparent.firstElementChild.nextElementSibling, div2) assert.equal(divparent.lastElementChild, div3) move = new history.MoveElementCommand(div1, null, divparent) move.unapply() assert.equal(divparent.firstElementChild, div2) assert.equal(divparent.firstElementChild.nextElementSibling, div3) assert.equal(divparent.lastElementChild, div1) move.apply() assert.equal(divparent.firstElementChild, div1) assert.equal(divparent.firstElementChild.nextElementSibling, div2) assert.equal(divparent.lastElementChild, div3) move = new history.MoveElementCommand(div2, div5, div4) move.unapply() assert.equal(divparent.firstElementChild, div1) assert.equal(divparent.firstElementChild.nextElementSibling, div3) assert.equal(divparent.lastElementChild, div3) assert.equal(div4.firstElementChild, div2) assert.equal(div4.firstElementChild.nextElementSibling, div5) move.apply() assert.equal(divparent.firstElementChild, div1) assert.equal(divparent.firstElementChild.nextElementSibling, div2) assert.equal(divparent.lastElementChild, div3) assert.equal(div4.firstElementChild, div5) assert.equal(div4.lastElementChild, div5) }) it('Test InsertElementCommand', function () { let insert = new history.InsertElementCommand(div3) assert.ok(insert.unapply) assert.ok(insert.apply) assert.equal(typeof insert.unapply, typeof function () { /* empty fn */ }) assert.equal(typeof insert.apply, typeof function () { /* empty fn */ }) insert.unapply() assert.equal(divparent.childElementCount, 2) assert.equal(divparent.firstElementChild, div1) assert.equal(div1.nextElementSibling, div2) assert.equal(divparent.lastElementChild, div2) insert.apply() assert.equal(divparent.childElementCount, 3) assert.equal(divparent.firstElementChild, div1) assert.equal(div1.nextElementSibling, div2) assert.equal(div2.nextElementSibling, div3) insert = new history.InsertElementCommand(div2) insert.unapply() assert.equal(divparent.childElementCount, 2) assert.equal(divparent.firstElementChild, div1) assert.equal(div1.nextElementSibling, div3) assert.equal(divparent.lastElementChild, div3) insert.apply() assert.equal(divparent.childElementCount, 3) assert.equal(divparent.firstElementChild, div1) assert.equal(div1.nextElementSibling, div2) assert.equal(div2.nextElementSibling, div3) }) it('Test RemoveElementCommand', function () { const div6 = document.createElement('div') div6.id = 'div6' let remove = new history.RemoveElementCommand(div6, null, divparent) assert.ok(remove.unapply) assert.ok(remove.apply) assert.equal(typeof remove.unapply, typeof function () { /* empty fn */ }) assert.equal(typeof remove.apply, typeof function () { /* empty fn */ }) remove.unapply() assert.equal(divparent.childElementCount, 4) assert.equal(divparent.firstElementChild, div1) assert.equal(div1.nextElementSibling, div2) assert.equal(div2.nextElementSibling, div3) assert.equal(div3.nextElementSibling, div6) remove.apply() assert.equal(divparent.childElementCount, 3) assert.equal(divparent.firstElementChild, div1) assert.equal(div1.nextElementSibling, div2) assert.equal(div2.nextElementSibling, div3) remove = new history.RemoveElementCommand(div6, div2, divparent) remove.unapply() assert.equal(divparent.childElementCount, 4) assert.equal(divparent.firstElementChild, div1) assert.equal(div1.nextElementSibling, div6) assert.equal(div6.nextElementSibling, div2) assert.equal(div2.nextElementSibling, div3) remove.apply() assert.equal(divparent.childElementCount, 3) assert.equal(divparent.firstElementChild, div1) assert.equal(div1.nextElementSibling, div2) assert.equal(div2.nextElementSibling, div3) }) it('Test ChangeElementCommand', function () { div1.setAttribute('title', 'new title') let change = new history.ChangeElementCommand(div1, { title: 'old title', class: 'foo' }) assert.ok(change.unapply) assert.ok(change.apply) assert.equal(typeof change.unapply, typeof function () { /* empty fn */ }) assert.equal(typeof change.apply, typeof function () { /* empty fn */ }) change.unapply() assert.equal(div1.getAttribute('title'), 'old title') assert.equal(div1.getAttribute('class'), 'foo') change.apply() assert.equal(div1.getAttribute('title'), 'new title') assert.ok(!div1.getAttribute('class')) div1.textContent = 'inner text' change = new history.ChangeElementCommand(div1, { '#text': null }) change.unapply() assert.ok(!div1.textContent) change.apply() assert.equal(div1.textContent, 'inner text') div1.textContent = '' change = new history.ChangeElementCommand(div1, { '#text': 'old text' }) change.unapply() assert.equal(div1.textContent, 'old text') change.apply() assert.ok(!div1.textContent) // TODO(codedread): Refactor this #href stuff in history.js and svgcanvas.js const rect = document.createElementNS(NS.SVG, 'rect') let justCalled = null let gethrefvalue = null let sethrefvalue = null utilities.mock({ getHref (elem) { assert.equal(elem, rect) justCalled = 'getHref' return gethrefvalue }, setHref (elem, val) { assert.equal(elem, rect) assert.equal(val, sethrefvalue) justCalled = 'setHref' }, getRotationAngle () { return 0 } }) gethrefvalue = '#newhref' change = new history.ChangeElementCommand(rect, { '#href': '#oldhref' }) assert.equal(justCalled, 'getHref') justCalled = null sethrefvalue = '#oldhref' change.unapply() assert.equal(justCalled, 'setHref') justCalled = null sethrefvalue = '#newhref' change.apply() assert.equal(justCalled, 'setHref') // Ensure numeric zero values are not treated like "remove attribute". const rectZero = document.createElementNS(NS.SVG, 'rect') rectZero.setAttribute('x', '5') change = new history.ChangeElementCommand(rectZero, { x: 0 }) change.unapply() assert.equal(rectZero.getAttribute('x'), '0') change.apply() assert.equal(rectZero.getAttribute('x'), '5') // Ensure "#href" can be removed when the previous value was null. const rectHref = document.createElementNS(NS.SVG, 'rect') rectHref.setAttribute('href', '#newhref') let calls = [] utilities.mock({ getHref (elem) { assert.equal(elem, rectHref) calls.push('getHref') return rectHref.getAttribute('href') }, setHref (elem, val) { assert.equal(elem, rectHref) calls.push('setHref') rectHref.setAttribute('href', val) }, getRotationAngle () { return 0 } }) calls = [] change = new history.ChangeElementCommand(rectHref, { '#href': null }) assert.deepEqual(calls, ['getHref']) calls = [] change.unapply() assert.equal(rectHref.hasAttribute('href'), false) assert.deepEqual(calls, []) calls = [] change.apply() assert.equal(rectHref.getAttribute('href'), '#newhref') assert.deepEqual(calls, ['setHref']) const line = document.createElementNS(NS.SVG, 'line') line.setAttribute('class', 'newClass') change = new history.ChangeElementCommand(line, { class: 'oldClass' }) assert.ok(change.unapply) assert.ok(change.apply) assert.equal(typeof change.unapply, typeof function () { /* empty fn */ }) assert.equal(typeof change.apply, typeof function () { /* empty fn */ }) change.unapply() assert.equal(line.getAttribute('class'), 'oldClass') change.apply() assert.equal(line.getAttribute('class'), 'newClass') }) it('Test BatchCommand', function () { let concatResult = '' MockCommand.prototype.apply = function () { concatResult += this.text } const batch = new history.BatchCommand() assert.ok(batch.unapply) assert.ok(batch.apply) assert.ok(batch.addSubCommand) assert.ok(batch.isEmpty) assert.equal(typeof batch.unapply, 'function') assert.equal(typeof batch.apply, 'function') assert.equal(typeof batch.addSubCommand, 'function') assert.equal(typeof batch.isEmpty, 'function') assert.ok(batch.isEmpty()) batch.addSubCommand(new MockCommand('a')) assert.ok(!batch.isEmpty()) batch.addSubCommand(new MockCommand('b')) batch.addSubCommand(new MockCommand('c')) assert.ok(!concatResult) batch.apply() assert.equal(concatResult, 'abc') MockCommand.prototype.apply = function () { /* empty fn */ } MockCommand.prototype.unapply = function () { concatResult += this.text } concatResult = '' assert.ok(!concatResult) batch.unapply() assert.equal(concatResult, 'cba') MockCommand.prototype.unapply = function () { /* empty fn */ } }) it('Test BatchCommand with elements() method', function () { const batch = new history.BatchCommand('test batch with elements') // Create some mock commands that reference elements class MockElementCommand { constructor (elem) { this.elem = elem } elements () { return [this.elem] } apply () { /* empty fn */ } unapply () { /* empty fn */ } getText () { return 'mock' } } const elem1 = document.createElementNS(NS.SVG, 'rect') const cmd1 = new MockElementCommand(elem1) batch.addSubCommand(cmd1) const elems = batch.elements() assert.ok(Array.isArray(elems)) }) it('Test BatchCommand getText()', function () { const batch = new history.BatchCommand('my test batch') assert.equal(batch.getText(), 'my test batch') }) })