Skip to content

Commit

Permalink
Merge branch 'develop' into feature/resolutons_fix
Browse files Browse the repository at this point in the history
  • Loading branch information
TebogoYungMercykay authored Sep 30, 2024
2 parents 07164fe + 6881f12 commit 21c7be7
Show file tree
Hide file tree
Showing 11 changed files with 484 additions and 24 deletions.
154 changes: 154 additions & 0 deletions frontend/__tests__/components/ChangePasswordForm.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import React from "react";
import { describe, expect } from "@jest/globals";
import { render, fireEvent, waitFor, screen } from "@testing-library/react";
import ChangePasswordForm from "@/components/ChangePasswordForm/ChangePasswordForm";
import { useUser } from "@/lib/contexts/UserContext";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

jest.mock("@supabase/supabase-js", () => ({
createClient: jest.fn().mockReturnValue({
auth: {
signIn: jest.fn().mockResolvedValue({
user: { id: "user-id" },
session: "session-token",
error: null,
}),
},
from: jest.fn(() => ({
select: jest.fn().mockResolvedValue({ data: [], error: null }),
insert: jest.fn().mockResolvedValue({ data: [], error: null }),
})),
}),
}));

jest.mock("@/lib/contexts/UserContext", () => ({
useUser: jest.fn(),
}));

jest.mock("@/lib/api/updateProfile", () => ({
changePassword: jest.fn(),
}));

const renderWithClient = (ui: React.ReactNode) => {
const testQueryClient = new QueryClient({
defaultOptions: {
queries: {
retry: false,
},
},
});
return render(
<QueryClientProvider client={testQueryClient}>{ui}</QueryClientProvider>
);
};

describe("ChangePasswordForm", () => {
beforeEach(() => {
jest.clearAllMocks();
jest.spyOn(console, "error").mockImplementation(() => {});
(useUser as jest.Mock).mockReturnValue({
user: {
user_id: "user123",
email_address: "user@example.com",
username: "user123",
fullname: "User Fullname",
image_url: "http://example.com/image.jpg",
bio: "User biography",
is_owner: true,
total_issues: 10,
resolved_issues: 5,
access_token: "access_token_value",
},
});
});

afterEach(() => {
(console.error as jest.Mock).mockRestore();
});

it("renders the form correctly", () => {
renderWithClient(<ChangePasswordForm />);

expect(screen.getByRole("heading", { name: "Change Password" })).toBeInTheDocument();
expect(screen.getByLabelText("Current Password")).toBeInTheDocument();
expect(screen.getByLabelText("New Password")).toBeInTheDocument();
expect(screen.getByLabelText("Confirm New Password")).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Change Password" })).toBeInTheDocument();
});

it("displays an error when passwords don't match", async () => {
renderWithClient(<ChangePasswordForm />);

fireEvent.change(screen.getByLabelText("Current Password"), { target: { value: "oldpass" } });
fireEvent.change(screen.getByLabelText("New Password"), { target: { value: "newpass" } });
fireEvent.change(screen.getByLabelText("Confirm New Password"), { target: { value: "differentpass" } });

fireEvent.click(screen.getByRole("button", { name: "Change Password" }));

await waitFor(() => {
expect(screen.getByText("New passwords do not match.")).toBeInTheDocument();
});
});

it("displays an error when new password is too short", async () => {
renderWithClient(<ChangePasswordForm />);

fireEvent.change(screen.getByLabelText("Current Password"), { target: { value: "oldpass" } });
fireEvent.change(screen.getByLabelText("New Password"), { target: { value: "short" } });
fireEvent.change(screen.getByLabelText("Confirm New Password"), { target: { value: "short" } });

fireEvent.click(screen.getByRole("button", { name: "Change Password" }));

await waitFor(() => {
expect(screen.getByText("Password must be at least 8 characters long.")).toBeInTheDocument();
});
});

it("calls changePassword function when form is submitted correctly", async () => {
const { changePassword } = require("@/lib/api/updateProfile");
changePassword.mockResolvedValue({});
renderWithClient(<ChangePasswordForm />);

fireEvent.change(screen.getByLabelText("Current Password"), { target: { value: "oldpass" } });
fireEvent.change(screen.getByLabelText("New Password"), { target: { value: "newpassword" } });
fireEvent.change(screen.getByLabelText("Confirm New Password"), { target: { value: "newpassword" } });

fireEvent.click(screen.getByRole("button", { name: "Change Password" }));

await waitFor(() => {
expect(changePassword).toHaveBeenCalledWith({ currentPassword: "oldpass", newPassword: "newpassword" });
});
});

it("displays success message when password is changed successfully", async () => {
const { changePassword } = require("@/lib/api/updateProfile");
changePassword.mockResolvedValue({});
renderWithClient(<ChangePasswordForm />);

fireEvent.change(screen.getByLabelText("Current Password"), { target: { value: "oldpass" } });
fireEvent.change(screen.getByLabelText("New Password"), { target: { value: "newpassword" } });
fireEvent.change(screen.getByLabelText("Confirm New Password"), { target: { value: "newpassword" } });

fireEvent.click(screen.getByRole("button", { name: "Change Password" }));

await waitFor(() => {
expect(screen.getByText("Password changed successfully! You will need to log in again.")).toBeInTheDocument();
});
});

it("displays error message when changePassword function fails", async () => {
const { changePassword } = require("@/lib/api/updateProfile");
changePassword.mockRejectedValue({ message: "Current password is incorrect" });
renderWithClient(<ChangePasswordForm />);

fireEvent.change(screen.getByLabelText("Current Password"), { target: { value: "wrongpass" } });
fireEvent.change(screen.getByLabelText("New Password"), { target: { value: "newpassword" } });
fireEvent.change(screen.getByLabelText("Confirm New Password"), { target: { value: "newpassword" } });

fireEvent.click(screen.getByRole("button", { name: "Change Password" }));

await waitFor(() => {
expect(screen.getByText("Current password is incorrect")).toBeInTheDocument();
});
});
});
3 changes: 3 additions & 0 deletions frontend/__tests__/components/Feed.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ jest.mock("next/navigation", () => ({
useSearchParams: jest.fn().mockReturnValue({
get: jest.fn().mockReturnValue(null),
}),
useRouter: jest.fn().mockReturnValue({
back: jest.fn().mockReturnValue(null),
}),
}));

