3

this is my first post — I'm new to both Stack Overflow and SvelteKit. I'm trying to test a Svelte 5 Navbar component that conditionally shows links/buttons based on:

page.data.user — if the user is logged in

page.url.pathname — to hide certain links on specific routes

This works fine in the browser, but I can't figure out how to mock page in unit tests using Vitest + Testing Library.

Navbar.svelte

<script lang="ts">
    import { page } from '$app/state';
    import { goto, invalidateAll } from '$app/navigation';

    let user = $state(page.data.user);
    let currentPath = $state(page.url.pathname || '');

    $effect(() => {
        user = page.data.user;
        currentPath = page.url.pathname || '';
        invalidateAll();
    });

    async function handleLogout() {
        try {
            const formData = new FormData();
            const response = await fetch('/logout', {
                method: 'POST',
                body: formData
            });
            if (response.ok) {
                await goto('/');
            }
        } catch (error) {
            console.error('Logout error:', error);
        }
    }
</script>

<nav
    class="fixed top-0 right-0 left-0 z-50 flex h-16 items-center border-b border-gray-200 bg-white px-6 font-serif"
>
    <!-- Left -->
    <div class="flex-1">
        {#if !user && !['/register', '/login', '/search'].includes(currentPath)}
            <a href="/register">
                <button class="rounded border border-gray-300 px-4 py-2 text-gray-700 hover:bg-gray-50">
                    Register
                </button>
            </a>
        {/if}
    </div>

    <!-- Right -->
    <div class="flex flex-1 items-center justify-end">
        {#if user}
            <button
                onclick={handleLogout}
                class="ml-4 rounded border border-gray-300 px-4 py-2 text-gray-700 hover:bg-gray-50"
            >
                Logout
            </button>
        {:else if !['/register', '/login', '/search'].includes(currentPath)}
            <a href="/login" class="text-gray-700 hover:text-gray-900">Sign in</a>
        {/if}
    </div>
</nav>

Navbar.svelte.test.ts

    import { describe, it, expect, vi } from 'vitest';
    import { render, screen } from '@testing-library/svelte';
    import Navbar from './Navbar.svelte';
    
    vi.mock('$app/navigation', () => ({
        goto: vi.fn(),
        invalidateAll: vi.fn(),
    }));
    
    // I need help mocking $app/state.page here
    describe('navigation bar', () => {
            it('shows logout button when user is logged in', async () => {
                render(Navbar);
    
                expect(screen.getByRole('button', { name: /logout/i })).toBeInTheDocument();
                expect(screen.queryByRole('button', { name: /register/i })).not.toBeInTheDocument();
                expect(screen.queryByRole('link', { name: /sign in/i })).not.toBeInTheDocument();
            });
    });

What I need help with:

How can I mock the page store ($app/state.page) so I can, for example, simulate:

page.data.user = { name: 'Alex' }

page.url.pathname = '/search'

Tech stack:

SvelteKit 2.17.2

Svelte 5.20.2

Vitest 3.0.0

@testing-library/svelte 5.2.7

I have no clue where to start.

1
  • I suppose you're the same person asking in Reddit. As stated in Reddit, vi.mock() should work fine. If it doesn't, describe in detail the problems when you attempt this. Commented May 1 at 0:32

1 Answer 1

1

First you need to mock page from $app/state like this at the top of your test file Navbar.svelte.test.ts:

import { page } from "$app/state";

vi.mock("$app/state", async () => {
  // This is to avoid mocking of other logic implemented in $app/state,
  // it can be omitted if you don't care about it.
  const original = await vi.importActual("$app/state");

  return {
    ...original,
    page: {},
  };
}); 

This makes page object from state a regular writable object for the purposes of your unit test (otherwise it's readonly by default).

Then in your test case you can assign to page whatever you need:

describe("navigation bar", () => {
  it("shows logout button when user is logged in", async () => {
    const url = new URL("http://example.com");
    url.pathname = "/search";
    const user = { name: "Alex" };

    page.data = { user };
    page.url = url;

    render(Navbar);
    ...
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.