Logo

dev-resources.site

for different kinds of informations.

πŸ’‘ Building a Nuxt 3 App with Pinia and Testing It with Cypress πŸš€

Published at
11/30/2024
Categories
nuxt
e2e
vue
pinia
Author
blamsa0mine
Categories
4 categories in total
nuxt
open
e2e
open
vue
open
pinia
open
Author
11 person written this
blamsa0mine
open
πŸ’‘ Building a Nuxt 3 App with Pinia and Testing It with Cypress πŸš€

In this article, I'll walk you through a simple project using Nuxt 3 with Pinia for state management and Cypress for end-to-end testing. This project implements an interactive counter with actions and tests to ensure everything works as expected. If you're looking to explore these technologies or enhance your skills, this article is for you!


πŸ—οΈ Project Structure

Here’s an overview of the main files in the project:

/layouts
  default.vue
/pages
  index.vue
/stores
  counter.ts
/tests
  main.cy.js
Enter fullscreen mode Exit fullscreen mode

✨ Global Layout: layouts/default.vue

This layout provides the base structure for the application, including a header, a footer, and a slot for the page content.

<template>
  <header>
    <h1>Pinia</h1>
    <p>The intuitive store for Vue</p>
  </header>

  <template v-if="route.path !== '/'">
    <router-link to="/">β†’ Back Home</router-link>
    <hr/>
  </template>

  <slot/>
  <hr/>

  <footer>
    <a href="https://github.com/posva/pinia">
      <svg class="logo" fill="none" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg">
        <path
            clip-rule="evenodd"
            d="M7.02751 0.333496C..."
            fill="currentColor"
            fill-rule="evenodd"
        />
      </svg>
      Github
    </a> - by
    <a href="https://github.com/posva">@posva</a> 2021
  </footer>
</template>

<script lang="ts" setup>
import { useRoute } from "#app";
const route = useRoute();
</script>

<style scoped>
.logo {
  width: 1.5rem;
  height: 1.5rem;
  color: white;
}
</style>
Enter fullscreen mode Exit fullscreen mode

Β πŸ–ΌοΈ Home Page: pages/index.vue

The home page uses the Pinia store to display and interact with the counter.

<template>
  <div>
    <div style="margin: 1rem 0">
      <PiniaLogo/>
    </div>

    <p>
      This is an example store to test out devtools.
    </p>

    <h2>Counter Store</h2>

    <p data-testid="counter-values">Counter: {{ counter.n }}. Double: {{ counter.double }}</p>

    <p>Increment the Store:</p>
    <button @click="counter.increment()" data-testid="increment">+1</button>
    <button @click="counter.increment(10)">+10</button>
    <button @click="counter.increment(100)">+100</button>
  </div>
</template>

<script setup lang="ts">
import { useCounter } from "~/stores/counter";
import PiniaLogo from "~/components/PiniaLogo.vue";

const counter = useCounter();
</script>

<style scoped>
button {
  margin-right: 0.5rem;
  margin-left: 0.5rem;
}
</style>
Enter fullscreen mode Exit fullscreen mode

πŸ“¦ Pinia Store: stores/counter.ts

The store manages the application state, along with its actions and getters.

import {acceptHMRUpdate, defineStore} from "pinia";

const delay = (t: number) => new Promise(resolve => setTimeout(resolve, t))

export const useCounter = defineStore('counter', {
    state: () => ({
        n: 2,
        incrementedTimes: 0,
        decrementedTimes: 0,
        numbers: [] as number[]
    }),

    getters: {
        double: (state) => state.n * 2
    },

    actions: {
        increment(amount: number = 1) {
            this.incrementedTimes++
            this.n += amount
        },

        changeMe() {
            console.log('Change me to test HMR')
        },

        async fail() {
            const n = this.n
            await delay(1000)
            this.numbers.push(n)
            await delay(1000)

            if (this.n !== n) {
                throw new Error('Someone changed n !')
            }
        },

        async decrementToZero(interval: number = 300) {
            if (this.n <= 0) return

            while (this.n > 0) {
                this.$patch((state) => {
                    this.n--
                    state.decrementedTimes++
                })
                await delay(interval)
            }
        }
    }
})

