Add LTI tool modal

closes COREFE-253

test plan:
  - in a class with some LTI tools enabled,
    open a page with the rce
  - click on the external tools toolbar button
  > expect the Select App modal to open
    > expect the tools to be listed aphabetically
    > expect the icon, tool title, and first line
      of the tools's description displayed
    > expect that clicking on the description expands so
      you can read it all
    > expect the 'x' and Cancel buttons to close the modal
  - click on an app
  > expect the modal to close and the app's modal to open
  - select content from the app
  > expect it to be added to the rce's content
  > expect that you can open the modal and do it all again

  - in a course with no installed apps
  > expect the external tools toolbar button not to appear in the rce

Change-Id: I6d798d08c4449f1c1e3a373057edb89af236a81e
Reviewed-on: https://gerrit.instructure.com/208422
Tested-by: Jenkins
Reviewed-by: Ryan Shaw <ryan@instructure.com>
Product-Review: Ryan Shaw <ryan@instructure.com>
QA-Review: Jeremy Putnam <jeremyp@instructure.com>
This commit is contained in:
Ed Schiebel 2019-09-05 20:00:20 -04:00
parent 4133f48733
commit 5794e7c0d8
19 changed files with 644 additions and 23 deletions

View File

@ -54,7 +54,8 @@ export default class ExternalToolDialog extends React.Component {
selection: PropTypes.shape({
getContent: PropTypes.func.isRequired
}),
getContent: PropTypes.func.isRequired
getContent: PropTypes.func.isRequired,
focus: PropTypes.func.isRequired
}).isRequired,
contextAssetString: PropTypes.string.isRequired,
iframeAllowances: PropTypes.string.isRequired,
@ -140,6 +141,7 @@ export default class ExternalToolDialog extends React.Component {
handleRemove = () => {
this.setState({button: EMPTY_BUTTON})
this.props.editor.focus()
}
handleInfoAlertFocus = ev => this.setState({infoAlert: ev.target})

View File

@ -68,7 +68,8 @@ function fakeEditor() {
selection: {
getContent: jest.fn()
},
getContent: jest.fn()
getContent: jest.fn(),
focus: () => {}
}
}

View File

@ -873,6 +873,7 @@ end
def self.editor_button_json(tools, context, user, session=nil)
tools.select! {|tool| visible?(tool.editor_button['visibility'], user, context, session)}
markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML.new({link_attributes: {target: '_blank'}}))
tools.map do |tool|
{
:name => tool.label_for(:editor_button, I18n.locale),
@ -882,7 +883,12 @@ end
:canvas_icon_class => tool.editor_button(:canvas_icon_class),
:width => tool.editor_button(:selection_width),
:height => tool.editor_button(:selection_height),
:use_tray => tool.editor_button(:use_tray) == "true"
:use_tray => tool.editor_button(:use_tray) == "true",
:description => if tool.description
Sanitize.clean(markdown.render(tool.description), CanvasSanitize::SANITIZE)
else
""
end
}
end
end

View File

@ -43,6 +43,7 @@ services:
environment:
<<: *BASE-ENV
VIRTUAL_HOST: .canvas.docker
HTTPS_METHOD: noredirect
postgres:
volumes:

View File

@ -9,7 +9,7 @@
"integration-test": "nightwatch --env integration",
"lint": "eslint \"src/**/*.js\" \"test/**/*.js\"",
"lint:fix": "eslint --fix \"src/**/*.js\" \"test/**/*.js\"",
"test": "Test cafe will be added back to test as part of CORE-2995",
"_test": "Test cafe will be added back to test as part of CORE-2995",
"test": "yarn test:mocha && yarn test:jest",
"test:mocha": "BABEL_ENV=test-node mocha 'test/**/*.test.js' --require @instructure/canvas-theme --require @babel/register --timeout 5000 --reporter mocha-multi-reporters --reporter-options configFile=mocha-reporter-config.json",
"test:mocha:one": "BABEL_ENV=test-node mocha --require @instructure/canvas-theme --require @babel/register --timeout 5000 --reporter mocha-multi-reporters --reporter-options configFile=mocha-reporter-config.json",

View File

