I've this ViewModel which is a login confirmation page viewmodel:
src/pages/confirm.ts
import { autoinject } from 'aurelia-framework'; import { Router, NavigationInstruction } from 'aurelia-router'; import { ValidationControllerFactory, ValidationController, ValidationRules } from 'aurelia-validation'; import { LoginService } from '../services/login.service'; import { Settings } from '../config/settings'; import { State } from '../services/state'; import { Helpers } from '../services/helpers'; @autoinject export class Confirm { userName: string; error: Error; controller: ValidationController; provider: string; constructor(public service: LoginService, private router: Router, private state: State, private helpers: Helpers, controllerFactory: ValidationControllerFactory) { this.controller = controllerFactory.createForCurrentScope(); this.provider = this.helpers.getUrlParameter('p'); this.userName = this.helpers.getUrlParameter('u'); window.history.replaceState(null, null, '/'); } confirm() { this.controller.validate() .then(() => { this.service.confirm(this.userName) .then(() => { this.router.navigateToRoute('home'); }) .catch((e: Error) => { if (e.name === 'NullInfo') { this.router.navigateToRoute('login'); } else { this.error = e; } }); }) .catch(e => this.error = e); } } ValidationRules .ensure((c: Confirm) => c.userName) .satisfies((value, obj) => obj.service.exists(value)) .withMessage('This user name already exists, please choose another one') .on(Confirm);
I want to test it by unit test using aurelia-cli, I wrote this specs:
test/pages/confirm.spec.ts
import { Router, NavigationInstruction } from 'aurelia-router'; import { Confirm } from '../../../src/pages/confirm'; import { LoginService } from '../../../src/services/login.service'; import { Settings } from '../../../src/config/settings'; import { State } from '../../../src/services/state'; import { Helpers } from '../../../src/services/helpers'; describe('confirm page spec', () => { let service: LoginService; let router: Router; let state: State; let helpers: Helpers; let controllerFactory; let userName; let promise; let resolveCallback; let rejectCallback; beforeEach(() => { // mock Promise promise = { then: r => { resolveCallback = r; return { catch: e => { rejectCallback = e; } } } }; // mock LoginService service = { confirm: u => { userName = u; return promise; } } as LoginService; // mock Router router = { navigateToRoute: r => { } } as Router; state = new State(); helpers = new Helpers(state); spyOn(helpers, 'getUrlParameter') // mock controllerFactory controllerFactory = { createForCurrentScope: () => { } }; spyOn(controllerFactory, 'createForCurrentScope') .and.returnValue({ validate: () => { return promise; } }); }); it('constructor should get url paratemeters', () => { // prepare spyOn(helpers, 'getUrlParameter'); // act let page = new Confirm(service, router, state, helpers, controllerFactory); // verify expect(helpers.getUrlParameter).toHaveBeenCalledWith('p'); expect(helpers.getUrlParameter).toHaveBeenCalledWith('u'); }) });
When I launch au test I get this error :
Chrome 53.0.2785 (Windows 7 0.0.0) ERROR Uncaught Error: Did you forget to add ".plugin('aurelia-validation)" to your main.js? at C:/Users/olefebvre/Source/Repos/chatle.aurelia/wwwroot/scripts/app-bundle.js:2616
How to fix it ?
The full project is on github at https://github.com/aguacongas/chatle.aurelia
UPDATE
I tried to workaround by put the validation in a custom element (and because I need it on an other page)
My component user-name code is :
user-name.html
<template> <div validation-errors.bind="userNameErrors" class.bind="userNameErrors.length ? 'has-error' : ''"> <input class="form-control" name="UserName" value.bind="userName & validate" /> <span class="help-block" repeat.for="errorInfo of userNameErrors"> ${errorInfo.error.message} <span> </div> </template>
user-name.ts
import { autoinject, bindable, bindingMode } from 'aurelia-framework'; import { ValidationControllerFactory, ValidationController, ValidationRules } from 'aurelia-validation'; import { LoginService } from '../services/login.service'; @autoinject export class UserName { @bindable({ defaultBindingMode: bindingMode.twoWay }) userName: string; controller: ValidationController; constructor(private service: LoginService, controllerFactory: ValidationControllerFactory) { this.controller = controllerFactory.createForCurrentScope(); } userNameAvailable(value: string) { return new Promise<boolean>(resolve => { this.service.exists(value) .then(r => resolve(!r)); }) } } ValidationRules .ensure((c: UserName) => c.userName) .satisfies((value, obj) => obj.userNameAvailable(value)) .withMessage('This user name already exists, please choose another one') .on(UserName);
the component spec is:
import {StageComponent} from 'aurelia-testing'; import {bootstrap} from 'aurelia-bootstrapper'; describe('user-name component specs', () => { let component; beforeEach(() => { component = StageComponent .withResources('components/user-name') .inView('<user-name userName.bind="firstName"></user-namet>') .boundTo({ firstName: 'Test' }); }); it('should render first name', done => { component.create(bootstrap).then(() => { const nameElement = document.querySelector('.form-control'); expect(nameElement.attributes['value']).toBe('Test'); done(); }); }); afterEach(() => { component.dispose(); }); });
when I run au test I receive this warning and the component is not created
WARN: '%cUnhandled rejection Error: Did you forget to add ".plugin('aurelia-validation)" to your main.js? at FluentEnsure.assertInitialized (http://localhost:9876/base/wwwroot/scripts/app-bundle.js?f3fa3f9ce9b587af8455ab05a0d491f872123546:9:197927) at FluentEnsure.ensure (http://localhost:9876/base/wwwroot/scripts/app-bundle.js?f3fa3f9ce9b587af8455ab05a0d491f872123546:9:196845) at Function.ValidationRules.ensure (http://localhost:9876/base/wwwroot/scripts/app-bundle.js?f3fa3f9ce9b587af8455ab05a0d491f872123546:9:198743) at Object. (http://localhost:9876/base/wwwroot/scripts/app-bundle.js?f3fa3f9ce9b587af8455ab05a0d491f872123546:9:95049) at Object.execCb (http://localhost:9876/base/wwwroot/scripts/vendor-bundle.js?8e5718043dfbefd1c3ad0ea29315a48c1ff7a645:3785:299) at Object.check (http://localhost:9876/base/wwwroot/scripts/vendor-bundle.js?8e5718043dfbefd1c3ad0ea29315a48c1ff7a645:3774:12) at Object.enable (http://localhost:9876/base/wwwroot/scripts/vendor-bundle.js?8e5718043dfbefd1c3ad0ea29315a48c1ff7a645:3779:58) at Object.enable (http://localhost:9876/base/wwwroot/scripts/vendor-bundle.js?8e5718043dfbefd1c3ad0ea29315a48c1ff7a645:3783:433) at Object. (http://localhost:9876/base/wwwroot/scripts/vendor-bundle.js?8e5718043dfbefd1c3ad0ea29315a48c1ff7a645:3778:436) at http://localhost:9876/base/wwwroot/scripts/vendor-bundle.js?8e5718043dfbefd1c3ad0ea29315a48c1ff7a645:3763:140 at y (http://localhost:9876/base/wwwroot/scripts/vendor-bundle.js?8e5718043dfbefd1c3ad0ea29315a48c1ff7a645:3762:207) at Object.enable (http://localhost:9876/base/wwwroot/scripts/vendor-bundle.js?8e5718043dfbefd1c3ad0ea29315a48c1ff7a645:3777:469) at Object.init (http://localhost:9876/base/wwwroot/scripts/vendor-bundle.js?8e5718043dfbefd1c3ad0ea29315a48c1ff7a645:3772:154) at http://localhost:9876/base/wwwroot/scripts/vendor-bundle.js?8e5718043dfbefd1c3ad0ea29315a48c1ff7a645:3782:308
UPDATE 2
Based on Matthew James Davis answer, I rewrote the spec:
import { Aurelia } from 'aurelia-framework'; import { StageComponent } from 'aurelia-testing'; import { bootstrap } from 'aurelia-bootstrapper'; describe('user-name component specs', () => { let component; beforeEach(() => { component = StageComponent .withResources('components/user-name') .inView('<user-name userName.bind="firstName"></user-namet>') .boundTo({ firstName: 'Test' }); // bootstrap function call the component configure function with an Aurelia instance component.configure = (aurelia:Aurelia) => { aurelia.use .standardConfiguration() .plugin('aurelia-validation'); } }); it('should render user name', done => { component.create(bootstrap).then(() => { const nameElement = document.querySelector('.form-control'); expect(nameElement['value']).toBe('Test'); done(); }); }); afterEach(() => { component.dispose(); }); });
but now I get this error on load module :
WARN: '%cUnhandled rejection Error: Unable to parse accessor function: function (c){__cov_YrDLEqGJfOMLMH3Hdk8_ZA.f['231']++;__cov_YrDLEqGJfOMLMH3Hdk8_ZA.s['724']++;return c.userName;} at ValidationParser.getAccessorExpression (http://localhost:9876/base/wwwroot/scripts/app-bundle.js?4e630e49e067afd65b1f5906dc4064c434c5e5df:9:177914) at ValidationParser.parseProperty (http://localhost:9876/base/wwwroot/scripts/app-bundle.js?4e630e49e067afd65b1f5906dc4064c434c5e5df:9:178586) at FluentEnsure.ensure (http://localhost:9876/base/wwwroot/scripts/app-bundle.js?4e630e49e067afd65b1f5906dc4064c434c5e5df:9:196210) at Function.ValidationRules.ensure (http://localhost:9876/base/wwwroot/scripts/app-bundle.js?4e630e49e067afd65b1f5906dc4064c434c5e5df:9:197995) at Object. (http://localhost:9876/base/wwwroot/scripts/app-bundle.js?4e630e49e067afd65b1f5906dc4064c434c5e5df:9:94307) at Object.execCb (http://localhost:9876/base/wwwroot/scripts/vendor-bundle.js?89c5527ca11655b8716186a7a911ca39c4069f47:3785:299) at Object.check (http://localhost:9876/base/wwwroot/scripts/vendor-bundle.js?89c5527ca11655b8716186a7a911ca39c4069f47:3774:12) at Object.enable (http://localhost:9876/base/wwwroot/scripts/vendor-bundle.js?89c5527ca11655b8716186a7a911ca39c4069f47:3779:58) at Object.enable (http://localhost:9876/base/wwwroot/scripts/vendor-bundle.js?89c5527ca11655b8716186a7a911ca39c4069f47:3783:433) at Object. (http://localhost:9876/base/wwwroot/scripts/vendor-bundle.js?89c5527ca11655b8716186a7a911ca39c4069f47:3778:436) at http://localhost:9876/base/wwwroot/scripts/vendor-bundle.js?89c5527ca11655b8716186a7a911ca39c4069f47:3763:140 at y (http://localhost:9876/base/wwwroot/scripts/vendor-bundle.js?89c5527ca11655b8716186a7a911ca39c4069f47:3762:207) at Object.enable (http://localhost:9876/base/wwwroot/scripts/vendor-bundle.js?89c5527ca11655b8716186a7a911ca39c4069f47:3777:469) at Object.init (http://localhost:9876/base/wwwroot/scripts/vendor-bundle.js?89c5527ca11655b8716186a7a911ca39c4069f47:3772:154) at http://localhost:9876/base/wwwroot/scripts/vendor-bundle.js?89c5527ca11655b8716186a7a911ca39c4069f47:3782:308 From previous event: at DefaultLoader.loadModule (http://localhost:9876/base/wwwroot/scripts/vendor-bundle.js?89c5527ca11655b8716186a7a911ca39c4069f47:11444:14)
UPDATE 3
Ok, the error is throw only when I cover app-bundle.js
in karma, if I comment it the parser error is not throwed :
preprocessors: { [project.unitTestRunner.source]: [project.transpiler.id], //[appBundle]: ['coverage'] },
But the value is not bound to the input field