diff --git "a/\bdocs/requirements.md" "b/\bdocs/requirements.md" new file mode 100644 index 00000000..ba81fc29 --- /dev/null +++ "b/\bdocs/requirements.md" @@ -0,0 +1,154 @@ +# Week 2 Assignment + +## Objectives + +Through this assignment, we want you to experience the following: + +1. Getting familiar with the TDD development cycle +2. Learning the basics of AI and Agents and applying them to write code +3. Studying how to instruct AI to write code effectively +4. Once comfortable with AI usage, building your own Agent + +These are the four main objectives. Since unfamiliar concepts are presented consecutively, it might be challenging! But donโ€™t give upโ€”if we work together, youโ€™ll have a fascinating first experience. Letโ€™s go! + +Additional requirements for recurring schedules have been added. + +> **Our server team went on vacation without implementing recurring schedules! +> Instead, they provide create, update, and delete APIs for the list, so the frontend has to handle the logic. ๐Ÿคฌ** + +## Assignment Specification + +- The goal of this assignment is simple! Please write the features below using the TDD cycle. **However, use AI to assist in writing the code!** +- Add all the following features to the existing app. + - The provided specification is the minimum functionality. Please plan more concretely and break down the work into smaller tasks! + +```markdown +1. Select Recurrence Type + - When creating or editing a schedule, users can select a recurrence type. + - Recurrence types are: Daily, Weekly, Monthly, Yearly + - If the 31st is selected for monthly recurrence โ†’ create only on the 31st, not on the last day of months with fewer than 31 days. + - If February 29th is selected for yearly recurrence โ†’ create only on the 29th during leap years! + - Recurring schedules do not consider overlapping schedules. +2. Display Recurring Schedules + - In the calendar view, distinguish recurring schedules by adding an icon. +3. Recurrence End + - Allow specifying an end condition for recurrence. + - Option: Until a certain date + - For this example, generate schedules up to 2025-12-31 as the maximum date. +4. **Edit Recurring Schedule** + 1. If the user selects โ€˜Edit only this occurrence?โ€™ and chooses โ€˜Yesโ€™, perform a single edit: + - Editing a recurring schedule converts it into a single schedule. + - The recurring schedule icon disappears. + 2. If the user selects โ€˜Edit only this occurrence?โ€™ and chooses โ€˜Noโ€™, perform a full edit: + - The recurring schedule remains. + - The recurring schedule icon remains. +5. **Delete Recurring Schedule** + 1. If the user selects โ€˜Delete only this occurrence?โ€™ and chooses โ€˜Yesโ€™, perform a single deletion: + 1. Delete only that occurrence. + 2. If the user selects โ€˜Delete only this occurrence?โ€™ and chooses โ€˜Noโ€™, perform a full deletion: + 1. Delete all occurrences of the recurring schedule. +``` + +## Basic Assignment (EASY) + +**Recommended for those less familiar with writing code using AI.** + +The goal of the basic assignment is to write tests and code for the features in the specification using the TDD approach with AI assistance. + +1. Before starting the assignment, document the rules for writing good tests that you have learned before. Using AI for this is fine. This knowledge will be used later for AI-assisted test code writing. +2. Write documents that can serve as guidelines for AI tools you use, such as [.cursor/rules](https://docs.cursor.com/en/context/rules) and [copilot-instructions.md](http://copilot-instructions.md), which help AI generate answers. + + If youโ€™re curious about helpful guidelines, sneak a peek at the [awesome-cursorrules](https://github.com/PatrickJS/awesome-cursorrules/tree/main) repository! + +3. Itโ€™s time to start development feature by feature. + Use AI to generate, review, and commit code through the TDD cycle stages: RED โ†’ GREEN โ†’ REFACTOR. + Commit after each stage. + +Be careful not to develop all features at once, and leave clear commit messages. + +For each stage, verify the code yourself and note in your PR any improvements you made to achieve better results. + +## Basic Assignment (HARD) + +**Recommended for those somewhat familiar with AI-assisted coding.** + +The goal of the advanced assignment is to build the workflow introduced in [2-2. Build Your Own AI Test Agent](https://www.notion.so/2-2-AI-2642dc3ef51480e589a8f1946588336c?pvs=21) and automatically implement the features in the specification. + +1. Before starting, document the rules for writing good tests you have learned before. Using AI is fine. This knowledge will be used later for AI-assisted test code writing. +2. Create Agents tailored to the AI tools you use. Build six Agents that operate within the workflow. +3. Start development feature by feature. + Use an orchestrator Agent to generate, review, and commit code through the TDD cycle stages: RED โ†’ GREEN โ†’ REFACTOR. + Commit after each stage. + +Be careful not to develop all features at once, and leave clear commit messages. + +If there are core chat histories from each AI tool, please share them! ([Example](https://github.com/jhlee0409/claude-code-history-viewer/blob/main/README.ko.md)) + +For each stage, document in your PR the instructions given to the AI Agents to smooth the workflow, verify the code yourself, and note any efforts made to improve results. + +## Advanced Assignment + +Document your experiences and efforts while working on the assignment! + +We have compiled topics worth considering and experiencing through various attempts. Write these carefully and share your feedback with your team. + +```jsx +# Report on Stable Feature Development Using AI and Testing + +## Why did you choose the tools you used? Have you researched the characteristics of each tool? + +## Was there a difference between AI-assisted feature development based on tests and development without it? + +## What additional information (context) did you provide to improve AI responses? + +## What efforts did you make to help the AI utilize this context well? + +## Were you satisfied with the various results generated? What criteria did you use to evaluate the AIโ€™s responses? + +## How did you phrase your questions to get better results? Share your various experiences. + +## How did you define the scope of tasks assigned to the AI? Try narrowing and widening the scope and describe the results. Also, share what you consider an appropriate unit of work. + +## Were there any good references or phrases you wanted to share with your peers? Feel free to brag. + +## Have you thought about what AI is good and bad at? Write about your thoughts on this. + +## Finally, share your impressions! +``` + +# 3. Evaluation Criteria + +### Common Submissions + +- [ ] Documented rules for writing good tests +- [ ] Wrote all tests to implement the specified features and implemented them correctly +- [ ] Implemented all specified features correctly and verified proper operation + +### Basic Assignment (Easy) + +- [ ] Additional guidelines written to help AI write code well +- [ ] Correctly committed work for each TDD stage +- [ ] Documented efforts to improve AI tool usage in PR + +### Basic Assignment (Hard) + +- [ ] Agent implementation specification document or code +- [ ] Correctly committed work for each TDD stage +- [ ] History or logs demonstrating proper results +- [ ] Documented efforts to improve AI tool usage in PR + +### Advanced Assignment + +- [ ] Thoroughly answered all questions + +### Others + +1. Adherence to TDD process + - Did you write tests first and confirm they fail before implementing? + - Did you write minimal code to pass tests for each requirement? + - Did you perform appropriate refactoring after tests passed? +2. Test quality + - Does each test clearly verify only one behavior or feature? + - Do the tests accurately reflect the requirements? + - Are tests written for all major features and scenarios (both positive and negative cases)? + - Do the tests include boundary and exception cases? diff --git a/.cursor/agent/committer.md b/.cursor/agent/committer.md new file mode 100644 index 00000000..c94d5328 --- /dev/null +++ b/.cursor/agent/committer.md @@ -0,0 +1,23 @@ +# Committer Agent + +Mission: Produce clean, stage-specific commits with clear messages. + +Stages: + +- RED: `test(red): ` +- GREEN: `feat(green): ` +- REFACTOR: `refactor: ` + +Rules: + +- Commit only relevant files per stage. +- Keep messages actionable and scoped to one behavior. +- ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€๋Š” ํ•œ๊ตญ์–ด๋กœ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค. (์ฝ”๋“œ ์‹๋ณ„์ž๋Š” ์˜์–ด ์œ ์ง€) + +Example Messages: + +- `test(red): ๋งค๋‹ฌ 31์ผ ๊ทœ์น™์€ 31์ผ์—๋งŒ ๋ฐœ์ƒํ•œ๋‹ค` +- `feat(green): 31์ผ ์ „์šฉ ์›”๊ฐ„ ๋ฐ˜๋ณต ๊ทœ์น™ ์ตœ์†Œ ๊ตฌํ˜„` +- `test(red): ๋ฐ˜๋ณต ์ด๋ฒคํŠธ์—๋งŒ ๋ฐ˜๋ณต ์•„์ด์ฝ˜์„ ํ‘œ์‹œํ•œ๋‹ค` +- `feat(green): isRecurring์ด true์ธ ์ด๋ฒคํŠธ์—๋งŒ RecurringIcon ๋ Œ๋”` +- `refactor: ๋ฐ˜๋ณต ์ƒ์„ฑ ๋กœ์ง ์ถ”์ถœ ๋ฐ ๋„ค์ด๋ฐ ๊ฐœ์„ ` diff --git a/.cursor/agent/minimal-implementer.md b/.cursor/agent/minimal-implementer.md new file mode 100644 index 00000000..86922b20 --- /dev/null +++ b/.cursor/agent/minimal-implementer.md @@ -0,0 +1,29 @@ +# Minimal Implementer Agent (English Only) + +Mission: Make the smallest code change to pass the failing tests. + +Constraints: + +- No premature abstractions; change only what the tests require. +- Preserve formatting and existing indentation. +- Keep performance/structure changes for the refactor step. + +Steps: + +1. Read failing test output and the exact assertions. +2. Implement just enough in `src/` to pass. +3. If time/clock is needed, accept a clock parameter or use a centralized date utility. +4. Re-run tests; ensure GREEN with no lints/types broken. + +Feature 2 implementation hints (do the minimum only): + +- Ensure recurring events carry an explicit `isRecurring` (or equivalent) boolean used by rendering. +- When an occurrence is edited as a single (detached), mark it to not show the recurring icon (`isRecurring === false` for the detached instance). +- Likely touch points (keep changes minimal): + - `src/components/RecurringIcon.tsx` (pure presentational; render icon only when the flag is true). + - `src/hooks/useCalendarView.ts` (surface the flag to the calendar view items). + - `src/hooks/useEventOperations.ts` (set/clear flags on edit/delete flows that detach occurrences). + +Exit Criteria: + +- All new tests pass. No unrelated files changed. diff --git a/.cursor/agent/orchestrator.md b/.cursor/agent/orchestrator.md new file mode 100644 index 00000000..0b29d43f --- /dev/null +++ b/.cursor/agent/orchestrator.md @@ -0,0 +1,38 @@ +# Orchestrator Agent + +Goal: Drive one full TDD cycle per feature slice using the six-agent workflow. + +Inputs: + +- Feature target (e.g., "Monthly 31st rule" or "Recurring icon rendering"). +- Specs: `DOCS/recurring-requirements.en.md` +- Rules: `.cursor/rules/testing-rules.md`, `.cursor/rules/agent-rules.md` + +Outputs: + +- Clear subtask for Test Author (what to test, scope, boundaries) +- Handoff notes for Minimal Implementer, Refactorer, Reviewer, Committer + +Operating Steps: + +1. Select the next smallest verifiable behavior from the spec. +2. Define acceptance criteria and boundaries (edge cases, negatives). +3. Dispatch to Test Author with exact file paths and naming. +4. After RED, dispatch to Minimal Implementer with constraints: minimal change only. +5. After GREEN, dispatch Reviewer; if clean, dispatch Refactorer; then Reviewer again. +6. Dispatch Committer with stage and message template. + +Feature 2 orchestration checklist (Recurring icon & detach): + +- Ensure separate cycles for: + - Icon renders for recurring events (integration). + - Detached occurrence renders without icon (integration + hook-level logic). +- Require fixed clock in tests and clear event flags (`isRecurring`, `isDetached`). +- Point tests to `src/__tests__/medium.integration.spec.tsx` and/or `src/__tests__/hooks/`. +- ์‚ฐ์ถœ๋ฌผ ๋ฌธ์„œ์™€ ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€๋Š” ํ•œ๊ตญ์–ด๋กœ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค. (์ฝ”๋“œ ์‹๋ณ„์ž๋Š” ์˜์–ด) + +Guardrails: + +- Do not bundle multiple behaviors in one cycle. +- Ensure deterministic tests (fake timers / injected clock). +- Keep each cycle small and independently shippable. diff --git a/.cursor/agent/refactorer.md b/.cursor/agent/refactorer.md new file mode 100644 index 00000000..40dfc2cb --- /dev/null +++ b/.cursor/agent/refactorer.md @@ -0,0 +1,21 @@ +# Refactorer Agent + +Mission: Improve clarity, structure, and duplication after GREEN without changing behavior. + +Targets: + +- Extract pure functions for recurrence rules (31st-only, leap-year Feb 29, end-cap) and for series/detach decisions. +- Improve naming; reduce branching; add small helpers. + +Rules: + +- Keep all tests GREEN. +- Avoid changing public APIs unless tests guarantee compatibility. +- Do not add new features; only structural improvement. + +Checklist: + +- Remove duplication introduced during minimal implementation. +- Centralize date handling with deterministic interfaces. +- Keep files small and functions readable. +- Deliverables, commit messages, and comments must be written in Korean. (Code identifiers should remain in English) diff --git a/.cursor/agent/reviewer.md b/.cursor/agent/reviewer.md new file mode 100644 index 00000000..eae2fb17 --- /dev/null +++ b/.cursor/agent/reviewer.md @@ -0,0 +1,26 @@ +# Reviewer Agent + +Mission: Guard quality gates (tests, lints, types, style, determinism). + +Review Items: + +- Tests: one behavior per test; AAA; domain language; edges/negatives included. +- Determinism: no real-time dependence; fake timers/injected clock used. +- Code clarity: names, small functions, no dead code, no over-mocking. +- Spec alignment: matches `DOCS/recurring-requirements.en.md` exactly. + +Feature 2 specific checks: + +- Integration tests assert icon visibility for recurring events and icon absence for detached single edits. +- Implementation exposes a clear flag for rendering (e.g., `isRecurring`) and updates it correctly on detach flows. +- No accidental deduplication or suppression of overlapping occurrences. +- Verify that test descriptions, comments, deliverables, and commit messages are consistently written in Korean. (Code identifiers should remain in English) + +Actions: + +- Suggest precise diffs or edits; avoid vague feedback. +- Block if flaky patterns or scope creep. + +Exit Criteria: + +- All checks pass; ready for refactor/commit. diff --git a/.cursor/agent/spec-agent.md b/.cursor/agent/spec-agent.md new file mode 100644 index 00000000..e83f5558 --- /dev/null +++ b/.cursor/agent/spec-agent.md @@ -0,0 +1,25 @@ +# Agent Workflow Overview + +This repository uses six agents to implement the recurring features via TDD. +All agents must follow `.cursor/rules/testing-rules.md` and `DOCS/recurring-requirements.en.md`. + +Agents: + +1. Orchestrator +2. Test Author +3. Minimal Implementer +4. Refactorer +5. Reviewer +6. Committer + +High-level flow (per feature slice): + +- Orchestrator โ†’ Test Author (RED) โ†’ Minimal Implementer (GREEN) โ†’ Reviewer โ†’ Refactorer (REFACTOR) โ†’ Reviewer โ†’ Committer. + +Artifacts and paths: + +- Specs: `DOCS/recurring-requirements.en.md` +- Rules: `.cursor/rules/testing-rules.md`, `.cursor/rules/agent-rules.md` +- Language: ์ปค๋ฐ‹/์ฃผ์„/ํ…Œ์ŠคํŠธ ์„ค๋ช…/์‚ฐ์ถœ๋ฌผ์€ ํ•œ๊ตญ์–ด, ์ฝ”๋“œ ์‹๋ณ„์ž๋Š” ์˜์–ด +- Tests: `src/__tests__/` +- Code: `src/` diff --git a/.cursor/agent/test-author.md b/.cursor/agent/test-author.md new file mode 100644 index 00000000..c7bb0b05 --- /dev/null +++ b/.cursor/agent/test-author.md @@ -0,0 +1,32 @@ +# Test Author Agent + +Mission: Write failing tests that precisely capture the behavior. + +Read: + +- `DOCS/recurring-requirements.en.md` +- `.cursor/rules/testing-rules.md` + +Deliverables: + +- New/updated test files under `src/__tests__/` using AAA and domain language. +- Clear, one-behavior-per-test with boundaries and negatives. + +Checklist: + +- Name tests as `should when `. +- Use RTL queries by role/label/text; prefer `userEvent` and `findBy*` for async. +- Fix the clock (fake timers or injected Date) for any date-based behavior. +- Use MSW for networking or function-level mocks for pure units. +- Cover special rules: Monthly 31st only; Yearly Feb 29 only; overlaps allowed; end-cap 2025-12-31. +- Test descriptions (it/describe) and comments must be written in Korean. (Code identifiers should remain in English) + +Feature 2 specific tests (Recurring icon & detach): + +- Integration: in `src/__tests__/medium.integration.spec.tsx`, assert recurring events render a distinct icon/marker. +- Integration: assert that a detached occurrence (edited as single) renders without the recurring icon. +- Hook/unit: in `src/__tests__/hooks/medium.useEventOperations.spec.ts` (or a new spec), assert `isRecurring` and `isDetached`/equivalent flags are set correctly during edit/delete flows. + +Exit Criteria: + +- Tests fail with meaningful errors before implementation begins (RED). diff --git a/.cursor/rules/agent-rules.md b/.cursor/rules/agent-rules.md new file mode 100644 index 00000000..90679567 --- /dev/null +++ b/.cursor/rules/agent-rules.md @@ -0,0 +1,82 @@ +# Agent Rules for This Project + +## Goals + +- Deliver features via strict TDD (RED โ†’ GREEN โ†’ REFACTOR) with small, safe, and deterministic steps. +- Keep tests fast, readable, and fully deterministic. + +## Operating Principles + +- Always read `docs/requirements.md` (if present) and `.cursor/rules/testing-rules.md` before writing tests or code. The source of truth for this assignment is `DOCS/recurring-requirements.en.md`. +- Language policy: ์ฝ”๋“œ ์‹๋ณ„์ž(ํ•จ์ˆ˜/๋ณ€์ˆ˜/ํŒŒ์ผ๋ช…)๋Š” ์˜์–ด๋กœ ์ž‘์„ฑํ•˜๊ณ , ์ปค๋ฐ‹/PR/์ฃผ์„/ํ…Œ์ŠคํŠธ ์„ค๋ช… ๋ฐ ๊ฐ ๋‹จ๊ณ„ ์‚ฐ์ถœ๋ฌผ ๋ฌธ์„œ๋Š” ํ•œ๊ตญ์–ด๋กœ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค. +- Preserve existing file indentation and formatting; do not reformat unrelated code. +- Use behavior-driven test names and the AAA pattern. +- Avoid over-mocking; control time/network/randomness deterministically. + +## Change Workflow + +1. RED: Add a failing test (unit or integration as appropriate). +2. GREEN: Make the minimal code change to pass only that test. +3. REFACTOR: Improve clarity and remove duplication without changing behavior. +4. Commit after each step using messages: + - `test(red): ` + - `feat(green): ` + - `refactor: ` + +## Testing Conventions + +- Use React Testing Library from the userโ€™s perspective (roles/labels/text). +- Use `userEvent` and `findBy*`/`waitFor` for async flows. +- Use MSW for network; use fake timers or an injected clock for date/recurrence logic. + +## File/Path Rules + +- Keep new design docs in `DOCS/` (not in rules folders or configs). +- Put tests under `src/__tests__/` with clear grouping (unit/hooks/integration). + +## Recurring Feature Specifics (Authoritative) + +- Monthly on the 31st: generate only on the 31st in months that have it. Do not substitute with the last day. +- Yearly on Feb 29: generate only in leap years (Feb 29). Do not substitute with Feb 28/Mar 1. +- Overlaps are allowed by design; never deduplicate or prevent overlaps. +- Recurring indicator in calendar: show a distinct icon/marker for recurring events only. Detached single events must not show the icon. +- Recurrence end condition: cap generated occurrences at 2025-12-31 at the latest. + +## Feature 2 Focus (Recurring Icon & Detach Behavior) + +- Tests must cover both: (a) recurring events render with the icon; (b) a detached single edit of a recurring occurrence renders without the icon. +- Preferred test locations: + - Integration: `src/__tests__/medium.integration.spec.tsx` (calendar rendering and user flows). + - Hooks/unit: `src/__tests__/hooks/medium.useEventOperations.spec.ts` or a new spec that validates the `isRecurring`/detach flags. +- Minimal implementation targets: + - `src/components/RecurringIcon.tsx` (render-only component; no business logic). + - `src/hooks/useCalendarView.ts` and/or `src/hooks/useEventOperations.ts` to ensure events carry `isRecurring` and a detach mechanism that clears the icon when an occurrence is converted to a single event. +- Determinism: fix the clock in tests; do not rely on system time. + +## Quality Gates + +- No flaky tests; total suite must run in seconds locally. +- Lints and types must be clean after each GREEN and REFACTOR. + +## Safety + +- Prefer pure functions for recurrence generation and series/detach decisions. +- Add boundary and negative tests for every rule above. + +## Stage Documentation & Traceability + +- For each TDD phase (RED, GREEN, REFACTOR), create a markdown doc: `DOCS/feature--RED.md`, `DOCS/feature--GREEN.md`, `DOCS/feature--REFACTOR.md`. +- ์‚ฐ์ถœ๋ฌผ ๋ฌธ์„œ๋Š” ํ•œ๊ตญ์–ด๋กœ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค. ๋ชฉํ‘œ, ๋‹จ๊ณ„, ๊ทผ๊ฑฐ, ๋ณ€๊ฒฝ ํŒŒ์ผ ๊ฒฝ๋กœ, ๊ฒฐ๊ณผ๋ฅผ ๊ฐ„๊ฒฐํžˆ ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค. +- ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€์™€ ๋ฌธ์„œ ํ—ค๋”, ์ŠคํŽ™ ์ฐธ์กฐ๋Š” ๋™์ผํ•œ ๋ฒˆํ˜ธ/์šฉ์–ด ์ฒด๊ณ„๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. +- ์˜ˆ์‹œ ๋งคํ•‘: + - Commit: `test(red): 2. ๋ฐ˜๋ณต ์ด๋ฒคํŠธ์—๋งŒ ์•„์ด์ฝ˜์„ ํ‘œ์‹œํ•œ๋‹ค` + - Doc: `DOCS/feature-2-RED.md` (๊ด€๋ จ ์ŠคํŽ™: 2 โ€“ ์บ˜๋ฆฐ๋” ๋ฐ˜๋ณต ์•„์ด์ฝ˜) + +## Sequential Scope (Unit โ†’ Integration) + +- Complete unit-level TDD first: RED โ†’ GREEN โ†’ REFACTOR (unit). +- Then add integration tests: + - RED (integration): failing integration test capturing UI behavior. + - GREEN (integration): minimal UI/hook/state wiring to pass. + - REFACTOR (integration): role separation/readability/duplication removal. +- Repeat the same discipline for each feature slice. diff --git a/.cursor/rules/testing-rules.md b/.cursor/rules/testing-rules.md new file mode 100644 index 00000000..ef32693c --- /dev/null +++ b/.cursor/rules/testing-rules.md @@ -0,0 +1,183 @@ +# Testing Rules (Good Tests Guide) + +This document defines how AI (and humans) should write tests for this project. +Follow the TDD loop: **RED โ†’ GREEN โ†’ REFACTOR**. + +--- + +## 0) Purpose + +- Provide a safety net to prevent regressions while evolving features +- Execute the TDD cycle quickly and reliably +- Serve as an executable spec that teammates/agents can reproduce and verify + +--- + +## 1) Core Principles + +1. **One behavior per test** + + - A single test should verify one user/system behavior and its outcome. + +2. **Assert observable outcomes only** + + - Verify externally observable results: DOM changes, return values, call counts, state transitions. + +3. **AAA structure (Arrangeโ€“Actโ€“Assert)** + + - **Arrange**: set up fixtures/initial state + - **Act**: perform the behavior (once) + - **Assert**: check the expected outcome + +4. **RED first** + + - Start with a meaningful failing test, then pass it with the minimal implementation. + +5. **Deterministic & isolated** + + - No order dependence or shared global state between tests. + - Fix or mock timers, random, time, and network. + +6. **Include boundaries & negatives** + + - Cover min/max/empty/dup/permission failures, etc. + +7. **Use domain language** + - Prefer domain terms in test names, descriptions, and fixtures so requirements read naturally. + +--- + +## 2) Naming Conventions + +- **File**: `..spec.ts(x)` or `*.spec.ts` per unit +- **Test title**: `should when ` + - e.g., `should create only on 31st when monthly recurrence is selected` + +Structure example: + +```ts +describe('[function or feature name]', () => { + it('should [expected behavior] when [situation]', () => { + // Arrangeโ€“Actโ€“Assert + }); +}); +``` + +## 3) TDD Operations & Commits + +1. RED: write a failing unit/integration test and confirm the failure +2. GREEN: implement the smallest change to pass +3. REFACTOR: remove duplication and improve clarity (tests stay green) +4. Commits (separate for each step): + โ€ข test(red): ... + โ€ข feat(green): ... + โ€ข refactor: ... + +## 4) Test Levels + + โ€ข Unit: pure functions/hooks/utils; remove external dependencies; fast and precise + โ€ข Integration: hook + component + state wiring; represent user flows and requirements + โ€ข E2E (optional): key scenarios; for this assignment, integration tests can sufficiently cover flows + +## 5) React/Hook Testing Conventions + + โ€ข Use React Testing Library: test from the userโ€™s perspective (roles, labels, text), not implementation details + โ€ข Use userEvent for interactions; for async rendering, use findBy*/waitFor + โ€ข Test hooks via component wrappers or a custom render utility + +## 6) Mocking / Stubs + + โ€ข Treat external systems as test doubles: API/time/random/storage + โ€ข Network: use MSW for contract-level mocking; for pure units, allow function-level mocks + โ€ข Time: prefer vi.useFakeTimers() or inject a fixed Date.now; fix the reference clock for recurrence logic + โ€ข Random/IDs: seed or inject the generator + โ€ข Avoid over-mocking: domain logic should be composed โ€œfor realโ€ in integration tests + +## 7) Fixtures & Data Builders + + โ€ข Prepare minimal inputs; use named builders/helpers to remove duplication + โ€ข No shared mutable state across tests; each test should be self-contained + +## 8) Assertion Quality + + โ€ข Aim for one clear assertion per test (or a small group for one outcome) + โ€ข Negative paths should assert thrown errors/messages explicitly (toThrow, message match) + โ€ข Prefer getByRole / getByLabelText / getByText for DOM queries + +## 9) Coverage Philosophy + + โ€ข Focus on risk & complexity: branch-heavy code, domain rules, high-regression areas + โ€ข Treat coverage as a result, not as the target + +## 10) Assignment-Specific Checklist (Recurring Schedules) + + โ€ข Recurrence types: Daily / Weekly / Monthly / Yearly + โ€ข Monthly 31st: generate only on the 31st (do not substitute with 30/28/29) + โ€ข Yearly Feb 29: generate only in leap years + โ€ข Overlaps allowed (do not de-duplicate by design) + โ€ข Calendar view: show an icon for recurring events; no icon for single (detached) events + โ€ข End condition: stop at the given end date (max: 2025-12-31) + โ€ข Edit + โ€ข Single occurrence: convert that instance to a single event (remove icon) + โ€ข Edit all: keep recurrence (keep icon) + โ€ข Delete + โ€ข Single occurrence: remove only that instance + โ€ข Delete all: remove every occurrence + +## 11) Recommended Test Suite (Examples) + +Utilities / Domain +โ€ข generateRecurrences +โ€ข Monthly 31st rule +โ€ข Yearly Feb 29 rule (leap years only) +โ€ข Weekly day alignment across month/year boundaries +โ€ข End date clamped at 2025-12-31 + +Hooks / State +โ€ข Create / edit / delete flows (single vs all) +โ€ข After single edit, icon disappears; after edit all, icon remains + +UI Integration +โ€ข Calendar renders recurrence icons distinctly +โ€ข User interactions update view/state correctly + +## 12) Quality Gates + + โ€ข Full test run should be fast (seconds) and stable locally + โ€ข Flaky tests are not allowed (remove timing races, eliminate time-dependence) + โ€ข Every new feature follows RED โ†’ GREEN โ†’ REFACTOR with commit history + +## 13) Commit Message Templates + + โ€ข test(red): monthly 31st occurs only on 31st + โ€ข feat(green): implement 31st-only monthly generation + โ€ข refactor: extract recurrence generator and clarify names + +##14) Example Test Snippet + +```ts +describe('generateRecurrences', () => { + it('should create events on Feb 29 only in leap years when yearly', () => { + // Arrange + const start = new Date('2024-02-29'); // leap year + const end = new Date('2030-12-31'); + + // Act + const events = generateRecurrences({ start, end, type: 'yearly' }); + + // Assert + const dates = events.map((e) => e.date); + expect(dates).toContain('2028-02-29'); + expect(dates).not.toContain('2025-02-28'); // do NOT substitute + }); +}); +``` + +## 15) AI Writing Notes + +When generating tests: +โ€ข Prefer behavioral descriptions over technical internals +โ€ข Add short comments documenting the intent of the test +โ€ข Include edge cases automatically for date/time logic +โ€ข Keep fixtures minimal and meaningful +โ€ข Avoid long, hard-coded datasets unless necessary diff --git a/DOCS/feature-1-GREEN.md b/DOCS/feature-1-GREEN.md new file mode 100644 index 00000000..1e69be9e --- /dev/null +++ b/DOCS/feature-1-GREEN.md @@ -0,0 +1,58 @@ +# [GREEN] Feature 1: ๋ฐ˜๋ณต ์œ ํ˜• ์„ ํƒ - ์ตœ์†Œ ๊ตฌํ˜„ + +## ๊ด€๋ จ ์š”๊ตฌ์‚ฌํ•ญ + +- 1. ๋ฐ˜๋ณต ์œ ํ˜• ์„ ํƒ (recurring type selection) + - 31์ผ ์„ ํƒ ์‹œ: ๋งค์›” 31์ผ์—๋งŒ ์ผ์ • ์ƒ์„ฑ(30/28/29์ผ ๋Œ€์ฒด ๊ธˆ์ง€) + - 2/29 ์„ ํƒ ์‹œ: ์œค๋…„ 2์›” 29์ผ์—๋งŒ ์ผ์ • ์ƒ์„ฑ + - ๋ฐ˜๋ณต ์ผ์ •์€ ๊ฒน์นจ ํ—ˆ์šฉ + +## ๋ชฉ์  + +- RED ๋‹จ๊ณ„์—์„œ ์ž‘์„ฑํ•œ ์‹คํŒจ ํ…Œ์ŠคํŠธ๋ฅผ ํ†ต๊ณผ์‹œํ‚ค๊ธฐ ์œ„ํ•œ **์ตœ์†Œ ๊ตฌํ˜„(GREEN)**์„ ์ ์šฉํ•œ๋‹ค. + +## ๋ณ€๊ฒฝ ํŒŒ์ผ + +- src/utils/eventUtils.ts + - `generateRecurrences(params)` ํ•จ์ˆ˜ ์ถ”๊ฐ€ (์ตœ์†Œ ๊ธฐ๋Šฅ) + +## ๊ตฌํ˜„ ์š”์•ฝ + +- ํƒ€์ž…: `monthly`, `yearly`์— ํ•œํ•ด RED ํ…Œ์ŠคํŠธ๋ฅผ ํ†ต๊ณผ์‹œํ‚ค๋Š” ์ตœ์†Œ ๋กœ์ง๋งŒ ๊ตฌํ˜„ +- ์›”๊ฐ„(monthly) + - ์‹œ์ž‘์ผ์ด 31์ผ์ด๋ฉด 31์ผ์ด ์กด์žฌํ•˜๋Š” ๋‹ฌ(1,3,5,7,8,10,12)์—๋งŒ ์ƒ์„ฑ + - interval ๊ฐ„๊ฒฉ์œผ๋กœ ์›”์„ ์ „์ง„ํ•จ +- ์—ฐ๊ฐ„(yearly) + - ์‹œ์ž‘์ผ์ด 2/29์ด๋ฉด ์œค๋…„์—๋งŒ ์ƒ์„ฑ(๋Œ€์ฒด ๊ธˆ์ง€) + - interval ๊ฐ„๊ฒฉ์œผ๋กœ ์—ฐ๋„๋ฅผ ์ „์ง„ํ•จ +- ์ข…๋ฃŒ ๋ฒ”์œ„: `end` ๋‚ ์งœ๋ฅผ ์ดˆ๊ณผํ•˜์ง€ ์•Š๋„๋ก ์ƒ์„ฑ ์ œํ•œ +- ๊ฒน์นจ ์ฒ˜๋ฆฌ: ๋ณ„๋„ ์ค‘๋ณต ์ œ๊ฑฐ ๋กœ์ง ์—†์Œ(์š”๊ตฌ์‚ฌํ•ญ์— ๋”ฐ๋ผ ํ—ˆ์šฉ) + +## ๊ฒฐ์ •์„ฑ/๋ฒ”์œ„ + +- ํ˜„์žฌ ํ…Œ์ŠคํŠธ๊ฐ€ ์š”๊ตฌํ•˜๋Š” ์ตœ์†Œ ๊ทœ์น™(31์ผ, 2/29, ๊ฒน์นจ ํ—ˆ์šฉ)๋งŒ ์ถฉ์กฑ +- ๊ธฐํƒ€ ์ผ๋ฐ˜ ์ผ€์ด์Šค(์˜ˆ: 30์ผ ์‹œ์ž‘, ์ฃผ๊ฐ„/์ผ๊ฐ„ ๋“ฑ)๋Š” ํ›„์† ์š”๊ตฌ/ํ…Œ์ŠคํŠธ์—์„œ ํ™•์žฅ + +## ํ•œ๊ณ„ ๋ฐ ๋ฆฌํŒฉํ† ๋ง ํ›„๋ณด(์ฐจํ›„ REFACTOR ๋‹จ๊ณ„) + +- ๊ทœ์น™ ๋ถ„๋ฆฌ: 31์ผ ๊ทœ์น™, ์œค๋…„ ๊ทœ์น™ ๋“ฑ ์ˆœ์ˆ˜ ํ•จ์ˆ˜ ์ถ”์ถœ +- ์ธํ„ฐํŽ˜์ด์Šค ๊ฐœ์„ : clock ์ฃผ์ž…/์ตœ๋Œ€ ์ข…๋ฃŒ์ผ(2025-12-31) ์ƒํ•œ ์ ์šฉ ์œ„์น˜ ๋ช…ํ™•ํ™” +- ๋ฐ˜๋ณต ํƒ€์ž… ์ „๋ฐ˜(Weekly/Daily) ์ผ๋ฐ˜ํ™” ๋ฐ ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€ ๋Œ€์‘ + +## ๊ฒ€์ฆ ์ฒดํฌ๋ฆฌ์ŠคํŠธ + +- [ ] RED ํ…Œ์ŠคํŠธ 3๊ฑด ์ „๋ถ€ ํ†ต๊ณผ +- [ ] ์›” 31์ผ: 31์ผ ์žˆ๋Š” ๋‹ฌ๋งŒ ํฌํ•จ +- [ ] ์—ฐ 2/29: ์œค๋…„๋งŒ ํฌํ•จ, ๋Œ€์ฒด ๊ธˆ์ง€ +- [ ] ์ข…๋ฃŒ์ผ ๊ฒฝ๊ณ„ ์ดˆ๊ณผ ์ƒ์„ฑ ์—†์Œ +- [ ] ๊ฒน์นจ ํ—ˆ์šฉ ๋กœ์ง ์œ ์ง€(์ค‘๋ณต ์ œ๊ฑฐ ์—†์Œ) + +## ์ฐจํ›„ ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€ ์˜ˆ์‹œ + +- feat(green): 1. ๋ฐ˜๋ณต ์œ ํ˜• ์„ ํƒ - 31์ผ/2์›”29์ผ ๊ทœ์น™ ์ตœ์†Œ ๊ตฌํ˜„ + +## ํŒŒ์ผ/์ปค๋ฐ‹/์š”๊ตฌ์‚ฌํ•ญ ๋งคํ•‘ + +- ๋ฌธ์„œ: DOCS/feature-1-GREEN.md +- ์ฝ”๋“œ: src/utils/eventUtils.ts (`generateRecurrences`) +- ๊ด€๋ จ ์š”๊ตฌ์‚ฌํ•ญ ๋ฒˆํ˜ธ: 1 diff --git a/DOCS/feature-1-INT-RED.md b/DOCS/feature-1-INT-RED.md new file mode 100644 index 00000000..90509978 --- /dev/null +++ b/DOCS/feature-1-INT-RED.md @@ -0,0 +1,29 @@ +# [RED-ํ†ตํ•ฉ] Feature 1: ๋ฐ˜๋ณต ์œ ํ˜• ์„ ํƒ - UI ์…€๋ ‰ํ„ฐ ๋…ธ์ถœ ๋ฐ ์˜ต์…˜ ๊ฒ€์ฆ + +## ๊ด€๋ จ ์š”๊ตฌ์‚ฌํ•ญ +- 1. ๋ฐ˜๋ณต ์œ ํ˜• ์„ ํƒ (recurring type selection) + - ํผ์—์„œ ๋ฐ˜๋ณต ์œ ํ˜•(๋งค์ผ/๋งค์ฃผ/๋งค์›”/๋งค๋…„)์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•จ + +## ๋ชฉ์  +- ์‹ค์ œ UI ์ƒ์—์„œ โ€œ๋ฐ˜๋ณต ์œ ํ˜•โ€ ์…€๋ ‰ํ„ฐ๊ฐ€ ๋…ธ์ถœ๋˜๊ณ , 4๊ฐ€์ง€ ์˜ต์…˜(๋งค์ผ/๋งค์ฃผ/๋งค์›”/๋งค๋…„)์„ ์ œ๊ณตํ•˜๋Š”์ง€ ํ†ตํ•ฉ ๊ด€์ ์—์„œ ์‹คํŒจ ํ…Œ์ŠคํŠธ(RED)๋ฅผ ์ž‘์„ฑํ•œ๋‹ค. + +## ํ…Œ์ŠคํŠธ ์‹œ๋‚˜๋ฆฌ์˜ค ์š”์•ฝ +1) ์ผ์ • ์ถ”๊ฐ€ ๋ฒ„ํŠผ ํด๋ฆญ โ†’ ํผ ์˜คํ”ˆ +2) ๋ผ๋ฒจ์ด `๋ฐ˜๋ณต ์œ ํ˜•`์ธ ์…€๋ ‰ํ„ฐ์˜ ์ฝค๋ณด๋ฐ•์Šค๋ฅผ ์—ฐ๋‹ค +3) ์˜ต์…˜: `daily-option`, `weekly-option`, `monthly-option`, `yearly-option`์ด DOM์— ๋…ธ์ถœ๋˜๋Š”์ง€ ํ™•์ธ + +## ๊ฒ€์ฆ ํฌ์ธํŠธ +- ๋ผ๋ฒจ ์ ‘๊ทผ์„ฑ(๋ผ๋ฒจ ํ…์ŠคํŠธ `๋ฐ˜๋ณต ์œ ํ˜•`) +- ์ฝค๋ณด๋ฐ•์Šค ์—ญํ•  ๊ธฐ๋ฐ˜ ์ฟผ๋ฆฌ๋กœ ์—ด ์ˆ˜ ์žˆ์–ด์•ผ ํ•จ +- 4๊ฐ€์ง€ ์˜ต์…˜์ด ์ •ํ™•ํžˆ ๋…ธ์ถœ๋˜์–ด์•ผ ํ•จ + +## ๋ณ€๊ฒฝ ํŒŒ์ผ(RED) +- ํ…Œ์ŠคํŠธ: `src/__tests__/medium.integration.spec.tsx` + +## ์ฐจํ›„ ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€ ์˜ˆ์‹œ +- `test(red): 1.i ๋ฐ˜๋ณต ์œ ํ˜• ์„ ํƒ - UI ์…€๋ ‰ํ„ฐ ๋ฐ ์˜ต์…˜ ๋…ธ์ถœ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€` + +## ํŒŒ์ผ/์ปค๋ฐ‹/์š”๊ตฌ์‚ฌํ•ญ ๋งคํ•‘ +- ๋ฌธ์„œ: `DOCS/feature-1-INT-RED.md` +- ํ…Œ์ŠคํŠธ: `src/__tests__/medium.integration.spec.tsx` +- ๊ด€๋ จ ์š”๊ตฌ์‚ฌํ•ญ ๋ฒˆํ˜ธ: 1 (UI ๊ด€์ ) diff --git a/DOCS/feature-1-INT-REFACTOR.md b/DOCS/feature-1-INT-REFACTOR.md new file mode 100644 index 00000000..f85deb83 --- /dev/null +++ b/DOCS/feature-1-INT-REFACTOR.md @@ -0,0 +1,27 @@ +# [REFACTOR-ํ†ตํ•ฉ] Feature 1: ๋ฐ˜๋ณต ์œ ํ˜• ์„ ํƒ - UI ๊ตฌ์กฐ ๊ฐœ์„ /์ปดํฌ๋„ŒํŠธ ๋ถ„๋ฆฌ + +## ๋ชฉ์  +- ๋ฐ˜๋ณต ์œ ํ˜• ์…€๋ ‰ํ„ฐ๋ฅผ ๋ณ„๋„ ์ปดํฌ๋„ŒํŠธ๋กœ ์ถ”์ถœํ•˜์—ฌ ์ฝ”๋“œ ์ค‘๋ณต ์ œ๊ฑฐ, ์ผ๊ด€์„ฑยทํ…Œ์ŠคํŠธ ์šฉ์ด์„ฑ ๊ฐ•ํ™” +- App.tsx UI ์˜์—ญ์„ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์œ ์ง€ํ•˜๊ณ , ์ƒํƒœ/์ด๋ฒคํŠธ ํ•ธ๋“ค๋ง์„ props๋กœ ์ œ์–ดํ•˜๋„๋ก ๋ฆฌํŒฉํ† ๋ง + +## ๋ณ€๊ฒฝ ํŒŒ์ผ +- src/components/RepeatTypeSelector.tsx : ๋ฐ˜๋ณต ์œ ํ˜• ์…€๋ ‰ํ„ฐ ์ปดํฌ๋„ŒํŠธ ์‹ ์„ค(์ฃผ์„ยท๋ผ๋ฒจ ๋ชจ๋‘ ํ•œ๊ธ€) +- src/App.tsx : ๊ธฐ์กด ์ธ๋ผ์ธ ์…€๋ ‰ํ„ฐ UI๋ฅผ RepeatTypeSelector๋กœ ๋Œ€์ฒด, value/onChange ์—ฐ๊ฒฐ + +## ๋ฆฌํŒฉํ† ๋ง ์š”์•ฝ +- FormLabel/Select/MenuItem ๊ตฌ์กฐ, ์ ‘๊ทผ์„ฑ(aria-)์ •์ฑ… ์ผ๊ด€์„ฑ ์œ ์ง€ +- ์ปดํฌ๋„ŒํŠธ prop๋กœ ์ƒํƒœ/์ด๋ฒคํŠธ ๋ฐ”์ธ๋”ฉ(App ์œ ์ง€๋ณด์ˆ˜์„ฑโ†‘) +- ์ผ๊ด€๋œ UX, ํ…Œ์ŠคํŠธ/์—…๋ฌด ๋‹จ์ˆœํ™”(์ค‘๋ณต ์ œ๊ฑฐ) + +## ๊ฒ€์ฆ ์ฒดํฌ๋ฆฌ์ŠคํŠธ +- [ ] ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์ „์ฒด GREEN ์œ ์ง€ +- [ ] ๋ผ๋ฒจยท์—ญํ• ยท์˜ต์…˜ ์ฟผ๋ฆฌ ๋ฐฉ์‹(ํ…Œ์ŠคํŠธยท์ ‘๊ทผ์„ฑ) ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ +- [ ] ๋ฐ˜๋ณต ์œ ํ˜• UI ์ƒํƒœ/ํ•ธ๋“ค๋ง์ด ์ •์ƒ ์ž‘๋™(ํผ ์ œ์ถœ ๋“ฑ ์—ฐ๋™ ํ™•์ธ) + +## ์ฐจํ›„ ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€ ์˜ˆ์‹œ +- refactor(int): 1.i ๋ฐ˜๋ณต ์œ ํ˜• ์„ ํƒ - ์…€๋ ‰ํ„ฐ ์ปดํฌ๋„ŒํŠธ ๋ถ„๋ฆฌ ๋ฐ App ๊ตฌ์กฐ ๊ฐœ์„  + +## ํŒŒ์ผ/์ปค๋ฐ‹/์š”๊ตฌ์‚ฌํ•ญ ๋งคํ•‘ +- ๋ฌธ์„œ: DOCS/feature-1-INT-REFACTOR.md +- ์ฝ”๋“œ: src/components/RepeatTypeSelector.tsx, src/App.tsx +- ๊ด€๋ จ ์š”๊ตฌ์‚ฌํ•ญ ๋ฒˆํ˜ธ: 1 (UI ๊ด€์ ) diff --git a/DOCS/feature-1-RED.md b/DOCS/feature-1-RED.md new file mode 100644 index 00000000..ce6cb04a --- /dev/null +++ b/DOCS/feature-1-RED.md @@ -0,0 +1,39 @@ +# [RED] Feature 1: ๋ฐ˜๋ณต ์œ ํ˜• ์„ ํƒ - ๋‹จ์ผ ์ฑ…์ž„ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ + +## ๊ด€๋ จ ์š”๊ตฌ์‚ฌํ•ญ + +- 1. ๋ฐ˜๋ณต ์œ ํ˜• ์„ ํƒ (recurring type selection) + - ์ผ์ • ์ƒ์„ฑ/์ˆ˜์ • ์‹œ ๋‹ค์Œ ๋ฐ˜๋ณต ์œ ํ˜• ์ค‘ ํ•˜๋‚˜๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋‹ค: ๋งค์ผ, ๋งค์ฃผ, ๋งค์›”, ๋งค๋…„ + - 31์ผ ์„ ํƒ ์‹œ: ๋งค์›” 31์ผ์—๋งŒ ์ผ์ • ์ƒ์„ฑ(30/28/29์ผ ๋Œ€์ฒด ๊ธˆ์ง€) + - 2/29 ์„ ํƒ ์‹œ: ์œค๋…„ 2์›” 29์ผ์—๋งŒ ์ผ์ • ์ƒ์„ฑ + - ๋ฐ˜๋ณต ์ผ์ •์€ ๊ฒน์นจ ํ—ˆ์šฉ + +## ์‚ฐ์ถœ ๋ชฉ์  + +- ์œ„ ์š”๊ตฌ์‚ฌํ•ญ์— ๋Œ€์‘ํ•˜๋Š” ํ•ต์‹ฌ โ€œ๋ฐ˜๋ณต ์œ ํ˜• ์„ ํƒโ€ ๋™์ž‘์— ๋Œ€ํ•œ ์‹คํŒจ ํ…Œ์ŠคํŠธ(RED) ์ž‘์„ฑ์„ ๋ชฉํ‘œ๋กœ ํ•œ๋‹ค. +- ์ผ์ • ์ƒ์„ฑ ์‹œ, ๋ฐ˜๋ณต ์œ ํ˜•์ด ์ •์ƒ ์ธ์‹ ๋ฐ ์ฒ˜๋ฆฌ๋˜๋Š”์ง€ ๊ฒ€์ฆ +- ํŠน์ˆ˜ ๊ทœ์น™(31์ผ, 2/29์ผ, ๊ฒน์นจ ํ—ˆ์šฉ)์— ๋Œ€ํ•œ ๋‹จ์ผ ์ฑ…์ž„ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ + +## ์‹ค์ œ ์ž‘์„ฑ ๊ฒฐ๊ณผ ์š”์•ฝ + +- src/**tests**/unit/recurrenceUtils.spec.ts + - "generateRecurrences" ์œ ํ‹ธ ํ•จ์ˆ˜์— ๋Œ€ํ•œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ 3๊ฑด ์ž‘์„ฑ(์ดˆ๊ธฐ ๋‹จ๊ณ„์—” ์ž„์‹œ ํ•จ์ˆ˜๋ช…) + 1. ๋งค์›” 31์ผ ๋ฐ˜๋ณต ์„ ํƒ ์‹œ 1, 3, 5, 7, 8, 10, 12์›” 31์ผ ์ผ์ •๋งŒ ์ƒ์„ฑ๋˜๋Š”์ง€ ๊ฒ€์ฆ(๋‚˜๋จธ์ง€ ๋‹ฌ์€ ์ƒ์„ฑ ๋ถˆ๊ฐ€) + 2. ์—ฐ 2/29 ๋ฐ˜๋ณต ์‹œ ์œค๋…„(2024, 2028)๋งŒ ํฌํ•จ (๋‹ค๋ฅธ ํ•ด๋Š” ์ƒ์„ฑ ๊ธˆ์ง€) + 3. ๊ธฐ์กด ์ผ์ •๊ณผ ๊ฒน์น˜๋”๋ผ๋„ ์ค‘๋ณต ํ—ˆ์šฉ๋˜์–ด ์ƒ์„ฑ๋จ์„ ๊ฒ€์ฆ(์ค‘๋ณต ์ œ๊ฑฐ ๋ถˆํ•„์š”) +- ํ…Œ์ŠคํŠธ ์„ค๋ช… ๋ฐ ์ฃผ์„ ๋ชจ๋‘ ํ•œ๊ตญ์–ด๋กœ ๋ช…ํ™•ํžˆ ์ž‘์„ฑ(๊ฐ€๋…์„ฑ/๋„๋ฉ”์ธ ์ถ”์ ์„ฑ ํ™•๋ณด) + +## ๊ฒ€์ฆ ํฌ์ธํŠธ + +- ์š”๊ตฌ์‚ฌํ•ญ์—์„œ ๋ช…์‹œํ•œ ํŠน์ˆ˜ ์‚ฌ๋ก€(์›” 31์ผ, ์œค๋…„ 2์›” 29์ผ, ์ค‘๋ณต ํ—ˆ์šฉ)์— ์ •ํ™•ํ•˜๊ฒŒ ๋Œ€์‘ํ•˜๋Š”๊ฐ€? +- ํ…Œ์ŠคํŠธ ๋ช…/๋‚ด์šฉ/๊ฒฝ๊ณ„์กฐ๊ฑด์ด ๋ช…ํ™•ํžˆ ๋“œ๋Ÿฌ๋‚˜๋Š”๊ฐ€? + +## ์ฐจํ›„ ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€ ์˜ˆ์‹œ + +- test(red): 1. ๋ฐ˜๋ณต ์œ ํ˜• ์„ ํƒ - 31์ผ/2์›”29์ผ/๊ฒน์นจ ํ—ˆ์šฉ ๊ทœ์น™ ์‹คํŒจ ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€ + +## ํŒŒ์ผ/์ปค๋ฐ‹/์š”๊ตฌ์‚ฌํ•ญ ๋งคํ•‘ + +- ๋ฌธ์„œ: DOCS/feature-1-RED.md +- ํ…Œ์ŠคํŠธ: src/**tests**/unit/recurrenceUtils.spec.ts +- ๊ด€๋ จ ์š”๊ตฌ์‚ฌํ•ญ ๋ฒˆํ˜ธ: 1 diff --git a/DOCS/feature-1-REFACTOR.md b/DOCS/feature-1-REFACTOR.md new file mode 100644 index 00000000..e1e9e3fd --- /dev/null +++ b/DOCS/feature-1-REFACTOR.md @@ -0,0 +1,45 @@ +# [REFACTOR] Feature 1: ๋ฐ˜๋ณต ์œ ํ˜• ์„ ํƒ - ๊ตฌ์กฐ ๊ฐœ์„  + +## ๊ด€๋ จ ์š”๊ตฌ์‚ฌํ•ญ +- 1. ๋ฐ˜๋ณต ์œ ํ˜• ์„ ํƒ (recurring type selection) + - 31์ผ ์„ ํƒ ์‹œ: ๋งค์›” 31์ผ์—๋งŒ ์ผ์ • ์ƒ์„ฑ(๋Œ€์ฒด ๊ธˆ์ง€) + - 2/29 ์„ ํƒ ์‹œ: ์œค๋…„์—๋งŒ ์ƒ์„ฑ(๋Œ€์ฒด ๊ธˆ์ง€) + - ๋ฐ˜๋ณต ์ผ์ •์€ ๊ฒน์นจ ํ—ˆ์šฉ + +## ๋ชฉ์  +- GREEN ๋‹จ๊ณ„์—์„œ ํ†ต๊ณผํ•œ ๋™์ž‘์„ ์œ ์ง€ํ•˜๋ฉด์„œ, ์ฝ”๋“œ ๊ฐ€๋…์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ ํ–ฅ์ƒ +- ๊ทœ์น™(31์ผ, ์œค๋…„) ๋ถ„๋ฆฌ๋กœ ์ถ”ํ›„ ๊ธฐ๋Šฅ ํ™•์žฅ(์ฃผ๊ฐ„/์ผ๊ฐ„, ์ข…๋ฃŒ ์ƒํ•œ ๋“ฑ)์— ๋Œ€๋น„ + +## ๋ณ€๊ฒฝ ํŒŒ์ผ +- src/utils/eventUtils.ts + - ์ˆœ์ˆ˜ ํ•จ์ˆ˜ ๋ถ„๋ฆฌ: `has31stDay(year, month)`, `isLeapYear(year)`, `formatDate(date)` + - `generateRecurrences` ๋‚ด๋ถ€ ํ๋ฆ„ ๊ฐ„์†Œํ™”(์กฐ๊ฑด ๋ถ„๊ธฐ/๋ฃจํ”„ ๊ฐ€๋…์„ฑ ๊ฐœ์„ ) + - ๋ชจ๋“  ์ฃผ์„ ํ•œ๊ตญ์–ด ์œ ์ง€, ์‹๋ณ„์ž ์˜์–ด + +## ๋ฆฌํŒฉํ† ๋ง ์š”์•ฝ +- 31์ผ ๊ทœ์น™: ์›” 31์ผ ์กด์žฌ ์—ฌ๋ถ€๋ฅผ `has31stDay`๋กœ ์บก์Аํ™” +- ์œค๋…„ ๊ทœ์น™: `isLeapYear`๋กœ ํŒ๋ณ„ ๋กœ์ง ๋ถ„๋ฆฌ +- ๋‚ ์งœ ๋ฌธ์ž์—ด ๋ณ€ํ™˜: `formatDate`๋กœ ํ†ต์ผ +- `generateRecurrences`๋Š” ์›”/์—ฐ ๋กœ์ง์„ ๊ฐ„๊ฒฐํ•œ ๋ฃจํ”„์™€ ๋ถ„๊ธฐ๋งŒ ๋‚จ๊ธฐ๋„๋ก ์ •๋ฆฌ + +## ์ „ํ›„ ๋น„๊ต(ํ•˜์ด๋ผ์ดํŠธ) +- Before: `generateRecurrences` ๋‚ด๋ถ€์—์„œ ๋ง๋‹จ ๋กœ์ง/๋ถ„๊ธฐ ํ˜ผ์žฌ โ†’ ๊ฐ€๋…์„ฑ ๋‚ฎ์Œ +- After: ๊ทœ์น™/ํฌ๋งทํ„ฐ ํ•จ์ˆ˜ ๋ถ„๋ฆฌ, ๋ฃจํ”„ ์ „๊ฐœ ๊ฐ„๊ฒฐํ™” โ†’ ์ฝ๊ธฐ ์‰ฌ์›€, ํ…Œ์ŠคํŠธ ์œ ์ง€๋จ +- ๋™์ž‘ ๋ณ€ํ™” ์—†์Œ(RED/GREEN ํ…Œ์ŠคํŠธ ๋™์ผ ํ†ต๊ณผ) + +## ๊ฒ€์ฆ ์ฒดํฌ๋ฆฌ์ŠคํŠธ +- [ ] ๊ธฐ์กด ํ…Œ์ŠคํŠธ ๋ชจ๋‘ GREEN ์œ ์ง€ +- [ ] ํ•จ์ˆ˜ ๋ถ„๋ฆฌ๋กœ ๋กœ์ง ์ค‘๋ณต/๋…ธ์ด์ฆˆ ๊ฐ์†Œ ํ™•์ธ +- [ ] ์ฃผ์„/์‹๋ณ„์ž ์–ธ์–ด ์ •์ฑ… ์ค€์ˆ˜(์ฃผ์„ ํ•œ๊ตญ์–ด, ์‹๋ณ„์ž ์˜์–ด) + +## ์ฐจํ›„ ํ™•์žฅ ์•„์ด๋””์–ด(์ฐธ๊ณ ) +- Weekly/Daily ๊ทœ์น™ ์ถ”๊ฐ€ ์‹œ ๋™์ผํ•œ ํŒจํ„ด์œผ๋กœ ์ˆœ์ˆ˜ ํ•จ์ˆ˜ ๋ถ„๋ฆฌ +- ์ข…๋ฃŒ ์ƒํ•œ(2025-12-31) ํด๋žจํ•‘ ๋กœ์ง์„ ๊ณตํ†ตํ™” + +## ์ฐจํ›„ ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€ ์˜ˆ์‹œ +- refactor: 1. ๋ฐ˜๋ณต ์œ ํ˜• ์„ ํƒ - 31์ผ/์œค๋…„ ๊ทœ์น™ ๋ถ„๋ฆฌ ๋ฐ ๊ตฌ์กฐ ๊ฐœ์„  + +## ํŒŒ์ผ/์ปค๋ฐ‹/์š”๊ตฌ์‚ฌํ•ญ ๋งคํ•‘ +- ๋ฌธ์„œ: DOCS/feature-1-REFACTOR.md +- ์ฝ”๋“œ: src/utils/eventUtils.ts +- ๊ด€๋ จ ์š”๊ตฌ์‚ฌํ•ญ ๋ฒˆํ˜ธ: 1 diff --git a/DOCS/recurring-requirements.en.md b/DOCS/recurring-requirements.en.md new file mode 100644 index 00000000..96679391 --- /dev/null +++ b/DOCS/recurring-requirements.en.md @@ -0,0 +1,50 @@ +# Recurring Events - Required Feature Specification + +This document defines the mandatory behavior for implementing recurring events. Use it as the source of truth for tests (TDD) and implementation. + +## 1) Recurrence Type Selection + +- Users can select a recurrence type when creating or editing an event. +- Supported types: Daily, Weekly, Monthly, Yearly. +- Special rules: + - Monthly on the 31st: generate occurrences only on the 31st of months that have 31 days. Do NOT substitute with the last day (30/28/29). + - Yearly on Feb 29: generate occurrences only in leap years (Feb 29). Do NOT substitute with Feb 28/Mar 1. +- Overlaps are allowed by design; do not prevent or merge overlapping occurrences. + +## 2) Recurring Indicator in Calendar + +- In the calendar view, display a distinct icon/marker for recurring events. +- Single (detached) events should not display the recurring icon. + +## 3) Recurrence End Condition + +- Users can specify an end condition for the recurrence. +- Option: Until a specific date. + - For this assignment, ensure the maximum generated date is capped at 2025-12-31. + +## 4) Edit Recurring Events + +- When editing an occurrence of a recurring event, prompt the user: "Edit only this occurrence?" + - If the user selects Yes (single edit): + - Convert the selected occurrence into a single (detached) event. + - Remove the recurring icon for that event. + - If the user selects No (edit all): + - Apply changes to the entire series (the event remains recurring). + - The recurring icon remains for occurrences in the series. + +## 5) Delete Recurring Events + +- When deleting an occurrence of a recurring event, prompt the user: "Delete only this occurrence?" + - If the user selects Yes (single delete): + - Remove only the selected occurrence. + - If the user selects No (delete all): + - Delete all occurrences in the series. + +## Notes + +- All rules must be covered by tests first (RED โ†’ GREEN โ†’ REFACTOR). +- Treat time/clock deterministically in tests (fake timers or injected clock). + + + + diff --git a/copilot-instructions.md b/copilot-instructions.md new file mode 100644 index 00000000..ffb577cb --- /dev/null +++ b/copilot-instructions.md @@ -0,0 +1,38 @@ +# Copilot Instructions (Project-Specific) + +## Mission + +- Assist with TDD development for recurring schedule features. +- Prefer small, safe, and readable edits that keep lints/types clean. + +## Behavior + +- Generate tests first (failing), then minimal implementation, then refactor. +- Use domain language and AAA structure in tests. +- Favor React Testing Library queries by role/label/text; use `userEvent`. +- Mock time/network/random deterministically (MSW, fake timers, injected clock). +- Keep files and paths consistent with the repository structure. + +## Do + +- Put new docs in `DOCS/` (except `.cursor` rules); tests in `src/__tests__/`. +- Write clear commit messages per stage: `test(red)`, `feat(green)`, `refactor`. +- Add boundary/negative test cases for date/recurrence rules. + +## Donโ€™t + +- Donโ€™t rely on implementation details in tests. +- Donโ€™t introduce flaky tests or time-dependent assertions. +- Donโ€™t over-mock core domain logic in integration tests. + +## Recurring Feature Notes + +- Monthly 31st โ†’ only on the 31st; no substitution with 30/28/29. +- Yearly Feb 29 โ†’ only in leap years. +- Overlaps allowed; no de-dup. +- Calendar shows icon for recurring; detached single edits remove the icon. +- End date must not exceed 2025-12-31. + + + + diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 00000000..9ff8d8fa --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +onlyBuiltDependencies: + - '@swc/core' + - esbuild + - msw diff --git a/src/App.tsx b/src/App.tsx index 195c5b05..28128213 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -36,7 +36,7 @@ import { useEventOperations } from './hooks/useEventOperations.ts'; import { useNotifications } from './hooks/useNotifications.ts'; import { useSearch } from './hooks/useSearch.ts'; // import { Event, EventForm, RepeatType } from './types'; -import { Event, EventForm } from './types'; +import { Event, EventForm, RepeatType } from './types'; import { formatDate, formatMonth, @@ -47,6 +47,7 @@ import { } from './utils/dateUtils'; import { findOverlappingEvents } from './utils/eventOverlap'; import { getTimeErrorMessage } from './utils/timeValidation'; +import RepeatTypeSelector from './components/RepeatTypeSelector'; const categories = ['์—…๋ฌด', '๊ฐœ์ธ', '๊ฐ€์กฑ', '๊ธฐํƒ€']; @@ -77,7 +78,7 @@ function App() { isRepeating, setIsRepeating, repeatType, - // setRepeatType, + setRepeatType, repeatInterval, // setRepeatInterval, repeatEndDate, @@ -421,6 +422,9 @@ function App() { /> + {/* ๋ฐ˜๋ณต ์œ ํ˜• ์…€๋ ‰ํ„ฐ ์ถ”์ถœํ•œ ์ปดํฌ๋„ŒํŠธ๋กœ ๋Œ€์ฒด */} + + ์•Œ๋ฆผ ์„ค์ • onChange(e.target.value as RepeatType)} + aria-labelledby="repeat-type-label" + aria-label="๋ฐ˜๋ณต ์œ ํ˜•" + > + + ๋งค์ผ + + + ๋งค์ฃผ + + + ๋งค์›” + + + ๋งค๋…„ + + + + ); +} diff --git a/src/utils/eventUtils.ts b/src/utils/eventUtils.ts index 9e75e947..3bd46ba0 100644 --- a/src/utils/eventUtils.ts +++ b/src/utils/eventUtils.ts @@ -56,3 +56,103 @@ export function getFilteredEvents( return searchedEvents; } + +// ---- ๋ฐ˜๋ณต ์ผ์ • ์ƒ์„ฑ (ํ…Œ์ŠคํŠธ ํ†ต๊ณผ๋ฅผ ์œ„ํ•œ ์ตœ์†Œ ๊ตฌํ˜„) ---- + +type RecurrenceType = 'daily' | 'weekly' | 'monthly' | 'yearly'; + +interface GenerateRecurrencesParams { + start: Date; + end: Date; + type: RecurrenceType; + interval: number; +} + +// ์›”์— 31์ผ์ด ์žˆ๋Š”์ง€ ํŒ๋ณ„ +function has31stDay(year: number, month: number): boolean { + return new Date(year, month + 1, 0).getDate() === 31; +} + +// ํ•ด๋‹น ์—ฐ๋„๊ฐ€ ์œค๋…„์ธ์ง€ ํŒ๋ณ„ +function isLeapYear(year: number): boolean { + return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; +} + +// ๋‚ ์งœ YYYY-MM-DD ํ˜•์‹ ๋ฌธ์ž์—ด ๋ณ€ํ™˜ +function formatDate(date: Date): string { + const y = date.getFullYear(); + const m = String(date.getMonth() + 1).padStart(2, '0'); + const d = String(date.getDate()).padStart(2, '0'); + return `${y}-${m}-${d}`; +} + +export function generateRecurrences({ + start, + end, + type, + interval, +}: GenerateRecurrencesParams): { date: string }[] { + const results: { date: string }[] = []; + const endTime = end.getTime(); + const startDay = start.getDate(); + let y = start.getFullYear(); + let m = start.getMonth(); + + if (type === 'monthly') { + // 31์ผ ์ „์šฉ: 31์ผ์ด ์žˆ๋Š” ๋‹ฌ์—๋งŒ + while (true) { + if (startDay === 31) { + if (has31stDay(y, m)) { + const candidate = new Date(y, m, 31); + if (candidate.getTime() > endTime) break; + if (candidate.getTime() >= start.getTime()) { + results.push({ date: formatDate(candidate) }); + } + } + } else { + // ์ผ๋ฐ˜ ๊ทœ์น™(ํ›„์† ํ™•์žฅ) + const candidate = new Date(y, m, startDay); + if (candidate.getTime() > endTime) break; + if (candidate.getDate() === startDay && candidate.getTime() >= start.getTime()) { + results.push({ date: formatDate(candidate) }); + } + } + // ์›”, ์—ฐ๋„ ์ฆ๊ฐ€ + m += interval; + while (m >= 12) { + m -= 12; + y += 1; + } + if (new Date(y, m, 1).getTime() > endTime) break; + } + return results; + } + + if (type === 'yearly') { + // 2/29 ์ „์šฉ: ์œค๋…„์—๋งŒ + const startMonth = start.getMonth(); + while (true) { + if (startMonth === 1 && startDay === 29) { + if (isLeapYear(y)) { + const candidate = new Date(y, 1, 29); + if (candidate.getTime() > endTime) break; + if (candidate.getTime() >= start.getTime()) { + results.push({ date: formatDate(candidate) }); + } + } + } else { + const candidate = new Date(y, startMonth, startDay); + if (candidate.getTime() > endTime) break; + if (candidate.getTime() >= start.getTime()) { + results.push({ date: formatDate(candidate) }); + } + } + y += interval; + if (new Date(y, startMonth, 1).getTime() > endTime) break; + } + return results; + } + + // ๊ทธ ์™ธ: ํ…Œ์ŠคํŠธ ์š”๊ตฌ ํƒ€์ž… ์™ธ์—” ๋ฐ˜ํ™˜ ์—†์Œ + return results; +}