@ -0,0 +1,45 @@
/*
* Copyright (C) 2019 - 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 ReactDOM from 'react-dom'
export default function clickCallback(ed, ltiButtons) {
return import('./components/LtiToolsModal').then(({LtiToolsModal}) => {
let container = document.querySelector('.canvas-rce-upload-container')
if (!container) {
container = document.createElement('div')
container.className = 'canvas-rce-upload-container'
document.body.appendChild(container)
}
const handleDismiss = () => {
ReactDOM.unmountComponentAtNode(container)
ed.focus()
}
ReactDOM.render(
<LtiToolsModal
editor={ed}
onDismiss={handleDismiss}
ltiButtons={ltiButtons}
/>, container
)
})
}

View File

@ -0,0 +1,101 @@
/*
* Copyright (C) 2019 - 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, {useState} from 'react'
import {string} from 'prop-types'
import { StyleSheet, css } from "aphrodite";
import {Text} from '@instructure/ui-elements'
import {View} from '@instructure/ui-layout'
import {IconArrowOpenDownLine, IconArrowOpenEndLine} from '@instructure/ui-icons'
export default function ExpandoText(props) {
const [descExpanded, setDescExpanded] = useState(false)
const [focused, setFocused] = useState(false)
const {text} = props
return (
<View
as="button"
className={css(styles.toggleButton)}
type="button"
position="relative"
aria-expanded={descExpanded}
focused={focused}
onClick={(event) => {
if(event.target.tagName !== 'A' || event.target.tagName !== 'BUTTON') {
// let the user click on links and buttons
setDescExpanded(!descExpanded)
}
}}
onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)}
>
<span style={{display: 'flex', alignItems: 'start'}}>
<span style={{marginRight: '.25rem', display: 'inline-block'}}>
<Text color="secondary">
{descExpanded ? <IconArrowOpenDownLine/> : <IconArrowOpenEndLine/>}
</Text>
</span>
<span style={{flexGrow: '1', minWidth: '10rem'}}>
<Text as="span" color="secondary">
<div
className={css(styles.descriptionText, descExpanded ? null : styles.overflow)}
dangerouslySetInnerHTML={{__html: text}}
/>
</Text>
</span>
</span>
</View>
)
}
ExpandoText.propTypes = {
text: string.isRequired
}
export const styles = StyleSheet.create({
toggleButton: {
background: 'transparent',
borderStyle: 'none',
display: 'block',
padding: '.25rem',
textAlign: 'start',
maxWidth: '100%'
},
descriptionText: {
overflow: 'hidden',
lineHeight: '1.2rem',
p: {
margin: '1rem 0'
},
':nth-child(1n)> :first-child': {
marginTop: '0',
display: 'inline-block'
},
':nth-child(1n)> :last-child': {
marginBottom: '0'
}
},
overflow: {
overflow: 'hidden',
whiteSpace: 'nowrap',
height: '1.2rem',
textOverflow: 'ellipsis'
}
});

View File

@ -0,0 +1,71 @@
/*
* Copyright (C) 2019 - 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, {useState} from 'react'
import {func, string} from 'prop-types'
import {Text} from '@instructure/ui-elements'
import {View} from '@instructure/ui-layout'
import ExpandoText from './ExpandoText'
export default function LtiTool(props) {
const [focused, setFocused] = useState(false)
const {title, image, description, onAction} = props
return (
<>
<View
as="div"
focused={focused}
role="button"
position="relative"
margin="none none small"
onClick={() => {
onAction()
}}
onKeyDown={(e) => {
if (e.keyCode === 13 || e.keyCode === 32) {
onAction()
}
}}
onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)}
tabIndex="0"
>
<span style={{marginRight: '.5rem'}}><img src={image} alt="" /></span>
<Text weight="bold">{title}</Text>
</View>
{description && renderDescription(description)}
</>
)
function renderDescription(desc) {
return (
<div style={{margin: '0 1.5rem', position: 'relative', boxSizing: 'content-box'}}>
<ExpandoText text={desc}/>
</div>
)
}
}
LtiTool.propTypes = {
title: String.isRequired,
image: string.isRequired,
onAction: func.isRequired,
description: string
}

View File

@ -0,0 +1,59 @@
/*
* Copyright (C) 2019 - 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, fireEvent} from '@testing-library/react'
import ExpandoText from '../ExpandoText'
describe('RCE Plugins > LtiTool', () => {
function renderComponent(text) {
return render(
<div style={{width: '10rem'}}>
<ExpandoText text={text} />)
</div>
)
}
it('renters right-arrow when first rendered', () => {
const {container} = renderComponent('hello world')
expect(container.querySelector('svg[name="IconArrowOpenEnd"]')).toBeInTheDocument()
})
it('renders the text', () => {
const {getByText} = renderComponent('hello world')
expect(getByText("hello world")).toBeInTheDocument()
})
it('renders the down-arrow when expanded', () => {
const {container} = renderComponent('hello world')
const arrowButton = container.querySelector('svg[name="IconArrowOpenEnd"]')
fireEvent.click(arrowButton)
expect(container.querySelector('svg[name="IconArrowOpenDown"]')).toBeInTheDocument()
})
it('renders the right-arrow when collapsed', () => {
const {container} = renderComponent('hello world')
fireEvent.click(container.querySelector('svg[name="IconArrowOpenEnd"]'))
const downButton = container.querySelector('svg[name="IconArrowOpenDown"]')
expect(downButton).toBeInTheDocument()
fireEvent.click(downButton)
expect(container.querySelector('svg[name="IconArrowOpenEnd"]')).toBeInTheDocument()
})
})

View File

@ -0,0 +1,56 @@
/*
* Copyright (C) 2019 - 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} from '@testing-library/react'
import LtiTool from '../LtiTool'
describe('RCE Plugins > LtiTool', () => {
function getProps(override={}) {
const props = {
title: "Tool 1",
id: 1,
description: "This is tool 1.",
image: "tool1/icon.png",
onAction: () => {},
...override
}
return props
}
function renderComponent(toolprops) {
return render(<LtiTool {...getProps(toolprops)} />)
}
it('renters the tool title', () => {
const {getByText} = renderComponent()
expect(getByText("Tool 1")).toBeInTheDocument()
})
it('renters the tool image', () => {
const {container} = renderComponent()
expect(container.querySelector('img[src="tool1/icon.png"]')).toBeInTheDocument()
})
it('renders the tool description', () => {
const {getByText} = renderComponent()
expect(getByText("This is tool 1.")).toBeInTheDocument()
})
})

View File

@ -0,0 +1,117 @@
/*
* Copyright (C) 2019 - 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} from '@testing-library/react'
import {LtiToolsModal} from '../index'
describe('RCE Plugins > LtiToolModal', () => {
function getProps(override={}) {
const props = {
onDismiss: () => {},
ltiButtons: [
{
title: "Tool 1",
id: 1,
description: "This is tool 1.",
image: "tool1/icon.png",
onAction: () => {}
},
{
title: "Tool 2",
id: 2,
description: "This is tool 2",
image: "/tool2/image.png",
onAction: () => {}
},
{
title: "Tool 3",
id: 3,
image: "https://www.edu-apps.org/assets/lti_public_resources/tool3.png",
onAction: () => {}
}
],
...override
}
return props
}
function renderComponent(modalprops) {
return render(<LtiToolsModal {...getProps(modalprops)} />)
}
it('is labeled "Select App"', () => {
const {getByLabelText} = renderComponent()
expect(getByLabelText("LTI Tools")).toBeInTheDocument()
})
it('has heading "Select App"', () => {
const {getByText} = renderComponent()
expect(getByText("Select App")).toBeInTheDocument()
})
it('shows the 3 tools', () => {
const {baseElement, getByText} = renderComponent()
expect(getByText('Tool 1')).toBeInTheDocument()
expect(getByText('This is tool 1.')).toBeInTheDocument()
expect(baseElement.querySelector('img[src="tool1/icon.png"]')).toBeInTheDocument()
expect(getByText('Tool 2')).toBeInTheDocument()
expect(getByText('This is tool 2')).toBeInTheDocument()
expect(baseElement.querySelector('img[src="/tool2/image.png"]')).toBeInTheDocument()
expect(getByText('Tool 3')).toBeInTheDocument()
expect(baseElement.querySelector('img[src="https://www.edu-apps.org/assets/lti_public_resources/tool3.png"]')).toBeInTheDocument()
})
it('calls onDismiss when clicking Cancel', () => {
const handleDismiss = jest.fn()
const {getByText} = renderComponent({onDismiss: handleDismiss})
const cancelButton = getByText('Cancel')
cancelButton.click()
expect(handleDismiss).toHaveBeenCalled()
})
it('calls onDismiss when clicking the close button', () => {
const handleDismiss = jest.fn()
const {getByText} = renderComponent({onDismiss: handleDismiss})
const closeButton = getByText('Close')
closeButton.click()
expect(handleDismiss).toHaveBeenCalled()
})
it('calls onAction when clicking a tool', () => {
const doAction = jest.fn()
const onDismiss = jest.fn()
const {getByText} = renderComponent({
onDismiss,
ltiButtons: [
{
title: "Tool 1",
id: 1,
description: "This is tool 1.",
image: "tool1/icon.png",
onAction: doAction
}
]})
const tool1 = getByText('Tool 1')
tool1.click()
expect(doAction).toHaveBeenCalled()
expect(onDismiss).toHaveBeenCalled()
})
})

View File

@ -0,0 +1,97 @@
/*
* Copyright (C) 2019 - 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 {func, arrayOf, oneOfType, number, shape, string} from 'prop-types'
import {Modal} from '@instructure/ui-overlays'
import {Button, CloseButton} from '@instructure/ui-buttons'
import {Heading, List} from '@instructure/ui-elements'
import {View} from '@instructure/ui-layout'
import formatMessage from '../../../../../format-message'
import LtiTool from './LtiTool'
// TODO: we really need a way for the client to pass this to the RCE
const getLiveRegion=() => document.getElementById('flash_screenreader_holder')
export function LtiToolsModal(props) {
return (
<Modal
data-mce-component
liveRegion={getLiveRegion}
size="medium"
label={formatMessage('LTI Tools')}
onDismiss={props.onDismiss}
open
shouldCloseOnDocumentClick
>
<Modal.Header>
<CloseButton placement="end" offset="medium" onClick={props.onDismiss}>
{formatMessage('Close')}
</CloseButton>
<Heading>{formatMessage('Select App')}</Heading>
</Modal.Header>
<Modal.Body>
{renderTools(props.ltiButtons)}
</Modal.Body>
<Modal.Footer>
<Button onClick={props.onDismiss}>{formatMessage('Cancel')}</Button>
</Modal.Footer>
</Modal>
)
function renderTools(ltiButtons) {
return (
<List variant="unstyled">
{ltiButtons.sort((a, b) => a.title.localeCompare(b.title)).map((b, i) => {
return (
<List.Item key={b.id}>
<View
as="div"
borderWidth={i === 0 ? "small none" : "none none small none"}
padding="medium"
>
<LtiTool
title={b.title}
image={b.image}
onAction={() => {
b.onAction()
props.onDismiss()
}}
description={b.description}
/>
</View>
</List.Item>
)
})}
</List>
)
}
}
LtiToolsModal.propTypes = {
ltiButtons: arrayOf(shape({
description: string.isRequired,
id: oneOfType([string, number]).isRequired,
image: string.isRequired,
onAction: func.isRequired,
title: string.isRequired
})),
onDismiss: func.isRequired
}

View File

@ -18,10 +18,14 @@
import dispatchInitEvent from "./dispatchInitEvent";
import {IconLtiLine} from '@instructure/ui-icons/es/svg'
import clickCallback from './clickCallback'
tinymce.create("tinymce.plugins.InstructureExternalTools", {
init(ed, url) {
document.addEventListener('tinyRCE/onExternalTools', (event) => {
clickCallback(ed, event.detail.ltiButtons)
})
ed.ui.registry.addIcon('lti', IconLtiLine.src)
dispatchInitEvent(ed, document, url);
},

View File

@ -54,9 +54,9 @@ export default {
classes: 'widget btn instructure_external_tool_button'
}
if (ENV.use_rce_enhancements) {
config.text = config.title
config.id = button.id
config.onAction = () => editor.execCommand(`instructureExternalButton${button.id}`)
config.type = 'menuitem'
config.description = button.description
} else {
config.cmd = `instructureExternalButton${button.id}`
}

View File

@ -82,9 +82,10 @@ const ExternalToolsPlugin = {
}
}
if (ltiButtons.length && ENV.use_rce_enhancements) {
ed.ui.registry.addMenuButton('lti_tool_dropdown', {
fetch(callback) {
callback(ltiButtons)
ed.ui.registry.addButton('lti_tool_dropdown', {
onAction: () => {
const ev = new CustomEvent('tinyRCE/onExternalTools', {detail: {ltiButtons}})
document.dispatchEvent(ev)
},
icon: 'lti',
tooltip: 'LTI Tools'

View File

@ -516,12 +516,28 @@ describe ApplicationHelper do
it "should return hash of tools if in group" do
@course = course_model
@group = @course.groups.create!(:name => "some group")
tool = @course.context_external_tools.new(:name => "bob", :consumer_key => "test", :shared_secret => "secret", :url => "http://example.com")
tool = @course.context_external_tools.new(
:name => "bob",
:consumer_key => "test",
:shared_secret => "secret",
:url => "http://example.com",
:description => "the description."
)
tool.editor_button = {:url => "http://example.com", :icon_url => "http://example.com", :canvas_icon_class => 'icon-commons'}
tool.save!
@context = @group
expect(editor_buttons).to eq([{:name=>"bob", :id=>tool.id, :url=>"http://example.com", :icon_url=>"http://example.com", :canvas_icon_class => 'icon-commons', :width=>800, :height=>400, :use_tray => false}])
expect(editor_buttons).to eq([{
:name=>"bob",
:id=>tool.id,
:url=>"http://example.com",
:icon_url=>"http://example.com",
:canvas_icon_class => 'icon-commons',
:width=>800,
:height=>400,
:use_tray => false,
:description => "<p>the description.</p>\n"
}])
end
it "should return hash of tools if in course" do
@ -532,7 +548,17 @@ describe ApplicationHelper do
allow(controller).to receive(:group_external_tool_path).and_return('http://dummy')
@context = @course
expect(editor_buttons).to eq([{:name=>"bob", :id=>tool.id, :url=>"http://example.com", :icon_url=>"http://example.com", :canvas_icon_class => 'icon-commons', :width=>800, :height=>400, :use_tray => false}])
expect(editor_buttons).to eq([{
:name=>"bob",
:id=>tool.id,
:url=>"http://example.com",
:icon_url=>"http://example.com",
:canvas_icon_class => 'icon-commons',
:width=>800,
:height=>400,
:use_tray => false,
:description => ""
}])
end
it "should not include tools from the domain_root_account for users" do

View File

@ -1624,5 +1624,21 @@ describe ContextExternalTool do
json = ContextExternalTool.editor_button_json([tool], @course, user_with_pseudonym)
expect(json[0][:use_tray]).to eq true
end
describe 'includes the description' do
it 'parsed into HTML' do
tool.editor_button = {}
tool.description = "the first paragraph.\n\nthe second paragraph."
json = ContextExternalTool.editor_button_json([tool], @course, user_with_pseudonym)
expect(json[0][:description]).to eq "<p>the first paragraph.</p>\n\n<p>the second paragraph.</p>\n"
end
it 'with target="_blank" on links' do
tool.editor_button = {}
tool.description = "[link text](http://the.url)"
json = ContextExternalTool.editor_button_json([tool], @course, user_with_pseudonym)
expect(json[0][:description]).to eq "<p><a href=\"http://the.url\" target=\"_blank\">link text</a></p>\n"
end
end
end
end

View File

@ -146,6 +146,10 @@ module RCENextPage
possibly_hidden_toolbar_button('button[aria-label="LTI Tools"')
end
def lti_tools_modal
f('[role="dialog"][aria-label="LTI Tools"]')
end
def course_images
f('[role="menuitem"][title="Course Images"]')
end

View File

@ -573,8 +573,10 @@ describe "RCE next tests" do
driver.switch_to.default_content
expect(f('.tox-pop__dialog button[title="Show link options"]')).to eq(driver.switch_to.active_element)
end
end
it "should display lti icon with a tool enabled for the course" do
describe 'lti tool integration' do
before(:each) do
# set up the lti tool
@tool = Account.default.context_external_tools.new({
:name => "Commons",
@ -591,7 +593,9 @@ describe "RCE next tests" do
:use_tray => "true"
})
@tool.save!
end
it "should display lti icon with a tool enabled for the course" do
page_title = "Page1"
create_wiki_page_with_embedded_image(page_title)
@ -599,6 +603,16 @@ describe "RCE next tests" do
expect(lti_tools_button).to be_displayed
end
it "should display the lti tool modal" do
page_title = "Page1"
create_wiki_page_with_embedded_image(page_title)
visit_existing_wiki_edit(@course, page_title)
lti_tools_button.click
expect(lti_tools_modal).to be_displayed
end
end
end
end