Files
svgedit/tests/e2e/unit/svgcore-history-draw.spec.js
JFH fa380402e1 increase test coverage
extend test coverage
2025-12-07 10:56:29 +01:00

208 lines
7.4 KiB
JavaScript

import { test, expect } from '../fixtures.js'
test.describe('SVG core history and draw', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/tests/unit-harness.html')
await page.waitForFunction(() => Boolean(window.svgHarness))
})
test('UndoManager tracks stacks and command texts through undo/redo', async ({ page }) => {
const result = await page.evaluate(() => {
const { history } = window.svgHarness
let lastCalled = ''
class MockCommand extends history.Command {
constructor (text) {
super()
this.text = text
}
apply () { lastCalled = `${this.text}:apply` }
unapply () { lastCalled = `${this.text}:unapply` }
elements () { return [] }
}
const um = new history.UndoManager()
;['First', 'Second', 'Third'].forEach((label) => {
um.addCommandToHistory(new MockCommand(label))
})
const beforeUndo = {
undo: um.getUndoStackSize(),
redo: um.getRedoStackSize(),
nextUndo: um.getNextUndoCommandText(),
nextRedo: um.getNextRedoCommandText()
}
um.undo()
const afterFirstUndo = {
undo: um.getUndoStackSize(),
redo: um.getRedoStackSize(),
nextUndo: um.getNextUndoCommandText(),
nextRedo: um.getNextRedoCommandText(),
lastCalled
}
um.undo()
um.redo()
const afterRedo = {
undo: um.getUndoStackSize(),
redo: um.getRedoStackSize(),
nextUndo: um.getNextUndoCommandText(),
nextRedo: um.getNextRedoCommandText(),
lastCalled
}
return { beforeUndo, afterFirstUndo, afterRedo }
})
expect(result.beforeUndo).toEqual({
undo: 3,
redo: 0,
nextUndo: 'Third',
nextRedo: ''
})
expect(result.afterFirstUndo.undo).toBe(2)
expect(result.afterFirstUndo.redo).toBe(1)
expect(result.afterFirstUndo.nextUndo).toBe('Second')
expect(result.afterFirstUndo.nextRedo).toBe('Third')
expect(result.afterFirstUndo.lastCalled).toBe('Third:unapply')
expect(result.afterRedo.undo).toBe(2)
expect(result.afterRedo.redo).toBe(1)
expect(result.afterRedo.nextUndo).toBe('Second')
expect(result.afterRedo.nextRedo).toBe('Third')
expect(result.afterRedo.lastCalled).toBe('Second:apply')
})
test('history commands move, insert and remove elements in the DOM', async ({ page }) => {
const result = await page.evaluate(() => {
const { history } = window.svgHarness
const makeParent = () => {
const parent = document.createElement('div')
parent.id = 'parent'
const children = ['div1', 'div2', 'div3'].map((id) => {
const el = document.createElement('div')
el.id = id
parent.append(el)
return el
})
document.body.append(parent)
return { parent, children }
}
const order = (parent) => [...parent.children].map((el) => el.id)
const { parent: parentMove, children: moveChildren } = makeParent()
const move = new history.MoveElementCommand(
moveChildren[2],
moveChildren[0],
parentMove
)
move.unapply()
const orderAfterMoveUnapply = order(parentMove)
move.apply()
const orderAfterMoveApply = order(parentMove)
const { parent: parentInsert, children: insertChildren } = makeParent()
const insert = new history.InsertElementCommand(insertChildren[2])
insert.unapply()
const orderAfterInsertUnapply = order(parentInsert)
insert.apply()
const orderAfterInsertApply = order(parentInsert)
const { parent: parentRemove } = makeParent()
const extra = document.createElement('div')
extra.id = 'div4'
const remove = new history.RemoveElementCommand(extra, null, parentRemove)
remove.unapply()
const orderAfterRemoveUnapply = order(parentRemove)
remove.apply()
const orderAfterRemoveApply = order(parentRemove)
return {
orderAfterMoveUnapply,
orderAfterMoveApply,
orderAfterInsertUnapply,
orderAfterInsertApply,
orderAfterRemoveUnapply,
orderAfterRemoveApply
}
})
expect(result.orderAfterMoveUnapply).toEqual(['div3', 'div1', 'div2'])
expect(result.orderAfterMoveApply).toEqual(['div1', 'div2', 'div3'])
expect(result.orderAfterInsertUnapply).toEqual(['div1', 'div2'])
expect(result.orderAfterInsertApply).toEqual(['div1', 'div2', 'div3'])
expect(result.orderAfterRemoveUnapply).toEqual(['div1', 'div2', 'div3', 'div4'])
expect(result.orderAfterRemoveApply).toEqual(['div1', 'div2', 'div3'])
})
test('BatchCommand applies and unapplies subcommands in order', async ({ page }) => {
const result = await page.evaluate(() => {
const { history } = window.svgHarness
let record = ''
class TextCommand extends history.Command {
constructor (text) {
super()
this.text = text
}
apply () { record += this.text }
unapply () { record += this.text.toUpperCase() }
elements () { return [] }
}
const batch = new history.BatchCommand()
const emptyBefore = batch.isEmpty()
batch.addSubCommand(new TextCommand('a'))
batch.addSubCommand(new TextCommand('b'))
batch.addSubCommand(new TextCommand('c'))
batch.apply()
const afterApply = record
record = ''
batch.unapply()
const afterUnapply = record
return { emptyBefore, afterApply, afterUnapply }
})
expect(result.emptyBefore).toBe(true)
expect(result.afterApply).toBe('abc')
expect(result.afterUnapply).toBe('CBA')
})
test('Drawing creates layers, generates ids and toggles nonce randomization', async ({ page }) => {
const result = await page.evaluate(() => {
const { draw, namespaces } = window.svgHarness
const svg = document.createElementNS(namespaces.NS.SVG, 'svg')
document.body.append(svg)
const drawing = new draw.Drawing(svg)
const beforeIdentify = drawing.getNumLayers()
drawing.identifyLayers()
const defaultLayer = drawing.getCurrentLayer()
const defaultName = drawing.getCurrentLayerName()
const hrCounts = { start: 0, end: 0, insert: 0 }
const newLayer = drawing.createLayer('Layer A', {
startBatchCommand: () => { hrCounts.start++ },
endBatchCommand: () => { hrCounts.end++ },
insertElement: () => { hrCounts.insert++ }
})
const afterCreate = {
num: drawing.getNumLayers(),
currentName: drawing.getCurrentLayerName(),
className: newLayer.getAttribute('class')
}
draw.randomizeIds(true, drawing)
const nonceAfterRandomize = drawing.getNonce()
draw.randomizeIds(false, drawing)
const nonceAfterClear = drawing.getNonce()
return {
beforeIdentify,
defaultName,
defaultLayerClass: defaultLayer?.getAttribute('class'),
afterCreate,
hrCounts,
nonceAfterRandomize,
nonceAfterClear
}
})
expect(result.beforeIdentify).toBe(0)
expect(result.defaultLayerClass).toBeDefined()
expect(result.defaultName.length).toBeGreaterThan(0)
expect(result.afterCreate.num).toBe(2)
expect(result.afterCreate.currentName).toBe('Layer A')
expect(result.afterCreate.className).toBe(result.defaultLayerClass)
expect(result.hrCounts).toEqual({ start: 1, end: 1, insert: 1 })
expect(result.nonceAfterRandomize).toBeTruthy()
expect(result.nonceAfterClear).toBe('')
})
})