Testing help (component.create(bootstrap).then issue


#1

Right now I absolutely despise unit tests! It’s time to ask for advice. I have a “working” solution but it’s a hack and I can’t use any of the waitForElement helpers which I really want to use.

I need to do some work to create the staged component and I’m trying to have a base method that does this for me. This is that code on my base class. You’ll see I can return either the created control (hacked with a timeout) or return the staged prior to create. I also have to cast my component as any, but that is a whole different problem, maybe just a bad definition in the testing module

public static createRendererComponent(json: IControl[], create: boolean = true): Promise<ComponentTester> {
    return new Promise<ComponentTester>((resolve) => {
      let viewModel = new ControlRenderer();

      this.CreateControlSet(json).then((controlSet) => {
        viewModel.controlSet = controlSet;

        let component: any = StageComponent
          .withResources("custom-controls/control-renderer")
          .inView("<control-renderer control-set.bind='controlSet'></control-renderer>")
          .boundTo(viewModel);

        component.bootstrap((aurelia: Aurelia) => {
          aurelia.use
            .standardConfiguration()
            .feature("custom-controls")
            .plugin("aurelia-validation")
        });

        if (create) {
          component.create(bootstrap).then(() => {
            setTimeout(() => {
              resolve(component);
            }, 100);  // This doesn't work without a timeout!
          });
        } else {
          resolve(component);
        }
      });
    });
  }

  public static CreateControlSet(json: IControl[]): Promise<ControlSet> {
    return new Promise<ControlSet>((resolve) => {
      let container = new Container();
      let customControlFactory: ControlFactory = container.get(ControlFactory);

      configureBindingLanguage({ container });
      configureValidation({ container });

      let controlFactoryOptions: ControlFactoryOptions = {
        controls: <IControl[]>json,
        viewStateMode: ViewStateMode.design,
        log: false
      };

      customControlFactory.loadControls(controlFactoryOptions, this).then((controlSet) => {
        resolve(controlSet);
      });
    });
  }

My tests look like this

import { ComponentTester } from "aurelia-testing";
import { TestBase } from "../test-base";


describe("String Control - valid", () => {
    let component: ComponentTester;

    let json = [
        {
            type: "string",
            value: "Testing value",
            id: "st01",
            validation: {
                required: true
            }
        }
    ];

    beforeEach((done) => {
        TestBase.CreateRendererComponent(json).then((c) => {
            component = c;
            done();
        });
    });

    it("content value", (done) => {
        let textbox = document.querySelector<HTMLInputElement>(".e-textbox");
        expect(textbox.value).toEqual(json[0].value);
        done();
    });

    it("control exists", (done) => {
        const controlElement = document.querySelector(`#${json[0].id}_${json[0].type}_wrapper`);
        expect(controlElement !== null).toBe(true);
        done();
    });

    afterEach((done) => {
        component.dispose();
        done();
    });
});

Those tests actually run and run properly. I don’t want that setTimeout in create, but when I do a component.create(bootstrap).then(() => { ...}); I get promise errors. This is the test with the error. In this case by passing false to my CreateRendererComponent it is up to the test to call component.create(). No matter what, I seem to have an issue with component.create not fully finishing before the then()

import { bootstrap } from 'aurelia-bootstrapper';
import { ComponentTester, waitForDocumentElement } from "aurelia-testing";
import { TestBase } from "../test-base";

describe("Label Control", () => {
  let component: ComponentTester;

  let json = [
    {
      type: "label",
      value: "Custom label <b>Only visible at all modes</b> <span style=\"color:red;\">Custom HTML</span>",
      id: "lbl0"
    }
  ];

  beforeEach((done) => {
    TestBase.CreateRendererComponent(json, false).then((c) => {
      component = c;
      done();
    });
  });

  it("content value", (done) => {
    component.create(bootstrap).then(() => {
      component.waitForElement(".label-control-content").then((element) => {
        expect(element.innerHTML).toEqual(json[0].value);
        done();
      })
    });

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


#2

Can you paste some html of your custom controls / control renderer? From what i understood, it seems this control tests fail without timeout?


#3

I cannot unfortunately.

A. I would have to post the entire plugin/feature
B. My company wont’ allow that :frowning:

But I created a simple example of this not working using the bare minimum. The wait for helpers do NOT work for me even in simple cases. What am I doing wrong? I’m not sure if it’s an issue with create or the waitfor helpers

View (Example.html)

<template>
    <span if.bind="show" id="exampleElement">${name}</span>
</template>

ViewModel (Example.ts)

export class Example {
    public name: string = "Hello";
    public show: boolean = false;

    constructor() {
        setTimeout(() => {
            this.show = true;
        }, 250);
    }
}

Test spec

import { ComponentTester, StageComponent, waitFor } from "aurelia-testing";
import { bootstrap } from "aurelia-bootstrapper";
import { Aurelia } from "aurelia-framework";

describe("Example", () => {
    let component: any;

    beforeEach((done) => {
        component = StageComponent
            .withResources("example")
            .inView("<example></example>");

        component.bootstrap((aurelia: Aurelia) => {
            aurelia.use
                .standardConfiguration()
        });
        done();
    });

    it("content value", (done) => {
       // THIS WORKS
        // (<ComponentTester>component).create(bootstrap).then(() => {
        //     setTimeout(() => {
        //         let element = document.querySelector("#exampleElement");
        //         expect(element.innerHTML).toEqual("Hello");
        //         done();
        //     }, 500);
        // });

      // DOES NOT WORK
        (<ComponentTester>component).create(bootstrap).then(() => {
            component.waitForElement("#exampleElement").then((element) => {
                done();
            })
        });

       // DOES NOT WORK
        // waitFor(() => $('#exampleElement')).then((nameElement) => {
        //     expect(nameElement.html()).toBe('Hello');
        //     done();
        // });
    });
});

#4

On a fresh project, using the code you provided works out of the box. Can you help give some more info related to the error you got?


#5

Will do later. I may try a fresh project as well…

Off the top of my head, i get some Promise.race error.


#6

What kind of project did you start with?


#7

Webpack typescript karma
Then copy your code to src
Add npm script test that basically points to karma in test folder
Npm test


#8

Thanks,
I’ll create a clean project hopefully tomorrow. I’m prepping for a demo right now. I am not using webpack in this project. Not sure if it has anything to do with it and I run the tests by au test.


#9

Theoretically it shouldn’t be webpack vs requirejs as it’s more like runtime error, at least for me.


#10

I updated my project by creating a brand new project and moved all of my code to it. All works as expected. I’m still not sure what was wrong even after looking at my source control history.


#11

Probably some duplicate modules stuff. Maybe try clean node modules, package json and try again with the old one?