Test with Jest and calling public method on component

I have a component that works as expected. I have also a working set up with Aurelia and Jest. I have also created some tests with something similar to below:

component = StageComponent
    model = {
      "strongMessage":"Yo"
    };
    .withResources('elements/message-bar')
    .inView('<message-bar strong-message.one-time="strongMessage"></message-bar>')
    .boundTo(model);

component.bootstrap((aurelia: Aurelia) => {
      return aurelia.use
        .standardConfiguration();
    });
    
    await component.create(bootstrap);
    const view:Element = component.element;
      expect(view.querySelector(".someclass").textContent.trim()).toBe('Yo');

Above works fine and as expected. In above example all changes to the UI is dependent on the model.

But if I call a public method that changes the values of the viewmodel

const messageBar = Container.instance.get(MessageBar) as MessageBar;
messageBar.DoStuff();

It is not reflected in the UI. The method updates a property (strongMessage) that are used in the view.

<span class="ms-fontWeight-semibold">${strongMessage}</span>

Is there anything I need to do to make it rerender when under test. It works just fine in the real app.

The point of the test is to make sure that the view presents the strong messsage and that the method show actually sets the property correctly. So it does not feel correct just to test the strongmessage value on the viewmodel.

Custom elements and attributes do not register their view model instances with the root container. When you do Container.instance.get(MessageBar), you are actually creating a new instance of that component.

What can be done is to get the element and then get the view model based on the controller instead

host.querySelector('message-bar').$au.controller
  .viewModel
  .DoStuff();

In v2, we have an official API for retrieving the controller of a custom element. The equivalent of it in v2 is

CustomElement.for(host.querySelector('message-bar'))
  .viewModel
  .DoStuff();
1 Like

Thank you @bigopon for your great feedback.

That did not really solve the issue for me but got me in the correct direction. I was testing some properties for equality and have found this (which probably everybody already knows)

    // below lines are all true
    // component.host.querySelector('message-bar') == component.element
    // component.element.au.controller == component.element.au['message-bar']
    // component.rootView.firstChild == component.host 
    // component.viewModel == component.element.au.controller.viewModel

So to me it looks like the easiest way to get to the view and the view-model is:

component.element
component.viewModel

And then updating with

component.viewModel.DoStuff();

This fails (as before)

expect(component.element.querySelector(".someclass").textContent.trim()).toBe('Yo');

This fails too

const queue = component.rootView.container.get(TaskQueue) as TaskQueue;
    queue.queueTask(() => {
      expect(component.element.querySelector(".someclass").textContent.trim()).toBe('Yo');
    });
queue.flushTaskQueue();

But this works fine

setTimeout(()=>{
      expect(component.element.querySelector(".someclass").textContent.trim()).toBe('Yo');
    },0);

To me it looks like I need to wait for the setTimeout()

This test is fine by me and I will keep it like this unless anyone has a strong argument against it.

Happy coding :slight_smile:

1 Like

Yes, the behavior you observed is because the component tester automatically retrieve the first view model controller (either custom element/attribute) to return it with component.viewModel. What you have works fine and simpler for single element/attribute test case. If you ever have more than one, then doing it the query + $au way will give easier time.
Regardless, happy coding :slight_smile:

1 Like

Excellent! Does it make sense to you that I hade to use

setTimeout( ()=> expect(),0);

to get the updated value from the view?

If anyone ends up here this is what I did in the end in the test method:

 it('says "abc"',async () => {
    var element1:Element = component.element; 
    var vm1:MessageBar = component.viewModel;       
    
   // make the call to the viewModel
    vm1.show("abc","abc","info");
    await TestHelper.waitForUI();
    expect(element1.querySelector(".ms-fontWeight-semibold").textContent.trim()).toBe('abc');
  });

And the we have the TestHelper:

export class TestHelper
{
    static waitForUI()
    {
        return new Promise(function(resolve, reject) {
            setTimeout(resolve, 0);
        });
    }
}