🏠 Home / Hub

🚀 Vue 17 — Testing, Build & Deploy

← Vue Menu · ← Prev: API & Composables

1. Unit Testing with Vitest

npm install -D vitest @vue/test-utils happy-dom

# vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  test: {
    environment: 'happy-dom',
    globals: true,              // no need to import test/expect
  },
})

# Run tests
npx vitest            # watch mode
npx vitest run        # single run (CI)
npx vitest --coverage # with coverage report

2. Testing Composables

// composables/useCounter.js
import { ref } from 'vue'
export function useCounter(initial = 0) {
  const count = ref(initial)
  const inc   = () => count.value++
  const dec   = () => count.value--
  return { count, inc, dec }
}

// tests/useCounter.test.js
import { describe, it, expect } from 'vitest'
import { useCounter } from '@/composables/useCounter'

describe('useCounter', () => {
  it('starts at initial value', () => {
    const { count } = useCounter(5)
    expect(count.value).toBe(5)
  })

  it('increments correctly', () => {
    const { count, inc } = useCounter(0)
    inc()
    inc()
    expect(count.value).toBe(2)
  })

  it('decrements correctly', () => {
    const { count, dec } = useCounter(10)
    dec()
    expect(count.value).toBe(9)
  })
})

3. Testing Vue Components (@vue/test-utils)

// components/ProductCard.vue (simplified)
// <template><div><h3>{{product.name}}</h3>
//   <button @click="$emit('delete', product.id)">Delete</button>
// </div></template>

// tests/ProductCard.test.js
import { describe, it, expect, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import ProductCard from '@/components/ProductCard.vue'

const mockProduct = { id: 1, name: 'Test Phone', price: 299 }

describe('ProductCard', () => {
  it('renders product name', () => {
    const wrapper = mount(ProductCard, {
      props: { product: mockProduct }
    })
    expect(wrapper.find('h3').text()).toBe('Test Phone')
  })

  it('emits delete event with product id', async () => {
    const wrapper = mount(ProductCard, {
      props: { product: mockProduct }
    })
    await wrapper.find('button').trigger('click')
    expect(wrapper.emitted('delete')).toBeTruthy()
    expect(wrapper.emitted('delete')[0]).toEqual([1])
  })

  it('shows price formatted', () => {
    const wrapper = mount(ProductCard, { props: { product: mockProduct } })
    expect(wrapper.text()).toContain('299')
  })
})

4. Testing Pinia Stores

// tests/useProductStore.test.js
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { setActivePinia, createPinia }           from 'pinia'
import { useProductStore }                        from '@/stores/useProductStore'

// Mock the API
vi.mock('@/services/api', () => ({
  default: {
    get: vi.fn().mockResolvedValue({
      data: [{ id: 1, name: 'Phone', price: 299 }]
    }),
    delete: vi.fn().mockResolvedValue({}),
  }
}))

describe('useProductStore', () => {
  beforeEach(() => {
    setActivePinia(createPinia())  // fresh store each test
  })

  it('fetches products', async () => {
    const store = useProductStore()
    await store.fetchAll()
    expect(store.products).toHaveLength(1)
    expect(store.products[0].name).toBe('Phone')
  })

  it('removes product', async () => {
    const store = useProductStore()
    store.products = [{ id: 1, name: 'Phone' }, { id: 2, name: 'Laptop' }]
    await store.removeProduct(1)
    expect(store.products).toHaveLength(1)
    expect(store.products[0].id).toBe(2)
  })
})

5. Build for Production

# Build
npm run build       # outputs to dist/
npm run preview     # preview built version locally

# dist/ contents:
dist/
├── index.html
├── assets/
│   ├── index-a1b2c3.js     ← bundled JS (hashed for cache busting)
│   ├── index-x4y5z6.css    ← bundled CSS
│   └── logo-abcdef.png

# Environment variables for production
# .env.production
VITE_API_BASE_URL=https://api.yourdomain.com

# Build with specific mode
npm run build -- --mode staging

6. Deploy Options

PlatformCommandCostNotes
GitHub PagesPush to gh-pages branchFreeStatic only
Netlifydrag & drop dist/ or CLIFree tierAuto-deploy from GitHub
Vercelvercel deployFree tierBest DX, preview URLs
Nginx VPSCopy dist/ to /var/www/VPS costFull control, SPA routing config
Firebase Hostingfirebase deployFree tierGoogle CDN

Netlify Deploy

# netlify.toml (SPA routing fix)
[[redirects]]
  from   = "/*"
  to     = "/index.html"
  status = 200

# .github/workflows/deploy.yml
name: Deploy to Netlify
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20' }
      - run: npm ci && npm run build
      - uses: netlify/actions/cli@master
        with:
          args: deploy --prod --dir=dist
        env:
          NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
          NETLIFY_SITE_ID:    ${{ secrets.NETLIFY_SITE_ID }}

Nginx SPA Routing Fix

# /etc/nginx/sites-available/vue-app
server {
    listen 80;
    server_name yourdomain.com;
    root /var/www/vue-app/dist;
    index index.html;

    # SPA: all routes → index.html
    location / {
        try_files $uri $uri/ /index.html;
    }

    # Cache static assets aggressively (hashed filenames)
    location /assets/ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    gzip on;
    gzip_types text/css application/javascript;
}

7. Performance Optimization

# Analyze bundle size
npm install -D rollup-plugin-visualizer

# vite.config.js
import { visualizer } from 'rollup-plugin-visualizer'
plugins: [vue(), visualizer({ open: true })]

# Code splitting — already automatic with lazy imports
const ProductsPage = () => import('@/pages/ProductsPage.vue')

# Preload important routes
const routes = [
  { path: '/products', component: ProductsPage,
    props: true },
]

# Tips:
# - Use v-show over v-if for frequently toggled elements
# - Use :key on v-for to enable efficient patching
# - Use computed for derived values (cached)
# - Avoid large reactive objects — keep state minimal
# - Use defineAsyncComponent for heavy components

🎉 Vue.js Complete!

CDN Basics → Vite + SFC → Composition API → Router → Pinia → API → Testing → Deploy

Vite SFC Composition API Vue Router Pinia Composables Vitest Deploy

Vue.js 17 Lessons မြောက် — အားကုန် Vue ကိုသင်ပြီး Real Projects ဆောက်ဖို့ Ready !

📌 Study Checklist