Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannot compare enums in jasmine after angular migration from 6 to 7

I am in the process of migrating an Angular application from v6 to v7. All is well except any test that compares enums. When I run my tests, I get many errors regarding my enums like so

ERROR in src/.../some-thing.component.spec.ts: error TS2345: Argument of type 'PlanDuration.SixMonths' is not assignable to parameter of type 'Expected<PlanDuration.TwelveMonths>'.

An example of test being run looks like this:

export enum PlanDuration {
  SixMonths,
  TwelveMonths
}

...
    it('should toggle plan duration to six months if the event source id is the toggle duration and the event is not checked', () => {

      component.selectedPlanDuration = PlanDuration.TwelveMonths;
      component.handleToggle(event);
      expect(component.selectedPlanDuration).toBe(PlanDuration.SixMonths); // Tests cannot run because of errors here
    });

However, if I cast my enum to number, my tests work perfectly! This would be less than ideal to update my specs everywhere like this:

expect(component.selectedPlanDuration).toBe(<number> PlanDuration.SixMonths);

I'm unsure if I missed something in my package.json. I've compared a fresh angular 7 project to my own projects and the versions of angular core, typescript, jasmine and karma between them are the same.

How can I get my tests to compare enums properly? Below is my package.json

"dependencies": {
    "@angular/animations": "~7.2.0",
    "@angular/common": "~7.2.0",
    "@angular/compiler": "~7.2.0",
    "@angular/core": "~7.2.0",
    "@angular/forms": "~7.2.0",
    "@angular/http": "~7.2.0",
    "@angular/platform-browser": "~7.2.0",
    "@angular/platform-browser-dynamic": "~7.2.0",
    "@angular/router": "~7.2.0",
    "core-js": "^2.5.4",
    "rxjs": "~6.4.0",
    "tslib": "^1.9.0",
    "zone.js": "~0.8.26",
    "@angular/cdk": "^7.0.3",
    "@angular/flex-layout": "7.0.0-beta.24",
    "@angular/material": "7.3.6",
    "hammerjs": "2.0.8",
    "intl": "1.2.5",
    "jshashes": "1.0.7",
    "lodash-es": "4.17.11",
    "request-promise-native": "1.0.5",
    "stream": "0.0.2",
    "timers": "0.1.1",
    "url-search-params-polyfill": "5.0.0",
    "xml2js": "0.4.19"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "~0.13.0",
    "@angular/cli": "~7.3.7",
    "@angular/compiler-cli": "~7.2.0",
    "@angular/language-service": "~7.2.0",
    "@types/node": "~8.9.4",
    "@types/jasmine": "~2.8.8",
    "@types/jasminewd2": "~2.0.3",
    "codelyzer": "~4.5.0",
    "jasmine-core": "~2.99.1",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "~4.0.0",
    "karma-chrome-launcher": "~2.2.0",
    "karma-coverage-istanbul-reporter": "~2.0.1",
    "karma-jasmine": "~1.1.2",
    "karma-jasmine-html-reporter": "^0.2.2",
    "protractor": "~5.4.0",
    "ts-node": "~7.0.0",
    "tslint": "~5.11.0",
    "typescript": "~3.2.2",
    "@types/lodash-es": "4.17.1",
    "gulp": "3.9.1",
    "gulp-stylelint": "7.0.0",
    "jasmine-data-provider": "2.2.0",
    "karma-cli": "1.0.1",
    "karma-junit-reporter": "1.2.0",
    "karma-parallel": "0.3.0",
    "karma-spec-reporter": "0.0.32",
    "lodash": "4.17.11",
    "moment": "2.22.2",
    "npm": "6.0.0",
    "protractor-beautiful-reporter": "1.2.5",
    "protractor-jasmine2-screenshot-reporter": "0.5.0",
    "stylelint": "9.6.0",
    "stylelint-order": "1.0.0",
    "tslint-jasmine-noSkipOrFocus": "1.0.9"
  }

tsconfig.json:

{
  "compileOnSave": false,
  "compilerOptions": {
    "importHelpers": true,
    "preserveConstEnums": true,
    "outDir": "./dist/out-tsc",
    "baseUrl": "src",
    "sourceMap": true,
    "declaration": false,
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "noUnusedLocals": true,
    "target": "es5",
    "typeRoots": [
      "node_modules/@types"
    ],
    "lib": [
      "es2016",
      "dom"
    ]
  }
}

tsconfig.spec.json

{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "outDir": "../out-tsc/spec",
    "module": "commonjs",
    "target": "es5",
    "baseUrl": "",
    "types": [
      "jasmine",
      "node"
    ]
  },
  "files": [
    "test.ts",
    "polyfills.ts"
  ],
  "include": [
    "**/*.spec.ts",
    "**/*.d.ts"
  ]
}
like image 856
Alex Fallenstedt Avatar asked Mar 28 '19 00:03

Alex Fallenstedt


2 Answers

I ran into issues a little while back where Angular was telling me that it could not access MyEnumValue of undefined. After some fiddling, I found that exporting all enums as const, and adding "preserveConstEnums": true to my tsconfig.json made it work just fine.

But enums are always numbers unless otherwise specified, and thankfully don't need casting, but Typescript's compilation of enums can be funky at times in the same way interfaces are funky.

Edit:

In your component, make sure:

// If you give this a default value, TypeScript will assume
// that the only "valid" type is PlanDuration.TwelveMonths
// Type evaluates to: PlanDuration | number between 0 and 1;
selectedPlanDuration: PlanDuration = PlanDuration.TwelveMonths;

// Type evaluates to: PlanDuration.TwelveMonths | 1;
selectedPlanDuration = PlanDuration.TwelveMonths
like image 155
Rachael Dawn Avatar answered Oct 21 '22 03:10

Rachael Dawn


TL;DR

Quick fix: go to node_modules/@types/jasmine/index.d.ts, search for type Expected

// Change this line:
// type Expected<T> = T | ObjectContaining<T> | Any | Spy;
// to:
type Expected<T> = any;

That's it :)


For more details, read on:

I believe this is a bug in jasmine's types definition. I set up a fresh ng7 workspace and try to reproduce your problem. Here's my finding:

There are two jasmine related .d.ts file in the workspace:

// package.json
  ...
  "@types/jasmine": "~2.8.8",
  "@types/jasminewd2": "~2.0.3",

I'm not 100% sure how these two work together, but they are declaring conflicting types for same jasmine utils. For example:

// jasminewd2/index.d.ts
declare namespace jasmine {
  interface Matchers<T> {
    toBe(expected: any, expectationFailOutput?: any): Promise<void>;
    ...
// jasmine/index.d.ts
declare namespace jasmine {
  interface Matchers<T> {
    toBe(expected: Expected<T>, expectationFailOutput?: any): boolean;

Now the problem is in jasmine/index.d.ts.

This line toBe(expected: Expected<T>) is simply WRONG. This is a test case, certainly you are allowed to test against any value. Yet Expected<T> is declared as some complex type of no point.

The easiest way to fix it, is to manually correct it. Solution is already given at the beginning. Cheers.

like image 33
hackape Avatar answered Oct 21 '22 02:10

hackape