[solved] Testing <compose>

How I “solved” it:

Guides:

Hi,

  • The problem I am having is during testing, au test
    • dev-server, au run, works fine
  • CodeSandBox
    • As suggested by @Sayan751
    • I don’t know how to get the .spec files working in CodeSandBox
    • All files works in local env (my computer / aurelia-cli)
  • I am stuck trying to test a <compose> element
    • ref-ed element within compose <template> tag is undefined when console.log-ed (see Spec code below)
    • I am unsure how to call the activate(model) method when testing (see Spec code below)

Thanks for looking at this.

I’m using:

  • .js (babel)
  • karma/jasmine
  • alameda
  • aurelia-cli: 1.3.0
  • aurelia-testing: 1.0.0

Parent (HTML):

<template>
  <compose view-model="./compose-elem" model.bind="model"></compose>
</template>

ViewModel:

import { inject, observable } from "aurelia-framework"

@inject(Element)
export class ComposeElem {
  @observable
  stuff

  constructor (Element) {
    this.element = Element
  }

  activate (model) {
    this.stuff = model
  }

  attached () {
    console.log("dev-server compose-elem ref check", this.divReference)
  }
}

View:

<template>
  <div ref="divReference">
    Compose Element
    <br />
    &#64;observable stuff = ${ stuff }
  </div>
</template>

Spec:

import { bootstrap } from "aurelia-bootstrapper"
import { StageComponent } from "aurelia-testing"

fdescribe("Compose-Elem", () => {
  let component

  beforeEach(async (done) => {
    component = StageComponent.withResources("./compose-elem")
      .inView(
        `
        <compose
          view-model="./compose-elem"
          model.bind="model">
        </compose>
      `,
      )
      .boundTo({
        model: "data",
      })

    done()
  })

  afterEach(async (done) => {
    component.dispose()

    done()
  })

  describe("Test Group", async () => {
    it("test unit", async (done) => {
      component
        .create(bootstrap)
        // .then(() => component.activate()) // Failed: component.activate is not a function
        // .then(() => component.viewModel.activate()) // Failed: component.viewModel.activate is not a function
        .then(async () => {
          const viewModel = component.viewModel
          console.log("test-server compose-elem ref check", viewModel.divReference) // undefined
          console.log("test-server compose-elem activate() check", viewModel.stuff) // undefined (cannot call activate())

          done()
        })
        .catch((e) => {
          fail(e)
          done()
        })
    })
  })
})

Edits:

  • Change confusing element name
  • Add more details about the problem
  • Edit/Add code
  • Edit/Add CodeSandBox
  • Add guide links from others’ post
  • Add my “solution”
1 Like

Hi @LetsZiggy, is it possible for you to share a reproducable example? You can also create a codesandbox if you want. Here is a starter app: https://codesandbox.io/s/aurelia-typescript-sandbox-zw9zjy0683.

On a different note, I am curious about if you are trying to create your own compose element or it is just a regular custom element with the same name.

1 Like

Hi @Sayan751,
CodeSandBox link
I’ve updated my original post to further clarify the problem. Sorry for the confusing names.
All code in dev-server, au run, works
Only in test-server, au test, I have problems

I’m using <compose> in the parent html; the example uses app.html
I’m cannot get the test running on CodeSandBox but it runs locally, au test, for what it’s worth

Thanks again for the help

Edits:

  • Add details
  • Edit CodeSandBox link
1 Like

I have arranged some example in this temp repo https://github.com/Sayan751/au-compose-el-unit-test.In short, when testing the a "compose"d custom element, it is better to handle the lifecycle manually.

I would suggest to fork it, so that I can delete it.

2 Likes

@Sayan751,
Thanks. I’ve forked the repo.

I’ll update this post after I’ve written the test.
Didn’t work for me even with au new
More details below

Thanks again.

Edits:

  • Minor post edit
  • Add more details
1 Like

@LetsZiggy No Problem :slight_smile:

1 Like

@Sayan751,
I cannot get the test to work even with au new

Below are the terminal outputs for au test --watch

compose-elem.spec.js => Default jasmine.DEFAULT_TIMEOUT_INTERVAL

