Search code examples
jqueryangularjestjsslickgridangular-test

Problems to run any Jest tests with jQuery and jQuery UI


So I have an Open Source library named Angular-Slickgrid which doesn't have tests yet and I'm trying to use Jest with it but it's really hard to get going with it. The library is a wrapper of and old jQuery datagrid library (SlickGrid) which also uses jQuery UI. I think I got partially over the jQuery problem (not even sure), but jQuery UI still complains. Also note that I'm new to Jest and Unit Testing in Angular but I really want this to work and make my lib safer.

You can see a commit on GitHub of all the code change I made to try implementing Jest with my Open Source lib. The commit is here. Feel free to create a PR if that is easier. I use the previous version of Jest (23.6.0) instead of the latest, because I have other kind of issues with latest.

This is the error I have currently

FAIL  src/app/modules/angular-slickgrid/components/angular-slickgrid.component.spec.ts
Test suite failed to run 
TypeError: Cannot read property 'ui' of undefined
  at node_modules/jquery-ui-dist/jquery-ui.js:18:10
  at Object.<anonymous>.$.ui (node_modules/jquery-ui-dist/jquery-ui.js:14:3)
  at Object.<anonymous> (node_modules/jquery-ui-dist/jquery-ui.js:16:2)
  at Object.<anonymous> (src/app/modules/angular-slickgrid/components/angular-slickgrid.component.ts:11193:1)
  at Object.<anonymous> (src/app/modules/angular-slickgrid/components/angular-slickgrid.component.spec.ts:7:37)

I tried to use unmock('jquery') and unmock('jquery-ui') but that doesn't seem to help. Here's the test that fails

jest.unmock('jquery');
jest.unmock('jquery-ui');
import { TestBed, async } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';

import { AngularSlickgridComponent } from './angular-slickgrid.component';

describe('AppComponent', () => {
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [
        AngularSlickgridComponent
      ],
      providers: [],
      imports: [RouterTestingModule]
    }).compileComponents();
  }));

  it('should create the app', async(() => {
    const fixture = TestBed.createComponent(AngularSlickgridComponent);
    const app = fixture.debugElement.componentInstance;
    expect(app).toBeTruthy();
  }));
});

Also my jest.config.js

module.exports = {
  globals: {
    'ts-jest': {
      tsConfigFile: './src/tsconfig.spec.json',
    },
    __TRANSFORM_HTML__: true,
  },
  testMatch: ['**/__tests__/**/*.+(ts|js)', '**/+(*.)+(spec|test).+(ts|js)'],
  setupFiles: ['<rootDir>/test-env.ts'],
  setupTestFrameworkScriptFile: '<rootDir>/node_modules/@angular-builders/jest/src/jest-config/setup.js',
  transform: {
    '^.+\\.(ts|html)$': '<rootDir>/node_modules/jest-preset-angular/preprocessor.js',
  },
  transformIgnorePatterns: ['node_modules/(?!@ngrx)'],
  moduleDirectories: [
    "node_modules",
    "src/app",
  ],
  collectCoverage: true,
  moduleFileExtensions: [
    'ts',
    'json',
    'js'
  ],
  testResultsProcessor: 'jest-sonar-reporter',
  moduleNameMapper: {
    "app/(.*)": "<rootDir>/src/app/$1",
    "@common/(.*)": "<rootDir>/src/app/common/$1",
  }
};

and finally a Test Setup to import jQuery globally for Jest

import jQuery from 'jquery';
declare var window: any;
declare var global: any;
window.$ = window.jQuery = jQuery;
global.$ = global.jQuery = jQuery;

What I wish to accomplish is to test at least my Angular Services and the Component creation, that would a good start but I can't get passed the jQuery and jQuery UI issue, even though I don't want to test any of the core library (SlickGrid), neither jQuery, jQuery UI.

EDIT

Thanks to @brian-lives-outdoors for the answer on jQuery and jQuery-UI, I got much further. Now I have another small issue with an @Inject() used directly in the Constructor (that is to pass configs to my Component library), which I'm not sure how to get around it, if someone knows how please help.

constructor(
  private elm: ElementRef,
  // ... more Services import
  //
  @Inject('config') private forRootConfig: GridOption
) {}

and the error is

StaticInjectorError(DynamicTestModule)[config]:
  StaticInjectorError(Platform: core)[config]:
    NullInjectorError: No provider for config!

at NullInjector.get (../packages/core/src/di/injector.ts:43:13)
at resolveToken (../packages/core/src/di/injector.ts:346:20)
...

ANSWER of last edit question

I found how to fix the Constructor and @Inject(), I can do that one with overrideComponent() inside the beforeEach as shown below

beforeEach(async(() => {
  TestBed.configureTestingModule({
    declarations: [
      AngularSlickgridComponent,
      SlickPaginationComponent
    ],
    providers: [
      // ... all Services
    ],
    imports: [
      RouterTestingModule,
      TranslateModule.forRoot()
    ]
  })
  // THIS LINE is for the @Inject('config')
  .overrideComponent(AngularSlickgridComponent, {
    set: { providers: [{ provide: 'config', useValue: {} }] },
  })
  .compileComponents();
}));

And finally I can now say that I have Jest running!!!


Solution

  • The issue is that jQuery was being imported like this:

    import jQuery from 'jquery';
    

    ...which doesn't work right and causes jQuery to be undefined.

    Because jQuery is imported as undefined the global $ is set to undefined and when jQuery UI tries to load it throws an error.


    This problem is strange because that syntax for importing jQuery shows up a lot, even as an example of import syntax in the official TypeScript docs.


    In any case, you can fix the problem by importing jQuery using this syntax:

    import * as jQuery from 'jquery';
    


    Change your test-env.ts to this:

    import * as jQuery from 'jquery';
    declare var window: any;
    declare var global: any;
    window.$ = window.jQuery = jQuery;
    global.$ = global.jQuery = jQuery;
    

    ...change the top of angular-slickgrid.components.ts to this:

    // import 3rd party vendor libs
    // only import the necessary core lib, each will be imported on demand when enabled (via require)
    import 'jquery-ui-dist/jquery-ui';
    import 'slickgrid/lib/jquery.event.drag-2.3.0';
    import 'slickgrid/slick.core';
    import 'slickgrid/slick.grid';
    import 'slickgrid/slick.dataview';
    
    // ...then everything else...
    import { AfterViewInit, Component, ElementRef, EventEmitter, Inject, Injectable, Input, Output, OnDestroy, OnInit } from '@angular/core';
    import { TranslateService } from '@ngx-translate/core';
    import { GlobalGridOptions } from './../global-grid-options';
    // ...
    

    ...and change your angular-slickgrid.component.spec.ts to this:

    import { TestBed, async } from '@angular/core/testing';
    import { RouterTestingModule } from '@angular/router/testing';
    
    import { AngularSlickgridComponent } from './angular-slickgrid.component';
    
    describe('AppComponent', () => {
      beforeEach(async(() => {
        TestBed.configureTestingModule({
          declarations: [
            AngularSlickgridComponent
          ],
          providers: [],
          imports: [RouterTestingModule]
        }).compileComponents();
      }));
    
      it('should create the app', async(() => {
        const fixture = TestBed.createComponent(AngularSlickgridComponent);
        const app = fixture.debugElement.componentInstance;
        expect(app).toBeTruthy();
      }));
    
      it(`should have as title 'Angular SlickGrid Demo'`, async(() => {
        const fixture = TestBed.createComponent(AngularSlickgridComponent);
        const app = fixture.debugElement.componentInstance;
        expect(app.title).toEqual('Angular SlickGrid Demo');
      }));
    });
    

    ...and that will get you past your initial jQuery errors.