A task management application built using Test-Driven Development (TDD) principles with React Testing Library and Vitest. This project demonstrates how to build a complete application by writing tests first, then implementing the functionality to make those tests pass.
- Project Overview
- Technology Stack
- Project Structure
- Features
- Prerequisites
- Installation
- Running the Project
- Testing Setup
- Running Tests
- TDD Workflow
- Project Components
- Understanding Test Files
- Key Concepts
- Code Snippets
- Reusing Components
- Environment Variables
- Configuration Files
- Keywords
- Conclusion
Focus Flow is a task management application that demonstrates Test-Driven Development (TDD) in practice. The project includes:
- Task Management System - Add, view, and delete tasks with categories
- Two Implementation Approaches - Custom hook pattern and React Context pattern
- Comprehensive Test Coverage - Unit tests, integration tests, and component tests
- TDD Methodology - Tests written before implementation
- Modern React Patterns - Hooks, Context API, and component composition
The application allows users to:
- Create tasks with title, description, and priority category
- View tasks in a responsive grid layout
- Delete tasks
- Organize tasks by priority (Urgent, Important, Normal, Low Priority)
- React 18.3.1 - Modern React library for building user interfaces
- TypeScript 5.5.3 - Type-safe JavaScript with enhanced developer experience
- Vite 5.4.8 - Next-generation frontend build tool for fast development
- Vitest 2.1.3 - Fast unit test framework powered by Vite
- React Testing Library 16.0.1 - Simple and complete React DOM testing utilities
- @testing-library/jest-dom 6.5.0 - Custom Jest matchers for DOM elements
- @testing-library/user-event 14.5.2 - Simulate user interactions in tests
- jsdom 25.0.1 - JavaScript implementation of the DOM for testing
- lucide-react 0.461.0 - Beautiful icon library for React
- Tailwind CSS 3.4.14 - Utility-first CSS framework
- ESLint 9.11.1 - Code linting and quality assurance
- PostCSS 8.4.47 - CSS transformation tool
03-Focus-Flow-Test-Driven-Development-Testing-Tutorial/
├── public/ # Static assets
│ └── vite.svg # Vite logo
├── src/
│ ├── __tests__/ # Test files
│ │ ├── AppWithContext.test.tsx # Integration tests with Context
│ │ ├── Form.test.tsx # Form component tests
│ │ ├── ItemCard.test.tsx # ItemCard component tests
│ │ └── List.test.tsx # List component tests
│ ├── components/ # React components
│ │ ├── App.tsx # App component (with custom hook)
│ │ ├── Form.tsx # Task form component
│ │ ├── ItemCard.tsx # Individual task card component
│ │ └── List.tsx # Task list component
│ ├── App.tsx # Main app (custom hook version)
│ ├── AppWithContext.tsx # Main app (Context version)
│ ├── FlowContext.tsx # React Context for state management
│ ├── utils.ts # Utility functions and types
│ ├── main.tsx # Application entry point
│ ├── index.css # Global styles (Tailwind)
│ ├── vitest.setup.ts # Vitest configuration and setup
│ └── vite-env.d.ts # Vite type definitions
├── index.html # HTML entry point
├── package.json # Project dependencies and scripts
├── vite.config.ts # Vite and Vitest configuration
├── tsconfig.json # TypeScript configuration
├── tsconfig.app.json # TypeScript app-specific config
├── tsconfig.node.json # TypeScript Node.js config
├── eslint.config.js # ESLint configuration
├── tailwind.config.js # Tailwind CSS configuration
├── postcss.config.js # PostCSS configuration
└── README.md # This file- Create tasks with title, description, and category
- View tasks in a responsive grid layout
- Delete tasks with confirmation
- Category-based color coding (Urgent, Important, Normal, Low Priority)
- Custom Hook Pattern (
App.tsxwithuseFlowManager) - Context API Pattern (
AppWithContext.tsxwithFlowContext)
- Unit tests for individual components
- Integration tests for component interactions
- Context testing patterns
- Mocking strategies
- User interaction testing
- Tests written before implementation
- Red-Green-Refactor cycle
- Test-driven component development
- Responsive design with Tailwind CSS
- Icon integration with lucide-react
- Clean, modern interface
- Accessible components
Before you begin, ensure you have the following installed on your system:
- Node.js (version 18.x or higher recommended)
- npm (comes with Node.js) or yarn or pnpm
- A code editor (VS Code recommended)
- Basic knowledge of React, TypeScript, and testing concepts
- Understanding of TDD principles
node --version # Should show v18.x or higher
npm --version # Should show 9.x or higher-
Clone or navigate to the project directory:
cd 03-Focus-Flow-Test-Driven-Development-Testing-Tutorial -
Install dependencies:
npm install
This will install all required dependencies including:
- React and React DOM
- Vitest and testing libraries
- TypeScript and type definitions
- Vite and build tools
- Tailwind CSS and PostCSS
- lucide-react for icons
-
Verify installation:
npm list --depth=0
Start the development server with hot module replacement:
npm run devThe application will be available at http://localhost:5173 (or the next available port).
Create an optimized production build:
npm run buildThe built files will be in the dist/ directory.
Preview the production build locally:
npm run previewCheck code quality with ESLint:
npm run lintThe testing environment is configured in two main files:
This file configures Vitest with React support:
import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
test: {
globals: true, // Enable global test APIs
environment: "jsdom", // Use jsdom for DOM APIs
setupFiles: "./src/vitest.setup.ts", // Global test setup
},
});Key Configuration Points:
globals: true- Makesit,describe,expectavailable globally without importsenvironment: "jsdom"- Provides a DOM environment for testing React componentssetupFiles- Points to the setup file that configures testing utilities
This file sets up testing utilities and global configurations:
import { expect, afterEach } from "vitest";
import { cleanup } from "@testing-library/react";
import "@testing-library/jest-dom";
import * as matchers from "@testing-library/jest-dom/matchers";
// Extend Vitest's expect with Jest-DOM matchers
expect.extend(matchers);
// Cleanup DOM after each test
afterEach(() => {
cleanup();
});What This Does:
- Extends expect with Jest-DOM matchers - Adds matchers like
toBeInTheDocument(),toHaveValue(), etc. - Automatic cleanup - Removes rendered components from the DOM after each test
TypeScript configuration includes testing types:
{
"compilerOptions": {
"types": ["vitest/globals", "@testing-library/jest-dom"]
}
}This ensures TypeScript recognizes:
- Global Vitest functions (
it,describe,expect) - Jest-DOM matcher types for better autocomplete
npm testThis starts Vitest in watch mode, automatically re-running tests when files change.
npm test -- --runnpm test -- --uiOpens an interactive UI to view and run tests.
npm test -- Form.test.tsxnpm test -- --grep "Form"npm test -- --coverage(Note: You may need to install @vitest/coverage-v8 for coverage support)
When in watch mode, you can use these keyboard shortcuts:
a- Run all testsf- Run only failed testsq- Quit watch modep- Filter by filename patternt- Filter by test name pattern
Test-Driven Development (TDD) is a software development approach where you:
- Write a failing test (Red)
- Write the minimum code to make it pass (Green)
- Refactor the code (Refactor)
- Repeat
// Form.test.tsx
test("submits form with entered values", async () => {
const mockOnSubmit = vi.fn();
render(<Form onSubmit={mockOnSubmit} />);
// This test will fail because Form doesn't exist yet
const titleInput = screen.getByRole("textbox", { name: /title/i });
await user.type(titleInput, "New Task");
// ... more test code
});// Form.tsx - Minimal implementation to make test pass
const Form = ({ onSubmit }: { onSubmit: (item: ItemWithoutId) => void }) => {
const [title, setTitle] = useState("");
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onSubmit({ title, description: "", category: "normal" });
};
return (
<form onSubmit={handleSubmit}>
<input value={title} onChange={(e) => setTitle(e.target.value)} />
<button type="submit">Add Task</button>
</form>
);
};// Form.tsx - Refactored with better structure
const Form = ({ onSubmit }: { onSubmit: (item: ItemWithoutId) => void }) => {
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [category, setCategory] = useState<ItemCategory | "">("");
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!title || !description || !category) return;
onSubmit({ title, description, category });
// Reset form
setTitle("");
setDescription("");
setCategory("");
};
return (
<form onSubmit={handleSubmit} className="space-y-4">
{/* Improved form structure */}
</form>
);
};- Better Design - Forces you to think about the API first
- Confidence - Tests provide safety net for refactoring
- Documentation - Tests serve as living documentation
- Fewer Bugs - Catch issues early in development
- Faster Development - Less time debugging, more time building
- Write one test at a time
- Make tests fail for the right reason
- Write the simplest code that makes the test pass
- Refactor frequently
- Keep tests fast and independent
- Test behavior, not implementation
The task creation form component:
const Form = ({ onSubmit }: { onSubmit: (item: ItemWithoutId) => void }) => {
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [category, setCategory] = useState<ItemCategory | "">("");
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!title || !description || !category) return;
onSubmit({ title, description, category });
// Reset form after submission
setTitle("");
setDescription("");
setCategory("");
};
return <form onSubmit={handleSubmit}>{/* Form inputs */}</form>;
};Features:
- Title input field
- Description input field
- Category select dropdown (Urgent, Important, Normal, Low Priority)
- Form validation
- Form reset after submission
Props:
onSubmit: (item: ItemWithoutId) => void- Callback function when form is submitted
The task list display component:
export default function List({
items,
onDelete,
}: {
items: Item[];
onDelete: (id: string) => void;
}) {
return (
<section className="mt-8">
<h2 className="text-xl font-semibold mb-2">Flow Board</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{items.map((item) => (
<ItemCard key={item.id} {...item} onDelete={onDelete} />
))}
</div>
</section>
);
}Features:
- Responsive grid layout
- Renders ItemCard components for each task
- Empty state handling (no items)
Props:
items: Item[]- Array of task itemsonDelete: (id: string) => void- Callback function when item is deleted
Individual task card component:
const ItemCard = ({
id,
title,
description,
category,
onDelete,
}: ItemCardProps) => {
return (
<article className="w-full rounded-lg border shadow-sm">
<div className="flex flex-row items-center justify-between p-6 pb-2">
<h3 className="text-lg font-semibold">{title}</h3>
<button onClick={() => onDelete(id)} aria-label={`Delete task: ${id}`}>
<Trash2 className="h-4 w-4" />
</button>
</div>
<div className="p-6 pt-2">
<p className="text-sm text-muted-foreground mb-2">{description}</p>
<div className={`inline-block ${categoryColors[category]} ...`}>
{category}
</div>
</div>
</article>
);
};Features:
- Displays task title, description, and category
- Delete button with trash icon
- Category-based color coding
- Accessible button with aria-label
Category Colors:
urgent- Red backgroundimportant- Yellow backgroundnormal- Blue backgroundlow- Green background
Props:
id: string- Unique task identifiertitle: string- Task titledescription: string- Task descriptioncategory: string- Task categoryonDelete: (id: string) => void- Callback function when delete is clicked
React Context for state management:
interface FlowContextType {
items: Item[];
handleAddItem: (newItem: ItemWithoutId) => void;
handleDeleteItem: (id: string) => void;
}
const FlowContext = createContext<FlowContextType | undefined>(undefined);
export function FlowProvider({ children }: { children: ReactNode }) {
const [items, setItems] = useState<Item[]>([]);
const handleAddItem = (newItem: ItemWithoutId) => {
setItems([...items, { ...newItem, id: Date.now().toString() }]);
};
const handleDeleteItem = (id: string) => {
setItems((prev) => prev.filter((item) => item.id !== id));
};
return (
<FlowContext.Provider value={{ items, handleAddItem, handleDeleteItem }}>
{children}
</FlowContext.Provider>
);
}
export function useFlowContext() {
const context = useContext(FlowContext);
if (context === undefined) {
throw new Error("useFlow must be used within a FlowProvider");
}
return context;
}Features:
- Centralized state management
- Add item functionality
- Delete item functionality
- Custom hook for accessing context
- Error handling for context usage outside provider
Utility functions and type definitions:
export type ItemCategory = "urgent" | "important" | "normal" | "low";
export type Item = {
id: string;
title: string;
description: string;
category: ItemCategory;
};
export type ItemWithoutId = Omit<Item, "id">;
export const useFlowManager = () => {
const [items, setItems] = useState<Item[]>([]);
const handleAddItem = (newItem: ItemWithoutId) => {
setItems([...items, { ...newItem, id: Date.now().toString() }]);
};
const handleDeleteItem = (id: string) => {
setItems(items.filter((item) => item.id !== id));
};
return { items, handleAddItem, handleDeleteItem };
};Types:
ItemCategory- Union type for task categoriesItem- Complete task object with idItemWithoutId- Task object without id (for creation)
Custom Hook:
useFlowManager- Custom hook for state management (alternative to Context)
Comprehensive tests for the Form component:
describe("Form Component", () => {
let user: UserEvent;
const mockOnSubmit = vi.fn();
beforeEach(() => {
mockOnSubmit.mockClear();
user = userEvent.setup();
});
test("renders form with empty fields initially", () => {
render(<Form onSubmit={mockOnSubmit} />);
// Verify all inputs are empty
});
test("submits form with entered values", async () => {
// Fill form and submit
// Verify onSubmit was called with correct data
});
test("validates required fields", async () => {
// Try to submit empty form
// Verify onSubmit was not called
});
test("clears form after successful submission", async () => {
// Fill and submit form
// Verify all fields are cleared
});
});Test Coverage:
- Initial render state
- Form submission with data
- Required field validation
- Form reset after submission
Tests for the List component with mocking:
// Mock ItemCard to simplify testing
vi.mock("../components/ItemCard", () => ({
default: () => <article>item card</article>,
}));
describe("List", () => {
const mockItems: Item[] = [
{
id: "1",
title: "Test Item 1",
description: "Content 1",
category: "urgent",
},
{
id: "2",
title: "Test Item 2",
description: "Content 2",
category: "normal",
},
];
test("renders the Flow Board heading", () => {
// Verify heading exists
});
test("renders correct number of ItemCards", () => {
// Verify correct number of items rendered
});
test("renders empty grid when no items provided", () => {
// Verify empty state
});
});Key Concepts:
- Mocking - Mocking child components to isolate testing
- Empty state testing - Testing behavior with no data
- Rendering verification - Checking correct number of items
Tests for individual task cards:
describe("ItemCard", () => {
const mockProps: MockProps = {
id: "1",
title: "Test Task",
description: "Test Description",
category: "urgent",
onDelete: vi.fn(),
};
test("renders card with correct content", () => {
// Verify all content is displayed
});
test("calls onDelete when delete button is clicked", async () => {
// Click delete button
// Verify onDelete was called with correct id
});
});Test Coverage:
- Content rendering
- Delete button functionality
- Callback invocation
Integration tests for the complete application:
const customRenderAppWithContext = () => {
return render(
<FlowProvider>
<AppWithContext />
</FlowProvider>
);
};
const addTestItem = async (user: UserEvent) => {
// Helper function to add an item
};
describe("AppWithContext", () => {
beforeEach(() => {
customRenderAppWithContext();
});
test("renders heading and form elements", () => {
// Verify initial render
});
test("handles adding an item", async () => {
// Add item via form
// Verify item appears in list
});
test("handles deleting an item", async () => {
// Add item
// Delete item
// Verify item is removed
});
});Key Concepts:
- Integration Testing - Testing multiple components together
- Context Testing - Testing with React Context
- Custom Render - Creating custom render function with providers
- Helper Functions - Reusable test utilities
Red-Green-Refactor Cycle:
- Red - Write a failing test
- Green - Write minimal code to pass
- Refactor - Improve code while keeping tests green
Benefits:
- Better code design
- Increased confidence
- Living documentation
- Fewer bugs
- Faster development
Why Mock?
- Isolate component under test
- Speed up tests
- Control dependencies
- Focus on specific behavior
Example - Mocking Child Component:
vi.mock("../components/ItemCard", () => ({
default: () => <article>item card</article>,
}));What is Integration Testing?
Testing multiple components working together to verify the complete user flow.
Example:
test("handles adding an item", async () => {
// Render app with context
render(
<FlowProvider>
<AppWithContext />
</FlowProvider>
);
// Fill form
await user.type(titleInput, "New Task");
await user.click(submitButton);
// Verify item appears in list
expect(screen.getByText("New Task")).toBeInTheDocument();
});Testing with React Context:
const customRender = (ui: React.ReactElement) => {
return render(<FlowProvider>{ui}</FlowProvider>);
};
test("uses context correctly", () => {
customRender(<Component />);
// Test component that uses context
});Creating Reusable Test Helpers:
const getElements = () => ({
titleInput: screen.getByRole("textbox", { name: /title/i }),
submitButton: screen.getByRole("button", { name: /add task/i }),
});
const addTestItem = async (user: UserEvent) => {
const { titleInput, submitButton } = getElements();
await user.type(titleInput, "Test Item");
await user.click(submitButton);
};import { render, screen } from "@testing-library/react";
import { test, expect } from "vitest";
import Component from "./Component";
test("renders component", () => {
render(<Component />);
expect(screen.getByText("Expected Text")).toBeInTheDocument();
});test("submits form with entered values", async () => {
const mockOnSubmit = vi.fn();
const user = userEvent.setup();
render(<Form onSubmit={mockOnSubmit} />);
const titleInput = screen.getByRole("textbox", { name: /title/i });
const submitButton = screen.getByRole("button", { name: /submit/i });
await user.type(titleInput, "New Task");
await user.click(submitButton);
expect(mockOnSubmit).toHaveBeenCalledWith({
title: "New Task",
description: "",
category: "normal",
});
});test("handles delete button click", async () => {
const mockOnDelete = vi.fn();
const user = userEvent.setup();
render(<ItemCard id="1" onDelete={mockOnDelete} />);
const deleteButton = screen.getByRole("button", { name: /delete/i });
await user.click(deleteButton);
expect(mockOnDelete).toHaveBeenCalledWith("1");
});test("adds item using context", async () => {
const user = userEvent.setup();
render(
<FlowProvider>
<AppWithContext />
</FlowProvider>
);
// Add item
await user.type(screen.getByRole("textbox", { name: /title/i }), "Task");
await user.click(screen.getByRole("button", { name: /add task/i }));
// Verify item appears
expect(screen.getByText("Task")).toBeInTheDocument();
});// Mock a component
vi.mock("./ChildComponent", () => ({
default: ({ children }: { children: React.ReactNode }) => (
<div>{children}</div>
),
}));
test("renders without child component complexity", () => {
render(<ParentComponent />);
// Test parent without worrying about child implementation
});test("renders empty state when no items", () => {
render(<List items={[]} onDelete={vi.fn()} />);
expect(screen.queryAllByRole("article")).toHaveLength(0);
});describe("Component", () => {
let user: UserEvent;
const mockHandler = vi.fn();
beforeEach(() => {
user = userEvent.setup();
mockHandler.mockClear();
render(<Component onAction={mockHandler} />);
});
test("first test", () => {
// Uses setup from beforeEach
});
test("second test", () => {
// Also uses setup from beforeEach
});
});-
Copy the component file:
cp src/components/Form.tsx /path/to/your/project/src/components/
-
Copy type definitions:
// Copy from utils.ts export type ItemCategory = "urgent" | "important" | "normal" | "low"; export type ItemWithoutId = { title: string; description: string; category: ItemCategory; };
-
Install dependencies if needed:
npm install react react-dom
-
Import and use:
import Form from "./components/Form"; function App() { const handleSubmit = (item: ItemWithoutId) => { console.log("New item:", item); }; return <Form onSubmit={handleSubmit} />; }
The ItemCard component can be reused with any item data:
import ItemCard from "./components/ItemCard";
function App() {
const items = [
{
id: "1",
title: "Task 1",
description: "Description",
category: "urgent",
},
];
const handleDelete = (id: string) => {
console.log("Delete:", id);
};
return (
<div>
{items.map((item) => (
<ItemCard key={item.id} {...item} onDelete={handleDelete} />
))}
</div>
);
}To reuse the Context pattern:
-
Copy FlowContext.tsx
-
Wrap your app:
import { FlowProvider } from "./FlowContext"; function App() { return ( <FlowProvider> <YourComponents /> </FlowProvider> ); }
-
Use the hook:
import { useFlowContext } from "./FlowContext"; function Component() { const { items, handleAddItem, handleDeleteItem } = useFlowContext(); // Use context values }
The getElements helper can be adapted:
// Generic form helper
export const getFormElements = (formName?: string) => {
const namePattern = formName ? new RegExp(formName, "i") : /form/i;
return {
titleInput: screen.getByRole("textbox", { name: /title/i }),
submitButton: screen.getByRole("button", { name: namePattern }),
};
};This project does not require environment variables. It's a client-side application without external API calls or configuration that needs environment-specific values.
If you extend this project to include:
- API endpoints for saving tasks
- Authentication tokens
- Feature flags
- Build-time configuration
- External service URLs
You would create a .env file:
-
Create
.envfile in project root:touch .env
-
Add environment variables:
VITE_API_URL=https://api.example.com VITE_API_KEY=your-api-key-here VITE_APP_NAME=Focus Flow
-
Access in code:
const apiUrl = import.meta.env.VITE_API_URL; const apiKey = import.meta.env.VITE_API_KEY;
-
Important Notes:
-
Vite requires
VITE_prefix for client-side variables -
Add
.envto.gitignoreto keep secrets safe -
Create
.env.exampleas a template:VITE_API_URL= VITE_API_KEY=
-
-
For Testing:
Create
.env.testfor test-specific values:VITE_API_URL=http://localhost:3000 VITE_API_KEY=test-key
- Never commit
.envfiles with real secrets - Use
.env.exampleto document required variables - Use different
.envfiles for different environments - Validate environment variables at application startup
- Use TypeScript types for environment variables:
interface ImportMetaEnv {
readonly VITE_API_URL: string;
readonly VITE_API_KEY: string;
}Configures the build tool and test environment:
import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: "jsdom",
setupFiles: "./src/vitest.setup.ts",
},
});{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"jsx": "react-jsx",
"strict": true,
"types": ["vitest/globals", "@testing-library/jest-dom"]
},
"include": ["src"]
}Provides code quality checks and React-specific linting rules.
Configures Tailwind CSS utility classes for styling.
- Test-Driven Development (TDD) - Development approach writing tests first
- Red-Green-Refactor - TDD cycle: write failing test, make it pass, improve code
- Unit Testing - Testing individual components in isolation
- Integration Testing - Testing component interactions
- Mocking - Simulating dependencies in tests
- Vitest - Modern test runner built on Vite
- React Testing Library - Testing utilities for React components
- Jest-DOM - Custom matchers for DOM element assertions
- jsdom - DOM implementation for Node.js testing
- User Events - Simulated user interactions
- Test Coverage - Percentage of code covered by tests
- Component - Reusable UI building blocks
- Props - Data passed to components
- State - Component internal data
- Context API - React's built-in state management solution
- Custom Hooks - Reusable stateful logic
- JSX - JavaScript syntax extension for React
- Hooks - Functions to use React features (useState, useContext)
- Provider - Context provider component
- Consumer - Component that consumes context
- TypeScript - Typed superset of JavaScript
- Vite - Fast build tool and dev server
- HMR - Hot Module Replacement for instant updates
- ESLint - Code linting and quality tool
- Tailwind CSS - Utility-first CSS framework
- lucide-react - Icon library for React
- Refactoring - Improving code without changing behavior
- Red Phase - Writing failing tests
- Green Phase - Making tests pass
- Refactor Phase - Improving code quality
- Test First - Writing tests before implementation
- Living Documentation - Tests that document behavior
- Regression Testing - Ensuring changes don't break existing functionality
This Focus Flow TDD project demonstrates how to build a complete React application using Test-Driven Development principles. By following this project, you've learned:
✅ How to apply TDD methodology (Red-Green-Refactor)
✅ How to write comprehensive component tests
✅ How to test React Context and custom hooks
✅ How to write integration tests
✅ How to mock components for isolated testing
✅ How to test user interactions and form submissions
✅ How to structure tests with helper functions
✅ Best practices for React Testing Library
✅ How to manage state with Context API and custom hooks
- Practice TDD - Try building a new feature using TDD
- Increase Coverage - Aim for higher test coverage
- Explore Advanced Testing - Learn about testing async operations, routing, and more
- Study Patterns - Research more testing patterns and best practices
- Build Projects - Apply TDD to your own projects
- React Testing Library Documentation
- Vitest Documentation
- TDD Best Practices
- React Context Documentation
- Test-Driven Development Guide
- Start Small - Begin with simple tests and gradually add complexity
- Test Behavior - Focus on what users see and do, not implementation
- Keep Tests Fast - Fast tests encourage running them frequently
- Refactor Regularly - Don't let code quality degrade
- Read Test Failures - They provide valuable feedback
- Practice Regularly - TDD is a skill that improves with practice
Feel free to use this project repository and extend this project further!
If you have any questions or want to share your work, reach out via GitHub or my portfolio at https://arnob-mahmud.vercel.app/.
Enjoy building and learning! 🚀
Thank you! 😊