How to tell if Stimulus is loaded for injected HTML

We’re writing some Rails system test (capybara) and one of the problems we’re coming across is intermittent test failures when there’s dynamic HTML injected.

For example, let’s say we fetch the following HTML and inject it into the DOM:

<div data-controller="foo">
  <button data-action="foo.perform">Go</button>
</div>

The moment it’s injected, the “foo” Stimulus controller gets attached via MutationObserver callback. However, during system tests, there a possibility that click_button("Go") is performed before the controller’s actions are even registered. This results in the click not performing the action perform().

So, is there a way to know when Stimulus is finished working its magic for newly injected content?

Thanks!

1 Like

Stimulus responds to document changes in the next tick of the microtask queue.

In JavaScript, the easiest way to queue a microtask is to wait on an empty promise:

document.body.insertAdjacentHTML("beforeend", "...")

Promise.resolve().then(() => {
  // Stimulus has responded to the change by now
})

In Capybara, you’ll probably just want to sleep for a very small amount of time after each DOM modification. (I’m not an expert with Capybara, so feel free to chime in here if you are!)

5 Likes

Seems I’ve got same problem. Didn’t understand. But how to give the cue to Capybara not to click the button before controller is connected?

The solution I’ve found for this problem is to set an attribute to the HTML element when the controller is connected

import { Controller } from '@hotwired/stimulus'

export default class extends Controller {
  connected() {
    this.element.setAttribute('data-controller-connected', 'true')
  }
}

My form is

<form data-controller="form">
  <!-- ... -->
</form>

Then I use the attribute to find the element on Capybara tests

it 'fills the form' do
  within 'form[data-controller-connected="true"]' do
    # ... I am sure Hotwire is loaded here
  end
end