update to newer InstUI Select component
closes ADMIN-2774 flag=none test plan: - In a Blueprint course, go to Associations in the sidebar - Verify term and subaccount select boxes work well and the course list updates after selection Change-Id: Ib55e8a01653fa8cd13aa7779ecf27bcd05861108 Reviewed-on: https://gerrit.instructure.com/212047 Tested-by: Jenkins Reviewed-by: Jon Willesen <jonw+gerrit@instructure.com> QA-Review: Jon Willesen <jonw+gerrit@instructure.com> Product-Review: Carl Kibler <ckibler@instructure.com>
This commit is contained in:
parent
6326cb31cd
commit
a94b7ca12e
|
@ -20,9 +20,9 @@ import I18n from 'i18n!blueprint_settingsCourseFilter'
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import {TextInput} from '@instructure/ui-forms'
|
||||
import Select from '@instructure/ui-core/lib/components/Select'
|
||||
import {ScreenReaderContent} from '@instructure/ui-a11y'
|
||||
import {Grid} from '@instructure/ui-layout'
|
||||
import CanvasSelect from 'jsx/shared/components/CanvasSelect'
|
||||
import propTypes from '../propTypes'
|
||||
|
||||
const { func } = PropTypes
|
||||
|
@ -61,9 +61,7 @@ export default class CourseFilter extends React.Component {
|
|||
|
||||
onChange = () => {
|
||||
this.setState({
|
||||
search: this.getSearchText(),
|
||||
term: this.termInput.value,
|
||||
subAccount: this.subAccountInput.value,
|
||||
search: this.getSearchText()
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -87,9 +85,7 @@ export default class CourseFilter extends React.Component {
|
|||
setTimeout(() => {
|
||||
if (this.state.isActive) {
|
||||
const search = this.searchInput.value
|
||||
const term = this.termInput.value
|
||||
const subAccount = this.subAccountInput.value
|
||||
const isEmpty = !search && !term && !subAccount
|
||||
const isEmpty = !search
|
||||
|
||||
if (isEmpty && !this.wrapper.contains(document.activeElement)) {
|
||||
this.setState({
|
||||
|
@ -101,6 +97,20 @@ export default class CourseFilter extends React.Component {
|
|||
}
|
||||
|
||||
render () {
|
||||
const termOptions = [
|
||||
<CanvasSelect.Option key="all" id="all" value="">{I18n.t('Any Term')}</CanvasSelect.Option>,
|
||||
...this.props.terms.map(term => (
|
||||
<CanvasSelect.Option key={term.id} id={term.id} value={term.id}>{term.name}</CanvasSelect.Option>
|
||||
))
|
||||
]
|
||||
|
||||
const subAccountOptions = [
|
||||
<CanvasSelect.Option key="all" id="all" value="">{I18n.t('Any Sub-Account')}</CanvasSelect.Option>,
|
||||
...this.props.subAccounts.map(account => (
|
||||
<CanvasSelect.Option key={account.id} id={account.id} value={account.id}>{account.name}</CanvasSelect.Option>
|
||||
))
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="bca-course-filter" ref={(c) => { this.wrapper = c }}>
|
||||
<Grid colSpacing="none">
|
||||
|
@ -119,38 +129,30 @@ export default class CourseFilter extends React.Component {
|
|||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col width={2}>
|
||||
<Select
|
||||
selectRef={(c) => { this.termInput = c }}
|
||||
<CanvasSelect
|
||||
id="termsFilter"
|
||||
key="terms"
|
||||
onChange={this.onChange}
|
||||
onFocus={this.handleFocus}
|
||||
onBlur={this.handleBlur}
|
||||
value={this.state.term}
|
||||
onChange={(e, value) => this.setState({term: value})}
|
||||
label={
|
||||
<ScreenReaderContent>{I18n.t('Select Term')}</ScreenReaderContent>
|
||||
}
|
||||
>
|
||||
<option key="all" value="">{I18n.t('Any Term')}</option>
|
||||
{this.props.terms.map(term => (
|
||||
<option key={term.id} value={term.id}>{term.name}</option>
|
||||
))}
|
||||
</Select>
|
||||
{termOptions}
|
||||
</CanvasSelect>
|
||||
</Grid.Col>
|
||||
<Grid.Col width={3}>
|
||||
<Select
|
||||
selectRef={(c) => { this.subAccountInput = c }}
|
||||
<CanvasSelect
|
||||
id="subAccountsFilter"
|
||||
key="subAccounts"
|
||||
onChange={this.onChange}
|
||||
onFocus={this.handleFocus}
|
||||
onBlur={this.handleBlur}
|
||||
value={this.state.subAccount}
|
||||
onChange={(e, value) => this.setState({subAccount: value})}
|
||||
label={
|
||||
<ScreenReaderContent>{I18n.t('Select Sub-Account')}</ScreenReaderContent>
|
||||
}
|
||||
>
|
||||
<option key="all" value="">{I18n.t('Any Sub-Account')}</option>
|
||||
{this.props.subAccounts.map(account => (
|
||||
<option key={account.id} value={account.id}>{account.name}</option>
|
||||
))}
|
||||
</Select>
|
||||
{subAccountOptions}
|
||||
</CanvasSelect>
|
||||
</Grid.Col>
|
||||
</Grid.Row>
|
||||
</Grid>
|
||||
|
|
|
@ -101,7 +101,7 @@ export default class CoursePicker extends React.Component {
|
|||
})
|
||||
}
|
||||
|
||||
// when user clicks "Courses" button to toggle visibliity
|
||||
// when user clicks "Courses" button to toggle visibility
|
||||
onToggleCoursePicker = (event, isExpanded) => {
|
||||
this.setState({isExpanded})
|
||||
}
|
||||
|
|
|
@ -113,7 +113,7 @@ export default class CourseSidebar extends Component {
|
|||
}),
|
||||
},
|
||||
children: (
|
||||
<Suspense fallback={<div>{I18n.t('Loading assotiations...')}</div>}>
|
||||
<Suspense fallback={<div>{I18n.t('Loading associations...')}</div>}>
|
||||
<BlueprintAssociations />
|
||||
</Suspense>
|
||||
),
|
||||
|
|
|
@ -17,95 +17,152 @@
|
|||
*/
|
||||
|
||||
import React from 'react'
|
||||
import {render} from '@testing-library/react'
|
||||
import * as enzyme from 'enzyme'
|
||||
import CourseFilter from 'jsx/blueprint_courses/components/CourseFilter'
|
||||
import getSampleData from '../getSampleData'
|
||||
|
||||
QUnit.module('CourseFilter component')
|
||||
|
||||
const defaultProps = () => ({
|
||||
subAccounts: getSampleData().subAccounts,
|
||||
terms: getSampleData().terms,
|
||||
terms: getSampleData().terms
|
||||
})
|
||||
let fixtures
|
||||
|
||||
test('renders the CourseFilter component', () => {
|
||||
const tree = enzyme.shallow(<CourseFilter {...defaultProps()} />)
|
||||
const node = tree.find('.bca-course-filter')
|
||||
ok(node.exists())
|
||||
})
|
||||
QUnit.module('CourseFilter', hooks => {
|
||||
hooks.beforeEach(() => {
|
||||
fixtures = document.getElementById('fixtures')
|
||||
fixtures.innerHTML = '<div id="flash_screenreader_holder" role="alert"></div>'
|
||||
})
|
||||
|
||||
test('onChange fires with search filter when text is entered in search box', (assert) => {
|
||||
const done = assert.async()
|
||||
const props = defaultProps()
|
||||
props.onChange = (filter) => {
|
||||
equal(filter.search, 'giraffe')
|
||||
done()
|
||||
}
|
||||
const tree = enzyme.mount(<CourseFilter {...props} />)
|
||||
const input = tree.find('TextInput input')
|
||||
input.instance().value = 'giraffe'
|
||||
input.simulate('change')
|
||||
})
|
||||
hooks.afterEach(() => {
|
||||
fixtures.innerHTML = ""
|
||||
})
|
||||
|
||||
test('onChange fires with term filter when term is selected', (assert) => {
|
||||
const done = assert.async()
|
||||
const props = defaultProps()
|
||||
props.onChange = (filter) => {
|
||||
equal(filter.term, '1')
|
||||
done()
|
||||
}
|
||||
const tree = enzyme.mount(<CourseFilter {...props} />)
|
||||
const input = tree.find('select').at(0)
|
||||
input.instance().value = '1'
|
||||
input.simulate('change')
|
||||
})
|
||||
test('renders the CourseFilter component', () => {
|
||||
const tree = enzyme.shallow(<CourseFilter {...defaultProps()} />)
|
||||
const node = tree.find('.bca-course-filter')
|
||||
ok(node.exists())
|
||||
})
|
||||
|
||||
test('onChange fires with subaccount filter when a subaccount is selected', (assert) => {
|
||||
const done = assert.async()
|
||||
const props = defaultProps()
|
||||
props.onChange = (filter) => {
|
||||
equal(filter.subAccount, '1')
|
||||
done()
|
||||
}
|
||||
const tree = enzyme.mount(<CourseFilter {...props} />)
|
||||
const input = tree.find('select').at(1)
|
||||
input.instance().value = '1'
|
||||
input.simulate('change')
|
||||
})
|
||||
test('onChange fires with search filter when text is entered in search box', (assert) => {
|
||||
const done = assert.async()
|
||||
const props = defaultProps()
|
||||
props.onChange = (filter) => {
|
||||
equal(filter.search, 'giraffe')
|
||||
done()
|
||||
}
|
||||
const tree = enzyme.mount(<CourseFilter {...props} />)
|
||||
const input = tree.find('input[type="search"]')
|
||||
input.instance().value = 'giraffe'
|
||||
input.simulate('change')
|
||||
})
|
||||
|
||||
test('onActivate fires when filters are focussed', () => {
|
||||
const props = defaultProps()
|
||||
props.onActivate = sinon.spy()
|
||||
const tree = enzyme.mount(<CourseFilter {...props} />)
|
||||
const input = tree.find('TextInput input')
|
||||
input.simulate('focus')
|
||||
ok(props.onActivate.calledOnce)
|
||||
})
|
||||
test('onActivate fires when filters are focused', () => {
|
||||
const props = defaultProps()
|
||||
props.onActivate = sinon.spy()
|
||||
const tree = enzyme.mount(<CourseFilter {...props} />)
|
||||
const input = tree.find('input[type="search"]')
|
||||
input.simulate('focus')
|
||||
ok(props.onActivate.calledOnce)
|
||||
})
|
||||
|
||||
test('onChange not fired when < 3 chars are entered in search text input', (assert) => {
|
||||
const done = assert.async()
|
||||
const props = defaultProps()
|
||||
props.onChange = sinon.spy()
|
||||
const tree = enzyme.mount(<CourseFilter {...props} />)
|
||||
const input = tree.find('input[type="search"]')
|
||||
input.instance().value = 'aa'
|
||||
input.simulate('change')
|
||||
setTimeout(() => {
|
||||
equal(props.onChange.callCount, 0)
|
||||
done()
|
||||
}, 0)
|
||||
})
|
||||
|
||||
test('onChange fired when 3 chars are entered in search text input', (assert) => {
|
||||
const done = assert.async()
|
||||
const props = defaultProps()
|
||||
props.onChange = sinon.spy()
|
||||
const tree = enzyme.mount(<CourseFilter {...props} />)
|
||||
const input = tree.find('input[type="search"]')
|
||||
input.instance().value = 'aaa'
|
||||
input.simulate('change')
|
||||
setTimeout(() => {
|
||||
ok(props.onChange.calledOnce)
|
||||
done()
|
||||
}, 0)
|
||||
})
|
||||
|
||||
QUnit.module('CourseFilter > Filter behavior', suiteHooks => {
|
||||
let container
|
||||
let component
|
||||
let select
|
||||
|
||||
suiteHooks.afterEach(() => {
|
||||
component.unmount()
|
||||
container.remove()
|
||||
})
|
||||
|
||||
function renderComponent(props) {
|
||||
return render(<CourseFilter {...props} />, {container})
|
||||
}
|
||||
|
||||
function clickToExpand() {
|
||||
select.click()
|
||||
}
|
||||
|
||||
function getOptionsList() {
|
||||
const optionsListId = select.getAttribute('aria-controls')
|
||||
return document.getElementById(optionsListId)
|
||||
}
|
||||
|
||||
function getOption(optionLabel) {
|
||||
return getOptions().find($option => $option.textContent.trim() === optionLabel)
|
||||
}
|
||||
|
||||
function getOptions() {
|
||||
return [...getOptionsList().querySelectorAll('[role="option"]')]
|
||||
}
|
||||
|
||||
function getOptionLabels() {
|
||||
return getOptions().map(option => option.textContent.trim())
|
||||
}
|
||||
|
||||
function selectOption(optionLabel) {
|
||||
getOption(optionLabel).click()
|
||||
}
|
||||
|
||||
test('onChange fires with term filter when term is selected', (assert) => {
|
||||
const done = assert.async()
|
||||
const props = defaultProps()
|
||||
props.onChange = (filter) => {
|
||||
equal(filter.term, '1')
|
||||
done()
|
||||
}
|
||||
container = document.body.appendChild(document.createElement('div'))
|
||||
component = renderComponent(props)
|
||||
select = container.querySelectorAll('input[type="text"]')[0]
|
||||
|
||||
clickToExpand()
|
||||
selectOption('Term One')
|
||||
})
|
||||
|
||||
test('onChange fires with subaccount filter when a subaccount is selected', (assert) => {
|
||||
const done = assert.async()
|
||||
const props = defaultProps()
|
||||
props.onChange = (filter) => {
|
||||
equal(filter.subAccount, '2')
|
||||
done()
|
||||
}
|
||||
container = document.body.appendChild(document.createElement('div'))
|
||||
component = renderComponent(props)
|
||||
select = container.querySelectorAll('input[type="text"]')[1]
|
||||
|
||||
clickToExpand()
|
||||
selectOption('Account Two')
|
||||
})
|
||||
})
|
||||
|
||||
test('onChange not fired when < 3 chars are entered in search text input', (assert) => {
|
||||
const done = assert.async()
|
||||
const props = defaultProps()
|
||||
props.onChange = sinon.spy()
|
||||
const tree = enzyme.mount(<CourseFilter {...props} />)
|
||||
const input = tree.find('input[type="search"]')
|
||||
input.instance().value = 'aa'
|
||||
input.simulate('change')
|
||||
setTimeout(() => {
|
||||
equal(props.onChange.callCount, 0)
|
||||
done()
|
||||
}, 0)
|
||||
})
|
||||
|
||||
test('onChange fired when 3 chars are entered in search text input', (assert) => {
|
||||
const done = assert.async()
|
||||
const props = defaultProps()
|
||||
props.onChange = sinon.spy()
|
||||
const tree = enzyme.mount(<CourseFilter {...props} />)
|
||||
const input = tree.find('input[type="search"]')
|
||||
input.instance().value = 'aaa'
|
||||
input.simulate('change')
|
||||
setTimeout(() => {
|
||||
ok(props.onChange.calledOnce)
|
||||
done()
|
||||
}, 0)
|
||||
})
|
||||
|
|
|
@ -149,7 +149,15 @@ module BlueprintCourseCommon
|
|||
expect(details_wrapper).to contain_css('.bca-table__course-row')
|
||||
end
|
||||
|
||||
# reutrn the <tboey> holding the list of avaiable courses
|
||||
def term_options
|
||||
INSTUI_Select_options('#termsFilter').map(&:text)
|
||||
end
|
||||
|
||||
def sub_account_options
|
||||
INSTUI_Select_options('#subAccountsFilter').map(&:text)
|
||||
end
|
||||
|
||||
# return the <tbody> holding the list of available courses
|
||||
def available_courses_table
|
||||
f('.bca-table__content-wrapper tbody')
|
||||
end
|
||||
|
|
|
@ -79,9 +79,9 @@ describe "Blueprint association settings" do
|
|||
it "course search dropdowns are populated", priority: "2", test_id: 3072438 do
|
||||
open_associations
|
||||
open_courses_list
|
||||
select_boxes = ff('.bca-course-filter select')
|
||||
expect(select_boxes[0]).to include_text("Default Term")
|
||||
expect(select_boxes[1]).to include_text("sub account 0")
|
||||
|
||||
expect(term_options).to include 'Default Term'
|
||||
expect(sub_account_options).to include 'sub account 0'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -68,8 +68,8 @@ describe "master courses - course picker" do
|
|||
let(:course_search_input) {'.bca-course-filter input[type="search"]'}
|
||||
let(:filter_output) {'.bca-course-details__wrapper'}
|
||||
let(:loading) {'.bca-course-picker__loading'}
|
||||
let(:term_filter) {'.bca-course-filter select:contains("Any Term")'}
|
||||
let(:sub_account_filter) {'.bca-course-filter select:contains("Any Sub-Account")'}
|
||||
let(:term_filter) {'#termsFilter'}
|
||||
let(:sub_account_filter) {'#subAccountsFilter'}
|
||||
|
||||
def wait_for_spinner
|
||||
begin
|
||||
|
@ -130,7 +130,7 @@ describe "master courses - course picker" do
|
|||
get "/courses/#{@master.id}"
|
||||
open_associations
|
||||
open_courses_list
|
||||
click_option(term_filter, 'fall term')
|
||||
click_INSTUI_Select_option(term_filter, 'fall term')
|
||||
wait_for_spinner
|
||||
expect(available_courses().length).to eq(4)
|
||||
end
|
||||
|
@ -139,7 +139,7 @@ describe "master courses - course picker" do
|
|||
get "/courses/#{@master.id}"
|
||||
open_associations
|
||||
open_courses_list
|
||||
click_option(sub_account_filter, 'sub-account 1')
|
||||
click_INSTUI_Select_option(sub_account_filter, 'sub-account 1')
|
||||
wait_for_spinner
|
||||
expect(available_courses().length).to eq(1)
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue