From c3c9b0f54d9bcda4d098372414c10e8f1921f4bf Mon Sep 17 00:00:00 2001 From: Brett Zamir Date: Sun, 24 Nov 2019 11:29:24 +0800 Subject: [PATCH] - Testing: Migrate remaining HTML tests to Cypress --- .eslintrc.js | 21 +- .npmignore | 1 - cypress/integration/browser-bugs.js | 11 + cypress/integration/contextmenu.js | 59 ++ cypress/integration/coords.js | 310 +++++++ cypress/integration/draw.js | 789 +++++++++++++++++ cypress/integration/history.js | 516 +++++++++++ cypress/integration/math.js | 108 +++ cypress/integration/path.js | 167 ++++ cypress/integration/recalculate.js | 153 ++++ cypress/integration/sanitize.js | 19 + cypress/integration/select.js | 130 +++ cypress/integration/svgtransformlist.js | 329 +++++++ cypress/integration/test1.js | 270 ++++++ cypress/integration/units.js | 93 ++ cypress/integration/utilities-bbox.js | 509 +++++++++++ cypress/integration/utilities-performance.js | 239 +++++ cypress/integration/utilities.js | 361 ++++++++ cypress/support/assert-almostEquals.js | 29 + .../support/assert-close.js | 52 +- .../assert-expectOutOfBoundsException.js | 20 +- cypress/support/assertion-wrapper.js | 15 + cypress/support/copy.js | 1 - package-lock.json | 165 ---- package.json | 4 - test/all_tests.html | 40 - test/all_tests.js | 8 - test/browser-bugs/removeItem-bug.html | 21 - test/contextmenu_test.html | 26 - test/contextmenu_test.js | 73 -- test/coords_test.html | 20 - test/coords_test.js | 355 -------- test/draw_test.html | 19 - test/draw_test.js | 830 ------------------ test/history_test.html | 26 - test/history_test.js | 583 ------------ test/jQuery.attr_test.html | 19 - test/jQuery.attr_test.js | 12 - test/math_test.html | 18 - test/math_test.js | 121 --- test/path_test.html | 19 - test/path_test.js | 181 ---- test/qunit/qunit-assert-almostEquals.js | 29 - test/recalculate_test.html | 19 - test/recalculate_test.js | 165 ---- test/sanitize_test.html | 19 - test/sanitize_test.js | 25 - test/select_test.html | 19 - test/select_test.js | 143 --- test/sinon/sinon-qunit.js | 30 - test/svgtransformlist_test.html | 19 - test/svgtransformlist_test.js | 389 -------- test/test1.html | 26 - test/test1.js | 269 ------ test/units_test.html | 23 - test/units_test.js | 90 -- test/utilities_bbox_test.html | 19 - test/utilities_bbox_test.js | 508 ----------- test/utilities_performance_test.html | 75 -- test/utilities_performance_test.js | 183 ---- test/utilities_test.html | 19 - test/utilities_test.js | 378 -------- 62 files changed, 4154 insertions(+), 5035 deletions(-) create mode 100644 cypress/integration/browser-bugs.js create mode 100644 cypress/integration/contextmenu.js create mode 100644 cypress/integration/coords.js create mode 100644 cypress/integration/draw.js create mode 100644 cypress/integration/history.js create mode 100644 cypress/integration/math.js create mode 100644 cypress/integration/path.js create mode 100644 cypress/integration/recalculate.js create mode 100644 cypress/integration/sanitize.js create mode 100644 cypress/integration/select.js create mode 100644 cypress/integration/svgtransformlist.js create mode 100644 cypress/integration/test1.js create mode 100644 cypress/integration/units.js create mode 100644 cypress/integration/utilities-bbox.js create mode 100644 cypress/integration/utilities-performance.js create mode 100644 cypress/integration/utilities.js create mode 100644 cypress/support/assert-almostEquals.js rename test/qunit/qunit-assert-close.js => cypress/support/assert-close.js (79%) rename test/qunit/qunit-assert-expectOutOfBoundsException.js => cypress/support/assert-expectOutOfBoundsException.js (53%) create mode 100644 cypress/support/assertion-wrapper.js delete mode 100644 test/all_tests.html delete mode 100644 test/all_tests.js delete mode 100644 test/browser-bugs/removeItem-bug.html delete mode 100644 test/contextmenu_test.html delete mode 100644 test/contextmenu_test.js delete mode 100644 test/coords_test.html delete mode 100644 test/coords_test.js delete mode 100644 test/draw_test.html delete mode 100644 test/draw_test.js delete mode 100644 test/history_test.html delete mode 100644 test/history_test.js delete mode 100644 test/jQuery.attr_test.html delete mode 100644 test/jQuery.attr_test.js delete mode 100644 test/math_test.html delete mode 100644 test/math_test.js delete mode 100644 test/path_test.html delete mode 100644 test/path_test.js delete mode 100644 test/qunit/qunit-assert-almostEquals.js delete mode 100644 test/recalculate_test.html delete mode 100644 test/recalculate_test.js delete mode 100644 test/sanitize_test.html delete mode 100644 test/sanitize_test.js delete mode 100644 test/select_test.html delete mode 100644 test/select_test.js delete mode 100644 test/sinon/sinon-qunit.js delete mode 100644 test/svgtransformlist_test.html delete mode 100644 test/svgtransformlist_test.js delete mode 100644 test/test1.html delete mode 100644 test/test1.js delete mode 100644 test/units_test.html delete mode 100644 test/units_test.js delete mode 100644 test/utilities_bbox_test.html delete mode 100644 test/utilities_bbox_test.js delete mode 100644 test/utilities_performance_test.html delete mode 100644 test/utilities_performance_test.js delete mode 100644 test/utilities_test.html delete mode 100644 test/utilities_test.js diff --git a/.eslintrc.js b/.eslintrc.js index fc453648..8cd17f50 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -3,8 +3,6 @@ module.exports = { parserOptions: { sourceType: "module" }, - // Need to make explicit here for processing by jsdoc/check-examples - plugins: ["qunit"], env: { browser: true }, @@ -87,7 +85,7 @@ module.exports = { "editor/redirect-on-no-module-support.js", "editor/extensions/imagelib/index.js", "editor/external/dom-polyfill/dom-polyfill.js", - "test/all_tests.js", "screencasts/svgopen2010/script.js", + "screencasts/svgopen2010/script.js", "opera-widget/handlers.js", "firefox-extension/handlers.js", "firefox-extension/content/svg-edit-overlay.js" @@ -96,12 +94,6 @@ module.exports = { "import/unambiguous": ["off"] } }, - { - files: ['test/browser-bugs/**'], - rules: { - 'no-var': 'off' - } - }, { files: ['**/*.html'], rules: { @@ -133,7 +125,7 @@ module.exports = { // Dis-apply Node rules mistakenly giving errors with browser files, // and treating Node global `root` as being present for shadowing { - files: ["editor/**", "test/**", "screencasts/**"], + files: ["editor/**", "screencasts/**"], globals: { root: "off" }, @@ -141,14 +133,6 @@ module.exports = { "node/no-unsupported-features/node-builtins": "off" } }, - // We want console in tests! - { - extends: ["plugin:qunit/recommended"], - files: ["test/**"], - rules: { - "no-console": ["off"] - } - }, { // Node files files: [ @@ -191,6 +175,7 @@ module.exports = { node: true }, rules: { + 'no-console': 0, 'import/unambiguous': 0, } } diff --git a/.npmignore b/.npmignore index 925485ae..2d18b13c 100644 --- a/.npmignore +++ b/.npmignore @@ -1,6 +1,5 @@ ignore screencasts -test .github/ISSUE_TEMPLATE/bug_report.md build diff --git a/cypress/integration/browser-bugs.js b/cypress/integration/browser-bugs.js new file mode 100644 index 00000000..49648ebe --- /dev/null +++ b/cypress/integration/browser-bugs.js @@ -0,0 +1,11 @@ + +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/cypress/integration/contextmenu.js b/cypress/integration/contextmenu.js new file mode 100644 index 00000000..0e1a2643 --- /dev/null +++ b/cypress/integration/contextmenu.js @@ -0,0 +1,59 @@ +import '../../instrumented/jquery.min.js'; +import * as contextmenu from '../../instrumented/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 () { /* */ }}; + 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 () { /**/ }}; + const validItem2 = {id: 'valid', label: 'anicelabel', action () { /**/ }}; + contextmenu.add(validItem1); + + assert.throws( + () => contextmenu.add(validItem2), + null, null, + 'duplicate menu item is rejected.' + ); + }); +}); diff --git a/cypress/integration/coords.js b/cypress/integration/coords.js new file mode 100644 index 00000000..94573927 --- /dev/null +++ b/cypress/integration/coords.js @@ -0,0 +1,310 @@ +import '../../instrumented/jquery.min.js'; + +import {NS} from '../../instrumented/namespaces.js'; +import * as utilities from '../../instrumented/utilities.js'; +import * as coords from '../../instrumented/coords.js'; + +describe('coords', function () { + let elemId = 1; + + // eslint-disable-next-line no-shadow + 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/cypress/integration/draw.js b/cypress/integration/draw.js new file mode 100644 index 00000000..5d7703ca --- /dev/null +++ b/cypress/integration/draw.js @@ -0,0 +1,789 @@ +import '../../instrumented/jquery.min.js'; + +import {NS} from '../../instrumented/namespaces.js'; +import * as draw from '../../instrumented/draw.js'; +import * as units from '../../instrumented/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 = (cg) => { /* */ }; + 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(document.createTextNode(LAYER1)); + layer1.append(layer1Title); + svgElem.append(layer1); + + const layer2 = document.createElementNS(NS.SVG, 'g'); + const layer2Title = document.createElementNS(NS.SVG, 'title'); + layer2Title.append(document.createTextNode(LAYER2)); + layer2.append(layer2Title); + svgElem.append(layer2); + + const layer3 = document.createElementNS(NS.SVG, 'g'); + const layer3Title = document.createElementNS(NS.SVG, 'title'); + layer3Title.append(document.createTextNode(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, currentDrawing_, svg, 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 with nonce. + svgN = document.createElementNS(NS.SVG, 'svg'); + svgN.setAttributeNS(NS.XMLNS, 'xmlns:se', NS.SE); + svgN.setAttributeNS(NS.SE, 'se:nonce', NONCE); + + const svgcontent = document.createElementNS(NS.SVG, 'svg'); + currentDrawing_ = new draw.Drawing(svgcontent, idprefix); + }); + + it('Test draw module', function () { + assert.ok(draw); + assert.equal(typeof draw, typeof {}); + + assert.ok(draw.Drawing); + assert.equal(typeof draw.Drawing, typeof function () { /* */ }); + }); + + it('Test document creation', function () { + let doc; + try { + doc = new draw.Drawing(); + assert.ok(false, 'Created drawing without a valid element'); + } catch (e) { + assert.ok(true); + } + + try { + doc = new draw.Drawing(svg); + assert.ok(doc); + assert.equal(typeof doc, typeof {}); + } catch (e) { + assert.ok(false, 'Could not create document from valid element: ' + e); + } + }); + + it('Test nonce', function () { + let doc = new draw.Drawing(svg); + assert.equal(doc.getNonce(), ''); + + doc = new draw.Drawing(svgN); + assert.equal(doc.getNonce(), NONCE); + assert.equal(doc.getSvgElem().getAttributeNS(NS.SE, 'nonce'), NONCE); + + doc.clearNonce(); + assert.ok(!doc.getNonce()); + assert.ok(!doc.getSvgElem().getAttributeNS(NS.SE, 'se:nonce')); + + doc.setNonce(NONCE); + assert.equal(doc.getNonce(), NONCE); + assert.equal(doc.getSvgElem().getAttributeNS(NS.SE, 'nonce'), NONCE); + }); + + it('Test getId() and getNextId() without nonce', function () { + const elem2 = document.createElementNS(NS.SVG, 'circle'); + elem2.id = 'svg_2'; + svg.append(elem2); + + const doc = new draw.Drawing(svg); + + assert.equal(doc.getId(), 'svg_0'); + + assert.equal(doc.getNextId(), 'svg_1'); + assert.equal(doc.getId(), 'svg_1'); + + assert.equal(doc.getNextId(), 'svg_3'); + assert.equal(doc.getId(), 'svg_3'); + + assert.equal(doc.getNextId(), 'svg_4'); + assert.equal(doc.getId(), 'svg_4'); + // clean out svg document + cleanupSVG(svg); + }); + + it('Test getId() and getNextId() with prefix without nonce', function () { + const prefix = 'Bar-'; + const doc = new draw.Drawing(svg, prefix); + + assert.equal(doc.getId(), prefix + '0'); + + assert.equal(doc.getNextId(), prefix + '1'); + assert.equal(doc.getId(), prefix + '1'); + + assert.equal(doc.getNextId(), prefix + '2'); + assert.equal(doc.getId(), prefix + '2'); + + assert.equal(doc.getNextId(), prefix + '3'); + assert.equal(doc.getId(), prefix + '3'); + + cleanupSVG(svg); + }); + + it('Test getId() and getNextId() with nonce', function () { + const prefix = 'svg_' + NONCE; + + const elem2 = document.createElementNS(NS.SVG, 'circle'); + elem2.id = prefix + '_2'; + svgN.append(elem2); + + const doc = new draw.Drawing(svgN); + + assert.equal(doc.getId(), prefix + '_0'); + + assert.equal(doc.getNextId(), prefix + '_1'); + assert.equal(doc.getId(), prefix + '_1'); + + assert.equal(doc.getNextId(), prefix + '_3'); + assert.equal(doc.getId(), prefix + '_3'); + + assert.equal(doc.getNextId(), prefix + '_4'); + assert.equal(doc.getId(), prefix + '_4'); + + cleanupSVG(svgN); + }); + + it('Test getId() and getNextId() with prefix with nonce', function () { + const PREFIX = 'Bar-'; + const doc = new draw.Drawing(svgN, PREFIX); + + const prefix = PREFIX + NONCE + '_'; + assert.equal(doc.getId(), prefix + '0'); + + assert.equal(doc.getNextId(), prefix + '1'); + assert.equal(doc.getId(), prefix + '1'); + + assert.equal(doc.getNextId(), prefix + '2'); + assert.equal(doc.getId(), prefix + '2'); + + assert.equal(doc.getNextId(), prefix + '3'); + assert.equal(doc.getId(), prefix + '3'); + + cleanupSVG(svgN); + }); + + it('Test releaseId()', function () { + const doc = new draw.Drawing(svg); + + const firstId = doc.getNextId(); + /* const secondId = */ doc.getNextId(); + + const result = doc.releaseId(firstId); + assert.ok(result); + assert.equal(doc.getNextId(), firstId); + assert.equal(doc.getNextId(), 'svg_3'); + + assert.ok(!doc.releaseId('bad-id')); + assert.ok(doc.releaseId(firstId)); + assert.ok(!doc.releaseId(firstId)); + + cleanupSVG(svg); + }); + + it('Test getNumLayers', function () { + const drawing = new draw.Drawing(svg); + assert.equal(typeof drawing.getNumLayers, typeof function () { /* */ }); + assert.equal(drawing.getNumLayers(), 0); + + setupSVGWith3Layers(svg); + drawing.identifyLayers(); + + assert.equal(drawing.getNumLayers(), 3); + + cleanupSVG(svg); + }); + + it('Test hasLayer', function () { + setupSVGWith3Layers(svg); + const drawing = new draw.Drawing(svg); + drawing.identifyLayers(); + + assert.equal(typeof drawing.hasLayer, typeof function () { /* */ }); + assert.ok(!drawing.hasLayer('invalid-layer')); + + assert.ok(drawing.hasLayer(LAYER3)); + assert.ok(drawing.hasLayer(LAYER2)); + assert.ok(drawing.hasLayer(LAYER1)); + + cleanupSVG(svg); + }); + + it('Test identifyLayers() with empty document', function () { + const drawing = new draw.Drawing(svg); + assert.equal(drawing.getCurrentLayer(), null); + // By default, an empty document gets an empty group created. + drawing.identifyLayers(); + + // Check that element now has one child node + assert.ok(drawing.getSvgElem().hasChildNodes()); + assert.equal(drawing.getSvgElem().childNodes.length, 1); + + // Check that all_layers are correctly set up. + assert.equal(drawing.getNumLayers(), 1); + const emptyLayer = drawing.all_layers[0]; + assert.ok(emptyLayer); + const layerGroup = emptyLayer.getGroup(); + assert.equal(layerGroup, drawing.getSvgElem().firstChild); + assert.equal(layerGroup.tagName, 'g'); + assert.equal(layerGroup.getAttribute('class'), LAYER_CLASS); + assert.ok(layerGroup.hasChildNodes()); + assert.equal(layerGroup.childNodes.length, 1); + const firstChild = layerGroup.childNodes.item(0); + assert.equal(firstChild.tagName, 'title'); + + cleanupSVG(svg); + }); + + it('Test identifyLayers() with some layers', function () { + const drawing = new draw.Drawing(svg); + setupSVGWith3Layers(svg); + + assert.equal(svg.childNodes.length, 3); + + drawing.identifyLayers(); + + assert.equal(drawing.getNumLayers(), 3); + assert.equal(drawing.all_layers[0].getGroup(), svg.childNodes.item(0)); + assert.equal(drawing.all_layers[1].getGroup(), svg.childNodes.item(1)); + assert.equal(drawing.all_layers[2].getGroup(), svg.childNodes.item(2)); + + assert.equal(drawing.all_layers[0].getGroup().getAttribute('class'), LAYER_CLASS); + assert.equal(drawing.all_layers[1].getGroup().getAttribute('class'), LAYER_CLASS); + assert.equal(drawing.all_layers[2].getGroup().getAttribute('class'), LAYER_CLASS); + + cleanupSVG(svg); + }); + + it('Test identifyLayers() with some layers and orphans', function () { + setupSVGWith3Layers(svg); + + const orphan1 = document.createElementNS(NS.SVG, 'rect'); + const orphan2 = document.createElementNS(NS.SVG, 'rect'); + svg.append(orphan1, orphan2); + + assert.equal(svg.childNodes.length, 5); + + const drawing = new draw.Drawing(svg); + drawing.identifyLayers(); + + assert.equal(drawing.getNumLayers(), 4); + assert.equal(drawing.all_layers[0].getGroup(), svg.childNodes.item(0)); + assert.equal(drawing.all_layers[1].getGroup(), svg.childNodes.item(1)); + assert.equal(drawing.all_layers[2].getGroup(), svg.childNodes.item(2)); + assert.equal(drawing.all_layers[3].getGroup(), svg.childNodes.item(3)); + + assert.equal(drawing.all_layers[0].getGroup().getAttribute('class'), LAYER_CLASS); + assert.equal(drawing.all_layers[1].getGroup().getAttribute('class'), LAYER_CLASS); + assert.equal(drawing.all_layers[2].getGroup().getAttribute('class'), LAYER_CLASS); + assert.equal(drawing.all_layers[3].getGroup().getAttribute('class'), LAYER_CLASS); + + const layer4 = drawing.all_layers[3].getGroup(); + assert.equal(layer4.tagName, 'g'); + assert.equal(layer4.childNodes.length, 3); + assert.equal(layer4.childNodes.item(1), orphan1); + assert.equal(layer4.childNodes.item(2), orphan2); + + cleanupSVG(svg); + }); + + it('Test getLayerName()', function () { + const drawing = new draw.Drawing(svg); + setupSVGWith3Layers(svg); + + drawing.identifyLayers(); + + assert.equal(drawing.getNumLayers(), 3); + assert.equal(drawing.getLayerName(0), LAYER1); + assert.equal(drawing.getLayerName(1), LAYER2); + assert.equal(drawing.getLayerName(2), LAYER3); + + cleanupSVG(svg); + }); + + it('Test getCurrentLayer()', function () { + const drawing = new draw.Drawing(svg); + setupSVGWith3Layers(svg); + drawing.identifyLayers(); + + assert.ok(drawing.getCurrentLayer); + assert.equal(typeof drawing.getCurrentLayer, typeof function () { /* */ }); + assert.ok(drawing.getCurrentLayer()); + assert.equal(drawing.getCurrentLayer(), drawing.all_layers[2].getGroup()); + + cleanupSVG(svg); + }); + + it('Test setCurrentLayer() and getCurrentLayerName()', function () { + const drawing = new draw.Drawing(svg); + setupSVGWith3Layers(svg); + drawing.identifyLayers(); + + assert.ok(drawing.setCurrentLayer); + assert.equal(typeof drawing.setCurrentLayer, typeof function () { /* */ }); + + drawing.setCurrentLayer(LAYER2); + assert.equal(drawing.getCurrentLayerName(), LAYER2); + assert.equal(drawing.getCurrentLayer(), drawing.all_layers[1].getGroup()); + + drawing.setCurrentLayer(LAYER3); + assert.equal(drawing.getCurrentLayerName(), LAYER3); + assert.equal(drawing.getCurrentLayer(), drawing.all_layers[2].getGroup()); + + cleanupSVG(svg); + }); + + it('Test setCurrentLayerName()', function () { + const mockHrService = { + changeElement () { + // empty + } + }; + addOwnSpies(mockHrService); + + const drawing = new draw.Drawing(svg); + setupSVGWith3Layers(svg); + drawing.identifyLayers(); + + assert.ok(drawing.setCurrentLayerName); + assert.equal(typeof drawing.setCurrentLayerName, typeof function () { /* */ }); + + const oldName = drawing.getCurrentLayerName(); + const newName = 'New Name'; + assert.ok(drawing.layer_map[oldName]); + assert.equal(drawing.layer_map[newName], undefined); // newName shouldn't exist. + const result = drawing.setCurrentLayerName(newName, mockHrService); + assert.equal(result, newName); + assert.equal(drawing.getCurrentLayerName(), newName); + // Was the map updated? + assert.equal(drawing.layer_map[oldName], undefined); + assert.equal(drawing.layer_map[newName], drawing.current_layer); + // Was mockHrService called? + assert.ok(mockHrService.changeElement.calledOnce); + assert.equal(oldName, mockHrService.changeElement.getCall(0).args[1]['#text']); + assert.equal(newName, mockHrService.changeElement.getCall(0).args[0].textContent); + + cleanupSVG(svg); + }); + + it('Test createLayer()', function () { + const mockHrService = { + startBatchCommand () { /**/ }, + endBatchCommand () { /**/ }, + insertElement () { /**/ } + }; + addOwnSpies(mockHrService); + + const drawing = new draw.Drawing(svg); + setupSVGWith3Layers(svg); + drawing.identifyLayers(); + + assert.ok(drawing.createLayer); + assert.equal(typeof drawing.createLayer, typeof function () { /* */ }); + + const NEW_LAYER_NAME = 'Layer A'; + const layerG = drawing.createLayer(NEW_LAYER_NAME, mockHrService); + assert.equal(drawing.getNumLayers(), 4); + assert.equal(layerG, drawing.getCurrentLayer()); + assert.equal(layerG.getAttribute('class'), LAYER_CLASS); + assert.equal(NEW_LAYER_NAME, drawing.getCurrentLayerName()); + assert.equal(NEW_LAYER_NAME, drawing.getLayerName(3)); + + assert.equal(layerG, mockHrService.insertElement.getCall(0).args[0]); + assert.ok(mockHrService.startBatchCommand.calledOnce); + assert.ok(mockHrService.endBatchCommand.calledOnce); + + cleanupSVG(svg); + }); + + it('Test mergeLayer()', function () { + const mockHrService = { + startBatchCommand () { /**/ }, + endBatchCommand () { /**/ }, + moveElement () { /**/ }, + removeElement () { /**/ } + }; + addOwnSpies(mockHrService); + + const drawing = new draw.Drawing(svg); + const layers = setupSVGWith3Layers(svg); + const elementCount = createSomeElementsInGroup(layers[2]) + 1; // +1 for title element + assert.equal(layers[1].childElementCount, 1); + assert.equal(layers[2].childElementCount, elementCount); + drawing.identifyLayers(); + assert.equal(drawing.getCurrentLayer(), layers[2]); + + assert.ok(drawing.mergeLayer); + assert.equal(typeof drawing.mergeLayer, typeof function () { /* */ }); + + drawing.mergeLayer(mockHrService); + + assert.equal(drawing.getNumLayers(), 2); + assert.equal(svg.childElementCount, 2); + assert.equal(drawing.getCurrentLayer(), layers[1]); + assert.equal(layers[1].childElementCount, elementCount); + + // check history record + assert.ok(mockHrService.startBatchCommand.calledOnce); + assert.ok(mockHrService.endBatchCommand.calledOnce); + assert.equal(mockHrService.startBatchCommand.getCall(0).args[0], 'Merge Layer'); + assert.equal(mockHrService.moveElement.callCount, elementCount - 1); // -1 because the title was not moved. + assert.equal(mockHrService.removeElement.callCount, 2); // remove group and title. + + cleanupSVG(svg); + }); + + it('Test mergeLayer() when no previous layer to merge', function () { + const mockHrService = { + startBatchCommand () { /**/ }, + endBatchCommand () { /**/ }, + moveElement () { /**/ }, + removeElement () { /**/ } + }; + addOwnSpies(mockHrService); + + const drawing = new draw.Drawing(svg); + const layers = setupSVGWith3Layers(svg); + drawing.identifyLayers(); + drawing.setCurrentLayer(LAYER1); + assert.equal(drawing.getCurrentLayer(), layers[0]); + + drawing.mergeLayer(mockHrService); + + assert.equal(drawing.getNumLayers(), 3); + assert.equal(svg.childElementCount, 3); + assert.equal(drawing.getCurrentLayer(), layers[0]); + assert.equal(layers[0].childElementCount, 1); + assert.equal(layers[1].childElementCount, 1); + assert.equal(layers[2].childElementCount, 1); + + // check history record + assert.equal(mockHrService.startBatchCommand.callCount, 0); + assert.equal(mockHrService.endBatchCommand.callCount, 0); + assert.equal(mockHrService.moveElement.callCount, 0); + assert.equal(mockHrService.removeElement.callCount, 0); + + cleanupSVG(svg); + }); + + it('Test mergeAllLayers()', function () { + const mockHrService = { + startBatchCommand () { /**/ }, + endBatchCommand () { /**/ }, + moveElement () { /**/ }, + removeElement () { /**/ } + }; + addOwnSpies(mockHrService); + + const drawing = new draw.Drawing(svg); + const layers = setupSVGWith3Layers(svg); + const elementCount = createSomeElementsInGroup(layers[0]) + 1; // +1 for title element + createSomeElementsInGroup(layers[1]); + createSomeElementsInGroup(layers[2]); + assert.equal(layers[0].childElementCount, elementCount); + assert.equal(layers[1].childElementCount, elementCount); + assert.equal(layers[2].childElementCount, elementCount); + drawing.identifyLayers(); + + assert.ok(drawing.mergeAllLayers); + assert.equal(typeof drawing.mergeAllLayers, typeof function () { /* */ }); + + drawing.mergeAllLayers(mockHrService); + + assert.equal(drawing.getNumLayers(), 1); + assert.equal(svg.childElementCount, 1); + assert.equal(drawing.getCurrentLayer(), layers[0]); + assert.equal(layers[0].childElementCount, elementCount * 3 - 2); // -2 because two titles were deleted. + + // check history record + assert.equal(mockHrService.startBatchCommand.callCount, 3); // mergeAllLayers + 2 * mergeLayer + assert.equal(mockHrService.endBatchCommand.callCount, 3); + assert.equal(mockHrService.startBatchCommand.getCall(0).args[0], 'Merge all Layers'); + assert.equal(mockHrService.startBatchCommand.getCall(1).args[0], 'Merge Layer'); + assert.equal(mockHrService.startBatchCommand.getCall(2).args[0], 'Merge Layer'); + // moveElement count is times 3 instead of 2, because one layer's elements were moved twice. + // moveElement count is minus 3 because the three titles were not moved. + assert.equal(mockHrService.moveElement.callCount, elementCount * 3 - 3); + assert.equal(mockHrService.removeElement.callCount, 2 * 2); // remove group and title twice. + + cleanupSVG(svg); + }); + + it('Test cloneLayer()', function () { + const mockHrService = { + startBatchCommand () { /**/ }, + endBatchCommand () { /**/ }, + insertElement () { /**/ } + }; + addOwnSpies(mockHrService); + + const drawing = new draw.Drawing(svg); + const layers = setupSVGWith3Layers(svg); + const layer3 = layers[2]; + const elementCount = createSomeElementsInGroup(layer3) + 1; // +1 for title element + assert.equal(layer3.childElementCount, elementCount); + drawing.identifyLayers(); + + assert.ok(drawing.cloneLayer); + assert.equal(typeof drawing.cloneLayer, typeof function () { /* */ }); + + const clone = drawing.cloneLayer('clone', mockHrService); + + assert.equal(drawing.getNumLayers(), 4); + assert.equal(svg.childElementCount, 4); + assert.equal(drawing.getCurrentLayer(), clone); + assert.equal(clone.childElementCount, elementCount); + + // check history record + assert.ok(mockHrService.startBatchCommand.calledOnce); // mergeAllLayers + 2 * mergeLayer + assert.ok(mockHrService.endBatchCommand.calledOnce); + assert.equal(mockHrService.startBatchCommand.getCall(0).args[0], 'Duplicate Layer'); + assert.equal(mockHrService.insertElement.callCount, 1); + assert.equal(mockHrService.insertElement.getCall(0).args[0], clone); + + // check that path is cloned properly + assert.equal(clone.childNodes.length, elementCount); + const path = clone.childNodes[1]; + assert.equal(path.id, 'svg_1'); + assert.equal(path.getAttribute('d'), PATH_ATTR.d); + assert.equal(path.getAttribute('transform'), PATH_ATTR.transform); + assert.equal(path.getAttribute('fill'), PATH_ATTR.fill); + assert.equal(path.getAttribute('stroke'), PATH_ATTR.stroke); + assert.equal(path.getAttribute('stroke-width'), PATH_ATTR['stroke-width']); + + // check that g is cloned properly + const g = clone.childNodes[4]; + assert.equal(g.childNodes.length, 1); + assert.equal(g.id, 'svg_4'); + + cleanupSVG(svg); + }); + + it('Test getLayerVisibility()', function () { + const drawing = new draw.Drawing(svg); + setupSVGWith3Layers(svg); + drawing.identifyLayers(); + + assert.ok(drawing.getLayerVisibility); + assert.equal(typeof drawing.getLayerVisibility, typeof function () { /* */ }); + assert.ok(drawing.getLayerVisibility(LAYER1)); + assert.ok(drawing.getLayerVisibility(LAYER2)); + assert.ok(drawing.getLayerVisibility(LAYER3)); + + cleanupSVG(svg); + }); + + it('Test setLayerVisibility()', function () { + const drawing = new draw.Drawing(svg); + setupSVGWith3Layers(svg); + drawing.identifyLayers(); + + assert.ok(drawing.setLayerVisibility); + assert.equal(typeof drawing.setLayerVisibility, typeof function () { /* */ }); + + drawing.setLayerVisibility(LAYER3, false); + drawing.setLayerVisibility(LAYER2, true); + drawing.setLayerVisibility(LAYER1, false); + + assert.ok(!drawing.getLayerVisibility(LAYER1)); + assert.ok(drawing.getLayerVisibility(LAYER2)); + assert.ok(!drawing.getLayerVisibility(LAYER3)); + + drawing.setLayerVisibility(LAYER3, 'test-string'); + assert.ok(!drawing.getLayerVisibility(LAYER3)); + + cleanupSVG(svg); + }); + + it('Test getLayerOpacity()', function () { + const drawing = new draw.Drawing(svg); + setupSVGWith3Layers(svg); + drawing.identifyLayers(); + + assert.ok(drawing.getLayerOpacity); + assert.equal(typeof drawing.getLayerOpacity, typeof function () { /* */ }); + assert.strictEqual(drawing.getLayerOpacity(LAYER1), 1.0); + assert.strictEqual(drawing.getLayerOpacity(LAYER2), 1.0); + assert.strictEqual(drawing.getLayerOpacity(LAYER3), 1.0); + + cleanupSVG(svg); + }); + + it('Test setLayerOpacity()', function () { + const drawing = new draw.Drawing(svg); + setupSVGWith3Layers(svg); + drawing.identifyLayers(); + + assert.ok(drawing.setLayerOpacity); + assert.equal(typeof drawing.setLayerOpacity, typeof function () { /* */ }); + + drawing.setLayerOpacity(LAYER1, 0.4); + drawing.setLayerOpacity(LAYER2, 'invalid-string'); + drawing.setLayerOpacity(LAYER3, -1.4); + + assert.strictEqual(drawing.getLayerOpacity(LAYER1), 0.4); + // console.log('layer2 opacity ' + drawing.getLayerOpacity(LAYER2)); + assert.strictEqual(drawing.getLayerOpacity(LAYER2), 1.0); + assert.strictEqual(drawing.getLayerOpacity(LAYER3), 1.0); + + drawing.setLayerOpacity(LAYER3, 100); + assert.strictEqual(drawing.getLayerOpacity(LAYER3), 1.0); + + cleanupSVG(svg); + }); + + it('Test deleteCurrentLayer()', function () { + const drawing = new draw.Drawing(svg); + setupSVGWith3Layers(svg); + drawing.identifyLayers(); + + drawing.setCurrentLayer(LAYER2); + + const curLayer = drawing.getCurrentLayer(); + assert.equal(curLayer, drawing.all_layers[1].getGroup()); + const deletedLayer = drawing.deleteCurrentLayer(); + + assert.equal(curLayer, deletedLayer); + assert.equal(drawing.getNumLayers(), 2); + assert.equal(LAYER1, drawing.all_layers[0].getName()); + assert.equal(LAYER3, drawing.all_layers[1].getName()); + assert.equal(drawing.getCurrentLayer(), drawing.all_layers[1].getGroup()); + }); + + it('Test svgedit.draw.randomizeIds()', function () { + // Confirm in LET_DOCUMENT_DECIDE mode that the document decides + // if there is a nonce. + let drawing = new draw.Drawing(svgN.cloneNode(true)); + assert.ok(drawing.getNonce()); + + drawing = new draw.Drawing(svg.cloneNode(true)); + assert.ok(!drawing.getNonce()); + + // Confirm that a nonce is set once we're in ALWAYS_RANDOMIZE mode. + draw.randomizeIds(true, drawing); + assert.ok(drawing.getNonce()); + + // Confirm new drawings in ALWAYS_RANDOMIZE mode have a nonce. + drawing = new draw.Drawing(svg.cloneNode(true)); + assert.ok(drawing.getNonce()); + + drawing.clearNonce(); + assert.ok(!drawing.getNonce()); + + // Confirm new drawings in NEVER_RANDOMIZE mode do not have a nonce + // but that their se:nonce attribute is left alone. + draw.randomizeIds(false, drawing); + assert.ok(!drawing.getNonce()); + assert.ok(drawing.getSvgElem().getAttributeNS(NS.SE, 'nonce')); + + drawing = new draw.Drawing(svg.cloneNode(true)); + assert.ok(!drawing.getNonce()); + + drawing = new draw.Drawing(svgN.cloneNode(true)); + assert.ok(!drawing.getNonce()); + }); +}); diff --git a/cypress/integration/history.js b/cypress/integration/history.js new file mode 100644 index 00000000..a6398f7d --- /dev/null +++ b/cypress/integration/history.js @@ -0,0 +1,516 @@ +import '../../instrumented/jquery.min.js'; + +import {NS} from '../../instrumented/namespaces.js'; +import * as transformlist from '../../instrumented/svgtransformlist.js'; +import * as utilities from '../../instrumented/utilities.js'; +import * as hstory from '../../instrumented/history.js'; + +describe('history', function () { + // TODO(codedread): Write tests for handling history events. + + // Mocked out methods. + transformlist.changeRemoveElementFromListMap((elem) => { /* */ }); + + utilities.mock({ + getHref (elem) { return '#foo'; }, + setHref (elem, val) { /* */ }, + getRotationAngle (elem) { return 0; } + }); + + // const svg = document.createElementNS(NS.SVG, 'svg'); + let undoMgr = null; + + class MockCommand { + constructor (optText) { this.text_ = optText; } + apply () { /* */ } // eslint-disable-line class-methods-use-this + unapply () { /* */ } // eslint-disable-line class-methods-use-this + getText () { return this.text_; } + elements () { return []; } // eslint-disable-line class-methods-use-this + } + + /* + class MockHistoryEventHandler { + handleHistoryEvent (eventType, command) {} + } + */ + + /** + * Set up tests (with undo manager). + * @returns {void} + */ + beforeEach(function () { + undoMgr = new hstory.UndoManager(); + + document.body.textContent = ''; + this.divparent = document.createElement('div'); + this.divparent.id = 'divparent'; + this.divparent.style.visibility = 'hidden'; + + for (let i = 1; i <= 5; i++) { + const div = document.createElement('div'); + const id = `div${i}`; + div.id = id; + this[id] = div; + } + + this.divparent.append(this.div1, this.div2, this.div3); + + this.div4.style.visibility = 'hidden'; + this.div4.append(this.div5); + + document.body.append(this.divparent, this.div); + }); + /** + * Tear down tests, destroying undo manager. + * @returns {void} + */ + afterEach(() => { + undoMgr = null; + }); + + it('Test svgedit.history package', function () { + assert.ok(hstory); + assert.ok(hstory.MoveElementCommand); + assert.ok(hstory.InsertElementCommand); + assert.ok(hstory.ChangeElementCommand); + assert.ok(hstory.RemoveElementCommand); + assert.ok(hstory.BatchCommand); + assert.ok(hstory.UndoManager); + assert.equal(typeof hstory.MoveElementCommand, typeof function () { /* */ }); + assert.equal(typeof hstory.InsertElementCommand, typeof function () { /* */ }); + assert.equal(typeof hstory.ChangeElementCommand, typeof function () { /* */ }); + assert.equal(typeof hstory.RemoveElementCommand, typeof function () { /* */ }); + assert.equal(typeof hstory.BatchCommand, typeof function () { /* */ }); + assert.equal(typeof hstory.UndoManager, typeof function () { /* */ }); + }); + + 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 () { /* */ }); + assert.equal(typeof undoMgr.getUndoStackSize, typeof function () { /* */ }); + assert.equal(typeof undoMgr.getRedoStackSize, typeof function () { /* */ }); + assert.equal(typeof undoMgr.resetUndoStack, typeof function () { /* */ }); + assert.equal(typeof undoMgr.getNextUndoCommandText, typeof function () { /* */ }); + assert.equal(typeof undoMgr.getNextRedoCommandText, typeof function () { /* */ }); + }); + + 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 hstory.MoveElementCommand(this.div3, this.div1, this.divparent); + assert.ok(move.unapply); + assert.ok(move.apply); + assert.equal(typeof move.unapply, typeof function () { /* */ }); + assert.equal(typeof move.apply, typeof function () { /* */ }); + + move.unapply(); + assert.equal(this.divparent.firstElementChild, this.div3); + assert.equal(this.divparent.firstElementChild.nextElementSibling, this.div1); + assert.equal(this.divparent.lastElementChild, this.div2); + + move.apply(); + assert.equal(this.divparent.firstElementChild, this.div1); + assert.equal(this.divparent.firstElementChild.nextElementSibling, this.div2); + assert.equal(this.divparent.lastElementChild, this.div3); + + move = new hstory.MoveElementCommand(this.div1, null, this.divparent); + + move.unapply(); + assert.equal(this.divparent.firstElementChild, this.div2); + assert.equal(this.divparent.firstElementChild.nextElementSibling, this.div3); + assert.equal(this.divparent.lastElementChild, this.div1); + + move.apply(); + assert.equal(this.divparent.firstElementChild, this.div1); + assert.equal(this.divparent.firstElementChild.nextElementSibling, this.div2); + assert.equal(this.divparent.lastElementChild, this.div3); + + move = new hstory.MoveElementCommand(this.div2, this.div5, this.div4); + + move.unapply(); + assert.equal(this.divparent.firstElementChild, this.div1); + assert.equal(this.divparent.firstElementChild.nextElementSibling, this.div3); + assert.equal(this.divparent.lastElementChild, this.div3); + assert.equal(this.div4.firstElementChild, this.div2); + assert.equal(this.div4.firstElementChild.nextElementSibling, this.div5); + + move.apply(); + assert.equal(this.divparent.firstElementChild, this.div1); + assert.equal(this.divparent.firstElementChild.nextElementSibling, this.div2); + assert.equal(this.divparent.lastElementChild, this.div3); + assert.equal(this.div4.firstElementChild, this.div5); + assert.equal(this.div4.lastElementChild, this.div5); + }); + + it('Test InsertElementCommand', function () { + let insert = new hstory.InsertElementCommand(this.div3); + assert.ok(insert.unapply); + assert.ok(insert.apply); + assert.equal(typeof insert.unapply, typeof function () { /* */ }); + assert.equal(typeof insert.apply, typeof function () { /* */ }); + + insert.unapply(); + assert.equal(this.divparent.childElementCount, 2); + assert.equal(this.divparent.firstElementChild, this.div1); + assert.equal(this.div1.nextElementSibling, this.div2); + assert.equal(this.divparent.lastElementChild, this.div2); + + insert.apply(); + assert.equal(this.divparent.childElementCount, 3); + assert.equal(this.divparent.firstElementChild, this.div1); + assert.equal(this.div1.nextElementSibling, this.div2); + assert.equal(this.div2.nextElementSibling, this.div3); + + insert = new hstory.InsertElementCommand(this.div2); + + insert.unapply(); + assert.equal(this.divparent.childElementCount, 2); + assert.equal(this.divparent.firstElementChild, this.div1); + assert.equal(this.div1.nextElementSibling, this.div3); + assert.equal(this.divparent.lastElementChild, this.div3); + + insert.apply(); + assert.equal(this.divparent.childElementCount, 3); + assert.equal(this.divparent.firstElementChild, this.div1); + assert.equal(this.div1.nextElementSibling, this.div2); + assert.equal(this.div2.nextElementSibling, this.div3); + }); + + it('Test RemoveElementCommand', function () { + const div6 = document.createElement('div'); + div6.id = 'div6'; + + let remove = new hstory.RemoveElementCommand(div6, null, this.divparent); + assert.ok(remove.unapply); + assert.ok(remove.apply); + assert.equal(typeof remove.unapply, typeof function () { /* */ }); + assert.equal(typeof remove.apply, typeof function () { /* */ }); + + remove.unapply(); + assert.equal(this.divparent.childElementCount, 4); + assert.equal(this.divparent.firstElementChild, this.div1); + assert.equal(this.div1.nextElementSibling, this.div2); + assert.equal(this.div2.nextElementSibling, this.div3); + assert.equal(this.div3.nextElementSibling, div6); + + remove.apply(); + assert.equal(this.divparent.childElementCount, 3); + assert.equal(this.divparent.firstElementChild, this.div1); + assert.equal(this.div1.nextElementSibling, this.div2); + assert.equal(this.div2.nextElementSibling, this.div3); + + remove = new hstory.RemoveElementCommand(div6, this.div2, this.divparent); + + remove.unapply(); + assert.equal(this.divparent.childElementCount, 4); + assert.equal(this.divparent.firstElementChild, this.div1); + assert.equal(this.div1.nextElementSibling, div6); + assert.equal(div6.nextElementSibling, this.div2); + assert.equal(this.div2.nextElementSibling, this.div3); + + remove.apply(); + assert.equal(this.divparent.childElementCount, 3); + assert.equal(this.divparent.firstElementChild, this.div1); + assert.equal(this.div1.nextElementSibling, this.div2); + assert.equal(this.div2.nextElementSibling, this.div3); + }); + + it('Test ChangeElementCommand', function () { + this.div1.setAttribute('title', 'new title'); + let change = new hstory.ChangeElementCommand(this.div1, + {title: 'old title', class: 'foo'}); + assert.ok(change.unapply); + assert.ok(change.apply); + assert.equal(typeof change.unapply, typeof function () { /* */ }); + assert.equal(typeof change.apply, typeof function () { /* */ }); + + change.unapply(); + assert.equal(this.div1.getAttribute('title'), 'old title'); + assert.equal(this.div1.getAttribute('class'), 'foo'); + + change.apply(); + assert.equal(this.div1.getAttribute('title'), 'new title'); + assert.ok(!this.div1.getAttribute('class')); + + this.div1.textContent = 'inner text'; + change = new hstory.ChangeElementCommand(this.div1, + {'#text': null}); + + change.unapply(); + assert.ok(!this.div1.textContent); + + change.apply(); + assert.equal(this.div1.textContent, 'inner text'); + + this.div1.textContent = ''; + change = new hstory.ChangeElementCommand(this.div1, + {'#text': 'old text'}); + + change.unapply(); + assert.equal(this.div1.textContent, 'old text'); + + change.apply(); + assert.ok(!this.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 (elem) { return 0; } + }); + + gethrefvalue = '#newhref'; + change = new hstory.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'); + + const line = document.createElementNS(NS.SVG, 'line'); + line.setAttribute('class', 'newClass'); + change = new hstory.ChangeElementCommand(line, {class: 'oldClass'}); + + assert.ok(change.unapply); + assert.ok(change.apply); + assert.equal(typeof change.unapply, typeof function () { /* */ }); + assert.equal(typeof change.apply, typeof function () { /* */ }); + + 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 hstory.BatchCommand(); + assert.ok(batch.unapply); + assert.ok(batch.apply); + assert.ok(batch.addSubCommand); + assert.ok(batch.isEmpty); + assert.equal(typeof batch.unapply, typeof function () { /* */ }); + assert.equal(typeof batch.apply, typeof function () { /* */ }); + assert.equal(typeof batch.addSubCommand, typeof function () { /* */ }); + assert.equal(typeof batch.isEmpty, typeof 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 () { /* */ }; + MockCommand.prototype.unapply = function () { concatResult += this.text_; }; + concatResult = ''; + batch.unapply(); + assert.equal(concatResult, 'cba'); + + MockCommand.prototype.unapply = function () { /* */ }; + }); +}); diff --git a/cypress/integration/math.js b/cypress/integration/math.js new file mode 100644 index 00000000..62024313 --- /dev/null +++ b/cypress/integration/math.js @@ -0,0 +1,108 @@ +import '../../instrumented/jquery.min.js'; + +import {NS} from '../../instrumented/namespaces.js'; +import * as math from '../../instrumented/math.js'; + +describe('math', function () { + const svg = document.createElementNS(NS.SVG, 'svg'); + + it('Test svgedit.math package', function () { + assert.ok(math); + assert.ok(math.transformPoint); + assert.ok(math.isIdentity); + assert.ok(math.matrixMultiply); + assert.equal(typeof math.transformPoint, typeof function () { /* */ }); + assert.equal(typeof math.isIdentity, typeof function () { /* */ }); + assert.equal(typeof math.matrixMultiply, typeof function () { /* */ }); + }); + + it('Test svgedit.math.transformPoint() function', function () { + const {transformPoint} = math; + + const m = svg.createSVGMatrix(); + m.a = 1; m.b = 0; + m.c = 0; m.d = 1; + m.e = 0; m.f = 0; + let pt = transformPoint(100, 200, m); + assert.equal(pt.x, 100); + assert.equal(pt.y, 200); + + m.e = 300; m.f = 400; + pt = transformPoint(100, 200, m); + assert.equal(pt.x, 400); + assert.equal(pt.y, 600); + + m.a = 0.5; m.b = 0.75; + m.c = 1.25; m.d = 2; + pt = transformPoint(100, 200, m); + assert.equal(pt.x, 100 * m.a + 200 * m.c + m.e); + assert.equal(pt.y, 100 * m.b + 200 * m.d + m.f); + }); + + it('Test svgedit.math.isIdentity() function', function () { + assert.ok(math.isIdentity(svg.createSVGMatrix())); + + const m = svg.createSVGMatrix(); + m.a = 1; m.b = 0; + m.c = 0; m.d = 1; + m.e = 0; m.f = 0; + assert.ok(math.isIdentity(m)); + }); + + it('Test svgedit.math.matrixMultiply() function', function () { + const mult = math.matrixMultiply; + const {isIdentity} = math; + + // translate there and back + const tr1 = svg.createSVGMatrix().translate(100, 50), + tr2 = svg.createSVGMatrix().translate(-90, 0), + tr3 = svg.createSVGMatrix().translate(-10, -50); + let I = mult(tr1, tr2, tr3); + assert.ok(isIdentity(I), 'Expected identity matrix when translating there and back'); + + // rotate there and back + // TODO: currently Mozilla fails this when rotating back at -50 and then -40 degrees + // (b and c are *almost* zero, but not zero) + const rotThere = svg.createSVGMatrix().rotate(90), + rotBack = svg.createSVGMatrix().rotate(-90), // TODO: set this to -50 + rotBackMore = svg.createSVGMatrix().rotate(0); // TODO: set this to -40 + I = mult(rotThere, rotBack, rotBackMore); + assert.ok(isIdentity(I), 'Expected identity matrix when rotating there and back'); + + // scale up and down + const scaleUp = svg.createSVGMatrix().scale(4), + scaleDown = svg.createSVGMatrix().scaleNonUniform(0.25, 1), + scaleDownMore = svg.createSVGMatrix().scaleNonUniform(1, 0.25); + I = mult(scaleUp, scaleDown, scaleDownMore); + assert.ok(isIdentity(I), 'Expected identity matrix when scaling up and down'); + + // test multiplication with its inverse + I = mult(rotThere, rotThere.inverse()); + assert.ok(isIdentity(I), 'Expected identity matrix when multiplying a matrix by its inverse'); + I = mult(rotThere.inverse(), rotThere); + assert.ok(isIdentity(I), 'Expected identity matrix when multiplying a matrix by its inverse'); + }); + + it('Test svgedit.math.transformBox() function', function () { + const {transformBox} = math; + + const m = svg.createSVGMatrix(); + m.a = 1; m.b = 0; + m.c = 0; m.d = 1; + m.e = 0; m.f = 0; + + const r = transformBox(10, 10, 200, 300, m); + assert.equal(r.tl.x, 10); + assert.equal(r.tl.y, 10); + assert.equal(r.tr.x, 210); + assert.equal(r.tr.y, 10); + assert.equal(r.bl.x, 10); + assert.equal(r.bl.y, 310); + assert.equal(r.br.x, 210); + assert.equal(r.br.y, 310); + assert.equal(r.aabox.x, 10); + assert.equal(r.aabox.y, 10); + assert.equal(r.aabox.width, 200); + assert.equal(r.aabox.height, 300); + }); +}); diff --git a/cypress/integration/path.js b/cypress/integration/path.js new file mode 100644 index 00000000..e3a7ff4f --- /dev/null +++ b/cypress/integration/path.js @@ -0,0 +1,167 @@ +/* globals SVGPathSeg */ +import '../../instrumented/jquery.min.js'; + +import '../../instrumented/svgpathseg.js'; +import {NS} from '../../instrumented/namespaces.js'; +import * as utilities from '../../instrumented/utilities.js'; +import * as pathModule from '../../instrumented/path.js'; + +describe('path', function () { + /** + * @typedef {GenericArray} EditorContexts + * @property {module:path.EditorContext} 0 + * @property {module:path.EditorContext} 1 + */ + + /** + * @param {SVGSVGElement} [svg] + * @returns {EditorContexts} + */ + function getMockContexts (svg) { + svg = svg || document.createElementNS(NS.SVG, 'svg'); + const selectorParentGroup = document.createElementNS(NS.SVG, 'g'); + selectorParentGroup.setAttribute('id', 'selectorParentGroup'); + svg.append(selectorParentGroup); + return [ + /** + * @implements {module:path.EditorContext} + */ + { + getSVGRoot () { return svg; }, + getCurrentZoom () { return 1; } + }, + /** + * @implements {module:utilities.EditorContext} + */ + { + getDOMDocument () { return svg; }, + getDOMContainer () { return svg; }, + getSVGRoot () { return svg; } + } + ]; + } + + it('Test svgedit.path.replacePathSeg', function () { + const path = document.createElementNS(NS.SVG, 'path'); + path.setAttribute('d', 'M0,0 L10,11 L20,21Z'); + + const [mockPathContext, mockUtilitiesContext] = getMockContexts(); + pathModule.init(mockPathContext); + utilities.init(mockUtilitiesContext); + new pathModule.Path(path); // eslint-disable-line no-new + + assert.equal(path.pathSegList.getItem(1).pathSegTypeAsLetter, 'L'); + assert.equal(path.pathSegList.getItem(1).x, 10); + assert.equal(path.pathSegList.getItem(1).y, 11); + + pathModule.replacePathSeg(SVGPathSeg.PATHSEG_LINETO_REL, 1, [30, 31], path); + + assert.equal(path.pathSegList.getItem(1).pathSegTypeAsLetter, 'l'); + assert.equal(path.pathSegList.getItem(1).x, 30); + assert.equal(path.pathSegList.getItem(1).y, 31); + }); + + it('Test svgedit.path.Segment.setType simple', function () { + const path = document.createElementNS(NS.SVG, 'path'); + path.setAttribute('d', 'M0,0 L10,11 L20,21Z'); + + const [mockPathContext, mockUtilitiesContext] = getMockContexts(); + pathModule.init(mockPathContext); + utilities.init(mockUtilitiesContext); + new pathModule.Path(path); // eslint-disable-line no-new + + assert.equal(path.pathSegList.getItem(1).pathSegTypeAsLetter, 'L'); + assert.equal(path.pathSegList.getItem(1).x, 10); + assert.equal(path.pathSegList.getItem(1).y, 11); + + const segment = new pathModule.Segment(1, path.pathSegList.getItem(1)); + segment.setType(SVGPathSeg.PATHSEG_LINETO_REL, [30, 31]); + assert.equal(segment.item.pathSegTypeAsLetter, 'l'); + assert.equal(segment.item.x, 30); + assert.equal(segment.item.y, 31); + + // Also verify that the actual path changed. + assert.equal(path.pathSegList.getItem(1).pathSegTypeAsLetter, 'l'); + assert.equal(path.pathSegList.getItem(1).x, 30); + assert.equal(path.pathSegList.getItem(1).y, 31); + }); + + it('Test svgedit.path.Segment.setType with control points', function () { + // Setup the dom for a mock control group. + const svg = document.createElementNS(NS.SVG, 'svg'); + const path = document.createElementNS(NS.SVG, 'path'); + path.setAttribute('d', 'M0,0 C11,12 13,14 15,16 Z'); + svg.append(path); + + const [mockPathContext, mockUtilitiesContext] = getMockContexts(svg); + pathModule.init(mockPathContext); + utilities.init(mockUtilitiesContext); + const segment = new pathModule.Segment(1, path.pathSegList.getItem(1)); + segment.path = new pathModule.Path(path); + + assert.equal(path.pathSegList.getItem(1).pathSegTypeAsLetter, 'C'); + assert.equal(path.pathSegList.getItem(1).x1, 11); + assert.equal(path.pathSegList.getItem(1).y1, 12); + assert.equal(path.pathSegList.getItem(1).x2, 13); + assert.equal(path.pathSegList.getItem(1).y2, 14); + assert.equal(path.pathSegList.getItem(1).x, 15); + assert.equal(path.pathSegList.getItem(1).y, 16); + + segment.setType(SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL, [30, 31, 32, 33, 34, 35]); + assert.equal(path.pathSegList.getItem(1).pathSegTypeAsLetter, 'c'); + assert.equal(path.pathSegList.getItem(1).x1, 32); + assert.equal(path.pathSegList.getItem(1).y1, 33); + assert.equal(path.pathSegList.getItem(1).x2, 34); + assert.equal(path.pathSegList.getItem(1).y2, 35); + assert.equal(path.pathSegList.getItem(1).x, 30); + assert.equal(path.pathSegList.getItem(1).y, 31); + }); + + it('Test svgedit.path.Segment.move', function () { + const path = document.createElementNS(NS.SVG, 'path'); + path.setAttribute('d', 'M0,0 L10,11 L20,21Z'); + + const [mockPathContext, mockUtilitiesContext] = getMockContexts(); + pathModule.init(mockPathContext); + utilities.init(mockUtilitiesContext); + new pathModule.Path(path); // eslint-disable-line no-new + + assert.equal(path.pathSegList.getItem(1).pathSegTypeAsLetter, 'L'); + assert.equal(path.pathSegList.getItem(1).x, 10); + assert.equal(path.pathSegList.getItem(1).y, 11); + + const segment = new pathModule.Segment(1, path.pathSegList.getItem(1)); + segment.move(-3, 4); + assert.equal(path.pathSegList.getItem(1).pathSegTypeAsLetter, 'L'); + assert.equal(path.pathSegList.getItem(1).x, 7); + assert.equal(path.pathSegList.getItem(1).y, 15); + }); + + it('Test svgedit.path.Segment.moveCtrl', function () { + const path = document.createElementNS(NS.SVG, 'path'); + path.setAttribute('d', 'M0,0 C11,12 13,14 15,16 Z'); + + const [mockPathContext, mockUtilitiesContext] = getMockContexts(); + pathModule.init(mockPathContext); + utilities.init(mockUtilitiesContext); + new pathModule.Path(path); // eslint-disable-line no-new + + assert.equal(path.pathSegList.getItem(1).pathSegTypeAsLetter, 'C'); + assert.equal(path.pathSegList.getItem(1).x1, 11); + assert.equal(path.pathSegList.getItem(1).y1, 12); + assert.equal(path.pathSegList.getItem(1).x2, 13); + assert.equal(path.pathSegList.getItem(1).y2, 14); + assert.equal(path.pathSegList.getItem(1).x, 15); + assert.equal(path.pathSegList.getItem(1).y, 16); + + const segment = new pathModule.Segment(1, path.pathSegList.getItem(1)); + segment.moveCtrl(1, 100, -200); + assert.equal(path.pathSegList.getItem(1).pathSegTypeAsLetter, 'C'); + assert.equal(path.pathSegList.getItem(1).x1, 111); + assert.equal(path.pathSegList.getItem(1).y1, -188); + assert.equal(path.pathSegList.getItem(1).x2, 13); + assert.equal(path.pathSegList.getItem(1).y2, 14); + assert.equal(path.pathSegList.getItem(1).x, 15); + assert.equal(path.pathSegList.getItem(1).y, 16); + }); +}); diff --git a/cypress/integration/recalculate.js b/cypress/integration/recalculate.js new file mode 100644 index 00000000..adc37f38 --- /dev/null +++ b/cypress/integration/recalculate.js @@ -0,0 +1,153 @@ +import '../../instrumented/jquery.min.js'; + +import {NS} from '../../instrumented/namespaces.js'; +import * as utilities from '../../instrumented/utilities.js'; +import * as coords from '../../instrumented/coords.js'; +import * as recalculate from '../../instrumented/recalculate.js'; + +describe('recalculate', function () { + // eslint-disable-next-line no-shadow + const root = document.createElement('div'); + root.id = 'root'; + root.style.visibility = 'hidden'; + + const svgroot = document.createElementNS(NS.SVG, 'svg'); + svgroot.id = 'svgroot'; + root.append(svgroot); + const svg = document.createElementNS(NS.SVG, 'svg'); + svgroot.append(svg); + + let elemId = 1; + + /** + * Initilize modules to set up the tests. + * @returns {void} + */ + function setUp () { + utilities.init( + /** + * @implements {module:utilities.EditorContext} + */ + { + getSVGRoot () { return svg; }, + getDOMDocument () { return null; }, + getDOMContainer () { return null; } + } + ); + coords.init( + /** + * @implements {module:coords.EditorContext} + */ + { + getGridSnapping () { return false; }, + getDrawing () { + return { + getNextId () { return String(elemId++); } + }; + } + } + ); + recalculate.init( + /** + * @implements {module:recalculate.EditorContext} + */ + { + getSVGRoot () { return svg; }, + getStartTransform () { return ''; }, + setStartTransform () { /* */ } + } + ); + } + + let elem; + + /** + * Initialize for tests and set up `rect` element. + * @returns {void} + */ + function setUpRect () { + setUp(); + elem = document.createElementNS(NS.SVG, 'rect'); + elem.setAttribute('x', '200'); + elem.setAttribute('y', '150'); + elem.setAttribute('width', '250'); + elem.setAttribute('height', '120'); + svg.append(elem); + } + + /** + * Initialize for tests and set up `text` element with `tspan` child. + * @returns {void} + */ + function setUpTextWithTspan () { + setUp(); + elem = document.createElementNS(NS.SVG, 'text'); + elem.setAttribute('x', '200'); + elem.setAttribute('y', '150'); + + const tspan = document.createElementNS(NS.SVG, 'tspan'); + tspan.setAttribute('x', '200'); + tspan.setAttribute('y', '150'); + + const theText = document.createTextNode('Foo bar'); + tspan.append(theText); + elem.append(tspan); + svg.append(elem); + } + + /** + * Tear down the tests (empty the svg element). + * @returns {void} + */ + afterEach(() => { + while (svg.hasChildNodes()) { + svg.firstChild.remove(); + } + }); + + it('Test recalculateDimensions() on rect with identity matrix', function () { + setUpRect(); + elem.setAttribute('transform', 'matrix(1,0,0,1,0,0)'); + + recalculate.recalculateDimensions(elem); + + // Ensure that the identity matrix is swallowed and the element has no + // transform on it. + assert.equal(elem.hasAttribute('transform'), false); + }); + + it('Test recalculateDimensions() on rect with simple translate', function () { + setUpRect(); + elem.setAttribute('transform', 'translate(100,50)'); + + recalculate.recalculateDimensions(elem); + + assert.equal(elem.hasAttribute('transform'), false); + assert.equal(elem.getAttribute('x'), '300'); + assert.equal(elem.getAttribute('y'), '200'); + assert.equal(elem.getAttribute('width'), '250'); + assert.equal(elem.getAttribute('height'), '120'); + }); + + it('Test recalculateDimensions() on text w/tspan with simple translate', function () { + setUpTextWithTspan(); + elem.setAttribute('transform', 'translate(100,50)'); + + recalculate.recalculateDimensions(elem); + + // Ensure that the identity matrix is swallowed and the element has no + // transform on it. + assert.equal(elem.hasAttribute('transform'), false); + assert.equal(elem.getAttribute('x'), '300'); + assert.equal(elem.getAttribute('y'), '200'); + + const tspan = elem.firstElementChild; + assert.equal(tspan.getAttribute('x'), '300'); + assert.equal(tspan.getAttribute('y'), '200'); + }); + + // TODO: Since recalculateDimensions() and surrounding code is + // probably the largest, most complicated and strange piece of + // code in SVG-edit, we need to write a whole lot of unit tests + // for it here. +}); diff --git a/cypress/integration/sanitize.js b/cypress/integration/sanitize.js new file mode 100644 index 00000000..1f65d7be --- /dev/null +++ b/cypress/integration/sanitize.js @@ -0,0 +1,19 @@ +import '../../instrumented/jquery.min.js'; + +import {NS} from '../../instrumented/namespaces.js'; +import * as sanitize from '../../instrumented/sanitize.js'; + +describe('sanitize', function () { + const svg = document.createElementNS(NS.SVG, 'svg'); + + it('Test sanitizeSvg() strips ws from style attr', function () { + const rect = document.createElementNS(NS.SVG, 'rect'); + rect.setAttribute('style', 'stroke: blue ;\t\tstroke-width :\t\t40;'); + // sanitizeSvg() requires the node to have a parent and a document. + svg.append(rect); + sanitize.sanitizeSvg(rect); + + assert.equal(rect.getAttribute('stroke'), 'blue'); + assert.equal(rect.getAttribute('stroke-width'), '40'); + }); +}); diff --git a/cypress/integration/select.js b/cypress/integration/select.js new file mode 100644 index 00000000..6912b852 --- /dev/null +++ b/cypress/integration/select.js @@ -0,0 +1,130 @@ +import '../../instrumented/jquery.min.js'; + +import * as select from '../../instrumented/select.js'; +import {NS} from '../../instrumented/namespaces.js'; + +describe('select', function () { + const sandbox = document.createElement('div'); + sandbox.id = 'sandbox'; + + let svgroot; + let svgcontent; + const mockConfig = { + dimensions: [640, 480] + }; + + /** + * @implements {module:select.SVGFactory} + */ + const mockFactory = { + createSVGElement (jsonMap) { + const elem = document.createElementNS(NS.SVG, jsonMap.element); + Object.entries(jsonMap.attr).forEach(([attr, value]) => { + elem.setAttribute(attr, value); + }); + return elem; + }, + svgRoot () { return svgroot; }, + svgContent () { return svgcontent; } + }; + + /** + * Potentially reusable test set-up. + * @returns {void} + */ + beforeEach(() => { + svgroot = mockFactory.createSVGElement({ + element: 'svg', + attr: {id: 'svgroot'} + }); + svgcontent = svgroot.appendChild( + mockFactory.createSVGElement({ + element: 'svg', + attr: {id: 'svgcontent'} + }) + ); + /* const rect = */ svgcontent.appendChild( + mockFactory.createSVGElement({ + element: 'rect', + attr: { + id: 'rect', + x: '50', + y: '75', + width: '200', + height: '100' + } + }) + ); + sandbox.append(svgroot); + }); + + /* + function setUpWithInit () { + select.init(mockConfig, mockFactory); + } + */ + + /** + * Tear down the test by emptying our sandbox area. + * @returns {void} + */ + afterEach(() => { + while (sandbox.hasChildNodes()) { + sandbox.firstChild.remove(); + } + }); + + it('Test svgedit.select package', function () { + assert.ok(select); + assert.ok(select.Selector); + assert.ok(select.SelectorManager); + assert.ok(select.init); + assert.ok(select.getSelectorManager); + assert.equal(typeof select, typeof {}); + assert.equal(typeof select.Selector, typeof function () { /* */ }); + assert.equal(typeof select.SelectorManager, typeof function () { /* */ }); + assert.equal(typeof select.init, typeof function () { /* */ }); + assert.equal(typeof select.getSelectorManager, typeof function () { /* */ }); + }); + + it('Test Selector DOM structure', function () { + assert.ok(svgroot); + assert.ok(svgroot.hasChildNodes()); + + // Verify non-existence of Selector DOM nodes + assert.equal(svgroot.childNodes.length, 1); + assert.equal(svgroot.childNodes.item(0), svgcontent); + assert.ok(!svgroot.querySelector('#selectorParentGroup')); + + select.init(mockConfig, mockFactory); + + assert.equal(svgroot.childNodes.length, 3); + + // Verify existence of canvas background. + const cb = svgroot.childNodes.item(0); + assert.ok(cb); + assert.equal(cb.id, 'canvasBackground'); + + assert.ok(svgroot.childNodes.item(1)); + assert.equal(svgroot.childNodes.item(1), svgcontent); + + // Verify existence of selectorParentGroup. + const spg = svgroot.childNodes.item(2); + assert.ok(spg); + assert.equal(svgroot.querySelector('#selectorParentGroup'), spg); + assert.equal(spg.id, 'selectorParentGroup'); + assert.equal(spg.tagName, 'g'); + + // Verify existence of all grip elements. + assert.ok(spg.querySelector('#selectorGrip_resize_nw')); + assert.ok(spg.querySelector('#selectorGrip_resize_n')); + assert.ok(spg.querySelector('#selectorGrip_resize_ne')); + assert.ok(spg.querySelector('#selectorGrip_resize_e')); + assert.ok(spg.querySelector('#selectorGrip_resize_se')); + assert.ok(spg.querySelector('#selectorGrip_resize_s')); + assert.ok(spg.querySelector('#selectorGrip_resize_sw')); + assert.ok(spg.querySelector('#selectorGrip_resize_w')); + assert.ok(spg.querySelector('#selectorGrip_rotateconnector')); + assert.ok(spg.querySelector('#selectorGrip_rotate')); + }); +}); diff --git a/cypress/integration/svgtransformlist.js b/cypress/integration/svgtransformlist.js new file mode 100644 index 00000000..dc61ac3b --- /dev/null +++ b/cypress/integration/svgtransformlist.js @@ -0,0 +1,329 @@ +import '../../instrumented/jquery.min.js'; + +import {NS} from '../../instrumented/namespaces.js'; +import * as transformlist from '../../instrumented/svgtransformlist.js'; +import {disableSupportsNativeTransformLists} from '../../instrumented/browser.js'; + +import almostEqualsPlugin from '../support/assert-almostEquals.js'; +import expectOutOfBoundsExceptionPlugin from '../support/assert-expectOutOfBoundsException.js'; + +chai.use(almostEqualsPlugin); +chai.use(expectOutOfBoundsExceptionPlugin); + +describe('svgtransformlist', function () { + disableSupportsNativeTransformLists(); + + let svgroot, svgcontent, rect, circle; + + /** + * Set up tests, adding elements. + * @returns {void} + */ + beforeEach(() => { + document.body.textContent = ''; + svgroot = document.createElement('div'); + svgroot.id = 'svgroot'; + svgroot.style.visibility = 'hidden'; + document.body.append(svgroot); + + svgcontent = svgroot.appendChild(document.createElementNS(NS.SVG, 'svg')); + rect = svgcontent.appendChild(document.createElementNS(NS.SVG, 'rect')); + rect.id = 'r'; + circle = svgcontent.appendChild(document.createElementNS(NS.SVG, 'circle')); + circle.id = 'c'; + }); + + /** + * Tear down tests, emptying SVG root, and resetting list map. + * @returns {void} + */ + afterEach(() => { + transformlist.resetListMap(); + while (svgroot.hasChildNodes()) { + svgroot.firstChild.remove(); + } + }); + + it('Test svgedit.transformlist package', function () { + assert.ok(transformlist); + assert.ok(transformlist.getTransformList); + }); + + it('Test svgedit.transformlist.getTransformList() function', function () { + const rxform = transformlist.getTransformList(rect); + const cxform = transformlist.getTransformList(circle); + + assert.ok(rxform); + assert.ok(cxform); + assert.equal(typeof rxform, typeof {}); + assert.equal(typeof cxform, typeof {}); + }); + + it('Test SVGTransformList.numberOfItems property', function () { + const rxform = transformlist.getTransformList(rect); + + assert.equal(typeof rxform.numberOfItems, typeof 0); + assert.equal(rxform.numberOfItems, 0); + }); + + it('Test SVGTransformList.initialize()', function () { + const rxform = transformlist.getTransformList(rect); + const cxform = transformlist.getTransformList(circle); + + const t = svgcontent.createSVGTransform(); + assert.ok(t); + assert.ok(rxform.initialize); + assert.equal(typeof rxform.initialize, typeof function () { /* */ }); + rxform.initialize(t); + assert.equal(rxform.numberOfItems, 1); + assert.equal(cxform.numberOfItems, 0); + + // If a transform was already in a transform list, this should + // remove it from its old list and add it to this list. + cxform.initialize(t); + // This also fails in Firefox native. + // assert.equal(rxform.numberOfItems, 0, 'Did not remove transform from list before initializing another transformlist'); + assert.equal(cxform.numberOfItems, 1); + }); + + it('Test SVGTransformList.appendItem() and getItem()', function () { + const rxform = transformlist.getTransformList(rect); + const cxform = transformlist.getTransformList(circle); + + const t1 = svgcontent.createSVGTransform(), + t2 = svgcontent.createSVGTransform(), + t3 = svgcontent.createSVGTransform(); + + assert.ok(rxform.appendItem); + assert.ok(rxform.getItem); + assert.equal(typeof rxform.appendItem, typeof function () { /* */ }); + assert.equal(typeof rxform.getItem, typeof function () { /* */ }); + + rxform.appendItem(t1); + rxform.appendItem(t2); + rxform.appendItem(t3); + + assert.equal(rxform.numberOfItems, 3); + const rxf = rxform.getItem(0); + assert.equal(rxf, t1); + assert.equal(rxform.getItem(1), t2); + assert.equal(rxform.getItem(2), t3); + + assert.expectOutOfBoundsException(rxform, 'getItem', -1); + assert.expectOutOfBoundsException(rxform, 'getItem', 3); + cxform.appendItem(t1); + // These also fail in Firefox native. + // assert.equal(rxform.numberOfItems, 2, 'Did not remove a transform from a list before appending it to a new transformlist'); + // assert.equal(rxform.getItem(0), t2, 'Found the wrong transform in a transformlist'); + // assert.equal(rxform.getItem(1), t3, 'Found the wrong transform in a transformlist'); + + assert.equal(cxform.numberOfItems, 1); + assert.equal(cxform.getItem(0), t1); + }); + + it('Test SVGTransformList.removeItem()', function () { + const rxform = transformlist.getTransformList(rect); + + const t1 = svgcontent.createSVGTransform(), + t2 = svgcontent.createSVGTransform(); + assert.ok(rxform.removeItem); + assert.equal(typeof rxform.removeItem, typeof function () { /* */ }); + rxform.appendItem(t1); + rxform.appendItem(t2); + + const removedTransform = rxform.removeItem(0); + assert.equal(rxform.numberOfItems, 1); + assert.equal(removedTransform, t1); + assert.equal(rxform.getItem(0), t2); + + assert.expectOutOfBoundsException(rxform, 'removeItem', -1); + assert.expectOutOfBoundsException(rxform, 'removeItem', 1); + }); + + it('Test SVGTransformList.replaceItem()', function () { + const rxform = transformlist.getTransformList(rect); + const cxform = transformlist.getTransformList(circle); + + assert.ok(rxform.replaceItem); + assert.equal(typeof rxform.replaceItem, typeof function () { /* */ }); + + const t1 = svgcontent.createSVGTransform(), + t2 = svgcontent.createSVGTransform(), + t3 = svgcontent.createSVGTransform(); + + rxform.appendItem(t1); + rxform.appendItem(t2); + cxform.appendItem(t3); + + const newItem = rxform.replaceItem(t3, 0); + assert.equal(rxform.numberOfItems, 2); + assert.equal(newItem, t3); + assert.equal(rxform.getItem(0), t3); + assert.equal(rxform.getItem(1), t2); + // Fails in Firefox native + // assert.equal(cxform.numberOfItems, 0); + + // test replaceItem within a list + rxform.appendItem(t1); + rxform.replaceItem(t1, 0); + // Fails in Firefox native + // assert.equal(rxform.numberOfItems, 2); + assert.equal(rxform.getItem(0), t1); + assert.equal(rxform.getItem(1), t2); + }); + + it('Test SVGTransformList.insertItemBefore()', function () { + const rxform = transformlist.getTransformList(rect); + const cxform = transformlist.getTransformList(circle); + + assert.ok(rxform.insertItemBefore); + assert.equal(typeof rxform.insertItemBefore, typeof function () { /* */ }); + + const t1 = svgcontent.createSVGTransform(), + t2 = svgcontent.createSVGTransform(), + t3 = svgcontent.createSVGTransform(); + + rxform.appendItem(t1); + rxform.appendItem(t2); + cxform.appendItem(t3); + + const newItem = rxform.insertItemBefore(t3, 0); + assert.equal(rxform.numberOfItems, 3); + assert.equal(newItem, t3); + assert.equal(rxform.getItem(0), t3); + assert.equal(rxform.getItem(1), t1); + assert.equal(rxform.getItem(2), t2); + // Fails in Firefox native + // assert.equal(cxform.numberOfItems, 0); + + rxform.insertItemBefore(t2, 1); + // Fails in Firefox native (they make copies of the transforms) + // assert.equal(rxform.numberOfItems, 3); + assert.equal(rxform.getItem(0), t3); + assert.equal(rxform.getItem(1), t2); + assert.equal(rxform.getItem(2), t1); + }); + + it('Test SVGTransformList.init() for translate(200,100)', function () { + rect.setAttribute('transform', 'translate(200,100)'); + + const rxform = transformlist.getTransformList(rect); + assert.equal(rxform.numberOfItems, 1); + + const translate = rxform.getItem(0); + assert.equal(translate.type, 2); + + const m = translate.matrix; + assert.equal(m.a, 1); + assert.equal(m.b, 0); + assert.equal(m.c, 0); + assert.equal(m.d, 1); + assert.equal(m.e, 200); + assert.equal(m.f, 100); + }); + + it('Test SVGTransformList.init() for scale(4)', function () { + rect.setAttribute('transform', 'scale(4)'); + + const rxform = transformlist.getTransformList(rect); + assert.equal(rxform.numberOfItems, 1); + + const scale = rxform.getItem(0); + assert.equal(scale.type, 3); + + const m = scale.matrix; + assert.equal(m.a, 4); + assert.equal(m.b, 0); + assert.equal(m.c, 0); + assert.equal(m.d, 4); + assert.equal(m.e, 0); + assert.equal(m.f, 0); + }); + + it('Test SVGTransformList.init() for scale(4,3)', function () { + rect.setAttribute('transform', 'scale(4,3)'); + + const rxform = transformlist.getTransformList(rect); + assert.equal(rxform.numberOfItems, 1); + + const scale = rxform.getItem(0); + assert.equal(scale.type, 3); + + const m = scale.matrix; + assert.equal(m.a, 4); + assert.equal(m.b, 0); + assert.equal(m.c, 0); + assert.equal(m.d, 3); + assert.equal(m.e, 0); + assert.equal(m.f, 0); + }); + + it('Test SVGTransformList.init() for rotate(45)', function () { + rect.setAttribute('transform', 'rotate(45)'); + + const rxform = transformlist.getTransformList(rect); + assert.equal(rxform.numberOfItems, 1); + + const rotate = rxform.getItem(0); + assert.equal(rotate.type, 4); + assert.equal(rotate.angle, 45); + + const m = rotate.matrix; + assert.almostEquals(1 / Math.sqrt(2), m.a); + assert.almostEquals(1 / Math.sqrt(2), m.b); + assert.almostEquals(-1 / Math.sqrt(2), m.c); + assert.almostEquals(1 / Math.sqrt(2), m.d); + assert.equal(m.e, 0); + assert.equal(m.f, 0); + }); + + it('Test SVGTransformList.init() for rotate(45, 100, 200)', function () { + rect.setAttribute('transform', 'rotate(45, 100, 200)'); + + const rxform = transformlist.getTransformList(rect); + assert.equal(rxform.numberOfItems, 1); + + const rotate = rxform.getItem(0); + assert.equal(rotate.type, 4); + assert.equal(rotate.angle, 45); + + const m = rotate.matrix; + assert.almostEquals(m.a, 1 / Math.sqrt(2)); + assert.almostEquals(m.b, 1 / Math.sqrt(2)); + assert.almostEquals(m.c, -1 / Math.sqrt(2)); + assert.almostEquals(m.d, 1 / Math.sqrt(2)); + + const r = svgcontent.createSVGMatrix(); + r.a = 1 / Math.sqrt(2); r.b = 1 / Math.sqrt(2); + r.c = -1 / Math.sqrt(2); r.d = 1 / Math.sqrt(2); + + const t = svgcontent.createSVGMatrix(); + t.e = -100; t.f = -200; + + const t_ = svgcontent.createSVGMatrix(); + t_.e = 100; t_.f = 200; + + const result = t_.multiply(r).multiply(t); + + assert.almostEquals(m.e, result.e); + assert.almostEquals(m.f, result.f); + }); + + it('Test SVGTransformList.init() for matrix(1, 2, 3, 4, 5, 6)', function () { + rect.setAttribute('transform', 'matrix(1,2,3,4,5,6)'); + + const rxform = transformlist.getTransformList(rect); + assert.equal(rxform.numberOfItems, 1); + + const mt = rxform.getItem(0); + assert.equal(mt.type, 1); + + const m = mt.matrix; + assert.equal(m.a, 1); + assert.equal(m.b, 2); + assert.equal(m.c, 3); + assert.equal(m.d, 4); + assert.equal(m.e, 5); + assert.equal(m.f, 6); + }); +}); diff --git a/cypress/integration/test1.js b/cypress/integration/test1.js new file mode 100644 index 00000000..2909dc9b --- /dev/null +++ b/cypress/integration/test1.js @@ -0,0 +1,270 @@ +import '../../instrumented/jquery.min.js'; +import '../../instrumented/jquery-ui/jquery-ui-1.8.17.custom.min.js'; + +import '../../instrumented/svgpathseg.js'; +import SvgCanvas from '../../instrumented/svgcanvas.js'; + +describe('Basic Module', function () { + // helper functions + /* + const isIdentity = function (m) { + return (m.a === 1 && m.b === 0 && m.c === 0 && m.d === 1 && m.e === 0 && m.f === 0); + }; + const matrixString = function (m) { + return [m.a, m.b, m.c, m.d, m.e, m.f].join(','); + }; + */ + + let svgCanvas; + + const + // svgroot = document.getElementById('svgroot'), + // svgdoc = svgroot.documentElement, + svgns = 'http://www.w3.org/2000/svg', + xlinkns = 'http://www.w3.org/1999/xlink'; + + beforeEach(() => { + document.body.textContent = ''; + const svgEditor = document.createElement('div'); + svgEditor.id = 'svg_editor'; + const svgcanvas = document.createElement('div'); + svgcanvas.style.visibility = 'hidden'; + svgcanvas.id = 'svgcanvas'; + const workarea = document.createElement('div'); + workarea.id = 'workarea'; + workarea.append(svgcanvas); + const toolsLeft = document.createElement('div'); + toolsLeft.id = 'tools_left'; + const toolsFlyout = document.createElement('div'); + toolsFlyout.id = 'tools_flyout'; + + svgEditor.append(workarea, toolsLeft, toolsFlyout); + document.body.append(svgEditor); + + svgCanvas = new SvgCanvas( + document.getElementById('svgcanvas'), { + canvas_expansion: 3, + dimensions: [640, 480], + initFill: { + color: 'FF0000', // solid red + opacity: 1 + }, + initStroke: { + width: 5, + color: '000000', // solid black + opacity: 1 + }, + initOpacity: 1, + imgPath: '../editor/images/', + langPath: 'locale/', + extPath: 'extensions/', + extensions: ['ext-arrows.js', 'ext-connector.js', 'ext-eyedropper.js'], + initTool: 'select', + wireframe: false + } + ); + }); + + it('Test existence of SvgCanvas object', function () { + assert.equal(typeof {}, typeof svgCanvas); + }); + + describe('Path Module', function () { + it('Test path conversion from absolute to relative', function () { + const convert = svgCanvas.pathActions.convertPath; + + // TODO: Test these paths: + // "m400.00491,625.01379a1.78688,1.78688 0 1 1-3.57373,0a1.78688,1.78688 0 1 13.57373,0z" + // "m36.812,15.8566c-28.03099,0 -26.28099,12.15601 -26.28099,12.15601l0.03099,12.59399h26.75v3.781h-37.37399c0,0 -17.938,-2.034 -133.00001,26.25c115.06201,28.284 130.71801,27.281 130.71801,27.281h9.34399v-13.125c0,0 -0.504,-15.656 15.40601,-15.656h26.532c0,0 14.90599,0.241 14.90599,-14.406v-24.219c0,0 2.263,-14.65601 -27.032,-14.65601zm-14.75,8.4684c2.662,0 4.813,2.151 4.813,4.813c0,2.661 -2.151,4.812 -4.813,4.812c-2.661,0 -4.812,-2.151 -4.812,-4.812c0,-2.662 2.151,-4.813 4.812,-4.813z" + // "m 0,0 l 200,0 l 0,100 L 0,100" + + svgCanvas.setSvgString( + "" + + "" + + "" + + '' + ); + + const p1 = document.getElementById('p1'), + p2 = document.getElementById('p2'), + dAbs = p1.getAttribute('d'), + seglist = p1.pathSegList; + + assert.equal(p1.nodeName, 'path', "Expected 'path', got"); + + assert.equal(seglist.numberOfItems, 4, 'Number of segments before conversion'); + + // verify segments before conversion + let curseg = seglist.getItem(0); + assert.equal(curseg.pathSegTypeAsLetter.toUpperCase(), 'M', 'Before conversion, segment #1 type'); + curseg = seglist.getItem(1); + assert.equal(curseg.pathSegTypeAsLetter.toUpperCase(), 'L', 'Before conversion, segment #2 type'); + curseg = seglist.getItem(3); + assert.equal(curseg.pathSegTypeAsLetter.toUpperCase(), 'Z', 'Before conversion, segment #3 type' + dAbs); + + // convert and verify segments + let d = convert(p1, true); + assert.equal(d, 'm100,100l100,0l-100,0z', 'Converted path to relative string'); + + // TODO: see why this isn't working in SVG-edit + d = convert(p2, true); + console.log('Convert true', d); + d = convert(p2, false); + console.log('Convert false', d); + }); + }); + + describe('Import Module', function () { + it('Test import use', function () { + svgCanvas.setSvgString( + "" + + "" + + "" + + "" + + "" + + '' + ); + + const u = document.getElementById('the-use'), + fu = document.getElementById('foreign-use'), + nfu = document.getElementById('no-use'); + + assert.equal((u && u.nodeName === 'use'), true, 'Did not import element'); + assert.equal(fu, null, 'Removed element that had a foreign href'); + assert.equal(nfu, null, 'Removed element that had no href'); + }); + + // This test shows that an element with an invalid attribute is still parsed in properly + // and only the attribute is not imported + it('Test invalid attribute', function () { + svgCanvas.setSvgString( + '' + + 'words' + + '' + ); + + const t = document.getElementById('the-text'); + + assert.equal((t && t.nodeName === 'text'), true, 'Did not import element'); + assert.equal(t.getAttribute('d'), null, 'Imported a with a d attribute'); + }); + + // This test makes sure import/export properly handles namespaced attributes + it('Test importing/exporting namespaced attributes', function () { + /* const setStr = */ svgCanvas.setSvgString( + '' + + '' + + '' + + '' + ); + const attrVal = document.getElementById('se_test_elem').getAttributeNS('http://svg-edit.googlecode.com', 'foo'); + + assert.equal(attrVal === 'bar', true, 'Preserved namespaced attribute on import'); + + const output = svgCanvas.getSvgString(); + // } catch(e) {console.log(e)} + // console.log('output',output); + const hasXlink = output.includes('xmlns:xlink="http://www.w3.org/1999/xlink"'); + const hasSe = output.includes('xmlns:se='); + const hasFoo = output.includes('xmlns:foo='); + const hasAttr = output.includes('se:foo="bar"'); + + assert.equal(hasAttr, true, 'Preserved namespaced attribute on export'); + assert.equal(hasXlink, true, 'Included xlink: xmlns'); + assert.equal(hasSe, true, 'Included se: xmlns'); + assert.equal(hasFoo, false, 'Did not include foo: xmlns'); + }); + + it('Test import math elements inside a foreignObject', function () { + /* const set = */ svgCanvas.setSvgString( + '' + + '' + + '' + + 'A' + + '0' + + '' + + '' + + '' + + '' + ); + const fo = document.getElementById('fo'); + // we cannot use getElementById('math') because not all browsers understand MathML and do not know to use the @id attribute + // see Bug https://bugs.webkit.org/show_bug.cgi?id=35042 + const math = fo.firstChild; + + assert.equal(Boolean(math), true, 'Math element exists'); + assert.equal(math.nodeName, 'math', 'Math element has the proper nodeName'); + assert.equal(math.getAttribute('id'), 'm', 'Math element has an id'); + assert.equal(math.namespaceURI, 'http://www.w3.org/1998/Math/MathML', 'Preserved MathML namespace'); + }); + + it('Test importing SVG into existing drawing', function () { + /* const doc = */ svgCanvas.setSvgString( + '' + + 'Layer 1' + + '' + + '' + + '' + + '' + ); + + svgCanvas.importSvgString( + '' + + '' + + '' + + '' + ); + + const svgcontent = document.getElementById('svgcontent'), + circles = svgcontent.getElementsByTagNameNS(svgns, 'circle'), + rects = svgcontent.getElementsByTagNameNS(svgns, 'rect'), + ellipses = svgcontent.getElementsByTagNameNS(svgns, 'ellipse'); + assert.equal(circles.length, 2, 'Found two circles upon importing'); + assert.equal(rects.length, 1, 'Found one rectangle upon importing'); + assert.equal(ellipses.length, 1, 'Found one ellipse upon importing'); + }); + + it('Test importing SVG remaps IDs', function () { + /* const doc = */ svgCanvas.setSvgString( + '' + + 'Layer 1' + + '' + + '' + + '' + + '' + + '' + ); + + svgCanvas.importSvgString( + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + ); + + const svgcontent = document.getElementById('svgcontent'), + circles = svgcontent.getElementsByTagNameNS(svgns, 'circle'), + rects = svgcontent.getElementsByTagNameNS(svgns, 'rect'), + // ellipses = svgcontent.getElementsByTagNameNS(svgns, 'ellipse'), + defs = svgcontent.getElementsByTagNameNS(svgns, 'defs'), + // grads = svgcontent.getElementsByTagNameNS(svgns, 'linearGradient'), + uses = svgcontent.getElementsByTagNameNS(svgns, 'use'); + assert.notEqual(circles.item(0).id, 'svg_1', 'Circle not re-identified'); + assert.notEqual(rects.item(0).id, 'svg_3', 'Rectangle not re-identified'); + // TODO: determine why this test fails in WebKit browsers + // assert.equal(grads.length, 1, 'Linear gradient imported'); + const grad = defs.item(0).firstChild; + assert.notEqual(grad.id, 'svg_2', 'Linear gradient not re-identified'); + assert.notEqual(circles.item(0).getAttribute('fill'), 'url(#svg_2)', 'Circle fill value not remapped'); + assert.notEqual(rects.item(0).getAttribute('stroke'), 'url(#svg_2)', 'Rectangle stroke value not remapped'); + assert.notEqual(uses.item(0).getAttributeNS(xlinkns, 'href'), '#svg_3'); + }); + }); +}); diff --git a/cypress/integration/units.js b/cypress/integration/units.js new file mode 100644 index 00000000..340f2a64 --- /dev/null +++ b/cypress/integration/units.js @@ -0,0 +1,93 @@ +import '../../instrumented/jquery.min.js'; + +import * as units from '../../instrumented/units.js'; + +describe('units', function () { + /** + * Set up tests, supplying mock data. + * @returns {void} + */ + beforeEach(() => { + document.body.textContent = ''; + const anchor = document.createElement('div'); + anchor.id = 'anchor'; + anchor.style.visibility = 'hidden'; + + const elementsContainer = document.createElement('div'); + elementsContainer.id = 'elementsContainer'; + + const uniqueId = document.createElement('div'); + uniqueId.id = 'uniqueId'; + uniqueId.style.visibility = 'hidden'; + + const nonUniqueId = document.createElement('div'); + nonUniqueId.id = 'nonUniqueId'; + nonUniqueId.style.visibility = 'hidden'; + + elementsContainer.append(uniqueId, nonUniqueId); + + document.body.append(anchor, elementsContainer); + + units.init( + /** + * @implements {module:units.ElementContainer} + */ + { + getBaseUnit () { return 'cm'; }, + getHeight () { return 600; }, + getWidth () { return 800; }, + getRoundDigits () { return 4; }, + getElement (elementId) { return document.getElementById(elementId); } + } + ); + }); + + it('Test svgedit.units package', function () { + assert.ok(units); + assert.equal(typeof units, typeof {}); + }); + + it('Test svgedit.units.shortFloat()', function () { + assert.ok(units.shortFloat); + assert.equal(typeof units.shortFloat, typeof function () { /* */ }); + + const {shortFloat} = units; + assert.equal(shortFloat(0.00000001), 0); + assert.equal(shortFloat(1), 1); + assert.equal(shortFloat(3.45678), 3.4568); + assert.equal(shortFloat(1.23443), 1.2344); + assert.equal(shortFloat(1.23455), 1.2346); + }); + + it('Test svgedit.units.isValidUnit()', function () { + assert.ok(units.isValidUnit); + assert.equal(typeof units.isValidUnit, typeof function () { /* */ }); + + const {isValidUnit} = units; + assert.ok(isValidUnit('0')); + assert.ok(isValidUnit('1')); + assert.ok(isValidUnit('1.1')); + assert.ok(isValidUnit('-1.1')); + assert.ok(isValidUnit('.6mm')); + assert.ok(isValidUnit('-.6cm')); + assert.ok(isValidUnit('6000in')); + assert.ok(isValidUnit('6px')); + assert.ok(isValidUnit('6.3pc')); + assert.ok(isValidUnit('-0.4em')); + assert.ok(isValidUnit('-0.ex')); + assert.ok(isValidUnit('40.123%')); + + assert.equal(isValidUnit('id', 'uniqueId', document.getElementById('uniqueId')), true); + assert.equal(isValidUnit('id', 'newId', document.getElementById('uniqueId')), true); + assert.equal(isValidUnit('id', 'uniqueId'), false); + assert.equal(isValidUnit('id', 'uniqueId', document.getElementById('nonUniqueId')), false); + }); + + it('Test svgedit.units.convertUnit()', function () { + assert.ok(units.convertUnit); + assert.equal(typeof units.convertUnit, typeof function () { /* */ }); + // cm in default setup + assert.equal(units.convertUnit(42), 1.1113); + assert.equal(units.convertUnit(42, 'px'), 42); + }); +}); diff --git a/cypress/integration/utilities-bbox.js b/cypress/integration/utilities-bbox.js new file mode 100644 index 00000000..6ff5f494 --- /dev/null +++ b/cypress/integration/utilities-bbox.js @@ -0,0 +1,509 @@ +import '../../instrumented/jquery.min.js'; + +import '../../instrumented/svgpathseg.js'; +import {NS} from '../../instrumented/namespaces.js'; +import * as utilities from '../../instrumented/utilities.js'; +import * as transformlist from '../../instrumented/svgtransformlist.js'; +import * as math from '../../instrumented/math.js'; +import * as path from '../../instrumented/path.js'; +import setAssertionMethods from '../support/assert-close.js'; + +chai.use(setAssertionMethods); + +describe('utilities bbox', function () { + /** + * Create an SVG element for a mock. + * @param {module:utilities.SVGElementJSON} jsonMap + * @returns {SVGElement} + */ + function mockCreateSVGElement (jsonMap) { + const elem = document.createElementNS(NS.SVG, jsonMap.element); + Object.entries(jsonMap.attr).forEach(([attr, value]) => { + elem.setAttribute(attr, value); + }); + return elem; + } + let mockaddSVGElementFromJsonCallCount = 0; + + /** + * Mock of {@link module:utilities.EditorContext#addSVGElementFromJson}. + * @param {module:utilities.SVGElementJSON} json + * @returns {SVGElement} + */ + function mockaddSVGElementFromJson (json) { + const elem = mockCreateSVGElement(json); + svgroot.append(elem); + mockaddSVGElementFromJsonCallCount++; + return elem; + } + const mockPathActions = { + resetOrientation (pth) { + if (utilities.isNullish(pth) || pth.nodeName !== 'path') { return false; } + const tlist = transformlist.getTransformList(pth); + const m = math.transformListToTransform(tlist).matrix; + tlist.clear(); + pth.removeAttribute('transform'); + const segList = pth.pathSegList; + + const len = segList.numberOfItems; + // let lastX, lastY; + + for (let i = 0; i < len; ++i) { + const seg = segList.getItem(i); + const type = seg.pathSegType; + if (type === 1) { continue; } + const pts = []; + ['', 1, 2].forEach(function (n, j) { + const x = seg['x' + n], y = seg['y' + n]; + if (x !== undefined && y !== undefined) { + const pt = math.transformPoint(x, y, m); + pts.splice(pts.length, 0, pt.x, pt.y); + } + }); + path.replacePathSeg(type, i, pts, pth); + } + // path.reorientGrads(pth, m); + return undefined; + } + }; + + const EPSILON = 0.001; + + let svgroot; + beforeEach(() => { + document.body.textContent = ''; + + // const svg = document.createElementNS(NS.SVG, 'svg'); + const sandbox = document.createElement('div'); + sandbox.id = 'sandbox'; + document.body.append(sandbox); + + svgroot = mockCreateSVGElement({ + element: 'svg', + attr: {id: 'svgroot'} + }); + sandbox.append(svgroot); + + // We're reusing ID's so we need to do this for transforms. + transformlist.resetListMap(); + path.init(null); + mockaddSVGElementFromJsonCallCount = 0; + }); + + it('Test svgedit.utilities package', function () { + assert.ok(utilities); + assert.ok(utilities.getBBoxWithTransform); + assert.ok(utilities.getStrokedBBox); + assert.ok(utilities.getRotationAngleFromTransformList); + assert.ok(utilities.getRotationAngle); + }); + + it('Test getBBoxWithTransform and no transform', function () { + const {getBBoxWithTransform} = utilities; + + let elem = mockCreateSVGElement({ + element: 'path', + attr: {id: 'path', d: 'M0,1 L2,3'} + }); + svgroot.append(elem); + let bbox = getBBoxWithTransform(elem, mockaddSVGElementFromJson, mockPathActions); + assert.deepEqual(bbox, {x: 0, y: 1, width: 2, height: 2}); + assert.equal(mockaddSVGElementFromJsonCallCount, 0); + elem.remove(); + + elem = mockCreateSVGElement({ + element: 'rect', + attr: {id: 'rect', x: '0', y: '1', width: '5', height: '10'} + }); + svgroot.append(elem); + bbox = getBBoxWithTransform(elem, mockaddSVGElementFromJson, mockPathActions); + assert.deepEqual(bbox, {x: 0, y: 1, width: 5, height: 10}); + assert.equal(mockaddSVGElementFromJsonCallCount, 0); + elem.remove(); + + elem = mockCreateSVGElement({ + element: 'line', + attr: {id: 'line', x1: '0', y1: '1', x2: '5', y2: '6'} + }); + svgroot.append(elem); + bbox = getBBoxWithTransform(elem, mockaddSVGElementFromJson, mockPathActions); + assert.deepEqual(bbox, {x: 0, y: 1, width: 5, height: 5}); + assert.equal(mockaddSVGElementFromJsonCallCount, 0); + elem.remove(); + + elem = mockCreateSVGElement({ + element: 'rect', + attr: {id: 'rect', x: '0', y: '1', width: '5', height: '10'} + }); + const g = mockCreateSVGElement({ + element: 'g', + attr: {} + }); + g.append(elem); + svgroot.append(g); + bbox = getBBoxWithTransform(elem, mockaddSVGElementFromJson, mockPathActions); + assert.deepEqual(bbox, {x: 0, y: 1, width: 5, height: 10}); + assert.equal(mockaddSVGElementFromJsonCallCount, 0); + g.remove(); + }); + + it('Test getBBoxWithTransform and a rotation transform', function () { + const {getBBoxWithTransform} = utilities; + + let elem = mockCreateSVGElement({ + element: 'path', + attr: {id: 'path', d: 'M10,10 L20,20', transform: 'rotate(45 10,10)'} + }); + svgroot.append(elem); + let bbox = getBBoxWithTransform(elem, mockaddSVGElementFromJson, mockPathActions); + assert.close(bbox.x, 10, EPSILON); + assert.close(bbox.y, 10, EPSILON); + assert.close(bbox.width, 0, EPSILON); + assert.close(bbox.height, Math.sqrt(100 + 100), EPSILON); + elem.remove(); + + elem = mockCreateSVGElement({ + element: 'rect', + attr: {id: 'rect', x: '10', y: '10', width: '10', height: '20', transform: 'rotate(90 15,20)'} + }); + svgroot.append(elem); + bbox = getBBoxWithTransform(elem, mockaddSVGElementFromJson, mockPathActions); + assert.close(bbox.x, 5, EPSILON); + assert.close(bbox.y, 15, EPSILON); + assert.close(bbox.width, 20, EPSILON); + assert.close(bbox.height, 10, EPSILON); + assert.equal(mockaddSVGElementFromJsonCallCount, 1); + elem.remove(); + + const rect = {x: 10, y: 10, width: 10, height: 20}; + const angle = 45; + const origin = {x: 15, y: 20}; // eslint-disable-line no-shadow + elem = mockCreateSVGElement({ + element: 'rect', + attr: {id: 'rect2', x: rect.x, y: rect.y, width: rect.width, height: rect.height, transform: 'rotate(' + angle + ' ' + origin.x + ',' + origin.y + ')'} + }); + svgroot.append(elem); + mockaddSVGElementFromJsonCallCount = 0; + bbox = getBBoxWithTransform(elem, mockaddSVGElementFromJson, mockPathActions); + const r2 = rotateRect(rect, angle, origin); + assert.close(bbox.x, r2.x, EPSILON, 'rect2 x is ' + r2.x); + assert.close(bbox.y, r2.y, EPSILON, 'rect2 y is ' + r2.y); + assert.close(bbox.width, r2.width, EPSILON, 'rect2 width is' + r2.width); + assert.close(bbox.height, r2.height, EPSILON, 'rect2 height is ' + r2.height); + assert.equal(mockaddSVGElementFromJsonCallCount, 0); + elem.remove(); + + // Same as previous but wrapped with g and the transform is with the g. + elem = mockCreateSVGElement({ + element: 'rect', + attr: {id: 'rect3', x: rect.x, y: rect.y, width: rect.width, height: rect.height} + }); + const g = mockCreateSVGElement({ + element: 'g', + attr: {transform: 'rotate(' + angle + ' ' + origin.x + ',' + origin.y + ')'} + }); + g.append(elem); + svgroot.append(g); + mockaddSVGElementFromJsonCallCount = 0; + bbox = getBBoxWithTransform(g, mockaddSVGElementFromJson, mockPathActions); + assert.close(bbox.x, r2.x, EPSILON, 'rect2 x is ' + r2.x); + assert.close(bbox.y, r2.y, EPSILON, 'rect2 y is ' + r2.y); + assert.close(bbox.width, r2.width, EPSILON, 'rect2 width is' + r2.width); + assert.close(bbox.height, r2.height, EPSILON, 'rect2 height is ' + r2.height); + assert.equal(mockaddSVGElementFromJsonCallCount, 0); + g.remove(); + + elem = mockCreateSVGElement({ + element: 'ellipse', + attr: {id: 'ellipse1', cx: '100', cy: '100', rx: '50', ry: '50', transform: 'rotate(45 100,100)'} + }); + svgroot.append(elem); + mockaddSVGElementFromJsonCallCount = 0; + bbox = getBBoxWithTransform(elem, mockaddSVGElementFromJson, mockPathActions); + // TODO: the BBox algorithm is using the bezier control points to calculate the bounding box. Should be 50, 50, 100, 100. + assert.ok(bbox.x > 45 && bbox.x <= 50); + assert.ok(bbox.y > 45 && bbox.y <= 50); + assert.ok(bbox.width >= 100 && bbox.width < 110); + assert.ok(bbox.height >= 100 && bbox.height < 110); + assert.equal(mockaddSVGElementFromJsonCallCount, 1); + elem.remove(); + }); + + it('Test getBBoxWithTransform with rotation and matrix transforms', function () { + const {getBBoxWithTransform} = utilities; + + let tx = 10; // tx right + let ty = 10; // tx down + let txInRotatedSpace = Math.sqrt(tx * tx + ty * ty); // translate in rotated 45 space. + let tyInRotatedSpace = 0; + let matrix = 'matrix(1,0,0,1,' + txInRotatedSpace + ',' + tyInRotatedSpace + ')'; + let elem = mockCreateSVGElement({ + element: 'path', + attr: {id: 'path', d: 'M10,10 L20,20', transform: 'rotate(45 10,10) ' + matrix} + }); + svgroot.append(elem); + let bbox = getBBoxWithTransform(elem, mockaddSVGElementFromJson, mockPathActions); + assert.close(bbox.x, 10 + tx, EPSILON); + assert.close(bbox.y, 10 + ty, EPSILON); + assert.close(bbox.width, 0, EPSILON); + assert.close(bbox.height, Math.sqrt(100 + 100), EPSILON); + elem.remove(); + + txInRotatedSpace = tx; // translate in rotated 90 space. + tyInRotatedSpace = -ty; + matrix = 'matrix(1,0,0,1,' + txInRotatedSpace + ',' + tyInRotatedSpace + ')'; + elem = mockCreateSVGElement({ + element: 'rect', + attr: {id: 'rect', x: '10', y: '10', width: '10', height: '20', transform: 'rotate(90 15,20) ' + matrix} + }); + svgroot.append(elem); + bbox = getBBoxWithTransform(elem, mockaddSVGElementFromJson, mockPathActions); + assert.close(bbox.x, 5 + tx, EPSILON); + assert.close(bbox.y, 15 + ty, EPSILON); + assert.close(bbox.width, 20, EPSILON); + assert.close(bbox.height, 10, EPSILON); + elem.remove(); + + const rect = {x: 10, y: 10, width: 10, height: 20}; + const angle = 45; + const origin = {x: 15, y: 20}; // eslint-disable-line no-shadow + tx = 10; // tx right + ty = 10; // tx down + txInRotatedSpace = Math.sqrt(tx * tx + ty * ty); // translate in rotated 45 space. + tyInRotatedSpace = 0; + matrix = 'matrix(1,0,0,1,' + txInRotatedSpace + ',' + tyInRotatedSpace + ')'; + elem = mockCreateSVGElement({ + element: 'rect', + attr: {id: 'rect2', x: rect.x, y: rect.y, width: rect.width, height: rect.height, transform: 'rotate(' + angle + ' ' + origin.x + ',' + origin.y + ') ' + matrix} + }); + svgroot.append(elem); + bbox = getBBoxWithTransform(elem, mockaddSVGElementFromJson, mockPathActions); + const r2 = rotateRect(rect, angle, origin); + assert.close(bbox.x, r2.x + tx, EPSILON, 'rect2 x is ' + r2.x); + assert.close(bbox.y, r2.y + ty, EPSILON, 'rect2 y is ' + r2.y); + assert.close(bbox.width, r2.width, EPSILON, 'rect2 width is' + r2.width); + assert.close(bbox.height, r2.height, EPSILON, 'rect2 height is ' + r2.height); + elem.remove(); + + // Same as previous but wrapped with g and the transform is with the g. + elem = mockCreateSVGElement({ + element: 'rect', + attr: {id: 'rect3', x: rect.x, y: rect.y, width: rect.width, height: rect.height} + }); + const g = mockCreateSVGElement({ + element: 'g', + attr: {transform: 'rotate(' + angle + ' ' + origin.x + ',' + origin.y + ') ' + matrix} + }); + g.append(elem); + svgroot.append(g); + bbox = getBBoxWithTransform(g, mockaddSVGElementFromJson, mockPathActions); + assert.close(bbox.x, r2.x + tx, EPSILON, 'rect2 x is ' + r2.x); + assert.close(bbox.y, r2.y + ty, EPSILON, 'rect2 y is ' + r2.y); + assert.close(bbox.width, r2.width, EPSILON, 'rect2 width is' + r2.width); + assert.close(bbox.height, r2.height, EPSILON, 'rect2 height is ' + r2.height); + g.remove(); + + elem = mockCreateSVGElement({ + element: 'ellipse', + attr: {id: 'ellipse1', cx: '100', cy: '100', rx: '50', ry: '50', transform: 'rotate(45 100,100) ' + matrix} + }); + svgroot.append(elem); + bbox = getBBoxWithTransform(elem, mockaddSVGElementFromJson, mockPathActions); + // TODO: the BBox algorithm is using the bezier control points to calculate the bounding box. Should be 50, 50, 100, 100. + assert.ok(bbox.x > 45 + tx && bbox.x <= 50 + tx); + assert.ok(bbox.y > 45 + ty && bbox.y <= 50 + ty); + assert.ok(bbox.width >= 100 && bbox.width < 110); + assert.ok(bbox.height >= 100 && bbox.height < 110); + elem.remove(); + }); + + it('Test getStrokedBBox with stroke-width 10', function () { + const {getStrokedBBox} = utilities; + + const strokeWidth = 10; + let elem = mockCreateSVGElement({ + element: 'path', + attr: {id: 'path', d: 'M0,1 L2,3', 'stroke-width': strokeWidth} + }); + svgroot.append(elem); + let bbox = getStrokedBBox([elem], mockaddSVGElementFromJson, mockPathActions); + assert.deepEqual(bbox, {x: 0 - strokeWidth / 2, y: 1 - strokeWidth / 2, width: 2 + strokeWidth, height: 2 + strokeWidth}); + elem.remove(); + + elem = mockCreateSVGElement({ + element: 'rect', + attr: {id: 'rect', x: '0', y: '1', width: '5', height: '10', 'stroke-width': strokeWidth} + }); + svgroot.append(elem); + bbox = getStrokedBBox([elem], mockaddSVGElementFromJson, mockPathActions); + assert.deepEqual(bbox, {x: 0 - strokeWidth / 2, y: 1 - strokeWidth / 2, width: 5 + strokeWidth, height: 10 + strokeWidth}); + elem.remove(); + + elem = mockCreateSVGElement({ + element: 'line', + attr: {id: 'line', x1: '0', y1: '1', x2: '5', y2: '6', 'stroke-width': strokeWidth} + }); + svgroot.append(elem); + bbox = getStrokedBBox([elem], mockaddSVGElementFromJson, mockPathActions); + assert.deepEqual(bbox, {x: 0 - strokeWidth / 2, y: 1 - strokeWidth / 2, width: 5 + strokeWidth, height: 5 + strokeWidth}); + elem.remove(); + + elem = mockCreateSVGElement({ + element: 'rect', + attr: {id: 'rect', x: '0', y: '1', width: '5', height: '10', 'stroke-width': strokeWidth} + }); + const g = mockCreateSVGElement({ + element: 'g', + attr: {} + }); + g.append(elem); + svgroot.append(g); + bbox = getStrokedBBox([elem], mockaddSVGElementFromJson, mockPathActions); + assert.deepEqual(bbox, {x: 0 - strokeWidth / 2, y: 1 - strokeWidth / 2, width: 5 + strokeWidth, height: 10 + strokeWidth}); + g.remove(); + }); + + it("Test getStrokedBBox with stroke-width 'none'", function () { + const {getStrokedBBox} = utilities; + + let elem = mockCreateSVGElement({ + element: 'path', + attr: {id: 'path', d: 'M0,1 L2,3', 'stroke-width': 'none'} + }); + svgroot.append(elem); + let bbox = getStrokedBBox([elem], mockaddSVGElementFromJson, mockPathActions); + assert.deepEqual(bbox, {x: 0, y: 1, width: 2, height: 2}); + elem.remove(); + + elem = mockCreateSVGElement({ + element: 'rect', + attr: {id: 'rect', x: '0', y: '1', width: '5', height: '10', 'stroke-width': 'none'} + }); + svgroot.append(elem); + bbox = getStrokedBBox([elem], mockaddSVGElementFromJson, mockPathActions); + assert.deepEqual(bbox, {x: 0, y: 1, width: 5, height: 10}); + elem.remove(); + + elem = mockCreateSVGElement({ + element: 'line', + attr: {id: 'line', x1: '0', y1: '1', x2: '5', y2: '6', 'stroke-width': 'none'} + }); + svgroot.append(elem); + bbox = getStrokedBBox([elem], mockaddSVGElementFromJson, mockPathActions); + assert.deepEqual(bbox, {x: 0, y: 1, width: 5, height: 5}); + elem.remove(); + + elem = mockCreateSVGElement({ + element: 'rect', + attr: {id: 'rect', x: '0', y: '1', width: '5', height: '10', 'stroke-width': 'none'} + }); + const g = mockCreateSVGElement({ + element: 'g', + attr: {} + }); + g.append(elem); + svgroot.append(g); + bbox = getStrokedBBox([elem], mockaddSVGElementFromJson, mockPathActions); + assert.deepEqual(bbox, {x: 0, y: 1, width: 5, height: 10}); + g.remove(); + }); + + it('Test getStrokedBBox with no stroke-width attribute', function () { + const {getStrokedBBox} = utilities; + + let elem = mockCreateSVGElement({ + element: 'path', + attr: {id: 'path', d: 'M0,1 L2,3'} + }); + svgroot.append(elem); + let bbox = getStrokedBBox([elem], mockaddSVGElementFromJson, mockPathActions); + assert.deepEqual(bbox, {x: 0, y: 1, width: 2, height: 2}); + elem.remove(); + + elem = mockCreateSVGElement({ + element: 'rect', + attr: {id: 'rect', x: '0', y: '1', width: '5', height: '10'} + }); + svgroot.append(elem); + bbox = getStrokedBBox([elem], mockaddSVGElementFromJson, mockPathActions); + assert.deepEqual(bbox, {x: 0, y: 1, width: 5, height: 10}); + elem.remove(); + + elem = mockCreateSVGElement({ + element: 'line', + attr: {id: 'line', x1: '0', y1: '1', x2: '5', y2: '6'} + }); + svgroot.append(elem); + bbox = getStrokedBBox([elem], mockaddSVGElementFromJson, mockPathActions); + assert.deepEqual(bbox, {x: 0, y: 1, width: 5, height: 5}); + elem.remove(); + + elem = mockCreateSVGElement({ + element: 'rect', + attr: {id: 'rect', x: '0', y: '1', width: '5', height: '10'} + }); + const g = mockCreateSVGElement({ + element: 'g', + attr: {} + }); + g.append(elem); + svgroot.append(g); + bbox = getStrokedBBox([elem], mockaddSVGElementFromJson, mockPathActions); + assert.deepEqual(bbox, {x: 0, y: 1, width: 5, height: 10}); + g.remove(); + }); + + /** + * Returns radians for degrees. + * @param {Float} degrees + * @returns {Float} + */ + function radians (degrees) { + return degrees * Math.PI / 180; + } + + /** + * + * @param {module:utilities.BBoxObject} point + * @param {Float} angle + * @param {module:math.XYObject} origin + * @returns {module:math.XYObject} + */ + function rotatePoint (point, angle, origin) { // eslint-disable-line no-shadow + if (!origin) { + origin = {x: 0, y: 0}; + } + const x = point.x - origin.x; + const y = point.y - origin.y; + const theta = radians(angle); + return { + x: x * Math.cos(theta) + y * Math.sin(theta) + origin.x, + y: x * Math.sin(theta) + y * Math.cos(theta) + origin.y + }; + } + /** + * + * @param {module:utilities.BBoxObject} rect + * @param {Float} angle + * @param {module:math.XYObject} origin + * @returns {module:utilities.BBoxObject} + */ + function rotateRect (rect, angle, origin) { // eslint-disable-line no-shadow + const tl = rotatePoint({x: rect.x, y: rect.y}, angle, origin); + const tr = rotatePoint({x: rect.x + rect.width, y: rect.y}, angle, origin); + const br = rotatePoint({x: rect.x + rect.width, y: rect.y + rect.height}, angle, origin); + const bl = rotatePoint({x: rect.x, y: rect.y + rect.height}, angle, origin); + + const minx = Math.min(tl.x, tr.x, bl.x, br.x); + const maxx = Math.max(tl.x, tr.x, bl.x, br.x); + const miny = Math.min(tl.y, tr.y, bl.y, br.y); + const maxy = Math.max(tl.y, tr.y, bl.y, br.y); + + return { + x: minx, + y: miny, + width: (maxx - minx), + height: (maxy - miny) + }; + } +}); diff --git a/cypress/integration/utilities-performance.js b/cypress/integration/utilities-performance.js new file mode 100644 index 00000000..0f811498 --- /dev/null +++ b/cypress/integration/utilities-performance.js @@ -0,0 +1,239 @@ +import '../../instrumented/jquery.min.js'; + +import '../../instrumented/svgpathseg.js'; +import {NS} from '../../instrumented/namespaces.js'; +import * as utilities from '../../instrumented/utilities.js'; +import * as transformlist from '../../instrumented/svgtransformlist.js'; +import * as math from '../../instrumented/math.js'; + +describe('utilities performance', function () { + let currentLayer, groupWithMatrixTransform, textWithMatrixTransform; + beforeEach(() => { + document.body.textContent = ''; + const style = document.createElement('style'); + style.id = 'styleoverrides'; + style.media = 'screen'; + style.textContent = ` + #svgcanvas svg * { + cursor: move; + pointer-events: all + } + #svgcanvas svg { + cursor: default + }`; + + document.head.append(style); + + const editor = new DOMParser().parseFromString(`
+
+ + + + + + + +
+ + + + + + + + + Layer 1 + + + + + + + + + + + + Some text + + + + Layer 2 + + + + +
+
`, 'application/xml'); + const newNode = document.body.ownerDocument.importNode(editor.documentElement, true); + document.body.append(newNode); + + currentLayer = document.getElementById('layer1'); + groupWithMatrixTransform = document.getElementById('svg_group_with_matrix_transform'); + textWithMatrixTransform = document.getElementById('svg_text_with_matrix_transform'); + }); + + /** + * Create an SVG element for a mock. + * @param {module:utilities.SVGElementJSON} jsonMap + * @returns {SVGElement} + */ + function mockCreateSVGElement (jsonMap) { + const elem = document.createElementNS(NS.SVG, jsonMap.element); + Object.entries(jsonMap.attr).forEach(([attr, value]) => { + elem.setAttribute(attr, value); + }); + return elem; + } + + /** + * Mock of {@link module:utilities.EditorContext#addSVGElementFromJson}. + * @param {module:utilities.SVGElementJSON} json + * @returns {SVGElement} + */ + function mockaddSVGElementFromJson (json) { + const elem = mockCreateSVGElement(json); + currentLayer.append(elem); + return elem; + } + + /** + * Toward performance testing, fill document with clones of element. + * @param {SVGElement} elem + * @param {Integer} count + * @returns {void} + */ + function fillDocumentByCloningElement (elem, count) { + const elemId = elem.getAttribute('id') + '-'; + for (let index = 0; index < count; index++) { + const clone = elem.cloneNode(true); // t: deep clone + // Make sure you set a unique ID like a real document. + clone.setAttribute('id', elemId + index); + const {parentNode} = elem; + parentNode.append(clone); + } + } + + const mockPathActions = { + resetOrientation (path) { + if (utilities.isNullish(path) || path.nodeName !== 'path') { return false; } + const tlist = transformlist.getTransformList(path); + const m = math.transformListToTransform(tlist).matrix; + tlist.clear(); + path.removeAttribute('transform'); + const segList = path.pathSegList; + + const len = segList.numberOfItems; + // let lastX, lastY; + + for (let i = 0; i < len; ++i) { + const seg = segList.getItem(i); + const type = seg.pathSegType; + if (type === 1) { + continue; + } + const pts = []; + ['', 1, 2].forEach(function (n, j) { + const x = seg['x' + n], + y = seg['y' + n]; + if (x !== undefined && y !== undefined) { + const pt = math.transformPoint(x, y, m); + pts.splice(pts.length, 0, pt.x, pt.y); + } + }); + // path.replacePathSeg(type, i, pts, path); + } + + // utilities.reorientGrads(path, m); + return undefined; + } + }; + + // ////////////////////////////////////////////////////////// + // Performance times with various browsers on Macbook 2011 8MB RAM OS X El Capitan 10.11.4 + // + // To see 'Before Optimization' performance, making the following two edits. + // 1. utilities.getStrokedBBox - change if( elems.length === 1) to if( false && elems.length === 1) + // 2. utilities.getBBoxWithTransform - uncomment 'Old technique that was very slow' + + // Chrome + // Before Optimization + // Pass1 svgCanvas.getStrokedBBox total ms 4,218, ave ms 41.0, min/max 37 51 + // Pass2 svgCanvas.getStrokedBBox total ms 4,458, ave ms 43.3, min/max 32 63 + // Optimized Code + // Pass1 svgCanvas.getStrokedBBox total ms 1,112, ave ms 10.8, min/max 9 20 + // Pass2 svgCanvas.getStrokedBBox total ms 34, ave ms 0.3, min/max 0 20 + + // Firefox + // Before Optimization + // Pass1 svgCanvas.getStrokedBBox total ms 3,794, ave ms 36.8, min/max 33 48 + // Pass2 svgCanvas.getStrokedBBox total ms 4,049, ave ms 39.3, min/max 28 53 + // Optimized Code + // Pass1 svgCanvas.getStrokedBBox total ms 104, ave ms 1.0, min/max 0 23 + // Pass2 svgCanvas.getStrokedBBox total ms 71, ave ms 0.7, min/max 0 23 + + // Safari + // Before Optimization + // Pass1 svgCanvas.getStrokedBBox total ms 4,840, ave ms 47.0, min/max 45 62 + // Pass2 svgCanvas.getStrokedBBox total ms 4,849, ave ms 47.1, min/max 34 62 + // Optimized Code + // Pass1 svgCanvas.getStrokedBBox total ms 42, ave ms 0.4, min/max 0 23 + // Pass2 svgCanvas.getStrokedBBox total ms 17, ave ms 0.2, min/max 0 23 + + it('Test svgCanvas.getStrokedBBox() performance with matrix transforms', function () { + const {getStrokedBBox} = utilities; + const {children} = currentLayer; + + let lastTime, now, + min = Number.MAX_VALUE, + max = 0, + total = 0; + + fillDocumentByCloningElement(groupWithMatrixTransform, 50); + fillDocumentByCloningElement(textWithMatrixTransform, 50); + + // The first pass through all elements is slower. + const count = children.length; + const start = lastTime = now = Date.now(); + // Skip the first child which is the title. + for (let index = 1; index < count; index++) { + const child = children[index]; + /* const obj = */ getStrokedBBox([child], mockaddSVGElementFromJson, mockPathActions); + now = Date.now(); const delta = now - lastTime; lastTime = now; + total += delta; + min = Math.min(min, delta); + max = Math.max(max, delta); + } + total = lastTime - start; + const ave = total / count; + assert.ok(ave < 20, 'svgedit.utilities.getStrokedBBox average execution time is less than 20 ms'); + console.log('Pass1 svgCanvas.getStrokedBBox total ms ' + total + ', ave ms ' + ave.toFixed(1) + ',\t min/max ' + min + ' ' + max); + + // eslint-disable-next-line promise/avoid-new + return new Promise((resolve) => { + // The second pass is two to ten times faster. + setTimeout(function () { + const ct = children.length; + + const strt = lastTime = now = Date.now(); + // Skip the first child which is the title. + for (let index = 1; index < ct; index++) { + const child = children[index]; + /* const obj = */ getStrokedBBox([child], mockaddSVGElementFromJson, mockPathActions); + now = Date.now(); const delta = now - lastTime; lastTime = now; + total += delta; + min = Math.min(min, delta); + max = Math.max(max, delta); + } + + total = lastTime - strt; + const avg = total / ct; + assert.ok(avg < 2, 'svgedit.utilities.getStrokedBBox average execution time is less than 1 ms'); + console.log('Pass2 svgCanvas.getStrokedBBox total ms ' + total + ', ave ms ' + avg.toFixed(1) + ',\t min/max ' + min + ' ' + max); + + resolve(); + }); + }); + }); +}); diff --git a/cypress/integration/utilities.js b/cypress/integration/utilities.js new file mode 100644 index 00000000..69aafd13 --- /dev/null +++ b/cypress/integration/utilities.js @@ -0,0 +1,361 @@ +import '../../instrumented/jquery.min.js'; + +import * as browser from '../../instrumented/browser.js'; +import * as utilities from '../../instrumented/utilities.js'; +import {NS} from '../../instrumented/namespaces.js'; + +describe('utilities', function () { + /** + * Create an element for test. + * @param {module:utilities.SVGElementJSON} jsonMap + * @returns {SVGElement} + */ + function mockCreateSVGElement (jsonMap) { + const elem = document.createElementNS(NS.SVG, jsonMap.element); + Object.entries(jsonMap.attr).forEach(([attr, value]) => { + elem.setAttribute(attr, value); + }); + return elem; + } + /** + * Adds SVG Element per parameters and appends to root. + * @param {module:utilities.SVGElementJSON} json + * @returns {SVGElement} + */ + function mockaddSVGElementFromJson (json) { + const elem = mockCreateSVGElement(json); + svgroot.append(elem); + return elem; + } + const mockPathActions = {resetOrientation () { /* */ }}; + let mockHistorySubCommands = []; + const mockHistory = { + BatchCommand: class { + // eslint-disable-next-line class-methods-use-this + addSubCommand (cmd) { + mockHistorySubCommands.push(cmd); + } + }, + RemoveElementCommand: class { + // Longhand needed since used as a constructor + constructor (elem, nextSibling, parent) { + this.elem = elem; + this.nextSibling = nextSibling; + this.parent = parent; + } + }, + InsertElementCommand: class { + constructor (path) { // Longhand needed since used as a constructor + this.path = path; + } + } + }; + const mockCount = { + clearSelection: 0, + addToSelection: 0, + addCommandToHistory: 0 + }; + + /** + * Increments clear seleciton count for mock test. + * @returns {void} + */ + function mockClearSelection () { + mockCount.clearSelection++; + } + /** + * Increments add selection count for mock test. + * @returns {void} + */ + function mockAddToSelection () { + mockCount.addToSelection++; + } + /** + * Increments add command to history count for mock test. + * @returns {void} + */ + function mockAddCommandToHistory () { + mockCount.addCommandToHistory++; + } + + let svg, svgroot; + beforeEach(() => { + document.body.textContent = ''; + + mockHistorySubCommands = []; + mockCount.clearSelection = 0; + mockCount.addToSelection = 0; + mockCount.addCommandToHistory = 0; + + const sandbox = document.createElement('div'); + svg = document.createElementNS(NS.SVG, 'svg'); + svgroot = mockCreateSVGElement({ + element: 'svg', + attr: {id: 'svgroot'} + }); + sandbox.append(svgroot); + document.body.append(sandbox); + }); + + it('Test svgedit.utilities package', function () { + assert.ok(utilities); + assert.ok(utilities.toXml); + assert.equal(typeof utilities.toXml, typeof function () { /* */ }); + }); + + it('Test svgedit.utilities.toXml() function', function () { + const {toXml} = utilities; + + assert.equal(toXml('a'), 'a'); + assert.equal(toXml('ABC_'), 'ABC_'); + assert.equal(toXml('PB&J'), 'PB&J'); + assert.equal(toXml('2 < 5'), '2 < 5'); + assert.equal(toXml('5 > 2'), '5 > 2'); + assert.equal(toXml('\'<&>"'), ''<&>"'); + }); + + it('Test svgedit.utilities.fromXml() function', function () { + const {fromXml} = utilities; + + assert.equal(fromXml('a'), 'a'); + assert.equal(fromXml('ABC_'), 'ABC_'); + assert.equal(fromXml('PB&J'), 'PB&J'); + assert.equal(fromXml('2 < 5'), '2 < 5'); + assert.equal(fromXml('5 > 2'), '5 > 2'); + assert.equal(fromXml('<&>'), '<&>'); + }); + + it('Test svgedit.utilities.encode64() function', function () { + const {encode64} = utilities; + + assert.equal(encode64('abcdef'), 'YWJjZGVm'); + assert.equal(encode64('12345'), 'MTIzNDU='); + assert.equal(encode64(' '), 'IA=='); + assert.equal(encode64('`~!@#$%^&*()-_=+[{]}\\|;:\'",<.>/?'), 'YH4hQCMkJV4mKigpLV89K1t7XX1cfDs6JyIsPC4+Lz8='); + }); + + it('Test svgedit.utilities.decode64() function', function () { + const {decode64} = utilities; + + assert.equal(decode64('YWJjZGVm'), 'abcdef'); + assert.equal(decode64('MTIzNDU='), '12345'); + assert.equal(decode64('IA=='), ' '); + assert.equal(decode64('YH4hQCMkJV4mKigpLV89K1t7XX1cfDs6JyIsPC4+Lz8='), '`~!@#$%^&*()-_=+[{]}\\|;:\'",<.>/?'); + }); + + it('Test svgedit.utilities.convertToXMLReferences() function', function () { + const convert = utilities.convertToXMLReferences; + assert.equal(convert('ABC'), 'ABC'); + // assert.equal(convert('�BC'), 'ÀBC'); + }); + + it('Test svgedit.utilities.bboxToObj() function', function () { + const {bboxToObj} = utilities; + + const rect = svg.createSVGRect(); + rect.x = 1; + rect.y = 2; + rect.width = 3; + rect.height = 4; + + const obj = bboxToObj(rect); + assert.equal(typeof obj, typeof {}); + assert.equal(obj.x, 1); + assert.equal(obj.y, 2); + assert.equal(obj.width, 3); + assert.equal(obj.height, 4); + }); + + it('Test getUrlFromAttr', function () { + assert.equal(utilities.getUrlFromAttr('url(#foo)'), '#foo'); + assert.equal(utilities.getUrlFromAttr('url(somefile.svg#foo)'), 'somefile.svg#foo'); + assert.equal(utilities.getUrlFromAttr('url("#foo")'), '#foo'); + assert.equal(utilities.getUrlFromAttr('url("#foo")'), '#foo'); + }); + + it('Test getPathBBox', function () { + if (browser.supportsPathBBox()) { + return; + } + const doc = utilities.text2xml(''); + const path = doc.createElementNS(NS.SVG, 'path'); + path.setAttribute('d', 'm0,0l5,0l0,5l-5,0l0,-5z'); + const bb = utilities.getPathBBox(path); + assert.equal(typeof bb, 'object', 'BBox returned object'); + assert.ok(bb.x && !isNaN(bb.x)); + assert.ok(bb.y && !isNaN(bb.y)); + }); + + it('Test getPathDFromSegments', function () { + const {getPathDFromSegments} = utilities; + + const doc = utilities.text2xml(''); + const path = doc.createElementNS(NS.SVG, 'path'); + path.setAttribute('d', 'm0,0l5,0l0,5l-5,0l0,-5z'); + let d = getPathDFromSegments([ + ['M', [1, 2]], + ['Z', []] + ]); + assert.equal(d, 'M1,2 Z'); + + d = getPathDFromSegments([ + ['M', [1, 2]], + ['M', [3, 4]], + ['Z', []] + ]); + assert.equal(d, 'M1,2 M3,4 Z'); + + d = getPathDFromSegments([ + ['M', [1, 2]], + ['C', [3, 4, 5, 6]], + ['Z', []] + ]); + assert.equal(d, 'M1,2 C3,4 5,6 Z'); + }); + + it('Test getPathDFromElement', function () { + const {getPathDFromElement} = utilities; + + let elem = mockCreateSVGElement({ + element: 'path', + attr: {id: 'path', d: 'M0,1 Z'} + }); + svgroot.append(elem); + assert.equal(getPathDFromElement(elem), 'M0,1 Z'); + elem.remove(); + + elem = mockCreateSVGElement({ + element: 'rect', + attr: {id: 'rect', x: '0', y: '1', width: '5', height: '10'} + }); + svgroot.append(elem); + assert.equal(getPathDFromElement(elem), 'M0,1 L5,1 L5,11 L0,11 L0,1 Z'); + elem.remove(); + + elem = mockCreateSVGElement({ + element: 'rect', + attr: {id: 'roundrect', x: '0', y: '1', rx: '2', ry: '3', width: '10', height: '11'} + }); + svgroot.append(elem); + const closeEnough = /M0,4 C0,2.3\d* 0.9\d*,1 2,1 L8,1 C9.0\d*,1 10,2.3\d* 10,4 L10,9 C10,10.6\d* 9.08675799086758,12 8,12 L2,12 C0.9\d*,12 0,10.6\d* 0,9 L0,4 Z/; + assert.equal(closeEnough.test(getPathDFromElement(elem)), true); + elem.remove(); + + elem = mockCreateSVGElement({ + element: 'line', + attr: {id: 'line', x1: '0', y1: '1', x2: '5', y2: '6'} + }); + svgroot.append(elem); + assert.equal(getPathDFromElement(elem), 'M0,1L5,6'); + elem.remove(); + + elem = mockCreateSVGElement({ + element: 'circle', + attr: {id: 'circle', cx: '10', cy: '11', rx: '5', ry: '10'} + }); + svgroot.append(elem); + assert.equal(getPathDFromElement(elem), 'M10,11 C10,11 10,11 10,11 C10,11 10,11 10,11 C10,11 10,11 10,11 C10,11 10,11 10,11 Z'); + elem.remove(); + + elem = mockCreateSVGElement({ + element: 'polyline', + attr: {id: 'polyline', points: '0,1 5,1 5,11 0,11'} + }); + svgroot.append(elem); + assert.equal(getPathDFromElement(elem), 'M0,1 5,1 5,11 0,11'); + elem.remove(); + + assert.equal(getPathDFromElement({tagName: 'something unknown'}), undefined); + }); + + it('Test getBBoxOfElementAsPath', function () { + /** + * Wrap `utilities.getBBoxOfElementAsPath` to convert bbox to object for testing. + * @type {module:utilities.getBBoxOfElementAsPath} + */ + function getBBoxOfElementAsPath (elem, addSVGElementFromJson, pathActions) { + const bbox = utilities.getBBoxOfElementAsPath(elem, addSVGElementFromJson, pathActions); + return utilities.bboxToObj(bbox); // need this for assert.equal() to work. + } + + let elem = mockCreateSVGElement({ + element: 'path', + attr: {id: 'path', d: 'M0,1 Z'} + }); + svgroot.append(elem); + let bbox = getBBoxOfElementAsPath(elem, mockaddSVGElementFromJson, mockPathActions); + assert.deepEqual(bbox, {x: 0, y: 1, width: 0, height: 0}); + elem.remove(); + + elem = mockCreateSVGElement({ + element: 'rect', + attr: {id: 'rect', x: '0', y: '1', width: '5', height: '10'} + }); + svgroot.append(elem); + bbox = getBBoxOfElementAsPath(elem, mockaddSVGElementFromJson, mockPathActions); + assert.deepEqual(bbox, {x: 0, y: 1, width: 5, height: 10}); + elem.remove(); + + elem = mockCreateSVGElement({ + element: 'line', + attr: {id: 'line', x1: '0', y1: '1', x2: '5', y2: '6'} + }); + svgroot.append(elem); + bbox = getBBoxOfElementAsPath(elem, mockaddSVGElementFromJson, mockPathActions); + assert.deepEqual(bbox, {x: 0, y: 1, width: 5, height: 5}); + elem.remove(); + + // TODO: test element with transform. Need resetOrientation above to be working or mock it. + }); + + it('Test convertToPath rect', function () { + const {convertToPath} = utilities; + const attrs = { + fill: 'red', + stroke: 'white', + 'stroke-width': '1', + visibility: 'hidden' + }; + + const elem = mockCreateSVGElement({ + element: 'rect', + attr: {id: 'rect', x: '0', y: '1', width: '5', height: '10'} + }); + svgroot.append(elem); + const path = convertToPath(elem, attrs, mockaddSVGElementFromJson, mockPathActions, mockClearSelection, mockAddToSelection, mockHistory, mockAddCommandToHistory); + assert.equal(path.getAttribute('d'), 'M0,1 L5,1 L5,11 L0,11 L0,1 Z'); + assert.equal(path.getAttribute('visibilituy'), null); + assert.equal(path.id, 'rect'); + assert.equal(path.parentNode, svgroot); + assert.equal(elem.parentNode, null); + assert.equal(mockHistorySubCommands.length, 2); + assert.equal(mockCount.clearSelection, 1); + assert.equal(mockCount.addToSelection, 1); + assert.equal(mockCount.addCommandToHistory, 1); + path.remove(); + }); + + it('Test convertToPath unknown element', function () { + const {convertToPath} = utilities; + const attrs = { + fill: 'red', + stroke: 'white', + 'stroke-width': '1', + visibility: 'hidden' + }; + + const elem = { + tagName: 'something unknown', + id: 'something-unknown', + getAttribute (attr) { return ''; }, + parentNode: svgroot + }; + const path = convertToPath(elem, attrs, mockaddSVGElementFromJson, mockPathActions, mockClearSelection, mockAddToSelection, mockHistory, mockAddCommandToHistory); + assert.equal(path, null); + assert.equal(elem.parentNode, svgroot); + assert.equal(mockHistorySubCommands.length, 0); + assert.equal(mockCount.clearSelection, 0); + assert.equal(mockCount.addToSelection, 0); + assert.equal(mockCount.addCommandToHistory, 0); + }); +}); diff --git a/cypress/support/assert-almostEquals.js b/cypress/support/assert-almostEquals.js new file mode 100644 index 00000000..08451b50 --- /dev/null +++ b/cypress/support/assert-almostEquals.js @@ -0,0 +1,29 @@ +import assertionWrapper from './assertion-wrapper.js'; + +const NEAR_ZERO = 5e-6; // 0.000005, Firefox fails at higher levels of precision. + +/** + * Checks that the supplied values are equal with a high though not absolute degree of precision. + * @param {Float} actual + * @param {Float} expected + * @param {string} message + * @returns {void} + */ +function almostEquals (actual, expected, message) { + message = message || (actual + ' did not equal ' + expected); + const result = Math.abs(actual - expected) < NEAR_ZERO; + return {result, message, actual, expected}; +} + +/** + * @param {external:chai} _chai + * @param {external:chai_utils} utils + * @returns {void} + */ +function setAssertionMethods (_chai, utils) { + const wrap = assertionWrapper(_chai, utils); + + assert.almostEquals = wrap(almostEquals); +} + +export default setAssertionMethods; diff --git a/test/qunit/qunit-assert-close.js b/cypress/support/assert-close.js similarity index 79% rename from test/qunit/qunit-assert-close.js rename to cypress/support/assert-close.js index e737b3e6..f5947f2d 100644 --- a/test/qunit/qunit-assert-close.js +++ b/cypress/support/assert-close.js @@ -1,3 +1,13 @@ +import assertionWrapper from './assertion-wrapper.js'; + +/** +* @typedef {PlainObject} InfoObject +* @property {boolean} result +* @property {string} message +* @property {Float} actual +* @property {Float} expected +*/ + /** * Checks that the first two arguments are equal, or are numbers close enough to be considered equal * based on a specified maximum allowable difference. @@ -8,26 +18,26 @@ * @param {Float} expected * @param {Float} maxDifference (the maximum inclusive difference allowed between the actual and expected numbers) * @param {string} [message] Defaults to structured message - * @returns {void} + * @returns {InfoObject} */ function close (actual, expected, maxDifference, message) { const actualDiff = (actual === expected) ? 0 : Math.abs(actual - expected), result = actualDiff <= maxDifference; message = message || (actual + ' should be within ' + maxDifference + ' (inclusive) of ' + expected + (result ? '' : '. Actual: ' + actualDiff)); - this.pushResult({result, actual, expected, message}); + return {result, message, actual, expected}; } /** * Checks that the first two arguments are equal, or are numbers close enough to be considered equal * based on a specified maximum allowable difference percentage. * - * @example assert.close.percent(155, 150, 3.4); // Difference is ~3.33% + * @example assert.closePercent(155, 150, 3.4); // Difference is ~3.33% * * @param {Float} actual * @param {Float} expected * @param {Float} maxPercentDifference (the maximum inclusive difference percentage allowed between the actual and expected numbers) * @param {string} [message] Defaults to a structured message - * @returns {void} + * @returns {InfoObject} */ function closePercent (actual, expected, maxPercentDifference, message) { let actualDiff, result; @@ -44,7 +54,7 @@ function closePercent (actual, expected, maxPercentDifference, message) { } message = message || (actual + ' should be within ' + maxPercentDifference + '% (inclusive) of ' + expected + (result ? '' : '. Actual: ' + actualDiff + '%')); - this.pushResult(result, actual, expected, message); + return {result, message, actual, expected}; } /** @@ -57,26 +67,26 @@ function closePercent (actual, expected, maxPercentDifference, message) { * @param {Float} expected * @param {Float} minDifference (the minimum exclusive difference allowed between the actual and expected numbers) * @param {string} [message] Defaults to structured message - * @returns {void} + * @returns {InfoObject} */ function notClose (actual, expected, minDifference, message) { const actualDiff = Math.abs(actual - expected), result = actualDiff > minDifference; message = message || (actual + ' should not be within ' + minDifference + ' (exclusive) of ' + expected + (result ? '' : '. Actual: ' + actualDiff)); - this.pushResult(result, actual, expected, message); + return {result, message, actual, expected}; } /** * Checks that the first two arguments are numbers with differences greater than the specified * minimum difference percentage. * - * @example assert.notClose.percent(156, 150, 3.5); // Difference is 4.0% + * @example assert.notClosePercent(156, 150, 3.5); // Difference is 4.0% * * @param {Float} actual * @param {Float} expected * @param {Float} minPercentDifference (the minimum exclusive difference percentage allowed between the actual and expected numbers) * @param {string} [message] Defaults to a structured message - * @returns {void} + * @returns {InfoObject} */ function notClosePercent (actual, expected, minPercentDifference, message) { let actualDiff, result; @@ -93,19 +103,21 @@ function notClosePercent (actual, expected, minPercentDifference, message) { } message = message || (actual + ' should not be within ' + minPercentDifference + '% (exclusive) of ' + expected + (result ? '' : '. Actual: ' + actualDiff + '%')); - this.pushResult({result, actual, expected, message}); + return {result, message, actual, expected}; } /** - * @param {external:qunit} QUnit - * @returns {external:qunit} The same instance passed in after extending + * @param {external:chai} _chai + * @param {external:chai_utils} utils + * @returns {void} */ -export default function extend (QUnit) { - QUnit.extend(QUnit.assert, { - close, - closePercent, - notClose, - notClosePercent - }); - return QUnit; +function setAssertionMethods (_chai, utils) { + const wrap = assertionWrapper(_chai, utils); + + assert.close = wrap(close); + assert.closePercent = wrap(closePercent); + assert.notClose = wrap(notClose); + assert.notClosePercent = wrap(notClosePercent); } + +export default setAssertionMethods; diff --git a/test/qunit/qunit-assert-expectOutOfBoundsException.js b/cypress/support/assert-expectOutOfBoundsException.js similarity index 53% rename from test/qunit/qunit-assert-expectOutOfBoundsException.js rename to cypress/support/assert-expectOutOfBoundsException.js index 1342f60f..9285ecfc 100644 --- a/test/qunit/qunit-assert-expectOutOfBoundsException.js +++ b/cypress/support/assert-expectOutOfBoundsException.js @@ -1,3 +1,5 @@ +import assertionWrapper from './assertion-wrapper.js'; + /** * Expects an out of bounds `INDEX_SIZE_ERR` exception. * @param {GenericObject} obj @@ -17,16 +19,18 @@ function expectOutOfBoundsException (obj, fn, arg1) { } } const actual = result; - this.pushResult({result, actual, expected, message}); + return {result, message, actual, expected}; } /** - * @param {external:qunit} QUnit - * @returns {external:qunit} The same instance passed in after extending + * @param {external:chai} _chai + * @param {external:chai_utils} utils + * @returns {void} */ -export default function extend (QUnit) { - QUnit.extend(QUnit.assert, { - expectOutOfBoundsException - }); - return QUnit; +function setAssertionMethods (_chai, utils) { + const wrap = assertionWrapper(_chai, utils); + + assert.expectOutOfBoundsException = wrap(expectOutOfBoundsException); } + +export default setAssertionMethods; diff --git a/cypress/support/assertion-wrapper.js b/cypress/support/assertion-wrapper.js new file mode 100644 index 00000000..e12248be --- /dev/null +++ b/cypress/support/assertion-wrapper.js @@ -0,0 +1,15 @@ +/** + * @param {external:chai} _chai + * @param {external:chai_utils} utils + * @returns {void} + */ +function setAssertionMethods (_chai, utils) { + return (method) => { + return (...args) => { + const {result, message, actual, expected} = method(...args); + const assertion = new _chai.Assertion(); + assertion.assert(result, `Expected ${actual} to be ${expected}`, message); + }; + }; +} +export default setAssertionMethods; diff --git a/cypress/support/copy.js b/cypress/support/copy.js index b53f83b4..db243b1f 100644 --- a/cypress/support/copy.js +++ b/cypress/support/copy.js @@ -10,6 +10,5 @@ copyfiles([ ], { up: 1 }, () => { - // eslint-disable-next-line no-console console.log('Done'); }); diff --git a/package-lock.json b/package-lock.json index ff4beea1..f242c630 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1664,42 +1664,6 @@ "integrity": "sha512-7XHoRB61hxsz8lBQrjC1tq/3OEIgpvGWg6DKAdwi7WRzruwkmsdwmOoUXbU4Dtd4RSOMDwed0SkP3y8UlMt1Bg==", "dev": true }, - "@sinonjs/commons": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.6.0.tgz", - "integrity": "sha512-w4/WHG7C4WWFyE5geCieFJF6MZkbW4VAriol5KlmQXpAQdxvV0p26sqNZOW6Qyw6Y0l9K4g+cHvvczR2sEEpqg==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/formatio": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.1.tgz", - "integrity": "sha512-tsHvOB24rvyvV2+zKMmPkZ7dXX6LSLKZ7aOtXY6Edklp0uRcgGpOsQTTGTcWViFyx4uhWc6GV8QdnALbIbIdeQ==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^3.1.0" - } - }, - "@sinonjs/samsam": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", - "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.3.0", - "array-from": "^2.1.1", - "lodash": "^4.17.15" - } - }, - "@sinonjs/text-encoding": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", - "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", - "dev": true - }, "@textlint/ast-node-types": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-4.2.5.tgz", @@ -2042,12 +2006,6 @@ "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", "dev": true }, - "array-from": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", - "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", - "dev": true - }, "array-includes": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", @@ -4406,12 +4364,6 @@ "minimist": "^1.1.1" } }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, "diffie-hellman": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", @@ -5106,12 +5058,6 @@ "integrity": "sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==", "dev": true }, - "eslint-plugin-qunit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-qunit/-/eslint-plugin-qunit-4.0.0.tgz", - "integrity": "sha512-+0i2xcYryUoLawi47Lp0iJKzkP931G5GXwIOq1KBKQc2pknV1VPjfE6b4mI2mR2RnL7WRoS30YjwC9SjQgJDXQ==", - "dev": true - }, "eslint-plugin-sonarjs": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.5.0.tgz", @@ -7045,12 +6991,6 @@ "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", "dev": true }, - "js-reporters": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/js-reporters/-/js-reporters-1.2.1.tgz", - "integrity": "sha1-+IxgjjJKM3OpW8xFrTBeXJecRZs=", - "dev": true - }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -7259,12 +7199,6 @@ "verror": "1.10.0" } }, - "just-extend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz", - "integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==", - "dev": true - }, "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", @@ -7798,12 +7732,6 @@ } } }, - "lolex": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-4.2.0.tgz", - "integrity": "sha512-gKO5uExCXvSm6zbF562EvM+rd1kQDnB9AZBbiQVzf1ZmdDpxUSvpnAaVOP83N/31mRK8Ml8/VE8DMvsAZQ+7wg==", - "dev": true - }, "longest-streak": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.3.tgz", @@ -8357,19 +8285,6 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, - "nise": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.2.tgz", - "integrity": "sha512-/6RhOUlicRCbE9s+94qCUsyE+pKlVJ5AhIv+jEE7ESKwnbXqulKZ1FYU+XAtHHWE9TinYvAxDUJAb912PwPoWA==", - "dev": true, - "requires": { - "@sinonjs/formatio": "^3.2.1", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "lolex": "^4.1.0", - "path-to-regexp": "^1.7.0" - } - }, "node-environment-flags": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", @@ -8406,12 +8321,6 @@ "optimist": ">=0.3.4" } }, - "node-watch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/node-watch/-/node-watch-0.6.1.tgz", - "integrity": "sha512-gwQiR7weFRV8mAtT0x0kXkZ18dfRLB45xH7q0hCOVQMLfLb2f1ZaSvR57q4/b/Vj6B0RwMNJYbvb69e1yM7qEA==", - "dev": true - }, "noms": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/noms/-/noms-0.0.0.tgz", @@ -9096,23 +9005,6 @@ "integrity": "sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I=", "dev": true }, - "path-to-regexp": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", - "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", - "dev": true, - "requires": { - "isarray": "0.0.1" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - } - } - }, "path-type": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", @@ -9345,36 +9237,6 @@ "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=", "dev": true }, - "qunit": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/qunit/-/qunit-2.9.3.tgz", - "integrity": "sha512-RH4VYSaVsNRDthMFFboTJAJ8q4kJM5LvOqWponKUYPEAeOcmc/YFV1QsZ7ikknA3TjqliWFJYEV63vvVXaALmQ==", - "dev": true, - "requires": { - "commander": "2.12.2", - "js-reporters": "1.2.1", - "minimatch": "3.0.4", - "node-watch": "0.6.1", - "resolve": "1.9.0" - }, - "dependencies": { - "commander": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.12.2.tgz", - "integrity": "sha512-BFnaq5ZOGcDN7FlrtBT4xxkgIToalIIxwjxLWVJ8bGTpe1LroqMiqQXdA7ygc7CRvaYS+9zfPGFnJqFSayx+AA==", - "dev": true - }, - "resolve": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.9.0.tgz", - "integrity": "sha512-TZNye00tI67lwYvzxCxHGjwTNlUV70io54/Ed4j6PscB8xVfuBJpRenI/o6dVk0cY0PYTY27AgCoGGxRnYuItQ==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - } - } - }, "ramda": { "version": "0.24.1", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.24.1.tgz", @@ -10292,27 +10154,6 @@ "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=", "dev": true }, - "sinon": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.5.0.tgz", - "integrity": "sha512-AoD0oJWerp0/rY9czP/D6hDTTUYGpObhZjMpd7Cl/A6+j0xBE+ayL/ldfggkBXUs0IkvIiM1ljM8+WkOc5k78Q==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.4.0", - "@sinonjs/formatio": "^3.2.1", - "@sinonjs/samsam": "^3.3.3", - "diff": "^3.5.0", - "lolex": "^4.2.0", - "nise": "^1.5.2", - "supports-color": "^5.5.0" - } - }, - "sinon-test": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/sinon-test/-/sinon-test-2.4.0.tgz", - "integrity": "sha512-oQnO02I7JDbtrSKN8Qs3upobCQRythJCBn3DzPmv4m/SoPvhZJDVqHDFkRZ1lZhN2GkBqOR3m7WT79190z9kEg==", - "dev": true - }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -11347,12 +11188,6 @@ "prelude-ls": "~1.1.2" } }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, "type-fest": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", diff --git a/package.json b/package.json index 819935f8..107b82f4 100644 --- a/package.json +++ b/package.json @@ -137,7 +137,6 @@ "eslint-plugin-no-use-extend-native": "^0.4.1", "eslint-plugin-node": "10.0.0", "eslint-plugin-promise": "4.2.1", - "eslint-plugin-qunit": "^4.0.0", "eslint-plugin-sonarjs": "^0.5.0", "eslint-plugin-standard": "4.0.1", "eslint-plugin-unicorn": "^13.0.0", @@ -153,7 +152,6 @@ "promise-fs": "^2.1.1", "qr-manipulation": "https://github.com/brettz9/qr-manipulation", "query-result": "https://github.com/WebReflection/query-result", - "qunit": "^2.9.3", "regenerator-runtime": "^0.13.3", "remark-cli": "^7.0.1", "remark-lint-ordered-list-marker-value": "^1.0.3", @@ -161,8 +159,6 @@ "rollup-plugin-babel": "^4.3.3", "rollup-plugin-re": "^1.0.7", "rollup-plugin-terser": "^5.1.2", - "sinon": "^7.5.0", - "sinon-test": "^2.4.0", "stackblur-canvas": "^2.2.0", "typescript": "^3.7.2" } diff --git a/test/all_tests.html b/test/all_tests.html deleted file mode 100644 index 680ee571..00000000 --- a/test/all_tests.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - All SVG-edit Tests - - - - -

All SVG-edit Tests

-

This file frames all SVG-edit test pages. This should only include - tests known to work. These tests are known to pass 100% in the - following: - Firefox 3.6, Chrome 7, IE9 Preview 6 (1.9.8006.6000), Opera 10.63. - If a test is broken in this page, it is possible that YOU - broke it. Please do not submit code that breaks any of these tests.

- - - - - - - - - - - - - - - - - - - diff --git a/test/all_tests.js b/test/all_tests.js deleted file mode 100644 index 2d45f3a4..00000000 --- a/test/all_tests.js +++ /dev/null @@ -1,8 +0,0 @@ -const iframes = document.querySelectorAll('iframe'); -[...iframes].forEach((f) => { - f.addEventListener('load', () => { - f.contentWindow.QUnit.done(() => { - f.style.height = (f.contentDocument.body.scrollHeight + 20) + 'px'; - }); - }); -}); diff --git a/test/browser-bugs/removeItem-bug.html b/test/browser-bugs/removeItem-bug.html deleted file mode 100644 index 3c844708..00000000 --- a/test/browser-bugs/removeItem-bug.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - removeItem and setAttribute test - - - - - Issue: - - Chromium 843901 - - - diff --git a/test/contextmenu_test.html b/test/contextmenu_test.html deleted file mode 100644 index 0a04723e..00000000 --- a/test/contextmenu_test.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - Unit Tests for contextmenu.js - - - - - - - - -

Unit Tests for contextmenu.js

-

-

-
    - - - diff --git a/test/contextmenu_test.js b/test/contextmenu_test.js deleted file mode 100644 index d745dd3c..00000000 --- a/test/contextmenu_test.js +++ /dev/null @@ -1,73 +0,0 @@ -/* eslint-env qunit */ -import * as contextmenu from '../editor/contextmenu.js'; - -// log function -QUnit.log((details) => { - if (window.console && window.console.log) { - window.console.log(details.result + ' :: ' + details.message); - } -}); - -/** - * Tear down tests, resetting custom menus. - * @returns {void} - */ -function tearDown () { - contextmenu.resetCustomMenus(); -} - -QUnit.module('svgedit.contextmenu'); - -QUnit.test('Test svgedit.contextmenu package', function (assert) { - assert.expect(4); - - 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'); -}); - -QUnit.test('Test svgedit.contextmenu does not add invalid menu item', function (assert) { - assert.expect(3); - - assert.throws( - () => contextmenu.add({id: 'justanid'}), - 'menu item with just an id is invalid' - ); - - assert.throws( - () => contextmenu.add({id: 'idandlabel', label: 'anicelabel'}), - 'menu item with just an id and label is invalid' - ); - - assert.throws( - () => contextmenu.add({id: 'idandlabel', label: 'anicelabel', action: 'notafunction'}), - 'menu item with action that is not a function is invalid' - ); -}); - -QUnit.test('Test svgedit.contextmenu adds valid menu item', function (assert) { - assert.expect(2); - - const validItem = {id: 'valid', label: 'anicelabel', action () { console.log('testing'); }}; - 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.'); - tearDown(); -}); - -QUnit.test('Test svgedit.contextmenu rejects valid duplicate menu item id', function (assert) { - assert.expect(1); - - const validItem1 = {id: 'valid', label: 'anicelabel', action () { console.log('testing'); }}; - const validItem2 = {id: 'valid', label: 'anicelabel', action () { console.log('testingtwice'); }}; - contextmenu.add(validItem1); - - assert.throws( - () => contextmenu.add(validItem2), - 'duplicate menu item is rejected.' - ); - - tearDown(); -}); diff --git a/test/coords_test.html b/test/coords_test.html deleted file mode 100644 index d20f4273..00000000 --- a/test/coords_test.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - Unit Tests for coords.js - - - - - - - - -

    Unit Tests for coords

    -

    -

    -
      - - - diff --git a/test/coords_test.js b/test/coords_test.js deleted file mode 100644 index e7d9de04..00000000 --- a/test/coords_test.js +++ /dev/null @@ -1,355 +0,0 @@ -/* eslint-env qunit */ -import {NS} from '../editor/namespaces.js'; -import * as utilities from '../editor/utilities.js'; -import * as coords from '../editor/coords.js'; - -// log function -QUnit.log((details) => { - if (window.console && window.console.log) { - window.console.log(details.result + ' :: ' + details.message); - } -}); - -const root = document.getElementById('root'); -const svgroot = document.createElementNS(NS.SVG, 'svg'); -svgroot.id = 'svgroot'; -root.append(svgroot); -const svg = document.createElementNS(NS.SVG, 'svg'); -svgroot.append(svg); - -let elemId = 1; - -/** - * Set up tests with mock data. - * @returns {void} - */ -function setUp () { - // Mock out editor context. - utilities.init( - /** - * @implements {module:utilities.EditorContext} - */ - { - getSVGRoot () { return 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} - */ -function tearDown () { - while (svg.hasChildNodes()) { - svg.firstChild.remove(); - } -} - -QUnit.test('Test remapElement(translate) for rect', function (assert) { - assert.expect(4); - - setUp(); - - const rect = document.createElementNS(NS.SVG, 'rect'); - rect.setAttribute('x', '200'); - rect.setAttribute('y', '150'); - rect.setAttribute('width', '250'); - rect.setAttribute('height', '120'); - svg.append(rect); - - const attrs = { - x: '200', - y: '150', - width: '125', - height: '75' - }; - - // Create a translate. - const m = 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'); - - tearDown(); -}); - -QUnit.test('Test remapElement(scale) for rect', function (assert) { - assert.expect(4); - setUp(); - - const rect = document.createElementNS(NS.SVG, 'rect'); - rect.setAttribute('width', '250'); - rect.setAttribute('height', '120'); - svg.append(rect); - - const attrs = { - x: '0', - y: '0', - width: '250', - height: '120' - }; - - // Create a translate. - const m = 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'); - - tearDown(); -}); - -QUnit.test('Test remapElement(translate) for circle', function (assert) { - assert.expect(3); - setUp(); - - const circle = document.createElementNS(NS.SVG, 'circle'); - circle.setAttribute('cx', '200'); - circle.setAttribute('cy', '150'); - circle.setAttribute('r', '125'); - svg.append(circle); - - const attrs = { - cx: '200', - cy: '150', - r: '125' - }; - - // Create a translate. - const m = 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'); - - tearDown(); -}); - -QUnit.test('Test remapElement(scale) for circle', function (assert) { - assert.expect(3); - setUp(); - - const circle = document.createElementNS(NS.SVG, 'circle'); - circle.setAttribute('cx', '200'); - circle.setAttribute('cy', '150'); - circle.setAttribute('r', '250'); - svg.append(circle); - - const attrs = { - cx: '200', - cy: '150', - r: '250' - }; - - // Create a translate. - const m = 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'); - - tearDown(); -}); - -QUnit.test('Test remapElement(translate) for ellipse', function (assert) { - assert.expect(4); - setUp(); - - const ellipse = document.createElementNS(NS.SVG, 'ellipse'); - ellipse.setAttribute('cx', '200'); - ellipse.setAttribute('cy', '150'); - ellipse.setAttribute('rx', '125'); - ellipse.setAttribute('ry', '75'); - svg.append(ellipse); - - const attrs = { - cx: '200', - cy: '150', - rx: '125', - ry: '75' - }; - - // Create a translate. - const m = 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'); - - tearDown(); -}); - -QUnit.test('Test remapElement(scale) for ellipse', function (assert) { - assert.expect(4); - setUp(); - - const ellipse = document.createElementNS(NS.SVG, 'ellipse'); - ellipse.setAttribute('cx', '200'); - ellipse.setAttribute('cy', '150'); - ellipse.setAttribute('rx', '250'); - ellipse.setAttribute('ry', '120'); - svg.append(ellipse); - - const attrs = { - cx: '200', - cy: '150', - rx: '250', - ry: '120' - }; - - // Create a translate. - const m = 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'); - - tearDown(); -}); - -QUnit.test('Test remapElement(translate) for line', function (assert) { - assert.expect(4); - setUp(); - - const line = document.createElementNS(NS.SVG, 'line'); - line.setAttribute('x1', '50'); - line.setAttribute('y1', '100'); - line.setAttribute('x2', '120'); - line.setAttribute('y2', '200'); - svg.append(line); - - const attrs = { - x1: '50', - y1: '100', - x2: '120', - y2: '200' - }; - - // Create a translate. - const m = 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'); - - tearDown(); -}); - -QUnit.test('Test remapElement(scale) for line', function (assert) { - assert.expect(4); - setUp(); - - const line = document.createElementNS(NS.SVG, 'line'); - line.setAttribute('x1', '50'); - line.setAttribute('y1', '100'); - line.setAttribute('x2', '120'); - line.setAttribute('y2', '200'); - svg.append(line); - - const attrs = { - x1: '50', - y1: '100', - x2: '120', - y2: '200' - }; - - // Create a translate. - const m = 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'); - - tearDown(); -}); - -QUnit.test('Test remapElement(translate) for text', function (assert) { - assert.expect(2); - setUp(); - - const text = document.createElementNS(NS.SVG, 'text'); - text.setAttribute('x', '50'); - text.setAttribute('y', '100'); - svg.append(text); - - const attrs = { - x: '50', - y: '100' - }; - - // Create a translate. - const m = 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'); - - tearDown(); -}); diff --git a/test/draw_test.html b/test/draw_test.html deleted file mode 100644 index 2c7d0234..00000000 --- a/test/draw_test.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - Unit Tests for draw.js - - - - - - - -

      Unit Tests for draw.js

      -

      -

      -
        - - - diff --git a/test/draw_test.js b/test/draw_test.js deleted file mode 100644 index 6393d198..00000000 --- a/test/draw_test.js +++ /dev/null @@ -1,830 +0,0 @@ -/* eslint-env qunit */ -import {NS} from '../editor/namespaces.js'; -import * as draw from '../editor/draw.js'; -import * as units from '../editor/units.js'; - -import sinon from '../node_modules/sinon/pkg/sinon-esm.js'; -import sinonTest from '../node_modules/sinon-test/dist/sinon-test-es.js'; -import sinonQunit from './sinon/sinon-qunit.js'; - -sinon.test = sinonTest(sinon, { - injectIntoThis: true, - injectInto: null, - properties: ['spy', 'stub', 'mock', 'clock', 'sandbox'], - useFakeTimers: false, - useFakeServer: false -}); -sinonQunit({sinon, QUnit}); - -// log function -QUnit.log((details) => { - if (window.console && window.console.log) { - window.console.log(details.result + ' :: ' + details.message); - } -}); - -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' -}; - -const svg = document.createElementNS(NS.SVG, 'svg'); -const sandbox = document.getElementById('sandbox'); -// Firefox throws exception in getBBox() when svg is not attached to DOM. -sandbox.append(svg); - -// Set up with nonce. -const svgN = document.createElementNS(NS.SVG, 'svg'); -svgN.setAttributeNS(NS.XMLNS, 'xmlns:se', NS.SE); -svgN.setAttributeNS(NS.SE, 'se:nonce', NONCE); - -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 svgcontent = document.createElementNS(NS.SVG, 'svg'); -const currentDrawing_ = new draw.Drawing(svgcontent, idprefix); -const getCurrentDrawing = function () { - return currentDrawing_; -}; -const setCurrentGroup = (cg) => { /* */ }; -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(document.createTextNode(LAYER1)); - layer1.append(layer1Title); - svgElem.append(layer1); - - const layer2 = document.createElementNS(NS.SVG, 'g'); - const layer2Title = document.createElementNS(NS.SVG, 'title'); - layer2Title.append(document.createTextNode(LAYER2)); - layer2.append(layer2Title); - svgElem.append(layer2); - - const layer3 = document.createElementNS(NS.SVG, 'g'); - const layer3Title = document.createElementNS(NS.SVG, 'title'); - layer3Title.append(document.createTextNode(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(); } -}; - -QUnit.module('svgedit.draw.Drawing'); - -QUnit.test('Test draw module', function (assert) { - assert.expect(4); - - assert.ok(draw); - assert.equal(typeof draw, typeof {}); - - assert.ok(draw.Drawing); - assert.equal(typeof draw.Drawing, typeof function () { /* */ }); -}); - -QUnit.test('Test document creation', function (assert) { - assert.expect(3); - - let doc; - try { - doc = new draw.Drawing(); - assert.ok(false, 'Created drawing without a valid element'); - } catch (e) { - assert.ok(true); - } - - try { - doc = new draw.Drawing(svg); - assert.ok(doc); - assert.equal(typeof doc, typeof {}); - } catch (e) { - assert.ok(false, 'Could not create document from valid element: ' + e); - } -}); - -QUnit.test('Test nonce', function (assert) { - assert.expect(7); - - let doc = new draw.Drawing(svg); - assert.equal(doc.getNonce(), ''); - - doc = new draw.Drawing(svgN); - assert.equal(doc.getNonce(), NONCE); - assert.equal(doc.getSvgElem().getAttributeNS(NS.SE, 'nonce'), NONCE); - - doc.clearNonce(); - assert.ok(!doc.getNonce()); - assert.ok(!doc.getSvgElem().getAttributeNS(NS.SE, 'se:nonce')); - - doc.setNonce(NONCE); - assert.equal(doc.getNonce(), NONCE); - assert.equal(doc.getSvgElem().getAttributeNS(NS.SE, 'nonce'), NONCE); -}); - -QUnit.test('Test getId() and getNextId() without nonce', function (assert) { - assert.expect(7); - - const elem2 = document.createElementNS(NS.SVG, 'circle'); - elem2.id = 'svg_2'; - svg.append(elem2); - - const doc = new draw.Drawing(svg); - - assert.equal(doc.getId(), 'svg_0'); - - assert.equal(doc.getNextId(), 'svg_1'); - assert.equal(doc.getId(), 'svg_1'); - - assert.equal(doc.getNextId(), 'svg_3'); - assert.equal(doc.getId(), 'svg_3'); - - assert.equal(doc.getNextId(), 'svg_4'); - assert.equal(doc.getId(), 'svg_4'); - // clean out svg document - cleanupSVG(svg); -}); - -QUnit.test('Test getId() and getNextId() with prefix without nonce', function (assert) { - assert.expect(7); - - const prefix = 'Bar-'; - const doc = new draw.Drawing(svg, prefix); - - assert.equal(doc.getId(), prefix + '0'); - - assert.equal(doc.getNextId(), prefix + '1'); - assert.equal(doc.getId(), prefix + '1'); - - assert.equal(doc.getNextId(), prefix + '2'); - assert.equal(doc.getId(), prefix + '2'); - - assert.equal(doc.getNextId(), prefix + '3'); - assert.equal(doc.getId(), prefix + '3'); - - cleanupSVG(svg); -}); - -QUnit.test('Test getId() and getNextId() with nonce', function (assert) { - assert.expect(7); - - const prefix = 'svg_' + NONCE; - - const elem2 = document.createElementNS(NS.SVG, 'circle'); - elem2.id = prefix + '_2'; - svgN.append(elem2); - - const doc = new draw.Drawing(svgN); - - assert.equal(doc.getId(), prefix + '_0'); - - assert.equal(doc.getNextId(), prefix + '_1'); - assert.equal(doc.getId(), prefix + '_1'); - - assert.equal(doc.getNextId(), prefix + '_3'); - assert.equal(doc.getId(), prefix + '_3'); - - assert.equal(doc.getNextId(), prefix + '_4'); - assert.equal(doc.getId(), prefix + '_4'); - - cleanupSVG(svgN); -}); - -QUnit.test('Test getId() and getNextId() with prefix with nonce', function (assert) { - assert.expect(7); - - const PREFIX = 'Bar-'; - const doc = new draw.Drawing(svgN, PREFIX); - - const prefix = PREFIX + NONCE + '_'; - assert.equal(doc.getId(), prefix + '0'); - - assert.equal(doc.getNextId(), prefix + '1'); - assert.equal(doc.getId(), prefix + '1'); - - assert.equal(doc.getNextId(), prefix + '2'); - assert.equal(doc.getId(), prefix + '2'); - - assert.equal(doc.getNextId(), prefix + '3'); - assert.equal(doc.getId(), prefix + '3'); - - cleanupSVG(svgN); -}); - -QUnit.test('Test releaseId()', function (assert) { - assert.expect(6); - - const doc = new draw.Drawing(svg); - - const firstId = doc.getNextId(); - /* const secondId = */ doc.getNextId(); - - const result = doc.releaseId(firstId); - assert.ok(result); - assert.equal(doc.getNextId(), firstId); - assert.equal(doc.getNextId(), 'svg_3'); - - assert.ok(!doc.releaseId('bad-id')); - assert.ok(doc.releaseId(firstId)); - assert.ok(!doc.releaseId(firstId)); - - cleanupSVG(svg); -}); - -QUnit.test('Test getNumLayers', function (assert) { - assert.expect(3); - const drawing = new draw.Drawing(svg); - assert.equal(typeof drawing.getNumLayers, typeof function () { /* */ }); - assert.equal(drawing.getNumLayers(), 0); - - setupSVGWith3Layers(svg); - drawing.identifyLayers(); - - assert.equal(drawing.getNumLayers(), 3); - - cleanupSVG(svg); -}); - -QUnit.test('Test hasLayer', function (assert) { - assert.expect(5); - - setupSVGWith3Layers(svg); - const drawing = new draw.Drawing(svg); - drawing.identifyLayers(); - - assert.equal(typeof drawing.hasLayer, typeof function () { /* */ }); - assert.ok(!drawing.hasLayer('invalid-layer')); - - assert.ok(drawing.hasLayer(LAYER3)); - assert.ok(drawing.hasLayer(LAYER2)); - assert.ok(drawing.hasLayer(LAYER1)); - - cleanupSVG(svg); -}); - -QUnit.test('Test identifyLayers() with empty document', function (assert) { - assert.expect(11); - - const drawing = new draw.Drawing(svg); - assert.equal(drawing.getCurrentLayer(), null); - // By default, an empty document gets an empty group created. - drawing.identifyLayers(); - - // Check that element now has one child node - assert.ok(drawing.getSvgElem().hasChildNodes()); - assert.equal(drawing.getSvgElem().childNodes.length, 1); - - // Check that all_layers are correctly set up. - assert.equal(drawing.getNumLayers(), 1); - const emptyLayer = drawing.all_layers[0]; - assert.ok(emptyLayer); - const layerGroup = emptyLayer.getGroup(); - assert.equal(layerGroup, drawing.getSvgElem().firstChild); - assert.equal(layerGroup.tagName, 'g'); - assert.equal(layerGroup.getAttribute('class'), LAYER_CLASS); - assert.ok(layerGroup.hasChildNodes()); - assert.equal(layerGroup.childNodes.length, 1); - const firstChild = layerGroup.childNodes.item(0); - assert.equal(firstChild.tagName, 'title'); - - cleanupSVG(svg); -}); - -QUnit.test('Test identifyLayers() with some layers', function (assert) { - assert.expect(8); - - const drawing = new draw.Drawing(svg); - setupSVGWith3Layers(svg); - - assert.equal(svg.childNodes.length, 3); - - drawing.identifyLayers(); - - assert.equal(drawing.getNumLayers(), 3); - assert.equal(drawing.all_layers[0].getGroup(), svg.childNodes.item(0)); - assert.equal(drawing.all_layers[1].getGroup(), svg.childNodes.item(1)); - assert.equal(drawing.all_layers[2].getGroup(), svg.childNodes.item(2)); - - assert.equal(drawing.all_layers[0].getGroup().getAttribute('class'), LAYER_CLASS); - assert.equal(drawing.all_layers[1].getGroup().getAttribute('class'), LAYER_CLASS); - assert.equal(drawing.all_layers[2].getGroup().getAttribute('class'), LAYER_CLASS); - - cleanupSVG(svg); -}); - -QUnit.test('Test identifyLayers() with some layers and orphans', function (assert) { - assert.expect(14); - - setupSVGWith3Layers(svg); - - const orphan1 = document.createElementNS(NS.SVG, 'rect'); - const orphan2 = document.createElementNS(NS.SVG, 'rect'); - svg.append(orphan1, orphan2); - - assert.equal(svg.childNodes.length, 5); - - const drawing = new draw.Drawing(svg); - drawing.identifyLayers(); - - assert.equal(drawing.getNumLayers(), 4); - assert.equal(drawing.all_layers[0].getGroup(), svg.childNodes.item(0)); - assert.equal(drawing.all_layers[1].getGroup(), svg.childNodes.item(1)); - assert.equal(drawing.all_layers[2].getGroup(), svg.childNodes.item(2)); - assert.equal(drawing.all_layers[3].getGroup(), svg.childNodes.item(3)); - - assert.equal(drawing.all_layers[0].getGroup().getAttribute('class'), LAYER_CLASS); - assert.equal(drawing.all_layers[1].getGroup().getAttribute('class'), LAYER_CLASS); - assert.equal(drawing.all_layers[2].getGroup().getAttribute('class'), LAYER_CLASS); - assert.equal(drawing.all_layers[3].getGroup().getAttribute('class'), LAYER_CLASS); - - const layer4 = drawing.all_layers[3].getGroup(); - assert.equal(layer4.tagName, 'g'); - assert.equal(layer4.childNodes.length, 3); - assert.equal(layer4.childNodes.item(1), orphan1); - assert.equal(layer4.childNodes.item(2), orphan2); - - cleanupSVG(svg); -}); - -QUnit.test('Test getLayerName()', function (assert) { - assert.expect(4); - - const drawing = new draw.Drawing(svg); - setupSVGWith3Layers(svg); - - drawing.identifyLayers(); - - assert.equal(drawing.getNumLayers(), 3); - assert.equal(drawing.getLayerName(0), LAYER1); - assert.equal(drawing.getLayerName(1), LAYER2); - assert.equal(drawing.getLayerName(2), LAYER3); - - cleanupSVG(svg); -}); - -QUnit.test('Test getCurrentLayer()', function (assert) { - assert.expect(4); - - const drawing = new draw.Drawing(svg); - setupSVGWith3Layers(svg); - drawing.identifyLayers(); - - assert.ok(drawing.getCurrentLayer); - assert.equal(typeof drawing.getCurrentLayer, typeof function () { /* */ }); - assert.ok(drawing.getCurrentLayer()); - assert.equal(drawing.getCurrentLayer(), drawing.all_layers[2].getGroup()); - - cleanupSVG(svg); -}); - -QUnit.test('Test setCurrentLayer() and getCurrentLayerName()', function (assert) { - assert.expect(6); - - const drawing = new draw.Drawing(svg); - setupSVGWith3Layers(svg); - drawing.identifyLayers(); - - assert.ok(drawing.setCurrentLayer); - assert.equal(typeof drawing.setCurrentLayer, typeof function () { /* */ }); - - drawing.setCurrentLayer(LAYER2); - assert.equal(drawing.getCurrentLayerName(), LAYER2); - assert.equal(drawing.getCurrentLayer(), drawing.all_layers[1].getGroup()); - - drawing.setCurrentLayer(LAYER3); - assert.equal(drawing.getCurrentLayerName(), LAYER3); - assert.equal(drawing.getCurrentLayer(), drawing.all_layers[2].getGroup()); - - cleanupSVG(svg); -}); - -QUnit.test('Test setCurrentLayerName()', function (assert) { - const mockHrService = { - changeElement: this.spy() - }; - - const drawing = new draw.Drawing(svg); - setupSVGWith3Layers(svg); - drawing.identifyLayers(); - - assert.ok(drawing.setCurrentLayerName); - assert.equal(typeof drawing.setCurrentLayerName, typeof function () { /* */ }); - - const oldName = drawing.getCurrentLayerName(); - const newName = 'New Name'; - assert.ok(drawing.layer_map[oldName]); - assert.equal(drawing.layer_map[newName], undefined); // newName shouldn't exist. - const result = drawing.setCurrentLayerName(newName, mockHrService); - assert.equal(result, newName); - assert.equal(drawing.getCurrentLayerName(), newName); - // Was the map updated? - assert.equal(drawing.layer_map[oldName], undefined); - assert.equal(drawing.layer_map[newName], drawing.current_layer); - // Was mockHrService called? - assert.ok(mockHrService.changeElement.calledOnce); - assert.equal(oldName, mockHrService.changeElement.getCall(0).args[1]['#text']); - assert.equal(newName, mockHrService.changeElement.getCall(0).args[0].textContent); - - cleanupSVG(svg); -}); - -QUnit.test('Test createLayer()', function (assert) { - assert.expect(10); - - const mockHrService = { - startBatchCommand: this.spy(), - endBatchCommand: this.spy(), - insertElement: this.spy() - }; - - const drawing = new draw.Drawing(svg); - setupSVGWith3Layers(svg); - drawing.identifyLayers(); - - assert.ok(drawing.createLayer); - assert.equal(typeof drawing.createLayer, typeof function () { /* */ }); - - const NEW_LAYER_NAME = 'Layer A'; - const layerG = drawing.createLayer(NEW_LAYER_NAME, mockHrService); - assert.equal(drawing.getNumLayers(), 4); - assert.equal(layerG, drawing.getCurrentLayer()); - assert.equal(layerG.getAttribute('class'), LAYER_CLASS); - assert.equal(NEW_LAYER_NAME, drawing.getCurrentLayerName()); - assert.equal(NEW_LAYER_NAME, drawing.getLayerName(3)); - - assert.equal(layerG, mockHrService.insertElement.getCall(0).args[0]); - assert.ok(mockHrService.startBatchCommand.calledOnce); - assert.ok(mockHrService.endBatchCommand.calledOnce); - - cleanupSVG(svg); -}); - -QUnit.test('Test mergeLayer()', function (assert) { - const mockHrService = { - startBatchCommand: this.spy(), - endBatchCommand: this.spy(), - moveElement: this.spy(), - removeElement: this.spy() - }; - - const drawing = new draw.Drawing(svg); - const layers = setupSVGWith3Layers(svg); - const elementCount = createSomeElementsInGroup(layers[2]) + 1; // +1 for title element - assert.equal(layers[1].childElementCount, 1); - assert.equal(layers[2].childElementCount, elementCount); - drawing.identifyLayers(); - assert.equal(drawing.getCurrentLayer(), layers[2]); - - assert.ok(drawing.mergeLayer); - assert.equal(typeof drawing.mergeLayer, typeof function () { /* */ }); - - drawing.mergeLayer(mockHrService); - - assert.equal(drawing.getNumLayers(), 2); - assert.equal(svg.childElementCount, 2); - assert.equal(drawing.getCurrentLayer(), layers[1]); - assert.equal(layers[1].childElementCount, elementCount); - - // check history record - assert.ok(mockHrService.startBatchCommand.calledOnce); - assert.ok(mockHrService.endBatchCommand.calledOnce); - assert.equal(mockHrService.startBatchCommand.getCall(0).args[0], 'Merge Layer'); - assert.equal(mockHrService.moveElement.callCount, elementCount - 1); // -1 because the title was not moved. - assert.equal(mockHrService.removeElement.callCount, 2); // remove group and title. - - cleanupSVG(svg); -}); - -QUnit.test('Test mergeLayer() when no previous layer to merge', function (assert) { - const mockHrService = { - startBatchCommand: this.spy(), - endBatchCommand: this.spy(), - moveElement: this.spy(), - removeElement: this.spy() - }; - - const drawing = new draw.Drawing(svg); - const layers = setupSVGWith3Layers(svg); - drawing.identifyLayers(); - drawing.setCurrentLayer(LAYER1); - assert.equal(drawing.getCurrentLayer(), layers[0]); - - drawing.mergeLayer(mockHrService); - - assert.equal(drawing.getNumLayers(), 3); - assert.equal(svg.childElementCount, 3); - assert.equal(drawing.getCurrentLayer(), layers[0]); - assert.equal(layers[0].childElementCount, 1); - assert.equal(layers[1].childElementCount, 1); - assert.equal(layers[2].childElementCount, 1); - - // check history record - assert.equal(mockHrService.startBatchCommand.callCount, 0); - assert.equal(mockHrService.endBatchCommand.callCount, 0); - assert.equal(mockHrService.moveElement.callCount, 0); - assert.equal(mockHrService.removeElement.callCount, 0); - - cleanupSVG(svg); -}); - -QUnit.test('Test mergeAllLayers()', function (assert) { - const mockHrService = { - startBatchCommand: this.spy(), - endBatchCommand: this.spy(), - moveElement: this.spy(), - removeElement: this.spy() - }; - - const drawing = new draw.Drawing(svg); - const layers = setupSVGWith3Layers(svg); - const elementCount = createSomeElementsInGroup(layers[0]) + 1; // +1 for title element - createSomeElementsInGroup(layers[1]); - createSomeElementsInGroup(layers[2]); - assert.equal(layers[0].childElementCount, elementCount); - assert.equal(layers[1].childElementCount, elementCount); - assert.equal(layers[2].childElementCount, elementCount); - drawing.identifyLayers(); - - assert.ok(drawing.mergeAllLayers); - assert.equal(typeof drawing.mergeAllLayers, typeof function () { /* */ }); - - drawing.mergeAllLayers(mockHrService); - - assert.equal(drawing.getNumLayers(), 1); - assert.equal(svg.childElementCount, 1); - assert.equal(drawing.getCurrentLayer(), layers[0]); - assert.equal(layers[0].childElementCount, elementCount * 3 - 2); // -2 because two titles were deleted. - - // check history record - assert.equal(mockHrService.startBatchCommand.callCount, 3); // mergeAllLayers + 2 * mergeLayer - assert.equal(mockHrService.endBatchCommand.callCount, 3); - assert.equal(mockHrService.startBatchCommand.getCall(0).args[0], 'Merge all Layers'); - assert.equal(mockHrService.startBatchCommand.getCall(1).args[0], 'Merge Layer'); - assert.equal(mockHrService.startBatchCommand.getCall(2).args[0], 'Merge Layer'); - // moveElement count is times 3 instead of 2, because one layer's elements were moved twice. - // moveElement count is minus 3 because the three titles were not moved. - assert.equal(mockHrService.moveElement.callCount, elementCount * 3 - 3); - assert.equal(mockHrService.removeElement.callCount, 2 * 2); // remove group and title twice. - - cleanupSVG(svg); -}); - -QUnit.test('Test cloneLayer()', function (assert) { - const mockHrService = { - startBatchCommand: this.spy(), - endBatchCommand: this.spy(), - insertElement: this.spy() - }; - - const drawing = new draw.Drawing(svg); - const layers = setupSVGWith3Layers(svg); - const layer3 = layers[2]; - const elementCount = createSomeElementsInGroup(layer3) + 1; // +1 for title element - assert.equal(layer3.childElementCount, elementCount); - drawing.identifyLayers(); - - assert.ok(drawing.cloneLayer); - assert.equal(typeof drawing.cloneLayer, typeof function () { /* */ }); - - const clone = drawing.cloneLayer('clone', mockHrService); - - assert.equal(drawing.getNumLayers(), 4); - assert.equal(svg.childElementCount, 4); - assert.equal(drawing.getCurrentLayer(), clone); - assert.equal(clone.childElementCount, elementCount); - - // check history record - assert.ok(mockHrService.startBatchCommand.calledOnce); // mergeAllLayers + 2 * mergeLayer - assert.ok(mockHrService.endBatchCommand.calledOnce); - assert.equal(mockHrService.startBatchCommand.getCall(0).args[0], 'Duplicate Layer'); - assert.equal(mockHrService.insertElement.callCount, 1); - assert.equal(mockHrService.insertElement.getCall(0).args[0], clone); - - // check that path is cloned properly - assert.equal(clone.childNodes.length, elementCount); - const path = clone.childNodes[1]; - assert.equal(path.id, 'svg_1'); - assert.equal(path.getAttribute('d'), PATH_ATTR.d); - assert.equal(path.getAttribute('transform'), PATH_ATTR.transform); - assert.equal(path.getAttribute('fill'), PATH_ATTR.fill); - assert.equal(path.getAttribute('stroke'), PATH_ATTR.stroke); - assert.equal(path.getAttribute('stroke-width'), PATH_ATTR['stroke-width']); - - // check that g is cloned properly - const g = clone.childNodes[4]; - assert.equal(g.childNodes.length, 1); - assert.equal(g.id, 'svg_4'); - - cleanupSVG(svg); -}); - -QUnit.test('Test getLayerVisibility()', function (assert) { - assert.expect(5); - - const drawing = new draw.Drawing(svg); - setupSVGWith3Layers(svg); - drawing.identifyLayers(); - - assert.ok(drawing.getLayerVisibility); - assert.equal(typeof drawing.getLayerVisibility, typeof function () { /* */ }); - assert.ok(drawing.getLayerVisibility(LAYER1)); - assert.ok(drawing.getLayerVisibility(LAYER2)); - assert.ok(drawing.getLayerVisibility(LAYER3)); - - cleanupSVG(svg); -}); - -QUnit.test('Test setLayerVisibility()', function (assert) { - assert.expect(6); - - const drawing = new draw.Drawing(svg); - setupSVGWith3Layers(svg); - drawing.identifyLayers(); - - assert.ok(drawing.setLayerVisibility); - assert.equal(typeof drawing.setLayerVisibility, typeof function () { /* */ }); - - drawing.setLayerVisibility(LAYER3, false); - drawing.setLayerVisibility(LAYER2, true); - drawing.setLayerVisibility(LAYER1, false); - - assert.ok(!drawing.getLayerVisibility(LAYER1)); - assert.ok(drawing.getLayerVisibility(LAYER2)); - assert.ok(!drawing.getLayerVisibility(LAYER3)); - - drawing.setLayerVisibility(LAYER3, 'test-string'); - assert.ok(!drawing.getLayerVisibility(LAYER3)); - - cleanupSVG(svg); -}); - -QUnit.test('Test getLayerOpacity()', function (assert) { - assert.expect(5); - - const drawing = new draw.Drawing(svg); - setupSVGWith3Layers(svg); - drawing.identifyLayers(); - - assert.ok(drawing.getLayerOpacity); - assert.equal(typeof drawing.getLayerOpacity, typeof function () { /* */ }); - assert.strictEqual(drawing.getLayerOpacity(LAYER1), 1.0); - assert.strictEqual(drawing.getLayerOpacity(LAYER2), 1.0); - assert.strictEqual(drawing.getLayerOpacity(LAYER3), 1.0); - - cleanupSVG(svg); -}); - -QUnit.test('Test setLayerOpacity()', function (assert) { - assert.expect(6); - - const drawing = new draw.Drawing(svg); - setupSVGWith3Layers(svg); - drawing.identifyLayers(); - - assert.ok(drawing.setLayerOpacity); - assert.equal(typeof drawing.setLayerOpacity, typeof function () { /* */ }); - - drawing.setLayerOpacity(LAYER1, 0.4); - drawing.setLayerOpacity(LAYER2, 'invalid-string'); - drawing.setLayerOpacity(LAYER3, -1.4); - - assert.strictEqual(drawing.getLayerOpacity(LAYER1), 0.4); - console.log('layer2 opacity ' + drawing.getLayerOpacity(LAYER2)); - assert.strictEqual(drawing.getLayerOpacity(LAYER2), 1.0); - assert.strictEqual(drawing.getLayerOpacity(LAYER3), 1.0); - - drawing.setLayerOpacity(LAYER3, 100); - assert.strictEqual(drawing.getLayerOpacity(LAYER3), 1.0); - - cleanupSVG(svg); -}); - -QUnit.test('Test deleteCurrentLayer()', function (assert) { - assert.expect(6); - - const drawing = new draw.Drawing(svg); - setupSVGWith3Layers(svg); - drawing.identifyLayers(); - - drawing.setCurrentLayer(LAYER2); - - const curLayer = drawing.getCurrentLayer(); - assert.equal(curLayer, drawing.all_layers[1].getGroup()); - const deletedLayer = drawing.deleteCurrentLayer(); - - assert.equal(curLayer, deletedLayer); - assert.equal(drawing.getNumLayers(), 2); - assert.equal(LAYER1, drawing.all_layers[0].getName()); - assert.equal(LAYER3, drawing.all_layers[1].getName()); - assert.equal(drawing.getCurrentLayer(), drawing.all_layers[1].getGroup()); -}); - -QUnit.test('Test svgedit.draw.randomizeIds()', function (assert) { - assert.expect(9); - - // Confirm in LET_DOCUMENT_DECIDE mode that the document decides - // if there is a nonce. - let drawing = new draw.Drawing(svgN.cloneNode(true)); - assert.ok(drawing.getNonce()); - - drawing = new draw.Drawing(svg.cloneNode(true)); - assert.ok(!drawing.getNonce()); - - // Confirm that a nonce is set once we're in ALWAYS_RANDOMIZE mode. - draw.randomizeIds(true, drawing); - assert.ok(drawing.getNonce()); - - // Confirm new drawings in ALWAYS_RANDOMIZE mode have a nonce. - drawing = new draw.Drawing(svg.cloneNode(true)); - assert.ok(drawing.getNonce()); - - drawing.clearNonce(); - assert.ok(!drawing.getNonce()); - - // Confirm new drawings in NEVER_RANDOMIZE mode do not have a nonce - // but that their se:nonce attribute is left alone. - draw.randomizeIds(false, drawing); - assert.ok(!drawing.getNonce()); - assert.ok(drawing.getSvgElem().getAttributeNS(NS.SE, 'nonce')); - - drawing = new draw.Drawing(svg.cloneNode(true)); - assert.ok(!drawing.getNonce()); - - drawing = new draw.Drawing(svgN.cloneNode(true)); - assert.ok(!drawing.getNonce()); -}); diff --git a/test/history_test.html b/test/history_test.html deleted file mode 100644 index faefe45a..00000000 --- a/test/history_test.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - Unit Tests for history.js - - - - - - - -

        Unit Tests for history.js

        -

        -

        -
          - - - - diff --git a/test/history_test.js b/test/history_test.js deleted file mode 100644 index e4ceec1f..00000000 --- a/test/history_test.js +++ /dev/null @@ -1,583 +0,0 @@ -/* eslint-env qunit */ - -import {NS} from '../editor/namespaces.js'; -import * as transformlist from '../editor/svgtransformlist.js'; -import * as utilities from '../editor/utilities.js'; -import * as hstory from '../editor/history.js'; - -// TODO(codedread): Write tests for handling history events. - -// Mocked out methods. -transformlist.changeRemoveElementFromListMap((elem) => { /* */ }); - -utilities.mock({ - getHref (elem) { return '#foo'; }, - setHref (elem, val) { /* */ }, - getRotationAngle (elem) { return 0; } -}); - -// log function -QUnit.log((details) => { - if (window.console && window.console.log) { - window.console.log(details.result + ' :: ' + details.message); - } -}); - -// const svg = document.createElementNS(NS.SVG, 'svg'); -let undoMgr = null; -const divparent = document.getElementById('divparent'); -const div1 = document.getElementById('div1'); -const div2 = document.getElementById('div2'); -const div3 = document.getElementById('div3'); -const div4 = document.getElementById('div4'); -const div5 = document.getElementById('div5'); - -QUnit.module('svgedit.history'); - -class MockCommand { - constructor (optText) { this.text_ = optText; } - apply () { /* */ } // eslint-disable-line class-methods-use-this - unapply () { /* */ } // eslint-disable-line class-methods-use-this - getText () { return this.text_; } - elements () { return []; } // eslint-disable-line class-methods-use-this -} - -/* -class MockHistoryEventHandler { - handleHistoryEvent (eventType, command) {} -} -*/ - -/** - * Set up tests (with undo manager). - * @returns {void} - */ -function setUp () { - undoMgr = new hstory.UndoManager(); -} -/** - * Tear down tests, destroying undo manager. - * @returns {void} - */ -function tearDown () { - undoMgr = null; -} - -QUnit.test('Test svgedit.history package', function (assert) { - assert.expect(13); - - assert.ok(hstory); - assert.ok(hstory.MoveElementCommand); - assert.ok(hstory.InsertElementCommand); - assert.ok(hstory.ChangeElementCommand); - assert.ok(hstory.RemoveElementCommand); - assert.ok(hstory.BatchCommand); - assert.ok(hstory.UndoManager); - assert.equal(typeof hstory.MoveElementCommand, typeof function () { /* */ }); - assert.equal(typeof hstory.InsertElementCommand, typeof function () { /* */ }); - assert.equal(typeof hstory.ChangeElementCommand, typeof function () { /* */ }); - assert.equal(typeof hstory.RemoveElementCommand, typeof function () { /* */ }); - assert.equal(typeof hstory.BatchCommand, typeof function () { /* */ }); - assert.equal(typeof hstory.UndoManager, typeof function () { /* */ }); -}); - -QUnit.test('Test UndoManager methods', function (assert) { - assert.expect(14); - setUp(); - - 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 () { /* */ }); - assert.equal(typeof undoMgr.getUndoStackSize, typeof function () { /* */ }); - assert.equal(typeof undoMgr.getRedoStackSize, typeof function () { /* */ }); - assert.equal(typeof undoMgr.resetUndoStack, typeof function () { /* */ }); - assert.equal(typeof undoMgr.getNextUndoCommandText, typeof function () { /* */ }); - assert.equal(typeof undoMgr.getNextRedoCommandText, typeof function () { /* */ }); - - tearDown(); -}); - -QUnit.test('Test UndoManager.addCommandToHistory() function', function (assert) { - assert.expect(3); - - setUp(); - - assert.equal(undoMgr.getUndoStackSize(), 0); - undoMgr.addCommandToHistory(new MockCommand()); - assert.equal(undoMgr.getUndoStackSize(), 1); - undoMgr.addCommandToHistory(new MockCommand()); - assert.equal(undoMgr.getUndoStackSize(), 2); - - tearDown(); -}); - -QUnit.test('Test UndoManager.getUndoStackSize() and getRedoStackSize() functions', function (assert) { - assert.expect(18); - - setUp(); - - 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); - - tearDown(); -}); - -QUnit.test('Test UndoManager.resetUndoStackSize() function', function (assert) { - assert.expect(4); - - setUp(); - - 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); - - tearDown(); -}); - -QUnit.test('Test UndoManager.getNextUndoCommandText() function', function (assert) { - assert.expect(9); - - setUp(); - - 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'); - - tearDown(); -}); - -QUnit.test('Test UndoManager.getNextRedoCommandText() function', function (assert) { - assert.expect(8); - - setUp(); - - 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(), ''); - - tearDown(); -}); - -QUnit.test('Test UndoManager.undo() and redo() functions', function (assert) { - assert.expect(10); - - setUp(); - - 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); - - tearDown(); -}); - -QUnit.test('Test MoveElementCommand', function (assert) { - assert.expect(26); - - setUp(); - - let move = new hstory.MoveElementCommand(div3, div1, divparent); - assert.ok(move.unapply); - assert.ok(move.apply); - assert.equal(typeof move.unapply, typeof function () { /* */ }); - assert.equal(typeof move.apply, typeof function () { /* */ }); - - 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 hstory.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 hstory.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); - - tearDown(); -}); - -QUnit.test('Test InsertElementCommand', function (assert) { - assert.expect(20); - - setUp(); - - let insert = new hstory.InsertElementCommand(div3); - assert.ok(insert.unapply); - assert.ok(insert.apply); - assert.equal(typeof insert.unapply, typeof function () { /* */ }); - assert.equal(typeof insert.apply, typeof function () { /* */ }); - - 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 hstory.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); - - tearDown(); -}); - -QUnit.test('Test RemoveElementCommand', function (assert) { - assert.expect(22); - - setUp(); - - const div6 = document.createElement('div'); - div6.id = 'div6'; - - let remove = new hstory.RemoveElementCommand(div6, null, divparent); - assert.ok(remove.unapply); - assert.ok(remove.apply); - assert.equal(typeof remove.unapply, typeof function () { /* */ }); - assert.equal(typeof remove.apply, typeof function () { /* */ }); - - 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 hstory.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); - - tearDown(); -}); - -QUnit.test('Test ChangeElementCommand', function (assert) { - assert.expect(26); - - setUp(); - - div1.setAttribute('title', 'new title'); - let change = new hstory.ChangeElementCommand(div1, - {title: 'old title', class: 'foo'}); - assert.ok(change.unapply); - assert.ok(change.apply); - assert.equal(typeof change.unapply, typeof function () { /* */ }); - assert.equal(typeof change.apply, typeof function () { /* */ }); - - 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 hstory.ChangeElementCommand(div1, - {'#text': null}); - - change.unapply(); - assert.ok(!div1.textContent); - - change.apply(); - assert.equal(div1.textContent, 'inner text'); - - div1.textContent = ''; - change = new hstory.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 (elem) { return 0; } - }); - - gethrefvalue = '#newhref'; - change = new hstory.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'); - - const line = document.createElementNS(NS.SVG, 'line'); - line.setAttribute('class', 'newClass'); - change = new hstory.ChangeElementCommand(line, {class: 'oldClass'}); - - assert.ok(change.unapply); - assert.ok(change.apply); - assert.equal(typeof change.unapply, typeof function () { /* */ }); - assert.equal(typeof change.apply, typeof function () { /* */ }); - - change.unapply(); - assert.equal(line.getAttribute('class'), 'oldClass'); - - change.apply(); - assert.equal(line.getAttribute('class'), 'newClass'); - - tearDown(); -}); - -QUnit.test('Test BatchCommand', function (assert) { - assert.expect(13); - - setUp(); - - let concatResult = ''; - MockCommand.prototype.apply = function () { concatResult += this.text_; }; - - const batch = new hstory.BatchCommand(); - assert.ok(batch.unapply); - assert.ok(batch.apply); - assert.ok(batch.addSubCommand); - assert.ok(batch.isEmpty); - assert.equal(typeof batch.unapply, typeof function () { /* */ }); - assert.equal(typeof batch.apply, typeof function () { /* */ }); - assert.equal(typeof batch.addSubCommand, typeof function () { /* */ }); - assert.equal(typeof batch.isEmpty, typeof 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 () { /* */ }; - MockCommand.prototype.unapply = function () { concatResult += this.text_; }; - concatResult = ''; - batch.unapply(); - assert.equal(concatResult, 'cba'); - - MockCommand.prototype.unapply = function () { /* */ }; - - tearDown(); -}); diff --git a/test/jQuery.attr_test.html b/test/jQuery.attr_test.html deleted file mode 100644 index 60354d00..00000000 --- a/test/jQuery.attr_test.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - Unit Tests for jQuery.attr.js - - - - - - - -

          Unit Tests for jQuery.attr

          -

          -

          -
            -
            - - diff --git a/test/jQuery.attr_test.js b/test/jQuery.attr_test.js deleted file mode 100644 index 351fdaa1..00000000 --- a/test/jQuery.attr_test.js +++ /dev/null @@ -1,12 +0,0 @@ -/* eslint-env qunit */ - -/* eslint-disable import/unambiguous */ - -// Todo: Incomplete! - -// log function -QUnit.log((details) => { - if (window.console && window.console.log) { - window.console.log(details.result + ' :: ' + details.message); - } -}); diff --git a/test/math_test.html b/test/math_test.html deleted file mode 100644 index ad361a75..00000000 --- a/test/math_test.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - Unit Tests for math.js - - - - - - - -

            Unit Tests for math.js

            -

            -

            -
              - - diff --git a/test/math_test.js b/test/math_test.js deleted file mode 100644 index 5b4ec399..00000000 --- a/test/math_test.js +++ /dev/null @@ -1,121 +0,0 @@ -/* eslint-env qunit */ -import {NS} from '../editor/namespaces.js'; -import * as math from '../editor/math.js'; - -// log function -QUnit.log((details) => { - if (window.console && window.console.log) { - window.console.log(details.result + ' :: ' + details.message); - } -}); - -const svg = document.createElementNS(NS.SVG, 'svg'); - -QUnit.module('svgedit.math'); - -QUnit.test('Test svgedit.math package', function (assert) { - assert.expect(7); - - assert.ok(math); - assert.ok(math.transformPoint); - assert.ok(math.isIdentity); - assert.ok(math.matrixMultiply); - assert.equal(typeof math.transformPoint, typeof function () { /* */ }); - assert.equal(typeof math.isIdentity, typeof function () { /* */ }); - assert.equal(typeof math.matrixMultiply, typeof function () { /* */ }); -}); - -QUnit.test('Test svgedit.math.transformPoint() function', function (assert) { - assert.expect(6); - const {transformPoint} = math; - - const m = svg.createSVGMatrix(); - m.a = 1; m.b = 0; - m.c = 0; m.d = 1; - m.e = 0; m.f = 0; - let pt = transformPoint(100, 200, m); - assert.equal(pt.x, 100); - assert.equal(pt.y, 200); - - m.e = 300; m.f = 400; - pt = transformPoint(100, 200, m); - assert.equal(pt.x, 400); - assert.equal(pt.y, 600); - - m.a = 0.5; m.b = 0.75; - m.c = 1.25; m.d = 2; - pt = transformPoint(100, 200, m); - assert.equal(pt.x, 100 * m.a + 200 * m.c + m.e); - assert.equal(pt.y, 100 * m.b + 200 * m.d + m.f); -}); - -QUnit.test('Test svgedit.math.isIdentity() function', function (assert) { - assert.expect(2); - - assert.ok(math.isIdentity(svg.createSVGMatrix())); - - const m = svg.createSVGMatrix(); - m.a = 1; m.b = 0; - m.c = 0; m.d = 1; - m.e = 0; m.f = 0; - assert.ok(math.isIdentity(m)); -}); - -QUnit.test('Test svgedit.math.matrixMultiply() function', function (assert) { - assert.expect(5); - const mult = math.matrixMultiply; - const {isIdentity} = math; - - // translate there and back - const tr1 = svg.createSVGMatrix().translate(100, 50), - tr2 = svg.createSVGMatrix().translate(-90, 0), - tr3 = svg.createSVGMatrix().translate(-10, -50); - let I = mult(tr1, tr2, tr3); - assert.ok(isIdentity(I), 'Expected identity matrix when translating there and back'); - - // rotate there and back - // TODO: currently Mozilla fails this when rotating back at -50 and then -40 degrees - // (b and c are *almost* zero, but not zero) - const rotThere = svg.createSVGMatrix().rotate(90), - rotBack = svg.createSVGMatrix().rotate(-90), // TODO: set this to -50 - rotBackMore = svg.createSVGMatrix().rotate(0); // TODO: set this to -40 - I = mult(rotThere, rotBack, rotBackMore); - assert.ok(isIdentity(I), 'Expected identity matrix when rotating there and back'); - - // scale up and down - const scaleUp = svg.createSVGMatrix().scale(4), - scaleDown = svg.createSVGMatrix().scaleNonUniform(0.25, 1), - scaleDownMore = svg.createSVGMatrix().scaleNonUniform(1, 0.25); - I = mult(scaleUp, scaleDown, scaleDownMore); - assert.ok(isIdentity(I), 'Expected identity matrix when scaling up and down'); - - // test multiplication with its inverse - I = mult(rotThere, rotThere.inverse()); - assert.ok(isIdentity(I), 'Expected identity matrix when multiplying a matrix by its inverse'); - I = mult(rotThere.inverse(), rotThere); - assert.ok(isIdentity(I), 'Expected identity matrix when multiplying a matrix by its inverse'); -}); - -QUnit.test('Test svgedit.math.transformBox() function', function (assert) { - assert.expect(12); - const {transformBox} = math; - - const m = svg.createSVGMatrix(); - m.a = 1; m.b = 0; - m.c = 0; m.d = 1; - m.e = 0; m.f = 0; - - const r = transformBox(10, 10, 200, 300, m); - assert.equal(r.tl.x, 10); - assert.equal(r.tl.y, 10); - assert.equal(r.tr.x, 210); - assert.equal(r.tr.y, 10); - assert.equal(r.bl.x, 10); - assert.equal(r.bl.y, 310); - assert.equal(r.br.x, 210); - assert.equal(r.br.y, 310); - assert.equal(r.aabox.x, 10); - assert.equal(r.aabox.y, 10); - assert.equal(r.aabox.width, 200); - assert.equal(r.aabox.height, 300); -}); diff --git a/test/path_test.html b/test/path_test.html deleted file mode 100644 index bc7ad028..00000000 --- a/test/path_test.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - Unit Tests for path.js - - - - - - - -

              Unit Tests for path.js

              -

              -

              -
                - - - diff --git a/test/path_test.js b/test/path_test.js deleted file mode 100644 index e53f82cf..00000000 --- a/test/path_test.js +++ /dev/null @@ -1,181 +0,0 @@ -/* eslint-env qunit */ -/* globals SVGPathSeg */ -import '../editor/svgpathseg.js'; -import {NS} from '../editor/namespaces.js'; -import * as utilities from '../editor/utilities.js'; -import * as pathModule from '../editor/path.js'; - -// log function -QUnit.log((details) => { - if (window.console && window.console.log) { - window.console.log(details.result + ' :: ' + details.message); - } -}); - -/** -* @typedef {GenericArray} EditorContexts -* @property {module:path.EditorContext} 0 -* @property {module:path.EditorContext} 1 -*/ - -/** -* @param {SVGSVGElement} [svg] -* @returns {EditorContexts} -*/ -function getMockContexts (svg) { - svg = svg || document.createElementNS(NS.SVG, 'svg'); - const selectorParentGroup = document.createElementNS(NS.SVG, 'g'); - selectorParentGroup.setAttribute('id', 'selectorParentGroup'); - svg.append(selectorParentGroup); - return [ - /** - * @implements {module:path.EditorContext} - */ - { - getSVGRoot () { return svg; }, - getCurrentZoom () { return 1; } - }, - /** - * @implements {module:utilities.EditorContext} - */ - { - getDOMDocument () { return svg; }, - getDOMContainer () { return svg; }, - getSVGRoot () { return svg; } - } - ]; -} - -QUnit.test('Test svgedit.path.replacePathSeg', function (assert) { - assert.expect(6); - - const path = document.createElementNS(NS.SVG, 'path'); - path.setAttribute('d', 'M0,0 L10,11 L20,21Z'); - - const [mockPathContext, mockUtilitiesContext] = getMockContexts(); - pathModule.init(mockPathContext); - utilities.init(mockUtilitiesContext); - new pathModule.Path(path); // eslint-disable-line no-new - - assert.equal(path.pathSegList.getItem(1).pathSegTypeAsLetter, 'L'); - assert.equal(path.pathSegList.getItem(1).x, 10); - assert.equal(path.pathSegList.getItem(1).y, 11); - - pathModule.replacePathSeg(SVGPathSeg.PATHSEG_LINETO_REL, 1, [30, 31], path); - - assert.equal(path.pathSegList.getItem(1).pathSegTypeAsLetter, 'l'); - assert.equal(path.pathSegList.getItem(1).x, 30); - assert.equal(path.pathSegList.getItem(1).y, 31); -}); - -QUnit.test('Test svgedit.path.Segment.setType simple', function (assert) { - assert.expect(9); - - const path = document.createElementNS(NS.SVG, 'path'); - path.setAttribute('d', 'M0,0 L10,11 L20,21Z'); - - const [mockPathContext, mockUtilitiesContext] = getMockContexts(); - pathModule.init(mockPathContext); - utilities.init(mockUtilitiesContext); - new pathModule.Path(path); // eslint-disable-line no-new - - assert.equal(path.pathSegList.getItem(1).pathSegTypeAsLetter, 'L'); - assert.equal(path.pathSegList.getItem(1).x, 10); - assert.equal(path.pathSegList.getItem(1).y, 11); - - const segment = new pathModule.Segment(1, path.pathSegList.getItem(1)); - segment.setType(SVGPathSeg.PATHSEG_LINETO_REL, [30, 31]); - assert.equal(segment.item.pathSegTypeAsLetter, 'l'); - assert.equal(segment.item.x, 30); - assert.equal(segment.item.y, 31); - - // Also verify that the actual path changed. - assert.equal(path.pathSegList.getItem(1).pathSegTypeAsLetter, 'l'); - assert.equal(path.pathSegList.getItem(1).x, 30); - assert.equal(path.pathSegList.getItem(1).y, 31); -}); - -QUnit.test('Test svgedit.path.Segment.setType with control points', function (assert) { - assert.expect(14); - - // Setup the dom for a mock control group. - const svg = document.createElementNS(NS.SVG, 'svg'); - const path = document.createElementNS(NS.SVG, 'path'); - path.setAttribute('d', 'M0,0 C11,12 13,14 15,16 Z'); - svg.append(path); - - const [mockPathContext, mockUtilitiesContext] = getMockContexts(svg); - pathModule.init(mockPathContext); - utilities.init(mockUtilitiesContext); - const segment = new pathModule.Segment(1, path.pathSegList.getItem(1)); - segment.path = new pathModule.Path(path); - - assert.equal(path.pathSegList.getItem(1).pathSegTypeAsLetter, 'C'); - assert.equal(path.pathSegList.getItem(1).x1, 11); - assert.equal(path.pathSegList.getItem(1).y1, 12); - assert.equal(path.pathSegList.getItem(1).x2, 13); - assert.equal(path.pathSegList.getItem(1).y2, 14); - assert.equal(path.pathSegList.getItem(1).x, 15); - assert.equal(path.pathSegList.getItem(1).y, 16); - - segment.setType(SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL, [30, 31, 32, 33, 34, 35]); - assert.equal(path.pathSegList.getItem(1).pathSegTypeAsLetter, 'c'); - assert.equal(path.pathSegList.getItem(1).x1, 32); - assert.equal(path.pathSegList.getItem(1).y1, 33); - assert.equal(path.pathSegList.getItem(1).x2, 34); - assert.equal(path.pathSegList.getItem(1).y2, 35); - assert.equal(path.pathSegList.getItem(1).x, 30); - assert.equal(path.pathSegList.getItem(1).y, 31); -}); - -QUnit.test('Test svgedit.path.Segment.move', function (assert) { - assert.expect(6); - - const path = document.createElementNS(NS.SVG, 'path'); - path.setAttribute('d', 'M0,0 L10,11 L20,21Z'); - - const [mockPathContext, mockUtilitiesContext] = getMockContexts(); - pathModule.init(mockPathContext); - utilities.init(mockUtilitiesContext); - new pathModule.Path(path); // eslint-disable-line no-new - - assert.equal(path.pathSegList.getItem(1).pathSegTypeAsLetter, 'L'); - assert.equal(path.pathSegList.getItem(1).x, 10); - assert.equal(path.pathSegList.getItem(1).y, 11); - - const segment = new pathModule.Segment(1, path.pathSegList.getItem(1)); - segment.move(-3, 4); - assert.equal(path.pathSegList.getItem(1).pathSegTypeAsLetter, 'L'); - assert.equal(path.pathSegList.getItem(1).x, 7); - assert.equal(path.pathSegList.getItem(1).y, 15); -}); - -QUnit.test('Test svgedit.path.Segment.moveCtrl', function (assert) { - assert.expect(14); - - const path = document.createElementNS(NS.SVG, 'path'); - path.setAttribute('d', 'M0,0 C11,12 13,14 15,16 Z'); - - const [mockPathContext, mockUtilitiesContext] = getMockContexts(); - pathModule.init(mockPathContext); - utilities.init(mockUtilitiesContext); - new pathModule.Path(path); // eslint-disable-line no-new - - assert.equal(path.pathSegList.getItem(1).pathSegTypeAsLetter, 'C'); - assert.equal(path.pathSegList.getItem(1).x1, 11); - assert.equal(path.pathSegList.getItem(1).y1, 12); - assert.equal(path.pathSegList.getItem(1).x2, 13); - assert.equal(path.pathSegList.getItem(1).y2, 14); - assert.equal(path.pathSegList.getItem(1).x, 15); - assert.equal(path.pathSegList.getItem(1).y, 16); - - const segment = new pathModule.Segment(1, path.pathSegList.getItem(1)); - segment.moveCtrl(1, 100, -200); - assert.equal(path.pathSegList.getItem(1).pathSegTypeAsLetter, 'C'); - assert.equal(path.pathSegList.getItem(1).x1, 111); - assert.equal(path.pathSegList.getItem(1).y1, -188); - assert.equal(path.pathSegList.getItem(1).x2, 13); - assert.equal(path.pathSegList.getItem(1).y2, 14); - assert.equal(path.pathSegList.getItem(1).x, 15); - assert.equal(path.pathSegList.getItem(1).y, 16); -}); diff --git a/test/qunit/qunit-assert-almostEquals.js b/test/qunit/qunit-assert-almostEquals.js deleted file mode 100644 index 41520cdd..00000000 --- a/test/qunit/qunit-assert-almostEquals.js +++ /dev/null @@ -1,29 +0,0 @@ -const NEAR_ZERO = 5e-6; // 0.000005, Firefox fails at higher levels of precision. - -/** - * Checks that the supplied values are equal with a high though not absolute degree of precision. - * @param {Float} actual - * @param {Float} expected - * @param {string} message - * @returns {void} - */ -function almostEquals (actual, expected, message) { - message = message || (actual + ' did not equal ' + expected); - this.pushResult({ - result: Math.abs(actual - expected) < NEAR_ZERO, - actual, - expected, - message - }); -} - -/** - * @param {external:qunit} QUnit - * @returns {external:qunit} The same instance passed in after extending - */ -export default function extend (QUnit) { - QUnit.extend(QUnit.assert, { - almostEquals - }); - return QUnit; -} diff --git a/test/recalculate_test.html b/test/recalculate_test.html deleted file mode 100644 index b37e46cf..00000000 --- a/test/recalculate_test.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - Unit Tests for recalculate.js - - - - - - - -

                Unit Tests for recalculate

                -

                -

                -
                  - - - diff --git a/test/recalculate_test.js b/test/recalculate_test.js deleted file mode 100644 index 934cf1a9..00000000 --- a/test/recalculate_test.js +++ /dev/null @@ -1,165 +0,0 @@ -/* eslint-env qunit */ - -import {NS} from '../editor/namespaces.js'; -import * as utilities from '../editor/utilities.js'; -import * as coords from '../editor/coords.js'; -import * as recalculate from '../editor/recalculate.js'; - -// log function -QUnit.log((details) => { - if (window.console && window.console.log) { - window.console.log(details.result + ' :: ' + details.message); - } -}); - -const root = document.getElementById('root'); -const svgroot = document.createElementNS(NS.SVG, 'svg'); -svgroot.id = 'svgroot'; -root.append(svgroot); -const svg = document.createElementNS(NS.SVG, 'svg'); -svgroot.append(svg); - -let elemId = 1; - -/** - * Initilize modules to set up the tests. - * @returns {void} - */ -function setUp () { - utilities.init( - /** - * @implements {module:utilities.EditorContext} - */ - { - getSVGRoot () { return svg; }, - getDOMDocument () { return null; }, - getDOMContainer () { return null; } - } - ); - coords.init( - /** - * @implements {module:coords.EditorContext} - */ - { - getGridSnapping () { return false; }, - getDrawing () { - return { - getNextId () { return String(elemId++); } - }; - } - } - ); - recalculate.init( - /** - * @implements {module:recalculate.EditorContext} - */ - { - getSVGRoot () { return svg; }, - getStartTransform () { return ''; }, - setStartTransform () { /* */ } - } - ); -} - -let elem; - -/** - * Initialize for tests and set up `rect` element. - * @returns {void} - */ -function setUpRect () { - setUp(); - elem = document.createElementNS(NS.SVG, 'rect'); - elem.setAttribute('x', '200'); - elem.setAttribute('y', '150'); - elem.setAttribute('width', '250'); - elem.setAttribute('height', '120'); - svg.append(elem); -} - -/** - * Initialize for tests and set up `text` element with `tspan` child. - * @returns {void} - */ -function setUpTextWithTspan () { - setUp(); - elem = document.createElementNS(NS.SVG, 'text'); - elem.setAttribute('x', '200'); - elem.setAttribute('y', '150'); - - const tspan = document.createElementNS(NS.SVG, 'tspan'); - tspan.setAttribute('x', '200'); - tspan.setAttribute('y', '150'); - - const theText = document.createTextNode('Foo bar'); - tspan.append(theText); - elem.append(tspan); - svg.append(elem); -} - -/** - * Tear down the tests (empty the svg element). - * @returns {void} - */ -function tearDown () { - while (svg.hasChildNodes()) { - svg.firstChild.remove(); - } -} - -QUnit.test('Test recalculateDimensions() on rect with identity matrix', function (assert) { - assert.expect(1); - - setUpRect(); - elem.setAttribute('transform', 'matrix(1,0,0,1,0,0)'); - - recalculate.recalculateDimensions(elem); - - // Ensure that the identity matrix is swallowed and the element has no - // transform on it. - assert.equal(elem.hasAttribute('transform'), false); - - tearDown(); -}); - -QUnit.test('Test recalculateDimensions() on rect with simple translate', function (assert) { - assert.expect(5); - - setUpRect(); - elem.setAttribute('transform', 'translate(100,50)'); - - recalculate.recalculateDimensions(elem); - - assert.equal(elem.hasAttribute('transform'), false); - assert.equal(elem.getAttribute('x'), '300'); - assert.equal(elem.getAttribute('y'), '200'); - assert.equal(elem.getAttribute('width'), '250'); - assert.equal(elem.getAttribute('height'), '120'); - tearDown(); -}); - -QUnit.test('Test recalculateDimensions() on text w/tspan with simple translate', function (assert) { - assert.expect(5); - - setUpTextWithTspan(); - elem.setAttribute('transform', 'translate(100,50)'); - - recalculate.recalculateDimensions(elem); - - // Ensure that the identity matrix is swallowed and the element has no - // transform on it. - assert.equal(elem.hasAttribute('transform'), false); - assert.equal(elem.getAttribute('x'), '300'); - assert.equal(elem.getAttribute('y'), '200'); - - const tspan = elem.firstElementChild; - assert.equal(tspan.getAttribute('x'), '300'); - assert.equal(tspan.getAttribute('y'), '200'); - - tearDown(); -}); - -// TODO: Since recalculateDimensions() and surrounding code is -// probably the largest, most complicated and strange piece of -// code in SVG-edit, we need to write a whole lot of unit tests -// for it here. diff --git a/test/sanitize_test.html b/test/sanitize_test.html deleted file mode 100644 index 151f0cd0..00000000 --- a/test/sanitize_test.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - Unit Tests for sanitize.js - - - - - - - -

                  Unit Tests for sanitize.js

                  -

                  -

                  -
                    - - - diff --git a/test/sanitize_test.js b/test/sanitize_test.js deleted file mode 100644 index d7261964..00000000 --- a/test/sanitize_test.js +++ /dev/null @@ -1,25 +0,0 @@ -/* eslint-env qunit */ -import {NS} from '../editor/namespaces.js'; -import * as sanitize from '../editor/sanitize.js'; - -// log function -QUnit.log((details) => { - if (window.console && window.console.log) { - window.console.log(details.result + ' :: ' + details.message); - } -}); - -const svg = document.createElementNS(NS.SVG, 'svg'); - -QUnit.test('Test sanitizeSvg() strips ws from style attr', function (assert) { - assert.expect(2); - - const rect = document.createElementNS(NS.SVG, 'rect'); - rect.setAttribute('style', 'stroke: blue ;\t\tstroke-width :\t\t40;'); - // sanitizeSvg() requires the node to have a parent and a document. - svg.append(rect); - sanitize.sanitizeSvg(rect); - - assert.equal(rect.getAttribute('stroke'), 'blue'); - assert.equal(rect.getAttribute('stroke-width'), '40'); -}); diff --git a/test/select_test.html b/test/select_test.html deleted file mode 100644 index 4c0ac378..00000000 --- a/test/select_test.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - Unit Tests for select.js - - - - - - - -

                    Unit Tests for select.js

                    -

                    -

                    -
                      -
                      - - diff --git a/test/select_test.js b/test/select_test.js deleted file mode 100644 index a3dcd54a..00000000 --- a/test/select_test.js +++ /dev/null @@ -1,143 +0,0 @@ -/* eslint-env qunit */ -import * as select from '../editor/select.js'; -import {NS} from '../editor/namespaces.js'; - -// log function -QUnit.log((details) => { - if (window.console && window.console.log) { - window.console.log(details.result + ' :: ' + details.message); - } -}); - -QUnit.module('svgedit.select'); - -const sandbox = document.getElementById('sandbox'); -let svgroot; -let svgcontent; -const mockConfig = { - dimensions: [640, 480] -}; - -/** -* @implements {module:select.SVGFactory} -*/ -const mockFactory = { - createSVGElement (jsonMap) { - const elem = document.createElementNS(NS.SVG, jsonMap.element); - Object.entries(jsonMap.attr).forEach(([attr, value]) => { - elem.setAttribute(attr, value); - }); - return elem; - }, - svgRoot () { return svgroot; }, - svgContent () { return svgcontent; } -}; - -/** - * Potentially reusable test set-up. - * @returns {void} - */ -function setUp () { - svgroot = mockFactory.createSVGElement({ - element: 'svg', - attr: {id: 'svgroot'} - }); - svgcontent = svgroot.appendChild( - mockFactory.createSVGElement({ - element: 'svg', - attr: {id: 'svgcontent'} - }) - ); - /* const rect = */ svgcontent.appendChild( - mockFactory.createSVGElement({ - element: 'rect', - attr: { - id: 'rect', - x: '50', - y: '75', - width: '200', - height: '100' - } - }) - ); - sandbox.append(svgroot); -} - -/* -function setUpWithInit () { - setUp(); - select.init(mockConfig, mockFactory); -} -*/ - -/** - * Tear down the test by emptying our sandbox area. - * @returns {void} - */ -function tearDown () { - while (sandbox.hasChildNodes()) { - sandbox.firstChild.remove(); - } -} - -QUnit.test('Test svgedit.select package', function (assert) { - assert.expect(10); - - assert.ok(select); - assert.ok(select.Selector); - assert.ok(select.SelectorManager); - assert.ok(select.init); - assert.ok(select.getSelectorManager); - assert.equal(typeof select, typeof {}); - assert.equal(typeof select.Selector, typeof function () { /* */ }); - assert.equal(typeof select.SelectorManager, typeof function () { /* */ }); - assert.equal(typeof select.init, typeof function () { /* */ }); - assert.equal(typeof select.getSelectorManager, typeof function () { /* */ }); -}); - -QUnit.test('Test Selector DOM structure', function (assert) { - assert.expect(24); - - setUp(); - - assert.ok(svgroot); - assert.ok(svgroot.hasChildNodes()); - - // Verify non-existence of Selector DOM nodes - assert.equal(svgroot.childNodes.length, 1); - assert.equal(svgroot.childNodes.item(0), svgcontent); - assert.ok(!svgroot.querySelector('#selectorParentGroup')); - - select.init(mockConfig, mockFactory); - - assert.equal(svgroot.childNodes.length, 3); - - // Verify existence of canvas background. - const cb = svgroot.childNodes.item(0); - assert.ok(cb); - assert.equal(cb.id, 'canvasBackground'); - - assert.ok(svgroot.childNodes.item(1)); - assert.equal(svgroot.childNodes.item(1), svgcontent); - - // Verify existence of selectorParentGroup. - const spg = svgroot.childNodes.item(2); - assert.ok(spg); - assert.equal(svgroot.querySelector('#selectorParentGroup'), spg); - assert.equal(spg.id, 'selectorParentGroup'); - assert.equal(spg.tagName, 'g'); - - // Verify existence of all grip elements. - assert.ok(spg.querySelector('#selectorGrip_resize_nw')); - assert.ok(spg.querySelector('#selectorGrip_resize_n')); - assert.ok(spg.querySelector('#selectorGrip_resize_ne')); - assert.ok(spg.querySelector('#selectorGrip_resize_e')); - assert.ok(spg.querySelector('#selectorGrip_resize_se')); - assert.ok(spg.querySelector('#selectorGrip_resize_s')); - assert.ok(spg.querySelector('#selectorGrip_resize_sw')); - assert.ok(spg.querySelector('#selectorGrip_resize_w')); - assert.ok(spg.querySelector('#selectorGrip_rotateconnector')); - assert.ok(spg.querySelector('#selectorGrip_rotate')); - - tearDown(); -}); diff --git a/test/sinon/sinon-qunit.js b/test/sinon/sinon-qunit.js deleted file mode 100644 index b8d4dbbd..00000000 --- a/test/sinon/sinon-qunit.js +++ /dev/null @@ -1,30 +0,0 @@ -// Adapted from https://www.npmjs.com/package/sinon-test - -/** - * @external QUnit - */ -/** - * @external sinon - */ - -/** - * Adds methods to Sinon using a QUnit implementation. - * @param {PlainObject} implementations - * @param {external:sinon} implementations.sinon - * @param {external:QUnit} implementations.QUnit - * @returns {void} - */ -export default function sinonQunit ({sinon, QUnit}) { - sinon.assert.fail = function (msg) { - QUnit.ok(false, msg); - }; - - sinon.assert.pass = function (assertion) { - QUnit.ok(true, assertion); - }; - - const qTest = QUnit.test; - QUnit.test = function (testName, callback) { // eslint-disable-line promise/prefer-await-to-callbacks - return qTest(testName, sinon.test(callback)); - }; -} diff --git a/test/svgtransformlist_test.html b/test/svgtransformlist_test.html deleted file mode 100644 index 06a0bccb..00000000 --- a/test/svgtransformlist_test.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - Unit Tests for svgtransformlist.js - - - - - - - -

                      Unit Tests for svgtransformlist.js

                      -

                      -

                      -
                        - - - diff --git a/test/svgtransformlist_test.js b/test/svgtransformlist_test.js deleted file mode 100644 index 77898d11..00000000 --- a/test/svgtransformlist_test.js +++ /dev/null @@ -1,389 +0,0 @@ -/* eslint-env qunit */ - -import {NS} from '../editor/namespaces.js'; -import * as transformlist from '../editor/svgtransformlist.js'; -import {disableSupportsNativeTransformLists} from '../editor/browser.js'; -import almostEqualsPlugin from './qunit/qunit-assert-almostEquals.js'; -import expectOutOfBoundsExceptionPlugin from './qunit/qunit-assert-expectOutOfBoundsException.js'; - -almostEqualsPlugin(QUnit); -expectOutOfBoundsExceptionPlugin(QUnit); - -disableSupportsNativeTransformLists(); - -// log function -QUnit.log((details) => { - if (window.console && window.console.log) { - window.console.log(details.result + ' :: ' + details.message); - } -}); - -const svgroot = document.querySelector('#svgroot'); -let svgcontent, rect, circle; - -/** - * Set up tests, adding elements. - * @returns {void} - */ -function setUp () { - svgcontent = svgroot.appendChild(document.createElementNS(NS.SVG, 'svg')); - rect = svgcontent.appendChild(document.createElementNS(NS.SVG, 'rect')); - rect.id = 'r'; - circle = svgcontent.appendChild(document.createElementNS(NS.SVG, 'circle')); - circle.id = 'c'; -} - -/** - * Tear down tests, emptying SVG root, and resetting list map. - * @returns {void} - */ -function tearDown () { - transformlist.resetListMap(); - while (svgroot.hasChildNodes()) { - svgroot.firstChild.remove(); - } -} - -QUnit.module('svgedit.svgtransformlist'); - -QUnit.test('Test svgedit.transformlist package', function (assert) { - assert.expect(2); - - assert.ok(transformlist); - assert.ok(transformlist.getTransformList); -}); - -QUnit.test('Test svgedit.transformlist.getTransformList() function', function (assert) { - assert.expect(4); - setUp(); - - const rxform = transformlist.getTransformList(rect); - const cxform = transformlist.getTransformList(circle); - - assert.ok(rxform); - assert.ok(cxform); - assert.equal(typeof rxform, typeof {}); - assert.equal(typeof cxform, typeof {}); - - tearDown(); -}); - -QUnit.test('Test SVGTransformList.numberOfItems property', function (assert) { - assert.expect(2); - setUp(); - - const rxform = transformlist.getTransformList(rect); - - assert.equal(typeof rxform.numberOfItems, typeof 0); - assert.equal(rxform.numberOfItems, 0); - - tearDown(); -}); - -QUnit.test('Test SVGTransformList.initialize()', function (assert) { - assert.expect(6); - setUp(); - - const rxform = transformlist.getTransformList(rect); - const cxform = transformlist.getTransformList(circle); - - const t = svgcontent.createSVGTransform(); - assert.ok(t); - assert.ok(rxform.initialize); - assert.equal(typeof rxform.initialize, typeof function () { /* */ }); - rxform.initialize(t); - assert.equal(rxform.numberOfItems, 1); - assert.equal(cxform.numberOfItems, 0); - - // If a transform was already in a transform list, this should - // remove it from its old list and add it to this list. - cxform.initialize(t); - // This also fails in Firefox native. - // assert.equal(rxform.numberOfItems, 0, 'Did not remove transform from list before initializing another transformlist'); - assert.equal(cxform.numberOfItems, 1); - - tearDown(); -}); - -QUnit.test('Test SVGTransformList.appendItem() and getItem()', function (assert) { - assert.expect(12); - setUp(); - - const rxform = transformlist.getTransformList(rect); - const cxform = transformlist.getTransformList(circle); - - const t1 = svgcontent.createSVGTransform(), - t2 = svgcontent.createSVGTransform(), - t3 = svgcontent.createSVGTransform(); - - assert.ok(rxform.appendItem); - assert.ok(rxform.getItem); - assert.equal(typeof rxform.appendItem, typeof function () { /* */ }); - assert.equal(typeof rxform.getItem, typeof function () { /* */ }); - - rxform.appendItem(t1); - rxform.appendItem(t2); - rxform.appendItem(t3); - - assert.equal(rxform.numberOfItems, 3); - const rxf = rxform.getItem(0); - assert.equal(rxf, t1); - assert.equal(rxform.getItem(1), t2); - assert.equal(rxform.getItem(2), t3); - - assert.expectOutOfBoundsException(rxform, 'getItem', -1); - assert.expectOutOfBoundsException(rxform, 'getItem', 3); - cxform.appendItem(t1); - // These also fail in Firefox native. - // assert.equal(rxform.numberOfItems, 2, 'Did not remove a transform from a list before appending it to a new transformlist'); - // assert.equal(rxform.getItem(0), t2, 'Found the wrong transform in a transformlist'); - // assert.equal(rxform.getItem(1), t3, 'Found the wrong transform in a transformlist'); - - assert.equal(cxform.numberOfItems, 1); - assert.equal(cxform.getItem(0), t1); - tearDown(); -}); - -QUnit.test('Test SVGTransformList.removeItem()', function (assert) { - assert.expect(7); - setUp(); - - const rxform = transformlist.getTransformList(rect); - - const t1 = svgcontent.createSVGTransform(), - t2 = svgcontent.createSVGTransform(); - assert.ok(rxform.removeItem); - assert.equal(typeof rxform.removeItem, typeof function () { /* */ }); - rxform.appendItem(t1); - rxform.appendItem(t2); - - const removedTransform = rxform.removeItem(0); - assert.equal(rxform.numberOfItems, 1); - assert.equal(removedTransform, t1); - assert.equal(rxform.getItem(0), t2); - - assert.expectOutOfBoundsException(rxform, 'removeItem', -1); - assert.expectOutOfBoundsException(rxform, 'removeItem', 1); - - tearDown(); -}); - -QUnit.test('Test SVGTransformList.replaceItem()', function (assert) { - assert.expect(8); - setUp(); - - const rxform = transformlist.getTransformList(rect); - const cxform = transformlist.getTransformList(circle); - - assert.ok(rxform.replaceItem); - assert.equal(typeof rxform.replaceItem, typeof function () { /* */ }); - - const t1 = svgcontent.createSVGTransform(), - t2 = svgcontent.createSVGTransform(), - t3 = svgcontent.createSVGTransform(); - - rxform.appendItem(t1); - rxform.appendItem(t2); - cxform.appendItem(t3); - - const newItem = rxform.replaceItem(t3, 0); - assert.equal(rxform.numberOfItems, 2); - assert.equal(newItem, t3); - assert.equal(rxform.getItem(0), t3); - assert.equal(rxform.getItem(1), t2); - // Fails in Firefox native - // assert.equal(cxform.numberOfItems, 0); - - // test replaceItem within a list - rxform.appendItem(t1); - rxform.replaceItem(t1, 0); - // Fails in Firefox native - // assert.equal(rxform.numberOfItems, 2); - assert.equal(rxform.getItem(0), t1); - assert.equal(rxform.getItem(1), t2); - - tearDown(); -}); - -QUnit.test('Test SVGTransformList.insertItemBefore()', function (assert) { - assert.expect(10); - setUp(); - - const rxform = transformlist.getTransformList(rect); - const cxform = transformlist.getTransformList(circle); - - assert.ok(rxform.insertItemBefore); - assert.equal(typeof rxform.insertItemBefore, typeof function () { /* */ }); - - const t1 = svgcontent.createSVGTransform(), - t2 = svgcontent.createSVGTransform(), - t3 = svgcontent.createSVGTransform(); - - rxform.appendItem(t1); - rxform.appendItem(t2); - cxform.appendItem(t3); - - const newItem = rxform.insertItemBefore(t3, 0); - assert.equal(rxform.numberOfItems, 3); - assert.equal(newItem, t3); - assert.equal(rxform.getItem(0), t3); - assert.equal(rxform.getItem(1), t1); - assert.equal(rxform.getItem(2), t2); - // Fails in Firefox native - // assert.equal(cxform.numberOfItems, 0); - - rxform.insertItemBefore(t2, 1); - // Fails in Firefox native (they make copies of the transforms) - // assert.equal(rxform.numberOfItems, 3); - assert.equal(rxform.getItem(0), t3); - assert.equal(rxform.getItem(1), t2); - assert.equal(rxform.getItem(2), t1); - tearDown(); -}); - -QUnit.test('Test SVGTransformList.init() for translate(200,100)', function (assert) { - assert.expect(8); - setUp(); - rect.setAttribute('transform', 'translate(200,100)'); - - const rxform = transformlist.getTransformList(rect); - assert.equal(rxform.numberOfItems, 1); - - const translate = rxform.getItem(0); - assert.equal(translate.type, 2); - - const m = translate.matrix; - assert.equal(m.a, 1); - assert.equal(m.b, 0); - assert.equal(m.c, 0); - assert.equal(m.d, 1); - assert.equal(m.e, 200); - assert.equal(m.f, 100); - - tearDown(); -}); - -QUnit.test('Test SVGTransformList.init() for scale(4)', function (assert) { - assert.expect(8); - setUp(); - rect.setAttribute('transform', 'scale(4)'); - - const rxform = transformlist.getTransformList(rect); - assert.equal(rxform.numberOfItems, 1); - - const scale = rxform.getItem(0); - assert.equal(scale.type, 3); - - const m = scale.matrix; - assert.equal(m.a, 4); - assert.equal(m.b, 0); - assert.equal(m.c, 0); - assert.equal(m.d, 4); - assert.equal(m.e, 0); - assert.equal(m.f, 0); - - tearDown(); -}); - -QUnit.test('Test SVGTransformList.init() for scale(4,3)', function (assert) { - assert.expect(8); - setUp(); - rect.setAttribute('transform', 'scale(4,3)'); - - const rxform = transformlist.getTransformList(rect); - assert.equal(rxform.numberOfItems, 1); - - const scale = rxform.getItem(0); - assert.equal(scale.type, 3); - - const m = scale.matrix; - assert.equal(m.a, 4); - assert.equal(m.b, 0); - assert.equal(m.c, 0); - assert.equal(m.d, 3); - assert.equal(m.e, 0); - assert.equal(m.f, 0); - - tearDown(); -}); - -QUnit.test('Test SVGTransformList.init() for rotate(45)', function (assert) { - assert.expect(9); - setUp(); - rect.setAttribute('transform', 'rotate(45)'); - - const rxform = transformlist.getTransformList(rect); - assert.equal(rxform.numberOfItems, 1); - - const rotate = rxform.getItem(0); - assert.equal(rotate.type, 4); - assert.equal(rotate.angle, 45); - - const m = rotate.matrix; - assert.almostEquals(1 / Math.sqrt(2), m.a); - assert.almostEquals(1 / Math.sqrt(2), m.b); - assert.almostEquals(-1 / Math.sqrt(2), m.c); - assert.almostEquals(1 / Math.sqrt(2), m.d); - assert.equal(m.e, 0); - assert.equal(m.f, 0); - - tearDown(); -}); - -QUnit.test('Test SVGTransformList.init() for rotate(45, 100, 200)', function (assert) { - assert.expect(9); - setUp(); - rect.setAttribute('transform', 'rotate(45, 100, 200)'); - - const rxform = transformlist.getTransformList(rect); - assert.equal(rxform.numberOfItems, 1); - - const rotate = rxform.getItem(0); - assert.equal(rotate.type, 4); - assert.equal(rotate.angle, 45); - - const m = rotate.matrix; - assert.almostEquals(m.a, 1 / Math.sqrt(2)); - assert.almostEquals(m.b, 1 / Math.sqrt(2)); - assert.almostEquals(m.c, -1 / Math.sqrt(2)); - assert.almostEquals(m.d, 1 / Math.sqrt(2)); - - const r = svgcontent.createSVGMatrix(); - r.a = 1 / Math.sqrt(2); r.b = 1 / Math.sqrt(2); - r.c = -1 / Math.sqrt(2); r.d = 1 / Math.sqrt(2); - - const t = svgcontent.createSVGMatrix(); - t.e = -100; t.f = -200; - - const t_ = svgcontent.createSVGMatrix(); - t_.e = 100; t_.f = 200; - - const result = t_.multiply(r).multiply(t); - - assert.almostEquals(m.e, result.e); - assert.almostEquals(m.f, result.f); - - tearDown(); -}); - -QUnit.test('Test SVGTransformList.init() for matrix(1, 2, 3, 4, 5, 6)', function (assert) { - assert.expect(8); - setUp(); - rect.setAttribute('transform', 'matrix(1,2,3,4,5,6)'); - - const rxform = transformlist.getTransformList(rect); - assert.equal(rxform.numberOfItems, 1); - - const mt = rxform.getItem(0); - assert.equal(mt.type, 1); - - const m = mt.matrix; - assert.equal(m.a, 1); - assert.equal(m.b, 2); - assert.equal(m.c, 3); - assert.equal(m.d, 4); - assert.equal(m.e, 5); - assert.equal(m.f, 6); - - tearDown(); -}); diff --git a/test/test1.html b/test/test1.html deleted file mode 100644 index 6876013b..00000000 --- a/test/test1.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - Unit Tests for SvgCanvas - - - - - - - - -

                        Unit Tests for SvgCanvas

                        -

                        -

                        -
                          -
                          -
                          - -
                          -
                          -
                          -
                          - - diff --git a/test/test1.js b/test/test1.js deleted file mode 100644 index 30117dce..00000000 --- a/test/test1.js +++ /dev/null @@ -1,269 +0,0 @@ -/* eslint-env qunit */ - -import '../editor/svgpathseg.js'; -import SvgCanvas from '../editor/svgcanvas.js'; - -// log function -QUnit.log((details) => { - if (window.console && window.console.log) { - window.console.log(details.result + ' :: ' + details.message); - } -}); - -// helper functions -/* -const isIdentity = function (m) { - return (m.a === 1 && m.b === 0 && m.c === 0 && m.d === 1 && m.e === 0 && m.f === 0); -}; -const matrixString = function (m) { - return [m.a, m.b, m.c, m.d, m.e, m.f].join(','); -}; -*/ - -const svgCanvas = new SvgCanvas( - document.getElementById('svgcanvas'), { - canvas_expansion: 3, - dimensions: [640, 480], - initFill: { - color: 'FF0000', // solid red - opacity: 1 - }, - initStroke: { - width: 5, - color: '000000', // solid black - opacity: 1 - }, - initOpacity: 1, - imgPath: '../editor/images/', - langPath: 'locale/', - extPath: 'extensions/', - extensions: ['ext-arrows.js', 'ext-connector.js', 'ext-eyedropper.js'], - initTool: 'select', - wireframe: false - } -); - -const - // svgroot = document.getElementById('svgroot'), - // svgdoc = svgroot.documentElement, - svgns = 'http://www.w3.org/2000/svg', - xlinkns = 'http://www.w3.org/1999/xlink'; - -QUnit.module('Basic Module'); - -QUnit.test('Test existence of SvgCanvas object', function (assert) { - assert.expect(1); - assert.equal(typeof {}, typeof svgCanvas); -}); - -QUnit.module('Path Module'); - -QUnit.test('Test path conversion from absolute to relative', function (assert) { - assert.expect(6); - const convert = svgCanvas.pathActions.convertPath; - - // TODO: Test these paths: - // "m400.00491,625.01379a1.78688,1.78688 0 1 1-3.57373,0a1.78688,1.78688 0 1 13.57373,0z" - // "m36.812,15.8566c-28.03099,0 -26.28099,12.15601 -26.28099,12.15601l0.03099,12.59399h26.75v3.781h-37.37399c0,0 -17.938,-2.034 -133.00001,26.25c115.06201,28.284 130.71801,27.281 130.71801,27.281h9.34399v-13.125c0,0 -0.504,-15.656 15.40601,-15.656h26.532c0,0 14.90599,0.241 14.90599,-14.406v-24.219c0,0 2.263,-14.65601 -27.032,-14.65601zm-14.75,8.4684c2.662,0 4.813,2.151 4.813,4.813c0,2.661 -2.151,4.812 -4.813,4.812c-2.661,0 -4.812,-2.151 -4.812,-4.812c0,-2.662 2.151,-4.813 4.812,-4.813z" - // "m 0,0 l 200,0 l 0,100 L 0,100" - - svgCanvas.setSvgString( - "" + - "" + - "" + - '' - ); - - const p1 = document.getElementById('p1'), - p2 = document.getElementById('p2'), - dAbs = p1.getAttribute('d'), - seglist = p1.pathSegList; - - assert.equal(p1.nodeName, 'path', "Expected 'path', got"); - - assert.equal(seglist.numberOfItems, 4, 'Number of segments before conversion'); - - // verify segments before conversion - let curseg = seglist.getItem(0); - assert.equal(curseg.pathSegTypeAsLetter.toUpperCase(), 'M', 'Before conversion, segment #1 type'); - curseg = seglist.getItem(1); - assert.equal(curseg.pathSegTypeAsLetter.toUpperCase(), 'L', 'Before conversion, segment #2 type'); - curseg = seglist.getItem(3); - assert.equal(curseg.pathSegTypeAsLetter.toUpperCase(), 'Z', 'Before conversion, segment #3 type' + dAbs); - - // convert and verify segments - let d = convert(p1, true); - assert.equal(d, 'm100,100l100,0l-100,0z', 'Converted path to relative string'); - - // TODO: see why this isn't working in SVG-edit - d = convert(p2, true); - console.log('Convert true', d); - d = convert(p2, false); - console.log('Convert false', d); -}); - -QUnit.module('Import Module'); - -QUnit.test('Test import use', function (assert) { - assert.expect(3); - - svgCanvas.setSvgString( - "" + - "" + - "" + - "" + - "" + - '' - ); - - const u = document.getElementById('the-use'), - fu = document.getElementById('foreign-use'), - nfu = document.getElementById('no-use'); - - assert.equal((u && u.nodeName === 'use'), true, 'Did not import element'); - assert.equal(fu, null, 'Removed element that had a foreign href'); - assert.equal(nfu, null, 'Removed element that had no href'); -}); - -// This test shows that an element with an invalid attribute is still parsed in properly -// and only the attribute is not imported -QUnit.test('Test invalid attribute', function (assert) { - assert.expect(2); - - svgCanvas.setSvgString( - '' + - 'words' + - '' - ); - - const t = document.getElementById('the-text'); - - assert.equal((t && t.nodeName === 'text'), true, 'Did not import element'); - assert.equal(t.getAttribute('d'), null, 'Imported a with a d attribute'); -}); - -// This test makes sure import/export properly handles namespaced attributes -QUnit.test('Test importing/exporting namespaced attributes', function (assert) { - assert.expect(5); - /* const setStr = */ svgCanvas.setSvgString( - '' + - '' + - '' + - '' - ); - const attrVal = document.getElementById('se_test_elem').getAttributeNS('http://svg-edit.googlecode.com', 'foo'); - - assert.equal(attrVal === 'bar', true, 'Preserved namespaced attribute on import'); - // - // console.log('getSvgString' in svgCanvas) - - const output = svgCanvas.getSvgString(); - // } catch(e) {console.log(e)} - // console.log('output',output); - const hasXlink = output.includes('xmlns:xlink="http://www.w3.org/1999/xlink"'); - const hasSe = output.includes('xmlns:se='); - const hasFoo = output.includes('xmlns:foo='); - const hasAttr = output.includes('se:foo="bar"'); - - assert.equal(hasAttr, true, 'Preserved namespaced attribute on export'); - assert.equal(hasXlink, true, 'Included xlink: xmlns'); - assert.equal(hasSe, true, 'Included se: xmlns'); - assert.equal(hasFoo, false, 'Did not include foo: xmlns'); -}); - -QUnit.test('Test import math elements inside a foreignObject', function (assert) { - assert.expect(4); - /* const set = */ svgCanvas.setSvgString( - '' + - '' + - '' + - 'A' + - '0' + - '' + - '' + - '' + - '' - ); - const fo = document.getElementById('fo'); - // we cannot use getElementById('math') because not all browsers understand MathML and do not know to use the @id attribute - // see Bug https://bugs.webkit.org/show_bug.cgi?id=35042 - const math = fo.firstChild; - - assert.equal(Boolean(math), true, 'Math element exists'); - assert.equal(math.nodeName, 'math', 'Math element has the proper nodeName'); - assert.equal(math.getAttribute('id'), 'm', 'Math element has an id'); - assert.equal(math.namespaceURI, 'http://www.w3.org/1998/Math/MathML', 'Preserved MathML namespace'); -}); - -QUnit.test('Test importing SVG into existing drawing', function (assert) { - assert.expect(3); - - /* const doc = */ svgCanvas.setSvgString( - '' + - 'Layer 1' + - '' + - '' + - '' + - '' - ); - - svgCanvas.importSvgString( - '' + - '' + - '' + - '' - ); - - const svgcontent = document.getElementById('svgcontent'), - circles = svgcontent.getElementsByTagNameNS(svgns, 'circle'), - rects = svgcontent.getElementsByTagNameNS(svgns, 'rect'), - ellipses = svgcontent.getElementsByTagNameNS(svgns, 'ellipse'); - assert.equal(circles.length, 2, 'Found two circles upon importing'); - assert.equal(rects.length, 1, 'Found one rectangle upon importing'); - assert.equal(ellipses.length, 1, 'Found one ellipse upon importing'); -}); - -QUnit.test('Test importing SVG remaps IDs', function (assert) { - assert.expect(6); - - /* const doc = */ svgCanvas.setSvgString( - '' + - 'Layer 1' + - '' + - '' + - '' + - '' + - '' - ); - - svgCanvas.importSvgString( - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' - ); - - const svgcontent = document.getElementById('svgcontent'), - circles = svgcontent.getElementsByTagNameNS(svgns, 'circle'), - rects = svgcontent.getElementsByTagNameNS(svgns, 'rect'), - // ellipses = svgcontent.getElementsByTagNameNS(svgns, 'ellipse'), - defs = svgcontent.getElementsByTagNameNS(svgns, 'defs'), - // grads = svgcontent.getElementsByTagNameNS(svgns, 'linearGradient'), - uses = svgcontent.getElementsByTagNameNS(svgns, 'use'); - assert.notEqual(circles.item(0).id, 'svg_1', 'Circle not re-identified'); - assert.notEqual(rects.item(0).id, 'svg_3', 'Rectangle not re-identified'); - // TODO: determine why this test fails in WebKit browsers - // assert.equal(grads.length, 1, 'Linear gradient imported'); - const grad = defs.item(0).firstChild; - assert.notEqual(grad.id, 'svg_2', 'Linear gradient not re-identified'); - assert.notEqual(circles.item(0).getAttribute('fill'), 'url(#svg_2)', 'Circle fill value not remapped'); - assert.notEqual(rects.item(0).getAttribute('stroke'), 'url(#svg_2)', 'Rectangle stroke value not remapped'); - assert.notEqual(uses.item(0).getAttributeNS(xlinkns, 'href'), '#svg_3'); -}); diff --git a/test/units_test.html b/test/units_test.html deleted file mode 100644 index 211f8609..00000000 --- a/test/units_test.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - Unit Tests for units.js - - - - - - - -

                          Unit Tests for units.js

                          -

                          -

                          -
                            - -
                            - - -
                            - - diff --git a/test/units_test.js b/test/units_test.js deleted file mode 100644 index 9d990b66..00000000 --- a/test/units_test.js +++ /dev/null @@ -1,90 +0,0 @@ -/* eslint-env qunit */ -import * as units from '../editor/units.js'; - -// log function -QUnit.log((details) => { - if (window.console && window.console.log) { - window.console.log(details.result + ' :: ' + details.message); - } -}); - -/** - * Set up tests, supplying mock data. - * @returns {void} - */ -function setUp () { - units.init( - /** - * @implements {module:units.ElementContainer} - */ - { - getBaseUnit () { return 'cm'; }, - getHeight () { return 600; }, - getWidth () { return 800; }, - getRoundDigits () { return 4; }, - getElement (elementId) { return document.getElementById(elementId); } - } - ); -} - -QUnit.test('Test svgedit.units package', function (assert) { - assert.expect(2); - assert.ok(units); - assert.equal(typeof units, typeof {}); -}); - -QUnit.test('Test svgedit.units.shortFloat()', function (assert) { - assert.expect(7); - - setUp(); - - assert.ok(units.shortFloat); - assert.equal(typeof units.shortFloat, typeof function () { /* */ }); - - const {shortFloat} = units; - assert.equal(shortFloat(0.00000001), 0); - assert.equal(shortFloat(1), 1); - assert.equal(shortFloat(3.45678), 3.4568); - assert.equal(shortFloat(1.23443), 1.2344); - assert.equal(shortFloat(1.23455), 1.2346); -}); - -QUnit.test('Test svgedit.units.isValidUnit()', function (assert) { - assert.expect(18); - - setUp(); - - assert.ok(units.isValidUnit); - assert.equal(typeof units.isValidUnit, typeof function () { /* */ }); - - const {isValidUnit} = units; - assert.ok(isValidUnit('0')); - assert.ok(isValidUnit('1')); - assert.ok(isValidUnit('1.1')); - assert.ok(isValidUnit('-1.1')); - assert.ok(isValidUnit('.6mm')); - assert.ok(isValidUnit('-.6cm')); - assert.ok(isValidUnit('6000in')); - assert.ok(isValidUnit('6px')); - assert.ok(isValidUnit('6.3pc')); - assert.ok(isValidUnit('-0.4em')); - assert.ok(isValidUnit('-0.ex')); - assert.ok(isValidUnit('40.123%')); - - assert.equal(isValidUnit('id', 'uniqueId', document.getElementById('uniqueId')), true); - assert.equal(isValidUnit('id', 'newId', document.getElementById('uniqueId')), true); - assert.equal(isValidUnit('id', 'uniqueId'), false); - assert.equal(isValidUnit('id', 'uniqueId', document.getElementById('nonUniqueId')), false); -}); - -QUnit.test('Test svgedit.units.convertUnit()', function (assert) { - assert.expect(4); - - setUp(); - - assert.ok(units.convertUnit); - assert.equal(typeof units.convertUnit, typeof function () { /* */ }); - // cm in default setup - assert.equal(units.convertUnit(42), 1.1113); - assert.equal(units.convertUnit(42, 'px'), 42); -}); diff --git a/test/utilities_bbox_test.html b/test/utilities_bbox_test.html deleted file mode 100644 index 025a5024..00000000 --- a/test/utilities_bbox_test.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - Unit Tests for utilities.js BBox functions - - - - - - - -

                            Unit Tests for utilities.js BBox functions

                            -

                            -

                            -
                              -
                              - - diff --git a/test/utilities_bbox_test.js b/test/utilities_bbox_test.js deleted file mode 100644 index a436567c..00000000 --- a/test/utilities_bbox_test.js +++ /dev/null @@ -1,508 +0,0 @@ -/* eslint-env qunit */ -import '../editor/svgpathseg.js'; -import {NS} from '../editor/namespaces.js'; -import * as utilities from '../editor/utilities.js'; -import * as transformlist from '../editor/svgtransformlist.js'; -import * as math from '../editor/math.js'; -import * as path from '../editor/path.js'; -import closePlugin from './qunit/qunit-assert-close.js'; - -closePlugin(QUnit); - -// log function -QUnit.log((details) => { - if (window.console && window.console.log) { - window.console.log(details.result + ' :: ' + details.message); - } -}); - -/** - * Create an SVG element for a mock. - * @param {module:utilities.SVGElementJSON} jsonMap - * @returns {SVGElement} - */ -function mockCreateSVGElement (jsonMap) { - const elem = document.createElementNS(NS.SVG, jsonMap.element); - Object.entries(jsonMap.attr).forEach(([attr, value]) => { - elem.setAttribute(attr, value); - }); - return elem; -} -let mockaddSVGElementFromJsonCallCount = 0; - -/** - * Mock of {@link module:utilities.EditorContext#addSVGElementFromJson}. - * @param {module:utilities.SVGElementJSON} json - * @returns {SVGElement} - */ -function mockaddSVGElementFromJson (json) { - const elem = mockCreateSVGElement(json); - svgroot.append(elem); - mockaddSVGElementFromJsonCallCount++; - return elem; -} -const mockPathActions = { - resetOrientation (pth) { - if (utilities.isNullish(pth) || pth.nodeName !== 'path') { return false; } - const tlist = transformlist.getTransformList(pth); - const m = math.transformListToTransform(tlist).matrix; - tlist.clear(); - pth.removeAttribute('transform'); - const segList = pth.pathSegList; - - const len = segList.numberOfItems; - // let lastX, lastY; - - for (let i = 0; i < len; ++i) { - const seg = segList.getItem(i); - const type = seg.pathSegType; - if (type === 1) { continue; } - const pts = []; - ['', 1, 2].forEach(function (n, j) { - const x = seg['x' + n], y = seg['y' + n]; - if (x !== undefined && y !== undefined) { - const pt = math.transformPoint(x, y, m); - pts.splice(pts.length, 0, pt.x, pt.y); - } - }); - path.replacePathSeg(type, i, pts, pth); - } - // path.reorientGrads(pth, m); - return undefined; - } -}; - -const EPSILON = 0.001; -// const svg = document.createElementNS(NS.SVG, 'svg'); -const sandbox = document.getElementById('sandbox'); -const svgroot = mockCreateSVGElement({ - element: 'svg', - attr: {id: 'svgroot'} -}); -sandbox.append(svgroot); - -QUnit.module('svgedit.utilities_bbox', { - beforeEach () { - // We're reusing ID's so we need to do this for transforms. - transformlist.resetListMap(); - path.init(null); - mockaddSVGElementFromJsonCallCount = 0; - } -}); - -QUnit.test('Test svgedit.utilities package', function (assert) { - assert.ok(utilities); - assert.ok(utilities.getBBoxWithTransform); - assert.ok(utilities.getStrokedBBox); - assert.ok(utilities.getRotationAngleFromTransformList); - assert.ok(utilities.getRotationAngle); -}); - -QUnit.test('Test getBBoxWithTransform and no transform', function (assert) { - const {getBBoxWithTransform} = utilities; - - let elem = mockCreateSVGElement({ - element: 'path', - attr: {id: 'path', d: 'M0,1 L2,3'} - }); - svgroot.append(elem); - let bbox = getBBoxWithTransform(elem, mockaddSVGElementFromJson, mockPathActions); - assert.deepEqual(bbox, {x: 0, y: 1, width: 2, height: 2}); - assert.equal(mockaddSVGElementFromJsonCallCount, 0); - elem.remove(); - - elem = mockCreateSVGElement({ - element: 'rect', - attr: {id: 'rect', x: '0', y: '1', width: '5', height: '10'} - }); - svgroot.append(elem); - bbox = getBBoxWithTransform(elem, mockaddSVGElementFromJson, mockPathActions); - assert.deepEqual(bbox, {x: 0, y: 1, width: 5, height: 10}); - assert.equal(mockaddSVGElementFromJsonCallCount, 0); - elem.remove(); - - elem = mockCreateSVGElement({ - element: 'line', - attr: {id: 'line', x1: '0', y1: '1', x2: '5', y2: '6'} - }); - svgroot.append(elem); - bbox = getBBoxWithTransform(elem, mockaddSVGElementFromJson, mockPathActions); - assert.deepEqual(bbox, {x: 0, y: 1, width: 5, height: 5}); - assert.equal(mockaddSVGElementFromJsonCallCount, 0); - elem.remove(); - - elem = mockCreateSVGElement({ - element: 'rect', - attr: {id: 'rect', x: '0', y: '1', width: '5', height: '10'} - }); - const g = mockCreateSVGElement({ - element: 'g', - attr: {} - }); - g.append(elem); - svgroot.append(g); - bbox = getBBoxWithTransform(elem, mockaddSVGElementFromJson, mockPathActions); - assert.deepEqual(bbox, {x: 0, y: 1, width: 5, height: 10}); - assert.equal(mockaddSVGElementFromJsonCallCount, 0); - g.remove(); -}); - -QUnit.test('Test getBBoxWithTransform and a rotation transform', function (assert) { - const {getBBoxWithTransform} = utilities; - - let elem = mockCreateSVGElement({ - element: 'path', - attr: {id: 'path', d: 'M10,10 L20,20', transform: 'rotate(45 10,10)'} - }); - svgroot.append(elem); - let bbox = getBBoxWithTransform(elem, mockaddSVGElementFromJson, mockPathActions); - assert.close(bbox.x, 10, EPSILON); - assert.close(bbox.y, 10, EPSILON); - assert.close(bbox.width, 0, EPSILON); - assert.close(bbox.height, Math.sqrt(100 + 100), EPSILON); - elem.remove(); - - elem = mockCreateSVGElement({ - element: 'rect', - attr: {id: 'rect', x: '10', y: '10', width: '10', height: '20', transform: 'rotate(90 15,20)'} - }); - svgroot.append(elem); - bbox = getBBoxWithTransform(elem, mockaddSVGElementFromJson, mockPathActions); - assert.close(bbox.x, 5, EPSILON); - assert.close(bbox.y, 15, EPSILON); - assert.close(bbox.width, 20, EPSILON); - assert.close(bbox.height, 10, EPSILON); - assert.equal(mockaddSVGElementFromJsonCallCount, 1); - elem.remove(); - - const rect = {x: 10, y: 10, width: 10, height: 20}; - const angle = 45; - const origin = {x: 15, y: 20}; // eslint-disable-line no-shadow - elem = mockCreateSVGElement({ - element: 'rect', - attr: {id: 'rect2', x: rect.x, y: rect.y, width: rect.width, height: rect.height, transform: 'rotate(' + angle + ' ' + origin.x + ',' + origin.y + ')'} - }); - svgroot.append(elem); - mockaddSVGElementFromJsonCallCount = 0; - bbox = getBBoxWithTransform(elem, mockaddSVGElementFromJson, mockPathActions); - const r2 = rotateRect(rect, angle, origin); - assert.close(bbox.x, r2.x, EPSILON, 'rect2 x is ' + r2.x); - assert.close(bbox.y, r2.y, EPSILON, 'rect2 y is ' + r2.y); - assert.close(bbox.width, r2.width, EPSILON, 'rect2 width is' + r2.width); - assert.close(bbox.height, r2.height, EPSILON, 'rect2 height is ' + r2.height); - assert.equal(mockaddSVGElementFromJsonCallCount, 0); - elem.remove(); - - // Same as previous but wrapped with g and the transform is with the g. - elem = mockCreateSVGElement({ - element: 'rect', - attr: {id: 'rect3', x: rect.x, y: rect.y, width: rect.width, height: rect.height} - }); - const g = mockCreateSVGElement({ - element: 'g', - attr: {transform: 'rotate(' + angle + ' ' + origin.x + ',' + origin.y + ')'} - }); - g.append(elem); - svgroot.append(g); - mockaddSVGElementFromJsonCallCount = 0; - bbox = getBBoxWithTransform(g, mockaddSVGElementFromJson, mockPathActions); - assert.close(bbox.x, r2.x, EPSILON, 'rect2 x is ' + r2.x); - assert.close(bbox.y, r2.y, EPSILON, 'rect2 y is ' + r2.y); - assert.close(bbox.width, r2.width, EPSILON, 'rect2 width is' + r2.width); - assert.close(bbox.height, r2.height, EPSILON, 'rect2 height is ' + r2.height); - assert.equal(mockaddSVGElementFromJsonCallCount, 0); - g.remove(); - - elem = mockCreateSVGElement({ - element: 'ellipse', - attr: {id: 'ellipse1', cx: '100', cy: '100', rx: '50', ry: '50', transform: 'rotate(45 100,100)'} - }); - svgroot.append(elem); - mockaddSVGElementFromJsonCallCount = 0; - bbox = getBBoxWithTransform(elem, mockaddSVGElementFromJson, mockPathActions); - // TODO: the BBox algorithm is using the bezier control points to calculate the bounding box. Should be 50, 50, 100, 100. - assert.ok(bbox.x > 45 && bbox.x <= 50); - assert.ok(bbox.y > 45 && bbox.y <= 50); - assert.ok(bbox.width >= 100 && bbox.width < 110); - assert.ok(bbox.height >= 100 && bbox.height < 110); - assert.equal(mockaddSVGElementFromJsonCallCount, 1); - elem.remove(); -}); - -QUnit.test('Test getBBoxWithTransform with rotation and matrix transforms', function (assert) { - const {getBBoxWithTransform} = utilities; - - let tx = 10; // tx right - let ty = 10; // tx down - let txInRotatedSpace = Math.sqrt(tx * tx + ty * ty); // translate in rotated 45 space. - let tyInRotatedSpace = 0; - let matrix = 'matrix(1,0,0,1,' + txInRotatedSpace + ',' + tyInRotatedSpace + ')'; - let elem = mockCreateSVGElement({ - element: 'path', - attr: {id: 'path', d: 'M10,10 L20,20', transform: 'rotate(45 10,10) ' + matrix} - }); - svgroot.append(elem); - let bbox = getBBoxWithTransform(elem, mockaddSVGElementFromJson, mockPathActions); - assert.close(bbox.x, 10 + tx, EPSILON); - assert.close(bbox.y, 10 + ty, EPSILON); - assert.close(bbox.width, 0, EPSILON); - assert.close(bbox.height, Math.sqrt(100 + 100), EPSILON); - elem.remove(); - - txInRotatedSpace = tx; // translate in rotated 90 space. - tyInRotatedSpace = -ty; - matrix = 'matrix(1,0,0,1,' + txInRotatedSpace + ',' + tyInRotatedSpace + ')'; - elem = mockCreateSVGElement({ - element: 'rect', - attr: {id: 'rect', x: '10', y: '10', width: '10', height: '20', transform: 'rotate(90 15,20) ' + matrix} - }); - svgroot.append(elem); - bbox = getBBoxWithTransform(elem, mockaddSVGElementFromJson, mockPathActions); - assert.close(bbox.x, 5 + tx, EPSILON); - assert.close(bbox.y, 15 + ty, EPSILON); - assert.close(bbox.width, 20, EPSILON); - assert.close(bbox.height, 10, EPSILON); - elem.remove(); - - const rect = {x: 10, y: 10, width: 10, height: 20}; - const angle = 45; - const origin = {x: 15, y: 20}; // eslint-disable-line no-shadow - tx = 10; // tx right - ty = 10; // tx down - txInRotatedSpace = Math.sqrt(tx * tx + ty * ty); // translate in rotated 45 space. - tyInRotatedSpace = 0; - matrix = 'matrix(1,0,0,1,' + txInRotatedSpace + ',' + tyInRotatedSpace + ')'; - elem = mockCreateSVGElement({ - element: 'rect', - attr: {id: 'rect2', x: rect.x, y: rect.y, width: rect.width, height: rect.height, transform: 'rotate(' + angle + ' ' + origin.x + ',' + origin.y + ') ' + matrix} - }); - svgroot.append(elem); - bbox = getBBoxWithTransform(elem, mockaddSVGElementFromJson, mockPathActions); - const r2 = rotateRect(rect, angle, origin); - assert.close(bbox.x, r2.x + tx, EPSILON, 'rect2 x is ' + r2.x); - assert.close(bbox.y, r2.y + ty, EPSILON, 'rect2 y is ' + r2.y); - assert.close(bbox.width, r2.width, EPSILON, 'rect2 width is' + r2.width); - assert.close(bbox.height, r2.height, EPSILON, 'rect2 height is ' + r2.height); - elem.remove(); - - // Same as previous but wrapped with g and the transform is with the g. - elem = mockCreateSVGElement({ - element: 'rect', - attr: {id: 'rect3', x: rect.x, y: rect.y, width: rect.width, height: rect.height} - }); - const g = mockCreateSVGElement({ - element: 'g', - attr: {transform: 'rotate(' + angle + ' ' + origin.x + ',' + origin.y + ') ' + matrix} - }); - g.append(elem); - svgroot.append(g); - bbox = getBBoxWithTransform(g, mockaddSVGElementFromJson, mockPathActions); - assert.close(bbox.x, r2.x + tx, EPSILON, 'rect2 x is ' + r2.x); - assert.close(bbox.y, r2.y + ty, EPSILON, 'rect2 y is ' + r2.y); - assert.close(bbox.width, r2.width, EPSILON, 'rect2 width is' + r2.width); - assert.close(bbox.height, r2.height, EPSILON, 'rect2 height is ' + r2.height); - g.remove(); - - elem = mockCreateSVGElement({ - element: 'ellipse', - attr: {id: 'ellipse1', cx: '100', cy: '100', rx: '50', ry: '50', transform: 'rotate(45 100,100) ' + matrix} - }); - svgroot.append(elem); - bbox = getBBoxWithTransform(elem, mockaddSVGElementFromJson, mockPathActions); - // TODO: the BBox algorithm is using the bezier control points to calculate the bounding box. Should be 50, 50, 100, 100. - assert.ok(bbox.x > 45 + tx && bbox.x <= 50 + tx); - assert.ok(bbox.y > 45 + ty && bbox.y <= 50 + ty); - assert.ok(bbox.width >= 100 && bbox.width < 110); - assert.ok(bbox.height >= 100 && bbox.height < 110); - elem.remove(); -}); - -QUnit.test('Test getStrokedBBox with stroke-width 10', function (assert) { - const {getStrokedBBox} = utilities; - - const strokeWidth = 10; - let elem = mockCreateSVGElement({ - element: 'path', - attr: {id: 'path', d: 'M0,1 L2,3', 'stroke-width': strokeWidth} - }); - svgroot.append(elem); - let bbox = getStrokedBBox([elem], mockaddSVGElementFromJson, mockPathActions); - assert.deepEqual(bbox, {x: 0 - strokeWidth / 2, y: 1 - strokeWidth / 2, width: 2 + strokeWidth, height: 2 + strokeWidth}); - elem.remove(); - - elem = mockCreateSVGElement({ - element: 'rect', - attr: {id: 'rect', x: '0', y: '1', width: '5', height: '10', 'stroke-width': strokeWidth} - }); - svgroot.append(elem); - bbox = getStrokedBBox([elem], mockaddSVGElementFromJson, mockPathActions); - assert.deepEqual(bbox, {x: 0 - strokeWidth / 2, y: 1 - strokeWidth / 2, width: 5 + strokeWidth, height: 10 + strokeWidth}); - elem.remove(); - - elem = mockCreateSVGElement({ - element: 'line', - attr: {id: 'line', x1: '0', y1: '1', x2: '5', y2: '6', 'stroke-width': strokeWidth} - }); - svgroot.append(elem); - bbox = getStrokedBBox([elem], mockaddSVGElementFromJson, mockPathActions); - assert.deepEqual(bbox, {x: 0 - strokeWidth / 2, y: 1 - strokeWidth / 2, width: 5 + strokeWidth, height: 5 + strokeWidth}); - elem.remove(); - - elem = mockCreateSVGElement({ - element: 'rect', - attr: {id: 'rect', x: '0', y: '1', width: '5', height: '10', 'stroke-width': strokeWidth} - }); - const g = mockCreateSVGElement({ - element: 'g', - attr: {} - }); - g.append(elem); - svgroot.append(g); - bbox = getStrokedBBox([elem], mockaddSVGElementFromJson, mockPathActions); - assert.deepEqual(bbox, {x: 0 - strokeWidth / 2, y: 1 - strokeWidth / 2, width: 5 + strokeWidth, height: 10 + strokeWidth}); - g.remove(); -}); - -QUnit.test("Test getStrokedBBox with stroke-width 'none'", function (assert) { - const {getStrokedBBox} = utilities; - - let elem = mockCreateSVGElement({ - element: 'path', - attr: {id: 'path', d: 'M0,1 L2,3', 'stroke-width': 'none'} - }); - svgroot.append(elem); - let bbox = getStrokedBBox([elem], mockaddSVGElementFromJson, mockPathActions); - assert.deepEqual(bbox, {x: 0, y: 1, width: 2, height: 2}); - elem.remove(); - - elem = mockCreateSVGElement({ - element: 'rect', - attr: {id: 'rect', x: '0', y: '1', width: '5', height: '10', 'stroke-width': 'none'} - }); - svgroot.append(elem); - bbox = getStrokedBBox([elem], mockaddSVGElementFromJson, mockPathActions); - assert.deepEqual(bbox, {x: 0, y: 1, width: 5, height: 10}); - elem.remove(); - - elem = mockCreateSVGElement({ - element: 'line', - attr: {id: 'line', x1: '0', y1: '1', x2: '5', y2: '6', 'stroke-width': 'none'} - }); - svgroot.append(elem); - bbox = getStrokedBBox([elem], mockaddSVGElementFromJson, mockPathActions); - assert.deepEqual(bbox, {x: 0, y: 1, width: 5, height: 5}); - elem.remove(); - - elem = mockCreateSVGElement({ - element: 'rect', - attr: {id: 'rect', x: '0', y: '1', width: '5', height: '10', 'stroke-width': 'none'} - }); - const g = mockCreateSVGElement({ - element: 'g', - attr: {} - }); - g.append(elem); - svgroot.append(g); - bbox = getStrokedBBox([elem], mockaddSVGElementFromJson, mockPathActions); - assert.deepEqual(bbox, {x: 0, y: 1, width: 5, height: 10}); - g.remove(); -}); - -QUnit.test('Test getStrokedBBox with no stroke-width attribute', function (assert) { - const {getStrokedBBox} = utilities; - - let elem = mockCreateSVGElement({ - element: 'path', - attr: {id: 'path', d: 'M0,1 L2,3'} - }); - svgroot.append(elem); - let bbox = getStrokedBBox([elem], mockaddSVGElementFromJson, mockPathActions); - assert.deepEqual(bbox, {x: 0, y: 1, width: 2, height: 2}); - elem.remove(); - - elem = mockCreateSVGElement({ - element: 'rect', - attr: {id: 'rect', x: '0', y: '1', width: '5', height: '10'} - }); - svgroot.append(elem); - bbox = getStrokedBBox([elem], mockaddSVGElementFromJson, mockPathActions); - assert.deepEqual(bbox, {x: 0, y: 1, width: 5, height: 10}); - elem.remove(); - - elem = mockCreateSVGElement({ - element: 'line', - attr: {id: 'line', x1: '0', y1: '1', x2: '5', y2: '6'} - }); - svgroot.append(elem); - bbox = getStrokedBBox([elem], mockaddSVGElementFromJson, mockPathActions); - assert.deepEqual(bbox, {x: 0, y: 1, width: 5, height: 5}); - elem.remove(); - - elem = mockCreateSVGElement({ - element: 'rect', - attr: {id: 'rect', x: '0', y: '1', width: '5', height: '10'} - }); - const g = mockCreateSVGElement({ - element: 'g', - attr: {} - }); - g.append(elem); - svgroot.append(g); - bbox = getStrokedBBox([elem], mockaddSVGElementFromJson, mockPathActions); - assert.deepEqual(bbox, {x: 0, y: 1, width: 5, height: 10}); - g.remove(); -}); - -/** - * Returns radians for degrees. - * @param {Float} degrees - * @returns {Float} - */ -function radians (degrees) { - return degrees * Math.PI / 180; -} - -/** - * - * @param {module:utilities.BBoxObject} point - * @param {Float} angle - * @param {module:math.XYObject} origin - * @returns {module:math.XYObject} - */ -function rotatePoint (point, angle, origin) { // eslint-disable-line no-shadow - if (!origin) { - origin = {x: 0, y: 0}; - } - const x = point.x - origin.x; - const y = point.y - origin.y; - const theta = radians(angle); - return { - x: x * Math.cos(theta) + y * Math.sin(theta) + origin.x, - y: x * Math.sin(theta) + y * Math.cos(theta) + origin.y - }; -} -/** - * - * @param {module:utilities.BBoxObject} rect - * @param {Float} angle - * @param {module:math.XYObject} origin - * @returns {module:utilities.BBoxObject} - */ -function rotateRect (rect, angle, origin) { // eslint-disable-line no-shadow - const tl = rotatePoint({x: rect.x, y: rect.y}, angle, origin); - const tr = rotatePoint({x: rect.x + rect.width, y: rect.y}, angle, origin); - const br = rotatePoint({x: rect.x + rect.width, y: rect.y + rect.height}, angle, origin); - const bl = rotatePoint({x: rect.x, y: rect.y + rect.height}, angle, origin); - - const minx = Math.min(tl.x, tr.x, bl.x, br.x); - const maxx = Math.max(tl.x, tr.x, bl.x, br.x); - const miny = Math.min(tl.y, tr.y, bl.y, br.y); - const maxy = Math.max(tl.y, tr.y, bl.y, br.y); - - return { - x: minx, - y: miny, - width: (maxx - minx), - height: (maxy - miny) - }; -} diff --git a/test/utilities_performance_test.html b/test/utilities_performance_test.html deleted file mode 100644 index ecc8cc27..00000000 --- a/test/utilities_performance_test.html +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - Performance Unit Tests for utilities.js - - - - - - - - - -

                              Performance Unit Tests for utilities.js

                              -

                              -

                              -
                                - -
                                -
                                - - - - - - - -
                                - - - - - - - - - Layer 1 - - - - - - - - - - - - Some text - - - - Layer 2 - - - - -
                                -
                                -
                                - - - diff --git a/test/utilities_performance_test.js b/test/utilities_performance_test.js deleted file mode 100644 index 8778adc6..00000000 --- a/test/utilities_performance_test.js +++ /dev/null @@ -1,183 +0,0 @@ -/* eslint-env qunit */ -import '../editor/svgpathseg.js'; -import {NS} from '../editor/namespaces.js'; -import * as utilities from '../editor/utilities.js'; -import * as transformlist from '../editor/svgtransformlist.js'; -import * as math from '../editor/math.js'; - -// log function -QUnit.log((details) => { - if (window.console && window.console.log) { - window.console.log(details.result + ' :: ' + details.message); - } -}); - -const currentLayer = document.getElementById('layer1'); - -/** - * Create an SVG element for a mock. - * @param {module:utilities.SVGElementJSON} jsonMap - * @returns {SVGElement} - */ -function mockCreateSVGElement (jsonMap) { - const elem = document.createElementNS(NS.SVG, jsonMap.element); - Object.entries(jsonMap.attr).forEach(([attr, value]) => { - elem.setAttribute(attr, value); - }); - return elem; -} - -/** - * Mock of {@link module:utilities.EditorContext#addSVGElementFromJson}. - * @param {module:utilities.SVGElementJSON} json - * @returns {SVGElement} - */ -function mockaddSVGElementFromJson (json) { - const elem = mockCreateSVGElement(json); - currentLayer.append(elem); - return elem; -} - -// const svg = document.createElementNS(NS.SVG, 'svg'); -const groupWithMatrixTransform = document.getElementById('svg_group_with_matrix_transform'); -const textWithMatrixTransform = document.getElementById('svg_text_with_matrix_transform'); - -/** - * Toward performance testing, fill document with clones of element. - * @param {SVGElement} elem - * @param {Integer} count - * @returns {void} - */ -function fillDocumentByCloningElement (elem, count) { - const elemId = elem.getAttribute('id') + '-'; - for (let index = 0; index < count; index++) { - const clone = elem.cloneNode(true); // t: deep clone - // Make sure you set a unique ID like a real document. - clone.setAttribute('id', elemId + index); - const {parentNode} = elem; - parentNode.append(clone); - } -} - -QUnit.module('svgedit.utilities_performance'); - -const mockPathActions = { - resetOrientation (path) { - if (utilities.isNullish(path) || path.nodeName !== 'path') { return false; } - const tlist = transformlist.getTransformList(path); - const m = math.transformListToTransform(tlist).matrix; - tlist.clear(); - path.removeAttribute('transform'); - const segList = path.pathSegList; - - const len = segList.numberOfItems; - // let lastX, lastY; - - for (let i = 0; i < len; ++i) { - const seg = segList.getItem(i); - const type = seg.pathSegType; - if (type === 1) { - continue; - } - const pts = []; - ['', 1, 2].forEach(function (n, j) { - const x = seg['x' + n], - y = seg['y' + n]; - if (x !== undefined && y !== undefined) { - const pt = math.transformPoint(x, y, m); - pts.splice(pts.length, 0, pt.x, pt.y); - } - }); - // path.replacePathSeg(type, i, pts, path); - } - - // utilities.reorientGrads(path, m); - return undefined; - } -}; - -// ////////////////////////////////////////////////////////// -// Performance times with various browsers on Macbook 2011 8MB RAM OS X El Capitan 10.11.4 -// -// To see 'Before Optimization' performance, making the following two edits. -// 1. utilities.getStrokedBBox - change if( elems.length === 1) to if( false && elems.length === 1) -// 2. utilities.getBBoxWithTransform - uncomment 'Old technique that was very slow' - -// Chrome -// Before Optimization -// Pass1 svgCanvas.getStrokedBBox total ms 4,218, ave ms 41.0, min/max 37 51 -// Pass2 svgCanvas.getStrokedBBox total ms 4,458, ave ms 43.3, min/max 32 63 -// Optimized Code -// Pass1 svgCanvas.getStrokedBBox total ms 1,112, ave ms 10.8, min/max 9 20 -// Pass2 svgCanvas.getStrokedBBox total ms 34, ave ms 0.3, min/max 0 20 - -// Firefox -// Before Optimization -// Pass1 svgCanvas.getStrokedBBox total ms 3,794, ave ms 36.8, min/max 33 48 -// Pass2 svgCanvas.getStrokedBBox total ms 4,049, ave ms 39.3, min/max 28 53 -// Optimized Code -// Pass1 svgCanvas.getStrokedBBox total ms 104, ave ms 1.0, min/max 0 23 -// Pass2 svgCanvas.getStrokedBBox total ms 71, ave ms 0.7, min/max 0 23 - -// Safari -// Before Optimization -// Pass1 svgCanvas.getStrokedBBox total ms 4,840, ave ms 47.0, min/max 45 62 -// Pass2 svgCanvas.getStrokedBBox total ms 4,849, ave ms 47.1, min/max 34 62 -// Optimized Code -// Pass1 svgCanvas.getStrokedBBox total ms 42, ave ms 0.4, min/max 0 23 -// Pass2 svgCanvas.getStrokedBBox total ms 17, ave ms 0.2, min/max 0 23 - -QUnit.test('Test svgCanvas.getStrokedBBox() performance with matrix transforms', function (assert) { - const done = assert.async(); - assert.expect(2); - const {getStrokedBBox} = utilities; - const {children} = currentLayer; - - let lastTime, now, - min = Number.MAX_VALUE, - max = 0, - total = 0; - - fillDocumentByCloningElement(groupWithMatrixTransform, 50); - fillDocumentByCloningElement(textWithMatrixTransform, 50); - - // The first pass through all elements is slower. - const count = children.length; - const start = lastTime = now = Date.now(); - // Skip the first child which is the title. - for (let index = 1; index < count; index++) { - const child = children[index]; - /* const obj = */ getStrokedBBox([child], mockaddSVGElementFromJson, mockPathActions); - now = Date.now(); const delta = now - lastTime; lastTime = now; - total += delta; - min = Math.min(min, delta); - max = Math.max(max, delta); - } - total = lastTime - start; - const ave = total / count; - assert.ok(ave < 20, 'svgedit.utilities.getStrokedBBox average execution time is less than 20 ms'); - console.log('Pass1 svgCanvas.getStrokedBBox total ms ' + total + ', ave ms ' + ave.toFixed(1) + ',\t min/max ' + min + ' ' + max); - - // The second pass is two to ten times faster. - setTimeout(function () { - const ct = children.length; - - const strt = lastTime = now = Date.now(); - // Skip the first child which is the title. - for (let index = 1; index < ct; index++) { - const child = children[index]; - /* const obj = */ getStrokedBBox([child], mockaddSVGElementFromJson, mockPathActions); - now = Date.now(); const delta = now - lastTime; lastTime = now; - total += delta; - min = Math.min(min, delta); - max = Math.max(max, delta); - } - - total = lastTime - strt; - const avg = total / ct; - assert.ok(avg < 2, 'svgedit.utilities.getStrokedBBox average execution time is less than 1 ms'); - console.log('Pass2 svgCanvas.getStrokedBBox total ms ' + total + ', ave ms ' + avg.toFixed(1) + ',\t min/max ' + min + ' ' + max); - - done(); - }); -}); diff --git a/test/utilities_test.html b/test/utilities_test.html deleted file mode 100644 index 5a583bc3..00000000 --- a/test/utilities_test.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - Unit Tests for utilities.js - - - - - - - -

                                Unit Tests for utilities.js

                                -

                                -

                                -
                                  -
                                  - - diff --git a/test/utilities_test.js b/test/utilities_test.js deleted file mode 100644 index 790e5c63..00000000 --- a/test/utilities_test.js +++ /dev/null @@ -1,378 +0,0 @@ -/* eslint-env qunit */ - -import * as browser from '../editor/browser.js'; -import * as utilities from '../editor/utilities.js'; -import {NS} from '../editor/namespaces.js'; - -// log function -QUnit.log((details) => { - if (window.console && window.console.log) { - window.console.log(details.result + ' :: ' + details.message); - } -}); - -/** - * Create an element for test. - * @param {module:utilities.SVGElementJSON} jsonMap - * @returns {SVGElement} - */ -function mockCreateSVGElement (jsonMap) { - const elem = document.createElementNS(NS.SVG, jsonMap.element); - Object.entries(jsonMap.attr).forEach(([attr, value]) => { - elem.setAttribute(attr, value); - }); - return elem; -} -/** - * Adds SVG Element per parameters and appends to root. - * @param {module:utilities.SVGElementJSON} json - * @returns {SVGElement} - */ -function mockaddSVGElementFromJson (json) { - const elem = mockCreateSVGElement(json); - svgroot.append(elem); - return elem; -} -const mockPathActions = {resetOrientation () { /* */ }}; -let mockHistorySubCommands = []; -const mockHistory = { - BatchCommand: class { - // eslint-disable-next-line class-methods-use-this - addSubCommand (cmd) { - mockHistorySubCommands.push(cmd); - } - }, - RemoveElementCommand: class { - // Longhand needed since used as a constructor - constructor (elem, nextSibling, parent) { - this.elem = elem; - this.nextSibling = nextSibling; - this.parent = parent; - } - }, - InsertElementCommand: class { - constructor (path) { // Longhand needed since used as a constructor - this.path = path; - } - } -}; -const mockCount = { - clearSelection: 0, - addToSelection: 0, - addCommandToHistory: 0 -}; - -/** - * Increments clear seleciton count for mock test. - * @returns {void} - */ -function mockClearSelection () { - mockCount.clearSelection++; -} -/** -* Increments add selection count for mock test. - * @returns {void} - */ -function mockAddToSelection () { - mockCount.addToSelection++; -} -/** -* Increments add command to history count for mock test. - * @returns {void} - */ -function mockAddCommandToHistory () { - mockCount.addCommandToHistory++; -} - -const svg = document.createElementNS(NS.SVG, 'svg'); -const sandbox = document.getElementById('sandbox'); -const svgroot = mockCreateSVGElement({ - element: 'svg', - attr: {id: 'svgroot'} -}); -sandbox.append(svgroot); - -QUnit.module('svgedit.utilities', { - beforeEach () { - mockHistorySubCommands = []; - mockCount.clearSelection = 0; - mockCount.addToSelection = 0; - mockCount.addCommandToHistory = 0; - }, - afterEach () { /* */ } -}); - -QUnit.test('Test svgedit.utilities package', function (assert) { - assert.expect(3); - - assert.ok(utilities); - assert.ok(utilities.toXml); - assert.equal(typeof utilities.toXml, typeof function () { /* */ }); -}); - -QUnit.test('Test svgedit.utilities.toXml() function', function (assert) { - assert.expect(6); - const {toXml} = utilities; - - assert.equal(toXml('a'), 'a'); - assert.equal(toXml('ABC_'), 'ABC_'); - assert.equal(toXml('PB&J'), 'PB&J'); - assert.equal(toXml('2 < 5'), '2 < 5'); - assert.equal(toXml('5 > 2'), '5 > 2'); - assert.equal(toXml('\'<&>"'), ''<&>"'); -}); - -QUnit.test('Test svgedit.utilities.fromXml() function', function (assert) { - assert.expect(6); - const {fromXml} = utilities; - - assert.equal(fromXml('a'), 'a'); - assert.equal(fromXml('ABC_'), 'ABC_'); - assert.equal(fromXml('PB&J'), 'PB&J'); - assert.equal(fromXml('2 < 5'), '2 < 5'); - assert.equal(fromXml('5 > 2'), '5 > 2'); - assert.equal(fromXml('<&>'), '<&>'); -}); - -QUnit.test('Test svgedit.utilities.encode64() function', function (assert) { - assert.expect(4); - const {encode64} = utilities; - - assert.equal(encode64('abcdef'), 'YWJjZGVm'); - assert.equal(encode64('12345'), 'MTIzNDU='); - assert.equal(encode64(' '), 'IA=='); - assert.equal(encode64('`~!@#$%^&*()-_=+[{]}\\|;:\'",<.>/?'), 'YH4hQCMkJV4mKigpLV89K1t7XX1cfDs6JyIsPC4+Lz8='); -}); - -QUnit.test('Test svgedit.utilities.decode64() function', function (assert) { - assert.expect(4); - const {decode64} = utilities; - - assert.equal(decode64('YWJjZGVm'), 'abcdef'); - assert.equal(decode64('MTIzNDU='), '12345'); - assert.equal(decode64('IA=='), ' '); - assert.equal(decode64('YH4hQCMkJV4mKigpLV89K1t7XX1cfDs6JyIsPC4+Lz8='), '`~!@#$%^&*()-_=+[{]}\\|;:\'",<.>/?'); -}); - -QUnit.test('Test svgedit.utilities.convertToXMLReferences() function', function (assert) { - assert.expect(1); - - const convert = utilities.convertToXMLReferences; - assert.equal(convert('ABC'), 'ABC'); - // assert.equal(convert('�BC'), 'ÀBC'); -}); - -QUnit.test('Test svgedit.utilities.bboxToObj() function', function (assert) { - assert.expect(5); - const {bboxToObj} = utilities; - - const rect = svg.createSVGRect(); - rect.x = 1; - rect.y = 2; - rect.width = 3; - rect.height = 4; - - const obj = bboxToObj(rect); - assert.equal(typeof obj, typeof {}); - assert.equal(obj.x, 1); - assert.equal(obj.y, 2); - assert.equal(obj.width, 3); - assert.equal(obj.height, 4); -}); - -QUnit.test('Test getUrlFromAttr', function (assert) { - assert.expect(4); - - assert.equal(utilities.getUrlFromAttr('url(#foo)'), '#foo'); - assert.equal(utilities.getUrlFromAttr('url(somefile.svg#foo)'), 'somefile.svg#foo'); - assert.equal(utilities.getUrlFromAttr('url("#foo")'), '#foo'); - assert.equal(utilities.getUrlFromAttr('url("#foo")'), '#foo'); -}); - -QUnit.test('Test getPathBBox', function (assert) { - if (browser.supportsPathBBox()) { - assert.expect(0); - return; - } - assert.expect(3); - const doc = utilities.text2xml(''); - const path = doc.createElementNS(NS.SVG, 'path'); - path.setAttribute('d', 'm0,0l5,0l0,5l-5,0l0,-5z'); - const bb = utilities.getPathBBox(path); - assert.equal(typeof bb, 'object', 'BBox returned object'); - assert.ok(bb.x && !isNaN(bb.x)); - assert.ok(bb.y && !isNaN(bb.y)); -}); - -QUnit.test('Test getPathDFromSegments', function (assert) { - const {getPathDFromSegments} = utilities; - - const doc = utilities.text2xml(''); - const path = doc.createElementNS(NS.SVG, 'path'); - path.setAttribute('d', 'm0,0l5,0l0,5l-5,0l0,-5z'); - let d = getPathDFromSegments([ - ['M', [1, 2]], - ['Z', []] - ]); - assert.equal(d, 'M1,2 Z'); - - d = getPathDFromSegments([ - ['M', [1, 2]], - ['M', [3, 4]], - ['Z', []] - ]); - assert.equal(d, 'M1,2 M3,4 Z'); - - d = getPathDFromSegments([ - ['M', [1, 2]], - ['C', [3, 4, 5, 6]], - ['Z', []] - ]); - assert.equal(d, 'M1,2 C3,4 5,6 Z'); -}); - -QUnit.test('Test getPathDFromElement', function (assert) { - const {getPathDFromElement} = utilities; - - let elem = mockCreateSVGElement({ - element: 'path', - attr: {id: 'path', d: 'M0,1 Z'} - }); - svgroot.append(elem); - assert.equal(getPathDFromElement(elem), 'M0,1 Z'); - elem.remove(); - - elem = mockCreateSVGElement({ - element: 'rect', - attr: {id: 'rect', x: '0', y: '1', width: '5', height: '10'} - }); - svgroot.append(elem); - assert.equal(getPathDFromElement(elem), 'M0,1 L5,1 L5,11 L0,11 L0,1 Z'); - elem.remove(); - - elem = mockCreateSVGElement({ - element: 'rect', - attr: {id: 'roundrect', x: '0', y: '1', rx: '2', ry: '3', width: '10', height: '11'} - }); - svgroot.append(elem); - const closeEnough = /M0,4 C0,2.3\d* 0.9\d*,1 2,1 L8,1 C9.0\d*,1 10,2.3\d* 10,4 L10,9 C10,10.6\d* 9.08675799086758,12 8,12 L2,12 C0.9\d*,12 0,10.6\d* 0,9 L0,4 Z/; - assert.equal(closeEnough.test(getPathDFromElement(elem)), true); - elem.remove(); - - elem = mockCreateSVGElement({ - element: 'line', - attr: {id: 'line', x1: '0', y1: '1', x2: '5', y2: '6'} - }); - svgroot.append(elem); - assert.equal(getPathDFromElement(elem), 'M0,1L5,6'); - elem.remove(); - - elem = mockCreateSVGElement({ - element: 'circle', - attr: {id: 'circle', cx: '10', cy: '11', rx: '5', ry: '10'} - }); - svgroot.append(elem); - assert.equal(getPathDFromElement(elem), 'M10,11 C10,11 10,11 10,11 C10,11 10,11 10,11 C10,11 10,11 10,11 C10,11 10,11 10,11 Z'); - elem.remove(); - - elem = mockCreateSVGElement({ - element: 'polyline', - attr: {id: 'polyline', points: '0,1 5,1 5,11 0,11'} - }); - svgroot.append(elem); - assert.equal(getPathDFromElement(elem), 'M0,1 5,1 5,11 0,11'); - elem.remove(); - - assert.equal(getPathDFromElement({tagName: 'something unknown'}), undefined); -}); - -QUnit.test('Test getBBoxOfElementAsPath', function (assert) { - /** - * Wrap `utilities.getBBoxOfElementAsPath` to convert bbox to object for testing. - * @type {module:utilities.getBBoxOfElementAsPath} - */ - function getBBoxOfElementAsPath (elem, addSVGElementFromJson, pathActions) { - const bbox = utilities.getBBoxOfElementAsPath(elem, addSVGElementFromJson, pathActions); - return utilities.bboxToObj(bbox); // need this for assert.equal() to work. - } - - let elem = mockCreateSVGElement({ - element: 'path', - attr: {id: 'path', d: 'M0,1 Z'} - }); - svgroot.append(elem); - let bbox = getBBoxOfElementAsPath(elem, mockaddSVGElementFromJson, mockPathActions); - assert.deepEqual(bbox, {x: 0, y: 1, width: 0, height: 0}); - elem.remove(); - - elem = mockCreateSVGElement({ - element: 'rect', - attr: {id: 'rect', x: '0', y: '1', width: '5', height: '10'} - }); - svgroot.append(elem); - bbox = getBBoxOfElementAsPath(elem, mockaddSVGElementFromJson, mockPathActions); - assert.deepEqual(bbox, {x: 0, y: 1, width: 5, height: 10}); - elem.remove(); - - elem = mockCreateSVGElement({ - element: 'line', - attr: {id: 'line', x1: '0', y1: '1', x2: '5', y2: '6'} - }); - svgroot.append(elem); - bbox = getBBoxOfElementAsPath(elem, mockaddSVGElementFromJson, mockPathActions); - assert.deepEqual(bbox, {x: 0, y: 1, width: 5, height: 5}); - elem.remove(); - - // TODO: test element with transform. Need resetOrientation above to be working or mock it. -}); - -QUnit.test('Test convertToPath rect', function (assert) { - const {convertToPath} = utilities; - const attrs = { - fill: 'red', - stroke: 'white', - 'stroke-width': '1', - visibility: 'hidden' - }; - - const elem = mockCreateSVGElement({ - element: 'rect', - attr: {id: 'rect', x: '0', y: '1', width: '5', height: '10'} - }); - svgroot.append(elem); - const path = convertToPath(elem, attrs, mockaddSVGElementFromJson, mockPathActions, mockClearSelection, mockAddToSelection, mockHistory, mockAddCommandToHistory); - assert.equal(path.getAttribute('d'), 'M0,1 L5,1 L5,11 L0,11 L0,1 Z'); - assert.equal(path.getAttribute('visibilituy'), null); - assert.equal(path.id, 'rect'); - assert.equal(path.parentNode, svgroot); - assert.equal(elem.parentNode, null); - assert.equal(mockHistorySubCommands.length, 2); - assert.equal(mockCount.clearSelection, 1); - assert.equal(mockCount.addToSelection, 1); - assert.equal(mockCount.addCommandToHistory, 1); - path.remove(); -}); - -QUnit.test('Test convertToPath unknown element', function (assert) { - const {convertToPath} = utilities; - const attrs = { - fill: 'red', - stroke: 'white', - 'stroke-width': '1', - visibility: 'hidden' - }; - - const elem = { - tagName: 'something unknown', - id: 'something-unknown', - getAttribute (attr) { return ''; }, - parentNode: svgroot - }; - const path = convertToPath(elem, attrs, mockaddSVGElementFromJson, mockPathActions, mockClearSelection, mockAddToSelection, mockHistory, mockAddCommandToHistory); - assert.equal(path, null); - assert.equal(elem.parentNode, svgroot); - assert.equal(mockHistorySubCommands.length, 0); - assert.equal(mockCount.clearSelection, 0); - assert.equal(mockCount.addToSelection, 0); - assert.equal(mockCount.addCommandToHistory, 0); -});