diff --git a/spec/javascripts/jsx/blueprint_courses/components/CourseSidebarSpec.jsx b/spec/javascripts/jsx/blueprint_courses/components/CourseSidebarSpec.jsx deleted file mode 100644 index ed22cadef6a..00000000000 --- a/spec/javascripts/jsx/blueprint_courses/components/CourseSidebarSpec.jsx +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright (C) 2017 - 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 . - */ - -import React from 'react' -import $ from 'jquery' -import 'jquery-migrate' -import {Provider} from 'react-redux' -import * as enzyme from 'enzyme' -import moxios from 'moxios' - -import {ConnectedCourseSidebar} from 'ui/features/blueprint_course_master/react/components/CourseSidebar' -import MigrationStates from '@canvas/blueprint-courses/react/migrationStates' -import getSampleData from '../getSampleData' -import mockStore from '../mockStore' - -let clock -let sidebarContentRef = null - -const initialState = { - masterCourse: getSampleData().masterCourse, - existingAssociations: getSampleData().courses, - unsyncedChanges: getSampleData().unsyncedChanges, - migrationStatus: MigrationStates.states.unknown, - canManageCourse: true, - hasLoadedAssociations: true, - hasLoadedUnsyncedChanges: true, -} - -const defaultProps = () => ({ - contentRef: cr => { - sidebarContentRef = cr - }, - routeTo: () => {}, -}) - -function connect(props = defaultProps(), storeState = initialState) { - return ( - - - - ) -} - -QUnit.module('Course Sidebar component', { - setup() { - clock = sinon.useFakeTimers() - const appElement = document.createElement('div') - appElement.id = 'application' - document.getElementById('fixtures').appendChild(appElement) - sidebarContentRef = null - moxios.install() - moxios.stubRequest('/api/v1/courses/4/blueprint_templates/default/migrations', { - status: 200, - response: [{id: '1'}], - }) - }, - teardown() { - moxios.uninstall() - document.getElementById('fixtures').innerHTML = '' - clock.restore() - }, -}) - -test('renders the closed CourseSidebar component', () => { - const tree = enzyme.mount(connect()) - const node = tree.find('button') - equal(node.text().trim(), 'Open Blueprint Sidebar') - tree.unmount() -}) - -test('renders the open CourseSidebar component', () => { - const tree = enzyme.mount(connect()) - tree.find('button').simulate('click') - clock.tick(500) - ok(sidebarContentRef, 'sidebar contents') - - const sidebar = $(sidebarContentRef) - const rows = sidebar.find('.bcs__row') - - // associations - ok(rows.eq(0).find('button#mcSidebarAsscBtn').size(), 'Associations button') - equal( - rows.eq(0).text().trim(), - `Associations${initialState.existingAssociations.length}`, - 'Associations count' - ) - - // sync history - ok(rows.eq(1).find('button#mcSyncHistoryBtn').size(), 'sync history button') - - // unsynced changes - ok(rows.eq(2).find('button#mcUnsyncedChangesBtn').size(), 'unsynced changes button') - equal(rows.eq(2).find('span').eq(0).text(), 'Unsynced Changes') - - const count = rows.eq(2).find('.bcs__row-right-content').text() - equal(count, initialState.unsyncedChanges.length, 'unsynced changes count') - tree.unmount() -}) - -test('renders no Uncynced Changes link if there are none', () => { - const props = defaultProps() - const state = {...initialState} - state.unsyncedChanges = [] - const tree = enzyme.mount(connect(props, state)) - tree.find('button').simulate('click') - ok(sidebarContentRef) - const sidebar = $(sidebarContentRef) - - // no unsynced changes - notOk(sidebar.find('button#mcUnsyncedChangesBtn').size()) - tree.unmount() -}) - -test('renders no Uncynced Changes link if there are no associations', () => { - const props = defaultProps() - const state = {...initialState} - state.existingAssociations = [] - const tree = enzyme.mount(connect(props, state)) - tree.find('button').simulate('click') - ok(sidebarContentRef) - const sidebar = $(sidebarContentRef) - - // no unsynced changes - notOk(sidebar.find('button#mcUnsyncedChangesBtn').size()) - tree.unmount() -}) - -test('renders no Uncynced Changes link if sync is in progress', () => { - const props = defaultProps() - const state = {...initialState} - state.migrationStatus = MigrationStates.states.imports_queued - const tree = enzyme.mount(connect(props, state)) - tree.find('button').simulate('click') - ok(sidebarContentRef) - const sidebar = $(sidebarContentRef) - - // no unsynced changes - notOk(sidebar.find('button#mcUnsyncedChangesBtn').size()) - tree.unmount() -}) - -test('renders no Associations link if the user not an admin', () => { - const props = defaultProps() - const state = {...initialState} - state.canManageCourse = false - const tree = enzyme.mount(connect(props, state)) - tree.find('button').simulate('click') - ok(sidebarContentRef) - const sidebar = $(sidebarContentRef) - - // no unsynced changes - notOk(sidebar.find('button#mcSidebarAsscBtn').size()) - tree.unmount() -}) - -test('renders Sync button if has associations and sync is active and no unsyced changes', () => { - const props = defaultProps() - const state = {...initialState} - state.unsyncedChanges = [] - state.migrationStatus = MigrationStates.states.imports_queued - const tree = enzyme.mount(connect(props, state)) - tree.find('button').simulate('click') - clock.tick(500) - ok(sidebarContentRef) - const sidebar = $(sidebarContentRef) - - ok(sidebar.find('.bcs__migration-sync').size()) - tree.unmount() -}) - -test('renders Sync button if has associations and has unsynced changes', () => { - const props = defaultProps() - const state = {...initialState} - const tree = enzyme.mount(connect(props, state)) - tree.find('button').simulate('click') - clock.tick(500) - ok(sidebarContentRef) - const sidebar = $(sidebarContentRef) - - ok(sidebar.find('.bcs__migration-sync').size()) - tree.unmount() -}) - -test('renders no Sync button if there are no associations', () => { - const props = defaultProps() - const state = {...initialState} - state.existingAssociations = [] - const tree = enzyme.mount(connect(props, state)) - tree.find('button').simulate('click') - ok(sidebarContentRef) - const sidebar = $(sidebarContentRef) - - notOk(sidebar.find('.bcs__migration-sync').size()) - tree.unmount() -}) - -test('renders no Sync button if there are associations, but no unsynced changes and no sync in progress', () => { - const props = defaultProps() - const state = {...initialState} - state.unsyncedChanges = [] - const tree = enzyme.mount(connect(props, state)) - tree.find('button').simulate('click') - ok(sidebarContentRef) - const sidebar = $(sidebarContentRef) - - notOk(sidebar.find('.bcs__migration-sync').size()) - tree.unmount() -}) diff --git a/spec/javascripts/jsx/blueprint_courses/components/LockBannerSpec.jsx b/spec/javascripts/jsx/blueprint_courses/components/LockBannerSpec.jsx deleted file mode 100644 index 0435ac0b0e6..00000000000 --- a/spec/javascripts/jsx/blueprint_courses/components/LockBannerSpec.jsx +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2017 - 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 . - */ - -import React from 'react' -import * as enzyme from 'enzyme' -import LockBanner from '@canvas/blueprint-courses/react/components/LockManager/LockBanner' - -QUnit.module('LockBanner component') - -const defaultProps = () => ({ - isLocked: true, - itemLocks: { - content: true, - points: false, - due_dates: false, - availability_dates: false, - }, -}) - -test('renders an Alert when LockBanner is locked', () => { - const props = defaultProps() - props.isLocked = true - const tree = enzyme.mount() - const node = tree.find('Alert') - ok(node.exists()) -}) - -test('does not render Alert when LockBanner is locked', () => { - const props = defaultProps() - props.isLocked = false - const tree = enzyme.mount() - const node = tree.find('Alert') - notOk(node.exists()) -}) - -test('displays locked description text appropriately when one attribute is locked', () => { - const props = defaultProps() - const tree = enzyme.mount() - const text = tree.find('Text').at(2).text() - equal(text, 'Content') -}) - -test('displays locked description text appropriately when two attributes are locked', () => { - const props = defaultProps() - props.itemLocks.points = true - const tree = enzyme.mount() - const text = tree.find('Text').at(2).text() - equal(text, 'Content & Points') -}) - -test('displays locked description text appropriately when more than two attributes are locked', () => { - const props = defaultProps() - props.itemLocks.points = true - props.itemLocks.due_dates = true - const tree = enzyme.mount() - const text = tree.find('Text').at(2).text() - equal(text, 'Content, Points & Due Dates') -}) diff --git a/spec/javascripts/jsx/blueprint_courses/components/LockToggleSpec.jsx b/spec/javascripts/jsx/blueprint_courses/components/LockToggleSpec.jsx deleted file mode 100644 index e329fb9acf0..00000000000 --- a/spec/javascripts/jsx/blueprint_courses/components/LockToggleSpec.jsx +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2017 - 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 . - */ - -import React from 'react' -import * as enzyme from 'enzyme' -import LockToggle from '@canvas/blueprint-courses/react/components/LockManager/LockToggle' - -QUnit.module('LockToggle component') - -const defaultProps = () => ({ - isLocked: true, - isToggleable: true, -}) - -test('renders the LockToggle component', () => { - const tree = enzyme.mount() - const node = tree.find('.bpc-lock-toggle') - ok(node.exists()) -}) - -test('renders a button when LockToggle is toggleable', () => { - const props = defaultProps() - props.isToggleable = true - const tree = enzyme.mount() - const node = tree.find('Button') - ok(node.exists()) -}) - -test('does not render a button when LockToggle is not toggleable', () => { - const props = defaultProps() - props.isToggleable = false - const tree = enzyme.shallow() - const node = tree.find('Button') - notOk(node.exists()) -}) - -test('renders a locked icon when LockToggle is locked', () => { - const props = defaultProps() - props.isLocked = true - const tree = enzyme.mount() - const node = tree.find('IconBlueprintLockSolid') - ok(node.exists()) -}) - -test('renders an unlocked icon when LockToggle is unlocked', () => { - const props = defaultProps() - props.isLocked = false - const tree = enzyme.mount() - const node = tree.find('IconBlueprintSolid') - ok(node.exists()) -}) diff --git a/spec/javascripts/jsx/blueprint_courses/components/MigrationOptionsSpec.jsx b/spec/javascripts/jsx/blueprint_courses/components/MigrationOptionsSpec.jsx deleted file mode 100644 index 5871ef76872..00000000000 --- a/spec/javascripts/jsx/blueprint_courses/components/MigrationOptionsSpec.jsx +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2017 - 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 . - */ - -import React from 'react' -import * as enzyme from 'enzyme' -import MigrationOptions from 'ui/features/blueprint_course_master/react/components/MigrationOptions' -import MigrationStates from '@canvas/blueprint-courses/react/migrationStates' - -const noop = () => {} - -QUnit.module('MigrationOptions component') - -const defaultProps = { - migrationStatus: MigrationStates.states.unknown, - willSendNotification: false, - willIncludeCustomNotificationMessage: false, - willIncludeCourseSettings: false, - notificationMessage: '', - enableSendNotification: noop, - includeCustomNotificationMessage: noop, - setNotificationMessage: noop, - includeCourseSettings: noop, -} - -test('renders the MigrationOptions component', () => { - const tree = enzyme.shallow() - const node = tree.find({as: 'fieldset'}) - ok(node.exists()) -}) - -test('renders the course-settings and notification-enable checkboxes', () => { - const tree = enzyme.mount() - const checkboxes = tree.find('input[type="checkbox"]') - equal(checkboxes.length, 2) - equal(checkboxes.at(0).prop('checked'), false) - equal(checkboxes.at(1).prop('checked'), false) -}) - -test('renders the add a message checkbox', () => { - const props = {...defaultProps} - props.willSendNotification = true - - const tree = enzyme.mount() - - ok(tree.find('Checkbox[label="Include Course Settings"]').first().exists()) - equal(tree.find('Checkbox[label="Include Course Settings"]').first().prop('checked'), false) - - ok(tree.find('Checkbox[label="Send Notification"]').first().exists()) - equal(tree.find('Checkbox[label="Send Notification"]').first().prop('checked'), true) - - const checkbox3 = tree - .find('Checkbox') - .filterWhere(n => n.text().includes('0/140')) - .first() - ok(checkbox3.exists()) - equal(checkbox3.prop('checked'), false) - - const messagebox = tree.find('TextArea') - ok(!messagebox.exists()) -}) - -test('renders the message text area', () => { - const props = {...defaultProps} - props.willSendNotification = true - props.willIncludeCustomNotificationMessage = true - - const tree = enzyme.mount() - const messagebox = tree.find('TextArea') - ok(messagebox.exists()) -}) diff --git a/spec/javascripts/jsx/blueprint_courses/components/MigrationSyncSpec.jsx b/spec/javascripts/jsx/blueprint_courses/components/MigrationSyncSpec.jsx deleted file mode 100644 index 06f6c6e29d2..00000000000 --- a/spec/javascripts/jsx/blueprint_courses/components/MigrationSyncSpec.jsx +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2017 - 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 . - */ - -import React from 'react' -import * as enzyme from 'enzyme' -import MigrationSync from 'ui/features/blueprint_course_master/react/components/MigrationSync' - -QUnit.module('MigrationSync component') - -const defaultProps = () => ({ - migrationStatus: 'void', - hasCheckedMigration: true, - isLoadingBeginMigration: false, - checkMigration: () => {}, - beginMigration: () => {}, - stopMigrationStatusPoll: () => {}, -}) - -test('renders the MigrationSync component', () => { - const tree = enzyme.shallow() - const node = tree.find('.bcs__migration-sync') - ok(node.exists()) -}) - -test('renders the progress indicator if in a loading migration state', () => { - const props = defaultProps() - props.migrationStatus = 'queued' - const tree = enzyme.shallow() - const node = tree.find('.bcs__migration-sync__loading') - ok(node.exists()) -}) - -test('renders the progress indicator if in the process of beginning a migration', () => { - const props = defaultProps() - props.isLoadingBeginMigration = true - const tree = enzyme.shallow() - const node = tree.find('.bcs__migration-sync__loading') - ok(node.exists()) -}) - -test('calls beginMigration when sync button is clicked', () => { - const props = defaultProps() - props.beginMigration = sinon.spy() - const tree = enzyme.mount() - const button = tree.find('.bcs__migration-sync button') - button.at(0).simulate('click') - equal(props.beginMigration.callCount, 1) -}) - -test('calls checkMigration on mount if it has not been checked already', () => { - const props = defaultProps() - props.hasCheckedMigration = false - props.checkMigration = sinon.spy() - const tree = enzyme.shallow() - equal(props.checkMigration.callCount, 1) -}) - -test('does not call checkMigration on mount if it has been checked already', () => { - const props = defaultProps() - props.hasCheckedMigration = true - props.checkMigration = sinon.spy() - const tree = enzyme.shallow() - equal(props.checkMigration.callCount, 0) -}) diff --git a/spec/javascripts/jsx/blueprint_courses/components/SyncHistoryItemSpec.jsx b/spec/javascripts/jsx/blueprint_courses/components/SyncHistoryItemSpec.jsx deleted file mode 100644 index c164f06fd4e..00000000000 --- a/spec/javascripts/jsx/blueprint_courses/components/SyncHistoryItemSpec.jsx +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2017 - 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 . - */ - -import React from 'react' -import * as enzyme from 'enzyme' -import SyncHistoryItem from '@canvas/blueprint-courses/react/components/SyncHistoryItem' -import getSampleData from '../getSampleData' - -QUnit.module('SyncHistoryItem component') - -const defaultProps = () => ({ - heading: null, - migration: getSampleData().history[0], -}) - -test('renders the SyncHistoryItem component', () => { - const tree = enzyme.shallow() - const node = tree.find('.bcs__history-item') - ok(node.exists()) -}) - -test('renders heading component when migration has changes', () => { - const props = defaultProps() - props.heading =

test

- const tree = enzyme.shallow() - const node = tree.find('.bcs__history-item .test-heading') - ok(node.exists()) -}) - -test('does not render the heading component when migration has no changes', () => { - const props = defaultProps() - props.heading =

test

- props.migration.changes = [] - const tree = enzyme.shallow() - const node = tree.find('.bcs__history-item .test-heading') - notOk(node.exists()) -}) - -test('renders changes using the appropriate prop component', () => { - const props = defaultProps() - props.ChangeComponent = () =>
- const tree = enzyme.mount() - const node = tree.find('.bcs__history-item .test-change') - equal(node.length, props.migration.changes.length) -}) - -test('includes the name of the person who started the sync', () => { - const tree = enzyme.mount() - const node = tree.find('.bcs__history-item__title') - const text = node.text() - notEqual(text.indexOf('changes pushed by Bob Jones'), -1) -}) diff --git a/spec/javascripts/jsx/blueprint_courses/components/SyncHistorySpec.jsx b/spec/javascripts/jsx/blueprint_courses/components/SyncHistorySpec.jsx deleted file mode 100644 index 95f4b3fc7d9..00000000000 --- a/spec/javascripts/jsx/blueprint_courses/components/SyncHistorySpec.jsx +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2017 - 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 . - */ - -import React from 'react' -import * as enzyme from 'enzyme' -import SyncHistory from 'ui/features/blueprint_course_master/react/components/SyncHistory' -import getSampleData from '../getSampleData' - -QUnit.module('SyncHistory component') - -const defaultProps = () => ({ - loadHistory: () => {}, - isLoadingHistory: false, - hasLoadedHistory: false, - loadAssociations: () => {}, - isLoadingAssociations: false, - hasLoadedAssociations: false, - migrations: getSampleData().history, -}) - -test('renders the SyncHistory component', () => { - const tree = enzyme.shallow() - const node = tree.find('.bcs__history') - ok(node.exists()) -}) - -test('displays spinner when loading courses', () => { - const props = defaultProps() - props.isLoadingHistory = true - const tree = enzyme.shallow() - const node = tree.find('.bcs__history Spinner') - ok(node.exists()) -}) - -test('renders SyncHistoryItem components for each migration', () => { - const tree = enzyme.mount() - const node = tree.find('SyncHistoryItem') - equal(node.length, 1) -}) diff --git a/spec/javascripts/jsx/blueprint_courses/mockStore.js b/spec/javascripts/jsx/blueprint_courses/mockStore.js deleted file mode 100644 index cb4b626232d..00000000000 --- a/spec/javascripts/jsx/blueprint_courses/mockStore.js +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2016 - 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 . - */ - -import {createStore, applyMiddleware} from 'redux' -import {thunk} from 'redux-thunk' -import rootReducer from '@canvas/blueprint-courses/react/reducer' - -export default function mockStore(initialState) { - return applyMiddleware(thunk)(createStore)(rootReducer, initialState) -} diff --git a/ui/features/blueprint_course_master/react/components/__tests__/CourseSidebar.test.jsx b/ui/features/blueprint_course_master/react/components/__tests__/CourseSidebar.test.jsx new file mode 100644 index 00000000000..d918b4ca685 --- /dev/null +++ b/ui/features/blueprint_course_master/react/components/__tests__/CourseSidebar.test.jsx @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2024 - 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 . + */ + +import React from 'react' +import $ from 'jquery' +import 'jquery-migrate' +import {Provider} from 'react-redux' +import {render} from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import moxios from 'moxios' +import sinon from 'sinon' + +import {ConnectedCourseSidebar} from '../CourseSidebar' +import MigrationStates from '@canvas/blueprint-courses/react/migrationStates' +import getSampleData from './getSampleData' + +import {createStore, applyMiddleware} from 'redux' +import {thunk} from 'redux-thunk' +import rootReducer from '@canvas/blueprint-courses/react/reducer' + +let clock +let sidebarContentRef = null + +const initialState = { + masterCourse: getSampleData().masterCourse, + existingAssociations: getSampleData().courses, + unsyncedChanges: getSampleData().unsyncedChanges, + migrationStatus: MigrationStates.states.unknown, + canManageCourse: true, + hasLoadedAssociations: true, + hasLoadedUnsyncedChanges: true, + canAutoPublishCourses: false, +} + +const defaultProps = () => ({ + contentRef: cr => { + sidebarContentRef = cr + }, + routeTo: () => {} +}) + +function mockStore(initialState) { + return applyMiddleware(thunk)(createStore)(rootReducer, initialState) +} + +function connect(props = defaultProps(), storeState = initialState) { + return ( + + + + ) +} + +describe('Course Sidebar component', () => { + beforeEach(() => { + clock = sinon.useFakeTimers() + moxios.install() + moxios.stubRequest('/api/v1/courses/4/blueprint_templates/default/migrations', { + status: 200, + response: [{id: '1'}], + }) + }) + + afterEach(() => { + moxios.uninstall() + clock.restore() + }) + + test('renders the closed CourseSidebar component', () => { + const tree = render(connect()) + const node = tree.container.querySelector('button') + expect(node.textContent.trim()).toEqual('Open Blueprint Sidebar') + tree.unmount() + }) + + test('renders the open CourseSidebar component', async () => { + const tree = render(connect()) + const button = tree.container.querySelector('button') + const user = userEvent.setup({delay: null}) + await user.click(button) + + clock.tick(500) + expect(sidebarContentRef).toBeTruthy() + + const sidebar = $(sidebarContentRef) + const rows = sidebar.find('.bcs__row') + + // associations + expect(rows.eq(0).find('button#mcSidebarAsscBtn').size()).toBeTruthy() + expect(rows.eq(0).text().trim()).toEqual(`Associations${initialState.existingAssociations.length}`) + + // sync history + expect(rows.eq(1).find('button#mcSyncHistoryBtn').size()).toBeTruthy() + + // unsynced changes + expect(rows.eq(2).find('button#mcUnsyncedChangesBtn').size()).toBeTruthy() + expect(rows.eq(2).find('span').eq(0).text()).toEqual('Unsynced Changes') + + const count = Number(rows.eq(2).find('.bcs__row-right-content').text()) + expect(count).toEqual(initialState.unsyncedChanges.length) + tree.unmount() + }) + + test('renders no Uncynced Changes link if there are none', async () => { + const props = defaultProps() + const state = {...initialState} + state.unsyncedChanges = [] + const tree = render(connect(props, state)) + const button = tree.container.querySelector('button') + const user = userEvent.setup({delay: null}) + await user.click(button) + + expect(sidebarContentRef).toBeTruthy() + const sidebar = $(sidebarContentRef) + + // no unsynced changes + expect(sidebar.find('button#mcUnsyncedChangesBtn').size()).toBeFalsy() + tree.unmount() + }) + + test('renders no Uncynced Changes link if there are no associations', async () => { + const props = defaultProps() + const state = {...initialState} + state.existingAssociations = [] + const tree = render(connect(props, state)) + const button = tree.container.querySelector('button') + const user = userEvent.setup({delay: null}) + await user.click(button) + + expect(sidebarContentRef).toBeTruthy() + const sidebar = $(sidebarContentRef) + + // no unsynced changes + expect(sidebar.find('button#mcUnsyncedChangesBtn').size()).toBeFalsy() + tree.unmount() + }) + + test('renders no Uncynced Changes link if sync is in progress', async () => { + const props = defaultProps() + const state = {...initialState} + state.migrationStatus = MigrationStates.states.imports_queued + const tree = render(connect(props, state)) + const button = tree.container.querySelector('button') + const user = userEvent.setup({delay: null}) + await user.click(button) + + expect(sidebarContentRef).toBeTruthy() + const sidebar = $(sidebarContentRef) + + // no unsynced changes + expect(sidebar.find('button#mcUnsyncedChangesBtn').size()).toBeFalsy() + tree.unmount() + }) + + test('renders no Associations link if the user not an admin', async () => { + const props = defaultProps() + const state = {...initialState} + state.canManageCourse = false + const tree = render(connect(props, state)) + const button = tree.container.querySelector('button') + const user = userEvent.setup({delay: null}) + await user.click(button) + + expect(sidebarContentRef).toBeTruthy() + const sidebar = $(sidebarContentRef) + + // no unsynced changes + expect(sidebar.find('button#mcSidebarAsscBtn').size()).toBeFalsy() + tree.unmount() + }) + + test('renders Sync button if has associations and sync is active and no unsyced changes', async () => { + const props = defaultProps() + const state = {...initialState} + state.unsyncedChanges = [] + state.migrationStatus = MigrationStates.states.imports_queued + const tree = render(connect(props, state)) + const button = tree.container.querySelector('button') + const user = userEvent.setup({delay: null}) + await user.click(button) + + clock.tick(500) + expect(sidebarContentRef).toBeTruthy() + const sidebar = $(sidebarContentRef) + + expect(sidebar.find('.bcs__migration-sync').size()).toBeTruthy() + tree.unmount() + }) + + test('renders Sync button if has associations and has unsynced changes', async() => { + const props = defaultProps() + const state = {...initialState} + const tree = render(connect(props, state)) + const button = tree.container.querySelector('button') + const user = userEvent.setup({delay: null}) + await user.click(button) + + clock.tick(500) + expect(sidebarContentRef).toBeTruthy() + const sidebar = $(sidebarContentRef) + + expect(sidebar.find('.bcs__migration-sync').size()).toBeTruthy() + tree.unmount() + }) + + test('renders no Sync button if there are no associations', async () => { + const props = defaultProps() + const state = {...initialState} + state.existingAssociations = [] + const tree = render(connect(props, state)) + const button = tree.container.querySelector('button') + const user = userEvent.setup({delay: null}) + await user.click(button) + + expect(sidebarContentRef).toBeTruthy() + const sidebar = $(sidebarContentRef) + + expect(sidebar.find('.bcs__migration-sync').size()).toBeFalsy() + tree.unmount() + }) + + test('renders no Sync button if there are associations, but no unsynced changes and no sync in progress', async () => { + const props = defaultProps() + const state = {...initialState} + state.unsyncedChanges = [] + const tree = render(connect(props, state)) + const button = tree.container.querySelector('button') + const user = userEvent.setup({delay: null}) + await user.click(button) + + expect(sidebarContentRef).toBeTruthy() + const sidebar = $(sidebarContentRef) + + expect(sidebar.find('.bcs__migration-sync').size()).toBeFalsy() + tree.unmount() + }) +}) diff --git a/ui/features/blueprint_course_master/react/components/__tests__/MigrationOptions.test.jsx b/ui/features/blueprint_course_master/react/components/__tests__/MigrationOptions.test.jsx new file mode 100644 index 00000000000..06a33a623a3 --- /dev/null +++ b/ui/features/blueprint_course_master/react/components/__tests__/MigrationOptions.test.jsx @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2024 - 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 . + */ + +import React from 'react' +import {shallow} from 'enzyme' +import {render} from '@testing-library/react' +import {getByLabelText} from '@testing-library/dom' +import MigrationOptions from '../MigrationOptions' +import MigrationStates from '@canvas/blueprint-courses/react/migrationStates' + +const noop = () => {} + +describe('MigrationOptions component', () => { + + const defaultProps = { + migrationStatus: MigrationStates.states.unknown, + willSendNotification: false, + willIncludeCustomNotificationMessage: false, + willIncludeCourseSettings: false, + notificationMessage: '', + enableSendNotification: noop, + includeCustomNotificationMessage: noop, + setNotificationMessage: noop, + includeCourseSettings: noop, + } + + test('renders the MigrationOptions component', () => { + const tree = shallow() + const node = tree.find({as: 'fieldset'}) + expect(node.exists()).toBeTruthy() + }) + + test('renders the course-settings and notification-enable checkboxes', () => { + const tree = render() + const checkboxes = tree.container.querySelectorAll('input[type="checkbox"]') + expect(checkboxes.length).toEqual(2) + expect(checkboxes[0].checked).toEqual(false) + expect(checkboxes[1].checked).toEqual(false) + }) + + test('renders the add a message checkbox', () => { + const props = {...defaultProps} + props.willSendNotification = true + + const tree = render() + + const courseSettingsCheckbox = getByLabelText(tree.container, 'Include Course Settings') + expect(courseSettingsCheckbox).toBeTruthy() + expect(courseSettingsCheckbox.checked).toEqual(false) + + const notificationCheckbox = getByLabelText(tree.container, 'Send Notification') + expect(notificationCheckbox).toBeTruthy() + expect(notificationCheckbox.checked).toEqual(true) + + const checkbox3 = getByLabelText(tree.container, 'Add a Message (0/140)') + expect(checkbox3).toBeTruthy() + expect(checkbox3.checked).toEqual(false) + + const messagebox = tree.container.querySelector('textarea') + expect(messagebox).toBeFalsy() + }) + + test('renders the message text area', () => { + const props = {...defaultProps} + props.willSendNotification = true + props.willIncludeCustomNotificationMessage = true + + const tree = render() + const messagebox = tree.container.querySelector('textarea') + expect(messagebox).toBeTruthy() + }) +}) diff --git a/ui/features/blueprint_course_master/react/components/__tests__/MigrationSync.test.jsx b/ui/features/blueprint_course_master/react/components/__tests__/MigrationSync.test.jsx new file mode 100644 index 00000000000..5169879dfd3 --- /dev/null +++ b/ui/features/blueprint_course_master/react/components/__tests__/MigrationSync.test.jsx @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2024 - 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 . + */ + +import React from 'react' +import {render} from '@testing-library/react' +import {shallow} from 'enzyme' +import sinon from 'sinon' +import userEvent from '@testing-library/user-event' +import MigrationSync from '../MigrationSync' + +describe('MigrationSync component', () => { + + const defaultProps = () => ({ + migrationStatus: 'void', + hasCheckedMigration: true, + isLoadingBeginMigration: false, + checkMigration: () => {}, + beginMigration: () => {}, + stopMigrationStatusPoll: () => {}, + }) + + test('renders the MigrationSync component', () => { + const tree = shallow() + const node = tree.find('.bcs__migration-sync') + expect(node).toBeTruthy() + }) + + test('renders the progress indicator if in a loading migration state', () => { + const props = defaultProps() + props.migrationStatus = 'queued' + const tree = shallow() + const node = tree.find('.bcs__migration-sync__loading') + expect(node).toBeTruthy() + }) + + test('renders the progress indicator if in the process of beginning a migration', () => { + const props = defaultProps() + props.isLoadingBeginMigration = true + const tree = shallow() + const node = tree.find('.bcs__migration-sync__loading') + expect(node).toBeTruthy() + }) + + test('calls beginMigration when sync button is clicked', async () => { + const props = defaultProps() + props.beginMigration = sinon.spy() + const tree = render() + const button = tree.container.querySelector('.bcs__migration-sync button') + const user = userEvent.setup({delay: null}) + await user.click(button) + expect(props.beginMigration.callCount).toEqual(1) + }) + + test('calls checkMigration on mount if it has not been checked already', () => { + const props = defaultProps() + props.hasCheckedMigration = false + props.checkMigration = sinon.spy() + const tree = shallow() + expect(props.checkMigration.callCount).toEqual(1) + }) + + test('does not call checkMigration on mount if it has been checked already', () => { + const props = defaultProps() + props.hasCheckedMigration = true + props.checkMigration = sinon.spy() + const tree = shallow() + expect(props.checkMigration.callCount).toEqual(0) + }) +}) diff --git a/ui/features/blueprint_course_master/react/components/__tests__/SyncHistory.test.jsx b/ui/features/blueprint_course_master/react/components/__tests__/SyncHistory.test.jsx new file mode 100644 index 00000000000..b52fa5a6086 --- /dev/null +++ b/ui/features/blueprint_course_master/react/components/__tests__/SyncHistory.test.jsx @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2024 - 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 . + */ + +import React from 'react' +import {render} from '@testing-library/react' +import {shallow} from 'enzyme' +import SyncHistory from '../SyncHistory' +import getSampleData from './getSampleData' + +describe('SyncHistory component', () => { + +const defaultProps = () => ({ + loadHistory: () => {}, + isLoadingHistory: false, + hasLoadedHistory: false, + loadAssociations: () => {}, + isLoadingAssociations: false, + hasLoadedAssociations: false, + migrations: getSampleData().history, +}) + + test('renders the SyncHistory component', () => { + const tree = shallow() + const node = tree.find('.bcs__history') + expect(node).toBeTruthy() + }) + + test('displays spinner when loading courses', () => { + const props = defaultProps() + props.isLoadingHistory = true + const tree = shallow() + const node = tree.find('.bcs__history Spinner') + expect(node).toBeTruthy() + }) + + test('renders SyncHistoryItem components for each migration', () => { + const tree = render() + console.log(tree.container.innerHTML) + const node = tree.container.querySelectorAll('.bcs__history-item') + expect(node.length).toEqual(1) + }) +}) diff --git a/spec/javascripts/jsx/blueprint_courses/components/UnsyncedChangesSpec.jsx b/ui/features/blueprint_course_master/react/components/__tests__/UnsyncedChanges.test.jsx similarity index 59% rename from spec/javascripts/jsx/blueprint_courses/components/UnsyncedChangesSpec.jsx rename to ui/features/blueprint_course_master/react/components/__tests__/UnsyncedChanges.test.jsx index 04ce73f38c2..26355631652 100644 --- a/spec/javascripts/jsx/blueprint_courses/components/UnsyncedChangesSpec.jsx +++ b/ui/features/blueprint_course_master/react/components/__tests__/UnsyncedChanges.test.jsx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 - present Instructure, Inc. + * Copyright (C) 2024 - present Instructure, Inc. * * This file is part of Canvas. * @@ -18,9 +18,10 @@ import React from 'react' import {Provider} from 'react-redux' -import {mount} from 'enzyme' +import {render} from '@testing-library/react' +import {getByText, getAllByText} from '@testing-library/dom' import createStore from '@canvas/blueprint-courses/react/store' -import {ConnectedUnsyncedChanges} from 'ui/features/blueprint_course_master/react/components/UnsyncedChanges' +import {ConnectedUnsyncedChanges} from '../UnsyncedChanges' import MigrationStates from '@canvas/blueprint-courses/react/migrationStates' const noop = () => {} @@ -90,45 +91,39 @@ function connect(props = {...defaultProps}) { ) } -QUnit.module('UnsyncedChanges component') +describe('UnsyncedChanges component', () => { -test('renders the UnsyncedChanges component', () => { - const tree = mount(connect()) - let node = tree.find('UnsyncedChanges') - ok(node.exists()) - node = tree.find('.bcs__history') - ok(node.exists()) -}) + test('renders the UnsyncedChanges component', () => { + const tree = render(connect()) + let node = tree.container.querySelector('.bcs__unsynced-item__table') + expect(node).toBeTruthy() + node = tree.container.querySelector('.bcs__history') + expect(node).toBeTruthy() + }) -test('renders the migration options component', () => { - const tree = mount(connect()) - const node = tree.find('MigrationOptions') - ok(node.exists()) -}) + test('renders the migration options component', () => { + const tree = render(connect()) + const node = getByText(tree.container, 'History Settings') + expect(node).toBeTruthy() + }) -test('renders the changes properly', () => { - const tree = mount(connect()) - const changes = tree.find('tr[data-testid="bcs__unsynced-item"]') - equal(changes.length, 4) - const locks = changes.find('IconBlueprintLockSolid') - equal(locks.length, 1) - const unlocks = changes.find('IconBlueprintSolid') - equal(unlocks.length, 3) -}) + test('renders the changes properly', () => { + const tree = render(connect()) + const changes = tree.container.querySelectorAll('tr[data-testid="bcs__unsynced-item"]') + expect(changes.length).toEqual(4) + const locks = tree.container.querySelectorAll('svg[name="IconBlueprintLock"]') + expect(locks.length).toEqual(1) + const unlocks = tree.container.querySelectorAll('svg[name="IconBlueprint"]') + expect(unlocks.length).toEqual(3) + }) -test('renders the media tracks properly', () => { - const tree = mount(connect()) - const changes = tree.find('tr[data-testid="bcs__unsynced-item"]') - equal(changes.length, 4) - const assetName = changes.findWhere( - node => - node.name() === 'Text' && - node.text() === 'media.mp4 (English)' && - node.parent().type() === 'span' - ) - equal(assetName.length, 1) - const assetType = changes.findWhere( - node => node.name() === 'Text' && node.text() === 'Caption' && node.parent().type() === 'td' - ) - equal(assetType.length, 1) + test('renders the media tracks properly', () => { + const tree = render(connect()) + const changes = tree.container.querySelectorAll('tr[data-testid="bcs__unsynced-item"]') + expect(changes.length).toEqual(4) + const assetName = getAllByText(tree.container, 'media.mp4 (English)') + expect(assetName.length).toEqual(1) + const assetType = getAllByText(tree.container, 'Caption') + expect(assetType.length).toEqual(1) + }) }) diff --git a/ui/shared/blueprint-courses/react/components/LockManager/LockBanner.jsx b/ui/shared/blueprint-courses/react/components/LockManager/LockBanner.jsx index 2da71bafc9a..94e7306659c 100644 --- a/ui/shared/blueprint-courses/react/components/LockManager/LockBanner.jsx +++ b/ui/shared/blueprint-courses/react/components/LockManager/LockBanner.jsx @@ -67,7 +67,7 @@ export default class LockBanner extends Component { {I18n.t('Locked:')}  - {formatLockObject(this.props.itemLocks)} + {formatLockObject(this.props.itemLocks)} ) } diff --git a/ui/shared/blueprint-courses/react/components/LockManager/__tests__/LockBanner.test.jsx b/ui/shared/blueprint-courses/react/components/LockManager/__tests__/LockBanner.test.jsx new file mode 100644 index 00000000000..ecda2aae0f2 --- /dev/null +++ b/ui/shared/blueprint-courses/react/components/LockManager/__tests__/LockBanner.test.jsx @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2024 - 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 . + */ + +import React from 'react' +import {render} from '@testing-library/react' +import LockBanner from '../LockBanner' + +describe('LockBanner component', () => { + + const defaultProps = () => ({ + isLocked: true, + itemLocks: { + content: true, + points: false, + due_dates: false, + availability_dates: false, + }, + }) + + test('renders an Alert when LockBanner is locked', () => { + const props = defaultProps() + props.isLocked = true + const tree = render() + const node = tree.container.querySelector('div') + expect(node).toBeTruthy() + }) + + test('does not render Alert when LockBanner is locked', () => { + const props = defaultProps() + props.isLocked = false + const tree = render() + const node = tree.container.querySelector('div') + expect(node).toBeFalsy() + }) + + test('displays locked description text appropriately when one attribute is locked', () => { + const props = defaultProps() + const tree = render() + const text = tree.container.querySelector("[data-testid='lockedMessage'").textContent + expect(text).toEqual('Content') + }) + + test('displays locked description text appropriately when two attributes are locked', () => { + const props = defaultProps() + props.itemLocks.points = true + const tree = render() + const text = tree.container.querySelector("[data-testid='lockedMessage'").textContent + expect(text).toEqual('Content & Points') + }) + + test('displays locked description text appropriately when more than two attributes are locked', () => { + const props = defaultProps() + props.itemLocks.points = true + props.itemLocks.due_dates = true + const tree = render() + const text = tree.container.querySelector("[data-testid='lockedMessage'").textContent + expect(text).toEqual('Content, Points & Due Dates') + }) +}) diff --git a/ui/shared/blueprint-courses/react/components/LockManager/__tests__/LockToggle.test.jsx b/ui/shared/blueprint-courses/react/components/LockManager/__tests__/LockToggle.test.jsx new file mode 100644 index 00000000000..66d01be7c72 --- /dev/null +++ b/ui/shared/blueprint-courses/react/components/LockManager/__tests__/LockToggle.test.jsx @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2024 - 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 . + */ + +import React from 'react' +import {shallow} from 'enzyme' +import {render} from '@testing-library/react' +import LockToggle from '../LockToggle' + +describe('LockToggle component', () => { + + const defaultProps = () => ({ + isLocked: true, + isToggleable: true, + }) + + test('renders the LockToggle component', () => { + const tree = render() + const node = tree.container.querySelector('.bpc-lock-toggle') + expect(node).toBeTruthy() + }) + + test('renders a button when LockToggle is toggleable', () => { + const props = defaultProps() + props.isToggleable = true + const tree = render() + const node = tree.container.querySelector('button') + expect(node).toBeTruthy() + }) + + test('does not render a button when LockToggle is not toggleable', () => { + const props = defaultProps() + props.isToggleable = false + const tree = shallow() + const node = tree.find('button') + expect(node.exists()).toBeFalsy() + }) + + test('renders a locked icon when LockToggle is locked', () => { + const props = defaultProps() + props.isLocked = true + const tree = render() + console.log(tree.container.innerHTML) + const node = tree.container.querySelector('svg[name="IconBlueprintLock"]') + expect(node).toBeTruthy() + }) + + test('renders an unlocked icon when LockToggle is unlocked', () => { + const props = defaultProps() + props.isLocked = false + const tree = render() + console.log(tree.container.innerHTML) + const node = tree.container.querySelector('svg[name="IconBlueprint"]') + expect(node).toBeTruthy() + }) +}) diff --git a/ui/shared/blueprint-courses/react/components/__tests__/SyncHistoryItem.test.jsx b/ui/shared/blueprint-courses/react/components/__tests__/SyncHistoryItem.test.jsx new file mode 100644 index 00000000000..ec26fb58682 --- /dev/null +++ b/ui/shared/blueprint-courses/react/components/__tests__/SyncHistoryItem.test.jsx @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2024 - 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 . + */ + +import React from 'react' +import {render} from '@testing-library/react' +import {shallow} from 'enzyme' +import SyncHistoryItem from '../SyncHistoryItem' +import getSampleData from './getSampleData' + +describe('SyncHistoryItem component', () => { + + const defaultProps = () => ({ + heading: null, + migration: getSampleData().history[0], + }) + + test('renders the SyncHistoryItem component', () => { + const tree = shallow() + const node = tree.find('.bcs__history-item') + expect(node).toBeTruthy() + }) + + test('renders heading component when migration has changes', () => { + const props = defaultProps() + props.heading =

test

+ const tree = shallow() + const node = tree.find('.bcs__history-item .test-heading') + expect(node).toBeTruthy() + }) + + test('does not render the heading component when migration has no changes', () => { + const props = defaultProps() + props.heading =

test

+ props.migration.changes = [] + const tree = shallow() + const node = tree.find('.bcs__history-item .test-heading') + expect(node.exists()).toBeFalsy() + }) + + test('renders changes using the appropriate prop component', () => { + const props = defaultProps() + props.ChangeComponent = () =>
+ const tree = render() + const node = tree.container.querySelectorAll('.bcs__history-item .test-change') + expect(node.length).toEqual(props.migration.changes.length) + }) + + test('includes the name of the person who started the sync', () => { + const tree = render() + const node = tree.container.querySelector('.bcs__history-item__title') + const text = node.textContent + expect(text.indexOf('changes pushed by Bob Jones')).toEqual(57) + }) +}) diff --git a/ui/shared/blueprint-courses/react/components/__tests__/getSampleData.js b/ui/shared/blueprint-courses/react/components/__tests__/getSampleData.js new file mode 100644 index 00000000000..6068277bedb --- /dev/null +++ b/ui/shared/blueprint-courses/react/components/__tests__/getSampleData.js @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2017 - 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 . + */ + +export default function getSampleData() { + return { + terms: [ + {id: '1', name: 'Term One'}, + {id: '2', name: 'Term Two'}, + ], + subAccounts: [ + {id: '1', name: 'Account One'}, + {id: '2', name: 'Account Two'}, + ], + childCourse: { + id: '1', + enrollment_term_id: '1', + name: 'Course 1', + }, + masterCourse: { + id: '2', + enrollment_term_id: '1', + name: 'Course 2', + }, + courses: [ + { + id: '1', + name: 'Course One', + course_code: 'course_1', + term: { + id: '1', + name: 'Term One', + }, + teachers: [ + { + display_name: 'Teacher One', + }, + ], + sis_course_id: '1001', + }, + { + id: '2', + name: 'Course Two', + course_code: 'course_2', + term: { + id: '2', + name: 'Term Two', + }, + teachers: [ + { + display_name: 'Teacher Two', + }, + ], + sis_course_id: '1001', + }, + ], + history: [ + { + id: '2', + workflow_state: 'completed', + created_at: '2013-08-28T23:59:00-06:00', + user: { + display_name: 'Bob Jones', + }, + changes: [ + { + asset_id: '2', + asset_type: 'quiz', + asset_name: 'Chapter 5 Quiz', + change_type: 'updated', + html_url: 'http://localhost:3000/courses/3/quizzes/2', + exceptions: [ + { + course_id: '1', + conflicting_changes: ['points'], + name: 'Course 1', + term: {name: 'Default Term'}, + }, + { + course_id: '5', + conflicting_changes: ['content'], + name: 'Course 5', + term: {name: 'Default Term'}, + }, + { + course_id: '56', + conflicting_changes: ['deleted'], + name: 'Course 56', + term: {name: 'Default Term'}, + }, + ], + }, + ], + }, + ], + unsyncedChanges: [ + { + asset_id: '22', + asset_type: 'assignment', + asset_name: 'Another Discussion', + change_type: 'deleted', + html_url: '/courses/4/assignments/22', + locked: false, + }, + { + asset_id: '22', + asset_type: 'attachment', + asset_name: 'Bulldog.png', + change_type: 'updated', + html_url: '/courses/4/files/96', + locked: true, + }, + { + asset_id: 'page-1', + asset_type: 'wiki_page', + asset_name: 'Page 1', + change_type: 'created', + html_url: '/4/pages/page-1', + locked: false, + }, + ], + } +}