diff --git a/data/4-svgedit-ui/LICENSE.txt b/data/4-svgedit-ui/LICENSE.txt
new file mode 100644
index 0000000..7778dc1
--- /dev/null
+++ b/data/4-svgedit-ui/LICENSE.txt
@@ -0,0 +1,19 @@
+Copyright (c) 2009-2022 by SVG-edit authors (see AUTHORS file)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff --git a/data/4-svgedit-ui/clipboard.cy.js b/data/4-svgedit-ui/clipboard.cy.js
new file mode 100644
index 0000000..acccc88
--- /dev/null
+++ b/data/4-svgedit-ui/clipboard.cy.js
@@ -0,0 +1,63 @@
+import {
+ visitAndApproveStorage
+} from '../../support/ui-test-helper.js'
+
+describe('UI - Clipboard', function () {
+ beforeEach(() => {
+ visitAndApproveStorage()
+ })
+
+ it('Editor - Copy and paste', () => {
+ cy.get('#tool_source').click({ force: true })
+
+ cy.get('#svg_source_textarea')
+ .type('{selectall}', { force: true })
+ .type(``, { force: true, parseSpecialCharSequences: false })
+ cy.get('#tool_source_save').click({ force: true })
+ cy.get('#testCircle').should('exist')
+ cy.get('#svg_1').should('not.exist')
+ cy.get('#svg_2').should('not.exist')
+
+ // Copy.
+ cy.get('#testCircle').click({ force: true }).rightclick({ force: true })
+ cy.get('#cmenu_canvas a[href="#copy"]').click({ force: true })
+
+ // Paste.
+ // Scrollbars fail to recenter in Cypress test. Works fine in reality.
+ // Thus forcing click is needed since workspace is mostly offscreen.
+ cy.get('#svgroot').rightclick({ force: true })
+ cy.get('#cmenu_canvas a[href="#paste"]').click({ force: true })
+ cy.get('#testCircle').should('exist')
+ cy.get('#svg_1').should('exist')
+ cy.get('#svg_2').should('not.exist')
+
+ // Cut.
+ cy.get('#testCircle').click({ force: true }).rightclick({ force: true })
+ cy.get('#cmenu_canvas a[href="#cut"]').click({ force: true })
+ cy.get('#testCircle').should('not.exist')
+ cy.get('#svg_1').should('exist')
+ cy.get('#svg_2').should('not.exist')
+
+ // Paste.
+ // Scrollbars fail to recenter in Cypress test. Works fine in reality.
+ // Thus forcing click is needed since workspace is mostly offscreen.
+ cy.get('#svgroot').rightclick({ force: true })
+ cy.get('#cmenu_canvas a[href="#paste"]').click({ force: true })
+ cy.get('#testCircle').should('not.exist')
+ cy.get('#svg_1').should('exist')
+ cy.get('#svg_2').should('exist')
+
+ // Delete.
+ cy.get('#svg_2').click({ force: true }).rightclick({ force: true })
+ cy.get('#cmenu_canvas a[href="#delete"]').click({ force: true })
+ cy.get('#svg_1').click({ force: true }).rightclick({ force: true })
+ cy.get('#cmenu_canvas a[href="#delete"]').click({ force: true })
+ cy.get('#svg_1').should('not.exist')
+ cy.get('#svg_2').should('not.exist')
+ })
+})
diff --git a/data/4-svgedit-ui/control-points.cy.js b/data/4-svgedit-ui/control-points.cy.js
new file mode 100644
index 0000000..ecee75a
--- /dev/null
+++ b/data/4-svgedit-ui/control-points.cy.js
@@ -0,0 +1,34 @@
+import {
+ visitAndApproveStorage
+} from '../../support/ui-test-helper.js'
+
+describe('UI - Control Points', function () {
+ beforeEach(() => {
+ visitAndApproveStorage()
+ })
+
+ it('Editor - No parameters: Drag control point of arc path', () => {
+ const randomOffset = () => 2 + Math.round(10 + Math.random() * 40)
+ cy.get('#tool_source').click({ force: true })
+ cy.get('#svg_source_textarea')
+ .type('{selectall}', { force: true })
+ .type(``, { force: true, parseSpecialCharSequences: false })
+ cy.get('#tool_source_save').click({ force: true })
+
+ cy.get('#svg_1').click({ force: true }).click({ force: true })
+
+ cy.get('#pathpointgrip_0').trigger('mousedown', { which: 1, force: true })
+ .trigger('mousemove', randomOffset(), randomOffset(), { force: true })
+ .trigger('mouseup', { force: true })
+ cy.get('#pathpointgrip_1').trigger('mousedown', { which: 1, force: true })
+ .trigger('mousemove', randomOffset(), randomOffset(), { force: true })
+ .trigger('mouseup', { force: true })
+
+ cy.get('#svg_1[d]').should('not.contain', 'NaN')
+ })
+})
diff --git a/data/4-svgedit-ui/export.cy.js b/data/4-svgedit-ui/export.cy.js
new file mode 100644
index 0000000..128a5f8
--- /dev/null
+++ b/data/4-svgedit-ui/export.cy.js
@@ -0,0 +1,20 @@
+import {
+ visitAndApproveStorage, openMainMenu
+} from '../../support/ui-test-helper.js'
+
+describe('UI - Export tests', function () {
+ beforeEach(() => {
+ visitAndApproveStorage()
+ })
+
+ it('Editor - No parameters: Has export button', () => {
+ openMainMenu()
+ cy.get('#tool_export')
+ })
+
+ it('Editor - No parameters: Export button clicking; dialog opens', () => {
+ openMainMenu()
+ cy.get('#tool_export').click({ force: true })
+ cy.get('#dialog_content select')
+ })
+})
diff --git a/data/4-svgedit-ui/issue-359.cy.js b/data/4-svgedit-ui/issue-359.cy.js
new file mode 100644
index 0000000..666694d
--- /dev/null
+++ b/data/4-svgedit-ui/issue-359.cy.js
@@ -0,0 +1,26 @@
+import {
+ visitAndApproveStorage
+} from '../../../support/ui-test-helper.js'
+
+// See https://github.com/SVG-Edit/svgedit/issues/359
+describe('Fix issue 359', function () {
+ beforeEach(() => {
+ visitAndApproveStorage()
+ })
+
+ it('can undo without throwing', function () {
+ cy.get('#tool_source').click({ force: true })
+ cy.get('#svg_source_textarea')
+ .type('{selectall}', { force: true })
+ .type(``, { parseSpecialCharSequences: false, force: true })
+ cy.get('#tool_source_save').click({ force: true })
+ cy.get('#tool_undo').click({ force: true })
+ cy.get('#tool_redo').click({ force: true }) // test also redo to make the test more comprehensive
+ // if the undo throws an error to the console, the test will fail
+ })
+})
diff --git a/data/4-svgedit-ui/issue-407.cy.js b/data/4-svgedit-ui/issue-407.cy.js
new file mode 100644
index 0000000..21800ef
--- /dev/null
+++ b/data/4-svgedit-ui/issue-407.cy.js
@@ -0,0 +1,35 @@
+import {
+ visitAndApproveStorage
+} from '../../../support/ui-test-helper.js'
+
+// See https://github.com/SVG-Edit/svgedit/issues/407
+describe('Fix issue 407', function () {
+ beforeEach(() => {
+ visitAndApproveStorage()
+ })
+ it('can enter edit on text child', function () {
+ cy.get('#tool_source').click({ force: true })
+ cy.get('#svg_source_textarea')
+ .type('{selectall}', { force: true })
+ .type(``, { force: true, parseSpecialCharSequences: false })
+ cy.get('#tool_source_save').click({ force: true })
+ cy.get('#svg_1').click({ force: true }).dblclick({ force: true })
+ cy.get('#a_text').should('exist')
+ /** @todo: need to understand the reason why this test now fails */
+ // cy.get('#a_text')
+ // .trigger('mousedown', { which: 1, force: true })
+ // .trigger('mouseup', { force: true })
+ // .dblclick({ force: true })
+ // svgedit use the #text text field to capture the text
+ // cy.get('#text').type('1234', {force: true})
+ // cy.get('#a_text').should('have.text', 'he1234llo')
+ })
+})
diff --git a/data/4-svgedit-ui/issue-408.cy.js b/data/4-svgedit-ui/issue-408.cy.js
new file mode 100644
index 0000000..94639ea
--- /dev/null
+++ b/data/4-svgedit-ui/issue-408.cy.js
@@ -0,0 +1,29 @@
+import {
+ visitAndApproveStorage
+} from '../../../support/ui-test-helper.js'
+
+// See https://github.com/SVG-Edit/svgedit/issues/408
+describe('Fix issue 408', function () {
+ beforeEach(() => {
+ visitAndApproveStorage()
+ })
+
+ it('should not throw when showing/saving svg content', function () {
+ cy.get('#tool_source').click({ force: true })
+ cy.get('#svg_source_textarea')
+ .type('{selectall}', { force: true })
+ .type(``, { force: true, parseSpecialCharSequences: false })
+ cy.get('#tool_source_save').click({ force: true })
+ cy.get('#svg_6').click({ force: true }).dblclick({ force: true }) // change context
+ cy.get('#tool_source').click({ force: true }) // reopen tool_source
+ cy.get('#tool_source_save').should('exist') // The save button should be here if it does not throw
+ })
+})
diff --git a/data/4-svgedit-ui/issue-423.cy.js b/data/4-svgedit-ui/issue-423.cy.js
new file mode 100644
index 0000000..a2cf7b1
--- /dev/null
+++ b/data/4-svgedit-ui/issue-423.cy.js
@@ -0,0 +1,33 @@
+import {
+ visitAndApproveStorage
+} from '../../../support/ui-test-helper.js'
+
+// See https://github.com/SVG-Edit/svgedit/issues/423
+describe('Fix issue 423', function () {
+ beforeEach(() => {
+ visitAndApproveStorage()
+ })
+
+ it('should not throw when undoing the move', function () {
+ cy.get('#tool_source').click({ force: true })
+ cy.get('#svg_source_textarea')
+ .type('{selectall}', { force: true })
+ .type(``, { parseSpecialCharSequences: false, force: true })
+ cy.get('#tool_source_save').click({ force: true })
+ cy.get('#TANK1')
+ .trigger('mousedown', { force: true })
+ .trigger('mousemove', 50, 0, { force: true })
+ .trigger('mouseup', { force: true })
+ cy.get('#tool_undo').click({ force: true })
+ })
+})
diff --git a/data/4-svgedit-ui/issue-660.cy.js b/data/4-svgedit-ui/issue-660.cy.js
new file mode 100644
index 0000000..f4b8eaa
--- /dev/null
+++ b/data/4-svgedit-ui/issue-660.cy.js
@@ -0,0 +1,35 @@
+import {
+ visitAndApproveStorage
+} from '../../../support/ui-test-helper.js'
+
+// See https://github.com/SVG-Edit/svgedit/issues/660
+describe('Fix issue 660', function () {
+ beforeEach(() => {
+ visitAndApproveStorage()
+ cy.viewport(512, 512)
+ })
+ /** @todo: reenable this test when we understand why it is passing locally but not on ci */
+ it.skip('can resize text', function () {
+ cy.get('#tool_source').click({ force: true })
+ cy.get('#svg_source_textarea')
+ .type('{selectall}', { force: true })
+ .type(``, { force: true, parseSpecialCharSequences: false })
+ cy.get('#tool_source_save').click({ force: true })
+ cy.get('#a_text').should('exist')
+ cy.get('#a_text')
+ .trigger('mousedown', { which: 1, force: true })
+ .trigger('mouseup', { force: true })
+ cy.get('#selectorGrip_resize_s')
+ .trigger('mousedown', { which: 1, force: true })
+ .trigger('mousemove', { clientX: 0, clientY: 600 })
+ .trigger('mouseup', { force: true })
+ // svgedit use the #text text field to capture the text
+ cy.get('#a_text').should('have.attr', 'transform')
+ .and('equal', 'matrix(1 0 0 4.54639 0 -540.825)') // Chrome 96 is matrix(1 0 0 4.17431 0 -325.367)
+ })
+})
diff --git a/data/4-svgedit-ui/issue-699.cy.js b/data/4-svgedit-ui/issue-699.cy.js
new file mode 100644
index 0000000..98dd8fa
--- /dev/null
+++ b/data/4-svgedit-ui/issue-699.cy.js
@@ -0,0 +1,29 @@
+import {
+ visitAndApproveStorage
+} from '../../../support/ui-test-helper.js'
+
+// See https://github.com/SVG-Edit/svgedit/issues/699
+describe('Fix issue 699', function () {
+ beforeEach(() => {
+ visitAndApproveStorage()
+ })
+
+ it('should not throw error when undoing and redoing convert to path for a rectangle', function () {
+ cy.get('#tool_rect')
+ .click({ force: true })
+ cy.get('#svgcontent')
+ .trigger('mousedown', 150, 150, { force: true })
+ .trigger('mousemove', 250, 200, { force: true })
+ .trigger('mouseup', { force: true })
+ cy.get('#tool_topath') // Check if undo redo is correct for tool_topath with tool_rect
+ .click({ force: true })
+ cy.get('#tool_undo')
+ .click({ force: true })
+ cy.get('#tool_redo')
+ .click({ force: true })
+ cy.get('#tool_undo') // Do twice just to make sure
+ .click({ force: true })
+ cy.get('#tool_redo')
+ .click({ force: true })
+ })
+})
diff --git a/data/4-svgedit-ui/issue-726.cy.js b/data/4-svgedit-ui/issue-726.cy.js
new file mode 100644
index 0000000..5e57bd6
--- /dev/null
+++ b/data/4-svgedit-ui/issue-726.cy.js
@@ -0,0 +1,41 @@
+import {
+ visitAndApproveStorage
+} from '../../../support/ui-test-helper.js'
+
+// See https://github.com/SVG-Edit/svgedit/issues/726
+describe('Fix issue 726', function () {
+ beforeEach(() => {
+ visitAndApproveStorage()
+ })
+
+ it('Send forward and send backward should move one layer at a time', function () {
+ cy.get('#tool_rect')
+ .click({ force: true })
+ cy.get('#svgcontent')
+ .trigger('mousedown', 250, 250, { force: true })
+ .trigger('mousemove', 350, 350, { force: true })
+ .trigger('mouseup', { force: true })
+ cy.wait(300)
+ cy.get('#tool_rect')
+ .click({ force: true })
+ cy.get('#svgcontent')
+ .trigger('mousedown', 10, 0, { force: true })
+ .trigger('mousemove', 100, 100, { force: true })
+ .trigger('mouseup', { force: true })
+ cy.wait(300)
+ cy.get('#tool_rect')
+ .click({ force: true })
+ cy.get('#svgcontent')
+ .trigger('mousedown', 10, 10, { force: true })
+ .trigger('mousemove', 100, 100, { force: true })
+ .trigger('mouseup', { force: true })
+ cy.wait(300)
+ cy.get('#svg_3')
+ .rightclick(0, 0, { force: true })
+ cy.get('a:contains("Send Backward")').click({ force: true })
+ cy.get('#svg_2').should(($div) => {
+ const id = $div[0].previousElementSibling.id
+ assert.equal(id, 'svg_3')
+ })
+ })
+})
diff --git a/data/4-svgedit-ui/issue-752.cy.js b/data/4-svgedit-ui/issue-752.cy.js
new file mode 100644
index 0000000..585d9e7
--- /dev/null
+++ b/data/4-svgedit-ui/issue-752.cy.js
@@ -0,0 +1,39 @@
+import {
+ visitAndApproveStorage
+} from '../../../support/ui-test-helper.js'
+
+// See https://github.com/SVG-Edit/svgedit/issues/752
+describe('Fix issue 752', function () {
+ beforeEach(() => {
+ visitAndApproveStorage()
+ })
+
+ it('Moving an unsnapped shape will not cause selector box misalignment', function () {
+ cy.get('#tool_rect')
+ .click({ force: true })
+ cy.get('#svgcontent')
+ .trigger('mousedown', 12, 12, { force: true })
+ .trigger('mousemove', 99, 99, { force: true })
+ .trigger('mouseup', { force: true })
+ cy.wait(300)
+ cy.get('#svg_1')
+ .click({ force: true })
+ cy.get('#tool_editor_prefs')
+ .click({ force: true })
+ cy.get('#grid_snapping_step')
+ .then(elem => {
+ elem.val('35')
+ })
+ cy.wait(300)
+ cy.get('#grid_snapping_on')
+ .click({ force: true })
+ cy.get('#tool_prefs_save')
+ .click({ force: true })
+ cy.get('#svg_1')
+ .trigger('mousedown', 20, 20, { force: true })
+ .trigger('mousemove', 203, 205, { force: true })
+ .trigger('mouseup', { force: true })
+
+ cy.get('#selectedBox0').should('have.attr', 'd', 'M192,194 L284,194 284,286 192,286z')
+ })
+})
diff --git a/data/4-svgedit-ui/scenario.cy.js b/data/4-svgedit-ui/scenario.cy.js
new file mode 100644
index 0000000..609da62
--- /dev/null
+++ b/data/4-svgedit-ui/scenario.cy.js
@@ -0,0 +1,209 @@
+import {
+ visitAndApproveStorage
+} from '../../support/ui-test-helper.js'
+
+describe('use text tools of svg-edit', function () {
+ before(() => {
+ visitAndApproveStorage()
+ })
+
+ it('check tool_source', function () {
+ cy.get('#tool_source').click({ force: true })
+ cy.get('#svg_source_textarea')
+ .type('{selectall}', { force: true })
+ .type(``, { force: true, parseSpecialCharSequences: false })
+ cy.get('#tool_source_save').click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_text', function () {
+ cy.get('#tool_text')
+ .click({ force: true })
+ cy.get('#svgroot')
+ .trigger('mousedown', { clientX: 400, clientY: 400, force: true })
+ .trigger('mouseup', { force: true })
+ // svgedit use the #text text field to capture the text
+ cy.get('#text').type('AB', { force: true })
+ // force text position for snapshot tests being consistent on CI/Interactive
+ cy.get('#selected_x').shadow().find('elix-number-spin-box').eq(0).shadow().find('#inner').eq(0).type('{selectall}200', { force: true })
+ cy.get('#selected_y').shadow().find('elix-number-spin-box').eq(0).shadow().find('#inner').eq(0).type('{selectall}200', { force: true })
+ cy.svgSnapshot()
+ // cy.get('#svg_1').should('exist')
+ })
+ it('check tool_clone', function () {
+ cy.get('#svg_1').click({ force: true })
+ cy.get('#tool_clone')
+ .click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_italic', function () {
+ cy.get('#svg_1').click({ force: true })
+ cy.get('#tool_italic')
+ .click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_bold', function () {
+ cy.get('#svg_1').click({ force: true })
+ cy.get('#tool_bold')
+ .click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_text_change_x_y_coordinate', function () {
+ cy.get('#svg_2').click({ force: true })
+ for (let n = 0; n < 25; n++) {
+ cy.get('#selected_x').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ }
+ for (let n = 0; n < 25; n++) {
+ cy.get('#selected_y').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ }
+ cy.svgSnapshot()
+ })
+ it('check tool_text_change_font_size', function () {
+ cy.get('#svg_1').click({ force: true })
+ for (let n = 0; n < 10; n++) {
+ cy.get('#font_size').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ }
+ cy.svgSnapshot()
+ })
+ it('check tool_text_change_stroke_width', function () {
+ cy.get('#svg_1').click({ force: true })
+ cy.get('#stroke_width').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_text_change_stoke_fill_color', function () {
+ cy.get('#svg_1').click({ force: true })
+ cy.get('#stroke_color').shadow().find('#picker').eq(0).click({ force: true })
+ cy.get('#stroke_color').shadow().find('#color_picker').eq(0)
+ .find('#jGraduate_colPick').eq(0).find('#jPicker-table').eq(0)
+ .find('.QuickColor').eq(51).click({ force: true })
+ cy.get('#stroke_color').shadow().find('#color_picker').eq(0)
+ .find('#jGraduate_colPick').eq(0).find('#jPicker-table').eq(0)
+ .find('#Ok').eq(0).click({ force: true })
+ cy.get('#fill_color').shadow().find('#picker').eq(0).click({ force: true })
+ cy.get('#fill_color').shadow().find('#color_picker').eq(0)
+ .find('#jGraduate_colPick').eq(0).find('#jPicker-table').eq(0)
+ .find('.QuickColor').eq(3).click({ force: true })
+ cy.get('#fill_color').shadow().find('#color_picker').eq(0)
+ .find('#jGraduate_colPick').eq(0).find('#jPicker-table').eq(0)
+ .find('#Ok').eq(0).click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_text_change_blur', function () {
+ cy.get('#svg_2').click({ force: true })
+ for (let n = 0; n < 10; n++) {
+ cy.get('#blur').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ }
+ cy.svgSnapshot()
+ })
+ it('check tool_text_change_opacity', function () {
+ cy.get('#svg_2').click({ force: true })
+ for (let n = 0; n < 10; n++) {
+ cy.get('#opacity').shadow().find('elix-number-spin-box').eq(0).shadow().find('#downButton').eq(0)
+ .click({ force: true })
+ }
+ cy.svgSnapshot()
+ })
+ it('check tool_text_align_to_page', function () {
+ cy.get('#svg_2').click({ force: true })
+ cy.get('#tool_position').shadow().find('#select-container').eq(0).click({ force: true })
+ cy.get('#tool_position').find('se-list-item').eq(2).shadow().find('[aria-label="option"]').eq(0)
+ .click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_text_change_class', function () {
+ cy.get('#svg_2').click({ force: true })
+ cy.get('#elem_class').shadow().find('elix-input').eq(0).shadow().find('#inner').eq(0)
+ .type('svg_2_class{enter}', { force: true })
+ cy.get('#svg_2')
+ .should('satisfy', ($el) => {
+ const classList = Array.from($el[0].classList)
+ return classList.includes('svg_2_class')
+ })
+ })
+ it('check tool_text_change_id', function () {
+ cy.get('#svg_2').click({ force: true }).click({ force: true })
+ cy.get('#elem_id').shadow().find('elix-input').eq(0).shadow().find('#inner').eq(0)
+ .type('_id{enter}', { force: true })
+ cy.get('#svg_2_id')
+ .should('satisfy', ($el) => {
+ const classList = Array.from($el[0].classList)
+ return classList.includes('svg_2_class')
+ })
+ })
+ it('check tool_text_delete', function () {
+ cy.get('#svg_2_id').click({ force: true })
+ cy.get('#tool_delete').click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_text_change_font_family', function () {
+ cy.get('#svg_1').click({ force: true })
+ cy.get('#tool_font_family').shadow().find('select').select('Serif', { force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_text_decoration_underline', function () {
+ cy.get('#svg_1').click({ force: true })
+ cy.get('#tool_text_decoration_underline')
+ .click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_text_decoration_linethrough', function () {
+ cy.get('#svg_1').click({ force: true })
+ cy.get('#tool_text_decoration_linethrough')
+ .click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_text_decoration_overline', function () {
+ cy.get('#svg_1').click({ force: true })
+ cy.get('#tool_text_decoration_overline')
+ .click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_letter_spacing', function () {
+ cy.get('#svg_1').click({ force: true })
+ for (let n = 0; n < 10; n++) {
+ cy.get('#tool_letter_spacing').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ }
+ cy.svgSnapshot()
+ })
+ it('check tool_word_spacing', function () {
+ cy.get('#svg_1').click({ force: true })
+ for (let n = 0; n < 15; n++) {
+ cy.get('#tool_word_spacing').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ }
+ cy.svgSnapshot()
+ })
+ it('check tool_text_length', function () {
+ cy.get('#svg_1').click({ force: true })
+ for (let n = 0; n < 20; n++) {
+ cy.get('#tool_text_length').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ }
+ cy.svgSnapshot()
+ })
+ it('check tool_length_adjust', function () {
+ cy.get('#svg_1').click({ force: true })
+ cy.get('#tool_length_adjust').shadow().find('select').select(1, { force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_text_change_rotation', function () {
+ cy.get('#svg_1').click({ force: true })
+ for (let n = 0; n < 6; n++) {
+ cy.get('#angle').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ }
+ cy.get('#svg_1').should('have.attr', 'transform')
+ .and('match', /rotate\(30/)
+ // snapshot removed below for inconsistency between local and CI tests.
+ // cy.svgSnapshot()
+ })
+})
diff --git a/data/4-svgedit-ui/scenario1.cy.js b/data/4-svgedit-ui/scenario1.cy.js
new file mode 100644
index 0000000..dcdc94c
--- /dev/null
+++ b/data/4-svgedit-ui/scenario1.cy.js
@@ -0,0 +1,60 @@
+import {
+ visitAndApproveStorage
+} from '../../support/ui-test-helper.js'
+
+describe('check tool shape and image of svg-edit', function () {
+ before(() => {
+ visitAndApproveStorage()
+ })
+
+ it('check tool_source_set', function () {
+ cy.get('#tool_source').click({ force: true })
+ cy.get('#svg_source_textarea')
+ .type('{selectall}', { force: true })
+ .type(``, { force: true, parseSpecialCharSequences: false })
+ cy.get('#tool_source_save').click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_shape', function () {
+ cy.get('#tool_shapelib').shadow().find('.overall').eq(0).click({ force: true })
+ cy.get('[data-shape="heart"]').click({ force: true })
+ cy.get('#svgroot')
+ .trigger('mousemove', { clientX: 400, clientY: 400, force: true })
+ .trigger('mousedown', { clientX: 400, clientY: 400, force: true })
+ .trigger('mousemove', { clientX: 20, clientY: 20, force: true })
+ .trigger('mouseup', { force: true })
+ cy.get('#selectorGrip_rotate')
+ .trigger('mousedown', { force: true })
+ .trigger('mousemove', { clientX: 20, clientY: 20, force: true })
+ .trigger('mouseup', { force: true })
+ // issue with snapshot not being consistent on CI/Interactive
+ // cy.svgSnapshot()
+ // so we use typical DOM tests to validate
+ cy.get('#svg_1').should('have.attr', 'd')
+
+ // cy.get('#a_text').should('have.attr', 'transform')
+ // .and('equal', 'matrix(1 0 0 4.54639 0 -540.825)') // Chrome 96 is matrix(1 0 0 4.17431 0 -325.367)
+ })
+ it('check tool_image', function () {
+ cy.get('#tool_image').click({ force: true })
+ cy.get('#svgroot')
+ .trigger('mousedown', { clientX: 100, clientY: 100, force: true })
+ .trigger('mousemove', { clientX: 120, clientY: 120, force: true })
+ .trigger('mouseup', { force: true })
+ // eslint-disable-next-line promise/catch-or-return
+ cy.window()
+ // eslint-disable-next-line promise/always-return
+ .then(($win) => {
+ cy.stub($win, 'prompt').returns('./images/logo.svg')
+ cy.contains('OK')
+ })
+ // issue with snapshot not being consistent on CI/Interactive
+ // cy.svgSnapshot()
+ // so we use typical DOM tests to validate
+ cy.get('#svg_2').should('have.attr', 'xlink:href').and('equal', './images/logo.svg')
+ })
+})
diff --git a/data/4-svgedit-ui/scenario2.cy.js b/data/4-svgedit-ui/scenario2.cy.js
new file mode 100644
index 0000000..78832de
--- /dev/null
+++ b/data/4-svgedit-ui/scenario2.cy.js
@@ -0,0 +1,120 @@
+import {
+ visitAndApproveStorage
+} from '../../support/ui-test-helper.js'
+
+describe('use ellipse and circle of svg-edit', function () {
+ before(() => {
+ visitAndApproveStorage()
+ })
+
+ it('check tool_source_set', function () {
+ cy.get('#tool_source').click({ force: true })
+ cy.get('#svg_source_textarea')
+ .type('{selectall}', { force: true })
+ .type(``, { force: true, parseSpecialCharSequences: false })
+ cy.get('#tool_source_save').click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_circle', function () {
+ cy.get('#tool_circle')
+ .click({ force: true })
+ cy.get('#svgcontent')
+ .trigger('mousedown', 200, 200, { force: true })
+ .trigger('mousemove', 300, 200, { force: true })
+ .trigger('mouseup', { force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_fhellipse', function () {
+ cy.get('#tool_fhellipse')
+ .click({ force: true })
+ cy.get('#svgcontent')
+ .trigger('mousedown', 400, 200, { force: true }).wait(100)
+ .trigger('mousemove', { force: true, pageX: 400, pageY: 200 }).wait(100)
+ .trigger('mousemove', { force: true, pageX: 400, pageY: 300 }).wait(100)
+ .trigger('mousemove', { force: true, pageX: 300, pageY: 400 }).wait(100)
+ .trigger('mousemove', { force: true, pageX: 200, pageY: 200 }).wait(100)
+ .trigger('mouseup', 200, 100, { force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_ellipse', function () {
+ cy.get('#tool_ellipse').click({ force: true })
+ cy.get('#svgcontent')
+ .trigger('mousedown', 100, 300, { force: true })
+ .trigger('mousemove', 200, 200, { force: true })
+ .trigger('mouseup', { force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_circle_change_fill_color', function () {
+ cy.get('#svg_2').click({ force: true })
+ cy.get('#js-se-palette').find('.square').eq(8)
+ .click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_circle_change_opacity', function () {
+ cy.get('#svg_2').click({ force: true })
+ for (let n = 0; n < 10; n++) {
+ cy.get('#opacity').shadow().find('elix-number-spin-box').eq(0).shadow().find('#downButton').eq(0)
+ .click({ force: true })
+ }
+ cy.svgSnapshot()
+ })
+ it('check tool_ellipse_change_rotation', function () {
+ cy.get('#svg_3').click({ force: true })
+ for (let n = 0; n < 5; n++) {
+ cy.get('#angle').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ }
+ cy.svgSnapshot()
+ })
+ it('check tool_ellipse_change_blur', function () {
+ cy.get('#svg_3').click({ force: true })
+ for (let n = 0; n < 10; n++) {
+ cy.get('#blur').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ }
+ cy.svgSnapshot()
+ })
+ it('check tool_ellipse_change_cx_cy_coordinate', function () {
+ cy.get('#svg_3').click({ force: true })
+ for (let n = 0; n < 20; n++) {
+ cy.get('#ellipse_cx').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ }
+ for (let n = 0; n < 20; n++) {
+ cy.get('#ellipse_cy').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ }
+ cy.svgSnapshot()
+ })
+ it('check tool_ellipse_change_rx_ry_radius', function () {
+ cy.get('#svg_3').click({ force: true })
+ for (let n = 0; n < 20; n++) {
+ cy.get('#ellipse_rx').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ }
+ for (let n = 0; n < 20; n++) {
+ cy.get('#ellipse_ry').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ }
+ cy.svgSnapshot()
+ })
+ it('check tool_ellipse_bring_to_back', function () {
+ cy.get('#svg_2').click({ force: true })
+ cy.get('#tool_move_bottom').click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_ellipse_bring_to_front', function () {
+ cy.get('#svg_2').click({ force: true })
+ cy.get('#tool_move_top').click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_ellipse_clone', function () {
+ cy.get('#svg_2').click({ force: true })
+ cy.get('#tool_clone').click({ force: true })
+ cy.svgSnapshot()
+ })
+})
diff --git a/data/4-svgedit-ui/scenario3.cy.js b/data/4-svgedit-ui/scenario3.cy.js
new file mode 100644
index 0000000..8923007
--- /dev/null
+++ b/data/4-svgedit-ui/scenario3.cy.js
@@ -0,0 +1,96 @@
+import {
+ visitAndApproveStorage
+} from '../../support/ui-test-helper.js'
+
+describe('use path tools of svg-edit', function () {
+ before(() => {
+ visitAndApproveStorage()
+ })
+
+ it('check tool_source_set', function () {
+ cy.get('#tool_source').click({ force: true })
+ cy.get('#svg_source_textarea')
+ .type('{selectall}', { force: true })
+ .type(``, { force: true, parseSpecialCharSequences: false })
+ cy.get('#tool_source_save').click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_path', function () {
+ cy.get('#tool_path')
+ .click({ force: true })
+ cy.get('#svgcontent')
+ .trigger('mousedown', 50, 50, { force: true })
+ .trigger('mouseup', { force: true })
+ .trigger('mousemove', 100, 50, { force: true })
+ .trigger('mousedown', 100, 50, { force: true })
+ .trigger('mouseup', { force: true })
+ .trigger('mousemove', 75, 150, { force: true })
+ .trigger('mousedown', 75, 150, { force: true })
+ .trigger('mouseup', { force: true })
+ .trigger('mousemove', 0, 0, { force: true })
+ .trigger('mousedown', 0, 0, { force: true })
+ .trigger('mouseup', { force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_path_change_node_xy', function () {
+ cy.get('#svg_1').click({ force: true })
+ cy.get('#svg_1').dblclick({ force: true })
+ for (let n = 0; n < 25; n++) {
+ cy.get('#path_node_x').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ }
+ for (let n = 0; n < 25; n++) {
+ cy.get('#path_node_y').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ }
+ cy.svgSnapshot()
+ })
+ it('check tool_path_change_seg_type', function () {
+ // cy.get('#svg_1').click({ force: true })
+ cy.get('#svg_1').dblclick({ force: true })
+ cy.get('#seg_type').shadow().find('select').select('6', { force: true }).should('have.value', '6')
+ cy.get('#ctrlpointgrip_3c1')
+ .trigger('mousedown', { force: true })
+ .trigger('mousemove', 130, 175, { force: true })
+ .trigger('mouseup', { force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_path_change_clone_node', function () {
+ // cy.get('#svg_1').click({ force: true })
+ cy.get('#svg_1').dblclick({ force: true })
+ cy.get('#tool_node_clone').click({ force: true })
+ cy.get('#pathpointgrip_4')
+ .trigger('mousedown', { force: true })
+ .trigger('mousemove', 130, 175, { force: true })
+ .trigger('mouseup', { force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_path_openclose', function () {
+ cy.get('#tool_select').click({ force: true })
+ cy.get('#svg_1').click({ force: true })
+ cy.get('#svg_1').dblclick({ force: true })
+ cy.get('#tool_openclose_path').click({ force: true })
+ cy.svgSnapshot()
+ })
+ /* it('check tool_path_add_subpath', function () {
+ cy.get('#tool_add_subpath').click({ force: true });
+ cy.get('#svgcontent')
+ .trigger('mousedown', 0, 0, { force: true })
+ .trigger('mouseup', { force: true })
+ .trigger('mousemove', 100, 50, { force: true })
+ .trigger('mousedown', 100, 50, { force: true })
+ .trigger('mouseup', { force: true })
+ .trigger('mousemove', 75, 150, { force: true })
+ .trigger('mousedown', 75, 150, { force: true })
+ .trigger('mouseup', { force: true })
+ .trigger('mousemove', 0, 0, { force: true })
+ .trigger('mousedown', 0, 0, { force: true })
+ .trigger('mouseup', { force: true });
+ cy.get('#tool_select').click({ force: true });
+ cy.svgSnapshot();
+ }); */
+})
diff --git a/data/4-svgedit-ui/scenario4.cy.js b/data/4-svgedit-ui/scenario4.cy.js
new file mode 100644
index 0000000..357bb7e
--- /dev/null
+++ b/data/4-svgedit-ui/scenario4.cy.js
@@ -0,0 +1,160 @@
+import {
+ visitAndApproveStorage
+} from '../../support/ui-test-helper.js'
+
+describe('use rect/square tools of svg-edit', function () {
+ before(() => {
+ visitAndApproveStorage()
+ })
+
+ it('check tool_source_set', function () {
+ cy.get('#tool_source').click({ force: true })
+ cy.get('#svg_source_textarea')
+ .type('{selectall}', { force: true })
+ .type(``, { force: true, parseSpecialCharSequences: false })
+ cy.get('#tool_source_save').click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_rect', function () {
+ cy.get('#tool_rect')
+ .click({ force: true })
+ cy.get('#svgcontent')
+ .trigger('mousedown', 150, 150, { force: true })
+ .trigger('mousemove', 250, 200, { force: true })
+ .trigger('mouseup', { force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_fhrect', function () {
+ cy.get('#tool_fhrect')
+ .click({ force: true })
+ cy.get('#svgcontent')
+ .trigger('mousedown', 200, 80, { force: true })
+ .trigger('mousemove', 320, 80, { force: true })
+ .trigger('mousemove', 320, 180, { force: true })
+ .trigger('mousemove', 200, 180, { force: true })
+ .trigger('mousemove', 200, 80, { force: true })
+ .trigger('mouseup', 200, 80, { force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_square', function () {
+ cy.get('#tool_square').click({ force: true })
+ cy.get('#svgcontent')
+ .trigger('mousedown', 75, 150, { force: true })
+ .trigger('mousemove', 125, 200, { force: true })
+ .trigger('mouseup', { force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_rect_change_fill_color', function () {
+ cy.get('#svg_1').click({ force: true })
+ cy.get('#js-se-palette').find('.square').eq(8)
+ .click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_rect_change_rotation', function () {
+ cy.get('#svg_1').click({ force: true })
+ for (let n = 0; n < 5; n++) {
+ cy.get('#angle').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ }
+ cy.svgSnapshot()
+ })
+ it('check tool_rect_change_blur', function () {
+ cy.get('#svg_1').click({ force: true })
+ for (let n = 0; n < 10; n++) {
+ cy.get('#blur').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ }
+ cy.svgSnapshot()
+ })
+ it('check tool_rect_change_opacity', function () {
+ cy.get('#svg_1').click({ force: true })
+ for (let n = 0; n < 10; n++) {
+ cy.get('#opacity').shadow().find('elix-number-spin-box').eq(0).shadow().find('#downButton').eq(0)
+ .click({ force: true })
+ }
+ cy.svgSnapshot()
+ })
+ it('check tool_fhrect_change_x_y_coordinate', function () {
+ cy.get('#svg_2').click({ force: true })
+ for (let n = 0; n < 25; n++) {
+ cy.get('#selected_x').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ }
+ for (let n = 0; n < 25; n++) {
+ cy.get('#selected_y').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ }
+ cy.svgSnapshot()
+ })
+ it('check tool_fhrect_change_width_height', function () {
+ cy.get('#svg_2').click({ force: true })
+ for (let n = 0; n < 25; n++) {
+ cy.get('#rect_width').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ }
+ for (let n = 0; n < 25; n++) {
+ cy.get('#rect_height').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ }
+ cy.svgSnapshot()
+ })
+ it('check tool_square_clone', function () {
+ cy.get('#svg_3').click({ force: true })
+ cy.get('#tool_clone').click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_square_bring_to_back', function () {
+ cy.get('#svg_3').click({ force: true })
+ cy.get('#tool_move_bottom').click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_square_bring_to_front', function () {
+ cy.get('#svg_3').click({ force: true })
+ cy.get('#tool_move_top').click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_square_change_corner_radius', function () {
+ cy.get('#svg_4').click({ force: true })
+ for (let n = 0; n < 25; n++) {
+ cy.get('#rect_rx').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ }
+ cy.svgSnapshot()
+ })
+ it('check tool_rect_change_to_path', function () {
+ cy.get('#svg_2').click({ force: true })
+ cy.get('#tool_topath').click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_rect_delete', function () {
+ cy.get('#svg_1').click({ force: true })
+ cy.get('#tool_delete').click({ force: true })
+ cy.get('#svg_3').click({ force: true })
+ cy.get('#tool_delete').click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_rect_change_class', function () {
+ cy.get('#svg_2').click({ force: true })
+ cy.get('#elem_class').shadow().find('elix-input').eq(0).shadow().find('#inner').eq(0)
+ .type('svg_2_class{enter}', { force: true })
+ cy.get('#svg_2')
+ .should('satisfy', ($el) => {
+ const classList = Array.from($el[0].classList)
+ return classList.includes('svg_2_class')
+ })
+ })
+ it('check tool_rect_change_id', function () {
+ cy.get('#svg_2').click({ force: true }).click({ force: true })
+ cy.get('#elem_id').shadow().find('elix-input').eq(0).shadow().find('#inner').eq(0)
+ .type('_id{enter}', { force: true })
+ cy.get('#svg_2_id')
+ .should('satisfy', ($el) => {
+ const classList = Array.from($el[0].classList)
+ return classList.includes('svg_2_class')
+ })
+ })
+})
diff --git a/data/4-svgedit-ui/scenario5.cy.js b/data/4-svgedit-ui/scenario5.cy.js
new file mode 100644
index 0000000..ff39df0
--- /dev/null
+++ b/data/4-svgedit-ui/scenario5.cy.js
@@ -0,0 +1,149 @@
+import {
+ visitAndApproveStorage
+} from '../../support/ui-test-helper.js'
+
+describe('use line tools of svg-edit', function () {
+ before(() => {
+ visitAndApproveStorage()
+ })
+
+ it('check tool_source_set', function () {
+ cy.get('#tool_source').click({ force: true })
+ cy.get('#svg_source_textarea')
+ .type('{selectall}', { force: true })
+ .type(``, { force: true, parseSpecialCharSequences: false })
+ cy.get('#tool_source_save').click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_line', function () {
+ cy.get('#tool_line')
+ .click({ force: true })
+ cy.get('#svgcontent')
+ .trigger('mousemove', 200, 200, { force: true })
+ .trigger('mousedown', 200, 200, { force: true })
+ .trigger('mousemove', 250, 250, { force: true })
+ .trigger('mouseup', { force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_line_change_class', function () {
+ cy.get('#svg_1').click({ force: true })
+ cy.get('#elem_class').shadow().find('elix-input').eq(0).shadow().find('#inner').eq(0)
+ .type('svg_1_class{enter}', { force: true })
+ cy.get('#svg_1')
+ .should('satisfy', ($el) => {
+ const classList = Array.from($el[0].classList)
+ return classList.includes('svg_1_class')
+ })
+ })
+ it('check tool_line_change_id', function () {
+ cy.get('#svg_1').click({ force: true }).click({ force: true })
+ cy.get('#elem_id').shadow().find('elix-input').eq(0).shadow().find('#inner').eq(0)
+ .type('_id{enter}', { force: true })
+ cy.get('#svg_1_id')
+ .should('satisfy', ($el) => {
+ const classList = Array.from($el[0].classList)
+ return classList.includes('svg_1_class')
+ })
+ })
+ it('check tool_line_change_rotation', function () {
+ cy.get('#svg_1_id').click({ force: true })
+ for (let n = 0; n < 5; n++) {
+ cy.get('#angle').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ }
+ cy.svgSnapshot()
+ })
+ it('check tool_line_change_blur', function () {
+ cy.get('#svg_1_id').click({ force: true })
+ for (let n = 0; n < 10; n++) {
+ cy.get('#blur').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ }
+ cy.svgSnapshot()
+ })
+ it('check tool_line_change_opacity', function () {
+ cy.get('#svg_1_id').click({ force: true })
+ for (let n = 0; n < 10; n++) {
+ cy.get('#opacity').shadow().find('elix-number-spin-box').eq(0).shadow().find('#downButton').eq(0)
+ .click({ force: true })
+ }
+ cy.svgSnapshot()
+ })
+ it('check tool_line_delete', function () {
+ cy.get('#svg_1_id').click({ force: true })
+ cy.get('#tool_delete').click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_line_clone', function () {
+ cy.get('#tool_line')
+ .click({ force: true })
+ cy.get('#svgcontent')
+ .trigger('mousemove', 200, 200, { force: true })
+ .trigger('mousedown', 200, 200, { force: true })
+ .trigger('mousemove', 250, 250, { force: true })
+ .trigger('mouseup', { force: true })
+ cy.get('#svg_2').click({ force: true })
+ cy.get('#tool_clone').click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_line_bring_to_back', function () {
+ cy.get('#svg_2').click({ force: true })
+ cy.get('#tool_move_bottom').click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_line_bring_to_front', function () {
+ cy.get('#svg_2').click({ force: true })
+ cy.get('#tool_move_top').click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_line_change_x_y_coordinate', function () {
+ cy.get('#svg_2').click({ force: true })
+ for (let n = 0; n < 25; n++) {
+ cy.get('#line_x1').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ }
+ for (let n = 0; n < 25; n++) {
+ cy.get('#line_y1').shadow().find('elix-number-spin-box').eq(0).shadow().find('#downButton').eq(0)
+ .click({ force: true })
+ }
+ for (let n = 0; n < 25; n++) {
+ cy.get('#line_x2').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ }
+ for (let n = 0; n < 25; n++) {
+ cy.get('#line_y2').shadow().find('elix-number-spin-box').eq(0).shadow().find('#downButton').eq(0)
+ .click({ force: true })
+ }
+ cy.svgSnapshot()
+ })
+ it('check tool_line_change_stroke_width', function () {
+ cy.get('#svg_2').click({ force: true })
+ for (let n = 0; n < 10; n++) {
+ cy.get('#stroke_width').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ }
+ cy.svgSnapshot()
+ })
+ it('check tool_line_change_stoke_color', function () {
+ cy.get('#svg_3').click({ force: true })
+ cy.get('#stroke_color').shadow().find('#picker').eq(0).click({ force: true })
+ cy.get('#stroke_color').shadow().find('#color_picker').eq(0)
+ .find('#jGraduate_colPick').eq(0).find('#jPicker-table').eq(0)
+ .find('.QuickColor').eq(9).click({ force: true })
+ cy.get('#stroke_color').shadow().find('#color_picker').eq(0)
+ .find('#jGraduate_colPick').eq(0).find('#jPicker-table').eq(0)
+ .find('#Ok').eq(0).click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_line_align_to_page', function () {
+ cy.get('#svg_3').click({ force: true })
+ cy.get('#tool_position').shadow().find('#select-container').eq(0).click({ force: true })
+ cy.get('#tool_position').find('se-list-item').eq(2).shadow().find('[aria-label="option"]').eq(0)
+ .click({ force: true })
+ cy.svgSnapshot()
+ })
+})
diff --git a/data/4-svgedit-ui/scenario6.cy.js b/data/4-svgedit-ui/scenario6.cy.js
new file mode 100644
index 0000000..dbcd9a6
--- /dev/null
+++ b/data/4-svgedit-ui/scenario6.cy.js
@@ -0,0 +1,146 @@
+import {
+ visitAndApproveStorage
+} from '../../support/ui-test-helper.js'
+
+describe('use polygon tools of svg-edit', function () {
+ before(() => {
+ visitAndApproveStorage()
+ })
+
+ it('check tool_source_set', function () {
+ cy.get('#tool_source').click({ force: true })
+ cy.get('#svg_source_textarea')
+ .type('{selectall}', { force: true })
+ .type(``, { force: true, parseSpecialCharSequences: false })
+ cy.get('#tool_source_save').click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_polygon', function () {
+ cy.get('#tool_polygon')
+ .click({ force: true })
+ cy.get('#svgcontent')
+ .trigger('mousedown', 325, 250, { force: true })
+ .trigger('mousemove', 325, 345, { force: true })
+ .trigger('mouseup', { force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_polygon_clone', function () {
+ cy.get('#svg_1').click({ force: true })
+ cy.get('#tool_clone').click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_polygon_change_class', function () {
+ cy.get('#svg_2').click({ force: true })
+ cy.get('#elem_class').shadow().find('elix-input').eq(0).shadow().find('#inner').eq(0)
+ .type('svg_2_class{enter}', { force: true })
+ cy.get('#svg_2')
+ .should('satisfy', ($el) => {
+ const classList = Array.from($el[0].classList)
+ return classList.includes('svg_2_class')
+ })
+ })
+ it('check tool_polygon_change_id', function () {
+ cy.get('#svg_2').click({ force: true }).click({ force: true })
+ cy.get('#elem_id').shadow().find('elix-input').eq(0).shadow().find('#inner').eq(0)
+ .type('_id{enter}', { force: true })
+ cy.get('#svg_2_id')
+ .should('satisfy', ($el) => {
+ const classList = Array.from($el[0].classList)
+ return classList.includes('svg_2_class')
+ })
+ })
+ it('check tool_polygon_change_rotation', function () {
+ cy.get('#svg_2_id').click({ force: true })
+ for (let n = 0; n < 5; n++) {
+ cy.get('#angle').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ }
+ cy.svgSnapshot()
+ })
+ it('check tool_polygon_change_blur', function () {
+ cy.get('#svg_2_id').click({ force: true })
+ for (let n = 0; n < 10; n++) {
+ cy.get('#blur').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ }
+ cy.svgSnapshot()
+ })
+ it('check tool_polygon_change_opacity', function () {
+ cy.get('#svg_2_id').click({ force: true })
+ for (let n = 0; n < 10; n++) {
+ cy.get('#opacity').shadow().find('elix-number-spin-box').eq(0).shadow().find('#downButton').eq(0)
+ .click({ force: true })
+ }
+ cy.svgSnapshot()
+ })
+ it('check tool_polygon_bring_to_back', function () {
+ cy.get('#svg_2_id').click({ force: true })
+ cy.get('#tool_move_bottom').click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_polygon_bring_to_front', function () {
+ cy.get('#svg_2_id').click({ force: true })
+ cy.get('#tool_move_top').click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_polygon_delete', function () {
+ cy.get('#svg_2_id').click({ force: true })
+ cy.get('#tool_delete').click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_polygon_align_to_page', function () {
+ cy.get('#svg_1').click({ force: true })
+ cy.get('#tool_position').shadow().find('#select-container').eq(0).click({ force: true })
+ cy.get('#tool_position').find('se-list-item').eq(2).shadow().find('[aria-label="option"]').eq(0)
+ .click({ force: true })
+ cy.svgSnapshot()
+ })
+ /* it('check tool_polygon_change_x_y_coordinate', function () {
+ cy.get('#svg_1').click({ force: true });
+ for(let n = 0; n < 25; n ++){
+ cy.get('#selected_x').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true });
+ }
+ for(let n = 0; n < 25; n ++){
+ cy.get('#selected_y').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true });
+ }
+ cy.svgSnapshot();
+ }); */
+ it('check tool_polygon_change_stroke_width', function () {
+ cy.get('#svg_1').click({ force: true })
+ for (let n = 0; n < 10; n++) {
+ cy.get('#stroke_width').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ }
+ cy.svgSnapshot()
+ })
+ it('check tool_polygon_change_stoke_fill_color', function () {
+ cy.get('#svg_1').click({ force: true })
+ cy.get('#stroke_color').shadow().find('#picker').eq(0).click({ force: true })
+ cy.get('#stroke_color').shadow().find('#color_picker').eq(0)
+ .find('#jGraduate_colPick').eq(0).find('#jPicker-table').eq(0)
+ .find('.QuickColor').eq(51).click({ force: true })
+ cy.get('#stroke_color').shadow().find('#color_picker').eq(0)
+ .find('#jGraduate_colPick').eq(0).find('#jPicker-table').eq(0)
+ .find('#Ok').eq(0).click({ force: true })
+ cy.get('#fill_color').shadow().find('#picker').eq(0).click({ force: true })
+ cy.get('#fill_color').shadow().find('#color_picker').eq(0)
+ .find('#jGraduate_colPick').eq(0).find('#jPicker-table').eq(0)
+ .find('.QuickColor').eq(3).click({ force: true })
+ cy.get('#fill_color').shadow().find('#color_picker').eq(0)
+ .find('#jGraduate_colPick').eq(0).find('#jPicker-table').eq(0)
+ .find('#Ok').eq(0).click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_polygon_change_sides', function () {
+ cy.get('#svg_1').click({ force: true })
+ cy.get('#polySides').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ cy.svgSnapshot()
+ })
+})
diff --git a/data/4-svgedit-ui/scenario7.cy.js b/data/4-svgedit-ui/scenario7.cy.js
new file mode 100644
index 0000000..a7f982a
--- /dev/null
+++ b/data/4-svgedit-ui/scenario7.cy.js
@@ -0,0 +1,134 @@
+import {
+ visitAndApproveStorage
+} from '../../support/ui-test-helper.js'
+
+describe('use star tools of svg-edit', function () {
+ before(() => {
+ visitAndApproveStorage()
+ })
+
+ it('check tool_source_set', function () {
+ cy.get('#tool_source').click({ force: true })
+ cy.get('#svg_source_textarea')
+ .type('{selectall}', { force: true })
+ .type(``, { force: true, parseSpecialCharSequences: false })
+ cy.get('#tool_source_save').click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_star', function () {
+ cy.get('#tool_star')
+ .click({ force: true })
+ cy.get('#svgcontent')
+ .trigger('mousedown', 300, 150, { force: true })
+ .trigger('mousemove', 300, 250, { force: true })
+ .trigger('mouseup', { force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_star_clone', function () {
+ cy.get('#svg_1').click({ force: true })
+ cy.get('#tool_clone').click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_star_change_class', function () {
+ cy.get('#svg_2').click({ force: true })
+ cy.get('#elem_class').shadow().find('elix-input').eq(0).shadow().find('#inner').eq(0)
+ .type('svg_2_class{enter}', { force: true })
+ cy.get('#svg_2')
+ .should('satisfy', ($el) => {
+ const classList = Array.from($el[0].classList)
+ return classList.includes('svg_2_class')
+ })
+ })
+ it('check tool_star_change_id', function () {
+ cy.get('#svg_2').click({ force: true }).click({ force: true })
+ cy.get('#elem_id').shadow().find('elix-input').eq(0).shadow().find('#inner').eq(0)
+ .type('_id{enter}', { force: true })
+ cy.get('#svg_2_id')
+ .should('satisfy', ($el) => {
+ const classList = Array.from($el[0].classList)
+ return classList.includes('svg_2_class')
+ })
+ })
+ it('check tool_star_change_rotation', function () {
+ cy.get('#svg_2_id').click({ force: true })
+ for (let n = 0; n < 5; n++) {
+ cy.get('#angle').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ }
+ cy.svgSnapshot()
+ })
+ it('check tool_star_change_blur', function () {
+ cy.get('#svg_2_id').click({ force: true })
+ for (let n = 0; n < 10; n++) {
+ cy.get('#blur').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ }
+ cy.svgSnapshot()
+ })
+ it('check tool_star_change_opacity', function () {
+ cy.get('#svg_2_id').click({ force: true })
+ for (let n = 0; n < 10; n++) {
+ cy.get('#opacity').shadow().find('elix-number-spin-box').eq(0).shadow().find('#downButton').eq(0)
+ .click({ force: true })
+ }
+ cy.svgSnapshot()
+ })
+ it('check tool_star_bring_to_back', function () {
+ cy.get('#svg_2_id').click({ force: true })
+ cy.get('#tool_move_bottom').click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_star_bring_to_front', function () {
+ cy.get('#svg_2_id').click({ force: true })
+ cy.get('#tool_move_top').click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_star_delete', function () {
+ cy.get('#svg_2_id').click({ force: true })
+ cy.get('#tool_delete').click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_star_align_to_page', function () {
+ cy.get('#svg_1').click({ force: true })
+ cy.get('#tool_position').shadow().find('#select-container').eq(0).click({ force: true })
+ cy.get('#tool_position').find('se-list-item').eq(2).shadow().find('[aria-label="option"]').eq(0)
+ .click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_star_change_stroke_width', function () {
+ cy.get('#svg_1').click({ force: true })
+ for (let n = 0; n < 10; n++) {
+ cy.get('#stroke_width').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ }
+ cy.svgSnapshot()
+ })
+ it('check tool_star_change_stoke_fill_color', function () {
+ cy.get('#svg_1').click({ force: true })
+ cy.get('#stroke_color').shadow().find('#picker').eq(0).click({ force: true })
+ cy.get('#stroke_color').shadow().find('#color_picker').eq(0)
+ .find('#jGraduate_colPick').eq(0).find('#jPicker-table').eq(0)
+ .find('.QuickColor').eq(51).click({ force: true })
+ cy.get('#stroke_color').shadow().find('#color_picker').eq(0)
+ .find('#jGraduate_colPick').eq(0).find('#jPicker-table').eq(0)
+ .find('#Ok').eq(0).click({ force: true })
+ cy.get('#fill_color').shadow().find('#picker').eq(0).click({ force: true })
+ cy.get('#fill_color').shadow().find('#color_picker').eq(0)
+ .find('#jGraduate_colPick').eq(0).find('#jPicker-table').eq(0)
+ .find('.QuickColor').eq(3).click({ force: true })
+ cy.get('#fill_color').shadow().find('#color_picker').eq(0)
+ .find('#jGraduate_colPick').eq(0).find('#jPicker-table').eq(0)
+ .find('#Ok').eq(0).click({ force: true })
+ cy.svgSnapshot()
+ })
+ it('check tool_star_change_sides', function () {
+ cy.get('#svg_1').click({ force: true })
+ cy.get('#starNumPoints').shadow().find('elix-number-spin-box').eq(0).shadow().find('#upButton').eq(0)
+ .click({ force: true })
+ cy.svgSnapshot()
+ })
+})
diff --git a/data/4-svgedit-ui/tool-selection.cy.js b/data/4-svgedit-ui/tool-selection.cy.js
new file mode 100644
index 0000000..98510bf
--- /dev/null
+++ b/data/4-svgedit-ui/tool-selection.cy.js
@@ -0,0 +1,17 @@
+import {
+ visitAndApproveStorage
+} from '../../support/ui-test-helper.js'
+
+describe('UI - Tool selection', function () {
+ beforeEach(() => {
+ visitAndApproveStorage()
+ })
+
+ it('should set rectangle selection by click', function () {
+ cy.get('#tools_rect')
+ .should('not.have.attr', 'pressed')
+ cy.get('#tools_rect')
+ .trigger('click', { force: true })
+ .should('have.attr', 'pressed')
+ })
+})
diff --git a/data/5-svgedit-unit/browser-bugs/removeItem-setAttribute.cy.js b/data/5-svgedit-unit/browser-bugs/removeItem-setAttribute.cy.js
new file mode 100644
index 0000000..0a81639
--- /dev/null
+++ b/data/5-svgedit-unit/browser-bugs/removeItem-setAttribute.cy.js
@@ -0,0 +1,10 @@
+describe('Browser bugs', function () {
+ it('removeItem and setAttribute test (Chromium 843901; now fixed)', function () {
+ // See https://bugs.chromium.org/p/chromium/issues/detail?id=843901
+ const elem = document.createElementNS('http://www.w3.org/2000/svg', 'rect')
+ elem.setAttribute('transform', 'matrix(1,0,0,1,0,0)')
+ elem.transform.baseVal.removeItem(0)
+ elem.removeAttribute('transform')
+ assert.equal(elem.hasAttribute('transform'), false)
+ })
+})
diff --git a/data/5-svgedit-unit/contextmenu.cy.js b/data/5-svgedit-unit/contextmenu.cy.js
new file mode 100644
index 0000000..056d6d7
--- /dev/null
+++ b/data/5-svgedit-unit/contextmenu.cy.js
@@ -0,0 +1,58 @@
+import * as contextmenu from '../../../src/editor/contextmenu.js'
+
+describe('contextmenu', function () {
+ /**
+ * Tear down tests, resetting custom menus.
+ * @returns {void}
+ */
+ afterEach(() => {
+ contextmenu.resetCustomMenus()
+ })
+
+ it('Test svgedit.contextmenu package', function () {
+ assert.ok(contextmenu, 'contextmenu registered correctly')
+ assert.ok(contextmenu.add, 'add registered correctly')
+ assert.ok(contextmenu.hasCustomHandler, 'contextmenu hasCustomHandler registered correctly')
+ assert.ok(contextmenu.getCustomHandler, 'contextmenu getCustomHandler registered correctly')
+ })
+
+ it('Test svgedit.contextmenu does not add invalid menu item', function () {
+ assert.throws(
+ () => contextmenu.add({ id: 'justanid' }),
+ null, null,
+ 'menu item with just an id is invalid'
+ )
+
+ assert.throws(
+ () => contextmenu.add({ id: 'idandlabel', label: 'anicelabel' }),
+ null, null,
+ 'menu item with just an id and label is invalid'
+ )
+
+ assert.throws(
+ () => contextmenu.add({ id: 'idandlabel', label: 'anicelabel', action: 'notafunction' }),
+ null, null,
+ 'menu item with action that is not a function is invalid'
+ )
+ })
+
+ it('Test svgedit.contextmenu adds valid menu item', function () {
+ const validItem = { id: 'valid', label: 'anicelabel', action () { /* empty fn */ } }
+ contextmenu.add(validItem)
+
+ assert.ok(contextmenu.hasCustomHandler('valid'), 'Valid menu item is added.')
+ assert.equal(contextmenu.getCustomHandler('valid'), validItem.action, 'Valid menu action is added.')
+ })
+
+ it('Test svgedit.contextmenu rejects valid duplicate menu item id', function () {
+ const validItem1 = { id: 'valid', label: 'anicelabel', action () { /* empty fn */ } }
+ const validItem2 = { id: 'valid', label: 'anicelabel', action () { /* empty fn */ } }
+ contextmenu.add(validItem1)
+
+ assert.throws(
+ () => contextmenu.add(validItem2),
+ null, null,
+ 'duplicate menu item is rejected.'
+ )
+ })
+})
diff --git a/data/5-svgedit-unit/coords.cy.js b/data/5-svgedit-unit/coords.cy.js
new file mode 100644
index 0000000..2a8f7fc
--- /dev/null
+++ b/data/5-svgedit-unit/coords.cy.js
@@ -0,0 +1,307 @@
+import { NS } from '../../../packages/svgcanvas/core/namespaces.js'
+import * as utilities from '../../../packages/svgcanvas/core/utilities.js'
+import * as coords from '../../../packages/svgcanvas/core/coords.js'
+
+describe('coords', function () {
+ let elemId = 1
+
+ const root = document.createElement('div')
+ root.id = 'root'
+ root.style.visibility = 'hidden'
+ document.body.append(root)
+
+ /**
+ * Set up tests with mock data.
+ * @returns {void}
+ */
+ beforeEach(function () {
+ const svgroot = document.createElementNS(NS.SVG, 'svg')
+ svgroot.id = 'svgroot'
+ root.append(svgroot)
+ this.svg = document.createElementNS(NS.SVG, 'svg')
+ svgroot.append(this.svg)
+
+ // Mock out editor context.
+ utilities.init(
+ /**
+ * @implements {module:utilities.EditorContext}
+ */
+ {
+ getSvgRoot: () => { return this.svg },
+ getDOMDocument () { return null },
+ getDOMContainer () { return null }
+ }
+ )
+ coords.init(
+ /**
+ * @implements {module:coords.EditorContext}
+ */
+ {
+ getGridSnapping () { return false },
+ getDrawing () {
+ return {
+ getNextId () { return String(elemId++) }
+ }
+ }
+ }
+ )
+ })
+
+ /**
+ * Tear down tests, removing elements.
+ * @returns {void}
+ */
+ afterEach(function () {
+ while (this.svg.hasChildNodes()) {
+ this.svg.firstChild.remove()
+ }
+ })
+
+ it('Test remapElement(translate) for rect', function () {
+ const rect = document.createElementNS(NS.SVG, 'rect')
+ rect.setAttribute('x', '200')
+ rect.setAttribute('y', '150')
+ rect.setAttribute('width', '250')
+ rect.setAttribute('height', '120')
+ this.svg.append(rect)
+
+ const attrs = {
+ x: '200',
+ y: '150',
+ width: '125',
+ height: '75'
+ }
+
+ // Create a translate.
+ const m = this.svg.createSVGMatrix()
+ m.a = 1; m.b = 0
+ m.c = 0; m.d = 1
+ m.e = 100; m.f = -50
+
+ coords.remapElement(rect, attrs, m)
+
+ assert.equal(rect.getAttribute('x'), '300')
+ assert.equal(rect.getAttribute('y'), '100')
+ assert.equal(rect.getAttribute('width'), '125')
+ assert.equal(rect.getAttribute('height'), '75')
+ })
+
+ it('Test remapElement(scale) for rect', function () {
+ const rect = document.createElementNS(NS.SVG, 'rect')
+ rect.setAttribute('width', '250')
+ rect.setAttribute('height', '120')
+ this.svg.append(rect)
+
+ const attrs = {
+ x: '0',
+ y: '0',
+ width: '250',
+ height: '120'
+ }
+
+ // Create a translate.
+ const m = this.svg.createSVGMatrix()
+ m.a = 2; m.b = 0
+ m.c = 0; m.d = 0.5
+ m.e = 0; m.f = 0
+
+ coords.remapElement(rect, attrs, m)
+
+ assert.equal(rect.getAttribute('x'), '0')
+ assert.equal(rect.getAttribute('y'), '0')
+ assert.equal(rect.getAttribute('width'), '500')
+ assert.equal(rect.getAttribute('height'), '60')
+ })
+
+ it('Test remapElement(translate) for circle', function () {
+ const circle = document.createElementNS(NS.SVG, 'circle')
+ circle.setAttribute('cx', '200')
+ circle.setAttribute('cy', '150')
+ circle.setAttribute('r', '125')
+ this.svg.append(circle)
+
+ const attrs = {
+ cx: '200',
+ cy: '150',
+ r: '125'
+ }
+
+ // Create a translate.
+ const m = this.svg.createSVGMatrix()
+ m.a = 1; m.b = 0
+ m.c = 0; m.d = 1
+ m.e = 100; m.f = -50
+
+ coords.remapElement(circle, attrs, m)
+
+ assert.equal(circle.getAttribute('cx'), '300')
+ assert.equal(circle.getAttribute('cy'), '100')
+ assert.equal(circle.getAttribute('r'), '125')
+ })
+
+ it('Test remapElement(scale) for circle', function () {
+ const circle = document.createElementNS(NS.SVG, 'circle')
+ circle.setAttribute('cx', '200')
+ circle.setAttribute('cy', '150')
+ circle.setAttribute('r', '250')
+ this.svg.append(circle)
+
+ const attrs = {
+ cx: '200',
+ cy: '150',
+ r: '250'
+ }
+
+ // Create a translate.
+ const m = this.svg.createSVGMatrix()
+ m.a = 2; m.b = 0
+ m.c = 0; m.d = 0.5
+ m.e = 0; m.f = 0
+
+ coords.remapElement(circle, attrs, m)
+
+ assert.equal(circle.getAttribute('cx'), '400')
+ assert.equal(circle.getAttribute('cy'), '75')
+ // Radius is the minimum that fits in the new bounding box.
+ assert.equal(circle.getAttribute('r'), '125')
+ })
+
+ it('Test remapElement(translate) for ellipse', function () {
+ const ellipse = document.createElementNS(NS.SVG, 'ellipse')
+ ellipse.setAttribute('cx', '200')
+ ellipse.setAttribute('cy', '150')
+ ellipse.setAttribute('rx', '125')
+ ellipse.setAttribute('ry', '75')
+ this.svg.append(ellipse)
+
+ const attrs = {
+ cx: '200',
+ cy: '150',
+ rx: '125',
+ ry: '75'
+ }
+
+ // Create a translate.
+ const m = this.svg.createSVGMatrix()
+ m.a = 1; m.b = 0
+ m.c = 0; m.d = 1
+ m.e = 100; m.f = -50
+
+ coords.remapElement(ellipse, attrs, m)
+
+ assert.equal(ellipse.getAttribute('cx'), '300')
+ assert.equal(ellipse.getAttribute('cy'), '100')
+ assert.equal(ellipse.getAttribute('rx'), '125')
+ assert.equal(ellipse.getAttribute('ry'), '75')
+ })
+
+ it('Test remapElement(scale) for ellipse', function () {
+ const ellipse = document.createElementNS(NS.SVG, 'ellipse')
+ ellipse.setAttribute('cx', '200')
+ ellipse.setAttribute('cy', '150')
+ ellipse.setAttribute('rx', '250')
+ ellipse.setAttribute('ry', '120')
+ this.svg.append(ellipse)
+
+ const attrs = {
+ cx: '200',
+ cy: '150',
+ rx: '250',
+ ry: '120'
+ }
+
+ // Create a translate.
+ const m = this.svg.createSVGMatrix()
+ m.a = 2; m.b = 0
+ m.c = 0; m.d = 0.5
+ m.e = 0; m.f = 0
+
+ coords.remapElement(ellipse, attrs, m)
+
+ assert.equal(ellipse.getAttribute('cx'), '400')
+ assert.equal(ellipse.getAttribute('cy'), '75')
+ assert.equal(ellipse.getAttribute('rx'), '500')
+ assert.equal(ellipse.getAttribute('ry'), '60')
+ })
+
+ it('Test remapElement(translate) for line', function () {
+ const line = document.createElementNS(NS.SVG, 'line')
+ line.setAttribute('x1', '50')
+ line.setAttribute('y1', '100')
+ line.setAttribute('x2', '120')
+ line.setAttribute('y2', '200')
+ this.svg.append(line)
+
+ const attrs = {
+ x1: '50',
+ y1: '100',
+ x2: '120',
+ y2: '200'
+ }
+
+ // Create a translate.
+ const m = this.svg.createSVGMatrix()
+ m.a = 1; m.b = 0
+ m.c = 0; m.d = 1
+ m.e = 100; m.f = -50
+
+ coords.remapElement(line, attrs, m)
+
+ assert.equal(line.getAttribute('x1'), '150')
+ assert.equal(line.getAttribute('y1'), '50')
+ assert.equal(line.getAttribute('x2'), '220')
+ assert.equal(line.getAttribute('y2'), '150')
+ })
+
+ it('Test remapElement(scale) for line', function () {
+ const line = document.createElementNS(NS.SVG, 'line')
+ line.setAttribute('x1', '50')
+ line.setAttribute('y1', '100')
+ line.setAttribute('x2', '120')
+ line.setAttribute('y2', '200')
+ this.svg.append(line)
+
+ const attrs = {
+ x1: '50',
+ y1: '100',
+ x2: '120',
+ y2: '200'
+ }
+
+ // Create a translate.
+ const m = this.svg.createSVGMatrix()
+ m.a = 2; m.b = 0
+ m.c = 0; m.d = 0.5
+ m.e = 0; m.f = 0
+
+ coords.remapElement(line, attrs, m)
+
+ assert.equal(line.getAttribute('x1'), '100')
+ assert.equal(line.getAttribute('y1'), '50')
+ assert.equal(line.getAttribute('x2'), '240')
+ assert.equal(line.getAttribute('y2'), '100')
+ })
+
+ it('Test remapElement(translate) for text', function () {
+ const text = document.createElementNS(NS.SVG, 'text')
+ text.setAttribute('x', '50')
+ text.setAttribute('y', '100')
+ this.svg.append(text)
+
+ const attrs = {
+ x: '50',
+ y: '100'
+ }
+
+ // Create a translate.
+ const m = this.svg.createSVGMatrix()
+ m.a = 1; m.b = 0
+ m.c = 0; m.d = 1
+ m.e = 100; m.f = -50
+
+ coords.remapElement(text, attrs, m)
+
+ assert.equal(text.getAttribute('x'), '150')
+ assert.equal(text.getAttribute('y'), '50')
+ })
+})
diff --git a/data/5-svgedit-unit/draw.cy.js b/data/5-svgedit-unit/draw.cy.js
new file mode 100644
index 0000000..af49b76
--- /dev/null
+++ b/data/5-svgedit-unit/draw.cy.js
@@ -0,0 +1,787 @@
+import 'pathseg'
+import { NS } from '../../../packages/svgcanvas/core/namespaces.js'
+import * as draw from '../../../packages/svgcanvas/core/draw.js'
+import * as units from '../../../packages/svgcanvas/core/units.js'
+
+describe('draw.Drawing', function () {
+ const addOwnSpies = (obj) => {
+ const methods = Object.keys(obj)
+ methods.forEach((method) => {
+ cy.spy(obj, method)
+ })
+ }
+
+ const LAYER_CLASS = draw.Layer.CLASS_NAME
+ const NONCE = 'foo'
+ const LAYER1 = 'Layer 1'
+ const LAYER2 = 'Layer 2'
+ const LAYER3 = 'Layer 3'
+ const PATH_ATTR = {
+ // clone will convert relative to absolute, so the test for equality fails.
+ // d: 'm7.38867,57.38867c0,-27.62431 22.37569,-50 50,-50c27.62431,0 50,22.37569 50,50c0,27.62431 -22.37569,50 -50,50c-27.62431,0 -50,-22.37569 -50,-50z',
+ d: 'M7.389,57.389C7.389,29.764 29.764,7.389 57.389,7.389C85.013,7.389 107.389,29.764 107.389,57.389C107.389,85.013 85.013,107.389 57.389,107.389C29.764,107.389 7.389,85.013 7.389,57.389z',
+ transform: 'rotate(45 57.388671875000036,57.388671874999986) ',
+ 'stroke-width': '5',
+ stroke: '#660000',
+ fill: '#ff0000'
+ }
+
+ units.init(
+ /**
+ * @implements {module:units.ElementContainer}
+ */
+ {
+ // used by units.shortFloat - call path: cloneLayer -> copyElem -> convertPath -> pathDSegment -> shortFloat
+ getRoundDigits () { return 3 }
+ }
+ )
+
+ // Simplifying from svgcanvas.js usage
+ const idprefix = 'svg_'
+
+ const getCurrentDrawing = function () {
+ return currentDrawing_
+ }
+ const setCurrentGroup = () => { /* empty fn */ }
+ draw.init(
+ /**
+ * @implements {module:draw.DrawCanvasInit}
+ */
+ {
+ getCurrentDrawing,
+ setCurrentGroup
+ }
+ )
+
+ /**
+ * @param {module:utilities.SVGElementJSON} jsonMap
+ * @returns {SVGElement}
+ */
+ function createSVGElement (jsonMap) {
+ const elem = document.createElementNS(NS.SVG, jsonMap.element)
+ Object.entries(jsonMap.attr).forEach(([attr, value]) => {
+ elem.setAttribute(attr, value)
+ })
+ return elem
+ }
+
+ const setupSVGWith3Layers = function (svgElem) {
+ const layer1 = document.createElementNS(NS.SVG, 'g')
+ const layer1Title = document.createElementNS(NS.SVG, 'title')
+ layer1Title.append(LAYER1)
+ layer1.append(layer1Title)
+ svgElem.append(layer1)
+
+ const layer2 = document.createElementNS(NS.SVG, 'g')
+ const layer2Title = document.createElementNS(NS.SVG, 'title')
+ layer2Title.append(LAYER2)
+ layer2.append(layer2Title)
+ svgElem.append(layer2)
+
+ const layer3 = document.createElementNS(NS.SVG, 'g')
+ const layer3Title = document.createElementNS(NS.SVG, 'title')
+ layer3Title.append(LAYER3)
+ layer3.append(layer3Title)
+ svgElem.append(layer3)
+
+ return [layer1, layer2, layer3]
+ }
+
+ const createSomeElementsInGroup = function (group) {
+ group.append(
+ createSVGElement({
+ element: 'path',
+ attr: PATH_ATTR
+ }),
+ // createSVGElement({
+ // element: 'path',
+ // attr: {d: 'M0,1L2,3'}
+ // }),
+ createSVGElement({
+ element: 'rect',
+ attr: { x: '0', y: '1', width: '5', height: '10' }
+ }),
+ createSVGElement({
+ element: 'line',
+ attr: { x1: '0', y1: '1', x2: '5', y2: '6' }
+ })
+ )
+
+ const g = createSVGElement({
+ element: 'g',
+ attr: {}
+ })
+ g.append(createSVGElement({
+ element: 'rect',
+ attr: { x: '0', y: '1', width: '5', height: '10' }
+ }))
+ group.append(g)
+ return 4
+ }
+
+ const cleanupSVG = function (svgElem) {
+ while (svgElem.firstChild) { svgElem.firstChild.remove() }
+ }
+
+ let sandbox; let currentDrawing_; let svg; let svgN
+ beforeEach(() => {
+ sandbox = document.createElement('div')
+ sandbox.id = 'sandbox'
+ sandbox.style.visibility = 'hidden'
+
+ svg = document.createElementNS(NS.SVG, 'svg')
+ // Firefox throws exception in getBBox() when svg is not attached to DOM.
+ sandbox.append(svg)
+
+ // Set up