inrupt/solid-client-authn-js

jest jsdom: ReferenceError: TextEncoder is not defined

angelo-v opened this issue ยท 22 comments

Search terms you've used

jsdom, TextEncoder

Impacted package

Which packages do you think might be impacted by the bug ?

  • solid-client-authn-browser
  • solid-client-authn-node
  • solid-client-authn-core
  • oidc-client-ext
  • Other (please specify): ...

Bug description

importing from @inrupt/solid-client-authn-browser fails in a jest test using jsdom environment:

Test suite failed to run

    ReferenceError: TextEncoder is not defined

      3 |  */
      4 |
    > 5 | import { login } from '@inrupt/solid-client-authn-browser';
        | ^
      6 |
      7 | describe('test', () => {
      8 |   it('should work', () => {

To Reproduce

Run this test:

/**
 * @jest-environment jsdom
 */

import { login } from '@inrupt/solid-client-authn-browser';

describe('test', () => {
  it('should work', () => {
    expect(login).toBeDefined();
  });
});

Expected result

Test runs successfully

Actual result

ReferenceError: TextEncoder is not defined

Environment

 @inrupt/solid-client-authn-browser: ^1.11.2 => 1.11.2

Additional information

  • The problem did not occur with @inrupt/solid-client-authn-browser 1.6.1
  • It works fine with @jest-environment node

A workarround (or solution?) is to polyfill TextEncoder and TextDecoder in a setupFilesAfterEnv script:

import { TextEncoder, TextDecoder } from 'util';
global.TextEncoder = TextEncoder;
global.TextDecoder = TextDecoder;

Hi @angelo-v ! I think this issue unfortunately goes beyond the scope of @inrupt/solid-client-authn-browser, and we have to work around it ourselves: jose expects TextEncoder to be present in the environment, but jsdom does not put it there (see jsdom/jsdom#2524). Our solution (suggested somewhere in the linked issue) is to use a custom Jest environment: https://github.com/inrupt/solid-client-authn-js/blob/main/tests/environment/customEnvironment.js, referenced e.g. in https://github.com/inrupt/solid-client-authn-js/blob/main/packages/browser/jest.config.js.

Thanks, I understand that. Using setupFilesAfterEnv works as well and is more lightweight then creating a custom jest env imo. Perhaps you can at least add a hint to the documentation about that, because usually people just using the inrupt lib do not know about jose and those internals, they just wonder why the inrupt lib causes an error in the tests

Still an issue today. Jest 29, added the util script

just import it with require
const { JSDOM } = require("jsdom");
I got the same issue and this solution worked for me

A workarround (or solution?) is to polyfill TextEncoder and TextDecoder in a setupFilesAfterEnv script:

import { TextEncoder, TextDecoder } from 'util';
global.TextEncoder = TextEncoder;
global.TextDecoder = TextDecoder;

How exactly do I add this to a setupFilesAfterEnv script? I am using typescript and I have a jest-config.d.ts and it has this inside,

declare const root: any;
export { root as rootDir };
export declare const setupFilesAfterEnv: string[];
export declare const testMatch: string[];
export declare const testPathIgnorePatterns: string[];
export declare const coveragePathIgnorePatterns: string[];
export declare const coverageDirectory: string;

We actually have a bit of an update here: we've just released a package that helps by polyfilling the required Web APIs: https://www.npmjs.com/package/@inrupt/jest-jsdom-polyfills

It's still being rolled out across the SDK repos, but in our testing so far it should provide everything needed. We're aware that there may be an issue with UInt8Arrays, and that's due to an underlying jsdom issue: jsdom/whatwg-encoding#13

@hjohnsick you can see an example here: https://github.com/inrupt/solid-client-access-grants-js/pull/366/files

You should likely have a jest.config.ts file in your repo, unless you're somehow using jest without any configuration at all (which I've only rarely seen)

Thanks, I understand that. Using setupFilesAfterEnv works as well and is more lightweight then creating a custom jest env imo. Perhaps you can at least add a hint to the documentation about that, because usually people just using the inrupt lib do not know about jose and those internals, they just wonder why the inrupt lib causes an error in the tests

@angelo-v Yup, we agree, we've since moved from custom environments to setupFilesAfterEnv

Seems like the answers didnt state where to put the said code.

You can use this repository as an example: see https://github.com/inrupt/solid-client-authn-js/blob/main/jest.setup.ts, where the polyfills are imported, and

setupFilesAfterEnv: ["<rootDir>/jest.setup.ts"],
, where jest is configured to pick up this setup.

Only solution worked to me is to add this line of code to src/setupTests.ts

import '@testing-library/jest-dom';
import { TextEncoder } from 'util';

global.TextEncoder = TextEncoder;

Only solution worked to me is to add this line of code to src/setupTests.ts

import '@testing-library/jest-dom';
import { TextEncoder } from 'util';

global.TextEncoder = TextEncoder;

It's strange you needed that as we do that polyfill of textencoder in our polyfill.. I can't comment on testing lib & jest-dom though. If you've a repo to share I'd love to run a few experiments to check our package is working as expected

Problem still present. Adding this to setupTests.js still works:

import { TextEncoder } from 'util';

global.TextEncoder = TextEncoder;
jeswr commented

Closing this issue as it is an error in Jest with a workaround (i.e. #1676 (comment))

vojdan commented

Only solution worked to me is to add this line of code to src/setupTests.ts

import '@testing-library/jest-dom';
import { TextEncoder } from 'util';

global.TextEncoder = TextEncoder;

This worked for me when using CRA.

This did not work for me. Still getting '
ReferenceError: TextEncoder is not defined'

Solution above did not work for me either, after some trail and error found this to work for me:

// inside your env.setup.js
testEnvironment: "<rootDir>/test/test-config/env-setup.js",

// env-setup.js

const Environment = require("jest-environment-jsdom").default;

module.exports = class CustomTestEnvironment extends Environment {
    async setup() {
        await super.setup();
        this.global.TextEncoder = TextEncoder;
        this.global.TextDecoder = TextDecoder;
        this.global.Response = Response;
        this.global.Request = Request;
        
    }
};

@BowaDAO can you describe your setup a bit more so that I would be able to help?

@Alfredoeb9 thanks for providing something that works around the issue in your case. Can you elaborate a bit more on your setup as well, so that I may figure out why the solution we use for ourselves now doesn't cover your use case?

I've been able to resolve the issue.

.jest/setup.js

import { TextEncoder, TextDecoder } from 'util';

global.TextEncoder = TextEncoder;
global.TextDecoder = TextDecoder;

I am also having the same issue and it does not help me to resolve that. :(