diff --git a/src/components/button-group/button-group.spec.js b/src/components/button-group/button-group.spec.js
index 11d92493954..ea67a4864bc 100644
--- a/src/components/button-group/button-group.spec.js
+++ b/src/components/button-group/button-group.spec.js
@@ -1,30 +1,80 @@
-import { loadFixture, testVM } from '../../../tests/utils'
+import ButtonGroup from './button-group'
+import { mount } from '@vue/test-utils'
describe('button-group', () => {
- beforeEach(loadFixture(__dirname, 'button-group'))
- testVM()
-
- it('basic should contain base class', async () => {
- const {
- app: { $refs }
- } = window
+ it('has expected default structure', async () => {
+ const wrapper = mount(ButtonGroup)
+ expect(wrapper.is('div')).toBe(true)
+ expect(wrapper.classes()).toContain('btn-group')
+ expect(wrapper.classes().length).toBe(1)
+ expect(wrapper.attributes('role')).toBeDefined()
+ expect(wrapper.attributes('role')).toBe('group')
+ expect(wrapper.text()).toBe('')
+ })
- expect($refs.basic).toHaveClass('btn-group')
+ it('should render default slot', async () => {
+ const wrapper = mount(ButtonGroup, {
+ slots: {
+ default: 'foobar'
+ }
+ })
+ expect(wrapper.is('div')).toBe(true)
+ expect(wrapper.classes()).toContain('btn-group')
+ expect(wrapper.classes().length).toBe(1)
+ expect(wrapper.attributes('role')).toBeDefined()
+ expect(wrapper.attributes('role')).toBe('group')
+ expect(wrapper.find('span').exists()).toBe(true)
+ expect(wrapper.text()).toBe('foobar')
})
it('should apply vertical class', async () => {
- const {
- app: { $refs }
- } = window
-
- expect($refs.vertical).toHaveClass('btn-group-vertical')
+ const wrapper = mount(ButtonGroup, {
+ propsData: {
+ vertical: true
+ }
+ })
+ expect(wrapper.is('div')).toBe(true)
+ expect(wrapper.classes()).toContain('btn-group-vertical')
+ expect(wrapper.classes()).not.toContain('btn-group')
+ expect(wrapper.classes().length).toBe(1)
})
it('should apply size class', async () => {
- const {
- app: { $refs }
- } = window
+ const wrapper = mount(ButtonGroup, {
+ propsData: {
+ size: 'sm'
+ }
+ })
+ expect(wrapper.is('div')).toBe(true)
+ expect(wrapper.classes()).toContain('btn-group')
+ expect(wrapper.classes()).toContain('btn-group-sm')
+ expect(wrapper.classes().length).toBe(2)
+ })
+
+ it('should apply size class when vertical', async () => {
+ const wrapper = mount(ButtonGroup, {
+ propsData: {
+ size: 'sm',
+ vertical: true
+ }
+ })
+ expect(wrapper.is('div')).toBe(true)
+ expect(wrapper.classes()).toContain('btn-group-sm')
+ expect(wrapper.classes()).toContain('btn-group-vertical')
+ expect(wrapper.classes()).not.toContain('btn-group')
+ expect(wrapper.classes().length).toBe(2)
+ })
- expect($refs.size).toHaveClass('btn-group-sm')
+ it('has custom role when aria-role prop set', async () => {
+ const wrapper = mount(ButtonGroup, {
+ propsData: {
+ ariaRole: 'foobar'
+ }
+ })
+ expect(wrapper.is('div')).toBe(true)
+ expect(wrapper.classes()).toContain('btn-group')
+ expect(wrapper.classes().length).toBe(1)
+ expect(wrapper.attributes('role')).toBeDefined()
+ expect(wrapper.attributes('role')).toBe('foobar')
})
})
diff --git a/src/components/button-group/fixtures/button-group.html b/src/components/button-group/fixtures/button-group.html
deleted file mode 100644
index 87e5159c641..00000000000
--- a/src/components/button-group/fixtures/button-group.html
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
- Top
- Middle
- Bottom
-
-
-
-
-
- Left
- Middle
- Right
-
-
-
-
- Left
- Middle
- Right
-
-
-
-
diff --git a/src/components/button-group/fixtures/button-group.js b/src/components/button-group/fixtures/button-group.js
deleted file mode 100644
index 0bae7b95202..00000000000
--- a/src/components/button-group/fixtures/button-group.js
+++ /dev/null
@@ -1,3 +0,0 @@
-window.app = new Vue({
- el: '#app'
-})
diff --git a/src/components/button/button.spec.js b/src/components/button/button.spec.js
index c9525e0d00e..7047262b12c 100644
--- a/src/components/button/button.spec.js
+++ b/src/components/button/button.spec.js
@@ -1,191 +1,270 @@
-import { loadFixture, testVM, nextTick, setData } from '../../../tests/utils'
-
-/**
- * Button functionality to test:
- * - Style variants: [ 'primary','secondary','success','outline-success','warning','danger','link' ]
- * - Sizes: [ 'sm','','lg' ]
- * - Props: [ disabled, block ]
- * - elements: [ , ]
- */
-const colorVariants = ['primary', 'secondary', 'success', 'warning', 'danger']
-const outlineVariants = colorVariants.map(v => `outline-${v}`)
-const variants = colorVariants.concat(outlineVariants, 'link')
-const sizes = ['sm', '', 'lg']
-
-const btnRefs = variants.reduce(
- (memo, variant) =>
- memo.concat(
- sizes.map(size => {
- return {
- variant,
- size,
- ref: `btn${size ? `_${size}` : ''}_${variant.replace(/-/g, '_')}`
- }
- })
- ),
- []
-)
+import Button from './button'
+import { mount } from '@vue/test-utils'
describe('button', () => {
- beforeEach(loadFixture(__dirname, 'button'))
- testVM()
-
- it('should contain class names', async () => {
- const {
- app: { $refs }
- } = window
+ it('has default structure and classes', async () => {
+ const wrapper = mount(Button)
+
+ expect(wrapper.is('button')).toBe(true)
+ expect(wrapper.attributes('type')).toBeDefined()
+ expect(wrapper.attributes('type')).toBe('button')
+ expect(wrapper.classes()).toContain('btn')
+ expect(wrapper.classes()).toContain('btn-secondary')
+ expect(wrapper.classes().length).toBe(2)
+ expect(wrapper.attributes('href')).not.toBeDefined()
+ expect(wrapper.attributes('role')).not.toBeDefined()
+ expect(wrapper.attributes('disabled')).not.toBeDefined()
+ expect(wrapper.attributes('aria-disabled')).not.toBeDefined()
+ expect(wrapper.attributes('aria-pressed')).not.toBeDefined()
+ expect(wrapper.attributes('autocomplete')).not.toBeDefined()
+ expect(wrapper.attributes('tabindex')).not.toBeDefined()
+ })
- btnRefs.forEach(({ ref, variant, size }) => {
- // ref will contain an array of children because of v-for
- const vm = $refs[ref][0]
+ it('renders a link when href provided', async () => {
+ const wrapper = mount(Button, {
+ propsData: {
+ href: '/foo/bar'
+ }
+ })
- let classList = ['btn', `btn-${variant}`]
- if (size) classList.push(`btn-${size}`)
+ expect(wrapper.is('a')).toBe(true)
+ expect(wrapper.attributes('href')).toBeDefined()
+ expect(wrapper.attributes('href')).toBe('/foo/bar')
+ expect(wrapper.attributes('type')).not.toBeDefined()
+ expect(wrapper.classes()).toContain('btn')
+ expect(wrapper.classes()).toContain('btn-secondary')
+ expect(wrapper.classes().length).toBe(2)
+ expect(wrapper.attributes('role')).not.toBeDefined()
+ expect(wrapper.attributes('disabled')).not.toBeDefined()
+ expect(wrapper.attributes('aria-disabled')).not.toBeDefined()
+ expect(wrapper.attributes('aria-pressed')).not.toBeDefined()
+ expect(wrapper.attributes('autocomplete')).not.toBeDefined()
+ expect(wrapper.attributes('tabindex')).not.toBeDefined()
+ })
- expect(vm).toHaveAllClasses(classList)
+ it('renders default slot content', async () => {
+ const wrapper = mount(Button, {
+ slots: {
+ default: 'foobar'
+ }
})
- const vmBlockDisabled = $refs.btn_block_disabled
- expect(vmBlockDisabled).toHaveAllClasses(['btn', 'btn-block', 'disabled'])
+ expect(wrapper.is('button')).toBe(true)
+ expect(wrapper.attributes('type')).toBeDefined()
+ expect(wrapper.attributes('type')).toBe('button')
+ expect(wrapper.classes()).toContain('btn')
+ expect(wrapper.classes()).toContain('btn-secondary')
+ expect(wrapper.classes().length).toBe(2)
+ expect(wrapper.find('span').exists()).toBe(true)
+ expect(wrapper.text()).toBe('foobar')
})
- it('should use when given href', async () => {
- const {
- app: { $refs }
- } = window
- const btnRootNode = $refs.btn_href
+ it('applies variant class', async () => {
+ const wrapper = mount(Button, {
+ propsData: {
+ variant: 'danger'
+ }
+ })
- expect(btnRootNode).toBeElement('a')
- expect(btnRootNode.href).toBe('https://github.com/bootstrap-vue/bootstrap-vue')
+ expect(wrapper.is('button')).toBe(true)
+ expect(wrapper.attributes('type')).toBeDefined()
+ expect(wrapper.attributes('type')).toBe('button')
+ expect(wrapper.classes()).toContain('btn')
+ expect(wrapper.classes()).toContain('btn-danger')
+ expect(wrapper.classes().length).toBe(2)
})
- it('should use the given tag', async () => {
- const {
- app: { $refs }
- } = window
- const btnRootNode = $refs.btn_div
+ it('applies block class', async () => {
+ const wrapper = mount(Button, {
+ propsData: {
+ block: true
+ }
+ })
- expect(btnRootNode).toBeElement('div')
+ expect(wrapper.is('button')).toBe(true)
+ expect(wrapper.attributes('type')).toBeDefined()
+ expect(wrapper.attributes('type')).toBe('button')
+ expect(wrapper.classes()).toContain('btn')
+ expect(wrapper.classes()).toContain('btn-secondary')
+ expect(wrapper.classes()).toContain('btn-block')
+ expect(wrapper.classes().length).toBe(3)
})
- it('should use button when no tag is given', async () => {
- const {
- app: { $refs }
- } = window
- const btnRootNode = $refs.btn_no_tag
+ it('renders custom root element', async () => {
+ const wrapper = mount(Button, {
+ propsData: {
+ tag: 'div'
+ }
+ })
- expect(btnRootNode).toBeElement('button')
+ expect(wrapper.is('div')).toBe(true)
+ expect(wrapper.attributes('type')).not.toBeDefined()
+ expect(wrapper.classes()).toContain('btn')
+ expect(wrapper.classes()).toContain('btn-secondary')
+ expect(wrapper.classes().length).toBe(2)
+ expect(wrapper.attributes('role')).toBeDefined()
+ expect(wrapper.attributes('role')).toBe('button')
+ expect(wrapper.attributes('aria-disabled')).toBeDefined()
+ expect(wrapper.attributes('aria-disabled')).toBe('false')
+ expect(wrapper.attributes('tabindex')).toBeDefined()
+ expect(wrapper.attributes('tabindex')).toBe('0')
+ expect(wrapper.attributes('disabled')).not.toBeDefined()
+ expect(wrapper.attributes('aria-pressed')).not.toBeDefined()
+ expect(wrapper.attributes('autocomplete')).not.toBeDefined()
})
- it('should emit "click" event when clicked', async () => {
- const {
- app: { $refs }
- } = window
- const btn = $refs.btn_click
- const spy = jest.fn()
-
- btn.addEventListener('click', spy)
- btn.click()
+ it('button has attribute disabled when disabled set', async () => {
+ const wrapper = mount(Button, {
+ propsData: {
+ disabled: true
+ }
+ })
- expect(spy).toHaveBeenCalled()
+ expect(wrapper.is('button')).toBe(true)
+ expect(wrapper.attributes('type')).toBe('button')
+ expect(wrapper.classes()).toContain('btn')
+ expect(wrapper.classes()).toContain('btn-secondary')
+ expect(wrapper.classes()).toContain('disabled')
+ expect(wrapper.classes().length).toBe(3)
+ expect(wrapper.attributes('aria-disabled')).not.toBeDefined()
})
- it('should "click" event should emit with native event object', async () => {
- const {
- app: { $refs }
- } = window
- const btn = $refs.btn_click
- let event = null
-
- btn.addEventListener('click', e => (event = e))
- btn.click()
+ it('link has attribute aria-disabled when disabled set', async () => {
+ const wrapper = mount(Button, {
+ propsData: {
+ href: '/foo/bar',
+ disabled: true
+ }
+ })
- expect(event).toBeInstanceOf(MouseEvent)
+ expect(wrapper.is('a')).toBe(true)
+ expect(wrapper.classes()).toContain('btn')
+ expect(wrapper.classes()).toContain('btn-secondary')
+ expect(wrapper.classes()).toContain('disabled')
+ // Both b-button and b-link add hte class 'disabled'
+ // vue-functional-data-merge or Vue doesn't appear to de-dup classes
+ // expect(wrapper.classes().length).toBe(3)
+ // actually returns 4, as disabled is there twice
+ expect(wrapper.attributes('aria-disabled')).toBeDefined()
+ expect(wrapper.attributes('aria-disabled')).toBe('true')
})
- it('should be disabled and not emit click event with `disabled` prop true', async () => {
- const {
- app: { $refs }
- } = window
- const btn = $refs.btn_block_disabled
- const spy = jest.fn()
+ it('should emit click event when clicked', async () => {
+ let called = 0
+ let evt = null
+ const wrapper = mount(Button, {
+ listeners: {
+ click: e => {
+ evt = e
+ called++
+ }
+ }
+ })
- btn.addEventListener('click', spy)
- btn.click()
+ expect(wrapper.is('button')).toBe(true)
+ expect(called).toBe(0)
+ expect(evt).toEqual(null)
+ wrapper.find('button').trigger('click')
+ expect(called).toBe(1)
+ expect(evt).toBeInstanceOf(MouseEvent)
+ })
- expect(btn.disabled).toBe(true)
- expect(spy).not.toHaveBeenCalled()
+ it('should not emit click event when clicked and disabled', async () => {
+ let called = 0
+ const wrapper = mount(Button, {
+ propsData: {
+ disabled: true
+ },
+ listeners: {
+ click: e => {
+ called++
+ }
+ }
+ })
+
+ expect(wrapper.is('button')).toBe(true)
+ expect(called).toBe(0)
+ wrapper.find('button').trigger('click')
+ expect(called).toBe(0)
})
it('should not have `.active` class and `aria-pressed` when pressed is null', async () => {
- const { app } = window
- const vm = app.$refs.btn_pressed
-
- await setData(app, 'btnToggle', null)
- await nextTick()
+ const wrapper = mount(Button, {
+ propsData: {
+ pressed: null
+ }
+ })
- expect(vm).not.toHaveClass('active')
- expect(vm.getAttribute('aria-pressed')).toBeNull()
- vm.click()
- expect(app.btnToggle).toBeNull()
+ expect(wrapper.classes()).not.toContain('active')
+ expect(wrapper.attributes('aria-pressed')).not.toBeDefined()
+ wrapper.find('button').trigger('click')
+ expect(wrapper.classes()).not.toContain('active')
+ expect(wrapper.attributes('aria-pressed')).not.toBeDefined()
+ expect(wrapper.attributes('autocomplete')).not.toBeDefined()
})
it('should not have `.active` class and have `aria-pressed="false"` when pressed is false', async () => {
- const { app } = window
- const vm = app.$refs.btn_pressed
-
- await setData(app, 'btnToggle', false)
- await nextTick()
+ const wrapper = mount(Button, {
+ propsData: {
+ pressed: false
+ }
+ })
- expect(vm).not.toHaveClass('active')
- expect(vm.getAttribute('aria-pressed')).toBe('false')
+ expect(wrapper.classes()).not.toContain('active')
+ expect(wrapper.attributes('aria-pressed')).toBeDefined()
+ expect(wrapper.attributes('aria-pressed')).toBe('false')
+ expect(wrapper.attributes('autocomplete')).toBeDefined()
+ expect(wrapper.attributes('autocomplete')).toBe('off')
})
it('should have `.active` class and have `aria-pressed="true"` when pressed is true', async () => {
- const { app } = window
- const vm = app.$refs.btn_pressed
-
- await setData(app, 'btnToggle', true)
- await nextTick()
-
- vm.click()
+ const wrapper = mount(Button, {
+ propsData: {
+ pressed: true
+ }
+ })
- expect(vm).toHaveClass('active')
- expect(vm.getAttribute('aria-pressed')).toBe('true')
+ expect(wrapper.classes()).toContain('active')
+ expect(wrapper.attributes('aria-pressed')).toBeDefined()
+ expect(wrapper.attributes('aria-pressed')).toBe('true')
+ expect(wrapper.attributes('autocomplete')).toBeDefined()
+ expect(wrapper.attributes('autocomplete')).toBe('off')
})
it('pressed should have `.focus` class when focused', async () => {
- const { app } = window
- const btn = app.$refs.btn_pressed
-
- await setData(app, 'btnToggle', false)
- await nextTick()
-
- const focusinEvt = new FocusEvent('focusin')
- btn.dispatchEvent(focusinEvt)
- await nextTick()
- expect(btn).toHaveClass('focus')
+ const wrapper = mount(Button, {
+ propsData: {
+ pressed: false
+ }
+ })
- const focusoutEvt = new FocusEvent('focusout')
- btn.dispatchEvent(focusoutEvt)
- await nextTick()
- expect(btn).not.toHaveClass('focus')
+ expect(wrapper.classes()).not.toContain('focus')
+ wrapper.trigger('focusin')
+ expect(wrapper.classes()).toContain('focus')
+ wrapper.trigger('focusout')
+ expect(wrapper.classes()).not.toContain('focus')
})
it('should update the parent sync value on click and when pressed is not null', async () => {
- const { app } = window
- const vm = app.$refs.btn_pressed
-
- await setData(app, 'btnToggle', false)
- await nextTick()
-
- expect(app.btnToggle).toBe(false)
- expect(vm).not.toHaveClass('active')
- expect(vm.getAttribute('aria-pressed')).toBe('false')
- vm.click()
- await nextTick()
- expect(app.btnToggle).toBe(true)
- expect(vm).toHaveClass('active')
- expect(vm.getAttribute('aria-pressed')).toBe('true')
+ let called = 0
+ const values = []
+ const wrapper = mount(Button, {
+ propsData: {
+ pressed: false
+ },
+ listeners: {
+ 'update:pressed': val => {
+ values.push(val)
+ called++
+ }
+ }
+ })
+
+ expect(called).toBe(0)
+
+ wrapper.find('button').trigger('click')
+
+ expect(called).toBe(1)
+ expect(values[0]).toBe(true)
})
})
diff --git a/src/components/button/fixtures/button-close.html b/src/components/button/fixtures/button-close.html
deleted file mode 100644
index f1192872cab..00000000000
--- a/src/components/button/fixtures/button-close.html
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
- close
-
-
-
-
-
diff --git a/src/components/button/fixtures/button-close.js b/src/components/button/fixtures/button-close.js
deleted file mode 100644
index 94343157bdb..00000000000
--- a/src/components/button/fixtures/button-close.js
+++ /dev/null
@@ -1,11 +0,0 @@
-window.app = new Vue({
- el: '#app',
- data: {
- spies: []
- },
- methods: {
- handleClick() {
- this.spies.forEach(spy => spy.apply(undefined, arguments))
- }
- }
-})
diff --git a/src/components/button/fixtures/button.html b/src/components/button/fixtures/button.html
deleted file mode 100644
index 971755303ea..00000000000
--- a/src/components/button/fixtures/button.html
+++ /dev/null
@@ -1,63 +0,0 @@
-
-
-
-
-
- {{variant}} {{size}}
-
-
-
-
-
-
- Link button!
-
-
-
-
-
- I am a div
-
-
-
-
- I am a button
-
-
-
-
- Click me!
-
-
-
-
- Can't touch this
-
-
-
-
- Toggle Me
-
-
-
-
diff --git a/src/components/button/fixtures/button.js b/src/components/button/fixtures/button.js
deleted file mode 100644
index 8e1172350b1..00000000000
--- a/src/components/button/fixtures/button.js
+++ /dev/null
@@ -1,25 +0,0 @@
-window.app = new Vue({
- el: '#app',
- data: {
- variants: [
- 'primary',
- 'secondary',
- 'success',
- 'warning',
- 'danger',
- 'outline-primary',
- 'outline-secondary',
- 'outline-success',
- 'outline-warning',
- 'outline-danger',
- 'link'
- ],
- sizes: ['sm', '', 'lg'],
- btnToggle: null
- },
- methods: {
- handleClick(event) {
- // STUB!
- }
- }
-})