A comprehensive React application for searching and visualizing GitHub user profiles using the GitHub GraphQL API. This project demonstrates advanced testing patterns including GraphQL API mocking with Mock Service Worker (MSW), Apollo Client integration, and data visualization with Recharts.
- Project Overview
- Technology Stack
- Project Structure
- Features
- Prerequisites
- Installation
- Environment Variables
- Running the Project
- Testing Setup
- Running Tests
- GraphQL API Overview
- Project Components
- Understanding Test Files
- Key Concepts
- Code Snippets
- Reusing Components
- Configuration Files
- Keywords
- Conclusion
This GitHub Users Search application is a full-featured React application that demonstrates:
- GraphQL Integration - Using Apollo Client to query GitHub's GraphQL API
- Advanced API Mocking - MSW for mocking GraphQL queries in tests
- Data Visualization - Interactive charts using Recharts library
- Modern UI Components - Radix UI (shadcn/ui) components
- Comprehensive Testing - Unit tests, integration tests, and GraphQL mocking
- TypeScript - Full type safety throughout the application
The application allows users to:
- Search for GitHub users by username
- View user profile information (avatar, bio, stats)
- See user statistics (repositories, followers, following, gists)
- Visualize repository data with interactive charts (popular repos, forked repos, used languages)
- React 18.3.1 - Modern React library for building user interfaces
- TypeScript 5.6.2 - Type-safe JavaScript with enhanced developer experience
- Vite 5.4.10 - Next-generation frontend build tool for fast development
- Apollo Client 3.11.10 - GraphQL client for React
- GraphQL 16.9.0 - Query language for APIs
- MSW 2.6.8 - Mock Service Worker for API mocking
- Radix UI - Unstyled, accessible UI components
@radix-ui/react-label- Label component@radix-ui/react-slot- Slot component@radix-ui/react-toast- Toast notification component
- Recharts 2.13.3 - Composable charting library built on React
- lucide-react 0.456.0 - Beautiful icon library
- Tailwind CSS 3.4.14 - Utility-first CSS framework
- tailwindcss-animate 1.0.7 - Animation utilities
- class-variance-authority 0.7.0 - Component variant management
- clsx 2.1.1 - Utility for constructing className strings
- tailwind-merge 2.5.4 - Merge Tailwind CSS classes
- Vitest 2.1.8 - Fast unit test framework powered by Vite
- React Testing Library 16.1.0 - Simple and complete React DOM testing utilities
- @testing-library/jest-dom 6.6.3 - 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
- ESLint 9.13.0 - Code linting and quality assurance
- PostCSS 8.4.49 - CSS transformation tool
- TypeScript ESLint 8.11.0 - TypeScript-specific linting rules
05-GitHub-Users-Search-Application-Testing-Tutorial/
├── public/ # Static assets
│ └── vite.svg # Vite logo
├── src/
│ ├── __tests__/ # Test files
│ │ ├── App.test.tsx # Integration tests
│ │ ├── SearchForm.test.tsx # Form component tests
│ │ ├── UserProfile.test.tsx # Profile component tests
│ │ ├── UserCard.test.tsx # User card tests
│ │ ├── StatsContainer.test.tsx # Stats container tests
│ │ ├── StatsCard.test.tsx # Stats card tests
│ │ ├── ForkedRepos.test.tsx # Chart component tests
│ │ └── utils.ts # Test utilities and mock data
│ ├── components/
│ │ ├── charts/ # Chart components
│ │ │ ├── ForkedRepos.tsx # Most forked repos chart
│ │ │ ├── PopularRepos.tsx # Most starred repos chart
│ │ │ └── UsedLanguages.tsx # Most used languages chart
│ │ ├── form/ # Form components
│ │ │ └── SearchForm.tsx # User search form
│ │ ├── ui/ # Reusable UI components (shadcn/ui)
│ │ │ ├── button.tsx # Button component
│ │ │ ├── card.tsx # Card component
│ │ │ ├── chart.tsx # Chart wrapper component
│ │ │ ├── input.tsx # Input component
│ │ │ ├── label.tsx # Label component
│ │ │ ├── skeleton.tsx # Loading skeleton
│ │ │ ├── toast.tsx # Toast component
│ │ │ └── toaster.tsx # Toast container
│ │ └── user/ # User-related components
│ │ ├── Loading.tsx # Loading skeleton
│ │ ├── StatsCard.tsx # Individual stat card
│ │ ├── StatsContainer.tsx # Stats cards container
│ │ ├── UserCard.tsx # User profile card
│ │ └── UserProfile.tsx # Main user profile component
│ ├── hooks/ # Custom React hooks
│ │ └── use-toast.ts # Toast notification hook
│ ├── lib/ # Utility libraries
│ │ └── utils.ts # Utility functions (cn helper)
│ ├── mocks/ # MSW mock handlers
│ │ ├── handlers.ts # GraphQL request handlers
│ │ └── server.ts # MSW server setup
│ ├── App.tsx # Main application component
│ ├── apolloClient.ts # Apollo Client configuration
│ ├── main.tsx # Application entry point
│ ├── queries.ts # GraphQL queries
│ ├── types.ts # TypeScript type definitions
│ ├── utils.ts # Utility functions for data processing
│ ├── index.css # Global styles
│ ├── vite-env.d.ts # Vite type definitions
│ └── vitest.setup.ts # Vitest configuration and MSW setup
├── .env.local # Environment variables (GitHub token)
├── components.json # shadcn/ui configuration
├── 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- Search for GitHub users by username
- Real-time profile information display
- Error handling for invalid users
- Loading states with skeleton screens
- User avatar and name
- User bio
- GitHub profile link
- Statistics cards (repositories, followers, following, gists)
- Popular Repos Chart - Bar chart showing most starred repositories
- Forked Repos Chart - Bar chart showing most forked repositories
- Used Languages Chart - Bar chart showing most used programming languages
- GraphQL query mocking with MSW
- Component unit tests
- Integration tests
- Error scenario testing
- Chart component mocking
- shadcn/ui components (Radix UI)
- Responsive design with Tailwind CSS
- Toast notifications
- Loading skeletons
- 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)
- GitHub Personal Access Token (for API access)
- Basic knowledge of React, TypeScript, GraphQL, and testing concepts
node --version # Should show v18.x or higher
npm --version # Should show 9.x or higher- Go to GitHub Settings → Developer settings → Personal access tokens → Tokens (classic)
- Click "Generate new token (classic)"
- Give it a name (e.g., "GitHub Search App")
- Select scopes:
read:userandpublic_repo - Click "Generate token"
- Copy the token (you won't see it again!)
-
Clone or navigate to the project directory:
cd 05-GitHub-Users-Search-Application-Testing-Tutorial -
Install dependencies:
npm install
This will install all required dependencies including:
- React and React DOM
- Apollo Client and GraphQL
- MSW for API mocking
- Recharts for data visualization
- Radix UI components
- Vitest and testing libraries
- TypeScript and type definitions
- Vite and build tools
- Tailwind CSS and PostCSS
-
Set up environment variables:
Create a
.env.localfile in the project root (see Environment Variables section):cp .env.local.example .env.local # Edit .env.local and add your GitHub token -
Verify installation:
npm list --depth=0
This project requires a GitHub Personal Access Token to access the GitHub GraphQL API.
-
Create
.env.localfile in project root:touch .env.local
-
Add your GitHub token:
VITE_GITHUB_TOKEN=your_github_token_here
Important Notes:
- Replace
your_github_token_herewith your actual GitHub Personal Access Token - Never commit
.env.localto version control (it's already in.gitignore) - The
VITE_prefix is required for Vite to expose the variable to client-side code
- Replace
-
Access in code:
The token is accessed in
src/apolloClient.ts:const httpLink = new HttpLink({ uri: GITHUB_GRAPHQL_API, headers: { Authorization: `Bearer ${import.meta.env.VITE_GITHUB_TOKEN}`, }, });
-
Never commit tokens - Always add
.env.localto.gitignore -
Use different tokens - Create separate tokens for development and production
-
Set token scopes properly - Only grant necessary permissions
-
Rotate tokens regularly - Update tokens periodically for security
-
Use
.env.example- Create an example file without actual tokens:VITE_GITHUB_TOKEN=your_github_token_here
MSW mocks handle GraphQL queries in tests, so you don't need a real token for testing. However, if you want to test against the real API, you can use a token in tests:
// In vitest.setup.ts or test files
process.env.VITE_GITHUB_TOKEN = "test-token";Start the development server with hot module replacement:
npm run devThe application will be available at http://localhost:5173 (or the next available port).
Note: Make sure you have set up your .env.local file with a valid GitHub token before running the app.
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 multiple files:
This file configures Vitest with React support and path aliases:
import path from "path";
import react from "@vitejs/plugin-react";
import { defineConfig } from "vitest/config";
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
},
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"), // Path alias for imports
},
},
});Key Configuration Points:
globals: true- Makesit,describe,expectavailable globallyenvironment: "jsdom"- Provides a DOM environment for testing React componentssetupFiles- Points to the setup file that configures MSWresolve.alias- Allows using@/instead of relative paths
This file sets up MSW for testing and testing utilities:
import { expect, afterEach, beforeAll, afterAll } from "vitest";
import { cleanup } from "@testing-library/react";
import * as matchers from "@testing-library/jest-dom/matchers";
import server from "./mocks/server";
// Extend Vitest's expect with Jest-DOM matchers
expect.extend(matchers);
// Cleanup DOM after each test
afterEach(() => {
cleanup();
server.resetHandlers(); // Reset MSW handlers for test isolation
});
// Start MSW server before all tests
beforeAll(() => {
server.listen();
});
// Close MSW server after all tests
afterAll(() => {
server.close();
});What This Does:
- Extends expect with Jest-DOM matchers - Adds matchers like
toBeInTheDocument(),toHaveValue(), etc. - Starts MSW server - Activates MSW to intercept GraphQL requests in tests
- Resets handlers - Ensures test isolation by resetting mock handlers between tests
- Automatic cleanup - Removes rendered components from the DOM after each test
MSW setup for Node.js (test environment):
import { setupServer } from "msw/node";
import { handlers } from "./handlers";
const server = setupServer(...handlers);
export default server;Purpose: Creates an MSW server instance for Node.js testing environment.
GraphQL request handlers for MSW:
import { graphql, HttpResponse } from "msw";
export const handlers = [
graphql.query("GetUser", ({ query, variables }) => {
const { login } = variables;
// Handle different scenarios based on username
if (login === "request-error") {
return HttpResponse.json({
errors: [{ message: "there was an error" }],
});
}
// Return mocked user data
return HttpResponse.json({
data: {
user: {
name: login,
avatarUrl: `https://github.com/images/${login}.jpg`,
// ... more user data
},
},
});
}),
];Purpose: Defines how MSW should respond to GraphQL queries in tests.
npm testThis starts Vitest in watch mode, automatically re-running tests when files change. MSW will intercept all GraphQL requests made during tests.
npm test -- --runnpm test -- --uiOpens an interactive UI to view and run tests.
npm test -- App.test.tsxnpm test -- --grep "SearchForm"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
This application uses GitHub's GraphQL API to fetch user data. The API endpoint is:
https://api.github.com/graphqlThe main query used is GetUser, defined in src/queries.ts:
query GetUser($login: String!) {
user(login: $login) {
name
avatarUrl
bio
url
repositories(first: 100) {
totalCount
nodes {
name
description
stargazerCount
forkCount
url
languages(first: 5) {
edges {
node {
name
}
size
}
}
}
}
followers {
totalCount
}
following {
totalCount
}
gists {
totalCount
}
}
}login: String!- The GitHub username to search for
The query returns a UserData object containing:
-
User Information:
name- User's display nameavatarUrl- User's avatar image URLbio- User's bio texturl- User's GitHub profile URL
-
Repository Data:
totalCount- Total number of repositoriesnodes- Array of repository objects with:name- Repository namedescription- Repository descriptionstargazerCount- Number of starsforkCount- Number of forksurl- Repository URLlanguages- Programming languages used
-
Statistics:
followers.totalCount- Number of followersfollowing.totalCount- Number of users followinggists.totalCount- Number of gists
All requests to the GitHub GraphQL API require authentication using a Personal Access Token. The token is sent in the Authorization header:
Authorization: Bearer YOUR_TOKEN_HEREThe main application component:
const App = () => {
const [userName, setUserName] = useState("quincylarson");
return (
<main className="mx-auto max-w-6xl px-8 py-20">
<SearchForm userName={userName} setUserName={setUserName} />
<UserProfile userName={userName} />
</main>
);
};Features:
- Manages search state
- Renders search form and user profile
- Default user is "quincylarson"
User search form component:
const SearchForm = ({ userName, setUserName }: SearchFormProps) => {
const { toast } = useToast();
const [text, setText] = useState(userName);
const handleSearch = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (text === "") {
toast({
description: "Please enter a valid username",
});
return;
}
setUserName(text);
};
return (
<form onSubmit={handleSearch}>
<Input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Search Github Users..."
/>
<Button type="submit">Search</Button>
</form>
);
};Features:
- Text input for username
- Form validation (empty check)
- Toast notification for errors
- Updates parent component state on submit
Props:
userName: string- Current usernamesetUserName: React.Dispatch<React.SetStateAction<string>>- Function to update username
Main user profile component that fetches and displays user data:
const UserProfile = ({ userName }: UserProfileProps) => {
const { data, loading, error } = useQuery<UserData>(GET_USER, {
variables: { login: userName },
});
if (loading) return <Loading />;
if (error) return <h2>{error.message}</h2>;
if (!data) return <h2>User Not Found.</h2>;
return (
<div>
<UserCard avatarUrl={data.user.avatarUrl} name={data.user.name} />
<StatsContainer
totalRepos={data.user.repositories.totalCount}
followers={data.user.followers.totalCount}
following={data.user.following.totalCount}
gists={data.user.gists.totalCount}
/>
{data.user.repositories.totalCount > 0 && (
<div className="grid md:grid-cols-2 gap-4">
<UsedLanguages repositories={data.user.repositories.nodes} />
<PopularRepos repositories={data.user.repositories.nodes} />
<ForkedRepos repositories={data.user.repositories.nodes} />
</div>
)}
</div>
);
};Features:
- Uses Apollo Client's
useQueryhook - Handles loading, error, and success states
- Renders user card, stats, and charts
- Conditionally renders charts only if user has repositories
Props:
userName: string- GitHub username to fetch data for
User profile card displaying basic information:
const UserCard = ({ avatarUrl, name, bio, url }: UserCardProps) => {
return (
<Card>
<CardHeader className="flex-row gap-x-8 items-center">
<img src={avatarUrl} alt={name} className="w-36 h-36 rounded" />
<div>
<CardTitle>{name || "Coding Addict"}</CardTitle>
<CardDescription>
{bio || "Passionate about coding and technology"}
</CardDescription>
<Button asChild>
<a href={url} target="_blank" rel="noreferrer">
Follow
</a>
</Button>
</div>
</CardHeader>
</Card>
);
};Features:
- Displays user avatar, name, and bio
- Fallback values when data is missing
- Link to GitHub profile
- Responsive layout
Props:
avatarUrl: string- User's avatar URLname: string- User's display namebio: string- User's bio texturl: string- User's GitHub profile URL
Container for displaying user statistics:
const StatsContainer = (props: StatsContainerProps) => {
const { totalRepos, followers, following, gists } = props;
return (
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-2">
<StatsCard title="Total Repositories" count={totalRepos} />
<StatsCard title="Followers" count={followers} />
<StatsCard title="Following" count={following} />
<StatsCard title="Gists" count={gists} />
</div>
);
};Features:
- Responsive grid layout
- Four statistics cards
- Renders StatsCard components
Props:
totalRepos: number- Total repository countfollowers: number- Follower countfollowing: number- Following countgists: number- Gist count
Individual statistics card:
const StatsCard = ({ title, count }: StatsCardProps) => {
return (
<Card>
<div className="flex flex-row justify-between items-center p-6">
<CardTitle>{title}</CardTitle>
<CardDescription>{count}</CardDescription>
</div>
</Card>
);
};Features:
- Displays title and count
- Reusable component
- Consistent styling
Props:
title: string- Statistic titlecount: number- Statistic count
Bar chart showing most starred repositories:
const PopularRepos = ({ repositories }: { repositories: Repository[] }) => {
const popularRepos = calculateMostStarredRepos(repositories);
return (
<div>
<h2>Popular Repos</h2>
<ChartContainer config={chartConfig}>
<BarChart data={popularRepos}>
<Bar dataKey="stars" />
</BarChart>
</ChartContainer>
</div>
);
};Bar chart showing most forked repositories:
const ForkedRepos = ({ repositories }: { repositories: Repository[] }) => {
const mostForkedRepos = calculateMostForkedRepos(repositories);
// Similar structure to PopularRepos
};Bar chart showing most used programming languages:
const UsedLanguages = ({ repositories }: { repositories: Repository[] }) => {
const popularLanguages = calculatePopularLanguages(repositories);
// Similar structure to PopularRepos
};Chart Features:
- Interactive tooltips
- Responsive design
- Accessible components
- Color-coded data visualization
Apollo Client setup with error handling:
const errorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
graphQLErrors.forEach(({ message, locations, path }) => {
console.error(`[GraphQL error]: Message: ${message}`);
});
}
if (networkError) {
console.error(`[Network Error]: ${networkError}`);
}
});
const httpLink = new HttpLink({
uri: GITHUB_GRAPHQL_API,
headers: {
Authorization: `Bearer ${import.meta.env.VITE_GITHUB_TOKEN}`,
},
});
const client = new ApolloClient({
link: ApolloLink.from([errorLink, httpLink]),
cache: new InMemoryCache(),
});Features:
- Error link for logging GraphQL and network errors
- HTTP link with GitHub API endpoint
- Authorization header with GitHub token
- In-memory cache for query results
Data processing functions for charts:
// Calculate top 5 most forked repositories
export const calculateMostForkedRepos = (
repositories: Repository[]
): { repo: string; count: number }[] => {
return repositories
.map((repo) => ({ repo: repo.name, count: repo.forkCount }))
.sort((a, b) => b.count - a.count)
.slice(0, 5);
};
// Calculate top 5 most starred repositories
export const calculateMostStarredRepos = (
repositories: Repository[]
): { repo: string; stars: number }[] => {
return repositories
.map((repo) => ({ repo: repo.name, stars: repo.stargazerCount }))
.sort((a, b) => b.stars - a.stars)
.slice(0, 5);
};
// Calculate top 5 most used languages
export const calculatePopularLanguages = (
repositories: Repository[]
): { language: string; count: number }[] => {
const languageMap: { [key: string]: number } = {};
// Count language occurrences across all repositories
// Return top 5 languages
};Integration tests for the complete application:
describe("App Integration", () => {
test("should update profile when searching for a user", async () => {
const user = userEvent.setup();
renderApp();
expect(await screen.findByText("quincylarson")).toBeInTheDocument();
const searchInput = screen.getByRole("textbox");
await user.clear(searchInput);
await user.type(searchInput, "john_doe");
const submitButton = screen.getByRole("button", { name: /search/i });
await user.click(submitButton);
expect(await screen.findByText("john_doe")).toBeInTheDocument();
});
});Key Concepts:
- Apollo Provider - Wraps app with ApolloProvider for GraphQL
- Chart Mocking - Mocks chart components to simplify testing
- Async Testing - Uses
findBy*queries for async operations - User Interactions - Simulates form submission and user typing
Unit tests for the search form:
describe("SearchForm", () => {
test("shows toast when submitting empty input", async () => {
const { button } = getFormElements();
await user.click(button);
expect(mockToast).toHaveBeenCalledWith({
description: "Please enter a valid username",
});
});
test("calls setUserName on valid form submission", async () => {
const { input, button } = getFormElements();
await user.type(input, "jane_doe");
await user.click(button);
expect(setUserNameMock).toHaveBeenCalledWith("jane_doe");
});
});Key Concepts:
- Hook Mocking - Mocks
useToasthook - Form Validation - Tests empty input validation
- Callback Testing - Verifies
setUserNameis called correctly
Integration tests for the user profile component:
describe("UserProfile", () => {
test("renders UserProfile component", async () => {
await renderUserProfile("john_doe");
expect(await screen.findByText("john_doe")).toBeInTheDocument();
});
test("renders error message when user not found", async () => {
await renderUserProfile("invalid-username");
expect(
await screen.findByText(/could not resolve to a user/i)
).toBeInTheDocument();
});
});Key Concepts:
- GraphQL Mocking - MSW intercepts GraphQL queries
- Error Scenarios - Tests error handling for invalid users
- Async Operations - Tests GraphQL query results
GraphQL request handlers for MSW:
export const handlers = [
graphql.query("GetUser", ({ query, variables }) => {
const { login } = variables;
// Error scenarios
if (login === "request-error") {
return HttpResponse.json({
errors: [{ message: "there was an error" }],
});
}
if (login === "invalid-username") {
return HttpResponse.json({
data: { user: null },
errors: [
{
message: `Could not resolve to a User with the login of ${login}.`,
},
],
});
}
// Success response
return HttpResponse.json({
data: {
user: {
name: login,
avatarUrl: `https://github.com/images/${login}.jpg`,
// ... mock user data
},
},
});
}),
];Key Concepts:
- GraphQL Query Mocking - Mocks specific GraphQL queries
- Variable-based Responses - Different responses based on query variables
- Error Simulation - Simulates GraphQL errors for testing
Using useQuery Hook:
const { data, loading, error } = useQuery<DataType>(QUERY, {
variables: { variableName: value },
});Features:
- Loading State -
loadingindicates if query is in progress - Error Handling -
errorcontains error information - Data -
datacontains query results - Type Safety - TypeScript generics for type safety
GraphQL Query Handler:
graphql.query("QueryName", ({ query, variables }) => {
// Access query and variables
const { variableName } = variables;
// Return mock response
return HttpResponse.json({
data: {
/* mock data */
},
});
});Error Simulation:
graphql.query("QueryName", ({ variables }) => {
return HttpResponse.json({
errors: [{ message: "Error message" }],
});
});Mocking Chart Components:
vi.mock("@/components/charts/UsedLanguages", () => ({
default: () => <div>Used Languages</div>,
}));Purpose: Simplifies testing by replacing complex chart components with simple divs.
Waiting for Async Results:
test("renders user data", async () => {
render(<Component />);
// Use findBy* queries for async content
expect(await screen.findByText("User Name")).toBeInTheDocument();
});import { ApolloClient, InMemoryCache, HttpLink } from "@apollo/client";
const client = new ApolloClient({
link: new HttpLink({
uri: "https://api.github.com/graphql",
headers: {
Authorization: `Bearer ${import.meta.env.VITE_GITHUB_TOKEN}`,
},
}),
cache: new InMemoryCache(),
});import { useQuery } from "@apollo/client";
import { GET_USER } from "./queries";
const { data, loading, error } = useQuery<UserData>(GET_USER, {
variables: { login: userName },
});import { graphql, HttpResponse } from "msw";
graphql.query("GetUser", ({ variables }) => {
const { login } = variables;
return HttpResponse.json({
data: {
user: {
name: login,
// ... more data
},
},
});
});import { ApolloProvider } from "@apollo/client";
import { render } from "@testing-library/react";
const renderApp = () => {
render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>
);
};vi.mock("@/components/charts/ChartComponent", () => ({
default: () => <div>Mock Chart</div>,
}));-
Copy component files:
cp src/components/form/SearchForm.tsx /path/to/your/project/src/components/ cp src/hooks/use-toast.ts /path/to/your/project/src/hooks/
-
Copy UI components:
cp -r src/components/ui /path/to/your/project/src/components/
-
Install dependencies:
npm install @radix-ui/react-label @radix-ui/react-slot
-
Import and use:
import SearchForm from "./components/form/SearchForm"; function App() { const [userName, setUserName] = useState(""); return <SearchForm userName={userName} setUserName={setUserName} />; }
Copy apolloClient.ts to your project and modify the endpoint and headers as needed:
const httpLink = new HttpLink({
uri: "https://your-api.com/graphql",
headers: {
Authorization: `Bearer ${import.meta.env.VITE_API_TOKEN}`,
},
});-
Copy mocks directory:
cp -r src/mocks /path/to/your/project/src/
-
Update handlers:
Modify
handlers.tswith your GraphQL queries. -
Update vitest.setup.ts:
Import your server setup.
Configures build tool, test environment, and path aliases:
import path from "path";
import react from "@vitejs/plugin-react";
import { defineConfig } from "vitest/config";
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: "jsdom",
setupFiles: "./src/vitest.setup.ts",
},
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
});Includes path aliases and type definitions:
{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"jsx": "react-jsx",
"strict": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
"types": ["vitest/globals", "@testing-library/jest-dom"]
}
}Configuration for shadcn/ui components:
{
"style": "new-york",
"tailwind": {
"config": "tailwind.config.js",
"css": "src/index.css",
"baseColor": "zinc"
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui"
}
}Provides code quality checks and React-specific linting rules.
Configures Tailwind CSS with shadcn/ui theme and animations.
- GraphQL - Query language for APIs
- Apollo Client - GraphQL client for React
- Query - GraphQL operation for fetching data
- Variables - Parameters passed to GraphQL queries
- Schema - GraphQL API structure definition
- Resolver - Function that resolves query fields
- useQuery - React hook for executing queries
- ApolloProvider - Context provider for Apollo Client
- InMemoryCache - Apollo Client's default cache
- HttpLink - Link for HTTP requests
- ApolloLink - Chainable link for request processing
- onError - Error handling link
- Mock Service Worker - API mocking library
- graphql.query - MSW handler for GraphQL queries
- setupServer - MSW setup for Node.js/test environment
- HttpResponse - MSW utility for creating responses
- Handler - Function that defines mock responses
- Integration Testing - Testing component interactions
- Unit Testing - Testing individual components
- GraphQL Mocking - Simulating GraphQL API responses
- Component Mocking - Replacing components in tests
- Async Testing - Testing asynchronous operations
- Error Scenario Testing - Testing error handling
- Component - Reusable UI building blocks
- Hooks - Functions to use React features
- Context - React's context API for state sharing
- Props - Data passed to components
- State - Component internal data
- shadcn/ui - Collection of UI components
- Radix UI - Unstyled, accessible UI primitives
- Recharts - Charting library for React
- Tailwind CSS - Utility-first CSS framework
- Toast Notification - Temporary notification messages
This GitHub Users Search application demonstrates how to build a modern React application with GraphQL, comprehensive testing, and data visualization. By following this project, you've learned:
✅ How to integrate Apollo Client with React
✅ How to query GitHub's GraphQL API
✅ How to mock GraphQL queries with MSW
✅ How to test GraphQL-based components
✅ How to create data visualizations with Recharts
✅ How to use shadcn/ui components
✅ How to handle async operations in tests
✅ How to test error scenarios
✅ Best practices for GraphQL applications
✅ How to structure a complex React application
- Explore More GraphQL - Learn about mutations, subscriptions, and fragments
- Add More Features - Implement repository details, search history, etc.
- Enhance Charts - Add more chart types and interactivity
- Improve Testing - Add more edge case tests and E2E tests
- Deploy - Deploy the application to Vercel or Netlify
- Apollo Client Documentation
- GraphQL Documentation
- GitHub GraphQL API
- MSW Documentation
- Recharts Documentation
- shadcn/ui Documentation
- React Testing Library Documentation
- Vitest Documentation
- Keep Token Secure - Never commit GitHub tokens to version control
- Handle Errors Gracefully - Always handle GraphQL errors in your UI
- Use Loading States - Provide feedback during API calls
- Test Error Scenarios - Don't just test happy paths
- Mock External Dependencies - Mock charts and complex components in tests
- Use TypeScript - Leverage type safety for GraphQL queries
- Practice Regularly - GraphQL and testing improve 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! 😊