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:
Carl Kibler 2019-09-17 10:22:36 -06:00
parent 6326cb31cd
commit a94b7ca12e
7 changed files with 181 additions and 114 deletions

View File

@ -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>

View File

@ -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})
}

View File

@ -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>
),

View File

@ -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)
})

View File

@ -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

View File

@ -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

View File

@ -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