2
votes

using aurelia-validation@^1.0.0-beta.1.0.0",

The posts below discuss testing a custom element with aurelia validation so the plugin is loaded during test time. I've tried many of the solutions, but nothing works (probably because i am not using a custom element). I do not want to use a custom element because the view-model is involved in dynamic composition. After the form is successfully posted i substitute a "thank you" view-model to clear everything out and show that the form was submitted.

How to load an Aurelia plugin in Karma

Load aurelia-validation plugin during Jasmine unit tests - with webpack

https://github.com/aurelia/validation/issues/377

I receive the same response in many of those posts when i import my view-model into my test:

Error: (SystemJS) Did you forget to add ".plugin('aurelia-validation)" to your main.js?

Here is an abbreviated version of my view-model src and test if it helps...

employment.html

<template>
  <section class="au-animate">
    <compose view.bind="view"></compose>
  </section>
</template>

employment.js

import {inject} from 'aurelia-framework';
import {HttpClient, json} from 'aurelia-fetch-client';
import { sendto } from './env';
import Canidate from './canidate.js';
import 'fetch';
import { ValidationControllerFactory, ValidationRules } from 'aurelia-validation';
import {BootstrapFormRenderer} from './resources/renderers/bootstrap-form-renderer'

@inject(HttpClient, ValidationControllerFactory)
export class Employment {

  constructor(http, controllerFactory) {
    this.view = "./employment-form.html";
    this.helpMsg = null
    this.loading = false;
    this.canidate = new Canidate();
    this.controller = controllerFactory.createForCurrentScope();
    this.controller.addRenderer(new BootstrapFormRenderer());

    this._http = http;
  }

  submit() {
    this.loading = true;

    return this.controller.validate()
      .then(result => {
        if (result.isValid) {
          return this._post();
        }
      })
      .catch(error => {
        this.canidate.error = error;
        return this._post();
      })
      .then(() => this.loading = false);
  }

  _post() {
    return this._http.fetch(sendto(), {
      method: `POST`,
      body: json(this.canidate)
    }).then(response => response.json())
      .then(() => {
        this.helpMsg = null;
        this.view = "./thanks.html";
      }).catch(err => {
        this.helpMsg = `there was an error submitting your form. ` + 
          `Please try again or contact us direct from the Contact Page`;
      });
  }
}

ValidationRules
  .ensure(a => a.fullName).required()
  .ensure(a => a.age).required()
  .ensure(a => a.city).required()
  .ensure(a => a.phone).required().matches(/\d{3}-\d{3}-\d{4}/)
  .withMessage('Please format your phone number like ###-###-####')
  .ensure(a => a.email).email()
  .ensure(a => a.experience).required()
  .on(Canidate);

employment.spec.js

import {Employment} from '../../src/employment';
import * as env from '../../src/env';
import Canidate from '../../src/canidate';
import {ValidationControllerFactory, ValidationController} from 'aurelia-validation';
import {StageComponent, ComponentTester} from 'aurelia-testing';
import {bootstrap} from 'aurelia-bootstrapper';

class HttpStub {
  constructor() {
    this.url = null;
    this.config = null;
    this.resolve = null;
    this.reject = null;
  }

  fetch(url, blob) {
    this.url = url;
    this.blob = blob;
    let promise = new Promise((resolve, reject) => {
      this.resolve = resolve;
      this.reject = reject;
    });
    return promise;
  }

  configure(func) {
    this.config = func;
  }
}

describe('the Employment module', () => {
  let sut;
  let http;
  let controller;
  let component;

  beforeAll(done => {
    component = StageComponent.withResources().inView('<div></div>').boundTo({});
    component.bootstrap(aurelia => aurelia.use.standardConfiguration().plugin('aurelia-validation'));
    component.create(bootstrap).then(done);
  });

  afterAll(() => {
    component.dispose();
  });

  beforeEach(() => {
    const factory = jasmine.setupSpy('factory', ValidationControllerFactory.prototype);
    controller = jasmine.setupSpy('controller', ValidationController.prototype);
    http = new HttpStub();
    factory.createForCurrentScope.and.returnValue(controller);
    controller.validate.and.returnValue(Promise.resolve({valid: true}));

    sut = new Employment(http, factory);
  });

  it('initiates the current view model variables', () => {
    expect(sut.view).toEqual('./employment-form.html');
    expect(sut.canidate).toEqual(new Canidate());
    expect(sut.helpMsg).toEqual(null);
    expect(sut.loading).toBeFalsy();
  });

  it('fetches with post data', done => {
    const expectJson = '{"fullName":"tom","age":2,"city":"lex","phone":"1900",' + 
      '"email":"@.com",'"experience":"none"}';
    sut.canidate.fullName = 'tom';
    sut.canidate.age = 2;
    sut.canidate.city = 'lex';
    sut.canidate.phone = '1900';
    sut.canidate.email = '@.com';
    sut.canidate.experience = 'none';

    spyOn(env, "sendto").and.returnValue('test');
    http.itemStub = sut.canidate;

    sut.submit().then(() => {
      expect(http.url).toEqual('test');
      expect(http.blob.method).toEqual('POST');
      expect(sut.loading).toBeFalsy();
      let fr = new FileReader();
      fr.addEventListener('loadend', function() {
        expect(fr.result).toEqual(expectJson);
        done();
      });
      fr.readAsText(http.blob.body);
    });

    expect(sut.loading).toBeTruthy();
    setTimeout(() => http.resolve({ json: () => sut.canidate }));
  });

  it('successfully posts the data', done => {
    sut.submit().then(() => {
      expect(sut.helpMsg).toEqual(null);
      expect(sut.view).toEqual('./thanks.html');
      done();
    });

    setTimeout(() => http.resolve({ json: () => {} }));
  });

  it('shows a help msg when posts fails', done => {
    sut.submit().then(() => {
      expect(sut.helpMsg).toContain('there was an error submitting your form');
      expect(sut.view).toEqual('./employment-form.html');
      done();
    });

    setTimeout(() => http.reject());
  });
});

UPDATE

I might be getting somewhere? I did what @MartinMason suggested and moved my ValidationRules into the ctor of the view-model and instead of using .on(Candidate) i did .on(this.candidate). This allowed my tests to run, but every test for my employment view-model failed with the same message Did you forget to add ".plugin('aurelia-validation)" to your main.js?. Then I went to the github link at the beginning of this post and added in the beforeAll that jeremy suggested and i received an error from what seems like the ValidationRules.

Error: Unable to parse accessor function:
function (a) {
      ++cov_x4vangzdw.f[2];
      ++cov_x4vangzdw.s[10];
      return a.fullName;
    } (line 57)
getAccessorExpression
parseProperty
ensure
ensure
Employment
tryCatchReject@jspm_packages/system-polyfills.src.js:1188:34
runContinuation1@jspm_packages/system-polyfills.src.js:1147:18
when@jspm_packages/system-polyfills.src.js:935:20
run@jspm_packages/system-polyfills.src.js:826:17
_drain@jspm_packages/system-polyfills.src.js:102:22
drain@jspm_packages/system-polyfills.src.js:67:15
TypeError: undefined is not an object (evaluating 'sut.submit') (line 160)
tryCatchReject@jspm_packages/system-polyfills.src.js:1188:34
runContinuation1@jspm_packages/system-polyfills.src.js:1147:18
when@jspm_packages/system-polyfills.src.js:935:20
run@jspm_packages/system-polyfills.src.js:826:17
_drain@jspm_packages/system-polyfills.src.js:102:22
drain@jspm_packages/system-polyfills.src.js:67:15

and here is Canidate.js where fullname is coming from

export default class {
  fullName = '';
  age = null;
  city = '';
  phone = '';
  email = '';
  experience = '';
}

So if i change ValidationRules.ensure(a => a.fullname) to ValidationRules.ensure('fullName') the error goes away (I guess I need to add the ValidationParser in the test?...haven't gotten that far yet, I just want my prior tests to pass first)

UPDATE

After fixing my tests to account for aurelia-validation they are passing again. So moving the ValidationRules to the ctor and then adding the aurelia-testing objects back into the beforeAll function made it work. I can still do .on(Canidate) instead of .on(this.canidate) in the ValidationRules and the tests/site run well, but not sure the impact this will have when actually testing the validation rules. I guess it would still be nice if jeremy can let us know if this is the correct.

1
include your test codeJeremy Danyow
@JeremyDanyow, i added all my tests as they looked before validation was added to employment.js view-model. I have tried many things not shown that in those links i reference in the post, some of which you suggest like bootstrapping with ComponentTester to add the plugin in a beforeAll. I also tried a global beforeEachjmzagorski
i was able to replicate this by cloning the skeleton-esnext and adding the two files above and aurelia-validation from jspm. In the employment view-model i took out everything except the ValidationControllerFactory call in the ctor and the ValidationRules. In the test file i just left the first test. The tests ran fine when I initially cloned the repo, but then failed when i performed the above steps.jmzagorski
I feel like this could be improved upon and PR'd back to the docs if anyone is interested.PW Kad

1 Answers

3
votes

I've forked the skeleton project and modified the skeleton-typescript-aspnetcore project to include aurelia-validation integration in the integrate_validation branch. https://github.com/SoftwareMasons/skeleton-navigation/tree/integrate_validation When ValidationRules are applied on an instance in the class constructor, I can get jasmine tests to pass. However, when rules are applied on a class, I can never get the static ValidateRules.initialize method to ever be called correctly which is why the errors are being thrown in the assertInitialized method of aurelia-validation.

I've included everything that I've tried to get it to work in the registionForm.spec.ts file along with a short description of what I tried. Hopefully, my failures will help you find the road to success. For now, this an acceptable workarond to the issues but if you find a better way, I'd appreciate you letting us know.