crud tests for subjects, assignments and tasks
This commit is contained in:
73
__tests__/assignment/createAssignment.test.tsx
Normal file
73
__tests__/assignment/createAssignment.test.tsx
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import { supabase } from "@/lib/supabase";
|
||||||
|
import { fireEvent, render, waitFor } from "@testing-library/react-native";
|
||||||
|
import { router } from "expo-router";
|
||||||
|
import CreateAssignment from "../../app/assignment/createAssignment";
|
||||||
|
|
||||||
|
const mockSingle = jest.fn();
|
||||||
|
const mockSelect = jest.fn(() => ({ single: mockSingle, }));
|
||||||
|
const mockInsert = jest.fn(() => ({ select: mockSelect, }));
|
||||||
|
|
||||||
|
jest.mock("expo-router", () => ({
|
||||||
|
router: {
|
||||||
|
back: jest.fn(),
|
||||||
|
replace: jest.fn(),
|
||||||
|
},
|
||||||
|
Stack: {
|
||||||
|
Screen: () => null,
|
||||||
|
},
|
||||||
|
useLocalSearchParams: () => ({
|
||||||
|
sId: null,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("@/lib/progress", () => ({
|
||||||
|
CheckAssignmentCompletion: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("@/lib/asyncStorage", () => ({
|
||||||
|
SaveAssignmentNotificationId: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("expo-notifications", () => ({
|
||||||
|
scheduleNotificationAsync: jest.fn(() => Promise.resolve("notification-123")),
|
||||||
|
SchedulableTriggerInputTypes: {
|
||||||
|
DATE: "date",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("@/lib/supabase", () => {
|
||||||
|
return {
|
||||||
|
supabase: {
|
||||||
|
auth: {
|
||||||
|
getUser: jest.fn(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
data: { user: { id: "user-123" } },
|
||||||
|
error: null,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
},
|
||||||
|
from: jest.fn(() => ({
|
||||||
|
insert: mockInsert,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
test("creates an assignment and navigates back", async () => {
|
||||||
|
mockSingle.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
aId: "assignment-123", title: "create a simple test", deadline: "",
|
||||||
|
},
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const screen = render(<CreateAssignment />);
|
||||||
|
fireEvent.changeText(screen.getByTestId("assignment-title-input"), "create a simple test");
|
||||||
|
fireEvent.press(screen.getByTestId("create-assignment-button"));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(supabase.from).toHaveBeenCalledWith("assignments");
|
||||||
|
expect(mockInsert).toHaveBeenCalled();
|
||||||
|
expect(router.back).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
110
__tests__/assignment/deleteAssignment.test.tsx
Normal file
110
__tests__/assignment/deleteAssignment.test.tsx
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
import { supabase } from "@/lib/supabase";
|
||||||
|
import { fireEvent, render, waitFor } from "@testing-library/react-native";
|
||||||
|
import { router } from "expo-router";
|
||||||
|
import { Alert } from "react-native";
|
||||||
|
import ViewDetailsAssignment from "../../app/assignment/viewDetailsAssignment";
|
||||||
|
|
||||||
|
const mockSingleAssignment = jest.fn();
|
||||||
|
const mockSelectAssignmentEq = jest.fn(() => ({ single: mockSingleAssignment, }));
|
||||||
|
const mockSelectAssignment = jest.fn(() => ({ eq: mockSelectAssignmentEq, }));
|
||||||
|
const mockSelectTasksEq = jest.fn();
|
||||||
|
const mockSelectTasks = jest.fn(() => ({ eq: mockSelectTasksEq }));
|
||||||
|
const mockDeleteAssignmentEq = jest.fn();
|
||||||
|
const mockDeleteAssignment = jest.fn(() => ({ eq: mockDeleteAssignmentEq, }));
|
||||||
|
|
||||||
|
jest.mock("expo-router", () => ({
|
||||||
|
router: {
|
||||||
|
back: jest.fn(),
|
||||||
|
replace: jest.fn(),
|
||||||
|
},
|
||||||
|
Stack: {
|
||||||
|
Screen: () => null,
|
||||||
|
},
|
||||||
|
useLocalSearchParams: () => ({
|
||||||
|
aId: "assignment-123",
|
||||||
|
}),
|
||||||
|
useFocusEffect: (callback: () => void) => callback(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("@/lib/supabase", () => {
|
||||||
|
return {
|
||||||
|
supabase: {
|
||||||
|
auth: {
|
||||||
|
getUser: jest.fn(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
data: { user: { uId: "user-123" } },
|
||||||
|
error: null,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
getSession: jest.fn(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
data: {
|
||||||
|
session: {
|
||||||
|
user: { uId: "user-123" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
),
|
||||||
|
onAuthStateChange: jest.fn(() => ({
|
||||||
|
data: {
|
||||||
|
subscription: {
|
||||||
|
unsubscribe: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
from: jest.fn((table) => {
|
||||||
|
if (table === "assignments") {
|
||||||
|
return {
|
||||||
|
select: mockSelectAssignment,
|
||||||
|
delete: mockDeleteAssignment,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (table === "tasks") {
|
||||||
|
return {
|
||||||
|
select: mockSelectTasks,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const alertSpy = jest.spyOn(Alert, "alert");
|
||||||
|
|
||||||
|
test("deletes a task and navigates back", async () => {
|
||||||
|
mockSingleAssignment.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
aId: "assignment-123",
|
||||||
|
title: "create a simple test",
|
||||||
|
uId: "user-123",
|
||||||
|
},
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
mockSelectTasksEq.mockResolvedValue({ data: [], error: null, })
|
||||||
|
mockDeleteAssignmentEq.mockResolvedValue({ error: null, });
|
||||||
|
|
||||||
|
const screen = render(<ViewDetailsAssignment />);
|
||||||
|
fireEvent.press(await screen.findByTestId("delete-assignment-button"));
|
||||||
|
|
||||||
|
expect(alertSpy).toHaveBeenCalledWith(
|
||||||
|
"Delete Assignment",
|
||||||
|
"Are you sure you want to delete this assignment?",
|
||||||
|
expect.any(Array),
|
||||||
|
);
|
||||||
|
|
||||||
|
const alertButtons = alertSpy.mock.calls[0][2];
|
||||||
|
const confirmDeleteButton = alertButtons[1];
|
||||||
|
|
||||||
|
await confirmDeleteButton.onPress();
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(supabase.from).toHaveBeenCalledWith("assignments");
|
||||||
|
expect(mockDeleteAssignment).toHaveBeenCalled();
|
||||||
|
expect(mockDeleteAssignmentEq).toHaveBeenCalledWith("aId", "assignment-123");
|
||||||
|
expect(router.back).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
99
__tests__/assignment/editAssignment.test.tsx
Normal file
99
__tests__/assignment/editAssignment.test.tsx
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import EditAssignment from "@/app/assignment/editAssignment";
|
||||||
|
import { supabase } from "@/lib/supabase";
|
||||||
|
import { fireEvent, render, waitFor } from "@testing-library/react-native";
|
||||||
|
import { router } from "expo-router";
|
||||||
|
|
||||||
|
const mockUpdateSingle = jest.fn();
|
||||||
|
const mockUpdateSelect = jest.fn(() => ({ single: mockUpdateSingle, }));
|
||||||
|
const mockUpdateEq = jest.fn(() => ({ select: mockUpdateSelect, }));
|
||||||
|
const mockUpdate = jest.fn(() => ({ eq: mockUpdateEq, }));
|
||||||
|
const mockSingle = jest.fn();
|
||||||
|
const mockSelectEq = jest.fn(() => ({ single: mockSingle, }));
|
||||||
|
const mockSelect = jest.fn(() => ({ eq: mockSelectEq, }));
|
||||||
|
|
||||||
|
jest.mock("expo-router", () => ({
|
||||||
|
router: {
|
||||||
|
back: jest.fn(),
|
||||||
|
replace: jest.fn(),
|
||||||
|
},
|
||||||
|
Stack: {
|
||||||
|
Screen: () => null,
|
||||||
|
},
|
||||||
|
useLocalSearchParams: () => ({
|
||||||
|
aId: "assignment-123",
|
||||||
|
}),
|
||||||
|
useFocusEffect: (callback: () => void) => callback(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("@/lib/progress", () => ({
|
||||||
|
CheckAssignmentCompletion: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("@/lib/asyncStorage", () => ({
|
||||||
|
GetAssignmentNotificationId: jest.fn(() => Promise.resolve(null)),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("expo-notifications", () => ({
|
||||||
|
scheduleNotificationAsync: jest.fn(() => Promise.resolve("notification-123")),
|
||||||
|
SchedulableTriggerInputTypes: {
|
||||||
|
DATE: "date",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("@/lib/supabase", () => {
|
||||||
|
return {
|
||||||
|
supabase: {
|
||||||
|
auth: {
|
||||||
|
getUser: jest.fn(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
data: { user: { id: "user-123" } },
|
||||||
|
error: null,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
},
|
||||||
|
from: jest.fn(() => ({
|
||||||
|
select: mockSelect,
|
||||||
|
update: mockUpdate,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
test("updates an assignment and navigates back", async () => {
|
||||||
|
mockSingle.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
aId: "assignment-123",
|
||||||
|
title: "create a simple test",
|
||||||
|
uId: "user-123",
|
||||||
|
deadline: "2026-04-25",
|
||||||
|
},
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
mockUpdateSingle.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
aId: "assignment-123",
|
||||||
|
title: "create a harder test",
|
||||||
|
uId: "user-123",
|
||||||
|
deadline: "2026-04-25",
|
||||||
|
},
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const screen = render(<EditAssignment />);
|
||||||
|
fireEvent.changeText(await screen.findByTestId("assignment-title-input"), "create a harder test");
|
||||||
|
fireEvent.press(screen.getByTestId("edit-assignment-button"));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(supabase.from).toHaveBeenCalledWith("assignments");
|
||||||
|
expect(mockSelect).toHaveBeenCalled();
|
||||||
|
expect(mockUpdate).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
title: "create a harder test",
|
||||||
|
uId: "user-123",
|
||||||
|
deadline: "2026-04-25",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(mockUpdateSingle).toHaveBeenCalled();
|
||||||
|
expect(router.back).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
48
__tests__/subject/createSubject.test.tsx
Normal file
48
__tests__/subject/createSubject.test.tsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { supabase } from "@/lib/supabase";
|
||||||
|
import { fireEvent, render, waitFor } from "@testing-library/react-native";
|
||||||
|
import { router } from "expo-router";
|
||||||
|
import CreateSubject from "../../app/subject/createSubject";
|
||||||
|
|
||||||
|
const mockInsert = jest.fn();
|
||||||
|
|
||||||
|
jest.mock("expo-router", () => ({
|
||||||
|
router: {
|
||||||
|
back: jest.fn(),
|
||||||
|
replace: jest.fn(),
|
||||||
|
},
|
||||||
|
Stack: {
|
||||||
|
Screen: () => null,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("@/lib/supabase", () => {
|
||||||
|
return {
|
||||||
|
supabase: {
|
||||||
|
auth: {
|
||||||
|
getUser: jest.fn(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
data: { user: { id: "user-123" } },
|
||||||
|
error: null,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
},
|
||||||
|
from: jest.fn(() => ({
|
||||||
|
insert: mockInsert,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
test("creates a subject and navigates back", async () => {
|
||||||
|
mockInsert.mockResolvedValue({ error: null });
|
||||||
|
|
||||||
|
const screen = render(<CreateSubject />);
|
||||||
|
fireEvent.changeText(screen.getByTestId("subject-title-input"), "ikt205g26v");
|
||||||
|
fireEvent.press(screen.getByTestId("create-subject-button"));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(supabase.from).toHaveBeenCalledWith("subjects");
|
||||||
|
expect(mockInsert).toHaveBeenCalled();
|
||||||
|
expect(router.back).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
111
__tests__/subject/deleteSubject.test.tsx
Normal file
111
__tests__/subject/deleteSubject.test.tsx
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
import { supabase } from "@/lib/supabase";
|
||||||
|
import { fireEvent, render, waitFor } from "@testing-library/react-native";
|
||||||
|
import { router } from "expo-router";
|
||||||
|
import { Alert } from "react-native";
|
||||||
|
import ViewDetailsSubject from "../../app/subject/viewDetailsSubject";
|
||||||
|
|
||||||
|
const mockSingleSubject = jest.fn();
|
||||||
|
const mockSelectSubjectEq = jest.fn(() => ({ single: mockSingleSubject, }));
|
||||||
|
const mockSelectSubject = jest.fn(() => ({ eq: mockSelectSubjectEq, }));
|
||||||
|
const mockOrderAssignments = jest.fn();
|
||||||
|
const mockSelectAssignmentsEq = jest.fn(() => ({ order: mockOrderAssignments }));
|
||||||
|
const mockSelectAssignments = jest.fn(() => ({ eq: mockSelectAssignmentsEq }));
|
||||||
|
const mockDeleteSubjectEq = jest.fn();
|
||||||
|
const mockDeleteSubject = jest.fn(() => ({ eq: mockDeleteSubjectEq, }));
|
||||||
|
|
||||||
|
jest.mock("expo-router", () => ({
|
||||||
|
router: {
|
||||||
|
back: jest.fn(),
|
||||||
|
replace: jest.fn(),
|
||||||
|
},
|
||||||
|
Stack: {
|
||||||
|
Screen: () => null,
|
||||||
|
},
|
||||||
|
useLocalSearchParams: () => ({
|
||||||
|
sId: "subject-123",
|
||||||
|
}),
|
||||||
|
useFocusEffect: (callback: () => void) => callback(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("@/lib/supabase", () => {
|
||||||
|
return {
|
||||||
|
supabase: {
|
||||||
|
auth: {
|
||||||
|
getUser: jest.fn(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
data: { user: { uId: "user-123" } },
|
||||||
|
error: null,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
getSession: jest.fn(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
data: {
|
||||||
|
session: {
|
||||||
|
user: { uId: "user-123" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
),
|
||||||
|
onAuthStateChange: jest.fn(() => ({
|
||||||
|
data: {
|
||||||
|
subscription: {
|
||||||
|
unsubscribe: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
from: jest.fn((table) => {
|
||||||
|
if (table === "subjects") {
|
||||||
|
return {
|
||||||
|
select: mockSelectSubject,
|
||||||
|
delete: mockDeleteSubject,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (table === "assignments") {
|
||||||
|
return {
|
||||||
|
select: mockSelectAssignments,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const alertSpy = jest.spyOn(Alert, "alert");
|
||||||
|
|
||||||
|
test("deletes a subject and navigates back", async () => {
|
||||||
|
mockSingleSubject.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
sId: "subject-123",
|
||||||
|
title: "ikt205g26v",
|
||||||
|
uId: "user-123",
|
||||||
|
},
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
mockOrderAssignments.mockResolvedValue({ data: [], error: null, })
|
||||||
|
mockDeleteSubjectEq.mockResolvedValue({ error: null, });
|
||||||
|
|
||||||
|
const screen = render(<ViewDetailsSubject />);
|
||||||
|
fireEvent.press(await screen.findByTestId("delete-subject-button"));
|
||||||
|
|
||||||
|
expect(alertSpy).toHaveBeenCalledWith(
|
||||||
|
"Delete Subject",
|
||||||
|
"Are you sure you want to delete this subject?",
|
||||||
|
expect.any(Array),
|
||||||
|
);
|
||||||
|
|
||||||
|
const alertButtons = alertSpy.mock.calls[0][2];
|
||||||
|
const confirmDeleteButton = alertButtons[1];
|
||||||
|
|
||||||
|
await confirmDeleteButton.onPress();
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(supabase.from).toHaveBeenCalledWith("subjects");
|
||||||
|
expect(mockDeleteSubject).toHaveBeenCalled();
|
||||||
|
expect(mockDeleteSubjectEq).toHaveBeenCalledWith("sId", "subject-123");
|
||||||
|
expect(router.back).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
72
__tests__/subject/editSubject.test.tsx
Normal file
72
__tests__/subject/editSubject.test.tsx
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import { supabase } from "@/lib/supabase";
|
||||||
|
import { fireEvent, render, waitFor } from "@testing-library/react-native";
|
||||||
|
import { router } from "expo-router";
|
||||||
|
import EditSubject from "../../app/subject/editSubject";
|
||||||
|
|
||||||
|
const mockUpdateEq = jest.fn();
|
||||||
|
const mockUpdate = jest.fn(() => ({ eq: mockUpdateEq, }));
|
||||||
|
const mockSingle = jest.fn();
|
||||||
|
const mockSelectEq = jest.fn(() => ({ single: mockSingle, }));
|
||||||
|
const mockSelect = jest.fn(() => ({ eq: mockSelectEq, }));
|
||||||
|
|
||||||
|
jest.mock("expo-router", () => ({
|
||||||
|
router: {
|
||||||
|
back: jest.fn(),
|
||||||
|
replace: jest.fn(),
|
||||||
|
},
|
||||||
|
Stack: {
|
||||||
|
Screen: () => null,
|
||||||
|
},
|
||||||
|
useLocalSearchParams: () => ({
|
||||||
|
sId: "subject-123",
|
||||||
|
}),
|
||||||
|
useFocusEffect: (callback: () => void) => callback(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("@/lib/supabase", () => {
|
||||||
|
return {
|
||||||
|
supabase: {
|
||||||
|
auth: {
|
||||||
|
getUser: jest.fn(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
data: { user: { id: "user-123" } },
|
||||||
|
error: null,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
},
|
||||||
|
from: jest.fn(() => ({
|
||||||
|
select: mockSelect,
|
||||||
|
update: mockUpdate,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
test("updates a subject and navigates back", async () => {
|
||||||
|
mockSingle.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
sId: "subject-123",
|
||||||
|
title: "ikt205g26v",
|
||||||
|
uId: "user-123",
|
||||||
|
},
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
mockUpdateEq.mockResolvedValue({ error: null, });
|
||||||
|
|
||||||
|
const screen = render(<EditSubject />);
|
||||||
|
fireEvent.changeText(await screen.findByTestId("subject-title-input"), "ikt206g26v");
|
||||||
|
fireEvent.press(screen.getByTestId("edit-subject-button"));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(supabase.from).toHaveBeenCalledWith("subjects");
|
||||||
|
expect(mockSelect).toHaveBeenCalled();
|
||||||
|
expect(mockUpdate).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
title: "ikt206g26v",
|
||||||
|
uId: "user-123",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(mockUpdateEq).toHaveBeenCalledWith("sId", "subject-123");
|
||||||
|
expect(router.back).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
55
__tests__/task/createTask.test.tsx
Normal file
55
__tests__/task/createTask.test.tsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { supabase } from "@/lib/supabase";
|
||||||
|
import { fireEvent, render, waitFor } from "@testing-library/react-native";
|
||||||
|
import { router } from "expo-router";
|
||||||
|
import CreateTask from "../../app/task/createTask";
|
||||||
|
|
||||||
|
const mockInsert = jest.fn();
|
||||||
|
|
||||||
|
jest.mock("expo-router", () => ({
|
||||||
|
router: {
|
||||||
|
back: jest.fn(),
|
||||||
|
replace: jest.fn(),
|
||||||
|
},
|
||||||
|
Stack: {
|
||||||
|
Screen: () => null,
|
||||||
|
},
|
||||||
|
useLocalSearchParams: () => ({
|
||||||
|
aId: null,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("@/lib/progress", () => ({
|
||||||
|
CheckAssignmentCompletion: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("@/lib/supabase", () => {
|
||||||
|
return {
|
||||||
|
supabase: {
|
||||||
|
auth: {
|
||||||
|
getUser: jest.fn(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
data: { user: { id: "user-123" } },
|
||||||
|
error: null,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
},
|
||||||
|
from: jest.fn(() => ({
|
||||||
|
insert: mockInsert,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
test("creates a task and navigates back", async () => {
|
||||||
|
mockInsert.mockResolvedValue({ error: null });
|
||||||
|
|
||||||
|
const screen = render(<CreateTask />);
|
||||||
|
fireEvent.changeText(screen.getByTestId("task-title-input"), "Read chapter 4");
|
||||||
|
fireEvent.press(screen.getByTestId("create-task-button"));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(supabase.from).toHaveBeenCalledWith("tasks");
|
||||||
|
expect(mockInsert).toHaveBeenCalled();
|
||||||
|
expect(router.back).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
97
__tests__/task/deleteTask.test.tsx
Normal file
97
__tests__/task/deleteTask.test.tsx
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import { supabase } from "@/lib/supabase";
|
||||||
|
import { fireEvent, render, waitFor } from "@testing-library/react-native";
|
||||||
|
import { router } from "expo-router";
|
||||||
|
import { Alert } from "react-native";
|
||||||
|
import ViewDetailsTask from "../../app/task/viewDetailsTask";
|
||||||
|
|
||||||
|
const mockSingleTask = jest.fn();
|
||||||
|
const mockSelectTaskEq = jest.fn(() => ({ single: mockSingleTask, }));
|
||||||
|
const mockSelectTask = jest.fn(() => ({ eq: mockSelectTaskEq, }));
|
||||||
|
const mockDeleteTaskEq = jest.fn();
|
||||||
|
const mockDeleteTask = jest.fn(() => ({ eq: mockDeleteTaskEq, }));
|
||||||
|
|
||||||
|
jest.mock("expo-router", () => ({
|
||||||
|
router: {
|
||||||
|
back: jest.fn(),
|
||||||
|
replace: jest.fn(),
|
||||||
|
},
|
||||||
|
Stack: {
|
||||||
|
Screen: () => null,
|
||||||
|
},
|
||||||
|
useLocalSearchParams: () => ({
|
||||||
|
tId: "task-123",
|
||||||
|
}),
|
||||||
|
useFocusEffect: (callback: () => void) => callback(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("@/lib/supabase", () => {
|
||||||
|
return {
|
||||||
|
supabase: {
|
||||||
|
auth: {
|
||||||
|
getUser: jest.fn(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
data: { user: { uId: "user-123" } },
|
||||||
|
error: null,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
getSession: jest.fn(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
data: {
|
||||||
|
session: {
|
||||||
|
user: { uId: "user-123" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
),
|
||||||
|
onAuthStateChange: jest.fn(() => ({
|
||||||
|
data: {
|
||||||
|
subscription: {
|
||||||
|
unsubscribe: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
from: jest.fn(() => {
|
||||||
|
return {
|
||||||
|
select: mockSelectTask,
|
||||||
|
delete: mockDeleteTask,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const alertSpy = jest.spyOn(Alert, "alert");
|
||||||
|
|
||||||
|
test("deletes a task and navigates back", async () => {
|
||||||
|
mockSingleTask.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
tId: "task-123",
|
||||||
|
title: "Read chapter 4",
|
||||||
|
uId: "user-123",
|
||||||
|
},
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
mockDeleteTaskEq.mockResolvedValue({ error: null, });
|
||||||
|
|
||||||
|
const screen = render(<ViewDetailsTask />);
|
||||||
|
fireEvent.press(await screen.findByTestId("delete-task-button"));
|
||||||
|
|
||||||
|
expect(alertSpy).toHaveBeenCalledWith(
|
||||||
|
"Delete Task",
|
||||||
|
"Are you sure you want to delete this task?",
|
||||||
|
expect.any(Array),
|
||||||
|
);
|
||||||
|
|
||||||
|
const alertButtons = alertSpy.mock.calls[0][2];
|
||||||
|
const confirmDeleteButton = alertButtons[1];
|
||||||
|
|
||||||
|
await confirmDeleteButton.onPress();
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(supabase.from).toHaveBeenCalledWith("tasks");
|
||||||
|
expect(mockDeleteTask).toHaveBeenCalled();
|
||||||
|
expect(mockDeleteTaskEq).toHaveBeenCalledWith("tId", "task-123");
|
||||||
|
expect(router.back).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
76
__tests__/task/editTask.test.tsx
Normal file
76
__tests__/task/editTask.test.tsx
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import { supabase } from "@/lib/supabase";
|
||||||
|
import { fireEvent, render, waitFor } from "@testing-library/react-native";
|
||||||
|
import { router } from "expo-router";
|
||||||
|
import EditTask from "../../app/task/editTask";
|
||||||
|
|
||||||
|
const mockUpdateEq = jest.fn();
|
||||||
|
const mockUpdate = jest.fn(() => ({ eq: mockUpdateEq, }));
|
||||||
|
const mockSingle = jest.fn();
|
||||||
|
const mockSelectEq = jest.fn(() => ({ single: mockSingle, }));
|
||||||
|
const mockSelect = jest.fn(() => ({ eq: mockSelectEq, }));
|
||||||
|
|
||||||
|
jest.mock("expo-router", () => ({
|
||||||
|
router: {
|
||||||
|
back: jest.fn(),
|
||||||
|
replace: jest.fn(),
|
||||||
|
},
|
||||||
|
Stack: {
|
||||||
|
Screen: () => null,
|
||||||
|
},
|
||||||
|
useLocalSearchParams: () => ({
|
||||||
|
tId: "task-123",
|
||||||
|
}),
|
||||||
|
useFocusEffect: (callback: () => void) => callback(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("@/lib/progress", () => ({
|
||||||
|
CheckAssignmentCompletion: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("@/lib/supabase", () => {
|
||||||
|
return {
|
||||||
|
supabase: {
|
||||||
|
auth: {
|
||||||
|
getUser: jest.fn(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
data: { user: { id: "user-123" } },
|
||||||
|
error: null,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
},
|
||||||
|
from: jest.fn(() => ({
|
||||||
|
select: mockSelect,
|
||||||
|
update: mockUpdate,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
test("updates a task and navigates back", async () => {
|
||||||
|
mockSingle.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
tId: "task-123",
|
||||||
|
title: "Read chapter 4",
|
||||||
|
uId: "user-123",
|
||||||
|
},
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
mockUpdateEq.mockResolvedValue({ error: null, });
|
||||||
|
|
||||||
|
const screen = render(<EditTask />);
|
||||||
|
fireEvent.changeText(await screen.findByTestId("task-title-input"), "Read chapter 5");
|
||||||
|
fireEvent.press(screen.getByTestId("edit-task-button"));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(supabase.from).toHaveBeenCalledWith("tasks");
|
||||||
|
expect(mockSelect).toHaveBeenCalled();
|
||||||
|
expect(mockUpdate).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
title: "Read chapter 5",
|
||||||
|
uId: "user-123",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(mockUpdateEq).toHaveBeenCalledWith("tId", "task-123");
|
||||||
|
expect(router.back).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
7
app.json
7
app.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"expo": {
|
"expo": {
|
||||||
"name": "Study-Sprint",
|
"name": "Study-Sprint",
|
||||||
"slug": "Study-Sprint",
|
"slug": "study-sprint",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"orientation": "portrait",
|
"orientation": "portrait",
|
||||||
"icon": "./assets/images/icon.png",
|
"icon": "./assets/images/icon.png",
|
||||||
@@ -49,8 +49,9 @@
|
|||||||
"extra": {
|
"extra": {
|
||||||
"router": {},
|
"router": {},
|
||||||
"eas": {
|
"eas": {
|
||||||
"projectId": "25652385-934a-4a29-8fa7-deff3281e03e"
|
"projectId": "d9e26d91-0f0c-4b97-b11a-20be2916e9f3"
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"owner": "ikt205g26v-g18"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,6 +153,7 @@ export default function CreateAssignment() {
|
|||||||
<View className="mb-5">
|
<View className="mb-5">
|
||||||
<Text className={labelClassName}>Title</Text>
|
<Text className={labelClassName}>Title</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
|
testID = "assignment-title-input"
|
||||||
className={inputClassName}
|
className={inputClassName}
|
||||||
placeholder="Enter assignment title"
|
placeholder="Enter assignment title"
|
||||||
value={title}
|
value={title}
|
||||||
@@ -219,6 +220,7 @@ export default function CreateAssignment() {
|
|||||||
</Pressable>
|
</Pressable>
|
||||||
|
|
||||||
<Pressable
|
<Pressable
|
||||||
|
testID = "create-assignment-button"
|
||||||
className={`h-14 items-center justify-center rounded-2xl ${
|
className={`h-14 items-center justify-center rounded-2xl ${
|
||||||
isSaving ? 'bg-accent-disabled' : 'bg-accent'
|
isSaving ? 'bg-accent-disabled' : 'bg-accent'
|
||||||
}`}
|
}`}
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ export default function EditAssignment() {
|
|||||||
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
||||||
<View style={defaultStyles.container}>
|
<View style={defaultStyles.container}>
|
||||||
<TextInput
|
<TextInput
|
||||||
|
testID="assignment-title-input"
|
||||||
style={defaultStyles.inputText}
|
style={defaultStyles.inputText}
|
||||||
placeholder="Title"
|
placeholder="Title"
|
||||||
value={assignment.title}
|
value={assignment.title}
|
||||||
@@ -176,7 +177,7 @@ export default function EditAssignment() {
|
|||||||
<Text style={defaultStyles.checkboxLabel}>{assignment.isCompleted ? 'Completed' : 'Not Completed'}</Text>
|
<Text style={defaultStyles.checkboxLabel}>{assignment.isCompleted ? 'Completed' : 'Not Completed'}</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
|
|
||||||
<Button title={isSaving ? "Saving..." : "Save"} onPress={EditAssignment} disabled={isSaving} />
|
<Button testID="edit-assignment-button" title={isSaving ? "Saving..." : "Save"} onPress={EditAssignment} disabled={isSaving} />
|
||||||
{isSaving && (
|
{isSaving && (
|
||||||
<ActivityIndicator size="large" />
|
<ActivityIndicator size="large" />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ export default function ViewDetailsAssignment() {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Button title="Edit" onPress={() => router.push({pathname: "/assignment/editAssignment", params: { aId: assignment.aId }})} />
|
<Button title="Edit" onPress={() => router.push({pathname: "/assignment/editAssignment", params: { aId: assignment.aId }})} />
|
||||||
<Button title="Delete" onPress={() => DeleteAssignment(assignment.aId)} />
|
<Button testID = "delete-assignment-button" title="Delete" onPress={() => DeleteAssignment(assignment.aId)} />
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={defaultStyles.buttonContainer}>
|
<View style={defaultStyles.buttonContainer}>
|
||||||
@@ -224,7 +224,7 @@ export default function ViewDetailsAssignment() {
|
|||||||
{isOwner && (
|
{isOwner && (
|
||||||
<View style={defaultStyles.buttonContainer}>
|
<View style={defaultStyles.buttonContainer}>
|
||||||
<Button title="Edit" onPress={() => router.push({pathname: "/task/editTask", params: { tId: item.tId }})} />
|
<Button title="Edit" onPress={() => router.push({pathname: "/task/editTask", params: { tId: item.tId }})} />
|
||||||
<Button title="Delete" onPress={() => DeleteTask(item.tId, item.tId)} />
|
<Button title="Delete" onPress={() => DeleteTask(item.tId, item.aId)} />
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ export default function CreateSubject() {
|
|||||||
<View className="mb-5">
|
<View className="mb-5">
|
||||||
<Text className={labelClassName}>Title</Text>
|
<Text className={labelClassName}>Title</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
|
testID="subject-title-input"
|
||||||
className={inputClassName}
|
className={inputClassName}
|
||||||
placeholder="Enter subject title"
|
placeholder="Enter subject title"
|
||||||
value={title}
|
value={title}
|
||||||
@@ -157,6 +158,7 @@ export default function CreateSubject() {
|
|||||||
</Pressable>
|
</Pressable>
|
||||||
|
|
||||||
<Pressable
|
<Pressable
|
||||||
|
testID="create-subject-button"
|
||||||
className={`h-14 items-center justify-center rounded-2xl ${
|
className={`h-14 items-center justify-center rounded-2xl ${
|
||||||
isSaving ? 'bg-accent-disabled' : 'bg-accent'
|
isSaving ? 'bg-accent-disabled' : 'bg-accent'
|
||||||
}`}
|
}`}
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ export default function EditSubject() {
|
|||||||
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
||||||
<View style={defaultStyles.container}>
|
<View style={defaultStyles.container}>
|
||||||
<TextInput
|
<TextInput
|
||||||
|
testID="subject-title-input"
|
||||||
style={defaultStyles.inputText}
|
style={defaultStyles.inputText}
|
||||||
placeholder="Title"
|
placeholder="Title"
|
||||||
value={subject.title}
|
value={subject.title}
|
||||||
@@ -109,7 +110,7 @@ export default function EditSubject() {
|
|||||||
<Text style={defaultStyles.checkboxLabel}>{subject.isActive ? 'Active' : 'inactive'}</Text>
|
<Text style={defaultStyles.checkboxLabel}>{subject.isActive ? 'Active' : 'inactive'}</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
|
|
||||||
<Button title={isSaving ? "Saving..." : "Save"} onPress={EditSubject} disabled={isSaving} />
|
<Button testID="edit-subject-button" title={isSaving ? "Saving..." : "Save"} onPress={EditSubject} disabled={isSaving} />
|
||||||
{isSaving && (
|
{isSaving && (
|
||||||
<ActivityIndicator size="large" />
|
<ActivityIndicator size="large" />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ export default function ViewDetailsSubject() {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Button title="Edit" onPress={() => router.push({pathname: "/subject/editSubject", params: { sId: subject.sId }})} />
|
<Button title="Edit" onPress={() => router.push({pathname: "/subject/editSubject", params: { sId: subject.sId }})} />
|
||||||
<Button title="Delete" onPress={() => DeleteSubject(subject.sId)} />
|
<Button testID = "delete-subject-button" title="Delete" onPress={() => DeleteSubject(subject.sId)} />
|
||||||
|
|
||||||
<View style={defaultStyles.buttonContainer}>
|
<View style={defaultStyles.buttonContainer}>
|
||||||
<Button title="Create Assignment" onPress={() => router.push({pathname: "/assignment/createAssignment", params: { sId: subject.sId }})} />
|
<Button title="Create Assignment" onPress={() => router.push({pathname: "/assignment/createAssignment", params: { sId: subject.sId }})} />
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ export default function CreateTask() {
|
|||||||
<View className="mb-5">
|
<View className="mb-5">
|
||||||
<Text className={labelClassName}>Title</Text>
|
<Text className={labelClassName}>Title</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
|
testID="task-title-input"
|
||||||
className={inputClassName}
|
className={inputClassName}
|
||||||
placeholder="Enter task title"
|
placeholder="Enter task title"
|
||||||
value={title}
|
value={title}
|
||||||
@@ -169,6 +170,7 @@ export default function CreateTask() {
|
|||||||
</Pressable>
|
</Pressable>
|
||||||
|
|
||||||
<Pressable
|
<Pressable
|
||||||
|
testID="create-task-button"
|
||||||
className={`h-14 items-center justify-center rounded-2xl ${
|
className={`h-14 items-center justify-center rounded-2xl ${
|
||||||
isSaving ? 'bg-accent-disabled' : 'bg-accent'
|
isSaving ? 'bg-accent-disabled' : 'bg-accent'
|
||||||
}`}
|
}`}
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ export default function EditTask() {
|
|||||||
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
||||||
<View style={defaultStyles.container}>
|
<View style={defaultStyles.container}>
|
||||||
<TextInput
|
<TextInput
|
||||||
|
testID="task-title-input"
|
||||||
style={defaultStyles.inputText}
|
style={defaultStyles.inputText}
|
||||||
placeholder="Title"
|
placeholder="Title"
|
||||||
value={task.title}
|
value={task.title}
|
||||||
@@ -119,7 +120,7 @@ export default function EditTask() {
|
|||||||
<Text style={defaultStyles.checkboxLabel}>{task.isCompleted ? 'Completed' : 'Not Completed'}</Text>
|
<Text style={defaultStyles.checkboxLabel}>{task.isCompleted ? 'Completed' : 'Not Completed'}</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
|
|
||||||
<Button title={isSaving ? "Saving..." : "Save"} onPress={EditTask} disabled={isSaving} />
|
<Button testID="edit-task-button" title={isSaving ? "Saving..." : "Save"} onPress={EditTask} disabled={isSaving} />
|
||||||
{isSaving && (
|
{isSaving && (
|
||||||
<ActivityIndicator size="large" />
|
<ActivityIndicator size="large" />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ export default function ViewDetailsTask() {
|
|||||||
|
|
||||||
<View style={defaultStyles.buttonContainer}>
|
<View style={defaultStyles.buttonContainer}>
|
||||||
<Button title="Edit" onPress={() => router.push({pathname: "/task/editTask", params: { tId: task.tId }})} />
|
<Button title="Edit" onPress={() => router.push({pathname: "/task/editTask", params: { tId: task.tId }})} />
|
||||||
<Button title="Delete" onPress={() => DeleteTask(task.tId)} />
|
<Button testID = "delete-task-button" title="Delete" onPress={() => DeleteTask(task.tId)} />
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|||||||
71
notes/work-report-2026-04-24-to-25.md
Normal file
71
notes/work-report-2026-04-24-to-25.md
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# CRUD Testing Summary (React Native + Jest + Supabase)
|
||||||
|
|
||||||
|
## What these tests are about
|
||||||
|
Tests verify **app behavior**, not Supabase itself.
|
||||||
|
|
||||||
|
They check:
|
||||||
|
- User interaction works
|
||||||
|
- Correct database functions are called
|
||||||
|
- Navigation happens after actions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CRUD Breakdown
|
||||||
|
|
||||||
|
### CREATE
|
||||||
|
- User inputs data
|
||||||
|
- `insert()` is called
|
||||||
|
- App navigates back
|
||||||
|
|
||||||
|
Flow:
|
||||||
|
User → type → press create → insert() → router.back()
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### READ
|
||||||
|
- Data is fetched (`select().eq().single()`)
|
||||||
|
- State updates
|
||||||
|
- UI renders correct content
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### UPDATE
|
||||||
|
- Existing data is loaded
|
||||||
|
- User edits input
|
||||||
|
- `update().eq()` is called with correct values
|
||||||
|
- Navigation happens
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### DELETE
|
||||||
|
- User presses delete
|
||||||
|
- `Alert.alert()` is triggered
|
||||||
|
- Confirm button (`onPress`) is manually called in test
|
||||||
|
- `delete().eq()` runs
|
||||||
|
- Navigation happens
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Why mocking is used
|
||||||
|
- No real database calls
|
||||||
|
- Faster tests
|
||||||
|
- Full control over success/error cases
|
||||||
|
- No side effects (no real data created/deleted)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Mock rule
|
||||||
|
The mock must match the real call chain:
|
||||||
|
|
||||||
|
Real:
|
||||||
|
from → update → eq → select → single
|
||||||
|
|
||||||
|
Mock:
|
||||||
|
from() → update() → eq() → select() → single()
|
||||||
|
|
||||||
|
If not → errors like:
|
||||||
|
".select is not a function"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
https://oss.callstack.com/react-native-testing-library/
|
||||||
2908
package-lock.json
generated
2908
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
13
package.json
13
package.json
@@ -8,7 +8,8 @@
|
|||||||
"android": "expo start --android",
|
"android": "expo start --android",
|
||||||
"ios": "expo start --ios",
|
"ios": "expo start --ios",
|
||||||
"web": "expo start --web",
|
"web": "expo start --web",
|
||||||
"lint": "expo lint"
|
"lint": "expo lint",
|
||||||
|
"test": "jest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/vector-icons": "^15.0.3",
|
"@expo/vector-icons": "^15.0.3",
|
||||||
@@ -45,12 +46,20 @@
|
|||||||
"react-native-worklets": "0.5.1"
|
"react-native-worklets": "0.5.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@testing-library/react-native": "^13.3.3",
|
||||||
|
"@types/jest": "^30.0.0",
|
||||||
"@types/react": "~19.1.0",
|
"@types/react": "~19.1.0",
|
||||||
"eslint": "^9.25.0",
|
"eslint": "^9.25.0",
|
||||||
"eslint-config-expo": "~10.0.0",
|
"eslint-config-expo": "~10.0.0",
|
||||||
|
"jest": "^29.7.0",
|
||||||
|
"jest-expo": "^55.0.16",
|
||||||
"patch-package": "^8.0.1",
|
"patch-package": "^8.0.1",
|
||||||
|
"react-test-renderer": "19.1.0",
|
||||||
"tailwindcss": "^3.4.19",
|
"tailwindcss": "^3.4.19",
|
||||||
"typescript": "~5.9.2"
|
"typescript": "~5.9.2"
|
||||||
},
|
},
|
||||||
"private": true
|
"private": true,
|
||||||
|
"jest": {
|
||||||
|
"preset": "jest-expo"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user