Files
Gnuboard7/tests/Playwright/specs/layout-editor/separate-edit-modes.spec.ts
T
2026-07-01 10:30:32 +09:00

96 lines
5.1 KiB
TypeScript

/**
* Layout Editor — 별도 편집 모드(base/modal/extension/iteration) 브라우저 통합
*
* Phase 1 에서 액션 골격만 도입됐던 4개 별도 편집 모드의 캔버스 단독 렌더 + 진입/이탈 +
* URL 동기화를 브라우저에서 검증한다(단위 테스트가 못 잡는 render-cycle + URL pushState).
*
* @scenario edit_mode + url_sync + followup_action
* @effects route_tree_renders_base_modal_extension_groups + base_edit_loads_and_renders_base_layout_standalone + modal_edit_renders_modal_open_standalone_no_dim + extension_edit_loads_via_layout_extensions_api + enter_edit_mode_pushes_edit_query_to_url + refresh_with_edit_query_restores_edit_mode + back_button_exits_edit_mode_to_route
*/
import { test, expect, issueToken, authenticatePage } from '../../fixtures/auth';
const TEMPLATE = 'sirsoft-admin_basic';
const EDITOR_URL = `/admin/layout-editor/${TEMPLATE}`;
async function enterEditor(page: import('@playwright/test').Page): Promise<void> {
const token = issueToken('core.templates.layouts.edit');
await authenticatePage(page, token);
await page.goto(EDITOR_URL);
await page.waitForLoadState('domcontentloaded', { timeout: 30_000 });
await page.waitForSelector('[data-testid="g7le-toolbar"]', { timeout: 30_000 });
await page.waitForSelector('[data-testid="g7le-route-tree-item"]', { timeout: 30_000 });
}
/** 트리에서 텍스트로 항목을 찾아 클릭 */
async function clickTreeItem(page: import('@playwright/test').Page, text: RegExp): Promise<void> {
const item = page.locator('[data-testid="g7le-route-tree-item"]', { hasText: text }).first();
await item.click();
}
/** 캔버스의 편집 가능 노드(data-editor-path) 개수 */
async function editorNodeCount(page: import('@playwright/test').Page): Promise<number> {
return page.evaluate(() => document.querySelectorAll('[data-editor-path]').length);
}
test.describe('@layout-editor 별도 편집 모드', () => {
test('base 진입 → URL ?edit= pushState + base 캔버스 단독 렌더', async ({ page }) => {
await enterEditor(page);
// 트리에 [공통 레이아웃] 그룹 + base 항목 존재.
await clickTreeItem(page, /_admin_base/);
// 모드 배지(확장 편집/공통 레이아웃 편집 중) + URL ?edit= 변경.
await expect.poll(() => page.url()).toMatch(/edit=__base__/);
await expect(page.locator('[data-mode="base"]')).toBeAttached();
// base 레이아웃 단독 렌더 — 편집 노드가 존재.
await page.waitForSelector('[data-testid="g7le-preview-frame"]', { timeout: 20_000 });
await expect.poll(() => editorNodeCount(page)).toBeGreaterThan(0);
});
test('base 진입 후 뒤로가기 → 편집 모드 종료(URL ?edit= 제거)', async ({ page }) => {
await enterEditor(page);
await clickTreeItem(page, /_admin_base/);
await expect.poll(() => page.url()).toMatch(/edit=__base__/);
await page.goBack();
// 뒤로가기로 편집 모드 종료 — URL 에서 ?edit= 제거.
await expect.poll(() => page.url()).not.toMatch(/edit=__base__/);
await expect(page.locator('[data-mode="base"]')).toHaveCount(0);
});
test('확장 주입 그룹 항목 진입 → 확장 조각 캔버스 렌더 + URL ?edit=__extension__', async ({ page }) => {
await enterEditor(page);
// [확장 주입] 그룹의 확장 항목(출처 · 대상명) 클릭.
await clickTreeItem(page, /·\s*(inquiry_board_setting|html_content|admin_user_detail)/);
await expect.poll(() => page.url()).toMatch(/edit=__extension__/);
await expect(page.locator('[data-mode="extension"]')).toBeAttached();
await page.waitForSelector('[data-testid="g7le-preview-frame"]', { timeout: 20_000 });
await expect.poll(() => editorNodeCount(page)).toBeGreaterThan(0);
});
test('?edit=__extension__ URL 직접 진입(새로고침) → 확장 편집 모드 복원', async ({ page }) => {
const token = issueToken('core.templates.layouts.edit');
await authenticatePage(page, token);
// 확장 id=1 로 직접 진입.
await page.goto(`${EDITOR_URL}?edit=__extension__/1`);
await page.waitForLoadState('domcontentloaded', { timeout: 30_000 });
await page.waitForSelector('[data-testid="g7le-toolbar"]', { timeout: 30_000 });
// 새로고침/직접 URL 로도 확장 편집 모드가 복원되고 캔버스가 렌더된다(크래시 회귀 가드).
await expect(page.locator('[data-mode="extension"]')).toBeAttached({ timeout: 20_000 });
await page.waitForSelector('[data-testid="g7le-preview-frame"]', { timeout: 20_000 });
await expect.poll(() => editorNodeCount(page)).toBeGreaterThan(0);
});
test('모달 그룹 항목 진입 → 모달 단독 open 렌더', async ({ page }) => {
await enterEditor(page);
// [모달] 그룹의 인라인 모달 항목 클릭(admin_user_list 의 delete_confirm_modal 등).
await clickTreeItem(page, /delete_confirm_modal|error_modal|bulk_/);
await expect(page.locator('[data-mode="modal"]')).toBeAttached();
await page.waitForSelector('[data-testid="g7le-preview-frame"]', { timeout: 20_000 });
// 모달이 열린 상태로 단독 렌더 — 편집 노드 존재.
await expect.poll(() => editorNodeCount(page)).toBeGreaterThan(0);
});
});