Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do my unit tests pass with Chrome and fail with PhantomJS?

I am using Angular2 final (2.0.2) with angular-cli. I am trying to set it up for running unit tests with PhantomJS. Running the specs with Chrome and karma-chrome-launcher works - all tests pass. Running the same with Phantomjs-prebuilt 2.1.13 and karma-phantomjs-launcher 1.0.2 tests fail.

I added the phantomjs launcher to the plugins array in karma.conf, as well as PahntomJS in the browsers array.

The error I am getting is:

PhantomJS 2.1.1 (Mac OS X 0.0.0) DataTableFormat should transform date in milliseconds FAILED ReferenceError: Can't find variable: Intl in src/main/js/test.ts (line 53565) intlDateFormat@webpack:///Users/sninio/dev/csp-ui/~/@angular/common/src/facade/intl.js:117:0 <- src/main/js/test.ts:53565:20 webpack:///Users/sninio/dev/csp-ui/~/@angular/common/src/facade/intl.js:148:36 <- src/main/js/test.ts:53596:59 dateFormatter@webpack:///Users/sninio/dev/csp-ui/~/@angular/common/src/facade/intl.js:157:0 <- src/main/js/test.ts:53605:39 format@webpack:///Users/sninio/dev/csp-ui/~/@angular/common/src/facade/intl.js:192:0 <- src/main/js/test.ts:53640:29 transform@webpack:///Users/sninio/dev/csp-ui/~/@angular/common/src/pipes/date_pipe.js:92:0 <- src/main/js/test.ts:70473:90 transform@webpack:///Users/sninio/dev/csp-ui/src/main/js/app/pages/+platform/events/data-table/data-table.pipe.ts:9:4418 <- src/main/js/test.ts:52698:5787 webpack:///Users/sninio/dev/csp-ui/src/main/js/app/pages/+platform/events/data-table/data-table.pipe.spec.ts:20:30 <- src/main/js/test.ts:60923:30 execute@webpack:///Users/sninio/dev/csp-ui/~/@angular/core/bundles/core-testing.umd.j <- src/main/js/test.ts:2997:28 webpack:///Users/sninio/dev/csp-ui/~/@angular/core/bundles/core-testing.umd.js:951:32 <- src/main/js/test.ts:3084:56 invoke@webpack:///Users/sninio/dev/csp-ui/~/zone.js/dist/zone.js:203:0 <- src/main/js/test.ts:98886:33 onInvoke@webpack:///Users/sninio/dev/csp-ui/~/zone.js/dist/proxy.js:72:0 <- src/main/js/test.ts:68790:45 invoke@webpack:///Users/sninio/dev/csp-ui/~/zone.js/dist/zone.js:202:0 <- src/main/js/test.ts:98885:42 run@webpack:///Users/sninio/dev/csp-ui/~/zone.js/dist/zone.js:96:0 <- src/main/js/test.ts:98779:49 webpack:///Users/sninio/dev/csp-ui/~/zone.js/dist/jasmine-patch.js:91:27 <- src/main/js/test.ts:68526:53 execute@webpack:///Users/sninio/dev/csp-ui/~/zone.js/dist/jasmine-patch.js:119:0 <- src/main/js/test.ts:68554:46 execute@webpack:///Users/sninio/dev/csp-ui/~/zone.js/dist/jasmine-patch.js:119:0 <- src/main/js/test.ts:68554:46 invokeTask@webpack:///Users/sninio/dev/csp-ui/~/zone.js/dist/zone.js:236:0 <- src/main/js/test.ts:98919:42 runTask@webpack:///Users/sninio/dev/csp-ui/~/zone.js/dist/zone.js:136:0 <- src/main/js/test.ts:98819:57 drainMicroTaskQueue@webpack:///Users/sninio/dev/csp-ui/~/zone.js/dist/zone.js:368:0 <- src/main/js/test.ts:99051:42 PhantomJS 2.1.1 (Mac OS X 0.0.0) DataTableFormat should transform date string FAILED

Maybe I am missing some configuration in the test.ts file created by angular-cli?

UPDATE: Seems the only tests that fail import DatePipe and JsonPipe.
I tried to also import @angular/common/testing in test.ts but that doesn't help - they are not exported in the relevant index.js.
Also tried importing the entire @angular/common/pipes but that didn't work either.

here is the Pipe:

import { Pipe, PipeTransform } from "@angular/core";
import { DatePipe, JsonPipe } from "@angular/common";

@Pipe({name: 'dataTableFormat'})
export class DataTablePipe implements PipeTransform {

    // values with type 'json' are parsed to json. As a result, string values may be displayed with quotes ("<string>").
    // To avoid that, we remove these quotes with this regex
    private quotesExp: RegExp = /^\"|\"$/gi;

