IS there a way to mock SVGTextElement to be able to test function, using Jest, that uses getBBox() to measure text?

Viewed 317

I wrote simple function to measure texts before I decide what font size should I use.

const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
const text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
svg.appendChild(text);

interface HeightAndWidth {
  width: number;
  height: number;
}

interface MeasureProps {
  label: string;
  fontFamily: string;
  size: number;
  fontWeight?: number;
  lineHeight?: number;
  letterSpacing?: string;
}

export const measureText = ({
  label,
  fontFamily,
  size,
  fontWeight = 400,
  lineHeight = 1,
  letterSpacing = '0',
}: MeasureProps): HeightAndWidth => {
  text.setAttribute('font-size', `${size}px`);
  text.setAttribute('line-height', `${lineHeight}px`);
  text.setAttribute('font-family', fontFamily);
  text.setAttribute('font-weight', `${fontWeight}`);
  text.setAttribute('letter-spacing', letterSpacing);
  text.textContent = label;
  document.body.appendChild(svg);

  const { width, height } = text.getBBox();

  return {
    width,
    height,
  };
};

It's pretty straightforward and it works nice but I'm having problem writing unit tests for this. The problem is that while working in browser it doesn't work during tests and throws an error

text.getBBox is not a function.

I tried mocking window.SVGTextElement but it keeps throwing error that it's prototype is missing 241 more properties. Even tried it's type but the error stays, and error with missing getBBox repeats.

Is there a way of testing code like this?

import { measureText } from '../measure';

interface MockSvgTextElement {
  prototype: {
    getBBox: () => { height: number; width: number };
  };
}

describe('animateNumber', () => {
  beforeEach(() => {
    window.SVGTextElement = {
      prototype: {
        getBBox: () => ({
          height: 20,
          width: 100,
        }),
      },
    } as MockSvgTextElement;
    const p = document.createElement('p');
    document.body.appendChild(p);
    console.log('window.SVGTextElement', window.SVGTextElement);
  });

  it('should measure text', () => {
    const textSize = measureText(
      {
        label: '12345',
        fontFamily: 'Roboto',
        size: 20,

      }
    );
    expect(textSize).toEqual({});
  });
});
1 Answers

getBBox can be used for the SVGGraphicsElement type only.

Create an <svg> element under body and then append a <text> under svg.

Then you can call getBBox().width for the text element

Related