Mocha Webcomponents testing - ReferenceError: customElements is not defined - javascript

I'm trying to do some very basic webcoponnets testing using typescript and mocha. I'm using jsdom to mock out the basic documents global, so I have --require jsdom-global/register in my moch opts.
Here is my test:
import { assert } from "chai";
class WordCount extends HTMLParagraphElement {
constructor() {
super();
}
}
describe("simple test", () => {
it("works", () => {
customElements.define('word-count', WordCount, { extends: 'p' });
assert.isOk(true);
});
});
But I get the following error:
ReferenceError: customElements is not defined
The latest version of JSDom (which I'm using) supports customElements. I think the issue boils down to window.customElements vs customElements. The former syntax works, but the code I'm trying to test uses the latter syntax. What's the difference?

In the browser context, there's not difference between window.customElements and customElements because window is the default namespace for the variables defined globally.
var my_var = 'foo"
console.log( window.my_var ) //foo
console.log( window.customElement === customElement )
The test JSDoc library is executed in the Node.js context, which is not a browser and therefore won't expose window as its global / default namespace.
However JSDoc exposes a simulated browser context through a window property. So you can use window.customElements() and there's no difference with the code you trying to test.

Related

Automatic mock of an ES6 class fails (Jest, Vanilla JavaScript)

I have the class FooStorage that has an Array of Foo objects as a member variable. When testing FooStorage I want to mock the class Foo.
As described at ES6 Class Mocks, an automatic mock is all I need in my case. But when I try to mock the class, it doesn't seem to succeed. Instead when I try to reset the mock with mockClear(), I recieve an error message.
Below is the code and the output from jest:
foo.js
class Foo {};
export default Foo;
foostorage.js
import Foo from "./foo.js";
class FooStorage {
constructor() {
this.storage = []; // Array of Foo objects
}
}
export default FooStorage;
foostorage.test.js
import Foo from "../src/foo.js";
import FooStorage from "../src/foostorage.js";
import { jest } from "#jest/globals";
jest.mock("../src/foo.js");
beforeEach(() => {
Foo.mockClear();
});
test("if the Foo constructor hasn`t been called", () => {
const storage = new FooStorage();
expect(Foo).not.toHaveBeenCalled();
});
output
if the Foo constructor hasn`t been called
TypeError: Foo.mockClear is not a function
7 |
8 | beforeEach(() => {
> 9 | Foo.mockClear();
| ^
10 | });
11 |
12 | test("if the Foo constructor hasn`t been called", () => {
at Object.<anonymous> (test/foostorage.test.js:9:6)
I have already tried to put jest.mock("../src/foo.js"); before import Foo from "../src/foo.js"; but the problem wasn't solved.
Edit:
I am using Jest v. 27.0.6 with jest-environment-node v. 27.0.6 and #types/jest v. 27.0.1.
I also use the nodejs arguments --experimental-modules and --experimental-vm-modules so that I can use ES6 imports. I don`t use Babel or anything else. Just plain JavaScript.
Automatic mocking is supposed to work like the documentation explains. If it doesn't and results in said error, this means that the module wasn't mocked, so it would fail with manual mock as well. This can happen because a mock wasn't performed correctly (different case or extension in module path), or jest.mock hoisting didn't work (most commonly because of misconfigured Babel transform).
The way this code differs from what's shown in the documentation is that jest global is commonly used, while here it is imported value, so it's not the same as using jest global. This is obvious and known reason for hoisting to work differently.
It can possibly be fixed by ordering dependencies in a way that doesn't interfere with expected execution order:
import { jest } from "#jest/globals";
jest.mock("../src/foo.js");
import Foo from "../src/foo.js";
...
The hoisting of jest.mock relies on undocumented, spec-incompliant hack that also depends on Jest version and a specific setup. It's not guaranteed to work because ES imports are expected to hoisted above other statements by specs, and there are no exclusions.
A dated but stable workaround is to switch to require that are regular JavaScript function calls and are evaluated as is:
let { jest } = require ("#jest/globals");
jest.mock("../src/foo.js");
let { default: Foo } = require("../src/foo.js");
...
And an efficient solution is to get rid of jest import and set up a generic Jest environment with respective globals, so jest.mock could be hoisted as expected regardless of Jest version and other circumstances.

Create React App - Unable to set window object before test runs

Trying to set up some tests for a component in a CRA-generated application.
Having an issue where the test is failing to run due to an imported file relying on a window object which isn't initialised.
Quick overview:
In public/index.html there is an object defined on the window called window.config.
<script src="%PUBLIC_URL%/config.js"></script>
<script>
// THESE PROPERTIES ARE DEFINED IN config.js
window.config = {
ENV,
IDENTITY_URL,
OIDC_CLIENT_ID,
};
</script>
There is a file called identity.constants.js which exports an object that uses these window variables.
export const IdentityConfig = {
authority: window.config.IDENTITY_URL,
client_id: window.config.OIDC_CLIENT_ID,
... etc
};
The component I am trying to test imports another component that relies on identity.constants.js shown above.
The problem is that the test fails to start and throws an error stating window.config is undefined from the identity.constants.js file.
After reading the CRA docs, I have tried adding the following code into src/setupTests.js to set the config object before the test starts but it seems to fail before this code runs.
global.config = {
ENV: "development",
IDENTITY_URL: "identityurl.com",
OIDC_CLIENT_ID: "i-am-a-clientId",
... etc
};
I'm looking for a way to set this window variable before the tests run (or a better way to structure the way I'm using window variables) so I can successfully run my tests.
globa.config defines a global variable named config, you'll need to define global.window firstly, then add config as a property of global.window.
It seems your test environment is node, if you have jsdom as the devDependencies of your project, try to add below piece of code to your setupTests.js:
import { JSDOM } from 'jsdom';
const jsdom = new JSDOM('<!doctype html><html><body></body></html>');
global.window = jsdom.window;
global.document = jsdom.window.document;
global.window.config = {
ENV: "development",
IDENTITY_URL: "identityurl.com",
OIDC_CLIENT_ID: "i-am-a-clientId",
... etc
};
Without jsdom, a simple fix is like below:
global.window = {
config: {
ENV: "development",
IDENTITY_URL: "identityurl.com",
OIDC_CLIENT_ID: "i-am-a-clientId",
... etc
}
}

Webpack how to shim module wrapped in window closure?

I have some 3rd party code that is exported with browser-only intentions. It is wrapped in a self-invoking function that expects window to be passed.
(function (window) { "use strict";
window['module'] = {}
})(window);
Is there a better name to describe this style module?
I would like to use webpack to require or import this code.
currently using webpack#3.5.1 I need this to work in Node.js and ES6 environments.
I was able to achieve this by using the imports-loader and exports-loader
rules: [
{
test: /MyModule\.js/,
use: [
"imports-loader?window=>{}",
"exports-loader?window.MyModule"
]
},
Unless the author updates its code to UMD (or similar) there would be no way for you to require/import it.
I can't think of a way you could actually make it work without the author's modification.
Although, with the help of the window package you could use the following trick :
//in a separate file
const Window = require("window");
const window = new Window();
global.window = window; //Try with and without this
require("my_module");
module.exports = window["module_global_variable_name"];
But that would only work if the author didn't use any other global variables (eg. fetch instead of window.fetch would ruin the trick).
An alternative to trying to require/import from window is to desctructure it after your import declarations. This gives you a importish feel without jumping through hoops.
// Normal Imports
import Foo from "Foo";
// Globals from window
const {
Bar
} = window;

How to export a TypeScript class in a way that won't disrupt accessing the object using plain old vanilla JavaScript

Forgive my ignorance on this, I'm stuck and hoping to get some guided direction.
Take this Greeter class example found on TypeScript's playground page:
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
I've been tasked to build and manage a single component that can be used both in a plain HTML + JavaScript environment, but also in an Angular application. I'm managing / writing this code in TypeScript, and using TypeScript to compile the output as JavaScript.
Referring back to the above example, if I wanted to make that class available (as an import) in Angular, I would need to export the class?
export class Greeter {
...
}
When I do that, the page throws errors:
Uncaught ReferenceError: define is not defined
The compiled JavaScript looks like this:
define(["require", "exports"], function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var Greeter = /** #class */ (function () {
function Greeter(message) {
this.greeting = message;
}
Greeter.prototype.greet = function () {
return "Hello, " + this.greeting;
};
return Greeter;
}());
exports.Greeter = Greeter;
var greeter = new Greeter("world");
});
Because the Greeter class is now bound to that define block, I obviously can't instantiate a new class as such:
let greeter = new Greeter("world"); // < -- Won't work.
Throws error:
Uncaught ReferenceError: Greeter is not defined
How would I go about exporting that class, in a way that won't disrupt accessing the object in say a vanilla JavaScript fashion?
Is what I'm trying to do even possible?
Is this a reasonable goal, or am I completely on the wrong track here?
Good question, what you're asking is definitely reasonable.
First of all it's worth mentioning that what you are referring to as "vanilla JavaScript" is ES5 with AMD module library compatibility. ES6 introduced native language-level modules to JavaScript, which is what the export and import syntax is.
TypeScript has a compiler option --module which lets you choose what module target to compile for and produce output for. However in the case of external module systems, like AMD and CommonJS, it does not actually output the module system required to make it work, it's just emitting JavaScript that's compatible with those systems. It's up to you to include the module system, such as RequireJS, or run in an environment that has built in support, like NodeJS' CommonJS module system. If you set the --module target to es6 you'll see it leaves the export statements as is, and this would work in a browser that supports ES6 modules (assuming the import path resolves to a file the browser can access).
Also worth mentioning is that with JavaScript bundlers like Webpack or Browserify, you can take practically any module output from TypeScript and bundle it for the browser without the need to introduce a module runtime yourself, because the bundler takes care of it.
Hope that helps.

Writing tests for javascript module using webpack's require.ensure function

I am running mocha tests on my server, testing source scripts an isolated unit test manner.
One of the scripts I am testing makes a call to Webpack's require.ensure function, which is useful for creating code-splitting points in the application when it gets bundled by Webpack.
The test I have written for this script does not run within a Webpack context, therefore the require.ensure function does not exist, and the test fails.
I have tried to create some sort of polyfill/stub/mock/spy for this function but have had no luck whatsoever.
There is a package, webpack-require, which does allow for the creation of a webpack context. This can work but it is unacceptably slow. I would prefer to have some sort of lightweight polyfill targeting the require.ensure function directly.
Any recommendations? :)
Here is a very basic starting point mocha test.
The mocha test loads a contrived module containing a method which returns true if require.ensure is defined.
foo.js
export default {
requireEnsureExists: () => {
return typeof require.ensure === 'function';
}
};
foo.test.js
import { expect } from 'chai';
describe('When requiring "foo"', () => {
let foo;
before(() => {
foo = require('./foo.js');
});
it('The requireEnsureExists() should be true', () => {
expect(foo.requireEnsureExists()).to.be.true;
});
});
Ok, I finally have an answer for this after much research and deliberation.
I initially thought that I could solve this using some sort of IoC / DI strategy, but then I found the source code for Node JS's Module library which is responsible for loading modules. Looking at the source code you will notice that the 'require' function for modules (i.e. foo.js in my example) get created by the _compile function of NodeJs's module loader. It's internally scoped and I couldn't see an immediate mechanism by which to modify it.
I am not quite sure how or where Webpack is extending the created "require" instance, but I suspect it is with some black magic. I realised that I would need some help to do something of a similar nature, and didn't want to write a massive block of complicated code to do so.
Then I stumbled on rewire...
Dependency injection for node.js applications.
rewire adds a special setter and getter to modules so you can modify their behaviour for better unit testing. You may
inject mocks for other modules
leak private variables
override variables within the module.
rewire does not load the file and eval the contents to emulate node's require mechanism. In fact it uses node's own require to load the module. Thus your module behaves exactly the same in your test environment as under regular circumstances (except your modifications).
Perfect. Access to private variables is all that I need.
After installing rewire, getting my test to work was easy:
foo.js
export default {
requireEnsureExists: () => {
return typeof require.ensure === 'function';
}
};
foo.test.js
import { expect } from 'chai';
import rewire from 'rewire';
describe('When requiring "foo"', () => {
let foo;
before(() => {
foo = rewire('./foo.js');
// Get the existing 'require' instance for our module.
let fooRequire = moduletest.__get__('require');
// Add an 'ensure' property to it.
fooRequire.ensure = (path) => {
// Do mocky/stubby stuff here.
};
// We don't need to set the 'require' again in our module, as the above
// is by reference.
});
it('The requireEnsureExists() should be true', () => {
expect(foo.requireEnsureExists()).to.be.true;
});
});
Aaaaah.... so happy. Fast running test land again.
Oh, in my case it's not needed, but if you are bundling your code via webpack for browser based testing, then you may need the rewire-webpack plugin. I also read somewhere that this may have problems with ES6 syntax.
Another note: for straight up mocking of require(...) statements I would recommend using mockery instead of rewire. It's less powerful than rewire (no private variable access), but this is a bit safer in my opinion. Also, it has a very helpful warning system to help you not do any unintentional mocking.
Update
I've also seen the following strategy being employed. In every module that uses require.ensure check that it exists and polyfill it if not:
// Polyfill webpack require.ensure.
if (typeof require.ensure !== `function`) require.ensure = (d, c) => c(require);

Categories

Resources