Skip to content
Merged
12 changes: 6 additions & 6 deletions src/app/admin/reviews/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,12 @@ export default function ReviewsPage() {
}, [isAdmin, router])

const fetchReviews = useCallback(
async (currentSkip = 0) => {
async (currentSkip = 0, options?: { silent?: boolean }) => {
if (abortRef.current) abortRef.current.abort()
const ctrl = new AbortController()
abortRef.current = ctrl

setLoading(true)
if (!options?.silent) setLoading(true)
setError(null)
try {
const res = await listReviews(
Expand All @@ -107,13 +107,13 @@ export default function ReviewsPage() {
setReviews(res.reviews)
setTotal(res.total)
setSkip(currentSkip)
setSelectedIds(new Set())
if (!options?.silent) setSelectedIds(new Set())
} catch (err: unknown) {
if ((err as { name?: string })?.name !== "AbortError") {
setError("Failed to load reviews")
}
} finally {
setLoading(false)
if (!options?.silent) setLoading(false)
}
},
[statusFilter, actionFilter, sort]
Expand Down Expand Up @@ -205,7 +205,7 @@ export default function ReviewsPage() {
`${failures} of ${selectedReviews.length} ${kind === "approve" ? "approvals" : "dismissals"} failed`
)
}
await fetchReviews(skip)
await fetchReviews(skip, { silent: true })
refreshPendingCount()
}

Expand Down Expand Up @@ -400,7 +400,7 @@ export default function ReviewsPage() {
key={review.ref_id}
review={review}
schemas={schemas}
onRefresh={() => fetchReviews(skip)}
onRefresh={() => fetchReviews(skip, { silent: true })}
onCountRefresh={refreshPendingCount}
selectable={review.status === "pending"}
selected={selectedIds.has(review.ref_id)}
Expand Down
27 changes: 16 additions & 11 deletions src/lib/__tests__/reviews.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,31 +126,32 @@ describe("ReviewRow", () => {
const { getByText } = render(
<ReviewRow schemas={[]} review={makeReview({ status: "pending" })} onRefresh={noop} />
)
expect(getByText("Approve")).toBeTruthy()
// merge_nodes action uses "Merge" as the approve verb (ACTION_LABELS)
expect(getByText("Merge")).toBeTruthy()
expect(getByText("Dismiss")).toBeTruthy()
})

it("does NOT show Approve/Dismiss buttons for approved rows", () => {
const { queryByText } = render(
<ReviewRow schemas={[]} review={makeReview({ status: "approved" })} onRefresh={noop} />
)
expect(queryByText("Approve")).toBeNull()
expect(queryByText("Merge")).toBeNull()
expect(queryByText("Dismiss")).toBeNull()
})

it("does NOT show Approve/Dismiss buttons for dismissed rows", () => {
const { queryByText } = render(
<ReviewRow schemas={[]} review={makeReview({ status: "dismissed" })} onRefresh={noop} />
)
expect(queryByText("Approve")).toBeNull()
expect(queryByText("Merge")).toBeNull()
expect(queryByText("Dismiss")).toBeNull()
})

it("does NOT show Approve/Dismiss buttons for failed rows", () => {
const { queryByText } = render(
<ReviewRow schemas={[]} review={makeReview({ status: "failed" })} onRefresh={noop} />
)
expect(queryByText("Approve")).toBeNull()
expect(queryByText("Merge")).toBeNull()
expect(queryByText("Dismiss")).toBeNull()
})

Expand Down Expand Up @@ -193,8 +194,9 @@ describe("ReviewRow", () => {
)

// First click opens the confirm popover
await user.click(getByText("Approve"))
expect(getByText("Approve this merge?")).toBeTruthy()
// merge_nodes action uses "Merge" as the approve verb (ACTION_LABELS)
await user.click(getByText("Merge"))
expect(getByText("Merge these nodes?")).toBeTruthy()

// Confirm in the popover triggers the API
await user.click(getByText("Confirm"))
Expand All @@ -213,7 +215,8 @@ describe("ReviewRow", () => {
<ReviewRow schemas={[]} review={makeReview()} onRefresh={noop} />
)

await user.click(getByText("Approve"))
// merge_nodes action uses "Merge" as the approve verb (ACTION_LABELS)
await user.click(getByText("Merge"))
await user.click(getByText("Confirm"))

const errEl = await findByText(/no handler registered for action: supersede/)
Expand Down Expand Up @@ -377,9 +380,10 @@ describe("ReviewRow", () => {
onRefresh={noop}
/>
)
// Row renders with an svg icon and subject count
// soft_delete action uses rowLabel: "Hide [displayName]" — the subject node
// has name "Mock Episode", so the row label becomes "Hide Mock Episode".
expect(container.querySelector("svg")).toBeTruthy()
expect(getByText(/1 subject/)).toBeTruthy()
expect(getByText(/Hide Mock Episode/)).toBeTruthy()
})

it("renders topic_review_candidate row without errors", () => {
Expand All @@ -402,9 +406,10 @@ describe("ReviewRow", () => {
onRefresh={noop}
/>
)
// Row renders with an svg icon and subject count
// soft_delete action uses rowLabel: "Hide [displayName]" — the subject node
// has name "Orphaned Topic", so the row label becomes "Hide Orphaned Topic".
expect(container.querySelector("svg")).toBeTruthy()
expect(getByText(/1 subject/)).toBeTruthy()
expect(getByText(/Hide Orphaned Topic/)).toBeTruthy()
})
})

Expand Down
Loading