Finished testing implementation
This commit is contained in:
54
FastNotes/__tests__/auth-guard.test.tsx
Normal file
54
FastNotes/__tests__/auth-guard.test.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import { renderRouter, screen, waitFor } from "expo-router/testing-library"
|
||||
import React from "react"
|
||||
|
||||
jest.mock("@/providers/auth-provider", () => {
|
||||
const React = require("react")
|
||||
const { AuthContext } = require("@/hooks/use-auth-context")
|
||||
|
||||
return {
|
||||
__esModule: true,
|
||||
default: ({ children }: React.PropsWithChildren) =>
|
||||
React.createElement(
|
||||
AuthContext.Provider,
|
||||
{
|
||||
value: {
|
||||
claims: null,
|
||||
profile: null,
|
||||
isLoading: false,
|
||||
isLoggedIn: false,
|
||||
},
|
||||
},
|
||||
children
|
||||
),
|
||||
}
|
||||
})
|
||||
|
||||
jest.mock("@/libs/supabase", () => ({
|
||||
supabase: {
|
||||
auth: {
|
||||
signInWithPassword: jest.fn(),
|
||||
signUp: jest.fn(),
|
||||
signOut: jest.fn(),
|
||||
getUser: jest.fn(),
|
||||
},
|
||||
from: jest.fn(),
|
||||
},
|
||||
supabaseUrl: "https://example.supabase.co",
|
||||
supabaseAnonKey: "test-anon-key",
|
||||
}))
|
||||
|
||||
describe("Auth guard", () => {
|
||||
it("redirects logged-out users to the login screen instead of protected content", async () => {
|
||||
const routerScreen = renderRouter("./app", {
|
||||
initialUrl: "/",
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(routerScreen.getPathname()).toBe("/login")
|
||||
})
|
||||
|
||||
expect(screen.getByText("Login")).toBeTruthy()
|
||||
expect(screen.getByText("Log in")).toBeTruthy()
|
||||
expect(screen.queryByText("FastNotes")).toBeNull()
|
||||
})
|
||||
})
|
||||
178
FastNotes/__tests__/detail-screen.test.tsx
Normal file
178
FastNotes/__tests__/detail-screen.test.tsx
Normal file
@@ -0,0 +1,178 @@
|
||||
import { act, render, screen, waitFor } from "@testing-library/react-native"
|
||||
import React, { PropsWithChildren } from "react"
|
||||
|
||||
import DetailScreen from "@/app/detail"
|
||||
import { AuthContext, AuthData } from "@/hooks/use-auth-context"
|
||||
import { supabase } from "@/libs/supabase"
|
||||
import { NotesProvider } from "@/src/notes/NotesContext"
|
||||
import { AppThemeProvider } from "@/src/theme/AppThemeProvider"
|
||||
import { useLocalSearchParams } from "expo-router"
|
||||
|
||||
type Deferred<T> = {
|
||||
promise: Promise<T>
|
||||
resolve: (value: T) => void
|
||||
reject: (reason?: unknown) => void
|
||||
}
|
||||
|
||||
function createDeferred<T>(): Deferred<T> {
|
||||
let resolve!: (value: T) => void
|
||||
let reject!: (reason?: unknown) => void
|
||||
|
||||
const promise = new Promise<T>((innerResolve, innerReject) => {
|
||||
resolve = innerResolve
|
||||
reject = innerReject
|
||||
})
|
||||
|
||||
return { promise, resolve, reject }
|
||||
}
|
||||
|
||||
jest.mock("expo-router", () => ({
|
||||
router: {
|
||||
replace: jest.fn(),
|
||||
back: jest.fn(),
|
||||
canGoBack: jest.fn(),
|
||||
},
|
||||
useLocalSearchParams: jest.fn(),
|
||||
}))
|
||||
|
||||
jest.mock("@/libs/supabase", () => ({
|
||||
supabase: {
|
||||
from: jest.fn(),
|
||||
},
|
||||
supabaseUrl: "https://example.supabase.co",
|
||||
supabaseAnonKey: "test-anon-key",
|
||||
}))
|
||||
|
||||
jest.mock("@/components/note-image-panel", () => ({
|
||||
__esModule: true,
|
||||
default: () => null,
|
||||
}))
|
||||
|
||||
describe("DetailScreen", () => {
|
||||
const mockUseLocalSearchParams = useLocalSearchParams as jest.MockedFunction<typeof useLocalSearchParams>
|
||||
const mockSupabase = supabase as unknown as {
|
||||
from: jest.Mock
|
||||
}
|
||||
|
||||
function TestWrapper({ children }: PropsWithChildren) {
|
||||
const authValue: AuthData = {
|
||||
claims: {
|
||||
sub: "user-1",
|
||||
email: "user-1@example.com",
|
||||
},
|
||||
profile: {
|
||||
id: "user-1",
|
||||
email: "user-1@example.com",
|
||||
username: null,
|
||||
full_name: null,
|
||||
},
|
||||
isLoading: false,
|
||||
isLoggedIn: true,
|
||||
}
|
||||
|
||||
return (
|
||||
<AppThemeProvider>
|
||||
<AuthContext.Provider value={authValue}>
|
||||
<NotesProvider>{children}</NotesProvider>
|
||||
</AuthContext.Provider>
|
||||
</AppThemeProvider>
|
||||
)
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
mockUseLocalSearchParams.mockReturnValue({ id: "42" })
|
||||
})
|
||||
|
||||
it("shows a loader while fetching a note, then renders the loaded content", async () => {
|
||||
const deferredNote = createDeferred<{
|
||||
data: {
|
||||
id: number
|
||||
created_by: string
|
||||
title: string
|
||||
content: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
image_url: null
|
||||
image_path: null
|
||||
image_mime_type: null
|
||||
image_size_bytes: null
|
||||
} | null
|
||||
error: null
|
||||
}>()
|
||||
const notesQuery = {
|
||||
order: jest.fn(),
|
||||
eq: jest.fn(),
|
||||
maybeSingle: jest.fn(() => deferredNote.promise),
|
||||
}
|
||||
|
||||
notesQuery.order
|
||||
.mockImplementationOnce(() => notesQuery)
|
||||
.mockImplementationOnce(() => Promise.resolve({ data: [], error: null }))
|
||||
notesQuery.eq.mockReturnValue(notesQuery)
|
||||
|
||||
mockSupabase.from.mockImplementation((table: string) => {
|
||||
if (table === "Notes") {
|
||||
return {
|
||||
select: jest.fn(() => notesQuery),
|
||||
}
|
||||
}
|
||||
|
||||
if (table === "profiles") {
|
||||
return {
|
||||
select: jest.fn(() => ({
|
||||
in: jest.fn(() =>
|
||||
Promise.resolve({
|
||||
data: [
|
||||
{
|
||||
id: "user-1",
|
||||
email: "user-1@example.com",
|
||||
username: null,
|
||||
full_name: "Exam User",
|
||||
},
|
||||
],
|
||||
error: null,
|
||||
})
|
||||
),
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Unexpected table requested in test: ${table}`)
|
||||
})
|
||||
|
||||
render(<DetailScreen />, {
|
||||
wrapper: TestWrapper,
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("note-detail-loader")).toBeTruthy()
|
||||
expect(screen.getByText("Loading note...")).toBeTruthy()
|
||||
})
|
||||
|
||||
await act(async () => {
|
||||
deferredNote.resolve({
|
||||
data: {
|
||||
id: 42,
|
||||
created_by: "user-1",
|
||||
title: "Fetched note",
|
||||
content: "Loaded from Supabase for the integration test",
|
||||
created_at: "2026-03-18T10:00:00.000Z",
|
||||
updated_at: "2026-03-18T10:05:00.000Z",
|
||||
image_url: null,
|
||||
image_path: null,
|
||||
image_mime_type: null,
|
||||
image_size_bytes: null,
|
||||
},
|
||||
error: null,
|
||||
})
|
||||
|
||||
await deferredNote.promise
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId("note-detail-loader")).toBeNull()
|
||||
expect(screen.getByDisplayValue("Fetched note")).toBeTruthy()
|
||||
expect(screen.getByDisplayValue("Loaded from Supabase for the integration test")).toBeTruthy()
|
||||
})
|
||||
})
|
||||
})
|
||||
77
FastNotes/__tests__/new-note.test.tsx
Normal file
77
FastNotes/__tests__/new-note.test.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import { fireEvent, screen, waitFor } from "@testing-library/react-native"
|
||||
import React from "react"
|
||||
|
||||
import NewNoteScreen from "@/app/newNote"
|
||||
import { useNotes } from "@/src/notes/NotesContext"
|
||||
import { renderWithTheme } from "@/test-utils/renderWithTheme"
|
||||
import { router } from "expo-router"
|
||||
|
||||
const mockAddNote = jest.fn()
|
||||
|
||||
jest.mock("expo-router", () => ({
|
||||
router: {
|
||||
canGoBack: jest.fn(),
|
||||
back: jest.fn(),
|
||||
replace: jest.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
jest.mock("@/src/notes/NotesContext", () => ({
|
||||
useNotes: jest.fn(),
|
||||
}))
|
||||
|
||||
jest.mock("@/components/note-image-panel", () => ({
|
||||
__esModule: true,
|
||||
default: () => null,
|
||||
}))
|
||||
|
||||
describe("NewNoteScreen", () => {
|
||||
const mockUseNotes = useNotes as jest.MockedFunction<typeof useNotes>
|
||||
const mockRouter = router as unknown as {
|
||||
canGoBack: jest.Mock
|
||||
back: jest.Mock
|
||||
replace: jest.Mock
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
mockAddNote.mockResolvedValue(true)
|
||||
|
||||
mockUseNotes.mockReturnValue({
|
||||
notes: [],
|
||||
isLoading: false,
|
||||
refreshNotes: jest.fn(),
|
||||
fetchNoteById: jest.fn(),
|
||||
addNote: mockAddNote,
|
||||
updateNote: jest.fn(),
|
||||
deleteNote: jest.fn(),
|
||||
errorMessage: null,
|
||||
})
|
||||
|
||||
mockRouter.canGoBack.mockReturnValue(true)
|
||||
})
|
||||
|
||||
it("submits a valid note and navigates back to the main screen", async () => {
|
||||
renderWithTheme(<NewNoteScreen />)
|
||||
|
||||
fireEvent.changeText(screen.getByPlaceholderText("Give it a title..."), "Exam note")
|
||||
fireEvent.changeText(screen.getByPlaceholderText("Write your note..."), "Testing the final assignment flow")
|
||||
fireEvent.press(screen.getByText("Save note"))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockAddNote).toHaveBeenCalledWith(
|
||||
"Exam note",
|
||||
"Testing the final assignment flow",
|
||||
null,
|
||||
expect.objectContaining({
|
||||
onImageUploadProgress: expect.any(Function),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockRouter.back).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
expect(mockRouter.replace).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user