This commit is contained in:
DaKheera47 2026-01-20 23:07:55 +00:00
parent 1106e95ad6
commit 3d55e78614
4 changed files with 200 additions and 0 deletions

View File

@ -0,0 +1,105 @@
import React from "react";
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import { JobHeader } from "./JobHeader";
import { useSettings } from "../hooks/useSettings";
import * as api from "../api";
import type { Job } from "../../shared/types";
// Mock useSettings
vi.mock("../hooks/useSettings", () => ({
useSettings: vi.fn(),
}));
// Mock api
vi.mock("../api", () => ({
checkSponsor: vi.fn(),
}));
// Mock Tooltip components to simplify testing
vi.mock("@/components/ui/tooltip", () => ({
TooltipProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>,
Tooltip: ({ children }: { children: React.ReactNode }) => <>{children}</>,
TooltipTrigger: ({ children }: { children: React.ReactNode }) => <>{children}</>,
TooltipContent: ({ children }: { children: React.ReactNode }) => (
<div data-testid="tooltip-content">{children}</div>
),
}));
const mockJob: Job = {
id: "job-1",
title: "Software Engineer",
employer: "Tech Corp",
location: "London",
salary: "£60,000",
deadline: "2025-12-31",
status: "discovered",
source: "linkedin",
suitabilityScore: 85,
suitabilityReason: "Strong match",
sponsorMatchScore: null,
sponsorMatchNames: null,
// Other fields...
} as Job;
describe("JobHeader", () => {
beforeEach(() => {
vi.clearAllMocks();
(useSettings as any).mockReturnValue({
showSponsorInfo: true,
});
});
it("renders basic job information", () => {
render(<JobHeader job={mockJob} />);
expect(screen.getByText("Software Engineer")).toBeInTheDocument();
expect(screen.getByText("Tech Corp")).toBeInTheDocument();
expect(screen.getByText("London")).toBeInTheDocument();
expect(screen.getByText("£60,000")).toBeInTheDocument();
});
it("shows 'Check Sponsorship Status' button when sponsorMatchScore is null", async () => {
const onCheckSponsor = vi.fn().mockResolvedValue(undefined);
render(<JobHeader job={mockJob} onCheckSponsor={onCheckSponsor} />);
const button = screen.getByText("Check Sponsorship Status");
expect(button).toBeInTheDocument();
fireEvent.click(button);
expect(onCheckSponsor).toHaveBeenCalled();
});
it("shows 'Confirmed Sponsor' when score >= 95", () => {
const jobWithSponsor = { ...mockJob, sponsorMatchScore: 98, sponsorMatchNames: '["Tech Corp Ltd"]' };
render(<JobHeader job={jobWithSponsor} />);
expect(screen.getByText("Confirmed Sponsor")).toBeInTheDocument();
});
it("shows 'Potential Sponsor' when score is between 80 and 94", () => {
const jobWithPotential = { ...mockJob, sponsorMatchScore: 85, sponsorMatchNames: '["Techy Corp"]' };
render(<JobHeader job={jobWithPotential} />);
expect(screen.getByText("Potential Sponsor")).toBeInTheDocument();
});
it("shows 'Sponsor Not Found' when score < 80", () => {
const jobNoSponsor = { ...mockJob, sponsorMatchScore: 40, sponsorMatchNames: '["Other Corp"]' };
render(<JobHeader job={jobNoSponsor} />);
expect(screen.getByText("Sponsor Not Found")).toBeInTheDocument();
});
it("hides sponsor info when showSponsorInfo is false", () => {
(useSettings as any).mockReturnValue({
showSponsorInfo: false,
});
const jobWithSponsor = { ...mockJob, sponsorMatchScore: 98 };
render(<JobHeader job={jobWithSponsor} />);
expect(screen.queryByText("Confirmed Sponsor")).not.toBeInTheDocument();
expect(screen.queryByText("Check Sponsorship Status")).not.toBeInTheDocument();
});
});

View File

@ -0,0 +1,66 @@
import { renderHook, waitFor } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { useSettings, _resetSettingsCache } from './useSettings';
import * as api from '../api';
vi.mock('../api', () => ({
getSettings: vi.fn(),
}));
describe('useSettings', () => {
beforeEach(() => {
vi.clearAllMocks();
_resetSettingsCache();
});
it('fetches settings on mount if not already cached', async () => {
const mockSettings = { showSponsorInfo: false };
(api.getSettings as any).mockResolvedValue(mockSettings);
const { result } = renderHook(() => useSettings());
// Should start in loading state
expect(result.current.settings).toBeNull();
await waitFor(() => {
expect(result.current.settings).toEqual(mockSettings);
});
expect(result.current.showSponsorInfo).toBe(false);
expect(api.getSettings).toHaveBeenCalledTimes(1);
});
it('uses default values when settings are null', async () => {
(api.getSettings as any).mockResolvedValue(null);
const { result } = renderHook(() => useSettings());
await waitFor(() => {
// settings is null, so showSponsorInfo should default to true
expect(result.current.showSponsorInfo).toBe(true);
});
});
it('provides a refresh function that updates settings', async () => {
const initialSettings = { showSponsorInfo: true };
const updatedSettings = { showSponsorInfo: false };
(api.getSettings as any).mockResolvedValueOnce(initialSettings);
(api.getSettings as any).mockResolvedValueOnce(updatedSettings);
const { result } = renderHook(() => useSettings());
await waitFor(() => {
expect(result.current.settings).toEqual(initialSettings);
});
let refreshed;
await waitFor(async () => {
refreshed = await result.current.refreshSettings();
});
expect(refreshed).toEqual(updatedSettings);
expect(result.current.settings).toEqual(updatedSettings);
expect(result.current.showSponsorInfo).toBe(false);
});
});

View File

@ -56,3 +56,10 @@ export function useSettings() {
refreshSettings,
};
}
/** @internal For testing only */
export function _resetSettingsCache() {
settingsCache = null;
isFetching = false;
subscribers.clear();
}

View File

@ -95,4 +95,26 @@ describe.sequential('Jobs API routes', () => {
})
);
});
it('checks visa sponsor status for a job', async () => {
const { searchSponsors } = await import('../../services/visa-sponsors/index.js');
vi.mocked(searchSponsors).mockReturnValue([
{ sponsor: { organisationName: 'ACME CORP SPONSOR' } as any, score: 100, matchedName: 'acme corp sponsor' }
]);
const { createJob } = await import('../../repositories/jobs.js');
const job = await createJob({
source: 'manual',
title: 'Sponsored Dev',
employer: 'Acme',
jobUrl: 'https://example.com/job/4',
});
const res = await fetch(`${baseUrl}/api/jobs/${job.id}/check-sponsor`, { method: 'POST' });
const body = await res.json();
expect(body.success).toBe(true);
expect(body.data.sponsorMatchScore).toBe(100);
expect(body.data.sponsorMatchNames).toContain('ACME CORP SPONSOR');
});
});