I have a Vue component 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 component.
Terminal.test.js
import Terminal from 'components/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:
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 component - in xterm's own testing files, they appear to be creating a fake DOM using jsdom
:
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 component.
Creates a Wrapper that contains the mounted and rendered Vue component.
https://vue-test-utils.vuejs.org/api/#mount
How can I successfully mock a DOM in such a way that I can test a Vue component that implements Xterm.js, using Jest?