{"id":31158099,"url":"https://github.com/arnobt78/react-vite-test-driven-development-testing-tutorial","last_synced_at":"2025-09-18T23:33:20.566Z","repository":{"id":304262824,"uuid":"1018276661","full_name":"arnobt78/React-Vite-Test-Driven-Development-Testing-Tutorial","owner":"arnobt78","description":"A productivity app built with React, TypeScript, and Tailwind CSS, designed to help you manage tasks by priority. This project is developed using a Test-Driven Development (TDD) approach, making it an excellent resource for learning both React and modern testing practices.","archived":false,"fork":false,"pushed_at":"2025-07-11T23:49:19.000Z","size":72,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-07-12T02:34:42.861Z","etag":null,"topics":["focus-flow-testing","react-test-fresher-ts","react-testing","react-testing-library","react-vite-typescript","rtl","test-automation","test-driven-development","test-driven-development-tdd-fundamentals","test-driven-development-testing","test-framework","testing","testing-tutorials","vitest","vitest-ts","vitest-ui"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/arnobt78.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-07-11T23:45:11.000Z","updated_at":"2025-07-11T23:56:37.000Z","dependencies_parsed_at":"2025-07-12T02:36:39.639Z","dependency_job_id":"955eabab-09a3-42aa-ba2d-49d04e09ec0a","html_url":"https://github.com/arnobt78/React-Vite-Test-Driven-Development-Testing-Tutorial","commit_stats":null,"previous_names":["arnobt78/react-vite-test-driven-development-testing-tutorial"],"tags_count":null,"template":true,"template_full_name":null,"purl":"pkg:github/arnobt78/React-Vite-Test-Driven-Development-Testing-Tutorial","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arnobt78%2FReact-Vite-Test-Driven-Development-Testing-Tutorial","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arnobt78%2FReact-Vite-Test-Driven-Development-Testing-Tutorial/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arnobt78%2FReact-Vite-Test-Driven-Development-Testing-Tutorial/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arnobt78%2FReact-Vite-Test-Driven-Development-Testing-Tutorial/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/arnobt78","download_url":"https://codeload.github.com/arnobt78/React-Vite-Test-Driven-Development-Testing-Tutorial/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arnobt78%2FReact-Vite-Test-Driven-Development-Testing-Tutorial/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":275852305,"owners_count":25540136,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-18T02:00:09.552Z","response_time":77,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["focus-flow-testing","react-test-fresher-ts","react-testing","react-testing-library","react-vite-typescript","rtl","test-automation","test-driven-development","test-driven-development-tdd-fundamentals","test-driven-development-testing","test-framework","testing","testing-tutorials","vitest","vitest-ts","vitest-ui"],"created_at":"2025-09-18T23:31:27.365Z","updated_at":"2025-09-18T23:33:20.516Z","avatar_url":"https://github.com/arnobt78.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Focus Flow - React-Vite Test-Driven Development (TDD) Testing Tutorial (Vitest)\n\n![Screenshot 2025-06-18 at 13 58 33](https://github.com/user-attachments/assets/0c3a88d2-7985-41ab-8e44-e5fae93ef1f6)\n![Screenshot 2025-06-18 at 13 58 10](https://github.com/user-attachments/assets/f0c9fa32-55d8-4953-94d0-2c5c5c8d59f5)\n\n---\n\nA productivity app built with React, TypeScript, and Tailwind CSS, designed to help you manage tasks by priority. This project is developed using a **Test-Driven Development (TDD)** approach, making it an excellent resource for learning both React and modern testing practices.\n\n---\n\n## Table of Contents\n\n1. [Project Summary](#project-summary)\n2. [Features](#features)\n3. [Technology Stack](#technology-stack)\n4. [Project Structure](#project-structure)\n5. [Getting Started](#getting-started)\n6. [App Walkthrough](#app-walkthrough)\n7. [TDD \u0026 Testing Guide](#tdd--testing-guide)\n8. [Teaching \u0026 TDD Walkthrough](#teaching--tdd-walkthrough)\n9. [Keywords](#keywords)\n10. [License](#license)\n\n---\n\n## Project Summary\n\n**Focus Flow** is a simple, extensible task management app that demonstrates how to build robust React applications using TDD. The project guides you through writing tests first, then implementing features, ensuring your code is reliable and maintainable.\n\n---\n\n## Features\n\n- Add, view, and delete tasks with categories (urgent, important, normal, low)\n- Responsive UI with Tailwind CSS\n- Uses React Context for state management\n- Fully tested with Vitest and React Testing Library\n- Clean, modular codebase for easy learning and extension\n\n---\n\n## Technology Stack\n\n- **React** (with Hooks)\n- **TypeScript**\n- **Tailwind CSS**\n- **Vite** (for fast development)\n- **Vitest** (unit/integration testing)\n- **React Testing Library** (component testing)\n- **lucide-react** (icon library)\n\n---\n\n## Project Structure\n\n```bash\n├── public/\n├── src/\n│   ├── __tests__/\n│   │   ├── AppWithContext.test.tsx\n│   │   ├── Form.test.tsx\n│   │   ├── ItemCard.test.tsx\n│   │   └── List.test.tsx\n│   ├── assets/\n│   ├── components/\n│   │   ├── App.tsx\n│   │   ├── Form.tsx\n│   │   ├── ItemCard.tsx\n│   │   └── List.tsx\n│   ├── AppWithContext.tsx\n│   ├── FlowContext.tsx\n│   ├── index.css\n│   ├── main.tsx\n│   ├── utils.ts\n│   └── vite-env.d.ts\n├── package.json\n├── tailwind.config.js\n├── vite.config.ts\n└── README.md\n```\n\n---\n\n## Getting Started\n\n1. **Install dependencies:**\n\n   ```sh\n   npm install\n   ```\n\n2. **Run the development server:**\n\n   ```sh\n   npm run dev\n   ```\n\n3. **Run tests:**\n\n   ```sh\n   npm test\n   ```\n\n   or\n\n   ```sh\n   npx vitest\n   ```\n\n---\n\n## App Walkthrough\n\n- **Main Logic:**  \n  The core logic (types and hooks) is in `src/utils.ts`. The `useFlowManager` hook manages the list of tasks and provides add/delete functionality.\n\n- **Components:**\n\n  - `Form.tsx`: Handles task creation.\n  - `List.tsx`: Displays all tasks.\n  - `ItemCard.tsx`: Shows individual task details and delete button.\n\n- **Context:**  \n  `FlowContext.tsx` provides global state using React Context, making the app scalable and testable.\n\n- **App Entry:**  \n  `AppWithContext.tsx` is the main app, wrapped in `FlowProvider` in `main.tsx`.\n\n---\n\n## TDD \u0026 Testing Guide\n\nThis project is built using **Test-Driven Development**. All major components have corresponding tests in `src/__tests__/`.\n\n### Form Component Tests\n\n- Test initial render (fields are empty)\n- Test form submission (calls `onSubmit` with correct data)\n- Test validation (required fields)\n- Test form reset after submission\n\n### List Component Tests\n\n- Renders heading and correct number of `ItemCard`s\n- Handles empty state\n\n### ItemCard Component Tests\n\n- Renders task details\n- Calls `onDelete` when delete button is clicked\n\n### Context \u0026 Integration Tests\n\n- Full app integration tests in `AppWithContext.test.tsx`\n- Add and delete tasks through the UI\n- Ensure context state updates and UI reflects changes\n\n**Testing Tools Used:**\n\n- [Vitest](https://vitest.dev/)\n- [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/)\n\n---\n\n## Teaching \u0026 TDD Walkthrough\n\nBelow is a detailed, step-by-step teaching walkthrough of the TDD process for this project, including code snippets, test strategies, and implementation notes. Use this as a learning resource for React, TDD, and component testing.\n\n## Main Logic\n\nTo make things simpler, we’ll start by building the core logic of the application first. Once that’s complete, we’ll move on to writing the tests. This approach will allow you to see the bigger picture of how the application works before diving into the details of test setup.\n\n- create `src/utils.ts` file\n\nThis file contains all of the types and the main logic of the application.\n\n```tsx\nimport { useState } from \"react\";\n\nexport type ItemCategory = \"urgent\" | \"important\" | \"normal\" | \"low\";\n\nexport type Item = {\n  id: string;\n  title: string;\n  description: string;\n  category: ItemCategory;\n};\n\nexport type ItemWithoutId = Omit\u003cItem, \"id\"\u003e;\n\nexport const useFlowManager = () =\u003e {\n  const [items, setItems] = useState\u003cItem[]\u003e([]);\n\n  const handleAddItem = (newItem: ItemWithoutId) =\u003e {\n    setItems([...items, { ...newItem, id: Date.now().toString() }]);\n  };\n\n  const handleDeleteItem = (id: string) =\u003e {\n    setItems(items.filter((item) =\u003e item.id !== id));\n  };\n\n  return { items, handleAddItem, handleDeleteItem };\n};\n```\n\n## Components\n\n- create `src/components/Form.tsx` file\n- create `src/components/List.tsx` file\n- create `src/components/ItemCard.tsx` file\n\n```tsx\nconst Form = () =\u003e {\n  return \u003cdiv\u003eForm\u003c/div\u003e;\n};\nexport default Form;\n```\n\n## App.tsx\n\n```tsx\nimport Form from \"./components/Form\";\nimport List from \"./components/List\";\nimport { useFlowManager } from \"./utils\";\n\nexport default function Home() {\n  const { items, handleAddItem, handleDeleteItem } = useFlowManager();\n}\n```\n\nIn order to fix we need to setup props for the components.\n\ncomponents/Form.tsx\n\n```tsx\nimport { type ItemWithoutId, type ItemCategory } from \"../utils\";\n\nconst Form = ({ onSubmit }: { onSubmit: (item: ItemWithoutId) =\u003e void }) =\u003e {\n  return \u003cdiv\u003eForm\u003c/div\u003e;\n};\nexport default Form;\n```\n\n```tsx\nimport ItemCard from \"./ItemCard\";\nimport { Item } from \"../utils\";\n\nconst List = ({\n  items,\n  onDelete,\n}: {\n  items: Item[];\n  onDelete: (id: string) =\u003e void;\n}) =\u003e {\n  return \u003cdiv\u003eList\u003c/div\u003e;\n};\nexport default List;\n```\n\n## Form Tests\n\n`src/__tests__/Form.test.tsx`\n\nFor the Form component I'm going to setup tests one by one but you can set all of them at once.\n\n1. Set up the test environment:\n\n   - Import necessary testing libraries (React Testing Library, Vitest)\n   - Import the component to be tested\n\n2. Create helper functions:\n\n   - Define a function to get commonly used elements (e.g., `getElements`)\n\n3. Set up the test suite:\n\n   - Use `describe` to group related tests\n   - Create mock functions for callbacks (e.g., `mockOnSubmit`)\n\n4. Write individual test cases:\n\n   - Use `test` or `it` to define each test case\n   - Render the component with necessary props\n   - Interact with the component using `userEvent`\n   - Make assertions using `expect`\n\n5. Test initial render:\n\n   - Check if form fields are empty initially\n\n6. Test form submission:\n\n   - Fill out form fields\n   - Submit the form\n   - Verify if the submit callback is called with correct data\n\n7. Test form validation:\n\n   - Try submitting an empty form\n   - Verify that the submit callback is not called\n\n8. Test form reset after submission:\n\n   - Fill out and submit the form\n   - Check if fields are cleared after submission\n\n9. Use `beforeEach` to reset mocks between tests\n\n10. Use async/await for asynchronous operations (e.g., user interactions)\n\n11. Use descriptive test names to clearly indicate what each test is checking\n\nsrc/**tests**/Form.test.tsx\n\n```tsx\nimport { render, screen } from \"@testing-library/react\";\nimport { describe, test, expect, vi } from \"vitest\";\nimport Form from \"../components/Form\";\nimport userEvent, { UserEvent } from \"@testing-library/user-event\";\n\nconst getElements = () =\u003e ({\n  titleInput: screen.getByRole(\"textbox\", { name: /title/i }),\n  descriptionInput: screen.getByRole(\"textbox\", { name: /description/i }),\n  categorySelect: screen.getByRole(\"combobox\", { name: /category/i }),\n  submitButton: screen.getByRole(\"button\", { name: /add task/i }),\n});\n\ndescribe(\"Form Component\", () =\u003e {\n  let user: UserEvent;\n  const mockOnSubmit = vi.fn();\n\n  beforeEach(() =\u003e {\n    mockOnSubmit.mockClear();\n    user = userEvent.setup();\n  });\n\n  // 1. Test renders form with empty fields initially\n  test(\"renders form with empty fields initially\", () =\u003e {\n    render(\u003cForm onSubmit={mockOnSubmit} /\u003e);\n\n    const { titleInput, descriptionInput, categorySelect } = getElements();\n\n    expect(titleInput).toHaveValue(\"\");\n    expect(descriptionInput).toHaveValue(\"\");\n    expect(categorySelect).toHaveValue(\"\");\n  });\n  // 2. Test submits form with entered values\n  test(\"submits form with entered values\", async () =\u003e {\n    render(\u003cForm onSubmit={mockOnSubmit} /\u003e);\n    const { titleInput, descriptionInput, categorySelect, submitButton } =\n      getElements();\n\n    await user.type(titleInput, \"New Task\");\n    await user.type(descriptionInput, \"Task Description\");\n    await user.selectOptions(categorySelect, \"urgent\");\n    await user.click(submitButton);\n\n    expect(mockOnSubmit).toHaveBeenCalledWith({\n      title: \"New Task\",\n      description: \"Task Description\",\n      category: \"urgent\",\n    });\n  });\n  // 3. Test validates required fields\n  test(\"validates required fields\", async () =\u003e {\n    render(\u003cForm onSubmit={mockOnSubmit} /\u003e);\n    const { submitButton } = getElements();\n    await user.click(submitButton);\n    expect(mockOnSubmit).not.toHaveBeenCalled();\n  });\n  // 4. Test clears form after successful submission\n  test(\"clears form after successful submission\", async () =\u003e {\n    render(\u003cForm onSubmit={mockOnSubmit} /\u003e);\n    const { titleInput, descriptionInput, categorySelect, submitButton } =\n      getElements();\n\n    await user.type(titleInput, \"New Task\");\n    await user.type(descriptionInput, \"Task Description\");\n    await user.selectOptions(categorySelect, \"urgent\");\n    await user.click(submitButton);\n\n    expect(titleInput).toHaveValue(\"\");\n    expect(descriptionInput).toHaveValue(\"\");\n    expect(categorySelect).toHaveValue(\"\");\n  });\n});\n```\n\nsrc/components/Form.tsx\n\n```tsx\nimport { useState } from \"react\";\nimport { type ItemWithoutId, type ItemCategory } from \"../utils\";\n\nconst Form = ({ onSubmit }: { onSubmit: (item: ItemWithoutId) =\u003e void }) =\u003e {\n  const [title, setTitle] = useState(\"\");\n  const [description, setDescription] = useState(\"\");\n  const [category, setCategory] = useState\u003cItemCategory | \"\"\u003e(\"\");\n\n  const handleSubmit = (e: React.FormEvent) =\u003e {\n    e.preventDefault();\n  };\n\n  return (\n    \u003cdiv className=\"max-w-xl\"\u003e\n      \u003ch2 className=\"text-xl font-semibold mb-2\"\u003eAdd New Task\u003c/h2\u003e\n      \u003cform onSubmit={handleSubmit} className=\"space-y-4\"\u003e\n        \u003cdiv\u003e\n          \u003clabel\n            htmlFor=\"title\"\n            className=\"text-sm font-medium leading-none block mb-2\"\n          \u003e\n            Title\n          \u003c/label\u003e\n          \u003cinput\n            id=\"title\"\n            value={title}\n            onChange={(e) =\u003e setTitle(e.target.value)}\n            required\n            className=\"flex h-10 w-full rounded-md border px-3 py-2 text-sm\"\n          /\u003e\n        \u003c/div\u003e\n        \u003cdiv\u003e\n          \u003clabel\n            htmlFor=\"description\"\n            className=\"text-sm font-medium leading-none block mb-2\"\n          \u003e\n            Description\n          \u003c/label\u003e\n          \u003cinput\n            id=\"description\"\n            value={description}\n            onChange={(e) =\u003e setDescription(e.target.value)}\n            required\n            className=\"flex h-10 w-full rounded-md border  px-3 py-2 text-sm\"\n          /\u003e\n        \u003c/div\u003e\n        \u003cdiv\u003e\n          \u003clabel\n            htmlFor=\"category\"\n            className=\"text-sm font-medium leading-none block mb-2\"\n          \u003e\n            Category\n          \u003c/label\u003e\n          \u003cselect\n            id=\"category\"\n            value={category}\n            onChange={(e) =\u003e setCategory(e.target.value as ItemCategory)}\n            required\n            className=\"flex h-10 w-full rounded-md border  px-3 py-2 text-sm\"\n          \u003e\n            \u003coption value=\"\"\u003eSelect Category\u003c/option\u003e\n            \u003coption value=\"urgent\"\u003eUrgent\u003c/option\u003e\n            \u003coption value=\"important\"\u003eImportant\u003c/option\u003e\n            \u003coption value=\"normal\"\u003eNormal\u003c/option\u003e\n            \u003coption value=\"low\"\u003eLow Priority\u003c/option\u003e\n          \u003c/select\u003e\n        \u003c/div\u003e\n        \u003cbutton\n          type=\"submit\"\n          className=\"rounded text-sm font-medium bg-blue-600 text-white hover:bg-blue-700 h-10 px-4 py-2 \"\n        \u003e\n          Add Task\n        \u003c/button\u003e\n      \u003c/form\u003e\n    \u003c/div\u003e\n  );\n};\n\nexport default Form;\n```\n\n### Test 2 : Submits form with entered values\n\n```tsx\n// 2. Test submits form with entered values\ntest(\"submits form with entered values\", async () =\u003e {\n  render(\u003cForm onSubmit={mockOnSubmit} /\u003e);\n  const { titleInput, descriptionInput, categorySelect, submitButton } =\n    getElements();\n\n  await user.type(titleInput, \"New Task\");\n  await user.type(descriptionInput, \"Task Description\");\n  await user.selectOptions(categorySelect, \"urgent\");\n  await user.click(submitButton);\n\n  expect(mockOnSubmit).toHaveBeenCalledWith({\n    title: \"New Task\",\n    description: \"Task Description\",\n    category: \"urgent\",\n  });\n});\n```\n\nsrc/components/Form.tsx\n\n```tsx\nconst handleSubmit = (e: React.FormEvent) =\u003e {\n  e.preventDefault();\n  // typescript will complain if we don't check for empty strings\n  onSubmit({ title, description, category });\n};\n```\n\n### Test 3 : Validates required fields\n\nSince we use html input `required` attribute, test will pass right away if we don't fill out all of the inputs in the form.\n\n```tsx\n// 3. Test validates required fields\ntest(\"validates required fields\", async () =\u003e {\n  render(\u003cForm onSubmit={mockOnSubmit} /\u003e);\n  const { submitButton } = getElements();\n  await user.click(submitButton);\n  expect(mockOnSubmit).not.toHaveBeenCalled();\n});\n```\n\n```tsx\nconst handleSubmit = (e: React.FormEvent) =\u003e {\n  e.preventDefault();\n  // typescript will complain if we don't check for empty strings\n  if (!title || !description || !category) return;\n  onSubmit({ title, description, category });\n};\n```\n\n### Test 4 : Clears form after successful submission\n\n```tsx\n// 4. Test clears form after successful submission\ntest(\"clears form after successful submission\", async () =\u003e {\n  render(\u003cForm onSubmit={mockOnSubmit} /\u003e);\n  const { titleInput, descriptionInput, categorySelect, submitButton } =\n    getElements();\n\n  await user.type(titleInput, \"New Task\");\n  await user.type(descriptionInput, \"Task Description\");\n  await user.selectOptions(categorySelect, \"urgent\");\n  await user.click(submitButton);\n\n  expect(titleInput).toHaveValue(\"\");\n  expect(descriptionInput).toHaveValue(\"\");\n  expect(categorySelect).toHaveValue(\"\");\n});\n```\n\n```tsx\nconst handleSubmit = (e: React.FormEvent) =\u003e {\n  e.preventDefault();\n  // typescript will complain if we don't check for empty strings\n  if (!title || !description || !category) return;\n  onSubmit({ title, description, category });\n  setTitle(\"\");\n  setDescription(\"\");\n  setCategory(\"\");\n};\n```\n\n## List Tests\n\n- create `src/__tests__/List.test.tsx` file\n\n```tsx\nimport { render, screen } from \"@testing-library/react\";\nimport { describe, test, expect, vi } from \"vitest\";\nimport List from \"../components/List\";\nimport { type Item } from \"../utils\";\n// Mock the ItemCard component to simplify testing\n// This replaces the actual ItemCard component with a simple article element\n// that contains the text \"item card\"\nvi.mock(\"../components/ItemCard\", () =\u003e ({\n  // The default export is a function that returns a simple article element\n  // This helps us test the List component in isolation without the complexity\n  // of the actual ItemCard implementation\n  default: () =\u003e \u003carticle\u003eitem card\u003c/article\u003e,\n}));\n\ndescribe(\"List\", () =\u003e {\n  const mockItems: Item[] = [\n    {\n      id: \"1\",\n      title: \"Test Item 1\",\n      description: \"Content 1\",\n      category: \"urgent\",\n    },\n    {\n      id: \"2\",\n      title: \"Test Item 2\",\n      description: \"Content 2\",\n      category: \"normal\",\n    },\n  ];\n  const mockOnDelete = vi.fn();\n\n  test(\"renders the Flow Board heading\", () =\u003e {\n    render(\u003cList items={mockItems} onDelete={mockOnDelete} /\u003e);\n    expect(\n      screen.getByRole(\"heading\", { level: 2, name: \"Flow Board\" })\n    ).toBeInTheDocument();\n  });\n\n  test(\"renders correct number of ItemCards\", () =\u003e {\n    render(\u003cList items={mockItems} onDelete={mockOnDelete} /\u003e);\n\n    const cards = screen.getAllByRole(\"article\");\n    expect(cards).toHaveLength(2);\n  });\n  //\n  test(\"renders empty grid when no items provided\", () =\u003e {\n    render(\u003cList items={[]} onDelete={mockOnDelete} /\u003e);\n    expect(screen.queryAllByRole(\"article\")).toHaveLength(0);\n  });\n  // ALTERNATIVE if we want to test the number of items only in the component. Useful if there are other places where such items are rendered.\n\n  test(\"ALTERNATIVE: renders correct number of ItemCards\", () =\u003e {\n    const { queryAllByRole } = render(\n      \u003cList items={mockItems} onDelete={mockOnDelete} /\u003e\n    );\n    expect(queryAllByRole(\"article\")).toHaveLength(2);\n  });\n\n  test(\"ALTERNATIVE: renders empty grid when no items provided\", () =\u003e {\n    const { queryAllByRole } = render(\n      \u003cList items={[]} onDelete={mockOnDelete} /\u003e\n    );\n    expect(queryAllByRole(\"article\")).toHaveLength(0);\n  });\n});\n```\n\nsrc/components/List.tsx\n\n```tsx\nimport ItemCard from \"./ItemCard\";\nimport { type Item } from \"../utils\";\n\nexport default function List({\n  items,\n  onDelete,\n}: {\n  items: Item[];\n  onDelete: (id: string) =\u003e void;\n}) {\n  return (\n    \u003csection className=\"mt-8\"\u003e\n      \u003ch2 className=\"text-xl font-semibold mb-2\"\u003eFlow Board\u003c/h2\u003e\n      \u003cdiv className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4\"\u003e\n        {items.map((item) =\u003e (\n          \u003cItemCard key={item.id} {...item} onDelete={onDelete} /\u003e\n        ))}\n      \u003c/div\u003e\n    \u003c/section\u003e\n  );\n}\n```\n\n## ItemCard Tests\n\ncomponents/ItemCard.tsx\n\n```tsx\nimport { Trash2 } from \"lucide-react\";\n\ntype ItemCardProps = {\n  id: string;\n  title: string;\n  description: string;\n  category: string;\n  onDelete: (id: string) =\u003e void;\n};\n\nconst ItemCard = ({\n  id,\n  title,\n  description,\n  category,\n  onDelete,\n}: ItemCardProps) =\u003e {\n  return \u003cdiv\u003eItemCard\u003c/div\u003e;\n};\nexport default ItemCard;\n```\n\n- create `src/__tests__/ItemCard.test.tsx` file\n\n```tsx\nimport { describe, test, vi } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport ItemCard from \"../components/ItemCard\";\nimport { type Item } from \"../utils\";\n\ntype MockProps = Item \u0026 { onDelete: () =\u003e void };\n\ndescribe(\"ItemCard\", () =\u003e {\n  const mockProps: MockProps = {\n    id: \"1\",\n    title: \"Test Task\",\n    description: \"Test Description\",\n    category: \"urgent\",\n    onDelete: vi.fn(),\n  };\n\n  test(\"renders card with correct content\", () =\u003e {\n    render(\u003cItemCard {...mockProps} /\u003e);\n\n    expect(\n      screen.getByRole(\"heading\", { name: \"Test Task\" })\n    ).toBeInTheDocument();\n    expect(screen.getByRole(\"article\")).toBeInTheDocument();\n    expect(screen.getByText(\"Test Description\")).toBeInTheDocument();\n    expect(screen.getByText(\"urgent\")).toBeInTheDocument();\n  });\n\n  test(\"calls onDelete when delete button is clicked\", async () =\u003e {\n    const user = userEvent.setup();\n    render(\u003cItemCard {...mockProps} /\u003e);\n\n    const deleteButton = screen.getByRole(\"button\", {\n      name: \"Delete task: 1\",\n    });\n    await user.click(deleteButton);\n\n    expect(mockProps.onDelete).toHaveBeenCalledWith(\"1\");\n  });\n});\n```\n\n## Context API\n\nYou can use current to setup to setup integration tests for App.tsx but in my case I will show you how to test components that use the context.\n\n- create `src/AppWithContext.tsx` file\n\n```tsx\nconst AppWithContext = () =\u003e {\n  return \u003cdiv\u003eAppWithContext\u003c/div\u003e;\n};\nexport default AppWithContext;\n```\n\n- create `src/FlowContext.tsx` file\n\n```tsx\nimport { createContext, useContext, ReactNode, useState } from \"react\";\nimport { Item, ItemWithoutId } from \"./utils\";\n\ninterface FlowContextType {\n  items: Item[];\n  handleAddItem: (newItem: ItemWithoutId) =\u003e void;\n  handleDeleteItem: (id: string) =\u003e void;\n}\n\nconst FlowContext = createContext\u003cFlowContextType | undefined\u003e(undefined);\n\nexport function FlowProvider({ children }: { children: ReactNode }) {\n  const [items, setItems] = useState\u003cItem[]\u003e([]);\n\n  const handleAddItem = (newItem: ItemWithoutId) =\u003e {\n    setItems([...items, { ...newItem, id: Date.now().toString() }]);\n  };\n\n  const handleDeleteItem = (id: string) =\u003e {\n    setItems((prev) =\u003e prev.filter((item) =\u003e item.id !== id));\n  };\n\n  return (\n    \u003cFlowContext.Provider value={{ items, handleAddItem, handleDeleteItem }}\u003e\n      {children}\n    \u003c/FlowContext.Provider\u003e\n  );\n}\n\nexport function useFlowContext() {\n  const context = useContext(FlowContext);\n  if (context === undefined) {\n    throw new Error(\"useFlow must be used within a FlowProvider\");\n  }\n  return context;\n}\n```\n\nsrc/main.tsx\n\n```tsx\nimport { StrictMode } from \"react\";\nimport { createRoot } from \"react-dom/client\";\n// import App from './App.tsx';\nimport AppWithContext from \"./AppWithContext.tsx\";\nimport \"./index.css\";\nimport { FlowProvider } from \"./FlowContext.tsx\";\n\ncreateRoot(document.getElementById(\"root\")!).render(\n  \u003cStrictMode\u003e\n    \u003cFlowProvider\u003e\n      \u003cAppWithContext /\u003e\n    \u003c/FlowProvider\u003e\n  \u003c/StrictMode\u003e\n);\n```\n\nsrc/AppWithContext.tsx\n\n```tsx\nimport Form from \"./components/Form\";\nimport List from \"./components/List\";\nimport { useFlowContext } from \"./FlowContext\";\nconst AppWithContext = () =\u003e {\n  const { items, handleAddItem, handleDeleteItem } = useFlowContext();\n  return (\n    \u003cmain className=\"container mx-auto p-4 max-w-6xl\"\u003e\n      \u003ch1 className=\"text-3xl font-bold mb-8\"\u003eFocus Flow\u003c/h1\u003e\n      \u003cForm onSubmit={handleAddItem} /\u003e\n      \u003cList items={items} onDelete={handleDeleteItem} /\u003e\n    \u003c/main\u003e\n  );\n};\nexport default AppWithContext;\n```\n\n## AppWithContext Tests\n\n- create `src/__tests__/AppWithContext.test.tsx` file\n\n```tsx\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent, { UserEvent } from \"@testing-library/user-event\";\nimport { describe, test, expect, beforeEach, vi } from \"vitest\";\nimport { FlowProvider } from \"../FlowContext\";\nimport AppWithContext from \"../AppWithContext\";\n\nconst getElements = () =\u003e ({\n  titleInput: screen.getByRole(\"textbox\", { name: /title/i }),\n  descriptionInput: screen.getByRole(\"textbox\", { name: /description/i }),\n  categorySelect: screen.getByRole(\"combobox\", { name: /category/i }),\n  submitButton: screen.getByRole(\"button\", { name: /add task/i }),\n});\n\nconst customRenderAppWithContext = () =\u003e {\n  return render(\n    \u003cFlowProvider\u003e\n      \u003cAppWithContext /\u003e\n    \u003c/FlowProvider\u003e\n  );\n};\n\nconst addTestItem = async (user: UserEvent) =\u003e {\n  const { titleInput, descriptionInput, categorySelect, submitButton } =\n    getElements();\n  await user.type(titleInput, \"Test Item\");\n  await user.type(descriptionInput, \"Test Content\");\n  await user.selectOptions(categorySelect, \"urgent\");\n  await user.click(submitButton);\n};\n\ndescribe(\"AppWithContext\", () =\u003e {\n  let user: UserEvent;\n\n  beforeEach(() =\u003e {\n    vi.clearAllMocks();\n    user = userEvent.setup();\n    customRenderAppWithContext();\n  });\n\n  test(\"renders heading and form elements\", () =\u003e {\n    expect(\n      screen.getByRole(\"heading\", { level: 1, name: \"Focus Flow\" })\n    ).toBeInTheDocument();\n    // Verify all form elements are present\n    const elements = getElements();\n    Object.values(elements).forEach((element) =\u003e {\n      expect(element).toBeInTheDocument();\n    });\n  });\n\n  test(\"handles adding an item\", async () =\u003e {\n    const cardsBefore = screen.queryAllByRole(\"article\");\n    expect(cardsBefore).toHaveLength(0);\n\n    await addTestItem(user);\n\n    // Use getByText instead of findByText since we already have findAllByRole above\n    const cardsAfter = await screen.findAllByRole(\"article\");\n    expect(cardsAfter).toHaveLength(1);\n    expect(screen.getByText(\"Test Item\")).toBeInTheDocument();\n    expect(screen.getByText(\"Test Content\")).toBeInTheDocument();\n    expect(screen.getByText(\"urgent\")).toBeInTheDocument();\n  });\n\n  test(\"handles deleting an item\", async () =\u003e {\n    await addTestItem(user);\n\n    const deleteButton = screen.getByRole(\"button\", { name: /delete/i });\n    expect(deleteButton).toBeInTheDocument(); // Verify delete button exists\n\n    await user.click(deleteButton);\n    expect(screen.queryByText(\"Test Item\")).not.toBeInTheDocument(); // Verify item content is gone\n    expect(screen.queryAllByRole(\"article\")).toHaveLength(0);\n  });\n});\n```\n\n## Conclusion\n\nThis project serves as a comprehensive guide to building a React application using Test-Driven Development (TDD). By following the steps outlined in this README, you can learn how to create a functional and testable application while gaining hands-on experience with React, TypeScript, and modern testing practices.\n\nFeel free to extend the application with new features, improve the UI, or add more tests as you continue to learn and grow your skills in React development.\n\n## Keywords\n\nreact, vite, tdd, test-driven development, testing, vitest, react testing library, tailwind css, typescript, productivity app, task management, context api\n\n## License\n\nThis project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farnobt78%2Freact-vite-test-driven-development-testing-tutorial","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Farnobt78%2Freact-vite-test-driven-development-testing-tutorial","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farnobt78%2Freact-vite-test-driven-development-testing-tutorial/lists"}