Compare commits
No commits in common. "e9d481a699dd6bc50f528b2fc174b6a59928b86d" and "e9ef03b8fca55041f6c7936eac2a2d6836d2d523" have entirely different histories.
e9d481a699
...
e9ef03b8fc
1 changed files with 0 additions and 216 deletions
|
|
@ -1,216 +0,0 @@
|
||||||
/**
|
|
||||||
* KIN-UI-013: Регрессионный тест — CSS-класс ошибки в SettingsView
|
|
||||||
*
|
|
||||||
* До фикса: .startsWith('Error') — хардкод английской строки, ломался при смене локали.
|
|
||||||
* После фикса: .startsWith(t('common.error')) — использует i18n-ключ.
|
|
||||||
*
|
|
||||||
* Проверяет:
|
|
||||||
* 1. Литеральный .startsWith('Error') отсутствует в SettingsView.vue
|
|
||||||
* 2. При ошибке API — статусный span получает CSS-класс text-red-400
|
|
||||||
* 3. При успехе API — статусный span получает CSS-класс text-green-400
|
|
||||||
* 4. Покрывает все 5 статусных полей:
|
|
||||||
* saveStatus, saveTestStatus, saveDeployConfigStatus,
|
|
||||||
* saveAutoTestStatus, saveWorktreesStatus
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
||||||
import { mount, flushPromises } from '@vue/test-utils'
|
|
||||||
import { readFileSync } from 'fs'
|
|
||||||
import { resolve } from 'path'
|
|
||||||
import SettingsView from '../SettingsView.vue'
|
|
||||||
|
|
||||||
vi.mock('../../api', async (importOriginal) => {
|
|
||||||
const actual = await importOriginal<typeof import('../../api')>()
|
|
||||||
return {
|
|
||||||
...actual,
|
|
||||||
api: {
|
|
||||||
projects: vi.fn(),
|
|
||||||
projectLinks: vi.fn(),
|
|
||||||
patchProject: vi.fn(),
|
|
||||||
syncObsidian: vi.fn(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
import { api } from '../../api'
|
|
||||||
|
|
||||||
const BASE_PROJECT = {
|
|
||||||
id: 'proj-1',
|
|
||||||
name: 'Test Project',
|
|
||||||
path: '/projects/test',
|
|
||||||
status: 'active',
|
|
||||||
priority: 5,
|
|
||||||
tech_stack: ['python'],
|
|
||||||
execution_mode: null as string | null,
|
|
||||||
autocommit_enabled: null,
|
|
||||||
auto_test_enabled: null,
|
|
||||||
worktrees_enabled: null as number | null,
|
|
||||||
obsidian_vault_path: null,
|
|
||||||
deploy_command: null,
|
|
||||||
test_command: null,
|
|
||||||
deploy_host: null,
|
|
||||||
deploy_path: null,
|
|
||||||
deploy_runtime: null,
|
|
||||||
deploy_restart_cmd: null,
|
|
||||||
created_at: '2024-01-01',
|
|
||||||
total_tasks: 0,
|
|
||||||
done_tasks: 0,
|
|
||||||
active_tasks: 0,
|
|
||||||
blocked_tasks: 0,
|
|
||||||
review_tasks: 0,
|
|
||||||
project_type: null,
|
|
||||||
ssh_host: null,
|
|
||||||
ssh_user: null,
|
|
||||||
ssh_key_path: null,
|
|
||||||
ssh_proxy_jump: null,
|
|
||||||
description: null,
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
vi.clearAllMocks()
|
|
||||||
vi.mocked(api.projectLinks).mockResolvedValue([])
|
|
||||||
})
|
|
||||||
|
|
||||||
async function mountSettings(overrides: Partial<typeof BASE_PROJECT> = {}) {
|
|
||||||
const project = { ...BASE_PROJECT, ...overrides }
|
|
||||||
vi.mocked(api.projects).mockResolvedValue([project as any])
|
|
||||||
const wrapper = mount(SettingsView)
|
|
||||||
await flushPromises()
|
|
||||||
return wrapper
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
// 1. Статический анализ: .startsWith('Error') не используется
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
describe('SettingsView.vue — статический анализ', () => {
|
|
||||||
it("не содержит .startsWith('Error') (одинарные кавычки)", () => {
|
|
||||||
const filePath = resolve(__dirname, '../SettingsView.vue')
|
|
||||||
const content = readFileSync(filePath, 'utf-8')
|
|
||||||
expect(content).not.toContain(".startsWith('Error')")
|
|
||||||
})
|
|
||||||
|
|
||||||
it('не содержит .startsWith("Error") (двойные кавычки)', () => {
|
|
||||||
const filePath = resolve(__dirname, '../SettingsView.vue')
|
|
||||||
const content = readFileSync(filePath, 'utf-8')
|
|
||||||
expect(content).not.toContain('.startsWith("Error")')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('содержит .startsWith(t(\'common.error\')) — i18n-версию', () => {
|
|
||||||
const filePath = resolve(__dirname, '../SettingsView.vue')
|
|
||||||
const content = readFileSync(filePath, 'utf-8')
|
|
||||||
expect(content).toContain("startsWith(t('common.error'))")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
// 2. saveVaultPath — CSS-классы при ошибке и успехе
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
describe('SettingsView — saveVaultPath CSS-классы', () => {
|
|
||||||
it('saveStatus: text-red-400 при ошибке API', async () => {
|
|
||||||
vi.mocked(api.patchProject).mockRejectedValue(new Error('network error'))
|
|
||||||
const wrapper = await mountSettings()
|
|
||||||
|
|
||||||
const saveBtn = wrapper.findAll('button').find(b => b.text() === 'Save Vault')
|
|
||||||
expect(saveBtn?.exists()).toBe(true)
|
|
||||||
await saveBtn!.trigger('click')
|
|
||||||
await flushPromises()
|
|
||||||
|
|
||||||
const errorSpan = wrapper.find('.text-red-400')
|
|
||||||
expect(errorSpan.exists()).toBe(true)
|
|
||||||
expect(errorSpan.text()).toMatch(/^Error:/)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('saveStatus: text-green-400 при успехе API', async () => {
|
|
||||||
vi.mocked(api.patchProject).mockResolvedValue({} as any)
|
|
||||||
const wrapper = await mountSettings()
|
|
||||||
|
|
||||||
const saveBtn = wrapper.findAll('button').find(b => b.text() === 'Save Vault')
|
|
||||||
await saveBtn!.trigger('click')
|
|
||||||
await flushPromises()
|
|
||||||
|
|
||||||
const successSpan = wrapper.find('.text-green-400')
|
|
||||||
expect(successSpan.exists()).toBe(true)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
// 3. saveTestCommand — CSS-класс при ошибке
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
describe('SettingsView — saveTestCommand CSS-классы', () => {
|
|
||||||
it('saveTestStatus: text-red-400 при ошибке API', async () => {
|
|
||||||
vi.mocked(api.patchProject).mockRejectedValue(new Error('test save failed'))
|
|
||||||
const wrapper = await mountSettings()
|
|
||||||
|
|
||||||
const saveBtn = wrapper.findAll('button').find(b => b.text() === 'Save Test')
|
|
||||||
expect(saveBtn?.exists()).toBe(true)
|
|
||||||
await saveBtn!.trigger('click')
|
|
||||||
await flushPromises()
|
|
||||||
|
|
||||||
const errorSpan = wrapper.find('.text-red-400')
|
|
||||||
expect(errorSpan.exists()).toBe(true)
|
|
||||||
expect(errorSpan.text()).toMatch(/^Error:/)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
// 4. saveDeployConfig — CSS-класс при ошибке
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
describe('SettingsView — saveDeployConfig CSS-классы', () => {
|
|
||||||
it('saveDeployConfigStatus: text-red-400 при ошибке API', async () => {
|
|
||||||
vi.mocked(api.patchProject).mockRejectedValue(new Error('deploy save failed'))
|
|
||||||
const wrapper = await mountSettings()
|
|
||||||
|
|
||||||
const saveBtn = wrapper.findAll('button').find(b => b.text() === 'Save Deploy Config')
|
|
||||||
expect(saveBtn?.exists()).toBe(true)
|
|
||||||
await saveBtn!.trigger('click')
|
|
||||||
await flushPromises()
|
|
||||||
|
|
||||||
const errorSpan = wrapper.find('.text-red-400')
|
|
||||||
expect(errorSpan.exists()).toBe(true)
|
|
||||||
expect(errorSpan.text()).toMatch(/^Error:/)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
// 5. toggleAutoTest — CSS-класс при ошибке
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
describe('SettingsView — toggleAutoTest CSS-классы', () => {
|
|
||||||
it('saveAutoTestStatus: text-red-400 при ошибке API', async () => {
|
|
||||||
vi.mocked(api.patchProject).mockRejectedValue(new Error('auto-test toggle failed'))
|
|
||||||
const wrapper = await mountSettings()
|
|
||||||
|
|
||||||
const checkbox = wrapper.findAll('input[type="checkbox"]').find((el) => {
|
|
||||||
const label = el.element.closest('label')
|
|
||||||
return label?.textContent?.includes('Auto-test')
|
|
||||||
})
|
|
||||||
expect(checkbox?.exists()).toBe(true)
|
|
||||||
await checkbox!.trigger('change')
|
|
||||||
await flushPromises()
|
|
||||||
|
|
||||||
const errorSpan = wrapper.find('.text-red-400')
|
|
||||||
expect(errorSpan.exists()).toBe(true)
|
|
||||||
expect(errorSpan.text()).toMatch(/^Error:/)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
// 6. toggleWorktrees — CSS-класс при ошибке
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
describe('SettingsView — toggleWorktrees CSS-классы', () => {
|
|
||||||
it('saveWorktreesStatus: text-red-400 при ошибке API', async () => {
|
|
||||||
vi.mocked(api.patchProject).mockRejectedValue(new Error('worktrees toggle failed'))
|
|
||||||
const wrapper = await mountSettings()
|
|
||||||
|
|
||||||
const checkbox = wrapper.findAll('input[type="checkbox"]').find((el) => {
|
|
||||||
const label = el.element.closest('label')
|
|
||||||
return label?.textContent?.includes('Worktrees')
|
|
||||||
})
|
|
||||||
expect(checkbox?.exists()).toBe(true)
|
|
||||||
await checkbox!.trigger('change')
|
|
||||||
await flushPromises()
|
|
||||||
|
|
||||||
const errorSpan = wrapper.find('.text-red-400')
|
|
||||||
expect(errorSpan.exists()).toBe(true)
|
|
||||||
expect(errorSpan.text()).toMatch(/^Error:/)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue