From e4e7e11da50c8129bcf31de03a2849f323aa4299 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Ulrich-Matthias=20Sch=C3=A4fer?= Date: Wed, 8 Apr 2020 10:08:36 +1000 Subject: [PATCH] fix defs and reference, tests for Element --- CHANGELOG.md | 2 + spec/spec/elements/Element.js | 276 ++++++++++++++++++++++++++++++++++ spec/spec/types/Box.js | 9 ++ src/elements/Element.js | 19 +-- src/types/Box.js | 10 ++ 5 files changed, 302 insertions(+), 14 deletions(-) create mode 100644 spec/spec/elements/Element.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 83af8ea..54cb5bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ The document follows the conventions described in [“Keep a CHANGELOG”](http: - fixed `add()` which correctly removes namespaces of non-root svg elements now when added to another svg element (#1086) - fixed `isRoot()` which correctly returns false, if the element is in a document-fragment - fixed `replace()` which works without a parent now, too + - fixed `defs()` which correctly returns `null` when called on a detached node that is not a root node + - fixed `reference()` which correctly returns `null` instead of throwing when specifying an attribute which holds a number ### Added - added second Parameter to `SVG(el, isHTML)` which allows to explicitely create elements in the HTML namespace (#1058) diff --git a/spec/spec/elements/Element.js b/spec/spec/elements/Element.js new file mode 100644 index 0000000..0d7fa55 --- /dev/null +++ b/spec/spec/elements/Element.js @@ -0,0 +1,276 @@ +/* globals describe, expect, it, beforeEach, spyOn, jasmine, container */ + +import { Element, create, Rect, G, SVG } from '../../../src/main.js' +const { any, objectContaining } = jasmine + +describe('Element.js', function () { + let element + + beforeEach(() => { + element = new Element(create('rect')) + }) + + describe('()', () => { + it('creates a new object of type Element', () => { + expect(element).toEqual(any(Element)) + }) + + it('sets passed attributes on the element', () => { + expect(new Element(create('rect'), { id: 'foo' }).id()).toBe('foo') + }) + + it('references the instance on the passed node', () => { + expect(element.node.instance).toBe(element) + }) + + it('sets the dom property to an empty object', () => { + expect(element.dom).toEqual({}) + }) + + it('hydrates the dom property with data found in the dom', () => { + element.dom = { foo: 'bar' } + element.writeDataToDom() + expect(new Element(element.node).dom).toEqual({ foo: 'bar' }) + }) + + it('falls back to empty object when attribute is null', () => { + element.node.setAttribute('svgjs:data', 'null') + expect(new Element(element.node).dom).toEqual({}) + }) + }) + + describe('center()', () => { + it('calls cx and cy with passed parameters and returns itself', () => { + const spyCx = spyOn(element, 'cx').and.callThrough() + const spyCy = spyOn(element, 'cy').and.callThrough() + expect(element.center(1, 2)).toBe(element) + expect(spyCx).toHaveBeenCalledWith(1) + expect(spyCy).toHaveBeenCalledWith(2) + }) + }) + + describe('cx()', () => { + it('gets the elements center along the x axis', () => { + element.attr({ x: 10, width: 100 }) + expect(element.cx()).toBe(60) + }) + + it('centers the element along the x axis and returns itself', () => { + element.attr({ x: 10, width: 100 }) + expect(element.cx(100)).toBe(element) + expect(element.attr('x')).toBe(50) + }) + }) + + describe('cy()', () => { + it('gets the elements center along the y axis', () => { + element.attr({ y: 10, height: 100 }) + expect(element.cy()).toBe(60) + }) + + it('centers the element along the y axis and returns itself', () => { + element.attr({ y: 10, height: 100 }) + expect(element.cy(100)).toBe(element) + expect(element.attr('y')).toBe(50) + }) + }) + + describe('defs()', () => { + it('returns null if detached', () => { + expect(new Rect().defs()).toBe(null) + expect(new G().put(new Rect()).defs()).toBe(null) + }) + + it('calls defs on root node', () => { + const canvas = SVG() + const rect = canvas.rect(100, 100) + const spy = spyOn(canvas, 'defs').and.callThrough() + expect(rect.defs()).toBe(canvas.defs()) + expect(spy.calls.count()).toBe(2) + }) + }) + + describe('dmove()', () => { + it('calls dx and dy with passed parameters and returns itself', () => { + const spyDx = spyOn(element, 'dx').and.callThrough() + const spyDy = spyOn(element, 'dy').and.callThrough() + expect(element.dmove(1, 2)).toBe(element) + expect(spyDx).toHaveBeenCalledWith(1) + expect(spyDy).toHaveBeenCalledWith(2) + }) + }) + + describe('dx()', () => { + it('moves by zero by default', () => { + element.attr({ x: 10, width: 100 }) + expect(element.dx().x()).toBe(10) + }) + + it('moves the element along the x axis relatively and returns itself', () => { + element.attr({ x: 10, width: 100 }) + expect(element.dx(100)).toBe(element) + expect(element.attr('x')).toBe(110) + }) + }) + + describe('dy()', () => { + it('moves by zero by default', () => { + element.attr({ y: 10, height: 100 }) + expect(element.dy().y()).toBe(10) + }) + + it('moves the element along the x axis relatively and returns itself', () => { + element.attr({ y: 10, height: 100 }) + expect(element.dy(100)).toBe(element) + expect(element.attr('y')).toBe(110) + }) + }) + + describe('root()', () => { + it('returns the root of this element', () => { + const canvas = SVG() + const rect = canvas.rect() + expect(rect.root()).toBe(canvas) + }) + + it('returns null if element is detached', () => { + expect(new G().put(new Rect()).root()).toBe(null) + }) + }) + + describe('getEventHolder()', () => { + it('returns itself', () => { + expect(element.getEventHolder()).toBe(element) + }) + }) + + describe('height()', () => { + it('calls attr with height', () => { + const spy = spyOn(element, 'attr') + element.height(123) + expect(spy).toHaveBeenCalledWith('height', 123) + }) + }) + + describe('move()', () => { + it('calls x and y with passed parameters and returns itself', () => { + const spyx = spyOn(element, 'x').and.callThrough() + const spyy = spyOn(element, 'y').and.callThrough() + expect(element.move(1, 2)).toBe(element) + expect(spyx).toHaveBeenCalledWith(1) + expect(spyy).toHaveBeenCalledWith(2) + }) + }) + + describe('parents()', () => { + it('returns array of parents until the passed element or root svg', () => { + const canvas = SVG().addTo(container) + const group1 = canvas.group().addClass('test') + const group2 = group1.group() + const group3 = group2.group() + const rect = group3.rect(100, 100) + + expect(rect.parents('.test')).toEqual([ group3, group2, group1 ]) + expect(rect.parents(group2)).toEqual([ group3, group2 ]) + expect(rect.parents(group1).length).toBe(3) + expect(rect.parents()).toEqual([ group3, group2, group1, canvas ]) + }) + }) + + describe('reference()', () => { + it('gets a referenced element from a given attribute', () => { + const canvas = SVG().addTo(container) + const rect = canvas.defs().rect(100, 100) + const use = canvas.use(rect) + const mark = canvas.marker(10, 10) + const path = canvas.path('M0 0 50 50').marker('end', mark) + + expect(use.reference('href')).toBe(rect) + expect(path.reference('marker-end')).toBe(mark) + expect(rect.reference('width')).toBe(null) + }) + }) + + describe('setData()', () => { + it('sets the given data to the dom property and returns itself', () => { + expect(element.setData({ foo: 'bar' })).toBe(element) + expect(element.dom).toEqual({ foo: 'bar' }) + }) + }) + + describe('size()', () => { + it('calls width and height with passed parameters and returns itself', () => { + const spyWidth = spyOn(element, 'width').and.callThrough() + const spyHeight = spyOn(element, 'height').and.callThrough() + expect(element.size(1, 2)).toBe(element) + expect(spyWidth).toHaveBeenCalledWith(objectContaining({ value: 1 })) + expect(spyHeight).toHaveBeenCalledWith(objectContaining({ value: 2 })) + }) + + it('changes height proportionally if null', () => { + const canvas = SVG().addTo(container) + const element = canvas.rect(100, 100) + const spyWidth = spyOn(element, 'width').and.callThrough() + const spyHeight = spyOn(element, 'height').and.callThrough() + expect(element.size(200, null)).toBe(element) + expect(spyWidth).toHaveBeenCalledWith(objectContaining({ value: 200 })) + expect(spyHeight).toHaveBeenCalledWith(objectContaining({ value: 200 })) + }) + + it('changes width proportionally if null', () => { + const canvas = SVG().addTo(container) + const element = canvas.rect(100, 100) + const spyWidth = spyOn(element, 'width').and.callThrough() + const spyHeight = spyOn(element, 'height').and.callThrough() + expect(element.size(null, 200)).toBe(element) + expect(spyWidth).toHaveBeenCalledWith(objectContaining({ value: 200 })) + expect(spyHeight).toHaveBeenCalledWith(objectContaining({ value: 200 })) + }) + }) + + describe('width()', () => { + it('calls attr with width', () => { + const spy = spyOn(element, 'attr') + element.width(123) + expect(spy).toHaveBeenCalledWith('width', 123) + }) + }) + + describe('writeDataToDom()', () => { + it('removes previously set data', () => { + element.node.setAttribute('svgjs:data', JSON.stringify({ foo: 'bar' })) + element.writeDataToDom() + expect(element.node.getAttribute('svgjs:data')).toBe(null) + }) + + it('writes data from the dom property into the dom', () => { + element.dom = { foo: 'bar' } + element.writeDataToDom() + expect(element.node.getAttribute('svgjs:data')).toBe(JSON.stringify({ foo: 'bar' })) + }) + + it('recursively calls writeDataToDom on all children', () => { + const g = new G() + const rect = g.rect(100, 100) + const spy = spyOn(rect, 'writeDataToDom') + g.writeDataToDom() + expect(spy).toHaveBeenCalled() + }) + }) + + describe('x()', () => { + it('calls attr with x', () => { + const spy = spyOn(element, 'attr') + element.x(123) + expect(spy).toHaveBeenCalledWith('x', 123) + }) + }) + + describe('y()', () => { + it('calls attr with y', () => { + const spy = spyOn(element, 'attr') + element.y(123) + expect(spy).toHaveBeenCalledWith('y', 123) + }) + }) +}) diff --git a/spec/spec/types/Box.js b/spec/spec/types/Box.js index 9735304..34b1e20 100644 --- a/spec/spec/types/Box.js +++ b/spec/spec/types/Box.js @@ -174,6 +174,15 @@ describe('Box.js', () => { }) }) + describe('inside()', () => { + it('checks if a point is in the elements borders', () => { + const canvas = SVG().addTo(container) + const rect = canvas.rect(100, 100) + expect(rect.inside(50, 50)).toBe(true) + expect(rect.inside(101, 101)).toBe(false) + }) + }) + describe('viewbox()', () => { it('sets the viewbox of the element', () => { const canvas = viewbox.call(SVG().addTo(container), 10, 10, 200, 200) diff --git a/src/elements/Element.js b/src/elements/Element.js index d75db17..bb11611 100644 --- a/src/elements/Element.js +++ b/src/elements/Element.js @@ -1,4 +1,4 @@ -import { bbox, rbox } from '../types/Box.js' +import { bbox, rbox, inside } from '../types/Box.js' import { ctm, screenCTM } from '../types/Matrix.js' import { extend, @@ -52,7 +52,8 @@ export default class Element extends Dom { // Get defs defs () { - return this.root().defs() + const root = this.root() + return root && root.defs() } // Relative move over x and y axes @@ -85,16 +86,6 @@ export default class Element extends Dom { return this.attr('height', height) } - // Checks whether the given point inside the bounding box of the element - inside (x, y) { - const box = this.bbox() - - return x > box.x - && y > box.y - && x < box.x + box.width - && y < box.y + box.height - } - // Move element to given x and y values move (x, y) { return this.x(x).y(y) @@ -126,7 +117,7 @@ export default class Element extends Dom { attr = this.attr(attr) if (!attr) return null - const m = attr.match(reference) + const m = (attr + '').match(reference) return m ? makeInstance(m[1]) : null } @@ -174,7 +165,7 @@ export default class Element extends Dom { } extend(Element, { - bbox, rbox, point, ctm, screenCTM + bbox, rbox, inside, point, ctm, screenCTM }) register(Element, 'Element') diff --git a/src/types/Box.js b/src/types/Box.js index 6d7d8a9..ae0f2cd 100644 --- a/src/types/Box.js +++ b/src/types/Box.js @@ -148,6 +148,16 @@ export function rbox (el) { return box.addOffset() } +// Checks whether the given point is inside the bounding box +export function inside (x, y) { + const box = this.bbox() + + return x > box.x + && y > box.y + && x < box.x + box.width + && y < box.y + box.height +} + registerMethods({ viewbox: { viewbox (x, y, width, height) { -- 2.39.5