This took me a lot longer to figure out than I feel it should have, so as always, I thought I'd write about it.

I've been working on a project where a Svelte application integrates with an Auth server. If the app discovers that the user is logged out, it displays a modal prompting the user to login. When the clicks the login button, a new tab is open, prompting the user through the login process, redirecting to the app, then closing the tab. The modal checks for the user's JWT using an interval timer, and closes the tab once it is set, allowing the user to continue to use the application exactly where they left off.

The system works great, and I've built something similar in multiple UI frameworks. But, writing unit tests for this in Svelte with Vitest turned out to be a much harder task than I expected. I'm gonna write a few blog posts on this. this is the first one that will tell you how to create and use the modal.

The Setup

First, create a Svelte app following the instructions.

Then setup Flowbite-Svelte, a UI Component Library built for Tailwind and Svelte.

Or you can just check out this repo for these samples and follow along.

Specific steps are beyond the scope of this of this article.

Create the Modal

Create a new Svelte component, MyModal.svelte. First, import the Modal and Button from Flowbite Svelte:

view plain print about
1<script lang="ts">
2 import { Modal, Button } from "flowbite-svelte";
3</script>

Then, create the Modal template:

view plain print about
1<Modal title="My Modal" bind:open={opened} size="md" permanent>
2</Modal>

We gave the Modal a title and a size, which is a Tailwind convention defining this as a mid-size modal. We also gave it the permanent property, which prevents the modal from being closed--although in my experience that does not quite work yet. Then we bind to a property named opened. This is how we determine whether or not the modal is displayed as part of the app, or completely hidden.

Back to TypeScript, create an input property to open or close the Modal:

view plain print about
1let { isModalOpen = false } = $props();

Then create a open property, using the $state rune:

view plain print about
1let opened = $state(isModalOpen);

And finally, add an effect so that when the isModalOpen input prop changes, so does the opened state:

view plain print about
1$effect(() => {
2 opened = isModalOpen;
3})

Back to the HTML section of this component, let's populate the rest of the Modal. Give it a body:

view plain print about
1<p class="mb-4 text-gray-500 dark:text-gray-400">
2 This is a modal example using Flowbite-Svelte.
3 </p>

This is just some HTML and Tailwind styles.

Then give it a footer:

view plain print about
1{#snippet footer()}
2 <div class="flex justify-end">
3 <Button color="gray" onclick={doSomething}>Do Something</Button>
4 </div>
5 {/snippet}

The footer includes the Flowbite Svelte button, when clicked runs the doSomething () method. Create that in our TypeScript block:

view plain print about
1const doSomething = () => {
2 const timer = setInterval(() => {
3 opened = false;
4 clearInterval(timer);
5 }, 1000)
6}

The click handler method sets an interval() and use that to close the modal. In my real-world use case, the method would start the user authentication flow to reset the User's JWT and then do a cookie check inside the interval. But, for the purposes of this sample, we're just gonna simulate that with an automatic delayed close.

Use the Modal

Now, move to your default Svelte page, +page.svelte, and set up the Modal. First, some imports in your TypeScript block:

view plain print about
1<script>
2 import { Button } from "flowbite-svelte";
3 import MyModal from "$lib/MyModal.svelte";
4</script>

Then in the HTML portion of the template set up a button that will show the Modal, and the actual Modal:

view plain print about
1<div class="p-8">
2 <Button onclick={openModal}>
3 Show Modal
4 </Button>
5
6 <MyModal bind:isModalOpen={opened}></MyModal>
7</div>

We need to add two items to the TypeScript block, from this: The openModal() Click Handler, and the opened state value:

view plain print about
1let opened = $state(false);
2
3const openModal = () =>
{
4 opened = !opened;
5}

Run the app, and you should see the Modal pop up, then close shortly thereafter:

Final Thoughts

Building the Modal was very easy, but when it came time to write unit tests that was a whole other story. In the next two parts of this article, I'm going to show you how I wrote tests for this using both fake timers, and real timers.