I want to test the following Svelte component with Vitest:
<script context="module">
import {push} from 'svelte-spa-router'
import {onDestroy} from 'svelte'
let time = 1000*120;
let interval = 1000;
let running = true;
export const twoDigits = (number) => {
return number.toLocaleString('en-US', {
minimumIntegerDigits: 2,
useGrouping: false
})
}
export const timeString = (ms) => {
let seconds = ms / 1000;
const minutes = Math.floor(seconds / 60);
seconds = seconds % 60;
const timeString = twoDigits(minutes) + ":" + twoDigits(seconds);
return timeString
}
let timeShown = timeString(time)
export const startCountdown = () => {
setTimeout(()=>{
time -= interval;
timeShown = timeString(time);
if (time > 0 && running) {
startCountdown();
} else if (time > 0 && !running) {
console.log("Countdown stopped")
} else {
console.log("Done!")
setTimeout( () => {
push('/gameover')
},1000)
}
}, interval)
}
startCountdown()
onDestroy(()=>{
running = false;
})
</script>
<div id="countdown">
{timeShown}
</div>
Unfortunately, I get an error saying Function called outside component initialization
as soon as I try to import the component in my test file with import Countdown from "../lib/Countdown.svelte"
.
The problem could have something to do with the onDestroy
function but I don't know, how to fix this.
stacktrace:
Error: Function called outside component initialization
❯ get_current_component node_modules/svelte/src/runtime/internal/lifecycle.js:14:32
14|
15| /**
16| * Schedules a callback to run immediately before the component is updated after any state chan… | ^
17| *
18| * The first time the callback runs will be before the initial `onMount`
❯ Module.onDestroy node_modules/svelte/src/runtime/internal/lifecycle.js:77:2
❯ src/lib/Countdown.svelte:47:5
❯ src/test/countdown.test.js:3:31
vite.config.js
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
// https://vitejs.dev/config/
export default defineConfig({
base: "",
test: {
environment: "jsdom"
},
plugins: [svelte()],
})
At the moment there is only a trivial test in the test file since the import of Countdown produces the error:
import {test, expect} from "vitest"
import { onDestroy } from "svelte"
import Countdown from "../lib/Countdown.svelte"
const hello = "hello"
test("something", ()=>{
expect(hello).toBe("hello")
})
The component is broken because of this:
<script context="module">
That code is independent of all instances, hence the error on calling onDestroy
, which is an component lifecycle hook. The attribute context="module"
should not be there.
Also note that onDestroy
runs on the server during server-side rendering. If you use SSR, it probably should be changed to this:
onMount(() => {
startCountdown()
return () => {
running = false;
};
})
This way no timers are involved on the server at all.