add save button, confirmation modal for mastery calculation tab
closes OUT-4074 flag=account_level_mastery_scales test-plan: - Enable the FF, if not already - Visit the Outcome > Calculation tab - Verify that the save button is dimmed and is not clickable until changes are made to the calculation method - After changes are made, verify that updating works correctly and a confirmation modal appears to confirm any changes - Verify clicking 'Save' in the confirmation modal dims the save button - Verify canceling out of the confirmation modal does not save the calculation method when reloading - Verify that the wording in the modal within an account is different than a course - Verify that users who do not have permissions to change the mastery calculation (can be changed via permissions) do not see the save button - Verify invalid integers/ non numbers cannot be saved and that the error message correctly appears Change-Id: I6b77caa2c0ff477221c6b3fb3865003ee620e503 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/253025 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: Michael Brewer-Davis <mbd@instructure.com> Reviewed-by: Augusto Callejas <acallejas@instructure.com> QA-Review: Augusto Callejas <acallejas@instructure.com> Product-Review: Jody Sailor
This commit is contained in:
parent
cc45c56cb3
commit
3a785a5280
|
@ -16,19 +16,20 @@
|
|||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import I18n from 'i18n!confirmMasteryScaleEditModal'
|
||||
import I18n from 'i18n!confirmMasteryModal'
|
||||
import React, {Component} from 'react'
|
||||
import {func, string, bool} from 'prop-types'
|
||||
import {Button} from '@instructure/ui-buttons'
|
||||
|
||||
import Modal from '../shared/components/InstuiModal'
|
||||
|
||||
export default class ConfirmMasteryScaleEdit extends Component {
|
||||
export default class ConfirmMasteryModal extends Component {
|
||||
static propTypes = {
|
||||
onConfirm: func.isRequired,
|
||||
contextType: string.isRequired,
|
||||
modalText: string.isRequired,
|
||||
isOpen: bool.isRequired,
|
||||
onClose: func.isRequired
|
||||
onClose: func.isRequired,
|
||||
title: string.isRequired
|
||||
}
|
||||
|
||||
onConfirm = () => {
|
||||
|
@ -39,28 +40,16 @@ export default class ConfirmMasteryScaleEdit extends Component {
|
|||
this.props.onClose()
|
||||
}
|
||||
|
||||
getModalText = () => {
|
||||
const {contextType} = this.props
|
||||
if (contextType === 'Course') {
|
||||
return I18n.t(
|
||||
'This will update all rubrics aligned to outcomes within this course that have not yet been assessed.'
|
||||
)
|
||||
}
|
||||
return I18n.t(
|
||||
'This will update all account and course level rubrics that are tied to the account level mastery scale and have not yet been assessed.'
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Modal
|
||||
label={I18n.t('Confirm Mastery Scale')}
|
||||
label={this.props.title}
|
||||
open={this.props.isOpen}
|
||||
onDismiss={this.onClose}
|
||||
size="small"
|
||||
>
|
||||
<Modal.Body>
|
||||
<div>{this.getModalText()}</div>
|
||||
<div>{this.props.modalText}</div>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button onClick={this.onClose}>{I18n.t('Cancel')}</Button>
|
|
@ -16,13 +16,13 @@
|
|||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, {useState, useEffect, useRef, useCallback} from 'react'
|
||||
import React, {useState, useEffect} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import _ from 'lodash'
|
||||
import $ from 'jquery'
|
||||
import 'compiled/jquery.rails_flash_notifications'
|
||||
import I18n from 'i18n!MasteryScale'
|
||||
import numberHelper from 'jsx/shared/helpers/numberHelper'
|
||||
import {Button} from '@instructure/ui-buttons'
|
||||
import {FormFieldGroup} from '@instructure/ui-form-field'
|
||||
import {Flex} from '@instructure/ui-flex'
|
||||
import {Text} from '@instructure/ui-text'
|
||||
|
@ -32,6 +32,7 @@ import {View} from '@instructure/ui-view'
|
|||
import {NumberInput} from '@instructure/ui-number-input'
|
||||
import {SimpleSelect} from '@instructure/ui-simple-select'
|
||||
import CalculationMethodContent from 'compiled/models/grade_summary/CalculationMethodContent'
|
||||
import ConfirmMasteryModal from 'jsx/outcomes/ConfirmMasteryModal'
|
||||
|
||||
const validInt = (method, value) => {
|
||||
if (method.validRange) {
|
||||
|
@ -43,42 +44,29 @@ const validInt = (method, value) => {
|
|||
}
|
||||
|
||||
const CalculationIntInput = ({updateCalculationInt, calculationMethod, calculationInt}) => {
|
||||
const [currentValue, setCurrentValue] = useState(calculationInt)
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentValue(calculationInt)
|
||||
}, [calculationInt])
|
||||
|
||||
const handleChange = (_event, data) => {
|
||||
if (data === '') {
|
||||
setCurrentValue(null)
|
||||
updateCalculationInt('')
|
||||
} else {
|
||||
const parsed = numberHelper.parse(data)
|
||||
if (!Number.isNaN(parsed)) {
|
||||
setCurrentValue(parsed)
|
||||
if (validInt(calculationMethod, parsed)) {
|
||||
updateCalculationInt(parsed)
|
||||
}
|
||||
updateCalculationInt(parsed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleIncrement = () => {
|
||||
if (validInt(calculationMethod, calculationInt + 1)) {
|
||||
updateCalculationInt(calculationInt + 1)
|
||||
}
|
||||
updateCalculationInt(calculationInt !== '' ? calculationInt + 1 : 1)
|
||||
}
|
||||
|
||||
const handleDecrement = () => {
|
||||
if (validInt(calculationMethod, calculationInt - 1)) {
|
||||
updateCalculationInt(calculationInt - 1)
|
||||
}
|
||||
updateCalculationInt(calculationInt - 1)
|
||||
}
|
||||
|
||||
const errorMessages = []
|
||||
if (currentValue === null) {
|
||||
if (calculationInt === '') {
|
||||
errorMessages.push({text: I18n.t('Must be a number'), type: 'error'})
|
||||
} else if (!validInt(calculationMethod, currentValue)) {
|
||||
} else if (!validInt(calculationMethod, calculationInt)) {
|
||||
errorMessages.push({
|
||||
text: I18n.t('Must be between %{lower} and %{upper}', {
|
||||
lower: calculationMethod.validRange[0],
|
||||
|
@ -90,8 +78,8 @@ const CalculationIntInput = ({updateCalculationInt, calculationMethod, calculati
|
|||
|
||||
return (
|
||||
<NumberInput
|
||||
renderLabel={I18n.t('Parameter')}
|
||||
value={currentValue || ''}
|
||||
renderLabel={() => I18n.t('Parameter')}
|
||||
value={typeof calculationInt === 'number' ? calculationInt : ''}
|
||||
messages={errorMessages}
|
||||
onIncrement={handleIncrement}
|
||||
onDecrement={handleDecrement}
|
||||
|
@ -103,7 +91,7 @@ const CalculationIntInput = ({updateCalculationInt, calculationMethod, calculati
|
|||
const Display = ({calculationInt, currentMethod}) => {
|
||||
return (
|
||||
<>
|
||||
<Heading level="h4">{I18n.t('Proficiency Calculation')}</Heading>
|
||||
<Heading level="h4">{I18n.t('Mastery Calculation')}</Heading>
|
||||
<Text color="primary" weight="normal">
|
||||
{currentMethod.friendlyCalculationMethod}
|
||||
</Text>
|
||||
|
@ -132,11 +120,11 @@ const Form = ({
|
|||
return (
|
||||
<FormFieldGroup
|
||||
description={
|
||||
<ScreenReaderContent>{I18n.t('Proficiency calculation parameters')}</ScreenReaderContent>
|
||||
<ScreenReaderContent>{I18n.t('Mastery calculation parameters')}</ScreenReaderContent>
|
||||
}
|
||||
>
|
||||
<SimpleSelect
|
||||
renderLabel={I18n.t('Proficiency Calculation')}
|
||||
renderLabel={I18n.t('Mastery Calculation')}
|
||||
value={calculationMethodKey}
|
||||
onChange={updateCalculationMethod}
|
||||
>
|
||||
|
@ -178,23 +166,23 @@ const Example = ({currentMethod}) => {
|
|||
)
|
||||
}
|
||||
|
||||
const ProficiencyCalculation = ({method, update: rawUpdate, updateError, canManage}) => {
|
||||
const getModalText = contextType => {
|
||||
if (contextType === 'Course') {
|
||||
return I18n.t('This will update all student mastery results within this course.')
|
||||
}
|
||||
return I18n.t(
|
||||
'This will update all student mastery results tied to the account level mastery calculation.'
|
||||
)
|
||||
}
|
||||
|
||||
const ProficiencyCalculation = ({method, update, updateError, canManage, contextType}) => {
|
||||
const {calculationMethod: initialMethodKey, calculationInt: initialInt} = method
|
||||
|
||||
const [calculationMethodKey, setCalculationMethodKey] = useState(initialMethodKey)
|
||||
const [calculationInt, setCalculationInt] = useState(initialInt)
|
||||
const firstRender = useRef(true)
|
||||
|
||||
const update = useCallback(_.debounce(rawUpdate, 500), [rawUpdate])
|
||||
|
||||
useEffect(() => () => update.cancel(), [update]) // cancel on unmount
|
||||
|
||||
useEffect(() => {
|
||||
if (!firstRender.current) {
|
||||
update(calculationMethodKey, calculationInt)
|
||||
}
|
||||
firstRender.current = false
|
||||
}, [calculationMethodKey, calculationInt, update])
|
||||
const [allowSave, setAllowSave] = useState(false)
|
||||
const [showConfirmation, setShowConfirmationModal] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (updateError) {
|
||||
|
@ -210,12 +198,33 @@ const ProficiencyCalculation = ({method, update: rawUpdate, updateError, canMana
|
|||
|
||||
const updateCalculationMethod = (_event, data) => {
|
||||
const newMethod = data.id
|
||||
const newCalculationInt = calculationMethods[newMethod].defaultInt || null
|
||||
if (newMethod !== calculationMethodKey) {
|
||||
setCalculationMethodKey(newMethod)
|
||||
setCalculationInt(calculationMethods[newMethod].defaultInt || null)
|
||||
setCalculationInt(newCalculationInt)
|
||||
if (initialMethodKey === newMethod && initialInt === newCalculationInt) {
|
||||
setAllowSave(false)
|
||||
} else {
|
||||
setAllowSave(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const updateCalculationInt = newCalculationInt => {
|
||||
setCalculationInt(newCalculationInt)
|
||||
if (initialMethodKey === calculationMethodKey && initialInt === newCalculationInt) {
|
||||
setAllowSave(false)
|
||||
} else {
|
||||
setAllowSave(true)
|
||||
}
|
||||
}
|
||||
|
||||
const saveCalculationMethod = () => {
|
||||
update(calculationMethodKey, calculationInt)
|
||||
setShowConfirmationModal(false)
|
||||
setAllowSave(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<View as="div">
|
||||
<Flex alignItems="start" wrap="wrap">
|
||||
|
@ -227,7 +236,7 @@ const ProficiencyCalculation = ({method, update: rawUpdate, updateError, canMana
|
|||
calculationMethods={calculationMethods}
|
||||
currentMethod={currentMethod}
|
||||
updateCalculationMethod={updateCalculationMethod}
|
||||
setCalculationInt={setCalculationInt}
|
||||
setCalculationInt={updateCalculationInt}
|
||||
/>
|
||||
) : (
|
||||
<Display currentMethod={currentMethod} calculationInt={calculationInt} />
|
||||
|
@ -237,6 +246,28 @@ const ProficiencyCalculation = ({method, update: rawUpdate, updateError, canMana
|
|||
<Example currentMethod={currentMethod} />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
{canManage && (
|
||||
<div className="save">
|
||||
<Button
|
||||
variant="primary"
|
||||
interaction={allowSave ? 'enabled' : 'disabled'}
|
||||
onClick={() => {
|
||||
if (validInt(currentMethod, calculationInt)) {
|
||||
setShowConfirmationModal(true)
|
||||
}
|
||||
}}
|
||||
>
|
||||
{I18n.t('Save Mastery Calculation')}
|
||||
</Button>
|
||||
<ConfirmMasteryModal
|
||||
isOpen={showConfirmation}
|
||||
onConfirm={saveCalculationMethod}
|
||||
modalText={getModalText(contextType)}
|
||||
title={I18n.t('Confirm Mastery Calculation')}
|
||||
onClose={() => setShowConfirmationModal(false)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
@ -248,7 +279,8 @@ ProficiencyCalculation.propTypes = {
|
|||
}),
|
||||
canManage: PropTypes.bool,
|
||||
update: PropTypes.func.isRequired,
|
||||
updateError: PropTypes.string
|
||||
updateError: PropTypes.string,
|
||||
contextType: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
ProficiencyCalculation.defaultProps = {
|
||||
|
|
|
@ -96,6 +96,11 @@ describe('ProficiencyCalculation', () => {
|
|||
expect(getByText('Example')).not.toBeNull()
|
||||
expect(getByText(/most recent graded/)).not.toBeNull()
|
||||
})
|
||||
|
||||
it('does not render the save button', () => {
|
||||
const {queryByText} = render(<ProficiencyCalculation {...makeProps({canManage: false})} />)
|
||||
expect(queryByText(/Save Mastery Calculation/)).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('unlocked', () => {
|
||||
|
@ -138,20 +143,38 @@ describe('ProficiencyCalculation', () => {
|
|||
expect(getByDisplayValue('5')).not.toBeNull()
|
||||
})
|
||||
|
||||
it('debounces save', async () => {
|
||||
it('calls save when the button is clicked', () => {
|
||||
const update = jest.fn()
|
||||
const {getByLabelText} = render(<ProficiencyCalculation {...makeProps({update})} />)
|
||||
const {getByText, getByLabelText} = render(
|
||||
<ProficiencyCalculation {...makeProps({update})} />
|
||||
)
|
||||
const parameter = getByLabelText('Parameter')
|
||||
fireEvent.input(parameter, {target: {value: '22'}})
|
||||
fireEvent.input(parameter, {target: {value: '40'}})
|
||||
fireEvent.input(parameter, {target: {value: '44'}})
|
||||
fireEvent.input(parameter, {target: {value: '41'}})
|
||||
await wait(() => expect(update).toHaveBeenCalledTimes(1))
|
||||
fireEvent.click(getByText('Save Mastery Calculation'))
|
||||
fireEvent.click(getByText('Save'))
|
||||
expect(update).toHaveBeenCalledTimes(1)
|
||||
expect(update).toHaveBeenCalledWith('decaying_average', 41)
|
||||
})
|
||||
|
||||
it('save button is initially disabled', () => {
|
||||
const {getByText} = render(<ProficiencyCalculation {...makeProps()} />)
|
||||
expect(getByText('Save Mastery Calculation').closest('button').disabled).toEqual(true)
|
||||
})
|
||||
|
||||
it('save button geos back to disabled if changes are reverted', () => {
|
||||
const {getByText, getByLabelText} = render(<ProficiencyCalculation {...makeProps()} />)
|
||||
const parameter = getByLabelText('Parameter')
|
||||
fireEvent.input(parameter, {target: {value: '22'}})
|
||||
expect(getByText('Save Mastery Calculation').closest('button').disabled).toEqual(false)
|
||||
fireEvent.input(parameter, {target: {value: '75'}})
|
||||
expect(getByText('Save Mastery Calculation').closest('button').disabled).toEqual(true)
|
||||
})
|
||||
|
||||
describe('highest', () => {
|
||||
it('saves when method changed', async () => {
|
||||
it('calls update with the correct arguments', () => {
|
||||
const update = jest.fn()
|
||||
const {getByDisplayValue, getByText} = render(
|
||||
<ProficiencyCalculation {...makeProps({update})} />
|
||||
|
@ -160,12 +183,14 @@ describe('ProficiencyCalculation', () => {
|
|||
fireEvent.click(method)
|
||||
const newMethod = getByText('Highest Score')
|
||||
fireEvent.click(newMethod)
|
||||
await wait(() => expect(update).toHaveBeenCalledWith('highest', null))
|
||||
fireEvent.click(getByText('Save Mastery Calculation'))
|
||||
fireEvent.click(getByText('Save'))
|
||||
expect(update).toHaveBeenCalledWith('highest', null)
|
||||
})
|
||||
})
|
||||
|
||||
describe('latest', () => {
|
||||
it('saves when method changed', async () => {
|
||||
it('calls update with the correct arguments', () => {
|
||||
const update = jest.fn()
|
||||
const {getByDisplayValue, getByText} = render(
|
||||
<ProficiencyCalculation {...makeProps({update})} />
|
||||
|
@ -174,7 +199,9 @@ describe('ProficiencyCalculation', () => {
|
|||
fireEvent.click(method)
|
||||
const newMethod = getByText('Most Recent Score')
|
||||
fireEvent.click(newMethod)
|
||||
await wait(() => expect(update).toHaveBeenCalledWith('latest', null))
|
||||
fireEvent.click(getByText('Save Mastery Calculation'))
|
||||
fireEvent.click(getByText('Save'))
|
||||
expect(update).toHaveBeenCalledWith('latest', null)
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -200,19 +227,22 @@ describe('ProficiencyCalculation', () => {
|
|||
expect(getByText('Must be between 1 and 99')).not.toBeNull()
|
||||
})
|
||||
|
||||
it('saves only when int is valid', async () => {
|
||||
it('renders the confirmation modal only when int is valid', async () => {
|
||||
const update = jest.fn()
|
||||
const {getByLabelText} = render(<ProficiencyCalculation {...makeProps({update})} />)
|
||||
const {getByText, queryByText, getByLabelText} = render(
|
||||
<ProficiencyCalculation {...makeProps({update})} />
|
||||
)
|
||||
const parameter = getByLabelText('Parameter')
|
||||
fireEvent.input(parameter, {target: {value: '0'}})
|
||||
fireEvent.input(parameter, {target: {value: '22'}})
|
||||
fireEvent.input(parameter, {target: {value: '0'}})
|
||||
await wait(() => expect(update).toHaveBeenCalledTimes(1))
|
||||
fireEvent.input(parameter, {target: {value: '199'}})
|
||||
fireEvent.click(getByText('Save Mastery Calculation'))
|
||||
expect(queryByText('Save')).not.toBeInTheDocument()
|
||||
fireEvent.input(parameter, {target: {value: '40'}})
|
||||
await wait(() => expect(update).toHaveBeenCalledTimes(2))
|
||||
expect(update).toHaveBeenCalledWith('decaying_average', 22)
|
||||
expect(update).toHaveBeenCalledWith('decaying_average', 40)
|
||||
fireEvent.click(getByText('Save Mastery Calculation'))
|
||||
fireEvent.click(getByText('Save'))
|
||||
await wait(() => {
|
||||
expect(update).toHaveBeenCalledTimes(1)
|
||||
expect(update).toHaveBeenCalledWith('decaying_average', 40)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -250,25 +280,54 @@ describe('ProficiencyCalculation', () => {
|
|||
expect(getByText('Must be between 1 and 5')).not.toBeNull()
|
||||
})
|
||||
|
||||
it('saves only when int is valid', async () => {
|
||||
it('renders the confirmation modal only when int is valid', async () => {
|
||||
const update = jest.fn()
|
||||
const {getByLabelText} = render(
|
||||
const {getByText, queryByText, getByLabelText} = render(
|
||||
<ProficiencyCalculation
|
||||
{...makeProps({update, method: {calculationMethod: 'n_mastery', calculationInt: 5}})}
|
||||
/>
|
||||
)
|
||||
const parameter = getByLabelText('Parameter')
|
||||
fireEvent.input(parameter, {target: {value: '0'}})
|
||||
fireEvent.input(parameter, {target: {value: '2'}})
|
||||
fireEvent.input(parameter, {target: {value: '0'}})
|
||||
await wait(() => expect(update).toHaveBeenCalledTimes(1))
|
||||
fireEvent.input(parameter, {target: {value: '6'}})
|
||||
fireEvent.input(parameter, {target: {value: '3'}})
|
||||
fireEvent.input(parameter, {target: {value: '9'}})
|
||||
await wait(() => expect(update).toHaveBeenCalledTimes(2))
|
||||
expect(update).toHaveBeenCalledWith('n_mastery', 2)
|
||||
expect(update).toHaveBeenCalledWith('n_mastery', 3)
|
||||
fireEvent.click(getByText('Save Mastery Calculation'))
|
||||
expect(queryByText('Save')).not.toBeInTheDocument()
|
||||
fireEvent.input(parameter, {target: {value: '3'}})
|
||||
fireEvent.click(getByText('Save Mastery Calculation'))
|
||||
fireEvent.click(getByText('Save'))
|
||||
await wait(() => {
|
||||
expect(update).toHaveBeenCalledTimes(1)
|
||||
expect(update).toHaveBeenCalledWith('n_mastery', 3)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('confirmation modal', () => {
|
||||
it('renders correct text for the Account context', () => {
|
||||
const {getByDisplayValue, getByText} = render(<ProficiencyCalculation {...makeProps()} />)
|
||||
const method = getByDisplayValue('Decaying Average')
|
||||
fireEvent.click(method)
|
||||
const newMethod = getByText('Most Recent Score')
|
||||
fireEvent.click(newMethod)
|
||||
fireEvent.click(getByText('Save Mastery Calculation'))
|
||||
expect(getByText(/Confirm Mastery Calculation/)).not.toBeNull()
|
||||
expect(
|
||||
getByText(/all student mastery results tied to the account level mastery calculation/)
|
||||
).not.toBeNull()
|
||||
})
|
||||
|
||||
it('renders correct text for the Course context', () => {
|
||||
const {getByDisplayValue, getByText} = render(
|
||||
<ProficiencyCalculation {...makeProps()} contextType="Course" />
|
||||
)
|
||||
const method = getByDisplayValue('Decaying Average')
|
||||
fireEvent.click(method)
|
||||
const newMethod = getByText('Most Recent Score')
|
||||
fireEvent.click(newMethod)
|
||||
fireEvent.click(getByText('Save Mastery Calculation'))
|
||||
expect(getByText(/Confirm Mastery Calculation/)).not.toBeNull()
|
||||
expect(getByText(/all student mastery results within this course/)).not.toBeNull()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -155,7 +155,7 @@ describe('MasteryCalculation', () => {
|
|||
</MockedProvider>
|
||||
)
|
||||
await waitForElementToBeRemoved(() => queryByText('Loading'))
|
||||
expect(getByText('Proficiency Calculation')).not.toBeNull()
|
||||
expect(getByText('Mastery Calculation')).not.toBeNull()
|
||||
})
|
||||
|
||||
describe('update outcomeCalculationMethod', () => {
|
||||
|
@ -187,16 +187,19 @@ describe('MasteryCalculation', () => {
|
|||
result: updateCall
|
||||
}
|
||||
]
|
||||
it('submits a request when calculation method is updated', async () => {
|
||||
const {findByLabelText} = render(
|
||||
it('submits a request when calculation method is saved', async () => {
|
||||
const {getByText, findByLabelText} = render(
|
||||
<MockedProvider mocks={updateMocks} addTypename={false}>
|
||||
<MasteryCalculation contextType="Account" contextId="11" />
|
||||
</MockedProvider>
|
||||
)
|
||||
const parameter = await findByLabelText(/Parameter/)
|
||||
fireEvent.input(parameter, {target: {value: '88'}})
|
||||
|
||||
await wait(() => expect(updateCall).toHaveBeenCalled())
|
||||
fireEvent.click(getByText('Save Mastery Calculation'))
|
||||
fireEvent.click(getByText('Save'))
|
||||
await wait(() => {
|
||||
expect(updateCall).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import $ from 'jquery'
|
||||
import React, {useCallback} from 'react'
|
||||
import I18n from 'i18n!MasteryScale'
|
||||
import {Spinner} from '@instructure/ui-spinner'
|
||||
|
@ -39,11 +40,12 @@ const MasteryCalculation = ({contextType, contextId}) => {
|
|||
const [setCalculationMethodQuery, {error: setCalculationMethodError}] = useMutation(
|
||||
SET_OUTCOME_CALCULATION_METHOD
|
||||
)
|
||||
|
||||
const setCalculationMethod = useCallback(
|
||||
(calculationMethod, calculationInt) => {
|
||||
setCalculationMethodQuery({
|
||||
variables: {contextType, contextId, calculationMethod, calculationInt}
|
||||
})
|
||||
}).then(() => $.flashMessage(I18n.t('Mastery calculation saved')))
|
||||
},
|
||||
[contextType, contextId, setCalculationMethodQuery]
|
||||
)
|
||||
|
@ -58,7 +60,7 @@ const MasteryCalculation = ({contextType, contextId}) => {
|
|||
if (error) {
|
||||
return (
|
||||
<Text color="danger">
|
||||
{I18n.t('An error occurred while loading the outcome calculation: %{error}', {error})}
|
||||
{I18n.t('An error occurred while loading the mastery calculation: %{error}', {error})}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ import PropTypes from 'prop-types'
|
|||
import {Button} from '@instructure/ui-buttons'
|
||||
import {Flex} from '@instructure/ui-flex'
|
||||
import {IconPlusLine} from '@instructure/ui-icons'
|
||||
import {capitalizeFirstLetter} from '@instructure/ui-utils'
|
||||
import I18n from 'i18n!ProficiencyTable'
|
||||
import {View} from '@instructure/ui-view'
|
||||
import ProficiencyRating from './ProficiencyRating'
|
||||
|
@ -30,7 +29,7 @@ import _ from 'lodash'
|
|||
import {fromJS, List} from 'immutable'
|
||||
import NumberHelper from '../../shared/helpers/numberHelper'
|
||||
import WithBreakpoints, {breakpointsShape} from '../../shared/WithBreakpoints'
|
||||
import ConfirmMasteryScaleEdit from 'jsx/outcomes/ConfirmMasteryScaleEdit'
|
||||
import ConfirmMasteryModal from 'jsx/outcomes/ConfirmMasteryModal'
|
||||
|
||||
const ADD_DEFAULT_COLOR = 'EF4437'
|
||||
|
||||
|
@ -152,17 +151,10 @@ class ProficiencyTable extends React.Component {
|
|||
() => {
|
||||
this.props
|
||||
.update(this.stateToConfig())
|
||||
.then(() => {
|
||||
$.flashMessage(
|
||||
I18n.t(`%{context} mastery scale saved`, {
|
||||
context: capitalizeFirstLetter(this.props.contextType)
|
||||
})
|
||||
)
|
||||
})
|
||||
.then(() => $.flashMessage(I18n.t(`Mastery scale saved`)))
|
||||
.catch(e => {
|
||||
$.flashError(
|
||||
I18n.t('An error occurred while saving %{context} mastery scale: %{message}', {
|
||||
context: capitalizeFirstLetter(this.props.contextType),
|
||||
I18n.t('An error occurred while saving the mastery scale: %{message}', {
|
||||
message: e.message
|
||||
})
|
||||
)
|
||||
|
@ -318,9 +310,21 @@ class ProficiencyTable extends React.Component {
|
|||
|
||||
invalidDescription = description => !description || description.trim().length === 0
|
||||
|
||||
getModalText = () => {
|
||||
const {contextType} = this.props
|
||||
if (contextType === 'Course') {
|
||||
return I18n.t(
|
||||
'This will update all rubrics aligned to outcomes within this course that have not yet been assessed.'
|
||||
)
|
||||
}
|
||||
return I18n.t(
|
||||
'This will update all account and course level rubrics that are tied to the account level mastery scale and have not yet been assessed.'
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {showConfirmation} = this.state
|
||||
const {breakpoints, canManage, contextType} = this.props
|
||||
const {breakpoints, canManage} = this.props
|
||||
const isMobileView = breakpoints.mobileOnly
|
||||
return (
|
||||
<>
|
||||
|
@ -397,10 +401,11 @@ class ProficiencyTable extends React.Component {
|
|||
{I18n.t('Save Mastery Scale')}
|
||||
</Button>
|
||||
</div>
|
||||
<ConfirmMasteryScaleEdit
|
||||
<ConfirmMasteryModal
|
||||
isOpen={showConfirmation}
|
||||
contextType={contextType}
|
||||
onConfirm={this.handleSubmit}
|
||||
modalText={this.getModalText()}
|
||||
title={I18n.t('Confirm Mastery Scale')}
|
||||
onClose={this.hideConfirmationModal}
|
||||
/>
|
||||
</>
|
||||
|
|
|
@ -146,7 +146,7 @@ describe('default proficiency', () => {
|
|||
fireEvent.click(getByText('Save'))
|
||||
await wait(() => {
|
||||
expect(updateSpy).toHaveBeenCalled()
|
||||
expect(flashMock).toHaveBeenCalledWith('Course mastery scale saved')
|
||||
expect(flashMock).toHaveBeenCalledWith('Mastery scale saved')
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -469,3 +469,25 @@ describe('custom proficiency', () => {
|
|||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('confirmation modal', () => {
|
||||
it('renders correct text for the Account context', () => {
|
||||
const {getByDisplayValue, getByText} = render(<ProficiencyTable {...defaultProps} />)
|
||||
const pointsInput = getByDisplayValue('3')
|
||||
fireEvent.change(pointsInput, {target: {value: '1000'}})
|
||||
fireEvent.click(getByText('Save Mastery Scale'))
|
||||
expect(getByText(/Confirm Mastery Scale/)).not.toBeNull()
|
||||
expect(getByText(/all account and course level rubrics/)).not.toBeNull()
|
||||
})
|
||||
|
||||
it('renders correct text for the Course context', () => {
|
||||
const {getByDisplayValue, getByText} = render(
|
||||
<ProficiencyTable {...defaultProps} contextType="Course" />
|
||||
)
|
||||
const pointsInput = getByDisplayValue('3')
|
||||
fireEvent.change(pointsInput, {target: {value: '1000'}})
|
||||
fireEvent.click(getByText('Save Mastery Scale'))
|
||||
expect(getByText(/Confirm Mastery Scale/)).not.toBeNull()
|
||||
expect(getByText(/all rubrics aligned to outcomes within this course/)).not.toBeNull()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -16,21 +16,22 @@
|
|||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import React from 'react'
|
||||
import ConfirmMasteryScaleEdit from '../ConfirmMasteryScaleEdit'
|
||||
import ConfirmMasteryModal from '../ConfirmMasteryModal'
|
||||
import {render, fireEvent} from '@testing-library/react'
|
||||
|
||||
const defaultProps = () => ({
|
||||
onConfirm: () => {},
|
||||
contextType: 'account',
|
||||
isOpen: true,
|
||||
onClose: () => {}
|
||||
onClose: () => {},
|
||||
title: 'title',
|
||||
modalText: 'body!!'
|
||||
})
|
||||
|
||||
it('calls onClose and does not call onConfirm when canceled', () => {
|
||||
const onConfirm = jest.fn()
|
||||
const onClose = jest.fn()
|
||||
const {getByText} = render(
|
||||
<ConfirmMasteryScaleEdit {...defaultProps()} onConfirm={onConfirm} onClose={onClose} />
|
||||
<ConfirmMasteryModal {...defaultProps()} onConfirm={onConfirm} onClose={onClose} />
|
||||
)
|
||||
fireEvent.click(getByText('Cancel'))
|
||||
expect(onConfirm).not.toHaveBeenCalled()
|
||||
|
@ -39,19 +40,13 @@ it('calls onClose and does not call onConfirm when canceled', () => {
|
|||
|
||||
it('does call onConfirm when saved', () => {
|
||||
const onConfirm = jest.fn()
|
||||
const {getByText} = render(<ConfirmMasteryScaleEdit {...defaultProps()} onConfirm={onConfirm} />)
|
||||
const {getByText} = render(<ConfirmMasteryModal {...defaultProps()} onConfirm={onConfirm} />)
|
||||
fireEvent.click(getByText('Save'))
|
||||
expect(onConfirm).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
describe('modal text', () => {
|
||||
it('renders correct text for an Account context', () => {
|
||||
const {getByText} = render(<ConfirmMasteryScaleEdit {...defaultProps()} />)
|
||||
expect(getByText(/all account and course level rubrics/)).not.toBeNull()
|
||||
})
|
||||
|
||||
it('renders correct text for a Course context', () => {
|
||||
const {getByText} = render(<ConfirmMasteryScaleEdit {...defaultProps()} contextType="Course" />)
|
||||
expect(getByText(/all rubrics aligned to outcomes within this course/)).not.toBeNull()
|
||||
})
|
||||
it('renders the modalText and title provided as props', () => {
|
||||
const {getByText} = render(<ConfirmMasteryModal {...defaultProps()} />)
|
||||
expect(getByText(/title/)).not.toBeNull()
|
||||
expect(getByText(/body!!/)).not.toBeNull()
|
||||
})
|
Loading…
Reference in New Issue