SyntaxError: Use of reserved word 'import' #14

Closed
mraible opened this Issue Nov 3, 2016 · 4 comments

Projects

None yet

2 participants

@mraible
mraible commented Nov 3, 2016

First of all, thanks for creating this Yeoman generator!

I'm having issues when I create helper test classes in test/mocks/*.ts and try to create new instances of them in my tests. Here's the error:

PhantomJS 2.1.1 (Mac OS X 0.0.0) ERROR
  SyntaxError: Use of reserved word 'import'
  at test/entry.ts:79285

PhantomJS 2.1.1 (Mac OS X 0.0.0) ERROR
  SyntaxError: Use of reserved word 'import'
  at test/entry.ts:79285

Is there some sort of pre-processor I need to add to process files in test/mocks and compile them first? I tried using https://www.npmjs.com/package/karma-typescript-preprocessor, but had no luck.

@mattlewis92
Owner

I'm assuming you're trying to include the files via karma's files: [] array? I wouldn't recommend doing that, and instead just import any files directly via the test/entry.ts file.

@mattlewis92 mattlewis92 closed this Nov 3, 2016
@mraible
mraible commented Nov 3, 2016

No, I didn't try to include them in karma's files: []. I tried importing the mock service I'm trying to use in entry.ts, but no dice. Here's steps to reproduce:

  1. Create a new project with yo angular-library

  2. Create src/search.service.ts with the following code:

    import { Injectable } from '@angular/core';
    import { Http, Response } from '@angular/http';
    import 'rxjs/add/operator/map';
    
    @Injectable()
    export class SearchService {
    
      constructor(private http: Http) {}
    
      getAll() {
        return this.http.get('app/shared/search/data/people.json').map((res: Response) => res.json());
      }
    }
  3. Install @angular/http for the above service: npm i @angular/http --save-dev

  4. Create a src/search.component.ts that uses this service:

    import { Component, OnInit, OnDestroy } from '@angular/core';
    import { Subscription } from 'rxjs';
    import { SearchService } from './search.service';
    
    @Component({
      selector: 'app-search',
      template: '<app-search></app-search>',
    })
    export class SearchComponent {
      query: string;
      searchResults: any;
      sub: Subscription;
    
      constructor(private searchService: SearchService) {
      }
    
      search(): void {
        this.searchService.getAll().subscribe(
          data => {
            this.searchResults = data;
          },
          error => console.log(error)
        );
      }
    }
  5. Create test/mocks/helper.ts with the following code:

    import {StringMapWrapper} from '@angular/core/src/facade/collection';
    
    export interface GuinessCompatibleSpy extends jasmine.Spy {
      /** By chaining the spy with and.returnValue, all calls to the function will return a specific
       * value. */
      andReturn(val: any): void;
      /** By chaining the spy with and.callFake, all calls to the spy will delegate to the supplied
       * function. */
      andCallFake(fn: Function): GuinessCompatibleSpy;
      /** removes all recorded calls */
      reset();
    }
    
    export class SpyObject {
      static stub(object = null, config = null, overrides = null) {
        if (!(object instanceof SpyObject)) {
          overrides = config;
          config = object;
          object = new SpyObject();
        }
    
        let m = StringMapWrapper.merge(config, overrides);
        for (let key in m) {
          object.spy(key).andReturn(m[key]);
        }
        return object;
      }
    
      constructor(type = null) {
        if (type) {
          for (let prop in type.prototype) {
            let m = null;
            try {
              m = type.prototype[prop];
            } catch (e) {
              // As we are creating spys for abstract classes,
              // these classes might have getters that throw when they are accessed.
              // As we are only auto creating spys for methods, this
              // should not matter.
            }
            if (typeof m === 'function') {
              this.spy(prop);
            }
          }
        }
      }
    
      spy(name) {
        if (!this[name]) {
          this[name] = this._createGuinnessCompatibleSpy(name);
        }
        return this[name];
      }
    
      prop(name, value) { this[name] = value; }
    
      /** @internal */
      _createGuinnessCompatibleSpy(name): GuinessCompatibleSpy {
        let newSpy: GuinessCompatibleSpy = <any>jasmine.createSpy(name);
        newSpy.andCallFake = <any>newSpy.and.callFake;
        newSpy.andReturn = <any>newSpy.and.returnValue;
        newSpy.reset = <any>newSpy.calls.reset;
        // revisit return null here (previously needed for rtts_assert).
        newSpy.and.returnValue(null);
        return newSpy;
      }
    }
  6. Create test/mocks/mock.search.service.ts:

    import { SpyObject } from './helper';
    import Spy = jasmine.Spy;
    import { SearchService } from '../../src/search.service';
    
    export class MockSearchService extends SpyObject {
      getAllSpy: Spy;
      fakeResponse: any;
    
      constructor() {
        super( SearchService );
    
        this.fakeResponse = null;
        this.getAllSpy = this.spy('getAll').andReturn(this);
      }
    
      subscribe(callback: any) {
        callback(this.fakeResponse);
      }
    
      setResponse(json: any): void {
        this.fakeResponse = json;
      }
    }
  7. Create a test for the search component at test/search.component.spec.ts:

    /* tslint:disable:no-unused-variable */
    
    import { TestBed } from '@angular/core/testing';
    import { MockSearchService } from './mocks/mock.search.service';
    import { SearchComponent } from '../src/search.component';
    import { SearchService } from '../src/search.service';
    
    describe('Component: Search', () => {
      let mockSearchService: MockSearchService;
    
      beforeEach(() => {
        mockSearchService = new MockSearchService();
    
        TestBed.configureTestingModule({
          declarations: [SearchComponent],
          providers: [
            {provide: SearchService, useValue: mockSearchService}
          ]
        });
      });
    
      it('should call service when search() is called', () => {
        let fixture = TestBed.createComponent(SearchComponent);
        let searchComponent = fixture.debugElement.componentInstance;
        searchComponent.search();
        expect(mockSearchService.getAllSpy).toHaveBeenCalled();
      });
    
    });
  8. At this point, when you run npm test, you'll get the following strange error:

    ts-loader: Using typescript@2.0.6 and /Users/mraible/dev/angular2-testing/tsconfig.json
    03 11 2016 08:37:55.633:ERROR [karma]: { [Error: no such file or directory]
      code: 'ENOENT',
      errno: 34,
      message: 'no such file or directory',
      path: '/_karma_webpack_/test/entry.ts' }
    Error: no such file or directory
    

    You can fix this by changing karma.config.js to use the following instead of webpack.plugins:

    devtool: 'inline-source-map'
    
  9. Now when you run npm test, you'll see the problem:

    PhantomJS 2.1.1 (Mac OS X 0.0.0) ERROR
      SyntaxError: Use of reserved word 'import'
      at test/entry.ts:79302
    
    PhantomJS 2.1.1 (Mac OS X 0.0.0) ERROR
      SyntaxError: Use of reserved word 'import'
      at test/entry.ts:79302
    

Adding import {MockSearchService} from './mocks/mock.search.service' to test/entry.ts does not solve the problem.

I've created a project on GitHub to demonstrate this problem: https://github.com/mraible/angular2-testing

Note that I have another project that uses a similar setup for mocking services when testing components. However, it's a bit different because it uses Angular CLI.

https://github.com/mraible/ng2-demo

@mattlewis92
Owner

Ah, thanks for the repro, I can see what's happening now. It's because you're consuming a private API from angular here: https://github.com/mraible/angular2-testing/blob/master/test/mocks/helper.ts#L1

This uses ES6 imports which webpack 1 can't understand, hence it throws the error. You can either upgrade to webpack 2 which can understand ES6 imports or just don't use the private API which will probably break / fail to exist on future versions of angular and break your app. Hope that helps! :)

@mraible
mraible commented Nov 3, 2016

Thanks for the tip! I was able to get rid of the import and change this:

let m = StringMapWrapper.merge(config, overrides);

To this:

let m = {};
Object.keys(config).forEach((key) => m[key] = config[key]);
Object.keys(overrides).forEach((key) => m[key] = overrides[key]);

I also had a bug in my component template. Here's the commit that gets everything passing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment