add popover details in student LMGB

closes OUT-2266

test plan:
- have some outcomes in a course
- attach some of the outcomes to a rubric and grade them
- go to the LMGB, and then click on a student's name to go
  to their individual view
- expand an outcome group, there should be an info icon
- hover over the icon with the mouse, details should be
  provided that show the most recent assessment, mastery level (if
  applicable), caclulation method, and some example text and scores
- use tab navigation - when tabbing to the info icon, the popover
  should appear
- enable VO. Using the VO keys, navigate to the icon. You should
  be prompted to click for more information. Press enter, then navigate
  forward - you should be navigating around a SR only version of the
  popover details
- navigate back to the icon - it should promt you to click to collapse
  the additional details. Click the button, then navigate forward again
  using the VO keys. The next element read/highlighted should be the title
- compare mastery levels shown in the student lmgb with how they appear
  in the teacher lmgb. All results should properly match.

Change-Id: I25d5943e1593cd9003de719b682275a4bf103db1
Reviewed-on: https://gerrit.instructure.com/158089
Reviewed-by: Augusto Callejas <acallejas@instructure.com>
Tested-by: Jenkins
Reviewed-by: Michael Brewer-Davis <mbd@instructure.com>
QA-Review: Augusto Callejas <acallejas@instructure.com>
Product-Review: Nathan Rogowski <nathan@instructure.com>
This commit is contained in:
Matthew Berns 2018-07-19 16:39:42 -05:00 committed by Matt Berns
parent 23f1dbee9a
commit 2e0515ad47
10 changed files with 663 additions and 125 deletions

View File

@ -1,122 +0,0 @@
#
# Copyright (C) 2015 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
define [
'underscore'
'i18n!outcomes'
'jsx/shared/helpers/numberFormat'
'../../underscore-ext/sum'
], (_, I18n, numberFormat) ->
class DecayingAverage
constructor: (@weight, @range) ->
@rest = @range[..-2]
@last = _.last(@range)
value: ->
n = ((_.sum(@rest) / @rest.length) * @toPercentage(@remainder())) +
(@last * @toPercentage(@weight))
Math.round(n * 100) / 100
remainder: ->
100 - @weight
toPercentage: (n) ->
n / 100
class NMastery
constructor: (@n, @mastery_points, @range) ->
aboveMastery: ->
_.filter(@range, (n) =>
n >= @mastery_points
)
value: ->
if @mastery_points? && @aboveMastery().length >= @n
Math.round(_.sum(@aboveMastery()) / @aboveMastery().length * 100) / 100
else
I18n.t("N/A")
class CalculationMethodContent
constructor: (model) ->
# We can pass in a straight object or a backbone model
_.each([
'calculation_method', 'calculation_int', 'mastery_points'
], (attr) =>
@[attr] = if model.get? then model.get(attr) else model[attr]
)
decayingAverage: ->
new DecayingAverage(@calculation_int, @exampleScoreIntegers()).value()
exampleScoreIntegers: ->
[ 1, 4, 2, 3, 5, 3, 6 ]
nMastery: ->
new NMastery(@calculation_int, @mastery_points, @exampleScoreIntegers()).value()
present: ->
@toJSON()[@calculation_method]
toJSON: ->
decaying_average:
method: I18n.t("%{recentInt}/%{remainderInt} Decaying Average", {
recentInt: @calculation_int
remainderInt: 100 - @calculation_int
})
friendlyCalculationMethod: I18n.t("Decaying Average")
calculationIntLabel: I18n.t("Last Item: ")
calculationIntDescription: I18n.t('Between 1% and 99%')
exampleText: I18n.t(
"Most recent result counts as %{calculation_int} of mastery weight, average of all other results count as %{remainder} of weight. If there is only one result, the single score will be returned.", {
calculation_int: I18n.n(@calculation_int, { percentage: true }),
remainder: I18n.n(100 - @calculation_int, { percentage: true })
}
),
exampleScores: @exampleScoreIntegers().join(', '),
exampleResult: numberFormat.outcomeScore(@decayingAverage())
n_mastery:
method: I18n.t({
one: "Achieve mastery one time",
other: "Achieve mastery %{count} times"
}, {
count: @calculation_int
})
friendlyCalculationMethod: I18n.t("n Number of Times")
calculationIntLabel: I18n.t('Items: ')
calculationIntDescription: I18n.t('Between 1 and 5')
exampleText: I18n.t(
{
one: "Must achieve mastery at least one time. Scores above mastery will be averaged to calculate final score.",
other: "Must achieve mastery at least %{count} times. Scores above mastery will be averaged to calculate final score."
}, {
count: @calculation_int
}),
exampleScores: @exampleScoreIntegers().join(', '),
exampleResult: numberFormat.outcomeScore(@nMastery())
latest:
method: I18n.t("Latest Score")
friendlyCalculationMethod: I18n.t("Most Recent Score")
exampleText: I18n.t("Mastery score reflects the most recent graded assignment or quiz."),
exampleScores: @exampleScoreIntegers()[..3].join(', '),
exampleResult: numberFormat.outcomeScore(_.last(@exampleScoreIntegers()[..3]))
highest:
method: I18n.t("Highest Score")
friendlyCalculationMethod: I18n.t("Highest Score")
exampleText: I18n.t("Mastery score reflects the highest score of a graded assignment or quiz."),
exampleScores: @exampleScoreIntegers()[..3].join(', '),
exampleResult: numberFormat.outcomeScore(_.max(@exampleScoreIntegers()[..3]))

View File

@ -0,0 +1,151 @@
// Copyright (C) 2015 - present Instructure, Inc.
// This file is part of Canvas.
// Canvas is free software: you can redistribute it and/or modify it under
// the terms of the GNU Affero General Public License as published by the Free
// Software Foundation, version 3 of the License.
// Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
// A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
// details.
// You should have received a copy of the GNU Affero General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
import _ from 'underscore'
import I18n from 'i18n!outcomes'
import numberFormat from 'jsx/shared/helpers/numberFormat'
_.mixin({
sum (array, accessor = null, start = 0) {
return _.reduce(array, (memo, el) => (accessor != null ? accessor(el) : el) + memo, start)
},
})
class DecayingAverage {
constructor(weight, range) {
this.weight = weight;
this.range = range;
this.rest = this.range.slice(0, +-2 + 1 || undefined);
this.last = _.last(this.range);
}
value() {
const n = ((_.sum(this.rest) / this.rest.length) * this.toPercentage(this.remainder())) +
(this.last * this.toPercentage(this.weight));
return Math.round(n * 100) / 100;
}
remainder() {
return 100 - this.weight;
}
toPercentage(n) {
return n / 100;
}
}
class NMastery {
constructor(n, mastery_points, range) {
this.n = n;
this.mastery_points = mastery_points;
this.range = range;
}
aboveMastery() {
return _.filter(this.range, n => (n >= this.mastery_points));
}
value() {
if ((this.mastery_points != null) && (this.aboveMastery().length >= this.n)) {
return Math.round((_.sum(this.aboveMastery()) / this.aboveMastery().length) * 100) / 100;
} else {
return I18n.t("N/A");
}
}
}
export default class CalculationMethodContent {
constructor(model) {
// We can pass in a straight object or a backbone model
_.each([
'calculation_method', 'calculation_int', 'mastery_points'
], attr => (this[attr] = (model.get != null) ? model.get(attr) : model[attr]));
}
decayingAverage() {
return new DecayingAverage(this.calculation_int, this.exampleScoreIntegers()).value();
}
exampleScoreIntegers() {
return [ 1, 4, 2, 3, 5, 3, 6 ];
}
nMastery() {
return new NMastery(this.calculation_int, this.mastery_points, this.exampleScoreIntegers()).value();
}
present() {
return this.toJSON()[this.calculation_method];
}
toJSON() {
return {
decaying_average: {
method: I18n.t("%{recentInt}/%{remainderInt} Decaying Average", {
recentInt: this.calculation_int,
remainderInt: 100 - this.calculation_int
}),
friendlyCalculationMethod: I18n.t("Decaying Average"),
calculationIntLabel: I18n.t("Last Item: "),
calculationIntDescription: I18n.t('Between 1% and 99%'),
exampleText: I18n.t(
"Most recent result counts as %{calculation_int} of mastery weight, average of all other results count as %{remainder} of weight. If there is only one result, the single score will be returned.", {
calculation_int: I18n.n(this.calculation_int, { percentage: true }),
remainder: I18n.n(100 - this.calculation_int, { percentage: true })
}
),
exampleScores: this.exampleScoreIntegers().join(', '),
exampleResult: numberFormat.outcomeScore(this.decayingAverage())
},
n_mastery: {
method: I18n.t({
one: "Achieve mastery one time",
other: "Achieve mastery %{count} times"
}, {
count: this.calculation_int
}),
friendlyCalculationMethod: I18n.t("n Number of Times"),
calculationIntLabel: I18n.t('Items: '),
calculationIntDescription: I18n.t('Between 1 and 5'),
exampleText: I18n.t(
{
one: "Must achieve mastery at least one time. Scores above mastery will be averaged to calculate final score.",
other: "Must achieve mastery at least %{count} times. Scores above mastery will be averaged to calculate final score."
}, {
count: this.calculation_int
}),
exampleScores: this.exampleScoreIntegers().join(', '),
exampleResult: numberFormat.outcomeScore(this.nMastery())
},
latest: {
method: I18n.t("Latest Score"),
friendlyCalculationMethod: I18n.t("Most Recent Score"),
exampleText: I18n.t("Mastery score reflects the most recent graded assignment or quiz."),
exampleScores: this.exampleScoreIntegers().slice(0, 4).join(', '),
exampleResult: numberFormat.outcomeScore(_.last(this.exampleScoreIntegers().slice(0, 4)))
},
highest: {
method: I18n.t("Highest Score"),
friendlyCalculationMethod: I18n.t("Highest Score"),
exampleText: I18n.t("Mastery score reflects the highest score of a graded assignment or quiz."),
exampleScores: this.exampleScoreIntegers().slice(0, 4).join(', '),
exampleResult: numberFormat.outcomeScore(_.max(this.exampleScoreIntegers().slice(0, 4)))
}
};
}
}

