Search code examples
javascriptunit-testingjasminekarma-runneraurelia

Underterministic unit-test issues with aurelia-testing


This question is related to my other question. I am briefly repeating some of the details. I want to unit test my aurelia custom elements, which look like below.

// BaseText.ts
import { bindable } from "aurelia-framework";
import { BaseI18N } from "aurelia-i18n";

export class BaseText extends BaseI18N {
    @bindable public value: string;
    @bindable public i18nKey: string;
}

// NormalText.html; NormalText inherits from BaseText
<template>
    <span t.bind="i18nKey">${value}</span>
</template>

// ValueText.html; ValueText inherits from BaseText
<template>
    <strong t.bind="i18nKey">${value}</strong>
</template>

Now, I want to test the views for these custom elements, which are something of below form.

describe("View specs", () => {

    it("Should render the text in a strong element without any additional style", (done) => {
        const randomId = randomIdGenerator();
        const component = StageComponent
            .withResources("ValueText/ValueText")
            .inView(`<value-text id='${randomId}' value='Hello Static Text'></value-text>`);

        component
            .create(bootstrap)
            .then(() => {
                const strongElement: HTMLElement = document.querySelector(`value-text#${randomId}>strong`);
                console.log(strongElement);
                expect(strongElement).toBeDefined();
                expect(strongElement.style.cssText).toBeFalsy();
                expect(Array.from(strongElement.classList).filter(item => !item.startsWith("au-")).length).toBe(0);
            })
            .catch(e => { console.log(e.toString()) })
            .finally(() => {
                component.dispose();
                done();
            });
    });

    it("Should render a dynamic text", (done) => {
        const randomId = randomIdGenerator();
        const component1 = StageComponent
            .withResources("ValueText/ValueText")
            .inView(`<value-text id='${randomId}' value.bind='boundValue'></value-text>`)
            .boundTo({ boundValue: "Hello Value Text" });

        component1
            .create(bootstrap)
            .then(() => {
                const strongElement = document.querySelector(`value-text#${randomId}>strong`);
                expect(strongElement.textContent.trim()).toBe('Hello Value Text');
            })
            .catch(e => { console.log(e.toString()) })
            .finally(() => {
                component1.dispose();
                done();
            });
    });

    it("Should render the provided static text without i18nKey", (done) => {
        const randomId = randomIdGenerator();
        const component2 = StageComponent
            .withResources("ValueText/ValueText")
            .inView(`<value-text id='${randomId}' value='Hello Static Text'></value-text>`);

        component2
            .create(bootstrap)
            .then(() => {
                const strongElement = document.querySelector(`value-text#${randomId}>strong`);
                expect(strongElement.textContent.trim()).toBe('Hello Static Text');
            })
            .catch(e => { console.log(e.toString()) })
            .finally(() => {
                component2.dispose();
                done();
            });
    });

});

Though here I have shown the tests for ValueText, the tests for NormalText also looks very similar.

When I run the test suites for both of these elements individually, all tests run fine. However, when those are run together, the undefined element is returned by document.querySelector('...') for some test cases. Which results in not running the expect assertions. This resulted in below error.

Spec 'HERE_GOES_SPEC_NAME' has no expectations.

After some debugging, it seems that when the tests are run together, for the test suite which runs second, view for the custom elements are not even created. It creates a DOM element as follows.

<normal-text value="Hello Static Text"></normal-text>

But the innerHTML part of the view is missing. More formally, during these tests the associated viewmodel/controller remains missing in aurelia-templating, which results in the malformed view.

What should I change, so that these test cases become deterministic?

Additional info: I have created a GitHub repo, so that interested reader/user can reproduce the problem. Please keep in mind that you might have run the tests multiple times to replicate the issue.


Solution

  • After long sessions of debugging, it turned out that for the failing test specs (i.e. the test suite that runs second), the metadata was incorrect. For example, if the test suite for NormalText is run first, then for the test suite of ValueText, metadata of ValueText reflected metadata of NormalText; more specifically the HTML (view) resources of NormalText.

    There might be a bug somewhere or a problem with how I have written the tests.

    However, I have found a simple workaround by applying @customElement("element-name") decorator on both of the classes. And that indeed solved the problem.