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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .github/workflows/pr-validation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,8 @@ jobs:
- name: Build application
run: npm run build

# Future addition when tests are implemented
# - name: Run tests
# run: npm test
- name: Run tests
run: npm test

- name: Scan for vulnerabilities
run: npm audit --production
Expand Down
83 changes: 83 additions & 0 deletions __tests__/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Algorithm Visualizer Tests

This directory contains tests for the Algorithm Visualizer application. The tests are organized to mirror the structure of the source code, making it easy to locate tests for specific components or functionality.

## Directory Structure

```
__tests__/
├── app/ # Tests for page components
│ └── sorting/
│ └── [algorithm]/
│ └── page.test.tsx # Tests for algorithm page
├── components/ # Tests for React components
│ ├── AlgorithmCard.test.tsx
│ └── visualizer/
│ ├── SortingVisualization.test.tsx
│ └── VisualizerControls.test.tsx
├── context/ # Tests for React context
│ └── AlgorithmContext.test.tsx
├── lib/ # Tests for utility functions and algorithms
│ ├── algorithms/
│ │ └── bubbleSort.test.ts
│ └── utils.test.ts
└── utils/ # Test utilities
└── test-utils.tsx # Common test utilities and helpers
```

## Running Tests

You can run the tests using the following npm scripts:

```bash
# Run all tests
npm test

# Run tests in watch mode (useful during development)
npm run test:watch

# Run tests with coverage report
npm run test:coverage
```

## Testing Approach

1. **Unit Tests**: Test individual functions and components in isolation.
2. **Integration Tests**: Test interactions between multiple components.
3. **Mock Dependencies**: External dependencies are mocked to ensure tests are reliable and fast.

## Test Utilities

The `test-utils.tsx` file provides:

- A custom render function that includes the AlgorithmProvider
- Mock implementations for key dependencies
- Helper functions for test setup

## Coverage Reports

After running `npm run test:coverage`, a coverage report will be generated in the `coverage/` directory. This report shows how much of the code is covered by tests and helps identify areas that need more testing.

## Writing New Tests

When adding new features or components, please follow these guidelines for writing tests:

1. Create test files that mirror the structure of the source code.
2. Test both happy paths and edge cases.
3. Mock external dependencies.
4. Keep tests focused on specific behavior.
5. Use descriptive test names that explain what is being tested.

Example:

```typescript
describe('ComponentName', () => {
it('should render correctly with default props', () => {
// Test code here
});

it('should handle edge case X properly', () => {
// Test code here
});
});
```
144 changes: 144 additions & 0 deletions __tests__/app/sorting/[algorithm]/page.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import AlgorithmPage from "@/app/sorting/[algorithm]/page";
import { AlgorithmProvider } from "@/context/AlgorithmContext";

// Mock the next/navigation module
jest.mock("next/navigation", () => ({
useParams: jest.fn(() => ({ algorithm: "bubbleSort" })),
notFound: jest.fn(),
}));

// Mock the algorithms module
jest.mock("@/lib/algorithms", () => {
const mockGetAlgorithmByName = jest.fn().mockReturnValue(() => ({
steps: [
{ array: [5, 3, 8], comparing: [], swapped: false, completed: [] },
{ array: [3, 5, 8], comparing: [], swapped: false, completed: [2] },
],
name: "Bubble Sort",
key: "bubbleSort",
category: "sorting",
description: "A simple sorting algorithm",
timeComplexity: "O(n²)",
spaceComplexity: "O(1)",
reference: "https://en.wikipedia.org/wiki/Bubble_sort",
pseudoCode: ["procedure bubbleSort(A: list of sortable items)"],
}));

return {
getAlgorithmByName: mockGetAlgorithmByName,
availableAlgorithms: [
{
name: "Bubble Sort",
key: "bubbleSort",
category: "sorting",
description: "A simple sorting algorithm",
difficulty: "easy",
},
],
};
});

// Mock the PageLayout component
jest.mock("@/components/layout/PageLayout", () => {
return ({ children, title, subtitle }: any) => (
<div data-testid="page-layout">
<h1 data-testid="page-title">{title}</h1>
<p data-testid="page-subtitle">{subtitle}</p>
<div data-testid="page-content">{children}</div>
</div>
);
});

// Mock the AlgorithmVisualizer component
jest.mock("@/components/visualizer/AlgorithmVisualizer", () => {
return () => (
<div data-testid="algorithm-visualizer">Algorithm Visualizer Mock</div>
);
});

