ref from store loses reactivity in unit tests with vitest #2559
-
The code below should be copy pasteable in a default setup vue project. My calculator component: <script setup lang="ts">
import { useCalculatorStore } from "@/stores/calculator";
import { storeToRefs } from "pinia";
const buttons = [
{ name: "7", type: "number", css: "" },
{ name: "8", type: "number", css: "" },
{ name: "9", type: "number", css: "" },
{ name: "/", type: "operator", css: "" },
{ name: "4", type: "number", css: "" },
{ name: "5", type: "number", css: "" },
{ name: "6", type: "number", css: "" },
{ name: "*", type: "operator", css: "" },
{ name: "1", type: "number", css: "" },
{ name: "2", type: "number", css: "" },
{ name: "3", type: "number", css: "" },
{ name: "-", type: "operator", css: "" },
{ name: "AC", type: "clear", css: "" },
{ name: "0", type: "number", css: "" },
{ name: "=", type: "equal", css: "" },
{ name: "+", type: "operator", css: "" },
] as const;
type ButtonType = typeof buttons[number]["type"];
function buttonCssFor(buttonType: ButtonType): string {
switch (buttonType) {
case "number":
return "text-white bg-gray-500";
case "operator":
return "text-white bg-blue-500/50";
case "clear":
return "bg-red-500 text-white";
case "equal":
return "bg-yellow-500 text-white";
}
}
const calculatorStore = useCalculatorStore();
const { display } = storeToRefs(calculatorStore);
function handleClick(button: (typeof buttons)[number]): void {
switch (button.type) {
case "number":
case "operator":
return calculatorStore.addInput(button.name);
case "clear":
return calculatorStore.clearDisplay();
case "equal":
return handleEqualsPressed();
default:
throw new Error("All button types were not handled in handleClick") as never;
}
}
function handleEqualsPressed() {
// TODO: Connect to backend
const result: any = eval(display.value);
if (typeof result !== "number") {
throw new Error("Result is not a number");
}
calculatorStore.setDisplay(result.toString());
}
</script>
<template>
<div id="calculator-container" class="border-2 border-white rounded-xl p-5 max-w-3xl bg-gray-800 mx-auto">
<div id="calculator-display" class="p-2 mb-4 border-2 border-white bg-gray-500 box-content h-8 text-2xl text-end align-middle">
{{ display }}
</div>
<div id="calculator-buttons" class="grid grid-cols-4">
<button
class="m-1 p-4 text-2xl opacity-80 hover:opacity-100"
v-for="button in buttons"
:key="button.name"
:class="`${button.css} ${buttonCssFor(button.type)}`"
@click="handleClick(button)"
>
{{ button.name }}
</button>
</div>
</div>
</template> The import { ref, computed } from "vue";
import { defineStore } from "pinia";
export const useCalculatorStore = defineStore("calculator", () => {
const calculatorDisplay = ref("");
const display = computed(() => calculatorDisplay.value);
const displayingMessage = ref(false);
function addInput(value: string) {
if (displayingMessage.value) {
calculatorDisplay.value = "";
displayingMessage.value = false;
}
calculatorDisplay.value += value;
}
function clearDisplay() {
calculatorDisplay.value = "";
}
function setDisplay(message: string) {
calculatorDisplay.value = message;
displayingMessage.value = true;
}
return { display, addInput, clearDisplay, setDisplay };
}); The vitest: import { describe, beforeEach, it, vi, expect } from "vitest";
import { setActivePinia, createPinia } from "pinia";
import { createTestingPinia } from "@pinia/testing";
import { mount } from "@vue/test-utils";
import BasicCalculator from "@/components/BasicCalculator.vue";
import { useCalculatorStore } from "@/stores/calculator";
describe("Calculator functions", () => {
beforeEach(() => {
setActivePinia(createPinia());
});
it("renders all buttons", () => {
const wrapper = mount(BasicCalculator, {
global: {
plugins: [createTestingPinia({ createSpy: vi.fn, stubActions: false, })],
},
});
expect(wrapper.text()).toContain("0");
expect(wrapper.text()).toContain("1");
expect(wrapper.text()).toContain("2");
expect(wrapper.text()).toContain("3");
expect(wrapper.text()).toContain("4");
expect(wrapper.text()).toContain("5");
expect(wrapper.text()).toContain("6");
expect(wrapper.text()).toContain("7");
expect(wrapper.text()).toContain("8");
expect(wrapper.text()).toContain("9");
expect(wrapper.text()).toContain("+");
expect(wrapper.text()).toContain("-");
expect(wrapper.text()).toContain("*");
expect(wrapper.text()).toContain("/");
expect(wrapper.text()).toContain("=");
});
it("renders the display from pinia store", () => {
const wrapper = mount(BasicCalculator, {
global: {
plugins: [createTestingPinia({ createSpy: vi.fn, stubActions: false, })],
},
});
const store = useCalculatorStore();
store.addInput("1");
store.addInput("2");
store.addInput("3");
const display = wrapper.find("#calculator-display");
wrapper.vm.$nextTick();
expect(display.text()).toBe("123");
});
}); The first test works, but the second one fails because it gets an empty string, but expects "123". I tried adding createSpy to createTestingPinia as suggested here: #1096 and also followed the pinia docs and create a new pinia before each test, but it still doesn't work. What am I doing wrong? |
Beta Was this translation helpful? Give feedback.
Answered by
BeatsuDev
Jan 23, 2024
Replies: 1 comment
-
This wasn't a problem with how I used pinia after all. I just forgot to await |
Beta Was this translation helpful? Give feedback.
0 replies
Answer selected by
BeatsuDev
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This wasn't a problem with how I used pinia after all. I just forgot to await
wrapper.vm.$nextTick();
...