Skip to content

chore: directives code cleanup #2927

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
0d6b023
fix(collapse): prevent state event to be triggered multiple times
jacobmllr95 Mar 27, 2019
e3d474d
minor tweaks to other directives
jacobmllr95 Mar 27, 2019
944e1cd
Merge remote-tracking branch 'upstream/dev' into fix-toggle-directive…
jacobmllr95 Mar 27, 2019
256af51
Update toggle.js
jacobmllr95 Mar 27, 2019
6f26596
Update toggle.js
jacobmllr95 Mar 27, 2019
193ea88
Create modal.spec.js
tmorehouse Mar 27, 2019
6b2eba7
Update modal.spec.js
tmorehouse Mar 27, 2019
8014fa7
Update modal.spec.js
tmorehouse Mar 27, 2019
0f352bd
Merge branch 'dev' into fix-toggle-directive-and-directives-cleanup
tmorehouse Mar 27, 2019
456331c
Update modal.spec.js
tmorehouse Mar 27, 2019
e7de950
Update modal.spec.js
tmorehouse Mar 27, 2019
911e1a9
Update modal.spec.js
tmorehouse Mar 27, 2019
c01143c
Update modal.spec.js
tmorehouse Mar 27, 2019
e321fb3
Update modal.js
tmorehouse Mar 27, 2019
0aafcfb
Update modal.spec.js
tmorehouse Mar 27, 2019
3fcfcf8
Update modal.spec.js
tmorehouse Mar 27, 2019
78c83ee
Create toggle.spec.js
tmorehouse Mar 27, 2019
c9bc9e3
lint
tmorehouse Mar 27, 2019
8491076
Update toggle.spec.js
tmorehouse Mar 27, 2019
b02866c
Update toggle.spec.js
tmorehouse Mar 27, 2019
a74023f
Update toggle.spec.js
tmorehouse Mar 27, 2019
8a889b0
Update scrollspy.js
tmorehouse Mar 27, 2019
0084898
Update tooltip.js
tmorehouse Mar 27, 2019
d58a200
Update popover.js
tmorehouse Mar 27, 2019
eff59e6
Create tooltip.spec.js
tmorehouse Mar 27, 2019
583bd6b
Update tooltip.js
tmorehouse Mar 27, 2019
f963355
Update tooltip.spec.js
tmorehouse Mar 27, 2019
eddc2cc
Update tooltip.js
tmorehouse Mar 27, 2019
0f22b16
Create popover.spec.js
tmorehouse Mar 27, 2019
eb8564b
Update popover.js
tmorehouse Mar 27, 2019
2c04fe4
Update tooltip.js
tmorehouse Mar 27, 2019
f56a18e
Update popover.spec.js
tmorehouse Mar 27, 2019
a287ca2
Update nuxt.config.js
tmorehouse Mar 27, 2019
99afa98
Merge branch 'dev' into fix-toggle-directive-and-directives-cleanup
tmorehouse Mar 27, 2019
079af09
Update nuxt.config.js
tmorehouse Mar 27, 2019
bb0fb34
Update nuxt.config.js
tmorehouse Mar 27, 2019
3374e65
Create scrollspy.spec.js
tmorehouse Mar 27, 2019
7d4d78b
Update scrollspy.js
tmorehouse Mar 27, 2019
3ea45e1
Update scrollspy.spec.js
tmorehouse Mar 27, 2019
7822d40
lint
tmorehouse Mar 27, 2019
eb84260
Update scrollspy.js
tmorehouse Mar 27, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/nuxt.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ module.exports = {
.readdirSync(`${root}/${dir}`)
.filter(c => c !== 'index.js' && c[0] !== '_')
.filter(c => excludeDirs.indexOf(c) === -1)
.filter(c => !/\.s?css$/.test(c))
.map(page => `/docs/${dir}/${page}`)

return []
Expand Down
27 changes: 20 additions & 7 deletions src/directives/modal/modal.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,38 @@
import { bindTargets, unbindTargets } from '../../utils/target'
import { setAttr, removeAttr } from '../../utils/dom'
import { bindTargets, unbindTargets } from '../../utils/target'

// Target listen types
const listenTypes = { click: true }

// Emitted show event for modal
const EVENT_SHOW = 'bv::show::modal'

const setRole = (el, binding, vnode) => {
if (el.tagName !== 'BUTTON') {
setAttr(el, 'role', 'button')
}
}

/*
* Export our directive
*/
export default {
// eslint-disable-next-line no-shadow-restricted-names
bind(el, binding, vnode) {
bindTargets(vnode, binding, listenTypes, ({ targets, vnode }) => {
targets.forEach(target => {
vnode.context.$root.$emit('bv::show::modal', target, vnode.elm)
vnode.context.$root.$emit(EVENT_SHOW, target, vnode.elm)
})
})
if (el.tagName !== 'BUTTON') {
// If element is not a button, we add `role="button"` for accessibility
setAttr(el, 'role', 'button')
}
// If element is not a button, we add `role="button"` for accessibility
setRole(el, binding, vnode)
},
updated: setRole,
componentUpdated: setRole,
unbind(el, binding, vnode) {
unbindTargets(vnode, binding, listenTypes)
// If element is not a button, we add `role="button"` for accessibility
if (el.tagName !== 'BUTTON') {
// If element is not a button, we add `role="button"` for accessibility
removeAttr(el, 'role', 'button')
}
}
Expand Down
88 changes: 88 additions & 0 deletions src/directives/modal/modal.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import modalDirective from './modal'
import { mount, createLocalVue as CreateLocalVue } from '@vue/test-utils'

const EVENT_SHOW = 'bv::show::modal'

describe('v-b-modal directive', () => {
it('works on buttons', async () => {
const localVue = new CreateLocalVue()
const spy = jest.fn()

const App = localVue.extend({
directives: {
bModal: modalDirective
},
data() {
return {}
},
mounted() {
this.$root.$on(EVENT_SHOW, spy)
},
beforeDestroy() {
this.$root.$off(EVENT_SHOW, spy)
},
template: '<button v-b-modal.test>button</button>'
})
const wrapper = mount(App, {
localVue: localVue
})

expect(wrapper.isVueInstance()).toBe(true)
expect(wrapper.is('button')).toBe(true)
expect(spy).not.toHaveBeenCalled()

const $button = wrapper.find('button')
$button.trigger('click')
expect(spy).toHaveBeenCalledTimes(1)
expect(spy).toBeCalledWith('test', $button.element)

wrapper.destroy()
})

it('works on non-buttons', async () => {
const localVue = new CreateLocalVue()
const spy = jest.fn()

const App = localVue.extend({
directives: {
bModal: modalDirective
},
data() {
return {
text: 'span'
}
},
mounted() {
this.$root.$on(EVENT_SHOW, spy)
},
beforeDestroy() {
this.$root.$off(EVENT_SHOW, spy)
},
template: '<span tabindex="0" v-b-modal.test>{{ text }}</span>'
})
const wrapper = mount(App, {
localVue: localVue
})

expect(wrapper.isVueInstance()).toBe(true)
expect(wrapper.is('span')).toBe(true)
expect(spy).not.toHaveBeenCalled()
expect(wrapper.find('span').attributes('role')).toBe('button')
expect(wrapper.find('span').text()).toBe('span')

const $span = wrapper.find('span')
$span.trigger('click')
expect(spy).toHaveBeenCalledTimes(1)
expect(spy).toBeCalledWith('test', $span.element)
expect(wrapper.find('span').attributes('role')).toBe('button')

// Test updating component. should maintain role attribute
wrapper.setData({
text: 'foobar'
})
expect(wrapper.find('span').text()).toBe('foobar')
expect(wrapper.find('span').attributes('role')).toBe('button')

wrapper.destroy()
})
})
83 changes: 39 additions & 44 deletions src/directives/popover/popover.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import Popper from 'popper.js'
import PopOver from '../../utils/popover.class'
import { inBrowser } from '../../utils/env'
import { keys } from '../../utils/object'
import warn from '../../utils/warn'

const inBrowser = typeof window !== 'undefined' && typeof document !== 'undefined'

// Key which we use to store tooltip object on element
const BVPO = '__BV_PopOver__'
const BV_POPOVER = '__BV_PopOver__'

// Valid event triggers
const validTriggers = {
Expand All @@ -17,9 +16,9 @@ const validTriggers = {
}

// Build a PopOver config based on bindings (if any)
// Arguments and modifiers take precedence over pased value config object
// Arguments and modifiers take precedence over passed value config object
/* istanbul ignore next: not easy to test */
function parseBindings(bindings) {
const parseBindings = bindings => /* istanbul ignore next: not easy to test */ {
// We start out with a blank config
let config = {}

Expand All @@ -35,9 +34,10 @@ function parseBindings(bindings) {
config = { ...config, ...bindings.value }
}

// If Argument, assume element ID of container element
// If argument, assume element ID of container element
if (bindings.arg) {
// Element ID specified as arg. We must prepend '#' to become a CSS selector
// Element ID specified as arg
// We must prepend '#' to become a CSS selector
config.container = `#${bindings.arg}`
}

Expand All @@ -55,35 +55,36 @@ function parseBindings(bindings) {
// placement of popover
config.placement = mod
} else if (/^(window|viewport)$/.test(mod)) {
// bounday of popover
// Boundary of popover
config.boundary = mod
} else if (/^d\d+$/.test(mod)) {
// delay value
// Delay value
const delay = parseInt(mod.slice(1), 10) || 0
if (delay) {
config.delay = delay
}
} else if (/^o-?\d+$/.test(mod)) {
// offset value (negative allowed)
// Offset value (negative allowed)
const offset = parseInt(mod.slice(1), 10) || 0
if (offset) {
config.offset = offset
}
}
})

// Special handling of event trigger modifiers Trigger is a space separated list
// Special handling of event trigger modifiers trigger is
// a space separated list
const selectedTriggers = {}

// parse current config object trigger
// Parse current config object trigger
let triggers = typeof config.trigger === 'string' ? config.trigger.trim().split(/\s+/) : []
triggers.forEach(trigger => {
if (validTriggers[trigger]) {
selectedTriggers[trigger] = true
}
})

// Parse Modifiers for triggers
// Parse modifiers for triggers
keys(validTriggers).forEach(trigger => {
if (bindings.modifiers[trigger]) {
selectedTriggers[trigger] = true
Expand All @@ -97,70 +98,64 @@ function parseBindings(bindings) {
config.trigger = 'focus'
}
if (!config.trigger) {
// remove trigger config
// Remove trigger config
delete config.trigger
}

return config
}

//
// Add or Update popover on our element
//
/* istanbul ignore next: not easy to test */
function applyBVPO(el, bindings, vnode) {
// Add or update PopOver on our element
const applyPopover = (el, bindings, vnode) => {
if (!inBrowser) {
/* istanbul ignore next */
return
}
// Popper is required for PopOvers to work
if (!Popper) {
// Popper is required for tooltips to work
warn('v-b-popover: Popper.js is required for popovers to work')
/* istanbul ignore next */
warn('v-b-popover: Popper.js is required for PopOvers to work')
/* istanbul ignore next */
return
}
if (el[BVPO]) {
el[BVPO].updateConfig(parseBindings(bindings))
const config = parseBindings(bindings)
if (el[BV_POPOVER]) {
el[BV_POPOVER].updateConfig(config)
} else {
el[BVPO] = new PopOver(el, parseBindings(bindings), vnode.context.$root)
el[BV_POPOVER] = new PopOver(el, config, vnode.context.$root)
}
}

//
// Remove popover on our element
//
/* istanbul ignore next */
function removeBVPO(el) {
if (!inBrowser) {
return
}
if (el[BVPO]) {
el[BVPO].destroy()
el[BVPO] = null
delete el[BVPO]
// Remove PopOver on our element
const removePopover = el => {
if (el[BV_POPOVER]) {
el[BV_POPOVER].destroy()
el[BV_POPOVER] = null
delete el[BV_POPOVER]
}
}

/*
* Export our directive
*/
/* istanbul ignore next: not easy to test */
export default {
bind(el, bindings, vnode) {
applyBVPO(el, bindings, vnode)
applyPopover(el, bindings, vnode)
},
inserted(el, bindings, vnode) {
applyBVPO(el, bindings, vnode)
applyPopover(el, bindings, vnode)
},
update(el, bindings, vnode) {
update(el, bindings, vnode) /* istanbul ignore next: not easy to test */ {
if (bindings.value !== bindings.oldValue) {
applyBVPO(el, bindings, vnode)
applyPopover(el, bindings, vnode)
}
},
componentUpdated(el, bindings, vnode) {
componentUpdated(el, bindings, vnode) /* istanbul ignore next: not easy to test */ {
if (bindings.value !== bindings.oldValue) {
applyBVPO(el, bindings, vnode)
applyPopover(el, bindings, vnode)
}
},
unbind(el) {
removeBVPO(el)
removePopover(el)
}
}
37 changes: 37 additions & 0 deletions src/directives/popover/popover.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import popoverDirective from './popover'
import PopOver from '../../utils/popover.class'
import { mount, createLocalVue as CreateLocalVue } from '@vue/test-utils'

// Key which we use to store tooltip object on element
const BV_POPOVER = '__BV_PopOver__'

describe('v-b-popover directive', () => {
it('should have PopOver class instance', async () => {
const localVue = new CreateLocalVue()

const App = localVue.extend({
directives: {
bPopover: popoverDirective
},
data() {
return {}
},
template: `<button v-b-popover="'content'" title="foobar">button</button>`
})

const wrapper = mount(App, {
localVue: localVue,
attachToDocument: true
})

expect(wrapper.isVueInstance()).toBe(true)
expect(wrapper.is('button')).toBe(true)
const $button = wrapper.find('button')

// Should have instance of popover class on it
expect($button.element[BV_POPOVER]).toBeDefined()
expect($button.element[BV_POPOVER]).toBeInstanceOf(PopOver)

wrapper.destroy()
})
})
Loading