UNPKG

bootstrap-vue

Version:

BootstrapVue, with over 40 plugins and more than 75 custom components, provides one of the most comprehensive implementations of Bootstrap v4 components and grid system for Vue.js. With extensive and automated WAI-ARIA accessibility markup.

1,205 lines (973 loc) 34.6 kB
import { mount, createLocalVue as CreateLocalVue } from '@vue/test-utils' import { waitNT, waitRAF } from '../../../tests/utils' import BModal from './modal' import BvModalEvent from './helpers/bv-modal-event.class' // The default Z-INDEX for modal backdrop const DEFAULT_ZINDEX = 1040 describe('modal', () => { const origGetBCR = Element.prototype.getBoundingClientRect beforeEach(() => { // Mock `getBCR()` so that the `isVisible(el)` test returns `true` // Needed for z-index checks Element.prototype.getBoundingClientRect = jest.fn(() => { return { width: 24, height: 24, top: 0, left: 0, bottom: 0, right: 0 } }) }) afterEach(() => { // Restore prototype Element.prototype.getBoundingClientRect = origGetBCR }) describe('structure', () => { it('has expected default structure', async () => { const wrapper = mount(BModal, { attachToDocument: true, propsData: { static: true, id: 'test' } }) expect(wrapper.isVueInstance()).toBe(true) await waitNT(wrapper.vm) // Main outer wrapper (has z-index, etc)... The stacker <div> expect(wrapper.is('div')).toBe(true) expect(wrapper.classes().length).toBe(0) expect(wrapper.element.style.position).toEqual('absolute') expect(wrapper.element.style.zIndex).toEqual(`${DEFAULT_ZINDEX}`) // Should not have a backdrop expect(wrapper.find('div.modal-backdrop').exists()).toBe(false) // Main modal wrapper const $modal = wrapper.find('div.modal') expect($modal.exists()).toBe(true) expect($modal.attributes('id')).toBeDefined() expect($modal.attributes('id')).toEqual('test') expect($modal.attributes('role')).toBeDefined() expect($modal.attributes('role')).toEqual('dialog') expect($modal.attributes('tabindex')).toBeDefined() expect($modal.attributes('tabindex')).toEqual('-1') expect($modal.attributes('aria-hidden')).toBeDefined() expect($modal.attributes('aria-hidden')).toEqual('true') expect($modal.classes()).toContain('modal') expect($modal.element.style.display).toEqual('none') // Modal dialog wrapper const $dialog = $modal.find('div.modal-dialog') expect($dialog.exists()).toBe(true) wrapper.destroy() }) it('has expected default structure when static and lazy', async () => { const wrapper = mount(BModal, { attachToDocument: true, propsData: { static: true, lazy: true } }) expect(wrapper.isVueInstance()).toBe(true) await waitNT(wrapper.vm) expect(wrapper.isEmpty()).toBe(true) expect(wrapper.element.nodeType).toEqual(Node.COMMENT_NODE) wrapper.destroy() }) it('has expected default structure when not static', async () => { const wrapper = mount(BModal, { attachToDocument: true, propsData: { static: false } }) expect(wrapper.isVueInstance()).toBe(true) await waitNT(wrapper.vm) expect(wrapper.isEmpty()).toBe(true) expect(wrapper.element.nodeType).toEqual(Node.COMMENT_NODE) wrapper.destroy() }) it('has expected structure when initially open', async () => { const wrapper = mount(BModal, { attachToDocument: true, stubs: { // Disable the use of transitionStub fake transition // as it doesn't run transition hooks transition: false }, propsData: { static: true, id: 'test', visible: true } }) expect(wrapper.isVueInstance()).toBe(true) await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() // Main outer wrapper (has z-index, etc)... The stacker <div> expect(wrapper.is('div')).toBe(true) expect(wrapper.classes().length).toBe(0) expect(wrapper.element.style.position).toEqual('absolute') expect(wrapper.element.style.zIndex).toEqual(`${DEFAULT_ZINDEX}`) // Main modal wrapper const $modal = wrapper.find('div.modal') expect($modal.exists()).toBe(true) expect($modal.attributes('id')).toBeDefined() expect($modal.attributes('id')).toEqual('test') expect($modal.attributes('role')).toBeDefined() expect($modal.attributes('role')).toEqual('dialog') expect($modal.attributes('tabindex')).toBeDefined() expect($modal.attributes('tabindex')).toEqual('-1') expect($modal.attributes('aria-hidden')).not.toBeDefined() expect($modal.attributes('aria-modal')).toBeDefined() expect($modal.attributes('aria-modal')).toEqual('true') expect($modal.classes()).toContain('modal') expect($modal.element.style.display).toEqual('block') // Should have a backdrop const $backdrop = wrapper.find('div.modal-backdrop') expect($backdrop.exists()).toBe(true) // Modal dialog wrapper const $dialog = $modal.find('div.modal-dialog') expect($dialog.exists()).toBe(true) wrapper.destroy() }) it('renders appended to body when initially open and not static', async () => { const wrapper = mount(BModal, { attachToDocument: true, stubs: { // Disable the use of transitionStub fake transition // as it doesn't run transition hooks transition: false }, propsData: { static: false, id: 'testtarget', visible: true } }) expect(wrapper.isVueInstance()).toBe(true) await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() expect(wrapper.isEmpty()).toBe(true) expect(wrapper.element.nodeType).toEqual(Node.COMMENT_NODE) let outer = document.getElementById('testtarget___BV_modal_outer_') expect(outer).toBeDefined() expect(outer).not.toBe(null) expect(outer.__vue__).toBeDefined() // Target expect(outer.__vue__.$options.name).toBe('BTransporterTargetSingle') expect(outer.parentElement).toBeDefined() expect(outer.parentElement).toBe(document.body) // Destroy modal wrapper.destroy() await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() // Should no longer be in document. expect(outer.parentElement).toEqual(null) }) it('has expected structure when closed after being initially open', async () => { const wrapper = mount(BModal, { attachToDocument: true, stubs: { // Disable the use of transitionStub fake transition // as it doesn't run transition hooks transition: false }, propsData: { static: true, id: 'test', visible: true } }) expect(wrapper.isVueInstance()).toBe(true) await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() // Main outer wrapper (has z-index, etc)... The stacker <div> expect(wrapper.is('div')).toBe(true) expect(wrapper.classes().length).toBe(0) expect(wrapper.element.style.position).toEqual('absolute') expect(wrapper.element.style.zIndex).toEqual(`${DEFAULT_ZINDEX}`) // Main modal wrapper const $modal = wrapper.find('div.modal') expect($modal.exists()).toBe(true) expect($modal.attributes('aria-hidden')).not.toBeDefined() expect($modal.attributes('aria-modal')).toBeDefined() expect($modal.attributes('aria-modal')).toEqual('true') expect($modal.element.style.display).toEqual('block') // Should have a backdrop const $backdrop = wrapper.find('div.modal-backdrop') expect($backdrop.exists()).toBe(true) // Now we close the modal via the value prop wrapper.setProps({ visible: false }) await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() expect($modal.attributes('aria-hidden')).toBeDefined() expect($modal.attributes('aria-hidden')).toEqual('true') expect($modal.attributes('aria-modal')).not.toBeDefined() expect($modal.element.style.display).toEqual('none') // Backdrop should be removed expect(wrapper.find('div.modal-backdrop').exists()).toBe(false) wrapper.destroy() }) it('title-html prop works', async () => { const wrapper = mount(BModal, { attachToDocument: true, propsData: { static: true, id: 'test', titleHtml: '<em>title</em>' } }) expect(wrapper.isVueInstance()).toBe(true) await waitNT(wrapper.vm) // Modal title const $title = wrapper.find('.modal-title') expect($title.exists()).toBe(true) expect($title.html()).toContain('<em>title</em>') wrapper.destroy() }) }) describe('default button content, classes and attributes', () => { // We may want to move these tests into individual files for manageability it('default footer ok and cancel buttons', async () => { const wrapper = mount(BModal, { attachToDocument: true, propsData: { static: true } }) expect(wrapper).toBeDefined() const $buttons = wrapper.findAll('footer button') expect($buttons.length).toBe(2) // Cancel button (left-most button) const $cancel = $buttons.at(0) expect($cancel.attributes('type')).toBe('button') expect($cancel.classes()).toContain('btn') expect($cancel.classes()).toContain('btn-secondary') expect($cancel.text()).toContain('Cancel') // OK button (right-most button) const $ok = $buttons.at(1) expect($ok.attributes('type')).toBe('button') expect($ok.classes()).toContain('btn') expect($ok.classes()).toContain('btn-primary') expect($ok.text()).toContain('OK') wrapper.destroy() }) it('default header close button', async () => { const wrapper = mount(BModal, { attachToDocument: true, propsData: { static: true } }) expect(wrapper).toBeDefined() const $buttons = wrapper.findAll('header button') expect($buttons.length).toBe(1) // Close button const $close = $buttons.at(0) expect($close.attributes('type')).toBe('button') expect($close.attributes('aria-label')).toBe('Close') expect($close.classes()).toContain('close') wrapper.destroy() }) it('ok-title-html and cancel-title-html works', async () => { const wrapper = mount(BModal, { attachToDocument: true, propsData: { static: true, okTitleHtml: '<em>ok</em>', cancelTitleHtml: '<em>cancel</em>' } }) expect(wrapper).toBeDefined() const $buttons = wrapper.findAll('footer button') expect($buttons.length).toBe(2) // Cancel button (left-most button) const $cancel = $buttons.at(0) expect($cancel.attributes('type')).toBe('button') expect($cancel.text()).toContain('cancel') // v-html is applied to a span expect($cancel.html()).toContain('<span><em>cancel</em></span>') // OK button (right-most button) const $ok = $buttons.at(1) expect($ok.attributes('type')).toBe('button') expect($ok.text()).toContain('ok') // v-html is applied to a span expect($ok.html()).toContain('<span><em>ok</em></span>') wrapper.destroy() }) }) describe('button and event functionality', () => { it('header close button triggers modal close and is preventable', async () => { let cancelHide = true let trigger = null let evt = null const wrapper = mount(BModal, { attachToDocument: true, stubs: { transition: false }, propsData: { static: true, id: 'test', visible: true }, listeners: { hide: bvEvent => { if (cancelHide) { bvEvent.preventDefault() } trigger = bvEvent.trigger evt = bvEvent } } }) expect(wrapper.isVueInstance()).toBe(true) await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() const $modal = wrapper.find('div.modal') expect($modal.exists()).toBe(true) expect($modal.element.style.display).toEqual('block') const $buttons = wrapper.findAll('header button') expect($buttons.length).toBe(1) // Close button const $close = $buttons.at(0) expect($close.attributes('type')).toBe('button') expect($close.attributes('aria-label')).toBe('Close') expect($close.classes()).toContain('close') expect(wrapper.emitted('hide')).not.toBeDefined() expect(trigger).toEqual(null) expect(evt).toEqual(null) // Try and close modal (but we prevent it) $close.trigger('click') expect(trigger).toEqual('headerclose') expect(evt).toBeInstanceOf(BvModalEvent) await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() // Modal should still be open expect($modal.element.style.display).toEqual('block') // Try and close modal (and not prevent it) cancelHide = false trigger = null evt = null $close.trigger('click') expect(trigger).toEqual('headerclose') expect(evt).toBeInstanceOf(BvModalEvent) await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() // Modal should now be closed expect($modal.element.style.display).toEqual('none') wrapper.destroy() }) it('footer OK and CANCEL buttons trigger modal close and are preventable', async () => { let cancelHide = true let trigger = null const wrapper = mount(BModal, { attachToDocument: true, stubs: { transition: false }, propsData: { static: true, id: 'test', visible: true }, listeners: { hide: bvEvent => { if (cancelHide) { bvEvent.preventDefault() } trigger = bvEvent.trigger } } }) expect(wrapper.isVueInstance()).toBe(true) await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() const $modal = wrapper.find('div.modal') expect($modal.exists()).toBe(true) expect($modal.element.style.display).toEqual('block') const $buttons = wrapper.findAll('footer button') expect($buttons.length).toBe(2) // Cancel button (left-most button) const $cancel = $buttons.at(0) expect($cancel.text()).toContain('Cancel') // OK button (right-most button) const $ok = $buttons.at(1) expect($ok.text()).toContain('OK') expect(wrapper.emitted('hide')).not.toBeDefined() expect(trigger).toEqual(null) // Try and close modal (but we prevent it) $ok.trigger('click') expect(trigger).toEqual('ok') await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() // Modal should still be open expect($modal.element.style.display).toEqual('block') // Try and close modal (and not prevent it) cancelHide = false trigger = null $cancel.trigger('click') expect(trigger).toEqual('cancel') await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() // Modal should now be closed expect($modal.element.style.display).toEqual('none') // Modal should have emitted these events expect(wrapper.emitted('ok')).toBeDefined() expect(wrapper.emitted('ok').length).toBe(1) expect(wrapper.emitted('cancel')).toBeDefined() expect(wrapper.emitted('cancel').length).toBe(1) expect(wrapper.emitted('hidden')).toBeDefined() expect(wrapper.emitted('hidden').length).toBe(1) wrapper.destroy() }) it('pressing ESC closes modal', async () => { let trigger = null const wrapper = mount(BModal, { attachToDocument: true, stubs: { transition: false }, propsData: { static: true, id: 'test', visible: true }, listeners: { hide: bvEvent => { trigger = bvEvent.trigger } } }) expect(wrapper.isVueInstance()).toBe(true) await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() const $modal = wrapper.find('div.modal') expect($modal.exists()).toBe(true) expect($modal.element.style.display).toEqual('block') expect(wrapper.emitted('hide')).not.toBeDefined() expect(trigger).toEqual(null) // Try and close modal via ESC $modal.trigger('keydown.esc') expect(trigger).toEqual('esc') await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() // Modal should now be closed expect($modal.element.style.display).toEqual('none') // Modal should have emitted these events expect(wrapper.emitted('hide')).toBeDefined() expect(wrapper.emitted('hide').length).toBe(1) expect(wrapper.emitted('hidden')).toBeDefined() expect(wrapper.emitted('hidden').length).toBe(1) expect(wrapper.emitted('ok')).not.toBeDefined() expect(wrapper.emitted('cancel')).not.toBeDefined() wrapper.destroy() }) it('click outside closes modal', async () => { let trigger = null const wrapper = mount(BModal, { attachToDocument: true, stubs: { transition: false }, propsData: { static: true, id: 'test', visible: true }, listeners: { hide: bvEvent => { trigger = bvEvent.trigger } } }) expect(wrapper.isVueInstance()).toBe(true) await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() const $modal = wrapper.find('div.modal') expect($modal.exists()).toBe(true) expect($modal.element.style.display).toEqual('block') expect(wrapper.emitted('hide')).not.toBeDefined() expect(trigger).toEqual(null) // Try and close modal via click out $modal.trigger('click') expect(trigger).toEqual('backdrop') await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() // Modal should now be closed expect($modal.element.style.display).toEqual('none') // Modal should have emitted these events expect(wrapper.emitted('hide')).toBeDefined() expect(wrapper.emitted('hide').length).toBe(1) expect(wrapper.emitted('hidden')).toBeDefined() expect(wrapper.emitted('hidden').length).toBe(1) expect(wrapper.emitted('ok')).not.toBeDefined() expect(wrapper.emitted('cancel')).not.toBeDefined() wrapper.destroy() }) it('mousedown inside followed by mouse up outside (click) does not close modal', async () => { let trigger = null let called = false const wrapper = mount(BModal, { attachToDocument: true, stubs: { transition: false }, propsData: { static: true, id: 'test', visible: true }, listeners: { hide: bvEvent => { called = true trigger = bvEvent.trigger } } }) expect(wrapper.isVueInstance()).toBe(true) await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() const $modal = wrapper.find('div.modal') expect($modal.exists()).toBe(true) const $dialog = wrapper.find('div.modal-dialog') expect($dialog.exists()).toBe(true) const $footer = wrapper.find('footer.modal-footer') expect($footer.exists()).toBe(true) expect($modal.element.style.display).toEqual('block') expect(wrapper.emitted('hide')).not.toBeDefined() expect(trigger).toEqual(null) // Try and close modal via a "dragged" click out // starting from inside modal and finishing on backdrop $dialog.trigger('mousedown') $modal.trigger('mouseup') $modal.trigger('click') await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() expect(called).toEqual(false) expect(trigger).toEqual(null) // Modal should not be closed expect($modal.element.style.display).toEqual('block') // Try and close modal via a "dragged" click out // starting from inside modal and finishing on backdrop $footer.trigger('mousedown') $modal.trigger('mouseup') $modal.trigger('click') await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() expect(called).toEqual(false) expect(trigger).toEqual(null) // Modal should not be closed expect($modal.element.style.display).toEqual('block') // Try and close modal via click out $modal.trigger('click') await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() expect(called).toEqual(true) expect(trigger).toEqual('backdrop') // Modal should now be closed expect($modal.element.style.display).toEqual('none') wrapper.destroy() }) it('$root bv::show::modal and bv::hide::modal work', async () => { const wrapper = mount(BModal, { attachToDocument: true, stubs: { transition: false }, propsData: { static: true, id: 'test', visible: false } }) expect(wrapper.isVueInstance()).toBe(true) await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() const $modal = wrapper.find('div.modal') expect($modal.exists()).toBe(true) expect($modal.element.style.display).toEqual('none') // Try and open modal via `bv::show::modal` wrapper.vm.$root.$emit('bv::show::modal', 'test') await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() // Modal should now be open expect($modal.element.style.display).toEqual('block') // Try and close modal via `bv::hide::modal` wrapper.vm.$root.$emit('bv::hide::modal', 'test') await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() // Modal should now be closed expect($modal.element.style.display).toEqual('none') wrapper.destroy() }) it('$root bv::toggle::modal works', async () => { const wrapper = mount(BModal, { attachToDocument: true, stubs: { transition: false }, propsData: { static: true, id: 'test', visible: false } }) expect(wrapper.isVueInstance()).toBe(true) await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() const $modal = wrapper.find('div.modal') expect($modal.exists()).toBe(true) expect($modal.element.style.display).toEqual('none') // Try and open modal via `bv::toggle::modal` wrapper.vm.$root.$emit('bv::toggle::modal', 'test') await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() // Modal should now be open expect($modal.element.style.display).toEqual('block') // Try and close modal via `bv::toggle::modal` wrapper.vm.$root.$emit('bv::toggle::modal', 'test') await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() // Modal should now be closed expect($modal.element.style.display).toEqual('none') // Try and open modal via `bv::toggle::modal` with wrong ID wrapper.vm.$root.$emit('bv::toggle::modal', 'not-test') await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() // Modal should not be open expect($modal.element.style.display).toEqual('none') wrapper.destroy() }) it('show event is cancellable', async () => { let prevent = true let called = 0 const wrapper = mount(BModal, { attachToDocument: true, stubs: { transition: false }, propsData: { static: true, id: 'test', visible: false } }) expect(wrapper.isVueInstance()).toBe(true) await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() const $modal = wrapper.find('div.modal') expect($modal.exists()).toBe(true) expect($modal.element.style.display).toEqual('none') wrapper.vm.$on('show', bvEvt => { called = true if (prevent) { bvEvt.preventDefault() } }) // Try and open modal via `bv::show::modal` wrapper.vm.$root.$emit('bv::show::modal', 'test') await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() // Modal should not open expect(called).toBe(true) expect($modal.element.style.display).toEqual('none') await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() // Allow modal to open prevent = false called = false // Try and open modal via `bv::show::modal` wrapper.vm.$root.$emit('bv::show::modal', 'test') await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() // Modal should now be open expect(called).toBe(true) expect($modal.element.style.display).toEqual('block') wrapper.destroy() }) it('instance .toggle() methods works', async () => { const wrapper = mount(BModal, { attachToDocument: true, stubs: { transition: false }, propsData: { static: true, id: 'test', visible: false } }) expect(wrapper.isVueInstance()).toBe(true) await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() const $modal = wrapper.find('div.modal') expect($modal.exists()).toBe(true) expect($modal.element.style.display).toEqual('none') // Try and open modal via `.toggle()` method wrapper.vm.toggle() await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() // Modal should now be open expect($modal.element.style.display).toEqual('block') // Try and close modal via `.toggle()` method wrapper.vm.toggle() await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() // Modal should now be closed expect($modal.element.style.display).toEqual('none') wrapper.destroy() }) }) describe('focus management', () => { const localVue = new CreateLocalVue() it('returns focus to previous active element when return focus not set and not using v-b-toggle', async () => { const App = localVue.extend({ render(h) { return h('div', {}, [ h('button', { class: 'trigger', attrs: { id: 'trigger', type: 'button' } }, 'trigger'), h(BModal, { props: { static: true, id: 'test', visible: false } }, 'modal content') ]) } }) const wrapper = mount(App, { attachToDocument: true, localVue: localVue, stubs: { transition: false } }) expect(wrapper.isVueInstance()).toBe(true) await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitNT(wrapper.vm) const $button = wrapper.find('button.trigger') expect($button.exists()).toBe(true) expect($button.is('button')).toBe(true) const $modal = wrapper.find('div.modal') expect($modal.exists()).toBe(true) expect($modal.element.style.display).toEqual('none') expect(document.activeElement).toBe(document.body) // Set the active element to the button $button.element.focus() expect(document.activeElement).toBe($button.element) // Try and open modal via `.toggle()` method wrapper.find(BModal).vm.toggle() await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitNT(wrapper.vm) // Modal should now be open expect($modal.element.style.display).toEqual('block') expect(document.activeElement).not.toBe(document.body) expect(document.activeElement).not.toBe($button.element) expect($modal.element.contains(document.activeElement)).toBe(true) // Try and close modal via `.toggle()` method wrapper.find(BModal).vm.toggle() await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitNT(wrapper.vm) // Modal should now be closed expect($modal.element.style.display).toEqual('none') expect(document.activeElement).toBe($button.element) wrapper.destroy() }) it('returns focus to element specified in toggle() method', async () => { const App = localVue.extend({ render(h) { return h('div', {}, [ h('button', { class: 'trigger', attrs: { id: 'trigger', type: 'button' } }, 'trigger'), h( 'button', { class: 'return-to', attrs: { id: 'return-to', type: 'button' } }, 'trigger' ), h(BModal, { props: { static: true, id: 'test', visible: false } }, 'modal content') ]) } }) const wrapper = mount(App, { attachToDocument: true, localVue: localVue, stubs: { transition: false } }) expect(wrapper.isVueInstance()).toBe(true) await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitNT(wrapper.vm) const $button = wrapper.find('button.trigger') expect($button.exists()).toBe(true) expect($button.is('button')).toBe(true) const $button2 = wrapper.find('button.return-to') expect($button2.exists()).toBe(true) expect($button2.is('button')).toBe(true) const $modal = wrapper.find('div.modal') expect($modal.exists()).toBe(true) expect($modal.element.style.display).toEqual('none') expect(document.activeElement).toBe(document.body) // Set the active element to the button $button.element.focus() expect(document.activeElement).toBe($button.element) // Try and open modal via `.toggle()` method wrapper.find(BModal).vm.toggle('button.return-to') await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitNT(wrapper.vm) // Modal should now be open expect($modal.element.style.display).toEqual('block') expect(document.activeElement).not.toBe(document.body) expect(document.activeElement).not.toBe($button.element) expect(document.activeElement).not.toBe($button2.element) expect($modal.element.contains(document.activeElement)).toBe(true) // Try and close modal via `.toggle()` method wrapper.find(BModal).vm.toggle() await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitNT(wrapper.vm) // Modal should now be closed expect($modal.element.style.display).toEqual('none') expect(document.activeElement).toBe($button2.element) wrapper.destroy() }) it('if focus leaves modal it returns to modal', async () => { const App = localVue.extend({ render(h) { return h('div', {}, [ h('button', { class: 'trigger', attrs: { id: 'trigger', type: 'button' } }, 'trigger'), h(BModal, { props: { static: true, id: 'test', visible: true } }, 'modal content') ]) } }) const wrapper = mount(App, { attachToDocument: true, localVue: localVue, stubs: { transition: false } }) expect(wrapper.isVueInstance()).toBe(true) await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitRAF() await waitNT(wrapper.vm) await waitNT(wrapper.vm) const $button = wrapper.find('button.trigger') expect($button.exists()).toBe(true) expect($button.is('button')).toBe(true) const $modal = wrapper.find('div.modal') expect($modal.exists()).toBe(true) expect($modal.element.style.display).toEqual('block') expect(document.activeElement).not.toBe(document.body) expect(document.activeElement).toBe($modal.element) // Try anf set focusin on external button $button.trigger('focusin') await waitNT(wrapper.vm) await waitNT(wrapper.vm) expect(document.activeElement).not.toBe($button.element) expect(document.activeElement).toBe($modal.element) // Try anf set focusin on external button $button.trigger('focus') await waitNT(wrapper.vm) await waitNT(wrapper.vm) expect(document.activeElement).not.toBe($button.element) expect(document.activeElement).toBe($modal.element) wrapper.destroy() }) }) })