0

I try to test a function recordItemColor but no matter what I try, the checks for instanceof don't recognize the Object.prototype type.

I tried a lot, but nothing works. Maybe it is something about jsdom? I would be even happy for other suggestions how to check if the pass target is an HTMLElment or an SVGElement.

Here is the function:

import { type StepType } from './handleClickableElement';

const pushStep = (steps: StepType[], step: StepType): void => {
  steps.push(step);
};

export const recordItemColor = (steps: StepType[], target: Element): void => {
  if (target instanceof HTMLElement) {
    const computedStyle = window.getComputedStyle(target);
    pushStep(steps, {
      target: target.id,
      color: computedStyle.backgroundColor,
    });
  }
  if (target instanceof SVGElement) {
    const computedStyle = window.getComputedStyle(target);
    pushStep(steps, {
      target: target.id,
      color: computedStyle.fill,
    });
  }
};

Here is the test:

import { expect } from 'chai';
import { recordItemColor } from './recordItemColor';
import { StepType } from './handleClickableElement';
import { JSDOM } from 'jsdom';

const { window } = new JSDOM();
const { document } = window;
(global as any).document = document;
(global as any).window = window;

describe('recordItemColor', () => {
  beforeEach(() => {
    const div = document.createElement('div');
    const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
    div.id = 'test-div';
    path.id = 'test-svg';
    div.style.backgroundColor = 'red';
    path.setAttribute('fill', 'blue');
    document.body.appendChild(div);
    document.body.appendChild(path);
  });
  afterEach(() => {
    const div = document.getElementById('test-div');
    const path = document.getElementById('test-svg');
    if (div) div.remove();
    if (path) path.remove();
  });

  it('records item color for HTMLElement', () => {
    const steps: StepType[] = [];
    const target = document.getElementById('test-div');
    recordItemColor(steps, target!);
    expect(steps).to.deep.equal([{ target: 'test-div', color: 'red' }]);
  });

  it('records item color for SVGElement', () => {
    const steps: StepType[] = [];
    const target = document.getElementById('test-svg');
    expect(target).to.exist;
    recordItemColor(steps, target!);
    expect(steps).to.deep.equal([{ target: 'test-svg', color: 'blue' }]);
  });
});

My jsdom setup:

import { JSDOM } from 'jsdom';

const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>');
const { window } = dom;

(global as any).HTMLElement = window.HTMLElement;
(global as any).SVGElement = window.SVGElement;

(global as any).document = dom.window.document;
(global as any).window = dom.window;
(global as any).navigator = dom.window.navigator;

When I log the target at the start of the function, it is not null. The node name appears to be DIV or SVG as expected and when I use console.log(Object.prototype.toString.call(target)) it logs the correct type which is a HTMLDivElement or SVGElement.

I set the type in the jsdom setup.ts, which I provide above. Otherwise, the code throws an exception at the check with the error: Includes HTMLElement.prototype: false. I found this solution in an issue on the repository of jsdom.

2
  • This is a guess, but instanceof checks to see if an object is an instance of a particular constructor function. An HTMLElement constructor in one realm is not the same as the same-named constructor in another realm.
    – Pointy
    Commented Feb 6 at 17:03
  • Is there another property of target you could use to determine its type? target.tagName?
    – James
    Commented Feb 6 at 17:55

0