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
14 changes: 4 additions & 10 deletions backend/models/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,12 @@ const UserSchema = new mongoose.Schema({
},
});

UserSchema.pre('save', async function (next) {

UserSchema.pre('save', async function () {
if (!this.isModified('password'))
return next();
return;

try {
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
next();
} catch (err) {
return next(err);
}
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
});

// Compare passwords during login
Expand Down
3 changes: 2 additions & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"scripts": {
"dev": "nodemon server.js",
"start": "node server.js",
"test": "echo \"Error: no test specified\" && exit 1"
"test": "jasmine spec/**/*.spec.cjs"

},
"keywords": [],
"author": "",
Expand Down
14 changes: 11 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
"dev": "vite --host",
"build": "vite build",
"lint": "eslint .",
"test": "vitest",
"test:backend": "jasmine spec/**/*.spec.cjs",
"preview": "vite preview",
"docker:dev": "docker compose --profile dev up --build",
"docker:prod": "docker compose --profile prod up -d --build"
Expand All @@ -32,6 +34,9 @@
},
"devDependencies": {
"@eslint/js": "^9.13.0",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
"@types/jasmine": "^5.1.8",
"@types/node": "^22.10.1",
"@types/react": "^18.3.23",
Expand All @@ -47,10 +52,13 @@
"eslint-plugin-react-refresh": "^0.4.14",
"express-session": "^1.18.2",
"globals": "^15.11.0",
"jasmine": "^5.9.0",
"jasmine": "^5.13.0",
"jasmine-spec-reporter": "^7.0.0",
"jsdom": "^29.1.1",
"passport": "^0.7.0",
"passport-local": "^1.0.0",
"supertest": "^7.1.4",
"vite": "^5.4.10"
"supertest": "^7.2.2",
"vite": "^5.4.10",
"vitest": "^4.1.6"
}
}
5 changes: 1 addition & 4 deletions spec/auth.routes.spec.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,7 @@ describe('Auth Routes', () => {
let app;

beforeAll(async () => {
await mongoose.connect('mongodb://127.0.0.1:27017/github_tracker_test', {
useNewUrlParser: true,
useUnifiedTopology: true,
});
await mongoose.connect('mongodb://127.0.0.1:27017/github_tracker_test');
app = createTestApp();
});

Expand Down
5 changes: 1 addition & 4 deletions spec/user.model.spec.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ const User = require('../backend/models/User');

describe('User Model', () => {
beforeAll(async () => {
await mongoose.connect('mongodb://127.0.0.1:27017/github_tracker_test', {
useNewUrlParser: true,
useUnifiedTopology: true,
});
await mongoose.connect('mongodb://127.0.0.1:27017/github_tracker_test');
});

afterAll(async () => {
Expand Down
91 changes: 91 additions & 0 deletions src/components/__test__/Navbar.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// src/components/__tests__/Navbar.test.tsx
import { render, screen, fireEvent } from '@testing-library/react'
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { MemoryRouter } from 'react-router-dom'
import { ThemeContext } from "../../context/ThemeContext";
import Navbar from '../Navbar.tsx'

// Helper to render Navbar with a mock ThemeContext
const renderNavbar = (mode: 'light' | 'dark' = 'light') => {
const toggleTheme = vi.fn()
render(
<MemoryRouter>
<ThemeContext.Provider value={{ mode, toggleTheme }}>
<Navbar />
</ThemeContext.Provider>
</MemoryRouter>
)
return { toggleTheme }
}

describe('Navbar', () => {
// --- Rendering ---
it('renders the GitHub Tracker logo link', () => {
renderNavbar()
expect(screen.getByText('GitHub Tracker')).toBeInTheDocument()
})

it('renders all desktop nav links', () => {
renderNavbar()
expect(screen.getByRole('link', { name: /home/i })).toBeInTheDocument()
expect(screen.getByRole('link', { name: /^tracker$/i })).toBeInTheDocument()
expect(screen.getByRole('link', { name: /contributors/i })).toBeInTheDocument()
expect(screen.getByRole('link', { name: /login/i })).toBeInTheDocument()
})

// --- Theme toggle ---
it('shows Moon icon in light mode', () => {
renderNavbar('light')
// Lucide renders an <svg> — check the button exists and toggleTheme is wired
const themeBtn = screen.getAllByRole('button')[0]
expect(themeBtn).toBeInTheDocument()
})

it('calls toggleTheme when the theme button is clicked', () => {
const { toggleTheme } = renderNavbar('light')
const themeBtn = screen.getAllByRole('button')[0]
fireEvent.click(themeBtn)
expect(toggleTheme).toHaveBeenCalledTimes(1)
})

// --- Mobile menu ---
it('mobile menu is hidden by default', () => {
renderNavbar()
expect(screen.queryByText('About')).not.toBeInTheDocument()
})

it('opens mobile menu when hamburger is clicked', () => {
renderNavbar()
const hamburger = screen.getAllByRole('button')[1] // second button = hamburger
fireEvent.click(hamburger)
expect(screen.getByText('About')).toBeInTheDocument()
expect(screen.getByText('Contact')).toBeInTheDocument()
})

it('closes mobile menu when a nav link is clicked', () => {
renderNavbar()
const hamburger = screen.getAllByRole('button')[1]
fireEvent.click(hamburger) // open
const homeLinks = screen.getAllByRole('link', { name: /home/i })
fireEvent.click(homeLinks[homeLinks.length - 1]) // click the mobile one
expect(screen.queryByText('About')).not.toBeInTheDocument() // closed
})

it('calls toggleTheme from the mobile menu button', () => {
const { toggleTheme } = renderNavbar('dark')
const hamburger = screen.getAllByRole('button')[1]
fireEvent.click(hamburger)
fireEvent.click(screen.getByText(/light/i))
expect(toggleTheme).toHaveBeenCalledTimes(1)
})

// --- Returns null when ThemeContext is missing ---
it('renders nothing if ThemeContext is not provided', () => {
const { container } = render(
<MemoryRouter>
<Navbar />
</MemoryRouter>
)
expect(container.firstChild).toBeNull()
})
})
1 change: 1 addition & 0 deletions src/setupTests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import '@testing-library/jest-dom'
8 changes: 6 additions & 2 deletions vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
})
test: {
globals: true,
environment: 'jsdom',
setupFiles: './src/setupTests.ts',
},
})
Loading