sveltejs / svelte

Cybernetically enhanced web apps
https://svelte.dev
MIT License
78.53k stars 4.12k forks source link

Add support for <script context="test"> for writing component tests #7085

Open NEO97online opened 2 years ago

NEO97online commented 2 years ago

Describe the problem

Testing does not currently feel like a first-class feature in Svelte, but I believe it should be. One way we could improve this is by adding support for a test context within components, which could be run by svelte-check.

Once tests are written in .svelte files, we open up the possibilities of the compiler to make test cases even easier to read and write and integrate directly with our svelte components.

Describe the proposed solution

Here's an example of how a component could look with tests:

<script lang="ts">
    export let isLoading = false
    export let disabled = false
    export let type = "button"
</script>

<template>
    <button {type} disabled={disabled || isLoading} on:click>
        <slot />
    </button>
</template>

<style lang="stylus">
    button {
        background: none;
        color: var(--color, var(--primary));
        border: 2px solid var(--color, var(--primary));
        padding: 8px 16px;
        font-weight: bold;
        font-size: 16px;
        cursor: pointer;
        width: var(--width);
        margin: var(--margin-top) var(--margin-right) var(--margin-bottom) var(--margin-left);
        grid-area: var(--grid-area);
        display: flex;
        align-items: center;
        gap: 8px;

        &:disabled {
            color: var(--dark-grey);
            border-color: var(--dark-grey);
            cursor: default;
        }
    }
</style>

<script context="test">
    import { render, fireEvent } from "@testing-library/svelte"

        // Svelte could automatically wrap these in `describe("<Button />")`

        it("renders a button", async () => {
            const { getByRole } = render(Button)
            expect(getByRole("button")).toBeInTheDocument()
        })

        it("fires click event", async () => {
            const { component, getByRole } = render(Button)
            const handleClick = jest.fn()
            component.$on("click", handleClick)
            const button = getByRole("button")
            fireEvent.click(button)
            expect(handleClick).toHaveBeenCalled()
        })

        it("is disabled while loading", async () => {
            const { getByRole } = render(Button, { isLoading: true })
            expect(getByRole("button")).toHaveAttribute("disabled")
        })

        it("sets the type attribute", async () => {
            const { getByRole } = render(Button, { type: "submit" })
            expect(getByRole("button")).toHaveAttribute("type", "submit")
        })

        it("can be disabled", async () => {
            const { getByRole } = render(Button, { disabled: true })
            expect(getByRole("button")).toBeDisabled()
        })
</script>

Alternatives considered

The current alternative is to write test cases in js or ts files, using a test runner like jest.

Importance

would make my life easier

Conduitry commented 2 years ago

This had come up before in #1255 and it's not at all clear to me who would actually be running these tests.

If, from the Svelte compiler's point of view, all it had to do was ignore these script blocks when building the app, that would be easy to do with a preprocessor. And telling the test runner how to read these files would have to be specific to that particular test runner.

bluwy commented 2 years ago

I agree with @Conduitry too. This seems to be possible to be supported in userland, and I think it should stay that way as it depends on the test runner. The most Svelte can do is endorse the context="test" convention, but I don't think there's a need to.