jest.mock("@/lib/contexts/UserContext", () => ({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import DeleteConfirmation from '@/components/DeleteConfirmation/DeleteConfirmation';

describe('DeleteConfirmation', () => {
const mockOnConfirm = jest.fn();
const mockOnCancel = jest.fn();

beforeEach(() => {
jest.clearAllMocks();
});

it('renders correctly', () => {
render(
<DeleteConfirmation
organizationName="Test Org"
onConfirm={mockOnConfirm}
onCancel={mockOnCancel}
/>
);

expect(screen.getByText('Delete Organization')).toBeInTheDocument();
expect(screen.getByText('Are you sure you want to delete Test Org? This action cannot be undone.')).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Delete' })).toBeInTheDocument();
});

it('calls onCancel when Cancel button is clicked', () => {
render(
<DeleteConfirmation
organizationName="Test Org"
onConfirm={mockOnConfirm}
onCancel={mockOnCancel}
/>
);

fireEvent.click(screen.getByRole('button', { name: 'Cancel' }));
expect(mockOnCancel).toHaveBeenCalledTimes(1);
expect(mockOnConfirm).not.toHaveBeenCalled();
});

it('calls onConfirm when Delete button is clicked', () => {
render(
<DeleteConfirmation
organizationName="Test Org"
onConfirm={mockOnConfirm}
onCancel={mockOnCancel}
/>
);

fireEvent.click(screen.getByRole('button', { name: 'Delete' }));
expect(mockOnConfirm).toHaveBeenCalledTimes(1);
expect(mockOnCancel).not.toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import TypedDeleteConfirmation from '@/components/DeleteConfirmation/TypedDeleteConfirmation';

// Mock the UI components
jest.mock("@/components/ui/input", () => ({
Input: (props: any) => <input {...props} />,
}));

jest.mock("@/components/ui/button", () => ({
Button: (props: any) => <button {...props}>{props.children}</button>,
}));

jest.mock("@/components/ui/alert-dialog", () => ({
AlertDialog: ({ children, open }: any) => open ? <div>{children}</div> : null,
AlertDialogContent: ({ children }: any) => <div>{children}</div>,
AlertDialogHeader: ({ children }: any) => <div>{children}</div>,
AlertDialogTitle: ({ children }: any) => <h2>{children}</h2>,
AlertDialogDescription: ({ children }: any) => <p>{children}</p>,
AlertDialogFooter: ({ children }: any) => <div>{children}</div>,
}));

describe('TypedDeleteConfirmation', () => {
const mockOnConfirm = jest.fn();
const mockOnClose = jest.fn();

const defaultProps = {
isOpen: true,
onClose: mockOnClose,
onConfirm: mockOnConfirm,
itemName: 'Test Item',
itemType: 'Organization',
};

beforeEach(() => {
jest.clearAllMocks();
});

it('renders correctly when open', () => {
render(<TypedDeleteConfirmation {...defaultProps} />);

expect(screen.getByText('Delete Organization')).toBeInTheDocument();
expect(screen.getByText(/This action cannot be undone/)).toBeInTheDocument();
expect(screen.getByPlaceholderText('delete Test Item')).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Delete' })).toBeInTheDocument();
});

it('does not render when closed', () => {
render(<TypedDeleteConfirmation {...defaultProps} isOpen={false} />);

expect(screen.queryByText('Delete Organization')).not.toBeInTheDocument();
});

it('disables Delete button when input is incorrect', async () => {
render(<TypedDeleteConfirmation {...defaultProps} />);

const deleteButton = screen.getByRole('button', { name: 'Delete' });
expect(deleteButton).toBeDisabled();

await userEvent.type(screen.getByPlaceholderText('delete Test Item'), 'incorrect input');
expect(deleteButton).toBeDisabled();
});

it('enables Delete button when input is correct', async () => {
render(<TypedDeleteConfirmation {...defaultProps} />);

const deleteButton = screen.getByRole('button', { name: 'Delete' });
expect(deleteButton).toBeDisabled();

await userEvent.type(screen.getByPlaceholderText('delete Test Item'), 'delete Test Item');
expect(deleteButton).toBeEnabled();
});

it('calls onClose when Cancel button is clicked', () => {
render(<TypedDeleteConfirmation {...defaultProps} />);

fireEvent.click(screen.getByRole('button', { name: 'Cancel' }));
expect(mockOnClose).toHaveBeenCalledTimes(1);
expect(mockOnConfirm).not.toHaveBeenCalled();
});

it('calls onConfirm when Delete button is clicked with correct input', async () => {
render(<TypedDeleteConfirmation {...defaultProps} />);

await userEvent.type(screen.getByPlaceholderText('delete Test Item'), 'delete Test Item');
fireEvent.click(screen.getByRole('button', { name: 'Delete' }));
expect(mockOnConfirm).toHaveBeenCalledTimes(1);
expect(mockOnClose).not.toHaveBeenCalled();
});

it('is case-insensitive for input matching', async () => {
render(<TypedDeleteConfirmation {...defaultProps} />);

await userEvent.type(screen.getByPlaceholderText('delete Test Item'), 'DELETE TEST ITEM');
expect(screen.getByRole('button', { name: 'Delete' })).toBeEnabled();
});

it('resets input when closed and reopened', () => {
const { rerender } = render(<TypedDeleteConfirmation {...defaultProps} />);

const input = screen.getByPlaceholderText('delete Test Item');
fireEvent.change(input, { target: { value: 'delete Test Item' } });
expect(input).toHaveValue('delete Test Item');

rerender(<TypedDeleteConfirmation {...defaultProps} isOpen={false} />);
rerender(<TypedDeleteConfirmation {...defaultProps} isOpen={true} />);


});
});
Loading

0 comments on commit 21c7be7

Please sign in to comment.