]> source.dussan.org Git - svg.js.git/commitdiff
finished tests for Point, PointArray, PathArray and SVGArray
authorUlrich-Matthias Schäfer <ulima.ums@googlemail.com>
Wed, 29 Apr 2020 06:25:47 +0000 (16:25 +1000)
committerUlrich-Matthias Schäfer <ulima.ums@googlemail.com>
Wed, 29 Apr 2020 06:25:47 +0000 (16:25 +1000)
CHANGELOG.md
spec/spec/types/Number.js [deleted file]
spec/spec/types/PathArray.js [new file with mode: 0644]
spec/spec/types/Point.js
spec/spec/types/PointArray.js
spec/spec/types/SVGArray.js [new file with mode: 0644]
spec/spec/types/SVGNumber.js [new file with mode: 0644]
src/types/PathArray.js
src/types/PointArray.js

index d89ea911bfe83f3335b3e25db36812d572a9d404..6292c29cc002195552f76081b2359e93e5883350 100644 (file)
@@ -28,6 +28,8 @@ The document follows the conventions described in [“Keep a CHANGELOG”](http:
  - fixed `flatten()` which correctly flattens now but doesnt accept parameters anymore (makes no sense)
  - fixed `ungroup()` which now inserts the elements at the correct position in the correct order and has position as second argument now
  - fixed `position` for `transform()` to also allow a position of 0
+ - fixed `bbox()` of `PathArray` and `PointArray` which returns an instance of `Box` now
+ - fixed bug in creation of PointArray which had still references to source arrays in it
 
 ### Added
  - added second Parameter to `SVG(el, isHTML)` which allows to explicitely create elements in the HTML namespace (#1058)
@@ -41,10 +43,12 @@ The document follows the conventions described in [“Keep a CHANGELOG”](http:
  - added position argument for `toRoot()`
  - added attr syntax for `data()` method
  - added index and array parameter when passing a function to `List.each()` so that it mostly behaves like map
+ - added possibility to pass transform object to `PointArray.transform()` ----- to Point
  - added lots of tests in es6 format
 
 ### Deleted
   - deleted undocumented `Matrix.compose()` method which did the same as `new Matrix()` or `Matrix.transform()`
+  - deleted undocumented `Path.morph()` and `Path.at()` which was replaced with Morphables in v3
 
 ## [3.0.16] - 2019-11-12
 
diff --git a/spec/spec/types/Number.js b/spec/spec/types/Number.js
deleted file mode 100644 (file)
index d7ef2fd..0000000
+++ /dev/null
@@ -1,184 +0,0 @@
-/* globals describe, expect, it, beforeEach, jasmine */
-
-import { Number as SVGNumber } from '../../../src/main.js'
-
-const { any } = jasmine
-
-describe('Number.js', () => {
-  var number
-
-  beforeEach(() => {
-    number = new SVGNumber()
-  })
-
-  describe('new', () => {
-    it('is zero', () => {
-      expect(number.value).toBe(0)
-    })
-    it('has a blank unit', () => {
-      expect(number.unit).toBe('')
-    })
-    it('accepts the unit as a second argument', () => {
-      number = new SVGNumber(30, '%')
-      expect(number.value).toBe(30)
-      expect(number.unit).toBe('%')
-    })
-    it('parses a pixel value', () => {
-      number = new SVGNumber('20px')
-      expect(number.value).toBe(20)
-      expect(number.unit).toBe('px')
-    })
-    it('parses a percent value', () => {
-      number = new SVGNumber('99%')
-      expect(number.value).toBe(0.99)
-      expect(number.unit).toBe('%')
-    })
-    it('parses a seconds value', () => {
-      number = new SVGNumber('2s')
-      expect(number.value).toBe(2000)
-      expect(number.unit).toBe('s')
-    })
-    it('parses a negative percent value', () => {
-      number = new SVGNumber('-89%')
-      expect(number.value).toBe(-0.89)
-      expect(number.unit).toBe('%')
-    })
-    it('falls back to 0 if given value is NaN', () => {
-      number = new SVGNumber(NaN)
-      expect(number.value).toBe(0)
-    })
-    it('falls back to maximum value if given number is positive infinite', () => {
-      number = new SVGNumber(1.7976931348623157E+10308)
-      expect(number.value).toBe(3.4e+38)
-    })
-    it('falls back to minimum value if given number is negative infinite', () => {
-      number = new SVGNumber(-1.7976931348623157E+10308)
-      expect(number.value).toBe(-3.4e+38)
-    })
-  })
-
-  describe('toString()', () => {
-    it('converts the number to a string', () => {
-      expect(number.toString()).toBe('0')
-    })
-    it('appends the unit', () => {
-      number.value = 1.21
-      number.unit = 'px'
-      expect(number.toString()).toBe('1.21px')
-    })
-    it('converts percent values properly', () => {
-      number.value = 1.36
-      number.unit = '%'
-      expect(number.toString()).toBe('136%')
-    })
-    it('converts second values properly', () => {
-      number.value = 2500
-      number.unit = 's'
-      expect(number.toString()).toBe('2.5s')
-    })
-  })
-
-  describe('valueOf()', () => {
-    it('returns a numeric value for default units', () => {
-      expect(typeof number.valueOf()).toBe('number')
-      number = new SVGNumber('12')
-      expect(typeof number.valueOf()).toBe('number')
-      number = new SVGNumber(13)
-      expect(typeof number.valueOf()).toBe('number')
-    })
-    it('returns a numeric value for pixel units', () => {
-      number = new SVGNumber('10px')
-      expect(typeof number.valueOf()).toBe('number')
-    })
-    it('returns a numeric value for percent units', () => {
-      number = new SVGNumber('20%')
-      expect(typeof number.valueOf()).toBe('number')
-    })
-    it('converts to a primitive when multiplying', () => {
-      number.value = 80
-      expect(number * 4).toBe(320)
-    })
-  })
-
-  describe('plus()', () => {
-    it('returns a new instance', () => {
-      expect(number.plus(4.5)).not.toBe(number)
-      expect(number.plus(4.5)).toEqual(any(SVGNumber))
-    })
-    it('adds a given number', () => {
-      expect(number.plus(3.5).valueOf()).toBe(3.5)
-    })
-    it('adds a given percentage value', () => {
-      expect(number.plus('225%').valueOf()).toBe(2.25)
-    })
-    it('adds a given pixel value', () => {
-      expect(number.plus('83px').valueOf()).toBe(83)
-    })
-    it('use the unit of this number as the unit of the returned number by default', () => {
-      expect(new SVGNumber('12s').plus('3%').unit).toBe('s')
-    })
-    it('use the unit of the passed number as the unit of the returned number when this number as no unit', () => {
-      expect(number.plus('15%').unit).toBe('%')
-    })
-  })
-
-  describe('minus()', () => {
-    it('subtracts a given number', () => {
-      expect(number.minus(3.7).valueOf()).toBe(-3.7)
-    })
-    it('subtracts a given percentage value', () => {
-      expect(number.minus('223%').valueOf()).toBe(-2.23)
-    })
-    it('subtracts a given pixel value', () => {
-      expect(number.minus('85px').valueOf()).toBe(-85)
-    })
-    it('use the unit of this number as the unit of the returned number by default', () => {
-      expect(new SVGNumber('12s').minus('3%').unit).toBe('s')
-    })
-    it('use the unit of the passed number as the unit of the returned number when this number as no unit', () => {
-      expect(number.minus('15%').unit).toBe('%')
-    })
-  })
-
-  describe('times()', () => {
-    beforeEach(() => {
-      number = number.plus(4)
-    })
-    it('multiplies with a given number', () => {
-      expect(number.times(3).valueOf()).toBe(12)
-    })
-    it('multiplies with a given percentage value', () => {
-      expect(number.times('110%').valueOf()).toBe(4.4)
-    })
-    it('multiplies with a given pixel value', () => {
-      expect(number.times('85px').valueOf()).toBe(340)
-    })
-    it('use the unit of this number as the unit of the returned number by default', () => {
-      expect(new SVGNumber('12s').times('3%').unit).toBe('s')
-    })
-    it('use the unit of the passed number as the unit of the returned number when this number as no unit', () => {
-      expect(number.times('15%').unit).toBe('%')
-    })
-  })
-
-  describe('divide()', () => {
-    beforeEach(() => {
-      number = number.plus(90)
-    })
-    it('divides by a given number', () => {
-      expect(number.divide(3).valueOf()).toBe(30)
-    })
-    it('divides by a given percentage value', () => {
-      expect(number.divide('3000%').valueOf()).toBe(3)
-    })
-    it('divides by a given pixel value', () => {
-      expect(number.divide('45px').valueOf()).toBe(2)
-    })
-    it('use the unit of this number as the unit of the returned number by default', () => {
-      expect(new SVGNumber('12s').divide('3%').unit).toBe('s')
-    })
-    it('use the unit of the passed number as the unit of the returned number when this number as no unit', () => {
-      expect(number.divide('15%').unit).toBe('%')
-    })
-  })
-})
diff --git a/spec/spec/types/PathArray.js b/spec/spec/types/PathArray.js
new file mode 100644 (file)
index 0000000..fd43480
--- /dev/null
@@ -0,0 +1,108 @@
+/* globals describe, expect, it, beforeEach */
+
+import { PathArray, Box } from '../../../src/main.js'
+
+describe('PathArray.js', () => {
+  let p1, p2, p3, p4, p5, p6, p7
+
+  beforeEach(() => {
+    p1 = new PathArray('m10 10 h 80 v 80 h -80 l 300 400 z')
+    p2 = new PathArray('m10 80 c 40 10 65 10 95 80 s 150 150 180 80 t 300 300 q 52 10 95 80 z')
+    p3 = new PathArray('m80 80 A 45 45, 0, 0, 0, 125 125 L 125 80 z')
+    p4 = new PathArray('M215.458,245.23c0,0,77.403,0,94.274,0S405,216.451,405,138.054S329.581,15,287.9,15c-41.68,0-139.924,0-170.688,0C86.45,15,15,60.65,15,134.084c0,73.434,96.259,112.137,114.122,112.137C146.984,246.221,215.458,245.23,215.458,245.23z')
+    p5 = new PathArray('M10 10-45-30.5.5 .89L2e-2.5.5.5-.5C.5.5.5.5.5.5L-3-4z')
+    p6 = new PathArray('m 0,0 0,3189 2209,0 0,-3189 -2209,0 z m 154,154 1901,0 0,2881 -1901,0 0,-2881 z')
+    p7 = new PathArray('m 0,0 a 45 45, 0, 0, 0, 125 125')
+  })
+
+  it('converts to absolute values', () => {
+    expect(p1.toString()).toBe('M10 10H90V90H10L310 490Z ')
+    expect(p2.toString()).toBe('M10 80C50 90 75 90 105 160S255 310 285 240T585 540Q637 550 680 620Z ')
+    expect(p3.toString()).toBe('M80 80A45 45 0 0 0 125 125L125 80Z ')
+    expect(p4.toString()).toBe('M215.458 245.23C215.458 245.23 292.861 245.23 309.73199999999997 245.23S405 216.451 405 138.054S329.581 15 287.9 15C246.21999999999997 15 147.97599999999997 15 117.21199999999999 15C86.45 15 15 60.65 15 134.084C15 207.518 111.259 246.221 129.122 246.221C146.984 246.221 215.458 245.23 215.458 245.23Z ')
+    expect(p6.toString()).toBe('M0 0L0 3189L2209 3189L2209 0L0 0ZM154 154L2055 154L2055 3035L154 3035L154 154Z ')
+    expect(p7.toString()).toBe('M0 0A45 45 0 0 0 125 125 ')
+  })
+
+  it('parses difficult syntax correctly', () => {
+    expect(p5.toString()).toBe('M10 10L-45 -30.5L0.5 0.89L0.02 0.5L0.5 -0.5C0.5 0.5 0.5 0.5 0.5 0.5L-3 -4Z ')
+  })
+
+  it('parses flat arrays correctly', () => {
+    const arr = new PathArray([ 'M', 0, 0, 'L', 100, 100, 'z' ])
+    expect(arr.toString()).toBe('M0 0L100 100Z ')
+  })
+
+  it('parses nested arrays correctly', () => {
+    const arr = new PathArray([ [ 'M', 0, 0 ], [ 'L', 100, 100 ], [ 'z' ] ])
+    expect(arr.toString()).toBe('M0 0L100 100Z ')
+  })
+
+  // this test is designed to cover a certain line but it doesnt work because of #608
+  it('returns the valueOf when PathArray is given', () => {
+    const p = new PathArray('m10 10 h 80 v 80 h -80 l 300 400 z')
+
+    expect((new PathArray(p))).toEqual(p)
+  })
+
+  it('can handle all formats which can be used', () => {
+    // when no command is specified after move, line is used automatically (specs say so)
+    expect(new PathArray('M10 10 80 80 30 30 Z').toString()).toBe('M10 10L80 80L30 30Z ')
+
+    // parsing can handle 0.5.3.3.2 stuff
+    expect(new PathArray('M10 10L.5.5.3.3Z').toString()).toBe('M10 10L0.5 0.5L0.3 0.3Z ')
+  })
+
+  describe('move()', () => {
+    it('moves all points in a straight path', () => {
+      expect(p1.move(100, 200).toString()).toBe('M100 200H180V280H100L400 680Z ')
+    })
+
+    it('moves all points in a curved path', () => {
+      expect(p2.move(100, 200).toString()).toBe('M100 200C140 210 165 210 195 280S345 430 375 360T675 660Q727 670 770 740Z ')
+    })
+
+    it('moves all points in a arc path', () => {
+      expect(p3.move(100, 200).toString()).toBe('M100 200A45 45 0 0 0 145 245L145 200Z ')
+    })
+
+    it('does nothing if passed number is not a number', () => {
+      expect(p3.move()).toEqual(p3)
+    })
+  })
+
+  describe('size()', () => {
+    it('resizes all points in a straight path', () => {
+      expect(p1.size(600, 200).toString()).toBe('M10 10H170V43.333333333333336H10L610 210Z ')
+    })
+
+    it('resizes all points in a curved path', () => {
+      expect(p2.size(600, 200).toString()).toBe('M10 80C45.82089552238806 83.70370370370371 68.2089552238806 83.70370370370371 95.07462686567165 109.62962962962963S229.40298507462686 165.1851851851852 256.2686567164179 139.25925925925927T524.9253731343283 250.37037037037038Q571.4925373134329 254.07407407407408 610 280Z ')
+    })
+
+    it('resizes all points in a arc path', () => {
+      const expected = [
+        [ 'M', 80, 80 ],
+        [ 'A', 600, 200, 0, 0, 0, 680, 280 ],
+        [ 'L', 680, 80 ],
+        [ 'Z' ]
+      ]
+
+      const toBeTested = p3.size(600, 200)
+
+      for (let i = toBeTested.length; i--;) {
+        expect(toBeTested[i].shift().toUpperCase()).toBe(expected[i].shift().toUpperCase())
+        for (let j = toBeTested[i].length; j--;) {
+          expect(toBeTested[i][j]).toBeCloseTo(expected[i][j])
+        }
+      }
+    })
+  })
+
+  describe('bbox()', () => {
+    it('calculates the bounding box of the PathArray', () => {
+      const box = new PathArray('M0 0 L 10 10').bbox()
+      expect(box).toEqual(new Box(0, 0, 10, 10))
+    })
+  })
+})
index ee0bbd4dd638f16a3ea4fcd7928082ae1b174805..ef0b6f61b4780935975b6ee3c13aad6a01265d56 100644 (file)
@@ -1,6 +1,6 @@
-/* globals describe, expect, it, beforeEach */
+/* globals describe, expect, it, beforeEach, spyOn */
 
-import { Point } from '../../../src/main.js'
+import { Point, Rect, Matrix } from '../../../src/main.js'
 
 describe('Point.js', () => {
   var point
@@ -66,6 +66,16 @@ describe('Point.js', () => {
     })
   })
 
+  describe('transform()', () => {
+    it('transforms a point with a matrix', () => {
+      expect(new Point().transform(new Matrix({ translate: [ 10, 10 ] }))).toEqual(new Point(10, 10))
+    })
+
+    it('transforms a point with a transformation object', () => {
+      expect(new Point().transform({ translate: [ 10, 10 ] })).toEqual(new Point(10, 10))
+    })
+  })
+
   describe('clone()', () => {
     it('returns cloned point', () => {
       var point1 = new Point(1, 1)
@@ -75,4 +85,21 @@ describe('Point.js', () => {
       expect(point1).not.toBe(point2)
     })
   })
+
+  describe('toArray()', () => {
+    it('creates an array representation of Point', () => {
+      const p = new Point(1, 2)
+      expect(p.toArray()).toEqual([ 1, 2 ])
+    })
+  })
+
+  describe('Element', () => {
+    describe('point()', () => {
+      it('transforms a screen point into the coordinate system of the element', () => {
+        const rect = new Rect()
+        spyOn(rect, 'screenCTM').and.callFake(() => new Matrix(1, 0, 0, 1, 20, 20))
+        expect(rect.point({ x: 10, y: 10 })).toEqual(new Point(-10, -10))
+      })
+    })
+  })
 })
index da8675e838f0a6865d3ffe90d51f4b2704ce2315..cbcc3c1ad2b4dd195300fda0bed67818736d4770 100644 (file)
 
 import { PointArray, Matrix, Point } from '../../../src/main.js'
 
-describe('PointArray', () => {
+describe('PointArray.js', () => {
   const squareString = '0,0 1,0 1,1 0,1'
-  const square = new PointArray(squareString)
+
+  describe('()', () => {
+
+    it('parses a string to a point array', () => {
+      var array = new PointArray('0,1 -.05,7.95 1000.0001,-200.222')
+      expect(array.valueOf()).toEqual([ [ 0, 1 ], [ -0.05, 7.95 ], [ 1000.0001, -200.222 ] ])
+    })
+
+    it('parses a points array correctly to string', () => {
+      var array = new PointArray([ [ 0, 0.15 ], [ -100, -3.141592654 ], [ 50, 100 ] ])
+      expect(array + '').toBe('0,0.15 -100,-3.141592654 50,100')
+    })
+
+    it('parses a flat array of x/y coordinates to a point array', () => {
+      var array = new PointArray([ 1, 4, 5, 68, 12, 24 ])
+      expect(array.valueOf()).toEqual([ [ 1, 4 ], [ 5, 68 ], [ 12, 24 ] ])
+    })
+
+    it('parses points with space delimitered x/y coordinates', () => {
+      var array = new PointArray('221.08 191.79 0.46 191.79 0.46 63.92 63.8 0.46 284.46 0.46 284.46 128.37 221.08 191.79')
+      expect(array + '').toBe('221.08,191.79 0.46,191.79 0.46,63.92 63.8,0.46 284.46,0.46 284.46,128.37 221.08,191.79')
+    })
+
+    it('parses points with comma delimitered x/y coordinates', () => {
+      var array = new PointArray('221.08,191.79,0.46,191.79,0.46,63.92,63.8,0.46,284.46,0.46,284.46,128.37,221.08,191.79')
+      expect(array + '').toBe('221.08,191.79 0.46,191.79 0.46,63.92 63.8,0.46 284.46,0.46 284.46,128.37 221.08,191.79')
+    })
+
+    it('parses points with comma and space delimitered x/y coordinates', () => {
+      var array = new PointArray('221.08, 191.79, 0.46, 191.79, 0.46, 63.92, 63.8, 0.46, 284.46, 0.46, 284.46, 128.37, 221.08, 191.79')
+      expect(array + '').toBe('221.08,191.79 0.46,191.79 0.46,63.92 63.8,0.46 284.46,0.46 284.46,128.37 221.08,191.79')
+    })
+
+    it('parses points with space and comma delimitered x/y coordinates', () => {
+      var array = new PointArray('221.08 ,191.79 ,0.46 ,191.79 ,0.46 ,63.92 ,63.8 ,0.46 ,284.46 ,0.46 ,284.46 ,128.37 ,221.08 ,191.79')
+      expect(array + '').toBe('221.08,191.79 0.46,191.79 0.46,63.92 63.8,0.46 284.46,0.46 284.46,128.37 221.08,191.79')
+    })
+
+    it('parses points with redundant spaces at the end', () => {
+      var array = new PointArray('2176.6,1708.8 2176.4,1755.8 2245.8,1801.5 2297,1787.8  ')
+      expect(array + '').toBe('2176.6,1708.8 2176.4,1755.8 2245.8,1801.5 2297,1787.8')
+    })
+
+    it('parses points with space delimitered x/y coordinates - even with leading or trailing space', () => {
+      var array = new PointArray('  1 2 3 4  ')
+      expect(array + '').toBe('1,2 3,4')
+    })
+
+    it('parses odd number of points with space delimitered x/y coordinates and silently remove the odd point', () => {
+      // this  is according to spec: https://svgwg.org/svg2-draft/shapes.html#DataTypePoints
+      var array = new PointArray('1 2 3')
+      expect(array + '').toBe('1,2')
+    })
+
+    it('parses odd number of points in a flat array of x/y coordinates and silently remove the odd point', () => {
+      // this  is according to spec: https://svgwg.org/svg2-draft/shapes.html#DataTypePoints
+      var array = new PointArray([ 1, 2, 3 ])
+      expect(array.valueOf()).toEqual([ [ 1, 2 ] ])
+    })
+
+  })
+
+  describe('move()', () => {
+    it('moves the whole array by the passed value', () => {
+      const arr = new PointArray([ 1, 2, 3, 4 ]).move(10, 10)
+      expect(arr.toArray()).toEqual([ 10, 10, 12, 12 ])
+    })
+
+    it('does nothing if values not numbers', () => {
+      const arr = new PointArray([ 1, 2, 3, 4 ]).move()
+      expect(arr.toArray()).toEqual([ 1, 2, 3, 4 ])
+    })
+  })
+
+  describe('size()', () => {
+    it('correctly sizes the points over the whole area', () => {
+      var array = new PointArray([ 10, 10, 20, 20, 30, 30 ])
+      expect(array.size(60, 60).valueOf()).toEqual([ [ 10, 10 ], [ 40, 40 ], [ 70, 70 ] ])
+    })
+
+    it('let coordinates untouched when width/height is zero', () => {
+      var array = new PointArray([ 10, 10, 10, 20, 10, 30 ])
+      expect(array.size(60, 60).valueOf()).toEqual([ [ 10, 10 ], [ 10, 40 ], [ 10, 70 ] ])
+
+      array = new PointArray([ 10, 10, 20, 10, 30, 10 ])
+      expect(array.size(60, 60).valueOf()).toEqual([ [ 10, 10 ], [ 40, 10 ], [ 70, 10 ] ])
+    })
+
+  })
 
   describe('toString()', () => {
-    it('round trips with string', () => {
+    it('converts to comma sepereated list', () => {
+      const square = new PointArray(squareString)
       expect(square.toString()).toEqual(squareString)
     })
   })
 
+  describe('toLine', () => {
+    it('returns an object which can be passed to a line as point attributes', () => {
+      const arr = new PointArray([ 1, 2, 3, 4 ])
+      expect(arr.toLine()).toEqual({ x1: 1, y1: 2, x2: 3, y2: 4 })
+    })
+  })
+
   describe('transform()', () => {
     it('translates correctly', () => {
-      const translation = new Matrix().translate(2, 1)
+      const square = new PointArray(squareString)
+      const translation = new Matrix({ translate: [ 2, 1 ] })
       const newSquare = square.transform(translation)
       expect(newSquare.toString()).toEqual('2,1 3,1 3,2 2,2')
     })
 
     it('transforms like Point', () => {
+      const square = new PointArray(squareString)
       const matrix = new Matrix(1, 2, 3, 4, 5, 6)
       const newSquare = square.transform(matrix)
       for (let i = 0; i < square.length; i++) {
@@ -28,5 +126,11 @@ describe('PointArray', () => {
         expect(squarePoint.transform(matrix)).toEqual(newSquarePoint)
       }
     })
+
+    it('works with transform object instead of matrix', () => {
+      const square = new PointArray(squareString)
+      const newSquare = square.transform({ translate: [ 2, 1 ] })
+      expect(newSquare.toString()).toEqual('2,1 3,1 3,2 2,2')
+    })
   })
 })
diff --git a/spec/spec/types/SVGArray.js b/spec/spec/types/SVGArray.js
new file mode 100644 (file)
index 0000000..cefc54e
--- /dev/null
@@ -0,0 +1,89 @@
+/* globals describe, expect, it, jasmine */
+
+import { Array as SVGArray, PointArray, PathArray } from '../../../src/main.js'
+
+const { any } = jasmine
+
+describe('SVGArray.js', () => {
+  describe('()', () => {
+
+    it('preallocates memory if only number is passed', () => {
+      const arr = new SVGArray(1)
+      expect(arr.length).toBe(1)
+    })
+
+    it('parses a matrix array correctly to string', () => {
+      const array = new SVGArray([
+        0.343, 0.669, 0.119, 0, 0,
+        0.249, -0.626, 0.130, 0, 0,
+        0.172, 0.334, 0.111, 0, 0,
+        0.000, 0.000, 0.000, 1, -0
+      ])
+
+      expect(array + '').toBe('0.343 0.669 0.119 0 0 0.249 -0.626 0.13 0 0 0.172 0.334 0.111 0 0 0 0 0 1 0')
+    })
+
+    it('parses space seperated string and converts it to array', () => {
+      expect((new SVGArray('1 2 3 4')).valueOf()).toEqual([ 1, 2, 3, 4 ])
+    })
+
+    it('parses comma seperated string and converts it to array', () => {
+      expect((new SVGArray('1,2,3,4')).valueOf()).toEqual([ 1, 2, 3, 4 ])
+    })
+
+  })
+
+  describe('reverse()', () => {
+    it('reverses the array', () => {
+      const array = new SVGArray([ 1, 2, 3, 4, 5 ]).reverse()
+      expect(array.valueOf()).toEqual([ 5, 4, 3, 2, 1 ])
+    })
+
+    it('returns itself', () => {
+      const array = new SVGArray()
+      expect(array.reverse()).toBe(array)
+    })
+  })
+
+  describe('clone()', () => {
+    it('creates a shallow clone of the array', () => {
+      const array = new SVGArray([ 1, 2, 3, 4, 5 ])
+      const clone = array.clone()
+
+      expect(array).toEqual(clone)
+      expect(array).not.toBe(clone)
+    })
+
+    it('also works with PointArray (one depths clone)', () => {
+      const array = new PointArray([ [ 1, 2 ], [ 3, 4 ], [ 5, 6 ] ])
+      const clone = array.clone()
+
+      expect(array).toEqual(clone)
+      expect(array).not.toBe(clone)
+
+      for (let i = array.length; i--;) {
+        expect(array[i]).not.toBe(clone[i])
+      }
+    })
+
+    it('also works with PathArray (one depths clone)', () => {
+      const array = new PathArray([ [ 'M', 1, 2 ], [ 'L', 3, 4 ], [ 'L', 5, 6 ] ])
+      const clone = array.clone()
+
+      expect(array).toEqual(clone)
+      expect(array).not.toBe(clone)
+
+      for (let i = array.length; i--;) {
+        expect(array[i]).not.toBe(clone[i])
+      }
+    })
+  })
+
+  describe('toSet()', () => {
+    it('creates a Set from the Array', () => {
+      const set = new SVGArray([ 1, 1, 2, 3 ]).toSet()
+      expect(set).toEqual(any(Set))
+      expect(set).toEqual(new Set([ 1, 2, 3 ]))
+    })
+  })
+})
diff --git a/spec/spec/types/SVGNumber.js b/spec/spec/types/SVGNumber.js
new file mode 100644 (file)
index 0000000..d7ef2fd
--- /dev/null
@@ -0,0 +1,184 @@
+/* globals describe, expect, it, beforeEach, jasmine */
+
+import { Number as SVGNumber } from '../../../src/main.js'
+
+const { any } = jasmine
+
+describe('Number.js', () => {
+  var number
+
+  beforeEach(() => {
+    number = new SVGNumber()
+  })
+
+  describe('new', () => {
+    it('is zero', () => {
+      expect(number.value).toBe(0)
+    })
+    it('has a blank unit', () => {
+      expect(number.unit).toBe('')
+    })
+    it('accepts the unit as a second argument', () => {
+      number = new SVGNumber(30, '%')
+      expect(number.value).toBe(30)
+      expect(number.unit).toBe('%')
+    })
+    it('parses a pixel value', () => {
+      number = new SVGNumber('20px')
+      expect(number.value).toBe(20)
+      expect(number.unit).toBe('px')
+    })
+    it('parses a percent value', () => {
+      number = new SVGNumber('99%')
+      expect(number.value).toBe(0.99)
+      expect(number.unit).toBe('%')
+    })
+    it('parses a seconds value', () => {
+      number = new SVGNumber('2s')
+      expect(number.value).toBe(2000)
+      expect(number.unit).toBe('s')
+    })
+    it('parses a negative percent value', () => {
+      number = new SVGNumber('-89%')
+      expect(number.value).toBe(-0.89)
+      expect(number.unit).toBe('%')
+    })
+    it('falls back to 0 if given value is NaN', () => {
+      number = new SVGNumber(NaN)
+      expect(number.value).toBe(0)
+    })
+    it('falls back to maximum value if given number is positive infinite', () => {
+      number = new SVGNumber(1.7976931348623157E+10308)
+      expect(number.value).toBe(3.4e+38)
+    })
+    it('falls back to minimum value if given number is negative infinite', () => {
+      number = new SVGNumber(-1.7976931348623157E+10308)
+      expect(number.value).toBe(-3.4e+38)
+    })
+  })
+
+  describe('toString()', () => {
+    it('converts the number to a string', () => {
+      expect(number.toString()).toBe('0')
+    })
+    it('appends the unit', () => {
+      number.value = 1.21
+      number.unit = 'px'
+      expect(number.toString()).toBe('1.21px')
+    })
+    it('converts percent values properly', () => {
+      number.value = 1.36
+      number.unit = '%'
+      expect(number.toString()).toBe('136%')
+    })
+    it('converts second values properly', () => {
+      number.value = 2500
+      number.unit = 's'
+      expect(number.toString()).toBe('2.5s')
+    })
+  })
+
+  describe('valueOf()', () => {
+    it('returns a numeric value for default units', () => {
+      expect(typeof number.valueOf()).toBe('number')
+      number = new SVGNumber('12')
+      expect(typeof number.valueOf()).toBe('number')
+      number = new SVGNumber(13)
+      expect(typeof number.valueOf()).toBe('number')
+    })
+    it('returns a numeric value for pixel units', () => {
+      number = new SVGNumber('10px')
+      expect(typeof number.valueOf()).toBe('number')
+    })
+    it('returns a numeric value for percent units', () => {
+      number = new SVGNumber('20%')
+      expect(typeof number.valueOf()).toBe('number')
+    })
+    it('converts to a primitive when multiplying', () => {
+      number.value = 80
+      expect(number * 4).toBe(320)
+    })
+  })
+
+  describe('plus()', () => {
+    it('returns a new instance', () => {
+      expect(number.plus(4.5)).not.toBe(number)
+      expect(number.plus(4.5)).toEqual(any(SVGNumber))
+    })
+    it('adds a given number', () => {
+      expect(number.plus(3.5).valueOf()).toBe(3.5)
+    })
+    it('adds a given percentage value', () => {
+      expect(number.plus('225%').valueOf()).toBe(2.25)
+    })
+    it('adds a given pixel value', () => {
+      expect(number.plus('83px').valueOf()).toBe(83)
+    })
+    it('use the unit of this number as the unit of the returned number by default', () => {
+      expect(new SVGNumber('12s').plus('3%').unit).toBe('s')
+    })
+    it('use the unit of the passed number as the unit of the returned number when this number as no unit', () => {
+      expect(number.plus('15%').unit).toBe('%')
+    })
+  })
+
+  describe('minus()', () => {
+    it('subtracts a given number', () => {
+      expect(number.minus(3.7).valueOf()).toBe(-3.7)
+    })
+    it('subtracts a given percentage value', () => {
+      expect(number.minus('223%').valueOf()).toBe(-2.23)
+    })
+    it('subtracts a given pixel value', () => {
+      expect(number.minus('85px').valueOf()).toBe(-85)
+    })
+    it('use the unit of this number as the unit of the returned number by default', () => {
+      expect(new SVGNumber('12s').minus('3%').unit).toBe('s')
+    })
+    it('use the unit of the passed number as the unit of the returned number when this number as no unit', () => {
+      expect(number.minus('15%').unit).toBe('%')
+    })
+  })
+
+  describe('times()', () => {
+    beforeEach(() => {
+      number = number.plus(4)
+    })
+    it('multiplies with a given number', () => {
+      expect(number.times(3).valueOf()).toBe(12)
+    })
+    it('multiplies with a given percentage value', () => {
+      expect(number.times('110%').valueOf()).toBe(4.4)
+    })
+    it('multiplies with a given pixel value', () => {
+      expect(number.times('85px').valueOf()).toBe(340)
+    })
+    it('use the unit of this number as the unit of the returned number by default', () => {
+      expect(new SVGNumber('12s').times('3%').unit).toBe('s')
+    })
+    it('use the unit of the passed number as the unit of the returned number when this number as no unit', () => {
+      expect(number.times('15%').unit).toBe('%')
+    })
+  })
+
+  describe('divide()', () => {
+    beforeEach(() => {
+      number = number.plus(90)
+    })
+    it('divides by a given number', () => {
+      expect(number.divide(3).valueOf()).toBe(30)
+    })
+    it('divides by a given percentage value', () => {
+      expect(number.divide('3000%').valueOf()).toBe(3)
+    })
+    it('divides by a given pixel value', () => {
+      expect(number.divide('45px').valueOf()).toBe(2)
+    })
+    it('use the unit of this number as the unit of the returned number by default', () => {
+      expect(new SVGNumber('12s').divide('3%').unit).toBe('s')
+    })
+    it('use the unit of the passed number as the unit of the returned number when this number as no unit', () => {
+      expect(number.divide('15%').unit).toBe('%')
+    })
+  })
+})
index f124b1bc3b1d6846a8e060ed2cbfcfc05e0c9c89..03fdee3318f2ec3bdc49ec070d566265d1f66878 100644 (file)
@@ -11,6 +11,7 @@ import { subClassArray } from './ArrayPolyfill.js'
 import Point from './Point.js'
 import SVGArray from './SVGArray.js'
 import parser from '../modules/core/parser.js'
+import Box from './Box.js'
 
 const PathArray = subClassArray('PathArray', SVGArray)
 
@@ -222,72 +223,8 @@ extend(PathArray, {
     return this
   },
 
-  // Test if the passed path array use the same path data commands as this path array
-  equalCommands (pathArray) {
-    var i, il, equalCommands
-
-    pathArray = new PathArray(pathArray)
-
-    equalCommands = this.length === pathArray.length
-    for (i = 0, il = this.length; equalCommands && i < il; i++) {
-      equalCommands = this[i][0] === pathArray[i][0]
-    }
-
-    return equalCommands
-  },
-
-  // Make path array morphable
-  morph (pathArray) {
-    pathArray = new PathArray(pathArray)
-
-    if (this.equalCommands(pathArray)) {
-      this.destination = pathArray
-    } else {
-      this.destination = null
-    }
-
-    return this
-  },
-
-  // Get morphed path array at given position
-  at (pos) {
-    // make sure a destination is defined
-    if (!this.destination) return this
-
-    var sourceArray = this
-    var destinationArray = this.destination.value
-    var array = []
-    var pathArray = new PathArray()
-    var i, il, j, jl
-
-    // Animate has specified in the SVG spec
-    // See: https://www.w3.org/TR/SVG11/paths.html#PathElement
-    for (i = 0, il = sourceArray.length; i < il; i++) {
-      array[i] = [ sourceArray[i][0] ]
-      for (j = 1, jl = sourceArray[i].length; j < jl; j++) {
-        array[i][j] = sourceArray[i][j] + (destinationArray[i][j] - sourceArray[i][j]) * pos
-      }
-      // For the two flags of the elliptical arc command, the SVG spec say:
-      // Flags and booleans are interpolated as fractions between zero and one, with any non-zero value considered to be a value of one/true
-      // Elliptical arc command as an array followed by corresponding indexes:
-      // ['A', rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y]
-      //   0    1   2        3                 4             5      6  7
-      if (array[i][0] === 'A') {
-        array[i][4] = +(array[i][4] !== 0)
-        array[i][5] = +(array[i][5] !== 0)
-      }
-    }
-
-    // Directly modify the value of a path array, this is done this way for performance
-    pathArray.value = array
-    return pathArray
-  },
-
   // Absolutize and parse path to array
-  parse (array = [ [ 'M', 0, 0 ] ]) {
-    // if it's already a patharray, no need to parse it
-    if (array instanceof PathArray) return array
-
+  parse (array = [ 'M', 0, 0 ]) {
     // prepare for parsing
     var s
     var paramCnt = { M: 2, L: 2, H: 1, V: 1, C: 6, S: 4, Q: 4, T: 2, A: 7, Z: 0 }
@@ -300,9 +237,8 @@ extend(PathArray, {
         .trim() // trim
         .split(delimiter) // split into array
     } else {
-      array = array.reduce(function (prev, curr) {
-        return [].concat.call(prev, curr)
-      }, [])
+      // Flatten array
+      array = Array.prototype.concat.apply([], array)
     }
 
     // array now is an array containing all parts of a path e.g. ['M', '0', '0', 'L', '30', '30' ...]
@@ -337,6 +273,6 @@ extend(PathArray, {
   // Get bounding box of path
   bbox () {
     parser().path.setAttribute('d', this.toString())
-    return parser.nodes.path.getBBox()
+    return new Box(parser.nodes.path.getBBox())
   }
 })
index 6a869d7700c40767eac960e6c6bb6e7e1ce1b475..54df08fb54d9dc805c6a589a14859ae65844461f 100644 (file)
@@ -2,6 +2,8 @@ import { delimiter } from '../modules/core/regex.js'
 import { extend } from '../utils/adopter.js'
 import { subClassArray } from './ArrayPolyfill.js'
 import SVGArray from './SVGArray.js'
+import { Matrix } from '../main.js'
+import Box from './Box.js'
 
 const PointArray = subClassArray('PointArray', SVGArray)
 
@@ -28,32 +30,13 @@ extend(PointArray, {
     }
   },
 
-  // Get morphed array at given position
-  at (pos) {
-    // make sure a destination is defined
-    if (!this.destination) return this
-
-    // generate morphed point string
-    for (var i = 0, il = this.length, array = []; i < il; i++) {
-      array.push([
-        this[i][0] + (this.destination[i][0] - this[i][0]) * pos,
-        this[i][1] + (this.destination[i][1] - this[i][1]) * pos
-      ])
-    }
-
-    return new PointArray(array)
-  },
-
   // Parse point string and flat array
-  parse (array = [ [ 0, 0 ] ]) {
+  parse (array = [ 0, 0 ]) {
     var points = []
 
-    // if it is an array
+    // if it is an array, we flatten it and therefore clone it to 1 depths
     if (array instanceof Array) {
-      // and it is not flat, there is no need to parse it
-      if (array[0] instanceof Array) {
-        return array
-      }
+      array = Array.prototype.concat.apply([], array)
     } else { // Else, it is considered as a string
       // parse points
       array = array.trim().split(delimiter).map(parseFloat)
@@ -71,21 +54,24 @@ extend(PointArray, {
     return points
   },
 
-  // transform points with matrix (similar to Point.transform)
   transform (m) {
-    const points = []
+    return this.clone().transformO(m)
+  },
+
+  // transform points with matrix (similar to Point.transform)
+  transformO (m) {
+    if (!Matrix.isMatrixLike(m)) {
+      m = new Matrix(m)
+    }
 
-    for (let i = 0; i < this.length; i++) {
-      const point = this[i]
+    for (let i = this.length; i--;) {
       // Perform the matrix multiplication
-      points.push([
-        m.a * point[0] + m.c * point[1] + m.e,
-        m.b * point[0] + m.d * point[1] + m.f
-      ])
+      const [ x, y ] = this[i]
+      this[i][0] = m.a * x + m.c * y + m.e
+      this[i][1] = m.b * x + m.d * y + m.f
     }
 
-    // Return the required point
-    return new PointArray(points)
+    return this
   },
 
   // Move point string
@@ -132,6 +118,6 @@ extend(PointArray, {
       minX = Math.min(el[0], minX)
       minY = Math.min(el[1], minY)
     })
-    return { x: minX, y: minY, width: maxX - minX, height: maxY - minY }
+    return new Box(minX, minY, maxX - minX, maxY - minY)
   }
 })