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
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,21 @@
.answerWrap {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: $sp-4;
.answerInput {
width: 120px;
}
}

.placementGuidelines {
flex-basis: 100%;
order: 1;
color: #767676;
font-size: 14px;
line-height: 20px;
}

.responseTypeWrap {
width: 240px;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/* eslint-disable import/no-extraneous-dependencies, ordered-imports/ordered-imports */
import { FC, useMemo } from 'react'
import { render, screen } from '@testing-library/react'
import { useForm } from 'react-hook-form'

import type {
ChallengeDetailContextModel,
FormReviews,
ReviewItemInfo,
ScorecardQuestion,
} from '../../../../../models'
import { ChallengeDetailContext } from '../../../../../contexts/ChallengeDetailContext'

import ScorecardQuestionEdit from './ScorecardQuestionEdit'

const mockUseScorecardViewerContext = jest.fn()

jest.mock('../../ScorecardViewer.context', () => ({
useScorecardViewerContext: () => mockUseScorecardViewerContext(),
}))

jest.mock('../../../../../utils', () => ({
getScoreResponseOptions: () => [{
label: '10',
value: '10',
}],
}))

jest.mock('~/libs/ui', () => ({
IconOutline: {
ChevronDownIcon: () => <span />,
},
}), { virtual: true })

jest.mock('~/apps/review/src/lib/assets/icons', () => ({
IconComment: () => <span />,
}), { virtual: true })

jest.mock('../../../../FieldMarkdownEditor', () => ({
FieldMarkdownEditor: () => <textarea />,
}))

jest.mock('../../../../MarkdownReview', () => ({
MarkdownReview: () => <div />,
}))

const submissionPlaceQuestion: ScorecardQuestion = {
description: 'Submission Place',
guidelines: '',
id: 'submission-place',
requiresUpload: false,
scaleMax: 10,
scaleMin: 1,
sortOrder: 0,
type: 'SCALE',
weight: 100,
}

const reviewItem: ReviewItemInfo = {
createdAt: '2026-07-30T00:00:00.000Z',
id: 'review-item-1',
initialAnswer: '10',
reviewItemComments: [],
scorecardQuestionId: 'submission-place',
}

interface QuestionHarnessProps {
question: ScorecardQuestion
trackName: string
}

/**
* Supplies the form, scorecard, and challenge contexts needed by an editable question.
*
* @param props.question scorecard question to render.
* @param props.trackName challenge track name exposed by the challenge context.
* @returns An editable scorecard question with realistic form state.
* @throws This test component does not throw.
*/
const QuestionHarness: FC<QuestionHarnessProps> = props => {
const form = useForm<FormReviews>({
defaultValues: {
reviews: [{
comments: [],
id: reviewItem.id,
index: 0,
initialAnswer: reviewItem.initialAnswer!,
scorecardQuestionId: props.question.id!,
}],
},
})
const challengeContext = useMemo(
() => ({
challengeInfo: {
track: {
name: props.trackName,
},
},
} as unknown as ChallengeDetailContextModel),
[props.trackName],
)

mockUseScorecardViewerContext.mockReturnValue({
form,
formErrors: {},
formTrigger: form.trigger,
isTouched: {},
scoreMap: new Map([[props.question.id, 100]]),
setIsTouched: jest.fn(),
toggledItems: {},
toggleItem: jest.fn(),
})

return (
<ChallengeDetailContext.Provider value={challengeContext}>
<ScorecardQuestionEdit
fieldIndex={0}
index='1.1.1'
question={props.question}
reviewItem={{
...reviewItem,
scorecardQuestionId: props.question.id!,
}}
/>
</ChallengeDetailContext.Provider>
)
}

describe('ScorecardQuestionEdit placement guidelines', () => {
beforeEach(() => {
jest.clearAllMocks()
})

it('explains placement ratings for a design Submission Place question', () => {
render(
<QuestionHarness
question={submissionPlaceQuestion}
trackName='Design'
/>,
)

const guidelines = screen.getByText(/10 - 1st place/i)

expect(guidelines.textContent)
.toContain('2 and 1 - no placement')
})

it('does not show placement ratings for a non-design challenge', () => {
render(
<QuestionHarness
question={submissionPlaceQuestion}
trackName='Development'
/>,
)

expect(screen.queryByText(/10 - 1st place/i))
.toBeNull()
})

it('does not show placement ratings for another design scorecard question', () => {
render(
<QuestionHarness
question={{
...submissionPlaceQuestion,
description: 'Visual Quality',
}}
trackName='Design'
/>,
)

expect(screen.queryByText(/10 - 1st place/i))
.toBeNull()
})
})
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { FC, useCallback, useMemo } from 'react'
import {
FC,
useCallback,
useContext,
useMemo,
} from 'react'
import {
Controller,
ControllerRenderProps,
Expand All @@ -12,14 +17,19 @@ import classNames from 'classnames'
import { IconOutline } from '~/libs/ui'
import { IconComment } from '~/apps/review/src/lib/assets/icons'

import { QUESTION_RESPONSE_OPTIONS } from '../../../../../../config/index.config'
import {
DESIGN,
QUESTION_RESPONSE_OPTIONS,
} from '../../../../../../config/index.config'
import { getScoreResponseOptions } from '../../../../../utils'
import {
ChallengeDetailContextModel,
FormReviews,
ReviewItemInfo,
ScorecardQuestion,
SelectOption,
} from '../../../../../models'
import { ChallengeDetailContext } from '../../../../../contexts/ChallengeDetailContext'
import { FieldMarkdownEditor } from '../../../../FieldMarkdownEditor'
import { MarkdownReview } from '../../../../MarkdownReview'
import { ScorecardViewerContextValue, useScorecardViewerContext } from '../../ScorecardViewer.context'
Expand All @@ -37,6 +47,7 @@ interface ScorecardQuestionEditProps {
}

export const ScorecardQuestionEdit: FC<ScorecardQuestionEditProps> = props => {
const { challengeInfo }: ChallengeDetailContextModel = useContext(ChallengeDetailContext)
const {
toggleItem,
toggledItems,
Expand All @@ -61,6 +72,13 @@ export const ScorecardQuestionEdit: FC<ScorecardQuestionEditProps> = props => {
const responseOptions = useMemo<SelectOption[]>(() => (
getScoreResponseOptions(props.question)
), [props.question])
const showPlacementGuidelines = (
challengeInfo?.track?.name?.trim()
.toLowerCase() === DESIGN.toLowerCase()
&& props.question.type === 'SCALE'
&& props.question.description.trim()
.toLowerCase() === 'submission place'
)

const {
fields,
Expand Down Expand Up @@ -205,6 +223,16 @@ export const ScorecardQuestionEdit: FC<ScorecardQuestionEditProps> = props => {
isDisabled={props.disabled}
/>

{showPlacementGuidelines && (
<div className={styles.placementGuidelines}>
10 - 1st place, 9 - 2nd place, 8 - 3rd place,
{' '}
7 - 4th place, 6 - 5th place, 5 - 6th place,
{' '}
4 - 7th place, 3 - 8th place, 2 and 1 - no placement.
</div>
)}

{errorMessage && (
<div className={styles.errorMessage}>
{errorMessage}
Expand Down
Loading