Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions __tests__/fix-ui.spec.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, it, expect, vi, beforeEach } from "vitest";
import { UrlInputSection } from "@/components/url-input-section";
import ContentExtractor from "@/components/content-extractor";

describe("fix-ui spec tests", () => {
beforeEach(() => {
vi.resetAllMocks();
});

// FEEDME-050
describe("FEEDME-050: clear 버튼 표시/숨김 조건", () => {
it("URL 입력창에 텍스트가 있으면 clear 버튼이 표시된다", () => {
render(
<UrlInputSection
url="https://example.com"
loading={false}
error={null}
onUrlChange={vi.fn()}
onErrorClear={vi.fn()}
onFetch={vi.fn()}
/>
);

const clearButton = screen.getByRole("button", { name: "입력 지우기" });
expect(clearButton).toBeInTheDocument();
});

it("URL 입력창이 비어있으면 clear 버튼이 표시되지 않는다", () => {
render(
<UrlInputSection
url=""
loading={false}
error={null}
onUrlChange={vi.fn()}
onErrorClear={vi.fn()}
onFetch={vi.fn()}
/>
);

const clearButton = screen.queryByRole("button", { name: "입력 지우기" });
expect(clearButton).not.toBeInTheDocument();
});
});

// FEEDME-051
describe("FEEDME-051: clear 버튼 클릭 시 URL만 비워지고 프리뷰는 유지된다", () => {
it("clear 버튼 클릭 후 URL이 비워지고 이미 불러온 결과(프리뷰)는 유지된다", async () => {
const user = userEvent.setup();
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: async () => ({
markdown: "# 테스트 콘텐츠",
title: "테스트 제목",
type: "webpage",
}),
} as Response);

render(<ContentExtractor />);

const input = screen.getByRole("textbox");
await user.type(input, "https://example.com");
await user.click(screen.getByRole("button", { name: "가져오기" }));

await waitFor(() => {
expect(screen.getByRole("button", { name: "복사하기" })).toBeInTheDocument();
});

const clearButton = screen.getByRole("button", { name: "입력 지우기" });
await user.click(clearButton);

expect(input).toHaveValue("");
expect(screen.getByRole("button", { name: "복사하기" })).toBeInTheDocument();
});
});

// FEEDME-052
describe("FEEDME-052: loading 중에는 clear 버튼이 표시되지 않는다", () => {
it("fetch 진행 중(loading=true)에는 clear 버튼이 표시되지 않는다", () => {
render(
<UrlInputSection
url="https://example.com"
loading={true}
error={null}
onUrlChange={vi.fn()}
onErrorClear={vi.fn()}
onFetch={vi.fn()}
/>
);

const clearButton = screen.queryByRole("button", { name: "입력 지우기" });
expect(clearButton).not.toBeInTheDocument();
});
});

// FEEDME-053
describe("FEEDME-053: 에러 상태에서 clear 버튼 클릭 시 URL과 에러 메시지 모두 사라진다", () => {
it("clear 버튼 클릭 시 URL 입력창이 비워지고 에러 메시지도 사라진다", async () => {
const user = userEvent.setup();
const mockOnUrlChange = vi.fn();
const mockOnErrorClear = vi.fn();

const { rerender } = render(
<UrlInputSection
url="https://bad-url.com"
loading={false}
error="잘못된 URL입니다"
onUrlChange={mockOnUrlChange}
onErrorClear={mockOnErrorClear}
onFetch={vi.fn()}
/>
);

expect(screen.getByText("잘못된 URL입니다")).toBeInTheDocument();

const clearButton = screen.getByRole("button", { name: "입력 지우기" });
await user.click(clearButton);

expect(mockOnUrlChange).toHaveBeenCalledWith("");
expect(mockOnErrorClear).toHaveBeenCalled();

rerender(
<UrlInputSection
url=""
loading={false}
error={null}
onUrlChange={mockOnUrlChange}
onErrorClear={mockOnErrorClear}
onFetch={vi.fn()}
/>
);

// 에러 메시지가 사라진다
expect(screen.queryByText("잘못된 URL입니다")).not.toBeInTheDocument();
});
});
});
22 changes: 22 additions & 0 deletions artifacts/fix-ui/decisions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# fix-ui Decisions

## 팀 편성

- 내용: Builder 1명, Reviewer 2명 (design-reviewer, react-reviewer)
- 판단: wireframe-reviewer 생략, ui-quality-reviewer 생략
- 근거: 단순 버튼 추가로 레이아웃 변경이 최소. 스크린샷 비교보다 코드 패턴 검증이 중요
- 결과: 성공 — Builder 1회 위임으로 전체 테스트 통과, Reviewer 2명 병렬 실행

## 실행 순서

- 내용: Task 1 → Task 2 순차 실행
- 판단: 병렬 불가, Task 2가 Task 1에 의존
- 근거: TDD 워크플로우 — 테스트 먼저 작성(Red) 후 구현(Green)
- 결과: 성공 — Task 1 Red 확인 후 Task 2 Green 달성

## Design Reviewer FAIL 항목 스킵

- 내용: design-reviewer가 2건 FAIL 보고 — (1) Field에 data-invalid 누락, (2) fetch 버튼의 aria-disabled + opacity 패턴
- 판단: 수정하지 않음
- 근거: 두 항목 모두 fix-ui 변경 이전부터 존재하던 기존 코드. 이번 feature의 spec 범위(clear 버튼 추가)와 무관하며, 기존 동작을 변경하면 예상치 못한 리그레션 위험이 있음
- 결과: 성공 — spec 범위 외 피드백으로 판단, 별도 feature에서 처리 권장
62 changes: 62 additions & 0 deletions artifacts/fix-ui/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# fix-ui 구현 계획