...
Starting 'karma'...
22 04 2020 16:47:37.100:WARN [karma]: No captured browser, open http://localhost:9876/
22 04 2020 16:47:37.133:INFO [karma-server]: Karma v4.4.1 server started at http://0.0.0.0:9876/
22 04 2020 16:47:37.134:INFO [launcher]: Launching browsers ChromeHeadless with concurrency unlimited
22 04 2020 16:47:37.144:INFO [launcher]: Starting browser Chrome
22 04 2020 16:47:37.655:INFO [HeadlessChrome 81.0.4044 (Linux 0.0.0)]: Connected on socket bekprfv7SpeUetCQAAAA with id 7053135
HeadlessChrome 81.0.4044 (Linux 0.0.0) Compose-Elem Test Group test unit FAILED
  Error: Timeout - Async function did not complete within 5000ms (set by jasmine.DEFAULT_TIMEOUT_INTERVAL)
      at <Jasmine>
HeadlessChrome 81.0.4044 (Linux 0.0.0): Executed 1 of 2 (1 FAILED) (skipped 1) (5.032 secs / 5.017 secs)
TOTAL: 1 FAILED, 0 SUCCESS

compose-elem.spec.js => it("test unit", async () => { ... }, 10000)

...
Starting 'karma'...
22 04 2020 16:51:39.042:WARN [karma]: No captured browser, open http://localhost:9876/
22 04 2020 16:51:39.071:INFO [karma-server]: Karma v4.4.1 server started at http://0.0.0.0:9876/
22 04 2020 16:51:39.072:INFO [launcher]: Launching browsers ChromeHeadless with concurrency unlimited
22 04 2020 16:51:39.091:INFO [launcher]: Starting browser Chrome
22 04 2020 16:51:39.593:INFO [HeadlessChrome 81.0.4044 (Linux 0.0.0)]: Connected on socket 7gOTejrCWW4t8ohoAAAA with id 4836018
HeadlessChrome 81.0.4044 (Linux 0.0.0) Compose-Elem Test Group test unit FAILED
  Error: Timeout - Async function did not complete within 10000ms (custom timeout)
      at <Jasmine>
HeadlessChrome 81.0.4044 (Linux 0.0.0): Executed 1 of 2 (1 FAILED) (skipped 1) (10.035 secs / 10.017 secs)
TOTAL: 1 FAILED, 0 SUCCESS

custom-elem.spec.js for comparison (with .manuallyHandleLifecycle())

Starting 'karma'...
22 04 2020 18:13:09.203:WARN [karma]: No captured browser, open http://localhost:9876/
22 04 2020 18:13:09.227:INFO [karma-server]: Karma v4.4.1 server started at http://0.0.0.0:9876/
22 04 2020 18:13:09.228:INFO [launcher]: Launching browsers ChromeHeadless with concurrency unlimited
22 04 2020 18:13:09.237:INFO [launcher]: Starting browser Chrome
22 04 2020 18:13:09.717:INFO [HeadlessChrome 81.0.4044 (Linux 0.0.0)]: Connected on socket bijYquQyX4vYXozJAAAA with id 57667270
LOG: 'dev-server compose-elem stuffChanged', 'new data', 'old data'
HeadlessChrome 81.0.4044 (Linux 0.0.0) Compose-Elem Test Group test unit FAILED
  Error: Timeout - Async function did not complete within 10000ms (custom timeout)
      at <Jasmine>
LOG: 'dev-server custom-elem stuffChanged', 'new data', 'old data'
LOG: 'test-server custom-elem ref check', <div ref="divReference" class="au-target" au-target-id="4">Custom Element<br>@bindable stuff = new data </div>
LOG: 'test-server custom-elem @bindable check', 'new data'
HeadlessChrome 81.0.4044 (Linux 0.0.0): Executed 2 of 3 (1 FAILED) (skipped 1) (10.064 secs / 10.043 secs)
TOTAL: 1 FAILED, 1 SUCCESS

Edits:

  • Minor post edit
  • Update git repo
1 Like

For a timeout failure like this, easiest way to debug is to return after each await until you hit the line that’s hanging everything.

1 Like

@bigopon,
ok thanks. will try
Found the problem: component.viewModel.activate("data")
I don’t know the way forward from this … any advice?
Thanks

Test:

