How to unit test form validation in Aurelia
Asked Answered
B

1

8

I am trying to implement some unit tests on a form to see if the validation rules are working as expected.

from this page : https://github.com/aurelia/testing/issues/63

I found this implementation : https://github.com/aurelia/validation/blob/master/test/validate-binding-behavior.ts

and I tried to implement it in my project

login.spec.js

import {bootstrap} from 'aurelia-bootstrapper';
import {StageComponent} from 'aurelia-testing';
import {PLATFORM} from 'aurelia-pal';
import { configure, blur, change } from './shared';
import { Login } from './login';


describe('ValidateBindingBehavior', () => {
    it('sets validateTrigger', (done) => {
        const component = StageComponent
            .withResources(PLATFORM.moduleName('features/account/login/login'))
            .inView('<login></login>')
            .boundTo({});
        component.bootstrap(configure);
        let viewModel;
        const renderer = { render: jasmine.createSpy() };
        component.create(bootstrap)
            // grab some references.
            .then(() => {
            viewModel = component.viewModel;
            viewModel.controller.addRenderer(renderer);
        })

            .then(() => expect(viewModel.controller.errors.length).toBe(0))
            .then(() => blur(viewModel.firstName))
            .then(() => expect(viewModel.controller.errors.length).toBe(1))
            .then(() => component.dispose())
            .then(done);
    });
});

login.js

import { inject, NewInstance } from 'aurelia-dependency-injection';
import {  ValidationController } from 'aurelia-validation';
import { User } from './login.model';

@inject(NewInstance.of(ValidationController), User)
export class Login {
  constructor(controller, user) {
    this.controller = controller;
    this.firstName = '';
    this.lastName = '';
    this.userName = '';
    this.showForm = true;
    this.user = user;
  }
};

login.model.js

import {ValidationRules} from 'aurelia-validation';

export class User {
    firstName = '';
    lastName = '';
    userName = '';

    constructor() {
      ValidationRules
        .ensure('firstName')
        .required()  
        .ensure('lastName')
        .required()
        .minLength(10)
        .ensure('userName')
        .required()
        .on(this);
    }
  }

shared.js

import {DOM, PLATFORM} from 'aurelia-pal';

export function configure(aurelia) {
    return aurelia.use
    .standardConfiguration()
    .plugin(PLATFORM.moduleName('aurelia-validation'))
}
export function blur(element) {
    element.dispatchEvent(DOM.createCustomEvent('blur', {}));
    return new Promise(resolve => setTimeout(resolve));
}
export function change(element, value) {
    element.value = value;
    element.dispatchEvent(DOM.createCustomEvent('change', { bubbles: true }));
    return new Promise(resolve => setTimeout(resolve));
}

and here is a piece of html markup :

    <div>
      <input ref="firstName" type="text" value.bind="user.firstName & validateOnBlur"
        validation-errors.bind="firstNameErrors">
      <label style="display: block;color:red" repeat.for="errorInfo of firstNameErrors">
        ${errorInfo.error.message}
      </label>
    </div>
    <div>

in the spec, when I blur the element I expect to get one error, but "controller.errors" is always an empty array. and I get this for the failed message :

Error: Expected 0 to be 1.

UPDATE 1:

I tried to validate manually, so I added this in my spec :

.then(()=> 
        viewModel.controller.validate({object: viewModel.user, propertyName: 'firstName' })

)

and it works fine, but the blur and change functions don't trigger validation.

UPDATE 2:

I changed it like "Sayan Pal" suggested. and it works now but with a tiny problem. when I "blur" the element once it shows one error. but when I "blur" several elements ( let's say three ) it doesn't show the last error. in this case controller.errors.length would be 2.

I can blur the last element two times to get the correct length of errors. but I think there should be a better solution.

.then(() => blur(viewModel.firstName))
.then(() => blur(viewModel.userName))
.then(() => blur(viewModel.lastName))
.then(() => blur(viewModel.lastName))
Bendix answered 24/7, 2019 at 6:40 Comment(1)
You're probably going to have to debug into the code to find out what's wrong.Harris
Z
3

I think instead of using createCustomEvent you simply need to do element.dispatchEvent(new Event("blur"));. Same goes for change event.

This has always worked for me, and hope it will help you too :)

On related note, I use a default ValidationController generator factory method that ensures the default trigger as follows.

import { validateTrigger, ValidationControllerFactory } from "aurelia-validation";

...

const validationController = validationControllerFactory.createForCurrentScope();
validationController.changeTrigger(validateTrigger.changeOrBlur);

Update after OP updated the question

It is difficult to say why it is happening, without debugging. As I don't see any imminent problem in your test code, my assumption is that it is a timing issue. The main idea is that you need to wait for the change to happen. There are several ways you can do it, all of those needs change in how you are asserting.

One way to do it is to employ a promise with a timeout that polls in a regular interval for the change. And then wait for the promise.

Or you can use TaskQueue to queue your assertion, and after the assertion call done. This looks something like below.

new TaskQueue().queueMicroTask(() => {
  expect(foo).toBe(bar);
  done();
});

Other alternative is to use cypress as an e2e test framework. Out of the box, Cypress waits for the change to happen until times out.

Choose what best fits your need.

Zonnya answered 30/7, 2019 at 10:31 Comment(1)
Thank you, it almost works but with a problem. I have updated the question, could you please check it.Bendix

© 2022 - 2024 — McMap. All rights reserved.