admin管理员组文章数量:1355713
I have a Vue ponent that renders an Xterm.js terminal.
Terminal.vue
<template>
<div id="terminal"></div>
</template>
<script>
import Vue from 'vue';
import { Terminal } from 'xterm/lib/public/Terminal';
import { ITerminalOptions, ITheme } from 'xterm';
export default Vue.extend({
data() {
return {};
},
mounted() {
Terminal.applyAddon(fit);
this.term = new Terminal(opts);
this.term.open(document.getElementById('terminal'));
},
</script>
I would like to test this ponent.
Terminal.test.js
import Terminal from 'ponents/Terminal'
import { mount } from '@vue/test-utils';
describe('test', ()=>{
const wrapper = mount(App);
});
When I run jest
on this test file, I get this error:
TypeError: Cannot set property 'globalCompositeOperation' of null
45 | this.term = new Terminal(opts);
> 46 | this.term.open(document.getElementById('terminal'));
Digging into the stack trace, I can see it has something to do with Xterm's ColorManager.
at new ColorManager (node_modules/xterm/src/renderer/ColorManager.ts:94:39)
at new Renderer (node_modules/xterm/src/renderer/Renderer.ts:41:25)
If I look at their code, I can see a relatively confusing thing:
xterm.js/ColorManager.ts
constructor(document: Document, public allowTransparency: boolean) {
const canvas = document.createElement('canvas');
canvas.width = 1;
canvas.height = 1;
const ctx = canvas.getContext('2d');
// I would expect to see the "could not get rendering context"
// error, as "ctx" shows up as "null" later, guessing from the
// error that Jest caught
if (!ctx) {
throw new Error('Could not get rendering context');
}
this._ctx = ctx;
// Somehow this._ctx is null here, but passed a boolean check earlier?
this._ctx.globalCompositeOperation = 'copy';
this._litmusColor = this._ctx.createLinearGradient(0, 0, 1, 1);
this.colors = {
foreground: DEFAULT_FOREGROUND,
background: DEFAULT_BACKGROUND,
cursor: DEFAULT_CURSOR,
cursorAccent: DEFAULT_CURSOR_ACCENT,
selection: DEFAULT_SELECTION,
ansi: DEFAULT_ANSI_COLORS.slice()
};
}
I'm not quite clear on how canvas.getContext
apparently returned something that passed the boolean check (at if(!ctx)
) but then later caused a cannot set globalCompositeOperation of null
error on that same variable.
I'm very confused about how I can successfully go about mock-rendering and thus testing this ponent - in xterm's own testing files, they appear to be creating a fake DOM using jsdom
:
xterm.js/ColorManager.test.ts
beforeEach(() => {
dom = new jsdom.JSDOM('');
window = dom.window;
document = window.document;
(<any>window).HTMLCanvasElement.prototype.getContext = () => ({
createLinearGradient(): any {
return null;
},
fillRect(): void { },
getImageData(): any {
return {data: [0, 0, 0, 0xFF]};
}
});
cm = new ColorManager(document, false);
});
But I believe that under the hood, vue-test-utils
is also creating a fake DOM using jsdom
. Furthermore, the documentation indicates that the mount
function both attaches and renders the vue ponent.
Creates a Wrapper that contains the mounted and rendered Vue ponent.
How can I successfully mock a DOM in such a way that I can test a Vue ponent that implements Xterm.js, using Jest?
I have a Vue ponent that renders an Xterm.js terminal.
Terminal.vue
<template>
<div id="terminal"></div>
</template>
<script>
import Vue from 'vue';
import { Terminal } from 'xterm/lib/public/Terminal';
import { ITerminalOptions, ITheme } from 'xterm';
export default Vue.extend({
data() {
return {};
},
mounted() {
Terminal.applyAddon(fit);
this.term = new Terminal(opts);
this.term.open(document.getElementById('terminal'));
},
</script>
I would like to test this ponent.
Terminal.test.js
import Terminal from 'ponents/Terminal'
import { mount } from '@vue/test-utils';
describe('test', ()=>{
const wrapper = mount(App);
});
When I run jest
on this test file, I get this error:
TypeError: Cannot set property 'globalCompositeOperation' of null
45 | this.term = new Terminal(opts);
> 46 | this.term.open(document.getElementById('terminal'));
Digging into the stack trace, I can see it has something to do with Xterm's ColorManager.
at new ColorManager (node_modules/xterm/src/renderer/ColorManager.ts:94:39)
at new Renderer (node_modules/xterm/src/renderer/Renderer.ts:41:25)
If I look at their code, I can see a relatively confusing thing:
xterm.js/ColorManager.ts
constructor(document: Document, public allowTransparency: boolean) {
const canvas = document.createElement('canvas');
canvas.width = 1;
canvas.height = 1;
const ctx = canvas.getContext('2d');
// I would expect to see the "could not get rendering context"
// error, as "ctx" shows up as "null" later, guessing from the
// error that Jest caught
if (!ctx) {
throw new Error('Could not get rendering context');
}
this._ctx = ctx;
// Somehow this._ctx is null here, but passed a boolean check earlier?
this._ctx.globalCompositeOperation = 'copy';
this._litmusColor = this._ctx.createLinearGradient(0, 0, 1, 1);
this.colors = {
foreground: DEFAULT_FOREGROUND,
background: DEFAULT_BACKGROUND,
cursor: DEFAULT_CURSOR,
cursorAccent: DEFAULT_CURSOR_ACCENT,
selection: DEFAULT_SELECTION,
ansi: DEFAULT_ANSI_COLORS.slice()
};
}
I'm not quite clear on how canvas.getContext
apparently returned something that passed the boolean check (at if(!ctx)
) but then later caused a cannot set globalCompositeOperation of null
error on that same variable.
I'm very confused about how I can successfully go about mock-rendering and thus testing this ponent - in xterm's own testing files, they appear to be creating a fake DOM using jsdom
:
xterm.js/ColorManager.test.ts
beforeEach(() => {
dom = new jsdom.JSDOM('');
window = dom.window;
document = window.document;
(<any>window).HTMLCanvasElement.prototype.getContext = () => ({
createLinearGradient(): any {
return null;
},
fillRect(): void { },
getImageData(): any {
return {data: [0, 0, 0, 0xFF]};
}
});
cm = new ColorManager(document, false);
});
But I believe that under the hood, vue-test-utils
is also creating a fake DOM using jsdom
. Furthermore, the documentation indicates that the mount
function both attaches and renders the vue ponent.
Creates a Wrapper that contains the mounted and rendered Vue ponent.
https://vue-test-utils.vuejs/api/#mount
How can I successfully mock a DOM in such a way that I can test a Vue ponent that implements Xterm.js, using Jest?
Share Improve this question edited Jun 4, 2019 at 7:53 skyboyer 23.8k7 gold badges62 silver badges71 bronze badges asked Jun 4, 2019 at 1:19 Caleb JayCaleb Jay 2,1993 gold badges41 silver badges76 bronze badges2 Answers
Reset to default 6There are multiple reasons for this.
First of all, Jest js uses jsdom under the hood, as I suspected.
Jsdom doesn't support the canvas DOM api out of the box. First of all, you need jest-canvas-mock.
npm install --save-dev jest-canvas-mock
Then, you need to add it to the setupFiles
portion of your jest config. Mine was in package.json
, so I added it like so:
package.json
{
"jest": {
"setupFiles": ["jest-canvas-mock"]
}
}
Then, I was getting errors about the insertAdjacentElement DOM element method. Specifically, the error was:
[Vue warn]: Error in mounted hook: "TypeError: _this._terminal.element.insertAdjacentElement is not a function"
This is because the version of jsdom used by jest is, as of today, 11.12.0
:
npm ls jsdom
└─┬ [email protected]
└─┬ [email protected]
└─┬ [email protected]
└─┬ [email protected]
└── [email protected]
Through the help of stackoverflow, I discovered that at version 11.12.0
, jsdom had not implemented insertAdjacentElement
. However, a more recent version of jsdom implemented insertAdjacentElement
back in July of 2018.
Efforts to convince the jest team to use a more up to date version of jsdom have failed. They are unwilling to let go of node6 patibility until the absolute last second (they claimed back in April), or alternatively don't want to implement jsdom at all anymore, and are remending people fork their own versions of the repo if they want the feature.
Luckily, you can manually set which version of jsdom jest uses.
First, install the jest-environment-jsdom-fourteen package.
npm install --save jest-environment-jsdom-fourteen
Then, you need to modify the testEnvironment
property of your jest config. So, now my jest config looks like:
package.json
"jest": {
"testEnvironment": "jest-environment-jsdom-fourteen",
"setupFiles": ["jest-canvas-mock"]
}
Now, I can run tests without errors.
Great answer above which was going to be my solution but since I'm using react-scripts I didn't really want to eject (testEnvironment is not supported config setting). So I had a look around in source code of react-scripts how I can potentially sneak in and override testEnvironment.
https://github./facebook/create-react-app/blob/master/packages/react-scripts/scripts/test.js
Line 124 looks quite interesting
resolvedEnv = resolveJestDefaultEnvironment(`jest-environment-${env}`);
So a brilliant idea came to my mind, no pun intended, to stick --env=jsdom-fourteen as mand line arg. My CI mand looks like this now
cross-env CI=true react-scripts test --coverage --env=jsdom-fourteen --testResultsProcessor=jest-teamcity-reporter
and it miraculously works :).
I also have setupTests.js file in src folder where I import jest-canvas-mock and jest-environment-jsdom-fourteen but before the --env hacky option the tests were spitting out the insertAdjacentElement mentioned above.
Obvs this is very hacky and it will break at some point but it's fine for now, hopefully Jest will start supporting JSDOM 14 soon.
本文标签:
版权声明:本文标题:javascript - How do I properly mock a DOM so that I can test a Vue app with Jest that uses Xterm.js? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1743987771a2571582.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论