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
95 changes: 49 additions & 46 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ name: CI Pipeline
# Trigger this workflow on Push to specific branches OR on Pull Requests to main
on:
push:
branches: [ "dev" ]
branches: ["dev"]
pull_request:
branches: [ "main" ]
branches: ["main"]

jobs:
# --- JOB 1: Test the Frontend ---
Expand All @@ -16,31 +16,34 @@ jobs:
working-directory: ./client

steps:
- name: Check out code
uses: actions/checkout@v4
- name: Check out code
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
cache: 'npm'
cache-dependency-path: client/package-lock.json
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "24"
cache: "npm"
cache-dependency-path: client/package-lock.json

- name: Install Dependencies
run: npm ci
- name: Install Dependencies
run: npm ci

- name: Build (Checks for Type Errors)
run: npm run build
env:
# We add dummy vars because the build needs them,
# we don't need real secrets just to check syntax.
NEXT_PUBLIC_API_URL: "http://localhost:8000"
NEXT_PUBLIC_FIREBASE_API_KEY: "dummy"
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: "dummy"
NEXT_PUBLIC_FIREBASE_PROJECT_ID: "dummy"
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: "dummy"
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: "dummy"
NEXT_PUBLIC_FIREBASE_APP_ID: "dummy"
- name: Run Client Tests
run: npm test

- name: Build (Checks for Type Errors)
run: npm run build
env:
# We add dummy vars because the build needs them,
# we don't need real secrets just to check syntax.
NEXT_PUBLIC_API_URL: "http://localhost:8000"
NEXT_PUBLIC_FIREBASE_API_KEY: "dummy"
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: "dummy"
NEXT_PUBLIC_FIREBASE_PROJECT_ID: "dummy"
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: "dummy"
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: "dummy"
NEXT_PUBLIC_FIREBASE_APP_ID: "dummy"

# --- JOB 2: Test the Backend ---
backend-test:
Expand All @@ -50,28 +53,28 @@ jobs:
working-directory: ./server

steps:
- name: Check out code
uses: actions/checkout@v4
- name: Check out code
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.13'
cache: 'pip'
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.13"
cache: "pip"

- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install httpx pytest # Ensure test tools are installed
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install httpx pytest # Ensure test tools are installed

- name: Run Tests
# We skip tests that require real Gemini credentials for now
# using the '-m "not integration"' marker if we had one,
# or just basic unit tests.
run: |
pytest
env:
# Dummy keys so the app creates the 'client' object without crashing
GEMINI_API_KEY: "dummy_key"
GOOGLE_APPLICATION_CREDENTIALS: "dummy_creds.json"
- name: Run Tests
# We skip tests that require real Gemini credentials for now
# using the '-m "not integration"' marker if we had one,
# or just basic unit tests.
run: |
pytest
env:
# Dummy keys so the app creates the 'client' object without crashing
GEMINI_API_KEY: "dummy_key"
GOOGLE_APPLICATION_CREDENTIALS: "dummy_creds.json"
50 changes: 50 additions & 0 deletions client/__test__/Navbar.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import '@testing-library/jest-dom'
import { render, screen } from '@testing-library/react'
import Navbar from '@/components/Navbar' // Use the alias '@' if possible
import { AuthProvider } from '@/context/AuthContext'

// 1. MOCK: Intercept the import
// Note: The path here must match what Navbar.tsx uses internally!
jest.mock("@/context/AuthContext", () => ({
useAuth: jest.fn(),
}));

// 2. IMPORT: Get the mocked function so we can control it
import { useAuth } from '@/context/AuthContext'

describe('Navbar Component', () => {

it('renders "Sign In" when user is NOT logged in', () => {
// ARRANGE: Tell the mock to return null (Logged Out)
(useAuth as jest.Mock).mockReturnValue({
user: null,
signIn: jest.fn(),
logOut: jest.fn(),
});

// ACT
render(<Navbar />);

// ASSERT
// Using regex /.../i to match "Sign In with Google" case-insensitively
const button = screen.getByRole('button', { name: /Sign In with Google/i });
expect(button).toBeInTheDocument();
});

it('renders "Sign Out" when user IS logged in', () => {
// ARRANGE: Tell the mock to return a user object
(useAuth as jest.Mock).mockReturnValue({
user: { displayName: "Test User", email: "test@example.com" },
signIn: jest.fn(),
logOut: jest.fn(),
});

// ACT
render(<Navbar />);

// ASSERT
const button = screen.getByRole('button', { name: /Sign Out/i });
expect(button).toBeInTheDocument();
expect(screen.getByText("Test User")).toBeInTheDocument();
});
});
19 changes: 19 additions & 0 deletions client/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Config } from "jest";
import nextJest from "next/jest.js";

const createJestConfig = nextJest({
dir: "./",
});

const config: Config = {
coverageProvider: "v8",
testEnvironment: "jsdom", // Simulates a browser environment
setupFilesAfterEnv: ["<rootDir>/jest.setup.ts"],
// This helps Jest understand the @/ alias
moduleNameMapper: {
"^@/components/(.*)$": "<rootDir>/components/$1",
"^@/context/(.*)$": "<rootDir>/context/$1",
},
};

export default createJestConfig(config);
1 change: 1 addition & 0 deletions client/jest.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import "@testing-library/jest-dom";
Loading