lili 7980c717be test(studio): Modify.vue 单测(4) + 修单测逮到的 i18n 占位符 bug
- 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>
2026-06-17 16:41:11 -07:00

101 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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()
})
})