This commit is contained in:
41
CONTEXT.md
41
CONTEXT.md
@ -6,9 +6,9 @@
|
||||
|
||||
## Текущий статус
|
||||
|
||||
**Этап:** Фаза 3.1 завершена ✅ | Фаза 3.2 запланирована 📋
|
||||
**Фаза MVP:** Базовый функционал + авторизация + расширенный функционал + AI-оценка + мини-ТЗ + история ТЗ готовы
|
||||
**Следующий этап:** Фаза 3.2 — Полный просмотр идеи (все поля)
|
||||
**Этап:** Фаза 3.2 завершена ✅
|
||||
**Фаза MVP:** Базовый функционал + авторизация + расширенный функционал + AI-оценка + мини-ТЗ + история ТЗ + полный просмотр идеи готовы
|
||||
**Следующий этап:** Фаза 4 — Права доступа
|
||||
**Последнее обновление:** 2026-01-15
|
||||
|
||||
---
|
||||
@ -82,6 +82,12 @@
|
||||
| 2026-01-15 | **Планирование:** Добавлены новые требования — права доступа, аудит, WebSocket, темная тема, экспорт |
|
||||
| 2026-01-15 | **Документация:** Обновлены REQUIREMENTS.md, ARCHITECTURE.md, ROADMAP.md — добавлены Фазы 4-8 |
|
||||
| 2026-01-15 | **Планирование:** Добавлена Фаза 3.2 — Полный просмотр идеи (все поля доступны для просмотра и редактирования) |
|
||||
| 2026-01-15 | **Фаза 3.2:** Добавлены колонки pain, aiRole, verificationMethod в таблицу идей |
|
||||
| 2026-01-15 | **Фаза 3.2:** ColumnVisibility компонент — управление видимостью колонок (Settings icon), сохранение в localStorage |
|
||||
| 2026-01-15 | **Фаза 3.2:** IdeaDetailModal компонент — просмотр всех полей идеи, режим редактирования, интеграция с ТЗ и AI-оценкой |
|
||||
| 2026-01-15 | **Фаза 3.2:** Кнопка "Подробнее" (Visibility icon) в actions колонке для открытия детального просмотра |
|
||||
| 2026-01-15 | **Фаза 3.2:** Исправлен баг — статус ТЗ сохраняется при редактировании идеи в модалке |
|
||||
| 2026-01-15 | **Testing:** E2E тесты Фазы 3.2 (Playwright) — 15 тестов покрывают детальный просмотр, редактирование, column visibility |
|
||||
|
||||
---
|
||||
|
||||
@ -89,23 +95,23 @@
|
||||
|
||||
> Смотри [ROADMAP.md](ROADMAP.md) для полного плана разработки
|
||||
|
||||
**Готово:** Фазы 0-3.1 завершены ✅
|
||||
**Следующий шаг:** Фаза 3.2 — Полный просмотр идеи 📋
|
||||
**Готово:** Фазы 0-3.2 завершены ✅
|
||||
**Следующий шаг:** Фаза 4 — Права доступа 📋
|
||||
|
||||
### Фаза 3.2: Полный просмотр идеи
|
||||
### Фаза 3.2: Полный просмотр идеи ✅
|
||||
|
||||
**Колонки в таблице:**
|
||||
- [ ] Колонки pain, aiRole, verificationMethod
|
||||
- [ ] Column visibility (скрытие/показ колонок, localStorage)
|
||||
- [x] Колонки pain, aiRole, verificationMethod
|
||||
- [x] Column visibility (скрытие/показ колонок, localStorage)
|
||||
|
||||
**Модалка IdeaDetailModal:**
|
||||
- [ ] Режим просмотра (readonly по умолчанию)
|
||||
- [ ] Режим редактирования (кнопка "Редактировать")
|
||||
- [ ] Кнопки "Сохранить" / "Отмена"
|
||||
- [ ] Быстрый доступ к ТЗ и AI-оценке
|
||||
- [x] Режим просмотра (readonly по умолчанию)
|
||||
- [x] Режим редактирования (кнопка "Редактировать")
|
||||
- [x] Кнопки "Сохранить" / "Отмена"
|
||||
- [x] Быстрый доступ к ТЗ и AI-оценке
|
||||
|
||||
**E2E тесты:**
|
||||
- [ ] Column visibility, модалка, редактирование, сохранение
|
||||
- [x] Column visibility, модалка, редактирование, сохранение (15 тестов)
|
||||
|
||||
### Новые требования (Фазы 4-8):
|
||||
|
||||
@ -163,7 +169,8 @@ team-planner/
|
||||
│ ├── auth.setup.ts # Авторизация для тестов (Keycloak)
|
||||
│ ├── phase1.spec.ts # Тесты Фазы 1 (17 тестов)
|
||||
│ ├── phase2.spec.ts # Тесты Фазы 2 (37 тестов — D&D, цвета, комментарии, команда)
|
||||
│ └── phase3.spec.ts # Тесты Фазы 3 (20 тестов — AI-оценка + мини-ТЗ)
|
||||
│ ├── phase3.spec.ts # Тесты Фазы 3 (20 тестов — AI-оценка + мини-ТЗ)
|
||||
│ └── phase3.2.spec.ts # Тесты Фазы 3.2 (15 тестов — детальный просмотр, column visibility) ✅
|
||||
├── backend/ # NestJS API
|
||||
│ ├── src/
|
||||
│ │ ├── auth/ # Модуль авторизации ✅
|
||||
@ -194,7 +201,8 @@ team-planner/
|
||||
│ │ ├── IdeasTable/
|
||||
│ │ │ ├── IdeasTable.tsx # Таблица с DndContext
|
||||
│ │ │ ├── DraggableRow.tsx # Сортируемая строка (useSortable)
|
||||
│ │ │ ├── columns.tsx # Колонки + drag handle
|
||||
│ │ │ ├── columns.tsx # Колонки + drag handle (13 колонок)
|
||||
│ │ │ ├── ColumnVisibility.tsx # Управление видимостью колонок ✅
|
||||
│ │ │ └── ...
|
||||
│ │ ├── IdeasFilters/ # Фильтры
|
||||
│ │ ├── CreateIdeaModal/ # Модалка создания
|
||||
@ -204,7 +212,8 @@ team-planner/
|
||||
│ │ │ └── RolesManager.tsx # Управление ролями
|
||||
│ │ ├── CommentsPanel/ # Комментарии к идеям
|
||||
│ │ ├── AiEstimateModal/ # Модалка AI-оценки (Фаза 3) ✅
|
||||
│ │ └── SpecificationModal/ # Модалка мини-ТЗ (Фаза 3.1) ✅
|
||||
│ │ ├── SpecificationModal/ # Модалка мини-ТЗ (Фаза 3.1) ✅
|
||||
│ │ └── IdeaDetailModal/ # Модалка детального просмотра (Фаза 3.2) ✅
|
||||
│ ├── hooks/
|
||||
│ │ ├── useIdeas.ts # React Query хуки + useReorderIdeas
|
||||
│ │ └── useAi.ts # useEstimateIdea + useGenerateSpecification + history hooks ✅
|
||||
|
||||
72
ROADMAP.md
72
ROADMAP.md
@ -15,7 +15,7 @@
|
||||
| 2 | Расширенный функционал | ✅ Завершена | Drag&Drop, цвета, комментарии, команда |
|
||||
| 3 | AI-интеграция | ✅ Завершена | Оценка времени, рекомендации |
|
||||
| 3.1 | Генерация мини-ТЗ | ✅ Завершена | Генерация, редактирование, история ТЗ |
|
||||
| 3.2 | Полный просмотр идеи | 📋 Планируется | Просмотр и редактирование всех полей |
|
||||
| 3.2 | Полный просмотр идеи | ✅ Завершена | Просмотр и редактирование всех полей |
|
||||
| 4 | Права доступа | 📋 Планируется | Гранулярные права, панель админа |
|
||||
| 5 | Аудит и история | 📋 Планируется | Логирование действий, восстановление |
|
||||
| 6 | Real-time и WebSocket | 📋 Планируется | Многопользовательская работа |
|
||||
@ -248,47 +248,51 @@
|
||||
|
||||
---
|
||||
|
||||
## Фаза 3.2: Полный просмотр идеи 📋
|
||||
## Фаза 3.2: Полный просмотр идеи ✅
|
||||
|
||||
> **Просмотр и редактирование ВСЕХ полей идеи**
|
||||
|
||||
### Проблема
|
||||
Сейчас в таблице отображаются не все поля идеи. Поля `pain`, `aiRole`, `verificationMethod` невозможно ни посмотреть, ни отредактировать.
|
||||
### Проблема (решена)
|
||||
Ранее в таблице отображались не все поля идеи. Поля `pain`, `aiRole`, `verificationMethod` было невозможно ни посмотреть, ни отредактировать.
|
||||
|
||||
### Frontend — Дополнительные колонки в таблице
|
||||
- [ ] Добавить колонку "Боль" (pain) с inline-редактированием
|
||||
- [ ] Добавить колонку "Роль AI" (aiRole) с inline-редактированием
|
||||
- [ ] Добавить колонку "Способ проверки" (verificationMethod) с inline-редактированием
|
||||
- [ ] Column visibility — возможность скрыть/показать колонки
|
||||
- [ ] Кнопка настройки колонок (⚙️) в header таблицы
|
||||
- [ ] Dropdown с чекбоксами для каждой колонки
|
||||
- [ ] Сохранение настроек в localStorage
|
||||
- [ ] data-testid для новых колонок
|
||||
- [x] Добавить колонку "Боль" (pain) с inline-редактированием
|
||||
- [x] Добавить колонку "Роль AI" (aiRole) с inline-редактированием
|
||||
- [x] Добавить колонку "Способ проверки" (verificationMethod) с inline-редактированием
|
||||
- [x] Column visibility — возможность скрыть/показать колонки
|
||||
- [x] Кнопка настройки колонок (⚙️) в header таблицы
|
||||
- [x] Dropdown с чекбоксами для каждой колонки
|
||||
- [x] Сохранение настроек в localStorage
|
||||
- [x] data-testid для новых колонок
|
||||
|
||||
### Frontend — Модалка детального просмотра
|
||||
- [ ] IdeaDetailModal компонент
|
||||
- [ ] Открытие по кнопке "Подробнее" (👁️ Visibility icon)
|
||||
- [ ] **Режим просмотра** (по умолчанию):
|
||||
- [ ] Все поля отображаются как readonly текст
|
||||
- [ ] Кнопка "Редактировать" для перехода в режим редактирования
|
||||
- [ ] **Режим редактирования**:
|
||||
- [ ] Все редактируемые поля становятся input/textarea/select
|
||||
- [ ] Кнопка "Сохранить" — сохраняет изменения и возвращает в режим просмотра
|
||||
- [ ] Кнопка "Отмена" — отменяет изменения и возвращает в режим просмотра
|
||||
- [ ] Поля для редактирования: title, description, status, priority, module, targetAudience, pain, aiRole, verificationMethod
|
||||
- [ ] Readonly поля (только просмотр): estimatedHours, complexity, createdAt, updatedAt
|
||||
- [ ] Быстрый доступ: кнопки "Открыть ТЗ" и "AI-оценка"
|
||||
- [ ] Кнопка "Подробнее" в колонке actions
|
||||
- [ ] data-testid для всех элементов модалки
|
||||
- [x] IdeaDetailModal компонент
|
||||
- [x] Открытие по кнопке "Подробнее" (👁️ Visibility icon)
|
||||
- [x] **Режим просмотра** (по умолчанию):
|
||||
- [x] Все поля отображаются как readonly текст
|
||||
- [x] Кнопка "Редактировать" для перехода в режим редактирования
|
||||
- [x] **Режим редактирования**:
|
||||
- [x] Все редактируемые поля становятся input/textarea/select
|
||||
- [x] Кнопка "Сохранить" — сохраняет изменения и возвращает в режим просмотра
|
||||
- [x] Кнопка "Отмена" — отменяет изменения и возвращает в режим просмотра
|
||||
- [x] Поля для редактирования: title, description, status, priority, module, targetAudience, pain, aiRole, verificationMethod
|
||||
- [x] Readonly поля (только просмотр): estimatedHours, complexity, createdAt, updatedAt
|
||||
- [x] Быстрый доступ: кнопки "Открыть ТЗ" и "AI-оценка"
|
||||
- [x] Кнопка "Подробнее" в колонке actions
|
||||
- [x] data-testid для всех элементов модалки
|
||||
|
||||
### E2E тестирование
|
||||
- [ ] Column visibility — скрытие/показ колонок
|
||||
- [ ] Открытие модалки детального просмотра
|
||||
- [ ] Просмотр всех полей в режиме readonly
|
||||
- [ ] Переход в режим редактирования
|
||||
- [ ] Редактирование полей pain, aiRole, verificationMethod
|
||||
- [ ] Сохранение изменений
|
||||
- [ ] Отмена редактирования
|
||||
### Исправлен баг
|
||||
- [x] Статус ТЗ сохраняется при редактировании идеи в модалке (обновляются только отправленные поля)
|
||||
|
||||
### E2E тестирование (15 тестов)
|
||||
- [x] Column visibility — скрытие/показ колонок
|
||||
- [x] Открытие модалки детального просмотра
|
||||
- [x] Просмотр всех полей в режиме readonly
|
||||
- [x] Переход в режим редактирования
|
||||
- [x] Редактирование полей pain, aiRole, verificationMethod
|
||||
- [x] Сохранение изменений
|
||||
- [x] Отмена редактирования
|
||||
- [x] Регрессионный тест на сохранение статуса ТЗ
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -230,8 +230,19 @@ export function IdeasTable() {
|
||||
updateIdea.mutate(
|
||||
{ id, dto },
|
||||
{
|
||||
onSuccess: (updatedIdea) => {
|
||||
setDetailIdea(updatedIdea);
|
||||
onSuccess: () => {
|
||||
// Обновляем только те поля, которые были отправлены в dto
|
||||
// Это сохраняет specification и другие поля которые не редактировались
|
||||
setDetailIdea((prev) => {
|
||||
if (!prev) return prev;
|
||||
const updates: Partial<Idea> = {};
|
||||
(Object.keys(dto) as (keyof UpdateIdeaDto)[]).forEach((key) => {
|
||||
if (dto[key] !== undefined) {
|
||||
(updates as Record<string, unknown>)[key] = dto[key];
|
||||
}
|
||||
});
|
||||
return { ...prev, ...updates };
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
4
frontend/test-results/.last-run.json
Normal file
4
frontend/test-results/.last-run.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"status": "failed",
|
||||
"failedTests": []
|
||||
}
|
||||
314
tests/e2e/phase3.2.spec.ts
Normal file
314
tests/e2e/phase3.2.spec.ts
Normal file
@ -0,0 +1,314 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* E2E тесты для Фазы 3.2 Team Planner
|
||||
* - Детальный просмотр идеи (IdeaDetailModal)
|
||||
* - Управление видимостью колонок
|
||||
*
|
||||
* Используем data-testid для стабильных селекторов
|
||||
*/
|
||||
|
||||
test.describe('Фаза 3.2: Детальный просмотр идеи', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForSelector('[data-testid="ideas-table"]', { timeout: 10000 });
|
||||
});
|
||||
|
||||
test('Кнопка "Подробнее" присутствует в каждой строке', async ({ page }) => {
|
||||
const emptyState = page.locator('[data-testid="ideas-empty-state"]');
|
||||
const hasData = !(await emptyState.isVisible().catch(() => false));
|
||||
|
||||
if (hasData) {
|
||||
const viewButtons = page.locator('[data-testid="view-details-button"]');
|
||||
const buttonCount = await viewButtons.count();
|
||||
expect(buttonCount).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
|
||||
test('Клик на кнопку "Подробнее" открывает модалку', async ({ page }) => {
|
||||
const emptyState = page.locator('[data-testid="ideas-empty-state"]');
|
||||
const hasData = !(await emptyState.isVisible().catch(() => false));
|
||||
|
||||
test.skip(!hasData, 'Нет данных для тестирования');
|
||||
|
||||
const viewButton = page.locator('[data-testid="view-details-button"]').first();
|
||||
await viewButton.click();
|
||||
|
||||
const modal = page.locator('[data-testid="idea-detail-modal"]');
|
||||
await expect(modal).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
test('Модалка показывает заголовок идеи', async ({ page }) => {
|
||||
const emptyState = page.locator('[data-testid="ideas-empty-state"]');
|
||||
const hasData = !(await emptyState.isVisible().catch(() => false));
|
||||
|
||||
test.skip(!hasData, 'Нет данных для тестирования');
|
||||
|
||||
const viewButton = page.locator('[data-testid="view-details-button"]').first();
|
||||
await viewButton.click();
|
||||
|
||||
const modal = page.locator('[data-testid="idea-detail-modal"]');
|
||||
await expect(modal).toBeVisible({ timeout: 5000 });
|
||||
|
||||
// Проверяем наличие заголовка
|
||||
const title = modal.locator('[data-testid="idea-detail-title"]');
|
||||
await expect(title).toBeVisible();
|
||||
const titleText = await title.textContent();
|
||||
expect(titleText?.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('Модалка показывает все поля идеи', async ({ page }) => {
|
||||
const emptyState = page.locator('[data-testid="ideas-empty-state"]');
|
||||
const hasData = !(await emptyState.isVisible().catch(() => false));
|
||||
|
||||
test.skip(!hasData, 'Нет данных для тестирования');
|
||||
|
||||
const viewButton = page.locator('[data-testid="view-details-button"]').first();
|
||||
await viewButton.click();
|
||||
|
||||
const modal = page.locator('[data-testid="idea-detail-modal"]');
|
||||
await expect(modal).toBeVisible({ timeout: 5000 });
|
||||
|
||||
// Проверяем наличие основных полей
|
||||
await expect(modal.locator('[data-testid="idea-detail-status"]')).toBeVisible();
|
||||
await expect(modal.locator('[data-testid="idea-detail-priority"]')).toBeVisible();
|
||||
await expect(modal.locator('[data-testid="idea-detail-description"]')).toBeVisible();
|
||||
await expect(modal.locator('[data-testid="idea-detail-specification-status"]')).toBeVisible();
|
||||
});
|
||||
|
||||
test('Кнопка редактирования переводит модалку в режим редактирования', async ({ page }) => {
|
||||
const emptyState = page.locator('[data-testid="ideas-empty-state"]');
|
||||
const hasData = !(await emptyState.isVisible().catch(() => false));
|
||||
|
||||
test.skip(!hasData, 'Нет данных для тестирования');
|
||||
|
||||
const viewButton = page.locator('[data-testid="view-details-button"]').first();
|
||||
await viewButton.click();
|
||||
|
||||
const modal = page.locator('[data-testid="idea-detail-modal"]');
|
||||
await expect(modal).toBeVisible({ timeout: 5000 });
|
||||
|
||||
// Нажимаем "Редактировать"
|
||||
const editButton = modal.locator('[data-testid="idea-detail-edit-button"]');
|
||||
await editButton.click();
|
||||
|
||||
// Должны появиться поля ввода
|
||||
await expect(modal.locator('[data-testid="idea-detail-title-input"]')).toBeVisible();
|
||||
|
||||
// И кнопки сохранения/отмены
|
||||
await expect(modal.locator('[data-testid="idea-detail-save-button"]')).toBeVisible();
|
||||
await expect(modal.locator('[data-testid="idea-detail-cancel-button"]')).toBeVisible();
|
||||
});
|
||||
|
||||
test('Кнопка "Отмена" возвращает режим просмотра', async ({ page }) => {
|
||||
const emptyState = page.locator('[data-testid="ideas-empty-state"]');
|
||||
const hasData = !(await emptyState.isVisible().catch(() => false));
|
||||
|
||||
test.skip(!hasData, 'Нет данных для тестирования');
|
||||
|
||||
const viewButton = page.locator('[data-testid="view-details-button"]').first();
|
||||
await viewButton.click();
|
||||
|
||||
const modal = page.locator('[data-testid="idea-detail-modal"]');
|
||||
await expect(modal).toBeVisible({ timeout: 5000 });
|
||||
|
||||
// Нажимаем "Редактировать"
|
||||
await modal.locator('[data-testid="idea-detail-edit-button"]').click();
|
||||
await expect(modal.locator('[data-testid="idea-detail-title-input"]')).toBeVisible();
|
||||
|
||||
// Нажимаем "Отмена"
|
||||
await modal.locator('[data-testid="idea-detail-cancel-button"]').click();
|
||||
|
||||
// Должен вернуться режим просмотра
|
||||
await expect(modal.locator('[data-testid="idea-detail-title"]')).toBeVisible();
|
||||
await expect(modal.locator('[data-testid="idea-detail-edit-button"]')).toBeVisible();
|
||||
});
|
||||
|
||||
test('Сохранение изменений работает корректно', async ({ page }) => {
|
||||
const emptyState = page.locator('[data-testid="ideas-empty-state"]');
|
||||
const hasData = !(await emptyState.isVisible().catch(() => false));
|
||||
|
||||
test.skip(!hasData, 'Нет данных для тестирования');
|
||||
|
||||
const viewButton = page.locator('[data-testid="view-details-button"]').first();
|
||||
await viewButton.click();
|
||||
|
||||
const modal = page.locator('[data-testid="idea-detail-modal"]');
|
||||
await expect(modal).toBeVisible({ timeout: 5000 });
|
||||
|
||||
// Запоминаем исходный заголовок
|
||||
const originalTitle = await modal.locator('[data-testid="idea-detail-title"]').textContent();
|
||||
|
||||
// Нажимаем "Редактировать"
|
||||
await modal.locator('[data-testid="idea-detail-edit-button"]').click();
|
||||
|
||||
// Изменяем заголовок
|
||||
const titleInput = modal.locator('[data-testid="idea-detail-title-input"]');
|
||||
const newTitle = `${originalTitle} (изменено ${Date.now()})`;
|
||||
await titleInput.fill(newTitle);
|
||||
|
||||
// Сохраняем
|
||||
await modal.locator('[data-testid="idea-detail-save-button"]').click();
|
||||
|
||||
// Ждём возврата в режим просмотра
|
||||
await expect(modal.locator('[data-testid="idea-detail-title"]')).toBeVisible({ timeout: 5000 });
|
||||
|
||||
// Проверяем что заголовок обновился
|
||||
const updatedTitle = await modal.locator('[data-testid="idea-detail-title"]').textContent();
|
||||
expect(updatedTitle).toBe(newTitle);
|
||||
});
|
||||
|
||||
test('Статус ТЗ сохраняется после редактирования идеи', async ({ page }) => {
|
||||
/**
|
||||
* Регрессионный тест:
|
||||
* 1. Сгенерировать ТЗ для идеи
|
||||
* 2. Открыть модалку детального просмотра
|
||||
* 3. Отредактировать и сохранить
|
||||
* 4. Статус ТЗ должен остаться "Есть"
|
||||
*/
|
||||
const emptyState = page.locator('[data-testid="ideas-empty-state"]');
|
||||
const hasData = !(await emptyState.isVisible().catch(() => false));
|
||||
|
||||
test.skip(!hasData, 'Нет данных для тестирования');
|
||||
|
||||
// Сначала генерируем ТЗ для первой идеи
|
||||
const firstRow = page.locator('[data-testid^="idea-row-"]').first();
|
||||
const specButton = firstRow.locator('[data-testid="specification-button"]');
|
||||
await specButton.click();
|
||||
|
||||
const specModal = page.locator('[data-testid="specification-modal"]');
|
||||
await expect(specModal).toBeVisible({ timeout: 5000 });
|
||||
|
||||
// Ждём пока сгенерируется ТЗ (контент или ошибка)
|
||||
const specContent = specModal.locator('[data-testid="specification-content"]');
|
||||
const specError = specModal.locator('[data-testid="specification-error"]');
|
||||
await expect(specContent.or(specError)).toBeVisible({ timeout: 60000 });
|
||||
|
||||
const hasSpec = await specContent.isVisible().catch(() => false);
|
||||
test.skip(!hasSpec, 'Не удалось сгенерировать ТЗ');
|
||||
|
||||
// Закрываем модалку ТЗ
|
||||
await page.locator('[data-testid="specification-close-button"]').click();
|
||||
await expect(specModal).not.toBeVisible({ timeout: 3000 });
|
||||
|
||||
// Теперь открываем детальный просмотр
|
||||
const viewButton = firstRow.locator('[data-testid="view-details-button"]');
|
||||
await viewButton.click();
|
||||
|
||||
const detailModal = page.locator('[data-testid="idea-detail-modal"]');
|
||||
await expect(detailModal).toBeVisible({ timeout: 5000 });
|
||||
|
||||
// Проверяем что ТЗ есть
|
||||
const specStatus = detailModal.locator('[data-testid="idea-detail-specification-status"]');
|
||||
await expect(specStatus).toContainText('Есть');
|
||||
|
||||
// Нажимаем "Редактировать"
|
||||
await detailModal.locator('[data-testid="idea-detail-edit-button"]').click();
|
||||
|
||||
// Меняем описание (не трогаем ТЗ)
|
||||
const descInput = detailModal.locator('[data-testid="idea-detail-description-input"]');
|
||||
await descInput.fill('Обновлённое описание для теста ' + Date.now());
|
||||
|
||||
// Сохраняем
|
||||
await detailModal.locator('[data-testid="idea-detail-save-button"]').click();
|
||||
|
||||
// Ждём возврата в режим просмотра
|
||||
await expect(detailModal.locator('[data-testid="idea-detail-title"]')).toBeVisible({ timeout: 5000 });
|
||||
|
||||
// БАГ: Статус ТЗ должен остаться "Есть", но показывает "Нет"
|
||||
await expect(specStatus).toContainText('Есть');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Фаза 3.2: Управление видимостью колонок', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForSelector('[data-testid="ideas-table"]', { timeout: 10000 });
|
||||
});
|
||||
|
||||
test('Кнопка настройки колонок присутствует', async ({ page }) => {
|
||||
const visibilityButton = page.locator('[data-testid="column-visibility-button"]');
|
||||
await expect(visibilityButton).toBeVisible();
|
||||
});
|
||||
|
||||
test('Клик на кнопку открывает меню с колонками', async ({ page }) => {
|
||||
const visibilityButton = page.locator('[data-testid="column-visibility-button"]');
|
||||
await visibilityButton.click();
|
||||
|
||||
const menu = page.locator('[data-testid="column-visibility-menu"]');
|
||||
await expect(menu).toBeVisible({ timeout: 3000 });
|
||||
});
|
||||
|
||||
test('Меню содержит опции для всех колонок', async ({ page }) => {
|
||||
const visibilityButton = page.locator('[data-testid="column-visibility-button"]');
|
||||
await visibilityButton.click();
|
||||
|
||||
const menu = page.locator('[data-testid="column-visibility-menu"]');
|
||||
await expect(menu).toBeVisible({ timeout: 3000 });
|
||||
|
||||
// Проверяем наличие основных колонок
|
||||
await expect(menu.locator('[data-testid="column-visibility-item-status"]')).toBeVisible();
|
||||
await expect(menu.locator('[data-testid="column-visibility-item-priority"]')).toBeVisible();
|
||||
await expect(menu.locator('[data-testid="column-visibility-item-description"]')).toBeVisible();
|
||||
});
|
||||
|
||||
test('Переключение видимости колонки работает', async ({ page }) => {
|
||||
// Проверяем что колонка "Описание" видна
|
||||
const table = page.locator('[data-testid="ideas-table"]');
|
||||
const descHeader = table.locator('th', { hasText: 'Описание' });
|
||||
await expect(descHeader).toBeVisible();
|
||||
|
||||
// Открываем меню
|
||||
const visibilityButton = page.locator('[data-testid="column-visibility-button"]');
|
||||
await visibilityButton.click();
|
||||
|
||||
// Скрываем колонку "Описание"
|
||||
const descItem = page.locator('[data-testid="column-visibility-item-description"]');
|
||||
await descItem.click();
|
||||
|
||||
// Закрываем меню кликом вне его
|
||||
await page.keyboard.press('Escape');
|
||||
|
||||
// Колонка должна скрыться
|
||||
await expect(descHeader).not.toBeVisible();
|
||||
|
||||
// Возвращаем обратно
|
||||
await visibilityButton.click();
|
||||
await descItem.click();
|
||||
await page.keyboard.press('Escape');
|
||||
|
||||
// Колонка должна появиться снова
|
||||
await expect(descHeader).toBeVisible();
|
||||
});
|
||||
|
||||
test('Кнопка "Показать все" возвращает все колонки', async ({ page }) => {
|
||||
// Скрываем несколько колонок
|
||||
const visibilityButton = page.locator('[data-testid="column-visibility-button"]');
|
||||
await visibilityButton.click();
|
||||
|
||||
await page.locator('[data-testid="column-visibility-item-description"]').click();
|
||||
await page.locator('[data-testid="column-visibility-item-module"]').click();
|
||||
|
||||
// Нажимаем "Показать все"
|
||||
await page.locator('[data-testid="column-visibility-show-all"]').click();
|
||||
await page.keyboard.press('Escape');
|
||||
|
||||
// Все колонки должны быть видны
|
||||
const table = page.locator('[data-testid="ideas-table"]');
|
||||
await expect(table.locator('th', { hasText: 'Описание' })).toBeVisible();
|
||||
await expect(table.locator('th', { hasText: 'Модуль' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('Название и действия нельзя скрыть', async ({ page }) => {
|
||||
const visibilityButton = page.locator('[data-testid="column-visibility-button"]');
|
||||
await visibilityButton.click();
|
||||
|
||||
// Проверяем что "Название" disabled
|
||||
const titleItem = page.locator('[data-testid="column-visibility-item-title"]');
|
||||
await expect(titleItem).toHaveAttribute('aria-disabled', 'true');
|
||||
|
||||
// Действия тоже disabled
|
||||
const actionsItem = page.locator('[data-testid="column-visibility-item-actions"]');
|
||||
await expect(actionsItem).toHaveAttribute('aria-disabled', 'true');
|
||||
});
|
||||
});
|
||||
@ -2,7 +2,7 @@
|
||||
"cookies": [
|
||||
{
|
||||
"name": "AUTH_SESSION_ID",
|
||||
"value": "aDItMWtlTDNkMEdGUGE1TTFqYmxvOFNyLjhnZHRLSUVtbW5ZZmp1RkpBc2lmdnROSWVIY3RLRXdlZmloN2I0WmV4UTRlVWY5dnRYVFZZNHlSdlE2OTdEVVJHT2NVNE11cGd1eVQ4RzVseUxnYThn.keycloak-keycloakx-0-24485",
|
||||
"value": "eDY0aWpRc3U0UEE3aTN0U2NranNrY29HLlVsTFZJcHVkMlNYd0g3LUkyMTZqdTQzak41bjRnX0FHQzJSZk4tUGp5eFJsYlB5VFBLbTloN2lEMFR6b2lCQ0RISjBtV2JqTmROdl9kOTBpMjlpRjFB.keycloak-keycloakx-0-37885",
|
||||
"domain": "auth.vigdorov.ru",
|
||||
"path": "/realms/team-planner/",
|
||||
"expires": -1,
|
||||
@ -12,17 +12,17 @@
|
||||
},
|
||||
{
|
||||
"name": "KC_AUTH_SESSION_HASH",
|
||||
"value": "\"w/aalxg9yi+TKbWYZgi8KimwA5UYaExWPPJvZT0MfoE\"",
|
||||
"value": "\"IDfhLlT83e6gUgo0mOmir0agF4uMho/Bgfm9pjzSUVA\"",
|
||||
"domain": "auth.vigdorov.ru",
|
||||
"path": "/realms/team-planner/",
|
||||
"expires": 1768433639.802328,
|
||||
"expires": 1768467678.660249,
|
||||
"httpOnly": false,
|
||||
"secure": true,
|
||||
"sameSite": "None"
|
||||
},
|
||||
{
|
||||
"name": "KEYCLOAK_IDENTITY",
|
||||
"value": "eyJhbGciOiJIUzUxMiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI2ZDRjMWU2My1hNTllLTQ0NjAtYThkYy05YTRiZjFjMTRjMDMifQ.eyJleHAiOjE3Njg0Njk1ODEsImlhdCI6MTc2ODQzMzU4MSwianRpIjoiOGVhZGVkMjgtZTMxNC1hZWMyLWJmNTYtMjJlMDBmM2YzZGM1IiwiaXNzIjoiaHR0cHM6Ly9hdXRoLnZpZ2Rvcm92LnJ1L3JlYWxtcy90ZWFtLXBsYW5uZXIiLCJzdWIiOiIyZDJiOTRmMC0xZWQ1LTQ0MTUtYmM4MC1jZTRlZWMxNDQ1NGQiLCJ0eXAiOiJTZXJpYWxpemVkLUlEIiwic2lkIjoiaDItMWtlTDNkMEdGUGE1TTFqYmxvOFNyIiwic3RhdGVfY2hlY2tlciI6ImxDcld3azFTQTJCSm1VaDlXNGdNbzRwLVBOc3h2UHVFUlZTWW1PLWtPSGMifQ.i_RwpCuiyyychgU4ODrBrgu-JA9R2TMyMM2q78LunCOkTXGCUdruGqPi-HuNk0lwH3mMo0Lr5Z8PGVBhalsNEw",
|
||||
"value": "eyJhbGciOiJIUzUxMiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI2ZDRjMWU2My1hNTllLTQ0NjAtYThkYy05YTRiZjFjMTRjMDMifQ.eyJleHAiOjE3Njg1MDM2MTksImlhdCI6MTc2ODQ2NzYxOSwianRpIjoiZGIyMzFlNjYtNTI3Ni1kMTBkLTA4ZDctZDQyZGQ2NDQ0YTY2IiwiaXNzIjoiaHR0cHM6Ly9hdXRoLnZpZ2Rvcm92LnJ1L3JlYWxtcy90ZWFtLXBsYW5uZXIiLCJzdWIiOiIyZDJiOTRmMC0xZWQ1LTQ0MTUtYmM4MC1jZTRlZWMxNDQ1NGQiLCJ0eXAiOiJTZXJpYWxpemVkLUlEIiwic2lkIjoieDY0aWpRc3U0UEE3aTN0U2NranNrY29HIiwic3RhdGVfY2hlY2tlciI6IlhoT3l4WGNDVFA3WjNZdjAxUk1lVC1GemNTZldIOExXQmRVSDA2Z1VxbjgifQ.9bWdKiU_C-BW12XOxC-jbLvwCOUoAcdPOZNqplSAJwO4sqEP-DRYfyaYJM-3ZthLec37X-Xxp_KS6pPmQjl8kQ",
|
||||
"domain": "auth.vigdorov.ru",
|
||||
"path": "/realms/team-planner/",
|
||||
"expires": -1,
|
||||
@ -32,10 +32,10 @@
|
||||
},
|
||||
{
|
||||
"name": "KEYCLOAK_SESSION",
|
||||
"value": "w_aalxg9yi-TKbWYZgi8KimwA5UYaExWPPJvZT0MfoE",
|
||||
"value": "IDfhLlT83e6gUgo0mOmir0agF4uMho_Bgfm9pjzSUVA",
|
||||
"domain": "auth.vigdorov.ru",
|
||||
"path": "/realms/team-planner/",
|
||||
"expires": 1768469581.267433,
|
||||
"expires": 1768503620.115594,
|
||||
"httpOnly": false,
|
||||
"secure": true,
|
||||
"sameSite": "None"
|
||||
|
||||
Reference in New Issue
Block a user