svelte-testing-library icon indicating copy to clipboard operation
svelte-testing-library copied to clipboard

Testing slots?

Open bestguy opened this issue 4 years ago • 28 comments

Perhaps this is more svelte API question, but it's not clear how to test component slots, for example:

Badge.svelte:

<span class="badge">
  <slot />
</span>

Badge.spec.js:

describe('Badge', () => {
  test('should render text', () => {
   // Where to pass child text?
    const { container } = render(Badge, { props: { color: 'primary' } }); 
  });
});

Looking for default slots primarily, and do not want to add props for this.

bestguy avatar Aug 15 '19 14:08 bestguy

Yes it's more a svelte API question that should be redirected to their repository.

Anyway:

import Button from './Button.svelte';

const { container } = render(Badge, { props: { color: 'primary' }, slots: { default: Button } }); 

ematipico avatar Aug 16 '19 10:08 ematipico

afaik is still wip, there's no way to declare slots from the Component API, see:

  • https://github.com/sveltejs/svelte/issues/2588
  • https://github.com/sveltejs/svelte/pull/2684

we could patch the workaround until it is merged to the main api, what do you think?

something like:

import Badge from './Badge.svelte';
import Button from './Button.svelte';

const { container } = render(
  Badge, 
  { props: { color: 'primary' } }, // component props
  { slots: { default: Button } } // component ctx
);

ping @benmonro @EmilTholin

oieduardorabelo avatar Aug 16 '19 11:08 oieduardorabelo

2684 looks like it would work and I'm of the opinion that we should wait for that to be merged but I'll defer to @EmilTholin

benmonro avatar Aug 16 '19 13:08 benmonro

I agree with the rest of you that it's a Svelte API issue and that 2684 will resolve it.

In the meantime you could do the less than ideal solution of creating a test-specific component that renders Badge with a Button child component.

<!-- TestBadgeSlot.svelte -->
<Badge>
  <Button />
</Badge>
// Badge.test.js
import TestBadgeSlot from './TestBadgeSlot.svelte';

const { container } = render(
  TestBadgeSlot, 
  { props: { color: 'primary' } }
);

EmilTholin avatar Aug 16 '19 14:08 EmilTholin

Thanks for the info, I'll wait until sveltejs/svelte#2684 is merged.
Feel free to close this if you prefer, or open if you'd like to track status.

If by chance it's not merged for some reason a patch in this lib would be very useful 🙏

bestguy avatar Aug 16 '19 14:08 bestguy

Closing for now not feel free to reopen if that doesn't get merged in svelte

benmonro avatar Aug 16 '19 17:08 benmonro

