Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/apps/review/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,19 @@ sudo yarn start

- Mock data files are under src/apps/review/src/mock-datas

### Design checkpoint screening:

- Checkpoint Screening keeps each submission returned by the Review API visible,
including an older submission from the same member when the Design submission
limit is greater than one. Each pending row links to its own screening scorecard
on desktop and mobile.
- The submissions API must return the configured review window to assigned Design
screeners and reviewers, ranking checkpoint and final submissions independently.
An `isLatest: false` submission within that window is still eligible for review.
The frontend cannot recover rows removed by the API's history-privacy filter.
- PM-6307 regression coverage uses two submissions from one member, with the older
screening still pending, plus a completed submission from another member.

### Winners result identity:

- The Winners tab loads every page from the Review API `projectResult` endpoint.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,44 @@ import type {
ReviewAppContextModel,
Screening,
} from '../../models'
import { TabContentCheckpoint } from '../ChallengeDetailsContent/TabContentCheckpoint'

import { TableCheckpointSubmissions } from './TableCheckpointSubmissions'

let mockScreenWidth = 1200

interface TestColumn {
label?: ReactNode
renderer?: (row: Screening, rows: Screening[]) => JSX.Element
}

/**
* Renders checkpoint IDs, scores, and actions using the table's real column renderers.
* Shared by desktop/mobile table mocks to verify row visibility and scorecard navigation.
*
* @param props - Visible rows and desktop or grouped mobile column definitions.
* @returns The relevant rendered cells for each submission.
* @throws Propagates errors from the production column renderers.
*/
function mockRenderTable(props: { columns: Array<TestColumn | TestColumn[]>, data: Screening[] }): JSX.Element {
const columns = props.columns.flat()
.filter(column => ['Submission ID', 'Screening Score', 'Review Score', 'Action'].includes(String(column.label)))

return (
<div>
{props.data.map(row => (
<div key={row.submissionId} data-testid={row.submissionId}>
{columns.map(column => (
<div key={String(column.label)}>
{column.renderer?.(row, props.data)}
</div>
))}
</div>
))}
</div>
)
}