if (import.meta.hot) {
    import.meta.hot.accept(acceptHMRUpdate(useCounter, import.meta.hot))
}
Enter fullscreen mode Exit fullscreen mode

Β πŸ› οΈ Testing with Cypress: cypress/e2e/main.cy.js

The tests verify that the counter works properly and that the Pinia store actions are correctly reflected in the UI.

const PORT = process.env.PORT || 3000;

describe("Pinia demo with counters", () => {
  beforeEach(() => {
    cy.visit(`http://localhost:${PORT}`);
  });

  it("works", () => {
    cy.get("[data-testid=counter-values]")
      .should("contain.text", "Counter: 2. Double: 4")
      .wait(500)
      .get("[data-testid=increment]")
      .click()
      .get("[data-testid=counter-values]")
      .should("contain.text", "Counter: 3. Double: 6")
      .get("[data-testid=increment]")
      .click();
  });
});
Enter fullscreen mode Exit fullscreen mode

 🎯 Results

Running the tests with npx cypress open validates the behavior of the Pinia store and its interactions with the UI.

nuxt Article's
30 articles in total
Favicon
Resolving Auto-Scroll issues for overflow container in a Nuxt app
Favicon
Nuxflare Auth: A lightweight self-hosted auth server built with Nuxt, Cloudflare and OpenAuth.js
Favicon
The easiest way to migrate from Nuxt 3 to Nuxt 4!
Favicon
Creating a Scroll-Spy Menu with Nuxt 3 and Intersection Observer API
Favicon
Nuxt
Favicon
How to add comment from BlueSky to static/vue/nuxt project
Favicon
Navigation guards in Nuxt 3 with defineNuxtRouteMiddleware
Favicon
2024 Nuxt3 Annual Ecosystem SummaryπŸš€
Favicon
13 Vue Composables Tips You Need to Know
Favicon
Building a multi-lingual web app with Nuxt 3 and Nuxt i18n
Favicon
πŸš€ Fetching and Displaying Data in Nuxt 3 with useAsyncData
Favicon
Nuxt File Storage Module reaching 2K Downloads per Month
Favicon
Why you should use both v-if and v-show to toggle heavy components in Vue ?
Favicon
How to Access a Child Component’s Ref with multi-root node (Fragment) in Vue 3
Favicon
Deploying Nuxt.js app to GitHubΒ pages
Favicon
Nuxt
Favicon
Add a Voice Search to your Nuxt3 App in 6 Easy Steps
Favicon
Nuxt.js in action: Vue.js server-side rendering framework
Favicon
@nuxt/test-utils - The First-Class Citizen for Nuxt Unit Testing
Favicon
Seamless Nuxt 2 Deployment: A Step-by-Step Guide with GitLab CI/CD and DigitalOcean
Favicon
Building Vhisper: Voice Notes App with AI Transcription and Post-Processing
Favicon
Easiest Way to Set Up GitHub Action CI/CD for Vue.js Apps
Favicon
Angular vs Next.js vs Nuxt.js: Choosing the Right Framework for Your Project
Favicon
Nuxt Authorization: How to Implement Team Role-Based Access Control in Nuxt 3
Favicon
Vue Fes Japan 2024
Favicon
πŸ’‘ Building a Nuxt 3 App with Pinia and Testing It with Cypress πŸš€
Favicon
Build a static website with Markdown content, using Nuxt and Fusionable (server API approach)
Favicon
Secure Your Nuxt 3 App
Favicon
Sending Emails in Nuxt 3: How I Handle Emails in My SaaS Boilerplate
Favicon
Build your new Storefront with Nuxt and Medusa 2.0.0

Featured ones: