I've got an AngularJS (v.1.7) app served by Rails. I just moved from Sprockets to Webpack.
While trying to migrate my Jasmine tests to Jest. I ran into an issue with html-loader
which I'm using to import my directive templates into the directive.
For one simple directive where I import the template, Jest fails to load the test because the html-loader
fails with the error
TypeError: Cannot read property 'query' of undefined
1 | const htmlLoader = require("html-loader");
2 | module.exports = {
> 3 | process: (src) => htmlLoader(src)
| ^
4 | };
5 |
at getOptions (node_modules/html-loader/node_modules/loader-utils/lib/getOptions.js:6:31)
I was following recommendations in this SO post and from this npm package html-loader-jest. In my package.json
, I've added the following jest
config
"jest": {
"moduleFileExtensions": [
"js",
"html"
],
"transform": {
"^.+\\.js$": "babel-jest",
"^.+\\.html$": "<rootDir>/jest/support/html_loader.js"
},
...
}
And the support file
// jest/support/html_loader.js
const htmlLoader = require("html-loader");
module.exports = {
process: (src) => htmlLoader(src)
};
The stacktrace points me to html-loader
(from node_modules)
// node_modules/html-loader/node_modules/loader-utils/lib/getOptions.js
function getOptions(loaderContext) {
const query = loaderContext.query;
...
If I trace into here during the Jest run, I find this loaderContext
undefined (as the error reports).
My question is... is this the correct way to use this htmlLoader
? If so, what should I expect loaderContext
to be? Is there a way to get jest to provide that value? Or is this not the way the htmlLoader
is supposed to be called outside of the actual Webpack pipeline.
This problem only happens when I'm running via jest
. webpack
properly compiles the all assets for the app.
html-loader: 1.0.0
webpack: 4.42.1
jest: 25.2.4
// mailer.js
import angular from "angular";
import ngInject from "@js/ng-inject";
import template from "./index.html";
const mailer = ngInject(function () {
return {
restrict: "E",
scope: {
text: "@",
},
template: template,
};
});
angular.module("app-directives").directive("mailer", mailer);
<!-- index.html -->
<a>{{text}}</a>
// mailer.test.js
import expect from "expect";
import "./mailer";
describe("app-directives.mailer", function () {
it("works", () => {
expect(true).toBeTruthy();
})
});
Well, I figured it out. It seems that when jest
runs, it's not passing the correct context (as I suspected above) causing the getOptions
call to fail.
The solution was to write a very simple loader just for jest
which does not bother with getOptions
. I was able to replace my jest/support/html_loader.js
code with the following (basically cribbed from webpack-contrib/raw-loader
.
// jest/support/html-loader.js
module.exports = {
process: (content, _path) => {
// escape newlines
const json = JSON.stringify(content)
.replace(/\u2028/g, '\\u2028')
.replace(/\u2029/g, '\\u2029');
return `module.exports = ${json};`
}
};
This basically returns the template as js module that exports the template. The replace
appears to be dealing with newlines.
I hope this saves someone else some digging.