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":