Use Rubric component for summary
Fixes OUT-2268 Test Plan: - Open speedgrader, navigate to an assessment of a free-form rubric. Verify: - the new UI is present before clicking "view rubric" - the summary rubric only has two columns - each criteria lists points assessed out of points possible right after the comments. - Now go to an assignment with a points-based rubric. Verify: - the spiky UI is present - the summary rubric only has two columns - each criteria lists the points assessment and comments right after the description of the rating. - Navigate to a student without an assessment using the top right arrow keys - Verify there is no summary rubric shown - Navigate back to the student with an assessment, verify the summary is shown again. Change-Id: Idae819264c507e42d67a6731367d2ddef5cd176d Reviewed-on: https://gerrit.instructure.com/152226 Tested-by: Jenkins Reviewed-by: Augusto Callejas <acallejas@instructure.com> Reviewed-by: Michael Brewer-Davis <mbd@instructure.com> QA-Review: Matt Berns <mberns@instructure.com> Product-Review: Sidharth Oberoi <soberoi@instructure.com>
This commit is contained in:
parent
e2b0a33311
commit
3ceaed757f
|
@ -28,7 +28,7 @@ exports[`renders the AlignmentResult component 1`] = `
|
|||
<FlexItem borderWidth=\\"small\\" as=\\"span\\" grow={false} shrink={false}>
|
||||
<div className=\\"react-rubric\\">
|
||||
<div className=\\"ratings\\">
|
||||
<Ratings tiers={{...}} points={1} onPointChange={[Function]} />
|
||||
<Ratings tiers={{...}} points={1} footer={{...}} onPointChange={[Function]} />
|
||||
</div>
|
||||
</div>
|
||||
</FlexItem>
|
||||
|
|
|
@ -104,7 +104,7 @@ CommentText.defaultProps = {
|
|||
}
|
||||
|
||||
const Comments = (props) => {
|
||||
const { assessing, assessment, ...commentProps } = props
|
||||
const { assessing, assessment, footer, ...commentProps } = props
|
||||
if (!assessing || assessment === null) {
|
||||
return (
|
||||
<div className="rubric-freeform">
|
||||
|
@ -113,6 +113,7 @@ const Comments = (props) => {
|
|||
placeholder={I18n.t("This area will be used by the assessor to leave comments related to this criterion.")}
|
||||
weight="normal"
|
||||
/>
|
||||
{footer}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -129,9 +130,13 @@ const Comments = (props) => {
|
|||
Comments.propTypes = {
|
||||
assessing: PropTypes.bool.isRequired,
|
||||
assessment: PropTypes.shape(assessmentShape),
|
||||
footer: PropTypes.node,
|
||||
savedComments: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
setComments: PropTypes.func.isRequired,
|
||||
setSaveLater: PropTypes.func.isRequired
|
||||
}
|
||||
Comments.defaultProps = {
|
||||
footer: null
|
||||
}
|
||||
|
||||
export default Comments
|
||||
|
|
|
@ -116,10 +116,11 @@ export default class Criterion extends React.Component {
|
|||
const {
|
||||
assessment,
|
||||
criterion,
|
||||
customRatings,
|
||||
freeForm,
|
||||
onAssessmentChange,
|
||||
savedComments,
|
||||
customRatings
|
||||
isSummary
|
||||
} = this.props
|
||||
const { dialogOpen } = this.state
|
||||
const isOutcome = criterion.learning_outcome_id !== undefined
|
||||
|
@ -135,24 +136,49 @@ export default class Criterion extends React.Component {
|
|||
}
|
||||
const onPointChange = assessing ? updatePoints : undefined
|
||||
|
||||
const pointsPossible = criterion.points
|
||||
const pointsElement = () => (
|
||||
<Points
|
||||
key="points"
|
||||
assessing={assessing}
|
||||
assessment={assessment}
|
||||
onPointChange={onPointChange}
|
||||
pointsPossible={pointsPossible}
|
||||
/>
|
||||
)
|
||||
|
||||
const pointsComment = () => (
|
||||
<CommentText key="comment" assessment={assessment} weight="light" />
|
||||
)
|
||||
|
||||
const pointsFooter = () => [
|
||||
pointsComment(),
|
||||
pointsElement()
|
||||
]
|
||||
|
||||
const commentRating = (
|
||||
<Comments
|
||||
assessing={assessing}
|
||||
assessment={assessment}
|
||||
footer={isSummary ? pointsElement() : null}
|
||||
savedComments={savedComments}
|
||||
setSaveLater={(saveCommentsForLater) => onAssessmentChange({ saveCommentsForLater })}
|
||||
setComments={(comments) => onAssessmentChange({ comments })}
|
||||
/>
|
||||
)
|
||||
|
||||
const ratings = freeForm ? commentRating : (
|
||||
<Ratings
|
||||
assessing={assessing}
|
||||
customRatings={customRatings}
|
||||
footer={isSummary ? pointsFooter() : null}
|
||||
tiers={criterion.ratings}
|
||||
onPointChange={onPointChange}
|
||||
points={_.get(assessment, 'points')}
|
||||
useRange={useRange}
|
||||
pointsPossible={pointsPossible}
|
||||
defaultMasteryThreshold={isOutcome ? criterion.mastery_points : criterion.points}
|
||||
customRatings={customRatings}
|
||||
isSummary={isSummary}
|
||||
useRange={useRange}
|
||||
/>
|
||||
)
|
||||
|
||||
|
@ -181,7 +207,6 @@ export default class Criterion extends React.Component {
|
|||
) : null
|
||||
|
||||
const noComments = (_.get(assessment, 'comments') || '').length > 0
|
||||
const pointsPossible = criterion.points
|
||||
const longDescription = criterion.long_description
|
||||
const threshold = criterion.mastery_points
|
||||
|
||||
|
@ -213,7 +238,7 @@ export default class Criterion extends React.Component {
|
|||
(freeForm || assessing || noComments) ? null : (
|
||||
<div className="assessment-comments">
|
||||
<Text size="x-small" weight="normal">{I18n.t('Instructor Comments')}</Text>
|
||||
<CommentText assessment={assessment} weight="light" />
|
||||
{pointsComment()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -221,30 +246,31 @@ export default class Criterion extends React.Component {
|
|||
<td className="ratings">
|
||||
{ratings}
|
||||
</td>
|
||||
<td>
|
||||
<Points
|
||||
assessing={assessing}
|
||||
assessment={assessment}
|
||||
onPointChange={onPointChange}
|
||||
pointsPossible={pointsPossible}
|
||||
/>
|
||||
{assessing && !freeForm ? commentInput : null}
|
||||
</td>
|
||||
{
|
||||
!isSummary ? (
|
||||
<td>
|
||||
{pointsElement()}
|
||||
{assessing && !freeForm ? commentInput : null}
|
||||
</td>
|
||||
) : null
|
||||
}
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
}
|
||||
Criterion.propTypes = {
|
||||
assessment: PropTypes.shape(assessmentShape),
|
||||
customRatings: PropTypes.arrayOf(PropTypes.object),
|
||||
criterion: PropTypes.shape(criterionShape).isRequired,
|
||||
freeForm: PropTypes.bool.isRequired,
|
||||
onAssessmentChange: PropTypes.func,
|
||||
savedComments: PropTypes.arrayOf(PropTypes.string),
|
||||
customRatings: PropTypes.arrayOf(PropTypes.object)
|
||||
isSummary: PropTypes.bool
|
||||
}
|
||||
Criterion.defaultProps = {
|
||||
assessment: null,
|
||||
customRatings: [],
|
||||
onAssessmentChange: null,
|
||||
savedComments: [],
|
||||
customRatings: []
|
||||
isSummary: false
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ import { assessmentShape } from './types'
|
|||
export const roundIfWhole = (n) => (
|
||||
I18n.toNumber(n, { precision: Math.floor(n) === n ? 0 : 1 })
|
||||
)
|
||||
const pointString = (n) => n !== null ? roundIfWhole(n) : ''
|
||||
const pointString = (n) => n !== undefined ? roundIfWhole(n) : '--'
|
||||
|
||||
export const possibleString = (possible) =>
|
||||
I18n.t('%{possible} pts', {
|
||||
|
@ -36,13 +36,13 @@ export const possibleString = (possible) =>
|
|||
|
||||
export const scoreString = (points, possible) =>
|
||||
I18n.t('%{points} / %{possible}', {
|
||||
points: roundIfWhole(points),
|
||||
points: pointString(points),
|
||||
possible: possibleString(possible)
|
||||
})
|
||||
|
||||
const invalid = () => [{ text: I18n.t('Invalid value'), type: 'error' }]
|
||||
const messages = (points, pointsText) =>
|
||||
(points === null && pointsText) ? invalid() : undefined
|
||||
(_.isNil(points) && pointsText) ? invalid() : undefined
|
||||
|
||||
const Points = (props) => {
|
||||
const {
|
||||
|
@ -68,6 +68,7 @@ const Points = (props) => {
|
|||
</div>
|
||||
)
|
||||
} else {
|
||||
const usePointsText = pointsText !== null && pointsText !== undefined
|
||||
return (
|
||||
<div className="container graded-points">
|
||||
<TextInput
|
||||
|
@ -75,7 +76,7 @@ const Points = (props) => {
|
|||
label={<ScreenReaderContent>{I18n.t('Points')}</ScreenReaderContent>}
|
||||
messages={messages(points, pointsText)}
|
||||
onChange={(e) => onPointChange(e.target.value)}
|
||||
value={pointsText || pointString(points)}
|
||||
value={usePointsText ? pointsText : pointString(points)}
|
||||
width="4rem"
|
||||
/> {`/ ${possibleString(pointsPossible)}`}
|
||||
</div>
|
||||
|
|
|
@ -42,17 +42,27 @@ const pointString = (points, endOfRangePoints) => {
|
|||
export const Rating = (props) => {
|
||||
const {
|
||||
assessing,
|
||||
classes,
|
||||
description,
|
||||
endOfRangePoints,
|
||||
footer,
|
||||
long_description,
|
||||
points,
|
||||
onClick,
|
||||
endOfRangePoints,
|
||||
classes,
|
||||
isSummary,
|
||||
tierColor
|
||||
} = props
|
||||
|
||||
const shaderStyle = {backgroundColor: tierColor}
|
||||
const triangleStyle = {borderBottomColor: tierColor}
|
||||
const shaderStyle = { backgroundColor: tierColor }
|
||||
const triangleStyle = { borderBottomColor: tierColor }
|
||||
|
||||
const ratingPoints = () => (
|
||||
<div className="rating-points">
|
||||
<Text size="x-small">
|
||||
{pointString(points, endOfRangePoints)}
|
||||
</Text>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -62,11 +72,7 @@ export const Rating = (props) => {
|
|||
role="button"
|
||||
tabIndex={assessing ? 0 : null}
|
||||
>
|
||||
<div className="rating-points">
|
||||
<Text size="x-small">
|
||||
{pointString(points, endOfRangePoints)}
|
||||
</Text>
|
||||
</div>
|
||||
{isSummary ? null : ratingPoints()}
|
||||
<div className="rating-description">
|
||||
<Text size="x-small" lineHeight="condensed">
|
||||
{description}
|
||||
|
@ -75,6 +81,13 @@ export const Rating = (props) => {
|
|||
<Text size="x-small" fontStyle="italic" lineHeight="condensed">
|
||||
{long_description}
|
||||
</Text>
|
||||
{
|
||||
footer !== null ? (
|
||||
<div className="rating-footer">
|
||||
{footer}
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
<div className='shader' style={shaderStyle}>
|
||||
<div className="triangle" style={triangleStyle}/>
|
||||
</div>
|
||||
|
@ -105,9 +118,12 @@ const getCustomColor = (points, customRatings) => {
|
|||
Rating.propTypes = {
|
||||
...tierShape,
|
||||
assessing: PropTypes.bool.isRequired,
|
||||
selected: PropTypes.bool
|
||||
footer: PropTypes.node,
|
||||
selected: PropTypes.bool,
|
||||
isSummary: PropTypes.bool.isRequired
|
||||
}
|
||||
Rating.defaultProps = {
|
||||
footer: null,
|
||||
selected: false,
|
||||
endOfRangePoints: null // eslint-disable-line react/default-props-match-prop-types
|
||||
}
|
||||
|
@ -115,12 +131,14 @@ Rating.defaultProps = {
|
|||
const Ratings = (props) => {
|
||||
const {
|
||||
assessing,
|
||||
customRatings,
|
||||
defaultMasteryThreshold,
|
||||
footer,
|
||||
tiers,
|
||||
points,
|
||||
onPointChange,
|
||||
defaultMasteryThreshold,
|
||||
useRange,
|
||||
customRatings
|
||||
isSummary,
|
||||
useRange
|
||||
} = props
|
||||
|
||||
const pairs = tiers.map((tier, index) => {
|
||||
|
@ -154,38 +172,55 @@ const Ratings = (props) => {
|
|||
}
|
||||
|
||||
const selectedIndex = points !== undefined ? currentIndex() : null
|
||||
const ratings = tiers.map((tier, index) => {
|
||||
const selected = selectedIndex === index
|
||||
if (isSummary && !selected) return null
|
||||
const classes = classNames({
|
||||
'rating-tier': true,
|
||||
'selected': selected,
|
||||
})
|
||||
|
||||
return (
|
||||
<Rating
|
||||
key={index} // eslint-disable-line react/no-array-index-key
|
||||
assessing={assessing}
|
||||
classes={classes}
|
||||
endOfRangePoints={useRange ? getRangePoints(tier.points, tiers[index + 1]) : null}
|
||||
footer={footer}
|
||||
isSummary={isSummary}
|
||||
onClick={() => onPointChange(tier.points)}
|
||||
tierColor={getTierColor(selected)}
|
||||
{...tier}
|
||||
/>
|
||||
)
|
||||
}).filter((v) => v !== null)
|
||||
|
||||
const defaultRating = () => (
|
||||
<Rating
|
||||
key={0}
|
||||
classes="rating-tier"
|
||||
description={I18n.t('No details')}
|
||||
footer={footer}
|
||||
points={0}
|
||||
isSummary
|
||||
/>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className={classNames("rating-tier-list", { 'react-assessing': assessing })}>
|
||||
{
|
||||
tiers.map((tier, index) => {
|
||||
const selected = selectedIndex === index
|
||||
const classes = classNames({
|
||||
'rating-tier': true,
|
||||
'selected': selected,
|
||||
})
|
||||
return (
|
||||
<Rating
|
||||
key={index} // eslint-disable-line react/no-array-index-key
|
||||
assessing={assessing}
|
||||
onClick={() => onPointChange(tier.points)}
|
||||
classes={classes}
|
||||
endOfRangePoints={useRange ? getRangePoints(tier.points, tiers[index + 1]) : null}
|
||||
tierColor={getTierColor(selected)}
|
||||
{...tier}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
{ratings.length > 0 || !isSummary ? ratings : defaultRating()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Ratings.propTypes = {
|
||||
...ratingShape,
|
||||
assessing: PropTypes.bool.isRequired,
|
||||
onPointChange: PropTypes.func
|
||||
footer: PropTypes.node,
|
||||
onPointChange: PropTypes.func,
|
||||
isSummary: PropTypes.bool.isRequired
|
||||
}
|
||||
Ratings.defaultProps = {
|
||||
footer: null,
|
||||
onPointChange: () => { }
|
||||
}
|
||||
|
||||
|
|
|
@ -37,7 +37,15 @@ const totalAssessingString = (score, possible) =>
|
|||
possible: I18n.toNumber(possible, { precision: 1 })
|
||||
})
|
||||
|
||||
const Rubric = ({ onAssessmentChange, rubric, rubricAssessment, rubricAssociation, customRatings }) => {
|
||||
const Rubric = (props) => {
|
||||
const {
|
||||
customRatings,
|
||||
onAssessmentChange,
|
||||
rubric,
|
||||
rubricAssessment,
|
||||
rubricAssociation,
|
||||
isSummary
|
||||
} = props
|
||||
const assessing = onAssessmentChange !== null
|
||||
const priorData = _.get(rubricAssessment, 'data', [])
|
||||
const byCriteria = _.keyBy(priorData, (ra) => ra.criterion_id)
|
||||
|
@ -59,10 +67,11 @@ const Rubric = ({ onAssessmentChange, rubric, rubricAssessment, rubricAssociatio
|
|||
key={criterion.id}
|
||||
assessment={assessment}
|
||||
criterion={criterion}
|
||||
customRatings={customRatings}
|
||||
freeForm={rubric.free_form_criterion_comments}
|
||||
isSummary={isSummary}
|
||||
onAssessmentChange={assessing ? onCriteriaChange(criterion.id) : undefined}
|
||||
savedComments={allComments[criterion.id]}
|
||||
customRatings={customRatings}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
@ -80,7 +89,11 @@ const Rubric = ({ onAssessmentChange, rubric, rubricAssessment, rubricAssociatio
|
|||
<tr>
|
||||
<th scope="col">{I18n.t('Criteria')}</th>
|
||||
<th scope="col">{I18n.t('Ratings')}</th>
|
||||
<th scope="col">{I18n.t('Pts')}</th>
|
||||
{
|
||||
isSummary ? null : (
|
||||
<th scope="col">{I18n.t('Pts')}</th>
|
||||
)
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="criterions">
|
||||
|
@ -100,6 +113,7 @@ const Rubric = ({ onAssessmentChange, rubric, rubricAssessment, rubricAssociatio
|
|||
)
|
||||
}
|
||||
Rubric.propTypes = {
|
||||
customRatings: PropTypes.arrayOf(PropTypes.object),
|
||||
onAssessmentChange: PropTypes.func,
|
||||
rubric: PropTypes.shape(rubricShape).isRequired,
|
||||
rubricAssessment: (props) => {
|
||||
|
@ -108,13 +122,14 @@ Rubric.propTypes = {
|
|||
return PropTypes.checkPropTypes({ rubricAssessment }, props, 'prop', 'Rubric')
|
||||
},
|
||||
rubricAssociation: PropTypes.shape(rubricAssociationShape),
|
||||
customRatings: PropTypes.arrayOf(PropTypes.object)
|
||||
isSummary: PropTypes.bool
|
||||
}
|
||||
Rubric.defaultProps = {
|
||||
customRatings: [],
|
||||
onAssessmentChange: null,
|
||||
rubricAssessment: null,
|
||||
rubricAssociation: {},
|
||||
customRatings: []
|
||||
isSummary: false
|
||||
}
|
||||
|
||||
export default Rubric
|
||||
|
|
|
@ -74,4 +74,10 @@ describe('The Comments component', () => {
|
|||
|
||||
expect(setSaveLater.args).toEqual([[true]])
|
||||
})
|
||||
|
||||
it('renders a footer after the comment when provided', () => {
|
||||
const el = component({ assessing: false, footer: <div>this is a footer</div> })
|
||||
|
||||
expect(el.shallow().debug()).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -95,4 +95,17 @@ describe('Criterion', () => {
|
|||
dialog.prop('close')()
|
||||
expectState(false)
|
||||
})
|
||||
|
||||
it('does not have a points column in summary mode', () => {
|
||||
const el = shallow(
|
||||
<Criterion
|
||||
assessment={assessments.points.data[1]}
|
||||
criterion={rubrics.points.criteria[1]}
|
||||
freeForm={false}
|
||||
isSummary
|
||||
/>
|
||||
)
|
||||
|
||||
expect(el.find('td')).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -40,7 +40,7 @@ describe('The Points component', () => {
|
|||
it('renders blank when points are undefined', () => {
|
||||
expect(component({
|
||||
assessing: true,
|
||||
assessment: id,
|
||||
assessment: { ...id, pointsText: '' },
|
||||
pointsPossible: 2
|
||||
}).debug()).toMatchSnapshot()
|
||||
})
|
||||
|
@ -57,7 +57,7 @@ describe('The Points component', () => {
|
|||
assessing: true,
|
||||
assessment: {
|
||||
...id,
|
||||
points: points === undefined ? null : points,
|
||||
points,
|
||||
pointsText,
|
||||
},
|
||||
pointsPossible: 2
|
||||
|
|
|
@ -23,13 +23,15 @@ import Ratings, { Rating } from '../Ratings'
|
|||
describe('The Ratings component', () => {
|
||||
const props = {
|
||||
assessing: false,
|
||||
footer: null,
|
||||
tiers: [
|
||||
{ description: 'Superb', points: 10 },
|
||||
{ description: 'Meh', long_description: 'More Verbosity', points: 5 },
|
||||
{ description: 'Subpar', points: 1 }
|
||||
],
|
||||
points: 5,
|
||||
defaultMasteryThreshold: 10,
|
||||
points: 5,
|
||||
isSummary: false,
|
||||
useRange: false
|
||||
}
|
||||
|
||||
|
@ -89,23 +91,41 @@ describe('The Ratings component', () => {
|
|||
expect(ratings(0, true)).toEqual(['transparent', 'transparent', '#F8971C'])
|
||||
})
|
||||
|
||||
describe('Rating component', () => {
|
||||
it('is navigable and clickable when assessing', () => {
|
||||
const onClick = sinon.spy()
|
||||
const wrapper = shallow(<Rating {...props.tiers[0]} assessing onClick={onClick} />)
|
||||
const div = wrapper.find('div').at(0)
|
||||
expect(div.prop('tabIndex')).toEqual(0)
|
||||
div.simulate('click')
|
||||
expect(onClick.called).toBe(true)
|
||||
})
|
||||
it('is navigable and clickable when assessing', () => {
|
||||
const onClick = sinon.spy()
|
||||
const wrapper = shallow(<Rating {...props.tiers[0]} assessing onClick={onClick} />)
|
||||
const div = wrapper.find('div').at(0)
|
||||
expect(div.prop('tabIndex')).toEqual(0)
|
||||
div.simulate('click')
|
||||
expect(onClick.called).toBe(true)
|
||||
})
|
||||
|
||||
it('is not navigable or clickable when not assessing', () => {
|
||||
const onClick = sinon.spy()
|
||||
const wrapper = shallow(<Rating {...props.tiers[0]} assessing={false} onClick={onClick} />)
|
||||
const div = wrapper.find('div').at(0)
|
||||
expect(div.prop('tabIndex')).toBeNull()
|
||||
div.simulate('click')
|
||||
expect(onClick.called).toBe(false)
|
||||
})
|
||||
it('is not navigable or clickable when not assessing', () => {
|
||||
const onClick = sinon.spy()
|
||||
const wrapper = shallow(<Rating {...props.tiers[0]} assessing={false} onClick={onClick} />)
|
||||
const div = wrapper.find('div').at(0)
|
||||
expect(div.prop('tabIndex')).toBeNull()
|
||||
div.simulate('click')
|
||||
expect(onClick.called).toBe(false)
|
||||
})
|
||||
|
||||
it('only renders the single selected Rating with a footer in summary mode', () => {
|
||||
const el = component({ points: 5, isSummary: true, footer: <div>ow my foot</div> })
|
||||
const ratings = el.find('Rating')
|
||||
|
||||
expect(ratings).toHaveLength(1)
|
||||
|
||||
const rating = ratings.at(0)
|
||||
expect(rating.shallow().debug()).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('renders a default rating if none of the ratings are selected', () => {
|
||||
const el = component({ points: 6, isSummary: true, footer: <div>ow my foot</div> })
|
||||
const ratings = el.find('Rating')
|
||||
|
||||
expect(ratings).toHaveLength(1)
|
||||
|
||||
const rating = ratings.at(0)
|
||||
expect(rating.shallow().debug()).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -82,4 +82,17 @@ describe('the Rubric component', () => {
|
|||
|
||||
expect(renderAssessing(onAssessmentChange.args[0][0]).debug()).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('does not have a points column in summary mode', () => {
|
||||
const el = shallow(
|
||||
<Rubric
|
||||
rubric={rubric}
|
||||
rubricAssessment={pointsAssessment}
|
||||
rubricAssociation={pointsAssessment.rubric_association}
|
||||
isSummary
|
||||
/>
|
||||
)
|
||||
|
||||
expect(el.find('th')).toHaveLength(2)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -2,6 +2,15 @@
|
|||
|
||||
exports[`The Comments component directly renders comments_html 1`] = `"<div>I award you <b>no</b> points, and may God have mercy on your soul.</div>"`;
|
||||
|
||||
exports[`The Comments component renders a footer after the comment when provided 1`] = `
|
||||
"<div className=\\"rubric-freeform\\">
|
||||
<CommentText assessment={{...}} placeholder=\\"This area will be used by the assessor to leave comments related to this criterion.\\" weight=\\"normal\\" />
|
||||
<div>
|
||||
this is a footer
|
||||
</div>
|
||||
</div>"
|
||||
`;
|
||||
|
||||
exports[`The Comments component renders a placeholder when no assessment provided 1`] = `
|
||||
"<Text size=\\"x-small\\" weight=\\"normal\\" as=\\"span\\" letterSpacing=\\"normal\\">
|
||||
This area will be used by the assessor to leave comments related to this criterion.
|
||||
|
|
|
@ -33,7 +33,7 @@ exports[`Free-form Rubric with a custom criterion by default renders the root co
|
|||
</div>
|
||||
</th>
|
||||
<td className=\\"ratings\\">
|
||||
<Comments assessing={false} assessment={{...}} savedComments={{...}} setSaveLater={[Function]} setComments={[Function]} />
|
||||
<Comments assessing={false} assessment={{...}} footer={{...}} savedComments={{...}} setSaveLater={[Function]} setComments={[Function]} />
|
||||
</td>
|
||||
<td>
|
||||
<Points assessing={false} assessment={{...}} onPointChange={{...}} pointsPossible={8} />
|
||||
|
@ -72,7 +72,7 @@ exports[`Free-form Rubric with a custom criterion when assessing renders the roo
|
|||
</div>
|
||||
</th>
|
||||
<td className=\\"ratings\\">
|
||||
<Comments assessing={true} assessment={{...}} savedComments={{...}} setSaveLater={[Function]} setComments={[Function]} />
|
||||
<Comments assessing={true} assessment={{...}} footer={{...}} savedComments={{...}} setSaveLater={[Function]} setComments={[Function]} />
|
||||
</td>
|
||||
<td>
|
||||
<Points assessing={true} assessment={{...}} onPointChange={[Function]} pointsPossible={8} />
|
||||
|
@ -111,7 +111,7 @@ exports[`Free-form Rubric with a custom criterion without an assessment renders
|
|||
</div>
|
||||
</th>
|
||||
<td className=\\"ratings\\">
|
||||
<Comments assessing={false} assessment={{...}} savedComments={{...}} setSaveLater={[Function]} setComments={[Function]} />
|
||||
<Comments assessing={false} assessment={{...}} footer={{...}} savedComments={{...}} setSaveLater={[Function]} setComments={[Function]} />
|
||||
</td>
|
||||
<td>
|
||||
<Points assessing={false} assessment={{...}} onPointChange={{...}} pointsPossible={8} />
|
||||
|
@ -177,7 +177,7 @@ exports[`Free-form Rubric with a outcome criterion by default renders the root c
|
|||
<Threshold threshold={3} />
|
||||
</th>
|
||||
<td className=\\"ratings\\">
|
||||
<Comments assessing={false} assessment={{...}} savedComments={{...}} setSaveLater={[Function]} setComments={[Function]} />
|
||||
<Comments assessing={false} assessment={{...}} footer={{...}} savedComments={{...}} setSaveLater={[Function]} setComments={[Function]} />
|
||||
</td>
|
||||
<td>
|
||||
<Points assessing={false} assessment={{...}} onPointChange={{...}} pointsPossible={3} />
|
||||
|
@ -243,7 +243,7 @@ exports[`Free-form Rubric with a outcome criterion when assessing renders the ro
|
|||
<Threshold threshold={3} />
|
||||
</th>
|
||||
<td className=\\"ratings\\">
|
||||
<Comments assessing={true} assessment={{...}} savedComments={{...}} setSaveLater={[Function]} setComments={[Function]} />
|
||||
<Comments assessing={true} assessment={{...}} footer={{...}} savedComments={{...}} setSaveLater={[Function]} setComments={[Function]} />
|
||||
</td>
|
||||
<td>
|
||||
<Points assessing={true} assessment={{...}} onPointChange={[Function]} pointsPossible={3} />
|
||||
|
@ -309,7 +309,7 @@ exports[`Free-form Rubric with a outcome criterion without an assessment renders
|
|||
<Threshold threshold={3} />
|
||||
</th>
|
||||
<td className=\\"ratings\\">
|
||||
<Comments assessing={false} assessment={{...}} savedComments={{...}} setSaveLater={[Function]} setComments={[Function]} />
|
||||
<Comments assessing={false} assessment={{...}} footer={{...}} savedComments={{...}} setSaveLater={[Function]} setComments={[Function]} />
|
||||
</td>
|
||||
<td>
|
||||
<Points assessing={false} assessment={{...}} onPointChange={{...}} pointsPossible={3} />
|
||||
|
@ -348,7 +348,7 @@ exports[`Point Rubric with a custom criterion by default renders the root compon
|
|||
</div>
|
||||
</th>
|
||||
<td className=\\"ratings\\">
|
||||
<Ratings assessing={false} tiers={{...}} onPointChange={[Function]} points={3.2} useRange={false} defaultMasteryThreshold={8} customRatings={{...}} />
|
||||
<Ratings assessing={false} customRatings={{...}} footer={{...}} tiers={{...}} onPointChange={[Function]} points={3.2} pointsPossible={8} defaultMasteryThreshold={8} isSummary={false} useRange={false} />
|
||||
</td>
|
||||
<td>
|
||||
<Points assessing={false} assessment={{...}} onPointChange={{...}} pointsPossible={8} />
|
||||
|
@ -387,7 +387,7 @@ exports[`Point Rubric with a custom criterion when assessing renders the root co
|
|||
</div>
|
||||
</th>
|
||||
<td className=\\"ratings\\">
|
||||
<Ratings assessing={true} tiers={{...}} onPointChange={[Function]} points={3.2} useRange={false} defaultMasteryThreshold={8} customRatings={{...}} />
|
||||
<Ratings assessing={true} customRatings={{...}} footer={{...}} tiers={{...}} onPointChange={[Function]} points={3.2} pointsPossible={8} defaultMasteryThreshold={8} isSummary={false} useRange={false} />
|
||||
</td>
|
||||
<td>
|
||||
<Points assessing={true} assessment={{...}} onPointChange={[Function]} pointsPossible={8} />
|
||||
|
@ -433,7 +433,7 @@ exports[`Point Rubric with a custom criterion without an assessment renders the
|
|||
</div>
|
||||
</th>
|
||||
<td className=\\"ratings\\">
|
||||
<Ratings assessing={false} tiers={{...}} onPointChange={[Function]} points={[undefined]} useRange={false} defaultMasteryThreshold={8} customRatings={{...}} />
|
||||
<Ratings assessing={false} customRatings={{...}} footer={{...}} tiers={{...}} onPointChange={[Function]} points={[undefined]} pointsPossible={8} defaultMasteryThreshold={8} isSummary={false} useRange={false} />
|
||||
</td>
|
||||
<td>
|
||||
<Points assessing={false} assessment={{...}} onPointChange={{...}} pointsPossible={8} />
|
||||
|
@ -499,7 +499,7 @@ exports[`Point Rubric with a outcome criterion by default renders the root compo
|
|||
<Threshold threshold={3} />
|
||||
</th>
|
||||
<td className=\\"ratings\\">
|
||||
<Ratings assessing={false} tiers={{...}} onPointChange={[Function]} points={3} useRange={false} defaultMasteryThreshold={3} customRatings={{...}} />
|
||||
<Ratings assessing={false} customRatings={{...}} footer={{...}} tiers={{...}} onPointChange={[Function]} points={3} pointsPossible={3} defaultMasteryThreshold={3} isSummary={false} useRange={false} />
|
||||
</td>
|
||||
<td>
|
||||
<Points assessing={false} assessment={{...}} onPointChange={{...}} pointsPossible={3} />
|
||||
|
@ -565,7 +565,7 @@ exports[`Point Rubric with a outcome criterion when assessing renders the root c
|
|||
<Threshold threshold={3} />
|
||||
</th>
|
||||
<td className=\\"ratings\\">
|
||||
<Ratings assessing={true} tiers={{...}} onPointChange={[Function]} points={3} useRange={false} defaultMasteryThreshold={3} customRatings={{...}} />
|
||||
<Ratings assessing={true} customRatings={{...}} footer={{...}} tiers={{...}} onPointChange={[Function]} points={3} pointsPossible={3} defaultMasteryThreshold={3} isSummary={false} useRange={false} />
|
||||
</td>
|
||||
<td>
|
||||
<Points assessing={true} assessment={{...}} onPointChange={[Function]} pointsPossible={3} />
|
||||
|
@ -638,7 +638,7 @@ exports[`Point Rubric with a outcome criterion without an assessment renders the
|
|||
</div>
|
||||
</th>
|
||||
<td className=\\"ratings\\">
|
||||
<Ratings assessing={false} tiers={{...}} onPointChange={[Function]} points={[undefined]} useRange={false} defaultMasteryThreshold={3} customRatings={{...}} />
|
||||
<Ratings assessing={false} customRatings={{...}} footer={{...}} tiers={{...}} onPointChange={[Function]} points={[undefined]} pointsPossible={3} defaultMasteryThreshold={3} isSummary={false} useRange={false} />
|
||||
</td>
|
||||
<td>
|
||||
<Points assessing={false} assessment={{...}} onPointChange={{...}} pointsPossible={3} />
|
||||
|
|
|
@ -1,5 +1,45 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`The Ratings component only renders the single selected Rating with a footer in summary mode 1`] = `
|
||||
"<div className=\\"rating-tier selected\\" onClick={{...}} onKeyPress={[Function]} role=\\"button\\" tabIndex={{...}}>
|
||||
<div className=\\"rating-description\\">
|
||||
<Text size=\\"x-small\\" lineHeight=\\"condensed\\" as=\\"span\\" letterSpacing=\\"normal\\">
|
||||
Meh
|
||||
</Text>
|
||||
</div>
|
||||
<Text size=\\"x-small\\" fontStyle=\\"italic\\" lineHeight=\\"condensed\\" as=\\"span\\" letterSpacing=\\"normal\\">
|
||||
More Verbosity
|
||||
</Text>
|
||||
<div className=\\"rating-footer\\">
|
||||
<div>
|
||||
ow my foot
|
||||
</div>
|
||||
</div>
|
||||
<div className=\\"shader\\" style={{...}}>
|
||||
<div className=\\"triangle\\" style={{...}} />
|
||||
</div>
|
||||
</div>"
|
||||
`;
|
||||
|
||||
exports[`The Ratings component renders a default rating if none of the ratings are selected 1`] = `
|
||||
"<div className=\\"rating-tier\\" onClick={{...}} onKeyPress={[Function]} role=\\"button\\" tabIndex={{...}}>
|
||||
<div className=\\"rating-description\\">
|
||||
<Text size=\\"x-small\\" lineHeight=\\"condensed\\" as=\\"span\\" letterSpacing=\\"normal\\">
|
||||
No details
|
||||
</Text>
|
||||
</div>
|
||||
<Text size=\\"x-small\\" fontStyle=\\"italic\\" lineHeight=\\"condensed\\" as=\\"span\\" letterSpacing=\\"normal\\" />
|
||||
<div className=\\"rating-footer\\">
|
||||
<div>
|
||||
ow my foot
|
||||
</div>
|
||||
</div>
|
||||
<div className=\\"shader\\" style={{...}}>
|
||||
<div className=\\"triangle\\" style={{...}} />
|
||||
</div>
|
||||
</div>"
|
||||
`;
|
||||
|
||||
exports[`The Ratings component renders the Rating sub-components as expected when range rating enabled 1`] = `
|
||||
"<div className=\\"rating-tier\\" onClick={{...}} onKeyPress={[Function]} role=\\"button\\" tabIndex={{...}}>
|
||||
<div className=\\"rating-points\\">
|
||||
|
@ -61,8 +101,8 @@ exports[`The Ratings component renders the Rating sub-components as expected whe
|
|||
|
||||
exports[`The Ratings component renders the root component as expected 1`] = `
|
||||
"<div className=\\"rating-tier-list\\">
|
||||
<Rating assessing={false} onClick={[Function]} classes=\\"rating-tier\\" endOfRangePoints={{...}} tierColor=\\"transparent\\" description=\\"Superb\\" points={10} selected={false} />
|
||||
<Rating assessing={false} onClick={[Function]} classes=\\"rating-tier selected\\" endOfRangePoints={{...}} tierColor=\\"#e0d773\\" description=\\"Meh\\" long_description=\\"More Verbosity\\" points={5} selected={false} />
|
||||
<Rating assessing={false} onClick={[Function]} classes=\\"rating-tier\\" endOfRangePoints={{...}} tierColor=\\"transparent\\" description=\\"Subpar\\" points={1} selected={false} />
|
||||
<Rating assessing={false} classes=\\"rating-tier\\" endOfRangePoints={{...}} footer={{...}} isSummary={false} onClick={[Function]} tierColor=\\"transparent\\" description=\\"Superb\\" points={10} selected={false} />
|
||||
<Rating assessing={false} classes=\\"rating-tier selected\\" endOfRangePoints={{...}} footer={{...}} isSummary={false} onClick={[Function]} tierColor=\\"#e0d773\\" description=\\"Meh\\" long_description=\\"More Verbosity\\" points={5} selected={false} />
|
||||
<Rating assessing={false} classes=\\"rating-tier\\" endOfRangePoints={{...}} footer={{...}} isSummary={false} onClick={[Function]} tierColor=\\"transparent\\" description=\\"Subpar\\" points={1} selected={false} />
|
||||
</div>"
|
||||
`;
|
||||
|
|
|
@ -17,8 +17,8 @@ exports[`the Rubric component hides the score total when needed 1`] = `
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody className=\\"criterions\\">
|
||||
<Criterion assessment={{...}} criterion={{...}} freeForm={false} onAssessmentChange={{...}} savedComments={{...}} customRatings={{...}} />
|
||||
<Criterion assessment={{...}} criterion={{...}} freeForm={false} onAssessmentChange={{...}} savedComments={{...}} customRatings={{...}} />
|
||||
<Criterion assessment={{...}} criterion={{...}} customRatings={{...}} freeForm={false} isSummary={false} onAssessmentChange={{...}} savedComments={{...}} />
|
||||
<Criterion assessment={{...}} criterion={{...}} customRatings={{...}} freeForm={false} isSummary={false} onAssessmentChange={{...}} savedComments={{...}} />
|
||||
<tr>
|
||||
<td colSpan=\\"3\\">
|
||||
<Flex justifyItems=\\"end\\" as=\\"span\\" direction=\\"row\\" alignItems=\\"center\\" inline={false} visualDebug={false} wrapItems={false}>
|
||||
|
@ -48,8 +48,8 @@ exports[`the Rubric component renders as expected 1`] = `
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody className=\\"criterions\\">
|
||||
<Criterion assessment={{...}} criterion={{...}} freeForm={false} onAssessmentChange={{...}} savedComments={{...}} customRatings={{...}} />
|
||||
<Criterion assessment={{...}} criterion={{...}} freeForm={false} onAssessmentChange={{...}} savedComments={{...}} customRatings={{...}} />
|
||||
<Criterion assessment={{...}} criterion={{...}} customRatings={{...}} freeForm={false} isSummary={false} onAssessmentChange={{...}} savedComments={{...}} />
|
||||
<Criterion assessment={{...}} criterion={{...}} customRatings={{...}} freeForm={false} isSummary={false} onAssessmentChange={{...}} savedComments={{...}} />
|
||||
<tr>
|
||||
<td colSpan=\\"3\\">
|
||||
<Flex justifyItems=\\"end\\" as=\\"span\\" direction=\\"row\\" alignItems=\\"center\\" inline={false} visualDebug={false} wrapItems={false}>
|
||||
|
@ -81,8 +81,8 @@ exports[`the Rubric component renders properly with no assessment 1`] = `
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody className=\\"criterions\\">
|
||||
<Criterion assessment={{...}} criterion={{...}} freeForm={false} onAssessmentChange={{...}} savedComments={{...}} customRatings={{...}} />
|
||||
<Criterion assessment={{...}} criterion={{...}} freeForm={false} onAssessmentChange={{...}} savedComments={{...}} customRatings={{...}} />
|
||||
<Criterion assessment={{...}} criterion={{...}} customRatings={{...}} freeForm={false} isSummary={false} onAssessmentChange={{...}} savedComments={{...}} />
|
||||
<Criterion assessment={{...}} criterion={{...}} customRatings={{...}} freeForm={false} isSummary={false} onAssessmentChange={{...}} savedComments={{...}} />
|
||||
<tr>
|
||||
<td colSpan=\\"3\\">
|
||||
<Flex justifyItems=\\"end\\" as=\\"span\\" direction=\\"row\\" alignItems=\\"center\\" inline={false} visualDebug={false} wrapItems={false}>
|
||||
|
@ -114,8 +114,8 @@ exports[`the Rubric component updates the total score when an individual criteri
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody className=\\"criterions\\">
|
||||
<Criterion assessment={{...}} criterion={{...}} freeForm={false} onAssessmentChange={[Function]} savedComments={{...}} customRatings={{...}} />
|
||||
<Criterion assessment={{...}} criterion={{...}} freeForm={false} onAssessmentChange={[Function]} savedComments={{...}} customRatings={{...}} />
|
||||
<Criterion assessment={{...}} criterion={{...}} customRatings={{...}} freeForm={false} isSummary={false} onAssessmentChange={[Function]} savedComments={{...}} />
|
||||
<Criterion assessment={{...}} criterion={{...}} customRatings={{...}} freeForm={false} isSummary={false} onAssessmentChange={[Function]} savedComments={{...}} />
|
||||
<tr>
|
||||
<td colSpan=\\"3\\">
|
||||
<Flex justifyItems=\\"end\\" as=\\"span\\" direction=\\"row\\" alignItems=\\"center\\" inline={false} visualDebug={false} wrapItems={false}>
|
||||
|
|
|
@ -109,6 +109,10 @@
|
|||
position: relative;
|
||||
}
|
||||
|
||||
.react-rubric .rating-tier .rating-footer {
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.react-rubric .rating-tier.selected {
|
||||
.shader {
|
||||
width: 100%;
|
||||
|
@ -137,4 +141,5 @@
|
|||
|
||||
.react-rubric .graded-points {
|
||||
white-space: nowrap;
|
||||
text-align: end;
|
||||
}
|
||||
|
|
|
@ -276,11 +276,16 @@ window.rubricAssessment = {
|
|||
},
|
||||
|
||||
fillAssessment: function(rubric, partialAssessment) {
|
||||
const fillText = (c) => ({
|
||||
pointsText: _.isNil(c.points) && _.isUndefined(c.pointsText) ? '--' : c.pointsText,
|
||||
...c
|
||||
})
|
||||
const defaultCriteria = (id) => ({ criterion_id: id, pointsText: '' })
|
||||
const prior = _.keyBy(_.cloneDeep(partialAssessment.data), (c) => c.criterion_id)
|
||||
return {
|
||||
score: 0,
|
||||
...partialAssessment,
|
||||
data: rubric.criteria.map((c) => (prior[c.id] || { criterion_id: c.id, score: null }))
|
||||
data: rubric.criteria.map((c) => fillText(prior[c.id] || defaultCriteria(c.id)))
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -374,6 +379,29 @@ window.rubricAssessment = {
|
|||
}
|
||||
},
|
||||
|
||||
populateNewRubricSummary: function(container, assessment, rubricAssociation, editData) {
|
||||
if (ENV.nonScoringRubrics && ENV.rubric) {
|
||||
if(assessment) {
|
||||
const filled = rubricAssessment.fillAssessment(ENV.rubric, assessment || {})
|
||||
ReactDOM.render(React.createElement(Rubric, {
|
||||
customRatings: ENV.outcome_proficiency ? ENV.outcome_proficiency.ratings : [],
|
||||
rubric: ENV.rubric,
|
||||
rubricAssessment: filled,
|
||||
rubricAssociation,
|
||||
isSummary: true
|
||||
}, null), container.get(0))
|
||||
} else {
|
||||
container.get(0).innerHTML = ''
|
||||
}
|
||||
} else {
|
||||
rubricAssessment.populateRubricSummary(
|
||||
container,
|
||||
assessment,
|
||||
editData
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
populateRubricSummary: function($rubricSummary, data, editing_data) {
|
||||
$rubricSummary.find(".criterion_points").text("").end()
|
||||
.find(".rating_custom").text("");
|
||||
|
|
|
@ -837,9 +837,10 @@ function initRubricStuff(){
|
|||
selectors.get('#rubric_assessments_select').change(() => {
|
||||
const editingData = rubricAssessment.assessmentData($("#rubric_full"))
|
||||
var selectedAssessment = getSelectedAssessment();
|
||||
rubricAssessment.populateRubricSummary(
|
||||
rubricAssessment.populateNewRubricSummary(
|
||||
$("#rubric_summary_holder .rubric_summary"),
|
||||
selectedAssessment,
|
||||
jsonData.rubric_association,
|
||||
editingData
|
||||
);
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue