UNPKG

bootstrap-vue

Version:

With more than 85 components, over 45 available plugins, several directives, and 1000+ icons, BootstrapVue provides one of the most comprehensive implementations of the Bootstrap v4 component and grid system available for Vue.js v2.6, complete with extens

559 lines (499 loc) 20.2 kB
import { createWrapper, mount } from '@vue/test-utils' import { waitNT, waitRAF } from '../../../tests/utils' import { BCollapse } from './collapse' const ROOT_ACTION_EVENT_NAME_REQUEST_STATE = 'bv::request-state::collapse' const ROOT_ACTION_EVENT_NAME_TOGGLE = 'bv::toggle::collapse' const ROOT_EVENT_NAME_ACCORDION = 'bv::collapse::accordion' const ROOT_EVENT_NAME_STATE = 'bv::collapse::state' const ROOT_EVENT_NAME_SYNC_STATE = 'bv::collapse::sync-state' describe('collapse', () => { const origGetBCR = Element.prototype.getBoundingClientRect beforeEach(() => { // Mock `getBoundingClientRect()` 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, { attachTo: document.body, propsData: { // 'id' is a required prop id: 'test' } }) // const rootWrapper = createWrapper(wrapper.vm.$root) expect(wrapper.vm).toBeDefined() await waitNT(wrapper.vm) await waitRAF() expect(wrapper.element.tagName).toBe('DIV') 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, { attachTo: document.body, propsData: { // 'id' is a required prop id: 'test', isNav: true } }) // const rootWrapper = createWrapper(wrapper.vm.$root) expect(wrapper.vm).toBeDefined() await waitNT(wrapper.vm) await waitRAF() expect(wrapper.element.tagName).toBe('DIV') 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, { attachTo: document.body, propsData: { // 'id' is a required prop id: 'test' }, slots: { default: '<div>foobar</div>' } }) expect(wrapper.vm).toBeDefined() await waitNT(wrapper.vm) await waitRAF() expect(wrapper.element.tagName).toBe('DIV') 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, { attachTo: document.body, propsData: { // 'id' is a required prop id: 'test', visible: true }, slots: { default: '<div>foobar</div>' } }) expect(wrapper.vm).toBeDefined() await waitNT(wrapper.vm) await waitRAF() expect(wrapper.element.tagName).toBe('DIV') 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 (initially hidden)', async () => { const wrapper = mount(BCollapse, { attachTo: document.body, propsData: { // 'id' is a required prop id: 'test' }, slots: { default: '<div>foobar</div>' } }) const rootWrapper = createWrapper(wrapper.vm.$root) await waitNT(wrapper.vm) await waitRAF() expect(wrapper.emitted('show')).toBeUndefined() expect(wrapper.emitted('input')).toBeDefined() expect(wrapper.emitted('input').length).toBe(1) expect(wrapper.emitted('input')[0][0]).toBe(false) expect(rootWrapper.emitted(ROOT_EVENT_NAME_ACCORDION)).toBeUndefined() expect(rootWrapper.emitted(ROOT_EVENT_NAME_STATE)).toBeDefined() expect(rootWrapper.emitted(ROOT_EVENT_NAME_STATE).length).toBe(1) expect(rootWrapper.emitted(ROOT_EVENT_NAME_STATE)[0][0]).toBe('test') // ID expect(rootWrapper.emitted(ROOT_EVENT_NAME_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, { attachTo: document.body, propsData: { // 'id' is a required prop id: 'test', visible: true }, slots: { default: '<div>foobar</div>' } }) const rootWrapper = createWrapper(wrapper.vm.$root) await waitNT(wrapper.vm) await waitRAF() expect(wrapper.emitted('show')).toBeUndefined() // 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(ROOT_EVENT_NAME_ACCORDION)).toBeUndefined() expect(rootWrapper.emitted(ROOT_EVENT_NAME_STATE)).toBeDefined() expect(rootWrapper.emitted(ROOT_EVENT_NAME_STATE).length).toBe(1) expect(rootWrapper.emitted(ROOT_EVENT_NAME_STATE)[0][0]).toBe('test') // ID expect(rootWrapper.emitted(ROOT_EVENT_NAME_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, { attachTo: document.body, propsData: { // 'id' is a required prop id: 'test', visible: true }, slots: { default: '<div>foobar</div>' } }) const rootWrapper = createWrapper(wrapper.vm.$root) await waitNT(wrapper.vm) await waitRAF() expect(wrapper.element.style.display).toEqual('') expect(wrapper.emitted('show')).toBeUndefined() // 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(ROOT_EVENT_NAME_ACCORDION)).toBeUndefined() expect(rootWrapper.emitted(ROOT_EVENT_NAME_STATE)).toBeDefined() expect(rootWrapper.emitted(ROOT_EVENT_NAME_STATE).length).toBe(1) expect(rootWrapper.emitted(ROOT_EVENT_NAME_STATE)[0][0]).toBe('test') // ID expect(rootWrapper.emitted(ROOT_EVENT_NAME_STATE)[0][1]).toBe(true) // Visible state expect(rootWrapper.emitted(ROOT_EVENT_NAME_SYNC_STATE)).toBeUndefined() rootWrapper.vm.$root.$emit(ROOT_ACTION_EVENT_NAME_REQUEST_STATE, 'test') await waitNT(wrapper.vm) await waitRAF() expect(rootWrapper.emitted(ROOT_EVENT_NAME_SYNC_STATE)).toBeDefined() expect(rootWrapper.emitted(ROOT_EVENT_NAME_SYNC_STATE).length).toBe(1) expect(rootWrapper.emitted(ROOT_EVENT_NAME_SYNC_STATE)[0][0]).toBe('test') // ID expect(rootWrapper.emitted(ROOT_EVENT_NAME_SYNC_STATE)[0][1]).toBe(true) // Visible state wrapper.destroy() }) it('setting visible to true after mount shows collapse', async () => { const wrapper = mount(BCollapse, { attachTo: document.body, propsData: { // 'id' is a required prop id: 'test', visible: false }, slots: { default: '<div>foobar</div>' } }) const rootWrapper = createWrapper(wrapper.vm.$root) await waitNT(wrapper.vm) await waitRAF() expect(wrapper.emitted('show')).toBeUndefined() expect(wrapper.emitted('input')).toBeDefined() expect(wrapper.emitted('input').length).toBe(1) expect(wrapper.emitted('input')[0][0]).toBe(false) expect(rootWrapper.emitted(ROOT_EVENT_NAME_STATE)).toBeDefined() expect(rootWrapper.emitted(ROOT_EVENT_NAME_STATE).length).toBe(1) expect(rootWrapper.emitted(ROOT_EVENT_NAME_STATE)[0][0]).toBe('test') // ID expect(rootWrapper.emitted(ROOT_EVENT_NAME_STATE)[0][1]).toBe(false) // Visible state expect(wrapper.element.style.display).toEqual('none') // Change visible prop await 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(ROOT_EVENT_NAME_STATE).length).toBe(2) expect(rootWrapper.emitted(ROOT_EVENT_NAME_STATE)[1][0]).toBe('test') // ID expect(rootWrapper.emitted(ROOT_EVENT_NAME_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, { attachTo: document.body, propsData: { // 'id' is a required prop id: 'test', accordion: 'foo', visible: true }, slots: { default: '<div>foobar</div>' } }) const rootWrapper = createWrapper(wrapper.vm.$root) await waitNT(wrapper.vm) await waitRAF() expect(wrapper.element.style.display).toEqual('') expect(wrapper.emitted('show')).toBeUndefined() expect(wrapper.emitted('input')).toBeDefined() expect(wrapper.emitted('input').length).toBe(1) expect(wrapper.emitted('input')[0][0]).toBe(true) expect(rootWrapper.emitted(ROOT_EVENT_NAME_STATE)).toBeDefined() expect(rootWrapper.emitted(ROOT_EVENT_NAME_STATE).length).toBe(1) expect(rootWrapper.emitted(ROOT_EVENT_NAME_STATE)[0][0]).toBe('test') // ID expect(rootWrapper.emitted(ROOT_EVENT_NAME_STATE)[0][1]).toBe(true) // Visible state expect(rootWrapper.emitted(ROOT_EVENT_NAME_ACCORDION)).toBeDefined() expect(rootWrapper.emitted(ROOT_EVENT_NAME_ACCORDION).length).toBe(1) expect(rootWrapper.emitted(ROOT_EVENT_NAME_ACCORDION)[0][0]).toBe('test') expect(rootWrapper.emitted(ROOT_EVENT_NAME_ACCORDION)[0][1]).toBe('foo') // Does not respond to accordion events for different accordion ID wrapper.vm.$root.$emit(ROOT_EVENT_NAME_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(ROOT_EVENT_NAME_STATE).length).toBe(1) expect(rootWrapper.emitted(ROOT_EVENT_NAME_ACCORDION).length).toBe(2) // The event we just emitted expect(rootWrapper.emitted(ROOT_EVENT_NAME_ACCORDION)[1][0]).toBe('test') expect(rootWrapper.emitted(ROOT_EVENT_NAME_ACCORDION)[1][1]).toBe('bar') expect(wrapper.element.style.display).toEqual('') // Should respond to accordion events wrapper.vm.$root.$emit(ROOT_EVENT_NAME_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(ROOT_EVENT_NAME_STATE).length).toBe(2) expect(rootWrapper.emitted(ROOT_EVENT_NAME_STATE)[1][0]).toBe('test') // ID expect(rootWrapper.emitted(ROOT_EVENT_NAME_STATE)[1][1]).toBe(false) // Visible state expect(rootWrapper.emitted(ROOT_EVENT_NAME_ACCORDION).length).toBe(3) // The event we just emitted expect(rootWrapper.emitted(ROOT_EVENT_NAME_ACCORDION)[2][0]).toBe('nottest') expect(rootWrapper.emitted(ROOT_EVENT_NAME_ACCORDION)[2][1]).toBe('foo') expect(wrapper.element.style.display).toEqual('none') // Toggling this closed collapse emits accordion event wrapper.vm.$root.$emit(ROOT_ACTION_EVENT_NAME_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(ROOT_EVENT_NAME_STATE).length).toBe(3) expect(rootWrapper.emitted(ROOT_EVENT_NAME_STATE)[2][0]).toBe('test') // ID expect(rootWrapper.emitted(ROOT_EVENT_NAME_STATE)[2][1]).toBe(true) // Visible state expect(rootWrapper.emitted(ROOT_EVENT_NAME_ACCORDION).length).toBe(4) // The event emitted by collapse expect(rootWrapper.emitted(ROOT_EVENT_NAME_ACCORDION)[3][0]).toBe('test') expect(rootWrapper.emitted(ROOT_EVENT_NAME_ACCORDION)[3][1]).toBe('foo') expect(wrapper.element.style.display).toEqual('') // Toggling this open collapse to be closed wrapper.vm.$root.$emit(ROOT_ACTION_EVENT_NAME_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(ROOT_EVENT_NAME_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 App = { 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, { attachTo: document.body }) expect(wrapper.vm).toBeDefined() const $collapse = wrapper.findComponent(BCollapse) expect($collapse.vm).toBeDefined() 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 await wrapper.find('.nav-link').trigger('click') await waitRAF() 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 App = { 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, { attachTo: document.body }) expect(wrapper.vm).toBeDefined() const $collapse = wrapper.findComponent(BCollapse) expect($collapse.vm).toBeDefined() 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 await wrapper.find('.nav-link').trigger('click') await waitRAF() 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, { attachTo: document.body, propsData: { // 'id' is a required prop id: 'test' }, slots: { default: '<div>foobar</div>' } }) // const rootWrapper = createWrapper(wrapper.vm.$root) expect(wrapper.vm).toBeDefined() 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(ROOT_ACTION_EVENT_NAME_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, { attachTo: document.body, propsData: { // 'id' is a required prop id: 'test', visible: true }, scopedSlots: { default(props) { scope = props return this.$createElement('div', 'foobar') } } }) const rootWrapper = createWrapper(wrapper.vm.$root) await waitNT(wrapper.vm) await waitRAF() expect(wrapper.element.style.display).toEqual('') expect(wrapper.emitted('show')).toBeUndefined() // 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(ROOT_EVENT_NAME_ACCORDION)).toBeUndefined() expect(rootWrapper.emitted(ROOT_EVENT_NAME_STATE)).toBeDefined() expect(rootWrapper.emitted(ROOT_EVENT_NAME_STATE).length).toBe(1) expect(rootWrapper.emitted(ROOT_EVENT_NAME_STATE)[0][0]).toBe('test') // ID expect(rootWrapper.emitted(ROOT_EVENT_NAME_STATE)[0][1]).toBe(true) // Visible state expect(rootWrapper.emitted(ROOT_EVENT_NAME_SYNC_STATE)).toBeUndefined() 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(ROOT_EVENT_NAME_SYNC_STATE)).toBeDefined() expect(rootWrapper.emitted(ROOT_EVENT_NAME_SYNC_STATE).length).toBe(2) expect(rootWrapper.emitted(ROOT_EVENT_NAME_SYNC_STATE)[1][0]).toBe('test') // ID expect(rootWrapper.emitted(ROOT_EVENT_NAME_SYNC_STATE)[1][1]).toBe(false) // Visible state expect(scope).not.toBe(null) expect(scope.visible).toBe(false) expect(typeof scope.close).toBe('function') wrapper.destroy() }) })