    constructor(private datePipe: DatePipe, private jsonPipe: JsonPipe) {

    }

    transform(value: any, type: string): string {
        switch (type) {
            case "date":
                return this.datePipe.transform(value, 'short');
            case "json":
                return this.jsonPipe.transform(value).replace(this.quotesExp, "");
            default:
                return value;
        }
    }
}

and the spec:

import { inject, TestBed } from "@angular/core/testing";
import { DataTablePipe } from "./data-table.pipe";
import { DatePipe, JsonPipe } from "@angular/common";

describe('DataTableFormat', () => {

    beforeEach(() => {
        TestBed.configureTestingModule({
            providers: [
                DatePipe, JsonPipe
            ]
        });

    });

    it('should transform date in milliseconds', inject([DatePipe, JsonPipe], (datePipe, jsonPipe) => {
        let pipe = new DataTablePipe(datePipe, jsonPipe);
        let testDate: Date = new Date();

        expect(pipe.transform(testDate.getTime(), 'date')).toBe(datePipe.transform(testDate, 'short'));
    }));

    it('should transform date string', inject([DatePipe, JsonPipe], (datePipe, jsonPipe) => {
        let pipe = new DataTablePipe(datePipe, jsonPipe);
        let testDate: Date = new Date();

        expect(pipe.transform(testDate.toISOString(), 'date')).toBe(datePipe.transform(testDate, 'short'));
    }));

    it('should transform json', inject([DatePipe, JsonPipe], (datePipe, jsonPipe) => {
        let pipe = new DataTablePipe(datePipe, jsonPipe);
        let testJson = {
            prop1: "val1",
            prop2: "val2"
        };

        expect(pipe.transform(testJson, 'json')).toBe(jsonPipe.transform(testJson));
    }));

});

here is my test.ts file - didn't change it much from the one generated by angular-cli...

import "./polyfills.ts";
import "zone.js/dist/long-stack-trace-zone";
import "zone.js/dist/proxy.js";
import "zone.js/dist/sync-test";
import "zone.js/dist/jasmine-patch";
import "zone.js/dist/async-test";
import "zone.js/dist/fake-async-test";


// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
declare var __karma__: any;
declare var require: any;

// Prevent Karma from running prematurely.
__karma__.loaded = function () {
};

//noinspection TypeScriptUnresolvedVariable
Promise.all([
    System.import('@angular/core/testing'),
    System.import('@angular/platform-browser-dynamic/testing'),
    System.import('../../../node_modules/nvd3/build/nv.d3.min.js'),
])
// First, initialize the Angular testing environment.
    .then(([testing, testingBrowser]) => {
        testing.getTestBed().initTestEnvironment(
            testingBrowser.BrowserDynamicTestingModule,
            testingBrowser.platformBrowserDynamicTesting()
        );
    })
    // Then we find all the tests.
    .then(() => require.context('./', true, /\.spec\.ts/))
    // And load the modules.
    .then(context => context.keys().map(context))
    // Finally, start Karma to run the tests.
    .then(__karma__.start, __karma__.error);

Any idea why this works for Chrome and not PhantomJS?

like image 622
Sefi Ninio Avatar asked Dec 07 '22 20:12

Sefi Ninio


2 Answers

Since PhantomJS does not implement the Intl implementation Angular2 depends on, the solution was to install npm package intl polyfill and edit the polyfills.ts to include

import 'intl';
import 'intl/locale-data/jsonp/en.js';

See here: https://github.com/angular/angular/issues/3333
here: https://github.com/angular/angular/issues/10809
and here: https://coryrylan.com/blog/adding-the-internationalization-polyfill-to-a-angular-cli-project

like image 142
Sefi Ninio Avatar answered Dec 10 '22 13:12

Sefi Ninio


Now you have Intl support in all your browsers. polyfill.io is a nifty service that will only load the polyfill if the requested browser actually needs it. This is a pretty good solution but what if you want your polyfill to be bundled with your scripts? You potentially get better performance and your application doesn’t rely on a third party CDN that if it goes down it takes your app down with it. So whats the next option?

Well with the Angular CLI its actually quite easy. First we will use npm to install the Intl polyfill directly into our Angular app. To do this run npm install intl --save. Once installed in your Angular CLI project go to the /src/polyfills.ts file. In here you can add the following lines.

import 'intl';
import 'intl/locale-data/jsonp/en.js';

That’s it! Now when your project builds it will add the Intl polyfill and the English language service. You can add more language support by importing more language files. Now the downside to this is even if the browser supports Intl it still has to download the code. You could potentially mitigate this using feature detection and dynamic loading. The great part about polyfilling is as browser support for Intl gets better we will be able to drop the dependency all together!

like image 31
Shailesh kala Avatar answered Dec 10 '22 11:12

Shailesh kala