diff --git a/README.md b/README.md index fe7d3c3..a012e17 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Ionic Unit Testing Example +Ionic Unit Testing Example with jest ===================== Based on the awesome [unit testing example](https://github.com/roblouie/unit-testing-demo) from [@roblouie](https://github.com/roblouie/) :thumbsup: @@ -11,12 +11,3 @@ Unit Tests To run the tests, run `npm test`. See the example test in `src/app/app.component.spec.ts` for an example of a component test. - -End-To-End Tests (Browser-Only) -------------------------------- - -To serve the app, run `ionic serve`. - -To run the end-to-end tests, run (while the app is being served) `npm run e2e`. - -See the example end-to-end test in `e2e/app.e2e-spec.ts`. diff --git a/package.json b/package.json index 0b5805a..d325f1f 100644 --- a/package.json +++ b/package.json @@ -8,50 +8,54 @@ "build": "ionic-app-scripts build", "ionic:build": "ionic-app-scripts build", "ionic:serve": "ionic-app-scripts serve", - "test": "karma start ./test-config/karma.conf.js", + "test": "jest", + "test:watch": "jest --watch", "e2e": "webdriver-manager update --standalone false --gecko false; protractor ./test-config/protractor.conf.js" }, "dependencies": { - "@angular/common": "2.4.8", - "@angular/compiler": "2.4.8", - "@angular/compiler-cli": "2.4.8", - "@angular/core": "2.4.8", - "@angular/forms": "2.4.8", - "@angular/http": "2.4.8", - "@angular/platform-browser": "2.4.8", - "@angular/platform-browser-dynamic": "2.4.8", - "@angular/platform-server": "2.4.8", - "@ionic-native/core": "3.1.0", - "@ionic-native/splash-screen": "3.1.0", - "@ionic-native/status-bar": "3.1.0", - "@ionic/storage": "2.0.0", - "ionic-angular": "2.2.0", + "@angular/common": "4.0.2", + "@angular/compiler": "4.0.2", + "@angular/compiler-cli": "4.0.2", + "@angular/core": "4.0.2", + "@angular/forms": "4.0.2", + "@angular/http": "4.0.2", + "@angular/platform-browser": "4.0.2", + "@angular/platform-browser-dynamic": "4.0.2", + "@ionic-native/core": "3.6.1", + "@ionic-native/splash-screen": "3.6.1", + "@ionic-native/status-bar": "3.6.1", + "@ionic/storage": "2.0.1", + "ionic-angular": "3.1.1", "ionicons": "3.0.0", - "rxjs": "5.0.1", + "rxjs": "5.1.1", "sw-toolbox": "3.4.0", - "zone.js": "0.7.2" + "zone.js": "^0.8.9" }, "devDependencies": { - "@ionic/app-scripts": "1.1.4", + "@ionic/app-scripts": "1.3.6", "@ionic/cli-build-ionic-angular": "0.0.3", "@ionic/cli-plugin-cordova": "0.0.9", "@types/jasmine": "^2.5.41", "@types/node": "^7.0.8", "angular2-template-loader": "^0.6.2", "html-loader": "^0.4.5", - "jasmine": "^2.5.3", - "jasmine-spec-reporter": "^3.2.0", - "karma": "^1.5.0", - "karma-chrome-launcher": "^2.0.0", - "karma-jasmine": "^1.1.0", - "karma-jasmine-html-reporter": "^0.2.2", - "karma-sourcemap-loader": "^0.3.7", - "karma-webpack": "^2.0.3", + "jest": "^20.0.0", + "jest-preset-angular": "^2.0.1", "null-loader": "^0.1.1", - "protractor": "^5.1.1", "ts-loader": "^2.0.3", "ts-node": "^3.0.2", - "typescript": "2.1.6" + "typescript": "2.2.1" + }, + "jest": { + "preset": "jest-preset-angular", + "setupTestFrameworkScriptFile": "/src/setupJest.ts", + "transformIgnorePatterns": [ + "node_modules/(?!@ngrx|angular2-ui-switch|ng-dynamic|@ionic-native|ionic-angular)" + ], + "globals": { + "__TS_CONFIG__": "tsconfig.json", + "__TRANSFORM_HTML__": true + } }, "version": "0.0.1", "description": "An Ionic project" diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index 249c252..06d3b39 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -28,11 +28,11 @@ describe('MyApp Component', () => { component = fixture.componentInstance; }); - it ('should be created', () => { + it('should be created', () => { expect(component instanceof MyApp).toBe(true); }); - it ('should have two pages', () => { + it('should have two pages', () => { expect(component.pages.length).toBe(2); }); diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 96dce50..4fc5537 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -15,7 +15,7 @@ export class MyApp { rootPage: any = Page1; - pages: Array<{title: string, component: any}>; + pages: Array<{ title: string, component: any }>; constructor(public platform: Platform, public statusBar: StatusBar, public splashScreen: SplashScreen) { this.initializeApp(); diff --git a/src/app/app.module.ts b/src/app/app.module.ts index f1b16b3..4f8de19 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,4 +1,5 @@ import { NgModule, ErrorHandler } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular'; import { MyApp } from './app.component'; @@ -15,6 +16,7 @@ import { SplashScreen } from '@ionic-native/splash-screen'; Page2 ], imports: [ + BrowserModule, IonicModule.forRoot(MyApp) ], bootstrap: [IonicApp], @@ -26,7 +28,7 @@ import { SplashScreen } from '@ionic-native/splash-screen'; providers: [ StatusBar, SplashScreen, - {provide: ErrorHandler, useClass: IonicErrorHandler} + { provide: ErrorHandler, useClass: IonicErrorHandler } ] }) -export class AppModule {} +export class AppModule { } diff --git a/src/jestGlobalMocks.ts b/src/jestGlobalMocks.ts new file mode 100644 index 0000000..3a48284 --- /dev/null +++ b/src/jestGlobalMocks.ts @@ -0,0 +1,47 @@ +const mock = () => { + let storage = {}; + return { + getItem: key => key in storage ? storage[key] : null, + setItem: (key, value) => storage[key] = value || '', + removeItem: key => delete storage[key], + clear: () => storage = {}, + }; +}; + +Object.defineProperty(window, 'localStorage', {value: mock()}); +Object.defineProperty(window, 'sessionStorage', {value: mock()}); +Object.defineProperty(window, 'getComputedStyle', { + value: () => ['-webkit-appearance'] +}); + +// http://paulirish.com/2011/requestanimationframe-for-smart-animating/ +// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating + +// requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel + +// MIT license + +(function() { + var lastTime = 0; + var vendors = ['ms', 'moz', 'webkit', 'o']; + for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { + window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; + window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] + || window[vendors[x]+'CancelRequestAnimationFrame']; + } + + if (!global.__zone_symbol__requestAnimationFrame) + global.__zone_symbol__requestAnimationFrame = function(callback, element) { + var currTime = new Date().getTime(); + var timeToCall = Math.max(0, 16 - (currTime - lastTime)); + var id = window.setTimeout(function() { callback(currTime + timeToCall); }, + timeToCall); + lastTime = currTime + timeToCall; + return id; + }; + +/* if (!global.cancelAnimationFrame) + global.cancelAnimationFrame = function(id) { + clearTimeout(id); + };*/ +}()); diff --git a/src/pages/page1/page1.spec.ts b/src/pages/page1/page1.spec.ts index b0ba48f..d1c46f7 100644 --- a/src/pages/page1/page1.spec.ts +++ b/src/pages/page1/page1.spec.ts @@ -4,14 +4,14 @@ import { DebugElement } from '@angular/core'; import { Page1 } from "./page1"; import { IonicModule, NavController } from "ionic-angular/index"; -describe('Page1', function () { +describe('Page1', function() { let de: DebugElement; let comp: Page1; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ Page1 ], + declarations: [Page1], imports: [ IonicModule.forRoot(Page1) ], @@ -27,12 +27,13 @@ describe('Page1', function () { de = fixture.debugElement.query(By.css('h3')); }); - it('should create component', () => expect(comp).toBeDefined() ); + it('should create component', () => expect(comp).toBeDefined()); - it('should have expected

text', () => { - fixture.detectChanges(); + it('should have expected

text', async () => { + await fixture.detectChanges(); const h3 = de.nativeElement; - expect(h3.innerText).toMatch(/ionic/i, + + expect(h3.innerHTML).toMatch(/ionic/i, '

should say something about "Ionic"'); }); }); diff --git a/src/pages/page1/page1.ts b/src/pages/page1/page1.ts index e1948ed..4dc4c43 100644 --- a/src/pages/page1/page1.ts +++ b/src/pages/page1/page1.ts @@ -1,15 +1,14 @@ import { Component } from '@angular/core'; -import { NavController } from 'ionic-angular'; - @Component({ selector: 'page-page1', templateUrl: 'page1.html' }) export class Page1 { - constructor(public navCtrl: NavController) { - + //@todo angular DI error when NavController is passed with jest as testrunner + constructor() { + } } diff --git a/src/pages/page2/page2.ts b/src/pages/page2/page2.ts index c44b769..dcdcd19 100644 --- a/src/pages/page2/page2.ts +++ b/src/pages/page2/page2.ts @@ -9,7 +9,7 @@ import { NavController, NavParams } from 'ionic-angular'; export class Page2 { selectedItem: any; icons: string[]; - items: Array<{title: string, note: string, icon: string}>; + items: Array<{ title: string, note: string, icon: string }>; constructor(public navCtrl: NavController, public navParams: NavParams) { // If we navigated to this page, we will have an item available as a nav param @@ -17,7 +17,7 @@ export class Page2 { // Let's populate this page with some filler content for funzies this.icons = ['flask', 'wifi', 'beer', 'football', 'basketball', 'paper-plane', - 'american-football', 'boat', 'bluetooth', 'build']; + 'american-football', 'boat', 'bluetooth', 'build']; this.items = []; for (let i = 1; i < 11; i++) { diff --git a/src/setupJest.ts b/src/setupJest.ts new file mode 100644 index 0000000..1d3bd02 --- /dev/null +++ b/src/setupJest.ts @@ -0,0 +1,2 @@ +import 'jest-preset-angular'; +import './jestGlobalMocks'; diff --git a/test-config/karma-test-shim.js b/test-config/karma-test-shim.js deleted file mode 100755 index 1c1040c..0000000 --- a/test-config/karma-test-shim.js +++ /dev/null @@ -1,21 +0,0 @@ -Error.stackTraceLimit = Infinity; - -require('core-js/es6'); -require('core-js/es7/reflect'); - -require('zone.js/dist/zone'); -require('zone.js/dist/long-stack-trace-zone'); -require('zone.js/dist/proxy'); -require('zone.js/dist/sync-test'); -require('zone.js/dist/jasmine-patch'); -require('zone.js/dist/async-test'); -require('zone.js/dist/fake-async-test'); - -var appContext = require.context('../src', true, /\.spec\.ts/); - -appContext.keys().forEach(appContext); - -var testing = require('@angular/core/testing'); -var browser = require('@angular/platform-browser-dynamic/testing'); - -testing.TestBed.initTestEnvironment(browser.BrowserDynamicTestingModule, browser.platformBrowserDynamicTesting()); \ No newline at end of file diff --git a/test-config/karma.conf.js b/test-config/karma.conf.js deleted file mode 100755 index daac6e5..0000000 --- a/test-config/karma.conf.js +++ /dev/null @@ -1,43 +0,0 @@ -var webpackConfig = require('./webpack.test.js'); - -module.exports = function (config) { - var _config = { - basePath: '', - - frameworks: ['jasmine'], - - files: [ - {pattern: './karma-test-shim.js', watched: true} - ], - - preprocessors: { - './karma-test-shim.js': ['webpack', 'sourcemap'] - }, - - webpack: webpackConfig, - - webpackMiddleware: { - stats: 'errors-only' - }, - - webpackServer: { - noInfo: true - }, - - browserConsoleLogOptions: { - level: 'log', - format: '%b %T: %m', - terminal: true - }, - - reporters: ['kjhtml', 'dots'], - port: 9876, - colors: true, - logLevel: config.LOG_INFO, - autoWatch: true, - browsers: ['Chrome'], - singleRun: false - }; - - config.set(_config); -}; diff --git a/test-config/protractor.conf.js b/test-config/protractor.conf.js deleted file mode 100644 index af689c6..0000000 --- a/test-config/protractor.conf.js +++ /dev/null @@ -1,32 +0,0 @@ -// Protractor configuration file, see link for more information -// https://github.com/angular/protractor/blob/master/lib/config.ts - -/*global jasmine */ -var SpecReporter = require('jasmine-spec-reporter').SpecReporter; - -exports.config = { - allScriptsTimeout: 11000, - specs: [ - '../e2e/**/*.e2e-spec.ts' - ], - capabilities: { - 'browserName': 'chrome' - }, - directConnect: true, - baseUrl: 'http://localhost:8100/', - framework: 'jasmine', - jasmineNodeOpts: { - showColors: true, - defaultTimeoutInterval: 30000, - print: function() {} - }, - useAllAngular2AppRoots: true, - beforeLaunch: function() { - require('ts-node').register({ - project: 'e2e' - }); - }, - onPrepare: function() { - jasmine.getEnv().addReporter(new SpecReporter()); - } -}; diff --git a/test-config/webpack.test.js b/test-config/webpack.test.js deleted file mode 100755 index 27d1dd0..0000000 --- a/test-config/webpack.test.js +++ /dev/null @@ -1,44 +0,0 @@ -var webpack = require('webpack'); -var path = require('path'); - -module.exports = { - devtool: 'inline-source-map', - - resolve: { - extensions: ['.ts', '.js'] - }, - - module: { - rules: [ - { - test: /\.ts$/, - loaders: [ - { - loader: 'ts-loader' - } , 'angular2-template-loader' - ] - }, - { - test: /\.html$/, - loader: 'html-loader' - }, - { - test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/, - loader: 'null-loader' - } - ] - }, - - plugins: [ - new webpack.ContextReplacementPlugin( - // The (\\|\/) piece accounts for path separators in *nix and Windows - /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/, - root('./src'), // location of your src - {} // a map of your routes - ) - ] -}; - -function root(localPath) { - return path.resolve(__dirname, localPath); -} diff --git a/tsconfig.json b/tsconfig.json index 35a6a71..04b30af 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,14 +11,17 @@ "module": "es2015", "moduleResolution": "node", "sourceMap": true, - "target": "es5" + "target": "es5", + "allowJs": true }, "include": [ "src/**/*.ts" ], "exclude": [ "node_modules", - "src/**/*.spec.ts" + "src/**/*.spec.ts", + "src/setupJest.ts", + "src/jestGlobalMocks.ts" ], "compileOnSave": false, "atom": {