describe("AlgorithmPage", () => {
beforeEach(() => {
jest.clearAllMocks();
});

it("should render the algorithm page with correct title and description", () => {
render(
<AlgorithmProvider>
<AlgorithmPage />
</AlgorithmProvider>
);

expect(screen.getByTestId("page-title")).toHaveTextContent("Bubble Sort");
expect(screen.getByTestId("page-subtitle")).toHaveTextContent(
"A simple sorting algorithm"
);
});

it("should render the AlgorithmVisualizer component", () => {
render(
<AlgorithmProvider>
<AlgorithmPage />
</AlgorithmProvider>
);

expect(screen.getByTestId("algorithm-visualizer")).toBeInTheDocument();
});

it("should render algorithm not found message for invalid algorithm", () => {
// Mock the useParams to return an invalid algorithm
require("next/navigation").useParams.mockReturnValue({
algorithm: "invalidAlgorithm",
});

// Mock the getAlgorithmByName to return null for invalid algorithm
require("@/lib/algorithms").getAlgorithmByName.mockReturnValue(null);

// Mock the availableAlgorithms to not include the invalid algorithm
require("@/lib/algorithms").availableAlgorithms = [];

render(
<AlgorithmProvider>
<AlgorithmPage />
</AlgorithmProvider>
);

// Use queryAllByText to handle multiple matches and check the first occurrence
const notFoundElements = screen.queryAllByText("Algorithm Not Found");
expect(notFoundElements.length).toBeGreaterThan(0);
expect(notFoundElements[0]).toBeInTheDocument();
});

it("should call getAlgorithmByName with the correct algorithm key", () => {
// Reset the useParams mock to return bubbleSort
require("next/navigation").useParams.mockReturnValue({
algorithm: "bubbleSort",
});

// Reset algorithm mock data
require("@/lib/algorithms").availableAlgorithms = [
{
name: "Bubble Sort",
key: "bubbleSort",
category: "sorting",
description: "A simple sorting algorithm",
difficulty: "easy",
},
];

// Get the mock function and clear it
const mockedGetAlgorithmByName =
require("@/lib/algorithms").getAlgorithmByName;
mockedGetAlgorithmByName.mockClear();

render(
<AlgorithmProvider>
<AlgorithmPage />
</AlgorithmProvider>
);

// Check that getAlgorithmByName was called with the right algorithm key
expect(mockedGetAlgorithmByName).toHaveBeenCalledWith("bubbleSort");
});
});
58 changes: 58 additions & 0 deletions __tests__/components/AlgorithmCard.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// __tests__/components/AlgorithmCard.test.tsx
import React from "react";
import { render, screen } from "@testing-library/react";
import AlgorithmCard from "@/components/AlgorithmCard";
import { AlgorithmInfo } from "@/lib/types";

// Mock the Next.js Link component
jest.mock("next/link", () => {
return ({ children, href }: { children: React.ReactNode; href: string }) => {
return <a href={href}>{children}</a>;
};
});

describe("AlgorithmCard", () => {
const mockAlgorithm: AlgorithmInfo = {
name: "Bubble Sort",
key: "bubbleSort",
category: "sorting",
description:
"A simple sorting algorithm that repeatedly steps through the list.",
difficulty: "easy",
};

it("should render the algorithm name", () => {
render(<AlgorithmCard algorithm={mockAlgorithm} />);
expect(screen.getByText("Bubble Sort")).toBeInTheDocument();
});

it("should render the algorithm description", () => {
render(<AlgorithmCard algorithm={mockAlgorithm} />);
expect(
screen.getByText(
"A simple sorting algorithm that repeatedly steps through the list."
)
).toBeInTheDocument();
});

it("should contain a link to the algorithm visualization page", () => {
render(<AlgorithmCard algorithm={mockAlgorithm} />);
const visualizeLink = screen.getByText("Visualize");
expect(visualizeLink.closest("a")).toHaveAttribute(
"href",
"/sorting/bubbleSort"
);
});

it("should contain a link to the difficulty page", () => {
render(<AlgorithmCard algorithm={mockAlgorithm} />);
const difficultyLink = screen.getByText("easy");
expect(difficultyLink.closest("a")).toHaveAttribute("href", "/easy");
});

it("should contain a link to the category page", () => {
render(<AlgorithmCard algorithm={mockAlgorithm} />);
const categoryLink = screen.getByText("sorting");
expect(categoryLink.closest("a")).toHaveAttribute("href", "/sorting");
});
});
Loading