diff --git a/.travis.yml b/.travis.yml
index 821085b..a6a8263 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,5 +6,6 @@ node_js:
- "9"
- "10"
- "11"
+ - "12"
after_success:
- npm run coveralls
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 40c703a..a0bdc0b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,10 +1,34 @@
# CHANGELOG
+# Version 2.1.0
+
+This minor version bump now has some major snapshot support but is backwards compatible.
+
+Changes:
+
+- Feature: Add `static` `CanvasRenderingContext2D` method: `#.__getEvents(ctx)`
+ - Feature: Every successful modification of the `CanvasRenderingContext2D` state machine logs an `_event`
+- Feature: Add `static` `CanvasRenderingContext2D` method: `#.__getPath(ctx)`
+ - Feature: Every path call adds a `_path` item and can be accessed via `__getPath(ctx)`
+ - Feature: `beginPath()` empties the `_path`
+- Feature: Add `static` `CanvasRenderingContext2D` method: `#.__getDrawCalls(ctx)`
+ - Feature: Every draw call adds a `_drawCall` item and can be accessed via `__getDrawCall(ctx)`
+- Feature: Add `types/index.d.ts` file for tooling types (in jest environment)
+- Feature: Support node 12
+- Docs
+ - Updated arc example
+ - Added snapshot testing documentation
+- Bug: `createLinearGradient` now accepts strings
+- Bug: `createRadialGradient` now accepts strings
+- Bug: `globalAlpha` now accepts `null` per `Number` coercion
+- Feature: Faster finite values checks
+- Feature: Add `_path` and `_events` to `Path2D`
+- Testing: Add and test snapshot outputs
+
# Version 2.0.0
Just publish a stable version.
-
# Version 2.0.0-beta.1
## Class Instances
diff --git a/README.md b/README.md
index b29080d..6b516e2 100644
--- a/README.md
+++ b/README.md
@@ -73,8 +73,11 @@ error. For instance, the `CanvasRenderingContext2D#arc` function will throw a `T
radius is negative, or if it was not provided with enough parameters.
```ts
+// arc throws a TypeError when the argument length is less than 5
expect(() => ctx.arc(1, 2, 3, 4)).toThrow(TypeError);
-expect(() => ctx.arc(0, 0, -10, 0, Math.PI * 2)).toThrow(TypeError);
+
+// when radius is negative, arc throws a dom exception when all parameters are finite
+expect(() => ctx.arc(0, 0, -10, 0, Math.PI * 2)).toThrow(DOMException);
```
The function will do `Number` type coercion and verify the inputs exactly like the browser does. So
@@ -95,16 +98,60 @@ expect(() => ctx.fill(new Path2D(), "invalid!")).toThrow(TypeError);
We try to follow the ECMAScript specification as closely as possible.
+# Snapshots
+
+There are multiple ways to validate canvas state. There are currently three `static` methods attached
+to the `CanvasRenderingContext2D` class. The first way to use this feature is by using the `__getEvents`
+method.
+
+```ts
+/**
+ * In order to see which functions and properties were used for the test, you can use `__getEvents`
+ * to gather this information.
+ */
+const events = ctx.__getEvents();
+
+expect(events).toMatchSnapshot(); // jest will assert the events match the snapshot
+```
+
+The second way is to inspect the current path associated with the context.
+
+```ts
+ctx.beginPath();
+ctx.arc(1, 2, 3, 4, 5);
+ctx.moveTo(6, 7);
+ctx.rect(6, 7, 8, 9);
+ctx.closePath();
+
+/**
+ * Any method that modifies the current path (and subpath) will be pushed to an event array. When
+ * using the `__getPath` method, that array will sliced and usable for snapshots.
+ */
+const path = ctx.__getPath();
+expect(path).toMatchSnapshot();
+```
+
+The third way is to inspect all of the success draw calls submitted to the context.
+
+```ts
+ctx.drawImage(img, 0, 0);
+
+/**
+ * Every drawImage, fill, stroke, fillText, or strokeText function call will be logged in an event
+ * array. This method will return those events here for inspection.
+ */
+const calls = ctx.__getDrawCalls();
+expect(calls).toMatchSnapshot();
+```
+
## Override default mock return value
You can override the default mock return value in your test to suit your need. For example, to override return value of `toDataURL`:
```ts
-HTMLCanvasElement.prototype.toDataURL = jest
- .fn()
- .mockReturnValue(
- 'data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='
- );
+canvas.toDataURL.mockReturnValueOnce(
+ 'data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='
+);
```
## License
diff --git a/__tests__/classes/CanvasRenderingContext2D.__getDrawCalls.js b/__tests__/classes/CanvasRenderingContext2D.__getDrawCalls.js
new file mode 100644
index 0000000..d9694dc
--- /dev/null
+++ b/__tests__/classes/CanvasRenderingContext2D.__getDrawCalls.js
@@ -0,0 +1,131 @@
+let ctx;
+beforeEach(() => {
+ // get a new context each test
+ ctx = document.createElement('canvas')
+ .getContext('2d');
+});
+
+const img = new Image();
+img.src = 'https://placekitten.com/400/300';
+img.width = 400;
+img.height = 300;
+
+const path = new Path2D();
+path.arc(100, 101, 10, 0, Math.PI * 2);
+
+afterEach(() => {
+ const drawCalls = ctx.__getDrawCalls(ctx);
+ expect(drawCalls).toMatchSnapshot();
+});
+
+describe('__getDrawCalls', () => {
+ it('should have a draw call when clearRect is called', () => {
+ ctx.clearRect(1, 2, 3, 4);
+ });
+
+ it('should not have a draw call when clearRect is passed bad values', () => {
+ ctx.clearRect(NaN, 1, 2, 3);
+ });
+
+ it('should have a draw call when fillRect is called', () => {
+ ctx.fillRect(1, 2, 3, 4);
+ });
+
+ it('should not have a draw call when fillRect is passed bad values', () => {
+ ctx.fillRect(NaN, 1, 2, 3);
+ });
+
+ it('should have a draw call when strokeRect is called', () => {
+ ctx.strokeRect(1, 2, 3, 4);
+ });
+
+ it('should not have a draw call when strokeRect is passed bad values', () => {
+ ctx.strokeRect(NaN, 1, 2, 3);
+ });
+
+ it('should have a draw call when drawImage is called', () => {
+ ctx.drawImage(img, 0, 0);
+ });
+
+ it('should not have a draw call when drawImage is called with non-finite numbers', () => {
+ ctx.drawImage(img, NaN, 0);
+ });
+
+ it('should have a draw call when drawImage is called with size parameters', () => {
+ ctx.drawImage(img, 0, 0, 100, 100);
+ });
+
+ it('should not have a draw call when drawImage is called with non-finite numbers and size parameters', () => {
+ ctx.drawImage(img, NaN, 0, 100, 100);
+ });
+
+ it('should have a draw call when drawImage is called with source parameters', () => {
+ ctx.drawImage(img, 0, 0, 100, 100, 0, 0, 200, 200);
+ });
+
+ it('should not have a draw call when drawImage is called with non-finite numbers and source parameters', () => {
+ ctx.drawImage(img, NaN, 0, 100, 100, 0, 0, 200, 200);
+ });
+
+ it('should have a draw call when fill() is called', () => {
+ ctx.beginPath();
+ ctx.arc(100, 101, 10, 0, Math.PI * 2);
+ ctx.fill();
+ });
+
+ it('should have a draw call when fill() is called with a fillRule', () => {
+ ctx.beginPath();
+ ctx.arc(100, 101, 10, 0, Math.PI * 2);
+ ctx.fill('evenodd');
+ });
+
+ it('should have a draw call when using a path', () => {
+ ctx.fill(path);
+ });
+
+ it('should have a draw call when fillRule is valid', () => {
+ ctx.fill(path, 'evenodd');
+ });
+
+ it('should have a draw call when fillText is valid', () => {
+ ctx.fillText('hello world', 0, 0);
+ });
+
+ it('should have a draw call when fillText has valid maxWidth', () => {
+ ctx.fillText('hello world', 0, 0, 100);
+ });
+
+ it('should not have a draw call when fillText is not valid', () => {
+ ctx.fillText('hello world', 0, NaN);
+ });
+
+ it('should not have a draw call when fillText is not valid', () => {
+ ctx.fillText('hello world', 0, 0, NaN);
+ });
+
+ it('should have a draw call when strokeText is valid', () => {
+ ctx.strokeText('hello world', 0, 0);
+ });
+
+ it('should have a draw call when strokeText has valid maxWidth', () => {
+ ctx.strokeText('hello world', 0, 0, 100);
+ });
+
+ it('should not have a draw call when strokeText is not valid', () => {
+ ctx.strokeText('hello world', 0, NaN);
+ });
+
+ it('should not have a draw call when strokeText is not valid', () => {
+ ctx.strokeText('hello world', 0, 0, NaN);
+ });
+
+ it('should have a draw call when stroke is called', () => {
+ ctx.beginPath();
+ ctx.arc(100, 101, 10, 0, Math.PI * 2);
+ ctx.stroke();
+ });
+
+ it('should have a draw call when stroke is called with a path', () => {
+ ctx.stroke(path);
+ });
+});
diff --git a/__tests__/classes/CanvasRenderingContext2D.__getEvents.js b/__tests__/classes/CanvasRenderingContext2D.__getEvents.js
new file mode 100644
index 0000000..b53d7e2
--- /dev/null
+++ b/__tests__/classes/CanvasRenderingContext2D.__getEvents.js
@@ -0,0 +1,533 @@
+let ctx;
+beforeEach(() => {
+ // get a new context each test
+ ctx = document.createElement('canvas')
+ .getContext('2d');
+});
+
+const img = new Image();
+img.src = 'https://placekitten.com/400/300';
+img.width = 400;
+img.height = 300;
+
+const path = new Path2D();
+path.arc(100, 101, 10, 0, Math.PI * 2);
+
+const imgData = new ImageData(100, 100);
+
+afterEach(() => {
+ const drawCalls = ctx.__getEvents(ctx);
+ expect(drawCalls).toMatchSnapshot();
+});
+
+describe('__getEvents', () => {
+ it('should have an event when addHitRegion is called', () => {
+ ctx.addHitRegion({ id: 'test' });
+ });
+
+ it('should have an event when arc is called', () => {
+ ctx.arc(1, 2, 3, 4, 5, true);
+ });
+
+ it('should not have an event when arc is called with a non-finite number', () => {
+ ctx.arc(NaN, 2, 3, 4, 5, false);
+ });
+
+ it('should have an event when arcTo is called', () => {
+ ctx.arcTo(1, 2, 3, 4, 5);
+ });
+
+ it('should not have an event when arcTo is called with a non-finite number', () => {
+ ctx.arcTo(NaN, 2, 3, 4, 5);
+ });
+
+ it('should have an event when beginPath is called', () => {
+ ctx.beginPath();
+ });
+
+ it('should have an event when bezierCurveTo is called', () => {
+ ctx.bezierCurveTo(1, 2, 3, 4, 5, 6);
+ });
+
+ it('should not have an event when bezierCurveTo is called with a non-finite number', () => {
+ ctx.bezierCurveTo(NaN, 2, 3, 4, 5, 6);
+ });
+
+ it('should have an event when clearHitRegions is called', () => {
+ ctx.clearHitRegions();
+ });
+
+ it('should have an event when clearRect is called', () => {
+ ctx.clearRect(1, 2, 3, 4);
+ });
+
+ it('should not have an event when clearRect is passed bad values', () => {
+ ctx.clearRect(NaN, 1, 2, 3);
+ });
+
+ it('should have an event when clip is called', () => {
+ ctx.rect(1, 2, 3, 4);
+ ctx.clip();
+ });
+
+ it('should have an event when clip is called with a fillRule', () => {
+ ctx.rect(1, 2, 3, 4);
+ ctx.clip('evenodd');
+ });
+
+ it('should have an event when clip is called with a path', () => {
+ ctx.clip(path);
+ });
+
+ it('should have an event when clip is called with a path', () => {
+ ctx.clip(path, 'evenodd');
+ });
+
+ it('should have an event when closePath is called', () => {
+ ctx.closePath();
+ });
+
+ it('should have an event when createImageData is called', () => {
+ ctx.createImageData(100, 100);
+ });
+
+ it('should have an event when createLinearGradient is called', () => {
+ ctx.createLinearGradient(1, 2, 3, 4);
+ });
+
+ it('should have an event when createPattern is called', () => {
+ ctx.createPattern(img, 'no-repeat');
+ });
+
+ it('should have an event when createRadialGradient is called', () => {
+ ctx.createLinearGradient(1, 2, 3, 4, 5, 6);
+ });
+
+ it('should create an event when currentTransform is set', () => {
+ const t = ctx.currentTransform;
+ t.a = 1;
+ t.b = 2;
+ t.c = 3;
+ t.d = 4;
+ t.e = 5;
+ t.f = 6;
+ ctx.currentTransform = t;
+ });
+
+ it('should create an event when direction is set', () => {
+ ctx.direction = 'rtl';
+ });
+
+ it('should not create an event when direction is invalid', () => {
+ ctx.direction = 'testing';
+ });
+
+ it('should create an event when the drawFocusIsNeeded function is called without a path', () => {
+ const button = document.createElement('button');
+ ctx.drawFocusIfNeeded(button);
+ });
+
+ it('should create an event when drawFocusIfNeeded is called', () => {
+ const button = document.createElement('button');
+ ctx.drawFocusIfNeeded(path, button);
+ });
+
+ it('should have a event when drawImage is called', () => {
+ ctx.drawImage(img, 0, 0);
+ });
+
+ it('should not have a event when drawImage is called with non-finite numbers', () => {
+ ctx.drawImage(img, NaN, 0);
+ });
+
+ it('should have a event when drawImage is called with size parameters', () => {
+ ctx.drawImage(img, 0, 0, 100, 100);
+ });
+
+ it('should not have a event when drawImage is called with non-finite numbers and size parameters', () => {
+ ctx.drawImage(img, NaN, 0, 100, 100);
+ });
+
+ it('should have a event when drawImage is called with source parameters', () => {
+ ctx.drawImage(img, 0, 0, 100, 100, 0, 0, 200, 200);
+ });
+
+ it('should not have a event when drawImage is called with non-finite numbers and source parameters', () => {
+ ctx.drawImage(img, NaN, 0, 100, 100, 0, 0, 200, 200);
+ });
+
+ it('should have an event when ellipse is called', () => {
+ ctx.ellipse(1, 2, 3, 4, 5, 6, 7, true);
+ });
+
+ it('should not have an event when ellipse is called with non-finite numbers', () => {
+ ctx.ellipse(NaN, 2, 3, 4, 5, 6, 7, false);
+ });
+
+ it('should have a event when fill() is called', () => {
+ ctx.beginPath();
+ ctx.arc(100, 101, 10, 0, Math.PI * 2);
+ ctx.fill();
+ });
+
+ it('should have a event when fill() is called with a fillRule', () => {
+ ctx.beginPath();
+ ctx.arc(100, 101, 10, 0, Math.PI * 2);
+ ctx.fill('evenodd');
+ });
+
+ it('should have a event when using a path', () => {
+ ctx.fill(path);
+ });
+
+ it('should have a event when fillRule is valid', () => {
+ ctx.fill(path, 'evenodd');
+ });
+
+ it('should have a event when fillRect is called', () => {
+ ctx.fillRect(1, 2, 3, 4);
+ });
+
+ it('should not have a event when fillRect is passed bad values', () => {
+ ctx.fillRect(NaN, 1, 2, 3);
+ });
+
+ it('should have an event when fillStyle is set', () => {
+ ctx.fillStyle = 'blue';
+ });
+
+ it('should not have an event when fillStyle is set with invalid input', () => {
+ ctx.fillStyle = 'testing';
+ });
+
+ it('should have a event when fillText is valid', () => {
+ ctx.fillText('hello world', 0, 0);
+ });
+
+ it('should have a event when fillText has valid maxWidth', () => {
+ ctx.fillText('hello world', 0, 0, 100);
+ });
+
+ it('should not have a event when fillText is not valid', () => {
+ ctx.fillText('hello world', 0, NaN);
+ });
+
+ it('should not have a event when fillText is not valid', () => {
+ ctx.fillText('hello world', 0, 0, NaN);
+ });
+
+ it('should have an event when filter is set', () => {
+ ctx.filter = 'test'; // no input is validated
+ });
+
+ it('should have an event when font is set', () => {
+ ctx.font = '12pt Comic Sans';
+ });
+
+ it('should not have an event when the font is not valid', () => {
+ ctx.font = 'invalid input';
+ });
+
+ it('should have an event when globalAlpha is set', () => {
+ ctx.globalAlpha = 0.5;
+ });
+
+ it('should not have an event when globalAlpha is invalid', () => {
+ ctx.globalAlpha = NaN;
+ });
+
+ it('should have an event when the globalCompositeOperation is set', () => {
+ ctx.globalCompositeOperation = 'source-in';
+ });
+
+ it('should not have an event when the globalCompositeOperation is invalid', () => {
+ ctx.globalCompositeOperation = 'bad-operation';
+ });
+
+ it('should have an event when the imageSmoothingEnabled property is set', () => {
+ ctx.imageSmoothingEnabled = true;
+ });
+
+ it('should have an event when the imageSmoothingQuality property is set', () => {
+ ctx.imageSmoothingQuality = 'high';
+ });
+
+ it('should not have an event when imageSmoothingQuality is invalid', () => {
+ ctx.imageSmoothingQuality = 'invalid';
+ });
+
+ it('should have an event when isPointInPath is called', () => {
+ ctx.isPointInPath(1, 2);
+ });
+
+ it('should have an event when isPointInStroke is called', () => {
+ ctx.isPointInStroke(1, 2);
+ });
+
+ it('should have an event when lineCap is set', () => {
+ ctx.lineCap = 'round';
+ });
+
+ it('should not have an event when lineCap is invalid', () => {
+ ctx.lineCap = 'bad-line-cap';
+ });
+
+ it('should have an event when lineDashOffset is set', () => {
+ ctx.lineDashOffset = 10;
+ });
+
+ it('should not have an event when lineDashOffset is invalid', () => {
+ ctx.lineDashOffset = NaN;
+ });
+
+ it('should have an event when lineJoin is set', () => {
+ ctx.lineJoin = 'round';
+ });
+
+ it('should not have an event when lineJoin is invalid', () => {
+ ctx.lineJoin = 'bad-line-join';
+ });
+
+ it('should have an event when lineTo is called', () => {
+ ctx.lineTo(1, 2);
+ });
+
+ it('should not have an event when lineTo is called with non-finite values', () => {
+ ctx.lineTo(NaN, 0);
+ });
+
+ it('should have an event when lineWidth is set', () => {
+ ctx.lineWidth = 10;
+ });
+
+ it('should not have an event when lineWidth is invalid', () => {
+ ctx.lineWidth = NaN;
+ });
+
+ it('should have an event when measureText is called', () => {
+ ctx.measureText('hello world!');
+ });
+
+ it('should have an event when miterLimit is set', () => {
+ ctx.miterLimit = 12;
+ });
+
+ it('should not have an event when miterLimit is negative', () => {
+ ctx.miterLimit = -10;
+ });
+
+ it('should not have an event when miterLimit is not finite', () => {
+ ctx.miterLimit = NaN;
+ });
+
+ it('should have an event when moveTo is called', () => {
+ ctx.moveTo(1, 2);
+ });
+
+ it('should not have an event when moveTo is called with non-finite values', () => {
+ ctx.moveTo(NaN, 1);
+ });
+
+ it('should have an event when putImageData is called', () => {
+ ctx.putImageData(imgData, 1, 2);
+ });
+
+ it('should not have an event when putImageData is called with non-finite values', () => {
+ ctx.putImageData(imgData, 1, NaN);
+ });
+
+ it('should have an event when putImageData with a dirty rectangle is called', () => {
+ ctx.putImageData(imgData, 1, 2, 3, 4, 5, 6);
+ });
+
+ it('should not have an event when putImageData with a dirty rectangle is called with non-finite values', () => {
+ ctx.putImageData(imgData, 1, NaN, 3, 4, 5, 6);
+ });
+
+ it('should have an event when quadraticCurveTo is called', () => {
+ ctx.quadraticCurveTo(1, 2, 3, 4);
+ });
+
+ it('should not have an event when quadraticCurveTo is called with non-finite values', () => {
+ ctx.quadraticCurveTo(NaN, 2, 3, 4);
+ });
+
+ it('should have an event when rect is called', () => {
+ ctx.rect(1, 2, 3, 4);
+ });
+
+ it('should not have an event when rect is called with non-finite values', () => {
+ ctx.rect(NaN, 2, 3, 4);
+ });
+
+ it('should have an event when removeHitRegion is called', () => {
+ ctx.removeHitRegion('test');
+ });
+
+ it('should have an event when resetTransform is called', () => {
+ ctx.resetTransform();
+ });
+
+ it('should not have an event when restore is called and the stack is empty', () => {
+ ctx.restore();
+ });
+
+ it('should have an event when save is called', () => {
+ ctx.save();
+ });
+
+ it('should have an event when restore is called after a save', () => {
+ ctx.save();
+ ctx.restore();
+ });
+
+ it('should have an event when rotate is called', () => {
+ ctx.rotate(Math.PI);
+ });
+
+ it('should not have an event when rotate is called with a non-finite value', () => {
+ ctx.rotate(NaN);
+ });
+
+ it('should have an event when scale is called', () => {
+ ctx.scale(1, 2);
+ });
+
+ it('should not have an event when scale is called with non-finite values', () => {
+ ctx.scale(NaN, 2);
+ });
+
+ it('should have an event when scrollPathIntoView is called', () => {
+ ctx.scrollPathIntoView();
+ });
+
+ it('should have an event when setLineDash is called', () => {
+ ctx.setLineDash([1, 2, 3]);
+ });
+
+ it('should not have an event when setLineDash is called with negative values', () => {
+ ctx.setLineDash([-1, -2, -3]);
+ });
+
+ it('should not have an event when setLineDash is called with non-finite values', () => {
+ ctx.setLineDash([NaN, 1, 2]);
+ });
+
+ it('should have an event when setTransform is called', () => {
+ ctx.setTransform(1, 2, 3, 4, 5, 6);
+ });
+
+ it('should not have an event when setTransform is called with non-finite values', () => {
+ ctx.setTransform(NaN, 2, 3, 4, 5, 6);
+ });
+
+ it('should have an event when shadowBlur is set', () => {
+ ctx.shadowBlur = 1;
+ });
+
+ it('should not have an event when shadowBlur is negative', () => {
+ ctx.shadowBlur = -1;
+ });
+
+ it('should not have an event when shadowBlur is not finite', () => {
+ ctx.shadowBlur = NaN;
+ });
+
+ it('should have an event when the shadowColor is valid', () => {
+ ctx.shadowColor = 'red';
+ });
+
+ it('should not have an event when the shadowColor is not valid', () => {
+ ctx.shadowColor = 'the color of my soul';
+ });
+
+ it('should have an event when the shadowOffsetX is valid', () => {
+ ctx.shadowOffsetX = 10;
+ });
+
+ it('should not have an event when the shadowOffsetX is not finite', () => {
+ ctx.shadowOffsetX = NaN;
+ });
+
+ it('should have an event when the shadowOffsetY is valid', () => {
+ ctx.shadowOffsetY = 10;
+ });
+
+ it('should not have an event when the shadowOffsetY is not finite', () => {
+ ctx.shadowOffsetY = NaN;
+ });
+
+ it('should have an event when stroke is called', () => {
+ ctx.beginPath();
+ ctx.arc(100, 101, 10, 0, Math.PI * 2);
+ ctx.stroke();
+ });
+
+ it('should have an event when stroke is called with a path', () => {
+ ctx.stroke(path);
+ });
+
+ it('should have an event when strokeRect is called', () => {
+ ctx.strokeRect(1, 2, 3, 4);
+ });
+
+ it('should not have an event when strokeRect is passed bad values', () => {
+ ctx.strokeRect(NaN, 1, 2, 3);
+ });
+
+ it('should have an event when the strokeStyle is set', () => {
+ ctx.strokeStyle = 'blue';
+ });
+
+ it('should not have an event when the strokeStyle is not valid', () => {
+ ctx.strokeStyle = 'invalid';
+ });
+
+ it('should have an event when strokeText is valid', () => {
+ ctx.strokeText('hello world', 0, 0);
+ });
+
+ it('should have an event when strokeText has valid maxWidth', () => {
+ ctx.strokeText('hello world', 0, 0, 100);
+ });
+
+ it('should not have an event when strokeText is not valid', () => {
+ ctx.strokeText('hello world', 0, NaN);
+ });
+
+ it('should not have an event when strokeText is not valid', () => {
+ ctx.strokeText('hello world', 0, 0, NaN);
+ });
+
+ it('should have an event when the textAlign is set', () => {
+ ctx.textAlign = 'right';
+ });
+
+ it('should not have an event when the textAlign is not valid', () => {
+ ctx.textAlign = 'invalid';
+ });
+
+ it('should have an event when the textBaseline is set', () => {
+ ctx.textBaseline = 'hanging';
+ });
+
+ it('should not have an event when the textBaseline is not valid', () => {
+ ctx.textBaseline = 'invalid';
+ });
+
+ it('should have an event when transform is called', () => {
+ ctx.transform(1, 2, 3, 4, 5, 6);
+ });
+
+ it('should not have an event when transform is called with non-finite values', () => {
+ ctx.transform(NaN, 2, 3, 4, 5, 6);
+ });
+
+ it('should have an event when translate is called', () => {
+ ctx.translate(1, 2);
+ });
+
+ it('should not have an event when translate is called with non-finite values', () => {
+ ctx.translate(NaN, 1);
+ });
+});
diff --git a/__tests__/classes/CanvasRenderingContext2D.__getPath.js b/__tests__/classes/CanvasRenderingContext2D.__getPath.js
new file mode 100644
index 0000000..36a59f4
--- /dev/null
+++ b/__tests__/classes/CanvasRenderingContext2D.__getPath.js
@@ -0,0 +1,110 @@
+let ctx;
+beforeEach(() => {
+ // get a new context each test
+ ctx = document.createElement('canvas')
+ .getContext('2d');
+});
+
+const path = new Path2D();
+path.arc(100, 101, 10, 0, Math.PI * 2);
+
+afterEach(() => {
+ const drawCalls = ctx.__getPath(ctx);
+ expect(drawCalls).toMatchSnapshot();
+});
+
+describe('__getPath', () => {
+ it('should have a path item when arc is called', () => {
+ ctx.arc(1, 2, 3, 4, 5, true);
+ });
+
+ it('should not have a path item when arc is called with a non-finite number', () => {
+ ctx.arc(NaN, 2, 3, 4, 5, false);
+ });
+
+ it('should have a path item when arcTo is called', () => {
+ ctx.arcTo(1, 2, 3, 4, 5);
+ });
+
+ it('should not have a path item when arcTo is called with a non-finite number', () => {
+ ctx.arcTo(NaN, 2, 3, 4, 5);
+ });
+
+ it('should reset the path with beginPath', () => {
+ ctx.arc(1, 2, 3, 4, 5, true);
+ ctx.arc(1, 2, 3, 4, 5, true);
+ ctx.arc(1, 2, 3, 4, 5, true);
+ ctx.arc(1, 2, 3, 4, 5, true);
+ ctx.beginPath();
+ });
+
+ it('should have a path item when bezierCurveTo is called', () => {
+ ctx.bezierCurveTo(1, 2, 3, 4, 5, 6);
+ });
+
+ it('should not have a path item when bezierCurveTo is called with a non-finite number', () => {
+ ctx.bezierCurveTo(NaN, 2, 3, 4, 5, 6);
+ });
+
+ it('should have a path item when clip is called', () => {
+ ctx.rect(1, 2, 3, 4);
+ ctx.clip();
+ });
+
+ it('should have a path item when clip is called with a fillRule', () => {
+ ctx.rect(1, 2, 3, 4);
+ ctx.clip('evenodd');
+ });
+
+ it('should have a path item when clip is called with a path', () => {
+ ctx.clip(path);
+ });
+
+ it('should have a path item when clip is called with a path', () => {
+ ctx.clip(path, 'evenodd');
+ });
+
+ it('should have a path item when closePath is called', () => {
+ ctx.closePath();
+ });
+
+ it('should have a path item when ellipse is called', () => {
+ ctx.ellipse(1, 2, 3, 4, 5, 6, 7, true);
+ });
+
+ it('should not have a path item when ellipse is called with non-finite numbers', () => {
+ ctx.ellipse(NaN, 2, 3, 4, 5, 6, 7, false);
+ });
+
+ it('should have a path item when lineTo is called', () => {
+ ctx.lineTo(1, 2);
+ });
+
+ it('should not have a path item when lineTo is called with non-finite values', () => {
+ ctx.lineTo(NaN, 0);
+ });
+
+ it('should have a path item when moveTo is called', () => {
+ ctx.moveTo(1, 2);
+ });
+
+ it('should not have a path item when moveTo is called with non-finite values', () => {
+ ctx.moveTo(NaN, 1);
+ });
+
+ it('should have a path item when quadraticCurveTo is called', () => {
+ ctx.quadraticCurveTo(1, 2, 3, 4);
+ });
+
+ it('should not have a path item when quadraticCurveTo is called with non-finite values', () => {
+ ctx.quadraticCurveTo(NaN, 2, 3, 4);
+ });
+
+ it('should have a path item when rect is called', () => {
+ ctx.rect(1, 2, 3, 4);
+ });
+
+ it('should not have a path item when rect is called with non-finite values', () => {
+ ctx.rect(NaN, 2, 3, 4);
+ });
+});
diff --git a/__tests__/classes/CanvasRenderingContext2D.createLinearGradient.js b/__tests__/classes/CanvasRenderingContext2D.createLinearGradient.js
index 7d32f64..d78790e 100644
--- a/__tests__/classes/CanvasRenderingContext2D.createLinearGradient.js
+++ b/__tests__/classes/CanvasRenderingContext2D.createLinearGradient.js
@@ -31,4 +31,9 @@ describe('createLinearGradient', () => {
expect(() => ctx.createLinearGradient(0, 1, Infinity, 3)).toThrow(TypeError);
expect(() => ctx.createLinearGradient(0, 1, 2, Infinity)).toThrow(TypeError);
});
+
+ it('should create a linear gradient with string values', () => {
+ const grd = ctx.createLinearGradient('1', '2', '3', '4');
+ expect(grd).toBeInstanceOf(CanvasGradient);
+ });
});
diff --git a/__tests__/classes/CanvasRenderingContext2D.createRadialGradient.js b/__tests__/classes/CanvasRenderingContext2D.createRadialGradient.js
index e7d3b0e..d6eaed1 100644
--- a/__tests__/classes/CanvasRenderingContext2D.createRadialGradient.js
+++ b/__tests__/classes/CanvasRenderingContext2D.createRadialGradient.js
@@ -35,4 +35,9 @@ describe('createRadialGradient', () => {
expect(() => ctx.createRadialGradient(0, 0, -1, 0, 0, 0)).toThrow(DOMException);
expect(() => ctx.createRadialGradient(0, 0, 0, 0, 0, -1)).toThrow(DOMException);
});
+
+ it('should create a radial gradient with string values', () => {
+ const result = ctx.createRadialGradient('1', '2', '3', '4', '5', '6');
+ expect(result).toBeInstanceOf(CanvasGradient);
+ });
});
diff --git a/__tests__/classes/CanvasRenderingContext2D.globalAlpha.js b/__tests__/classes/CanvasRenderingContext2D.globalAlpha.js
index 1d46fc3..3ca180b 100644
--- a/__tests__/classes/CanvasRenderingContext2D.globalAlpha.js
+++ b/__tests__/classes/CanvasRenderingContext2D.globalAlpha.js
@@ -10,7 +10,7 @@ beforeEach(() => {
describe('globalAlpha', () => {
it('should ignore non finite globalAlpha values', () => {
- [Infinity, -Infinity, null, void 0, NaN].forEach(e => {
+ [Infinity, -Infinity, void 0, NaN].forEach(e => {
ctx.globalAlpha = e;
expect(ctx.globalAlpha).toBe(1);
});
@@ -29,4 +29,9 @@ describe('globalAlpha', () => {
expect(ctx.globalAlpha).toBe(e);
});
});
+
+ it('should be 0 when globalAlpha is set to null', () => {
+ ctx.globalAlpha = null;
+ expect(ctx.globalAlpha).toBe(0);
+ });
});
diff --git a/__tests__/classes/__snapshots__/CanvasRenderingContext2D.__getDrawCalls.js.snap b/__tests__/classes/__snapshots__/CanvasRenderingContext2D.__getDrawCalls.js.snap
new file mode 100644
index 0000000..1155a42
--- /dev/null
+++ b/__tests__/classes/__snapshots__/CanvasRenderingContext2D.__getDrawCalls.js.snap
@@ -0,0 +1,542 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`__getDrawCalls should have a draw call when clearRect is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "height": 4,
+ "width": 3,
+ "x": 1,
+ "y": 2,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "clearRect",
+ },
+]
+`;
+
+exports[`__getDrawCalls should have a draw call when drawImage is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "dHeight": 300,
+ "dWidth": 400,
+ "dx": 0,
+ "dy": 0,
+ "img": ,
+ "sHeight": 300,
+ "sWidth": 400,
+ "sx": 0,
+ "sy": 0,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "drawImage",
+ },
+]
+`;
+
+exports[`__getDrawCalls should have a draw call when drawImage is called with size parameters 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "dHeight": 300,
+ "dWidth": 400,
+ "dx": 0,
+ "dy": 0,
+ "img": ,
+ "sHeight": 300,
+ "sWidth": 400,
+ "sx": 0,
+ "sy": 0,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "drawImage",
+ },
+]
+`;
+
+exports[`__getDrawCalls should have a draw call when drawImage is called with source parameters 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "dHeight": 200,
+ "dWidth": 200,
+ "dx": 0,
+ "dy": 0,
+ "img": ,
+ "sHeight": 100,
+ "sWidth": 100,
+ "sx": 0,
+ "sy": 0,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "drawImage",
+ },
+]
+`;
+
+exports[`__getDrawCalls should have a draw call when fill() is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "fillRule": "nonzero",
+ "path": Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+ Object {
+ "props": Object {
+ "anticlockwise": false,
+ "endAngle": 6.283185307179586,
+ "radius": 10,
+ "startAngle": 0,
+ "x": 100,
+ "y": 101,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "arc",
+ },
+ ],
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "fill",
+ },
+]
+`;
+
+exports[`__getDrawCalls should have a draw call when fill() is called with a fillRule 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "fillRule": "evenodd",
+ "path": Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+ Object {
+ "props": Object {
+ "anticlockwise": false,
+ "endAngle": 6.283185307179586,
+ "radius": 10,
+ "startAngle": 0,
+ "x": 100,
+ "y": 101,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "arc",
+ },
+ ],
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "fill",
+ },
+]
+`;
+
+exports[`__getDrawCalls should have a draw call when fillRect is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "height": 4,
+ "width": 3,
+ "x": 1,
+ "y": 2,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "fillRect",
+ },
+]
+`;
+
+exports[`__getDrawCalls should have a draw call when fillRule is valid 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "fillRule": "evenodd",
+ "path": Array [
+ Object {
+ "props": Object {
+ "anticlockwise": false,
+ "endAngle": 6.283185307179586,
+ "radius": 10,
+ "startAngle": 0,
+ "x": 100,
+ "y": 101,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "arc",
+ },
+ ],
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "fill",
+ },
+]
+`;
+
+exports[`__getDrawCalls should have a draw call when fillText has valid maxWidth 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "maxWidth": 100,
+ "text": "hello world",
+ "x": 0,
+ "y": 0,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "fillText",
+ },
+]
+`;
+
+exports[`__getDrawCalls should have a draw call when fillText is valid 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "maxWidth": null,
+ "text": "hello world",
+ "x": 0,
+ "y": 0,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "fillText",
+ },
+]
+`;
+
+exports[`__getDrawCalls should have a draw call when stroke is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "path": Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+ Object {
+ "props": Object {
+ "anticlockwise": false,
+ "endAngle": 6.283185307179586,
+ "radius": 10,
+ "startAngle": 0,
+ "x": 100,
+ "y": 101,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "arc",
+ },
+ ],
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "stroke",
+ },
+]
+`;
+
+exports[`__getDrawCalls should have a draw call when stroke is called with a path 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "path": Array [
+ Object {
+ "props": Object {
+ "anticlockwise": false,
+ "endAngle": 6.283185307179586,
+ "radius": 10,
+ "startAngle": 0,
+ "x": 100,
+ "y": 101,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "arc",
+ },
+ ],
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "stroke",
+ },
+]
+`;
+
+exports[`__getDrawCalls should have a draw call when strokeRect is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "height": 4,
+ "width": 3,
+ "x": 1,
+ "y": 2,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "strokeRect",
+ },
+]
+`;
+
+exports[`__getDrawCalls should have a draw call when strokeText has valid maxWidth 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "maxWidth": 100,
+ "text": "hello world",
+ "x": 0,
+ "y": 0,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "strokeText",
+ },
+]
+`;
+
+exports[`__getDrawCalls should have a draw call when strokeText is valid 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "maxWidth": null,
+ "text": "hello world",
+ "x": 0,
+ "y": 0,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "strokeText",
+ },
+]
+`;
+
+exports[`__getDrawCalls should have a draw call when using a path 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "fillRule": "nonzero",
+ "path": Array [
+ Object {
+ "props": Object {
+ "anticlockwise": false,
+ "endAngle": 6.283185307179586,
+ "radius": 10,
+ "startAngle": 0,
+ "x": 100,
+ "y": 101,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "arc",
+ },
+ ],
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "fill",
+ },
+]
+`;
+
+exports[`__getDrawCalls should not have a draw call when clearRect is passed bad values 1`] = `Array []`;
+
+exports[`__getDrawCalls should not have a draw call when drawImage is called with non-finite numbers 1`] = `Array []`;
+
+exports[`__getDrawCalls should not have a draw call when drawImage is called with non-finite numbers and size parameters 1`] = `Array []`;
+
+exports[`__getDrawCalls should not have a draw call when drawImage is called with non-finite numbers and source parameters 1`] = `Array []`;
+
+exports[`__getDrawCalls should not have a draw call when fillRect is passed bad values 1`] = `Array []`;
+
+exports[`__getDrawCalls should not have a draw call when fillText is not valid 1`] = `Array []`;
+
+exports[`__getDrawCalls should not have a draw call when fillText is not valid 2`] = `Array []`;
+
+exports[`__getDrawCalls should not have a draw call when strokeRect is passed bad values 1`] = `Array []`;
+
+exports[`__getDrawCalls should not have a draw call when strokeText is not valid 1`] = `Array []`;
+
+exports[`__getDrawCalls should not have a draw call when strokeText is not valid 2`] = `Array []`;
diff --git a/__tests__/classes/__snapshots__/CanvasRenderingContext2D.__getEvents.js.snap b/__tests__/classes/__snapshots__/CanvasRenderingContext2D.__getEvents.js.snap
new file mode 100644
index 0000000..4dddbae
--- /dev/null
+++ b/__tests__/classes/__snapshots__/CanvasRenderingContext2D.__getEvents.js.snap
@@ -0,0 +1,2125 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`__getEvents should create an event when currentTransform is set 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "a": 1,
+ "b": 2,
+ "c": 3,
+ "d": 4,
+ "e": 5,
+ "f": 6,
+ },
+ "transform": Array [
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ ],
+ "type": "currentTransform",
+ },
+]
+`;
+
+exports[`__getEvents should create an event when direction is set 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "value": "rtl",
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "direction",
+ },
+]
+`;
+
+exports[`__getEvents should create an event when drawFocusIfNeeded is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "element": ,
+ "path": Array [
+ Object {
+ "props": Object {
+ "anticlockwise": false,
+ "endAngle": 6.283185307179586,
+ "radius": 10,
+ "startAngle": 0,
+ "x": 100,
+ "y": 101,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "arc",
+ },
+ ],
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "drawFocusIfNeeded",
+ },
+]
+`;
+
+exports[`__getEvents should create an event when the drawFocusIsNeeded function is called without a path 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "element": ,
+ "path": null,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "drawFocusIfNeeded",
+ },
+]
+`;
+
+exports[`__getEvents should have a event when drawImage is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "dHeight": 300,
+ "dWidth": 400,
+ "dx": 0,
+ "dy": 0,
+ "img": ,
+ "sHeight": 300,
+ "sWidth": 400,
+ "sx": 0,
+ "sy": 0,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "drawImage",
+ },
+]
+`;
+
+exports[`__getEvents should have a event when drawImage is called with size parameters 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "dHeight": 300,
+ "dWidth": 400,
+ "dx": 0,
+ "dy": 0,
+ "img": ,
+ "sHeight": 300,
+ "sWidth": 400,
+ "sx": 0,
+ "sy": 0,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "drawImage",
+ },
+]
+`;
+
+exports[`__getEvents should have a event when drawImage is called with source parameters 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "dHeight": 200,
+ "dWidth": 200,
+ "dx": 0,
+ "dy": 0,
+ "img": ,
+ "sHeight": 100,
+ "sWidth": 100,
+ "sx": 0,
+ "sy": 0,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "drawImage",
+ },
+]
+`;
+
+exports[`__getEvents should have a event when fill() is called 1`] = `
+Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+ Object {
+ "props": Object {
+ "anticlockwise": false,
+ "endAngle": 6.283185307179586,
+ "radius": 10,
+ "startAngle": 0,
+ "x": 100,
+ "y": 101,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "arc",
+ },
+ Object {
+ "props": Object {
+ "fillRule": "nonzero",
+ "path": Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+ Object {
+ "props": Object {
+ "anticlockwise": false,
+ "endAngle": 6.283185307179586,
+ "radius": 10,
+ "startAngle": 0,
+ "x": 100,
+ "y": 101,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "arc",
+ },
+ ],
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "fill",
+ },
+]
+`;
+
+exports[`__getEvents should have a event when fill() is called with a fillRule 1`] = `
+Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+ Object {
+ "props": Object {
+ "anticlockwise": false,
+ "endAngle": 6.283185307179586,
+ "radius": 10,
+ "startAngle": 0,
+ "x": 100,
+ "y": 101,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "arc",
+ },
+ Object {
+ "props": Object {
+ "fillRule": "evenodd",
+ "path": Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+ Object {
+ "props": Object {
+ "anticlockwise": false,
+ "endAngle": 6.283185307179586,
+ "radius": 10,
+ "startAngle": 0,
+ "x": 100,
+ "y": 101,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "arc",
+ },
+ ],
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "fill",
+ },
+]
+`;
+
+exports[`__getEvents should have a event when fillRect is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "height": 4,
+ "width": 3,
+ "x": 1,
+ "y": 2,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "fillRect",
+ },
+]
+`;
+
+exports[`__getEvents should have a event when fillRule is valid 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "fillRule": "evenodd",
+ "path": Array [
+ Object {
+ "props": Object {
+ "anticlockwise": false,
+ "endAngle": 6.283185307179586,
+ "radius": 10,
+ "startAngle": 0,
+ "x": 100,
+ "y": 101,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "arc",
+ },
+ ],
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "fill",
+ },
+]
+`;
+
+exports[`__getEvents should have a event when fillText has valid maxWidth 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "maxWidth": 100,
+ "text": "hello world",
+ "x": 0,
+ "y": 0,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "fillText",
+ },
+]
+`;
+
+exports[`__getEvents should have a event when fillText is valid 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "maxWidth": null,
+ "text": "hello world",
+ "x": 0,
+ "y": 0,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "fillText",
+ },
+]
+`;
+
+exports[`__getEvents should have a event when using a path 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "fillRule": "nonzero",
+ "path": Array [
+ Object {
+ "props": Object {
+ "anticlockwise": false,
+ "endAngle": 6.283185307179586,
+ "radius": 10,
+ "startAngle": 0,
+ "x": 100,
+ "y": 101,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "arc",
+ },
+ ],
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "fill",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when addHitRegion is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "control": undefined,
+ "cursor": undefined,
+ "fillRule": undefined,
+ "id": "test",
+ "label": undefined,
+ "parentID": undefined,
+ "path": undefined,
+ "role": undefined,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "addHitRegion",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when arc is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "anticlockwise": true,
+ "endAngle": 5,
+ "radius": 3,
+ "startAngle": 4,
+ "x": 1,
+ "y": 2,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "arc",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when arcTo is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "cpx1": 1,
+ "cpx2": 3,
+ "cpy1": 2,
+ "cpy2": 4,
+ "radius": 5,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "arcTo",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when beginPath is called 1`] = `
+Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when bezierCurveTo is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "cpx1": 1,
+ "cpx2": 3,
+ "cpy1": 2,
+ "cpy2": 4,
+ "x": 5,
+ "y": 6,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "bezierCurveTo",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when clearHitRegions is called 1`] = `
+Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "clearHitRegions",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when clearRect is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "height": 4,
+ "width": 3,
+ "x": 1,
+ "y": 2,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "clearRect",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when clip is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "height": 4,
+ "width": 3,
+ "x": 1,
+ "y": 2,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "rect",
+ },
+ Object {
+ "props": Object {
+ "fillRule": "nonzero",
+ "path": Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+ Object {
+ "props": Object {
+ "height": 4,
+ "width": 3,
+ "x": 1,
+ "y": 2,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "rect",
+ },
+ ],
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "clip",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when clip is called with a fillRule 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "height": 4,
+ "width": 3,
+ "x": 1,
+ "y": 2,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "rect",
+ },
+ Object {
+ "props": Object {
+ "fillRule": "evenodd",
+ "path": Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+ Object {
+ "props": Object {
+ "height": 4,
+ "width": 3,
+ "x": 1,
+ "y": 2,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "rect",
+ },
+ ],
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "clip",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when clip is called with a path 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "fillRule": "nonzero",
+ "path": Array [
+ Object {
+ "props": Object {
+ "anticlockwise": false,
+ "endAngle": 6.283185307179586,
+ "radius": 10,
+ "startAngle": 0,
+ "x": 100,
+ "y": 101,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "arc",
+ },
+ ],
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "clip",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when clip is called with a path 2`] = `
+Array [
+ Object {
+ "props": Object {
+ "fillRule": "evenodd",
+ "path": Array [
+ Object {
+ "props": Object {
+ "anticlockwise": false,
+ "endAngle": 6.283185307179586,
+ "radius": 10,
+ "startAngle": 0,
+ "x": 100,
+ "y": 101,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "arc",
+ },
+ ],
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "clip",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when closePath is called 1`] = `
+Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "closePath",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when createImageData is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "height": 100,
+ "width": 100,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "createImageData",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when createLinearGradient is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "x0": 1,
+ "x1": 3,
+ "y0": 2,
+ "y1": 4,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "createLinearGradient",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when createPattern is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "image": ,
+ "type": "no-repeat",
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "createPattern",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when createRadialGradient is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "x0": 1,
+ "x1": 3,
+ "y0": 2,
+ "y1": 4,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "createLinearGradient",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when ellipse is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "anticlockwise": true,
+ "endAngle": 7,
+ "radiusX": 3,
+ "radiusY": 4,
+ "rotation": 5,
+ "startAngle": 6,
+ "x": 1,
+ "y": 2,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "ellipse",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when fillStyle is set 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "value": "#00f",
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "fillStyle",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when filter is set 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "value": "test",
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "filter",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when font is set 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "value": "16px \\"Comic Sans\\"",
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "font",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when globalAlpha is set 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "value": 0.5,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "globalAlpha",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when isPointInPath is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "fillRule": "nonzero",
+ "path": Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+ ],
+ "x": 1,
+ "y": 2,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "isPointInPath",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when isPointInStroke is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "path": Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+ ],
+ "x": 1,
+ "y": 2,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "isPointInPath",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when lineCap is set 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "value": "round",
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "lineCap",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when lineDashOffset is set 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "value": 10,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "lineDashOffset",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when lineJoin is set 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "value": "round",
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "lineJoin",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when lineTo is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "x": 1,
+ "y": 2,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "lineTo",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when lineWidth is set 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "value": 10,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "lineWidth",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when measureText is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "text": "hello world!",
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "measureText",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when miterLimit is set 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "value": 12,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "lineWidth",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when moveTo is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "x": 1,
+ "y": 2,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "moveTo",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when putImageData is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "dirtyHeight": NaN,
+ "dirtyWidth": NaN,
+ "dirtyX": NaN,
+ "dirtyY": NaN,
+ "x": 1,
+ "y": 2,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "putImageData",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when putImageData with a dirty rectangle is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "dirtyHeight": 6,
+ "dirtyWidth": 5,
+ "dirtyX": 3,
+ "dirtyY": 4,
+ "x": 1,
+ "y": 2,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "putImageData",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when quadraticCurveTo is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "cpx": 1,
+ "cpy": 2,
+ "x": 3,
+ "y": 4,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "quadraticCurveTo",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when rect is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "height": 4,
+ "width": 3,
+ "x": 1,
+ "y": 2,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "rect",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when removeHitRegion is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "id": "test",
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "removeHitRegion",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when resetTransform is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "a": 1,
+ "b": 0,
+ "c": 0,
+ "d": 1,
+ "e": 0,
+ "f": 0,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "resetTransform",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when restore is called after a save 1`] = `
+Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "save",
+ },
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "restore",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when rotate is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "angle": 3.141592653589793,
+ },
+ "transform": Array [
+ -1,
+ 1.2246467991473532e-16,
+ -1.2246467991473532e-16,
+ -1,
+ 0,
+ 0,
+ ],
+ "type": "rotate",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when save is called 1`] = `
+Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "save",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when scale is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "x": 1,
+ "y": 2,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 2,
+ 0,
+ 0,
+ ],
+ "type": "scale",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when scrollPathIntoView is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "path": Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+ ],
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "scrollPathIntoView",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when setLineDash is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "value": Array [
+ 1,
+ 2,
+ 3,
+ 1,
+ 2,
+ 3,
+ ],
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "setLineDash",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when setTransform is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "a": 1,
+ "b": 2,
+ "c": 3,
+ "d": 4,
+ "e": 5,
+ "f": 6,
+ },
+ "transform": Array [
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ ],
+ "type": "setTransform",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when shadowBlur is set 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "value": 1,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "shadowBlur",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when stroke is called 1`] = `
+Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+ Object {
+ "props": Object {
+ "anticlockwise": false,
+ "endAngle": 6.283185307179586,
+ "radius": 10,
+ "startAngle": 0,
+ "x": 100,
+ "y": 101,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "arc",
+ },
+ Object {
+ "props": Object {
+ "path": Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+ Object {
+ "props": Object {
+ "anticlockwise": false,
+ "endAngle": 6.283185307179586,
+ "radius": 10,
+ "startAngle": 0,
+ "x": 100,
+ "y": 101,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "arc",
+ },
+ ],
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "stroke",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when stroke is called with a path 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "path": Array [
+ Object {
+ "props": Object {
+ "anticlockwise": false,
+ "endAngle": 6.283185307179586,
+ "radius": 10,
+ "startAngle": 0,
+ "x": 100,
+ "y": 101,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "arc",
+ },
+ ],
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "stroke",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when strokeRect is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "height": 4,
+ "width": 3,
+ "x": 1,
+ "y": 2,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "strokeRect",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when strokeText has valid maxWidth 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "maxWidth": 100,
+ "text": "hello world",
+ "x": 0,
+ "y": 0,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "strokeText",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when strokeText is valid 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "maxWidth": null,
+ "text": "hello world",
+ "x": 0,
+ "y": 0,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "strokeText",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when the globalCompositeOperation is set 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "value": "source-in",
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "globalCompositeOperation",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when the imageSmoothingEnabled property is set 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "value": true,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "imageSmoothingEnabled",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when the imageSmoothingQuality property is set 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "value": "high",
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "imageSmoothingQuality",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when the shadowColor is valid 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "value": "#f00",
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "shadowColor",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when the shadowOffsetX is valid 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "value": 10,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "shadowOffsetX",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when the shadowOffsetY is valid 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "value": 10,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "shadowOffsetY",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when the strokeStyle is set 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "value": "#00f",
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "strokeStyle",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when the textAlign is set 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "value": "right",
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "textAlign",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when the textBaseline is set 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "value": "hanging",
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "textBaseline",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when transform is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "a": 1,
+ "b": 2,
+ "c": 3,
+ "d": 4,
+ "e": 5,
+ "f": 6,
+ },
+ "transform": Array [
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ ],
+ "type": "transform",
+ },
+]
+`;
+
+exports[`__getEvents should have an event when translate is called 1`] = `
+Array [
+ Object {
+ "props": Object {
+ "x": 1,
+ "y": 2,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 1,
+ 2,
+ ],
+ "type": "translate",
+ },
+]
+`;
+
+exports[`__getEvents should not create an event when direction is invalid 1`] = `Array []`;
+
+exports[`__getEvents should not have a event when drawImage is called with non-finite numbers 1`] = `Array []`;
+
+exports[`__getEvents should not have a event when drawImage is called with non-finite numbers and size parameters 1`] = `Array []`;
+
+exports[`__getEvents should not have a event when drawImage is called with non-finite numbers and source parameters 1`] = `Array []`;
+
+exports[`__getEvents should not have a event when fillRect is passed bad values 1`] = `Array []`;
+
+exports[`__getEvents should not have a event when fillText is not valid 1`] = `Array []`;
+
+exports[`__getEvents should not have a event when fillText is not valid 2`] = `Array []`;
+
+exports[`__getEvents should not have an event when arc is called with a non-finite number 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when arcTo is called with a non-finite number 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when bezierCurveTo is called with a non-finite number 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when clearRect is passed bad values 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when ellipse is called with non-finite numbers 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when fillStyle is set with invalid input 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when globalAlpha is invalid 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when imageSmoothingQuality is invalid 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when lineCap is invalid 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when lineDashOffset is invalid 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when lineJoin is invalid 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when lineTo is called with non-finite values 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when lineWidth is invalid 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when miterLimit is negative 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when miterLimit is not finite 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when moveTo is called with non-finite values 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when putImageData is called with non-finite values 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when putImageData with a dirty rectangle is called with non-finite values 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when quadraticCurveTo is called with non-finite values 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when rect is called with non-finite values 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when restore is called and the stack is empty 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when rotate is called with a non-finite value 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when scale is called with non-finite values 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when setLineDash is called with negative values 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when setLineDash is called with non-finite values 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when setTransform is called with non-finite values 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when shadowBlur is negative 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when shadowBlur is not finite 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when strokeRect is passed bad values 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when strokeText is not valid 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when strokeText is not valid 2`] = `Array []`;
+
+exports[`__getEvents should not have an event when the font is not valid 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when the globalCompositeOperation is invalid 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when the shadowColor is not valid 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when the shadowOffsetX is not finite 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when the shadowOffsetY is not finite 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when the strokeStyle is not valid 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when the textAlign is not valid 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when the textBaseline is not valid 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when transform is called with non-finite values 1`] = `Array []`;
+
+exports[`__getEvents should not have an event when translate is called with non-finite values 1`] = `Array []`;
diff --git a/__tests__/classes/__snapshots__/CanvasRenderingContext2D.__getPath.js.snap b/__tests__/classes/__snapshots__/CanvasRenderingContext2D.__getPath.js.snap
new file mode 100644
index 0000000..426f7c7
--- /dev/null
+++ b/__tests__/classes/__snapshots__/CanvasRenderingContext2D.__getPath.js.snap
@@ -0,0 +1,705 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`__getPath should have a path item when arc is called 1`] = `
+Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+ Object {
+ "props": Object {
+ "anticlockwise": true,
+ "endAngle": 5,
+ "radius": 3,
+ "startAngle": 4,
+ "x": 1,
+ "y": 2,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "arc",
+ },
+]
+`;
+
+exports[`__getPath should have a path item when arcTo is called 1`] = `
+Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+ Object {
+ "props": Object {
+ "cpx1": 1,
+ "cpx2": 3,
+ "cpy1": 2,
+ "cpy2": 4,
+ "radius": 5,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "arcTo",
+ },
+]
+`;
+
+exports[`__getPath should have a path item when bezierCurveTo is called 1`] = `
+Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+ Object {
+ "props": Object {
+ "cpx1": 1,
+ "cpx2": 3,
+ "cpy1": 2,
+ "cpy2": 4,
+ "x": 5,
+ "y": 6,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "bezierCurveTo",
+ },
+]
+`;
+
+exports[`__getPath should have a path item when clip is called 1`] = `
+Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+ Object {
+ "props": Object {
+ "height": 4,
+ "width": 3,
+ "x": 1,
+ "y": 2,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "rect",
+ },
+ Object {
+ "props": Object {
+ "fillRule": "nonzero",
+ "path": Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+ Object {
+ "props": Object {
+ "height": 4,
+ "width": 3,
+ "x": 1,
+ "y": 2,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "rect",
+ },
+ ],
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "clip",
+ },
+]
+`;
+
+exports[`__getPath should have a path item when clip is called with a fillRule 1`] = `
+Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+ Object {
+ "props": Object {
+ "height": 4,
+ "width": 3,
+ "x": 1,
+ "y": 2,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "rect",
+ },
+ Object {
+ "props": Object {
+ "fillRule": "evenodd",
+ "path": Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+ Object {
+ "props": Object {
+ "height": 4,
+ "width": 3,
+ "x": 1,
+ "y": 2,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "rect",
+ },
+ ],
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "clip",
+ },
+]
+`;
+
+exports[`__getPath should have a path item when clip is called with a path 1`] = `
+Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+ Object {
+ "props": Object {
+ "fillRule": "nonzero",
+ "path": Array [
+ Object {
+ "props": Object {
+ "anticlockwise": false,
+ "endAngle": 6.283185307179586,
+ "radius": 10,
+ "startAngle": 0,
+ "x": 100,
+ "y": 101,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "arc",
+ },
+ ],
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "clip",
+ },
+]
+`;
+
+exports[`__getPath should have a path item when clip is called with a path 2`] = `
+Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+ Object {
+ "props": Object {
+ "fillRule": "evenodd",
+ "path": Array [
+ Object {
+ "props": Object {
+ "anticlockwise": false,
+ "endAngle": 6.283185307179586,
+ "radius": 10,
+ "startAngle": 0,
+ "x": 100,
+ "y": 101,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "arc",
+ },
+ ],
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "clip",
+ },
+]
+`;
+
+exports[`__getPath should have a path item when closePath is called 1`] = `
+Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "closePath",
+ },
+]
+`;
+
+exports[`__getPath should have a path item when ellipse is called 1`] = `
+Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+ Object {
+ "props": Object {
+ "anticlockwise": true,
+ "endAngle": 7,
+ "radiusX": 3,
+ "radiusY": 4,
+ "rotation": 5,
+ "startAngle": 6,
+ "x": 1,
+ "y": 2,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "ellipse",
+ },
+]
+`;
+
+exports[`__getPath should have a path item when lineTo is called 1`] = `
+Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+ Object {
+ "props": Object {
+ "x": 1,
+ "y": 2,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "lineTo",
+ },
+]
+`;
+
+exports[`__getPath should have a path item when moveTo is called 1`] = `
+Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+ Object {
+ "props": Object {
+ "x": 1,
+ "y": 2,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "moveTo",
+ },
+]
+`;
+
+exports[`__getPath should have a path item when quadraticCurveTo is called 1`] = `
+Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+]
+`;
+
+exports[`__getPath should have a path item when rect is called 1`] = `
+Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+ Object {
+ "props": Object {
+ "height": 4,
+ "width": 3,
+ "x": 1,
+ "y": 2,
+ },
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "rect",
+ },
+]
+`;
+
+exports[`__getPath should not have a path item when arc is called with a non-finite number 1`] = `
+Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+]
+`;
+
+exports[`__getPath should not have a path item when arcTo is called with a non-finite number 1`] = `
+Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+]
+`;
+
+exports[`__getPath should not have a path item when bezierCurveTo is called with a non-finite number 1`] = `
+Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+]
+`;
+
+exports[`__getPath should not have a path item when ellipse is called with non-finite numbers 1`] = `
+Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+]
+`;
+
+exports[`__getPath should not have a path item when lineTo is called with non-finite values 1`] = `
+Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+]
+`;
+
+exports[`__getPath should not have a path item when moveTo is called with non-finite values 1`] = `
+Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+]
+`;
+
+exports[`__getPath should not have a path item when quadraticCurveTo is called with non-finite values 1`] = `
+Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+]
+`;
+
+exports[`__getPath should not have a path item when rect is called with non-finite values 1`] = `
+Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+]
+`;
+
+exports[`__getPath should reset the path with beginPath 1`] = `
+Array [
+ Object {
+ "props": Object {},
+ "transform": Array [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ ],
+ "type": "beginPath",
+ },
+]
+`;
diff --git a/package.json b/package.json
index e496f77..c362529 100644
--- a/package.json
+++ b/package.json
@@ -1,8 +1,9 @@
{
"name": "jest-canvas-mock",
- "version": "2.0.0",
- "description": "A module to mock canvas in Jest.",
+ "version": "2.1.0",
+ "description": "Mock a canvas in your jest tests.",
"main": "lib/index.js",
+ "types": "types/index.d.ts",
"scripts": {
"test": "jest --no-cache",
"build": "babel src --out-dir lib",
@@ -14,16 +15,17 @@
"parse-color": "^1.0.0"
},
"devDependencies": {
- "@babel/cli": "^7.2.3",
- "@babel/plugin-proposal-class-properties": "^7.3.0",
- "@babel/preset-env": "^7.3.1",
+ "@babel/cli": "^7.4.4",
+ "@babel/core": "^7.4.4",
+ "@babel/plugin-proposal-class-properties": "^7.4.4",
+ "@babel/preset-env": "^7.4.4",
"@commitlint/cli": "^7.5.2",
"@commitlint/config-angular": "^7.5.0",
- "babel-jest": "^24.0.0",
+ "babel-jest": "^24.8.0",
"babel-plugin-version": "^0.2.3",
- "coveralls": "^3.0.2",
- "husky": "^1.3.1",
- "jest": "^24.0.0"
+ "coveralls": "^3.0.3",
+ "husky": "^2.2.0",
+ "jest": "^24.8.0"
},
"commitlint": {
"extends": [
@@ -60,7 +62,7 @@
"unit"
],
"author": "hustcc",
- "license": "ISC",
+ "license": "MIT",
"bugs": {
"url": "https://github.com/hustcc/jest-canvas-mock/issues"
},
diff --git a/src/classes/CanvasRenderingContext2D.js b/src/classes/CanvasRenderingContext2D.js
index eacb31d..766b9b9 100644
--- a/src/classes/CanvasRenderingContext2D.js
+++ b/src/classes/CanvasRenderingContext2D.js
@@ -3,6 +3,7 @@ import CanvasPattern from './CanvasPattern';
import parseColor from 'parse-color';
import cssfontparser from 'cssfontparser';
import TextMetrics from './TextMetrics';
+import createCanvasEvent from '../mock/createCanvasEvent';
function parseCSSColor(value) {
const result = parseColor(value);
@@ -27,7 +28,38 @@ function parseCSSColor(value) {
const testFuncs = ['setLineDash', 'getLineDash', 'setTransform', 'getTransform', 'getImageData', 'save', 'restore', 'createPattern', 'createRadialGradient', 'addHitRegion', 'arc', 'arcTo', 'beginPath', 'clip', 'closePath', 'scale', 'stroke', 'clearHitRegions', 'clearRect', 'fillRect', 'strokeRect', 'rect', 'resetTransform', 'translate', 'moveTo', 'lineTo', 'bezierCurveTo', 'createLinearGradient', 'ellipse', 'measureText', 'rotate', 'drawImage', 'drawFocusIfNeeded', 'isPointInPath', 'isPointInStroke', 'putImageData', 'strokeText', 'fillText', 'quadraticCurveTo', 'removeHitRegion', 'fill', 'transform', 'scrollPathIntoView', 'createImageData'];
const compositeOperations = ['source-over', 'source-in', 'source-out', 'source-atop', 'destination-over', 'destination-in', 'destination-out', 'destination-atop', 'lighter', 'copy', 'xor', 'multiply', 'screen', 'overlay', 'darken', 'lighten', 'color-dodge', 'color-burn', 'hard-light', 'soft-light', 'difference', 'exclusion', 'hue', 'saturation', 'color', 'luminosity'];
+function getTransformSlice(ctx) {
+ return ctx._transformStack[ctx._stackIndex].slice();
+}
+
export default class CanvasRenderingContext2D {
+ /**
+ * Every time a function call would result in a drawing operation, it should be added to this array.
+ * This goes for only draw call functions.
+ */
+ _drawCalls = [];
+ __getDrawCalls() {
+ return this._drawCalls.slice();
+ }
+
+ /**
+ * Every time a function call results in something that would have modified the state of the context,
+ * an event is added to this array. This goes for every property set, and draw call.
+ */
+ _events = [];
+ __getEvents(ctx) {
+ return this._events.slice();
+ }
+
+ /**
+ * This array keeps track of the current path, so that fill and stroke operations can store the
+ * path.
+ */
+ _path = [createCanvasEvent('beginPath', [1, 0, 0, 1, 0, 0], {})];
+ __getPath(ctx) {
+ return ctx._path.slice();
+ }
+
_directionStack = ['inherit'];
_fillStyleStack = ['#000'];
_filterStack = ['none'];
@@ -72,16 +104,37 @@ export default class CanvasRenderingContext2D {
} = options;
if (!path && !id) throw new DOMException('ConstraintError', 'Failed to execute \'addHitRegion\' on \'' + this.constructor.name + '\': Both id and control are null.');
if (fillRule && fillRule !== 'evenodd' && fillRule !== 'nonzero') throw new TypeError('Failed to execute \'addHitRegion\' on \'' + this.constructor.name + '\': The provided value \'' + fillRule + '\' is not a valid enum value of type CanvasFillRule.');
+ this._events.push(
+ createCanvasEvent(
+ 'addHitRegion',
+ getTransformSlice(this),
+ { path, fillRule, id, parentID, cursor, control, label, role },
+ ),
+ );
}
arc(x, y, radius, startAngle, endAngle, anticlockwise = false) {
if (arguments.length < 5) throw new TypeError('Failed to execute \'arc\' on \'' + this.constructor.name + '\': 5 arguments required, but only ' + arguments.length + ' present.');
- for (let i = 0; i < 5; i++) {
- if (!Number.isFinite(Number(arguments[i]))) return;
- }
+ const xResult = Number(x);
+ const yResult = Number(y);
+ const radiusResult = Number(radius);
+ const startAngleResult = Number(startAngle);
+ const endAngleResult = Number(endAngle);
+ const anticlockwiseResult = Boolean(anticlockwise);
+
+ // quick is finite check
+ if (!Number.isFinite(xResult + yResult + radiusResult + startAngleResult + endAngleResult)) return;
if (Number(radius) < 0) throw new DOMException('IndexSizeError', 'Failed to execute \'arc\' on \'' + this.constructor.name + '\': The radius provided (' + radius + ') is negative.');
+
+ const event = createCanvasEvent(
+ 'arc',
+ getTransformSlice(this),
+ { x: xResult, y: yResult, radius: radiusResult, startAngle: startAngleResult, endAngle: endAngleResult, anticlockwise: anticlockwiseResult },
+ );
+ this._path.push(event);
+ this._events.push(event);
}
arcTo(cpx1, cpy1, cpx2, cpy2, radius) {
@@ -90,42 +143,125 @@ export default class CanvasRenderingContext2D {
const cpy1Result = Number(cpy1);
const cpx2Result = Number(cpx2);
const cpy2Result = Number(cpy2);
+ const radiusResult = Number(radius);
- if (Number.isFinite(cpx1Result) && Number.isFinite(cpx2Result) && Number.isFinite(cpy1Result) && Number.isFinite(cpy2Result)) {
- const radiusResult = Number(radius);
- if (Number.isFinite(radiusResult) && radiusResult < 0) throw new TypeError('Failed to execute \'arcTo\' on \'' + this.constructor.name + '\': The radius provided (' + radius + ') is negative.');
- }
+ if (!Number.isFinite(cpx1Result + cpx2Result + cpy1Result + cpy2Result + radiusResult)) return;
+ if (radiusResult < 0) throw new TypeError('Failed to execute \'arcTo\' on \'' + this.constructor.name + '\': The radius provided (' + radius + ') is negative.');
+
+ const event = createCanvasEvent(
+ 'arcTo',
+ getTransformSlice(this),
+ { cpx1: cpx1Result, cpy1: cpy1Result, cpx2: cpx2Result, cpy2: cpy2Result, radius: radiusResult },
+ );
+
+ this._path.push(event);
+ this._events.push(event);
}
- beginPath() {}
+ beginPath() {
+ const event = createCanvasEvent(
+ 'beginPath',
+ getTransformSlice(this),
+ { },
+ );
+ this._path = [event];
+ this._events.push(event);
+ }
bezierCurveTo(cpx1, cpy1, cpx2, cpy2, x, y) {
if (arguments.length < 6) throw new TypeError('Failed to execute \'bezierCurveTo\' on \'' + this.constructor.name + '\': 6 arguments required, but only ' + arguments.length + ' present.');
+ const cpx1Result = Number(cpx1);
+ const cpy1Result = Number(cpy1);
+ const cpx2Result = Number(cpx2);
+ const cpy2Result = Number(cpy2);
+ const xResult = Number(x);
+ const yResult = Number(y);
+
+ if (!Number.isFinite(cpx1Result + cpy1Result + cpx2Result + cpy2Result + xResult + yResult)) return;
+
+ const event = createCanvasEvent(
+ 'bezierCurveTo',
+ getTransformSlice(this),
+ { cpx1, cpy1, cpx2, cpy2, x, y },
+ );
+
+ this._path.push(event);
+ this._events.push(event);
}
get canvas() {
return this._canvas;
}
- clearHitRegions() {}
+ clearHitRegions() {
+ const event = createCanvasEvent(
+ 'clearHitRegions',
+ getTransformSlice(this),
+ { },
+ );
+ this._events.push(event);
+ }
clearRect(x, y, width, height) {
if (arguments.length < 4) throw new TypeError('Failed to execute \'clearRect\' on \'' + this.constructor.name + '\': 4 arguments required, but only ' + arguments.length + ' present.');
+
+ const xResult = Number(x);
+ const yResult = Number(y);
+ const widthResult = Number(width);
+ const heightResult = Number(height);
+
+ if (!Number.isFinite(x + y + width + height)) return;
+
+ const event = createCanvasEvent(
+ 'clearRect',
+ getTransformSlice(this),
+ { x: xResult,
+ y: yResult,
+ width: widthResult,
+ height: heightResult, },
+ );
+
+ this._events.push(event);
+ this._drawCalls.push(event);
}
clip(path, fillRule) {
- if (arguments.length === 0) return;
- if (arguments.length === 1) fillRule = 'nonzero';
- if (path instanceof Path2D) {
- fillRule = String(fillRule);
- if (fillRule !== 'nonzero' && fillRule !== 'evenodd') throw new TypeError('Failed to execute \'clip\' on \'' + this.constructor.name + '\': The provided value \'' + fillRule + '\' is not a valid enum value of type CanvasFillRule.');
+ if (arguments.length === 0) {
+ fillRule = 'nonzero';
+ path = this._path.slice();
} else {
- path = String(path);
- if (path !== 'nonzero' && path !== 'evenodd') throw new TypeError('Failed to execute \'clip\' on \'' + this.constructor.name + '\': The provided value \'' + path + '\' is not a valid enum value of type CanvasFillRule.');
+ if (arguments.length === 1) fillRule = 'nonzero';
+ if (path instanceof Path2D) {
+ fillRule = String(fillRule);
+ if (fillRule !== 'nonzero' && fillRule !== 'evenodd') throw new TypeError('Failed to execute \'clip\' on \'' + this.constructor.name + '\': The provided value \'' + fillRule + '\' is not a valid enum value of type CanvasFillRule.');
+ path = path._path.slice();
+ } else {
+ fillRule = String(path);
+ if (fillRule !== 'nonzero' && fillRule !== 'evenodd') throw new TypeError('Failed to execute \'clip\' on \'' + this.constructor.name + '\': The provided value \'' + fillRule + '\' is not a valid enum value of type CanvasFillRule.');
+ path = this._path.slice();
+ }
}
+
+ const event = createCanvasEvent(
+ 'clip',
+ getTransformSlice(this),
+ { path, fillRule },
+ );
+
+ this._path.push(event);
+ this._events.push(event);
}
- closePath() {}
+ closePath() {
+ const event = createCanvasEvent(
+ 'closePath',
+ getTransformSlice(this),
+ { },
+ );
+
+ this._events.push(event);
+ this._path.push(event);
+ }
createImageData(width, height) {
if (arguments.length < 1) throw new TypeError('Failed to execute \'createImageData\' on \'' + this.constructor.name + '\': 1 argument required, but only 0 present.');
@@ -133,22 +269,45 @@ export default class CanvasRenderingContext2D {
if (!(width instanceof ImageData)) throw new TypeError('Failed to execute \'createImageData\' on \'' + this.constructor.name + '\': parameter 1 is not of type \'ImageData\'.');
let result = new ImageData(width.width, width.height);
result.data.set(width.data);
+ const event = createCanvasEvent(
+ 'createImageData',
+ getTransformSlice(this),
+ { width: width.width, height: width.height }
+ );
+ this._events.push(event);
return result;
} else {
width = Math.abs(Number(width));
height = Math.abs(Number(height));
if (!Number.isFinite(width) || width === 0) throw new TypeError('Failed to execute \'createImageData\' on \'' + this.constructor.name + '\': The source width is 0.');
if (!Number.isFinite(height) || height === 0) throw new TypeError('Failed to execute \'createImageData\' on \'' + this.constructor.name + '\': The source height is 0.');
+ const event = createCanvasEvent(
+ 'createImageData',
+ getTransformSlice(this),
+ { width, height }
+ );
+ this._events.push(event);
return new ImageData(width, height);
}
}
createLinearGradient(x0, y0, x1, y1) {
if (arguments.length < 4) throw new TypeError('Failed to execute \'createLinearGradient\' on \'' + this.constructor.name + '\': 4 arguments required, but only ' + arguments.length + ' present.');
- if (!Number.isFinite(x0)) throw new TypeError('Failed to execute \'createLinearGradient\' on \'' + this.constructor.name + '\': The provided double value is non-finite.');
- if (!Number.isFinite(y0)) throw new TypeError('Failed to execute \'createLinearGradient\' on \'' + this.constructor.name + '\': The provided double value is non-finite.');
- if (!Number.isFinite(x1)) throw new TypeError('Failed to execute \'createLinearGradient\' on \'' + this.constructor.name + '\': The provided double value is non-finite.');
- if (!Number.isFinite(y1)) throw new TypeError('Failed to execute \'createLinearGradient\' on \'' + this.constructor.name + '\': The provided double value is non-finite.');
+ const x0Result = Number(x0);
+ const y0Result = Number(y0);
+ const x1Result = Number(x1);
+ const y1Result = Number(y1);
+
+ if (!Number.isFinite(x0Result + y0Result + x1Result + y1Result)) throw new TypeError('Failed to execute \'createLinearGradient\' on \'' + this.constructor.name + '\': The provided double value is non-finite.');
+
+ const event = createCanvasEvent(
+ 'createLinearGradient',
+ getTransformSlice(this),
+ { x0: x0Result, y0: y0Result, x1: x1Result, y1: y1Result },
+ );
+
+ this._events.push(event);
+
return new CanvasGradient();
}
@@ -158,13 +317,28 @@ export default class CanvasRenderingContext2D {
if (type === '') type = 'repeat';
if (type === 'repeat' || type === 'repeat-x' || type === 'repeat-y' || type === 'no-repeat') {
- if (image instanceof HTMLImageElement) return new CanvasPattern();
+ const event = createCanvasEvent(
+ 'createPattern',
+ getTransformSlice(this),
+ { image, type },
+ );
if (image instanceof ImageBitmap) {
if (image._closed) throw new DOMException('InvalidStateError', 'Failed to execute \'createPattern\' on \'CanvasRenderingContext2D\': The image source is detached.');
+ this._events.push(event);
+ return new CanvasPattern();
+ }
+ if (image instanceof HTMLImageElement) {
+ this._events.push(event);
+ return new CanvasPattern();
+ }
+ if (image instanceof HTMLVideoElement) {
+ this._events.push(event);
+ return new CanvasPattern();
+ }
+ if (image instanceof HTMLCanvasElement) {
+ this._events.push(event);
return new CanvasPattern();
}
- if (image instanceof HTMLVideoElement) return new CanvasPattern();
- if (image instanceof HTMLCanvasElement) return new CanvasPattern();
} else {
throw new TypeError('Failed to execute \'createPattern\' on \'' + this.constructor.name + '\': The provided type (\'' + type + '\') is not one of \'repeat\', \'no-repeat\', \'repeat-x\', or \'repeat-y\'.');
}
@@ -174,14 +348,28 @@ export default class CanvasRenderingContext2D {
createRadialGradient(x0, y0, r0, x1, y1, r1) {
if (arguments.length < 6) throw new TypeError('Failed to execute \'createRadialGradient\' on \'' + this.constructor.name + '\': 6 arguments required, but only ' + arguments.length + ' present.');
- if (!Number.isFinite(x0)) throw new TypeError('Failed to execute \'createRadialGradient\' on \'' + this.constructor.name + '\': The provided double value is non-finite.');
- if (!Number.isFinite(y0)) throw new TypeError('Failed to execute \'createRadialGradient\' on \'' + this.constructor.name + '\': The provided double value is non-finite.');
- if (!Number.isFinite(r0)) throw new TypeError('Failed to execute \'createRadialGradient\' on \'' + this.constructor.name + '\': The provided double value is non-finite.');
- if (!Number.isFinite(x1)) throw new TypeError('Failed to execute \'createRadialGradient\' on \'' + this.constructor.name + '\': The provided double value is non-finite.');
- if (!Number.isFinite(y1)) throw new TypeError('Failed to execute \'createRadialGradient\' on \'' + this.constructor.name + '\': The provided double value is non-finite.');
- if (!Number.isFinite(r1)) throw new TypeError('Failed to execute \'createRadialGradient\' on \'' + this.constructor.name + '\': The provided double value is non-finite.');
- if (r0 < 0) throw new DOMException('IndexSizeError', 'Failed to execute \'createRadialGradient\' on \'' + this.constructor.name + '\': The r0 provided is less than 0.');
- if (r1 < 0) throw new DOMException('IndexSizeError', 'Failed to execute \'createRadialGradient\' on \'' + this.constructor.name + '\': The r0 provided is less than 1.');
+ const x0Result = Number(x0);
+ const y0Result = Number(y0);
+ const r0Result = Number(r0);
+ const x1Result = Number(x1);
+ const y1Result = Number(y1);
+ const r1Result = Number(r1);
+
+ if (!Number.isFinite(x0Result + y0Result + r0Result + x1Result + y1Result + r1Result)) throw new TypeError('Failed to execute \'createRadialGradient\' on \'' + this.constructor.name + '\': The provided double value is non-finite.');
+ if (r0Result < 0) throw new DOMException('IndexSizeError', 'Failed to execute \'createRadialGradient\' on \'' + this.constructor.name + '\': The r0 provided is less than 0.');
+ if (r1Result < 0) throw new DOMException('IndexSizeError', 'Failed to execute \'createRadialGradient\' on \'' + this.constructor.name + '\': The r1 provided is less than 0.');
+
+ const event = createCanvasEvent(
+ 'createRadialGradient',
+ getTransformSlice(this),
+ { x0: x0Result,
+ y0: y0Result,
+ r0: r0Result,
+ x1: x1Result,
+ y1: y1Result,
+ r1: r1Result, },
+ );
+ this._events.push(event);
return new CanvasGradient();
}
@@ -193,6 +381,17 @@ export default class CanvasRenderingContext2D {
this._transformStack[this._stackIndex][3] = value.d;
this._transformStack[this._stackIndex][4] = value.e;
this._transformStack[this._stackIndex][5] = value.f;
+ const event = createCanvasEvent(
+ 'currentTransform',
+ getTransformSlice(this),
+ { a: value.a,
+ b: value.b,
+ c: value.c,
+ d: value.d,
+ e: value.e,
+ f: value.f, },
+ );
+ this._events.push(event);
}
}
@@ -203,6 +402,12 @@ export default class CanvasRenderingContext2D {
set direction(value) {
if (value === 'rtl' || value === 'ltr' || value === 'inherit') {
this._directionStack[this._stackIndex] = value;
+ const event = createCanvasEvent(
+ 'direction',
+ getTransformSlice(this),
+ { value },
+ );
+ this._events.push(event);
}
}
@@ -216,62 +421,194 @@ export default class CanvasRenderingContext2D {
if (arguments.length === 1) {
element = path;
+ path = null;
}
if (!(element instanceof Element)) throw new TypeError('Failed to execute \'drawFocusIfNeeded\' on \'' + this.constructor.name + '\': parameter ' + arguments.length + ' is not of type \'Element\'.');
+ const event = createCanvasEvent(
+ 'drawFocusIfNeeded',
+ getTransformSlice(this),
+ { path: path ? path._path : null, element },
+ );
+ this._events.push(event);
}
drawImage(img, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) {
if (arguments.length < 3) throw new TypeError('Failed to execute \'drawImage\' on \'' + this.constructor.name + '\': 3 arguments required, but only ' + arguments.length + ' present.');
if (arguments.length === 4 || arguments.length > 5 && arguments.length < 9) throw new TypeError('Failed to execute \'drawImage\' on \'' + this.constructor.name + '\': Valid arities are: [3, 5, 9], but 4 arguments provided.');
- if (img instanceof HTMLImageElement) return;
+ let valid = false;
+ if (img instanceof HTMLImageElement) valid = true;;
if (img instanceof ImageBitmap) {
if (img._closed) throw new DOMException('InvalidStateError', 'DOMException: Failed to execute \'drawImage\' on \'CanvasRenderingContext2D\': The image source is detached.');
- return;
+ valid = true;
+ }
+ if (img instanceof HTMLVideoElement) valid = true;
+ if (img instanceof HTMLCanvasElement) valid = true;
+ if (!valid) throw new TypeError('Failed to execute \'drawImage\' on \'' + this.constructor.name + '\': The provided value is not of type \'(CSSImageValue or HTMLImageElement or SVGImageElement or HTMLVideoElement or HTMLCanvasElement or ImageBitmap or OffscreenCanvas)\'');
+
+ const sxResult = Number(sx);
+ const syResult = Number(sy);
+ const sWidthResult = Number(sWidth);
+ const sHeightResult = Number(sHeight);
+ const dxResult = Number(dx);
+ const dyResult = Number(dy);
+ const dWidthResult = Number(dWidth);
+ const dHeightResult = Number(dHeight);
+
+ if (arguments.length === 3) {
+ if (!Number.isFinite(sxResult + syResult)) return;
+ sx = 0;
+ sy = 0;
+ sWidth = img.width;
+ sHeight = img.height;
+ dx = sxResult;
+ dy = syResult;
+ dWidth = img.width;
+ dHeight = img.height;
+ } else if (arguments.length === 5) {
+ if (!Number.isFinite(sxResult + syResult + sWidthResult + sHeightResult)) return;
+ sx = 0;
+ sy = 0;
+ sWidth = img.width;
+ sHeight = img.height;
+ dx = sxResult;
+ dy = syResult;
+ dWidth = sWidth;
+ dHeight = sHeight;
+ } else {
+ if (!Number.isFinite(sx + sy + sWidth + sHeight + dx + dy + dWidth + dHeight)) return;
+ sx = sxResult;
+ sy = syResult;
+ sWidth = sWidthResult;
+ sHeight = sHeightResult;
+ dx = dxResult;
+ dy = dyResult;
+ dWidth = dWidthResult;
+ dHeight = dHeightResult;
}
- if (img instanceof HTMLVideoElement) return;
- if (img instanceof HTMLCanvasElement) return;
- throw new TypeError('Failed to execute \'drawImage\' on \'' + this.constructor.name + '\': The provided value is not of type \'(CSSImageValue or HTMLImageElement or SVGImageElement or HTMLVideoElement or HTMLCanvasElement or ImageBitmap or OffscreenCanvas)\'');
+
+ const event = createCanvasEvent(
+ 'drawImage',
+ getTransformSlice(this),
+ { img,
+ sx,
+ sy,
+ sWidth,
+ sHeight,
+ dx,
+ dy,
+ dWidth,
+ dHeight, },
+ );
+ this._events.push(event);
+ this._drawCalls.push(event);
}
ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise = false) {
if (arguments.length < 7) throw new TypeError('Failed to execute \'ellipse\' on \'' + this.constructor.name + '\': 6 arguments required, but only ' + arguments.length + ' present.');
+ const xResult = Number(x);
+ const yResult = Number(y);
+ const radiusXResult = Number(radiusX);
+ const radiusYResult = Number(radiusY);
+ const rotationResult = Number(rotation);
+ const startAngleResult = Number(startAngle);
+ const endAngleResult = Number(endAngle);
+ const anticlockwiseResult = Boolean(anticlockwise);
- for (let i = 0; i < 7; i++) {
- if (!Number.isFinite(Number(arguments[i]))) return;
- }
+ if (!Number.isFinite(xResult + yResult + radiusXResult + radiusYResult + rotationResult + startAngleResult + endAngleResult)) return;
if (Number(radiusX) < 0) throw new DOMException('IndexSizeError', 'Failed to execute \'ellipse\' on \'' + this.constructor.name + '\': The major-axis radius provided (' + radiusX + ') is negative.');
if (Number(radiusY) < 0) throw new DOMException('IndexSizeError', 'Failed to execute \'ellipse\' on \'' + this.constructor.name + '\': The minor-axis radius provided (' + radiusY + ') is negative.');
+
+ const event = createCanvasEvent(
+ 'ellipse',
+ getTransformSlice(this),
+ { x: xResult,
+ y: yResult,
+ radiusX: radiusXResult,
+ radiusY: radiusYResult,
+ rotation: rotationResult,
+ startAngle: startAngleResult,
+ endAngle: endAngleResult,
+ anticlockwise: anticlockwiseResult, },
+ );
+ this._path.push(event);
+ this._events.push(event);
}
fill(path, fillRule) {
- if (arguments.length === 0) return;
- if (arguments.length === 1) fillRule = 'nonzero';
- if (path instanceof Path2D) {
- fillRule = String(fillRule);
- if (fillRule !== 'nonzero' && fillRule !== 'evenodd') throw new TypeError('Failed to execute \'fill\' on \'' + this.constructor.name + '\': The provided value \'' + fillRule + '\' is not a valid enum value of type CanvasFillRule.');
+ if (arguments.length === 0) {
+ fillRule = 'nonzero';
+ path = this._path.slice();
} else {
- path = String(path);
- if (path !== 'nonzero' && path !== 'evenodd') throw new TypeError('Failed to execute \'fill\' on \'' + this.constructor.name + '\': The provided value \'' + path + '\' is not a valid enum value of type CanvasFillRule.');
+ if (arguments.length === 1) fillRule = 'nonzero';
+ if (path instanceof Path2D) {
+ fillRule = String(fillRule);
+ if (fillRule !== 'nonzero' && fillRule !== 'evenodd') throw new TypeError('Failed to execute \'clip\' on \'' + this.constructor.name + '\': The provided value \'' + fillRule + '\' is not a valid enum value of type CanvasFillRule.');
+ path = path._path.slice();
+ } else {
+ fillRule = String(path);
+ if (fillRule !== 'nonzero' && fillRule !== 'evenodd') throw new TypeError('Failed to execute \'clip\' on \'' + this.constructor.name + '\': The provided value \'' + fillRule + '\' is not a valid enum value of type CanvasFillRule.');
+ path = this._path.slice();
+ }
}
+
+ const event = createCanvasEvent(
+ 'fill',
+ getTransformSlice(this),
+ { path, fillRule },
+ );
+
+ this._events.push(event);
+ this._drawCalls.push(event);
}
fillRect(x, y, width, height) {
if (arguments.length < 4) throw new TypeError('Failed to execute \'fillRect\' on \'' + this.constructor.name + '\': 4 arguments required, but only ' + arguments.length + ' present.');
+
+ const xResult = Number(x);
+ const yResult = Number(y);
+ const widthResult = Number(width);
+ const heightResult = Number(height);
+
+ if (!Number.isFinite(x + y + width + height)) return;
+
+ const event = createCanvasEvent(
+ 'fillRect',
+ getTransformSlice(this),
+ { x: xResult,
+ y: yResult,
+ width: widthResult,
+ height: heightResult, },
+ );
+
+ this._events.push(event);
+ this._drawCalls.push(event);
}
set fillStyle(value) {
+ let valid = false;
if (typeof value === 'string') {
const result = parseCSSColor(value);
if (result) {
- this._fillStyleStack[this._stackIndex] = result;
+ valid = true;
+ value = this._fillStyleStack[this._stackIndex] = result;
}
} else if (value instanceof CanvasGradient || value instanceof CanvasPattern) {
+ valid = true;
this._fillStyleStack[this._stackIndex] = value;
}
+
+ if (valid) {
+ const event = createCanvasEvent(
+ 'fillStyle',
+ getTransformSlice(this),
+ { value },
+ );
+ this._events.push(event);
+ }
}
get fillStyle() {
@@ -280,11 +617,33 @@ export default class CanvasRenderingContext2D {
fillText(text, x, y, maxWidth) {
if (arguments.length < 3) throw new TypeError('Failed to execute \'fillText\' on \'' + this.constructor.name + '\': 3 arguments required, but only ' + arguments.length + ' present.');
+ const textResult = String(text);
+ const xResult = Number(x);
+ const yResult = Number(y);
+ const maxWidthResult = Number(maxWidth);
+ if (arguments.length === 3 && !Number.isFinite(xResult + yResult)) return;
+ if (arguments.length > 3 && !Number.isFinite(xResult + yResult + maxWidthResult)) return;
+ const event = createCanvasEvent(
+ 'fillText',
+ getTransformSlice(this),
+ { text: textResult,
+ x: xResult,
+ y: yResult,
+ maxWidth: arguments.length === 3 ? null : maxWidthResult, },
+ );
+ this._events.push(event);
+ this._drawCalls.push(event);
}
set filter(value) {
if (value === '') value = 'none';
- this._filterStack[this._stackIndex] = typeof value === 'string' ? value : 'none';
+ value = this._filterStack[this._stackIndex] = typeof value === 'string' ? value : 'none';
+ const event = createCanvasEvent(
+ 'filter',
+ getTransformSlice(this),
+ { value, },
+ );
+ this._events.push(event);
}
get filter() {
@@ -296,7 +655,13 @@ export default class CanvasRenderingContext2D {
try {
const result = cssfontparser(value);
- this._fontStack[this._stackIndex] = result.toString();
+ value = this._fontStack[this._stackIndex] = result.toString();
+ const event = createCanvasEvent(
+ 'font',
+ getTransformSlice(this),
+ { value, },
+ );
+ this._events.push(event);
} catch (ex) {}
}
@@ -317,10 +682,17 @@ export default class CanvasRenderingContext2D {
}
set globalAlpha(value) {
+ value = Number(value);
if (!Number.isFinite(value)) return;
if (value < 0) return;
if (value > 1) return;
this._globalAlphaStack[this._stackIndex] = value;
+ const event = createCanvasEvent(
+ 'globalAlpha',
+ getTransformSlice(this),
+ { value, },
+ );
+ this._events.push(event);
}
get globalAlpha() {
@@ -330,6 +702,12 @@ export default class CanvasRenderingContext2D {
set globalCompositeOperation(value) {
if (compositeOperations.indexOf(value) !== -1) {
this._globalCompositeOperationStack[this._stackIndex] = value;
+ const event = createCanvasEvent(
+ 'globalCompositeOperation',
+ getTransformSlice(this),
+ { value, },
+ );
+ this._events.push(event);
}
}
@@ -338,7 +716,13 @@ export default class CanvasRenderingContext2D {
}
set imageSmoothingEnabled(value) {
- this._imageSmoothingEnabledStack[this._stackIndex] = Boolean(value);
+ value = this._imageSmoothingEnabledStack[this._stackIndex] = Boolean(value);
+ const event = createCanvasEvent(
+ 'imageSmoothingEnabled',
+ getTransformSlice(this),
+ { value, },
+ );
+ this._events.push(event);
}
get imageSmoothingEnabled() {
@@ -348,6 +732,12 @@ export default class CanvasRenderingContext2D {
set imageSmoothingQuality(value) {
if (value === 'high' || value === 'medium' || value === 'low') {
this._imageSmoothingQualityStack[this._stackIndex] = value;
+ const event = createCanvasEvent(
+ 'imageSmoothingQuality',
+ getTransformSlice(this),
+ { value, },
+ );
+ this._events.push(event);
}
}
@@ -357,19 +747,53 @@ export default class CanvasRenderingContext2D {
isPointInPath(path, x, y, fillRule = 'nonzero') {
if (arguments.length < 2) throw new TypeError('Failed to execute \'isPointInPath\' on \'' + this.constructor.name + '\': 2 arguments required, but only ' + arguments.length + ' present.');
- if (arguments.length === 3 && !(path instanceof Path2D)) fillRule = y;
+ if (!(path instanceof Path2D)) {
+ if (arguments.length > 2) {
+ fillRule = y;
+ }
+ y = x;
+ x = path;
+ }
if (fillRule !== 'nonzero' && fillRule !== 'evenodd') throw new TypeError('Failed to execute \'isPointInPath\' on \'' + this.constructor.name + '\': The provided value \'' + fillRule + '\' is not a valid enum value of type CanvasFillRule.');
+
+ const event = createCanvasEvent(
+ 'isPointInPath',
+ getTransformSlice(this),
+ { x: Number(x),
+ y: Number(y),
+ fillRule,
+ path: path instanceof Path2D ? path._path.slice() : this._path.slice(), },
+ );
+ this._events.push(event);
return false; // return false in a mocking environment, unless I can verify a point is actually within the path
}
isPointInStroke(path, x, y) {
if (arguments.length < 2) throw new TypeError('Failed to execute \'isPointInStroke\' on \'' + this.constructor.name + '\': 2 arguments required, but only ' + arguments.length + ' present.');
+ if (!(path instanceof Path2D)) {
+ y = x;
+ x = path;
+ }
+ const event = createCanvasEvent(
+ 'isPointInPath',
+ getTransformSlice(this),
+ { x: Number(x),
+ y: Number(y),
+ path: path instanceof Path2D ? path._path.slice() : this._path.slice(), },
+ );
+ this._events.push(event);
return false; // return false in a mocking environment, unless I can verify a point is actually within the path
}
set lineCap(value) {
if (value === 'butt' || value === 'round' || value === 'square') {
this._lineCapStack[this._stackIndex] = value;
+ const event = createCanvasEvent(
+ 'lineCap',
+ getTransformSlice(this),
+ { value, },
+ );
+ this._events.push(event);
}
}
@@ -382,6 +806,12 @@ export default class CanvasRenderingContext2D {
if (Number.isFinite(result)) {
this._lineDashOffsetStack[this._stackIndex] = result;
+ const event = createCanvasEvent(
+ 'lineDashOffset',
+ getTransformSlice(this),
+ { value, },
+ );
+ this._events.push(event);
}
}
@@ -392,6 +822,12 @@ export default class CanvasRenderingContext2D {
set lineJoin(value) {
if (value === 'round' || value === 'bevel' || value === 'miter') {
this._lineJoinStack[this._stackIndex] = value;
+ const event = createCanvasEvent(
+ 'lineJoin',
+ getTransformSlice(this),
+ { value, },
+ );
+ this._events.push(event);
}
}
@@ -401,6 +837,17 @@ export default class CanvasRenderingContext2D {
lineTo(x, y) {
if (arguments.length < 2) throw new TypeError('Failed to execute \'lineTo\' on \'' + this.constructor.name + '\': 2 arguments required, but only ' + arguments.length + ' present.');
+ const xResult = Number(x);
+ const yResult = Number(y);
+
+ if (!Number.isFinite(xResult + yResult)) return;
+ const event = createCanvasEvent(
+ 'lineTo',
+ getTransformSlice(this),
+ { x: xResult, y: yResult, },
+ );
+ this._events.push(event);
+ this._path.push(event);
}
set lineWidth(value) {
@@ -408,6 +855,12 @@ export default class CanvasRenderingContext2D {
if (Number.isFinite(result) && result > 0) {
this._lineWidthStack[this._stackIndex] = result;
+ const event = createCanvasEvent(
+ 'lineWidth',
+ getTransformSlice(this),
+ { value: result, },
+ );
+ this._events.push(event);
}
}
@@ -417,7 +870,15 @@ export default class CanvasRenderingContext2D {
measureText(text) {
if (arguments.length < 1) throw new TypeError('Failed to execute \'measureText\' on \'' + this.constructor.name + '\': 1 argument required, but only 0 present.');
- return new TextMetrics(String(text));
+ text = text == null ? '' : text;
+ text = text.toString();
+ const event = createCanvasEvent(
+ 'measureText',
+ getTransformSlice(this),
+ { text },
+ );
+ this._events.push(event);
+ return new TextMetrics(text);
}
set miterLimit(value) {
@@ -425,6 +886,12 @@ export default class CanvasRenderingContext2D {
if (Number.isFinite(result) && result > 0) {
this._miterLimitStack[this._stackIndex] = result;
+ const event = createCanvasEvent(
+ 'lineWidth',
+ getTransformSlice(this),
+ { value: result, },
+ );
+ this._events.push(event);
}
}
@@ -434,24 +901,101 @@ export default class CanvasRenderingContext2D {
moveTo(x, y) {
if (arguments.length < 2) throw new TypeError('Failed to execute \'moveTo\' on \'' + this.constructor.name + '\': 2 arguments required, but only ' + arguments.length + ' present.');
+ const xResult = Number(x);
+ const yResult = Number(y);
+
+ if (!Number.isFinite(x + y)) return;
+ const event = createCanvasEvent(
+ 'moveTo',
+ getTransformSlice(this),
+ { x: xResult,
+ y: yResult, },
+ );
+ this._events.push(event);
+ this._path.push(event);
}
putImageData(data, x, y, dirtyX, dirtyY, dirtyWidth, dirtyHeight) {
if (arguments.length < 3) throw new TypeError('Failed to execute \'putImageData\' on \'' + this.constructor.name + '\': 3 arguments required, but only ' + arguments.length + ' present.');
if (arguments.length > 3 && arguments.length < 7) throw new TypeError('Failed to execute \'putImageData\' on \'' + this.constructor.name + '\': Valid arities are: [3, 7], but ' + arguments.length + ' arguments provided.');
if (!(data instanceof ImageData)) throw new TypeError('Failed to execute \'putImageData\' on \'' + this.constructor.name + '\': parameter 1 is not of type \'ImageData\'.');
+
+ const xResult = Number(x);
+ const yResult = Number(y);
+ const dirtyXResult = Number(dirtyX);
+ const dirtyYResult = Number(dirtyY);
+ const dirtyWidthResult = Number(dirtyWidth);
+ const dirtyHeightResult = Number(dirtyHeight);
+
+ if (arguments.length === 3) {
+ if (!Number.isFinite(xResult + yResult)) return;
+ } else {
+ if (!Number.isFinite(xResult + yResult + dirtyXResult + dirtyYResult + dirtyWidthResult + dirtyHeightResult)) return;
+ }
+
+ const event = createCanvasEvent(
+ 'putImageData',
+ getTransformSlice(this),
+ { x: xResult,
+ y: yResult,
+ dirtyX: dirtyXResult,
+ dirtyY: dirtyYResult,
+ dirtyWidth: dirtyWidthResult,
+ dirtyHeight: dirtyHeightResult, },
+ );
+ this._events.push(event);
}
quadraticCurveTo(cpx, cpy, x, y) {
if (arguments.length < 4) throw new TypeError('Failed to execute \'quadraticCurveTo\' on \'' + this.constructor.name + '\': 4 arguments required, but only ' + arguments.length + ' present.');
+ const cpxResult = Number(cpx);
+ const cpyResult = Number(cpy);
+ const xResult = Number(x);
+ const yResult = Number(y);
+
+ if (!Number.isFinite(cpxResult + cpyResult + xResult + yResult)) return;
+
+ const event = createCanvasEvent(
+ 'quadraticCurveTo',
+ getTransformSlice(this),
+ { cpx: cpxResult,
+ cpy: cpyResult,
+ x: xResult,
+ y: yResult, },
+ );
+ this._events.push(event);
}
rect(x, y, width, height) {
if (arguments.length < 4) throw new TypeError('Failed to execute \'rect\' on \'' + this.constructor.name + '\': 4 arguments required, but only ' + arguments.length + ' present.');
+ if (!Number.isFinite(x + y + width + height)) return;
+
+ const xResult = Number(x);
+ const yResult = Number(y);
+ const widthResult = Number(width);
+ const heightResult = Number(height);
+ const event = createCanvasEvent(
+ 'rect',
+ getTransformSlice(this),
+ { x: xResult,
+ y: yResult,
+ width: widthResult,
+ height: heightResult, },
+ );
+
+ this._events.push(event);
+ this._path.push(event);
}
removeHitRegion(id) {
if (arguments.length < 1) throw new TypeError('Failed to execute \'removeHitRegion\' on \'' + this.constructor.name + '\': 1 argument required, but only ' + arguments.length + ' present.');
+ const event = createCanvasEvent(
+ 'removeHitRegion',
+ getTransformSlice(this),
+ { id },
+ );
+
+ this._events.push(event);
}
resetTransform() {
@@ -461,9 +1005,23 @@ export default class CanvasRenderingContext2D {
this._transformStack[this._stackIndex][3] = 1;
this._transformStack[this._stackIndex][4] = 0;
this._transformStack[this._stackIndex][5] = 0;
+
+ const event = createCanvasEvent(
+ 'resetTransform',
+ getTransformSlice(this),
+ { a: 1,
+ b: 0,
+ c: 0,
+ d: 1,
+ e: 0,
+ f: 0, },
+ );
+ this._events.push(event);
}
restore() {
+ if (this._stackIndex <= 0) return;
+
this._transformStack.pop();
this._directionStack.pop();
this._fillStyleStack.pop();
@@ -487,6 +1045,13 @@ export default class CanvasRenderingContext2D {
this._textAlignStack.pop();
this._textBaselineStack.pop();
this._stackIndex -= 1;
+
+ const event = createCanvasEvent(
+ 'restore',
+ getTransformSlice(this),
+ { },
+ );
+ this._events.push(event);
}
rotate(angle) {
@@ -503,6 +1068,13 @@ export default class CanvasRenderingContext2D {
this._transformStack[this._stackIndex][1] = b * cos + d * sin;
this._transformStack[this._stackIndex][2] = c * cos - a * sin;
this._transformStack[this._stackIndex][3] = d * cos - b * sin;
+
+ const event = createCanvasEvent(
+ 'rotate',
+ getTransformSlice(this),
+ { angle },
+ );
+ this._events.push(event);
}
save() {
@@ -529,6 +1101,13 @@ export default class CanvasRenderingContext2D {
this._textAlignStack.push(this._textAlignStack[this._stackIndex]);
this._textBaselineStack.push(this._textBaselineStack[this._stackIndex]);
this._stackIndex += 1;
+
+ const event = createCanvasEvent(
+ 'save',
+ getTransformSlice(this),
+ { },
+ );
+ this._events.push(event);
}
scale(x, y) {
@@ -541,16 +1120,33 @@ export default class CanvasRenderingContext2D {
this._transformStack[this._stackIndex][1] *= xResult;
this._transformStack[this._stackIndex][2] *= yResult;
this._transformStack[this._stackIndex][3] *= yResult;
+
+ const event = createCanvasEvent(
+ 'scale',
+ getTransformSlice(this),
+ { x: xResult,
+ y: yResult, },
+ );
+ this._events.push(event);
}
}
- scrollPathIntoView() {}
+ scrollPathIntoView(path) {
+ if (arguments.length > 0 && path instanceof Path2D) path = path._path.slice();
+ else path = this._path.slice();
+ const event = createCanvasEvent(
+ 'scrollPathIntoView',
+ getTransformSlice(this),
+ { path },
+ );
+ this._events.push(event);
+ }
setLineDash(lineDash) {
const isSequence = [Array, Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array]
.reduce((left, right) => left || lineDash instanceof right, false);
if (!isSequence) throw new TypeError('Failed to execute \'setLineDash\' on \'' + this.constructor.name + '\': The provided value cannot be converted to a sequence.');
- const result = [];
+ let result = [];
for (let i = 0; i < lineDash.length; i++) {
const value = Number(lineDash[i]);
@@ -562,49 +1158,59 @@ export default class CanvasRenderingContext2D {
}
}
- this._lineDashStack[this._stackIndex] = result.length % 2 === 1 ? result.concat(result) : result;
+ result = this._lineDashStack[this._stackIndex] = result.length % 2 === 1 ? result.concat(result) : result;
+ const event = createCanvasEvent(
+ 'setLineDash',
+ getTransformSlice(this),
+ { value: result.slice() },
+ );
+ this._events.push(event);
}
setTransform(a, b, c, d, e, f) {
- if (arguments.length === 0) {
- this._transformStack[this._stackIndex][0] = 1;
- this._transformStack[this._stackIndex][1] = 0;
- this._transformStack[this._stackIndex][2] = 0;
- this._transformStack[this._stackIndex][3] = 1;
- this._transformStack[this._stackIndex][4] = 0;
- this._transformStack[this._stackIndex][5] = 0;
- return;
- }
- if (arguments.length === 1) {
+ if (arguments.length === 0) {
+ a = 1;
+ b = 0;
+ c = 0;
+ d = 1;
+ e = 0;
+ f = 0;
+ } else if (arguments.length === 1) {
if (a instanceof DOMMatrix) {
- this.currentTransform = a;
+ let transform = a;
+ a = transform.a;
+ b = transform.b;
+ c = transform.c;
+ d = transform.d;
+ e = transform.e;
+ f = transform.f;
} else {
throw new TypeError('Failed to execute \'setTransform\' on \'' + this.constructor.name + '\': parameter ' + a + ' (\'transform\') is not an object.');
}
-
- return;
+ } else if (arguments.length < 6) {
+ throw new TypeError('Failed to execute \'setTransform\' on \'' + this.constructor.name + '\': Valid arities are: [0, 1, 6], but ' + arguments.length + ' arguments provided.');
}
-
- if (arguments.length < 6) throw new TypeError('Failed to execute \'setTransform\' on \'' + this.constructor.name + '\': Valid arities are: [0, 1, 6], but ' + arguments.length + ' arguments provided.');
a = Number(a);
b = Number(b);
c = Number(c);
d = Number(d);
e = Number(e);
f = Number(f);
- if (!Number.isFinite(a)) return;
- if (!Number.isFinite(b)) return;
- if (!Number.isFinite(c)) return;
- if (!Number.isFinite(d)) return;
- if (!Number.isFinite(e)) return;
- if (!Number.isFinite(f)) return;
+ if (!Number.isFinite(a + b + c + d + e + f)) return;
this._transformStack[this._stackIndex][0] = a;
this._transformStack[this._stackIndex][1] = b;
this._transformStack[this._stackIndex][2] = c;
this._transformStack[this._stackIndex][3] = d;
this._transformStack[this._stackIndex][4] = e;
this._transformStack[this._stackIndex][5] = f;
+
+ const event = createCanvasEvent(
+ 'setTransform',
+ getTransformSlice(this),
+ { a, b, c, d, e, f },
+ );
+ this._events.push(event);
}
set shadowBlur(value) {
@@ -612,6 +1218,12 @@ export default class CanvasRenderingContext2D {
if (Number.isFinite(result) && result > 0) {
this._shadowBlurStack[this._stackIndex] = result;
+ const event = createCanvasEvent(
+ 'shadowBlur',
+ getTransformSlice(this),
+ { value: result },
+ );
+ this._events.push(event);
}
}
@@ -625,6 +1237,12 @@ export default class CanvasRenderingContext2D {
if (result) {
this._shadowColorStack[this._stackIndex] = result;
+ const event = createCanvasEvent(
+ 'shadowColor',
+ getTransformSlice(this),
+ { value: result },
+ );
+ this._events.push(event);
}
}
}
@@ -638,6 +1256,12 @@ export default class CanvasRenderingContext2D {
if (Number.isFinite(result)) {
this._shadowOffsetXStack[this._stackIndex] = result;
+ const event = createCanvasEvent(
+ 'shadowOffsetX',
+ getTransformSlice(this),
+ { value: result },
+ );
+ this._events.push(event);
}
}
@@ -650,6 +1274,12 @@ export default class CanvasRenderingContext2D {
if (Number.isFinite(result)) {
this._shadowOffsetXStack[this._stackIndex] = result;
+ const event = createCanvasEvent(
+ 'shadowOffsetY',
+ getTransformSlice(this),
+ { value: result },
+ );
+ this._events.push(event);
}
}
@@ -658,23 +1288,61 @@ export default class CanvasRenderingContext2D {
}
stroke(path) {
- if (arguments.length > 0 && !(path instanceof Path2D)) throw new TypeError('Failed to execute \'stroke\' on \'' + this.constructor.name + '\': parameter 1 is not of type \'Path2D\'.');
+ if (arguments.length === 0) {
+ path = this._path.slice();
+ } else {
+ if (!(path instanceof Path2D)) throw new TypeError('Failed to execute \'stroke\' on \'' + this.constructor.name + '\': parameter 1 is not of type \'Path2D\'.');
+ path = path._path.slice();
+ }
+
+ const event = createCanvasEvent(
+ 'stroke',
+ getTransformSlice(this),
+ { path },
+ );
+ this._events.push(event);
+ this._drawCalls.push(event);
}
strokeRect(x, y, width, height) {
if (arguments.length < 4) throw new TypeError('Failed to execute \'strokeRect\' on \'' + this.constructor.name + '\': 4 arguments required, but only ' + arguments.length + ' present.');
+ x = Number(x);
+ y = Number(y);
+ width = Number(width);
+ height = Number(height);
+
+ if (!Number.isFinite(x + y + width + height)) return;
+ const event = createCanvasEvent(
+ 'strokeRect',
+ getTransformSlice(this),
+ { x, y, width, height },
+ );
+ this._events.push(event);
+ this._drawCalls.push(event);
}
set strokeStyle(value) {
+ let valid = false;
if (typeof value === 'string') {
const result = parseCSSColor(value);
if (result) {
- this._strokeStyleStack[this._stackIndex] = result;
+ valid = true;
+ value = this._strokeStyleStack[this._stackIndex] = result;
}
} else if (value instanceof CanvasGradient || value instanceof CanvasPattern) {
+ valid = true;
this._strokeStyleStack[this._stackIndex] = value;
}
+
+ if (valid) {
+ const event = createCanvasEvent(
+ 'strokeStyle',
+ getTransformSlice(this),
+ { value },
+ );
+ this._events.push(event);
+ }
}
get strokeStyle() {
@@ -683,11 +1351,33 @@ export default class CanvasRenderingContext2D {
strokeText(text, x, y, maxWidth) {
if (arguments.length < 3) throw new TypeError('Failed to execute \'strokeText\' on \'' + this.constructor.name + '\': 3 arguments required, but only ' + arguments.length + ' present.');
+ const textResult = String(text);
+ const xResult = Number(x);
+ const yResult = Number(y);
+ const maxWidthResult = Number(maxWidth);
+ if (arguments.length === 3 && !Number.isFinite(xResult + yResult)) return;
+ if (arguments.length > 3 && !Number.isFinite(xResult + yResult + maxWidthResult)) return;
+ const event = createCanvasEvent(
+ 'strokeText',
+ getTransformSlice(this),
+ { text: textResult,
+ x: xResult,
+ y: yResult,
+ maxWidth: arguments.length === 3 ? null : maxWidthResult, },
+ );
+ this._events.push(event);
+ this._drawCalls.push(event);
}
set textAlign(value) {
if (value === 'left' || value === 'right' || value === 'center' || value === 'start' || value === 'end') {
this._textAlignStack[this._stackIndex] = value;
+ const event = createCanvasEvent(
+ 'textAlign',
+ getTransformSlice(this),
+ { value, },
+ );
+ this._events.push(event);
}
}
@@ -698,6 +1388,12 @@ export default class CanvasRenderingContext2D {
set textBaseline(value) {
if (value === 'top' || value === 'hanging' || value === 'middle' || value === 'alphabetic' || value === 'ideographic' || value === 'bottom') {
this._textBaselineStack[this._stackIndex] = value;
+ const event = createCanvasEvent(
+ 'textBaseline',
+ getTransformSlice(this),
+ { value, },
+ );
+ this._events.push(event);
}
}
@@ -708,14 +1404,15 @@ export default class CanvasRenderingContext2D {
transform(a, b, c, d, e, f) {
if (arguments.length < 6) throw new TypeError('Failed to execute \'transform\' on \'' + this.constructor.name + '\': 6 arguments required, but only ' + arguments.length + ' present.');
- for (let i = 0; i < 6; i++) if (!Number.isFinite(Number(arguments[i]))) return;
-
a = Number(a);
b = Number(b);
c = Number(c);
d = Number(d);
e = Number(e);
f = Number(f);
+
+ if (!Number.isFinite(a + b + c + d + e + f)) return;
+
const sa = this._transformStack[this._stackIndex][0];
const sb = this._transformStack[this._stackIndex][1];
const sc = this._transformStack[this._stackIndex][2];
@@ -728,6 +1425,13 @@ export default class CanvasRenderingContext2D {
this._transformStack[this._stackIndex][3] = sb * c + sd * d;
this._transformStack[this._stackIndex][4] = sa * e + sc * f + se;
this._transformStack[this._stackIndex][5] = sb * e + sd * f + sf;
+
+ const event = createCanvasEvent(
+ 'transform',
+ getTransformSlice(this),
+ { a, b, c, d, e, f },
+ );
+ this._events.push(event);
}
translate(x, y) {
@@ -739,10 +1443,16 @@ export default class CanvasRenderingContext2D {
const c = this._transformStack[this._stackIndex][2];
const d = this._transformStack[this._stackIndex][3];
- if (Number.isFinite(xResult) && Number.isFinite(yResult)) {
+ if (Number.isFinite(xResult + yResult)) {
this._transformStack[this._stackIndex][4] += a * xResult + c * yResult;
this._transformStack[this._stackIndex][5] += b * xResult + d * yResult;
+
+ const event = createCanvasEvent(
+ 'translate',
+ getTransformSlice(this),
+ { x: xResult, y: yResult },
+ );
+ this._events.push(event);
}
}
-
}
diff --git a/src/classes/Path2D.js b/src/classes/Path2D.js
index c300a15..467e2d9 100644
--- a/src/classes/Path2D.js
+++ b/src/classes/Path2D.js
@@ -18,6 +18,11 @@ const borrowedFromCanvas = [
];
export default class Path2D {
+ _path = [];
+ _events = [];
+ _stackIndex = 0;
+ _transformStack = [[1, 0, 0, 1, 0, 0]];
+
constructor() {
borrowedFromCanvas.forEach((key) => {
this[key] = jest.fn(CanvasRenderingContext2D.prototype[key].bind(this));
@@ -30,5 +35,6 @@ export default class Path2D {
addPath(path) {
if (arguments.length < 1) throw new TypeError('Failed to execute \'addPath\' on \'Path2D\': 1 argument required, but only 0 present.');
if (!(path instanceof Path2D)) throw new TypeError('Failed to execute \'addPath\' on \'Path2D\': parameter 1 is not of type \'Path2D\'.');
+ this._path = this._path.concat(path._path);
}
}
diff --git a/src/mock/createCanvasEvent.js b/src/mock/createCanvasEvent.js
new file mode 100644
index 0000000..50ff06b
--- /dev/null
+++ b/src/mock/createCanvasEvent.js
@@ -0,0 +1,18 @@
+/**
+ * This function returns a CanvasRenderingContext2DEvent. Whenever an operation would modify the canvas
+ * context, this function should be used to generate an "event" that represents that sort of modification.
+ * This will be used to mock for snapshots.
+ *
+ * @example
+ * interface CanvasRenderingContext2DEvent {
+ * type: string;
+ * transform: [number, number, number, number, number, number]; // the resulting current transform
+ * props: {
+ * // if the event is a property was set, `event.props.value` would be set
+ * [propName: string]: any;
+ * };
+ * }
+ */
+export default function createCanvasEvent(type, transform, props) {
+ return { type, transform, props };
+}
diff --git a/types/index.d.ts b/types/index.d.ts
new file mode 100644
index 0000000..3646ad8
--- /dev/null
+++ b/types/index.d.ts
@@ -0,0 +1,54 @@
+export interface CanvasRenderingContext2DEvent {
+ /**
+ * This is the type of canvas event that occured.
+ */
+ type: string;
+ /**
+ * This is a six element array that contains the current state of the canvas `currentTransform`
+ * value.
+ */
+ transform: [number, number, number, number, number, number];
+ /**
+ * These are the relevant properties related to this canvas event.
+ */
+ props: {
+ [key: string]: any;
+ };
+}
+
+declare global {
+ interface CanvasRenderingContext2D {
+ /**
+ * Get all the events associated with this CanvasRenderingContext2D object.
+ *
+ * This method cannot be used in a production environment, only with `jest` using
+ * `jest-canvas-mock` and should only be used for testing.
+ *
+ * @example
+ * expect(ctx.__getEvents()).toMatchSnapshot();
+ */
+ __getEvents(): CanvasRenderingContext2DEvent[];
+
+ /**
+ * Get all the successful draw calls associated with this CanvasRenderingContext2D object.
+ *
+ * This method cannot be used in a production environment, only with `jest` using
+ * `jest-canvas-mock` and should only be used for testing.
+ *
+ * @example
+ * expect(ctx.__getDrawCalls()).toMatchSnapshot();
+ */
+ __getDrawCalls(): CanvasRenderingContext2DEvent[];
+
+ /**
+ * Get the current path associated with this CanvasRenderingContext2D object.
+ *
+ * This method cannot be used in a production environment, only with `jest` using
+ * `jest-canvas-mock` and should only be used for testing.
+ *
+ * @example
+ * expect(ctx.__getPath()).toMatchSnapshot();
+ */
+ __getPath(): CanvasRenderingContext2DEvent[];
+ }
+}