jsdom: `Image.onload` not fired after upgrading for 9.x to 10.x

This code works fine in 9.x:

import jsdom from 'jsdom';
const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>');
global.document = dom.window.document;
global.window = dom.window;

const img = new window.Image();
img.onload = () => console.log('This is fired without issue');
img.src = 'data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7';

However, the following code does NOT work in 10.x

import {JSDOM} from 'jsdom';
const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>');
global.document = dom.window.document;
global.window = dom.window;

const img = new window.Image();
img.onload = () => console.log('This is never fired');
img.src = 'data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7';

It appears that new Image().onload is never executed in v10.x

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 1
  • Comments: 19 (11 by maintainers)

Commits related to this issue

Most upvoted comments

Just in case someone lands on here because of failing tests in Jest 22, it can be fixed via:

"testEnvironmentOptions": { "resources": "usable" },

Note: features: { FetchExternalResources: ["img"] } doesn’t work with Jest.

I could not get the jest settings + canvas working but for anyone who comes here and has this issue you can do this:

const LOAD_FAILURE_SRC = 'LOAD_FAILURE_SRC';
const LOAD_SUCCESS_SRC = 'LOAD_SUCCESS_SRC';

beforeAll(() => {
    Object.defineProperty(global.Image.prototype, 'src', {
        set(src) {
            if (src === LOAD_FAILURE_SRC) {
                setTimeout(() => this.onerror(new Error('mocked error')));
            } else if (src === LOAD_SUCCESS_SRC) {
                setTimeout(() => this.onload());
            }
        },
    });
});

and then just set you src to either LOAD_SUCCESS_SRC or LOAD_SUCCESS_SRC to trigger onload or onerror

Found this solve here: https://stackoverflow.com/questions/44462665/how-do-you-use-jest-to-test-img-onerror/49204336#49204336

Sure, that’d work.

Just in case someone lands on here because of failing tests in Jest 22, it can be fixed via: "testEnvironmentOptions": { "resources": "usable" },

@joscha can you provide an example where you are getting this to work? I cannot seem to get it to work, Image.onload never fires 😦 .

To make this work in Jest you have to have the canvas package installed as well

what was the reason for changing this? The new behavior is inconsistent with how browsers behave: They fire the load event in those cases.

Browsers do not fire a load event when they are configured to not load images. jsdom is by default configured to not load any external resources, so it follows browsers in this regard. This brings images in line with other not-loaded resources (like video, audio, link rel=preload, etc.)

You can also switch into a mode where it loads “usable” external resources, which includes images only if you have the canvas package installed.

As you can see from a few lines above, that test is executed only if the canvas package is installed, in which case load events do fire.

The following isolated test case doesn’t work for me. The returned promise never resolves or rejects; hence, the test times out after 5000 ms.

describe(('Image.prototype.src') => {
  test('triggers load for valid data URL', async done => {
    // A 1x1 pixel, base64-encoded PNG with a white background
    const pngPixel = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII='

    async function loadImage(src) {
      return new Promise((resolve, reject) => {
        const image = new Image()

        image.onload = event => resolve(true)
        image.onerror = error => reject(error)
        image.src = src
      })
    }

    return loadImage(pngPixel).then(result => {
      expect(result).toEqual(true)
      done()
    })
  })
})

I’ve tried it with setting "testEnvironmentOptions": { "resources": "usable" } in jest.config.json without the outcome changing.

I added the hacky workaround posted in https://github.com/jsdom/jsdom/issues/1816#issuecomment-432496573 which makes the test pass. But this only works if one uses old-style image.onload instead of image.addEventListener('load', ...).

Am I doing something wrong?

Ah yep, forgot that changelog entry. Can fix the changelog, but this is an intentional breaking change.

element.dispatchEvent works perfect for me. Simple and no worrying about mocking:

test("should call onError",  async () => {
	const {findByTestId} = render(<ComponentWithImage/>);

	// find your image in the document
	const img = await findByTestId("your-img-test-id");

	// dispatch HTML error event
	img.dispatchEvent("error");

	// validate that onError was called
	// do your validation
});

I added the hacky workaround posted in #1816 (comment) which makes the test pass. But this only works if one uses old-style image.onload instead of image.addEventListener(‘load’, …).

@kleinfreund if you are using image.addEventListener("load", ...) I think you would have to swap out this.onload() for this.dispatchEvent(new Event("load")) instead.