bootstrap-vue
Version:
BootstrapVue, with over 40 plugins and more than 80 custom components, custom directives, and over 300 icons, provides one of the most comprehensive implementations of Bootstrap v4 components and grid system for Vue.js. With extensive and automated WAI-AR
625 lines (561 loc) • 21.3 kB
JavaScript
import { mount, createWrapper, createLocalVue as CreateLocalVue } from '@vue/test-utils'
import { waitNT, waitRAF } from '../../../tests/utils'
import { BCollapse } from './collapse'
// Events collapse emits on $root
const EVENT_STATE = 'bv::collapse::state'
const EVENT_ACCORDION = 'bv::collapse::accordion'
// Events collapse listens to on $root
const EVENT_TOGGLE = 'bv::toggle::collapse'
const EVENT_STATE_SYNC = 'bv::collapse::sync::state'
const EVENT_STATE_REQUEST = 'bv::request::collapse::state'
describe('collapse', () => {
const origGetBCR = Element.prototype.getBoundingClientRect
beforeEach(() => {
// Mock getBCR so that the we can get a fake height for element
// Needed for keyboard navigation testing
Element.prototype.getBoundingClientRect = jest.fn(() => ({
width: 100,
height: 100,
top: 0,
left: 0,
bottom: 0,
right: 0
}))
})
afterEach(() => {
// Reset overrides
Element.prototype.getBoundingClientRect = origGetBCR
})
it('should have expected default structure', async () => {
const wrapper = mount(BCollapse, {
attachToDocument: true,
propsData: {
// 'id' is a required prop
id: 'test'
},
stubs: {
// Disable use of default test transitionStub component
transition: false
}
})
// const rootWrapper = createWrapper(wrapper.vm.$root)
expect(wrapper.isVueInstance()).toBe(true)
await waitNT(wrapper.vm)
await waitRAF()
expect(wrapper.is('div')).toBe(true)
expect(wrapper.attributes('id')).toBeDefined()
expect(wrapper.attributes('id')).toEqual('test')
expect(wrapper.classes()).toContain('collapse')
expect(wrapper.classes()).not.toContain('navbar-collapse')
expect(wrapper.classes()).not.toContain('show')
expect(wrapper.element.style.display).toEqual('none')
expect(wrapper.text()).toEqual('')
wrapper.destroy()
})
it('should have expected structure when prop is-nav is set', async () => {
const wrapper = mount(BCollapse, {
attachToDocument: true,
propsData: {
// 'id' is a required prop
id: 'test',
isNav: true
},
stubs: {
// Disable use of default test transitionStub component
transition: false
}
})
// const rootWrapper = createWrapper(wrapper.vm.$root)
expect(wrapper.isVueInstance()).toBe(true)
await waitNT(wrapper.vm)
await waitRAF()
expect(wrapper.is('div')).toBe(true)
expect(wrapper.attributes('id')).toBeDefined()
expect(wrapper.attributes('id')).toEqual('test')
expect(wrapper.classes()).toContain('collapse')
expect(wrapper.classes()).toContain('navbar-collapse')
expect(wrapper.classes()).not.toContain('show')
expect(wrapper.element.style.display).toEqual('none')
expect(wrapper.text()).toEqual('')
wrapper.destroy()
})
it('renders default slot content', async () => {
const wrapper = mount(BCollapse, {
attachToDocument: true,
propsData: {
// 'id' is a required prop
id: 'test'
},
slots: {
default: '<div>foobar</div>'
},
stubs: {
// Disable use of default test transitionStub component
transition: false
}
})
expect(wrapper.isVueInstance()).toBe(true)
await waitNT(wrapper.vm)
await waitRAF()
expect(wrapper.is('div')).toBe(true)
expect(wrapper.attributes('id')).toBeDefined()
expect(wrapper.attributes('id')).toEqual('test')
expect(wrapper.classes()).toContain('collapse')
expect(wrapper.classes()).not.toContain('show')
expect(wrapper.element.style.display).toEqual('none')
expect(wrapper.find('div > div').exists()).toBe(true)
expect(wrapper.text()).toEqual('foobar')
wrapper.destroy()
})
it('should mount as visible when prop visible is true', async () => {
const wrapper = mount(BCollapse, {
attachToDocument: true,
propsData: {
// 'id' is a required prop
id: 'test',
visible: true
},
slots: {
default: '<div>foobar</div>'
},
stubs: {
// Disable use of default test transitionStub component
transition: false
}
})
expect(wrapper.isVueInstance()).toBe(true)
await waitNT(wrapper.vm)
await waitRAF()
expect(wrapper.is('div')).toBe(true)
expect(wrapper.attributes('id')).toBeDefined()
expect(wrapper.attributes('id')).toEqual('test')
expect(wrapper.classes()).toContain('show')
expect(wrapper.classes()).toContain('collapse')
expect(wrapper.element.style.display).toEqual('')
expect(wrapper.find('div > div').exists()).toBe(true)
expect(wrapper.text()).toEqual('foobar')
wrapper.destroy()
})
it('should emit its state on mount (initialy hidden)', async () => {
const wrapper = mount(BCollapse, {
attachToDocument: true,
propsData: {
// 'id' is a required prop
id: 'test'
},
slots: {
default: '<div>foobar</div>'
},
stubs: {
// Disable use of default test transitionStub component
transition: false
}
})
const rootWrapper = createWrapper(wrapper.vm.$root)
await waitNT(wrapper.vm)
await waitRAF()
expect(wrapper.emitted('show')).not.toBeDefined()
expect(wrapper.emitted('input')).toBeDefined()
expect(wrapper.emitted('input').length).toBe(1)
expect(wrapper.emitted('input')[0][0]).toBe(false)
expect(rootWrapper.emitted(EVENT_ACCORDION)).not.toBeDefined()
expect(rootWrapper.emitted(EVENT_STATE)).toBeDefined()
expect(rootWrapper.emitted(EVENT_STATE).length).toBe(1)
expect(rootWrapper.emitted(EVENT_STATE)[0][0]).toBe('test') // ID
expect(rootWrapper.emitted(EVENT_STATE)[0][1]).toBe(false) // Visible state
expect(wrapper.element.style.display).toEqual('none')
wrapper.destroy()
})
it('should emit its state on mount (initially visible)', async () => {
const wrapper = mount(BCollapse, {
attachToDocument: true,
propsData: {
// 'id' is a required prop
id: 'test',
visible: true
},
slots: {
default: '<div>foobar</div>'
},
stubs: {
// Disable use of default test transitionStub component
transition: false
}
})
const rootWrapper = createWrapper(wrapper.vm.$root)
await waitNT(wrapper.vm)
await waitRAF()
expect(wrapper.emitted('show')).not.toBeDefined() // Does not emit show when initially visible
expect(wrapper.emitted('input')).toBeDefined()
expect(wrapper.emitted('input').length).toBe(1)
expect(wrapper.emitted('input')[0][0]).toBe(true)
expect(rootWrapper.emitted(EVENT_ACCORDION)).not.toBeDefined()
expect(rootWrapper.emitted(EVENT_STATE)).toBeDefined()
expect(rootWrapper.emitted(EVENT_STATE).length).toBe(1)
expect(rootWrapper.emitted(EVENT_STATE)[0][0]).toBe('test') // ID
expect(rootWrapper.emitted(EVENT_STATE)[0][1]).toBe(true) // Visible state
expect(wrapper.element.style.display).toEqual('')
wrapper.destroy()
})
it('should respond to state sync requests', async () => {
const wrapper = mount(BCollapse, {
attachToDocument: true,
propsData: {
// 'id' is a required prop
id: 'test',
visible: true
},
slots: {
default: '<div>foobar</div>'
},
stubs: {
// Disable use of default test transitionStub component
transition: false
}
})
const rootWrapper = createWrapper(wrapper.vm.$root)
await waitNT(wrapper.vm)
await waitRAF()
expect(wrapper.element.style.display).toEqual('')
expect(wrapper.emitted('show')).not.toBeDefined() // Does not emit show when initially visible
expect(wrapper.emitted('input')).toBeDefined()
expect(wrapper.emitted('input').length).toBe(1)
expect(wrapper.emitted('input')[0][0]).toBe(true)
expect(rootWrapper.emitted(EVENT_ACCORDION)).not.toBeDefined()
expect(rootWrapper.emitted(EVENT_STATE)).toBeDefined()
expect(rootWrapper.emitted(EVENT_STATE).length).toBe(1)
expect(rootWrapper.emitted(EVENT_STATE)[0][0]).toBe('test') // ID
expect(rootWrapper.emitted(EVENT_STATE)[0][1]).toBe(true) // Visible state
expect(rootWrapper.emitted(EVENT_STATE_SYNC)).not.toBeDefined()
rootWrapper.vm.$root.$emit(EVENT_STATE_REQUEST, 'test')
await waitNT(wrapper.vm)
await waitRAF()
expect(rootWrapper.emitted(EVENT_STATE_SYNC)).toBeDefined()
expect(rootWrapper.emitted(EVENT_STATE_SYNC).length).toBe(1)
expect(rootWrapper.emitted(EVENT_STATE_SYNC)[0][0]).toBe('test') // ID
expect(rootWrapper.emitted(EVENT_STATE_SYNC)[0][1]).toBe(true) // Visible state
wrapper.destroy()
})
it('setting visible to true after mount shows collapse', async () => {
const wrapper = mount(BCollapse, {
attachToDocument: true,
propsData: {
// 'id' is a required prop
id: 'test',
visible: false
},
slots: {
default: '<div>foobar</div>'
},
stubs: {
// Disable use of default test transitionStub component
transition: false
}
})
const rootWrapper = createWrapper(wrapper.vm.$root)
await waitNT(wrapper.vm)
await waitRAF()
expect(wrapper.emitted('show')).not.toBeDefined()
expect(wrapper.emitted('input')).toBeDefined()
expect(wrapper.emitted('input').length).toBe(1)
expect(wrapper.emitted('input')[0][0]).toBe(false)
expect(rootWrapper.emitted(EVENT_STATE)).toBeDefined()
expect(rootWrapper.emitted(EVENT_STATE).length).toBe(1)
expect(rootWrapper.emitted(EVENT_STATE)[0][0]).toBe('test') // ID
expect(rootWrapper.emitted(EVENT_STATE)[0][1]).toBe(false) // Visible state
expect(wrapper.element.style.display).toEqual('none')
// Change visible prop
wrapper.setProps({
visible: true
})
await waitNT(wrapper.vm)
await waitRAF()
expect(wrapper.emitted('show')).toBeDefined()
expect(wrapper.emitted('show').length).toBe(1)
expect(wrapper.emitted('input').length).toBe(2)
expect(wrapper.emitted('input')[1][0]).toBe(true)
expect(rootWrapper.emitted(EVENT_STATE).length).toBe(2)
expect(rootWrapper.emitted(EVENT_STATE)[1][0]).toBe('test') // ID
expect(rootWrapper.emitted(EVENT_STATE)[1][1]).toBe(true) // Visible state
expect(wrapper.element.style.display).toEqual('')
wrapper.destroy()
})
it('should respond to according events', async () => {
const wrapper = mount(BCollapse, {
attachToDocument: true,
propsData: {
// 'id' is a required prop
id: 'test',
accordion: 'foo',
visible: true
},
slots: {
default: '<div>foobar</div>'
},
stubs: {
// Disable use of default test transitionStub component
transition: false
}
})
const rootWrapper = createWrapper(wrapper.vm.$root)
await waitNT(wrapper.vm)
await waitRAF()
expect(wrapper.element.style.display).toEqual('')
expect(wrapper.emitted('show')).not.toBeDefined()
expect(wrapper.emitted('input')).toBeDefined()
expect(wrapper.emitted('input').length).toBe(1)
expect(wrapper.emitted('input')[0][0]).toBe(true)
expect(rootWrapper.emitted(EVENT_STATE)).toBeDefined()
expect(rootWrapper.emitted(EVENT_STATE).length).toBe(1)
expect(rootWrapper.emitted(EVENT_STATE)[0][0]).toBe('test') // ID
expect(rootWrapper.emitted(EVENT_STATE)[0][1]).toBe(true) // Visible state
expect(rootWrapper.emitted(EVENT_ACCORDION)).toBeDefined()
expect(rootWrapper.emitted(EVENT_ACCORDION).length).toBe(1)
expect(rootWrapper.emitted(EVENT_ACCORDION)[0][0]).toBe('test')
expect(rootWrapper.emitted(EVENT_ACCORDION)[0][1]).toBe('foo')
// Does not respond to accordion events for different accordion ID
wrapper.vm.$root.$emit(EVENT_ACCORDION, 'test', 'bar')
await waitNT(wrapper.vm)
await waitRAF()
expect(wrapper.emitted('input').length).toBe(1)
expect(wrapper.emitted('input')[0][0]).toBe(true)
expect(rootWrapper.emitted(EVENT_STATE).length).toBe(1)
expect(rootWrapper.emitted(EVENT_ACCORDION).length).toBe(2) // The event we just emitted
expect(rootWrapper.emitted(EVENT_ACCORDION)[1][0]).toBe('test')
expect(rootWrapper.emitted(EVENT_ACCORDION)[1][1]).toBe('bar')
expect(wrapper.element.style.display).toEqual('')
// Should respond to accordion events
wrapper.vm.$root.$emit(EVENT_ACCORDION, 'nottest', 'foo')
await waitNT(wrapper.vm)
await waitRAF()
await waitNT(wrapper.vm)
await waitRAF()
expect(wrapper.emitted('input').length).toBe(2)
expect(wrapper.emitted('input')[1][0]).toBe(false)
expect(rootWrapper.emitted(EVENT_STATE).length).toBe(2)
expect(rootWrapper.emitted(EVENT_STATE)[1][0]).toBe('test') // ID
expect(rootWrapper.emitted(EVENT_STATE)[1][1]).toBe(false) // Visible state
expect(rootWrapper.emitted(EVENT_ACCORDION).length).toBe(3) // The event we just emitted
expect(rootWrapper.emitted(EVENT_ACCORDION)[2][0]).toBe('nottest')
expect(rootWrapper.emitted(EVENT_ACCORDION)[2][1]).toBe('foo')
expect(wrapper.element.style.display).toEqual('none')
// Toggling this closed collapse emits accordion event
wrapper.vm.$root.$emit(EVENT_TOGGLE, 'test')
await waitNT(wrapper.vm)
await waitRAF()
await waitNT(wrapper.vm)
await waitRAF()
expect(wrapper.emitted('input').length).toBe(3)
expect(wrapper.emitted('input')[2][0]).toBe(true)
expect(rootWrapper.emitted(EVENT_STATE).length).toBe(3)
expect(rootWrapper.emitted(EVENT_STATE)[2][0]).toBe('test') // ID
expect(rootWrapper.emitted(EVENT_STATE)[2][1]).toBe(true) // Visible state
expect(rootWrapper.emitted(EVENT_ACCORDION).length).toBe(4) // The event emitted by collapse
expect(rootWrapper.emitted(EVENT_ACCORDION)[3][0]).toBe('test')
expect(rootWrapper.emitted(EVENT_ACCORDION)[3][1]).toBe('foo')
expect(wrapper.element.style.display).toEqual('')
// Toggling this open collapse to be closed
wrapper.vm.$root.$emit(EVENT_TOGGLE, 'test')
await waitNT(wrapper.vm)
await waitRAF()
await waitNT(wrapper.vm)
await waitRAF()
expect(wrapper.element.style.display).toEqual('none')
// Should respond to accordion events targeting this ID when closed
wrapper.vm.$root.$emit(EVENT_ACCORDION, 'test', 'foo')
await waitNT(wrapper.vm)
await waitRAF()
await waitNT(wrapper.vm)
await waitRAF()
expect(wrapper.element.style.display).toEqual('')
wrapper.destroy()
})
it('should close when clicking on contained nav-link prop is-nav is set', async () => {
const localVue = CreateLocalVue()
const App = localVue.extend({
render(h) {
return h('div', {}, [
// JSDOM supports getComputedStyle when using stylesheets (non responsive)
// https://github.com/jsdom/jsdom/blob/master/Changelog.md#030
h('style', { attrs: { type: 'text/css' } }, '.collapse:not(.show) { display: none; }'),
h(
BCollapse,
{
props: {
id: 'test',
isNav: true,
visible: true
}
},
[h('a', { class: 'nav-link', attrs: { href: '#' } }, 'nav link')]
)
])
}
})
const wrapper = mount(App, {
attachToDocument: true,
localVue: localVue,
stubs: {
// Disable use of default test transitionStub component
transition: false
}
})
expect(wrapper.isVueInstance()).toBe(true)
const $collapse = wrapper.find(BCollapse)
expect($collapse.isVueInstance()).toBe(true)
expect(wrapper.find('style').exists()).toBe(true)
await waitNT(wrapper.vm)
await waitRAF()
await waitNT(wrapper.vm)
await waitRAF()
expect($collapse.classes()).toContain('show')
expect($collapse.element.style.display).toEqual('')
expect($collapse.find('.nav-link').exists()).toBe(true)
// Click on link
wrapper.find('.nav-link').trigger('click')
await waitNT(wrapper.vm)
await waitRAF()
await waitNT(wrapper.vm)
await waitRAF()
expect($collapse.classes()).not.toContain('show')
expect($collapse.element.style.display).toEqual('none')
wrapper.destroy()
})
it('should not close when clicking on nav-link prop is-nav is set & collapse is display block important', async () => {
const localVue = CreateLocalVue()
const App = localVue.extend({
render(h) {
return h('div', {}, [
// JSDOM supports getComputedStyle when using stylesheets (non responsive)
// Although it appears to be picky about CSS definition ordering
// https://github.com/jsdom/jsdom/blob/master/Changelog.md#030
h(
'style',
{ attrs: { type: 'text/css' } },
'.collapse:not(.show) { display: none; } .d-block { display: block !important; }'
),
h(
BCollapse,
{
class: 'd-block',
props: {
id: 'test',
isNav: true,
visible: true
}
},
[h('a', { class: 'nav-link', attrs: { href: '#' } }, 'nav link')]
)
])
}
})
const wrapper = mount(App, {
attachToDocument: true,
localVue: localVue,
stubs: {
// Disable use of default test transitionStub component
transition: false
}
})
expect(wrapper.isVueInstance()).toBe(true)
const $collapse = wrapper.find(BCollapse)
expect($collapse.isVueInstance()).toBe(true)
expect(wrapper.find('style').exists()).toBe(true)
await waitNT(wrapper.vm)
await waitRAF()
await waitNT(wrapper.vm)
await waitRAF()
expect($collapse.classes()).toContain('show')
expect($collapse.element.style.display).toEqual('')
expect($collapse.find('.nav-link').exists()).toBe(true)
// Click on link
wrapper.find('.nav-link').trigger('click')
await waitNT(wrapper.vm)
await waitRAF()
await waitNT(wrapper.vm)
await waitRAF()
expect($collapse.classes()).toContain('show')
expect($collapse.element.style.display).toEqual('')
wrapper.destroy()
})
it('should not respond to root toggle event that does not match ID', async () => {
const wrapper = mount(BCollapse, {
attachToDocument: true,
propsData: {
// 'id' is a required prop
id: 'test'
},
slots: {
default: '<div>foobar</div>'
},
stubs: {
// Disable use of default test transitionStub component
transition: false
}
})
// const rootWrapper = createWrapper(wrapper.vm.$root)
expect(wrapper.isVueInstance()).toBe(true)
await waitNT(wrapper.vm)
await waitRAF()
expect(wrapper.classes()).not.toContain('show')
expect(wrapper.element.style.display).toEqual('none')
// Emit root event with different ID
wrapper.vm.$root.$emit(EVENT_TOGGLE, 'not-test')
await waitNT(wrapper.vm)
await waitRAF()
await waitNT(wrapper.vm)
await waitRAF()
expect(wrapper.classes()).not.toContain('show')
expect(wrapper.element.style.display).toEqual('none')
wrapper.destroy()
})
it('default slot scope works', async () => {
let scope = null
const wrapper = mount(BCollapse, {
attachToDocument: true,
propsData: {
// 'id' is a required prop
id: 'test',
visible: true
},
scopedSlots: {
default(props) {
scope = props
return this.$createElement('div', 'foobar')
}
},
stubs: {
// Disable use of default test transitionStub component
transition: false
}
})
const rootWrapper = createWrapper(wrapper.vm.$root)
await waitNT(wrapper.vm)
await waitRAF()
expect(wrapper.element.style.display).toEqual('')
expect(wrapper.emitted('show')).not.toBeDefined() // Does not emit show when initially visible
expect(wrapper.emitted('input')).toBeDefined()
expect(wrapper.emitted('input').length).toBe(1)
expect(wrapper.emitted('input')[0][0]).toBe(true)
expect(rootWrapper.emitted(EVENT_ACCORDION)).not.toBeDefined()
expect(rootWrapper.emitted(EVENT_STATE)).toBeDefined()
expect(rootWrapper.emitted(EVENT_STATE).length).toBe(1)
expect(rootWrapper.emitted(EVENT_STATE)[0][0]).toBe('test') // ID
expect(rootWrapper.emitted(EVENT_STATE)[0][1]).toBe(true) // Visible state
expect(rootWrapper.emitted(EVENT_STATE_SYNC)).not.toBeDefined()
expect(scope).not.toBe(null)
expect(scope.visible).toBe(true)
expect(typeof scope.close).toBe('function')
scope.close()
await waitNT(wrapper.vm)
await waitRAF()
expect(rootWrapper.emitted(EVENT_STATE_SYNC)).toBeDefined()
expect(rootWrapper.emitted(EVENT_STATE_SYNC).length).toBe(2)
expect(rootWrapper.emitted(EVENT_STATE_SYNC)[1][0]).toBe('test') // ID
expect(rootWrapper.emitted(EVENT_STATE_SYNC)[1][1]).toBe(false) // Visible state
expect(scope).not.toBe(null)
expect(scope.visible).toBe(false)
expect(typeof scope.close).toBe('function')
wrapper.destroy()
})
})