Rapidly create, exercise, and share Svelte component states via URL-encoded permalinks.
Developing and testing components should be seamless. Svelte Gym provides a playground environment to exercise your components and ensure they respond correctly to various inputs and constraints.
Key Features:
npm install -D svelte-gym
# or
pnpm install -D svelte-gym
# or
bun install -D svelte-gym
Create a +page.svelte route for your component (e.g., src/routes/gym/my-component/+page.svelte):
<script lang="ts">
import { TestHarness, restoreProps, GymCheckbox, GymTextbox } from "svelte-gym";
import MyComponent from '$lib/MyComponent.svelte';
// 1. Define your component properties
let props = $state({
label: "Hello World",
isActive: true,
count: 0
});
// 2. Restore properties from URL parameters automatically
// This allows the URL to drive the component state
restoreProps(props);
</script>
<!-- 3. Wrap your component in the TestHarness -->
<TestHarness>
{#snippet componentToTest()}
<MyComponent {...props} />
{/snippet}
<!-- 4. Add controls to manipulate props -->
{#snippet controls()}
<ul>
<li><GymCheckbox bind:props name="isActive" /></li>
<li><GymTextbox bind:props name="label" /></li>
</ul>
{/snippet}
</TestHarness>
Svelte Gym is designed to be "interpreter-friendly." To ensure the best results and visual parity when using LLMs for component development:
componentToTest SnippetThe componentToTest snippet should only contain the component being tested.
[!IMPORTANT] Avoid wrapping the component in extra
divorsectiontags. This ensures that the component's layout and styles are tested in isolation, matching how it will appear when deployed.
Correct:
{#snippet componentToTest()}
<MyComponent {...props} />
{/snippet}
Incorrect:
{#snippet componentToTest()}
<div class="wrapper"> <!-- ❌ DON'T DO THIS -->
<MyComponent {...props} />
</div>
Gym* Input ComponentsAlways use the built-in Gym prefixed input components (e.g., GymSlider, GymTextbox, GymCheckbox) in the controls snippet.
[!IMPORTANT] Do not use standard HTML inputs or other custom components.
Gym*inputs are specifically designed to handle and test edge cases likenull,undefined,NaN, andInfinity, which are critical for robust component testing and are not supported by standard inputs.
http://localhost:5173/gym/button?label=Super+Long+Label+That+Breaks+LayoutTestHarnessThe main wrapper for your component playground.
Props:
maxWidth (number, optional): Maximum width constraint for the test area.maxHeight (number, optional): Maximum height constraint for the test area.maxFontSize (number, optional): Maximum font size for the test area.Snippets:
componentToTest: Place the component you want to test here.controls: Place Gym* controls here to modify props.restoreProps(props)Synchronizes the URL search parameters with your local props object. This must be called in your component's <script> section.
Gym* Components)All controls support bind:props and a name attribute corresponding to the property key in props.
GymCheckbox: Boolean toggle.GymTextbox: String input.GymSlider: Numeric slider (requires min, max).GymDropdown: Select from a list of options.GymRadioGroup: Radio button group.GymLog: Displays a log of events (passed as an array of strings).Svelte Gym supports nested properties using dot notation. This is useful for complex state objects.
<script lang="ts">
let props = $state({
config: {
theme: {
mode: 'dark'
}
}
});
</script>
In your controls:
<GymDropdown bind:props name="config.theme.mode" options={['light', 'dark']} />
Svelte Gym ships with an ESLint plugin (eslint-plugin-svelte-gym) to help catch common test harness setup mistakes at lint time.
# The plugin is included in the svelte-gym package
npm install -D eslint-plugin-svelte-gym
Or reference it locally if using the source:
{
"devDependencies": {
"eslint-plugin-svelte-gym": "file:./eslint-plugin"
}
}
Add to your .eslintrc.cjs:
module.exports = {
overrides: [
{
files: ['*.svelte'],
plugins: ['svelte-gym'],
rules: {
'svelte-gym/require-restore-props': 'warn',
'svelte-gym/no-duplicate-prop-names': 'warn',
'svelte-gym/require-props-state': 'error',
'svelte-gym/single-component-in-test': 'error'
}
}
]
};
svelte-gym/require-restore-props ⚠️Warns when a file imports TestHarness but never calls restoreProps(). Without this call, URL parameters won't be restored into component state.
Correct:
<script>
import { TestHarness, restoreProps } from 'svelte-gym';
let props = $state({ label: 'Hello' });
restoreProps(props);
</script>
Incorrect:
<script>
import { TestHarness } from 'svelte-gym'; // ⚠️ Missing restoreProps
let props = $state({ label: 'Hello' });
</script>
svelte-gym/no-duplicate-prop-names ⚠️Warns when multiple Gym* components use the same name prop. Duplicate names cause permalink parameter collisions.
Incorrect:
<GymTextbox bind:props name="label" />
<GymSlider bind:props name="label" /> <!-- ⚠️ Duplicate "label" -->
svelte-gym/require-props-state ❌Errors when restoreProps(props) is called but props was not declared with $state(). Without $state(), restored values won't trigger Svelte reactivity.
Correct:
<script>
let props = $state({ count: 0 }); // ✅ Uses $state
restoreProps(props);
</script>
Incorrect:
<script>
let props = { count: 0 }; // ❌ Not reactive
restoreProps(props);
</script>
svelte-gym/single-component-in-test ❌Errors when the componentToTest snippet contains more than one element, or when its single element is a plain HTML element (not a component). This prevents wrapper <div> or <section> tags that make things look correct in the harness but behave differently in production.
Correct:
{#snippet componentToTest()}
<MyComponent {...props} />
{/snippet}
Incorrect:
{#snippet componentToTest()}
<div class="wrapper"> <!-- ❌ HTML wrapper -->
<MyComponent {...props} />
</div>
{/snippet}
Record<string, never>: If your published package emits broken .d.ts files where all component props are typed as Record<string, never>, ensure your peerDependencies.svelte is set to "^5.0.0" (not ">=3.0.0"). The @sveltejs/package type generator uses this field to select the correct type shims — a range that intersects with Svelte 3 causes it to use legacy shims that don't understand $props(). See sveltejs/kit#12972 for details.MIT