The mentioned issue (sveltejs/svelte#268) has been closed in favor of another refactoring in the svelte repo (which is still open!). Are there any news on how slots can be tested?

tlux avatar Oct 12 '20 15:10 tlux

the new pr to svelte api is still open: https://github.com/sveltejs/svelte/pull/4296

this suggestion still stands: https://github.com/testing-library/svelte-testing-library/issues/48#issuecomment-522029988

oieduardorabelo avatar Oct 12 '20 20:10 oieduardorabelo

If it helps, I worked around this by adding an optional children prop ala React:

https://github.com/bestguy/sveltestrap/blob/master/src/Alert.svelte#L37 that will conditionally render the slot unless children is passed.

Allows testing like: https://github.com/bestguy/sveltestrap/blob/master/src/test/Alert.spec.js#L8

Not perfect 🤷‍♂️ but helps until there is a better option

bestguy avatar Oct 12 '20 20:10 bestguy

wait for the pr merged.

cbbfcd avatar Apr 26 '21 09:04 cbbfcd

Still actual.

gyurielf avatar Jun 14 '21 09:06 gyurielf

Should this be reopened until https://github.com/sveltejs/svelte/pull/4296 is merged?

brettinternet avatar Jul 17 '21 17:07 brettinternet

It has been a year with no movement on that svelte PR. Can we open this again?

Akolyte01 avatar Dec 30 '21 18:12 Akolyte01

Sure

benmonro avatar Dec 30 '21 20:12 benmonro

FWIW I discovered this library recently: https://github.com/DockYard/svelte-inline-compile which I am now using in svelte-headlessui. I think something like this library seems to me to be the nicest way of dealing with this, though it's not perfect.

rgossiaux avatar Dec 31 '21 02:12 rgossiaux

@rgossiaux @akolyte01 I am one of the developers of https://github.com/DockYard/svelte-inline-compile. This week we plan to make it work for Vitest, a new trending test runner better suited for sveltekit apps than jest. We are working with the core team to make the testing experience nicer out of the box. But it will remain usable in jest. The current syntax makes almost impossible to test slots and multiple components cooperating among them.

cibernox avatar Jan 05 '22 16:01 cibernox

For whoever is interested, we've released two packages that make testing components much nicer than the current state of things in Jest. It is similar to https://github.com/DockYard/svelte-inline-compile but less complicated internally and with less drawbacks.

Right now the experience of testing components that take slots, actions or that must interact with other components was pretty bad. Essentially the only way was to create a on-off component in the test folder that used your component in the way you wanted and render that component instead.

Our package https://github.com/dockyard/svelte-inline-component allows to define components inline in the test itself. This is an example using sveltekit/vitest and this package:

import { render } from '@testing-library/svelte'
import { svelte } from 'svelte-inline-component';

describe('DefinitionEntry.svelte', () => {
  it('renders a link with the given href', async () => {
    const { getByTestId } = render(await svelte`
      <script>import DefinitionEntry from '$lib/DefinitionEntry.svelte';</script>
      <DefinitionEntry background="gray">
        <svelte:fragment slot="dt">I'm the description term</svelte:fragment>
        <svelte:fragment slot="dd">I'm the description definition</svelte:fragment>
      </DefinitionEntry>
    `);
    expect(getByTestId('definition-entry')).to.have.class('bg-gray-50');
    expect(getByTestId('dt')).to.have.text("I'm the description term");
    expect(getByTestId('dd')).to.have.text("I'm the description definition");
  });
});

I'm biased but IMO it is SO. MUCH. NICER to invoke components in tests with the same syntax you use in regular app code that I can't go back.

There are some downsides that we're working on to polish, like publishing VSCode plugin that enables svelte syntax and intellisense inside the strings for better developer experience. But it's very functional.

cibernox avatar Jan 11 '22 22:01 cibernox

@cibernox I agree that is nice. feel free to update the testing-library.com docs to list this as an alternative, that will help raise awareness of your library too

benmonro avatar Jan 11 '22 22:01 benmonro

@benmonro I'll take a look. I'm not sure how to frame it within the docs of testing-library, because right now there's a lot of "if"s. Namely, that your app must use vitest, which is, as their own documentation takes great care of explaining, not production ready (although in my opinion, neither is jest and yet everyone uses it).

cibernox avatar Jan 11 '22 22:01 cibernox

I would frame it just like that, maybe put a yellow warning at the top stating this is still early in development. But in my opinion, it's totally fine to offer alternative runners/environments. Additionally, you could also put it in the 'ecosystem' section and maybe just link to it there.
image

benmonro avatar Jan 11 '22 23:01 benmonro

How are we currently supposed to test something that is like this currently? It just doesn't work. The Layout component takes a slot ands obviously this library doesn't support that, right? Is the workaround to use something like svelte-inline-compile?

ParentComponent.svelte

<div
  class="position"
  in:fade={{ duration: 200 }}
  out:fade|local={{ duration: 200 }}
>
  <div
    class="feedback-frame"
    in:fly={{ y: 30, duration: 200 }}
    out:fly|local={{ y: 30, duration: 200 }}
  >
    <div class="close">
      <ClearButton on:click={cancelFeedback} />
    </div>
    <Layout gap="XS">
      {#if step === 0}
        <div class="ratings">
          {#each ratings as number}
            <ActionButton
              size="L"
              emphasized
              selected={number === rating}
              on:click={() => (step = 1)}
            >
              {number}
            </ActionButton>
          {/each}
        </div>
      {:else if step === 1}
        <Heading size="XS">What could be improved most in Budibase?</Heading>
        <Divider />
        <RadioGroup bind:value={improvements} {options} />
        <div class="footer">
          <Detail size="S">STEP 2 OF 3</Detail>
          <ButtonGroup>
            <Button secondary on:click={() => (step -= 1)}>Previous</Button>
            <Button primary on:click={() => (step += 1)}>Next</Button>
          </ButtonGroup>
        </div>

      {/if}
    </Layout>
  </div>
</div>

Layout.svelte

<div
  style="align-content:{alignContent};justify-items:{justifyItems};"
  class:horizontal
  class="container paddingX-{!noPadding && paddingX} paddingY-{!noPadding &&
    paddingY} gap-{!noGap && gap}"
>
  <slot />
</div>

PClmnt avatar Jan 28 '22 10:01 PClmnt

Any news on this?

hnrq avatar Apr 05 '22 02:04 hnrq

For whoever is interested, we've released two packages that make testing components much nicer than the current state of things in Jest. It is similar to https://github.com/DockYard/svelte-inline-compile but less complicated internally and with less drawbacks.

This is great, thank you for your efforts with the library @cibernox - it would be amazing if the render() function would natively support taking Svelte inline components as an argument. In the React world, I've written so many tests using the same approach (using jSX) and I couldn't go without it:

render(<Button>Click me</Button>)

tobiasbueschel avatar May 12 '22 16:05 tobiasbueschel

I'm currently using svelte-htm and it works well 👍

ivanhofer avatar Jul 29 '22 14:07 ivanhofer

I'm currently using svelte-htm and it works well 👍

@ivanhofer Can you share how you use svelte-htm? This error pops out when I try to pass component to the html template literals:

render(html`<${MyComponent} //>`)

TypeError: Cannot read properties of undefined (reading '$$') It seems this issue hasn't been addressed https://github.com/sveltejs/svelte/issues/6584

wd-David avatar Aug 27 '22 14:08 wd-David

@davipon it worked fine until we upgraded our dependencies a few weeks back. Now we use patch-package to work around the issue. Changing parent_component.$$.root to parent_component?.$$.root will not have any side effects when running an application normally, but fixex the tests.

ivanhofer avatar Aug 27 '22 14:08 ivanhofer

@davipon that looks like an issue in https://github.com/kenoxa/svelte-htm. I filed an issue there to see if the author can resolve it: https://github.com/kenoxa/svelte-htm/issues/234

benmccann avatar Sep 08 '22 03:09 benmccann

@benmccann Thank you for your input. I'll dive into the issue and see how I can help to resolve that.

wd-David avatar Sep 08 '22 06:09 wd-David

Any news on this? svelte-htm does not support Svelte 4 https://github.com/kenoxa/svelte-htm/issues/269

How do you test slots?

tomolenet avatar Jul 26 '23 06:07 tomolenet

same as looking for new solution for sveltev4

winston0410 avatar Aug 29 '23 08:08 winston0410