Inconsistent behaviour of mocking DOMPoint in Jest - jestjs

I have a number of geometry functions that use DOMPoint and DOMRect, which I want to test using jest. The framework is configured to use jsdom as test environment. Unfortunately, jsdom does not have definitions for DOMPoint and DOMRect, hence I need to mock them.
In my setupUnitTests.ts file, which is set as setupFilesAfterEnv value in jest.config.ts I have this mock call:
Object.defineProperty(globalThis, "DOMPoint", {
writable: true,
enumerable: true,
value: jest.fn().mockImplementation((x?: number, y?: number, z?: number, w?: number) => {
return {
x: x ?? 0,
y: y ?? 0,
z: z ?? 0,
w: w ?? 0,
matrixTransform: jest.fn(),
toJSON: jest.fn(),
};
}),
});
const p = new DOMPoint(1, 2, 3, 4);
The call at the end returns a DOM point instance with the expected values, fine.
In my test spec, however, the DOMPoint instance is not initialised. The mock implementation function is not called, like it is when I create a DOM point in the setup file. My spec file is very simple:
import { inflateRect, pointInRect, rectsAreEqual } from "../../../utilities/graphics";
describe("Graphics Tests", () => {
it("Rectangles", () => {
const point1 = new DOMPoint(2, 1, 3, 4);
const point2 = new DOMPoint(-95, 3, 4, 1);
const rect1 = new DOMRect(0, 0, 0, 0);
const rect2 = new DOMRect(-100, 0, 10, 10);
const rect3 = new DOMRect(0, 0, 10, 10);
const rect4 = new DOMRect(0, 0, 10, 10);
const rect5 = new DOMRect(-1, -1, 12, 12);
expect(pointInRect()).toBe(false);
expect(pointInRect(point1)).toBe(false);
expect(pointInRect(point1, rect1)).toBe(false);
expect(pointInRect(point1, rect2)).toBe(false);
expect(pointInRect(point2, rect2)).toBe(true);
expect(rectsAreEqual(rect1, rect2)).toBe(false);
expect(rectsAreEqual(rect3, rect4)).toBe(true);
expect(rectsAreEqual(rect4, rect3)).toBe(true);
expect(inflateRect(rect3, 1, 1, 1, 1)).toStrictEqual(rect5);
});
});
What's missing here? Why is the function given to jest.fn().mockImplementation not called?
The mock itself is definitely used, because when I remove it I get the error about DOMPoint not being defined.
I then moved the mock to the spec file:
describe("Graphics Tests", () => {
Object.defineProperty(global.self, "DOMPoint", {
writable: true,
enumerable: true,
value: jest.fn().mockImplementation((x?: number, y?: number, z?: number, w?: number) => {
return {
x: x ?? 0,
y: y ?? 0,
z: z ?? 0,
w: w ?? 0,
matrixTransform: jest.fn(),
toJSON: jest.fn(),
};
}),
});
it("Rectangles", () => {
const point1 = new DOMPoint(2, 1, 3, 4);
...
});
});
and found the mock function still not being called (but the mock defined). This is pretty confusing...

While debugging through the Jest code to compare the execution paths between the constructor invocations, I saw that in the spec file I had no mock config anymore (the default was used), which ultimately led me to the solution of my problem. In the jest config file I had set resetMocks: true, which caused the mocks to be removed. After setting that to false things started working as expected.

Related

Jasmine expecting a spy, but got a Function

