While writing tests I got bug TypeError: $.extend is not a function on toastr plugin that we are using. It seems that jQuery is not picked up properly, even tho is working normally in browser.
In our main mock file we imported jQuery and bind it to global windows object and it's accessible across whole application (but toastr plugin), even while testing in mocha:
import jsdom from 'jsdom';
import $ from 'jquery';
import chai from 'chai';
import chaiImmutable from 'chai-immutable';
import React from 'react';
const doc = jsdom.jsdom('<!doctype html><html><body></body></html>');
const win = doc.defaultView;
global.document = doc;
global.window = win;
global.expect = chai.expect;
global.$ = $(win);
global.jquery = $(win);
global.jQuery = $(win);
Object.keys(window).forEach((key) => {
if (!(key in global)) {
global[key] = window[key];
}
});
chai.use(chaiImmutable);
So while taking closer look at toastr I noticed this:
; (function (define) {
define(['jquery'], function ($) {
// this function is called on inizialization
function getOptions() {
return $.extend({}, getDefaults(), toastr.options);
}
It takes jquery from node_modules directly and then defines object $ in function scope, that means that it's ignoring window.$ (which is working normally even in here).
Therefore logging $ will return function, and logging $.anyMethodFromjQuery ($.extend) will return undefined.
In the end I tried logging $.prototype, in the browser it will return me jQuery object while in mocha it returns empty object {}
So in the end it define didn't created prototype in mocha environment and I cannot add one line of code $ = window.$; in plugin, since no one should edit a plugin + we are using npm.
Is there any solution for this?
You're running into trouble because you are loading code that should be loaded by JSDom outside of it. While it is possible in some cases to load code in Node and then pass it to the window that JSDom creates, that's a brittle approach, as you've discovered. Whenever I use JSDom for things other than write-and-toss cases, I load every script that would normally be loaded by a browser through JSDom. This avoids running into the issue you ran into. Here's a proof-of-concept based on the code you've shown in the question. You'll see that toastr.getContainer() runs fine, because Toastr has been loaded by JSDom. If you try to use the same code with an import toastr from "toastr" you'll get the same problem you ran into.
import jsdom from 'jsdom';
import $ from 'jquery';
import path from "path";
const doc = jsdom.jsdom('<!doctype html><html><body></body></html>', {
features: {
FetchExternalResources: ["script"],
ProcessExternalResources: ["script"]
}
});
const win = doc.defaultView;
global.document = doc;
global.window = win;
global.$ = $(win);
global.jquery = $(win);
global.jQuery = $(win);
window.addEventListener("error", function () {
console.log("ERROR");
});
const script = document.createElement("script");
script.src = path.join(__dirname, "./node_modules/toastr/toastr.js");
document.head.appendChild(script);
// The load will necessarily be asynchronous, so we have to wait for it.
script.addEventListener("load", function () {
console.log("LOADED");
console.log(window.toastr);
// We do this here so that `toastr` is also picked up.
Object.keys(window).forEach((key) => {
if (!(key in global)) {
global[key] = window[key];
}
});
toastr.getContainer();
});
Note that the code hung when I tried calling toastr.info(...). I took a look at the code of Toastr but it is not clear to me what causes the problem. There are features of browsers that JSDom is unable to emulate. It is possible that Toastr is dependent on such features. In the past, I've sometimes had to switch test suites away from JSDom due to its limitations.
Related
I'm trying to use the D3 extension in a Nuxt 3 project and for that I created a d3.client.js file in the plugins/ directory.
import * as d3 from "d3";
import { defineNuxtPlugin } from '#app'
export default defineNuxtPlugin(nuxtApp => {
nuxtApp.vueApp.use(d3)
})
However, when I try to use it gives me a 500 Internal Server Error document is not defined.
<script>
import * as d3 from "d3";
export default {
name: "globe",
created() {
d3.select("#globe");
}
}
</script>
How can I solve this?
d3.select() uses document.querySelector() under the hood. Since you're working server side, you don't have access to document yet. So you'll need to mock it or to avoid using it.
You can avoid using it all together by passing an element instead of a string to d3.select(), as it will then create a functioning d3 selection without running document.querySelector(). And since every other chained .select() or .selectAll() uses previousSelection.querySelector(), you can just continue from there.
If you do not have access to the DOM element directly, you might want to mock document. This article suggests using JSDOM:
import { JSDOM } from 'jsdom';
// create a new JSDOM instance for d3-selection to use
const document = new JSDOM().window.document;
d3.select(document.body)
.append('div');
I managed to solve it by using the d3.select with a Vue reference.
const globe = d3.select(this.$refs.globe)
I am trying to reconfigure my Symfony 3 project to use webpack encore instead of normal asstets. I am a little stuck with this.
For one part of the application I developed some JS plugins which inherits from Kube (a CSS and JS Framework I am unsing). Here is an exmaple:
(function (Kube) {
Kube.Assignment = function (element, options) {
this.namespace = 'assignment';
this.defaults = {
//...
};
Kube.apply(this, arguments);
this.start();
};
Kube.Assignment.prototype = {
start: function () {
//...
}
};
Kube.Assignment.inherits(Kube.Distribution);
Kube.Plugin.create('Assignment');
Kube.Plugin.autoload('Assignment');
}(Kube));
Unfortunatly Kube is not recognized.
The module 'imperavi-kube' is installed via yarn and is imported via
let Kube = require('imperavi-kube');
I am getting the following error: TypeError: Kube.Assignment.inherits is not a function
Propably this is not a issue of the Kube Framework but somthing about handeling JS plugins with webpack.
Inside the Kube module there is a class 'Kube' defined with a prototype. The Prototype ends with window.Kube = Kube;
Try to import Kube like this:
import * as Kube from <your path> or <alias> or <module name>
I may be wrong, but as far as I remember sometimes it works a bit differently.
I'am using chakram + mocha.
How can I use shared variables for all test?
For example, I would like to use variable API_PATH="http://example.com/v1/" in tests. And this variable could be changed during running test. So, my test looks like this.
var response = chakram.get(API_PATH + "products");
expect(response).to.have.status(200);
As example, protractor has conf.js with parameter baseUrl. Running test looks like protractor conf.js --baseUrl http://example.com/
what have you tried so far? Have you tried using beforeEach to reinitialize the object that you are using? You could just make the the shared variables declared outside of your tests.
EDIT: Adding details from what Jerry said:
If you want all variable to be reused within the same test, you must make them global variables. See example below
///include dependencies
var assert = require('assert'),
chai = require('chai'),
expect = chai.expect,
chakram = require('chakram');
//INIT GLOBAL VARAIBLES FOR within the same test
var testObj,
dummyData = {
user: 'John Kim',
lastSeenOnline: 'Wed August 11 12:05 2017'
};
describe ('#User', function () {
beforeEach(function () {
//init what the object contains
testObj = new DataStore(data, ContainerStore);
});
it ('#Should return the name of the user', function () {
assert.equal(testObj.get('user'), dummyData.user);
});
it("should offer simple HTTP request capabilities", function () {
return chakram.get("http://httpbin.org/get");
});
});
Note: I work with react but this is an example. We assume that the ContainerStore contains a method that has a method for get() which just gets the value of our JSON object. You can use testObj many time in different describe blocks since it is declared outside of your tests. But you should remember to always reinitialize your tesObj in a beforeEach(); otherwise, you risk populating your individual tests. There are cases were you do not have to initialize the beforeEach() and it is optional.
For Example in config.js
module.exports = {
"baseUrl": "http://example.com/",
"googleUrl": "http://www.google.com.tr/"
};
And use in javascript code:
let config = require('/config');
describle("test describle", () => {
it("test", () => {
chakram.get(config.baseUrl + "products"); //for example use
})
})
I am making a game engine in haxe/openfl. so far it's just supposed to display the image that belongs to the thing object. what I have built so far runs perfectly on when deployed as a flash application, but closes instantly when I deploy it as a windows application. It just creates a blank screen in html5. I have not tested other targets. I am using HIDE, and every time it crashes, HIDE brings up the message: "File c:\Users\Adu\Documents\HaxeProjects\Downloaded\Export\windows\cpp\bin\Downloaded.exe was changed. Reload?" and gives me the options yes or no. My answer doesn't seem to change the situation. when I manually go into the export directory and run the application, it gives the error: "Error Custom([file_write,stderr]). Here is my code:
Main:
package;
import openfl.display.Graphics;
import openfl.Assets;
import openfl.display.Bitmap;
import openfl.display.Sprite;
import openfl.events.Event;
import openfl.Lib;
import openfl.text.TextField;
import openfl.text.TextFormat;
import openfl.ui.Keyboard;
import openfl.events.*;
class Main
{
static var obj(default,default):ObjectManager; //contains the list that all gameobjects add themselves to
static var stage = Lib.current.stage;
public static function main() // this is the gameloop
{
// static entry point
startUp();
var running = true; // gives me a way to exit the gameloop
while (running)
{
logic();
render();
Lib.current.stage.addEventListener(KeyboardEvent.KEY_DOWN, function(event)
{
if (event.keyCode == Keyboard.ESCAPE)
{
running=false;
}
});
}
}
static function startUp() // runs once, when the game is started
{
obj= new ObjectManager();
stage.align = openfl.display.StageAlign.TOP_LEFT;
stage.scaleMode = openfl.display.StageScaleMode.NO_SCALE;
}
static function logic() // loops, this handles the logic
{
var thing = new GameObject("assets/pixel_thing.png", 1, obj);
var mech = new GameObject("assets/mechwarrior.png", 0, obj);
}
static function render() // runs right after logic and draws everything to the screen
{
for (i in obj.objects) //iterates through a list of gabeobjects and draws them, it is 2 dimensional so that I can draw objects in blocks
{
for (j in i)
{
Lib.current.addChild(j);
}
}
}
}
GameObject:
package ;
import openfl.display.BitmapData;
import openfl.Assets;
import openfl.display.Bitmap;
import openfl.display.Sprite;
import openfl.events.Event;
import openfl.Lib;
import openfl.text.TextField;
import openfl.text.TextFormat;
class GameObject extends Sprite
{
public function new(?image:String, zOrder:Int, objectManager:ObjectManager) // image is the image, zorder is which layer it's drawn on, lower layers are drawn on top objectmanager is just there to help me pass the list to the object
{
super();
var data = Assets.getBitmapData(image);//this is the image data
var bitmap:Bitmap = new Bitmap(data);//this is the actual image
Lib.current.stage.addChild(bitmap);//this sraws the image when the object is instantiated
objectManager.objects[zOrder].push(this);// this adds it to the list of objects
}
}
ObjectManager:
package ;
class ObjectManager
{
public var objects = new Array<Array<GameObject>>();
}
why is it that it works on flash but not windows? How do I fix this?
First off - this doesn't work fine on flash either. Are you running this in the flash debug player? If you don't, which I assume is the case, you won't see any exceptions.
There's a null reference error at this line:
objectManager.objects[zOrder].push(this);// this adds it to the list of objects
You are accessing the array at index zOrder, which doesn't exist. objects is being initialized to [], which does not include the "inner arrays" (it can't, really, how would it know how many of them there should be?).
Now, Windows builds don't give you very helpful debug information by default. A simple way around is to use neko (which mostly behaves the same as hxcpp builds, except it compiles faster and performs worse) for debugging, where you get a stacktrace by default on crashes.
Sure enough, it's the same issue as in flash, the only difference is that native builds crash while flash just "ignores it" and tries to carry on.
Invalid field access : objects
Called from GameObject::new line 92
Called from GameObject::$init line 83
Called from Main::logic line 61
Called from Main::main line 38
Called from Reflect::callMethod line 58
Called from ApplicationMain::main line 91
Called from openfl.Lib::create line 113
For better hxcpp build debug info, you might want to have a look at the crashdumper lib.
There are plenty of documents that show how to add a matcher to a Jasmine spec (here, for example).
Has anyone found a way to add matchers to the whole environment; I'm wanting to create a set of useful matchers to be called by any and all tests, without copypasta all over my specs.
Currently working to reverse engineer the source, but would prefer a tried and true method, if one exists.
Sure, you just call beforeEach() without any spec scoping at all, and add matchers there.
This would globally add a toBeOfType matcher.
beforeEach(function() {
var matchers = {
toBeOfType: function(typeString) {
return typeof this.actual == typeString;
}
};
this.addMatchers(matchers);
});
describe('Thing', function() {
// matchers available here.
});
I've made a file named spec_helper.js full of things like custom matchers that I just need to load onto the page before I run the rest of the spec suite.
Here's one for jasmine 2.0+:
beforeEach(function(){
jasmine.addMatchers({
toEqualData: function() {
return {
compare: function(actual, expected) {
return { pass: angular.equals(actual, expected) };
}
};
}
});
});
Note that this uses angular's angular.equals.
Edit: I didn't know it was an internal implementation that may be subjected to change. Use at your own risk.
jasmine.Expectation.addCoreMatchers(matchers)
Based on previous answers, I created the following setup for angular-cli. I also need an external module in my matcher (in this case moment.js)
Note In this example I added an equalityTester, but it should work with a customer matcher
Create a file src/spec_helper.ts with the following contents:
// Import module
import { Moment } from 'moment';
export function initSpecHelper() {
beforeEach(() => {
// Add your matcher
jasmine.addCustomEqualityTester((a: Moment, b: Moment) => {
if (typeof a.isSame === 'function') {
return a.isSame(b);
}
});
});
}
Then, in src/test.ts import the initSpecHelper() function add execute it. I placed it before Angular's TestBed init, wich seems to work just fine.
import { initSpecHelper } from './spec_helper';
//...
// Prevent Karma from running prematurely.
__karma__.loaded = function () {};
// Init our own spec helper
initSpecHelper();
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
//...