Issue
I am creating a simple calculator for my fullstack uni class. My vitest to check if the calculator display updates correctly when the pinia store with the display value updates, fails because it gets an empty string, but expects "123".
The code below should be copy pasteable in a default setup vue project. I'm using Vue 3, setup + composition API with TypeScript.
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 calculator.ts
store:
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");
});
});
So it seems like the store.addInput function is being stubbed (although I specifically specify it to not stub the functions with stubActions: false. I also tried removing createSpy property by adding globals: true in vitest config), and I also tried forcing nextTick for reactive data to update (not sure if that's correct logic, but I tries it at least). I also tried adding createSpy to createTestingPinia as suggested here: https://github.com/vuejs/pinia/discussions/1096 and also followed the pinia docs and create a new pinia before each test, but it still doesn't work.
So it's not because of stubbing I assume. And it works fine when I run and test it manually. So it's in vitest that I experience this problem.
What am I doing wrong?
Solution
I simply forgot to await in wrapper.vm.$nextTick();
So I changed this line to await wrapper.vm.$nextTick();
and it works.
Answered By - Eric B
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.