Florestan Korp
Florestan Korp

Reputation: 711

Global propery in JSDOM not being set in integration test (angular-testing-library)

I'm not able to set up my testing-library integration tests so they recognize the global interface with the third party static methods from my component library Preline UI

I'm using JSDOM and angular-testing-library, but I don't know how I can get autoInit() to fire and keep getting an error that the test cannot find HSStaticMethods, which means the internal functionality is not being loaded and I can not test if their components actually integrate with my app!

declare global {
  interface Window {
    HSStaticMethods: IStaticMethods;
  }
}

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: 'app-component',
  templateUrl: './app.component.html',
  standalone: true,
})
export class AppComponent implements OnInit {
  private readonly router = inject(Router);

  public ngOnInit(): void {
    this.router.events.subscribe((event: Event) => {
      if (event instanceof NavigationEnd) {
        setTimeout(() => {
          // https://www.preline.co/docs/frameworks-angular.html
          window.HSStaticMethods.autoInit();
        }, 100);
      }
    });
  }

I've tried

export default {
  moduleNameMapper: {
    preline: 'node_modules/preline/dist/preline.js', // added this
  }
}

My tsconfig.spec.json:

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "outDir": "../../../../dist/out-tsc",
    "module": "commonjs",
    "types": ["jest", "node", "@testing-library/jest-dom", "preline"] // added the lib just in case?
  },
  "files": ["src/test-setup.ts"],
  "include": [
    "jest.config.ts",
    "src/**/*.test.ts",
    "src/**/*.spec.ts",
    "src/**/*.d.ts"
  ]
}

And here's the actual integration test:


import { signal } from '@angular/core';
import type { RouterEvent } from '@angular/router';
import { NavigationEnd, Router } from '@angular/router';
import type { IUser } from '@front-and-workspace/projects/grip-clone';
import { render } from '@testing-library/angular';
import { screen } from '@testing-library/dom';
import userEvent from '@testing-library/user-event';
import type { IStaticMethods } from 'preline';
import { BehaviorSubject } from 'rxjs';
import { AppComponent } from './app.component';
import { AuthService } from './auth/auth.service';

// I got desperate and started adding this everywhere :(
declare global {
  interface Window {
    // eslint-disable-next-line @typescript-eslint/naming-convention
    HSStaticMethods: IStaticMethods;
  }
}

jest.useFakeTimers(); // Jest timer so I can control when the setTimeout goes off

it('should open the Preline dropdown', async () => {
  const user = userEvent.setup();
  const mockEvents$ = new BehaviorSubject<null | RouterEvent>(null);
  const { fixture } = await render(AppComponent, {
    providers: [
      {
        provide: Router,
        useValue: {
          events: mockEvents$,
          routerState: { root: '' },
          createUrlTree: jest.fn(),
          serializeUrl: jest.fn().mockReturnValue(''),
        },
      },
    ],
  });

  mockEvents$.next(new NavigationEnd(0, '', ''));
  jest.runAllTimers(); // ERROR: TypeError: Cannot read properties of undefined (reading 'autoInit')

  let themeToggleDropdownContainer = screen.getByTestId(
    'front-and-workspace-theme-toggle-dropdown-container',
  );

  expect(themeToggleDropdownContainer.className).toBe('hs-dropdown');

  await user.click(
    screen.getByTestId('front-and-workspace-theme-toggle-button'),
  );

  themeToggleDropdownContainer = screen.getByTestId(
    'front-and-workspace-theme-toggle-dropdown-container',
  );

  fixture.detectChanges();

  //Check if drop-down was opened
  expect(themeToggleDropdownContainer.className).toBe('hs-dropdown open');
});

Upvotes: 0

Views: 19

Answers (1)

Florestan Korp
Florestan Korp

Reputation: 711

The solution was to import 'preline' in my test-setup.ts

import '@testing-library/jest-dom';
import '@testing-library/jest-dom/jest-globals';
import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';
import 'preline'; // this 

setupZoneTestEnv();

And finally override window.matchMedia in the test itself. Here is the final integration test:

import { provideRouter } from '@angular/router';
import { render } from '@testing-library/angular';
import { screen } from '@testing-library/dom';
import userEvent from '@testing-library/user-event';
import { AppComponent } from './app.component';

beforeAll(() => {
  Object.defineProperty(window, 'matchMedia', {
    value: jest.fn().mockImplementation(() => ({})),
  });
});

it('should open the Preline dropdown', async () => {
  const user = userEvent.setup();
  await render(AppComponent, {
    providers: [provideRouter([])],
  });

  let themeToggleDropdownContainer = screen.getByTestId(
    'theme-toggle-dropdown-container',
  );

  expect(themeToggleDropdownContainer.className).toBe('hs-dropdown');

  await user.click(screen.getByTestId('theme-toggle-button'));

  themeToggleDropdownContainer = screen.getByTestId(
    'theme-toggle-dropdown-container',
  );

  expect(themeToggleDropdownContainer.className).toBe('hs-dropdown open');
});

Upvotes: 0

Related Questions