show saved buttons and icons in rce tray

closes MAT-185
flag=rce_buttons_and_icons

Test Plan
- Create many (ideally 25+) Buttons and Icons.
- Open the Saved Buttons and Icons tray.
- Verify that the Buttons and Icons appear.
- Verify that pagination works (paginates at 25).
- Verify that filtering by a search string works.
- Verify that sorting by date works.
- Verify that sorting alphabetically works.
- Verify that switching to another Tray type and back works.

Change-Id: Ie4fec38cca8cb827c23b4ce044beb67809df7216
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/271811
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Product-Review: David Lyons <lyons@instructure.com>
Reviewed-by: Weston Dransfield <wdransfield@instructure.com>
QA-Review: Weston Dransfield <wdransfield@instructure.com>
This commit is contained in:
Gary Mei 2021-08-11 14:04:45 -05:00
parent cc27686aeb
commit a13867e00f
14 changed files with 484 additions and 67 deletions

View File

@ -22,7 +22,7 @@ import ReactDOM from 'react-dom'
import bridge from '../../../bridge'
import {StoreProvider} from '../shared/StoreContext'
export default function (ed, document, type) {
export default function (ed, document) {
return import('./components/ButtonsTray').then(({ButtonsTray}) => {
let container = document.querySelector('#instructure-rce-buttons-tray-container')
const trayProps = bridge.trayProps.get(ed)
@ -40,7 +40,7 @@ export default function (ed, document, type) {
ReactDOM.render(
<StoreProvider {...trayProps}>
{() => <ButtonsTray editor={ed} onUnmount={handleUnmount} type={type} />}
{() => <ButtonsTray editor={ed} onUnmount={handleUnmount} />}
</StoreProvider>,
container
)

View File

@ -25,15 +25,11 @@ import {Tray} from '@instructure/ui-tray'
import formatMessage from '../../../../format-message'
import {getTrayHeight} from '../../shared/trayUtils'
import {CreateButtonForm} from './CreateButtonForm'
import {SavedButtonList} from './SavedButtonList'
export function ButtonsTray({editor, onUnmount, type}) {
export function ButtonsTray({editor, onUnmount}) {
const [isOpen, setIsOpen] = useState(true)
const title =
type === 'create'
? formatMessage('Buttons and Icons')
: formatMessage('Saved Buttons and Icons')
const title = formatMessage('Buttons and Icons')
return (
<Tray
@ -62,12 +58,8 @@ export function ButtonsTray({editor, onUnmount, type}) {
</Flex>
</Flex.Item>
<Flex.Item as="content" padding="small">
{type === 'create' ? (
<CreateButtonForm editor={editor} onClose={() => setIsOpen(false)} />
) : (
<SavedButtonList />
)}
<Flex.Item as="slot" padding="small">
<CreateButtonForm editor={editor} onClose={() => setIsOpen(false)} />
</Flex.Item>
</Flex>
</Tray>

View File

@ -16,6 +16,95 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
export const SavedButtonList = () => {
return null
import React, {useEffect, useState} from 'react'
import {func, shape, string} from 'prop-types';
import Images from '../../instructure_image/Images'
export function rceToFile({createdAt, id, name, thumbnailUrl, type, url}) {
return {
content_type: type,
date: createdAt,
display_name: name,
filename: name,
href: url,
id,
thumbnail_url: thumbnailUrl
}
}
const SavedButtonList = ({context, onImageEmbed, searchString, sortBy, source}) => {
const [buttonsAndIconsBookmark, setButtonsAndIconsBookmark] = useState(null)
const [buttonsAndIcons, setButtonsAndIcons] = useState([])
const [hasMore, setHasMore] = useState(true)
const [isLoading, setIsLoading] = useState(true)
const resetState = () => {
setButtonsAndIconsBookmark(null)
setButtonsAndIcons([])
setHasMore(true)
setIsLoading(true)
}
const onLoadedImages = ({bookmark, files}) => {
setButtonsAndIconsBookmark(bookmark)
setHasMore(bookmark !== null)
setIsLoading(false)
setButtonsAndIcons(prevButtonsAndIcons => [
...prevButtonsAndIcons,
...files
.filter(({type}) => type === 'image/svg+xml')
.map(rceToFile)
])
}
const fetchButtonsAndIcons = bookmark => {
setIsLoading(true)
source.fetchButtonsAndIcons(
{contextId: context.id, contextType: context.type},
bookmark,
searchString,
sortBy,
onLoadedImages
)
}
useEffect(() => {
resetState()
}, [searchString, sortBy.order, sortBy.sort])
return (
<Images
contextType={context.type}
fetchInitialImages={() => {
fetchButtonsAndIcons()
}}
fetchNextImages={() => {
fetchButtonsAndIcons(buttonsAndIconsBookmark)
}}
images={{[context.type]: {error: null, files: buttonsAndIcons, hasMore, isLoading}}}
onImageEmbed={onImageEmbed}
searchString={searchString}
sortBy={sortBy}
/>
)
}
SavedButtonList.propTypes = {
context: shape({
id: string.isRequired,
type: string.isRequired,
}),
onImageEmbed: func.isRequired,
searchString: string,
sortBy: shape({
order: string,
sort: string
}),
source: shape({
fetchButtonsAndIcons: func.isRequired
})
}
export default SavedButtonList

View File

@ -42,11 +42,6 @@ describe('RCE "Buttons and Icons" Plugin > ButtonsTray', () => {
screen.getByRole('heading', {name: /buttons and icons/i})
})
it('renders the list view', () => {
renderComponent({...defaults, type: 'list'})
screen.getByRole('heading', {name: /saved buttons and icons/i})
})
it('closes the tray', async () => {
const onUnmount = jest.fn()
renderComponent({...defaults, onUnmount})

View File

@ -0,0 +1,140 @@
/*
* Copyright (C) 2021 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from 'react'
import {render, waitFor} from '@testing-library/react'
import sinon from 'sinon'
import RceApiSource from '../../../../../sidebar/sources/api'
import SavedButtonList from '../SavedButtonList'
import {rceToFile} from '../SavedButtonList'
describe('RCE "Buttons and Icons" Plugin > SavedButtonList', () => {
let defaultProps, fetchPageStub, globalFetchStub
const apiSource = new RceApiSource({
alertFunc: () => {},
jwt: 'theJWT'
})
beforeEach(() => {
globalFetchStub = sinon.stub(global, 'fetch')
const context = {id: '101', type: 'course'}
const buttonsAndIconsFolder = {filesUrl: 'http://rce.example.com/api/folders/52', id: '1'}
const buttonAndIcon = {
createdAt: '',
id: 1,
name: 'button.svg',
thumbnailUrl: '',
type: 'image/svg+xml',
url: ''
}
const otherImage = {
createdAt: '',
id: 2,
name: 'screenshot.jpg',
thumbnailUrl: '',
type: 'image/jpeg',
url: ''
}
const folders = [buttonsAndIconsFolder]
fetchPageStub = sinon.stub(apiSource, 'fetchPage')
fetchPageStub
.withArgs(`/api/folders/buttons_and_icons?contextType=course&contextId=${context.id}`)
.returns(Promise.resolve({folders}))
fetchPageStub
.withArgs(
'http://rce.example.com/api/folders/52?per_page=25&sort=created_at&order=desc',
'theJWT'
)
.returns(Promise.resolve({bookmark: '', files: [buttonAndIcon, otherImage]}))
defaultProps = {
context,
onImageEmbed: () => {},
searchString: '',
sortBy: {order: 'desc', sort: 'date_added'},
source: apiSource
}
})
afterEach(() => {
fetchPageStub.restore()
globalFetchStub.restore()
})
const renderComponent = componentProps => {
return render(<SavedButtonList {...defaultProps} {...componentProps} />)
}
it('loads and displays svgs', async () => {
const {getByAltText} = renderComponent()
await waitFor(() => expect(getByAltText('button.svg')).toBeInTheDocument())
})
it('ignores non-svg files', async () => {
const {queryByAltText} = renderComponent()
await waitFor(() => queryByAltText('button.svg') != null)
expect(queryByAltText('screenshot.jpg')).toBeNull()
})
})
describe('rceToFile', () => {
const rceFile = {
createdAt: '2021-08-12T18:30:53Z',
id: '101',
name: 'kitten.gif',
thumbnailUrl: 'http://example.com/kitten.png',
type: 'image/gif',
url: 'http://example.com/kitten.gif'
}
it('returns an object with type as content_type', () => {
expect(rceToFile(rceFile).content_type).toStrictEqual('image/gif')
})
it('returns an object with createdAt as date', () => {
expect(rceToFile(rceFile).date).toStrictEqual('2021-08-12T18:30:53Z')
})
it('returns an object with name as display_name', () => {
expect(rceToFile(rceFile).display_name).toStrictEqual('kitten.gif')
})
it('returns an object with name as filename', () => {
expect(rceToFile(rceFile).filename).toStrictEqual('kitten.gif')
})
it('returns an object with url as href', () => {
expect(rceToFile(rceFile).href).toStrictEqual('http://example.com/kitten.gif')
})
it('returns an object with id as id', () => {
expect(rceToFile(rceFile).id).toStrictEqual('101')
})
it('returns an object with thumbnailUrl as thumbnail_url', () => {
expect(rceToFile(rceFile).thumbnail_url).toStrictEqual('http://example.com/kitten.png')
})
})

View File

@ -16,12 +16,13 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import bridge from '../../../bridge'
import formatMessage from '../../../format-message'
import {isOKToLink} from '../../contentInsertionUtils'
import clickCallback from './clickCallback'
const CREATE_BUTTON = 'create'
const LIST_BUTTON = 'list'
const LIST_BUTTON = 'list_buttons_and_icons'
function getMenuItems() {
return [
@ -52,9 +53,13 @@ function handleOptionSelected(ed, value) {
tinymce.create('tinymce.plugins.InstructureButtonsPlugin', {
init(ed) {
// Register tray control command
ed.addCommand('instructureTrayForButtonsPlugin', (ui, type) =>
clickCallback(ed, document, type)
)
ed.addCommand('instructureTrayForButtonsPlugin', (_ui, type) => {
if (type === LIST_BUTTON) {
bridge.showTrayForPlugin(type, ed.id)
} else {
clickCallback(ed, document)
}
})
// Register menu items
ed.ui.registry.addNestedMenuItem('instructure_buttons', {

View File

@ -46,6 +46,8 @@ function getTrayLabel(contentType, contentSubtype, contextType) {
}
switch (contentSubtype) {
case 'buttons_and_icons':
return formatMessage('Buttons and Icons')
case 'images':
if (contentType === 'course_files') return formatMessage('Course Images')
if (contentType === 'group_files') return formatMessage('Group Images')
@ -64,6 +66,7 @@ function getTrayLabel(contentType, contentSubtype, contextType) {
}
const thePanels = {
buttons_and_icons: React.lazy(() => import('../instructure_buttons/components/SavedButtonList')),
links: React.lazy(() => import('../instructure_links/components/LinksPanel')),
images: React.lazy(() => import('../instructure_image/Images')),
documents: React.lazy(() => import('../instructure_documents/components/DocumentsPanel')),
@ -182,6 +185,14 @@ const FILTER_SETTINGS_BY_PLUGIN = {
sortDir: 'desc',
searchString: ''
},
list_buttons_and_icons: {
contextType: 'course',
contentType: 'course_files',
contentSubtype: 'buttons_and_icons',
sortValue: 'date_added',
sortDir: 'desc',
searchString: ''
},
all: {
contextType: 'course',
contentType: 'course_files',

View File

@ -72,6 +72,7 @@ function renderTypeOptions(contentType, contentSubtype, userContextType) {
{formatMessage('Links')}
</SimpleSelect.Option>
]
if (userContextType === 'course' && contentType !== 'links' && contentSubtype !== 'all') {
options.push(
<SimpleSelect.Option
@ -84,6 +85,7 @@ function renderTypeOptions(contentType, contentSubtype, userContextType) {
</SimpleSelect.Option>
)
}
if (userContextType === 'group' && contentType !== 'links' && contentSubtype !== 'all') {
options.push(
<SimpleSelect.Option
@ -96,16 +98,23 @@ function renderTypeOptions(contentType, contentSubtype, userContextType) {
</SimpleSelect.Option>
)
}
options.push(
<SimpleSelect.Option
key="user_files"
id="user_files"
value="user_files"
renderBeforeLabel={IconFolderLine}
>
{fileLabelFromContext(contentType === 'links' || contentSubtype === 'all' ? 'files' : 'user')}
</SimpleSelect.Option>
)
// Buttons and Icons are only stored in course folders.
if (contentSubtype !== 'buttons_and_icons') {
options.push(
<SimpleSelect.Option
key="user_files"
id="user_files"
value="user_files"
renderBeforeLabel={IconFolderLine}
>
{fileLabelFromContext(
contentType === 'links' || contentSubtype === 'all' ? 'files' : 'user'
)}
</SimpleSelect.Option>
)
}
return options
}
@ -222,6 +231,9 @@ export default function Filter(props) {
// when flipped to All, the context needs to be user
// so we can get media_objects, which are all returned in the user context
changed.contentType = 'user_files'
} else if (changed.contentSubtype === 'buttons_and_icons') {
// Buttons and Icons only belong to Courses.
changed.contentType = 'course_files'
}
onChange(changed)
}}
@ -243,6 +255,14 @@ export default function Filter(props) {
{formatMessage('Media')}
</SimpleSelect.Option>
<SimpleSelect.Option
id="buttons_and_icons"
value="buttons_and_icons"
renderBeforeLabel={IconImageLine}
>
{formatMessage('Buttons and Icons')}
</SimpleSelect.Option>
<SimpleSelect.Option id="all" value="all">
{formatMessage('All')}
</SimpleSelect.Option>

View File

@ -112,6 +112,11 @@ describe('RCE Plugins > CanvasContentTray', () => {
await showTrayForPlugin('user_documents')
expect(getTrayLabel()).toEqual('User Documents')
})
it('is labeled with "Buttons and Icons" when using the "list_buttons_and_icons" content type', async () => {
await showTrayForPlugin('list_buttons_and_icons')
expect(getTrayLabel()).toEqual('Buttons and Icons')
})
})
describe('Tray Label in group context', () => {
@ -165,6 +170,13 @@ describe('RCE Plugins > CanvasContentTray', () => {
)
})
it('is the images panel for button and icons content types', async () => {
await showTrayForPlugin('list_buttons_and_icons')
await waitFor(() =>
expect(component.getByTestId('instructure_links-ImagesPanel')).toBeInTheDocument()
)
})
it('is the media panel for media content types', async () => {
await showTrayForPlugin('course_media')
await waitFor(() =>

View File

@ -230,6 +230,28 @@ describe('RCE Plugins > Filter', () => {
selectContentSubtype('Media')
expect(currentFilterSettings.sortValue).toEqual('date_added')
})
describe('when "Buttons and Icons" is selected', () => {
beforeEach(() => {
selectContentSubtype('Buttons and Icons')
})
it('sets the content subtype to "buttons_and_icons"', () => {
expect(currentFilterSettings.contentSubtype).toEqual('buttons_and_icons')
})
it('sets the content type to "course_files"', () => {
expect(currentFilterSettings.contentType).toEqual('course_files')
})
it('does not render "User Files" content type', () => {
expect(component.queryByTitle('User Files')).toBeNull()
})
it('renders the "Course Files" content type', () => {
expect(component.getByTitle('Course Files')).toBeInTheDocument()
})
})
})
describe('deals with switching to and from Links', () => {

View File

@ -34,7 +34,8 @@ export function propsFromState(state) {
newPageLinkExpanded,
all_files,
jwt,
host
host,
source
} = state
const collections = {}
@ -61,6 +62,7 @@ export function propsFromState(state) {
...ui,
all_files,
jwt,
host
host,
source
}
}

View File

@ -264,6 +264,10 @@ class RceApiSource {
if (!bookmark) {
const perPageQuery = props.perPage ? `per_page=${props.perPage}` : ''
uri = `${props.filesUrl}?${perPageQuery}${getSearchParam(props.searchString)}`
if (props.sortBy) {
uri += `${getSortParams(props.sortBy.sort, props.sortBy.order)}`
}
}
return this.fetchPage(uri || bookmark, this.jwt)
@ -274,11 +278,44 @@ class RceApiSource {
return this.apiFetch(uri, headerFor(this.jwt))
}
fetchButtonsAndIconsFolder(props) {
const uri = this.uriFor('folders/buttons_and_icons', props)
fetchButtonsAndIconsFolder({contextId, contextType}) {
const uri = this.uriFor('folders/buttons_and_icons', {
contextId,
contextType,
host: this.host,
jwt: this.jwt
})
return this.fetchPage(uri)
}
fetchButtonsAndIcons(
{contextId, contextType},
bookmark = null,
searchString = null,
sortBy,
onSuccess
) {
const onSuccessWithFixedFileData = data => {
onSuccess({
...data,
files: data.files.map(file => fixupFileUrl(contextType, contextId, file))
})
}
if (bookmark) {
this.fetchFilesForFolder(null, bookmark).then(onSuccessWithFixedFileData)
} else {
this.fetchButtonsAndIconsFolder({contextId, contextType}).then(({folders}) => {
this.fetchFilesForFolder({
filesUrl: folders[0].filesUrl,
perPage: 25,
searchString,
sortBy
}).then(onSuccessWithFixedFileData)
})
}
}
fetchMediaFolder(props) {
let uri
if (props.contextType === 'user') {

View File

@ -443,8 +443,7 @@ const UNSPLASH_RESULTS = {
results: [
{
urls: {
link:
'https://images.unsplash.com/photo-1479065476818-424362c3a854?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ',
link: 'https://images.unsplash.com/photo-1479065476818-424362c3a854?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ',
thumbnail:
'https://images.unsplash.com/photo-1479065476818-424362c3a854?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ'
},
@ -458,8 +457,7 @@ const UNSPLASH_RESULTS = {
},
{
urls: {
link:
'https://images.unsplash.com/photo-1484733544471-3abf5846a4ff?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ',
link: 'https://images.unsplash.com/photo-1484733544471-3abf5846a4ff?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ',
thumbnail:
'https://images.unsplash.com/photo-1484733544471-3abf5846a4ff?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ'
},
@ -479,8 +477,7 @@ const UNSPLASH_RESULTS = {
results: [
{
urls: {
link:
'https://images.unsplash.com/photo-1554530700-747e22bb4e56?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ',
link: 'https://images.unsplash.com/photo-1554530700-747e22bb4e56?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ',
thumbnail:
'https://images.unsplash.com/photo-1554530700-747e22bb4e56?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ'
},
@ -494,8 +491,7 @@ const UNSPLASH_RESULTS = {
},
{
urls: {
link:
'https://images.unsplash.com/photo-1548878811-cddbc06f9d4c?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ',
link: 'https://images.unsplash.com/photo-1548878811-cddbc06f9d4c?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ',
thumbnail:
'https://images.unsplash.com/photo-1548878811-cddbc06f9d4c?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ'
},
@ -515,8 +511,7 @@ const UNSPLASH_RESULTS = {
results: [
{
urls: {
link:
'https://images.unsplash.com/photo-1479065476818-424362c3a854?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ',
link: 'https://images.unsplash.com/photo-1479065476818-424362c3a854?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ',
thumbnail:
'https://images.unsplash.com/photo-1479065476818-424362c3a854?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ'
},
@ -530,8 +525,7 @@ const UNSPLASH_RESULTS = {
},
{
urls: {
link:
'https://images.unsplash.com/photo-1484733544471-3abf5846a4ff?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ',
link: 'https://images.unsplash.com/photo-1484733544471-3abf5846a4ff?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ',
thumbnail:
'https://images.unsplash.com/photo-1484733544471-3abf5846a4ff?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ'
},
@ -545,8 +539,7 @@ const UNSPLASH_RESULTS = {
},
{
urls: {
link:
'https://images.unsplash.com/photo-1554530700-747e22bb4e56?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ',
link: 'https://images.unsplash.com/photo-1554530700-747e22bb4e56?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ',
thumbnail:
'https://images.unsplash.com/photo-1554530700-747e22bb4e56?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ'
},
@ -560,8 +553,7 @@ const UNSPLASH_RESULTS = {
},
{
urls: {
link:
'https://images.unsplash.com/photo-1548878811-cddbc06f9d4c?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ',
link: 'https://images.unsplash.com/photo-1548878811-cddbc06f9d4c?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ',
thumbnail:
'https://images.unsplash.com/photo-1548878811-cddbc06f9d4c?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ'
},
@ -575,8 +567,7 @@ const UNSPLASH_RESULTS = {
},
{
urls: {
link:
'https://images.unsplash.com/photo-1554146445-58ae3448247d?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ',
link: 'https://images.unsplash.com/photo-1554146445-58ae3448247d?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ',
thumbnail:
'https://images.unsplash.com/photo-1554146445-58ae3448247d?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ'
},
@ -590,8 +581,7 @@ const UNSPLASH_RESULTS = {
},
{
urls: {
link:
'https://images.unsplash.com/photo-1547239246-d2f052bdccf8?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ',
link: 'https://images.unsplash.com/photo-1547239246-d2f052bdccf8?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ',
thumbnail:
'https://images.unsplash.com/photo-1547239246-d2f052bdccf8?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ'
},
@ -605,8 +595,7 @@ const UNSPLASH_RESULTS = {
},
{
urls: {
link:
'https://images.unsplash.com/photo-1520811607976-6d7812b0ecac?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ',
link: 'https://images.unsplash.com/photo-1520811607976-6d7812b0ecac?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ',
thumbnail:
'https://images.unsplash.com/photo-1520811607976-6d7812b0ecac?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ'
},
@ -620,8 +609,7 @@ const UNSPLASH_RESULTS = {
},
{
urls: {
link:
'https://images.unsplash.com/photo-1532386236358-a33d8a9434e3?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ',
link: 'https://images.unsplash.com/photo-1532386236358-a33d8a9434e3?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ',
thumbnail:
'https://images.unsplash.com/photo-1532386236358-a33d8a9434e3?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ'
},
@ -635,8 +623,7 @@ const UNSPLASH_RESULTS = {
},
{
urls: {
link:
'https://images.unsplash.com/photo-1554181192-3ebfd857cc40?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ',
link: 'https://images.unsplash.com/photo-1554181192-3ebfd857cc40?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ',
thumbnail:
'https://images.unsplash.com/photo-1554181192-3ebfd857cc40?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ'
},
@ -650,8 +637,7 @@ const UNSPLASH_RESULTS = {
},
{
urls: {
link:
'https://images.unsplash.com/photo-1557369560-a25f3fad22cf?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ',
link: 'https://images.unsplash.com/photo-1557369560-a25f3fad22cf?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ',
thumbnail:
'https://images.unsplash.com/photo-1557369560-a25f3fad22cf?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&ixid=eyJhcHBfaWQiOjY4MTA0fQ'
},
@ -749,6 +735,8 @@ export function initializeMedia(_props) {
}
}
export function fetchButtonsAndIcons() {}
export function fetchFolders() {
return new Promise(resolve => {
setTimeout(() => {

View File

@ -411,6 +411,110 @@ describe('sources/api', () => {
})
})
describe('fetchButtonsAndIcons', () => {
const props = {contextId: '1', contextType: 'course'}
beforeEach(() => {
const fetchPageResponseBody = {
bookmark: 'http://example.com/?p=3',
files: [
{
id: '101',
name: 'button.svg',
thumbnailUrl: '/files/1/download',
type: 'image/svg+xml',
url: '/files/1/download/'
}
]
}
sinon.stub(apiSource, 'fetchPage').returns(Promise.resolve(fetchPageResponseBody))
})
afterEach(() => {
apiSource.fetchPage.restore()
})
describe('when a bookmark is present', () => {
it('fetches with the bookmark', () => {
apiSource.fetchButtonsAndIcons(props, 'http://example.com/?p=2', () => {})
sinon.assert.calledWith(apiSource.fetchPage, 'http://example.com/?p=2')
})
it('calls the onSuccess arg with the returned bookmark', () => {
const onSuccess = ({bookmark}) => {
assert.strictEqual(bookmark, fetchPageResponseBody.bookmark)
}
apiSource.fetchButtonsAndIcons(props, 'http://example.com/?p=2', onSuccess)
})
it('calls the onSuccess arg with the returned files', () => {
const onSuccess = ({files}) => {
assert.deepEqual(files, [
{
id: '101',
name: 'button.svg',
thumbnailUrl: '/files/1/download',
type: 'image/svg+xml',
url: '/courses/1/files/1?wrap=1' // url is normalized to include the context
}
])
}
apiSource.fetchButtonsAndIcons(props, 'http://example.com/?p=2', onSuccess)
})
})
describe('when a bookmark is not present', () => {
const filesUrl = 'http://example.com'
const folderResponseBody = {
bookmark: 'http://example.com/?p=2',
folders: [{filesUrl, id: 24}]
}
let fetchButtonsAndIconsFolderPromise
beforeEach(() => {
fetchButtonsAndIconsFolderPromise = Promise.resolve(folderResponseBody)
sinon.stub(apiSource, 'fetchButtonsAndIconsFolder').returns(promise)
})
afterEach(() => {
apiSource.fetchButtonsAndIconsFolder.restore()
})
it('fetches the buttons and icons folder, then fetches the files within that folder', async () => {
apiSource.fetchButtonsAndIcons(props, null, () => {})
await fetchButtonsAndIconsFolderPromise
sinon.assert.calledWith(apiSource.fetchPage, `${filesUrl}?per_page=25`)
})
it('calls the onSuccess arg with the returned bookmark', () => {
const onSuccess = ({bookmark}) => {
assert.strictEqual(bookmark, folderResponseBody.bookmark)
}
apiSource.fetchButtonsAndIcons(props, null, onSuccess)
})
it('calls the onSuccess arg with the returned files', () => {
const onSuccess = ({files}) => {
assert.deepEqual(files, [
{
id: '101',
name: 'button.svg',
thumbnailUrl: '/files/1/download',
type: 'image/svg+xml',
url: '/courses/1/files/1?wrap=1' // url is normalized to include the context
}
])
}
apiSource.fetchButtonsAndIcons(props, null, onSuccess)
})
})
})
describe('fetchMediaFolder', () => {
let files
beforeEach(() => {