View File

@ -25,9 +25,9 @@ import ToggleGroup from '@instructure/ui-toggle-details/lib/components/ToggleGro
import List, { ListItem } from '@instructure/ui-elements/lib/components/List'
import Pill from '@instructure/ui-elements/lib/components/Pill'
import Text from '@instructure/ui-elements/lib/components/Text'
import IconOutcomes from '@instructure/ui-icons/lib/Line/IconOutcomes'
import natcompare from 'compiled/util/natcompare'
import AssignmentResult from './AssignmentResult'
import OutcomePopover from './OutcomePopover'
import * as shapes from './shapes'
export default class Outcome extends React.Component {
@ -47,7 +47,7 @@ export default class Outcome extends React.Component {
}
renderHeader () {
const { outcome } = this.props
const { outcome, outcomeProficiency } = this.props
const { mastered, results, title } = outcome
const numAlignments = results.length
@ -58,7 +58,9 @@ export default class Outcome extends React.Component {
<FlexItem>
<Text size="medium">
<Flex>
<FlexItem><IconOutcomes /></FlexItem>
<FlexItem>
<OutcomePopover outcome={outcome} outcomeProficiency={outcomeProficiency}/>
</FlexItem>
<FlexItem padding="0 x-small">{ title }</FlexItem>
</Flex>
</Text>

View File

@ -0,0 +1,166 @@
/*
* Copyright (C) 2018 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from 'react'
import _ from 'lodash'
import I18n from 'i18n!outcomes'
import View from '@instructure/ui-layout/lib/components/View'
import Flex, { FlexItem } from '@instructure/ui-layout/lib/components/Flex'
import Text from '@instructure/ui-elements/lib/components/Text'
import Link from '@instructure/ui-elements/lib/components/Link'
import ScreenReaderContent from '@instructure/ui-a11y/lib/components/ScreenReaderContent'
import CalculationMethodContent from 'compiled/models/grade_summary/CalculationMethodContent'
import Popover, {PopoverTrigger, PopoverContent} from '@instructure/ui-overlays/lib/components/Popover'
import IconInfo from '@instructure/ui-icons/lib/Line/IconInfo'
import DatetimeDisplay from '../../shared/DatetimeDisplay'
import * as shapes from './shapes'
export default class OutcomePopover extends React.Component {
static propTypes = {
outcome: shapes.outcomeShape.isRequired,
outcomeProficiency: shapes.outcomeProficiencyShape
}
static defaultProps = {
outcomeProficiency: null
}
constructor () {
super()
this.state = { moreInformation: false }
}
getSelectedRating () {
const { outcomeProficiency } = this.props
const { points_possible, mastery_points, score } = this.props.outcome
const hasScore = score >= 0
if (outcomeProficiency && hasScore) {
const totalPoints = points_possible || mastery_points
const percentage = totalPoints ? (score / totalPoints) : score
const maxRating = outcomeProficiency.ratings[0].points
const scaledScore = maxRating * percentage
return _.find(outcomeProficiency.ratings, (r) => (scaledScore >= r.points)) || _.last(outcomeProficiency.ratings)
} else if (hasScore) {
return _.find(this.defaultProficiency(mastery_points).ratings, (r) => (score >= r.points))
}
return null
}
defaultProficiency = _.memoize((mastery_points) => (
{
ratings: [
{points: mastery_points * 1.5, color: '127A1B', description: I18n.t('Exceeds Mastery')},
{points: mastery_points, color: '00AC18', description: I18n.t('Meets Mastery')},
{points: mastery_points/2, color: 'FAB901', description: I18n.t('Near Mastery')},
{points: 0, color: 'EE0612', description: I18n.t('Well Below Mastery')}
]
}
))
latestTime () {
const { outcome } = this.props
if (outcome.results.length > 0) {
return _.sortBy(outcome.results, (r) => (r.submitted_or_assessed_at))[0].submitted_or_assessed_at
}
return null
}
expandDetails = () => { this.setState({ moreInformation: !this.state.moreInformation }) }
renderPopoverContent () {
const selectedRating = this.getSelectedRating()
const latestTime = this.latestTime()
const popoverContent = new CalculationMethodContent(this.props.outcome).present()
const {
method,
exampleText,
exampleScores,
exampleResult
} = popoverContent
return (
<View as='div' padding='small' maxWidth='30rem'>
<Text size='small'>
<Flex
alignItems='stretch'
direction='row'
justifyItems='space-between'
>
<FlexItem grow shrink>
<div>{I18n.t('Last Assessment: ')}
{ latestTime ?
<DatetimeDisplay datetime={latestTime} format='%b %d, %l:%M %p' /> :
I18n.t('No submissions')
}
</div>
</FlexItem>
<FlexItem grow shrink align='stretch'>
<Text size='small' weight='bold'>
<div>
{selectedRating &&
<div style={{color: `#${selectedRating.color}`, textAlign: 'end'}}>
{selectedRating.description}
</div>}
</div>
</Text>
</FlexItem>
</Flex>
<hr role='presentation'/>
<div>
<Text size='small' weight='bold'>{I18n.t('Calculation Method')}</Text>
<div>{method}</div>
<div style={{padding: '0.5rem 0 0 0'}}><Text size='small' weight="bold">{I18n.t('Example')}</Text></div>
<div>{exampleText}</div>
<div>{I18n.t('1- Item Scores: %{exampleScores}', { exampleScores })}</div>
<div>{I18n.t('2- Final Score: %{exampleResult}', { exampleResult })}</div>
</div>
</Text>
</View>
)
}
render () {
const popoverContent = this.renderPopoverContent()
return (
<span>
<Popover
placement="bottom"
>
<PopoverTrigger>
<Link onClick={() => this.expandDetails()}>
<span style={{color: 'black'}}><IconInfo /></span>
<span>
{!this.state.moreInformation ?
<ScreenReaderContent>{I18n.t('Click to expand outcome details')}</ScreenReaderContent> :
<ScreenReaderContent>{I18n.t('Click to collapse outcome details')}</ScreenReaderContent>
}
</span>
</Link>
</PopoverTrigger>
<PopoverContent>
{popoverContent}
</PopoverContent>
</Popover>
<FlexItem>
{this.state.moreInformation &&
<ScreenReaderContent>{popoverContent}</ScreenReaderContent>
}
</FlexItem>
</span>
)
}
}

View File

@ -25,6 +25,7 @@ const defaultProps = (props = {}) => (
outcome: {
id: 1,
mastered: false,
calculation_method: 'highest',
ratings: [
{ description: 'My first rating' },
{ description: 'My second rating' }

View File

@ -41,6 +41,7 @@ const defaultProps = (props = {}) => (
mastered: false,
mastery_points: 3,
points_possible: 5,
calculation_method: 'highest',
ratings: [
{ description: 'My first rating' },
{ description: 'My second rating' }

View File

@ -27,6 +27,7 @@ const outcome = (id, title) => ({
mastered: false,
mastery_points: 3,
points_possible: 5,
calculation_method: 'highest',
ratings: [
{ description: 'My first rating' },
{ description: 'My second rating' }
@ -58,6 +59,7 @@ const defaultProps = (props = {}) => (
mastered: false,
mastery_points: 3,
points_possible: 5,
calculation_method: 'highest',
ratings: [
{ description: 'My first rating' },
{ description: 'My second rating' }

View File

@ -0,0 +1,137 @@
/*
* Copyright (C) 2018 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from 'react'
import { shallow } from 'enzyme'
import OutcomePopover from '../OutcomePopover'
const time1 = new Date(Date.UTC(2018, 1, 1, 7, 1, 0))
const time2 = new Date(Date.UTC(2018, 1, 1, 8, 1, 0))
const defaultProps = (props = {}) => (
Object.assign({
outcome: {
id: 1,
expansionId: 100,
mastered: false,
mastery_points: 3,
points_possible: 5,
calculation_method: 'highest',
score: 3,
ratings: [
{ description: 'My first rating' },
{ description: 'My second rating' }
],
results: [
{
id: 1,
score: 1,
percent: 0.1,
assignment: {
id: 1,
html_url: 'http://foo',
name: 'My assignment',
submission_types: 'online_quiz',
score: 0
},
submitted_or_assessed_at: time1
},
{
id: 1,
score: 7,
percent: 0.7,
assignment: {
id: 2,
html_url: 'http://bar',
name: 'Assignment 2',
submission_types: 'online_quiz',
score: 3
},
submitted_or_assessed_at: time2
}
],
title: 'My outcome'
},
outcomeProficiency: {
ratings: [
{ color: 'blue', description: "I am blue", points: 10},
{ color: 'green', description: "I am Groot", points: 5},
{ color: 'red', description: "I am red", points: 0}
]
}
}, props)
)
it('renders the OutcomePopover component', () => {
const wrapper = shallow(<OutcomePopover {...defaultProps()}/>)
expect(wrapper.debug()).toMatchSnapshot()
})
it('renders correctly with no results', () => {
const props = defaultProps()
props.outcome.results = []
const wrapper = shallow(<OutcomePopover {...props}/>)
expect(wrapper.debug()).toMatchSnapshot()
})
it('renders correctly with no custom outcomeProficiency', () => {
const props = defaultProps()
props.outcomeProficiency = null
const wrapper = shallow(<OutcomePopover {...props}/>)
expect(wrapper.debug()).toMatchSnapshot()
})
it('properly expands details for screenreader users', () => {
const props = defaultProps()
const wrapper = shallow(<OutcomePopover {...props}/>)
expect(wrapper.state('moreInformation')).toEqual(false)
wrapper.find('Link').simulate('click')
expect(wrapper.state('moreInformation')).toEqual(true)
})
describe('latestTime', () => {
it('properly returns the most recent submission time', () => {
const props = defaultProps()
const wrapper = shallow(<OutcomePopover {...props}/>)
expect(wrapper.instance().latestTime()).toEqual(time1)
})
it('properly returns nothing when there are no results', () => {
const props = defaultProps()
props.outcome.results = []
const wrapper = shallow(<OutcomePopover {...props}/>)
expect(wrapper.instance().latestTime()).toBeNull()
})
})
describe('getSelectedRating', () => {
it('properly returns the custom proficiency level', () => {
const props = defaultProps()
const wrapper = shallow(<OutcomePopover {...props}/>)
const rating = wrapper.instance().getSelectedRating()
expect(rating.description).toEqual('I am Groot')
})
it('properly returns the default proficiency level', () => {
const props = defaultProps()
props.outcomeProficiency = null
const wrapper = shallow(<OutcomePopover {...props}/>)
const rating = wrapper.instance().getSelectedRating()
expect(rating.description).toEqual('Meets Mastery')
})
})

View File

@ -0,0 +1,199 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly with no custom outcomeProficiency 1`] = `
"<span>
<Popover placement=\\"bottom\\" onToggle={[Function]} onClick={[Function]} onFocus={[Function]} onBlur={[Function]} onMouseOver={[Function]} onMouseOut={[Function]} onShow={[Function]} onDismiss={[Function]} stacking=\\"topmost\\" offsetX={0} offsetY={0} variant=\\"default\\" on={{...}} contentRef={[Function]} defaultShow={false} withArrow={true} trackPosition={true} constrain=\\"window\\" onPositioned={[Function]} onPositionChanged={[Function]} shouldRenderOffscreen={false} shouldContainFocus={false} shouldReturnFocus={true} shouldCloseOnDocumentClick={true} shouldCloseOnEscape={true} defaultFocusElement={{...}} label={{...}} mountNode={{...}} insertAt=\\"bottom\\" liveRegion={{...}} positionTarget={{...}} alignArrow={false}>
<PopoverTrigger>
<Link onClick={[Function]} variant=\\"default\\" as=\\"button\\" linkRef={[Function]} ellipsis={false} iconPlacement=\\"start\\">
<span style={{...}}>
<IconInfo />
</span>
<span>
<ScreenReaderContent as=\\"span\\">
Click to expand outcome details
</ScreenReaderContent>
</span>
</Link>
</PopoverTrigger>
<PopoverContent>
<View as=\\"div\\" padding=\\"small\\" maxWidth=\\"30rem\\" display=\\"auto\\">
<Text size=\\"small\\" as=\\"span\\" letterSpacing=\\"normal\\">
<Flex alignItems=\\"stretch\\" direction=\\"row\\" justifyItems=\\"space-between\\" as=\\"span\\" inline={false} visualDebug={false} wrapItems={false}>
<FlexItem grow={true} shrink={true} as=\\"span\\">
<div>
Last Assessment:
<DatetimeDisplay datetime={{...}} format=\\"%b %d, %l:%M %p\\" />
</div>
</FlexItem>
<FlexItem grow={true} shrink={true} align=\\"stretch\\" as=\\"span\\">
<Text size=\\"small\\" weight=\\"bold\\" as=\\"span\\" letterSpacing=\\"normal\\">
<div>
<div style={{...}}>
Meets Mastery
</div>
</div>
</Text>
</FlexItem>
</Flex>
<hr role=\\"presentation\\" />
<div>
<Text size=\\"small\\" weight=\\"bold\\" as=\\"span\\" letterSpacing=\\"normal\\">
Calculation Method
</Text>
<div>
Highest Score
</div>
<div style={{...}}>
<Text size=\\"small\\" weight=\\"bold\\" as=\\"span\\" letterSpacing=\\"normal\\">
Example
</Text>
</div>
<div>
Mastery score reflects the highest score of a graded assignment or quiz.
</div>
<div>
1- Item Scores: 1, 4, 2, 3
</div>
<div>
2- Final Score: 4
</div>
</div>
</Text>
</View>
</PopoverContent>
</Popover>
<FlexItem as=\\"span\\" grow={false} shrink={false} />
</span>"
`;
exports[`renders correctly with no results 1`] = `
"<span>
<Popover placement=\\"bottom\\" onToggle={[Function]} onClick={[Function]} onFocus={[Function]} onBlur={[Function]} onMouseOver={[Function]} onMouseOut={[Function]} onShow={[Function]} onDismiss={[Function]} stacking=\\"topmost\\" offsetX={0} offsetY={0} variant=\\"default\\" on={{...}} contentRef={[Function]} defaultShow={false} withArrow={true} trackPosition={true} constrain=\\"window\\" onPositioned={[Function]} onPositionChanged={[Function]} shouldRenderOffscreen={false} shouldContainFocus={false} shouldReturnFocus={true} shouldCloseOnDocumentClick={true} shouldCloseOnEscape={true} defaultFocusElement={{...}} label={{...}} mountNode={{...}} insertAt=\\"bottom\\" liveRegion={{...}} positionTarget={{...}} alignArrow={false}>
<PopoverTrigger>
<Link onClick={[Function]} variant=\\"default\\" as=\\"button\\" linkRef={[Function]} ellipsis={false} iconPlacement=\\"start\\">
<span style={{...}}>
<IconInfo />
</span>
<span>
<ScreenReaderContent as=\\"span\\">
Click to expand outcome details
</ScreenReaderContent>
</span>
</Link>
</PopoverTrigger>
<PopoverContent>
<View as=\\"div\\" padding=\\"small\\" maxWidth=\\"30rem\\" display=\\"auto\\">
<Text size=\\"small\\" as=\\"span\\" letterSpacing=\\"normal\\">
<Flex alignItems=\\"stretch\\" direction=\\"row\\" justifyItems=\\"space-between\\" as=\\"span\\" inline={false} visualDebug={false} wrapItems={false}>
<FlexItem grow={true} shrink={true} as=\\"span\\">
<div>
Last Assessment:
No submissions
</div>
</FlexItem>
<FlexItem grow={true} shrink={true} align=\\"stretch\\" as=\\"span\\">
<Text size=\\"small\\" weight=\\"bold\\" as=\\"span\\" letterSpacing=\\"normal\\">
<div>
<div style={{...}}>
I am Groot
</div>
</div>
</Text>
</FlexItem>
</Flex>
<hr role=\\"presentation\\" />
<div>
<Text size=\\"small\\" weight=\\"bold\\" as=\\"span\\" letterSpacing=\\"normal\\">
Calculation Method
</Text>
<div>
Highest Score
</div>
<div style={{...}}>
<Text size=\\"small\\" weight=\\"bold\\" as=\\"span\\" letterSpacing=\\"normal\\">
Example
</Text>
</div>
<div>
Mastery score reflects the highest score of a graded assignment or quiz.
</div>
<div>
1- Item Scores: 1, 4, 2, 3
</div>
<div>
2- Final Score: 4
</div>
</div>
</Text>
</View>
</PopoverContent>
</Popover>
<FlexItem as=\\"span\\" grow={false} shrink={false} />
</span>"
`;
exports[`renders the OutcomePopover component 1`] = `
"<span>
<Popover placement=\\"bottom\\" onToggle={[Function]} onClick={[Function]} onFocus={[Function]} onBlur={[Function]} onMouseOver={[Function]} onMouseOut={[Function]} onShow={[Function]} onDismiss={[Function]} stacking=\\"topmost\\" offsetX={0} offsetY={0} variant=\\"default\\" on={{...}} contentRef={[Function]} defaultShow={false} withArrow={true} trackPosition={true} constrain=\\"window\\" onPositioned={[Function]} onPositionChanged={[Function]} shouldRenderOffscreen={false} shouldContainFocus={false} shouldReturnFocus={true} shouldCloseOnDocumentClick={true} shouldCloseOnEscape={true} defaultFocusElement={{...}} label={{...}} mountNode={{...}} insertAt=\\"bottom\\" liveRegion={{...}} positionTarget={{...}} alignArrow={false}>
<PopoverTrigger>
<Link onClick={[Function]} variant=\\"default\\" as=\\"button\\" linkRef={[Function]} ellipsis={false} iconPlacement=\\"start\\">
<span style={{...}}>
<IconInfo />
</span>
<span>
<ScreenReaderContent as=\\"span\\">
Click to expand outcome details
</ScreenReaderContent>
</span>
</Link>
</PopoverTrigger>
<PopoverContent>
<View as=\\"div\\" padding=\\"small\\" maxWidth=\\"30rem\\" display=\\"auto\\">
<Text size=\\"small\\" as=\\"span\\" letterSpacing=\\"normal\\">
<Flex alignItems=\\"stretch\\" direction=\\"row\\" justifyItems=\\"space-between\\" as=\\"span\\" inline={false} visualDebug={false} wrapItems={false}>
<FlexItem grow={true} shrink={true} as=\\"span\\">
<div>
Last Assessment:
<DatetimeDisplay datetime={{...}} format=\\"%b %d, %l:%M %p\\" />
</div>
</FlexItem>
<FlexItem grow={true} shrink={true} align=\\"stretch\\" as=\\"span\\">
<Text size=\\"small\\" weight=\\"bold\\" as=\\"span\\" letterSpacing=\\"normal\\">
<div>
<div style={{...}}>
I am Groot
</div>
</div>
</Text>
</FlexItem>
</Flex>
<hr role=\\"presentation\\" />
<div>
<Text size=\\"small\\" weight=\\"bold\\" as=\\"span\\" letterSpacing=\\"normal\\">
Calculation Method
</Text>
<div>
Highest Score
</div>
<div style={{...}}>
<Text size=\\"small\\" weight=\\"bold\\" as=\\"span\\" letterSpacing=\\"normal\\">
Example
</Text>
</div>
<div>
Mastery score reflects the highest score of a graded assignment or quiz.
</div>
<div>
1- Item Scores: 1, 4, 2, 3
</div>
<div>
2- Final Score: 4
</div>
</div>
</Text>
</View>
</PopoverContent>
</Popover>
<FlexItem as=\\"span\\" grow={false} shrink={false} />
</span>"
`;

View File

@ -93,6 +93,7 @@ describe('expand and contract', () => {
mastered: false,
mastery_points: 0,
points_possible: 0,
calculation_method: 'highest',
results: [],
ratings: []
}