add conference ui to calendar details page
refs CAL-4 flag=calendar_conferences Test plan: - enable FF "Add Conferences from Calendar" and "Allow Conference Selection..." - enable BigBlueButton with dummy settings at /plugins/big_blue_button - create two courses, one with multiple sections - add LTI tool for conference selection to one course - open add calendar event dialog at /calendar - switch context to one of the courses - verify that conferencing options appear (two options if in course with LTI; one option otherwise) - click more options to open detailed edit page - verify that conferencing options appear the same way - add a conference - save the conference - verify that conference appears on the calendar - repeat in the other context to verify other select UI - add a conference before clicking "more options" and verify that it is included in the more options page - verfy that updating the conference and then cancelling on the more options page does not save the update - in the course with multiple sections, select "use a different date for each section" - verify that the conference for the parent event is shown for each of the child events in the calendar - verify that the conference can't be edited from the child events - verify that updating the parent event conference from the More Details page also updates the child events (as seen in the calendar) Change-Id: I9a7dccc9962d3c056f6a6a5fdb8a501ce8960c18 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/235298 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: Ken McGrady <kmcgrady@instructure.com> QA-Review: Steve Shepherd <sshepherd@instructure.com> Product-Review: Michael Brewer-Davis <mbd@instructure.com>
This commit is contained in:
parent
f869f6b1f9
commit
addd35f280
|
@ -71,12 +71,14 @@ export default class CalendarEvent extends Backbone.Model {
|
|||
return result
|
||||
}
|
||||
|
||||
fetch(otps = {}) {
|
||||
fetch(opts = {}) {
|
||||
let sectionsDfd, syncDfd
|
||||
|
||||
this.showSpinner()
|
||||
|
||||
const {success, error, ...options} = otps
|
||||
const {success, error, ...options} = opts
|
||||
|
||||
options.url = this.url() + '?include[]=web_conference'
|
||||
|
||||
if (this.get('id')) {
|
||||
syncDfd = (this.sync || Backbone.sync).call(this, 'read', this, options)
|
||||
|
@ -128,6 +130,7 @@ export default class CalendarEvent extends Backbone.Model {
|
|||
|
||||
static mergeSectionsIntoCalendarEvent(eventData = {}, sections) {
|
||||
eventData.recurring_calendar_events = ENV.RECURRING_CALENDAR_EVENTS_ENABLED
|
||||
eventData.include_conference_selection = ENV.CALENDAR?.CONFERENCES_ENABLED
|
||||
eventData.course_sections = sections
|
||||
eventData.use_section_dates = !!(eventData.child_events && eventData.child_events.length)
|
||||
_(eventData.child_events).each((child, index) => {
|
||||
|
|
|
@ -80,6 +80,10 @@ export default class EditCalendarEventDetails {
|
|||
this.renderConferenceWidget()
|
||||
}
|
||||
|
||||
canUpdateConference() {
|
||||
return !this.event.lockedTitle
|
||||
}
|
||||
|
||||
setConference = conference => {
|
||||
this.conference = conference
|
||||
setTimeout(this.renderConferenceWidget, 0)
|
||||
|
@ -97,7 +101,8 @@ export default class EditCalendarEventDetails {
|
|||
}
|
||||
const conferenceNode = document.getElementById('calendar_event_conference_selection')
|
||||
const activeConferenceTypes = this.getActiveConferenceTypes()
|
||||
if (activeConferenceTypes.length === 0) {
|
||||
const setConference = this.canUpdateConference() ? this.setConference : null
|
||||
if (!this.conference && (!this.canUpdateConference() || activeConferenceTypes.length === 0)) {
|
||||
this.conference = null
|
||||
conferenceNode.closest('tr').className = 'hide'
|
||||
} else {
|
||||
|
@ -106,7 +111,7 @@ export default class EditCalendarEventDetails {
|
|||
<CalendarConferenceWidget
|
||||
context={this.currentContextInfo.asset_string}
|
||||
conference={this.conference}
|
||||
setConference={this.setConference}
|
||||
setConference={setConference}
|
||||
conferenceTypes={activeConferenceTypes}
|
||||
/>,
|
||||
conferenceNode
|
||||
|
@ -189,7 +194,7 @@ export default class EditCalendarEventDetails {
|
|||
if (data.end_time) params.end_time = data.end_time
|
||||
if (data.duplicate) params.duplicate = data.duplicate
|
||||
|
||||
if (ENV.CALENDAR?.CONFERENCES_ENABLED) {
|
||||
if (ENV.CALENDAR?.CONFERENCES_ENABLED && this.canUpdateConference()) {
|
||||
if (this.conference) {
|
||||
params.web_conference = this.conference
|
||||
} else {
|
||||
|
@ -228,7 +233,7 @@ export default class EditCalendarEventDetails {
|
|||
}
|
||||
this.$form.find('.more_options_link').attr('href', moreOptionsHref)
|
||||
|
||||
if (ENV.CALENDAR?.CONFERENCES_ENABLED) {
|
||||
if (ENV.CALENDAR?.CONFERENCES_ENABLED && this.canUpdateConference()) {
|
||||
// check conference is still valid in context
|
||||
if (
|
||||
this.conference &&
|
||||
|
@ -286,7 +291,7 @@ export default class EditCalendarEventDetails {
|
|||
'calendar_event[end_at]': data.end_at ? data.end_at.toISOString() : '',
|
||||
'calendar_event[location_name]': location_name
|
||||
}
|
||||
if (ENV.CALENDAR?.CONFERENCES_ENABLED) {
|
||||
if (ENV.CALENDAR?.CONFERENCES_ENABLED && this.canUpdateConference()) {
|
||||
if (this.conference) {
|
||||
const conferenceParams = new URLSearchParams(
|
||||
$.param({calendar_event: {web_conference: this.conference}})
|
||||
|
|
|
@ -22,6 +22,9 @@ import I18n from 'i18n!calendar.edit'
|
|||
import {showFlashAlert} from 'jsx/shared/FlashAlert'
|
||||
import tz from 'timezone'
|
||||
import Backbone from 'Backbone'
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import 'jquery.instructure_forms'
|
||||
import editCalendarEventFullTemplate from 'jst/calendar/editCalendarEventFull'
|
||||
import MissingDateDialogView from '../views/calendar/MissingDateDialogView'
|
||||
import RichContentEditor from 'jsx/shared/rce/RichContentEditor'
|
||||
|
@ -30,6 +33,8 @@ import deparam from '../util/deparam'
|
|||
import KeyboardShortcuts from '../views/editor/KeyboardShortcuts'
|
||||
import coupleTimeFields from '../util/coupleTimeFields'
|
||||
import datePickerFormat from 'jsx/shared/helpers/datePickerFormat'
|
||||
import CalendarConferenceWidget from 'jsx/conferences/calendar/CalendarConferenceWidget'
|
||||
import filterConferenceTypes from 'jsx/conferences/utils/filterConferenceTypes'
|
||||
|
||||
RichContentEditor.preloadRemoteModule()
|
||||
|
||||
|
@ -46,6 +51,7 @@ export default class EditCalendarEventView extends Backbone.View {
|
|||
this.toggleUseSectionDates = this.toggleUseSectionDates.bind(this)
|
||||
this.enableDuplicateFields = this.enableDuplicateFields.bind(this)
|
||||
this.duplicateCheckboxChanged = this.duplicateCheckboxChanged.bind(this)
|
||||
this.renderConferenceWidget = this.renderConferenceWidget.bind(this)
|
||||
|
||||
super.initialize(...arguments)
|
||||
this.model.fetch().done(() => {
|
||||
|
@ -59,7 +65,8 @@ export default class EditCalendarEventView extends Backbone.View {
|
|||
'description',
|
||||
'location_name',
|
||||
'location_address',
|
||||
'duplicate'
|
||||
'duplicate',
|
||||
'web_conference'
|
||||
)
|
||||
if (picked_params.start_at) {
|
||||
picked_params.start_date = tz.format(
|
||||
|
@ -115,7 +122,7 @@ export default class EditCalendarEventView extends Backbone.View {
|
|||
datepicker: {dateFormat: datePickerFormat(I18n.t('#date.formats.default'))}
|
||||
})
|
||||
this.$('.time_field').time_field()
|
||||
this.$('.date_start_end_row').each((_, row) => {
|
||||
this.$('.date_start_end_row').each((_unused, row) => {
|
||||
const date = $('.start_date', row).first()
|
||||
const start = $('.start_time', row).first()
|
||||
const end = $('.end_time', row).first()
|
||||
|
@ -128,9 +135,44 @@ export default class EditCalendarEventView extends Backbone.View {
|
|||
|
||||
_.defer(this.attachKeyboardShortcuts)
|
||||
_.defer(this.toggleDuplicateOptions)
|
||||
_.defer(this.renderConferenceWidget)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
setConference = conference => {
|
||||
this.model.set('web_conference', conference)
|
||||
setTimeout(this.renderConferenceWidget, 0)
|
||||
}
|
||||
|
||||
getActiveConferenceTypes() {
|
||||
const conferenceTypes = ENV.conferences?.conference_types || []
|
||||
const context = this.model.get('context_code')
|
||||
return filterConferenceTypes(conferenceTypes, context)
|
||||
}
|
||||
|
||||
renderConferenceWidget() {
|
||||
if (!ENV.CALENDAR?.CONFERENCES_ENABLED) {
|
||||
return
|
||||
}
|
||||
const conferenceNode = document.getElementById('calendar_event_conference_selection')
|
||||
const activeConferenceTypes = this.getActiveConferenceTypes()
|
||||
if (!this.model.get('web_conference') && activeConferenceTypes.length === 0) {
|
||||
conferenceNode.closest('tr').className = 'hide'
|
||||
} else {
|
||||
conferenceNode.closest('tr').className = ''
|
||||
ReactDOM.render(
|
||||
<CalendarConferenceWidget
|
||||
context={this.model.get('context_code')}
|
||||
conference={this.model.get('web_conference')}
|
||||
setConference={this.setConference}
|
||||
conferenceTypes={activeConferenceTypes}
|
||||
/>,
|
||||
conferenceNode
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
attachKeyboardShortcuts() {
|
||||
if (!ENV.use_rce_enhancements) {
|
||||
return $('.switch_event_description_view')
|
||||
|
@ -242,6 +284,15 @@ export default class EditCalendarEventView extends Backbone.View {
|
|||
if (dialog.render()) return
|
||||
}
|
||||
|
||||
if (ENV.CALENDAR?.CONFERENCES_ENABLED) {
|
||||
const conference = this.model.get('web_conference')
|
||||
if (conference) {
|
||||
eventData.web_conference = conference
|
||||
} else {
|
||||
eventData.web_conference = ''
|
||||
}
|
||||
}
|
||||
|
||||
return this.saveEvent(eventData)
|
||||
}
|
||||
|
||||
|
@ -249,7 +300,7 @@ export default class EditCalendarEventView extends Backbone.View {
|
|||
return this.$el.disableWhileLoading(
|
||||
this.model.save(eventData, {
|
||||
success: () => this.redirectWithMessage(I18n.t('event_saved', 'Event Saved Successfully')),
|
||||
error: (model, response, options) =>
|
||||
error: (_model, response, _options) =>
|
||||
showFlashAlert({
|
||||
message: response.responseText,
|
||||
err: null,
|
||||
|
@ -305,7 +356,6 @@ export default class EditCalendarEventView extends Backbone.View {
|
|||
append_iterator: this.$el.find('#append_iterator').is(':checked')
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
|
@ -320,7 +370,7 @@ export default class EditCalendarEventView extends Backbone.View {
|
|||
return this.$el.find('.duplicate_event_row').toggle(!disableValue)
|
||||
}
|
||||
|
||||
duplicateCheckboxChanged(jsEvent, propagate) {
|
||||
duplicateCheckboxChanged(jsEvent, _propagate) {
|
||||
return this.enableDuplicateFields(jsEvent.target.checked)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {within, fireEvent} from '@testing-library/dom'
|
||||
import {within, fireEvent, getByText} from '@testing-library/dom'
|
||||
import commonEventFactory from '../commonEventFactory'
|
||||
import EditCalendarEventDetails from '../EditCalendarEventDetails'
|
||||
|
||||
|
@ -36,8 +36,17 @@ describe('EditCalendarEventDetails', () => {
|
|||
window.ENV = null
|
||||
})
|
||||
|
||||
function render() {
|
||||
const event = commonEventFactory({calendar_event: {id: 1, context_code: 'course_1'}}, CONTEXTS)
|
||||
function render(overrides = {}) {
|
||||
const event = commonEventFactory(
|
||||
{
|
||||
calendar_event: {
|
||||
id: 1,
|
||||
context_code: 'course_1',
|
||||
...overrides
|
||||
}
|
||||
},
|
||||
CONTEXTS
|
||||
)
|
||||
event.allPossibleContexts = CONTEXTS
|
||||
|
||||
return new EditCalendarEventDetails(
|
||||
|
@ -76,59 +85,98 @@ describe('EditCalendarEventDetails', () => {
|
|||
expect(conferencingNode.closest('tr').className).not.toEqual('hide')
|
||||
})
|
||||
|
||||
it('does not show conferencing options when context does not support conferences', () => {
|
||||
enableConferences(CONFERENCE_TYPES.slice(1))
|
||||
render()
|
||||
const conferencingNode = within(document.body).getByText('Conferencing:')
|
||||
expect(conferencingNode.closest('tr').className).toEqual('hide')
|
||||
describe('when context does not support conferences', () => {
|
||||
it('does not show conferencing options when there is no current conference', () => {
|
||||
enableConferences(CONFERENCE_TYPES.slice(1))
|
||||
render()
|
||||
const conferencingRow = within(document.body)
|
||||
.getByText('Conferencing:')
|
||||
.closest('tr')
|
||||
expect(conferencingRow.className).toEqual('hide')
|
||||
})
|
||||
|
||||
it('does show current conference when there is a current conference', () => {
|
||||
enableConferences(CONFERENCE_TYPES.slice(1))
|
||||
render({web_conference: {id: 1, conference_type: 'type1', title: 'FooConf'}})
|
||||
const conferencingRow = within(document.body)
|
||||
.getByText('Conferencing:')
|
||||
.closest('tr')
|
||||
expect(conferencingRow.className).not.toEqual('hide')
|
||||
expect(getByText(conferencingRow, 'FooConf')).not.toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
it('submits web_conference params for current conference', () => {
|
||||
enableConferences()
|
||||
const view = render()
|
||||
view.conference = {
|
||||
id: 1,
|
||||
name: 'Foo',
|
||||
conference_type: 'type1',
|
||||
lti_settings: {a: 1, b: 2, c: 3}
|
||||
}
|
||||
view.event.save = jest.fn(params => {
|
||||
;[
|
||||
['calendar_event[web_conference][id]', '1'],
|
||||
['calendar_event[web_conference][name]', 'Foo'],
|
||||
['calendar_event[web_conference][conference_type]', 'type1'],
|
||||
['calendar_event[web_conference][lti_settings][a]', '1'],
|
||||
['calendar_event[web_conference][lti_settings][b]', '2'],
|
||||
['calendar_event[web_conference][lti_settings][c]', '3']
|
||||
].forEach(([key, value]) => {
|
||||
expect(params[key]).toEqual(value)
|
||||
describe('when event conference can be updated', () => {
|
||||
it('submits web_conference params for current conference', () => {
|
||||
enableConferences()
|
||||
const view = render()
|
||||
view.conference = {
|
||||
id: 1,
|
||||
name: 'Foo',
|
||||
conference_type: 'type1',
|
||||
lti_settings: {a: 1, b: 2, c: 3}
|
||||
}
|
||||
view.event.save = jest.fn(params => {
|
||||
;[
|
||||
['calendar_event[web_conference][id]', '1'],
|
||||
['calendar_event[web_conference][name]', 'Foo'],
|
||||
['calendar_event[web_conference][conference_type]', 'type1'],
|
||||
['calendar_event[web_conference][lti_settings][a]', '1'],
|
||||
['calendar_event[web_conference][lti_settings][b]', '2'],
|
||||
['calendar_event[web_conference][lti_settings][c]', '3']
|
||||
].forEach(([key, value]) => {
|
||||
expect(params[key]).toEqual(value)
|
||||
})
|
||||
})
|
||||
const submit = within(document.body).getByText('Submit')
|
||||
fireEvent.click(submit)
|
||||
expect(view.event.save).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('submits empty web_conference params when no current conference', () => {
|
||||
enableConferences()
|
||||
const view = render()
|
||||
view.conference = null
|
||||
view.event.save = jest.fn(params => {
|
||||
expect(params['calendar_event[web_conference]']).toEqual('')
|
||||
})
|
||||
const submit = within(document.body).getByText('Submit')
|
||||
fireEvent.click(submit)
|
||||
expect(view.event.save).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('does not submit web_conference params when conferencing is disabled', () => {
|
||||
const view = render()
|
||||
view.event.save = jest.fn(params => {
|
||||
expect(params['calendar_event[web_conference]']).toBeUndefined()
|
||||
})
|
||||
const submit = within(document.body).getByText('Submit')
|
||||
fireEvent.click(submit)
|
||||
expect(view.event.save).toHaveBeenCalled()
|
||||
})
|
||||
const submit = within(document.body).getByText('Submit')
|
||||
fireEvent.click(submit)
|
||||
expect(view.event.save).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('submits empty web_conference params when no current conference', () => {
|
||||
enableConferences()
|
||||
const view = render()
|
||||
view.conference = null
|
||||
view.event.save = jest.fn(params => {
|
||||
expect(params['calendar_event[web_conference]']).toEqual('')
|
||||
describe('when event conference cannot be updated', () => {
|
||||
it('does not show conferencing options when there is no current conference', () => {
|
||||
enableConferences()
|
||||
render({parent_event_id: 1000})
|
||||
const conferencingRow = within(document.body)
|
||||
.getByText('Conferencing:')
|
||||
.closest('tr')
|
||||
expect(conferencingRow.className).toEqual('hide')
|
||||
})
|
||||
const submit = within(document.body).getByText('Submit')
|
||||
fireEvent.click(submit)
|
||||
expect(view.event.save).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('does not submit web_conference params when conferencing is disabled', () => {
|
||||
const view = render()
|
||||
view.event.save = jest.fn(params => {
|
||||
expect(params['calendar_event[web_conference]']).toBeUndefined()
|
||||
it('does not submit web_conference params', () => {
|
||||
enableConferences()
|
||||
const view = render({parent_event_id: 1000})
|
||||
view.conference = null
|
||||
view.event.save = jest.fn(params => {
|
||||
expect(params['calendar_event[web_conference]']).toBeUndefined()
|
||||
})
|
||||
const submit = within(document.body).getByText('Submit')
|
||||
fireEvent.click(submit)
|
||||
expect(view.event.save).toHaveBeenCalled()
|
||||
})
|
||||
const submit = within(document.body).getByText('Submit')
|
||||
fireEvent.click(submit)
|
||||
expect(view.event.save).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
describe('when an event is moved between contexts', () => {
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* Copyright (C) 2020 - 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 'Backbone'
|
||||
import _ from 'lodash'
|
||||
import {within, getByText} from '@testing-library/dom'
|
||||
import CalendarEvent from '../CalendarEvent'
|
||||
import EditEventView from '../EditEventView'
|
||||
|
||||
jest.mock('jsx/shared/rce/RichContentEditor')
|
||||
|
||||
describe('EditEventView', () => {
|
||||
beforeEach(() => {
|
||||
window.ENV = {}
|
||||
document.body.innerHTML = '<div id="application"><form id="content"></form></div>'
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
window.ENV = null
|
||||
})
|
||||
|
||||
function render(overrides = {}) {
|
||||
const event = new CalendarEvent({
|
||||
id: 1,
|
||||
context_code: 'course_1',
|
||||
...overrides
|
||||
})
|
||||
event.sync = () => {}
|
||||
|
||||
return new EditEventView({el: document.getElementById('content'), model: event})
|
||||
}
|
||||
|
||||
async function waitForRender() {
|
||||
let rendered
|
||||
const promise = new Promise(resolve => {
|
||||
rendered = resolve
|
||||
})
|
||||
_.defer(() => rendered())
|
||||
await promise
|
||||
}
|
||||
|
||||
it('renders', () => {
|
||||
render()
|
||||
expect(within(document.body).getByText('Edit Calendar Event')).not.toBeNull()
|
||||
})
|
||||
|
||||
describe('conferences', () => {
|
||||
const CONFERENCE_TYPES = [
|
||||
{name: 'Type1', type: 'type1', contexts: ['course_1']},
|
||||
{name: 'Type2', type: 'type2', contexts: ['course_2', 'course_3']}
|
||||
]
|
||||
|
||||
function enableConferences(conference_types = CONFERENCE_TYPES) {
|
||||
window.ENV.CALENDAR = {CONFERENCES_ENABLED: true}
|
||||
window.ENV.conferences = {conference_types}
|
||||
}
|
||||
|
||||
it('does not show conferencing options when calendar conferences are disabled', () => {
|
||||
render()
|
||||
expect(within(document.body).queryByText('Conferencing:')).toBeNull()
|
||||
})
|
||||
|
||||
it('shows conferencing options when calendar conferences are enabled', () => {
|
||||
enableConferences()
|
||||
render()
|
||||
const conferencingNode = within(document.body).getByText('Conferencing:')
|
||||
expect(conferencingNode.closest('tr').className).not.toEqual('hide')
|
||||
})
|
||||
|
||||
describe('when context does not support conferences', () => {
|
||||
it('does not show conferencing options when there is no current conference', async () => {
|
||||
enableConferences(CONFERENCE_TYPES.slice(1))
|
||||
render()
|
||||
await waitForRender()
|
||||
const conferencingRow = within(document.body)
|
||||
.getByText('Conferencing:')
|
||||
.closest('tr')
|
||||
expect(conferencingRow.className).toEqual('hide')
|
||||
})
|
||||
|
||||
it('does show current conference when there is a current conference', async () => {
|
||||
enableConferences(CONFERENCE_TYPES.slice(1))
|
||||
render({web_conference: {id: 1, conference_type: 'type1', title: 'FooConf'}})
|
||||
const conferencingRow = within(document.body)
|
||||
.getByText('Conferencing:')
|
||||
.closest('tr')
|
||||
await waitForRender()
|
||||
expect(conferencingRow.className).not.toEqual('hide')
|
||||
expect(getByText(conferencingRow, 'FooConf')).not.toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
it('submits web_conference params for current conference', () => {
|
||||
enableConferences()
|
||||
const web_conference = {
|
||||
id: '1',
|
||||
name: 'Foo',
|
||||
conference_type: 'type1',
|
||||
lti_settings: {a: 1, b: 2, c: 3}
|
||||
}
|
||||
const view = render({
|
||||
web_conference
|
||||
})
|
||||
view.model.save = jest.fn(params => {
|
||||
expect(params.web_conference).toEqual(web_conference)
|
||||
})
|
||||
view.submit(null)
|
||||
expect(view.model.save).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('submits empty web_conference params when no current conference', () => {
|
||||
enableConferences()
|
||||
const view = render()
|
||||
view.model.save = jest.fn(params => {
|
||||
expect(params.web_conference).toEqual('')
|
||||
})
|
||||
view.submit(null)
|
||||
expect(view.model.save).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('does not submit web_conference params when conferencing is disabled', () => {
|
||||
const view = render()
|
||||
view.model.save = jest.fn(params => {
|
||||
expect(params.web_conference).toBeUndefined()
|
||||
})
|
||||
view.submit(null)
|
||||
expect(view.model.save).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
|
@ -470,9 +470,11 @@ class CalendarEventsApiController < ApplicationController
|
|||
params_for_create[:description] = process_incoming_html_content(params_for_create[:description])
|
||||
end
|
||||
if Account.site_admin.feature_enabled?(:calendar_conferences)
|
||||
web_conference = find_or_initialize_conference(@context, params_for_create[:web_conference])
|
||||
return unless authorize_user_for_conference(@current_user, web_conference)
|
||||
params_for_create[:web_conference] = web_conference
|
||||
if params_for_create.key?(:web_conference)
|
||||
web_conference = find_or_initialize_conference(@context, params_for_create[:web_conference])
|
||||
return unless authorize_user_for_conference(@current_user, web_conference)
|
||||
params_for_create[:web_conference] = web_conference
|
||||
end
|
||||
end
|
||||
|
||||
@event = @context.calendar_events.build(params_for_create)
|
||||
|
@ -657,9 +659,11 @@ class CalendarEventsApiController < ApplicationController
|
|||
params_for_update[:description] = process_incoming_html_content(params_for_update[:description])
|
||||
end
|
||||
if Account.site_admin.feature_enabled?(:calendar_conferences)
|
||||
web_conference = find_or_initialize_conference(@event.context, params_for_update[:web_conference])
|
||||
return unless authorize_user_for_conference(@current_user, web_conference)
|
||||
params_for_update[:web_conference] = web_conference
|
||||
if params_for_update.key?(:web_conference)
|
||||
web_conference = find_or_initialize_conference(@event.context, params_for_update[:web_conference])
|
||||
return unless authorize_user_for_conference(@current_user, web_conference)
|
||||
params_for_update[:web_conference] = web_conference
|
||||
end
|
||||
end
|
||||
|
||||
if @event.update(params_for_update)
|
||||
|
|
|
@ -17,12 +17,13 @@
|
|||
#
|
||||
|
||||
class CalendarEventsController < ApplicationController
|
||||
include CalendarConferencesHelper
|
||||
|
||||
before_action :require_context
|
||||
before_action :rce_js_env, only: [:new, :edit]
|
||||
|
||||
add_crumb(proc { t(:'#crumbs.calendar_events', "Calendar Events")}, :only => [:show, :new, :edit]) { |c| c.send :calendar_url_for, c.instance_variable_get("@context") }
|
||||
|
||||
|
||||
def show
|
||||
@event = @context.calendar_events.find(params[:id])
|
||||
add_crumb(@event.title, named_context_url(@context, :context_calendar_event_url, @event))
|
||||
|
@ -44,19 +45,19 @@ class CalendarEventsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
def new
|
||||
@event = @context.calendar_events.temp_record
|
||||
add_crumb(t('crumbs.new', "New Calendar Event"), named_context_url(@context, :new_context_calendar_event_url))
|
||||
@event.assign_attributes(params.permit(:title, :start_at, :end_at, :location_name, :location_address))
|
||||
@event.assign_attributes(permit_params(params, [:title, :start_at, :end_at, :location_name, :location_address, web_conference: strong_anything]))
|
||||
js_env(:RECURRING_CALENDAR_EVENTS_ENABLED => feature_context.feature_enabled?(:recurring_calendar_events))
|
||||
authorized_action(@event, @current_user, :create)
|
||||
add_conference_types_to_js_env([@context])
|
||||
authorized_action(@event, @current_user, :create) && authorize_user_for_conference(@current_user, @event.web_conference)
|
||||
end
|
||||
|
||||
def create
|
||||
params[:calendar_event][:time_zone_edited] = Time.zone.name if params[:calendar_event]
|
||||
@event = @context.calendar_events.build(calendar_event_params)
|
||||
if authorized_action(@event, @current_user, :create)
|
||||
if authorized_action(@event, @current_user, :create) && authorize_user_for_conference(@current_user, @event.web_conference)
|
||||
respond_to do |format|
|
||||
@event.updating_user = @current_user
|
||||
if @event.save
|
||||
|
@ -73,10 +74,13 @@ class CalendarEventsController < ApplicationController
|
|||
|
||||
def edit
|
||||
@event = @context.calendar_events.find(params[:id])
|
||||
event_params = permit_params(params, [:title, :start_at, :end_at, :location_name, :location_address, web_conference: strong_anything])
|
||||
return unless authorize_user_for_conference(@current_user, event_params[:web_conference])
|
||||
if @event.grants_right?(@current_user, session, :update)
|
||||
@event.update!(params.permit(:title, :start_at, :end_at, :location_name, :location_address))
|
||||
@event.update!(event_params)
|
||||
end
|
||||
if authorized_action(@event, @current_user, :update_content)
|
||||
add_conference_types_to_js_env([@context])
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
@ -85,9 +89,11 @@ class CalendarEventsController < ApplicationController
|
|||
@event = @context.calendar_events.find(params[:id])
|
||||
if authorized_action(@event, @current_user, :update)
|
||||
respond_to do |format|
|
||||
params[:calendar_event][:time_zone_edited] = Time.zone.name if params[:calendar_event]
|
||||
params_for_update = calendar_event_params
|
||||
params_for_update[:calendar_event][:time_zone_edited] = Time.zone.name if params_for_update[:calendar_event]
|
||||
return unless authorize_user_for_conference(@current_user, params_for_update[:web_conference])
|
||||
@event.updating_user = @current_user
|
||||
if @event.update(calendar_event_params)
|
||||
if @event.update(params_for_update)
|
||||
log_asset_access(@event, "calendar", "calendar", 'participate')
|
||||
flash[:notice] = t 'notices.updated', "Event was successfully updated."
|
||||
format.html { redirect_to calendar_url_for(@context) }
|
||||
|
@ -124,7 +130,19 @@ class CalendarEventsController < ApplicationController
|
|||
end
|
||||
|
||||
def calendar_event_params
|
||||
params.require(:calendar_event).
|
||||
permit(CalendarEvent.permitted_attributes + [:child_event_data => strong_anything])
|
||||
permit_params(
|
||||
params.require(:calendar_event),
|
||||
CalendarEvent.permitted_attributes + [:child_event_data => strong_anything, web_conference: strong_anything]
|
||||
)
|
||||
end
|
||||
|
||||
def permit_params(params, attrs)
|
||||
params.permit(attrs).tap do |p|
|
||||
if Account.site_admin.feature_enabled?(:calendar_conferences)
|
||||
if p.key?(:web_conference)
|
||||
p[:web_conference] = find_or_initialize_conference(@context, p[:web_conference])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,14 +17,18 @@
|
|||
#
|
||||
|
||||
module CalendarConferencesHelper
|
||||
include Api::V1::Conferences
|
||||
|
||||
def find_or_initialize_conference(context, conference_params)
|
||||
return nil if conference_params.blank?
|
||||
valid_params = conference_params.slice(:title, :description, :conference_type, :lti_settings)
|
||||
|
||||
if conference_params[:id]
|
||||
WebConference.find(conference_params[:id]).tap do |conf|
|
||||
conf.context = context
|
||||
conf.assign_attributes(valid_params)
|
||||
if conf.grants_right?(@current_user, session, :update)
|
||||
conf.context = context
|
||||
conf.assign_attributes(valid_params)
|
||||
end
|
||||
end
|
||||
elsif conference_params[:title].present?
|
||||
context.web_conferences.build(valid_params.merge(user: @current_user))
|
||||
|
@ -38,13 +42,15 @@ module CalendarConferencesHelper
|
|||
elsif conference.changed?
|
||||
authorized_action(conference, user, :update)
|
||||
else
|
||||
true
|
||||
authorized_action(conference, user, :read)
|
||||
end
|
||||
end
|
||||
|
||||
def add_conference_types_to_js_env(contexts)
|
||||
allowed_contexts = contexts.select {|c| c.grants_right?(@current_user, session, :create_conferences)}
|
||||
|
||||
type_to_contexts_map = {}
|
||||
conference_types = contexts.flat_map do |context|
|
||||
conference_types = allowed_contexts.flat_map do |context|
|
||||
WebConference.conference_types(context).map do |type|
|
||||
type_to_contexts_map[type] ||= []
|
||||
type_to_contexts_map[type] << context
|
||||
|
@ -56,8 +62,7 @@ module CalendarConferencesHelper
|
|||
|
||||
js_env(
|
||||
conferences: {
|
||||
conference_types: conference_types_json(conference_types),
|
||||
root_context: @domain_root_account.asset_string
|
||||
conference_types: conference_types_json(conference_types)
|
||||
}
|
||||
)
|
||||
end
|
||||
|
|
|
@ -46,12 +46,12 @@ const AddConference = ({context, currentConferenceType, conferenceTypes, setConf
|
|||
const onLtiContent = useCallback(
|
||||
ltiContent => {
|
||||
setRetrievingLTI(false)
|
||||
const {title = '', text: description = '', ...ltiSettings} = ltiContent
|
||||
const {title, text: description, ...ltiSettings} = ltiContent
|
||||
ltiSettings.tool_id = selectedType?.lti_settings?.tool_id
|
||||
setConference({
|
||||
conference_type: 'LtiConference',
|
||||
title: title || I18n.t('%{name} Conference', {name: selectedType.name}),
|
||||
description,
|
||||
description: description || '',
|
||||
lti_settings: ltiSettings
|
||||
})
|
||||
},
|
||||
|
@ -82,13 +82,15 @@ const AddConference = ({context, currentConferenceType, conferenceTypes, setConf
|
|||
onSelect={onSelect}
|
||||
/>
|
||||
)}
|
||||
<AddLtiConferenceDialog
|
||||
context={context}
|
||||
conferenceType={selectedType}
|
||||
isOpen={isRetrievingLTI}
|
||||
onRequestClose={onLtiClose}
|
||||
onContent={onLtiContent}
|
||||
/>
|
||||
{isRetrievingLTI && (
|
||||
<AddLtiConferenceDialog
|
||||
context={context}
|
||||
conferenceType={selectedType}
|
||||
isOpen
|
||||
onRequestClose={onLtiClose}
|
||||
onContent={onLtiContent}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -17,16 +17,22 @@
|
|||
*/
|
||||
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import {View} from '@instructure/ui-view'
|
||||
import AddConference from './AddConference'
|
||||
import Conference from './Conference'
|
||||
import getConferenceType from '../utils/getConferenceType'
|
||||
import webConference from 'jsx/shared/proptypes/webConference'
|
||||
import webConferenceType from 'jsx/shared/proptypes/webConferenceType'
|
||||
|
||||
const CalendarConferenceWidget = ({context, conference, conferenceTypes, setConference}) => {
|
||||
const currentConferenceType = conference && getConferenceType(conferenceTypes, conference)
|
||||
const showAddConference =
|
||||
setConference && (conferenceTypes.length > 1 || (conferenceTypes.length > 0 && !conference))
|
||||
const removeConference = setConference ? () => setConference(null) : null
|
||||
return (
|
||||
<View as="div" padding="0 0 x-small">
|
||||
{(!conference || conferenceTypes.length > 1) && (
|
||||
{showAddConference && (
|
||||
<AddConference
|
||||
context={context}
|
||||
currentConferenceType={currentConferenceType}
|
||||
|
@ -45,7 +51,7 @@ const CalendarConferenceWidget = ({context, conference, conferenceTypes, setConf
|
|||
<Conference
|
||||
conference={conference}
|
||||
conferenceType={currentConferenceType}
|
||||
removeConference={() => setConference(null)}
|
||||
removeConference={removeConference}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
|
@ -53,4 +59,16 @@ const CalendarConferenceWidget = ({context, conference, conferenceTypes, setConf
|
|||
)
|
||||
}
|
||||
|
||||
CalendarConferenceWidget.propTypes = {
|
||||
context: PropTypes.string.isRequired,
|
||||
conference: webConference,
|
||||
conferenceTypes: PropTypes.arrayOf(webConferenceType).isRequired,
|
||||
setConference: PropTypes.func
|
||||
}
|
||||
|
||||
CalendarConferenceWidget.defaultProps = {
|
||||
conference: null,
|
||||
setConference: null
|
||||
}
|
||||
|
||||
export default CalendarConferenceWidget
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
*/
|
||||
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import {CloseButton, IconButton} from '@instructure/ui-buttons'
|
||||
import {Flex} from '@instructure/ui-flex'
|
||||
import {IconXLine} from '@instructure/ui-icons'
|
||||
|
@ -29,6 +30,7 @@ import {PresentationContent} from '@instructure/ui-a11y-content'
|
|||
import sanitizeHtml from 'jsx/shared/sanitizeHtml'
|
||||
import RichContentEditor from 'jsx/shared/rce/RichContentEditor'
|
||||
import I18n from 'i18n!conferences'
|
||||
import webConference from 'jsx/shared/proptypes/webConference'
|
||||
|
||||
// we use this to consolidate the import of tinymce into our environment
|
||||
// (as recommended by jsx/shared/sanitizeHTML)
|
||||
|
@ -100,4 +102,13 @@ const Conference = ({conference, removeConference}) =>
|
|||
<LinkConference conference={conference} removeConference={removeConference} />
|
||||
)
|
||||
|
||||
Conference.propTypes = {
|
||||
conference: webConference.isRequired,
|
||||
removeConference: PropTypes.func
|
||||
}
|
||||
|
||||
Conference.defaultProps = {
|
||||
removeConference: null
|
||||
}
|
||||
|
||||
export default Conference
|
||||
|
|
|
@ -17,9 +17,15 @@
|
|||
*/
|
||||
|
||||
import React from 'react'
|
||||
import 'tinymce/tinymce'
|
||||
import {render} from '@testing-library/react'
|
||||
import CalendarConferenceWidget from '../CalendarConferenceWidget'
|
||||
|
||||
// we use RichContentEditor.preloadRemoteModule() to consolidate the import of
|
||||
// tinymce in the code, but since dynamic loading takes time during tests, we do
|
||||
// a static import here and mock out the dynamic
|
||||
jest.mock('jsx/shared/rce/RichContentEditor')
|
||||
|
||||
describe('CalendarConferenceWidget', () => {
|
||||
const conferenceTypes = [
|
||||
{type: 'foo', name: 'Foo Conference', contexts: ['course_1', 'group_2']},
|
||||
|
@ -59,4 +65,11 @@ describe('CalendarConferenceWidget', () => {
|
|||
const {getByText} = render(<CalendarConferenceWidget {...makeParams()} />)
|
||||
expect(getByText('Select Conference Provider')).not.toBeNull()
|
||||
})
|
||||
|
||||
it('does not show a selector if setConference is not defined', () => {
|
||||
const {queryByText} = render(
|
||||
<CalendarConferenceWidget {...makeParams({setConference: null})} />
|
||||
)
|
||||
expect(queryByText('Select Conference Provider')).toBeNull()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -23,7 +23,8 @@ const createConference = async (context, conferenceType) => {
|
|||
const [contextType, contextId] = context.split('_')
|
||||
const conferenceParams = {
|
||||
conference_type: conferenceType.type,
|
||||
title: I18n.t('%{name} Conference', {name: conferenceType.name})
|
||||
title: I18n.t('%{name} Conference', {name: conferenceType.name}),
|
||||
description: ''
|
||||
}
|
||||
const {json} = await doFetchApi({
|
||||
path: `/api/v1/${contextType}s/${contextId}/conferences`,
|
||||
|
|
|
@ -16,10 +16,10 @@
|
|||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {object, shape, string} from 'prop-types'
|
||||
import {number, object, oneOfType, shape, string} from 'prop-types'
|
||||
|
||||
const webConference = shape({
|
||||
id: string,
|
||||
id: oneOfType([string, number]),
|
||||
conference_type: string.isRequired,
|
||||
context_id: string,
|
||||
context_type: string,
|
||||
|
|
|
@ -20,6 +20,7 @@ const RichContentEditor = {
|
|||
preloadRemoteModule() {},
|
||||
loadNewEditor() {},
|
||||
destroyRCE() {},
|
||||
initSidebar() {},
|
||||
callOnRCE(textarea, opName) {
|
||||
if (opName === 'get_code') return textarea.innerHTML
|
||||
}
|
||||
|
|
|
@ -305,7 +305,8 @@ class CalendarEvent < ActiveRecord::Base
|
|||
:title,
|
||||
:description,
|
||||
:location_name,
|
||||
:location_address
|
||||
:location_address,
|
||||
:web_conference
|
||||
].freeze
|
||||
LOCKED_ATTRIBUTES = CASCADED_ATTRIBUTES + [
|
||||
:start_at,
|
||||
|
|
|
@ -21,7 +21,7 @@ class WebConference < ActiveRecord::Base
|
|||
include TextHelper
|
||||
attr_readonly :context_id, :context_type
|
||||
belongs_to :context, polymorphic: [:course, :group, :account]
|
||||
has_one :calendar_event, inverse_of: :web_conference
|
||||
has_one :calendar_event, inverse_of: :web_conference, dependent: :nullify
|
||||
has_many :web_conference_participants
|
||||
has_many :users, :through => :web_conference_participants
|
||||
has_many :invitees, -> { where(web_conference_participants: { participation_type: 'invitee' }) }, through: :web_conference_participants, source: :user
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
event_attrs[:sections_url] = context_url(@context, :api_v1_context_sections_url)
|
||||
end
|
||||
js_env :CALENDAR_EVENT => event_attrs
|
||||
|
||||
js_env CALENDAR: { CONFERENCES_ENABLED: Account.site_admin.feature_enabled?(:calendar_conferences) }
|
||||
js_bundle :edit_calendar_event
|
||||
css_bundle :tinymce, :edit_calendar_event_full
|
||||
provide :right_side, render(:partial => 'shared/wiki_sidebar')
|
||||
|
|
|
@ -108,6 +108,14 @@
|
|||
<input id="calendar_event_location_address" name="location_address" size="30" maxlength="255" type="text" value="{{location_address}}"/>
|
||||
</td>
|
||||
</tr>
|
||||
{{#if include_conference_selection}}
|
||||
<tr>
|
||||
<td>
|
||||
<label for="calendar_event_conference_selection">{{#t}}Conferencing:{{/t}}</label>
|
||||
<div id="calendar_event_conference_selection"></div>
|
||||
</td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
{{#if recurring_calendar_events}}
|
||||
<tr class="duplicate_event_toggle_row hide">
|
||||
<td style="vertical-align: top;"><label for="duplicate_event">{{#t "repeat"}}Duplicate{{/t}}</label> <input type="checkbox" id="duplicate_event" name="duplicate" value="true" style="margin-left: 10px;" />
|
||||
|
|
|
@ -27,7 +27,8 @@ module.exports = {
|
|||
'^jsx/(.*)$': '<rootDir>/app/jsx/$1',
|
||||
'^jst/(.*)$': '<rootDir>/app/views/jst/$1',
|
||||
'^timezone$': '<rootDir>/public/javascripts/timezone_core.js',
|
||||
'node_modules-version-of-backbone': require.resolve('backbone')
|
||||
'node_modules-version-of-backbone': require.resolve('backbone'),
|
||||
Backbone: '<rootDir>/public/javascripts/Backbone.js'
|
||||
},
|
||||
roots: ['app/jsx', 'app/coffeescripts', 'public/javascripts', 'gems/plugins'],
|
||||
moduleDirectories: ['node_modules', 'public/javascripts', 'public/javascripts/vendor'],
|
||||
|
|
|
@ -23,7 +23,7 @@ module Lti::Messages
|
|||
'assignment_selection' => %w(ltiResourceLink).freeze,
|
||||
'homework_submission' => %w(file).freeze,
|
||||
'link_selection' => %w(ltiResourceLink).freeze,
|
||||
'conference_selection' => %w(ltiResourceLink).freeze
|
||||
'conference_selection' => %w(link html).freeze
|
||||
}.freeze
|
||||
|
||||
DOCUMENT_TARGETS = {
|
||||
|
@ -47,7 +47,7 @@ module Lti::Messages
|
|||
'assignment_selection' => %w(application/vnd.ims.lti.v1.ltilink).freeze,
|
||||
'homework_submission' => %w(*/*).freeze,
|
||||
'link_selection' => %w(application/vnd.ims.lti.v1.ltilink).freeze,
|
||||
'conference_selection' => %w(application/vnd.ims.lti.v1.ltilink).freeze
|
||||
'conference_selection' => %w(text/html */*).freeze
|
||||
}.freeze
|
||||
|
||||
AUTO_CREATE = {
|
||||
|
|
|
@ -1424,6 +1424,18 @@ describe CalendarEventsApiController, type: :request do
|
|||
})
|
||||
expect(event.reload.web_conference).to be nil
|
||||
end
|
||||
|
||||
it "should not remove a web conference if no argument provided" do
|
||||
event = @course.calendar_events.create(title: 'to update', workflow_state: 'active', web_conference: conference)
|
||||
api_call(:put, "/api/v1/calendar_events/#{event.id}", {
|
||||
:controller => 'calendar_events_api', :action => 'update', :format => 'json', id: event.id
|
||||
}, {
|
||||
:calendar_event => {
|
||||
location: 'foo'
|
||||
}
|
||||
})
|
||||
expect(event.reload.web_conference_id).to eq conference.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -2422,9 +2434,9 @@ describe CalendarEventsApiController, type: :request do
|
|||
plugin = PluginSetting.create!(name: 'big_blue_button')
|
||||
plugin.update_attribute(:settings, { key: 'value' })
|
||||
3.times do |idx|
|
||||
conference = WebConference.create(context: @course, user: @user, conference_type: 'BigBlueButton')
|
||||
conference = WebConference.create!(context: @course, user: @user, conference_type: 'BigBlueButton')
|
||||
conference.add_initiator(@user)
|
||||
@course.calendar_events.create(title: "event #{idx}", workflow_state: 'active',
|
||||
@course.calendar_events.create!(title: "event #{idx}", workflow_state: 'active',
|
||||
web_conference: conference)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -19,14 +19,55 @@
|
|||
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
||||
|
||||
describe CalendarEventsController do
|
||||
before :once do
|
||||
course_with_teacher(active_all: true)
|
||||
student_in_course(active_all: true)
|
||||
course_event
|
||||
def stub_conference_plugins
|
||||
allow(WebConference).to receive(:plugins).and_return(
|
||||
[web_conference_plugin_mock("big_blue_button", {:domain => "bbb.instructure.com", :secret_dec => "secret"})]
|
||||
)
|
||||
end
|
||||
|
||||
def course_event
|
||||
@event = @course.calendar_events.create(:title => "some assignment")
|
||||
let_once(:teacher_enrollment) { course_with_teacher(active_all: true) }
|
||||
let_once(:course) { teacher_enrollment.course }
|
||||
let_once(:student_enrollment) { student_in_course(course: course) }
|
||||
let_once(:course_event) { course.calendar_events.create(:title => "some assignment") }
|
||||
let_once(:other_teacher_enrollment) { course_with_teacher(active_all: true) }
|
||||
|
||||
before do
|
||||
@course = course
|
||||
@teacher = teacher_enrollment.user
|
||||
@student = student_enrollment.user
|
||||
@event = course_event
|
||||
stub_conference_plugins
|
||||
end
|
||||
let(:conference_params) do
|
||||
{ conference_type: 'BigBlueButton', title: 'a conference', user: teacher_enrollment.user }
|
||||
end
|
||||
let(:other_teacher_conference) { other_teacher_enrollment.course.web_conferences.create!(**conference_params, user: other_teacher_enrollment.user) }
|
||||
|
||||
shared_examples "accepts web_conference" do
|
||||
before(:once) do
|
||||
Account.site_admin.enable_feature! 'calendar_conferences'
|
||||
end
|
||||
|
||||
it "accepts a new conference" do
|
||||
user_session(@teacher)
|
||||
make_request.call(conference_params)
|
||||
expect(response.status).to be < 400
|
||||
expect(get_event.call.web_conference).not_to be nil
|
||||
end
|
||||
|
||||
it "accepts an existing conference" do
|
||||
user_session(@teacher)
|
||||
conference = @course.web_conferences.create!(conference_params)
|
||||
make_request.call(id: conference.id, **conference_params)
|
||||
expect(response.status).to be < 400
|
||||
expect(get_event.call.web_conference_id).to eq conference.id
|
||||
end
|
||||
|
||||
it "does not accept an existing conference the user doesn't have permission for" do
|
||||
user_session(@teacher)
|
||||
make_request.call(id: other_teacher_conference.id)
|
||||
assert_unauthorized
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET 'show'" do
|
||||
|
@ -92,6 +133,27 @@ describe CalendarEventsController do
|
|||
get 'new', params: {user_id: @teacher.id}
|
||||
expect(@controller.js_env[:use_rce_enhancements]).to be(true)
|
||||
end
|
||||
|
||||
context "with web conferences" do
|
||||
before(:once) do
|
||||
Account.site_admin.enable_feature! 'calendar_conferences'
|
||||
end
|
||||
|
||||
it "includes conference environment" do
|
||||
user_session(@teacher)
|
||||
get 'new', params: {course_id: @course.id}
|
||||
expect(@controller.js_env.dig(:conferences, :conference_types).length).to eq 1
|
||||
end
|
||||
|
||||
include_examples 'accepts web_conference' do
|
||||
let(:make_request) do
|
||||
->(params) { get 'new', params: {course_id: @course.id, web_conference: params} }
|
||||
end
|
||||
let(:get_event) do
|
||||
->{ @controller.instance_variable_get(:@event) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST 'create'" do
|
||||
|
@ -113,6 +175,15 @@ describe CalendarEventsController do
|
|||
expect(assigns[:event]).not_to be_nil
|
||||
expect(assigns[:event].title).to eql("some event")
|
||||
end
|
||||
|
||||
include_examples 'accepts web_conference' do
|
||||
let(:make_request) do
|
||||
->(params) { post 'create', params: {course_id: @course.id, calendar_event: {title: 'some event', web_conference: params}} }
|
||||
end
|
||||
let(:get_event) do
|
||||
->{ assigns[:event] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET 'edit'" do
|
||||
|
@ -126,6 +197,41 @@ describe CalendarEventsController do
|
|||
get 'edit', params: {:course_id => @course.id, :id => @event.id}
|
||||
assert_unauthorized
|
||||
end
|
||||
|
||||
include_examples 'accepts web_conference' do
|
||||
let(:make_request) do
|
||||
->(params) { get 'edit', params: {course_id: @course.id, id: @event.id, web_conference: params} }
|
||||
end
|
||||
let(:get_event) do
|
||||
->{ @event.reload }
|
||||
end
|
||||
end
|
||||
|
||||
# context "with web conferences" do
|
||||
# before(:once) do
|
||||
# Account.site_admin.enable_feature! 'calendar_conferences'
|
||||
# end
|
||||
|
||||
# it "can update with a new conference" do
|
||||
# user_session(@teacher)
|
||||
# get 'edit', params: {course_id: @course.id, id: @event.id, web_conference: conference_params}
|
||||
# expect(response).to be_successful
|
||||
# expect(@event.reload.web_conference_id).not_to be nil
|
||||
# end
|
||||
|
||||
# it "can update with an existing conference" do
|
||||
# user_session(@teacher)
|
||||
# conference = @course.web_conferences.create!(conference_params)
|
||||
# get 'edit', params: {course_id: @course.id, id: @event.id, web_conference: {id: conference.id, **conference_params}}
|
||||
# expect(@event.reload.web_conference_id).to eq conference.id
|
||||
# end
|
||||
|
||||
# it "cannot create with an existing conference the user doesn't have permission for" do
|
||||
# user_session(@teacher)
|
||||
# get 'edit', params: {course_id: @course.id, id: @event.id, web_conference: {id: other_teacher_conference.id}}
|
||||
# assert_unauthorized
|
||||
# end
|
||||
# end
|
||||
end
|
||||
|
||||
describe "PUT 'update'" do
|
||||
|
@ -148,6 +254,15 @@ describe CalendarEventsController do
|
|||
expect(assigns[:event]).to eql(@event)
|
||||
expect(assigns[:event].title).to eql("new title")
|
||||
end
|
||||
|
||||
include_examples 'accepts web_conference' do
|
||||
let(:make_request) do
|
||||
->(params) { put 'update', params: {course_id: @course.id, id: @event.id, calendar_event: {web_conference: params}} }
|
||||
end
|
||||
let(:get_event) do
|
||||
->{ assigns[:event] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "DELETE 'destroy'" do
|
||||
|
|
|
@ -205,7 +205,8 @@ describe Lti::Messages::DeepLinkingRequest do
|
|||
|
||||
it 'sets the correct "accept_types"' do
|
||||
expect(subject['accept_types']).to match_array %w(
|
||||
ltiResourceLink
|
||||
html
|
||||
link
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -218,7 +219,7 @@ describe Lti::Messages::DeepLinkingRequest do
|
|||
|
||||
it 'sets the correct "accept_media_types"' do
|
||||
expect(subject['accept_media_types']).to eq(
|
||||
'application/vnd.ims.lti.v1.ltilink'
|
||||
'text/html,*/*'
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -310,6 +310,17 @@ describe WebConference do
|
|||
end
|
||||
end
|
||||
|
||||
context "calendar events" do
|
||||
|
||||
it "nullifies event conference when a conference is destroyed" do
|
||||
course_with_teacher(active_all: true)
|
||||
conference = WimbaConference.create!(title: "my conference", user: @user, context: @course)
|
||||
event = calendar_event_model web_conference: conference
|
||||
conference.destroy!
|
||||
expect(event.reload.web_conference).to be nil
|
||||
end
|
||||
end
|
||||
|
||||
context "LTI conferences" do
|
||||
let_once(:course) { course_model }
|
||||
let_once(:tool) do
|
||||
|
|
Loading…
Reference in New Issue