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,
+ },
+ ],
+ }
+}