diff --git a/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/SubmissionsSection/SubmissionsSection.spec.tsx b/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/SubmissionsSection/SubmissionsSection.spec.tsx
new file mode 100644
index 000000000..f0d58c0b4
--- /dev/null
+++ b/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/SubmissionsSection/SubmissionsSection.spec.tsx
@@ -0,0 +1,238 @@
+/* eslint-disable global-require, import/no-extraneous-dependencies */
+/* eslint-disable @typescript-eslint/no-var-requires, ordered-imports/ordered-imports */
+import {
+ fireEvent,
+ render,
+ screen,
+} from '@testing-library/react'
+
+import type {
+ Challenge,
+ Submission,
+} from '../../../../../lib/models'
+
+import { SubmissionsSection } from './SubmissionsSection'
+
+const mockUseDownloadAllSubmissions = jest.fn()
+const mockUseDownloadSubmission = jest.fn()
+const mockUseFetchSubmissions = jest.fn()
+const mockFetchMembersByUserIds = jest.fn()
+const mockIsMarathonMatchChallenge = jest.fn()
+
+jest.mock('~/libs/ui', () => ({
+ Button: (props: {
+ disabled?: boolean
+ label: string
+ onClick?: () => void
+ }): JSX.Element => (
+
+ ),
+}), {
+ virtual: true,
+})
+
+jest.mock('../../../../../lib/assets/icons/lock.svg', () => ({
+ ReactComponent: (): JSX.Element => ,
+}))
+
+jest.mock('../../../../../lib/components', () => ({
+ ArtifactsModal: (): JSX.Element =>
Artifacts modal
,
+ Pagination: (props: { total: number }): JSX.Element => (
+ {props.total}
+ ),
+ SubmissionRunnerLogsModal: (): JSX.Element => Runner logs modal
,
+ SubmissionsTable: (props: { submissions: Submission[] }): JSX.Element => (
+
+ {props.submissions.map(submission => (
+
+ {submission.id}
+ {submission.memberHandle}
+
+ ))}
+
+ ),
+}))
+
+jest.mock('../../../../../lib/constants', () => ({
+ PAGE_SIZE: 10,
+}))
+
+jest.mock('../../../../../lib/contexts', () => {
+ const React = require('react') as typeof import('react')
+
+ return {
+ WorkAppContext: React.createContext({
+ isAdmin: false,
+ isAnonymous: false,
+ isCopilot: false,
+ isManager: false,
+ isReadOnly: false,
+ loginUserInfo: undefined,
+ userRoles: [],
+ }),
+ }
+})
+
+jest.mock('../../../../../lib/hooks', () => ({
+ useDownloadAllSubmissions: (): unknown => mockUseDownloadAllSubmissions(),
+ useDownloadSubmission: (): unknown => mockUseDownloadSubmission(),
+ useFetchSubmissions: (...args: unknown[]): unknown => mockUseFetchSubmissions(...args),
+}))
+
+jest.mock('../../../../../lib/services', () => ({
+ fetchMembersByUserIds: (...args: unknown[]): unknown => mockFetchMembersByUserIds(...args),
+}))
+
+jest.mock('../../../../../lib/utils', () => ({
+ canDownloadSubmissions: (): boolean => false,
+ canViewMarathonMatchRunnerLogs: (): boolean => false,
+ getSubmissionFinalScore: (): number => 0,
+ getSubmissionInitialScore: (): number => 0,
+ getSubmissionProvisionalScore: (): number => 0,
+ getSubmissionSystemScore: (): number => 0,
+ getSubmissionTestProgress: (
+ submission: {
+ reviewSummation?: Array<{
+ metadata?: {
+ testProcess?: string
+ testType?: string
+ }
+ }>
+ },
+ ): { process?: string } => {
+ const metadata = submission.reviewSummation?.[0]?.metadata
+
+ return {
+ process: metadata?.testProcess ?? metadata?.testType,
+ }
+ },
+ isMarathonMatchChallenge: (...args: unknown[]): unknown => mockIsMarathonMatchChallenge(...args),
+ showErrorToast: jest.fn(),
+}))
+
+jest.mock('./SubmissionsSection.module.scss', () => new Proxy({}, {
+ get: (_target, property) => String(property),
+}))
+
+const baseChallenge: Challenge = {
+ id: 'challenge-1',
+ name: 'Marathon Match',
+ status: 'ACTIVE',
+}
+
+const submissions: Submission[] = [
+ {
+ challengeId: 'challenge-1',
+ createdAt: '2026-07-01T10:00:00.000Z',
+ createdBy: 'member-1',
+ id: 'system-submission',
+ memberHandle: 'system-user',
+ reviewSummation: [
+ {
+ metadata: {
+ testProcess: 'system',
+ },
+ },
+ ],
+ type: 'SUBMISSION',
+ },
+ {
+ challengeId: 'challenge-1',
+ createdAt: '2026-07-01T11:00:00.000Z',
+ createdBy: 'member-2',
+ id: 'provisional-submission',
+ memberHandle: 'provisional-user',
+ reviewSummation: [
+ {
+ metadata: {
+ testProcess: 'provisional',
+ },
+ },
+ ],
+ type: 'SUBMISSION',
+ },
+ {
+ challengeId: 'challenge-1',
+ createdAt: '2026-07-01T12:00:00.000Z',
+ createdBy: 'member-3',
+ id: 'example-submission',
+ memberHandle: 'example-user',
+ reviewSummation: [
+ {
+ metadata: {
+ testType: 'example',
+ },
+ },
+ ],
+ type: 'SUBMISSION',
+ },
+]
+
+describe('SubmissionsSection', () => {
+ beforeEach(() => {
+ jest.clearAllMocks()
+
+ mockIsMarathonMatchChallenge.mockReturnValue(true)
+ mockUseDownloadAllSubmissions.mockReturnValue({
+ downloadAll: jest.fn(),
+ isDownloading: false,
+ progress: 0,
+ })
+ mockUseDownloadSubmission.mockReturnValue({
+ downloadSubmission: jest.fn(),
+ isLoading: {},
+ })
+ mockUseFetchSubmissions.mockReturnValue({
+ error: undefined,
+ isError: false,
+ isLoading: false,
+ mutate: jest.fn(),
+ submissions,
+ total: submissions.length,
+ })
+ mockFetchMembersByUserIds.mockResolvedValue([])
+ })
+
+ it('filters marathon submissions by test type', () => {
+ render(
+ ,
+ )
+
+ fireEvent.change(screen.getByLabelText('Test type'), {
+ target: {
+ value: 'system',
+ },
+ })
+
+ expect(screen.getByText('system-submission'))
+ .toBeTruthy()
+ expect(screen.queryByText('provisional-submission'))
+ .toBeNull()
+ expect(screen.queryByText('example-submission'))
+ .toBeNull()
+ expect(screen.getByTestId('pagination-total').textContent)
+ .toBe('1')
+
+ fireEvent.change(screen.getByLabelText('Test type'), {
+ target: {
+ value: 'example',
+ },
+ })
+
+ expect(screen.getByText('example-submission'))
+ .toBeTruthy()
+ expect(screen.queryByText('system-submission'))
+ .toBeNull()
+ expect(screen.queryByText('provisional-submission'))
+ .toBeNull()
+ })
+})
diff --git a/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/SubmissionsSection/SubmissionsSection.tsx b/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/SubmissionsSection/SubmissionsSection.tsx
index 70662c63d..f73961cd5 100644
--- a/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/SubmissionsSection/SubmissionsSection.tsx
+++ b/src/apps/work/src/pages/challenges/ChallengeEditorPage/components/SubmissionsSection/SubmissionsSection.tsx
@@ -36,6 +36,7 @@ import {
getSubmissionInitialScore,
getSubmissionProvisionalScore,
getSubmissionSystemScore,
+ getSubmissionTestProgress,
isMarathonMatchChallenge,
showErrorToast,
} from '../../../../../lib/utils'
@@ -57,6 +58,7 @@ interface FilterState {
handle: string
minScore: string
startDate: string
+ testType: string
}
interface SubmissionsSectionProps {
@@ -330,6 +332,37 @@ function matchesFilterHandle(submission: Submission, handleFilter: string): bool
.includes(normalizedHandleFilter)
}
+/**
+ * Checks whether a submission matches the selected marathon test type.
+ * @param submission Submission row being evaluated.
+ * @param testTypeFilter Selected normalized test type filter value.
+ * @param useMarathonMatchScores Whether the current challenge is a Marathon Match.
+ * @returns True when the filter is empty, the challenge is not Marathon Match, or the current test type matches.
+ * Used by `matchesFilters` before the submissions table is sorted and paginated.
+ */
+function matchesFilterTestType(
+ submission: Submission,
+ testTypeFilter: string,
+ useMarathonMatchScores: boolean,
+): boolean {
+ if (!testTypeFilter || !useMarathonMatchScores) {
+ return true
+ }
+
+ const normalizedTestTypeFilter = normalizeValue(testTypeFilter)
+ .toLowerCase()
+
+ return getSubmissionTestProgress(submission).process === normalizedTestTypeFilter
+}
+
+/**
+ * Checks whether a submission satisfies all active submissions list filters.
+ * @param submission Submission row being evaluated.
+ * @param filters Current filter values from the submissions form.
+ * @param useMarathonMatchScores Whether the current challenge is a Marathon Match.
+ * @returns True when the submission should remain visible in the table.
+ * Used before sorting and paginating the submissions list.
+ */
function matchesFilters(
submission: Submission,
filters: FilterState,
@@ -343,6 +376,10 @@ function matchesFilters(
return false
}
+ if (!matchesFilterTestType(submission, filters.testType, useMarathonMatchScores)) {
+ return false
+ }
+
return matchesFilterScore(submission, filters.minScore, useMarathonMatchScores)
}
@@ -361,6 +398,7 @@ export const SubmissionsSection: FC = (
handle: '',
minScore: '',
startDate: '',
+ testType: '',
})
const [memberCache, setMemberCache] = useState({})
const [page, setPage] = useState(1)
@@ -584,7 +622,9 @@ export const SubmissionsSection: FC = (
setPerPage(nextPerPage)
}, [])
- const handleFilterChange = useCallback((event: ChangeEvent): void => {
+ const handleFilterChange = useCallback((
+ event: ChangeEvent,
+ ): void => {
const fieldName: string = event.target.name
const fieldValue: string = event.target.value
@@ -642,6 +682,26 @@ export const SubmissionsSection: FC = (
/>
+ {isMarathonMatch
+ ? (
+
+ )
+ : undefined}
+