12451 b553c4e519 feat: API 统一 + 设备详情补全 + H5 导航优化
- API 路由统一: 移除 /api/h5/,统一 /api/pms/ 入口
- 二进制改名: api-server -> pms-api(与服务名一致)
- nginx: 合并 /api/ + /pms/api/ 为 /api/pms/
- 新增 docs/API.md 接口文档
- 后端: CabinetDetail 增加 rssi/pow_fail/address/soc 等字段
- Redis 管道批量读取仓体实时数据(SOC/电压/电流/功率)
- DB: 所有 TIMESTAMP 列改为 DATETIME(修复类型不匹配)
- 用户管理: 新增创建时间/修改时间列,状态改用 Switch
- 角色管理/用户管理: 操作按钮改为图标
- PC 设备详情: 基本信息补全 RSSI/ICCID/电源/地址
- PC 卡片: 通道统计改用 Lucide 图标,颜色统一
- PC 二维码: canvas 渲染,含编号文字,支持右键下载
- H5: 按 uni-app 规范修正导航(navigateTo 替代 reLaunch)
- H5: 详情页加返回导航条
- H5: 扫码支持摄像头(jsQR)+ ?id= 参数
- H5: 底部 tabBar 在所有页面常显
- H5: 首页统计改为 6 项(设备/通道/空闲/充电/充满/故障)
- H5: 下拉刷新 / 上拉加载
- 产品首页: 添加业务联系
- 文档: 清理过时文件,补充缺失规范章节
2026-07-04 04:03:02 +08:00

3.7 KiB

name, description
name description
testing Unit testing stores and components with @pinia/testing

Testing Stores

Unit Testing Stores

Create a fresh pinia instance for each test:

import { setActivePinia, createPinia } from 'pinia'
import { useCounterStore } from '../src/stores/counter'

describe('Counter Store', () => {
  beforeEach(() => {
    setActivePinia(createPinia())
  })

  it('increments', () => {
    const counter = useCounterStore()
    expect(counter.n).toBe(0)
    counter.increment()
    expect(counter.n).toBe(1)
  })
})

With Plugins

import { setActivePinia, createPinia } from 'pinia'
import { createApp } from 'vue'
import { somePlugin } from '../src/stores/plugin'

const app = createApp({})

beforeEach(() => {
  const pinia = createPinia().use(somePlugin)
  app.use(pinia)
  setActivePinia(pinia)
})

Testing Components

Install @pinia/testing:

npm i -D @pinia/testing

Use createTestingPinia():

import { mount } from '@vue/test-utils'
import { createTestingPinia } from '@pinia/testing'
import { useSomeStore } from '@/stores/myStore'

const wrapper = mount(Counter, {
  global: {
    plugins: [createTestingPinia()],
  },
})

const store = useSomeStore()

// Manipulate state directly
store.name = 'new name'
store.$patch({ name: 'new name' })

// Actions are stubbed by default
store.someAction()
expect(store.someAction).toHaveBeenCalledTimes(1)

Initial State

Set initial state for tests:

const wrapper = mount(Counter, {
  global: {
    plugins: [
      createTestingPinia({
        initialState: {
          counter: { n: 20 }, // Store name → initial state
        },
      }),
    ],
  },
})

Action Stubbing

Execute Real Actions

createTestingPinia({ stubActions: false })

Selective Stubbing

// Only stub specific actions
createTestingPinia({
  stubActions: ['increment', 'reset'],
})

// Or use a function
createTestingPinia({
  stubActions: (actionName, store) => {
    if (actionName.startsWith('set')) return true
    return false
  },
})

Mock Action Return Values

import type { Mock } from 'vitest'

// After getting store
store.someAction.mockResolvedValue('mocked value')

Mocking Getters

Getters are writable in tests:

const pinia = createTestingPinia()
const counter = useCounterStore(pinia)

counter.double = 3 // Override computed value

// Reset to default behavior
counter.double = undefined
counter.double // Now computed normally

Custom Spy Function

If not using Jest/Vitest with globals:

import { vi } from 'vitest'

createTestingPinia({
  createSpy: vi.fn,
})

With Sinon:

import sinon from 'sinon'

createTestingPinia({
  createSpy: sinon.spy,
})

Pinia Plugins in Tests

Pass plugins to createTestingPinia():

import { somePlugin } from '../src/stores/plugin'

createTestingPinia({
  stubActions: false,
  plugins: [somePlugin],
})

Don't use testingPinia.use(MyPlugin) - pass plugins in options.

Type-Safe Mocked Store

import type { Mock } from 'vitest'
import type { Store, StoreDefinition } from 'pinia'

function mockedStore<TStoreDef extends () => unknown>(
  useStore: TStoreDef
): TStoreDef extends StoreDefinition<infer Id, infer State, infer Getters, infer Actions>
  ? Store<Id, State, Record<string, never>, {
      [K in keyof Actions]: Actions[K] extends (...args: any[]) => any
        ? Mock<Actions[K]>
        : Actions[K]
    }>
  : ReturnType<TStoreDef> {
  return useStore() as any
}

// Usage
const store = mockedStore(useSomeStore)
store.someAction.mockResolvedValue('value') // Typed!

E2E Tests

No special handling needed - Pinia works normally.