- src/views/create/Modify.spec.ts:验三方式提交各构造契约合法请求——扩展→extend、改玩法→modify(regenerate-module,空目标默认首个 behavior)、调参→modify(deterministic,JSON 解析)、JSON 非法→Toast 不提交;记 vant mock 须 vi.hoisted(直接返回非嵌套→被提升)坑
- 修 i18n bug(单测逮到):modify.tweakValuePlaceholder `如 {"speed":5}` 的 `{}` 被 vue-i18n 当插值语法("Invalid token in placeholder")→占位符 mis-render;改为无花括号 `JSON,如 "speed": 5`(zh/en)
验证:`npm test` 16/16 绿(i18n 编译告警消除);`npm run build` 绿。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
101 lines
4.6 KiB
TypeScript
101 lines
4.6 KiB
TypeScript
/**
|
||
* Modify.spec.ts —— 改进入口组件单测(plan 002 U6)。
|
||
* 验三方式提交各构造【契约合法】请求:扩展→extend / 改玩法→modify(regenerate-module) / 调参→modify(deterministic);
|
||
* 调参 JSON 非法 → Toast 不提交。经 mock store/router/vant 隔离。跑 `npm test`。
|
||
*/
|
||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||
import { mount, flushPromises } from '@vue/test-utils'
|
||
import { createI18n } from 'vue-i18n'
|
||
|
||
// router push 间谍
|
||
const push = vi.fn()
|
||
const back = vi.fn()
|
||
vi.mock('vue-router', () => ({ useRouter: () => ({ push, back }) }))
|
||
|
||
// vant showToast 间谍(JSON 非法路径校验)。
|
||
// 注:vant mock 工厂【直接返回】showToast(非嵌套在函数内),会被 vi.mock 提升到顶→须用 vi.hoisted 先建间谍,
|
||
// 否则「Cannot access 'showToast' before initialization」(router/store 的 spy 嵌在函数内惰性引用故无此问题)。
|
||
const { showToast } = vi.hoisted(() => ({ showToast: vi.fn() }))
|
||
vi.mock('vant', () => ({ showToast }))
|
||
|
||
// store 间谍(直接断言 Modify 构造的请求对象)
|
||
const modify = vi.fn().mockResolvedValue({ id: 1002, gameId: 1 })
|
||
const extend = vi.fn().mockResolvedValue({ id: 1003, gameId: 1 })
|
||
vi.mock('../../store/create', () => ({ useCreateStore: () => ({ modify, extend }) }))
|
||
|
||
import Modify from './Modify.vue'
|
||
|
||
const i18n = createI18n({ legacy: false, locale: 'zh-CN', messages: { 'zh-CN': {} }, missingWarn: false, fallbackWarn: false })
|
||
const AppButtonStub = {
|
||
props: ['disabled', 'loading', 'block', 'size', 'type'],
|
||
emits: ['click'],
|
||
template: '<button class="app-btn" :disabled="disabled" @click="$emit(\'click\')"><slot/></button>',
|
||
}
|
||
|
||
function mountModify() {
|
||
return mount(Modify, {
|
||
props: { versionId: '9001' },
|
||
global: { plugins: [i18n], stubs: { Icon: true, AppButton: AppButtonStub } },
|
||
})
|
||
}
|
||
|
||
describe('Modify.vue(改进入口·三方式→契约请求)', () => {
|
||
beforeEach(() => {
|
||
vi.clearAllMocks()
|
||
modify.mockResolvedValue({ id: 1002, gameId: 1 })
|
||
extend.mockResolvedValue({ id: 1003, gameId: 1 })
|
||
})
|
||
|
||
it('扩展(默认 tab) → extend({baseVersionId, intent}) + 跳进度页', async () => {
|
||
const w = mountModify()
|
||
await w.find('.modify-body textarea').setValue('再加一个 boss 关卡')
|
||
await w.find('.modify-foot .app-btn').trigger('click')
|
||
await flushPromises()
|
||
expect(extend).toHaveBeenCalledWith({ baseVersionId: 9001, intent: '再加一个 boss 关卡' })
|
||
expect(modify).not.toHaveBeenCalled()
|
||
expect(push).toHaveBeenCalledOnce()
|
||
expect(push.mock.calls[0][0].params.taskId).toBe('1003')
|
||
})
|
||
|
||
it('改玩法 tab → modify(regenerate-module, kind=behavior, payload.intent);目标空走默认首个 behavior 路径', async () => {
|
||
const w = mountModify()
|
||
await w.findAll('.modify-body .seg-item')[1].trigger('click') // 改玩法
|
||
await w.find('.modify-body textarea').setValue('把陨石改成会左右摇摆')
|
||
await w.find('.modify-foot .app-btn').trigger('click')
|
||
await flushPromises()
|
||
expect(modify).toHaveBeenCalledOnce()
|
||
const arg = modify.mock.calls[0][0]
|
||
expect(arg.mode).toBe('regenerate-module')
|
||
expect(arg.target.kind).toBe('behavior')
|
||
expect(arg.target.path).toBe('/gameDefinition/behaviors/0') // 空目标 → 默认首个
|
||
expect(arg.payload.intent).toBe('把陨石改成会左右摇摆')
|
||
})
|
||
|
||
it('调参 tab → modify(deterministic, kind/path, payload.value 解析 JSON)', async () => {
|
||
const w = mountModify()
|
||
await w.findAll('.modify-body .seg-item')[2].trigger('click') // 调参(默认 kind=config)
|
||
const inputs = w.findAll('.modify-body input')
|
||
await inputs[0].setValue('/config/speed') // path
|
||
await w.find('.modify-body textarea').setValue('{"speed": 5}')
|
||
await w.find('.modify-foot .app-btn').trigger('click')
|
||
await flushPromises()
|
||
expect(modify).toHaveBeenCalledOnce()
|
||
const arg = modify.mock.calls[0][0]
|
||
expect(arg.mode).toBe('deterministic')
|
||
expect(arg.target).toEqual({ kind: 'config', path: '/config/speed' })
|
||
expect(arg.payload.value).toEqual({ speed: 5 })
|
||
})
|
||
|
||
it('调参 JSON 非法 → showToast 且不提交', async () => {
|
||
const w = mountModify()
|
||
await w.findAll('.modify-body .seg-item')[2].trigger('click')
|
||
await w.findAll('.modify-body input')[0].setValue('/config/speed')
|
||
await w.find('.modify-body textarea').setValue('{速度: 5}') // 非法 JSON
|
||
await w.find('.modify-foot .app-btn').trigger('click')
|
||
await flushPromises()
|
||
expect(showToast).toHaveBeenCalledOnce()
|
||
expect(modify).not.toHaveBeenCalled()
|
||
expect(push).not.toHaveBeenCalled()
|
||
})
|
||
})
|