I'm stuck being stubborn to give up and find a workaround for something that looks so simple and should be working out of the box...
I'm dealing with a Jasmine test suite using the Node environment, that is unable to spy upon an object that is clearly there/provided/imported.
I assume it's about the module resolution, and the writable property...
Can someone point me out in the proper direction, please?
For what it's worth: using Angular with testBed and stuff I never had such issues with spies, they work like...they're out of the box.
Notes:
using prototype to spy on did not fix: spyOn(client.prototype,'clone').and.callThrough();
and as you can see below, nothing gets overwritten.
Below a simplified implementation to demo.
index.js:
`
let state = null;
const templates = { obj: { a:1, b:2} },
init = () => state = clone(templates.obj),
clone = (obj) => JSON.parse(JSON.stringify(obj));
export {
init,
clone
}`
index.spec.js:
`
import * as client from '../../index.js';
describe("test", () => {
it("should spy on the clone method", () => {
console.log(Object.getOwnPropertyDescriptors(client));
spyOn(client, 'clone').and.callThrough();
client.init();
expect(client.clone).toHaveBeenCalled();
});
})`
test result:
`
> client#1.0.0 test
> jasmine
Randomized with seed 24629
Started
client.clone: [Function: clone]
{
clone: {
value: [Function: clone],
writable: true,
enumerable: true,
configurable: false
},
init: {
value: [Function: init],
writable: true,
enumerable: true,
configurable: false
},
[Symbol(Symbol.toStringTag)]: {
value: 'Module',
writable: false,
enumerable: false,
configurable: false
}
}
F
Failures:
1) test should spy on the clone method
Message:
Error: <toHaveBeenCalled> : Expected a spy, but got Function.
Usage: expect(<spyObj>).toHaveBeenCalled()`
index.spec.js
import * as importedModule from '../../index.js';
const client = Object.assign({}, importedModule);
describe("test", () => {
it("should spy on the clone method", () => {
spyOn(client, 'clone').and.callThrough();
client.clone({a:1,b:2});
expect(client.clone).toHaveBeenCalled();
});
})
test result:
client#1.0.0 test
jasmine
Randomized with seed 17205
Started
.
1 spec, 0 failures
Finished in 0.007 seconds
Randomized with seed 17205 (jasmine --random=true --seed=17205)
index.js
let state = null;
const templates = { obj: { a:1, b:2} },
init = () => state = clone(templates.obj),
clone = (obj) => JSON.parse(JSON.stringify(obj));
export {
init,
clone
}
index.spec.js
import * as client from '../../index.js';
describe("test", () => {
it("should spy on the clone method", () => {
spyOn(client, 'clone').and.callThrough();
client.clone({a:1,b:2});
expect(client.clone).toHaveBeenCalled();
});
});
test result
client#1.0.0 test
jasmine
Randomized with seed 76297
Started
F
Failures:
1. test should spy on the clone method
Message:
Error: : Expected a spy, but got Function.
Usage: expect().toHaveBeenCalled()
Stack:
at
at UserContext. (file:///home/masterjones/Projects/test/spec/support/index.spec.js:7:30)
at

Can't get node-cache to persist data between requests

I'm writing an API endpoint in node and can't seem to get node-cache to work. I've played around with the TTL, key names and anything else I can think of. What am I missing?
const NodeCache = require( "node-cache" );
export const getLiveFeed = async (
) => {
const cache = new NodeCache({ stdTTL: 10000000, checkperiod: 120 });
const cacheKey = 'liveFeed';
let items = cache.get(cacheKey);
console.log(`Got ${items} from the cache...`);
console.log(cache.getStats());
if (Array.isArray(items)) {
console.log(`Got ${items.length} from the cache`);
return items;
}
items = [1, 2, 3, 4, 5];
cache.set(cacheKey, items);
console.log(cache.getStats());
console.log(`Setting ${items.length} items in the cache`);
return items;
};
~
Making multiple requests to the endpoint produces:
Got undefined from the cache...
{ hits: 0, misses: 1, keys: 0, ksize: 0, vsize: 0 }
{ hits: 0, misses: 1, keys: 1, ksize: 8, vsize: 200 }
Setting 5 items in the cache
Got undefined from the cache...
{ hits: 0, misses: 1, keys: 0, ksize: 0, vsize: 0 }
{ hits: 0, misses: 1, keys: 1, ksize: 8, vsize: 200 }
Setting 5 items in the cache
Got undefined from the cache...
{ hits: 0, misses: 1, keys: 0, ksize: 0, vsize: 0 }
{ hits: 0, misses: 1, keys: 1, ksize: 8, vsize: 200 }
Setting 5 items in the cache
I see now, I have to declare the cache outside of the function.
const NodeCache = require( "node-cache" );
const cache = new NodeCache({ stdTTL: 10000000, checkperiod: 120 });
export const getLiveFeed = async (
) => {
....
}

Socket.io connection refused

I'm currently working on a simple game using WebSockets (first time!). However, my console is filled with net::ERR_CONNECTION_REFUSED errors, and I can't get anything to work.
I've been trying to fix the errors for hours and have seen a few other questions online with similar problems, but nothing has worked for me so far.
Here is my code:
server.js:
const { createGameState, gameLoop } = require("./game")
const { FRAME_RATE } = require("./constants")
const io = require("socket.io")(httpServer, {
cors: {
origin: "http://localhost:3000",
methods: ["GET", "POST"]
}
});
io.on("connection", client => {
const state = createGameState()
startGameInterval(client, state)
});
function startGameInterval(client, state) {
const intervalId = setInterval(() => {
const winner = gameLoop(state);
console.log("interval");
if(!winner) {
client.emit("gameOver");
clearInterval(intervalId);
} else {
client.emit("gameover");
clearInterval(intervalId);
}
}, 1000 / FRAME_RATE); // 1000
}
io.listen(3000);
index.js (top part):
const backgroundColor = "#1c1315";
const snakeColor = "#7d7778";
const foodColor = "#f55872";
const socket = io("http://localhost:3000")
socket.on("init", handleInit());
// socket.on("gamestate", handleGameState());
const gameScreen = document.getElementById("gameScreen");
let canvas, ctx;
const gameState = {
player: {
pos: {
x: 3,
y: 10,
},
vel: {
x: 1,
y: 0,
},
snake: [
{x: 1, y: 10},
{x: 2, y: 10},
{x: 3, y: 10},
]
},
food: {
x: 7,
y: 7,
},
gridsize: 20,
};
Here is the error that I'm getting; it gets printed in the console every 5 seconds or so.
polling-xhr.js:206 GET http://localhost:3000/socket.io/?EIO=4&transport=polling&t=NW6GI07 net::ERR_CONNECTION_REFUSED
I'm very new to socket.io/websockets in general, so I don't know what this error means or what I could do to fix the issue. Any help would be massively appreciated!

Lottie Animation in fabricjs canvas

Is it possible to load the Lottie animation in fabricjs canvas
I have tried the following samples
bodymovin.loadAnimation({
wrapper: animateElement, // div element
loop: true,
animType: 'canvas', // fabricjs canvas
animationData: dataValue, // AE json
rendererSettings: {
scaleMode: 'noScale',
clearCanvas: true,
progressiveLoad: false,
hideOnTransparent: true,
}
});
canvas.add(bodymovin);
canvas.renderAll();
I cant able to add the animation in the fabric js canvas. if any one overcome this issue kindly do comments on it
I might be late to answer this, but for anyone else looking, this pen could give you some pointers: https://codepen.io/shkaper/pen/oEKEgG
The idea here, first of all, is to extend fabric.Image class overriding its internal render method to render the contents of an arbitrary canvas that you yourself provide:
fabric.AEAnimation = fabric.util.createClass(fabric.Image, {
drawCacheOnCanvas: function(ctx) {
ctx.drawImage(this._AECanvas, -this.width / 2, -this.height / 2);
},
})
You can make this canvas a constructor argument, e.g.
initialize: function (AECanvas, options) {
options = options || {}
this.callSuper('initialize', AECanvas, options)
this._AECanvas = AECanvas
},
Then you'll just need to use lottie's canvas renderer to draw animation on a canvas and pass it to your new fabric.AEAnimation object.
I would assume so, by combining your code with something similar to https://itnext.io/video-element-serialization-and-deserialization-of-canvas-fc5dbf47666d. Depending on your scenario you might be able to get away with using something like http://fabricjs.com/interaction-with-objects-outside-canvas
If of any help, I've created this Lottie class with the support of exporting toObject/JSON
import { fabric } from 'fabric'
import lottie from 'lottie-web'
const Lottie = fabric.util.createClass(fabric.Image, {
type: 'lottie',
lockRotation: true,
lockSkewingX: true,
lockSkewingY: true,
srcFromAttribute: false,
initialize: function (path, options) {
if (!options.width) options.width = 480
if (!options.height) options.height = 480
this.path = path
this.tmpCanvasEl = fabric.util.createCanvasElement()
this.tmpCanvasEl.width = options.width
this.tmpCanvasEl.height = options.height
this.lottieItem = lottie.loadAnimation({
renderer: 'canvas',
loop: true,
autoplay: true,
path,
rendererSettings: {
context: this.tmpCanvasEl.getContext('2d'),
preserveAspectRatio: 'xMidYMid meet',
},
})
// this.lottieItem.addEventListener('DOMLoaded', () => {
// console.log('DOMLoaded')
// })
this.lottieItem.addEventListener('enterFrame', (e) => {
this.canvas?.requestRenderAll()
})
this.callSuper('initialize', this.tmpCanvasEl, options)
},
play: function () {
this.lottieItem.play()
},
stop: function () {
this.lottieItem.stop()
},
getSrc: function () {
return this.path
},
})
Lottie.fromObject = function (_object, callback) {
const object = fabric.util.object.clone(_object)
fabric.Image.prototype._initFilters.call(object, object.filters, function (filters) {
object.filters = filters || []
fabric.Image.prototype._initFilters.call(object, [object.resizeFilter], function (resizeFilters) {
object.resizeFilter = resizeFilters[0]
fabric.util.enlivenObjects([object.clipPath], function (enlivedProps) {
object.clipPath = enlivedProps[0]
const fabricLottie = new fabric.Lottie(object.src, object)
callback(fabricLottie, false)
})
})
})
}
Lottie.async = true
export default Lottie
To create Lottie element just pass JSON
const fabricImage = new fabric.Lottie('https://assets5.lottiefiles.com/private_files/lf30_rttpmsbc.json', {
scaleX: 0.5,
})
canvas.add(fabricImage)

Not able to Display Data properly in node cli-table

I'm trying to display data that i received from an api on console using cli-table. The api call and everything works fine. I'm just having trouble displaying the output. Here's my code
const
url = require('./url'),
Table = require('cli-table2'),
fs = require('fs'),
inquirer = require('inquirer')
let table = new Table({
head: ['Name', 'Type', 'Language',
'Genres', 'Status', 'Premiered', 'OfficialSite', 'Schedule', 'Rating', 'Streamed On', 'Externals', 'Images', 'summary'
],
colWidths: [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10]
});
const search = (item) => {
let shows = []
url.wordsearch(item)
.then(result => {
//console.log(result)
result.forEach(element => {
shows.push({ 'name': element.show['name'], 'value': element.show['id'] })
})
//console.log(shows)
inquirer.prompt([{
type: 'checkbox',
message: 'select tvshows you want see',
name: 'tvshow',
pageSize: 25,
choices: shows,
validate: (answer) => {
if (answer.length < 1) {
return "You must choose at least one card";
}
return true;
}
}]).then((answer) => {
//console.log(answer)
printresult(answer)
})
})
.catch(err => console.log(err))
}
const printresult = (answer) => {
answer.tvshow.forEach(show => {
url.idsearch(show)
.then(element => {
clean(element)
table.push([element['name'],
element['type'],
element['language'],
element['genres'],
element['status'],
element['premiered'],
element['officialSite'],
`Time :${element['schedule']['time']} \n Days: ${element['schedule']['days']}`,
element['rating'],
` name:${element['network']['name']}\n Country: ${element['network']['country']}`,
`tvrage:${element['externals']['tvrage']}\n thetvdb:${element['externals']['thetvdb']}\n imdb:${element['externals']['imdb']}`,
`medium:${element['image']['medium']}\n original:${element['image']['original']}`,
element['summary']
])
console.log(table.toString())
})
})
}
What am I doing wrong ? I want the output in a proper table format with no ... or anything. The printresult method does the work of printing to the console. I'm attaching a screenshot of my output.

Resources