How to test the reaction to a component event in Svelte?

1.2k views Asked by At

In Svelte, I have a parent component which listens to a component event dispatched by a child component.

I know how to use component.$on to check that the dispatched event does the right thing within the component which is dispatching, like so.

But I can't figure out how to check that the component which receives the dispatch does the right thing in response.

Here's a basic example:

Child.svelte

<script>
  import { createEventDispatcher } from 'svelte'

  const dispatch = createEventDispatcher()
  
  function handleSubmit(event) {
    dispatch('results', 'some results')
  }
</script>

<form on:submit|preventDefault={ handleSubmit }>
  <button type='submit'>Submit</button>
</form>

Parent.svelte

<script>
  import Child from './Child.svelte'

  let showResults = false

  function handleResults(event) {
    showResults = true
  }
</script>

<Child on:results={ handleResults } />

{ #if showResults }
  <p id='results'>Some results.</p>
{ /if }

The idea is to eventually write a test using @testing-library/svelte like:

import { render } from '@testing-library/svelte'
import Parent from './Parent.svelte'

test('shows results when it receives them', () => {
  const rendered = render(Parent)
 
  // ***
  // Simulate the `results` event from the child component?
  // ***

  // Check that the results appear.
})

If the parent were reacting to a DOM event, I would use fireEvent.

But I don't know how I would get a hold of the <Child> component in this case, and even if I could I'm guessing that Svelte is using a different mechanism for component events.

(Just to test it out, I used createEvent to fire a custom results event on one of the DOM elements rendered by <Child> but it didn't seem to do anything.)

Anyone have any ideas? Thanks!

1

There are 1 answers

2
M. Desjardins On BEST ANSWER

If you're already planning on using @testing-library/svelte, I think the easiest way is not to try to manually trigger the Child component's results event, but to use Testing Library to grab the form/submit elements and trigger the submit event (using fireEvent a SubmitEvent on the <form> or their @testing-library/user-event library, or even a vanilla dispatchEvent). Svelte would then dispatch the custom results event that Parent is listening on.

Something like:

test('shows results when it receives them', async () => {
  // Arrange
  const rendered = render(Parent)

  const submitButton = rendered.getByRole('button', {
    name: /submit/i
  });

  const user = userEvent.setup();

  // Act
  await user.click(submitButton);

  // Assert
  const results = rendered.queryByText(/some results\./i);

  expect(results).not.toBe(null);
});

Hope this is what you had in mind.


Edit: For mocking Child.svelte, something like this in a __mocks__/Child.svelte should work:

<script>
  import { createEventDispatcher } from "svelte";

  const dispatch = createEventDispatcher();

  function handleSubmit(event) {
    dispatch("results", "some results");
  }
</script>

<form on:submit|preventDefault={handleSubmit}>
  <button type="submit">Test</button>
</form>

Which is the exact same implementation as the actual module (I gave the button a different label just to make it clear it's the mocked version when querying it), but the idea is that this would never need to change and is only used to dispatch a results event. Then you'd just need to tell Jest or whatever you're using that you're mocking it (jest.mock("./Child.svelte");), change the getByRole query to match the new name (or just leave the mock with the original name), then it should just work.

Whether you think that's worth it or not is up to you. I've generally had success testing the UI as a whole rather than mocking sub-components, but I guess it comes down to preference. Yes, you might have to change the test if the Child component changes, but only if you change the label of the button or change the user interaction mechanism.

You don't need to know about the details of the components, you don't even need to know that it's split into a separate Child component, all the test would care about is a general idea of the structure of the UIā€”that there's a button called "Submit" and that clicking on it should show an additional <p> tag.