jest.mock('react-router-dom', () => ({
Link: (props: PropsWithChildren<{ className?: string, to: string }>) => (
<a className={props.className} href={props.to}>{props.children}</a>
Expand All @@ -37,12 +72,16 @@ jest.mock('~/libs/shared', () => ({
copyTextToClipboard: () => Promise.resolve(),
useWindowSize: () => ({
height: 800,
width: 1200,
width: mockScreenWidth,
}),
}), { virtual: true })

jest.mock('~/apps/admin/src/lib/components/common/TableMobile', () => ({
TableMobile: () => <div>Mobile table</div>,
TableMobile: (props: { columns: TestColumn[][], data: Screening[] }) => mockRenderTable(props),
}), { virtual: true })

jest.mock('~/apps/admin/src/lib', () => ({
TableLoading: () => <div>Loading</div>,
}), { virtual: true })

jest.mock('~/apps/admin/src/lib/utils', () => ({
Expand All @@ -57,25 +96,7 @@ jest.mock('~/libs/ui', () => ({
IconSolid: {
StarIcon: () => <span data-testid='checkpoint-winner-star' />,
},
Table: (props: {
columns: Array<{
label?: ReactNode
renderer?: (row: Screening, rows: Screening[]) => JSX.Element
}>
data: Screening[]
}) => {
const scoreColumn = props.columns.find(column => column.label === 'Review Score')

return (
<div>
{props.data.map(row => (
<div key={row.submissionId}>
{scoreColumn?.renderer?.(row, props.data)}
</div>
))}
</div>
)
},
Table: (props: { columns: TestColumn[], data: Screening[] }) => mockRenderTable(props),
Tooltip: (props: PropsWithChildren<{
content?: ReactNode
triggerOn?: string
Expand All @@ -100,6 +121,7 @@ jest.mock('../../contexts', () => {
})

jest.mock('../../hooks', () => ({
useRole: jest.requireActual('../../hooks/useRole').useRole,
useRolePermissions: () => ({
canViewAllSubmissions: true,
}),
Expand Down Expand Up @@ -195,6 +217,7 @@ const challengeInfo = {

const challengeContext = {
challengeInfo,
duplicatesBySubmissionId: {},
myResources: [],
myRoles: [],
} as unknown as ChallengeDetailContextModel
Expand Down Expand Up @@ -231,6 +254,10 @@ function renderCheckpointTable(): ReturnType<typeof render> {
}

describe('TableCheckpointSubmissions checkpoint winner indicator', () => {
beforeEach(() => {
mockScreenWidth = 1200
})

it('marks only passing rows whose member id matches a checkpoint winner', () => {
renderCheckpointTable()

Expand Down Expand Up @@ -260,3 +287,91 @@ describe('TableCheckpointSubmissions checkpoint winner indicator', () => {
.toBe('./../reviews/non-winner-submission?reviewId=non-winner-review')
})
})

describe('checkpoint screening multiple Design submissions (PM-6307)', () => {
const screenings: Screening[] = [
{
...winnerRow,
isLatest: true,
myReviewId: 'review-new',
myReviewResourceId: 'checkpoint-screener',
myReviewStatus: 'COMPLETED',
reviewId: 'review-new',
reviewStatus: 'COMPLETED',
submissionId: 'checkpoint-new',
type: 'CHECKPOINT_SUBMISSION',
},
{
...winnerRow,
isLatest: false,
myReviewId: 'review-pending',
myReviewResourceId: 'checkpoint-screener',
myReviewStatus: 'PENDING',
result: '-',
reviewId: 'review-pending',
reviewStatus: 'PENDING',
score: 'Pending',
submissionId: 'checkpoint-pending',
type: 'CHECKPOINT_SUBMISSION',
},
{
...nonWinnerRow,
isLatest: true,
myReviewId: 'review-other',
myReviewResourceId: 'checkpoint-screener',
myReviewStatus: 'COMPLETED',
reviewId: 'review-other',
reviewStatus: 'COMPLETED',
type: 'CHECKPOINT_SUBMISSION',
},
]

it.each([1200, 640])('keeps the older pending submission actionable at width %s', width => {
mockScreenWidth = width
const context = {
...challengeContext,
challengeId: 'challenge-id',
challengeInfo: {
...challengeInfo,
currentPhase: 'Checkpoint Screening',
metadata: [{
name: 'submissionLimit',
value: '{"count":"2","limit":"true","unlimited":"false"}',
}],
status: 'Active',
},
myResources: [{
id: 'checkpoint-screener',
memberId: 'screener-member',
roleName: 'Checkpoint Screener',
}],
myRoles: ['Checkpoint Screener'],
} as ChallengeDetailContextModel

render(
<ReviewAppContext.Provider value={reviewAppContext}>
<ChallengeDetailContext.Provider value={context}>
<TabContentCheckpoint
checkpoint={screenings}
downloadSubmission={jest.fn()}
isDownloading={{}}
isLoading={false}
mode='screening'
/>
</ChallengeDetailContext.Provider>
</ReviewAppContext.Provider>,
)

screenings.forEach(row => {
expect(screen.getByRole('button', { name: row.submissionId }))
.toBeTruthy()
})
const pendingRow = within(screen.getByTestId('checkpoint-pending'))
expect(pendingRow.getByRole('link', { name: 'Complete Screening' })
.getAttribute('href'))
.toBe('./../reviews/checkpoint-pending?reviewId=review-pending')
expect(pendingRow.getByRole('link', { name: 'Pending' })
.getAttribute('href'))
.toBe('./../reviews/checkpoint-pending?reviewId=review-pending')
})
})
31 changes: 31 additions & 0 deletions src/apps/review/src/lib/utils/reviewProgress.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,37 @@ describe('calculateReviewProgress', () => {
.toBe(50)
})

it('counts an older pending Design checkpoint submission toward progress (PM-6307)', () => {
const progress = calculateReviewProgress({
challengePhases: [createPhase('Checkpoint Screening')],
checkpointReviewRows: [],
checkpointScreeningRows: [
createScreeningRow('checkpoint-new', 'PASS', {
isLatest: true,
memberId: 'member-one',
reviewStatus: 'COMPLETED',
}),
createScreeningRow('checkpoint-older', '-', {
isLatest: false,
memberId: 'member-one',
reviewStatus: 'PENDING',
}),
createScreeningRow('checkpoint-other', 'PASS', {
isLatest: true,
memberId: 'member-two',
reviewStatus: 'COMPLETED',
}),
],
currentPhaseName: 'Checkpoint Screening',
isDesignChallenge: true,
reviewRows: [],
screeningRows: [],
})

expect(progress)
.toBe(67)
})

it('counts each reviewer assignment in multi-screener phase progress', () => {
const screeningRows: Screening[] = [
createScreeningRow('submission-one', '-', {
Expand Down
Loading