diff --git a/app/jsx/content_shares/CourseImportPanel.js b/app/jsx/content_shares/CourseImportPanel.js index 09e5bb96d52..bc98a539727 100644 --- a/app/jsx/content_shares/CourseImportPanel.js +++ b/app/jsx/content_shares/CourseImportPanel.js @@ -46,7 +46,7 @@ export default function CourseImportPanel({contentShare, onClose, onImport}) { migration_type: 'canvas_cartridge_importer', settings: { content_export_id: contentShare.content_export.id, - insert_into_module_id: selectedModule?.id, + insert_into_module_id: selectedModule?.id || null, insert_into_module_type: contentShare.content_type, insert_into_module_position: selectedPosition } @@ -56,6 +56,11 @@ export default function CourseImportPanel({contentShare, onClose, onImport}) { onImport(contentShare) } + function handleSelectedCourse(course) { + setSelectedModule(null) + setSelectedCourse(course) + } + return ( <> diff --git a/app/jsx/content_shares/__tests__/CourseImportPanel.test.js b/app/jsx/content_shares/__tests__/CourseImportPanel.test.js index c2a13e5945f..8cd2862efca 100644 --- a/app/jsx/content_shares/__tests__/CourseImportPanel.test.js +++ b/app/jsx/content_shares/__tests__/CourseImportPanel.test.js @@ -20,7 +20,9 @@ import React from 'react' import {render, fireEvent, act} from '@testing-library/react' import fetchMock from 'fetch-mock' import useManagedCourseSearchApi from 'jsx/shared/effects/useManagedCourseSearchApi' -import useModuleCourseSearchApi from 'jsx/shared/effects/useModuleCourseSearchApi' +import useModuleCourseSearchApi, { + useCourseModuleItemApi +} from 'jsx/shared/effects/useModuleCourseSearchApi' import CourseImportPanel from '../CourseImportPanel' import {mockShare} from 'jsx/content_shares/__tests__/test-utils' @@ -43,7 +45,10 @@ describe('CourseImportPanel', () => { beforeEach(() => { useManagedCourseSearchApi.mockImplementationOnce(({success}) => { - success([{id: 'abc', name: 'abc'}, {id: 'cde', name: 'cde'}]) + success([ + {id: 'abc', name: 'abc'}, + {id: 'cde', name: 'cde'} + ]) }) }) @@ -93,7 +98,10 @@ describe('CourseImportPanel', () => { workflow_state: 'running' }) useModuleCourseSearchApi.mockImplementationOnce(({success}) => { - success([{id: '1', name: 'Module 1'}, {id: '2', name: 'Module 2'}]) + success([ + {id: '1', name: 'Module 1'}, + {id: '2', name: 'Module 2'} + ]) }) const {getByText, getByLabelText, queryByText} = render( @@ -121,6 +129,34 @@ describe('CourseImportPanel', () => { expect(onImport.mock.calls[0][0]).toBe(share) }) + it('deletes the module and removes the position selector when a new course is selected', () => { + const share = mockShare() + useModuleCourseSearchApi.mockImplementationOnce(({success}) => { + success([ + {id: '1', name: 'Module 1'}, + {id: '2', name: 'Module 2'} + ]) + }) + const {getByText, getByLabelText, queryByText} = render( + + ) + const courseSelector = getByText(/select a course/i) + fireEvent.click(courseSelector) + fireEvent.click(getByText('abc')) + fireEvent.click(getByText(/select a module/i)) + fireEvent.click(getByText(/Module 1/)) + expect(getByText(/Position/)).toBeInTheDocument() + useManagedCourseSearchApi.mockImplementationOnce(({success}) => { + success([{id: 'ghi', name: 'foo'}]) + }) + useCourseModuleItemApi.mockClear() + const input = getByLabelText(/select a course/i) + fireEvent.change(input, {target: {value: 'fo'}}) + fireEvent.click(getByText('foo')) + expect(queryByText(/Position/)).not.toBeInTheDocument() + expect(useCourseModuleItemApi).not.toHaveBeenCalled() + }) + describe('errors', () => { beforeEach(() => { jest.spyOn(console, 'error').mockImplementation() diff --git a/app/jsx/shared/components/ConfirmActionButtonBar.js b/app/jsx/shared/components/ConfirmActionButtonBar.js index 55793b5a348..df074affe51 100644 --- a/app/jsx/shared/components/ConfirmActionButtonBar.js +++ b/app/jsx/shared/components/ConfirmActionButtonBar.js @@ -19,7 +19,7 @@ import React from 'react' import {bool, string, func} from 'prop-types' import {Button} from '@instructure/ui-buttons' -import {Flex} from '@instructure/ui-layout' +import {Flex} from '@instructure/ui-flex' ConfirmActionButtonBar.propTypes = { // only buttons with labels will be displayed diff --git a/app/jsx/shared/direct_share/CourseAndModulePicker.js b/app/jsx/shared/direct_share/CourseAndModulePicker.js index 4b1043796b6..95750388f37 100644 --- a/app/jsx/shared/direct_share/CourseAndModulePicker.js +++ b/app/jsx/shared/direct_share/CourseAndModulePicker.js @@ -52,25 +52,25 @@ export default function CourseAndModulePicker({ itemSearchFunction={useManagedCourseSearchApi} /> - - {selectedCourseId && ( + {selectedCourseId && ( + - )} - - - {selectedCourseId && selectedModuleId && ( + + )} + {selectedCourseId && selectedModuleId && ( + - )} - + + )} ) diff --git a/app/jsx/shared/direct_share/DirectShareCoursePanel.js b/app/jsx/shared/direct_share/DirectShareCoursePanel.js index 7fa3ca63be8..04bd30b5fc8 100644 --- a/app/jsx/shared/direct_share/DirectShareCoursePanel.js +++ b/app/jsx/shared/direct_share/DirectShareCoursePanel.js @@ -52,7 +52,7 @@ export default function DirectShareCoursePanel({sourceCourseId, contentSelection select: contentSelection, settings: { source_course_id: sourceCourseId, - insert_into_module_id: selectedModule?.id, + insert_into_module_id: selectedModule?.id || null, insert_into_module_type: contentSelection ? Object.keys(contentSelection)[0] : null, insert_into_module_position: selectedPosition } @@ -61,6 +61,11 @@ export default function DirectShareCoursePanel({sourceCourseId, contentSelection ) } + function handleSelectedCourse(course) { + setSelectedModule(null) + setSelectedCourse(course) + } + return ( <> diff --git a/app/jsx/shared/direct_share/__tests__/CourseAndModulePicker.test.js b/app/jsx/shared/direct_share/__tests__/CourseAndModulePicker.test.js index e1de49bba39..debdc8f748e 100644 --- a/app/jsx/shared/direct_share/__tests__/CourseAndModulePicker.test.js +++ b/app/jsx/shared/direct_share/__tests__/CourseAndModulePicker.test.js @@ -17,9 +17,11 @@ */ import React from 'react' -import {render} from '@testing-library/react' +import {render, fireEvent} from '@testing-library/react' import useManagedCourseSearchApi from 'jsx/shared/effects/useManagedCourseSearchApi' -import useModuleCourseSearchApi from 'jsx/shared/effects/useModuleCourseSearchApi' +import useModuleCourseSearchApi, { + useCourseModuleItemApi +} from 'jsx/shared/effects/useModuleCourseSearchApi' import CourseAndModulePicker from '../CourseAndModulePicker' jest.mock('jsx/shared/effects/useManagedCourseSearchApi') @@ -39,15 +41,73 @@ describe('CourseAndModulePicker', () => { if (ariaLive) ariaLive.remove() }) - it('enables the module selector when a course is selected', () => { + it('shows course selector by default', () => { useManagedCourseSearchApi.mockImplementationOnce(({success}) => { - success([{id: 'abc', name: 'abc'}, {id: 'cde', name: 'cde'}]) + success([ + {id: 'abc', name: 'abc'}, + {id: 'cde', name: 'cde'} + ]) + }) + const setCourse = jest.fn() + const {getByText} = render() + const selector = getByText(/select a course/i) + fireEvent.click(selector) + fireEvent.click(getByText('abc')) + expect(setCourse).toHaveBeenLastCalledWith({id: 'abc', name: 'abc'}) + }) + + it('shows the course and module selector when a course is given', () => { + useManagedCourseSearchApi.mockImplementationOnce(({success}) => { + success([ + {id: 'abc', name: 'abc'}, + {id: 'cde', name: 'cde'} + ]) }) useModuleCourseSearchApi.mockImplementationOnce(({success}) => { - success([{id: '1', name: 'Module 1'}, {id: '2', name: 'Module 2'}]) + success([ + {id: '1', name: 'Module 1'}, + {id: '2', name: 'Module 2'} + ]) }) - const {getByText} = render() - expect(getByText(/select a course/i)).toBeInTheDocument() - expect(getByText(/select a module/i)).toBeInTheDocument() + const setModule = jest.fn() + const {getByText} = render( + + ) + const selector = getByText(/select a module/i) + fireEvent.click(selector) + fireEvent.click(getByText('Module 1')) + expect(setModule).toHaveBeenLastCalledWith({id: '1', name: 'Module 1'}) + }) + + it('shows the position selector when a module is given', () => { + useManagedCourseSearchApi.mockImplementationOnce(({success}) => { + success([ + {id: 'abc', name: 'abc'}, + {id: 'cde', name: 'cde'} + ]) + }) + useModuleCourseSearchApi.mockImplementationOnce(({success}) => { + success([ + {id: '1', name: 'Module 1'}, + {id: '2', name: 'Module 2'} + ]) + }) + useCourseModuleItemApi.mockImplementationOnce(({success}) => { + success([ + {id: 'a', title: 'Item 1', position: '5'}, + {id: 'b', title: 'Item 2', position: '6'} + ]) + }) + const setPosition = jest.fn() + const {getByTestId} = render( + + ) + const selector = getByTestId('select-position') + fireEvent.change(selector, {target: {value: 'top'}}) + expect(setPosition).toHaveBeenLastCalledWith(1) }) }) diff --git a/app/jsx/shared/direct_share/__tests__/DirectShareCoursePanel.test.js b/app/jsx/shared/direct_share/__tests__/DirectShareCoursePanel.test.js index d4b303dfdf8..f0d60a55a02 100644 --- a/app/jsx/shared/direct_share/__tests__/DirectShareCoursePanel.test.js +++ b/app/jsx/shared/direct_share/__tests__/DirectShareCoursePanel.test.js @@ -20,9 +20,13 @@ import React from 'react' import {render, fireEvent, act} from '@testing-library/react' import fetchMock from 'fetch-mock' import useManagedCourseSearchApi from 'jsx/shared/effects/useManagedCourseSearchApi' +import useModuleCourseSearchApi, { + useCourseModuleItemApi +} from 'jsx/shared/effects/useModuleCourseSearchApi' import DirectShareCoursePanel from '../DirectShareCoursePanel' jest.mock('jsx/shared/effects/useManagedCourseSearchApi') +jest.mock('jsx/shared/effects/useModuleCourseSearchApi') describe('DirectShareCoursePanel', () => { let ariaLive @@ -40,7 +44,10 @@ describe('DirectShareCoursePanel', () => { beforeEach(() => { useManagedCourseSearchApi.mockImplementationOnce(({success}) => { - success([{id: 'abc', name: 'abc'}, {id: 'cde', name: 'cde'}]) + success([ + {id: 'abc', name: 'abc'}, + {id: 'cde', name: 'cde'} + ]) }) }) @@ -121,6 +128,36 @@ describe('DirectShareCoursePanel', () => { expect(getByText('Close')).toBeInTheDocument() }) + it('deletes the module and removes the position selector when a new course is selected', () => { + useModuleCourseSearchApi.mockImplementationOnce(({success}) => { + success([ + {id: '1', name: 'Module 1'}, + {id: '2', name: 'Module 2'} + ]) + }) + const {getByText, getByLabelText, queryByText} = render( + + ) + const courseSelector = getByText(/select a course/i) + fireEvent.click(courseSelector) + fireEvent.click(getByText('abc')) + fireEvent.click(getByText(/select a module/i)) + fireEvent.click(getByText(/Module 1/)) + expect(getByText(/Position/)).toBeInTheDocument() + useManagedCourseSearchApi.mockImplementationOnce(({success}) => { + success([{id: 'ghi', name: 'foo'}]) + }) + useCourseModuleItemApi.mockClear() + const input = getByLabelText(/select a course/i) + fireEvent.change(input, {target: {value: 'fo'}}) + fireEvent.click(getByText('foo')) + expect(queryByText(/Position/)).not.toBeInTheDocument() + expect(useCourseModuleItemApi).not.toHaveBeenCalled() + }) + describe('errors', () => { beforeEach(() => { jest.spyOn(console, 'error').mockImplementation() diff --git a/app/jsx/shared/helpers/SelectPosition.js b/app/jsx/shared/helpers/SelectPosition.js index f43599e1bb2..a0f8ee451d9 100644 --- a/app/jsx/shared/helpers/SelectPosition.js +++ b/app/jsx/shared/helpers/SelectPosition.js @@ -20,10 +20,10 @@ import React from 'react' import {string, func, bool, arrayOf, node, shape} from 'prop-types' import I18n from 'i18n!selectPosition' import ConnectorIcon from '../../move_item/ConnectorIcon' -import {Text} from '@instructure/ui-elements' +import {Text} from '@instructure/ui-text' import {FormField} from '@instructure/ui-form-field' import {View} from '@instructure/ui-layout' -import {ScreenReaderContent} from '@instructure/ui-a11y' +import {ScreenReaderContent} from '@instructure/ui-a11y-content' import {positions} from '../../move_item/positions' import {itemShape} from '../../move_item/propTypes' diff --git a/package.json b/package.json index 95a58664758..e0dc32f5298 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "@instructure/ui-svg-images": "6", "@instructure/ui-table": "6", "@instructure/ui-tabs": "6", + "@instructure/ui-text": "6", "@instructure/ui-text-input": "6", "@instructure/ui-themeable": "6", "@instructure/ui-themes": "6", diff --git a/yarn.lock b/yarn.lock index bd83bf2fd43..3fc1084cf12 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2331,6 +2331,18 @@ classnames "^2" prop-types "^15" +"@instructure/ui-text@6": + version "6.15.0" + resolved "https://registry.yarnpkg.com/@instructure/ui-text/-/ui-text-6.15.0.tgz#0bceef3fb9a2b5640b54d5f00664a37cee4ce4ba" + integrity sha512-KXF73rRC/RkIcryWnHGNnQJi3JcGcGWjJLm0kaDljXCQBS25ypvURg9OCiSYhvyInFB7fLQFP1anAAY2UyPISQ== + dependencies: + "@babel/runtime" "^7.5.0" + "@instructure/console" "^6.15.0" + "@instructure/ui-react-utils" "^6.15.0" + "@instructure/ui-themeable" "^6.15.0" + classnames "^2" + prop-types "^15" + "@instructure/ui-themeable@6", "@instructure/ui-themeable@^6.15.0", "@instructure/ui-themeable@^6.9.0": version "6.15.0" resolved "https://registry.yarnpkg.com/@instructure/ui-themeable/-/ui-themeable-6.15.0.tgz#8ef3f08020731385d9df5d0213c10692e3a1cbd3"