## Architecture Decisions

| 결정 사항 | 선택 | 사유 |
|-----------|------|------|
| Clear 버튼 위치 | InputGroupAddon 내 fetch 버튼 앞 | wireframe 확정: X 버튼 → 구분선 → fetch 버튼 순서 |
| Clear 범위 | URL만 비움 (프리뷰, 프롬프트 유지) | 사용자 확인 완료. 전체 초기화는 로고 클릭으로 이미 제공됨 |
| 구분선 | clear 버튼과 fetch 버튼 사이 세로 구분선 | wireframe 피드백 반영 |

## Required Skills

| 스킬 | 적용 Task | 용도 |
|------|-----------|------|
| shadcn | Task 2 | InputGroupButton 규칙 준수 확인 |

## Affected Files

| 파일 경로 | 변경 유형 | 관련 Task |
|-----------|----------|-----------|
| `__tests__/fix-ui.spec.test.tsx` | 신규 | Task 1 |
| `components/url-input-section.tsx` | 수정 | Task 2 |

## Tasks

### Task 1: spec 테스트 작성

- **시나리오**: FEEDME-050, FEEDME-051, FEEDME-052, FEEDME-053
- **의존성**: 없음
- **구현 대상**:
- `__tests__/fix-ui.spec.test.tsx`
- FEEDME-050: URL 있을 때 clear 버튼 표시, 비어있으면 숨김
- FEEDME-051: clear 클릭 시 URL만 비움, 프리뷰 유지
- FEEDME-052: 로딩 중 clear 버튼 숨김
- FEEDME-053: clear 클릭 시 에러 메시지도 함께 제거
- **수용 기준**:
- [ ] `bun run test __tests__/fix-ui.spec.test.tsx` 실행 시 4개 시나리오 모두 FAIL (Red)

---

### Task 2: Clear 버튼 구현

- **시나리오**: FEEDME-050, FEEDME-051, FEEDME-052, FEEDME-053
- **의존성**: Task 1 (테스트가 먼저 존재해야 Green 확인 가능)
- **참조**:
- shadcn — InputGroupButton, InputGroupAddon
- **구현 대상**:
- `components/url-input-section.tsx`
- X 아이콘 clear 버튼 (InputGroupButton, Lucide X 아이콘)
- 구분선 (세로 separator)
- 조건부 렌더링: `url.length > 0 && !loading`일 때만 표시
- onClick: `onUrlChange("")` + `onErrorClear()` 호출
- aria-label="입력 지우기"
- **수용 기준**:
- [ ] `bun run test __tests__/fix-ui.spec.test.tsx` 실행 시 4개 시나리오 모두 PASS (Green)
- [ ] `bun run test` 전체 테스트 통과

---

## 미결정 사항

없음
51 changes: 51 additions & 0 deletions artifacts/fix-ui/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
## Overview
URL 입력창에 텍스트가 있을 때 X(clear) 버튼을 표시하여, 한 번의 클릭으로 URL 입력값을 빠르게 지울 수 있게 한다.

## 시나리오

### 1. Clear 버튼 표시
[상황] URL 입력창에 텍스트가 입력되어 있으면
[동작] 입력창 오른쪽(fetch 버튼 왼쪽)에 X 아이콘 버튼이 표시된다

성공 기준:
- [ ] URL 입력창에 "https://example.com" 입력 -> X 버튼이 표시됨
- [ ] URL 입력창이 비어 있음 -> X 버튼이 표시되지 않음

### 2. Clear 버튼 클릭 시 URL만 초기화
[상황] URL 입력창에 텍스트가 있는 상태에서 X 버튼을 클릭하면
[동작] URL 입력값만 비워지고, 이미 불러온 결과(프리뷰)와 프롬프트는 유지된다

성공 기준:
- [ ] "https://example.com" 입력 후 X 클릭 -> URL 입력창이 비워짐
- [ ] 결과가 있는 상태에서 X 클릭 -> 프리뷰 영역은 그대로 유지됨

### 3. 로딩 중 Clear 버튼 숨김
[상황] fetch가 진행 중일 때
[동작] Clear 버튼이 표시되지 않는다

성공 기준:
- [ ] fetch 로딩 중 -> X 버튼이 표시되지 않음

### 4. Clear 후 에러 메시지 제거
[상황] 에러 메시지가 표시된 상태에서 X 버튼을 클릭하면
[동작] URL이 비워지고, 에러 메시지도 함께 사라진다

성공 기준:
- [ ] 에러 상태에서 X 클릭 -> URL 비워짐 + 에러 메시지 사라짐

## 범위

### 포함
- URL 입력창 내 clear(X) 버튼 추가
- 버튼 표시/숨김 조건 처리

### 제외
- 전체 초기화 (결과, 프롬프트 등) — 로고 클릭으로 이미 제공됨
- 키보드 단축키 (Escape 등) — 이번 범위 아님

## 전제 조건
- URL 입력 컴포넌트(UrlInputSection)가 구현되어 있다
- InputGroup 컴포넌트가 addon 슬롯을 지원한다

## 미결정 사항
- 없음
Loading
Loading