diff --git a/web/frontend/src/views/__tests__/SettingsView.error-css.test.ts b/web/frontend/src/views/__tests__/SettingsView.error-css.test.ts deleted file mode 100644 index 4f2c960..0000000 --- a/web/frontend/src/views/__tests__/SettingsView.error-css.test.ts +++ /dev/null @@ -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() - 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 = {}) { - 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:/) - }) -})