Skip to content
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

feat(compat): add Vue 3 support via @vue/compat, round 2 (fixes #5196) #6905

Merged
merged 34 commits into from Oct 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
2ce6672
chore(compat): introduce Vue3 testing infrastructure
xanf Nov 17, 2021
7b02d33
chore(compat): replace providing components with getter functions
xanf Nov 6, 2021
c096e2d
chore(compat): replace parent/root access with wrappers
xanf Nov 6, 2021
71b37ff
chore(compat): introduce vue3 compatibility wrapper
xanf Nov 17, 2021
f873121
chore(compat): update attrs mixin for vue3
xanf Nov 17, 2021
92db60b
chore(compat): implement component access from vnode for vue3
xanf Nov 9, 2021
9726a64
chore(compat): update listeners mixin for vue3
xanf Nov 17, 2021
358ee9b
chore(compat): delay first attempt to show image for nextTick
xanf Nov 17, 2021
e65f802
feature(vue3): replace transporter implementation with teleport
xanf Nov 17, 2021
35c888e
chore(compat): disable tests related to has-listener in Vue 3
xanf Nov 17, 2021
3f0a141
chore(compat): skip tbody-transition tests in Vue 3
xanf Nov 17, 2021
50afc56
chore(compat): disable subset of config specs due to localVue
xanf Nov 17, 2021
543b9f5
chore(compat): unify access to component instance from directive
xanf Nov 17, 2021
ea1269f
chore(compat): make tabs properly filter in Vue 3
xanf Nov 17, 2021
2d6660f
chore(compat): silence most warning from Vue 3 compat build
xanf Nov 17, 2021
b7e764a
chore: fix warnings about accessing undefined fields in render
xanf Nov 17, 2021
d035937
chore(compat): drop $children usage in tabs component
xanf Nov 20, 2021
05db323
chore(compat): do not pass extra props in link if there is no nuxt
xanf Nov 20, 2021
c7d1065
chore(compat): do not pass false value in BVTransition
xanf Nov 20, 2021
7e16f96
chore(compat): update hook event names for vue3 (now vnode events)
xanf Nov 20, 2021
411e0e9
chore(ci): add vue3 testing to pipeline
xanf Jan 28, 2022
ae4bac8
fix: update refs inside v-for to work for @vue/compat
xanf Oct 2, 2022
779dd69
chore(tests): Update tests to work with latest @vue/test-utils
xanf Oct 3, 2022
d8edafc
chore(test): simplify Vue3 detection
xanf Oct 3, 2022
fe13503
fix(vue3): do not rely on __vueParentComponent in tooltip
xanf Oct 3, 2022
725d31b
fix(compat): correctly handle undefined in slots
xanf Oct 4, 2022
c7699c8
chore(vue3): avoid patching global Vue.extend
xanf Oct 6, 2022
56cdff4
chore(form-radio): remove useless watcher
xanf Oct 6, 2022
975e4de
chore(ci): update prepare scripts
xanf Oct 12, 2022
d9cd860
chore: bump version
xanf Oct 15, 2022
7462a49
chore: bump @vue/test-utils version
xanf Oct 24, 2022
b69b4c5
chore(vue3): use vue-test-utils-compat v0.0.6
xanf Oct 24, 2022
b442471
chore: remove unused vars
xanf Oct 24, 2022
6ef2ba4
chore: ignore vue3-specific code
xanf Oct 24, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 0 additions & 3 deletions .eslintrc.js
Expand Up @@ -15,9 +15,6 @@ module.exports = {
es6: true,
'jest/globals': true
},
globals: {
Vue: true
},
rules: {
'no-unused-vars': [
'error',
Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/test.yml
Expand Up @@ -125,8 +125,19 @@ jobs:
- name: Test unit
run: yarn run test:unit --coverage --maxWorkers=2

- name: Test unit (Vue 3)
run: yarn run test:unit --coverage --maxWorkers=2
env:
USE_VUE3: '1'

- name: Merge coverage
run:
npx istanbul-merge --out ./coverage-final.json coverage/coverage-final.json
coverage-vue3/coverage-final.json

- name: CodeCov
uses: codecov/codecov-action@v3.1.1
with:
token: ${{ secrets.CODECOV_TOKEN }}
flags: unittests
files: ./coverage-final.json
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -4,6 +4,7 @@
.vercel/
.vscode/
coverage/
coverage-vue3/
dist/
docs-dist/
esm/
Expand Down
16 changes: 13 additions & 3 deletions jest.config.js
@@ -1,11 +1,21 @@
const useVue3 = 'USE_VUE3' in process.env

const moduleNameMapper = useVue3
? {
'^vue$': '@vue/compat',
'^@vue/test-utils$': '@vue/test-utils-vue3'
}
: {}

module.exports = {
testRegex: 'spec.js$',
moduleFileExtensions: ['js', 'vue'],
moduleNameMapper,
transform: {
'^.+\\.js$': 'babel-jest',
'.*\\.(vue)$': 'vue-jest'
'^.+\\.js$': 'babel-jest'
},
coverageDirectory: './coverage/',
transformIgnorePatterns: ['/node_modules(?![\\\\/]vue-test-utils-compat[\\\\/])'],
coverageDirectory: useVue3 ? './coverage-vue3' : './coverage/',
testEnvironmentOptions: {
pretendToBeVisual: true
},
Expand Down
11 changes: 7 additions & 4 deletions package.json
@@ -1,6 +1,6 @@
{
"name": "bootstrap-vue",
"version": "2.22.0",
"version": "2.23.0",
"description": "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 extensive and automated WAI-ARIA accessibility markup.",
"main": "./dist/bootstrap-vue.common.js",
"web": "./dist/bootstrap-vue.js",
Expand Down Expand Up @@ -66,7 +66,7 @@
"docs-gen": "cross-env NODE_ENV=docs nuxt generate -c docs/nuxt.config.js",
"lint": "eslint --ext .js,.md,.vue ./",
"postinstall": "opencollective || exit 0",
"prepare": "husky install",
"prepare": "husky install && yarn run build",
"prettify": "prettier --write '**/*.{js,json,md,scss,ts,vue}'",
"release": "yarn run prettify && yarn run test && yarn run build && yarn run release-notes && standard-version",
"release-notes": "jiti ./scripts/release-notes",
Expand Down Expand Up @@ -99,7 +99,10 @@
"@nuxtjs/robots": "^2.5.0",
"@nuxtjs/sitemap": "^2.4.0",
"@testing-library/jest-dom": "^5.12.0",
"@vue/compat": "^3.2.40",
"@vue/compiler-dom": "^3.2.40",
"@vue/test-utils": "^1.3.0",
"@vue/test-utils-vue3": "npm:@vue/test-utils@2.2.0",
"autoprefixer": "^10.4.0",
"babel-core": "^7.0.0-bridge.0",
"babel-eslint": "^10.1.0",
Expand Down Expand Up @@ -148,10 +151,10 @@
"standard-version": "^9.3.0",
"terser": "^5.15.0",
"vue": "^2.6.12",
"vue-jest": "^3.0.7",
"vue-router": "^3.5.1",
"vue-server-renderer": "^2.6.12",
"vue-template-compiler": "^2.6.12"
"vue-template-compiler": "^2.6.12",
"vue-test-utils-compat": "0.0.6"
},
"keywords": [
"Bootstrap",
Expand Down
6 changes: 3 additions & 3 deletions src/components/alert/alert.js
@@ -1,4 +1,3 @@
import { COMPONENT_UID_KEY, Vue } from '../../vue'
import { NAME_ALERT } from '../../constants/components'
import { EVENT_NAME_DISMISSED, EVENT_NAME_DISMISS_COUNT_DOWN } from '../../constants/events'
import {
Expand All @@ -7,13 +6,14 @@ import {
PROP_TYPE_STRING
} from '../../constants/props'
import { SLOT_NAME_DISMISS } from '../../constants/slots'
import { normalizeSlotMixin } from '../../mixins/normalize-slot'
import { requestAF } from '../../utils/dom'
import { isBoolean, isNumeric } from '../../utils/inspect'
import { makeModelMixin } from '../../utils/model'
import { toInteger } from '../../utils/number'
import { sortKeys } from '../../utils/object'
import { makeProp, makePropsConfigurable } from '../../utils/props'
import { normalizeSlotMixin } from '../../mixins/normalize-slot'
import { COMPONENT_UID_KEY, extend } from '../../vue'
import { BButtonClose } from '../button/button-close'
import { BVTransition } from '../transition/bv-transition'

Expand Down Expand Up @@ -68,7 +68,7 @@ export const props = makePropsConfigurable(
// --- Main component ---

// @vue/component
export const BAlert = /*#__PURE__*/ Vue.extend({
export const BAlert = /*#__PURE__*/ extend({
name: NAME_ALERT,
mixins: [modelMixin, normalizeSlotMixin],
props,
Expand Down
4 changes: 2 additions & 2 deletions src/components/aspect/aspect.js
@@ -1,4 +1,4 @@
import { Vue } from '../../vue'
import { extend } from '../../vue'
import { NAME_ASPECT } from '../../constants/components'
import { PROP_TYPE_NUMBER_STRING, PROP_TYPE_STRING } from '../../constants/props'
import { RX_ASPECT, RX_ASPECT_SEPARATOR } from '../../constants/regex'
Expand Down Expand Up @@ -26,7 +26,7 @@ export const props = makePropsConfigurable(
// --- Main component ---

// @vue/component
export const BAspect = /*#__PURE__*/ Vue.extend({
export const BAspect = /*#__PURE__*/ extend({
name: NAME_ASPECT,
mixins: [normalizeSlotMixin],
props,
Expand Down
8 changes: 4 additions & 4 deletions src/components/avatar/avatar-group.js
@@ -1,15 +1,15 @@
import { Vue } from '../../vue'
import { NAME_AVATAR_GROUP } from '../../constants/components'
import {
PROP_TYPE_BOOLEAN,
PROP_TYPE_BOOLEAN_STRING,
PROP_TYPE_NUMBER_STRING,
PROP_TYPE_STRING
} from '../../constants/props'
import { normalizeSlotMixin } from '../../mixins/normalize-slot'
import { mathMax, mathMin } from '../../utils/math'
import { toFloat } from '../../utils/number'
import { makeProp, makePropsConfigurable } from '../../utils/props'
import { normalizeSlotMixin } from '../../mixins/normalize-slot'
import { extend } from '../../vue'
import { computeSize } from './avatar'

// --- Props ---
Expand All @@ -33,11 +33,11 @@ export const props = makePropsConfigurable(
// --- Main component ---

// @vue/component
export const BAvatarGroup = /*#__PURE__*/ Vue.extend({
export const BAvatarGroup = /*#__PURE__*/ extend({
name: NAME_AVATAR_GROUP,
mixins: [normalizeSlotMixin],
provide() {
return { bvAvatarGroup: this }
return { getBvAvatarGroup: () => this }
},
props,
computed: {
Expand Down
9 changes: 6 additions & 3 deletions src/components/avatar/avatar.js
@@ -1,4 +1,4 @@
import { Vue } from '../../vue'
import { extend } from '../../vue'
import { NAME_AVATAR } from '../../constants/components'
import { EVENT_NAME_CLICK, EVENT_NAME_IMG_ERROR } from '../../constants/events'
import {
Expand Down Expand Up @@ -67,11 +67,11 @@ export const props = makePropsConfigurable(
// --- Main component ---

// @vue/component
export const BAvatar = /*#__PURE__*/ Vue.extend({
export const BAvatar = /*#__PURE__*/ extend({
name: NAME_AVATAR,
mixins: [normalizeSlotMixin],
inject: {
bvAvatarGroup: { default: null }
getBvAvatarGroup: { default: () => () => null }
},
props,
data() {
Expand All @@ -80,6 +80,9 @@ export const BAvatar = /*#__PURE__*/ Vue.extend({
}
},
computed: {
bvAvatarGroup() {
return this.getBvAvatarGroup()
},
computedSize() {
// Always use the avatar group size
const { bvAvatarGroup } = this
Expand Down
12 changes: 6 additions & 6 deletions src/components/avatar/avatar.spec.js
Expand Up @@ -250,7 +250,7 @@ describe('avatar', () => {
const wrapper1 = mount(BAvatar, {
provide: {
// Emulate `undefined`/`null` props
bvAvatarGroup: {}
getBvAvatarGroup: () => ({})
}
})

Expand All @@ -265,9 +265,9 @@ describe('avatar', () => {

const wrapper2 = mount(BAvatar, {
provide: {
bvAvatarGroup: {
getBvAvatarGroup: () => ({
variant: 'danger'
}
})
}
})

Expand All @@ -289,7 +289,7 @@ describe('avatar', () => {
},
provide: {
// Emulate `undefined`/`null` props
bvAvatarGroup: {}
getBvAvatarGroup: () => ({})
}
})

Expand All @@ -307,9 +307,9 @@ describe('avatar', () => {
size: '2em'
},
provide: {
bvAvatarGroup: {
getBvAvatarGroup: () => ({
size: '5em'
}
})
}
})

Expand Down
4 changes: 2 additions & 2 deletions src/components/badge/badge.js
@@ -1,4 +1,4 @@
import { Vue, mergeData } from '../../vue'
import { extend, mergeData } from '../../vue'
import { NAME_BADGE } from '../../constants/components'
import { PROP_TYPE_BOOLEAN, PROP_TYPE_STRING } from '../../constants/props'
import { omit, sortKeys } from '../../utils/object'
Expand All @@ -25,7 +25,7 @@ export const props = makePropsConfigurable(
// --- Main component ---

// @vue/component
export const BBadge = /*#__PURE__*/ Vue.extend({
export const BBadge = /*#__PURE__*/ extend({
name: NAME_BADGE,
functional: true,
props,
Expand Down
4 changes: 2 additions & 2 deletions src/components/breadcrumb/breadcrumb-item.js
@@ -1,4 +1,4 @@
import { Vue, mergeData } from '../../vue'
import { extend, mergeData } from '../../vue'
import { NAME_BREADCRUMB_ITEM } from '../../constants/components'
import { makePropsConfigurable } from '../../utils/props'
import { BBreadcrumbLink, props as BBreadcrumbLinkProps } from './breadcrumb-link'
Expand All @@ -10,7 +10,7 @@ export const props = makePropsConfigurable(BBreadcrumbLinkProps, NAME_BREADCRUMB
// --- Main component ---

// @vue/component
export const BBreadcrumbItem = /*#__PURE__*/ Vue.extend({
export const BBreadcrumbItem = /*#__PURE__*/ extend({
name: NAME_BREADCRUMB_ITEM,
functional: true,
props,
Expand Down
4 changes: 2 additions & 2 deletions src/components/breadcrumb/breadcrumb-link.js
@@ -1,4 +1,4 @@
import { Vue, mergeData } from '../../vue'
import { extend, mergeData } from '../../vue'
import { NAME_BREADCRUMB_LINK } from '../../constants/components'
import { PROP_TYPE_STRING } from '../../constants/props'
import { htmlOrText } from '../../utils/html'
Expand All @@ -21,7 +21,7 @@ export const props = makePropsConfigurable(
// --- Main component ---

// @vue/component
export const BBreadcrumbLink = /*#__PURE__*/ Vue.extend({
export const BBreadcrumbLink = /*#__PURE__*/ extend({
name: NAME_BREADCRUMB_LINK,
functional: true,
props,
Expand Down
4 changes: 2 additions & 2 deletions src/components/breadcrumb/breadcrumb.js
@@ -1,4 +1,4 @@
import { Vue, mergeData } from '../../vue'
import { extend, mergeData } from '../../vue'
import { NAME_BREADCRUMB } from '../../constants/components'
import { PROP_TYPE_ARRAY } from '../../constants/props'
import { isArray, isObject } from '../../utils/inspect'
Expand All @@ -18,7 +18,7 @@ export const props = makePropsConfigurable(
// --- Main component ---

// @vue/component
export const BBreadcrumb = /*#__PURE__*/ Vue.extend({
export const BBreadcrumb = /*#__PURE__*/ extend({
name: NAME_BREADCRUMB,
functional: true,
props,
Expand Down
4 changes: 2 additions & 2 deletions src/components/button-group/button-group.js
@@ -1,4 +1,4 @@
import { Vue, mergeData } from '../../vue'
import { extend, mergeData } from '../../vue'
import { NAME_BUTTON_GROUP } from '../../constants/components'
import { PROP_TYPE_BOOLEAN, PROP_TYPE_STRING } from '../../constants/props'
import { pick, sortKeys } from '../../utils/object'
Expand All @@ -21,7 +21,7 @@ export const props = makePropsConfigurable(
// --- Main component ---

// @vue/component
export const BButtonGroup = /*#__PURE__*/ Vue.extend({
export const BButtonGroup = /*#__PURE__*/ extend({
name: NAME_BUTTON_GROUP,
functional: true,
props,
Expand Down
4 changes: 2 additions & 2 deletions src/components/button-toolbar/button-toolbar.js
@@ -1,4 +1,4 @@
import { Vue } from '../../vue'
import { extend } from '../../vue'
import { NAME_BUTTON_TOOLBAR } from '../../constants/components'
import { PROP_TYPE_BOOLEAN } from '../../constants/props'
import { CODE_DOWN, CODE_LEFT, CODE_RIGHT, CODE_UP } from '../../constants/key-codes'
Expand Down Expand Up @@ -30,7 +30,7 @@ export const props = makePropsConfigurable(
// --- Main component ---

// @vue/component
export const BButtonToolbar = /*#__PURE__*/ Vue.extend({
export const BButtonToolbar = /*#__PURE__*/ extend({
name: NAME_BUTTON_TOOLBAR,
mixins: [normalizeSlotMixin],
props,
Expand Down
42 changes: 6 additions & 36 deletions src/components/button-toolbar/button-toolbar.spec.js
Expand Up @@ -108,42 +108,12 @@ describe('button-toolbar', () => {
const $btns = wrapper.findAll('button')
expect($btns).toBeDefined()
expect($btns.length).toBe(6)
expect(
$btns
.at(0)
.find('button[tabindex="-1"')
.exists()
).toBe(true)
expect(
$btns
.at(1)
.find('button[tabindex="-1"')
.exists()
).toBe(true)
expect(
$btns
.at(2)
.find('button[tabindex="-1"')
.exists()
).toBe(false) // Disabled button
expect(
$btns
.at(3)
.find('button[tabindex="-1"')
.exists()
).toBe(true)
expect(
$btns
.at(4)
.find('button[tabindex="-1"')
.exists()
).toBe(true)
expect(
$btns
.at(5)
.find('button[tabindex="-1"')
.exists()
).toBe(true)
expect($btns.at(0).element.matches('button[tabindex="-1"')).toBe(true)
expect($btns.at(1).element.matches('button[tabindex="-1"')).toBe(true)
expect($btns.at(2).element.matches('button[tabindex="-1"')).toBe(false) // Disabled button
expect($btns.at(3).element.matches('button[tabindex="-1"')).toBe(true)
expect($btns.at(4).element.matches('button[tabindex="-1"')).toBe(true)
expect($btns.at(5).element.matches('button[tabindex="-1"')).toBe(true)

wrapper.destroy()
})
Expand Down