Add the choose quiz engine modal
closes QUIZ-7285 Test Plan --------- - Test with canvas provisioned with quiz_lti/quiz_api - With the new quizzes on quiz page FF enabled: -- Navigate to the quizzes page and click the |+ Quiz| button -- Ensure a modal is shown and is a11y compliant -- Ensure canceling/submitting with either option works as expected -- Ensure new quizzes created are shown on the quizzes page - With the new quizzes on quiz page FF disabled: -- Navigate to the quizzes page and click the |+ Quiz| button -- Ensure you are taken to the quizzes old creation flow -- Ensure new quizzes are not shown on the quizzes page -- Navigate to the Assignments page and click the |+ Quiz/Test| button -- Ensure you can create new quizzes and they are displayed only on the assignments page Change-Id: I022a0f857d74cf4d315a7c69e467c450889e4aad Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/223118 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: Jared Crystal <jcrystal@instructure.com> QA-Review: Jared Crystal <jcrystal@instructure.com> Product-Review: Kevin Dougherty <jdougherty@instructure.com>
This commit is contained in:
parent
01165c1db3
commit
3eb57d0489
|
@ -24,6 +24,7 @@ import '../../jquery.rails_flash_notifications'
|
|||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import ContentTypeExternalToolTray from 'jsx/shared/ContentTypeExternalToolTray'
|
||||
import QuizEngineModal from 'jsx/quizzes/QuizEngineModal'
|
||||
import {ltiState} from '../../../../public/javascripts/lti/post_message/handleLtiPostMessage'
|
||||
|
||||
export default class IndexView extends Backbone.View {
|
||||
|
@ -40,7 +41,8 @@ export default class IndexView extends Backbone.View {
|
|||
this.prototype.events = {
|
||||
'keyup #searchTerm': 'keyUpSearch',
|
||||
'mouseup #searchTerm': 'keyUpSearch',
|
||||
'click .header-bar-right .menu_tool_link': 'openExternalTool'
|
||||
'click .header-bar-right .menu_tool_link': 'openExternalTool',
|
||||
'click .choose-quiz-engine': 'chooseQuizEngine'
|
||||
}
|
||||
|
||||
this.prototype.keyUpSearch = _.debounce(function() {
|
||||
|
@ -104,6 +106,22 @@ export default class IndexView extends Backbone.View {
|
|||
return json
|
||||
}
|
||||
|
||||
chooseQuizEngine() {
|
||||
this.renderQuizEngineModal(true, $('.choose-quiz-engine'))
|
||||
}
|
||||
|
||||
renderQuizEngineModal(setOpen, returnFocusTo) {
|
||||
const handleDismiss = () => {
|
||||
this.renderQuizEngineModal(false)
|
||||
returnFocusTo && returnFocusTo.focus()
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<QuizEngineModal onDismiss={handleDismiss} setOpen={setOpen} />,
|
||||
$('#quiz-modal-mount-point')[0]
|
||||
)
|
||||
}
|
||||
|
||||
openExternalTool(ev) {
|
||||
if (ev != null) {
|
||||
ev.preventDefault()
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* Copyright (C) 2019 - 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, {useState} from 'react'
|
||||
import I18n from 'i18n!quiz_engine_modal'
|
||||
import CanvasModal from 'jsx/shared/components/CanvasModal'
|
||||
import {Link} from '@instructure/ui-link'
|
||||
import {RadioInputGroup, RadioInput} from '@instructure/ui-radio-input'
|
||||
import {Text} from '@instructure/ui-text'
|
||||
import {Button} from '@instructure/ui-buttons'
|
||||
import getCookie from 'jsx/shared/helpers/getCookie'
|
||||
|
||||
const CLASSIC = 'classic'
|
||||
const NEW = 'new'
|
||||
|
||||
function QuizEngineModal({setOpen, onDismiss}) {
|
||||
const [option, setOption] = useState()
|
||||
const authenticity_token = () => getCookie('_csrf_token')
|
||||
|
||||
const link = (
|
||||
<Link href="https://community.canvaslms.com/docs/DOC-12115-quizzes-lti-feature-comparison">
|
||||
{I18n.t('Learn more about the differences.')}
|
||||
</Link>
|
||||
)
|
||||
const newQuizLabel = <Text weight="bold">{I18n.t('New Quizzes')}</Text>
|
||||
const classicLabel = <Text weight="bold">{I18n.t('Classic Quizzes')}</Text>
|
||||
const newDesc = (
|
||||
<div style={{paddingLeft: '1.75rem', maxWidth: '23.5rem'}}>
|
||||
<Text weight="light">
|
||||
{I18n.t(`This has more question types like hotspot,
|
||||
categorization, matching, and ordering. It also has
|
||||
more moderation and accommodation features.`)}
|
||||
</Text>
|
||||
</div>
|
||||
)
|
||||
const classicDesc = (
|
||||
<div style={{paddingLeft: '1.75rem', maxWidth: '23.5rem'}}>
|
||||
<Text weight="light">
|
||||
{I18n.t(`For the time being, if you need security from
|
||||
3rd-party tools, Speedgrader, or CSVs for student
|
||||
response analysis, this is the better choice.`)}
|
||||
</Text>
|
||||
</div>
|
||||
)
|
||||
const footer = (
|
||||
<div>
|
||||
<Button onClick={onDismiss} margin="0 x-small 0 0" variant="light">
|
||||
{I18n.t('Cancel')}
|
||||
</Button>
|
||||
<Button type="submit" onClick={handleSubmit} variant="primary" disabled={!option}>
|
||||
{I18n.t('Submit')}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
const description = (
|
||||
<div style={{paddingBottom: '1.5rem', maxWidth: '25rem'}}>
|
||||
<Text>
|
||||
{I18n.t(`Canvas now has two quiz engines. Please choose which
|
||||
you'd like to use.`)}
|
||||
{link}
|
||||
</Text>
|
||||
</div>
|
||||
)
|
||||
|
||||
function post(path, params, method = 'post') {
|
||||
const form = document.createElement('form')
|
||||
form.method = method
|
||||
form.action = path
|
||||
for (const key in params) {
|
||||
if (params.hasOwnProperty(key)) {
|
||||
const hiddenField = document.createElement('input')
|
||||
hiddenField.type = 'hidden'
|
||||
hiddenField.name = key
|
||||
hiddenField.value = params[key]
|
||||
form.appendChild(hiddenField)
|
||||
}
|
||||
}
|
||||
document.body.appendChild(form)
|
||||
form.submit()
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
if (option === CLASSIC) {
|
||||
post(ENV.URLS.new_quiz_url, {authenticity_token: authenticity_token()})
|
||||
} else if (option === NEW) {
|
||||
window.location.href = `${ENV.URLS.new_assignment_url}?quiz_lti`
|
||||
}
|
||||
}
|
||||
|
||||
function handleChange(e, value) {
|
||||
setOption(value)
|
||||
}
|
||||
|
||||
return (
|
||||
<CanvasModal
|
||||
open={setOpen}
|
||||
onDismiss={onDismiss}
|
||||
padding="medium"
|
||||
label={I18n.t('Choose a Quiz Engine')}
|
||||
footer={footer}
|
||||
>
|
||||
{description}
|
||||
<RadioInputGroup name="quizEngine" onChange={handleChange} defaultValue={option}>
|
||||
<RadioInput key={CLASSIC} value={CLASSIC} label={classicLabel} size="large" />
|
||||
{classicDesc}
|
||||
<RadioInput key={NEW} value={NEW} label={newQuizLabel} size="large" />
|
||||
{newDesc}
|
||||
</RadioInputGroup>
|
||||
</CanvasModal>
|
||||
)
|
||||
}
|
||||
|
||||
export default QuizEngineModal
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright (C) 2019 - 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 {render, fireEvent} from '@testing-library/react'
|
||||
import QuizEngineModal from '../QuizEngineModal'
|
||||
|
||||
describe('QuizEngineModal', () => {
|
||||
beforeAll(() => {
|
||||
ENV = Object.assign(ENV, {
|
||||
URLS: {
|
||||
new_assignment_url: 'http://localhost/assignments',
|
||||
new_quiz_url: 'http://localhost/quizzes'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('renders a header, close button, and children', () => {
|
||||
const handleDismiss = jest.fn()
|
||||
const {getByText} = render(<QuizEngineModal setOpen onDismiss={handleDismiss} />)
|
||||
expect(getByText('Choose a Quiz Engine').tagName).toBe('H2')
|
||||
expect(getByText('Submit')).toBeInTheDocument()
|
||||
expect(getByText('Cancel')).toBeInTheDocument()
|
||||
const closeButton = getByText('Close').closest('button')
|
||||
expect(closeButton).toBeInTheDocument()
|
||||
fireEvent.click(closeButton)
|
||||
expect(handleDismiss).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('submit is disabled without a selected choice', () => {
|
||||
const handleDismiss = jest.fn()
|
||||
const {getByText} = render(<QuizEngineModal setOpen onDismiss={handleDismiss} />)
|
||||
expect(
|
||||
getByText('Submit')
|
||||
.closest('button')
|
||||
.getAttribute('disabled')
|
||||
).toBeDefined()
|
||||
})
|
||||
|
||||
it('submit is enabled with a selected choice', () => {
|
||||
const handleDismiss = jest.fn()
|
||||
const {getByText} = render(<QuizEngineModal setOpen onDismiss={handleDismiss} />)
|
||||
fireEvent.click(getByText('Classic Quizzes'))
|
||||
expect(
|
||||
getByText('Submit')
|
||||
.closest('button')
|
||||
.getAttribute('disabled')
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
it('submits to new quizzes', () => {
|
||||
const handleDismiss = jest.fn()
|
||||
const windLoc = global.window.location
|
||||
delete global.window.location
|
||||
global.window.location = {href: 'http://localhost'}
|
||||
const {getByText} = render(<QuizEngineModal setOpen onDismiss={handleDismiss} />)
|
||||
fireEvent.click(getByText('New Quizzes'))
|
||||
fireEvent.click(getByText('Submit').closest('button'))
|
||||
expect(window.location.href).toBe('http://localhost/assignments?quiz_lti')
|
||||
global.window.location = windLoc
|
||||
})
|
||||
|
||||
it('submits to classic quizzes', () => {
|
||||
const handleDismiss = jest.fn()
|
||||
window.HTMLFormElement.prototype.submit = jest.fn()
|
||||
const {getByText} = render(<QuizEngineModal setOpen onDismiss={handleDismiss} />)
|
||||
fireEvent.click(getByText('Classic Quizzes'))
|
||||
fireEvent.click(getByText('Submit').closest('button'))
|
||||
expect(window.HTMLFormElement.prototype.submit).toHaveBeenCalled()
|
||||
const form = document.querySelector(`[method="post"][action="${ENV.URLS.new_quiz_url}"]`)
|
||||
expect(form).toBeTruthy()
|
||||
})
|
||||
})
|
|
@ -156,13 +156,6 @@ li.quiz {
|
|||
overflow: visible;
|
||||
}
|
||||
|
||||
.new_quiz_lti_wrapper {
|
||||
display: inline-block;
|
||||
margin-#{direction(right)}: 6px;
|
||||
padding-#{direction(right)}: 10px;
|
||||
border-#{direction(right)}: 1px solid $ic-color-medium-lighter;
|
||||
}
|
||||
|
||||
.quiz-header {
|
||||
border-bottom: 1px solid #a4a4a4;
|
||||
overflow: hidden;
|
||||
|
|
|
@ -8,24 +8,11 @@
|
|||
{{#ifAny permissions.create permissions.manage}}
|
||||
<div class="header-bar-right">
|
||||
{{#if flags.quiz_lti_enabled}}
|
||||
<span class="new_quiz_lti_wrapper">
|
||||
<a
|
||||
href="{{ENV.URLS.new_assignment_url}}?quiz_lti"
|
||||
class="new_quiz_lti btn icon-plus"
|
||||
role="button"
|
||||
title='{{#t}}Add Quiz{{/t}}'
|
||||
aria-label='{{#t}}Add Quiz{{/t}}'
|
||||
>{{#t}}Quiz{{/t}}</a>
|
||||
</span>
|
||||
|
||||
<form class="new-quiz-form" action="{{urls.new_quiz_url}}" method="post">
|
||||
<input type="hidden" name="authenticity_token" />
|
||||
<button class="btn btn-primary new-quiz-link"
|
||||
title='{{#t}}Add Old Quiz{{/t}}'
|
||||
aria-label='{{#t}}Add Old Quiz{{/t}}'>
|
||||
<i class="icon-plus" /> {{#t}}Old Quiz{{/t}}
|
||||
</button>
|
||||
</form>
|
||||
<button class="btn btn-primary choose-quiz-engine"
|
||||
title='{{#t}}Add Quiz{{/t}}'
|
||||
aria-label='{{#t}}Add Quiz{{/t}}'>
|
||||
<i class="icon-plus" /> {{#t}}Quiz{{/t}}
|
||||
</button>
|
||||
{{else}}
|
||||
<form class="new-quiz-form" action="{{urls.new_quiz_url}}" method="post">
|
||||
<input type="hidden" name="authenticity_token" />
|
||||
|
@ -79,6 +66,7 @@
|
|||
|
||||
<div id="direct-share-mount-point" />
|
||||
<div id="external-tool-mount-point" />
|
||||
<div id="quiz-modal-mount-point" />
|
||||
|
||||
<div class="item-group-container">
|
||||
{{#if hasNoQuizzes}}
|
||||
|
|
|
@ -23,6 +23,7 @@ import NoQuizzesView from 'compiled/views/quizzes/NoQuizzesView'
|
|||
import $ from 'jquery'
|
||||
import fakeENV from 'helpers/fakeENV'
|
||||
import 'helpers/jquery.simulate'
|
||||
import ReactDOM from 'react-dom'
|
||||
|
||||
let fixtures = null
|
||||
const indexView = function(assignments, open, surveys) {
|
||||
|
@ -136,17 +137,25 @@ test('#hasSurveys if has surveys', () => {
|
|||
const view = indexView(null, null, surveys)
|
||||
ok(view.options.hasSurveys)
|
||||
})
|
||||
test("shows '+ Quiz' button if quiz lti enabled", () => {
|
||||
test("shows modified '+ Quiz' button if quiz lti enabled", () => {
|
||||
ENV.flags.quiz_lti_enabled = true
|
||||
const view = indexView(null, null, null)
|
||||
const $button = view.$('.new_quiz_lti')
|
||||
const $button = view.$('.choose-quiz-engine')
|
||||
equal($button.length, 1)
|
||||
ok(/\?quiz_lti$/.test($button.attr('href')))
|
||||
})
|
||||
test("does not show '+ Quiz' button when quiz lti disabled", () => {
|
||||
test("does not show modified '+ Quiz' button when quiz lti disabled", () => {
|
||||
ENV.flags.quiz_lti_enabled = false
|
||||
const view = indexView(null, null, null)
|
||||
equal(view.$('.new_quiz_lti').length, 0)
|
||||
equal(view.$('.choose-quiz-engine').length, 0)
|
||||
})
|
||||
test('renders choose quiz engine modal', () => {
|
||||
ENV.flags.quiz_lti_enabled = true
|
||||
sinon.stub(ReactDOM, 'render')
|
||||
const view = indexView(null, null, null)
|
||||
view.$('.choose-quiz-engine').simulate('click')
|
||||
const args = ReactDOM.render.firstCall.args
|
||||
equal(args[0].props.setOpen, true)
|
||||
ReactDOM.render.restore()
|
||||
})
|
||||
test('should render the view', () => {
|
||||
const assignments = new QuizCollection([
|
||||
|
|
Loading…
Reference in New Issue