import { bootstrap } from "aurelia-bootstrapper"
import { StageComponent } from "aurelia-testing"

fdescribe("Compose-Elem", () => {
  let component

  beforeEach(async (done) => {
    /*
    component = StageComponent
      .withResources("elements/compose-elem")
      .inView(`
        <compose>
        </compose>
      `)
      .manuallyHandleLifecycle()
    */

    /**/
    component = StageComponent
      .withResources("elements/compose-elem")
      .inView(`
        <compose
          view-model="elements/compose-elem"
          model.bind="model">
        </compose>
      `)
      .boundTo({ model: "new data" })
      .manuallyHandleLifecycle()
    /**/

    done()
  })

  afterEach(async (done) => {
    component
      .dispose()

    done()
  })

  describe("Test Group", async () => {
    it("test unit", async (done) => {
      await component.create(bootstrap)
      await component.bind()
      component.viewModel.activate("new data")

      done()
      /*
      - Error on activate()
      - Success on bind() and create()
      */

      // await component.attached()

      // const element = component.element
      // expect(element.querySelector("div").textContent)
      //   .toContain("stuff = new data")

      // const viewModel = component.viewModel
      // console.log(
      //   "test-server compose-elem ref check",
      //   viewModel.divReference,
      // )
      // console.log(
      //   "test-server compose-elem activate() check",
      //   viewModel.stuff,
      // )

      // await component.detached()
      // await component.unbind()

      // done()
    })
  })
})

Edits:

  • Update post after trying @bigopon 's advice

@LetsZiggy As you need to test your component, you need to change your inView template. Also as the “model” for the component will be bound via the component.viewModel.activate method, you can also drop the .boundTo. In summary the changes look as follows (from your compose-elem.spec.js):

    component = StageComponent
      .withResources("elements/compose-elem")
-     .inView(`
-      <compose
-        view-model="elements/compose-elem"
-        model.bind="model">
-      </compose>
-     `)
-     .boundTo({ model: "new data" })
+     .inView(`<compose-elem></compose-elem>`)
1 Like

it’s the same either way, i.e. as code below or as code in previous post

anyway i’ll chalk it up to either my computer or my au setup (i’ve not tried with webpack/requirejs or jest or both)
i’ll just use regular custom-elements and au.enhance if i need it to be dynamic

my bad … I misread your answer :grimacing:. You correctly showed to test as custom-element.

thanks for taking your time looking into this @Sayan751 & @bigopon

Ignore the code below…

...
component = StageComponent
  .withResources("elements/compose-elem")
  .inView(`<compose></compose>`)
  .manuallyHandleLifecycle()
...

Edits:

  • Add more details
  • Add where I went wrong…please see post

my bad … I misread the answer given :grimacing:. You correctly showed to test as custom-element.

I think I’ve figured out how to test a <compose> element. Test <compose> as custom element; i.e. .inView('<compose-elem></compose-elem>')

github repo

Thanks @Sayan751 and @bigopon for helping me on this

import { bootstrap } from "aurelia-bootstrapper"
import { StageComponent } from "aurelia-testing"

const sleep = (ms = 0) => new Promise((resolve) => setTimeout(resolve, ms))

describe("Compose-Elem", () => {
  let component

  beforeEach(async (done) => {
    /*
    component = StageComponent
      .withResources("compose-elem")
      .inView(`<compose></compose>`)
      .manuallyHandleLifecycle()
    */

    component = StageComponent
      .withResources("compose-elem")
      .inView(`<compose-elem></compose-elem>`)
      .manuallyHandleLifecycle()

    done()
  })

  afterEach(async (done) => {
    component
      .dispose()

    done()
  })

  describe("Test Group", async () => {
    it("test unit", async (done) => {
      await component.create(bootstrap)
      await component.bind(undefined)
      component.viewModel.activate("new data")
      await component.attached()

      const viewModel = component.viewModel
      const element = component.element
      expect(element.querySelector("div").textContent)
        .toContain("stuff = new data")

      viewModel.stuff = "extra new data"
      await sleep(200)
      expect(element.querySelector("div").textContent)
        .toContain("stuff = extra new data")

      await component.detached()
      await component.unbind()

      done()
    })
  })
})

Edits:

  • Update code in post
  • Add where I went wrong…please see post
1 Like