` element with class
-`b-table-busy-slot`, which has one single `` with a `colspan` set to the number of fields.
-
-**Example of `table-busy` slot usage:**
-
-```html
-
-
-
Toggle Busy State
-
-
-
-
- Loading...
-
-
-
-
-
-
-
-
-```
-
-Also see the [**Using Items Provider Functions**](#using-items-provider-functions) below for
-additional information on the `busy` state.
-
-**Note:** All click related and hover events, and sort-changed events will **not** be emitted when
-the table is in the `busy` state.
+| Property | Type | Description |
+| --------- | ------ | ----------------------------------------------------------------------------- |
+| `columns` | Number | The number of columns in the rendered table |
+| `fields` | Array | Array of field definition objects (normalized to the array of objects format) |
## Custom Data Rendering
@@ -892,7 +896,7 @@ scoped field slot
-
+
@@ -915,8 +919,14 @@ scoped field slot
```
-**Warning:** Be cautious of using this to display user supplied content, **as script tags could be
-injected into your page!**
+
+ Warning: Be cautious of using the v-html
method to display user
+ supplied content, as it may make your application vulnerable to
+
+ XSS attacks , if you do not first
+ sanitize the
+ user supplied string.
+
### Formatter callback
@@ -1123,10 +1133,10 @@ is inserted before the header cells row, and is not encapsulated by `..
Slot `thead-top` can be optionally scoped, receiving an object with the following properties:
-| Property | Type | Description |
-| --------- | ------ | ---------------------------------------------------------------------------- |
-| `columns` | Number | The number of columns in the rendered table |
-| `fields` | Array | Array of field defintion objects (normalized to the array of objects format) |
+| Property | Type | Description |
+| --------- | ------ | ----------------------------------------------------------------------------- |
+| `columns` | Number | The number of columns in the rendered table |
+| `fields` | Array | Array of field definition objects (normalized to the array of objects format) |
## Row select support
@@ -1147,7 +1157,7 @@ as read-only.**
-
+
+ >
{{ selected }}
@@ -1188,6 +1198,16 @@ as read-only.**
```
+When table is selectable, it will have class `b-table-selectable`, and one of the following three
+classes (depending on which mode is in use), on the `` element:
+
+- `b-table-select-single`
+- `b-table-select-multi`
+- `b-table-select-range`
+
+When at least one row is selected the class `b-table-selecting` will be active on the ``
+element.
+
**Notes:**
- _Paging, filtering, or sorting will clear the selection. The `row-selected` event will be emitted
@@ -1238,7 +1258,7 @@ initially showing.
-
+
Details via check
@@ -1299,10 +1319,20 @@ pre-specify the column to be sorted, set the `sort-by` prop to the field's key.
direction by setting `sort-desc` to either `true` (for descending) or `false` (for ascending, the
default).
+- **Ascending**: Items are sorted lowest to highest (i.e. `A` to `Z`) and will be displayed with the
+ lowest value in the first row with progressively higher values in the following rows. The header
+ indicator arrow will point in the direction of lowest to highest. (i.e. down for ascending).
+- **Descending**: Items are sorted highest to lowest (i.e. `Z` to `A`) and will be displayed with
+ the highest value in the first row with progressively lower values in the following rows. The
+ header indicator arrow will point in the direction of lowest to highest (i.e. up for descending).
+
The props `sort-by` and `sort-desc` can be turned into _two-way_ (syncable) props by adding the
`.sync` modifier. Your bound variables will then be updated accordingly based on the current sort
criteria. See the [Vue docs](http://vuejs.org/v2/guide/components.html#sync-Modifier) for details on
-the `.sync` prop modifier
+the `.sync` prop modifier.
+
+Setting `sort-by` to a column that is not defined in the fields as `sortable` will result in the
+table not being sorted.
When the prop `foot-clone` is set, the footer headings will also allow sorting by clicking, even if
you have custom formatted footer field headers. To disable the sort icons and sorting via heading
@@ -1317,7 +1347,12 @@ presentational data.
```html
-
+
Sorting By: {{ sortBy }} , Sort Direction:
@@ -1377,6 +1412,8 @@ routine handle only certain fields (keys) or in the special case of virtual colu
The default sort-compare routine works similar to the following. Note the fourth argument (sorting
direction) is **not** used in the sort comparison:
+
+
```js
function sortCompare(a, b, key) {
if (typeof a[key] === 'number' && typeof b[key] === 'number') {
@@ -1417,16 +1454,19 @@ with a single argument containing the context object of ``. See the
[Detection of sorting change](#detection-of-sorting-change) section below for details about the
sort-changed event and the context object.
-### Change sort direction
+### Change initial sort direction
Control the order in which ascending and descending sorting is applied when a sortable column header
is clicked, by using the `sort-direction` prop. The default value `'asc'` applies ascending sort
-first. To reverse the behavior and sort in descending direction first, set it to `'desc'`.
+first (when a column is not currently sorted). To reverse the behavior and sort in descending
+direction first, set it to `'desc'`.
-If you don't want the sorting direction to change at all when clicking another sortable column
-header, set `sort-direction` to `'last'`.
+If you don't want the current sorting direction to change when clicking another sortable column
+header, set `sort-direction` to `'last'`. This will maintain the sorting direction of the previously
+sorted column.
-For individual column sort directions, specify the property `sortDirection` in `fields`. See the
+For individual column initial sort direction (which applies when the column transitions from
+unsorted to sorted), specify the property `sortDirection` in `fields`. See the
[Complete Example](#complete-example) below for an example of using this feature.
## Filtering
@@ -1554,7 +1594,7 @@ table#table-transition-example .flip-list-move {
small
primary-key="a"
:tbody-transition-props="transProps"
- />
+ >
@@ -1592,6 +1632,8 @@ function to provide the row data (items), by specifying a function reference via
The provider function is called with the following signature:
+
+
```js
provider(ctx, [callback])
```
@@ -1613,6 +1655,8 @@ method.
**Example: returning an array of data (synchronous):**
+
+
```js
function myProvider(ctx) {
let items = []
@@ -1626,18 +1670,20 @@ function myProvider(ctx) {
**Example: Using callback to return data (asynchronous):**
+
+
```js
function myProvider(ctx, callback) {
- let params = '?page=' + ctx.currentPage + '&size=' + ctx.perPage
+ const params = '?page=' + ctx.currentPage + '&size=' + ctx.perPage
this.fetchData('/some/url' + params)
.then(data => {
// Pluck the array of items off our axios response
- let items = data.items
+ const items = data.items
// Provide the array of items to the callback
callback(items)
})
- .catch(error => {
+ .catch(() => {
callback([])
})
@@ -1648,6 +1694,8 @@ function myProvider(ctx, callback) {
**Example: Using a Promise to return data (asynchronous):**
+
+
```js
function myProvider(ctx) {
let promise = axios.get('/some/url?page=' + ctx.currentPage + '&size=' + ctx.perPage)
@@ -1666,7 +1714,8 @@ function myProvider(ctx) {
`
` automatically tracks/controls it's `busy` state when items provider functions are used,
however it also provides a `busy` prop that can be used either to override the inner `busy` state,
-or to monitor ``'s current busy state in your application using the 2-way `.sync` modifier.
+or to monitor ``'s current busy state in your application using the 2-way `.sync`
+modifier.
**Note:** in order to allow `` fully track it's `busy` state, the custom items provider
function should handle errors from data sources and return an empty array to ``.
@@ -1682,7 +1731,7 @@ function should handle errors from data sources and return an empty array to `
+ >
@@ -1764,7 +1813,7 @@ Or by calling the `refresh()` method on the table reference
```html
-
+
```
@@ -1783,7 +1832,7 @@ have changed.
```html
-
+
```
@@ -1791,10 +1840,12 @@ The `sort-changed` event provides a single argument of the table's current state
This context object has the same format as used by items provider functions.
```js
-methods: {
- sortingChanged (ctx) {
- // ctx.sortBy ==> Field key for sorting by (or null for no sorting)
- // ctx.sortDesc ==> true if sorting descending, false otherwise
+export default {
+ methods: {
+ sortingChanged(ctx) {
+ // ctx.sortBy ==> Field key for sorting by (or null for no sorting)
+ // ctx.sortDesc ==> true if sorting descending, false otherwise
+ }
}
}
```
@@ -1805,7 +1856,7 @@ details).
```html
-
+
```
@@ -1818,12 +1869,22 @@ When `` is mounted in the document, it will automatically trigger a pro
## Table accessibility notes
+When a column (field) is sortable, the header (and footer) heading cells will also be placed into
+the document tab sequence for accessibility.
+
When the table is in `selectable` mode, or if there is a `row-clicked` event listener registered,
all data item rows (`` elements) will be placed into the document tab sequence (via
`tabindex="0"`) to allow keyboard-only and screen reader users the ability to click the rows.
-When a column (field) is sortable, the header (and footer) heading cells will also be placed into
-the document tab sequence for accessibility.
+When the table items rows are in the table sequence, they will also support basic keyboard
+navigation when focused:
+
+- DOWN will move to the next row
+- UP will move to the previous row
+- END or DOWN +SHIFT will move to the last row
+- HOME or UP +SHIFT will move to the first row
+- ENTER or SPACE to click the row. SHIFT and CTRL
+ modifiers will also work (depending on the table selectable mode).
Note the following row based events/actions are not considered accessible, and should only be used
if the functionality is non critical or can be provided via other means:
@@ -1856,7 +1917,7 @@ differences between operating systems, this too is not a preventable default beh
-
+
Clear
@@ -1870,7 +1931,7 @@ differences between operating systems, this too is not a preventable default beh
-- none --
-
+
Asc Desc
@@ -1890,7 +1951,7 @@ differences between operating systems, this too is not a preventable default beh
-
+
@@ -1938,16 +1999,16 @@ differences between operating systems, this too is not a preventable default beh
+ >
-
+
{{ modalInfo.content }}
diff --git a/src/components/table/_table.scss b/src/components/table/_table.scss
index 14f20c57192..aa6e24c516d 100644
--- a/src/components/table/_table.scss
+++ b/src/components/table/_table.scss
@@ -159,7 +159,15 @@
}
/* b-table: selectable rows */
-table.b-table.b-table-selectable > tbody > tr {
- cursor: pointer;
- // user-select: none;
+.b-table.table.b-table-selectable {
+ & > tbody > tr {
+ cursor: pointer;
+ }
+
+ &.b-table-selecting {
+ // Disabled text-selection when in mode range when at least one row selected
+ &.b-table-select-range > tbody > tr {
+ user-select: none;
+ }
+ }
}
diff --git a/src/components/table/fixtures/table.html b/src/components/table/fixtures/table.html
deleted file mode 100644
index e81446a62eb..00000000000
--- a/src/components/table/fixtures/table.html
+++ /dev/null
@@ -1,148 +0,0 @@
-
-
-
Basic table
-
-
- {{ data.value.first }} {{ data.value.last }}
-
-
- {{ data.value ? 'Yes' : 'No' }}
-
-
- Details
-
-
-
-
-
- {{ data.value.first }} {{ data.value.last }}
-
-
-
-
-
-
-
-
-
-
- Table Caption
-
-
-
-
-
-
-
Paginated Table
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ data.value.first }} {{ data.value.last }}
-
-
- Showing {{ visibleRecords.length }} People
-
-
- {{ data.value ? 'Active' : 'Inactive' }}
-
-
- Select
-
-
-
-
-
- Selected: {{ selectedRecords.length }}
-
-
-
-
Dark Table
-
-
-
- {{ data.value.first }} {{ data.value.last }}
-
-
- {{ data.value ? 'Active' : 'Inactive' }}
-
-
- Details
-
-
-
-
Provider Test Table
-
-
-
-
-
-
-
- {{ data.value.first }} {{ data.value.last }}
-
-
- {{ data.value ? 'Active' : 'Inactive' }}
-
-
- Details
-
-
-
-
-
diff --git a/src/components/table/fixtures/table.js b/src/components/table/fixtures/table.js
deleted file mode 100644
index 15a3a3c3207..00000000000
--- a/src/components/table/fixtures/table.js
+++ /dev/null
@@ -1,175 +0,0 @@
-window.app = new Vue({
- el: '#app',
- data: {
- fields: {
- name: {
- label: 'Person Full name',
- sortable: true,
- tdClass: 'bg-primary',
- tdAttr: { title: 'Person Full name' }
- },
- age: {
- label: 'Person age',
- sortable: true,
- formatter: 'formatAge',
- tdClass: ['bg-primary', 'text-dark']
- },
- isActive: {
- label: 'is Active',
- tdClass: (value, key, item) => {
- return 'bg-danger'
- },
- tdAttr: (value, key, item) => {
- return { title: 'is Active' }
- }
- },
- actions: {
- label: 'Actions',
- tdClass: 'formatCell',
- tdAttr: 'formatCellAttrs'
- }
- },
- currentPage: 1,
- perPage: 5,
- filter: null,
- selectedRecords: [],
- visibleRecords: [],
- isBusy: false,
- providerType: 'array',
- secondaryItems: [
- {
- isActive: false,
- age: 26,
- _rowVariant: 'success',
- name: 'Mitzi'
- }
- ],
- items: [
- {
- isActive: true,
- age: 40,
- name: { first: 'Dickerson', last: 'Macdonald' }
- },
- {
- isActive: false,
- age: 21,
- name: { first: 'Larsen', last: 'Shaw' }
- },
- {
- isActive: false,
- age: 26,
- _rowVariant: 'success',
- name: { first: 'Mitzi', last: 'Navarro' }
- },
- {
- isActive: false,
- age: 22,
- name: { first: 'Geneva', last: 'Wilson' }
- },
- {
- isActive: true,
- age: 38,
- name: { first: 'Jami', last: 'Carney' }
- },
- {
- isActive: false,
- age: 27,
- name: { first: 'Essie', last: 'Dunlap' }
- },
- {
- isActive: true,
- age: 65,
- name: { first: 'Alfred', last: 'Macdonald' }
- },
- {
- isActive: false,
- age: 21,
- name: { first: 'Lauren', last: 'Shaw' }
- },
- {
- isActive: false,
- age: 29,
- name: { first: 'Mini', last: 'Navarro' }
- },
- {
- isActive: false,
- age: 22,
- name: { first: 'Frank', last: 'Wilson' }
- },
- {
- isActive: true,
- age: 38,
- name: { first: 'Jami-Lee', last: 'Curtis' }
- },
- {
- isActive: false,
- age: 72,
- name: { first: 'Elsie', last: 'Dunlap' }
- }
- ]
- },
- computed: {
- provider() {
- // we are using provider wrappers here to trigger a reload
- switch (this.providerType) {
- case 'promise':
- return this._promiseProvider
- case 'callback':
- return this._callbackProvider
- default:
- return this._arrayProvider
- }
- }
- },
- methods: {
- details(item) {
- /* eslint-disable no-alert */
- alert(JSON.stringify(item))
- },
- _arrayProvider(ctx) {
- return this._provider(ctx)
- },
- _callbackProvider(ctx, cb) {
- return this._provider(ctx, cb)
- },
- _promiseProvider(ctx) {
- return this._provider(ctx)
- },
- _provider(ctx, cb) {
- const items = this.items.slice()
-
- switch (this.providerType) {
- case 'callback':
- setTimeout(() => {
- cb(items)
- }, 1)
- return
- case 'promise':
- const p = new Promise(resolve => setTimeout(resolve, 1))
- return p.then(() => {
- return items
- })
- default:
- return items
- }
- },
- formatAge(value) {
- return `${value} years old`
- },
- formatCell(value, key, item) {
- return ['bg-primary', 'text-light']
- },
- formatCellAttrs(value, key, item) {
- return { title: 'Actions' }
- },
- styleRow(item) {
- if (!item) {
- return
- }
- return {
- 'tr-start-with-l': item.name.first.charAt(0) === 'L',
- 'tr-last-name-macdonald': item.name.last === 'Macdonald'
- }
- }
- }
-})
diff --git a/src/components/table/helpers/mixin-empty.js b/src/components/table/helpers/mixin-empty.js
index 10bcdacb076..dcb396ba006 100644
--- a/src/components/table/helpers/mixin-empty.js
+++ b/src/components/table/helpers/mixin-empty.js
@@ -54,7 +54,7 @@ export default {
{
attrs: {
colspan: String(this.computedFields.length),
- role: this.isStacked ? 'cell' : null
+ role: 'cell'
}
},
[h('div', { attrs: { role: 'alert', 'aria-live': 'polite' } }, [$empty])]
@@ -69,7 +69,7 @@ export default {
? this.tbodyTrClass(null, 'row-empty')
: this.tbodyTrClass
],
- attrs: this.isStacked ? { role: 'row' } : {}
+ attrs: { role: 'row' }
},
[$empty]
)
diff --git a/src/components/table/helpers/mixin-filtering.js b/src/components/table/helpers/mixin-filtering.js
new file mode 100644
index 00000000000..a4b1030b739
--- /dev/null
+++ b/src/components/table/helpers/mixin-filtering.js
@@ -0,0 +1,202 @@
+import stringifyRecordValues from './stringify-record-values'
+import looseEqual from '../../../utils/loose-equal'
+import warn from '../../../utils/warn'
+
+const DEPRECATION_MSG =
+ 'Supplying a function to prop "filter" is deprecated. Use "filter-function" instead.'
+
+export default {
+ props: {
+ filter: {
+ // Pasing a function to filter is deprecated and should be avoided
+ type: [String, RegExp, Object, Array, Function],
+ default: null,
+ deprecated: DEPRECATION_MSG
+ },
+ filterFunction: {
+ type: Function,
+ default: null
+ }
+ },
+ data() {
+ return {
+ // Flag for displaying which empty slot to show, and for some event triggering.
+ isFiltered: false
+ }
+ },
+ computed: {
+ localFiltering() {
+ return this.hasProvider ? !!this.noProviderFiltering : true
+ },
+ filteredCheck() {
+ // For watching changes to filteredItems vs localItems
+ return {
+ filteredItems: this.filteredItems,
+ localItems: this.localItems,
+ localFilter: this.localFilter
+ }
+ },
+ localFilter() {
+ // Returns a sanitized/normalized version of filter prop
+ if (typeof this.filter === 'function') {
+ // this.localFilterFn will contain the correct function ref.
+ // Deprecate setting prop filter to a function
+ /* istanbul ignore next */
+ return ''
+ } else if (
+ typeof this.filterFunction !== 'function' &&
+ !(typeof this.filter === 'string' || this.filter instanceof RegExp)
+ ) {
+ // Using internal filter function, which only accepts string or regexp at the moment
+ return ''
+ } else {
+ // Could be a string, object or array, as needed by external filter function
+ return this.filter
+ }
+ },
+ localFilterFn() {
+ let filter = this.filter
+ let filterFn = this.filterFunction
+ // Sanitized/normalize filter-function prop
+ if (typeof filterFn === 'function') {
+ return filterFn
+ } else if (typeof filter === 'function') {
+ // Deprecate setting prop filter to a function
+ /* istanbul ignore next */
+ warn(`b-table: ${DEPRECATION_MSG}`)
+ /* istanbul ignore next */
+ return filter
+ } else {
+ // no filterFunction, so signal to use internal filter function
+ return null
+ }
+ },
+ filteredItems() {
+ // Returns the records in localItems that match the filter criteria.
+ // Returns the original localItems array if not sorting
+ let items = this.localItems || []
+ const criteria = this.localFilter
+ const filterFn =
+ this.filterFnFactory(this.localFilterFn, criteria) || this.defaultFilterFnFactory(criteria)
+
+ // We only do local filtering if requested, and if the are records to filter and
+ // if a filter criteria was specified
+ if (this.localFiltering && filterFn && items.length > 0) {
+ items = items.filter(filterFn)
+ }
+ return items
+ }
+ },
+ watch: {
+ // Watch for changes to the filter criteria and filtered items vs localItems).
+ // And set visual state and emit events as required
+ filteredCheck({ filteredItems, localItems, localFilter }) {
+ // Determine if the dataset is filtered or not
+ let isFiltered
+ if (!localFilter) {
+ // If filter criteria is falsey
+ isFiltered = false
+ } else if (looseEqual(localFilter, []) || looseEqual(localFilter, {})) {
+ // If filter criteria is an empty array or object
+ isFiltered = false
+ } else if (localFilter) {
+ // if Filter criteria is truthy
+ isFiltered = true
+ } else {
+ /* istanbul ignore next: rare chance of reaching this else */
+ isFiltered = false
+ }
+ if (isFiltered) {
+ this.$emit('filtered', filteredItems, filteredItems.length)
+ }
+ this.isFiltered = isFiltered
+ },
+ isFiltered(newVal, oldVal) {
+ if (newVal === false && oldVal === true) {
+ // We need to emit a filtered event if isFiltered transitions from true to
+ // false so that users can update their pagination controls.
+ this.$emit('filtered', this.localItems, this.localItems.length)
+ }
+ }
+ },
+ created() {
+ // Set the initial filtered state.
+ // In a nextTick so that we trigger a filtered event if needed
+ this.$nextTick(() => {
+ this.isFiltered = Boolean(this.localFilter)
+ })
+ },
+ methods: {
+ // Filter Function factories
+ filterFnFactory(filterFn, criteria) {
+ // Wrapper factory for external filter functions.
+ // Wrap the provided filter-function and return a new function.
+ // Returns null if no filter-function defined or if criteria is falsey.
+ // Rather than directly grabbing this.computedLocalFilterFn or this.filterFunction
+ // we have it passed, so that the caller computed prop will be reactive to changes
+ // in the original filter-function (as this routine is a method)
+ if (
+ !filterFn ||
+ typeof filterFn !== 'function' ||
+ !criteria ||
+ looseEqual(criteria, []) ||
+ looseEqual(criteria, {})
+ ) {
+ return null
+ }
+
+ // Build the wrapped filter test function, passing the criteria to the provided function
+ const fn = item => {
+ // Generated function returns true if the criteria matches part
+ // of the serialized data, otherwise false
+ return filterFn(item, criteria)
+ }
+
+ // Return the wrapped function
+ return fn
+ },
+ defaultFilterFnFactory(criteria) {
+ // Generates the default filter function, using the given filter criteria
+ if (!criteria || !(typeof criteria === 'string' || criteria instanceof RegExp)) {
+ // Built in filter can only support strings or RegExp criteria (at the moment)
+ return null
+ }
+
+ // Build the regexp needed for filtering
+ let regexp = criteria
+ if (typeof regexp === 'string') {
+ // Escape special RegExp characters in the string and convert contiguous
+ // whitespace to \s+ matches
+ const pattern = criteria
+ .replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')
+ .replace(/[\s\uFEFF\xA0]+/g, '\\s+')
+ // Build the RegExp (no need for global flag, as we only need
+ // to find the value once in the string)
+ regexp = new RegExp(`.*${pattern}.*`, 'i')
+ }
+
+ // Generate the wrapped filter test function to use
+ const fn = item => {
+ // This searches all row values (and sub property values) in the entire (excluding
+ // special _ prefixed keys), because we convert the record to a space-separated
+ // string containing all the value properties (recursively), even ones that are
+ // not visible (not specified in this.fields).
+ //
+ // TODO: Enable searching on formatted fields and scoped slots
+ // TODO: Should we filter only on visible fields (i.e. ones in this.fields) by default?
+ // TODO: Allow for searching on specific fields/key, this could be combined with the previous TODO
+ // TODO: Give stringifyRecordValues extra options for filtering (i.e. passing the
+ // fields definition and a reference to $scopedSlots)
+ //
+ // Generated function returns true if the criteria matches part of
+ // the serialized data, otherwise false
+ // We set lastIndex = 0 on regex in case someone uses the /g global flag
+ regexp.lastIndex = 0
+ return regexp.test(stringifyRecordValues(item))
+ }
+
+ // Return the generated function
+ return fn
+ }
+ }
+}
diff --git a/src/components/table/helpers/mixin-items.js b/src/components/table/helpers/mixin-items.js
new file mode 100644
index 00000000000..c919933c5da
--- /dev/null
+++ b/src/components/table/helpers/mixin-items.js
@@ -0,0 +1,57 @@
+import normalizeFields from './normalize-fields'
+import { isArray } from '../../../utils/array'
+
+export default {
+ props: {
+ items: {
+ type: [Array, Function],
+ default() /* istanbul ignore next */ {
+ return []
+ }
+ },
+ fields: {
+ // Object format is deprecated and should be avoided
+ type: [Array, Object],
+ default: null
+ },
+ primaryKey: {
+ // Primary key for record.
+ // If provided the value in each row must be unique!!!
+ type: String,
+ default: null
+ }
+ },
+ data() {
+ return {
+ // Our local copy of the items. Must be an array
+ localItems: isArray(this.items) ? this.items.slice() : []
+ }
+ },
+ computed: {
+ computedFields() {
+ // We normalize fields into an array of objects
+ // [ { key:..., label:..., ...}, {...}, ..., {..}]
+ return normalizeFields(this.fields, this.localItems)
+ },
+ computedFieldsObj() /* istanbul ignore next: not using at the moment */ {
+ // Fields as a simple lookup hash object
+ // Mainly for scopedSlots for convenience
+ return this.computedFields.reduce((f, obj) => {
+ obj[f.key] = f
+ return obj
+ }, {})
+ }
+ },
+ watch: {
+ items(newItems) {
+ /* istanbul ignore else */
+ if (isArray(newItems)) {
+ // Set localItems/filteredItems to a copy of the provided array
+ this.localItems = newItems.slice()
+ } else if (newItems === null || newItems === undefined) {
+ /* istanbul ignore next */
+ this.localItems = []
+ }
+ }
+ }
+}
diff --git a/src/components/table/helpers/mixin-pagination.js b/src/components/table/helpers/mixin-pagination.js
new file mode 100644
index 00000000000..cb668244848
--- /dev/null
+++ b/src/components/table/helpers/mixin-pagination.js
@@ -0,0 +1,29 @@
+export default {
+ props: {
+ perPage: {
+ type: [Number, String],
+ default: 0
+ },
+ currentPage: {
+ type: [Number, String],
+ default: 1
+ }
+ },
+ computed: {
+ localPaging() {
+ return this.hasProvider ? !!this.noProviderPaging : true
+ },
+ paginatedItems() {
+ let items = this.sortedItems || []
+ const currentPage = Math.max(parseInt(this.currentPage, 10) || 1, 1)
+ const perPage = Math.max(parseInt(this.perPage, 10) || 0, 0)
+ // Apply local pagination
+ if (this.localPaging && !!perPage) {
+ // Grab the current page of data (which may be past filtered items limit)
+ items = items.slice((currentPage - 1) * perPage, currentPage * perPage)
+ }
+ // Return the items to display in the table
+ return items
+ }
+ }
+}
diff --git a/src/components/table/helpers/mixin-provider.js b/src/components/table/helpers/mixin-provider.js
index ce564d10a9a..8df30811883 100644
--- a/src/components/table/helpers/mixin-provider.js
+++ b/src/components/table/helpers/mixin-provider.js
@@ -54,6 +54,12 @@ export default {
},
watch: {
// Provider update triggering
+ items(newVal, oldVal) {
+ // If a new provider has been specified, trigger an update
+ if (this.hasProvider || newVal instanceof Function) {
+ this.$nextTick(this._providerUpdate)
+ }
+ },
providerTriggerContext(newVal, oldVal) {
// Trigger the provider to update as the relevant context values have changed.
if (!looseEqual(newVal, oldVal)) {
diff --git a/src/components/table/helpers/mixin-selectable.js b/src/components/table/helpers/mixin-selectable.js
index b350dc9e893..2eaba14ee03 100644
--- a/src/components/table/helpers/mixin-selectable.js
+++ b/src/components/table/helpers/mixin-selectable.js
@@ -1,5 +1,5 @@
import looseEqual from '../../../utils/loose-equal'
-import { isArray } from '../../../utils/array'
+import { isArray, arrayIncludes } from '../../../utils/array'
import sanitizeRow from './sanitize-row'
export default {
@@ -23,6 +23,29 @@ export default {
selectedLastRow: -1
}
},
+ computed: {
+ selectableTableClasses() {
+ const selectable = this.selectable
+ const isSelecting = selectable && this.selectedRows && this.selectedRows.some(Boolean)
+ return {
+ 'b-table-selectable': selectable,
+ [`b-table-select-${this.selectMode}`]: selectable,
+ 'b-table-selecting': isSelecting
+ }
+ },
+ selectableTableAttrs() {
+ return {
+ 'aria-multiselectable': this.selectableIsMultiSelect
+ }
+ },
+ selectableIsMultiSelect() {
+ if (this.selectable) {
+ return arrayIncludes(['range', 'multi'], this.selectMode) ? 'true' : 'false'
+ } else {
+ return null
+ }
+ }
+ },
watch: {
computedItems(newVal, oldVal) {
// Reset for selectable
@@ -72,17 +95,18 @@ export default {
isRowSelected(idx) {
return Boolean(this.selectedRows[idx])
},
- rowSelectedClasses(idx) {
- if (this.selectable) {
- const rowSelected = this.isRowSelected(idx)
- const base = this.dark ? 'bg' : 'table'
- const variant = this.selectedVariant
- return {
- 'b-row-selected': rowSelected,
- [`${base}-${variant}`]: rowSelected && variant
- }
- } else {
- return {}
+ selectableRowClasses(idx) {
+ const rowSelected = this.isRowSelected(idx)
+ const base = this.dark ? 'bg' : 'table'
+ const variant = this.selectedVariant
+ return {
+ 'b-table-row-selected': this.selectable && rowSelected,
+ [`${base}-${variant}`]: this.selectable && rowSelected && variant
+ }
+ },
+ selectableRowAttrs(idx) {
+ return {
+ 'aria-selected': !this.selectable ? null : this.isRowSelected(idx) ? 'true' : 'false'
}
},
clearSelected() {
@@ -125,7 +149,6 @@ export default {
idx <= Math.max(this.selectedLastRow, index);
idx++
) {
- // this.$set(this.selectedRows, idx, true)
selectedRows[idx] = true
}
selected = true
@@ -138,7 +161,6 @@ export default {
this.selectedLastRow = selected ? index : -1
}
}
- // this.$set(this.selectedRows, index, selected)
selectedRows[index] = selected
this.selectedRows = selectedRows
}
diff --git a/src/components/table/helpers/mixin-sorting.js b/src/components/table/helpers/mixin-sorting.js
new file mode 100644
index 00000000000..06b617e9707
--- /dev/null
+++ b/src/components/table/helpers/mixin-sorting.js
@@ -0,0 +1,252 @@
+import stableSort from '../../../utils/stable-sort'
+import startCase from '../../../utils/startcase'
+import { arrayIncludes } from '../../../utils/array'
+import defaultSortCompare from './default-sort-compare'
+
+export default {
+ props: {
+ sortBy: {
+ type: String,
+ default: null
+ },
+ sortDesc: {
+ // To Do: Make this tri-state: true, false, null
+ type: Boolean,
+ default: false
+ },
+ sortDirection: {
+ // This prop is named incorrectly.
+ // It should be initialSortDirection
+ // As it is a bit misleading (not to mention screws up
+ // the Aria Label on the headers)
+ type: String,
+ default: 'asc',
+ validator: direction => arrayIncludes(['asc', 'desc', 'last'], direction)
+ },
+ sortCompare: {
+ type: Function,
+ default: null
+ },
+ noSortReset: {
+ // Another prop that should have had a better name.
+ // It should be noSortClear (on non-sortable headers).
+ // We will need to make sure the documentation is clear on what
+ // this prop does (as well as in the code for future reference)
+ type: Boolean,
+ default: false
+ },
+ labelSortAsc: {
+ type: String,
+ default: 'Click to sort Ascending'
+ },
+ labelSortDesc: {
+ type: String,
+ default: 'Click to sort Descending'
+ },
+ labelSortClear: {
+ type: String,
+ default: 'Click to clear sorting'
+ },
+ noLocalSorting: {
+ type: Boolean,
+ default: false
+ },
+ noFooterSorting: {
+ type: Boolean,
+ default: false
+ }
+ },
+ data() {
+ return {
+ localSortBy: this.sortBy || '',
+ localSortDesc: this.sortDesc || false
+ }
+ },
+ computed: {
+ localSorting() {
+ return this.hasProvider ? !!this.noProviderSorting : !this.noLocalSorting
+ },
+ isSortable() {
+ return this.computedFields.some(f => f.sortable)
+ },
+ sortedItems() {
+ // Sorts the filtered items and returns a new array of the sorted items
+ // or the original items array if not sorted.
+ let items = (this.filteredItems || []).slice()
+ const sortBy = this.localSortBy
+ const sortDesc = this.localSortDesc
+ const sortCompare = this.sortCompare
+ const localSorting = this.localSorting
+ if (sortBy && localSorting) {
+ // stableSort returns a new array, and leaves the original array intact
+ return stableSort(items, (a, b) => {
+ let result = null
+ if (typeof sortCompare === 'function') {
+ // Call user provided sortCompare routine
+ result = sortCompare(a, b, sortBy, sortDesc)
+ }
+ if (result === null || result === undefined || result === false) {
+ // Fallback to built-in defaultSortCompare if sortCompare
+ // is not defined or returns null/false
+ result = defaultSortCompare(a, b, sortBy)
+ }
+ // Negate result if sorting in descending order
+ return (result || 0) * (sortDesc ? -1 : 1)
+ })
+ }
+ return items
+ }
+ },
+ watch: {
+ isSortable(newVal, oldVal) /* istanbul ignore next: pain in the butt to test */ {
+ if (newVal) {
+ if (this.isSortable) {
+ this.$on('head-clicked', this.handleSort)
+ }
+ } else {
+ this.$off('head-clicked', this.handleSort)
+ }
+ },
+ sortDesc(newVal, oldVal) {
+ if (newVal === this.localSortDesc) {
+ /* istanbul ignore next */
+ return
+ }
+ this.localSortDesc = newVal || false
+ },
+ sortBy(newVal, oldVal) {
+ if (newVal === this.localSortBy) {
+ /* istanbul ignore next */
+ return
+ }
+ this.localSortBy = newVal || null
+ },
+ // Update .sync props
+ localSortDesc(newVal, oldVal) {
+ // Emit update to sort-desc.sync
+ if (newVal !== oldVal) {
+ this.$emit('update:sortDesc', newVal)
+ }
+ },
+ localSortBy(newVal, oldVal) {
+ if (newVal !== oldVal) {
+ this.$emit('update:sortBy', newVal)
+ }
+ }
+ },
+ created() {
+ if (this.isSortable) {
+ this.$on('head-clicked', this.handleSort)
+ }
+ },
+ methods: {
+ // Handlers
+ // Need to move from thead-mixin
+ handleSort(key, field, evt, isFoot) {
+ if (!this.isSortable) {
+ /* istanbul ignore next */
+ return
+ }
+ if (isFoot && this.noFooterSorting) {
+ return
+ }
+ // TODO: make this tri-state sorting
+ // cycle desc => asc => none => desc => ...
+ let sortChanged = false
+ const toggleLocalSortDesc = () => {
+ const sortDirection = field.sortDirection || this.sortDirection
+ if (sortDirection === 'asc') {
+ this.localSortDesc = false
+ } else if (sortDirection === 'desc') {
+ this.localSortDesc = true
+ } else {
+ // sortDirection === 'last'
+ // Leave at last sort direction from previous column
+ }
+ }
+ if (field.sortable) {
+ if (key === this.localSortBy) {
+ // Change sorting direction on current column
+ this.localSortDesc = !this.localSortDesc
+ } else {
+ // Start sorting this column ascending
+ this.localSortBy = key
+ // this.localSortDesc = false
+ toggleLocalSortDesc()
+ }
+ sortChanged = true
+ } else if (this.localSortBy && !this.noSortReset) {
+ this.localSortBy = null
+ toggleLocalSortDesc()
+ sortChanged = true
+ }
+ if (sortChanged) {
+ // Sorting parameters changed
+ this.$emit('sort-changed', this.context)
+ }
+ },
+ // methods to compute classes and attrs for thead>th cells
+ sortTheadThClasses(key, field, isFoot) {
+ return {
+ // No Classes for sorting currently...
+ // All styles targeted using aria-* attrs
+ }
+ },
+ sortTheadThAttrs(key, field, isFoot) {
+ if (!this.isSortable || (isFoot && this.noFooterSorting)) {
+ // No atributes if not a sortable table
+ return {}
+ }
+ const sortable = field.sortable
+ let ariaLabel = ''
+ if ((!field.label || !field.label.trim()) && !field.headerTitle) {
+ // In case field's label and title are empty/blank, we need to
+ // add a hint about what the column is about for non-sighted users.
+ // This is dulicated code from tbody-row mixin, but we need it
+ // here as well, since we overwrite the original aria-label.
+ /* istanbul ignore next */
+ ariaLabel = startCase(key)
+ }
+ // The correctness of these labels is very important for screen-reader users.
+ let ariaLabelSorting = ''
+ if (sortable) {
+ if (this.localSortBy === key) {
+ // currently sorted sortable column.
+ ariaLabelSorting = this.localSortDesc ? this.labelSortAsc : this.labelSortDesc
+ } else {
+ // Not currently sorted sortable column.
+ // Not using nested ternary's here for clarity/readability
+ // Default for ariaLabel
+ ariaLabelSorting = this.localSortDesc ? this.labelSortDesc : this.labelSortAsc
+ // Handle sortDirection setting
+ const sortDirection = this.sortDirection || field.sortDirection
+ if (sortDirection === 'asc') {
+ ariaLabelSorting = this.labelSortAsc
+ } else if (sortDirection === 'desc') {
+ ariaLabelSorting = this.labelSortDesc
+ }
+ }
+ } else if (!this.noSortReset) {
+ // Non sortable column
+ ariaLabelSorting = this.localSortBy ? this.labelSortClear : ''
+ }
+ // Assemble the aria-label attribute value
+ ariaLabel = [ariaLabel.trim(), ariaLabelSorting.trim()].filter(Boolean).join(': ')
+ // Assemble the aria-sort attribute value
+ const ariaSort =
+ sortable && this.localSortBy === key
+ ? this.localSortDesc
+ ? 'descending'
+ : 'ascending'
+ : sortable
+ ? 'none'
+ : null
+ // Return the attributes
+ // (All the above just to get these two values)
+ return {
+ 'aria-label': ariaLabel || null,
+ 'aria-sort': ariaSort
+ }
+ }
+ }
+}
diff --git a/src/components/table/helpers/mixin-tbody-row.js b/src/components/table/helpers/mixin-tbody-row.js
index a53a4e4519e..4be4107feb9 100644
--- a/src/components/table/helpers/mixin-tbody-row.js
+++ b/src/components/table/helpers/mixin-tbody-row.js
@@ -1,6 +1,7 @@
import toString from '../../../utils/to-string'
import get from '../../../utils/get'
import KeyCodes from '../../../utils/key-codes'
+import { arrayIncludes } from '../../../utils/array'
import filterEvent from './filter-event'
import textSelectionActive from './text-selection-active'
@@ -74,6 +75,52 @@ export default {
}
return value === null || typeof value === 'undefined' ? '' : value
},
+ tbodyRowKeydown(evt, item, rowIndex) {
+ const keyCode = evt.keyCode
+ const target = evt.target
+ const trs = this.$refs.itemRows
+ if (this.stopIfBusy(evt)) {
+ // If table is busy (via provider) then don't propagate
+ return
+ } else if (!(target && target.tagName === 'TR' && target === document.activeElement)) {
+ // Ignore if not the active tr element
+ return
+ } else if (target.tabIndex !== 0) {
+ // Ignore if not focusable
+ /* istanbul ignore next */
+ return
+ } else if (trs && trs.length === 0) {
+ /* istanbul ignore next */
+ return
+ }
+ const index = trs.indexOf(target)
+ if (keyCode === KeyCodes.ENTER || keyCode === KeyCodes.SPACE) {
+ evt.stopPropagation()
+ evt.preventDefault()
+ // We also allow enter/space to trigger a click (when row is focused)
+ // We translate to a row-clicked event
+ this.rowClicked(evt, item, rowIndex)
+ } else if (
+ arrayIncludes([KeyCodes.UP, KeyCodes.DOWN, KeyCodes.HOME, KeyCodes.END], keyCode)
+ ) {
+ evt.stopPropagation()
+ evt.preventDefault()
+ const shift = evt.shiftKey
+ if (keyCode === KeyCodes.HOME || (shift && keyCode === KeyCodes.UP)) {
+ // Focus first row
+ trs[0].focus()
+ } else if (keyCode === KeyCodes.END || (shift && keyCode === KeyCodes.DOWN)) {
+ // Focus last row
+ trs[trs.length - 1].focus()
+ } else if (keyCode === KeyCodes.UP && index > 0) {
+ // Focus previous row
+ trs[index - 1].focus()
+ } else if (keyCode === KeyCodes.DOWN && index < trs.length - 1) {
+ // Focus next row
+ trs[index + 1].focus()
+ }
+ }
+ },
// Row event handlers
rowClicked(e, item, index) {
if (this.stopIfBusy(e)) {
@@ -87,11 +134,6 @@ export default {
/* istanbul ignore next: JSDOM doesn't support getSelection() */
return
}
- if (e.type === 'keydown') {
- // If the click was generated by space or enter, stop page scroll
- e.stopPropagation()
- e.preventDefault()
- }
this.$emit('row-clicked', item, index, e)
},
middleMouseRowClicked(e, item, index) {
@@ -107,6 +149,7 @@ export default {
return
} else if (filterEvent(e)) {
// clicked on a non-disabled control so ignore
+ /* istanbul ignore next: event filtering already tested via click handler */
return
}
this.$emit('row-dblclicked', item, index, e)
@@ -152,39 +195,23 @@ export default {
this.$set(item, '_showDetails', !item._showDetails)
}
}
- let $childNodes
-
- if ($scoped[field.key]) {
- // Has scoped field slot
- $childNodes = [
- $scoped[field.key]({
- item: item,
- index: rowIndex,
- field: field,
- unformatted: get(item, field.key, ''),
- value: formatted,
- toggleDetails: toggleDetailsFn,
- detailsShowing: Boolean(item._showDetails),
- rowSelected: Boolean(rowSelected)
- })
- ]
- if (this.isStacked) {
- // We wrap in a DIV to ensure rendered as a single cell when visually stacked!
- $childNodes = [h('div', {}, [$childNodes])]
- }
- } else {
- // No scoped field slot
- if (this.isStacked) {
- // We wrap in a DIV to ensure rendered as a single cell when visually stacked!
- $childNodes = [h('div', toString(formatted))]
- } else {
- // Non stacked
- $childNodes = toString(formatted)
- }
+ const slotScope = {
+ item: item,
+ index: rowIndex,
+ field: field,
+ unformatted: get(item, field.key, ''),
+ value: formatted,
+ toggleDetails: toggleDetailsFn,
+ detailsShowing: Boolean(item._showDetails),
+ rowSelected: Boolean(rowSelected)
+ }
+ let $childNodes = $scoped[field.key] ? $scoped[field.key](slotScope) : toString(formatted)
+ if (this.isStacked) {
+ // We wrap in a DIV to ensure rendered as a single cell when visually stacked!
+ $childNodes = [h('div', {}, [$childNodes])]
}
-
// Render either a td or th cell
- return h(field.isRowHeader ? 'th' : 'td', data, $childNodes)
+ return h(field.isRowHeader ? 'th' : 'td', data, [$childNodes])
},
renderTbodyRow(item, rowIndex) {
// Renders an item's row (or rows if details supported)
@@ -195,7 +222,6 @@ export default {
const hasRowClickHandler = this.$listeners['row-clicked'] || this.selectable
const $detailsSlot = $scoped['row-details']
const rowShowDetails = Boolean(item._showDetails && $detailsSlot)
- const rowSelected = this.isRowSelected(rowIndex) /* from selctable mixin */
// We can return more than one TR if rowDetails enabled
const $rows = []
@@ -236,15 +262,27 @@ export default {
? this.safeId(`_row_${item[primaryKey]}`)
: null
+ const handlers = {}
+ if (hasRowClickHandler) {
+ handlers['click'] = evt => {
+ this.rowClicked(evt, item, rowIndex)
+ }
+ handlers['keydown'] = evt => {
+ this.tbodyRowKeydown(evt, item, rowIndex)
+ }
+ }
+
// Add the item row
$rows.push(
h(
'tr',
{
key: `__b-table-row-${rowKey}__`,
+ ref: 'itemRows',
+ refInFor: true,
class: [
this.rowClasses(item),
- this.rowSelectedClasses(rowIndex),
+ this.selectableRowClasses(rowIndex),
{
'b-table-has-details': rowShowDetails
}
@@ -256,32 +294,18 @@ export default {
'aria-describedby': detailsId,
'aria-owns': detailsId,
'aria-rowindex': ariaRowIndex,
- 'aria-selected': this.selectable ? (rowSelected ? 'true' : 'false') : null,
- role: 'row'
+ role: 'row',
+ ...this.selectableRowAttrs(rowIndex)
},
on: {
- // TODO: only instatiate handlers if we have registered listeners (except row-clicked)
+ ...handlers,
+ // TODO: instatiate the following handlers only if we have registered
+ // listeners i.e. this.$listeners['row-middle-clicked'], etc.
auxclick: evt => {
if (evt.which === 2) {
this.middleMouseRowClicked(evt, item, rowIndex)
}
},
- click: evt => {
- this.rowClicked(evt, item, rowIndex)
- },
- keydown: evt => {
- // We also allow enter/space to trigger a click (when row is focused)
- const keyCode = evt.keyCode
- if (keyCode === KeyCodes.ENTER || keyCode === KeyCodes.SPACE) {
- if (
- evt.target &&
- evt.target.tagName === 'TR' &&
- evt.target === document.activeElement
- ) {
- this.rowClicked(evt, item, rowIndex)
- }
- }
- },
contextmenu: evt => {
this.rowContextmenu(evt, item, rowIndex)
},
diff --git a/src/components/table/helpers/mixin-thead.js b/src/components/table/helpers/mixin-thead.js
index c00ade5cb0e..748c4e86d7e 100644
--- a/src/components/table/helpers/mixin-thead.js
+++ b/src/components/table/helpers/mixin-thead.js
@@ -33,11 +33,11 @@ export default {
field.thClass ? field.thClass : ''
]
},
- headClicked(e, field, isFoot) {
- if (this.stopIfBusy(e)) {
+ headClicked(evt, field, isFoot) {
+ if (this.stopIfBusy(evt)) {
// If table is busy (via provider) then don't propagate
return
- } else if (filterEvent(e)) {
+ } else if (filterEvent(evt)) {
// clicked on a non-disabled control so ignore
return
} else if (textSelectionActive(this.$el)) {
@@ -45,39 +45,9 @@ export default {
/* istanbul ignore next: JSDOM doesn't support getSelection() */
return
}
- e.stopPropagation()
- e.preventDefault()
- let sortChanged = false
- const toggleLocalSortDesc = () => {
- const sortDirection = field.sortDirection || this.sortDirection
- if (sortDirection === 'asc') {
- this.localSortDesc = false
- } else if (sortDirection === 'desc') {
- this.localSortDesc = true
- }
- }
- if (!(isFoot && this.noFooterSorting)) {
- if (field.sortable) {
- if (field.key === this.localSortBy) {
- // Change sorting direction on current column
- this.localSortDesc = !this.localSortDesc
- } else {
- // Start sorting this column ascending
- this.localSortBy = field.key
- toggleLocalSortDesc()
- }
- sortChanged = true
- } else if (this.localSortBy && !this.noSortReset) {
- this.localSortBy = null
- toggleLocalSortDesc()
- sortChanged = true
- }
- }
- this.$emit('head-clicked', field.key, field, e, isFoot)
- if (sortChanged) {
- // Sorting parameters changed
- this.$emit('sort-changed', this.context)
- }
+ evt.stopPropagation()
+ evt.preventDefault()
+ this.$emit('head-clicked', field.key, field, evt, isFoot)
},
renderThead(isFoot = false) {
const h = this.$createElement
@@ -91,54 +61,42 @@ export default {
// Helper function to generate a field TH cell
const makeCell = (field, colIndex) => {
- let ariaLabel = ''
+ let ariaLabel = null
if (!field.label.trim() && !field.headerTitle) {
// In case field's label and title are empty/blank
// We need to add a hint about what the column is about for non-sighted users
/* istanbul ignore next */
ariaLabel = startCase(field.key)
}
- const sortable = field.sortable && !(isFoot && this.noFooterSorting)
- const ariaLabelSorting = sortable
- ? this.localSortDesc && this.localSortBy === field.key
- ? this.labelSortAsc
- : this.labelSortDesc
- : null
- // Assemble the aria-label
- ariaLabel = [ariaLabel, ariaLabelSorting].filter(a => a).join(': ') || null
- const ariaSort =
- sortable && this.localSortBy === field.key
- ? this.localSortDesc
- ? 'descending'
- : 'ascending'
- : sortable
- ? 'none'
- : null
+ const hasHeadClickListener = this.$listeners['head-clicked'] || this.isSortable
+ const handlers = {}
+ if (hasHeadClickListener) {
+ handlers.click = evt => {
+ this.headClicked(evt, field, isFoot)
+ }
+ handlers.keydown = evt => {
+ const keyCode = evt.keyCode
+ if (keyCode === KeyCodes.ENTER || keyCode === KeyCodes.SPACE) {
+ this.headClicked(evt, field, isFoot)
+ }
+ }
+ }
const data = {
key: field.key,
- class: this.fieldClasses(field),
+ class: [this.fieldClasses(field), this.sortTheadThClasses(field.key, field, isFoot)],
style: field.thStyle || {},
attrs: {
- tabindex: sortable ? '0' : null,
+ // We only add a tabindex of 0 if there is a head-clicked listener
+ tabindex: hasHeadClickListener ? '0' : null,
abbr: field.headerAbbr || null,
title: field.headerTitle || null,
role: 'columnheader',
scope: 'col',
'aria-colindex': String(colIndex + 1),
'aria-label': ariaLabel,
- 'aria-sort': ariaSort
+ ...this.sortTheadThAttrs(field.key, field, isFoot)
},
- on: {
- click: evt => {
- this.headClicked(evt, field, isFoot)
- },
- keydown: evt => {
- const keyCode = evt.keyCode
- if (keyCode === KeyCodes.ENTER || keyCode === KeyCodes.SPACE) {
- this.headClicked(evt, field, isFoot)
- }
- }
- }
+ on: handlers
}
let fieldScope = { label: field.label, column: field.key, field: field }
let slot =
@@ -148,9 +106,9 @@ export default {
if (slot) {
slot = [slot]
} else {
- data.domProps = htmlOrText(field.labelHtml, field.label)
+ data.domProps = htmlOrText(field.labelHtml)
}
- return h('th', data, [slot])
+ return h('th', data, slot || field.label)
}
// Generate the array of TH cells
@@ -159,7 +117,7 @@ export default {
// Genrate the row(s)
const $trs = []
if (isFoot) {
- $trs.push(h('tr', { class: this.tfootTrClass }, $cells))
+ $trs.push(h('tr', { class: this.tfootTrClass, attrs: { role: 'row' } }, $cells))
} else {
const scope = {
columns: fields.length,
diff --git a/src/components/table/index.js b/src/components/table/index.js
index 90b3a33c0a1..ed8c197e9b2 100644
--- a/src/components/table/index.js
+++ b/src/components/table/index.js
@@ -1,12 +1,10 @@
import BTable from './table'
-import { registerComponents } from '../../utils/plugins'
+import { installFactory } from '../../utils/plugins'
const components = {
BTable
}
export default {
- install(Vue) {
- registerComponents(Vue, components)
- }
+ install: installFactory({ components })
}
diff --git a/src/components/table/table-busy.spec.js b/src/components/table/table-busy.spec.js
index 59c34ad48c3..a9d6607d27c 100644
--- a/src/components/table/table-busy.spec.js
+++ b/src/components/table/table-busy.spec.js
@@ -3,7 +3,7 @@ import { mount } from '@vue/test-utils'
const testItems = [{ a: 1, b: 2, c: 3 }, { a: 5, b: 5, c: 6 }, { a: 7, b: 8, c: 9 }]
-describe('b-table busy state', () => {
+describe('table > busy state', () => {
it('default should have attribute aria-busy=false', async () => {
const wrapper = mount(Table, {
propsData: {
diff --git a/src/components/table/table-caption.spec.js b/src/components/table/table-caption.spec.js
index fa2f1ba9520..b4821129d07 100644
--- a/src/components/table/table-caption.spec.js
+++ b/src/components/table/table-caption.spec.js
@@ -4,7 +4,7 @@ import { mount } from '@vue/test-utils'
const testItems = [{ a: 1, b: 2, c: 3 }, { a: 5, b: 5, c: 6 }, { a: 7, b: 8, c: 9 }]
const testFields = ['a', 'b', 'c']
-describe('table caption', () => {
+describe('table > caption', () => {
it('should not have caption by default', async () => {
const wrapper = mount(Table, {
propsData: {
diff --git a/src/components/table/table-colgroup.spec.js b/src/components/table/table-colgroup.spec.js
index 4ddcbfdba52..8795e457d90 100644
--- a/src/components/table/table-colgroup.spec.js
+++ b/src/components/table/table-colgroup.spec.js
@@ -5,7 +5,7 @@ import { mount } from '@vue/test-utils'
const testItems = [{ a: 1, b: 2, c: 3 }, { a: 5, b: 5, c: 6 }, { a: 7, b: 8, c: 9 }]
const testFields = ['a', 'b', 'c']
-describe('table colgroup', () => {
+describe('table > colgroup', () => {
it('should not have colgroup by default', async () => {
const wrapper = mount(Table, {
propsData: {
@@ -27,7 +27,7 @@ describe('table colgroup', () => {
items: testItems
},
slots: {
- 'table-colgroup': ' '
+ 'table-colgroup': ' '
}
})
expect(wrapper).toBeDefined()
diff --git a/src/components/table/table-filtering.spec.js b/src/components/table/table-filtering.spec.js
new file mode 100644
index 00000000000..2f8b67562d8
--- /dev/null
+++ b/src/components/table/table-filtering.spec.js
@@ -0,0 +1,237 @@
+import Table from './table'
+import stringifyRecordValues from './helpers/stringify-record-values'
+import { mount } from '@vue/test-utils'
+
+const testItems = [{ a: 3, b: 'b', c: 'x' }, { a: 1, b: 'c', c: 'y' }, { a: 2, b: 'a', c: 'z' }]
+const testFields = ['a', 'b', 'c']
+
+describe('table > filtering', () => {
+ it('should not be filtered by default', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ fields: testFields,
+ items: testItems
+ }
+ })
+ expect(wrapper).toBeDefined()
+ expect(wrapper.findAll('tbody > tr').exists()).toBe(true)
+ expect(wrapper.findAll('tbody > tr').length).toBe(3)
+ await wrapper.vm.$nextTick()
+ expect(wrapper.emitted('input')).toBeDefined()
+ expect(wrapper.emitted('input').length).toBe(1)
+ expect(wrapper.emitted('input')[0][0]).toEqual(testItems)
+ const $rows = wrapper.findAll('tbody > tr').wrappers
+ expect($rows.length).toBe(3)
+ // Map the rows to the first column text value
+ const columnA = $rows.map(row => {
+ return row
+ .findAll('td')
+ .at(0)
+ .text()
+ })
+ expect(columnA[0]).toBe('3')
+ expect(columnA[1]).toBe('1')
+ expect(columnA[2]).toBe('2')
+
+ wrapper.destroy()
+ })
+
+ it('should be filtered when filter is a string', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ fields: testFields,
+ items: testItems,
+ filter: 'z'
+ }
+ })
+ expect(wrapper).toBeDefined()
+ await wrapper.vm.$nextTick()
+
+ expect(wrapper.findAll('tbody > tr').exists()).toBe(true)
+ expect(wrapper.findAll('tbody > tr').length).toBe(1)
+
+ let $rows = wrapper.findAll('tbody > tr')
+ expect($rows.length).toBe(1)
+
+ const $tds = $rows.at(0).findAll('td')
+
+ expect($tds.at(0).text()).toBe('2')
+ expect($tds.at(1).text()).toBe('a')
+ expect($tds.at(2).text()).toBe('z')
+
+ wrapper.destroy()
+ })
+
+ it('should emit filtered event when filter string is changed', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ fields: testFields,
+ items: testItems,
+ filter: ''
+ }
+ })
+ expect(wrapper).toBeDefined()
+ await wrapper.vm.$nextTick()
+
+ expect(wrapper.findAll('tbody > tr').exists()).toBe(true)
+ expect(wrapper.findAll('tbody > tr').length).toBe(3)
+ expect(wrapper.emitted('filtered')).not.toBeDefined()
+
+ wrapper.setProps({
+ filter: 'z'
+ })
+ await wrapper.vm.$nextTick()
+
+ expect(wrapper.findAll('tbody > tr').exists()).toBe(true)
+ expect(wrapper.findAll('tbody > tr').length).toBe(1)
+
+ expect(wrapper.emitted('filtered')).toBeDefined()
+ expect(wrapper.emitted('filtered').length).toBe(1)
+ // Copy of items matching filter
+ expect(wrapper.emitted('filtered')[0][0]).toEqual([testItems[2]])
+ // Number of rows matching filter
+ expect(wrapper.emitted('filtered')[0][1]).toEqual(1)
+
+ wrapper.setProps({
+ filter: ''
+ })
+ await wrapper.vm.$nextTick()
+
+ expect(wrapper.findAll('tbody > tr').exists()).toBe(true)
+ expect(wrapper.findAll('tbody > tr').length).toBe(3)
+
+ expect(wrapper.emitted('filtered').length).toBe(2)
+ // Copy of items matching filter
+ expect(wrapper.emitted('filtered')[1][0]).toEqual(testItems)
+ // Number of rows matching filter
+ expect(wrapper.emitted('filtered')[1][1]).toEqual(3)
+
+ wrapper.setProps({
+ filter: '3'
+ })
+ await wrapper.vm.$nextTick()
+
+ expect(wrapper.findAll('tbody > tr').exists()).toBe(true)
+ expect(wrapper.findAll('tbody > tr').length).toBe(1)
+
+ expect(wrapper.emitted('filtered').length).toBe(3)
+ // Copy of items matching filter
+ expect(wrapper.emitted('filtered')[2][0]).toEqual([testItems[0]])
+ // Number of rows matching filter
+ expect(wrapper.emitted('filtered')[2][1]).toEqual(1)
+
+ wrapper.setProps({
+ // Setting to null will also clear the filter
+ filter: null
+ })
+ await wrapper.vm.$nextTick()
+
+ expect(wrapper.findAll('tbody > tr').exists()).toBe(true)
+ expect(wrapper.findAll('tbody > tr').length).toBe(3)
+
+ expect(wrapper.emitted('filtered').length).toBe(4)
+ // Copy of items matching filter
+ expect(wrapper.emitted('filtered')[3][0]).toEqual(testItems)
+ // Number of rows matching filter
+ expect(wrapper.emitted('filtered')[3][1]).toEqual(3)
+
+ wrapper.destroy()
+ })
+
+ it('should work with filter function', async () => {
+ const filterFn = (item, regexp) => {
+ // We are passing a regexp for this test
+ return regexp.test(stringifyRecordValues(item))
+ }
+ const wrapper = mount(Table, {
+ propsData: {
+ fields: testFields,
+ items: testItems,
+ filter: '',
+ filterFunction: filterFn
+ }
+ })
+ expect(wrapper).toBeDefined()
+ await wrapper.vm.$nextTick()
+
+ expect(wrapper.findAll('tbody > tr').exists()).toBe(true)
+ expect(wrapper.findAll('tbody > tr').length).toBe(3)
+ expect(wrapper.emitted('filtered')).not.toBeDefined()
+
+ wrapper.setProps({
+ filter: /z/
+ })
+ await wrapper.vm.$nextTick()
+
+ expect(wrapper.findAll('tbody > tr').exists()).toBe(true)
+ expect(wrapper.findAll('tbody > tr').length).toBe(1)
+
+ expect(wrapper.emitted('filtered')).toBeDefined()
+ expect(wrapper.emitted('filtered').length).toBe(1)
+ // Copy of items matching filter
+ expect(wrapper.emitted('filtered')[0][0]).toEqual([testItems[2]])
+ // Number of rows matching filter
+ expect(wrapper.emitted('filtered')[0][1]).toEqual(1)
+
+ wrapper.setProps({
+ filter: []
+ })
+ await wrapper.vm.$nextTick()
+
+ expect(wrapper.findAll('tbody > tr').exists()).toBe(true)
+ expect(wrapper.findAll('tbody > tr').length).toBe(3)
+
+ expect(wrapper.emitted('filtered').length).toBe(2)
+ // Copy of items matching filter
+ expect(wrapper.emitted('filtered')[1][0]).toEqual(testItems)
+ // Number of rows matching filter
+ expect(wrapper.emitted('filtered')[1][1]).toEqual(3)
+
+ wrapper.destroy()
+ })
+
+ it('should be filtered with no rows when no matches', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ fields: testFields,
+ items: testItems,
+ filter: 'ZZZZZZZZ'
+ }
+ })
+ expect(wrapper).toBeDefined()
+ await wrapper.vm.$nextTick()
+
+ expect(wrapper.findAll('tbody > tr').length).toBe(0)
+
+ wrapper.destroy()
+ })
+
+ it('should show empty filtered message when no matches and show-empty=true', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ fields: testFields,
+ items: testItems,
+ filter: '',
+ showEmpty: true
+ }
+ })
+ expect(wrapper).toBeDefined()
+ await wrapper.vm.$nextTick()
+ expect(wrapper.findAll('tbody > tr').length).toBe(testItems.length)
+
+ wrapper.setProps({
+ filter: 'ZZZZZZ'
+ })
+ await wrapper.vm.$nextTick()
+
+ expect(wrapper.findAll('tbody > tr').length).toBe(1)
+ expect(wrapper.find('tbody > tr').text()).toBe(wrapper.vm.emptyFilteredText)
+ expect(wrapper.find('tbody > tr').classes()).toContain('b-table-empty-row')
+ expect(wrapper.find('tbody > tr').attributes('role')).toBe('row')
+ expect(wrapper.find('tbody > tr > td').attributes('role')).toBe('cell')
+ expect(wrapper.find('tbody > tr > td > div').attributes('role')).toBe('alert')
+ expect(wrapper.find('tbody > tr > td > div').attributes('aria-live')).toBe('polite')
+
+ wrapper.destroy()
+ })
+})
diff --git a/src/components/table/table-pagination.spec.js b/src/components/table/table-pagination.spec.js
new file mode 100644
index 00000000000..d42b264d023
--- /dev/null
+++ b/src/components/table/table-pagination.spec.js
@@ -0,0 +1,139 @@
+import Table from './table'
+import { mount } from '@vue/test-utils'
+
+const testItems = [
+ { a: 1, b: 2, c: 3 },
+ { a: 4, b: 5, c: 6 },
+ { a: 7, b: 8, c: 9 },
+ { a: 10, b: 11, c: 12 },
+ { a: 13, b: 14, c: 15 }
+]
+
+describe('table > pagination', () => {
+ it('default should not be paginated', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ items: testItems
+ }
+ })
+ expect(wrapper.findAll('tbody > tr').length).toBe(5)
+
+ wrapper.destroy()
+ })
+
+ it('should have 3 rows when per-page=3', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ items: testItems,
+ perPage: 3,
+ currentPage: 1
+ }
+ })
+ expect(wrapper.findAll('tbody > tr').length).toBe(3)
+ const $trs = wrapper.findAll('tbody > tr')
+ expect(
+ $trs
+ .at(0)
+ .find('td')
+ .text()
+ ).toBe('1')
+ expect(
+ $trs
+ .at(1)
+ .find('td')
+ .text()
+ ).toBe('4')
+ expect(
+ $trs
+ .at(2)
+ .find('td')
+ .text()
+ ).toBe('7')
+
+ wrapper.destroy()
+ })
+
+ it('changing pages should update rows', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ items: testItems,
+ perPage: 3,
+ currentPage: 1
+ }
+ })
+ expect(wrapper.findAll('tbody > tr').length).toBe(3)
+ let $trs = wrapper.findAll('tbody > tr')
+ expect(
+ $trs
+ .at(0)
+ .find('td')
+ .text()
+ ).toBe('1')
+ expect(
+ $trs
+ .at(1)
+ .find('td')
+ .text()
+ ).toBe('4')
+ expect(
+ $trs
+ .at(2)
+ .find('td')
+ .text()
+ ).toBe('7')
+
+ wrapper.setProps({
+ currentPage: 2
+ })
+
+ expect(wrapper.findAll('tbody > tr').length).toBe(2)
+ $trs = wrapper.findAll('tbody > tr')
+ expect(
+ $trs
+ .at(0)
+ .find('td')
+ .text()
+ ).toBe('10')
+ expect(
+ $trs
+ .at(1)
+ .find('td')
+ .text()
+ ).toBe('13')
+
+ wrapper.setProps({
+ currentPage: 3
+ })
+
+ expect(wrapper.findAll('tbody > tr').length).toBe(0)
+
+ wrapper.destroy()
+ })
+
+ it('setting current-page to more than pages shows empty row when show-empty=true', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ items: testItems,
+ perPage: 3,
+ currentPage: 1,
+ showEmpty: true
+ }
+ })
+ expect(wrapper.findAll('tbody > tr').length).toBe(3)
+
+ wrapper.setProps({
+ currentPage: 10
+ })
+
+ expect(wrapper.findAll('tbody > tr').length).toBe(1)
+ const $tr = wrapper.find('tbody > tr')
+ expect($tr.text()).toBe(wrapper.vm.emptyText)
+ expect($tr.classes()).toContain('b-table-empty-row')
+ expect($tr.attributes('role')).toBe('row')
+ expect(wrapper.find('tbody > tr > td').attributes('role')).toBe('cell')
+ expect(wrapper.find('tbody > tr > td > div').attributes('role')).toBe('alert')
+ expect(wrapper.find('tbody > tr > td > div').attributes('aria-live')).toBe('polite')
+
+ wrapper.destroy()
+ })
+})
diff --git a/src/components/table/table-primarykey.spec.js b/src/components/table/table-primarykey.spec.js
index fafbf9e00b3..e02300d3731 100644
--- a/src/components/table/table-primarykey.spec.js
+++ b/src/components/table/table-primarykey.spec.js
@@ -3,7 +3,7 @@ import { mount } from '@vue/test-utils'
const testItems = [{ a: 1, b: 2, c: 3 }, { a: 5, b: 5, c: 6 }, { a: 7, b: 8, c: 9 }]
-describe('b-table primary key', () => {
+describe('table > primary key', () => {
it('default should not have ids on table rows', async () => {
const wrapper = mount(Table, {
propsData: {
diff --git a/src/components/table/table-provider.spec.js b/src/components/table/table-provider.spec.js
index 22b89f5717b..82a7722d6b8 100644
--- a/src/components/table/table-provider.spec.js
+++ b/src/components/table/table-provider.spec.js
@@ -12,7 +12,7 @@ const testItems = [
const testFields = Object.keys(testItems[0]).sort()
-describe('b-table provider functions', () => {
+describe('table > provider functions', () => {
it('syncronous items provider works', async () => {
function provider(ctx) {
return testItems.slice()
@@ -263,4 +263,54 @@ describe('b-table provider functions', () => {
wrapper.destroy()
})
+
+ it('reacts to items provider function change', async () => {
+ function provider1(ctx) {
+ return testItems.slice()
+ }
+
+ function provider2(ctx) {
+ return testItems.slice(testItems.length - 1)
+ }
+
+ const wrapper = mount(Table, {
+ propsData: {
+ fields: testFields,
+ items: provider1
+ }
+ })
+ expect(wrapper).toBeDefined()
+
+ await Vue.nextTick()
+
+ expect(wrapper.emitted('update:busy')).toBeDefined()
+ expect(wrapper.emitted('input')).toBeDefined()
+
+ expect(wrapper.find('tbody').exists()).toBe(true)
+ expect(
+ wrapper
+ .find('tbody')
+ .findAll('tr')
+ .exists()
+ ).toBe(true)
+ expect(wrapper.find('tbody').findAll('tr').length).toBe(testItems.length)
+
+ wrapper.setProps({
+ items: provider2
+ })
+
+ await Vue.nextTick()
+
+ expect(wrapper.find('tbody').exists()).toBe(true)
+ expect(
+ wrapper
+ .find('tbody')
+ .findAll('tr')
+ .exists()
+ ).toBe(true)
+
+ expect(wrapper.find('tbody').findAll('tr').length).toBe(1)
+
+ wrapper.destroy()
+ })
})
diff --git a/src/components/table/table-row-details.spec.js b/src/components/table/table-row-details.spec.js
index 6c865418d0c..6d3b4a83618 100644
--- a/src/components/table/table-row-details.spec.js
+++ b/src/components/table/table-row-details.spec.js
@@ -1,7 +1,7 @@
import Table from './table'
import { mount } from '@vue/test-utils'
-describe('table row details', () => {
+describe('table > row details', () => {
it('does not show details if slot row-details not defined', async () => {
const testItems = [
{ a: 1, b: 2, c: 3, _showDetails: true },
@@ -170,4 +170,63 @@ describe('table row details', () => {
wrapper.destroy()
})
+
+ it('should show details slot when slot method toggleDetails() called', async () => {
+ const testItems = [{ a: 1, b: 2, c: 3, _showDetails: true }]
+ const testFields = ['a', 'b', 'c']
+ let scopeDetails = null
+ let scopeField = null
+ const wrapper = mount(Table, {
+ propsData: {
+ fields: testFields,
+ items: testItems
+ },
+ scopedSlots: {
+ 'row-details': function(scope) {
+ scopeDetails = scope
+ return 'foobar'
+ },
+ a: function(scope) {
+ scopeField = scope
+ return 'AAA'
+ }
+ }
+ })
+ let $trs
+ expect(wrapper).toBeDefined()
+ expect(wrapper.find('tbody').exists()).toBe(true)
+ expect(wrapper.findAll('tbody > tr').length).toBe(2)
+
+ $trs = wrapper.findAll('tbody > tr')
+ expect($trs.length).toBe(2)
+ expect($trs.at(0).is('tr.b-table-details')).toBe(false)
+ expect($trs.at(1).is('tr.b-table-details')).toBe(true)
+ expect($trs.at(1).text()).toBe('foobar')
+
+ // Toggle details via details slot
+ expect(scopeDetails).not.toBe(null)
+ scopeDetails.toggleDetails()
+ await wrapper.vm.$nextTick()
+
+ expect(wrapper.findAll('tbody > tr').length).toBe(1)
+
+ $trs = wrapper.findAll('tbody > tr')
+ expect($trs.length).toBe(1)
+ expect($trs.at(0).is('tr.b-table-details')).toBe(false)
+
+ // Toggle details via field slot
+ expect(scopeField).not.toBe(null)
+ scopeField.toggleDetails()
+ await wrapper.vm.$nextTick()
+
+ expect(wrapper.findAll('tbody > tr').length).toBe(2)
+
+ $trs = wrapper.findAll('tbody > tr')
+ expect($trs.length).toBe(2)
+ expect($trs.at(0).is('tr.b-table-details')).toBe(false)
+ expect($trs.at(1).is('tr.b-table-details')).toBe(true)
+ expect($trs.at(1).text()).toBe('foobar')
+
+ wrapper.destroy()
+ })
})
diff --git a/src/components/table/table-selectable.spec.js b/src/components/table/table-selectable.spec.js
index 5ebf113454e..5601c128bb7 100644
--- a/src/components/table/table-selectable.spec.js
+++ b/src/components/table/table-selectable.spec.js
@@ -4,7 +4,7 @@ import { mount } from '@vue/test-utils'
const testItems = [{ a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }]
const testFields = [{ key: 'a', sortable: true }]
-describe('table row select', () => {
+describe('table > row select', () => {
it('should not emit row-selected event default', async () => {
const wrapper = mount(Table, {
propsData: {
@@ -28,6 +28,12 @@ describe('table row select', () => {
})
expect(wrapper).toBeDefined()
await wrapper.vm.$nextTick()
+ expect(wrapper.attributes('aria-multiselectable')).not.toBeDefined()
+ expect(wrapper.classes()).not.toContain('b-table-selectable')
+ expect(wrapper.classes()).not.toContain('b-table-selecting')
+ expect(wrapper.classes()).not.toContain('b-table-select-single')
+ expect(wrapper.classes()).not.toContain('b-table-select-multi')
+ expect(wrapper.classes()).not.toContain('b-table-select-range')
const $rows = wrapper.findAll('tbody > tr')
expect($rows.length).toBe(4)
// Doesn't have aria-selected attribute on all TRs
@@ -50,6 +56,12 @@ describe('table row select', () => {
})
expect(wrapper).toBeDefined()
await wrapper.vm.$nextTick()
+ expect(wrapper.attributes('aria-multiselectable')).not.toBeDefined()
+ expect(wrapper.classes()).not.toContain('b-table-selectable')
+ expect(wrapper.classes()).not.toContain('b-table-selecting')
+ expect(wrapper.classes()).not.toContain('b-table-select-single')
+ expect(wrapper.classes()).not.toContain('b-table-select-multi')
+ expect(wrapper.classes()).not.toContain('b-table-select-range')
const $rows = wrapper.findAll('tbody > tr')
expect($rows.length).toBe(4)
// Doesn't have aria-selected attribute on all TRs
@@ -73,6 +85,12 @@ describe('table row select', () => {
expect(wrapper).toBeDefined()
await wrapper.vm.$nextTick()
+ expect(wrapper.attributes('aria-multiselectable')).toBe('false')
+ expect(wrapper.classes()).toContain('b-table-selectable')
+ expect(wrapper.classes()).toContain('b-table-select-single')
+ expect(wrapper.classes()).not.toContain('b-table-selecting')
+ expect(wrapper.classes()).not.toContain('b-table-select-multi')
+ expect(wrapper.classes()).not.toContain('b-table-select-range')
expect(wrapper.emitted('row-selected')).not.toBeDefined()
$rows = wrapper.findAll('tbody > tr')
expect($rows.length).toBe(4)
@@ -94,8 +112,13 @@ describe('table row select', () => {
expect($rows.at(1).is('[aria-selected="false"]')).toBe(true)
expect($rows.at(2).is('[aria-selected="false"]')).toBe(true)
expect($rows.at(3).is('[aria-selected="false"]')).toBe(true)
+ expect(wrapper.classes()).toContain('b-table-selectable')
+ expect(wrapper.classes()).toContain('b-table-select-single')
+ expect(wrapper.classes()).toContain('b-table-selecting')
+ expect(wrapper.classes()).not.toContain('b-table-select-multi')
+ expect(wrapper.classes()).not.toContain('b-table-select-range')
- // Click third row
+ // Click third row to select it
wrapper
.findAll('tbody > tr')
.at(2)
@@ -109,8 +132,13 @@ describe('table row select', () => {
expect($rows.at(1).is('[aria-selected="false"]')).toBe(true)
expect($rows.at(2).is('[aria-selected="true"]')).toBe(true)
expect($rows.at(3).is('[aria-selected="false"]')).toBe(true)
+ expect(wrapper.classes()).toContain('b-table-selectable')
+ expect(wrapper.classes()).toContain('b-table-select-single')
+ expect(wrapper.classes()).toContain('b-table-selecting')
+ expect(wrapper.classes()).not.toContain('b-table-select-multi')
+ expect(wrapper.classes()).not.toContain('b-table-select-range')
- // Click third row again
+ // Click third row again to clear selection
wrapper
.findAll('tbody > tr')
.at(2)
@@ -124,6 +152,11 @@ describe('table row select', () => {
expect($rows.at(1).is('[aria-selected="false"]')).toBe(true)
expect($rows.at(2).is('[aria-selected="false"]')).toBe(true)
expect($rows.at(3).is('[aria-selected="false"]')).toBe(true)
+ expect(wrapper.classes()).toContain('b-table-selectable')
+ expect(wrapper.classes()).toContain('b-table-select-single')
+ expect(wrapper.classes()).not.toContain('b-table-selecting')
+ expect(wrapper.classes()).not.toContain('b-table-select-multi')
+ expect(wrapper.classes()).not.toContain('b-table-select-range')
wrapper.destroy()
})
@@ -140,6 +173,12 @@ describe('table row select', () => {
let $rows
expect(wrapper).toBeDefined()
await wrapper.vm.$nextTick()
+ expect(wrapper.attributes('aria-multiselectable')).toBe('true')
+ expect(wrapper.classes()).toContain('b-table-selectable')
+ expect(wrapper.classes()).toContain('b-table-select-multi')
+ expect(wrapper.classes()).not.toContain('b-table-select-single')
+ expect(wrapper.classes()).not.toContain('b-table-select-range')
+ expect(wrapper.classes()).not.toContain('b-table-selecting')
expect(wrapper.emitted('row-selected')).not.toBeDefined()
// Click first row
@@ -157,6 +196,11 @@ describe('table row select', () => {
expect($rows.at(1).is('[aria-selected="false"]')).toBe(true)
expect($rows.at(2).is('[aria-selected="false"]')).toBe(true)
expect($rows.at(3).is('[aria-selected="false"]')).toBe(true)
+ expect(wrapper.classes()).toContain('b-table-selectable')
+ expect(wrapper.classes()).toContain('b-table-selecting')
+ expect(wrapper.classes()).toContain('b-table-select-multi')
+ expect(wrapper.classes()).not.toContain('b-table-select-single')
+ expect(wrapper.classes()).not.toContain('b-table-select-range')
// Click third row
wrapper
@@ -172,6 +216,11 @@ describe('table row select', () => {
expect($rows.at(1).is('[aria-selected="false"]')).toBe(true)
expect($rows.at(2).is('[aria-selected="true"]')).toBe(true)
expect($rows.at(3).is('[aria-selected="false"]')).toBe(true)
+ expect(wrapper.classes()).toContain('b-table-selectable')
+ expect(wrapper.classes()).toContain('b-table-selecting')
+ expect(wrapper.classes()).toContain('b-table-select-multi')
+ expect(wrapper.classes()).not.toContain('b-table-select-single')
+ expect(wrapper.classes()).not.toContain('b-table-select-range')
// Click third row again
wrapper
@@ -187,6 +236,11 @@ describe('table row select', () => {
expect($rows.at(1).is('[aria-selected="false"]')).toBe(true)
expect($rows.at(2).is('[aria-selected="false"]')).toBe(true)
expect($rows.at(3).is('[aria-selected="false"]')).toBe(true)
+ expect(wrapper.classes()).toContain('b-table-selectable')
+ expect(wrapper.classes()).toContain('b-table-selecting')
+ expect(wrapper.classes()).toContain('b-table-select-multi')
+ expect(wrapper.classes()).not.toContain('b-table-select-single')
+ expect(wrapper.classes()).not.toContain('b-table-select-range')
// Click first row again
wrapper
@@ -202,6 +256,11 @@ describe('table row select', () => {
expect($rows.at(1).is('[aria-selected="false"]')).toBe(true)
expect($rows.at(2).is('[aria-selected="false"]')).toBe(true)
expect($rows.at(3).is('[aria-selected="false"]')).toBe(true)
+ expect(wrapper.classes()).toContain('b-table-selectable')
+ expect(wrapper.classes()).toContain('b-table-select-multi')
+ expect(wrapper.classes()).not.toContain('b-table-selecting')
+ expect(wrapper.classes()).not.toContain('b-table-select-single')
+ expect(wrapper.classes()).not.toContain('b-table-select-range')
wrapper.destroy()
})
@@ -218,6 +277,12 @@ describe('table row select', () => {
let $rows
expect(wrapper).toBeDefined()
await wrapper.vm.$nextTick()
+ expect(wrapper.attributes('aria-multiselectable')).toBe('true')
+ expect(wrapper.classes()).toContain('b-table-selectable')
+ expect(wrapper.classes()).toContain('b-table-select-range')
+ expect(wrapper.classes()).not.toContain('b-table-selecting')
+ expect(wrapper.classes()).not.toContain('b-table-select-single')
+ expect(wrapper.classes()).not.toContain('b-table-select-multi')
expect(wrapper.emitted('row-selected')).not.toBeDefined()
$rows = wrapper.findAll('tbody > tr')
expect($rows.is('[tabindex="0"]')).toBe(true)
@@ -238,6 +303,11 @@ describe('table row select', () => {
expect($rows.at(1).is('[aria-selected="false"]')).toBe(true)
expect($rows.at(2).is('[aria-selected="false"]')).toBe(true)
expect($rows.at(3).is('[aria-selected="false"]')).toBe(true)
+ expect(wrapper.classes()).toContain('b-table-selectable')
+ expect(wrapper.classes()).toContain('b-table-select-range')
+ expect(wrapper.classes()).toContain('b-table-selecting')
+ expect(wrapper.classes()).not.toContain('b-table-select-single')
+ expect(wrapper.classes()).not.toContain('b-table-select-multi')
// Shift-Click third row
wrapper
@@ -257,6 +327,11 @@ describe('table row select', () => {
expect($rows.at(1).is('[aria-selected="true"]')).toBe(true)
expect($rows.at(2).is('[aria-selected="true"]')).toBe(true)
expect($rows.at(3).is('[aria-selected="false"]')).toBe(true)
+ expect(wrapper.classes()).toContain('b-table-selectable')
+ expect(wrapper.classes()).toContain('b-table-select-range')
+ expect(wrapper.classes()).toContain('b-table-selecting')
+ expect(wrapper.classes()).not.toContain('b-table-select-single')
+ expect(wrapper.classes()).not.toContain('b-table-select-multi')
// Click third row again
wrapper
@@ -272,6 +347,11 @@ describe('table row select', () => {
expect($rows.at(1).is('[aria-selected="false"]')).toBe(true)
expect($rows.at(2).is('[aria-selected="true"]')).toBe(true)
expect($rows.at(3).is('[aria-selected="false"]')).toBe(true)
+ expect(wrapper.classes()).toContain('b-table-selectable')
+ expect(wrapper.classes()).toContain('b-table-select-range')
+ expect(wrapper.classes()).toContain('b-table-selecting')
+ expect(wrapper.classes()).not.toContain('b-table-select-single')
+ expect(wrapper.classes()).not.toContain('b-table-select-multi')
// Click fourth row
wrapper
@@ -287,6 +367,11 @@ describe('table row select', () => {
expect($rows.at(1).is('[aria-selected="false"]')).toBe(true)
expect($rows.at(2).is('[aria-selected="false"]')).toBe(true)
expect($rows.at(3).is('[aria-selected="true"]')).toBe(true)
+ expect(wrapper.classes()).toContain('b-table-selectable')
+ expect(wrapper.classes()).toContain('b-table-select-range')
+ expect(wrapper.classes()).toContain('b-table-selecting')
+ expect(wrapper.classes()).not.toContain('b-table-select-single')
+ expect(wrapper.classes()).not.toContain('b-table-select-multi')
// Click fourth row again
wrapper
@@ -302,6 +387,11 @@ describe('table row select', () => {
expect($rows.at(1).is('[aria-selected="false"]')).toBe(true)
expect($rows.at(2).is('[aria-selected="false"]')).toBe(true)
expect($rows.at(3).is('[aria-selected="true"]')).toBe(true)
+ expect(wrapper.classes()).toContain('b-table-selectable')
+ expect(wrapper.classes()).toContain('b-table-select-range')
+ expect(wrapper.classes()).toContain('b-table-selecting')
+ expect(wrapper.classes()).not.toContain('b-table-select-single')
+ expect(wrapper.classes()).not.toContain('b-table-select-multi')
// Ctrl-Click second row
wrapper
@@ -317,6 +407,11 @@ describe('table row select', () => {
expect($rows.at(1).is('[aria-selected="true"]')).toBe(true)
expect($rows.at(2).is('[aria-selected="false"]')).toBe(true)
expect($rows.at(3).is('[aria-selected="true"]')).toBe(true)
+ expect(wrapper.classes()).toContain('b-table-selectable')
+ expect(wrapper.classes()).toContain('b-table-select-range')
+ expect(wrapper.classes()).toContain('b-table-selecting')
+ expect(wrapper.classes()).not.toContain('b-table-select-single')
+ expect(wrapper.classes()).not.toContain('b-table-select-multi')
// Ctrl-Click second row
wrapper
@@ -332,6 +427,11 @@ describe('table row select', () => {
expect($rows.at(1).is('[aria-selected="false"]')).toBe(true)
expect($rows.at(2).is('[aria-selected="false"]')).toBe(true)
expect($rows.at(3).is('[aria-selected="true"]')).toBe(true)
+ expect(wrapper.classes()).toContain('b-table-selectable')
+ expect(wrapper.classes()).toContain('b-table-select-range')
+ expect(wrapper.classes()).toContain('b-table-selecting')
+ expect(wrapper.classes()).not.toContain('b-table-select-single')
+ expect(wrapper.classes()).not.toContain('b-table-select-multi')
// Ctrl-Click fourth row
wrapper
@@ -347,6 +447,11 @@ describe('table row select', () => {
expect($rows.at(1).is('[aria-selected="false"]')).toBe(true)
expect($rows.at(2).is('[aria-selected="false"]')).toBe(true)
expect($rows.at(3).is('[aria-selected="false"]')).toBe(true)
+ expect(wrapper.classes()).toContain('b-table-selectable')
+ expect(wrapper.classes()).toContain('b-table-select-range')
+ expect(wrapper.classes()).not.toContain('b-table-selecting')
+ expect(wrapper.classes()).not.toContain('b-table-select-single')
+ expect(wrapper.classes()).not.toContain('b-table-select-multi')
wrapper.destroy()
})
@@ -572,6 +677,8 @@ describe('table row select', () => {
expect($rows.at(1).is('[aria-selected="false"]')).toBe(true)
expect($rows.at(2).is('[aria-selected="false"]')).toBe(true)
expect($rows.at(3).is('[aria-selected="false"]')).toBe(true)
+ expect(wrapper.classes()).toContain('b-table-selectable')
+ expect(wrapper.classes()).not.toContain('b-table-selecting-range')
// Disabled selectable
wrapper.setProps({
@@ -584,6 +691,8 @@ describe('table row select', () => {
// Should remove tabindex and aria-selected attributes
expect($rows.is('[tabindex]')).toBe(false)
expect($rows.is('[aria-selected]')).toBe(false)
+ expect(wrapper.classes()).not.toContain('b-table-selectable')
+ expect(wrapper.classes()).not.toContain('b-table-selecting-range')
wrapper.destroy()
})
diff --git a/src/components/table/table-sort.spec.js b/src/components/table/table-sort.spec.js
deleted file mode 100644
index 677a5e7dc97..00000000000
--- a/src/components/table/table-sort.spec.js
+++ /dev/null
@@ -1,268 +0,0 @@
-import Table from './table'
-import defaultSortCompare from './helpers/default-sort-compare'
-import { mount } from '@vue/test-utils'
-
-const testItems = [{ a: 3, b: 'b', c: 'x' }, { a: 1, b: 'c', c: 'y' }, { a: 2, b: 'a', c: 'z' }]
-const testFields = [
- { key: 'a', label: 'A', sortable: true },
- { key: 'b', label: 'B', sortable: true },
- { key: 'c', label: 'C', sortable: false }
-]
-
-describe('table sorting', () => {
- it('should not be sorted by default', async () => {
- const wrapper = mount(Table, {
- propsData: {
- fields: testFields,
- items: testItems
- }
- })
- expect(wrapper).toBeDefined()
- expect(wrapper.findAll('tbody > tr').exists()).toBe(true)
- expect(wrapper.findAll('tbody > tr').length).toBe(3)
- await wrapper.vm.$nextTick()
- expect(wrapper.emitted('input')).toBeDefined()
- expect(wrapper.emitted('input').length).toBe(1)
- expect(wrapper.emitted('input')[0][0]).toEqual(testItems)
- const $rows = wrapper.findAll('tbody > tr').wrappers
- expect($rows.length).toBe(3)
- // Map the rows to the first column text value
- const columnA = $rows.map(row => {
- return row
- .findAll('td')
- .at(0)
- .text()
- })
- expect(columnA[0]).toBe('3')
- expect(columnA[1]).toBe('1')
- expect(columnA[2]).toBe('2')
-
- wrapper.destroy()
- })
-
- it('should sort column descending when sortBy set and sortDesc changed', async () => {
- const wrapper = mount(Table, {
- propsData: {
- fields: testFields,
- items: testItems,
- sortBy: 'a',
- sortDesc: false
- }
- })
- expect(wrapper).toBeDefined()
- expect(wrapper.findAll('tbody > tr').exists()).toBe(true)
- expect(wrapper.findAll('tbody > tr').length).toBe(3)
- let $rows
- let columnA
-
- await wrapper.vm.$nextTick()
- expect(wrapper.emitted('input')).toBeDefined()
- expect(wrapper.emitted('input').length).toBe(1)
- $rows = wrapper.findAll('tbody > tr').wrappers
- expect($rows.length).toBe(3)
- // Map the rows to the first column text value
- columnA = $rows.map(row => {
- return row
- .findAll('td')
- .at(0)
- .text()
- })
- expect(columnA[0]).toBe('1')
- expect(columnA[1]).toBe('2')
- expect(columnA[2]).toBe('3')
-
- // Change sort direction
- wrapper.setProps({
- sortDesc: true
- })
- await wrapper.vm.$nextTick()
- expect(wrapper.emitted('input').length).toBe(2)
- $rows = wrapper.findAll('tbody > tr').wrappers
- expect($rows.length).toBe(3)
- // Map the rows to the first column text value
- columnA = $rows.map(row => {
- return row
- .findAll('td')
- .at(0)
- .text()
- })
- expect(columnA[0]).toBe('3')
- expect(columnA[1]).toBe('2')
- expect(columnA[2]).toBe('1')
-
- // Clear sort
- wrapper.setProps({
- sortBy: null,
- sortDesc: false
- })
- await wrapper.vm.$nextTick()
- expect(wrapper.emitted('input').length).toBe(4)
- $rows = wrapper.findAll('tbody > tr').wrappers
- expect($rows.length).toBe(3)
- // Map the rows to the first column text value
- columnA = $rows.map(row => {
- return row
- .findAll('td')
- .at(0)
- .text()
- })
- expect(columnA[0]).toBe('3')
- expect(columnA[1]).toBe('1')
- expect(columnA[2]).toBe('2')
-
- wrapper.destroy()
- })
-
- it('should accept custom sort compare', async () => {
- const wrapper = mount(Table, {
- propsData: {
- fields: testFields,
- items: testItems,
- sortBy: 'a',
- sortDesc: false,
- sortCompare: (a, b, sortBy) => {
- // We just use our default sort compare to test passing a function
- return defaultSortCompare(a, b, sortBy)
- }
- }
- })
- expect(wrapper).toBeDefined()
- expect(wrapper.findAll('tbody > tr').exists()).toBe(true)
- expect(wrapper.findAll('tbody > tr').length).toBe(3)
- let $rows
- let columnA
-
- await wrapper.vm.$nextTick()
- expect(wrapper.emitted('input')).toBeDefined()
- expect(wrapper.emitted('input').length).toBe(1)
- $rows = wrapper.findAll('tbody > tr').wrappers
- expect($rows.length).toBe(3)
- // Map the rows to the first column text value
- columnA = $rows.map(row => {
- return row
- .findAll('td')
- .at(0)
- .text()
- })
- expect(columnA[0]).toBe('1')
- expect(columnA[1]).toBe('2')
- expect(columnA[2]).toBe('3')
-
- wrapper.destroy()
- })
-
- it('should sort columns when clicking headers', async () => {
- const wrapper = mount(Table, {
- propsData: {
- fields: testFields,
- items: testItems
- }
- })
- expect(wrapper).toBeDefined()
- expect(wrapper.findAll('tbody > tr').exists()).toBe(true)
- expect(wrapper.findAll('tbody > tr').length).toBe(3)
- let $rows
- let columnA
- let columnB
-
- // Should not be sorted
- await wrapper.vm.$nextTick()
- expect(wrapper.emitted('input')).toBeDefined()
- expect(wrapper.emitted('sort-changed')).not.toBeDefined()
- $rows = wrapper.findAll('tbody > tr').wrappers
- expect($rows.length).toBe(3)
- // Map the rows to the first column text value
- columnA = $rows.map(row => {
- return row
- .findAll('td')
- .at(0)
- .text()
- })
- expect(columnA[0]).toBe('3')
- expect(columnA[1]).toBe('1')
- expect(columnA[2]).toBe('2')
-
- // Sort by first column
- wrapper
- .findAll('thead > tr > th')
- .at(0)
- .trigger('click')
- await wrapper.vm.$nextTick()
- expect(wrapper.emitted('sort-changed')).toBeDefined()
- expect(wrapper.emitted('sort-changed').length).toBe(1)
- $rows = wrapper.findAll('tbody > tr').wrappers
- expect($rows.length).toBe(3)
- // Map the rows to the column text value
- columnA = $rows.map(row => {
- return row
- .findAll('td')
- .at(0)
- .text()
- })
- expect(columnA[0]).toBe('1')
- expect(columnA[1]).toBe('2')
- expect(columnA[2]).toBe('3')
-
- // Click first column header again to reverse sort
- wrapper
- .findAll('thead > tr > th')
- .at(0)
- .trigger('click')
- await wrapper.vm.$nextTick()
- expect(wrapper.emitted('sort-changed').length).toBe(2)
- $rows = wrapper.findAll('tbody > tr').wrappers
- expect($rows.length).toBe(3)
- // Map the rows to the column text value
- columnA = $rows.map(row => {
- return row
- .findAll('td')
- .at(0)
- .text()
- })
- expect(columnA[0]).toBe('3')
- expect(columnA[1]).toBe('2')
- expect(columnA[2]).toBe('1')
-
- // Click second column header to sort by it (by using keydown.enter)
- wrapper
- .findAll('thead > tr > th')
- .at(1)
- .trigger('keydown.enter')
- await wrapper.vm.$nextTick()
- expect(wrapper.emitted('sort-changed').length).toBe(3)
- $rows = wrapper.findAll('tbody > tr').wrappers
- expect($rows.length).toBe(3)
- // Map the rows to the column text value
- columnB = $rows.map(row => {
- return row
- .findAll('td')
- .at(1)
- .text()
- })
- expect(columnB[0]).toBe('a')
- expect(columnB[1]).toBe('b')
- expect(columnB[2]).toBe('c')
-
- // Click third column header to clear sort
- wrapper
- .findAll('thead > tr > th')
- .at(2)
- .trigger('click')
- await wrapper.vm.$nextTick()
- expect(wrapper.emitted('sort-changed').length).toBe(4)
- $rows = wrapper.findAll('tbody > tr').wrappers
- expect($rows.length).toBe(3)
- // Map the rows to the column text value
- columnA = $rows.map(row => {
- return row
- .findAll('td')
- .at(0)
- .text()
- })
- expect(columnA[0]).toBe('3')
- expect(columnA[1]).toBe('1')
- expect(columnA[2]).toBe('2')
-
- wrapper.destroy()
- })
-})
diff --git a/src/components/table/table-sorting.spec.js b/src/components/table/table-sorting.spec.js
new file mode 100644
index 00000000000..eeadee28d84
--- /dev/null
+++ b/src/components/table/table-sorting.spec.js
@@ -0,0 +1,700 @@
+import Table from './table'
+import defaultSortCompare from './helpers/default-sort-compare'
+import { mount } from '@vue/test-utils'
+
+const testItems = [{ a: 3, b: 'b', c: 'x' }, { a: 1, b: 'c', c: 'y' }, { a: 2, b: 'a', c: 'z' }]
+const testFields = [
+ { key: 'a', label: 'A', sortable: true },
+ { key: 'b', label: 'B', sortable: true },
+ { key: 'c', label: 'C', sortable: false }
+]
+
+describe('table > sorting', () => {
+ it('should not be sorted by default', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ fields: testFields,
+ items: testItems
+ }
+ })
+ expect(wrapper).toBeDefined()
+ expect(wrapper.findAll('tbody > tr').exists()).toBe(true)
+ expect(wrapper.findAll('tbody > tr').length).toBe(3)
+ await wrapper.vm.$nextTick()
+ expect(wrapper.emitted('input')).toBeDefined()
+ expect(wrapper.emitted('input').length).toBe(1)
+ expect(wrapper.emitted('input')[0][0]).toEqual(testItems)
+ const $rows = wrapper.findAll('tbody > tr').wrappers
+ expect($rows.length).toBe(3)
+ // Map the rows to the first column text value
+ const columnA = $rows.map(row => {
+ return row
+ .findAll('td')
+ .at(0)
+ .text()
+ })
+ expect(columnA[0]).toBe('3')
+ expect(columnA[1]).toBe('1')
+ expect(columnA[2]).toBe('2')
+
+ wrapper.destroy()
+ })
+
+ it('should sort column descending when sortBy set and sortDesc changed, with proper attributes', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ fields: testFields,
+ items: testItems,
+ sortBy: 'a',
+ sortDesc: false
+ }
+ })
+ expect(wrapper).toBeDefined()
+ expect(wrapper.findAll('tbody > tr').exists()).toBe(true)
+ expect(wrapper.findAll('tbody > tr').length).toBe(3)
+ let $rows
+ let columnA
+
+ await wrapper.vm.$nextTick()
+ expect(wrapper.emitted('input')).toBeDefined()
+ expect(wrapper.emitted('input').length).toBe(1)
+ $rows = wrapper.findAll('tbody > tr').wrappers
+ expect($rows.length).toBe(3)
+ // Map the rows to the first column text value
+ columnA = $rows.map(row => {
+ return row
+ .findAll('td')
+ .at(0)
+ .text()
+ })
+ expect(columnA[0]).toBe('1')
+ expect(columnA[1]).toBe('2')
+ expect(columnA[2]).toBe('3')
+
+ let $ths = wrapper.findAll('thead > tr > th')
+
+ // currently sorted as ascending
+ expect($ths.at(0).attributes('aria-sort')).toBe('ascending')
+ // for switching to descending
+ expect($ths.at(0).attributes('aria-label')).toBe(wrapper.vm.labelSortDesc)
+
+ // not sorted by this column
+ expect($ths.at(1).attributes('aria-sort')).toBe('none')
+ // for sorting by ascending
+ expect($ths.at(1).attributes('aria-label')).toBe(wrapper.vm.labelSortAsc)
+
+ // not a sortable column
+ expect($ths.at(2).attributes('aria-sort')).not.toBeDefined()
+ // for clearing sorting
+ expect($ths.at(2).attributes('aria-label')).toBe(wrapper.vm.labelSortClear)
+
+ // Change sort direction
+ wrapper.setProps({
+ sortDesc: true
+ })
+ await wrapper.vm.$nextTick()
+ expect(wrapper.emitted('input').length).toBe(2)
+ $rows = wrapper.findAll('tbody > tr').wrappers
+ expect($rows.length).toBe(3)
+ // Map the rows to the first column text value
+ columnA = $rows.map(row => {
+ return row
+ .findAll('td')
+ .at(0)
+ .text()
+ })
+ expect(columnA[0]).toBe('3')
+ expect(columnA[1]).toBe('2')
+ expect(columnA[2]).toBe('1')
+
+ $ths = wrapper.findAll('thead > tr > th')
+
+ // currently sorted as descending
+ expect($ths.at(0).attributes('aria-sort')).toBe('descending')
+ // for switching to ascending
+ expect($ths.at(0).attributes('aria-label')).toBe(wrapper.vm.labelSortAsc)
+
+ // not sorted by this column
+ expect($ths.at(1).attributes('aria-sort')).toBe('none')
+ // for sorting by ascending
+ expect($ths.at(1).attributes('aria-label')).toBe(wrapper.vm.labelSortAsc)
+
+ // not a sortable column
+ expect($ths.at(2).attributes('aria-sort')).not.toBeDefined()
+ // for clearing sorting
+ expect($ths.at(2).attributes('aria-label')).toBe(wrapper.vm.labelSortClear)
+
+ // Clear sort
+ wrapper.setProps({
+ sortBy: null,
+ sortDesc: false
+ })
+ await wrapper.vm.$nextTick()
+ expect(wrapper.emitted('input').length).toBe(4)
+ $rows = wrapper.findAll('tbody > tr').wrappers
+ expect($rows.length).toBe(3)
+ // Map the rows to the first column text value
+ columnA = $rows.map(row => {
+ return row
+ .findAll('td')
+ .at(0)
+ .text()
+ })
+ expect(columnA[0]).toBe('3')
+ expect(columnA[1]).toBe('1')
+ expect(columnA[2]).toBe('2')
+
+ $ths = wrapper.findAll('thead > tr > th')
+
+ // currently not sorted
+ expect($ths.at(0).attributes('aria-sort')).toBe('none')
+ // for sorting by ascending
+ expect($ths.at(0).attributes('aria-label')).toBe(wrapper.vm.labelSortAsc)
+
+ // not sorted by this column
+ expect($ths.at(1).attributes('aria-sort')).toBe('none')
+ // for sorting by ascending
+ expect($ths.at(1).attributes('aria-label')).toBe(wrapper.vm.labelSortAsc)
+
+ // not a sortable column
+ expect($ths.at(2).attributes('aria-sort')).not.toBeDefined()
+ // for clearing sorting
+ expect($ths.at(2).attributes('aria-label')).not.toBeDefined()
+
+ wrapper.destroy()
+ })
+
+ it('should accept custom sort compare', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ fields: testFields,
+ items: testItems,
+ sortBy: 'a',
+ sortDesc: false,
+ sortCompare: (a, b, sortBy) => {
+ // We just use our default sort compare to test passing a function
+ return defaultSortCompare(a, b, sortBy)
+ }
+ }
+ })
+ expect(wrapper).toBeDefined()
+ expect(wrapper.findAll('tbody > tr').exists()).toBe(true)
+ expect(wrapper.findAll('tbody > tr').length).toBe(3)
+ let $rows
+ let columnA
+
+ await wrapper.vm.$nextTick()
+ expect(wrapper.emitted('input')).toBeDefined()
+ expect(wrapper.emitted('input').length).toBe(1)
+ $rows = wrapper.findAll('tbody > tr').wrappers
+ expect($rows.length).toBe(3)
+ // Map the rows to the first column text value
+ columnA = $rows.map(row => {
+ return row
+ .findAll('td')
+ .at(0)
+ .text()
+ })
+ expect(columnA[0]).toBe('1')
+ expect(columnA[1]).toBe('2')
+ expect(columnA[2]).toBe('3')
+
+ wrapper.destroy()
+ })
+
+ it('should sort columns when clicking headers', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ fields: testFields,
+ items: testItems
+ }
+ })
+ expect(wrapper).toBeDefined()
+ expect(wrapper.findAll('tbody > tr').exists()).toBe(true)
+ expect(wrapper.findAll('tbody > tr').length).toBe(3)
+ let $rows
+ let columnA
+ let columnB
+
+ // Should not be sorted
+ await wrapper.vm.$nextTick()
+ expect(wrapper.emitted('input')).toBeDefined()
+ expect(wrapper.emitted('sort-changed')).not.toBeDefined()
+ $rows = wrapper.findAll('tbody > tr').wrappers
+ expect($rows.length).toBe(3)
+ // Map the rows to the first column text value
+ columnA = $rows.map(row => {
+ return row
+ .findAll('td')
+ .at(0)
+ .text()
+ })
+ expect(columnA[0]).toBe('3')
+ expect(columnA[1]).toBe('1')
+ expect(columnA[2]).toBe('2')
+
+ // Sort by first column
+ wrapper
+ .findAll('thead > tr > th')
+ .at(0)
+ .trigger('click')
+ await wrapper.vm.$nextTick()
+ expect(wrapper.emitted('sort-changed')).toBeDefined()
+ expect(wrapper.emitted('sort-changed').length).toBe(1)
+ expect(wrapper.emitted('sort-changed')[0][0]).toEqual(wrapper.vm.context)
+ $rows = wrapper.findAll('tbody > tr').wrappers
+ expect($rows.length).toBe(3)
+ // Map the rows to the column text value
+ columnA = $rows.map(row => {
+ return row
+ .findAll('td')
+ .at(0)
+ .text()
+ })
+ expect(columnA[0]).toBe('1')
+ expect(columnA[1]).toBe('2')
+ expect(columnA[2]).toBe('3')
+
+ // Click first column header again to reverse sort
+ wrapper
+ .findAll('thead > tr > th')
+ .at(0)
+ .trigger('click')
+ await wrapper.vm.$nextTick()
+ expect(wrapper.emitted('sort-changed').length).toBe(2)
+ expect(wrapper.emitted('sort-changed')[1][0]).toEqual(wrapper.vm.context)
+ $rows = wrapper.findAll('tbody > tr').wrappers
+ expect($rows.length).toBe(3)
+ // Map the rows to the column text value
+ columnA = $rows.map(row => {
+ return row
+ .findAll('td')
+ .at(0)
+ .text()
+ })
+ expect(columnA[0]).toBe('3')
+ expect(columnA[1]).toBe('2')
+ expect(columnA[2]).toBe('1')
+
+ // Click second column header to sort by it (by using keydown.enter)
+ wrapper
+ .findAll('thead > tr > th')
+ .at(1)
+ .trigger('keydown.enter')
+ await wrapper.vm.$nextTick()
+ expect(wrapper.emitted('sort-changed').length).toBe(3)
+ expect(wrapper.emitted('sort-changed')[2][0]).toEqual(wrapper.vm.context)
+ $rows = wrapper.findAll('tbody > tr').wrappers
+ expect($rows.length).toBe(3)
+ // Map the rows to the column text value
+ columnB = $rows.map(row => {
+ return row
+ .findAll('td')
+ .at(1)
+ .text()
+ })
+ expect(columnB[0]).toBe('a')
+ expect(columnB[1]).toBe('b')
+ expect(columnB[2]).toBe('c')
+
+ // Click third column header to clear sort
+ wrapper
+ .findAll('thead > tr > th')
+ .at(2)
+ .trigger('click')
+ await wrapper.vm.$nextTick()
+ expect(wrapper.emitted('sort-changed').length).toBe(4)
+ expect(wrapper.emitted('sort-changed')[3][0]).toEqual(wrapper.vm.context)
+ $rows = wrapper.findAll('tbody > tr').wrappers
+ expect($rows.length).toBe(3)
+ // Map the rows to the column text value
+ columnA = $rows.map(row => {
+ return row
+ .findAll('td')
+ .at(0)
+ .text()
+ })
+ expect(columnA[0]).toBe('3')
+ expect(columnA[1]).toBe('1')
+ expect(columnA[2]).toBe('2')
+
+ wrapper.destroy()
+ })
+
+ it('should sort columns when clicking footers', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ fields: testFields,
+ items: testItems,
+ footClone: true
+ }
+ })
+ expect(wrapper).toBeDefined()
+ expect(wrapper.findAll('tbody > tr').exists()).toBe(true)
+ expect(wrapper.findAll('tbody > tr').length).toBe(3)
+ let $rows
+ let columnA
+ let columnB
+
+ // Should not be sorted
+ await wrapper.vm.$nextTick()
+ expect(wrapper.emitted('input')).toBeDefined()
+ expect(wrapper.emitted('sort-changed')).not.toBeDefined()
+ $rows = wrapper.findAll('tbody > tr').wrappers
+ expect($rows.length).toBe(3)
+ // Map the rows to the first column text value
+ columnA = $rows.map(row => {
+ return row
+ .findAll('td')
+ .at(0)
+ .text()
+ })
+ expect(columnA[0]).toBe('3')
+ expect(columnA[1]).toBe('1')
+ expect(columnA[2]).toBe('2')
+ // Should have aria-* labels
+ expect(wrapper.findAll('tfoot > tr > th[aria-sort]').length).toBe(2)
+ expect(wrapper.findAll('tfoot > tr > th[aria-label]').length).toBe(2)
+
+ // Sort by first column
+ wrapper
+ .findAll('tfoot > tr > th')
+ .at(0)
+ .trigger('click')
+ await wrapper.vm.$nextTick()
+ expect(wrapper.emitted('sort-changed')).toBeDefined()
+ expect(wrapper.emitted('sort-changed').length).toBe(1)
+ expect(wrapper.emitted('sort-changed')[0][0]).toEqual(wrapper.vm.context)
+ $rows = wrapper.findAll('tbody > tr').wrappers
+ expect($rows.length).toBe(3)
+ // Map the rows to the column text value
+ columnA = $rows.map(row => {
+ return row
+ .findAll('td')
+ .at(0)
+ .text()
+ })
+ expect(columnA[0]).toBe('1')
+ expect(columnA[1]).toBe('2')
+ expect(columnA[2]).toBe('3')
+ // Should have aria-* labels
+ expect(wrapper.findAll('tfoot > tr > th[aria-sort]').length).toBe(2)
+ expect(wrapper.findAll('tfoot > tr > th[aria-label]').length).toBe(3)
+
+ // Click first column header again to reverse sort
+ wrapper
+ .findAll('tfoot > tr > th')
+ .at(0)
+ .trigger('click')
+ await wrapper.vm.$nextTick()
+ expect(wrapper.emitted('sort-changed').length).toBe(2)
+ expect(wrapper.emitted('sort-changed')[1][0]).toEqual(wrapper.vm.context)
+ $rows = wrapper.findAll('tbody > tr').wrappers
+ expect($rows.length).toBe(3)
+ // Map the rows to the column text value
+ columnA = $rows.map(row => {
+ return row
+ .findAll('td')
+ .at(0)
+ .text()
+ })
+ expect(columnA[0]).toBe('3')
+ expect(columnA[1]).toBe('2')
+ expect(columnA[2]).toBe('1')
+ // Should have aria-* labels
+ expect(wrapper.findAll('tfoot > tr > th[aria-sort]').length).toBe(2)
+ expect(wrapper.findAll('tfoot > tr > th[aria-label]').length).toBe(3)
+
+ // Click second column header to sort by it (by using keydown.enter)
+ wrapper
+ .findAll('tfoot > tr > th')
+ .at(1)
+ .trigger('keydown.enter')
+ await wrapper.vm.$nextTick()
+ expect(wrapper.emitted('sort-changed').length).toBe(3)
+ expect(wrapper.emitted('sort-changed')[2][0]).toEqual(wrapper.vm.context)
+ $rows = wrapper.findAll('tbody > tr').wrappers
+ expect($rows.length).toBe(3)
+ // Map the rows to the column text value
+ columnB = $rows.map(row => {
+ return row
+ .findAll('td')
+ .at(1)
+ .text()
+ })
+ expect(columnB[0]).toBe('a')
+ expect(columnB[1]).toBe('b')
+ expect(columnB[2]).toBe('c')
+
+ // Click third column header to clear sort
+ wrapper
+ .findAll('tfoot > tr > th')
+ .at(2)
+ .trigger('click')
+ await wrapper.vm.$nextTick()
+ expect(wrapper.emitted('sort-changed').length).toBe(4)
+ expect(wrapper.emitted('sort-changed')[3][0]).toEqual(wrapper.vm.context)
+ $rows = wrapper.findAll('tbody > tr').wrappers
+ expect($rows.length).toBe(3)
+ // Map the rows to the column text value
+ columnA = $rows.map(row => {
+ return row
+ .findAll('td')
+ .at(0)
+ .text()
+ })
+ expect(columnA[0]).toBe('3')
+ expect(columnA[1]).toBe('1')
+ expect(columnA[2]).toBe('2')
+ // Should have aria-* labels
+ expect(wrapper.findAll('tfoot > tr > th[aria-sort]').length).toBe(2)
+ expect(wrapper.findAll('tfoot > tr > th[aria-label]').length).toBe(2)
+
+ wrapper.destroy()
+ })
+
+ it('should not sort columns when clicking footers and no-footer-sorting set', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ fields: testFields,
+ items: testItems,
+ footClone: true,
+ noFooterSorting: true
+ }
+ })
+ expect(wrapper).toBeDefined()
+ expect(wrapper.findAll('tbody > tr').exists()).toBe(true)
+ expect(wrapper.findAll('tbody > tr').length).toBe(3)
+ let $rows
+ let columnA
+
+ // Should not be sorted
+ await wrapper.vm.$nextTick()
+ expect(wrapper.emitted('input')).toBeDefined()
+ expect(wrapper.emitted('sort-changed')).not.toBeDefined()
+ $rows = wrapper.findAll('tbody > tr').wrappers
+ expect($rows.length).toBe(3)
+ // Map the rows to the first column text value
+ columnA = $rows.map(row => {
+ return row
+ .findAll('td')
+ .at(0)
+ .text()
+ })
+ expect(columnA[0]).toBe('3')
+ expect(columnA[1]).toBe('1')
+ expect(columnA[2]).toBe('2')
+ // Shouldn't have aria-* labels
+ expect(wrapper.findAll('tfoot > tr > th[aria-sort]').length).toBe(0)
+ expect(wrapper.findAll('tfoot > tr > th[aria-label]').length).toBe(0)
+
+ // Click first column
+ wrapper
+ .findAll('tfoot > tr > th')
+ .at(0)
+ .trigger('click')
+ await wrapper.vm.$nextTick()
+ expect(wrapper.emitted('sort-changed')).not.toBeDefined()
+ $rows = wrapper.findAll('tbody > tr').wrappers
+ expect($rows.length).toBe(3)
+ // Map the rows to the column text value
+ columnA = $rows.map(row => {
+ return row
+ .findAll('td')
+ .at(0)
+ .text()
+ })
+ expect(columnA[0]).toBe('3')
+ expect(columnA[1]).toBe('1')
+ expect(columnA[2]).toBe('2')
+ // Shouldn't have aria-* labels
+ expect(wrapper.findAll('tfoot > tr > th[aria-sort]').length).toBe(0)
+ expect(wrapper.findAll('tfoot > tr > th[aria-label]').length).toBe(0)
+
+ // Click third column header
+ wrapper
+ .findAll('tfoot > tr > th')
+ .at(2)
+ .trigger('click')
+ await wrapper.vm.$nextTick()
+ expect(wrapper.emitted('sort-changed')).not.toBeDefined()
+ $rows = wrapper.findAll('tbody > tr').wrappers
+ expect($rows.length).toBe(3)
+ // Map the rows to the column text value
+ columnA = $rows.map(row => {
+ return row
+ .findAll('td')
+ .at(0)
+ .text()
+ })
+ expect(columnA[0]).toBe('3')
+ expect(columnA[1]).toBe('1')
+ expect(columnA[2]).toBe('2')
+ // Shouldn't have aria-* labels
+ expect(wrapper.findAll('tfoot > tr > th[aria-sort]').length).toBe(0)
+ expect(wrapper.findAll('tfoot > tr > th[aria-label]').length).toBe(0)
+
+ wrapper.destroy()
+ })
+
+ it('should sort column descending first, when sort-direction=desc', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ fields: testFields,
+ items: testItems,
+ sortDesc: false,
+ sortDirection: 'desc'
+ }
+ })
+ expect(wrapper).toBeDefined()
+ expect(wrapper.findAll('tbody > tr').exists()).toBe(true)
+ expect(wrapper.findAll('tbody > tr').length).toBe(3)
+ let $rows
+ let columnA
+
+ await wrapper.vm.$nextTick()
+ $rows = wrapper.findAll('tbody > tr').wrappers
+ expect($rows.length).toBe(3)
+ // Map the rows to the first column text value
+ columnA = $rows.map(row => {
+ return row
+ .findAll('td')
+ .at(0)
+ .text()
+ })
+ expect(columnA[0]).toBe('3')
+ expect(columnA[1]).toBe('1')
+ expect(columnA[2]).toBe('2')
+
+ let $ths = wrapper.findAll('thead > tr > th')
+
+ // currently not sorted
+ expect($ths.at(0).attributes('aria-sort')).toBe('none')
+ // for switching to descending
+ expect($ths.at(0).attributes('aria-label')).toBe(wrapper.vm.labelSortDesc)
+
+ // not sorted by this column
+ expect($ths.at(1).attributes('aria-sort')).toBe('none')
+ // for sorting by ascending
+ expect($ths.at(1).attributes('aria-label')).toBe(wrapper.vm.labelSortDesc)
+
+ // not a sortable column
+ expect($ths.at(2).attributes('aria-sort')).not.toBeDefined()
+ // for clearing sorting
+ expect($ths.at(2).attributes('aria-label')).not.toBeDefined()
+
+ // Change sort direction (should be descending first)
+ wrapper
+ .findAll('thead > tr > th')
+ .at(0)
+ .trigger('click')
+ await wrapper.vm.$nextTick()
+
+ $rows = wrapper.findAll('tbody > tr').wrappers
+ expect($rows.length).toBe(3)
+ // Map the rows to the first column text value
+ columnA = $rows.map(row => {
+ return row
+ .findAll('td')
+ .at(0)
+ .text()
+ })
+ expect(columnA[0]).toBe('3')
+ expect(columnA[1]).toBe('2')
+ expect(columnA[2]).toBe('1')
+
+ $ths = wrapper.findAll('thead > tr > th')
+
+ // currently sorted as descending
+ expect($ths.at(0).attributes('aria-sort')).toBe('descending')
+ // for switching to ascending
+ expect($ths.at(0).attributes('aria-label')).toBe(wrapper.vm.labelSortAsc)
+
+ // not sorted by this column
+ expect($ths.at(1).attributes('aria-sort')).toBe('none')
+ // for sorting by ascending
+ expect($ths.at(1).attributes('aria-label')).toBe(wrapper.vm.labelSortDesc)
+
+ // not a sortable column
+ expect($ths.at(2).attributes('aria-sort')).not.toBeDefined()
+ // for clearing sorting
+ expect($ths.at(2).attributes('aria-label')).toBe(wrapper.vm.labelSortClear)
+
+ wrapper.destroy()
+ })
+
+ it('non-sortable header th should not emit a sort-changed event when clicked and prop no-sort-reset is set', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ fields: testFields,
+ items: testItems,
+ noSortReset: true
+ }
+ })
+ expect(wrapper).toBeDefined()
+ expect(wrapper.findAll('tbody > tr').exists()).toBe(true)
+ expect(wrapper.findAll('tbody > tr').length).toBe(3)
+ let $rows
+ let columnA
+
+ // Should not be sorted
+ await wrapper.vm.$nextTick()
+ expect(wrapper.emitted('input')).toBeDefined()
+ expect(wrapper.emitted('sort-changed')).not.toBeDefined()
+ $rows = wrapper.findAll('tbody > tr').wrappers
+ expect($rows.length).toBe(3)
+ // Map the rows to the first column text value
+ columnA = $rows.map(row => {
+ return row
+ .findAll('td')
+ .at(0)
+ .text()
+ })
+ expect(columnA[0]).toBe('3')
+ expect(columnA[1]).toBe('1')
+ expect(columnA[2]).toBe('2')
+
+ // Click first column to sort
+ wrapper
+ .findAll('thead > tr > th')
+ .at(0)
+ .trigger('click')
+ await wrapper.vm.$nextTick()
+ expect(wrapper.emitted('sort-changed')).toBeDefined()
+ expect(wrapper.emitted('sort-changed').length).toBe(1)
+ $rows = wrapper.findAll('tbody > tr').wrappers
+ expect($rows.length).toBe(3)
+ // Map the rows to the column text value
+ columnA = $rows.map(row => {
+ return row
+ .findAll('td')
+ .at(0)
+ .text()
+ })
+ expect(columnA[0]).toBe('1')
+ expect(columnA[1]).toBe('2')
+ expect(columnA[2]).toBe('3')
+
+ // Click third column header (should not clear sorting)
+ wrapper
+ .findAll('thead > tr > th')
+ .at(2)
+ .trigger('click')
+ await wrapper.vm.$nextTick()
+ expect(wrapper.emitted('sort-changed').length).toBe(1)
+ $rows = wrapper.findAll('tbody > tr').wrappers
+ expect($rows.length).toBe(3)
+ // Map the rows to the column text value
+ columnA = $rows.map(row => {
+ return row
+ .findAll('td')
+ .at(0)
+ .text()
+ })
+ expect(columnA[0]).toBe('1')
+ expect(columnA[1]).toBe('2')
+ expect(columnA[2]).toBe('3')
+
+ wrapper.destroy()
+ })
+})
diff --git a/src/components/table/table-bottom-row.spec.js b/src/components/table/table-tbody-bottom-row.spec.js
similarity index 98%
rename from src/components/table/table-bottom-row.spec.js
rename to src/components/table/table-tbody-bottom-row.spec.js
index df91e2d256c..8f8bb731705 100644
--- a/src/components/table/table-bottom-row.spec.js
+++ b/src/components/table/table-tbody-bottom-row.spec.js
@@ -5,7 +5,7 @@ import { mount } from '@vue/test-utils'
const testItems = [{ a: 1, b: 2, c: 3 }, { a: 5, b: 5, c: 6 }, { a: 7, b: 8, c: 9 }]
const testFields = ['a', 'b', 'c']
-describe('table bottom-row slot', () => {
+describe('table > tbody bottom-row slot', () => {
it('should not have bottom row by default', async () => {
const wrapper = mount(Table, {
propsData: {
diff --git a/src/components/table/table-tbody-row-events.spec.js b/src/components/table/table-tbody-row-events.spec.js
index 44f8c6c61d3..27373b669bb 100644
--- a/src/components/table/table-tbody-row-events.spec.js
+++ b/src/components/table/table-tbody-row-events.spec.js
@@ -4,12 +4,16 @@ import { mount } from '@vue/test-utils'
const testItems = [{ a: 1, b: 2, c: 3 }, { a: 5, b: 5, c: 6 }, { a: 7, b: 8, c: 9 }]
const testFields = ['a', 'b', 'c']
-describe('table tbody row events', () => {
+describe('table > tbody row events', () => {
it('should emit row-clicked event when a row is clicked', async () => {
const wrapper = mount(Table, {
propsData: {
fields: testFields,
items: testItems
+ },
+ listeners: {
+ // Row Clicked will only occur if there is a registered listener
+ 'row-clicked': () => {}
}
})
expect(wrapper).toBeDefined()
@@ -32,6 +36,10 @@ describe('table tbody row events', () => {
fields: testFields,
items: testItems,
busy: true
+ },
+ listeners: {
+ // Row Clicked will only occur if there is a registered listener
+ 'row-clicked': () => {}
}
})
expect(wrapper).toBeDefined()
@@ -50,6 +58,10 @@ describe('table tbody row events', () => {
propsData: {
fields: testFields,
items: testItems
+ },
+ listeners: {
+ // Row Clicked will only occur if there is a registered listener
+ 'row-clicked': () => {}
}
})
expect(wrapper).toBeDefined()
@@ -283,7 +295,7 @@ describe('table tbody row events', () => {
expect(wrapper.emitted('row-clicked').length).toBe(1)
expect(wrapper.emitted('row-clicked')[0][0]).toEqual(testItems[1]) /* row item */
expect(wrapper.emitted('row-clicked')[0][1]).toEqual(1) /* row index */
- // Note: the KeyboardEvent is forwarded to the click handler
+ // Note: the KeyboardEvent is passed to the row-clicked handler
expect(wrapper.emitted('row-clicked')[0][2]).toBeInstanceOf(KeyboardEvent) /* event */
wrapper.destroy()
@@ -295,6 +307,10 @@ describe('table tbody row events', () => {
fields: testFields,
items: testItems,
busy: true
+ },
+ listeners: {
+ // Row Clicked will only occur if there is a registered listener
+ 'row-clicked': () => {}
}
})
expect(wrapper).toBeDefined()
@@ -319,10 +335,14 @@ describe('table tbody row events', () => {
slots: {
// in Vue 2.6x, slots get translated into scopedSlots
a: 'button ',
- b: ' ',
+ b: ' ',
c: 'link ',
d: '',
- e: 'label '
+ e: 'label '
+ },
+ listeners: {
+ // Row Clicked will only occur if there is a registered listener
+ 'row-clicked': () => {}
}
})
expect(wrapper).toBeDefined()
@@ -358,4 +378,53 @@ describe('table tbody row events', () => {
wrapper.destroy()
})
+
+ it('keyboard events moves focus to apropriate rows', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ fields: testFields,
+ items: testItems
+ },
+ listeners: {
+ // Tabindex will only be set if htere is a row-clicked listener
+ 'row-clicked': () => {}
+ }
+ })
+ expect(wrapper).toBeDefined()
+ const $rows = wrapper.findAll('tbody > tr')
+ expect($rows.length).toBe(3)
+ expect(document.activeElement).not.toBe($rows.at(0).element)
+ expect(document.activeElement).not.toBe($rows.at(1).element)
+ expect(document.activeElement).not.toBe($rows.at(2).element)
+
+ $rows.at(0).element.focus()
+ expect(document.activeElement).toBe($rows.at(0).element)
+
+ $rows.at(0).trigger('keydown.end')
+ expect(document.activeElement).toBe($rows.at(2).element)
+
+ $rows.at(2).trigger('keydown.home')
+ expect(document.activeElement).toBe($rows.at(0).element)
+
+ $rows.at(0).trigger('keydown.down')
+ expect(document.activeElement).toBe($rows.at(1).element)
+
+ $rows.at(1).trigger('keydown.up')
+ expect(document.activeElement).toBe($rows.at(0).element)
+
+ $rows.at(0).trigger('keydown.down', { shiftKey: true })
+ expect(document.activeElement).toBe($rows.at(2).element)
+
+ $rows.at(2).trigger('keydown.up', { shiftKey: true })
+ expect(document.activeElement).toBe($rows.at(0).element)
+
+ // SHould only move focus if TR was target
+ $rows
+ .at(0)
+ .find('td')
+ .trigger('keydown.down')
+ expect(document.activeElement).toBe($rows.at(0).element)
+
+ wrapper.destroy()
+ })
})
diff --git a/src/components/table/table-top-row.spec.js b/src/components/table/table-tbody-top-row.spec.js
similarity index 98%
rename from src/components/table/table-top-row.spec.js
rename to src/components/table/table-tbody-top-row.spec.js
index aa56b8099be..73398c79933 100644
--- a/src/components/table/table-top-row.spec.js
+++ b/src/components/table/table-tbody-top-row.spec.js
@@ -5,7 +5,7 @@ import { mount } from '@vue/test-utils'
const testItems = [{ a: 1, b: 2, c: 3 }, { a: 5, b: 5, c: 6 }, { a: 7, b: 8, c: 9 }]
const testFields = ['a', 'b', 'c']
-describe('table top-row', () => {
+describe('table > tbody top-row slot', () => {
it('should not have top row by default', async () => {
const wrapper = mount(Table, {
propsData: {
diff --git a/src/components/table/table-tbody-transition.spec.js b/src/components/table/table-tbody-transition.spec.js
index f7f391b21d0..7075af0daea 100644
--- a/src/components/table/table-tbody-transition.spec.js
+++ b/src/components/table/table-tbody-transition.spec.js
@@ -4,7 +4,7 @@ import { mount, TransitionGroupStub } from '@vue/test-utils'
const testItems = [{ a: 1, b: 2, c: 3 }, { a: 5, b: 5, c: 6 }, { a: 7, b: 8, c: 9 }]
const testFields = ['a', 'b', 'c']
-describe('table body transition', () => {
+describe('table > tbody transition', () => {
it('tbody should not be a transition-group component by default', async () => {
const wrapper = mount(Table, {
attachToDocument: true,
diff --git a/src/components/table/table-tfoot-events.spec.js b/src/components/table/table-tfoot-events.spec.js
index afdec6a389a..52b477a4e98 100644
--- a/src/components/table/table-tfoot-events.spec.js
+++ b/src/components/table/table-tfoot-events.spec.js
@@ -4,13 +4,17 @@ import { mount } from '@vue/test-utils'
const testItems = [{ a: 1, b: 2, c: 3 }]
const testFields = [{ key: 'a', label: 'A' }, { key: 'b', label: 'B' }, { key: 'c', label: 'C' }]
-describe('table tfoot events', () => {
+describe('table > tfoot events', () => {
it('should emit head-clicked event when a head cell is clicked', async () => {
const wrapper = mount(Table, {
propsData: {
fields: testFields,
items: testItems,
footClone: true
+ },
+ listeners: {
+ // head-clicked will not be emitted unless there is a registered head-clicked listener
+ 'head-clicked': () => {}
}
})
expect(wrapper).toBeDefined()
@@ -44,6 +48,10 @@ describe('table tfoot events', () => {
items: testItems,
footClone: true,
busy: true
+ },
+ listeners: {
+ // head-clicked will not be emitted unless there is a registered head-clicked listener
+ 'head-clicked': () => {}
}
})
expect(wrapper).toBeDefined()
@@ -62,6 +70,10 @@ describe('table tfoot events', () => {
fields: testFields,
items: testItems,
footClone: true
+ },
+ listeners: {
+ // head-clicked will not be emitted unless there is a registered head-clicked listener
+ 'head-clicked': () => {}
}
})
wrapper.setData({
@@ -84,10 +96,14 @@ describe('table tfoot events', () => {
items: testItems,
footClone: true
},
+ listeners: {
+ // head-clicked will not be emitted unless there is a registered head-clicked listener
+ 'head-clicked': () => {}
+ },
slots: {
// in Vue 2.6x, slots get translated into scopedSlots
FOOT_a: 'button ',
- FOOT_b: ' ',
+ FOOT_b: ' ',
// Will use HEAD slot if foot slot not defined
HEAD_c: 'link '
}
diff --git a/src/components/table/table-thead-events.spec.js b/src/components/table/table-thead-events.spec.js
index bd5c8de46e8..431a2caae13 100644
--- a/src/components/table/table-thead-events.spec.js
+++ b/src/components/table/table-thead-events.spec.js
@@ -4,12 +4,38 @@ import { mount } from '@vue/test-utils'
const testItems = [{ a: 1, b: 2, c: 3 }]
const testFields = [{ key: 'a', label: 'A' }, { key: 'b', label: 'B' }, { key: 'c', label: 'C' }]
-describe('table thead events', () => {
+describe('table > thead events', () => {
+ it('should not emit head-clicked event when a head cell is clicked and no head-clicked listener', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ fields: testFields,
+ items: testItems
+ },
+ listeners: {}
+ })
+ expect(wrapper).toBeDefined()
+ const $rows = wrapper.findAll('thead > tr')
+ expect($rows.length).toBe(1)
+ const $ths = wrapper.findAll('thead > tr > th')
+ expect($ths.length).toBe(testFields.length)
+ expect(wrapper.emitted('head-clicked')).not.toBeDefined()
+ $ths.at(0).trigger('click')
+ expect(wrapper.emitted('head-clicked')).not.toBeDefined()
+ $ths.at(1).trigger('click')
+ expect(wrapper.emitted('head-clicked')).not.toBeDefined()
+ $ths.at(2).trigger('click')
+ expect(wrapper.emitted('head-clicked')).not.toBeDefined()
+ })
+
it('should emit head-clicked event when a head cell is clicked', async () => {
const wrapper = mount(Table, {
propsData: {
fields: testFields,
items: testItems
+ },
+ listeners: {
+ // head-clicked will only be emitted if there is a registered listener
+ 'head-clicked': () => {}
}
})
expect(wrapper).toBeDefined()
@@ -42,6 +68,10 @@ describe('table thead events', () => {
fields: testFields,
items: testItems,
busy: true
+ },
+ listeners: {
+ // head-clicked will only be emitted if there is a registered listener
+ 'head-clicked': () => {}
}
})
expect(wrapper).toBeDefined()
@@ -59,6 +89,10 @@ describe('table thead events', () => {
propsData: {
fields: testFields,
items: testItems
+ },
+ listeners: {
+ // head-clicked will only be emitted if there is a registered listener
+ 'head-clicked': () => {}
}
})
wrapper.setData({
@@ -80,10 +114,14 @@ describe('table thead events', () => {
fields: testFields,
items: testItems
},
+ listeners: {
+ // head-clicked will only be emitted if there is a registered listener
+ 'head-clicked': () => {}
+ },
slots: {
// in Vue 2.6x, slots get translated into scopedSlots
HEAD_a: 'button ',
- HEAD_b: ' ',
+ HEAD_b: ' ',
HEAD_c: 'link '
}
})
diff --git a/src/components/table/table-thead-top.spec.js b/src/components/table/table-thead-top.spec.js
index e8d020c45ac..aca91ddefe4 100644
--- a/src/components/table/table-thead-top.spec.js
+++ b/src/components/table/table-thead-top.spec.js
@@ -5,7 +5,7 @@ import { mount } from '@vue/test-utils'
const testItems = [{ a: 1, b: 2, c: 3 }, { a: 5, b: 5, c: 6 }, { a: 7, b: 8, c: 9 }]
const testFields = ['a', 'b', 'c']
-describe('table thead-top slot', () => {
+describe('table > thead thead-top slot', () => {
it('should not have thead-top row by default', async () => {
const wrapper = mount(Table, {
propsData: {
diff --git a/src/components/table/table.js b/src/components/table/table.js
index fb484250fd7..e4918054d48 100644
--- a/src/components/table/table.js
+++ b/src/components/table/table.js
@@ -1,18 +1,15 @@
// Utilities
import looseEqual from '../../utils/loose-equal'
-import stableSort from '../../utils/stable-sort'
-import { arrayIncludes, isArray } from '../../utils/array'
-
-// Table helper functions
-import normalizeFields from './helpers/normalize-fields'
-import stringifyRecordValues from './helpers/stringify-record-values'
-import defaultSortCompare from './helpers/default-sort-compare'
// Mixins
import idMixin from '../../mixins/id'
import normalizeSlotMixin from '../../mixins/normalize-slot'
-// Table helper mixins
+// Table helper Mixins
+import itemsMixin from './helpers/mixin-items'
+import filteringMixin from './helpers/mixin-filtering'
+import sortingMixin from './helpers/mixin-sorting'
+import paginationMixin from './helpers/mixin-pagination'
import captionMixin from './helpers/mixin-caption'
import colgroupMixin from './helpers/mixin-colgroup'
import theadMixin from './helpers/mixin-thead'
@@ -26,9 +23,15 @@ import providerMixin from './helpers/mixin-provider'
// @vue/component
export default {
name: 'BTable',
+ // Order of mixins is important.
+ // They are merged from left to fight, followed by this component.
mixins: [
idMixin,
normalizeSlotMixin,
+ itemsMixin,
+ filteringMixin,
+ sortingMixin,
+ paginationMixin,
busyMixin,
captionMixin,
colgroupMixin,
@@ -41,22 +44,6 @@ export default {
// Don't place ATTRS on root element automatically, as table could be wrapped in responsive div
inheritAttrs: false,
props: {
- items: {
- type: [Array, Function],
- default() /* istanbul ignore next */ {
- return []
- }
- },
- fields: {
- type: [Object, Array],
- default: null
- },
- primaryKey: {
- // Primary key for record.
- // If provided the value in each row must be unique!!!
- type: String,
- default: null
- },
striped: {
type: Boolean,
default: false
@@ -97,59 +84,6 @@ export default {
type: [Boolean, String],
default: false
},
- sortBy: {
- type: String,
- default: null
- },
- sortDesc: {
- type: Boolean,
- default: false
- },
- sortDirection: {
- type: String,
- default: 'asc',
- validator: direction => arrayIncludes(['asc', 'desc', 'last'], direction)
- },
- sortCompare: {
- type: Function,
- default: null
- },
- noSortReset: {
- type: Boolean,
- default: false
- },
- labelSortAsc: {
- type: String,
- default: 'Click to sort Ascending'
- },
- labelSortDesc: {
- type: String,
- default: 'Click to sort Descending'
- },
- perPage: {
- type: [Number, String],
- default: 0
- },
- currentPage: {
- type: [Number, String],
- default: 1
- },
- filter: {
- type: [String, RegExp, Object, Array, Function],
- default: null
- },
- filterFunction: {
- type: Function,
- default: null
- },
- noLocalSorting: {
- type: Boolean,
- default: false
- },
- noFooterSorting: {
- type: Boolean,
- default: false
- },
value: {
// v-model for retrieving the current displayed rows
type: Array,
@@ -159,15 +93,8 @@ export default {
}
},
data() {
- return {
- // Mixins will also add to data
- localSortBy: this.sortBy || '',
- localSortDesc: this.sortDesc || false,
- // Our local copy of the items. Must be an array
- localItems: isArray(this.items) ? this.items.slice() : [],
- // Flag for displaying which empty slot to show, and for some event triggering.
- isFiltered: false
- }
+ // Mixins add to data
+ return {}
},
computed: {
// Layout related computed props
@@ -186,218 +113,65 @@ export default {
: ''
},
tableClasses() {
+ return [
+ {
+ 'table-striped': this.striped,
+ 'table-hover': this.hover,
+ 'table-dark': this.dark,
+ 'table-bordered': this.bordered,
+ 'table-borderless': this.borderless,
+ 'table-sm': this.small,
+ border: this.outlined,
+ // The following are b-table custom styles
+ 'b-table-fixed': this.fixed,
+ 'b-table-stacked': this.stacked === true || this.stacked === '',
+ [`b-table-stacked-${this.stacked}`]: this.stacked !== true && this.stacked
+ },
+ // Selectable classes
+ this.selectableTableClasses
+ ]
+ },
+ tableAttrs() {
+ // Preserve user supplied aria-describedby, if provided in $attrs
+ const adb =
+ [(this.$attrs || {})['aria-describedby'], this.captionId].filter(Boolean).join(' ') || null
+ const items = this.computedItems
+ const fields = this.computedFields
return {
- 'table-striped': this.striped,
- 'table-hover': this.hover,
- 'table-dark': this.dark,
- 'table-bordered': this.bordered,
- 'table-borderless': this.borderless,
- 'table-sm': this.small,
- border: this.outlined,
- // The following are b-table custom styles
- 'b-table-fixed': this.fixed,
- 'b-table-stacked': this.stacked === true || this.stacked === '',
- [`b-table-stacked-${this.stacked}`]: this.stacked !== true && this.stacked,
- 'b-table-selectable': this.selectable
+ // We set aria-rowcount before merging in $attrs, in case user has supplied their own
+ 'aria-rowcount':
+ this.filteredItems.length > items.length ? String(this.filteredItems.length) : null,
+ // Merge in user supplied $attrs if any
+ ...this.$attrs,
+ // Now we can override any $attrs here
+ id: this.safeId(),
+ role: this.isStacked ? 'table' : null,
+ 'aria-busy': this.computedBusy ? 'true' : 'false',
+ 'aria-colcount': String(fields.length),
+ 'aria-describedby': adb,
+ ...this.selectableTableAttrs
}
},
- // Items related computed props
- localFiltering() {
- return this.hasProvider ? !!this.noProviderFiltering : true
- },
- localSorting() {
- return this.hasProvider ? !!this.noProviderSorting : !this.noLocalSorting
- },
- localPaging() {
- return this.hasProvider ? !!this.noProviderPaging : true
- },
context() {
// Current state of sorting, filtering and pagination props/values
return {
filter: this.localFilter,
sortBy: this.localSortBy,
sortDesc: this.localSortDesc,
- perPage: this.perPage,
- currentPage: this.currentPage,
+ perPage: parseInt(this.perPage, 10) || 0,
+ currentPage: parseInt(this.currentPage, 10) || 1,
apiUrl: this.apiUrl
}
},
- computedFields() {
- // We normalize fields into an array of objects
- // [ { key:..., label:..., ...}, {...}, ..., {..}]
- return normalizeFields(this.fields, this.localItems)
- },
- filteredCheck() {
- // For watching changes to filteredItems vs localItems
- return {
- filteredItems: this.filteredItems,
- localItems: this.localItems,
- localFilter: this.localFilter
- }
- },
- localFilter() {
- // Returns a sanitized/normalized version of filter prop
- if (typeof this.filter === 'function') {
- // this.localFilterFn will contain the correct function ref.
- // Deprecate setting prop filter to a function
- /* istanbul ignore next */
- return ''
- } else if (
- typeof this.filterFunction !== 'function' &&
- !(typeof this.filter === 'string' || this.filter instanceof RegExp)
- ) {
- // Using internal filter function, which only accepts string or regexp at the moment
- return ''
- } else {
- // Could be a string, object or array, as needed by external filter function
- return this.filter
- }
- },
- localFilterFn() {
- let filter = this.filter
- let filterFn = this.filterFunction
- // Sanitized/normalize filter-function prop
- if (typeof filterFn === 'function') {
- return filterFn
- } else if (typeof filter === 'function') {
- // Deprecate setting prop filter to a function
- /* istanbul ignore next */
- return filter
- } else {
- // no filterFunction, so signal to use internal filter function
- return null
- }
- },
- filteredItems() {
- // Returns the records in localItems that match the filter criteria.
- // Returns the original localItems array if not sorting
- let items = this.localItems || []
- const criteria = this.localFilter
- const filterFn =
- this.filterFnFactory(this.localFilterFn, criteria) || this.defaultFilterFnFactory(criteria)
-
- // We only do local filtering if requested, and if the are records to filter and
- // if a filter criteria was specified
- if (this.localFiltering && filterFn && items.length > 0) {
- items = items.filter(filterFn)
- }
- return items
- },
- sortedItems() {
- // Sorts the filtered items and returns a new array of the sorted items
- // or the original items array if not sorted.
- let items = this.filteredItems || []
- const sortBy = this.localSortBy
- const sortDesc = this.localSortDesc
- const sortCompare = this.sortCompare
- const localSorting = this.localSorting
- if (sortBy && localSorting) {
- // stableSort returns a new array, and leaves the original array intact
- return stableSort(items, (a, b) => {
- let result = null
- if (typeof sortCompare === 'function') {
- // Call user provided sortCompare routine
- result = sortCompare(a, b, sortBy, sortDesc)
- }
- if (result === null || result === undefined || result === false) {
- // Fallback to built-in defaultSortCompare if sortCompare
- // is not defined or returns null/false
- result = defaultSortCompare(a, b, sortBy)
- }
- // Negate result if sorting in descending order
- return (result || 0) * (sortDesc ? -1 : 1)
- })
- }
- return items
- },
- paginatedItems() {
- let items = this.sortedItems || []
- const currentPage = Math.max(parseInt(this.currentPage, 10) || 1, 1)
- const perPage = Math.max(parseInt(this.perPage, 10) || 0, 0)
- // Apply local pagination
- if (this.localPaging && !!perPage) {
- // Grab the current page of data (which may be past filtered items limit)
- items = items.slice((currentPage - 1) * perPage, currentPage * perPage)
- }
- // Return the items to display in the table
- return items
- },
computedItems() {
return this.paginatedItems || []
}
},
watch: {
- // Watch props for changes and update local values
- items(newItems) {
- if (this.hasProvider || newItems instanceof Function) {
- this.$nextTick(this._providerUpdate)
- } else if (isArray(newItems)) {
- // Set localItems/filteredItems to a copy of the provided array
- this.localItems = newItems.slice()
- } else {
- /* istanbul ignore next */
- this.localItems = []
- }
- },
- sortDesc(newVal, oldVal) {
- if (newVal === this.localSortDesc) {
- /* istanbul ignore next */
- return
- }
- this.localSortDesc = newVal || false
- },
- sortBy(newVal, oldVal) {
- if (newVal === this.localSortBy) {
- /* istanbul ignore next */
- return
- }
- this.localSortBy = newVal || null
- },
- // Update .sync props
- localSortDesc(newVal, oldVal) {
- // Emit update to sort-desc.sync
- if (newVal !== oldVal) {
- this.$emit('update:sortDesc', newVal)
- }
- },
- localSortBy(newVal, oldVal) {
- if (newVal !== oldVal) {
- this.$emit('update:sortBy', newVal)
- }
- },
// Watch for changes on computedItems and update the v-model
computedItems(newVal, oldVal) {
this.$emit('input', newVal)
},
- // Watch for changes to the filter criteria and filtered items vs localItems).
- // And set visual state and emit events as required
- filteredCheck({ filteredItems, localItems, localFilter }) {
- // Determine if the dataset is filtered or not
- let isFiltered
- if (!localFilter) {
- // If filter criteria is falsey
- isFiltered = false
- } else if (looseEqual(localFilter, []) || looseEqual(localFilter, {})) {
- // If filter criteria is an empty array or object
- isFiltered = false
- } else if (localFilter) {
- // if Filter criteria is truthy
- isFiltered = true
- } else {
- isFiltered = false
- }
- if (isFiltered) {
- this.$emit('filtered', filteredItems, filteredItems.length)
- }
- this.isFiltered = isFiltered
- },
- isFiltered(newVal, oldVal) {
- if (newVal === false && oldVal === true) {
- // We need to emit a filtered event if isFiltered transitions from true to
- // false so that users can update their pagination controls.
- this.$emit('filtered', this.localItems, this.localItems.length)
- }
- },
context(newVal, oldVal) {
// Emit context info for external paging/filtering/sorting handling
if (!looseEqual(newVal, oldVal)) {
@@ -409,77 +183,7 @@ export default {
// Initially update the v-model of displayed items
this.$emit('input', this.computedItems)
},
- methods: {
- // Filter Function factories
- filterFnFactory(filterFn, criteria) {
- // Wrapper factory for external filter functions.
- // Wrap the provided filter-function and return a new function.
- // returns null if no filter-function defined or if criteria is falsey.
- // Rather than directly grabbing this.computedLocalFilterFn or this.filterFunction
- // We have it passed, so that the caller computed prop will be reactive to changes
- // in the original filter-function (as this routine is a method)
- if (!filterFn || !criteria || typeof filterFn !== 'function') {
- return null
- }
-
- // Build the wrapped filter test function, passing the criteria to the provided function
- const fn = item => {
- // Generated function returns true if the criteria matches part
- // of the serialized data, otherwise false
- return filterFn(item, criteria)
- }
-
- // Return the wrapped function
- return fn
- },
- defaultFilterFnFactory(criteria) {
- // Generates the default filter function, using the given filter criteria
- if (!criteria || !(typeof criteria === 'string' || criteria instanceof RegExp)) {
- // Built in filter can only support strings or RegExp criteria (at the moment)
- return null
- }
-
- // Build the regexp needed for filtering
- let regexp = criteria
- if (typeof regexp === 'string') {
- // Escape special RegExp characters in the string and convert contiguous
- // whitespace to \s+ matches
- const pattern = criteria
- .replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')
- .replace(/[\s\uFEFF\xA0]+/g, '\\s+')
- // Build the RegExp (no need for global flag, as we only need
- // to find the value once in the string)
- regexp = new RegExp(`.*${pattern}.*`, 'i')
- }
-
- // Generate the wrapped filter test function to use
- const fn = item => {
- // This searches all row values (and sub property values) in the entire (excluding
- // special _ prefixed keys), because we convert the record to a space-separated
- // string containing all the value properties (recursively), even ones that are
- // not visible (not specified in this.fields).
- //
- // TODO: Enable searching on formatted fields and scoped slots
- // TODO: Should we filter only on visible fields (i.e. ones in this.fields) by default?
- // TODO: Allow for searching on specific fields/key, this could be combined with the previous TODO
- // TODO: Give stringifyRecordValues extra options for filtering (i.e. passing the
- // fields definition and a reference to $scopedSlots)
- //
- // Generated function returns true if the criteria matches part of
- // the serialized data, otherwise false
- // We set lastIndex = 0 on regex in case someone uses the /g global flag
- regexp.lastIndex = 0
- return regexp.test(stringifyRecordValues(item))
- }
-
- // Return the generated function
- return fn
- }
- },
render(h) {
- const fields = this.computedFields
- const items = this.computedItems
-
// Build the caption (from caption mixin)
const $caption = this.renderCaption()
@@ -502,31 +206,7 @@ export default {
key: 'b-table',
staticClass: 'table b-table',
class: this.tableClasses,
- attrs: {
- // We set aria-rowcount before merging in $attrs, in case user has supplied their own
- 'aria-rowcount':
- this.filteredItems.length > items.length ? String(this.filteredItems.length) : null,
- // Merge in user supplied $attrs if any
- ...this.$attrs,
- // Now we can override any $attrs here
- id: this.safeId(),
- role: this.isStacked ? 'table' : null,
- 'aria-multiselectable': this.selectable
- ? this.selectMode === 'single'
- ? 'false'
- : 'true'
- : null,
- 'aria-busy': this.computedBusy ? 'true' : 'false',
- 'aria-colcount': String(fields.length),
- 'aria-describedby':
- [
- // Preserve user supplied aria-describedby, if provided in $attrs
- (this.$attrs || {})['aria-describedby'],
- this.captionId
- ]
- .filter(a => a)
- .join(' ') || null
- }
+ attrs: this.tableAttrs
},
[$caption, $colgroup, $thead, $tfoot, $tbody]
)
diff --git a/src/components/table/table.spec.js b/src/components/table/table.spec.js
index 85c94171e04..e67e06ad135 100644
--- a/src/components/table/table.spec.js
+++ b/src/components/table/table.spec.js
@@ -1,923 +1,643 @@
-import { loadFixture, testVM, setData, nextTick, sleep } from '../../../tests/utils'
+import Table from './table'
+import { mount } from '@vue/test-utils'
+
+const items1 = [{ a: 1, b: 2, c: 3 }, { a: 4, b: 5, c: 6 }]
+const fields1 = ['a', 'b', 'c']
describe('table', () => {
- beforeEach(loadFixture(__dirname, 'table'))
- testVM()
-
- it('all example tables should contain class names', async () => {
- const {
- app: { $refs }
- } = window
-
- expect($refs.table_basic).toHaveAllClasses(['table', 'b-table', 'table-striped', 'table-hover'])
-
- expect($refs.table_paginated).toHaveAllClasses([
- 'table',
- 'b-table',
- 'table-sm',
- 'table-striped',
- 'table-bordered',
- 'table-hover'
- ])
-
- expect($refs.table_dark).toHaveAllClasses([
- 'table',
- 'b-table',
- 'table-sm',
- 'table-bordered',
- 'table-dark'
- ])
- })
+ it('has expected default classes', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ items: items1,
+ fields: fields1
+ }
+ })
- it('table_responsive should be wrapped in a div', async () => {
- const {
- app: { $refs }
- } = window
- const table = $refs.table_responsive
+ expect(wrapper).toBeDefined()
+ expect(wrapper.is(Table)).toBe(true)
+ expect(wrapper.is('table')).toBe(true)
+ expect(wrapper.classes()).toContain('table')
+ expect(wrapper.classes()).toContain('b-table')
+ expect(wrapper.classes().length).toBe(2)
- expect(table.$el.tagName).toBe('DIV')
- expect(table).toHaveAllClasses(['table-responsive'])
- expect(table.$el.children.length).toBe(1)
- expect(table.$el.children[0].tagName).toBe('TABLE')
+ wrapper.destroy()
})
- it('should generate fields automatically from the first item', async () => {
- const {
- app: { $refs }
- } = window
- const table = $refs.table_without_fields
- const thead = $refs.table_without_fields.$el.children[0]
- const tr = thead.children[0]
-
- // The row should be equal to the items without any of Bootstrap Vue's
- // utility fields, like _rowVariant, or _cellVariants
- expect(tr.children.length).toBe(Object.keys(table.items[0]).length - 1)
- })
+ it('has class "table-striped" when striped=true', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ items: items1,
+ fields: fields1,
+ striped: true
+ }
+ })
- it('table_basic should have thead and tbody', async () => {
- const {
- app: { $refs }
- } = window
+ expect(wrapper).toBeDefined()
+ expect(wrapper.is(Table)).toBe(true)
+ expect(wrapper.is('table')).toBe(true)
+ expect(wrapper.classes()).toContain('table-striped')
+ expect(wrapper.classes()).toContain('table')
+ expect(wrapper.classes()).toContain('b-table')
+ expect(wrapper.classes().length).toBe(3)
- const parts = [...$refs.table_basic.$el.children]
+ wrapper.destroy()
+ })
- const thead = parts.find(el => el.tagName && el.tagName === 'THEAD')
- expect(thead).toBeDefined()
+ it('has class "table-bordered" when bordered=true', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ items: items1,
+ fields: fields1,
+ bordered: true
+ }
+ })
- const tbody = parts.find(el => el.tagName && el.tagName === 'TBODY')
- expect(tbody).toBeDefined()
+ expect(wrapper).toBeDefined()
+ expect(wrapper.is(Table)).toBe(true)
+ expect(wrapper.is('table')).toBe(true)
+ expect(wrapper.classes()).toContain('table-bordered')
+ expect(wrapper.classes()).toContain('table')
+ expect(wrapper.classes()).toContain('b-table')
+ expect(wrapper.classes().length).toBe(3)
- const tfoot = parts.find(el => el.tagName && el.tagName === 'TFOOT')
- expect(tfoot).not.toBeDefined()
+ wrapper.destroy()
})
- it('table_paginated should have thead, tbody and tfoot', async () => {
- const {
- app: { $refs }
- } = window
-
- const parts = [...$refs.table_paginated.$el.children]
-
- const thead = parts.find(el => el.tagName && el.tagName === 'THEAD')
- expect(thead).toBeDefined()
+ it('has class "table-borderless" when borderless=true', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ items: items1,
+ fields: fields1,
+ borderless: true
+ }
+ })
- const tbody = parts.find(el => el.tagName && el.tagName === 'TBODY')
- expect(tbody).toBeDefined()
+ expect(wrapper).toBeDefined()
+ expect(wrapper.is(Table)).toBe(true)
+ expect(wrapper.is('table')).toBe(true)
+ expect(wrapper.classes()).toContain('table-borderless')
+ expect(wrapper.classes()).toContain('table')
+ expect(wrapper.classes()).toContain('b-table')
+ expect(wrapper.classes().length).toBe(3)
- const tfoot = parts.find(el => el.tagName && el.tagName === 'TFOOT')
- expect(tfoot).toBeDefined()
+ wrapper.destroy()
})
- it('table_dark should have thead and tbody', async () => {
- const {
- app: { $refs }
- } = window
-
- const parts = [...$refs.table_dark.$el.children]
-
- const thead = parts.find(el => el.tagName && el.tagName === 'THEAD')
- expect(thead).toBeDefined()
+ it('has class "table-hover" when hover=true', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ items: items1,
+ fields: fields1,
+ hover: true
+ }
+ })
- const tbody = parts.find(el => el.tagName && el.tagName === 'TBODY')
- expect(tbody).toBeDefined()
+ expect(wrapper).toBeDefined()
+ expect(wrapper.is(Table)).toBe(true)
+ expect(wrapper.is('table')).toBe(true)
+ expect(wrapper.classes()).toContain('table-hover')
+ expect(wrapper.classes()).toContain('table')
+ expect(wrapper.classes()).toContain('b-table')
+ expect(wrapper.classes().length).toBe(3)
- const tfoot = parts.find(el => el.tagName && el.tagName === 'TFOOT')
- expect(tfoot).not.toBeDefined()
+ wrapper.destroy()
})
- it('table_paginated thead should contain class thead-dark', async () => {
- const {
- app: { $refs }
- } = window
- const thead = [...$refs.table_paginated.$el.children].find(el => el && el.tagName === 'THEAD')
- expect(thead).toBeDefined()
- if (thead) {
- expect(thead.classList.contains('thead-dark')).toBe(true)
- }
- })
+ it('has class "table-sm" when small=true', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ items: items1,
+ fields: fields1,
+ small: true
+ }
+ })
- it('table_paginated tfoot should contain class thead-light', async () => {
- const {
- app: { $refs }
- } = window
- const tfoot = [...$refs.table_paginated.$el.children].find(el => el && el.tagName === 'TFOOT')
- expect(tfoot).toBeDefined()
- if (tfoot) {
- expect(tfoot.classList.contains('thead-light')).toBe(true)
- }
+ expect(wrapper).toBeDefined()
+ expect(wrapper.is(Table)).toBe(true)
+ expect(wrapper.is('table')).toBe(true)
+ expect(wrapper.classes()).toContain('table-sm')
+ expect(wrapper.classes()).toContain('table')
+ expect(wrapper.classes()).toContain('b-table')
+ expect(wrapper.classes().length).toBe(3)
+
+ wrapper.destroy()
})
- it('all examples have correct number of columns', async () => {
- const {
- app: { $refs }
- } = window
-
- const tables = ['table_basic', 'table_paginated', 'table_dark']
-
- tables.forEach((table, idx) => {
- const vm = $refs[table]
- const thead = [...vm.$el.children].find(el => el && el.tagName === 'THEAD')
- expect(thead).toBeDefined()
- if (thead) {
- const tr = [...thead.children].find(el => el && el.tagName === 'TR')
- expect(tr).toBeDefined()
- if (tr) {
- expect(tr.children.length).toBe(Object.keys(vm.fields).length)
- }
+ it('has class "table-dark" when dark=true', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ items: items1,
+ fields: fields1,
+ dark: true
}
})
- })
- it('all examples should show the correct number of visible rows', async () => {
- const {
- app: { $refs }
- } = window
- const app = window.app
+ expect(wrapper).toBeDefined()
+ expect(wrapper.is(Table)).toBe(true)
+ expect(wrapper.is('table')).toBe(true)
+ expect(wrapper.classes()).toContain('table-dark')
+ expect(wrapper.classes()).toContain('table')
+ expect(wrapper.classes()).toContain('b-table')
+ expect(wrapper.classes().length).toBe(3)
- const tables = ['table_basic', 'table_paginated', 'table_dark']
+ wrapper.destroy()
+ })
- tables.forEach((table, idx) => {
- const vm = $refs[table]
- const tbody = [...vm.$el.children].find(el => el && el.tagName === 'TBODY')
- expect(tbody).toBeDefined()
- if (tbody) {
- expect(tbody.children.length).toBe(vm.perPage || app.items.length)
+ it('has class "border" when outlined=true', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ items: items1,
+ fields: fields1,
+ outlined: true
}
})
+
+ expect(wrapper).toBeDefined()
+ expect(wrapper.is(Table)).toBe(true)
+ expect(wrapper.is('table')).toBe(true)
+ expect(wrapper.classes()).toContain('border')
+ expect(wrapper.classes()).toContain('table')
+ expect(wrapper.classes()).toContain('b-table')
+ expect(wrapper.classes().length).toBe(3)
+
+ wrapper.destroy()
})
- it('all examples have sortable & unsortable headers', async () => {
- const {
- app: { $refs }
- } = window
-
- const tables = ['table_basic', 'table_paginated', 'table_dark']
- // const sortables = [true, true, false, false]
-
- tables.forEach(table => {
- const vm = $refs[table]
- const thead = [...vm.$el.children].find(el => el && el.tagName === 'THEAD')
- expect(thead).toBeDefined()
- if (thead) {
- const tr = [...thead.children].find(el => el && el.tagName === 'TR')
- expect(tr).toBeDefined()
- if (tr) {
- const fieldKeys = Object.keys(vm.fields)
- const ths = [...tr.children]
- expect(ths.length).toBe(fieldKeys.length)
- ths.forEach((th, idx) => {
- expect(th.hasAttribute('aria-sort')).toBe(vm.fields[fieldKeys[idx]].sortable || false)
- })
- }
+ it('has class "b-table-fixed" when fixed=true', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ items: items1,
+ fields: fields1,
+ fixed: true
}
})
- })
- it('table_paginated has sortable & unsortable footers', async () => {
- const {
- app: { $refs }
- } = window
- const vm = $refs.table_paginated
- const fieldKeys = Object.keys(vm.fields)
-
- const tfoot = [...vm.$el.children].find(el => el && el.tagName === 'TFOOT')
- expect(tfoot).toBeDefined()
- if (tfoot) {
- const tr = [...tfoot.children].find(el => el && el.tagName === 'TR')
- expect(tr).toBeDefined()
- if (tr) {
- const ths = [...tr.children]
- expect(ths.length).toBe(fieldKeys.length)
- ths.forEach((th, idx) => {
- expect(th.hasAttribute('aria-sort')).toBe(vm.fields[fieldKeys[idx]].sortable || false)
- })
- }
- }
- })
+ expect(wrapper).toBeDefined()
+ expect(wrapper.is(Table)).toBe(true)
+ expect(wrapper.is('table')).toBe(true)
+ expect(wrapper.classes()).toContain('b-table-fixed')
+ expect(wrapper.classes()).toContain('table')
+ expect(wrapper.classes()).toContain('b-table')
+ expect(wrapper.classes().length).toBe(3)
- it('all example tables should have attribute aria-busy="false" when busy is false', async () => {
- const { app } = window
+ wrapper.destroy()
+ })
- const tables = ['table_basic', 'table_paginated', 'table_dark']
+ it('has class "b-table-stacked" when stacked=true', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ items: items1,
+ fields: fields1,
+ stacked: true
+ }
+ })
- await setData(app, 'isBusy', false)
- await nextTick()
+ expect(wrapper).toBeDefined()
+ expect(wrapper.is(Table)).toBe(true)
+ expect(wrapper.is('table')).toBe(true)
+ expect(wrapper.classes()).toContain('b-table-stacked')
+ expect(wrapper.classes()).toContain('table')
+ expect(wrapper.classes()).toContain('b-table')
+ expect(wrapper.classes().length).toBe(3)
- tables.forEach(table => {
- expect(app.$refs[table].$el.getAttribute('aria-busy')).toBe('false')
- })
+ wrapper.destroy()
})
- it('table_paginated should have attribute aria-busy="true" when busy is true', async () => {
- const {
- app: { $refs }
- } = window
- const app = window.app
+ it('has class "b-table-stacked-md" when stacked=md', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ items: items1,
+ fields: fields1,
+ stacked: 'md'
+ }
+ })
- await setData(app, 'isBusy', true)
- await nextTick()
- expect($refs.table_paginated.$el.getAttribute('aria-busy')).toBe('true')
+ expect(wrapper).toBeDefined()
+ expect(wrapper.is(Table)).toBe(true)
+ expect(wrapper.is('table')).toBe(true)
+ expect(wrapper.classes()).toContain('b-table-stacked-md')
+ expect(wrapper.classes()).toContain('table')
+ expect(wrapper.classes()).toContain('b-table')
+ expect(wrapper.classes().length).toBe(3)
- await setData(app, 'isBusy', false)
- await nextTick()
- expect($refs.table_paginated.$el.getAttribute('aria-busy')).toBe('false')
+ wrapper.destroy()
})
- it('sortable columns should have ARIA labels in thead', async () => {
- const {
- app: { $refs }
- } = window
- const vm = $refs.table_paginated
- const ariaLabel = vm.labelSortDesc
-
- const thead = [...vm.$el.children].find(el => el && el.tagName === 'THEAD')
- expect(thead).toBeDefined()
- if (thead) {
- const tr = [...thead.children].find(el => el && el.tagName === 'TR')
- expect(tr).toBeDefined()
- if (tr) {
- expect(tr.children[0].getAttribute('aria-label')).toBe(ariaLabel)
- expect(tr.children[1].getAttribute('aria-label')).toBe(ariaLabel)
- expect(tr.children[2].getAttribute('aria-label')).toBe(null)
- expect(tr.children[3].getAttribute('aria-label')).toBe(null)
+ it('has class "table-responsive" when responsive=true', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ items: items1,
+ fields: fields1,
+ responsive: true
}
- }
- })
+ })
- it('sortable columns should have ARIA labels in tfoot', async () => {
- const {
- app: { $refs }
- } = window
- const vm = $refs.table_paginated
- const ariaLabel = vm.labelSortDesc
-
- const tfoot = [...vm.$el.children].find(el => el && el.tagName === 'THEAD')
- expect(tfoot).toBeDefined()
- if (tfoot) {
- const tr = [...tfoot.children].find(el => el && el.tagName === 'TR')
- expect(tr).toBeDefined()
- if (tr) {
- expect(tr.children[0].getAttribute('aria-label')).toBe(ariaLabel)
- expect(tr.children[1].getAttribute('aria-label')).toBe(ariaLabel)
- expect(tr.children[2].getAttribute('aria-label')).toBe(null)
- expect(tr.children[3].getAttribute('aria-label')).toBe(null)
- }
- }
+ expect(wrapper).toBeDefined()
+ expect(wrapper.is(Table)).toBe(true)
+ expect(wrapper.is('div')).toBe(true)
+ expect(wrapper.classes()).toContain('table-responsive')
+ expect(wrapper.classes().length).toBe(1)
+ expect(wrapper.find('table').classes()).toContain('table')
+ expect(wrapper.find('table').classes()).toContain('b-table')
+ expect(wrapper.find('table').classes().length).toBe(2)
+
+ wrapper.destroy()
})
- it('all examples should have variant "success" on 1st row', async () => {
- const {
- app: { $refs }
- } = window
- const app = window.app
-
- const tables = ['table_basic', 'table_paginated', 'table_dark']
-
- const items = app.items.slice()
- items[0]._rowVariant = 'success'
- await setData(app, 'items', items)
- await nextTick()
-
- tables.forEach((table, idx) => {
- const vm = $refs[table]
- const tbody = [...vm.$el.children].find(el => el && el.tagName === 'TBODY')
- expect(tbody).toBeDefined()
- if (tbody) {
- const tr = tbody.children[0]
- const variant = vm.dark ? 'bg-success' : 'table-success'
- expect(Boolean(tr) && Boolean(tr.classList) && tr.classList.contains(variant)).toBe(true)
+ it('has class "table-responsive-md" when responsive=md', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ items: items1,
+ fields: fields1,
+ responsive: 'md'
}
})
- })
- it('table_basic should contain custom formatted columns', async () => {
- const { app } = window
- const vm = app.$refs.table_basic
-
- const tbody = [...vm.$el.children].find(el => el && el.tagName === 'TBODY')
- expect(tbody).toBeDefined()
- if (tbody) {
- const tr = [...tbody.children].find(el => el && el.tagName === 'TR')
- expect(tr).toBeDefined()
- if (tr) {
- expect(tr.children[0].textContent).toContain(
- vm.items[0].name.first + ' ' + vm.items[0].name.last
- )
- expect(tr.children[1].textContent).toContain(String(vm.items[0].age))
- expect(tr.children[3].children[0].tagName).toBe('BUTTON')
+ expect(wrapper).toBeDefined()
+ expect(wrapper.is(Table)).toBe(true)
+ expect(wrapper.is('div')).toBe(true)
+ expect(wrapper.classes()).toContain('table-responsive-md')
+ expect(wrapper.classes().length).toBe(1)
+ expect(wrapper.find('table').classes()).toContain('table')
+ expect(wrapper.find('table').classes()).toContain('b-table')
+ expect(wrapper.find('table').classes().length).toBe(2)
+
+ wrapper.destroy()
+ })
+
+ it('stacked has precedence over responsive', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ items: items1,
+ fields: fields1,
+ stacked: true,
+ responsive: true
}
- }
- })
+ })
- it('table_paginated should contain custom formatted columns', async () => {
- const { app } = window
- const vm = app.$refs.table_basic
-
- const tbody = [...app.$refs.table_paginated.$el.children].find(
- el => el && el.tagName === 'TBODY'
- )
- expect(tbody).toBeDefined()
- if (tbody) {
- const tr = [...tbody.children].find(el => el && el.tagName === 'TR')
- expect(tr).toBeDefined()
- if (tr) {
- expect(tr.children[0].textContent).toContain(
- vm.items[0].name.first + ' ' + vm.items[0].name.last
- )
- expect(tr.children[1].textContent).toContain(String(vm.items[0].age))
- expect(tr.children[3].children[0].tagName).toBe('INPUT')
- }
- }
- })
+ expect(wrapper).toBeDefined()
+ expect(wrapper.is(Table)).toBe(true)
+ expect(wrapper.is('table')).toBe(true)
+ expect(wrapper.classes()).not.toContain('table-responsive')
+ expect(wrapper.classes()).toContain('b-table-stacked')
+ expect(wrapper.classes()).toContain('table')
+ expect(wrapper.classes()).toContain('b-table')
+ expect(wrapper.classes().length).toBe(3)
- it('table_paginated should contain custom formatted headers', async () => {
- const {
- app: { $refs }
- } = window
-
- const thead = [...$refs.table_paginated.$el.children].find(el => el && el.tagName === 'THEAD')
- expect(thead).toBeDefined()
- if (thead) {
- const tr = [...thead.children].find(el => el && el.tagName === 'TR')
- expect(tr).toBeDefined()
- if (tr) {
- expect(tr.children[0].textContent).toContain('Person Full name')
- expect(tr.children[1].textContent).toContain('Person age')
- expect(tr.children[2].textContent).toContain('is Active')
- expect(tr.children[3].textContent).toContain('Select')
- }
- }
+ wrapper.destroy()
})
- it('table_paginated should contain custom formatted footers', async () => {
- const {
- app: { $refs }
- } = window
-
- await nextTick()
-
- const tfoot = [...$refs.table_paginated.$el.children].find(el => el && el.tagName === 'TFOOT')
- expect(tfoot).toBeDefined()
- if (tfoot) {
- const tr = [...tfoot.children].find(el => el && el.tagName === 'TR')
- expect(tr).toBeDefined()
- if (tr) {
- expect(tr.children[0].textContent).toContain('Showing 5 People')
- expect(tr.children[1].textContent).toContain('Person age')
- expect(tr.children[2].textContent).toContain('is Active')
- expect(tr.children[3].textContent).toContain('Selected: 0')
+ it('stacked has data-label attribute on all tbody > tr td', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ items: items1,
+ fields: fields1,
+ stacked: true
}
- }
- })
+ })
+ expect(wrapper).toBeDefined()
+ expect(wrapper.findAll('tbody > tr').length).toBe(2)
+ const $trs = wrapper.findAll('tbody > tr').wrappers
+
+ // Labels will have run through startCase
+ expect(
+ $trs[0]
+ .findAll('td')
+ .at(0)
+ .attributes('data-label')
+ ).toBe('A')
+ expect(
+ $trs[1]
+ .findAll('td')
+ .at(0)
+ .attributes('data-label')
+ ).toBe('A')
+
+ expect(
+ $trs[0]
+ .findAll('td')
+ .at(1)
+ .attributes('data-label')
+ ).toBe('B')
+ expect(
+ $trs[1]
+ .findAll('td')
+ .at(1)
+ .attributes('data-label')
+ ).toBe('B')
+
+ expect(
+ $trs[0]
+ .findAll('td')
+ .at(2)
+ .attributes('data-label')
+ ).toBe('C')
+ expect(
+ $trs[1]
+ .findAll('td')
+ .at(2)
+ .attributes('data-label')
+ ).toBe('C')
+
+ wrapper.destroy()
+ })
+
+ it('item _rowVariant works', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ items: [{ a: 1, _rowVariant: 'primary' }],
+ fields: ['a'],
+ dark: false
+ }
+ })
- it('each data row should emit a row-clicked event when clicked', async () => {
- const {
- app: { $refs }
- } = window
- const vm = $refs.table_paginated
-
- const tbody = [...vm.$el.children].find(el => el && el.tagName === 'TBODY')
- expect(tbody).toBeDefined()
- if (tbody) {
- const trs = [...tbody.children]
- expect(trs.length).toBe(vm.perPage)
- trs.forEach((tr, idx) => {
- const spy = jest.fn()
- vm.$on('row-clicked', spy)
- tr.click()
- vm.$off('row-clicked', spy)
- expect(spy).toHaveBeenCalled()
- })
- }
- })
+ expect(wrapper).toBeDefined()
+ expect(wrapper.findAll('tbody > tr').length).toBe(1)
+ expect(wrapper.find('tbody > tr').classes()).toContain('table-primary')
- /*
- * This test needs a polyfill for getSelection and createRange
- *
- it('row-clicked event should not happen when textSelection is active', async () => {
- const {
- app: { $refs }
- } = window
- const vm = $refs.table_paginated
-
- const tbody = [...vm.$el.children].find(el => el && el.tagName === 'TBODY')
- expect(tbody).toBeDefined()
- const trs = [...tbody.children]
- expect(trs.length).toBe(vm.perPage)
-
- // Clear selection if any current
- let selection = window.getSelection()
- if (selection.rangeCount > 0) {
- selection.removeAllRanges()
- }
+ wrapper.setProps({
+ dark: true
+ })
- const spy = jest.fn()
- vm.$on('row-clicked', spy)
- expect(spy).not.toHaveBeenCalled()
+ expect(wrapper.findAll('tbody > tr').length).toBe(1)
+ expect(wrapper.find('tbody > tr').classes()).toContain('bg-primary')
- // Select text in first TR
- const range = document.createRange()
- range.selectNode(trs[0])
- selection.addRange(range)
+ wrapper.destroy()
+ })
- // Click row
- trs[0].click()
- expect(spy).not.toHaveBeenCalled()
+ it('item _cellVariants works', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ items: [{ a: 1, _cellVariants: { a: 'info' } }],
+ fields: ['a'],
+ dark: false
+ }
+ })
- // Clear selection
- if (selection.rangeCount > 0) {
- selection.removeAllRanges()
- }
+ expect(wrapper).toBeDefined()
+ expect(wrapper.findAll('tbody > tr').length).toBe(1)
+ expect(wrapper.findAll('tbody > tr > td').length).toBe(1)
+ expect(wrapper.find('tbody > tr > td').classes()).toContain('table-info')
- // Click row
- trs[0].click()
- expect(spy).toHaveBeenCalled()
- })
- */
-
- it('each data row should emit a row-contextmenu event when right clicked', async () => {
- const {
- app: { $refs }
- } = window
- const vm = $refs.table_paginated
-
- const tbody = [...vm.$el.children].find(el => el && el.tagName === 'TBODY')
- expect(tbody).toBeDefined()
- if (tbody) {
- const trs = [...tbody.children]
- expect(trs.length).toBe(vm.perPage)
- trs.forEach((tr, idx) => {
- const spy = jest.fn()
- vm.$on('row-contextmenu', spy)
- tr.dispatchEvent(new MouseEvent('contextmenu', { button: 2 }))
- vm.$off('row-contextmenu', spy)
- expect(spy).toHaveBeenCalled()
- })
- }
- })
+ wrapper.setProps({
+ dark: true
+ })
- it('each data row should emit a row-middle-clicked event when middle clicked', async () => {
- const {
- app: { $refs }
- } = window
- const vm = $refs.table_paginated
-
- const tbody = [...vm.$el.children].find(el => el && el.tagName === 'TBODY')
- expect(tbody).toBeDefined()
- if (tbody) {
- const trs = [...tbody.children]
- expect(trs.length).toBe(vm.perPage)
- trs.forEach((tr, idx) => {
- const spy = jest.fn()
- vm.$on('row-middle-clicked', spy)
- tr.dispatchEvent(new MouseEvent('auxclick', { button: 1, which: 2 }))
- vm.$off('row-middle-clicked', spy)
- expect(spy).toHaveBeenCalled()
- })
- }
- })
+ expect(wrapper.findAll('tbody > tr').length).toBe(1)
+ expect(wrapper.findAll('tbody > tr > td').length).toBe(1)
+ expect(wrapper.find('tbody > tr > td').classes()).toContain('bg-info')
- it('each header th should emit a head-clicked event when clicked', async () => {
- const {
- app: { $refs }
- } = window
- const vm = $refs.table_paginated
- const fieldKeys = Object.keys(vm.fields)
-
- const thead = [...vm.$el.children].find(el => el && el.tagName === 'THEAD')
- expect(thead).toBeDefined()
- if (thead) {
- const tr = [...thead.children].find(el => el && el.tagName === 'TR')
- expect(tr).toBeDefined()
- if (tr) {
- const ths = [...tr.children]
- expect(ths.length).toBe(fieldKeys.length)
- ths.forEach((th, idx) => {
- const spy = jest.fn()
- vm.$on('head-clicked', spy)
- th.click()
- vm.$off('head-clicked', spy)
- expect(spy).toHaveBeenCalled()
- })
- }
- }
+ wrapper.destroy()
})
- it('each footer th should emit a head-clicked event when clicked', async () => {
- const {
- app: { $refs }
- } = window
- const vm = $refs.table_paginated
- const fieldKeys = Object.keys(vm.fields)
-
- const tfoot = [...vm.$el.children].find(el => el && el.tagName === 'TFOOT')
- expect(tfoot).toBeDefined()
- if (tfoot) {
- const tr = [...tfoot.children].find(el => el && el.tagName === 'TR')
- expect(tr).toBeDefined()
- if (tr) {
- const ths = [...tr.children]
- expect(ths.length).toBe(fieldKeys.length)
- ths.forEach((th, idx) => {
- const spy = jest.fn()
- vm.$on('head-clicked', spy)
- th.click()
- vm.$off('head-clicked', spy)
- expect(spy).toHaveBeenCalled()
- })
+ it('changing items array works', async () => {
+ const items1 = [{ a: 1, b: 2 }, { a: 3, b: 4 }]
+ const items2 = [{ a: 3, b: 4 }]
+ const wrapper = mount(Table, {
+ propsData: {
+ items: items1,
+ fields: ['a', 'b']
}
- }
+ })
+ expect(wrapper).toBeDefined()
+
+ expect(wrapper.findAll('tbody > tr').length).toBe(2)
+ wrapper.setProps({
+ items: items2
+ })
+ expect(wrapper.findAll('tbody > tr').length).toBe(1)
+
+ wrapper.destroy()
})
- it('sortable header th should emit a sort-changed event with context when clicked and sort changed', async () => {
- const {
- app: { $refs }
- } = window
- const vm = $refs.table_paginated
- const spy = jest.fn()
- const fieldKeys = Object.keys(vm.fields)
-
- vm.$on('sort-changed', spy)
- const thead = [...vm.$el.children].find(el => el && el.tagName === 'THEAD')
- expect(thead).toBeDefined()
- if (thead) {
- const tr = [...thead.children].find(el => el && el.tagName === 'TR')
- expect(tr).toBeDefined()
- if (tr) {
- let sortBy = null
- const ths = [...tr.children]
- expect(ths.length).toBe(fieldKeys.length)
- ths.forEach((th, idx) => {
- th.click()
- if (vm.fields[fieldKeys[idx]].sortable) {
- expect(spy).toHaveBeenCalledWith(vm.context)
- expect(vm.context.sortBy).toBe(fieldKeys[idx])
- sortBy = vm.context.sortBy
- } else {
- if (sortBy) {
- expect(spy).toHaveBeenCalledWith(vm.context)
- expect(vm.context.sortBy).toBe(null)
- sortBy = null
- } else {
- expect(spy).not.toHaveBeenCalled()
- expect(vm.context.sortBy).toBe(null)
- }
- }
- spy.mockClear()
- })
+ it('tbody-tr-class works', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ items: [{ a: 1, b: 2 }, { a: 3, b: 4 }],
+ fields: ['a', 'b'],
+ tbodyTrClass: 'foobar'
}
- }
- })
+ })
- it('sortable footer th should emit a sort-changed event with context when clicked and sort changed', async () => {
- const {
- app: { $refs }
- } = window
- const vm = $refs.table_paginated
- const spy = jest.fn()
- const fieldKeys = Object.keys(vm.fields)
-
- vm.$on('sort-changed', spy)
- const tfoot = [...vm.$el.children].find(el => el && el.tagName === 'TFOOT')
- expect(tfoot).toBeDefined()
- if (tfoot) {
- const tr = [...tfoot.children].find(el => el && el.tagName === 'TR')
- expect(tr).toBeDefined()
- if (tr) {
- let sortBy = null
- const ths = [...tr.children]
- expect(ths.length).toBe(fieldKeys.length)
- ths.forEach((th, idx) => {
- th.click()
- if (vm.fields[fieldKeys[idx]].sortable) {
- expect(spy).toHaveBeenCalledWith(vm.context)
- expect(vm.context.sortBy).toBe(fieldKeys[idx])
- sortBy = vm.context.sortBy
- } else {
- if (sortBy) {
- expect(spy).toHaveBeenCalledWith(vm.context)
- expect(vm.context.sortBy).toBe(null)
- sortBy = null
- } else {
- expect(spy).not.toHaveBeenCalled()
- expect(vm.context.sortBy).toBe(null)
- }
- }
- spy.mockClear()
- })
+ expect(wrapper).toBeDefined()
+
+ // prop as a string
+ expect(wrapper.findAll('tbody > tr').length).toBe(2)
+ let $trs = wrapper.findAll('tbody > tr')
+ expect($trs.at(0).classes()).toContain('foobar')
+ expect($trs.at(1).classes()).toContain('foobar')
+
+ // As a function
+ wrapper.setProps({
+ tbodyTrClass: item => {
+ return item.a === 1 ? 'foo' : 'bar'
}
- }
+ })
+
+ expect(wrapper.findAll('tbody > tr').length).toBe(2)
+ $trs = wrapper.findAll('tbody > tr')
+ expect($trs.at(0).classes()).toContain('foo')
+ expect($trs.at(0).classes()).not.toContain('bar')
+ expect($trs.at(1).classes()).toContain('bar')
+ expect($trs.at(1).classes()).not.toContain('foo')
+
+ wrapper.destroy()
})
- it('non-sortable header th should not emit a sort-changed event when clicked and prop no-sort-reset is set', async () => {
- const {
- app: { $refs }
- } = window
- const vm = $refs.table_no_sort_reset
- const spy = jest.fn()
- const fieldKeys = Object.keys(vm.fields)
-
- vm.$on('sort-changed', spy)
- const thead = [...vm.$el.children].find(el => el && el.tagName === 'THEAD')
- expect(thead).toBeDefined()
- if (thead) {
- const tr = [...thead.children].find(el => el && el.tagName === 'TR')
- expect(tr).toBeDefined()
- if (tr) {
- let sortBy = null
- const ths = [...tr.children]
- expect(ths.length).toBe(fieldKeys.length)
- ths.forEach((th, idx) => {
- th.click()
- if (vm.fields[fieldKeys[idx]].sortable) {
- expect(spy).toHaveBeenCalledWith(vm.context)
- expect(vm.context.sortBy).toBe(fieldKeys[idx])
- sortBy = vm.context.sortBy
- } else {
- expect(spy).not.toHaveBeenCalled()
- expect(vm.context.sortBy).toBe(sortBy)
- }
- spy.mockClear()
- })
+ it('thead and tfoot variant and classes work', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ items: [{ a: 1, b: 2 }],
+ fields: ['a', 'b'],
+ footClone: true
}
- }
- })
+ })
- it('table_paginated pagination works', async () => {
- const {
- app: { $refs }
- } = window
- const vm = $refs.table_paginated
- const app = window.app
- const spy = jest.fn()
-
- const tbody = [...vm.$el.children].find(el => el && el.tagName === 'TBODY')
- expect(tbody).toBeDefined()
- if (tbody) {
- // We need between 11 and 14 ites for this test
- expect(app.items.length > 10).toBe(true)
- expect(app.items.length < 15).toBe(true)
-
- vm.$on('input', spy)
-
- // Page size to be less then number of items
- await setData(app, 'currentPage', 1)
- await setData(app, 'perPage', 10)
- await nextTick()
- expect(vm.perPage).toBe(10)
- expect(vm.value.length).toBe(10)
- expect(tbody.children.length).toBe(10)
-
- // Goto page 2, should have length 1
- await setData(app, 'currentPage', 2)
- await nextTick()
- expect(vm.value.length).toBe(app.items.length - 10)
- expect(tbody.children.length).toBe(app.items.length - 10)
-
- expect(spy).toHaveBeenCalled()
- }
- })
+ expect(wrapper).toBeDefined()
+ expect(wrapper.findAll('thead > tr').length).toBe(1)
+ expect(wrapper.findAll('tfoot > tr').length).toBe(1)
- it('table_paginated filtering works', async () => {
- const {
- app: { $refs }
- } = window
- const vm = $refs.table_paginated
- const app = window.app
- const spyInput = jest.fn()
- const spyFiltered = jest.fn()
-
- expect(vm.showEmpty).toBe(true)
- expect(app.items.length > 10).toBe(true)
- expect(app.items.length < 15).toBe(true)
-
- const tbody = [...vm.$el.children].find(el => el && el.tagName === 'TBODY')
- expect(tbody).toBeDefined()
- if (tbody) {
- expect(app.items.length > 1).toBe(true)
-
- vm.$on('input', spyInput)
-
- // Set page size to max number of items
- await setData(app, 'currentPage', 1)
- await setData(app, 'perPage', 15)
- await nextTick()
- expect(vm.value.length).toBe(app.items.length)
- expect(tbody.children.length).toBe(app.items.length)
-
- // Apply Fiter
- await setData(app, 'filter', String(app.items[0].name.last))
- await nextTick()
- expect(vm.value.length < app.items.length).toBe(true)
- expect(tbody.children.length < app.items.length).toBe(true)
-
- // Empty filter alert
- vm.$on('filtered', spyFiltered)
- await setData(app, 'filter', 'ZZZZZZZZZZZZZZZZZzzzzzzzzzzzzzzzzz........')
- await nextTick()
-
- expect(spyFiltered).toHaveBeenCalled()
-
- expect(vm.value.length).toBe(0)
- expect(tbody.children.length).toBe(1)
- expect(tbody.children[0].children[0].textContent).toContain(vm.emptyFilteredText)
-
- expect(spyInput).toHaveBeenCalled()
- }
- })
+ expect(wrapper.find('thead').classes().length).toBe(0)
+ expect(wrapper.find('tfoot').classes().length).toBe(0)
- it('table_paginated shows empty message when no items', async () => {
- const {
- app: { $refs }
- } = window
- const vm = $refs.table_paginated
- const app = window.app
- const spy = jest.fn()
-
- expect(vm.showEmpty).toBe(true)
-
- const tbody = [...vm.$el.children].find(el => el && el.tagName === 'TBODY')
- expect(tbody).toBeDefined()
- if (tbody) {
- expect(app.items.length > 10).toBe(true)
- expect(app.items.length < 15).toBe(true)
-
- vm.$on('input', spy)
-
- // Set page size to show all items
- await setData(app, 'currentPage', 1)
- await setData(app, 'perPage', 15)
- await nextTick()
- expect(vm.value.length).toBe(app.items.length)
- expect(tbody.children.length).toBe(app.items.length)
-
- // Set items to empty list
- await setData(app, 'items', [])
- await nextTick()
- expect(app.items.length).toBe(0)
- expect(vm.value.length).toBe(0)
- expect(tbody.children.length).toBe(1)
- expect(tbody.textContent).toContain(vm.emptyText)
-
- expect(spy).toHaveBeenCalled()
- }
- })
+ wrapper.setProps({
+ headVariant: 'light'
+ })
- it('table_provider should emit a refreshed event for providerArray', async () => {
- const { app } = window
- const vm = app.$refs.table_provider
- const spy = jest.fn()
+ expect(wrapper.find('thead').classes()).toContain('thead-light')
+ expect(wrapper.find('tfoot').classes()).toContain('thead-light')
- await setData(app, 'providerType', 'array')
- await nextTick()
- await sleep(100)
+ wrapper.setProps({
+ footVariant: 'dark'
+ })
- vm.$on('refreshed', spy)
- vm.refresh()
- await nextTick()
- await sleep(100)
+ expect(wrapper.find('thead').classes()).toContain('thead-light')
+ expect(wrapper.find('tfoot').classes()).toContain('thead-dark')
- expect(spy).toHaveBeenCalled()
- // expect(vm.value.length).toBe(app.items.length)
- })
+ wrapper.setProps({
+ theadClass: 'foo',
+ tfootClass: 'bar'
+ })
- it('table_provider should emit a refreshed event for providerCallback', async () => {
- const { app } = window
- const vm = app.$refs.table_provider
- const spy = jest.fn()
+ expect(wrapper.find('thead').classes()).toContain('thead-light')
+ expect(wrapper.find('thead').classes()).toContain('foo')
+ expect(wrapper.find('tfoot').classes()).toContain('thead-dark')
+ expect(wrapper.find('tfoot').classes()).toContain('bar')
- await setData(app, 'providerType', 'callback')
- await nextTick()
- await sleep(100)
+ wrapper.setProps({
+ theadTrClass: 'willy',
+ tfootTrClass: 'wonka'
+ })
- vm.$on('refreshed', spy)
- vm.refresh()
- await nextTick()
- await sleep(100)
+ expect(wrapper.find('thead > tr').classes()).toContain('willy')
+ expect(wrapper.find('tfoot > tr').classes()).toContain('wonka')
- expect(spy).toHaveBeenCalled()
+ wrapper.destroy()
})
- it('table_provider should emit a refreshed event for providerPromise', async () => {
- const { app } = window
- const vm = app.$refs.table_provider
- const spy = jest.fn()
+ it('item field isRowHeader works', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ items: [{ a: 1, b: 2 }],
+ fields: [{ key: 'a', isRowHeader: true }, 'b']
+ }
+ })
- await setData(app, 'providerType', 'promise')
- await nextTick()
- await sleep(100)
+ expect(wrapper).toBeDefined()
+ expect(wrapper.findAll('tbody > tr').length).toBe(1)
+ expect(wrapper.findAll('tbody > tr > *').length).toBe(2)
+
+ expect(
+ wrapper
+ .findAll('tbody > tr > *')
+ .at(0)
+ .is('th')
+ ).toBe(true)
+ expect(
+ wrapper
+ .findAll('tbody > tr > *')
+ .at(0)
+ .attributes('role')
+ ).toBe('rowheader')
+ expect(
+ wrapper
+ .findAll('tbody > tr > *')
+ .at(0)
+ .attributes('scope')
+ ).toBe('row')
+
+ expect(
+ wrapper
+ .findAll('tbody > tr > *')
+ .at(1)
+ .is('td')
+ ).toBe(true)
+ expect(
+ wrapper
+ .findAll('tbody > tr > *')
+ .at(1)
+ .attributes('role')
+ ).toBe('cell')
+ expect(
+ wrapper
+ .findAll('tbody > tr > *')
+ .at(1)
+ .attributes('scope')
+ ).not.toBeDefined()
+
+ wrapper.destroy()
+ })
+
+ it('item field tdAttr and tdClass works', async () => {
+ const Parent = {
+ methods: {
+ parentTdAttrs(value, key, item) {
+ return { 'data-parent': 'parent' }
+ }
+ }
+ }
+ const wrapper = mount(Table, {
+ parentComponent: Parent,
+ propsData: {
+ items: [{ a: 1, b: 2, c: 3 }],
+ fields: [
+ { key: 'a', tdAttr: { 'data-foo': 'bar' } },
+ { key: 'b', tdClass: () => 'baz' },
+ { key: 'c', tdAttr: 'parentTdAttrs' }
+ ]
+ }
+ })
- vm.$on('refreshed', spy)
- vm.refresh()
- await nextTick()
- await sleep(100)
+ expect(wrapper).toBeDefined()
+ expect(wrapper.findAll('tbody > tr').length).toBe(1)
+ expect(wrapper.findAll('tbody > tr > td').length).toBe(3)
- expect(spy).toHaveBeenCalled()
- })
+ const $tds = wrapper.findAll('tbody > tr > td')
- it('should render stacked table', async () => {
- const { app } = window
- const vm = app.$refs.table_stacked
+ expect($tds.at(0).attributes('data-foo')).toBe('bar')
+ expect($tds.at(0).attributes('data-parent')).not.toBeDefined()
+ expect($tds.at(0).classes().length).toBe(0)
- expect(vm).toHaveAllClasses(['b-table-stacked'])
+ expect($tds.at(1).classes()).toContain('baz')
+ expect($tds.at(1).attributes('data-foo')).not.toBeDefined()
+ expect($tds.at(1).attributes('data-parent')).not.toBeDefined()
+
+ expect($tds.at(2).attributes('data-parent')).toBe('parent')
+ expect($tds.at(2).attributes('data-foo')).not.toBeDefined()
+ expect($tds.at(2).classes().length).toBe(0)
+
+ wrapper.destroy()
})
- it('all example tables should have custom formatted cells', async () => {
- const {
- app: { $refs }
- } = window
-
- const tables = ['table_basic', 'table_paginated', 'table_dark']
- await nextTick()
-
- tables.forEach((table, idx) => {
- const vm = $refs[table]
- const tbody = [...vm.$el.children].find(el => el && el.tagName === 'TBODY')
- expect(tbody).toBeDefined()
- if (tbody) {
- const tr = tbody.children[0]
- expect(tr).toBeDefined()
- expect(
- Boolean(tr.children[0]) &&
- Boolean(tr.children[0].classList) &&
- tr.children[0].classList.contains('bg-primary')
- ).toBe(true)
- expect(
- Boolean(tr.children[1]) &&
- Boolean(tr.children[1].classList) &&
- tr.children[1].classList.contains('bg-primary') &&
- tr.children[1].classList.contains('text-dark')
- ).toBe(true)
- expect(
- Boolean(tr.children[2]) &&
- Boolean(tr.children[2].classList) &&
- tr.children[2].classList.contains('bg-danger')
- ).toBe(true)
- expect(
- Boolean(tr.children[3]) &&
- Boolean(tr.children[3].classList) &&
- tr.children[3].classList.contains('bg-primary') &&
- tr.children[3].classList.contains('text-light')
- ).toBe(true)
- expect(
- Boolean(tr.children[0]) &&
- Boolean(tr.children[0].attributes) &&
- tr.children[0].getAttribute('title') === 'Person Full name'
- ).toBe(true)
- expect(
- Boolean(tr.children[2]) &&
- Boolean(tr.children[2].attributes) &&
- tr.children[2].getAttribute('title') === 'is Active'
- ).toBe(true)
- expect(
- Boolean(tr.children[3]) &&
- Boolean(tr.children[3].attributes) &&
- tr.children[3].getAttribute('title') === 'Actions'
- ).toBe(true)
+ it('item field formatter as function works', async () => {
+ const wrapper = mount(Table, {
+ propsData: {
+ items: [{ a: 1, b: 2 }],
+ fields: [
+ {
+ key: 'a',
+ formatter(value, key, item) {
+ return item.a + item.b
+ }
+ },
+ 'b'
+ ]
}
})
+
+ expect(wrapper).toBeDefined()
+ expect(wrapper.findAll('tbody > tr').length).toBe(1)
+ expect(wrapper.findAll('tbody > tr > td').length).toBe(2)
+ const $tds = wrapper.findAll('tbody > tr > td')
+ expect($tds.at(0).text()).toBe('3')
+ expect($tds.at(1).text()).toBe('2')
+
+ wrapper.destroy()
})
- it('should set row classes', async () => {
- // Classes that children rows must contain
- const classesTest = {
- 'tr-start-with-l': [1, 7],
- 'tr-last-name-macdonald': [0, 6]
- }
- const { app } = window
- const vm = app.$refs.table_style_row
- const tbody = [...vm.$el.children].find(el => el && el.tagName === 'TBODY')
- expect(tbody).toBeDefined()
- for (const className in classesTest) {
- const children = classesTest[className]
- for (let childIndex = 0, len = tbody.children.length - 1; childIndex < len; ++childIndex) {
- const hasClass = children.indexOf(childIndex) >= 0
- expect(
- Boolean(tbody.children[childIndex]) &&
- Boolean(tbody.children[childIndex].classList) &&
- tbody.children[childIndex].classList.contains(className)
- ).toBe(hasClass)
+ it('item field formatter as string works', async () => {
+ const Parent = {
+ methods: {
+ formatter(value, key, item) {
+ return item.a + item.b
+ }
}
}
+ const wrapper = mount(Table, {
+ parentComponent: Parent,
+ propsData: {
+ items: [{ a: 1, b: 2 }],
+ fields: [{ key: 'a', formatter: 'formatter' }, 'b']
+ }
+ })
+
+ expect(wrapper).toBeDefined()
+ expect(wrapper.findAll('tbody > tr').length).toBe(1)
+ expect(wrapper.findAll('tbody > tr > td').length).toBe(2)
+ const $tds = wrapper.findAll('tbody > tr > td')
+ expect($tds.at(0).text()).toBe('3')
+ expect($tds.at(1).text()).toBe('2')
+
+ wrapper.destroy()
})
})
diff --git a/src/components/tabs/README.md b/src/components/tabs/README.md
index faa1a6394eb..2cec7d003d6 100644
--- a/src/components/tabs/README.md
+++ b/src/components/tabs/README.md
@@ -57,17 +57,17 @@ When `` is in `card` mode, each `` sub-component will automatical
-
+
Picture 1 footer
-
+
Picture 2 footer
-
+
Picture 3 footer
@@ -235,14 +235,14 @@ Vue component, this possible by using `title` slot of ``.
- I'm Custom Title
+ I'm Custom Title
Tab Contents 1
- Tab 2
+ Tab 2
Tab Contents 2
@@ -270,7 +270,7 @@ need to accommodate your custom classes for this._
-
+
Tab Contents 1
Tab Contents 2
Tab Contents 3
@@ -349,7 +349,7 @@ order to use these methods.
-
+
I'm the first fading tab
I'm the second tab
@@ -393,7 +393,7 @@ order to use these methods.
-
+
Tab Contents {{ i }}
closeTab(i)">
Close tab
@@ -407,7 +407,7 @@ order to use these methods.
- There are no open tabs
+ There are no open tabs
Open a new tab using the + button above.
diff --git a/src/components/tabs/index.js b/src/components/tabs/index.js
index eba0c8f20f5..01423a0b7ab 100644
--- a/src/components/tabs/index.js
+++ b/src/components/tabs/index.js
@@ -1,6 +1,6 @@
import BTabs from './tabs'
import BTab from './tab'
-import { registerComponents } from '../../utils/plugins'
+import { installFactory } from '../../utils/plugins'
const components = {
BTabs,
@@ -8,7 +8,5 @@ const components = {
}
export default {
- install(Vue) {
- registerComponents(Vue, components)
- }
+ install: installFactory({ components })
}
diff --git a/src/components/tabs/tabs.js b/src/components/tabs/tabs.js
index 610148026d2..0cfca0c5075 100644
--- a/src/components/tabs/tabs.js
+++ b/src/components/tabs/tabs.js
@@ -135,6 +135,10 @@ export default {
bvTabs: this
}
},
+ model: {
+ prop: 'value',
+ event: 'input'
+ },
props: {
tag: {
type: String,
diff --git a/src/components/tooltip/README.md b/src/components/tooltip/README.md
index e16f2cbf6d1..d952d2c9098 100644
--- a/src/components/tooltip/README.md
+++ b/src/components/tooltip/README.md
@@ -116,26 +116,26 @@ then clicks the trigger element, they must click it again **and** move focus to
- Live chat
+ Live chat
- Html chat
+ Html chat
- Alternative chat
+ Alternative chat
-
+
-
+
Hello World!
-
+
@@ -153,7 +153,7 @@ then clicks the trigger element, they must click it again **and** move focus to
| `delay` | `0` | Delay showing and hiding of tooltip by specified number of milliseconds. Can also be specified as an object in the form of `{ show: 100, hide: 400 }` allowing different show and hide delays | `0` and up, integers only. |
| `offset` | `0` | Shift the center of the tooltip by specified number of pixels | Any negative or positive integer |
| `container` | `null` | Element string ID to append rendered tooltip into. If `null` or element not found, tooltip is appended to `` (default) | Any valid in-document unique element ID. |
-| `boundary` | `'scrollParent'` | The container that the tooltip will be constrained visually. The default should suffice in most cases, but you may need to chagne this if your target element is in a small container with overflow scroll | `'scrollParent'` (default), `'viewport'`, `'window'`, or a reference to an HTML element. |
+| `boundary` | `'scrollParent'` | The container that the tooltip will be constrained visually. The default should suffice in most cases, but you may need to change this if your target element is in a small container with overflow scroll | `'scrollParent'` (default), `'viewport'`, `'window'`, or a reference to an HTML element. |
| `boundaryPadding` | `5` | Amount of pixel used to define a minimum distance between the boundaries and the tooltip. This makes sure the tooltip always has a little padding between the edges of its container. | Any positive number |
### Programmatically show and hide tooltip
@@ -165,15 +165,14 @@ it to `true` will show the tooltip, while setting it to `false` will hide the to
- I have a tooltip
+ I have a tooltip
Toggle Tooltip
-
-
+
Hello World!
@@ -193,8 +192,8 @@ To make the tooltip shown on initial render, simply add the `show` prop on `
- Button
- I start open
+ Button
+ I start open
@@ -207,7 +206,7 @@ by reference.
- I have a tooltip
+ I have a tooltip
@@ -215,7 +214,7 @@ by reference.
Close
-
+
Hello World!
@@ -252,7 +251,7 @@ long as you have provided the `.sync` prop modifier.
- I have a tooltip
+ I have a tooltip
@@ -263,7 +262,7 @@ long as you have provided the `.sync` prop modifier.
{{ disabled ? 'Enable' : 'Disable' }} Tooltip by $ref event
-
+
Hello World!
@@ -406,10 +405,12 @@ These events work for both the component **and** directive versions of tooltip.
To listen to any tooltip opening, use:
```js
-mounted() {
- this.$root.$on('bv::tooltip::show', (bvEventObj) => {
- console.log('bvEventObj:', bvEventObj);
- })
+export default {
+ mounted() {
+ this.$root.$on('bv::tooltip::show', bvEvent => {
+ console.log('bvEvent:', bvEvent)
+ })
+ }
}
```
diff --git a/src/components/tooltip/index.js b/src/components/tooltip/index.js
index b0a0bfdc4a6..480079637b9 100644
--- a/src/components/tooltip/index.js
+++ b/src/components/tooltip/index.js
@@ -1,14 +1,15 @@
import BTooltip from './tooltip'
-import tooltipDirectivePlugin from '../../directives/tooltip'
-import { registerComponents } from '../../utils/plugins'
+import BTooltipDirectivePlugin from '../../directives/tooltip'
+import { installFactory } from '../../utils/plugins'
const components = {
BTooltip
}
+const plugins = {
+ BTooltipDirectivePlugin
+}
+
export default {
- install(Vue) {
- registerComponents(Vue, components)
- Vue.use(tooltipDirectivePlugin)
- }
+ install: installFactory({ components, plugins })
}
diff --git a/src/directives/modal/index.js b/src/directives/modal/index.js
index 6c51b236744..f3183280c5b 100644
--- a/src/directives/modal/index.js
+++ b/src/directives/modal/index.js
@@ -1,12 +1,10 @@
-import bModal from './modal'
-import { registerDirectives } from '../../utils/plugins'
+import BModalDirective from './modal'
+import { installFactory } from '../../utils/plugins'
const directives = {
- bModal
+ BModal: BModalDirective
}
export default {
- install(Vue) {
- registerDirectives(Vue, directives)
- }
+ install: installFactory({ directives })
}
diff --git a/src/directives/modal/modal.js b/src/directives/modal/modal.js
index 6b26c4e54c0..0b64b7828eb 100644
--- a/src/directives/modal/modal.js
+++ b/src/directives/modal/modal.js
@@ -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')
}
}
diff --git a/src/directives/modal/modal.spec.js b/src/directives/modal/modal.spec.js
new file mode 100644
index 00000000000..530cff5dc32
--- /dev/null
+++ b/src/directives/modal/modal.spec.js
@@ -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 '
+ })
+ 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: '
{{ text }} '
+ })
+ 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()
+ })
+})
diff --git a/src/directives/popover/README.md b/src/directives/popover/README.md
index 1287032be57..286d5e13a14 100644
--- a/src/directives/popover/README.md
+++ b/src/directives/popover/README.md
@@ -252,22 +252,26 @@ By default, popover will use the `title` attribute of the element as the popover
content is passed as a string to the `v-b-popover` directive. The title and content can also be
passed as an object to `v-b-popover` in the form of
+
+
```js
-{
- title: 'This is the title',
- content: 'This is the content'
+const options = {
+ title: 'This is the title',
+ content: 'This is the content'
}
```
If your content has basic HTML markup, then you will also need to set the `html` property to true,
or use the directive modifier `html`
+
+
```js
// Object format with HTML:
-{
- title: 'This is the
title',
- content: 'This is the content',
- html: true
+const options = {
+ title: 'This is the title',
+ content: 'This is the content',
+ html: true
}
```
diff --git a/src/directives/popover/index.js b/src/directives/popover/index.js
index 5cabeb2bd0a..518544d033a 100644
--- a/src/directives/popover/index.js
+++ b/src/directives/popover/index.js
@@ -1,12 +1,10 @@
-import bPopover from './popover'
-import { registerDirectives } from '../../utils/plugins'
+import BPopoverDirective from './popover'
+import { installFactory } from '../../utils/plugins'
const directives = {
- bPopover
+ BPopover: BPopoverDirective
}
export default {
- install(Vue) {
- registerDirectives(Vue, directives)
- }
+ install: installFactory({ directives })
}
diff --git a/src/directives/popover/popover.js b/src/directives/popover/popover.js
index 5991f7ddd77..36337ee8c62 100644
--- a/src/directives/popover/popover.js
+++ b/src/directives/popover/popover.js
@@ -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 = {
@@ -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 = {}
@@ -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}`
}
@@ -55,16 +55,16 @@ 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
@@ -72,10 +72,11 @@ function parseBindings(bindings) {
}
})
- // 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]) {
@@ -83,7 +84,7 @@ function parseBindings(bindings) {
}
})
- // Parse Modifiers for triggers
+ // Parse modifiers for triggers
keys(validTriggers).forEach(trigger => {
if (bindings.modifiers[trigger]) {
selectedTriggers[trigger] = true
@@ -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)
}
}
diff --git a/src/directives/popover/popover.spec.js b/src/directives/popover/popover.spec.js
new file mode 100644
index 00000000000..b301c23db2d
--- /dev/null
+++ b/src/directives/popover/popover.spec.js
@@ -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 `
+ })
+
+ 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()
+ })
+})
diff --git a/src/directives/scrollspy/README.md b/src/directives/scrollspy/README.md
index 6224c204d42..8b41c2bc869 100644
--- a/src/directives/scrollspy/README.md
+++ b/src/directives/scrollspy/README.md
@@ -34,7 +34,7 @@ as well.
one
two
-
+
three
@pi0
@@ -254,8 +254,10 @@ element.
### Config object properties
+
+
```js
-config = {
+const config = {
element: 'body',
offset: 10,
method: 'auto',
@@ -372,16 +374,18 @@ node reference
Whenever a target is activated, the event `bv:scrollspy::activate` is emitted on `$root` with the
targets HREF (ID) as the argument (i.e. `#bar`)
+
+
```js
-new Vue({
+const app = new Vue({
el: '#app',
+ created() {
+ this.$root.$on('bv::scrollspy::activate', this.onActivate)
+ },
methods: {
onActivate(target) {
console.log('Receved Event: scrollspy::activate for target ', target)
}
- },
- created() {
- this.$root.$on('bv::scrollspy::activate', this.onActivate)
}
})
```
diff --git a/src/directives/scrollspy/index.js b/src/directives/scrollspy/index.js
index 8d69983b436..731aebfb04c 100644
--- a/src/directives/scrollspy/index.js
+++ b/src/directives/scrollspy/index.js
@@ -1,12 +1,10 @@
-import bScrollspy from './scrollspy'
-import { registerDirectives } from '../../utils/plugins'
+import BScrollspyDirective from './scrollspy'
+import { installFactory } from '../../utils/plugins'
const directives = {
- bScrollspy
+ BScrollspy: BScrollspyDirective
}
export default {
- install(Vue) {
- registerDirectives(Vue, directives)
- }
+ install: installFactory({ directives })
}
diff --git a/src/directives/scrollspy/scrollspy.js b/src/directives/scrollspy/scrollspy.js
index 60e252c1e55..f3496e4f77e 100644
--- a/src/directives/scrollspy/scrollspy.js
+++ b/src/directives/scrollspy/scrollspy.js
@@ -1,28 +1,27 @@
-/*
- * ScrollSpy directive v-b-scrollspy
- */
-
import ScrollSpy from './scrollspy.class'
+import { inBrowser } from '../../utils/env'
import { keys } from '../../utils/object'
-import { isServer } from '../../utils/env'
-// Key we use to store our Instance
-const BVSS = '__BV_ScrollSpy__'
+// Key we use to store our instance
+const BV_SCROLLSPY = '__BV_ScrollSpy__'
-// Generate config from bindings
-function makeConfig(binding) /* istanbul ignore next: not easy to test */ {
+// Build a ScrollSpy config based on bindings (if any)
+// Arguments and modifiers take precedence over passed value config object
+/* istanbul ignore next: not easy to test */
+const parseBindings = bindings => /* istanbul ignore next: not easy to test */ {
const config = {}
- // If Argument, assume element ID
- if (binding.arg) {
- // Element ID specified as arg. We must pre-pend #
- config.element = '#' + binding.arg
+ // If argument, assume element ID
+ if (bindings.arg) {
+ // Element ID specified as arg
+ // We must prepend '#' to become a CSS selector
+ config.element = `#${bindings.arg}`
}
// Process modifiers
- keys(binding.modifiers).forEach(mod => {
+ keys(bindings.modifiers).forEach(mod => {
if (/^\d+$/.test(mod)) {
- // Offest value
+ // Offset value
config.offset = parseInt(mod, 10)
} else if (/^(auto|position|offset)$/.test(mod)) {
// Offset method
@@ -31,67 +30,70 @@ function makeConfig(binding) /* istanbul ignore next: not easy to test */ {
})
// Process value
- if (typeof binding.value === 'string') {
+ if (typeof bindings.value === 'string') {
// Value is a CSS ID or selector
- config.element = binding.value
- } else if (typeof binding.value === 'number') {
+ config.element = bindings.value
+ } else if (typeof bindings.value === 'number') {
// Value is offset
- config.offset = Math.round(binding.value)
- } else if (typeof binding.value === 'object') {
+ config.offset = Math.round(bindings.value)
+ } else if (typeof bindings.value === 'object') {
// Value is config object
// Filter the object based on our supported config options
- keys(binding.value)
+ keys(bindings.value)
.filter(k => Boolean(ScrollSpy.DefaultType[k]))
.forEach(k => {
- config[k] = binding.value[k]
+ config[k] = bindings.value[k]
})
}
return config
}
-function addBVSS(el, binding, vnode) /* istanbul ignore next: not easy to test */ {
- if (isServer) {
+// Add or update ScrollSpy on our element
+const applyScrollspy = (el, bindings, vnode) => /* istanbul ignore next: not easy to test */ {
+ if (!inBrowser) {
+ /* istanbul ignore next */
return
}
- const cfg = makeConfig(binding)
- if (!el[BVSS]) {
- el[BVSS] = new ScrollSpy(el, cfg, vnode.context.$root)
+ const config = parseBindings(bindings)
+ if (el[BV_SCROLLSPY]) {
+ el[BV_SCROLLSPY].updateConfig(config, vnode.context.$root)
} else {
- el[BVSS].updateConfig(cfg, vnode.context.$root)
+ el[BV_SCROLLSPY] = new ScrollSpy(el, config, vnode.context.$root)
}
- return el[BVSS]
}
-function removeBVSS(el) /* istanbul ignore next: not easy to test */ {
- if (el[BVSS]) {
- el[BVSS].dispose()
- el[BVSS] = null
+// Remove ScrollSpy on our element
+/* istanbul ignore next: not easy to test */
+const removeScrollspy = el => /* istanbul ignore next: not easy to test */ {
+ if (el[BV_SCROLLSPY]) {
+ el[BV_SCROLLSPY].dispose()
+ el[BV_SCROLLSPY] = null
+ delete el[BV_SCROLLSPY]
}
}
/*
* Export our directive
*/
-
export default {
- bind(el, binding, vnode) /* istanbul ignore next: not easy to test */ {
- addBVSS(el, binding, vnode)
+ bind(el, bindings, vnode) /* istanbul ignore next: not easy to test */ {
+ applyScrollspy(el, bindings, vnode)
},
- inserted(el, binding, vnode) /* istanbul ignore next: not easy to test */ {
- addBVSS(el, binding, vnode)
+ inserted(el, bindings, vnode) /* istanbul ignore next: not easy to test */ {
+ applyScrollspy(el, bindings, vnode)
},
- update(el, binding, vnode) /* istanbul ignore next: not easy to test */ {
- addBVSS(el, binding, vnode)
+ update(el, bindings, vnode) /* istanbul ignore next: not easy to test */ {
+ if (bindings.value !== bindings.oldValue) {
+ applyScrollspy(el, bindings, vnode)
+ }
},
- componentUpdated(el, binding, vnode) /* istanbul ignore next: not easy to test */ {
- addBVSS(el, binding, vnode)
+ componentUpdated(el, bindings, vnode) /* istanbul ignore next: not easy to test */ {
+ if (bindings.value !== bindings.oldValue) {
+ applyScrollspy(el, bindings, vnode)
+ }
},
unbind(el) /* istanbul ignore next: not easy to test */ {
- if (isServer) {
- return
- }
- // Remove scroll event listener on scrollElId
- removeBVSS(el)
+ removeScrollspy(el)
}
}
diff --git a/src/directives/toggle/index.js b/src/directives/toggle/index.js
index 94a08965006..792bace5268 100644
--- a/src/directives/toggle/index.js
+++ b/src/directives/toggle/index.js
@@ -1,12 +1,10 @@
-import bToggle from './toggle'
-import { registerDirectives } from '../../utils/plugins'
+import BToggleDirective from './toggle'
+import { installFactory } from '../../utils/plugins'
const directives = {
- bToggle
+ BToggle: BToggleDirective
}
export default {
- install(Vue) {
- registerDirectives(Vue, directives)
- }
+ install: installFactory({ directives })
}
diff --git a/src/directives/toggle/toggle.js b/src/directives/toggle/toggle.js
index 568ecd43b83..b8a0d066d72 100644
--- a/src/directives/toggle/toggle.js
+++ b/src/directives/toggle/toggle.js
@@ -1,24 +1,52 @@
-import target from '../../utils/target'
-import { setAttr, addClass, removeClass } from '../../utils/dom'
+import { setAttr, removeAttr, addClass, removeClass } from '../../utils/dom'
+import { inBrowser } from '../../utils/env'
+import { bindTargets, unbindTargets } from '../../utils/target'
-// Are we client side?
-const inBrowser = typeof window !== 'undefined'
-
-// target listen types
+// Target listen types
const listenTypes = { click: true }
// Property key for handler storage
-const BVT = '__BV_toggle__'
+const BV_TOGGLE = '__BV_toggle__'
+const BV_TOGGLE_STATE = '__BV_toggle_STATE__'
+const BV_TOGGLE_CONTROLS = '__BV_toggle_CONTROLS__'
-// Emitted Control Event for collapse (emitted to collapse)
+// Emitted control event for collapse (emitted to collapse)
const EVENT_TOGGLE = 'bv::toggle::collapse'
-// Listen to Event for toggle state update (Emited by collapse)
+// Listen to event for toggle state update (emitted by collapse)
const EVENT_STATE = 'bv::collapse::state'
+// Reset and remove a property from the provided element
+const resetProp = (el, prop) => {
+ el[prop] = null
+ delete el[prop]
+}
+
+// Handle directive updates
+/* istanbul ignore next: not easy to test */
+const handleUpdate = (el, binding, vnode) => {
+ if (!inBrowser) {
+ return
+ }
+ // Ensure the collapse class and aria-* attributes persist
+ // after element is updated (either by parent re-rendering
+ // or changes to this element or it's contents
+ if (el[BV_TOGGLE_STATE] === true) {
+ addClass(el, 'collapsed')
+ setAttr(el, 'aria-expanded', 'true')
+ } else if (el[BV_TOGGLE_STATE] === false) {
+ removeClass(el, 'collapsed')
+ setAttr(el, 'aria-expanded', 'false')
+ }
+ setAttr(el, 'aria-controls', el[BV_TOGGLE_CONTROLS])
+}
+
+/*
+ * Export our directive
+ */
export default {
bind(el, binding, vnode) {
- const targets = target(vnode, binding, listenTypes, ({ targets, vnode }) => {
+ const targets = bindTargets(vnode, binding, listenTypes, ({ targets, vnode }) => {
targets.forEach(target => {
vnode.context.$root.$emit(EVENT_TOGGLE, target)
})
@@ -26,19 +54,23 @@ export default {
if (inBrowser && vnode.context && targets.length > 0) {
// Add aria attributes to element
- setAttr(el, 'aria-controls', targets.join(' '))
+ el[BV_TOGGLE_CONTROLS] = targets.join(' ')
+ // State is initially collapsed until we receive a state event
+ el[BV_TOGGLE_STATE] = false
+ setAttr(el, 'aria-controls', el[BV_TOGGLE_CONTROLS])
setAttr(el, 'aria-expanded', 'false')
+ // 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
setAttr(el, 'role', 'button')
}
- // Toggle state hadnler, stored on element
- el[BVT] = function toggleDirectiveHandler(id, state) {
+ // Toggle state handler, stored on element
+ el[BV_TOGGLE] = function toggleDirectiveHandler(id, state) {
if (targets.indexOf(id) !== -1) {
// Set aria-expanded state
setAttr(el, 'aria-expanded', state ? 'true' : 'false')
// Set/Clear 'collapsed' class state
+ el[BV_TOGGLE_STATE] = state
if (state) {
removeClass(el, 'collapsed')
} else {
@@ -48,14 +80,25 @@ export default {
}
// Listen for toggle state changes
- vnode.context.$root.$on(EVENT_STATE, el[BVT])
+ vnode.context.$root.$on(EVENT_STATE, el[BV_TOGGLE])
}
},
- unbind(el, binding, vnode) {
- if (el[BVT]) {
- // Remove our $root listener
- vnode.context.$root.$off(EVENT_STATE, el[BVT])
- el[BVT] = null
+ componentUpdated: handleUpdate,
+ updated: handleUpdate,
+ unbind(el, binding, vnode) /* istanbul ignore next */ {
+ unbindTargets(vnode, binding, listenTypes)
+ // Remove our $root listener
+ if (el[BV_TOGGLE]) {
+ vnode.context.$root.$off(EVENT_STATE, el[BV_TOGGLE])
}
+ // Reset custom props
+ resetProp(el, BV_TOGGLE)
+ resetProp(el, BV_TOGGLE_STATE)
+ resetProp(el, BV_TOGGLE_CONTROLS)
+ // Reset classes/attrs
+ removeClass(el, 'collapsed')
+ removeAttr(el, 'aria-expanded')
+ removeAttr(el, 'aria-controls')
+ removeAttr(el, 'role')
}
}
diff --git a/src/directives/toggle/toggle.spec.js b/src/directives/toggle/toggle.spec.js
new file mode 100644
index 00000000000..7baaeac3a66
--- /dev/null
+++ b/src/directives/toggle/toggle.spec.js
@@ -0,0 +1,150 @@
+import toggleDirective from './toggle'
+import { mount, createLocalVue as CreateLocalVue } from '@vue/test-utils'
+
+// Emitted control event for collapse (emitted to collapse)
+const EVENT_TOGGLE = 'bv::toggle::collapse'
+
+// Listen to event for toggle state update (emitted by collapse)
+const EVENT_STATE = 'bv::collapse::state'
+
+describe('v-b-toggle directive', () => {
+ it('works on buttons', async () => {
+ const localVue = new CreateLocalVue()
+ const spy = jest.fn()
+
+ const App = localVue.extend({
+ directives: {
+ bToggle: toggleDirective
+ },
+ data() {
+ return {}
+ },
+ mounted() {
+ this.$root.$on(EVENT_TOGGLE, spy)
+ },
+ beforeDestroy() {
+ this.$root.$off(EVENT_TOGGLE, spy)
+ },
+ template: 'button '
+ })
+
+ const wrapper = mount(App, {
+ localVue: localVue
+ })
+
+ expect(wrapper.isVueInstance()).toBe(true)
+ expect(wrapper.is('button')).toBe(true)
+ expect(wrapper.find('button').attributes('aria-controls')).toBe('test')
+ expect(wrapper.find('button').attributes('aria-expanded')).toBe('false')
+ expect(wrapper.find('button').classes()).not.toContain('collapsed')
+ expect(spy).not.toHaveBeenCalled()
+
+ const $button = wrapper.find('button')
+ $button.trigger('click')
+ expect(spy).toHaveBeenCalledTimes(1)
+ expect(spy).toBeCalledWith('test')
+ expect(wrapper.find('button').attributes('aria-controls')).toBe('test')
+ expect(wrapper.find('button').attributes('aria-expanded')).toBe('false')
+ expect(wrapper.find('button').classes()).not.toContain('collapsed')
+
+ wrapper.destroy()
+ })
+
+ it('works on non-buttons', async () => {
+ const localVue = new CreateLocalVue()
+ const spy = jest.fn()
+
+ const App = localVue.extend({
+ directives: {
+ bToggle: toggleDirective
+ },
+ data() {
+ return {
+ text: 'span'
+ }
+ },
+ mounted() {
+ this.$root.$on(EVENT_TOGGLE, spy)
+ },
+ beforeDestroy() {
+ this.$root.$off(EVENT_TOGGLE, spy)
+ },
+ template: '{{ text }} '
+ })
+
+ 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').attributes('aria-controls')).toBe('test')
+ expect(wrapper.find('span').attributes('aria-expanded')).toBe('false')
+ expect(wrapper.find('span').classes()).not.toContain('collapsed')
+ expect(wrapper.find('span').text()).toBe('span')
+
+ const $span = wrapper.find('span')
+ $span.trigger('click')
+ expect(spy).toHaveBeenCalledTimes(1)
+ expect(spy).toBeCalledWith('test')
+ expect(wrapper.find('span').attributes('role')).toBe('button')
+ expect(wrapper.find('span').attributes('aria-controls')).toBe('test')
+ expect(wrapper.find('span').attributes('aria-expanded')).toBe('false')
+ expect(wrapper.find('span').classes()).not.toContain('collapsed')
+
+ // 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')
+ expect(wrapper.find('span').attributes('aria-controls')).toBe('test')
+ expect(wrapper.find('span').attributes('aria-expanded')).toBe('false')
+ expect(wrapper.find('span').classes()).not.toContain('collapsed')
+
+ wrapper.destroy()
+ })
+ it('responds to state update events', async () => {
+ const localVue = new CreateLocalVue()
+
+ const App = localVue.extend({
+ directives: {
+ bToggle: toggleDirective
+ },
+ data() {
+ return {}
+ },
+ template: 'button '
+ })
+
+ const wrapper = mount(App, {
+ localVue: localVue
+ })
+
+ expect(wrapper.isVueInstance()).toBe(true)
+ expect(wrapper.is('button')).toBe(true)
+ expect(wrapper.find('button').attributes('aria-controls')).toBe('test')
+ expect(wrapper.find('button').attributes('aria-expanded')).toBe('false')
+ expect(wrapper.find('button').classes()).not.toContain('collapsed')
+
+ const $root = wrapper.vm.$root
+
+ $root.$emit(EVENT_STATE, 'test', true)
+ await wrapper.vm.$nextTick()
+
+ expect(wrapper.find('button').attributes('aria-controls')).toBe('test')
+ expect(wrapper.find('button').attributes('aria-expanded')).toBe('true')
+ expect(wrapper.find('button').classes()).not.toContain('collapsed')
+
+ $root.$emit(EVENT_STATE, 'test', false)
+ await wrapper.vm.$nextTick()
+
+ expect(wrapper.find('button').attributes('aria-controls')).toBe('test')
+ expect(wrapper.find('button').attributes('aria-expanded')).toBe('false')
+ expect(wrapper.find('button').classes()).toContain('collapsed')
+
+ wrapper.destroy()
+ })
+})
diff --git a/src/directives/tooltip/README.md b/src/directives/tooltip/README.md
index f2d7515e356..19474bf69b3 100644
--- a/src/directives/tooltip/README.md
+++ b/src/directives/tooltip/README.md
@@ -158,20 +158,24 @@ There are several options for providing the title of a tooltip.
By default, tooltip will use the `title` attribute of the element as the tooltip content. The title
can also be passed as an object to `v-b-tooltip` in the form of
+
+
```js
-{
- title: 'This is the title',
+const options = {
+ title: 'This is the title'
}
```
If your title content has basic HTML markup, then you will also need to set the `html` property to
true, or use the directive modifier `html`
+
+
```js
-// Object format with HTML:
-{
- title: 'This is the title',
- html: true
+// Object format with HTML
+const options = {
+ title: 'This is the title',
+ html: true
}
```
diff --git a/src/directives/tooltip/index.js b/src/directives/tooltip/index.js
index 33002eb31b7..f3e8fbefa3b 100644
--- a/src/directives/tooltip/index.js
+++ b/src/directives/tooltip/index.js
@@ -1,12 +1,10 @@
-import bTooltip from './tooltip'
-import { registerDirectives } from '../../utils/plugins'
+import BTooltipDirective from './tooltip'
+import { installFactory } from '../../utils/plugins'
const directives = {
- bTooltip
+ BTooltip: BTooltipDirective
}
export default {
- install(Vue) {
- registerDirectives(Vue, directives)
- }
+ install: installFactory({ directives })
}
diff --git a/src/directives/tooltip/tooltip.js b/src/directives/tooltip/tooltip.js
index a16e447a79f..60b20cd53d8 100644
--- a/src/directives/tooltip/tooltip.js
+++ b/src/directives/tooltip/tooltip.js
@@ -1,12 +1,11 @@
import Popper from 'popper.js'
import ToolTip from '../../utils/tooltip.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 BVTT = '__BV_ToolTip__'
+const BV_TOOLTIP = '__BV_ToolTip__'
// Valid event triggers
const validTriggers = {
@@ -19,7 +18,7 @@ const validTriggers = {
// Build a ToolTip config based on bindings (if any)
// 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 = {}
@@ -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}`
}
@@ -47,24 +47,24 @@ function parseBindings(bindings) {
// Title allows HTML
config.html = true
} else if (/^nofade$/.test(mod)) {
- // no animation
+ // No animation
config.animation = false
} else if (
/^(auto|top(left|right)?|bottom(left|right)?|left(top|bottom)?|right(top|bottom)?)$/.test(mod)
) {
- // placement of tooltip
+ // Placement of tooltip
config.placement = mod
} else if (/^(window|viewport)$/.test(mod)) {
- // bounday of tooltip
+ // Boundary of tooltip
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
@@ -72,10 +72,11 @@ function parseBindings(bindings) {
}
})
- // 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]) {
@@ -83,7 +84,7 @@ function parseBindings(bindings) {
}
})
- // Parse Modifiers for triggers
+ // Parse modifiers for triggers
keys(validTriggers).forEach(trigger => {
if (bindings.modifiers[trigger]) {
selectedTriggers[trigger] = true
@@ -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 tooltip on our element
-//
-/* istanbul ignore next: not easy to test */
-function applyBVTT(el, bindings, vnode) {
+// Add or update ToolTip on our element
+const applyTooltip = (el, bindings, vnode) => {
if (!inBrowser) {
+ /* istanbul ignore next */
return
}
if (!Popper) {
- // Popper is required for tooltips to work
- warn('v-b-tooltip: Popper.js is required for tooltips to work')
+ // Popper is required for ToolTips to work
+ /* istanbul ignore next */
+ warn('v-b-tooltip: Popper.js is required for ToolTips to work')
+ /* istanbul ignore next */
return
}
- if (el[BVTT]) {
- el[BVTT].updateConfig(parseBindings(bindings))
+ const config = parseBindings(bindings)
+ if (el[BV_TOOLTIP]) {
+ el[BV_TOOLTIP].updateConfig(config)
} else {
- el[BVTT] = new ToolTip(el, parseBindings(bindings), vnode.context.$root)
+ el[BV_TOOLTIP] = new ToolTip(el, config, vnode.context.$root)
}
}
-//
-// Remove tooltip on our element
-//
-/* istanbul ignore next: not easy to test */
-function removeBVTT(el) {
- if (!inBrowser) {
- return
- }
- if (el[BVTT]) {
- el[BVTT].destroy()
- el[BVTT] = null
- delete el[BVTT]
+// Remove ToolTip on our element
+const removeTooltip = el => {
+ if (el[BV_TOOLTIP]) {
+ el[BV_TOOLTIP].destroy()
+ el[BV_TOOLTIP] = null
+ delete el[BV_TOOLTIP]
}
}
/*
* Export our directive
*/
-/* istanbul ignore next: not easy to test */
export default {
bind(el, bindings, vnode) {
- applyBVTT(el, bindings, vnode)
+ applyTooltip(el, bindings, vnode)
},
inserted(el, bindings, vnode) {
- applyBVTT(el, bindings, vnode)
+ applyTooltip(el, bindings, vnode)
},
- update(el, bindings, vnode) {
+ update(el, bindings, vnode) /* istanbul ignore next: not easy to test */ {
if (bindings.value !== bindings.oldValue) {
- applyBVTT(el, bindings, vnode)
+ applyTooltip(el, bindings, vnode)
}
},
- componentUpdated(el, bindings, vnode) {
+ componentUpdated(el, bindings, vnode) /* istanbul ignore next: not easy to test */ {
if (bindings.value !== bindings.oldValue) {
- applyBVTT(el, bindings, vnode)
+ applyTooltip(el, bindings, vnode)
}
},
unbind(el) {
- removeBVTT(el)
+ removeTooltip(el)
}
}
diff --git a/src/directives/tooltip/tooltip.spec.js b/src/directives/tooltip/tooltip.spec.js
new file mode 100644
index 00000000000..08eb172d9b1
--- /dev/null
+++ b/src/directives/tooltip/tooltip.spec.js
@@ -0,0 +1,37 @@
+import tooltipDirective from './tooltip'
+import ToolTip from '../../utils/tooltip.class'
+import { mount, createLocalVue as CreateLocalVue } from '@vue/test-utils'
+
+// Key which we use to store tooltip object on element
+const BV_TOOLTIP = '__BV_ToolTip__'
+
+describe('v-b-tooltip directive', () => {
+ it('should have ToolTip class instance', async () => {
+ const localVue = new CreateLocalVue()
+
+ const App = localVue.extend({
+ directives: {
+ bTooltip: tooltipDirective
+ },
+ data() {
+ return {}
+ },
+ template: '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_TOOLTIP]).toBeDefined()
+ expect($button.element[BV_TOOLTIP]).toBeInstanceOf(ToolTip)
+
+ wrapper.destroy()
+ })
+})
diff --git a/src/index.js b/src/index.js
index 6a0bbb61bd2..14a9d6c4ce1 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,21 +1,25 @@
import * as componentPlugins from './components'
import * as directivePlugins from './directives'
-import { vueUse } from './utils/plugins'
+import { registerPlugins, vueUse } from './utils/plugins'
+import { setConfig } from './utils/config'
+
+const BootstrapVue = {
+ install(Vue, config = {}) {
+ // Configure BootstrapVue
+ setConfig(config)
-const VuePlugin = {
- install: function(Vue) {
// Register component plugins
- for (let plugin in componentPlugins) {
- Vue.use(componentPlugins[plugin])
- }
+ registerPlugins(Vue, componentPlugins)
// Register directive plugins
- for (let plugin in directivePlugins) {
- Vue.use(directivePlugins[plugin])
- }
+ registerPlugins(Vue, directivePlugins)
+ },
+ setConfig(config = {}) /* istanbul ignore next */ {
+ setConfig(config)
}
}
-vueUse(VuePlugin)
+// Auto installation only occurs if window.Vue exists
+vueUse(BootstrapVue)
-export default VuePlugin
+export default BootstrapVue
diff --git a/src/mixins/form-radio-check.js b/src/mixins/form-radio-check.js
index 2ad961c9783..901696093e9 100644
--- a/src/mixins/form-radio-check.js
+++ b/src/mixins/form-radio-check.js
@@ -32,6 +32,11 @@ export default {
// Only applicable when rendered with button style
type: String,
default: null
+ },
+ ariaLabel: {
+ // Placed on the input if present.
+ type: String,
+ default: null
}
},
data() {
@@ -172,7 +177,9 @@ export default {
'form-check-input': this.is_Plain,
'custom-control-input': this.is_Custom,
'is-valid': this.get_State === true && !this.is_BtnMode,
- 'is-invalid': this.get_State === false && !this.is_BtnMode
+ 'is-invalid': this.get_State === false && !this.is_BtnMode,
+ // https://github.com/bootstrap-vue/bootstrap-vue/issues/2911
+ 'position-static': this.is_Plain && !defaultSlot
},
directives: [
{
@@ -190,7 +197,8 @@ export default {
disabled: this.is_Disabled,
required: this.is_Required,
autocomplete: 'off',
- 'aria-required': this.is_Required || null
+ 'aria-required': this.is_Required || null,
+ 'aria-label': this.ariaLabel || null
},
domProps: {
value: this.value,
@@ -209,17 +217,22 @@ export default {
return button
} else {
// Not button mode
- const label = h(
- 'label',
- {
- class: {
- 'form-check-label': this.is_Plain,
- 'custom-control-label': this.is_Custom
+ let label = h(false)
+ // If no label content in plain mode we dont render the label
+ // https://github.com/bootstrap-vue/bootstrap-vue/issues/2911
+ if (!(this.is_Plain && !defaultSlot)) {
+ label = h(
+ 'label',
+ {
+ class: {
+ 'form-check-label': this.is_Plain,
+ 'custom-control-label': this.is_Custom
+ },
+ attrs: { for: this.safeId() }
},
- attrs: { for: this.safeId() }
- },
- defaultSlot
- )
+ defaultSlot
+ )
+ }
// Wrap it in a div
return h(
'div',
diff --git a/src/mixins/pagination.js b/src/mixins/pagination.js
index 6137e4074d9..822664fb47c 100644
--- a/src/mixins/pagination.js
+++ b/src/mixins/pagination.js
@@ -145,6 +145,10 @@ const props = {
export default {
components: { BLink },
mixins: [normalizeSlotMixin],
+ model: {
+ prop: 'value',
+ event: 'input'
+ },
props,
data() {
const curr = parseInt(this.value, 10)
diff --git a/src/utils/clone-deep.js b/src/utils/clone-deep.js
new file mode 100644
index 00000000000..0249228cb3d
--- /dev/null
+++ b/src/utils/clone-deep.js
@@ -0,0 +1,17 @@
+import { isArray } from './array'
+import { isPlainObject, keys } from './object'
+
+export const cloneDeep = (obj, defaultValue = obj) => {
+ if (isArray(obj)) {
+ return obj.reduce((result, val) => [...result, cloneDeep(val, val)], [])
+ }
+ if (isPlainObject(obj)) {
+ return keys(obj).reduce(
+ (result, key) => ({ ...result, [key]: cloneDeep(obj[key], obj[key]) }),
+ {}
+ )
+ }
+ return defaultValue
+}
+
+export default cloneDeep
diff --git a/src/utils/clone-deep.spec.js b/src/utils/clone-deep.spec.js
new file mode 100644
index 00000000000..ce23d73c24d
--- /dev/null
+++ b/src/utils/clone-deep.spec.js
@@ -0,0 +1,70 @@
+import cloneDeep from './clone-deep'
+
+describe('cloneDeep()', () => {
+ it('should clone arrays', () => {
+ const a = [{ a: 0 }, { b: 1 }]
+ expect(cloneDeep(a)).toEqual(a)
+
+ const b = [1, 2, 3]
+ expect(b).toEqual(b)
+
+ const c = [{ a: 0 }, { b: 1 }]
+ const d = cloneDeep(c)
+ expect(d).toEqual(c)
+ expect(d[0]).toEqual(c[0])
+
+ const e = [0, 'a', {}, [{}], [() => {}], () => {}]
+ expect(cloneDeep(e)).toEqual(e)
+ })
+
+ it('should deeply clone an array', () => {
+ const a = [[{ a: 'b' }], [{ a: 'b' }]]
+ const b = cloneDeep(a)
+ expect(b).not.toBe(a)
+ expect(b[0]).not.toBe(a[0])
+ expect(b[1]).not.toBe(a[1])
+ expect(b).toEqual(a)
+ })
+
+ it('should deeply clone object', () => {
+ const a = { a: 'b' }
+ const b = cloneDeep(a)
+ b.c = 'd'
+ expect(b).not.toEqual(a)
+ })
+
+ it('should deeply clone arrays', () => {
+ const a = { a: 'b' }
+ const b = [a]
+ const c = cloneDeep(b)
+ a.c = 'd'
+ expect(c).not.toEqual(b)
+ })
+
+ it('should return primitives', () => {
+ expect(cloneDeep(0)).toEqual(0)
+ expect(cloneDeep('foo')).toEqual('foo')
+ })
+
+ it('should clone a regex', () => {
+ expect(cloneDeep(/foo/g)).toEqual(/foo/g)
+ })
+
+ it('should clone objects', () => {
+ const a = { a: 1, b: 2, c: 3 }
+ expect(cloneDeep(a)).toEqual(a)
+ expect(cloneDeep(a)).not.toBe(a)
+ })
+
+ it('should deeply clone objects', () => {
+ const a = { a: { a: 1, b: 2, c: 3 }, b: { a: 1, b: 2, c: 3 }, c: { a: 1, b: 2, c: 3 } }
+ expect(cloneDeep(a)).toEqual(a)
+ expect(cloneDeep(a)).not.toBe(a)
+ expect(cloneDeep(a).a).toEqual(a.a)
+ expect(cloneDeep(a).a).not.toBe(a.a)
+ expect(cloneDeep(a).b).toEqual(a.b)
+ expect(cloneDeep(a).b).not.toBe(a.b)
+ expect(cloneDeep(a).c).toEqual(a.c)
+ expect(cloneDeep(a).c).not.toBe(a.c)
+ })
+})
diff --git a/src/utils/config.js b/src/utils/config.js
new file mode 100644
index 00000000000..47493a509b5
--- /dev/null
+++ b/src/utils/config.js
@@ -0,0 +1,206 @@
+import cloneDeep from './clone-deep'
+import get from './get'
+import warn from './warn'
+import { isArray } from './array'
+import { keys, isObject } from './object'
+
+// General Bootstrap Vue configuration
+//
+// BREAKPOINT DEFINITIONS
+//
+// Some components (BCol and BFormGroup) generate props based on breakpoints, and this
+// occurs when the component is first loaded (evaluated), which may happen before the
+// config is created/modified
+//
+// To get around this we make these components async (lazy evaluation)
+// The component definition is only called/executed when the first access to the
+// component is used (and cached on subsequent uses)
+//
+// See: https://vuejs.org/v2/guide/components-dynamic-async.html#Async-Components
+//
+// PROP DEFAULTS
+//
+// For default values on props, we use the default value factory function approach so
+// so that the default values are pulled in at each component instantiation
+//
+// props: {
+// variant: {
+// type: String,
+// default: () => getConfigComponent('BAlert', 'variant')
+// }
+// }
+
+// prettier-ignore
+const DEFAULTS = {
+ // Breakpoints
+ breakpoints: ['xs', 'sm', 'md', 'lg', 'xl'],
+
+ // Component Specific defaults are keyed by the component
+ // name (PascalCase) and prop name (camelCase)
+ BAlert: {
+ dismissLabel: 'Close',
+ variant: 'info'
+ },
+ BBadge: {
+ variant: 'secondary'
+ },
+ BButton: {
+ variant: 'secondary'
+ },
+ BButtonClose: {
+ // `textVariant` is `null` to inherit the current text color
+ textVariant: null,
+ ariaLabel: 'Close'
+ },
+ BCardSubTitle: {
+ // BCard and BCardBody also inherit this prop
+ subTitleTextVariant: 'muted'
+ },
+ BDropdown: {
+ toggleText: 'Toggle Dropdown',
+ variant: 'secondary'
+ },
+ BFormFile: {
+ browseText: 'Browse',
+ // Chrome default file prompt
+ placeholder: 'No file chosen',
+ dropPlaceholder: 'Drop files here'
+ },
+ BFormText: {
+ textVariant: 'muted'
+ },
+ BImg: {
+ blankColor: 'transparent'
+ },
+ BImgLazy: {
+ blankColor: 'transparent'
+ },
+ BModal: {
+ cancelTitle: 'Cancel',
+ cancelVariant: 'secondary',
+ okTitle: 'OK',
+ okVariant: 'primary',
+ headerCloseLabel: 'Close'
+ }
+}
+
+// This contains user defined configuration
+let CONFIG = {}
+
+// Method to get a deep clone (immutable) copy of the defaults
+const getDefaults = () => cloneDeep(DEFAULTS)
+
+// Method to set the config
+// Merges in only known top-level and sub-level keys
+// Vue.use(BootstrapVue, config)
+// or
+// BootstrapVue.setConfig(config)
+// Vue.use(BootstrapVue)
+
+const setConfig = (config = {}) => {
+ if (!isObject(config)) {
+ /* istanbul ignore next */
+ return
+ }
+
+ keys(config)
+ .filter(cmpName => config.hasOwnProperty(cmpName))
+ .forEach(cmpName => {
+ if (!DEFAULTS.hasOwnProperty(cmpName)) {
+ /* istanbul ignore next */
+ warn(`config: unknown config property "${cmpName}"`)
+ /* istanbul ignore next */
+ return
+ }
+ const cmpConfig = config[cmpName]
+ if (cmpName === 'breakpoints') {
+ // Special case for breakpoints
+ const breakpoints = config.breakpoints
+ if (
+ !isArray(breakpoints) ||
+ breakpoints.length < 2 ||
+ breakpoints.some(b => typeof b !== 'string' || b.length === 0)
+ ) {
+ /* istanbul ignore next */
+ warn('config: "breakpoints" must be an array of at least 2 breakpoint names')
+ } else {
+ CONFIG.breakpoints = cloneDeep(breakpoints)
+ }
+ } else if (isObject(cmpConfig)) {
+ keys(cmpConfig)
+ .filter(key => cmpConfig.hasOwnProperty(key))
+ .forEach(key => {
+ if (!DEFAULTS[cmpName].hasOwnProperty(key)) {
+ /* istanbul ignore next */
+ warn(`config: unknown config property "${cmpName}.{$key}"`)
+ } else {
+ // If we pre-populate the config with defaults, we can skip this line
+ CONFIG[cmpName] = CONFIG[cmpName] || {}
+ if (cmpConfig[key] !== undefined) {
+ CONFIG[cmpName][key] = cloneDeep(cmpConfig[key])
+ }
+ }
+ })
+ }
+ })
+}
+
+// Reset the user config to default
+// For testing purposes only
+const resetConfig = () => {
+ CONFIG = {}
+}
+
+// Get the current user config
+// For testing purposes only
+const getConfig = () => cloneDeep(CONFIG)
+
+// Method to grab a config value based on a dotted/array notation key
+// Returns a deep clone (immutable) copy
+const getConfigValue = key => {
+ // First we try the user config, and if key not found we fall back to default value
+ // NOTE: If we deep clone DEFAULTS into config, then we can skip the fallback for get
+ return cloneDeep(get(CONFIG, key, get(getDefaults(), key)))
+}
+
+// Method to grab a config value for a particular component.
+// Returns a deep clone (immutable) copy
+const getComponentConfig = (cmpName, key = null) => {
+ // Return the particular config value for key for if specified,
+ // otherwise we return the full config
+ return key ? getConfigValue(`${cmpName}.${key}`) : getConfigValue(cmpName) || {}
+}
+
+// Convenience method for getting all breakpoint names
+const getBreakpoints = () => getConfigValue('breakpoints')
+
+// Convenience method for getting breakpoints with
+// the smallest breakpoint set as ''
+// Useful for components that create breakpoint specific props
+const getBreakpointsUp = () => {
+ const breakpoints = getBreakpoints()
+ breakpoints[0] = ''
+ return breakpoints
+}
+
+// Convenience method for getting breakpoints with
+// the largest breakpoint set as ''
+// Useful for components that create breakpoint specific props
+const getBreakpointsDown = () => {
+ const breakpoints = getBreakpoints()
+ breakpoints[breakpoints.length - 1] = ''
+ return breakpoints
+}
+
+// Named Exports
+export {
+ setConfig,
+ resetConfig,
+ getConfig,
+ getDefaults,
+ getConfigValue,
+ getComponentConfig,
+ getBreakpoints,
+ getBreakpointsUp,
+ getBreakpointsDown
+}
diff --git a/src/utils/config.spec.js b/src/utils/config.spec.js
new file mode 100644
index 00000000000..9d2a4ad9286
--- /dev/null
+++ b/src/utils/config.spec.js
@@ -0,0 +1,169 @@
+import {
+ setConfig,
+ resetConfig,
+ getConfig,
+ getDefaults,
+ getConfigValue,
+ getComponentConfig,
+ getBreakpoints,
+ getBreakpointsUp,
+ getBreakpointsDown
+} from './config'
+import looseEqual from './loose-equal'
+import { createLocalVue } from '@vue/test-utils'
+import BootstrapVue from '../../src'
+import AlertPlugin from '../../src/components/alert'
+import BVConfigPlugin from '../../src/bv-config'
+
+describe('utils/config', () => {
+ afterEach(() => {
+ resetConfig()
+ })
+
+ it('getConfigValue() works', async () => {
+ expect(getConfigValue('breakpoints')).toEqual(['xs', 'sm', 'md', 'lg', 'xl'])
+ // Should return a deep clone
+ expect(getConfigValue('breakpoints')).not.toBe(getConfigValue('breakpoints'))
+ // Should return null for not found
+ expect(getConfigValue('foo.bar[1].baz')).toBe(null)
+ })
+
+ it('getComponentConfig() works', async () => {
+ // Specific component config key
+ expect(getComponentConfig('BAlert', 'variant')).toEqual('info')
+ // Component's full config
+ expect(getComponentConfig('BAlert')).toEqual(getDefaults().BAlert)
+ // Should return a deep clone for full config
+ expect(getComponentConfig('BAlert')).not.toBe(getDefaults().BAlert)
+ // Should return empty object for not found component
+ expect(getComponentConfig('foobar')).toEqual({})
+ // Should return null for not found component key
+ expect(getComponentConfig('BAlert', 'foobar')).toBe(null)
+ })
+
+ it('getBreakpoints() works', async () => {
+ expect(getBreakpoints()).toEqual(['xs', 'sm', 'md', 'lg', 'xl'])
+ // Should return a deep clone
+ expect(getBreakpoints()).not.toBe(getBreakpoints())
+ })
+
+ it('getBreakpointsUp() works', async () => {
+ expect(getBreakpointsUp()).toEqual(['', 'sm', 'md', 'lg', 'xl'])
+ // Should return a deep clone
+ expect(getBreakpointsUp()).not.toBe(getBreakpointsUp())
+ })
+
+ it('getBreakpointsDown() works', async () => {
+ expect(getBreakpointsDown()).toEqual(['xs', 'sm', 'md', 'lg', ''])
+ // Should return a deep clone
+ expect(getBreakpointsDown()).not.toBe(getBreakpointsDown())
+ })
+
+ it('getDefaults() returns deep clone', async () => {
+ const defaults = getDefaults()
+
+ expect(Object.keys(defaults).length).toBeGreaterThan(1)
+ expect(getDefaults()).toEqual(defaults)
+ expect(getDefaults()).not.toBe(defaults)
+
+ // Each key should be a clone (top level key test)
+ expect(
+ Object.keys(defaults).every(key => {
+ // Should not point to the same reference
+ const param = getConfigValue(key)
+ return param !== defaults[key] && looseEqual(param, defaults[key])
+ })
+ ).toBe(true)
+
+ // TODO: Test each nested key (if Array or plain Object)
+ })
+
+ it('getConfig() return current empty user config', async () => {
+ expect(getConfig()).toEqual({})
+ })
+
+ it('setConfig() works', async () => {
+ const testConfig = {
+ BAlert: { variant: 'danger' }
+ }
+ const testBreakpoints = {
+ breakpoints: ['aa', 'bb', 'cc', 'dd', 'ee']
+ }
+
+ const defaults = getDefaults()
+
+ expect(getConfig()).toEqual({})
+
+ // Try a conponent config
+ setConfig(testConfig)
+ expect(getConfig()).toEqual(testConfig)
+ expect(getConfig()).not.toBe(testConfig)
+ expect(getComponentConfig('BAlert')).toEqual(testConfig.BAlert)
+ expect(getComponentConfig('BAlert', 'variant')).toEqual('danger')
+
+ // Try breakpoint config (should merge)
+ setConfig(testBreakpoints)
+ expect(getBreakpoints()).toEqual(testBreakpoints.breakpoints)
+ expect(getBreakpoints()).not.toBe(testBreakpoints.breakpoints)
+ expect(getConfigValue('breakpoints')).toEqual(testBreakpoints.breakpoints)
+ // should leave previous config
+ expect(getComponentConfig('BAlert', 'variant')).toEqual('danger')
+ // Should merge config
+ expect(getConfig()).toEqual({ ...testConfig, ...testBreakpoints })
+
+ // Reset the configuration
+ resetConfig()
+ expect(getConfig()).toEqual({})
+ expect(getComponentConfig('BAlert', 'variant')).toEqual('info')
+ expect(getComponentConfig('BAlert', 'variant')).toEqual(defaults.BAlert.variant)
+ expect(getBreakpoints()).toEqual(['xs', 'sm', 'md', 'lg', 'xl'])
+ })
+
+ it('config via Vue.use(BootstrapVue) works', async () => {
+ const testConfig = {
+ BAlert: { variant: 'foobar' }
+ }
+ const localVue = createLocalVue()
+
+ expect(getConfig()).toEqual({})
+
+ localVue.use(BootstrapVue, testConfig)
+ expect(getConfig()).toEqual(testConfig)
+
+ // Reset the configuration
+ resetConfig()
+ expect(getConfig()).toEqual({})
+ })
+
+ it('config via Vue.use(ComponentPlugin) works', async () => {
+ const testConfig = {
+ BAlert: { variant: 'foobar' }
+ }
+ const localVue = createLocalVue()
+
+ expect(getConfig()).toEqual({})
+
+ localVue.use(AlertPlugin, testConfig)
+ expect(getConfig()).toEqual(testConfig)
+
+ // Reset the configuration
+ resetConfig()
+ expect(getConfig()).toEqual({})
+ })
+
+ it('config via Vue.use(BVConfig) works', async () => {
+ const testConfig = {
+ BAlert: { variant: 'foobar' }
+ }
+ const localVue = createLocalVue()
+
+ expect(getConfig()).toEqual({})
+
+ localVue.use(BVConfigPlugin, testConfig)
+ expect(getConfig()).toEqual(testConfig)
+
+ // Reset the configuration
+ resetConfig()
+ expect(getConfig()).toEqual({})
+ })
+})
diff --git a/src/utils/dom.js b/src/utils/dom.js
index 7a0acbec3ac..500b1160844 100644
--- a/src/utils/dom.js
+++ b/src/utils/dom.js
@@ -146,8 +146,8 @@ export const closest = (selector, root) => {
if (matches(element, sel)) {
return element
}
- element = element.parentElement
- } while (element !== null)
+ element = element.parentElement || element.parentNode
+ } while (element !== null && element.nodeType === Node.ELEMENT_NODE)
return null
}
diff --git a/src/utils/env.js b/src/utils/env.js
index 5395044e97e..800aebba97a 100644
--- a/src/utils/env.js
+++ b/src/utils/env.js
@@ -1,5 +1,7 @@
// Info about the current environment
+// Constants
+
export const inBrowser = typeof document !== 'undefined' && typeof window !== 'undefined'
export const isServer = !inBrowser
@@ -8,3 +10,7 @@ export const hasTouchSupport =
inBrowser && ('ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0)
export const hasPointerEvent = inBrowser && Boolean(window.PointerEvent || window.MSPointerEvent)
+
+// Getters
+
+export const getNoWarn = () => process && process.env && process.env.BOOTSTRAP_VUE_NO_WARN
diff --git a/src/utils/noop.js b/src/utils/noop.js
new file mode 100644
index 00000000000..9b98d9cfac1
--- /dev/null
+++ b/src/utils/noop.js
@@ -0,0 +1,3 @@
+const noop = () => {}
+
+export default noop
diff --git a/src/utils/plugins.js b/src/utils/plugins.js
index dc36aab1103..f320ffcec7c 100644
--- a/src/utils/plugins.js
+++ b/src/utils/plugins.js
@@ -1,3 +1,32 @@
+import { setConfig } from './config'
+
+/**
+ * Plugin install factory function.
+ * @param {object} { components, directives }
+ * @returns {function} plugin install function
+ */
+export const installFactory = ({ components, directives, plugins }) => {
+ return (Vue, config = {}) => {
+ setConfig(config)
+ registerComponents(Vue, components)
+ registerDirectives(Vue, directives)
+ registerPlugins(Vue, plugins)
+ }
+}
+
+/**
+ * Load a group of plugins.
+ * @param {object} Vue
+ * @param {object} Plugin definitions
+ */
+export const registerPlugins = (Vue, plugins = {}) => {
+ for (let plugin in plugins) {
+ if (plugin && plugins[plugin]) {
+ Vue.use(plugins[plugin])
+ }
+ }
+}
+
/**
* Load a component.
* @param {object} Vue
@@ -5,7 +34,9 @@
* @param {object} Component definition
*/
export const registerComponent = (Vue, name, def) => {
- Vue.component(name, def)
+ if (Vue && name && def) {
+ Vue.component(name, def)
+ }
}
/**
@@ -13,7 +44,7 @@ export const registerComponent = (Vue, name, def) => {
* @param {object} Vue
* @param {object} Object of component definitions
*/
-export const registerComponents = (Vue, components) => {
+export const registerComponents = (Vue, components = {}) => {
for (let component in components) {
registerComponent(Vue, component, components[component])
}
@@ -26,7 +57,9 @@ export const registerComponents = (Vue, components) => {
* @param {object} Directive definition
*/
export const registerDirective = (Vue, name, def) => {
- Vue.directive(name, def)
+ if (Vue && name && def) {
+ Vue.directive(name, def)
+ }
}
/**
@@ -34,7 +67,7 @@ export const registerDirective = (Vue, name, def) => {
* @param {object} Vue
* @param {object} Object of directive definitions
*/
-export const registerDirectives = (Vue, directives) => {
+export const registerDirectives = (Vue, directives = {}) => {
for (let directive in directives) {
registerDirective(Vue, directive, directives[directive])
}
diff --git a/src/utils/startcase.spec.js b/src/utils/startcase.spec.js
new file mode 100644
index 00000000000..0c17bdd78c9
--- /dev/null
+++ b/src/utils/startcase.spec.js
@@ -0,0 +1,11 @@
+import startCase from './startcase'
+
+describe('utils/startcase', () => {
+ it('works', async () => {
+ expect(startCase('foobar')).toBe('Foobar')
+ expect(startCase('Foobar')).toBe('Foobar')
+ expect(startCase('foo_bar')).toBe('Foo Bar')
+ expect(startCase('foo bar')).toBe('Foo Bar')
+ expect(startCase('fooBar')).toBe('Foo Bar')
+ })
+})
diff --git a/src/utils/tooltip.class.js b/src/utils/tooltip.class.js
index 580e921638a..36358963689 100644
--- a/src/utils/tooltip.class.js
+++ b/src/utils/tooltip.class.js
@@ -1,5 +1,6 @@
import Popper from 'popper.js'
import BvEvent from './bv-event.class'
+import noop from './noop'
import { from as arrayFrom } from './array'
import {
closest,
@@ -25,7 +26,7 @@ const TRANSITION_DURATION = 150
// Modal $root hidden event
const MODAL_CLOSE_EVENT = 'bv::modal::hidden'
-// Modal container for appending tip/popover
+// Modal container for appending tooltip/popover
const MODAL_CLASS = '.modal-content'
const AttachmentMap = {
@@ -76,15 +77,6 @@ const Selector = {
ARROW: '.arrow'
}
-// ESLINT: Not used
-// const Trigger = {
-// HOVER: 'hover',
-// FOCUS: 'focus',
-// CLICK: 'click',
-// BLUR: 'blur',
-// MANUAL: 'manual'
-// }
-
const Defaults = {
animation: true,
template:
@@ -105,7 +97,7 @@ const Defaults = {
boundary: 'scrollParent'
}
-// Transition Event names
+// Transition event names
const TransitionEndEvents = {
WebkitTransition: ['webkitTransitionEnd'],
MozTransition: ['transitionend'],
@@ -113,15 +105,14 @@ const TransitionEndEvents = {
transition: ['transitionend']
}
-// Client Side Tip ID counter for aria-describedby attribute
-// Could use Alex's uid generator util
+// Client-side tip ID counter for aria-describedby attribute
// Each tooltip requires a unique client side ID
let NEXTID = 1
/* istanbul ignore next */
const generateId = name => `__BV_${name}_${NEXTID++}__`
/*
- * ToolTip Class definition
+ * ToolTip class definition
*/
/* istanbul ignore next: difficult to test in Jest/JSDOM environment */
class ToolTip {
@@ -147,6 +138,7 @@ class ToolTip {
this.$doShow = this.doShow.bind(this)
this.$doDisable = this.doDisable.bind(this)
this.$doEnable = this.doEnable.bind(this)
+ this._noop = noop.bind(this)
// Set the configuration
this.updateConfig(config)
}
@@ -284,7 +276,7 @@ class ToolTip {
this.fixTitle()
this.setContent(tip)
if (!this.isWithContent(tip)) {
- // if No content, don't bother showing
+ // If no content, don't bother showing
this.$tip = null
return
}
@@ -327,7 +319,7 @@ class ToolTip {
this.removePopper()
this.$popper = new Popper(this.$element, tip, this.getPopperConfig(placement, tip))
- // Transitionend Callback
+ // Transitionend callback
const complete = () => {
if (this.$config.animation) {
this.fixTransition(tip)
@@ -356,7 +348,7 @@ class ToolTip {
this.transitionOnce(tip, complete)
}
- // handler for periodic visibility check
+ // Handler for periodic visibility check
visibleCheck(on) {
clearInterval(this.$visibleInterval)
this.$visibleInterval = null
@@ -382,14 +374,14 @@ class ToolTip {
// On-touch start listeners
this.setOnTouchStartListener(on)
if (on && /(focus|blur)/.test(this.$config.trigger)) {
- // If focus moves between trigger element and tip container, dont close
+ // If focus moves between trigger element and tip container, don't close
eventOn(this.$tip, 'focusout', this)
} else {
eventOff(this.$tip, 'focusout', this)
}
}
- // force hide of tip (internal method)
+ // Force hide of tip (internal method)
forceHide() {
if (!this.$tip || !hasClass(this.$tip, ClassName.SHOW)) {
return
@@ -424,11 +416,11 @@ class ToolTip {
return
}
- // Transitionend Callback
+ // Transitionend callback
/* istanbul ignore next */
const complete = () => {
if (this.$hoverState !== HoverState.SHOW && tip.parentNode) {
- // Remove tip from dom, and force recompile on next show
+ // Remove tip from DOM, and force recompile on next show
tip.parentNode.removeChild(tip)
this.removeAriaDescribedby()
this.removePopper()
@@ -481,13 +473,14 @@ class ToolTip {
getContainer() {
const container = this.$config.container
const body = document.body
- // If we are in a modal, we append to the modal instead of body, unless a container is specified
+ // If we are in a modal, we append to the modal instead of body,
+ // unless a container is specified
return container === false
? closest(MODAL_CLASS, this.$element) || body
: select(container, body) || body
}
- // Will be overridden by popover if needed
+ // Will be overridden by PopOver if needed
addAriaDescribedby() {
// Add aria-describedby on trigger element, without removing any other IDs
let desc = getAttr(this.$element, 'aria-describedby') || ''
@@ -499,7 +492,7 @@ class ToolTip {
setAttr(this.$element, 'aria-describedby', desc)
}
- // Will be overridden by popover if needed
+ // Will be overridden by PopOver if needed
removeAriaDescribedby() {
let desc = getAttr(this.$element, 'aria-describedby') || ''
desc = desc
@@ -544,7 +537,7 @@ class ToolTip {
transEvents.forEach(evtName => {
eventOn(tip, evtName, fnOnce)
})
- // Fallback to setTimeout
+ // Fallback to setTimeout()
this.$fadeTimeout = setTimeout(fnOnce, TRANSITION_DURATION)
} else {
fnOnce()
@@ -620,7 +613,7 @@ class ToolTip {
}
const allowHtml = this.$config.html
if (typeof content === 'object' && content.nodeType) {
- // content is a DOM node
+ // Content is a DOM node
if (allowHtml) {
if (content.parentElement !== container) {
container.innerHTML = ''
@@ -643,7 +636,8 @@ class ToolTip {
title = title(this.$element)
}
if (typeof title === 'object' && title.nodeType && !title.innerHTML.trim()) {
- // We have a DOM node, but without inner content, so just return empty string
+ // We have a DOM node, but without inner content,
+ // so just return empty string
title = ''
}
if (typeof title === 'string') {
@@ -669,8 +663,8 @@ class ToolTip {
// Listen for global show/hide events
this.setRootListener(true)
- // Using 'this' as the handler will get automatically directed to this.handleEvent
- // And maintain our binding to 'this'
+ // Using 'this' as the handler will get automatically directed to
+ // this.handleEvent and maintain our binding to 'this'
triggers.forEach(trigger => {
if (trigger === 'click') {
eventOn(el, 'click', this)
@@ -701,7 +695,7 @@ class ToolTip {
handleEvent(e) {
// This special method allows us to use "this" as the event handlers
if (isDisabled(this.$element)) {
- // If disabled, don't do anything. Note: if tip is shown before element gets
+ // If disabled, don't do anything. Note: If tip is shown before element gets
// disabled, then tip not close until no longer disabled or forcefully closed.
return
}
@@ -720,7 +714,7 @@ class ToolTip {
this.enter(e)
} else if (type === 'focusout') {
// target is the element which is loosing focus
- // And relatedTarget is the element gaining focus
+ // and relatedTarget is the element gaining focus
if ($tip && $element && $element.contains(target) && $tip.contains(relatedTarget)) {
// If focus moves from $element to $tip, don't trigger a leave
return
@@ -759,7 +753,7 @@ class ToolTip {
}
} else {
if (this.$routeWatcher) {
- // cancel the route watcher by calling the stored reference
+ // Cancel the route watcher by calling the stored reference
this.$routeWatcher()
this.$routeWatcher = null
}
@@ -836,9 +830,9 @@ class ToolTip {
/* istanbul ignore next */
setOnTouchStartListener(on) {
- // if this is a touch-enabled device we add extra
- // empty mouseover listeners to the body's immediate children;
- // only needed because of broken event delegation on iOS
+ // If this is a touch-enabled device we add extra
+ // empty mouseover listeners to the body's immediate children
+ // Only needed because of broken event delegation on iOS
// https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
if ('ontouchstart' in document.documentElement) {
arrayFrom(document.body.children).forEach(el => {
@@ -851,11 +845,6 @@ class ToolTip {
}
}
- /* istanbul ignore next */
- _noop() {
- // Empty noop handler for ontouchstart devices
- }
-
fixTitle() {
const el = this.$element
const titleType = typeof getAttr(el, 'data-original-title')
diff --git a/src/utils/warn.js b/src/utils/warn.js
index b1258e970d3..53f7f6ddeb9 100644
--- a/src/utils/warn.js
+++ b/src/utils/warn.js
@@ -1,10 +1,14 @@
+import { getNoWarn } from './env'
+
/**
* Log a warning message to the console with bootstrap-vue formatting sugar.
* @param {string} message
*/
/* istanbul ignore next */
const warn = message => {
- console.warn(`[BootstrapVue warn]: ${message}`)
+ if (!getNoWarn()) {
+ console.warn(`[BootstrapVue warn]: ${message}`)
+ }
}
export default warn
diff --git a/src/utils/warn.spec.js b/src/utils/warn.spec.js
new file mode 100644
index 00000000000..993f5a11c70
--- /dev/null
+++ b/src/utils/warn.spec.js
@@ -0,0 +1,45 @@
+import warn from './warn'
+
+describe('utils/warn', () => {
+ const dummyWarning = 'A Rush Of Blood To The Head'
+
+ let originalProcess
+
+ beforeAll(() => {
+ jest.spyOn(console, 'warn').mockImplementation(() => {})
+ originalProcess = global.process
+ })
+
+ afterEach(() => {
+ console.warn.mockClear()
+ global.process = originalProcess
+ })
+
+ describe('with BOOTSTRAP_VUE_NO_WARN environment variable set', () => {
+ beforeEach(() => {
+ global.process = {
+ env: {
+ BOOTSTRAP_VUE_NO_WARN: true
+ }
+ }
+ })
+
+ it('does not call console.warn()', () => {
+ warn(dummyWarning)
+
+ expect(console.warn).not.toHaveBeenCalled()
+ })
+ })
+
+ describe('without process object', () => {
+ beforeEach(() => {
+ global.process = null
+ })
+
+ it('calls console.warn()', () => {
+ warn(dummyWarning)
+
+ expect(console.warn).toHaveBeenCalledWith(`[BootstrapVue warn]: ${dummyWarning}`)
+ })
+ })
+})
diff --git a/tests/utils.js b/tests/utils.js
index bd6a436d641..97114b32258 100644
--- a/tests/utils.js
+++ b/tests/utils.js
@@ -1,3 +1,4 @@
+/* istanbul ignore file */
import { readFileSync } from 'fs'
import { resolve } from 'path'
import BootstrapVue from '../src'
@@ -14,7 +15,7 @@ Vue.config.devtools = false
window.Vue = Vue
Vue.use(BootstrapVue)
-export function loadFixture(dirName, name) {
+export function loadFixture(dirName, name) /* istanbul ignore next */ {
const fixtureBase = resolve(dirName, 'fixtures')
const template = readFileSync(resolve(fixtureBase, name + '.html'), 'UTF-8')
const js = readFileSync(resolve(fixtureBase, name + '.js'), 'UTF-8')
@@ -33,25 +34,31 @@ export function loadFixture(dirName, name) {
}
export async function testVM() {
+ /* istanbul ignore next */
it(`vm mounts`, async () => {
return expect(window.app.$el).toBeDefined()
})
}
export function nextTick() {
+ /* istanbul ignore next */
return new Promise((resolve, reject) => {
Vue.nextTick(resolve)
})
}
export async function setData(app, key, value) {
+ /* istanbul ignore next */
app[key] = value
+ /* istanbul ignore next */
await nextTick()
}
// Usage: await sleep(1000);
export function sleep(ms) {
+ /* istanbul ignore next */
ms = ms || 0
+ /* istanbul ignore next */
return new Promise((resolve, reject) => setTimeout(resolve, ms))
}
@@ -76,13 +83,14 @@ const throwIfNotHTMLElement = el => {
}
}
+/* istanbul ignore next */
const throwIfNotArray = array => {
- /* istanbul ignore next */
if (!Array.isArray(array)) {
throw new TypeError(`The matcher requires an array. Given ${typeof array}`)
}
}
+/* istanbul ignore next */
const vmHasClass = (vm, className) => {
throwIfNotVueInstance(vm)
return vm.$el._prevClass.indexOf(className) !== -1
@@ -93,6 +101,7 @@ const vmHasClass = (vm, className) => {
* @param {string} className
* @return {boolean}
*/
+/* istanbul ignore next */
const elHasClass = (el, className) => {
throwIfNotHTMLElement(el)
return el.classList.contains(className)
@@ -103,14 +112,19 @@ const elHasClass = (el, className) => {
* @param {string} className
* @return {boolean}
*/
+/* istanbul ignore next */
const hasClass = (node, className) =>
isVueInstance(node) ? vmHasClass(node, className) : elHasClass(node, className)
+/* istanbul ignore next */
const getVmTag = vm => vm.$options._componentTag
+/* istanbul ignore next */
const getHTMLTag = el => String(el.tagName).toLowerCase()
+/* istanbul ignore next */
const getTagName = node => (isVueInstance(node) ? getVmTag(node) : getHTMLTag(node))
// Extend Jest marchers
+/* istanbul ignore next */
expect.extend({
toHaveClass(node, className) {
/* istanbul ignore next */
diff --git a/yarn.lock b/yarn.lock
index fa4150d4f61..fe59cc10e1b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -26,18 +26,18 @@
dependencies:
"@babel/highlight" "^7.0.0"
-"@babel/core@^7.1.0", "@babel/core@^7.2.2", "@babel/core@^7.3.4":
- version "7.3.4"
- resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.3.4.tgz#921a5a13746c21e32445bf0798680e9d11a6530b"
- integrity sha512-jRsuseXBo9pN197KnDwhhaaBzyZr2oIcLHHTt2oDdQrej5Qp57dCCJafWx5ivU8/alEYDpssYqv1MUqcxwQlrA==
+"@babel/core@^7.1.0", "@babel/core@^7.4.0":
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.4.0.tgz#248fd6874b7d755010bfe61f557461d4f446d9e9"
+ integrity sha512-Dzl7U0/T69DFOTwqz/FJdnOSWS57NpjNfCwMKHABr589Lg8uX1RrlBIJ7L5Dubt/xkLsx0xH5EBFzlBVes1ayA==
dependencies:
"@babel/code-frame" "^7.0.0"
- "@babel/generator" "^7.3.4"
- "@babel/helpers" "^7.2.0"
- "@babel/parser" "^7.3.4"
- "@babel/template" "^7.2.2"
- "@babel/traverse" "^7.3.4"
- "@babel/types" "^7.3.4"
+ "@babel/generator" "^7.4.0"
+ "@babel/helpers" "^7.4.0"
+ "@babel/parser" "^7.4.0"
+ "@babel/template" "^7.4.0"
+ "@babel/traverse" "^7.4.0"
+ "@babel/types" "^7.4.0"
convert-source-map "^1.1.0"
debug "^4.1.0"
json5 "^2.1.0"
@@ -46,12 +46,12 @@
semver "^5.4.1"
source-map "^0.5.0"
-"@babel/generator@^7.0.0", "@babel/generator@^7.3.4":
- version "7.3.4"
- resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.3.4.tgz#9aa48c1989257877a9d971296e5b73bfe72e446e"
- integrity sha512-8EXhHRFqlVVWXPezBW5keTiQi/rJMQTg/Y9uVCEZ0CAF3PKtCCaVRnp64Ii1ujhkoDhhF1fVsImoN4yJ2uz4Wg==
+"@babel/generator@^7.0.0", "@babel/generator@^7.4.0":
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.4.0.tgz#c230e79589ae7a729fd4631b9ded4dc220418196"
+ integrity sha512-/v5I+a1jhGSKLgZDcmAUZ4K/VePi43eRkUs3yePW1HB1iANOD5tqJXwGSG4BZhSksP8J9ejSlwGeTiiOFZOrXQ==
dependencies:
- "@babel/types" "^7.3.4"
+ "@babel/types" "^7.4.0"
jsesc "^2.5.1"
lodash "^4.17.11"
source-map "^0.5.0"
@@ -72,35 +72,35 @@
"@babel/helper-explode-assignable-expression" "^7.1.0"
"@babel/types" "^7.0.0"
-"@babel/helper-call-delegate@^7.1.0":
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.1.0.tgz#6a957f105f37755e8645343d3038a22e1449cc4a"
- integrity sha512-YEtYZrw3GUK6emQHKthltKNZwszBcHK58Ygcis+gVUrF4/FmTVr5CCqQNSfmvg2y+YDEANyYoaLz/SHsnusCwQ==
+"@babel/helper-call-delegate@^7.4.0":
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.4.0.tgz#f308eabe0d44f451217853aedf4dea5f6fe3294f"
+ integrity sha512-SdqDfbVdNQCBp3WhK2mNdDvHd3BD6qbmIc43CAyjnsfCmgHMeqgDcM3BzY2lchi7HBJGJ2CVdynLWbezaE4mmQ==
dependencies:
- "@babel/helper-hoist-variables" "^7.0.0"
- "@babel/traverse" "^7.1.0"
- "@babel/types" "^7.0.0"
+ "@babel/helper-hoist-variables" "^7.4.0"
+ "@babel/traverse" "^7.4.0"
+ "@babel/types" "^7.4.0"
-"@babel/helper-create-class-features-plugin@^7.3.0", "@babel/helper-create-class-features-plugin@^7.3.4":
- version "7.3.4"
- resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.3.4.tgz#092711a7a3ad8ea34de3e541644c2ce6af1f6f0c"
- integrity sha512-uFpzw6L2omjibjxa8VGZsJUPL5wJH0zzGKpoz0ccBkzIa6C8kWNUbiBmQ0rgOKWlHJ6qzmfa6lTiGchiV8SC+g==
+"@babel/helper-create-class-features-plugin@^7.4.0":
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.4.0.tgz#30fd090e059d021995c1762a5b76798fa0b51d82"
+ integrity sha512-2K8NohdOT7P6Vyp23QH4w2IleP8yG3UJsbRKwA4YP6H8fErcLkFuuEEqbF2/BYBKSNci/FWJiqm6R3VhM/QHgw==
dependencies:
"@babel/helper-function-name" "^7.1.0"
"@babel/helper-member-expression-to-functions" "^7.0.0"
"@babel/helper-optimise-call-expression" "^7.0.0"
"@babel/helper-plugin-utils" "^7.0.0"
- "@babel/helper-replace-supers" "^7.3.4"
- "@babel/helper-split-export-declaration" "^7.0.0"
+ "@babel/helper-replace-supers" "^7.4.0"
+ "@babel/helper-split-export-declaration" "^7.4.0"
-"@babel/helper-define-map@^7.1.0":
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.1.0.tgz#3b74caec329b3c80c116290887c0dd9ae468c20c"
- integrity sha512-yPPcW8dc3gZLN+U1mhYV91QU3n5uTbx7DUdf8NnPbjS0RMwBuHi9Xt2MUgppmNz7CJxTBWsGczTiEp1CSOTPRg==
+"@babel/helper-define-map@^7.4.0":
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.4.0.tgz#cbfd8c1b2f12708e262c26f600cd16ed6a3bc6c9"
+ integrity sha512-wAhQ9HdnLIywERVcSvX40CEJwKdAa1ID4neI9NXQPDOHwwA+57DqwLiPEVy2AIyWzAk0CQ8qx4awO0VUURwLtA==
dependencies:
"@babel/helper-function-name" "^7.1.0"
- "@babel/types" "^7.0.0"
- lodash "^4.17.10"
+ "@babel/types" "^7.4.0"
+ lodash "^4.17.11"
"@babel/helper-explode-assignable-expression@^7.1.0":
version "7.1.0"
@@ -126,12 +126,12 @@
dependencies:
"@babel/types" "^7.0.0"
-"@babel/helper-hoist-variables@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.0.0.tgz#46adc4c5e758645ae7a45deb92bab0918c23bb88"
- integrity sha512-Ggv5sldXUeSKsuzLkddtyhyHe2YantsxWKNi7A+7LeD12ExRDWTRk29JCXpaHPAbMaIPZSil7n+lq78WY2VY7w==
+"@babel/helper-hoist-variables@^7.4.0":
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.4.0.tgz#25b621399ae229869329730a62015bbeb0a6fbd6"
+ integrity sha512-/NErCuoe/et17IlAQFKWM24qtyYYie7sFIrW/tIQXpck6vAu2hhtYYsKLBWQV+BQZMbcIYPU/QMYuTufrY4aQw==
dependencies:
- "@babel/types" "^7.0.0"
+ "@babel/types" "^7.4.0"
"@babel/helper-member-expression-to-functions@^7.0.0":
version "7.0.0"
@@ -189,15 +189,15 @@
"@babel/traverse" "^7.1.0"
"@babel/types" "^7.0.0"
-"@babel/helper-replace-supers@^7.1.0", "@babel/helper-replace-supers@^7.3.4":
- version "7.3.4"
- resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.3.4.tgz#a795208e9b911a6eeb08e5891faacf06e7013e13"
- integrity sha512-pvObL9WVf2ADs+ePg0jrqlhHoxRXlOa+SHRHzAXIz2xkYuOHfGl+fKxPMaS4Fq+uje8JQPobnertBBvyrWnQ1A==
+"@babel/helper-replace-supers@^7.1.0", "@babel/helper-replace-supers@^7.4.0":
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.4.0.tgz#4f56adb6aedcd449d2da9399c2dcf0545463b64c"
+ integrity sha512-PVwCVnWWAgnal+kJ+ZSAphzyl58XrFeSKSAJRiqg5QToTsjL+Xu1f9+RJ+d+Q0aPhPfBGaYfkox66k86thxNSg==
dependencies:
"@babel/helper-member-expression-to-functions" "^7.0.0"
"@babel/helper-optimise-call-expression" "^7.0.0"
- "@babel/traverse" "^7.3.4"
- "@babel/types" "^7.3.4"
+ "@babel/traverse" "^7.4.0"
+ "@babel/types" "^7.4.0"
"@babel/helper-simple-access@^7.1.0":
version "7.1.0"
@@ -207,12 +207,12 @@
"@babel/template" "^7.1.0"
"@babel/types" "^7.0.0"
-"@babel/helper-split-export-declaration@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz#3aae285c0311c2ab095d997b8c9a94cad547d813"
- integrity sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==
+"@babel/helper-split-export-declaration@^7.0.0", "@babel/helper-split-export-declaration@^7.4.0":
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.0.tgz#571bfd52701f492920d63b7f735030e9a3e10b55"
+ integrity sha512-7Cuc6JZiYShaZnybDmfwhY4UYHzI6rlqhWjaIqbsJGsIqPimEYy5uh3akSRLMg65LSdSEnJ8a8/bWQN6u2oMGw==
dependencies:
- "@babel/types" "^7.0.0"
+ "@babel/types" "^7.4.0"
"@babel/helper-wrap-function@^7.1.0":
version "7.2.0"
@@ -224,14 +224,14 @@
"@babel/traverse" "^7.1.0"
"@babel/types" "^7.2.0"
-"@babel/helpers@^7.2.0":
- version "7.3.1"
- resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.3.1.tgz#949eec9ea4b45d3210feb7dc1c22db664c9e44b9"
- integrity sha512-Q82R3jKsVpUV99mgX50gOPCWwco9Ec5Iln/8Vyu4osNIOQgSrd9RFrQeUvmvddFNoLwMyOUWU+5ckioEKpDoGA==
+"@babel/helpers@^7.4.0":
+ version "7.4.2"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.4.2.tgz#3bdfa46a552ca77ef5a0f8551be5f0845ae989be"
+ integrity sha512-gQR1eQeroDzFBikhrCccm5Gs2xBjZ57DNjGbqTaHo911IpmSxflOQWMAHPw/TXk8L3isv7s9lYzUkexOeTQUYg==
dependencies:
- "@babel/template" "^7.1.2"
- "@babel/traverse" "^7.1.5"
- "@babel/types" "^7.3.0"
+ "@babel/template" "^7.4.0"
+ "@babel/traverse" "^7.4.0"
+ "@babel/types" "^7.4.0"
"@babel/highlight@^7.0.0":
version "7.0.0"
@@ -242,10 +242,10 @@
esutils "^2.0.2"
js-tokens "^4.0.0"
-"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.2.2", "@babel/parser@^7.3.4":
- version "7.3.4"
- resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.3.4.tgz#a43357e4bbf4b92a437fb9e465c192848287f27c"
- integrity sha512-tXZCqWtlOOP4wgCp6RjRvLmfuhnqTLy9VHwRochJBCP2nDm27JnnuFEnXFASVyQNHk36jD1tAammsCEEqgscIQ==
+"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.4.0":
+ version "7.4.2"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.4.2.tgz#b4521a400cb5a871eab3890787b4bc1326d38d91"
+ integrity sha512-9fJTDipQFvlfSVdD/JBtkiY0br9BtfvW2R8wo6CX/Ej2eMuV0gWPk1M67Mt3eggQvBqYW1FCEk8BN7WvGm/g5g==
"@babel/plugin-proposal-async-generator-functions@^7.2.0":
version "7.2.0"
@@ -256,20 +256,20 @@
"@babel/helper-remap-async-to-generator" "^7.1.0"
"@babel/plugin-syntax-async-generators" "^7.2.0"
-"@babel/plugin-proposal-class-properties@^7.3.0":
- version "7.3.4"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.3.4.tgz#410f5173b3dc45939f9ab30ca26684d72901405e"
- integrity sha512-lUf8D3HLs4yYlAo8zjuneLvfxN7qfKv1Yzbj5vjqaqMJxgJA3Ipwp4VUJ+OrOdz53Wbww6ahwB8UhB2HQyLotA==
+"@babel/plugin-proposal-class-properties@^7.4.0":
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.4.0.tgz#d70db61a2f1fd79de927eea91f6411c964e084b8"
+ integrity sha512-t2ECPNOXsIeK1JxJNKmgbzQtoG27KIlVE61vTqX0DKR9E9sZlVVxWUtEW9D5FlZ8b8j7SBNCHY47GgPKCKlpPg==
dependencies:
- "@babel/helper-create-class-features-plugin" "^7.3.4"
+ "@babel/helper-create-class-features-plugin" "^7.4.0"
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-proposal-decorators@^7.3.0":
- version "7.3.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.3.0.tgz#637ba075fa780b1f75d08186e8fb4357d03a72a7"
- integrity sha512-3W/oCUmsO43FmZIqermmq6TKaRSYhmh/vybPfVFwQWdSb8xwki38uAIvknCRzuyHRuYfCYmJzL9or1v0AffPjg==
+"@babel/plugin-proposal-decorators@^7.4.0":
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.4.0.tgz#8e1bfd83efa54a5f662033afcc2b8e701f4bb3a9"
+ integrity sha512-d08TLmXeK/XbgCo7ZeZ+JaeZDtDai/2ctapTRsWWkkmy7G/cqz8DQN/HlWG7RR4YmfXxmExsbU3SuCjlM7AtUg==
dependencies:
- "@babel/helper-create-class-features-plugin" "^7.3.0"
+ "@babel/helper-create-class-features-plugin" "^7.4.0"
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-syntax-decorators" "^7.2.0"
@@ -281,10 +281,10 @@
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-syntax-json-strings" "^7.2.0"
-"@babel/plugin-proposal-object-rest-spread@^7.3.4":
- version "7.3.4"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.3.4.tgz#47f73cf7f2a721aad5c0261205405c642e424654"
- integrity sha512-j7VQmbbkA+qrzNqbKHrBsW3ddFnOeva6wzSe/zB7T+xaxGc+RCpwo44wCmRixAIGRoIpmVgvzFzNJqQcO3/9RA==
+"@babel/plugin-proposal-object-rest-spread@^7.4.0":
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.4.0.tgz#e4960575205eadf2a1ab4e0c79f9504d5b82a97f"
+ integrity sha512-uTNi8pPYyUH2eWHyYWWSYJKwKg34hhgl4/dbejEjL+64OhbHjTX7wEVWMQl82tEmdDsGeu77+s8HHLS627h6OQ==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-syntax-object-rest-spread" "^7.2.0"
@@ -297,14 +297,14 @@
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-syntax-optional-catch-binding" "^7.2.0"
-"@babel/plugin-proposal-unicode-property-regex@^7.2.0":
- version "7.2.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.2.0.tgz#abe7281fe46c95ddc143a65e5358647792039520"
- integrity sha512-LvRVYb7kikuOtIoUeWTkOxQEV1kYvL5B6U3iWEGCzPNRus1MzJweFqORTj+0jkxozkTSYNJozPOddxmqdqsRpw==
+"@babel/plugin-proposal-unicode-property-regex@^7.4.0":
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.4.0.tgz#202d91ee977d760ef83f4f416b280d568be84623"
+ integrity sha512-h/KjEZ3nK9wv1P1FSNb9G079jXrNYR0Ko+7XkOx85+gM24iZbPn0rh4vCftk+5QKY7y1uByFataBTmX7irEF1w==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/helper-regex" "^7.0.0"
- regexpu-core "^4.2.0"
+ regexpu-core "^4.5.4"
"@babel/plugin-syntax-async-generators@^7.2.0":
version "7.2.0"
@@ -362,10 +362,10 @@
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-async-to-generator@^7.3.4":
- version "7.3.4"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.3.4.tgz#4e45408d3c3da231c0e7b823f407a53a7eb3048c"
- integrity sha512-Y7nCzv2fw/jEZ9f678MuKdMo99MFDJMT/PvD9LisrR5JDFcJH6vYeH6RnjVt3p5tceyGRvTtEN0VOlU+rgHZjA==
+"@babel/plugin-transform-async-to-generator@^7.4.0":
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.4.0.tgz#234fe3e458dce95865c0d152d256119b237834b0"
+ integrity sha512-EeaFdCeUULM+GPFEsf7pFcNSxM7hYjoj5fiYbyuiXobW4JhFnjAv9OWzNwHyHcKoPNpAfeRDuW6VyaXEDUBa7g==
dependencies:
"@babel/helper-module-imports" "^7.0.0"
"@babel/helper-plugin-utils" "^7.0.0"
@@ -378,26 +378,26 @@
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-block-scoping@^7.3.4":
- version "7.3.4"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.3.4.tgz#5c22c339de234076eee96c8783b2fed61202c5c4"
- integrity sha512-blRr2O8IOZLAOJklXLV4WhcEzpYafYQKSGT3+R26lWG41u/FODJuBggehtOwilVAcFu393v3OFj+HmaE6tVjhA==
+"@babel/plugin-transform-block-scoping@^7.4.0":
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.4.0.tgz#164df3bb41e3deb954c4ca32ffa9fcaa56d30bcb"
+ integrity sha512-AWyt3k+fBXQqt2qb9r97tn3iBwFpiv9xdAiG+Gr2HpAZpuayvbL55yWrsV3MyHvXk/4vmSiedhDRl1YI2Iy5nQ==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
lodash "^4.17.11"
-"@babel/plugin-transform-classes@^7.3.4":
- version "7.3.4"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.3.4.tgz#dc173cb999c6c5297e0b5f2277fdaaec3739d0cc"
- integrity sha512-J9fAvCFBkXEvBimgYxCjvaVDzL6thk0j0dBvCeZmIUDBwyt+nv6HfbImsSrWsYXfDNDivyANgJlFXDUWRTZBuA==
+"@babel/plugin-transform-classes@^7.4.0":
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.4.0.tgz#e3428d3c8a3d01f33b10c529b998ba1707043d4d"
+ integrity sha512-XGg1Mhbw4LDmrO9rSTNe+uI79tQPdGs0YASlxgweYRLZqo/EQktjaOV4tchL/UZbM0F+/94uOipmdNGoaGOEYg==
dependencies:
"@babel/helper-annotate-as-pure" "^7.0.0"
- "@babel/helper-define-map" "^7.1.0"
+ "@babel/helper-define-map" "^7.4.0"
"@babel/helper-function-name" "^7.1.0"
"@babel/helper-optimise-call-expression" "^7.0.0"
"@babel/helper-plugin-utils" "^7.0.0"
- "@babel/helper-replace-supers" "^7.3.4"
- "@babel/helper-split-export-declaration" "^7.0.0"
+ "@babel/helper-replace-supers" "^7.4.0"
+ "@babel/helper-split-export-declaration" "^7.4.0"
globals "^11.1.0"
"@babel/plugin-transform-computed-properties@^7.2.0":
@@ -407,10 +407,10 @@
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-destructuring@^7.2.0":
- version "7.3.2"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.3.2.tgz#f2f5520be055ba1c38c41c0e094d8a461dd78f2d"
- integrity sha512-Lrj/u53Ufqxl/sGxyjsJ2XNtNuEjDyjpqdhMNh5aZ+XFOdThL46KBj27Uem4ggoezSYBxKWAil6Hu8HtwqesYw==
+"@babel/plugin-transform-destructuring@^7.4.0":
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.4.0.tgz#acbb9b2418d290107db333f4d6cd8aa6aea00343"
+ integrity sha512-HySkoatyYTY3ZwLI8GGvkRWCFrjAGXUHur5sMecmCIdIharnlcWWivOqDJI76vvmVZfzwb6G08NREsrY96RhGQ==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
@@ -438,10 +438,10 @@
"@babel/helper-builder-binary-assignment-operator-visitor" "^7.1.0"
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-for-of@^7.2.0":
- version "7.2.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.2.0.tgz#ab7468befa80f764bb03d3cb5eef8cc998e1cad9"
- integrity sha512-Kz7Mt0SsV2tQk6jG5bBv5phVbkd0gd27SgYD4hH1aLMJRchM0dzHaXvrWhVZ+WxAlDoAKZ7Uy3jVTW2mKXQ1WQ==
+"@babel/plugin-transform-for-of@^7.4.0":
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.4.0.tgz#56c8c36677f5d4a16b80b12f7b768de064aaeb5f"
+ integrity sha512-vWdfCEYLlYSxbsKj5lGtzA49K3KANtb8qCPQ1em07txJzsBwY+cKJzBHizj5fl3CCx7vt+WPdgDLTHmydkbQSQ==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
@@ -468,21 +468,21 @@
"@babel/helper-module-transforms" "^7.1.0"
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-modules-commonjs@^7.2.0":
- version "7.2.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.2.0.tgz#c4f1933f5991d5145e9cfad1dfd848ea1727f404"
- integrity sha512-V6y0uaUQrQPXUrmj+hgnks8va2L0zcZymeU7TtWEgdRLNkceafKXEduv7QzgQAE4lT+suwooG9dC7LFhdRAbVQ==
+"@babel/plugin-transform-modules-commonjs@^7.4.0":
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.4.0.tgz#3b8ec61714d3b75d20c5ccfa157f2c2e087fd4ca"
+ integrity sha512-iWKAooAkipG7g1IY0eah7SumzfnIT3WNhT4uYB2kIsvHnNSB6MDYVa5qyICSwaTBDBY2c4SnJ3JtEa6ltJd6Jw==
dependencies:
"@babel/helper-module-transforms" "^7.1.0"
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/helper-simple-access" "^7.1.0"
-"@babel/plugin-transform-modules-systemjs@^7.3.4":
- version "7.3.4"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.3.4.tgz#813b34cd9acb6ba70a84939f3680be0eb2e58861"
- integrity sha512-VZ4+jlGOF36S7TjKs8g4ojp4MEI+ebCQZdswWb/T9I4X84j8OtFAyjXjt/M16iIm5RIZn0UMQgg/VgIwo/87vw==
+"@babel/plugin-transform-modules-systemjs@^7.4.0":
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.4.0.tgz#c2495e55528135797bc816f5d50f851698c586a1"
+ integrity sha512-gjPdHmqiNhVoBqus5qK60mWPp1CmYWp/tkh11mvb0rrys01HycEGD7NvvSoKXlWEfSM9TcL36CpsK8ElsADptQ==
dependencies:
- "@babel/helper-hoist-variables" "^7.0.0"
+ "@babel/helper-hoist-variables" "^7.4.0"
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-transform-modules-umd@^7.2.0":
@@ -493,17 +493,17 @@
"@babel/helper-module-transforms" "^7.1.0"
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-named-capturing-groups-regex@^7.3.0":
- version "7.3.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.3.0.tgz#140b52985b2d6ef0cb092ef3b29502b990f9cd50"
- integrity sha512-NxIoNVhk9ZxS+9lSoAQ/LM0V2UEvARLttEHUrRDGKFaAxOYQcrkN/nLRE+BbbicCAvZPl7wMP0X60HsHE5DtQw==
+"@babel/plugin-transform-named-capturing-groups-regex@^7.4.2":
+ version "7.4.2"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.4.2.tgz#800391136d6cbcc80728dbdba3c1c6e46f86c12e"
+ integrity sha512-NsAuliSwkL3WO2dzWTOL1oZJHm0TM8ZY8ZSxk2ANyKkt5SQlToGA4pzctmq1BEjoacurdwZ3xp2dCQWJkME0gQ==
dependencies:
regexp-tree "^0.1.0"
-"@babel/plugin-transform-new-target@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.0.0.tgz#ae8fbd89517fa7892d20e6564e641e8770c3aa4a"
- integrity sha512-yin069FYjah+LbqfGeTfzIBODex/e++Yfa0rH0fpfam9uTbuEeEOx5GLGr210ggOV77mVRNoeqSYqeuaqSzVSw==
+"@babel/plugin-transform-new-target@^7.4.0":
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.4.0.tgz#67658a1d944edb53c8d4fa3004473a0dd7838150"
+ integrity sha512-6ZKNgMQmQmrEX/ncuCwnnw1yVGoaOW5KpxNhoWI7pCQdA0uZ0HqHGqenCUIENAnxRjy2WwNQ30gfGdIgqJXXqw==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
@@ -515,26 +515,26 @@
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/helper-replace-supers" "^7.1.0"
-"@babel/plugin-transform-parameters@^7.2.0":
- version "7.3.3"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.3.3.tgz#3a873e07114e1a5bee17d04815662c8317f10e30"
- integrity sha512-IrIP25VvXWu/VlBWTpsjGptpomtIkYrN/3aDp4UKm7xK6UxZY88kcJ1UwETbzHAlwN21MnNfwlar0u8y3KpiXw==
+"@babel/plugin-transform-parameters@^7.4.0":
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.4.0.tgz#a1309426fac4eecd2a9439a4c8c35124a11a48a9"
+ integrity sha512-Xqv6d1X+doyiuCGDoVJFtlZx0onAX0tnc3dY8w71pv/O0dODAbusVv2Ale3cGOwfiyi895ivOBhYa9DhAM8dUA==
dependencies:
- "@babel/helper-call-delegate" "^7.1.0"
+ "@babel/helper-call-delegate" "^7.4.0"
"@babel/helper-get-function-arity" "^7.0.0"
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-regenerator@^7.3.4":
- version "7.3.4"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.3.4.tgz#1601655c362f5b38eead6a52631f5106b29fa46a"
- integrity sha512-hvJg8EReQvXT6G9H2MvNPXkv9zK36Vxa1+csAVTpE1J3j0zlHplw76uudEbJxgvqZzAq9Yh45FLD4pk5mKRFQA==
+"@babel/plugin-transform-regenerator@^7.4.0":
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.4.0.tgz#0780e27ee458cc3fdbad18294d703e972ae1f6d1"
+ integrity sha512-SZ+CgL4F0wm4npojPU6swo/cK4FcbLgxLd4cWpHaNXY/NJ2dpahODCqBbAwb2rDmVszVb3SSjnk9/vik3AYdBw==
dependencies:
regenerator-transform "^0.13.4"
-"@babel/plugin-transform-runtime@^7.2.0", "@babel/plugin-transform-runtime@^7.3.4":
- version "7.3.4"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.3.4.tgz#57805ac8c1798d102ecd75c03b024a5b3ea9b431"
- integrity sha512-PaoARuztAdd5MgeVjAxnIDAIUet5KpogqaefQvPOmPYCxYoaPhautxDh3aO8a4xHsKgT/b9gSxR0BKK1MIewPA==
+"@babel/plugin-transform-runtime@^7.4.0":
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.4.0.tgz#b4d8c925ed957471bc57e0b9da53408ebb1ed457"
+ integrity sha512-1uv2h9wnRj98XX3g0l4q+O3jFM6HfayKup7aIu4pnnlzGz0H+cYckGBC74FZIWJXJSXAmeJ9Yu5Gg2RQpS4hWg==
dependencies:
"@babel/helper-module-imports" "^7.0.0"
"@babel/helper-plugin-utils" "^7.0.0"
@@ -587,103 +587,105 @@
"@babel/helper-regex" "^7.0.0"
regexpu-core "^4.1.3"
-"@babel/polyfill@^7.0.0", "@babel/polyfill@^7.2.5":
- version "7.2.5"
- resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.2.5.tgz#6c54b964f71ad27edddc567d065e57e87ed7fa7d"
- integrity sha512-8Y/t3MWThtMLYr0YNC/Q76tqN1w30+b0uQMeFUYauG2UGTR19zyUtFrAzT23zNtBxPp+LbE5E/nwV/q/r3y6ug==
+"@babel/polyfill@^7.0.0":
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.4.0.tgz#90f9d68ae34ac42ab4b4aa03151848f536960218"
+ integrity sha512-bVsjsrtsDflIHp5I6caaAa2V25Kzn50HKPL6g3X0P0ni1ks+58cPB8Mz6AOKVuRPgaVdq/OwEUc/1vKqX+Mo4A==
dependencies:
- core-js "^2.5.7"
- regenerator-runtime "^0.12.0"
+ core-js "^2.6.5"
+ regenerator-runtime "^0.13.2"
-"@babel/preset-env@^7.3.1", "@babel/preset-env@^7.3.4":
- version "7.3.4"
- resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.3.4.tgz#887cf38b6d23c82f19b5135298bdb160062e33e1"
- integrity sha512-2mwqfYMK8weA0g0uBKOt4FE3iEodiHy9/CW0b+nWXcbL+pGzLx8ESYc+j9IIxr6LTDHWKgPm71i9smo02bw+gA==
+"@babel/preset-env@^7.4.2":
+ version "7.4.2"
+ resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.4.2.tgz#2f5ba1de2daefa9dcca653848f96c7ce2e406676"
+ integrity sha512-OEz6VOZaI9LW08CWVS3d9g/0jZA6YCn1gsKIy/fut7yZCJti5Lm1/Hi+uo/U+ODm7g4I6gULrCP+/+laT8xAsA==
dependencies:
"@babel/helper-module-imports" "^7.0.0"
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-proposal-async-generator-functions" "^7.2.0"
"@babel/plugin-proposal-json-strings" "^7.2.0"
- "@babel/plugin-proposal-object-rest-spread" "^7.3.4"
+ "@babel/plugin-proposal-object-rest-spread" "^7.4.0"
"@babel/plugin-proposal-optional-catch-binding" "^7.2.0"
- "@babel/plugin-proposal-unicode-property-regex" "^7.2.0"
+ "@babel/plugin-proposal-unicode-property-regex" "^7.4.0"
"@babel/plugin-syntax-async-generators" "^7.2.0"
"@babel/plugin-syntax-json-strings" "^7.2.0"
"@babel/plugin-syntax-object-rest-spread" "^7.2.0"
"@babel/plugin-syntax-optional-catch-binding" "^7.2.0"
"@babel/plugin-transform-arrow-functions" "^7.2.0"
- "@babel/plugin-transform-async-to-generator" "^7.3.4"
+ "@babel/plugin-transform-async-to-generator" "^7.4.0"
"@babel/plugin-transform-block-scoped-functions" "^7.2.0"
- "@babel/plugin-transform-block-scoping" "^7.3.4"
- "@babel/plugin-transform-classes" "^7.3.4"
+ "@babel/plugin-transform-block-scoping" "^7.4.0"
+ "@babel/plugin-transform-classes" "^7.4.0"
"@babel/plugin-transform-computed-properties" "^7.2.0"
- "@babel/plugin-transform-destructuring" "^7.2.0"
+ "@babel/plugin-transform-destructuring" "^7.4.0"
"@babel/plugin-transform-dotall-regex" "^7.2.0"
"@babel/plugin-transform-duplicate-keys" "^7.2.0"
"@babel/plugin-transform-exponentiation-operator" "^7.2.0"
- "@babel/plugin-transform-for-of" "^7.2.0"
+ "@babel/plugin-transform-for-of" "^7.4.0"
"@babel/plugin-transform-function-name" "^7.2.0"
"@babel/plugin-transform-literals" "^7.2.0"
"@babel/plugin-transform-modules-amd" "^7.2.0"
- "@babel/plugin-transform-modules-commonjs" "^7.2.0"
- "@babel/plugin-transform-modules-systemjs" "^7.3.4"
+ "@babel/plugin-transform-modules-commonjs" "^7.4.0"
+ "@babel/plugin-transform-modules-systemjs" "^7.4.0"
"@babel/plugin-transform-modules-umd" "^7.2.0"
- "@babel/plugin-transform-named-capturing-groups-regex" "^7.3.0"
- "@babel/plugin-transform-new-target" "^7.0.0"
+ "@babel/plugin-transform-named-capturing-groups-regex" "^7.4.2"
+ "@babel/plugin-transform-new-target" "^7.4.0"
"@babel/plugin-transform-object-super" "^7.2.0"
- "@babel/plugin-transform-parameters" "^7.2.0"
- "@babel/plugin-transform-regenerator" "^7.3.4"
+ "@babel/plugin-transform-parameters" "^7.4.0"
+ "@babel/plugin-transform-regenerator" "^7.4.0"
"@babel/plugin-transform-shorthand-properties" "^7.2.0"
"@babel/plugin-transform-spread" "^7.2.0"
"@babel/plugin-transform-sticky-regex" "^7.2.0"
"@babel/plugin-transform-template-literals" "^7.2.0"
"@babel/plugin-transform-typeof-symbol" "^7.2.0"
"@babel/plugin-transform-unicode-regex" "^7.2.0"
- browserslist "^4.3.4"
+ "@babel/types" "^7.4.0"
+ browserslist "^4.4.2"
+ core-js-compat "^3.0.0"
invariant "^2.2.2"
js-levenshtein "^1.1.3"
semver "^5.3.0"
-"@babel/runtime@^7.3.1":
- version "7.3.4"
- resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.3.4.tgz#73d12ba819e365fcf7fd152aed56d6df97d21c83"
- integrity sha512-IvfvnMdSaLBateu0jfsYIpZTxAc2cKEXEMiezGGN75QcBcecDUKd3PgLAncT0oOgxKy8dd8hrJKj9MfzgfZd6g==
+"@babel/runtime@^7.4.2":
+ version "7.4.2"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.2.tgz#f5ab6897320f16decd855eed70b705908a313fe8"
+ integrity sha512-7Bl2rALb7HpvXFL7TETNzKSAeBVCPHELzc0C//9FCxN8nsiueWSJBqaF+2oIJScyILStASR/Cx5WMkXGYTiJFA==
dependencies:
- regenerator-runtime "^0.12.0"
+ regenerator-runtime "^0.13.2"
-"@babel/standalone@^7.3.4":
- version "7.3.4"
- resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.3.4.tgz#b622c1e522acef91b2a14f22bdcdd4f935a1a474"
- integrity sha512-4L9c5i4WlGqbrjOVX0Yp8TIR5cEiw1/tPYYZENW/iuO2uI6viY38U7zALidzNfGdZIwNc+A/AWqMEWKeScWkBg==
+"@babel/standalone@^7.4.2":
+ version "7.4.2"
+ resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.4.2.tgz#4929048447ac98d6652b524aef768c2e14b3eae8"
+ integrity sha512-pXo5sMf6eF6jYFJ1NCISO2kY4kDuiEedYJDQ2uc1kx4sybEt5DNHtar25c2VlXgm0VedFZABsHeuP0EjXW79qg==
-"@babel/template@^7.0.0", "@babel/template@^7.1.0", "@babel/template@^7.1.2", "@babel/template@^7.2.2":
- version "7.2.2"
- resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.2.2.tgz#005b3fdf0ed96e88041330379e0da9a708eb2907"
- integrity sha512-zRL0IMM02AUDwghf5LMSSDEz7sBCO2YnNmpg3uWTZj/v1rcG2BmQUvaGU8GhU8BvfMh1k2KIAYZ7Ji9KXPUg7g==
+"@babel/template@^7.0.0", "@babel/template@^7.1.0", "@babel/template@^7.2.2", "@babel/template@^7.4.0":
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.4.0.tgz#12474e9c077bae585c5d835a95c0b0b790c25c8b"
+ integrity sha512-SOWwxxClTTh5NdbbYZ0BmaBVzxzTh2tO/TeLTbF6MO6EzVhHTnff8CdBXx3mEtazFBoysmEM6GU/wF+SuSx4Fw==
dependencies:
"@babel/code-frame" "^7.0.0"
- "@babel/parser" "^7.2.2"
- "@babel/types" "^7.2.2"
+ "@babel/parser" "^7.4.0"
+ "@babel/types" "^7.4.0"
-"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.1.5", "@babel/traverse@^7.3.4":
- version "7.3.4"
- resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.3.4.tgz#1330aab72234f8dea091b08c4f8b9d05c7119e06"
- integrity sha512-TvTHKp6471OYEcE/91uWmhR6PrrYywQntCHSaZ8CM8Vmp+pjAusal4nGB2WCCQd0rvI7nOMKn9GnbcvTUz3/ZQ==
+"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.4.0":
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.4.0.tgz#14006967dd1d2b3494cdd650c686db9daf0ddada"
+ integrity sha512-/DtIHKfyg2bBKnIN+BItaIlEg5pjAnzHOIQe5w+rHAw/rg9g0V7T4rqPX8BJPfW11kt3koyjAnTNwCzb28Y1PA==
dependencies:
"@babel/code-frame" "^7.0.0"
- "@babel/generator" "^7.3.4"
+ "@babel/generator" "^7.4.0"
"@babel/helper-function-name" "^7.1.0"
- "@babel/helper-split-export-declaration" "^7.0.0"
- "@babel/parser" "^7.3.4"
- "@babel/types" "^7.3.4"
+ "@babel/helper-split-export-declaration" "^7.4.0"
+ "@babel/parser" "^7.4.0"
+ "@babel/types" "^7.4.0"
debug "^4.1.0"
globals "^11.1.0"
lodash "^4.17.11"
-"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.3.0", "@babel/types@^7.3.4":
- version "7.3.4"
- resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.3.4.tgz#bf482eaeaffb367a28abbf9357a94963235d90ed"
- integrity sha512-WEkp8MsLftM7O/ty580wAmZzN1nDmCACc5+jFzUt+GUFNNIi3LdRlueYz0YIlmJhlZx1QYDMZL5vdWCL0fNjFQ==
+"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.3.0", "@babel/types@^7.4.0":
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.4.0.tgz#670724f77d24cce6cc7d8cf64599d511d164894c"
+ integrity sha512-aPvkXyU2SPOnztlgo8n9cEiXW755mgyvueUPcpStqdzoSPm0fjO0vQBjLkt3JKJW7ufikfcnMTTPsN1xaTsBPA==
dependencies:
esutils "^2.0.2"
lodash "^4.17.11"
@@ -1104,83 +1106,87 @@
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b"
integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==
-"@nuxt/babel-preset-app@2.4.5":
- version "2.4.5"
- resolved "https://registry.yarnpkg.com/@nuxt/babel-preset-app/-/babel-preset-app-2.4.5.tgz#707043fe4686b7375df0917cca9134b7681ae9bf"
- integrity sha512-Pfpp9++AjTLSvr0EQY00SPacSxw6nvIgARVTiFG8xEkqzUzChx1xz424u4e8mKhu3qEgj9ldcF5iKC5A87RYkw==
+"@nuxt/babel-preset-app@2.5.1":
+ version "2.5.1"
+ resolved "https://registry.yarnpkg.com/@nuxt/babel-preset-app/-/babel-preset-app-2.5.1.tgz#11512252c93c4b1268a24134e841096f7743e80c"
+ integrity sha512-5LWXz5rtxrtEtghvdyaZ17Y6SnS/+8LQsfguJ7HYpljZkadZrQNs01AaLnnkpwvp9CejVNI0T8hslliw23ylvg==
dependencies:
- "@babel/core" "^7.2.2"
- "@babel/plugin-proposal-class-properties" "^7.3.0"
- "@babel/plugin-proposal-decorators" "^7.3.0"
+ "@babel/core" "^7.4.0"
+ "@babel/plugin-proposal-class-properties" "^7.4.0"
+ "@babel/plugin-proposal-decorators" "^7.4.0"
"@babel/plugin-syntax-dynamic-import" "^7.2.0"
- "@babel/plugin-transform-runtime" "^7.2.0"
- "@babel/preset-env" "^7.3.1"
- "@babel/runtime" "^7.3.1"
+ "@babel/plugin-transform-runtime" "^7.4.0"
+ "@babel/preset-env" "^7.4.2"
+ "@babel/runtime" "^7.4.2"
"@vue/babel-preset-jsx" "^1.0.0-beta.2"
+ core-js "^2.6.5"
-"@nuxt/builder@2.4.5":
- version "2.4.5"
- resolved "https://registry.yarnpkg.com/@nuxt/builder/-/builder-2.4.5.tgz#afe5b1b91b7fd315cd7a1fe2c461ba361fe3e020"
- integrity sha512-WPgNmDK7UgInCNECl13u6tJ9woC8c1ToPXgEfqL0pTZWlztqOyGXMcXaQnI0n1QKsqQPWFUfNAtztAum7xZLpw==
- dependencies:
- "@nuxt/devalue" "^1.2.0"
- "@nuxt/utils" "2.4.5"
- "@nuxt/vue-app" "2.4.5"
- chokidar "^2.0.4"
- consola "^2.3.2"
+"@nuxt/builder@2.5.1":
+ version "2.5.1"
+ resolved "https://registry.yarnpkg.com/@nuxt/builder/-/builder-2.5.1.tgz#4c390dd51d2179afbdc9e35a60b12e8661b57751"
+ integrity sha512-h/GT4qoyN7yHxY4JsIgmWpDf5lfpwTBszJaf4nu0AOJyGqMP8sbj5K8X6TzRVjKJeF1dcDNJFFeGJomSNJIFzA==
+ dependencies:
+ "@nuxt/devalue" "^1.2.2"
+ "@nuxt/utils" "2.5.1"
+ "@nuxt/vue-app" "2.5.1"
+ chokidar "^2.1.2"
+ consola "^2.5.7"
fs-extra "^7.0.1"
glob "^7.1.3"
hash-sum "^1.0.2"
+ ignore "^5.0.6"
lodash "^4.17.11"
pify "^4.0.1"
semver "^5.6.0"
serialize-javascript "^1.6.1"
- upath "^1.1.0"
+ upath "^1.1.2"
-"@nuxt/cli@2.4.5":
- version "2.4.5"
- resolved "https://registry.yarnpkg.com/@nuxt/cli/-/cli-2.4.5.tgz#f923963b8238d4530ac390e4e32025f687a9347a"
- integrity sha512-mBrh8sZySEx4v6IqAgdq9aPY6JKl0m3BREt90anV8w+63YMmNHRFizGdWyEgq/6YUrCvuCua2RvJCZphBdnhFQ==
+"@nuxt/cli@2.5.1":
+ version "2.5.1"
+ resolved "https://registry.yarnpkg.com/@nuxt/cli/-/cli-2.5.1.tgz#418cb1a2a819c920dcd34c4514aa3f1d96cecccd"
+ integrity sha512-BHe90Ove6mCV3XRyNmGWiYbXx0OT/moUu1+rR89PTOzBugmf1v/tH3tdUfeDKC/d3sa52fktjVcv9JEo6QIESw==
dependencies:
- "@nuxt/config" "2.4.5"
+ "@nuxt/config" "2.5.1"
+ "@nuxt/utils" "2.5.1"
boxen "^3.0.0"
chalk "^2.4.2"
- consola "^2.3.2"
- esm "^3.2.3"
+ consola "^2.5.7"
+ esm "^3.2.20"
execa "^1.0.0"
exit "^0.1.2"
minimist "^1.2.0"
+ opener "1.5.1"
pretty-bytes "^5.1.0"
std-env "^2.2.1"
- wrap-ansi "^4.0.0"
+ wrap-ansi "^5.0.0"
-"@nuxt/config@2.4.5":
- version "2.4.5"
- resolved "https://registry.yarnpkg.com/@nuxt/config/-/config-2.4.5.tgz#78a03fe347006d5f3b620cb9acd73f62c13db61e"
- integrity sha512-Yn1FqOVG7Si+clikYg5ILAxDWfTlweKULzZDtAZriWjQPg0D2sJ9VWV+mdggPQfyn+n4mvPvD4BEIyzvKVaXdg==
+"@nuxt/config@2.5.1":
+ version "2.5.1"
+ resolved "https://registry.yarnpkg.com/@nuxt/config/-/config-2.5.1.tgz#818dfe434ce4e71c0b758d8e4d1d33a26ea428b7"
+ integrity sha512-v2Q24xzkrs5S9fmRrRFpjwHEFNPWYDZPL9xcgJDEKL9ngPnSJpo2NCrvH57thkGaAqWJF1KqCKGx7LZX1UmVVg==
dependencies:
- "@nuxt/utils" "2.4.5"
- consola "^2.3.2"
+ "@nuxt/utils" "2.5.1"
+ consola "^2.5.7"
std-env "^2.2.1"
-"@nuxt/core@2.4.5":
- version "2.4.5"
- resolved "https://registry.yarnpkg.com/@nuxt/core/-/core-2.4.5.tgz#92cc97c99fadd90d34ad29db94eab22b6a494680"
- integrity sha512-2hjyLRLmLMkG+9e1bhLmeU+ow4Ph5lPrArW8BPBNohO4Oxjzb/A3UUO6UhMMA24/9+qsBQT6rwsQ0WA66UCpJA==
- dependencies:
- "@nuxt/config" "2.4.5"
- "@nuxt/devalue" "^1.2.0"
- "@nuxt/server" "2.4.5"
- "@nuxt/utils" "2.4.5"
- "@nuxt/vue-renderer" "2.4.5"
- consola "^2.3.2"
+"@nuxt/core@2.5.1":
+ version "2.5.1"
+ resolved "https://registry.yarnpkg.com/@nuxt/core/-/core-2.5.1.tgz#2ec9a1ae476189fed73e4cdace02f90934a9d980"
+ integrity sha512-BPGJDRG39utDwCZYbevichsQxsULYjsiOB5DcY80o4JcaDPh49WWfqqrPXgfbK2e2cNlpF6ng/+vGke03kS1Uw==
+ dependencies:
+ "@nuxt/config" "2.5.1"
+ "@nuxt/devalue" "^1.2.2"
+ "@nuxt/server" "2.5.1"
+ "@nuxt/utils" "2.5.1"
+ "@nuxt/vue-renderer" "2.5.1"
+ consola "^2.5.7"
debug "^4.1.1"
- esm "^3.2.3"
+ esm "^3.2.20"
fs-extra "^7.0.1"
hash-sum "^1.0.2"
std-env "^2.2.1"
-"@nuxt/devalue@^1.2.0":
+"@nuxt/devalue@^1.2.2":
version "1.2.2"
resolved "https://registry.yarnpkg.com/@nuxt/devalue/-/devalue-1.2.2.tgz#1d7993f9a6029df07f597a20246b16282302b156"
integrity sha512-T3S20YKOG0bzhvFRuGWqXLjqnwTczvRns5BgzHKRosijWHjl6tOpWCIr+2PFC5YQ3gTE4c5ZOLG5wOEcMLvn1w==
@@ -1197,17 +1203,27 @@
error-stack-parser "^2.0.0"
string-width "^2.0.0"
-"@nuxt/generator@2.4.5":
- version "2.4.5"
- resolved "https://registry.yarnpkg.com/@nuxt/generator/-/generator-2.4.5.tgz#9471d0fe7584597fc7089f1247127ed355a31f15"
- integrity sha512-DUi8BnoGiuBN1jVe3J8QZNR68IvD/xhE6fX3vgcBylaeKTL5kC7h+CBnQ2w30bFQpsdmjWcaitTzdklvrm44Tg==
+"@nuxt/generator@2.5.1":
+ version "2.5.1"
+ resolved "https://registry.yarnpkg.com/@nuxt/generator/-/generator-2.5.1.tgz#61dead16a285c32f77f925546325010acff21280"
+ integrity sha512-ao4qWJHnYmVjKjLYwFnZAaWSRFmMXaPk/poJ5mby+5mErynC+KNuN1MBDmSlv48lmR4XIJkMQ/B0UADrxBbMAA==
dependencies:
- "@nuxt/utils" "2.4.5"
+ "@nuxt/utils" "2.5.1"
chalk "^2.4.2"
- consola "^2.3.2"
+ consola "^2.5.7"
fs-extra "^7.0.1"
html-minifier "^3.5.21"
+"@nuxt/loading-screen@^0.1.2":
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/@nuxt/loading-screen/-/loading-screen-0.1.2.tgz#2438ce21fcc1ae33cbd5168430b763c7ee93626d"
+ integrity sha512-IsSySwZ5nHF34RUdsTEElvx/dLP8uBDTek1xL5vuqgBL+4hsanbBPMhz2B9XSkCLM0wrqKORaZAfY1ZYXmHajA==
+ dependencies:
+ connect "^3.6.6"
+ fs-extra "^7.0.1"
+ serve-static "^1.13.2"
+ ws "^6.2.0"
+
"@nuxt/opencollective@^0.2.1":
version "0.2.1"
resolved "https://registry.yarnpkg.com/@nuxt/opencollective/-/opencollective-0.2.1.tgz#8290f1220072637e575c3935733719a78ad2d056"
@@ -1217,84 +1233,89 @@
consola "^2.3.0"
node-fetch "^2.3.0"
-"@nuxt/server@2.4.5":
- version "2.4.5"
- resolved "https://registry.yarnpkg.com/@nuxt/server/-/server-2.4.5.tgz#c4b921a878423215bfbbb6d02855a3c26a82f946"
- integrity sha512-bJAA53xS5JV80mGjVcZRffU2FA/qL6diLyMAykO9MdTB8OOo6onLssWXH0Rl/89uWfs+z4iVXUpZsv9nMdhL0w==
+"@nuxt/server@2.5.1":
+ version "2.5.1"
+ resolved "https://registry.yarnpkg.com/@nuxt/server/-/server-2.5.1.tgz#e57a2b903164b3fe7478709e79d24270b250bb20"
+ integrity sha512-pTNSkrF86aTvt9+WmQhrQQsddG89sa98vBDV4vTqnCvnfzZm8hHIwD6Aw6zaF3aFR0aUTJTc4K8+0qYhjdW+ug==
dependencies:
- "@nuxt/config" "2.4.5"
- "@nuxt/utils" "2.4.5"
+ "@nuxt/config" "2.5.1"
+ "@nuxt/utils" "2.5.1"
"@nuxtjs/youch" "^4.2.3"
chalk "^2.4.2"
- compression "^1.7.3"
+ compression "^1.7.4"
connect "^3.6.6"
- consola "^2.3.2"
+ consola "^2.5.7"
etag "^1.8.1"
fresh "^0.5.2"
fs-extra "^7.0.1"
ip "^1.1.5"
launch-editor-middleware "^2.2.1"
- on-headers "^1.0.1"
+ on-headers "^1.0.2"
pify "^4.0.1"
semver "^5.6.0"
- serve-placeholder "^1.1.1"
+ serve-placeholder "^1.2.1"
serve-static "^1.13.2"
server-destroy "^1.0.1"
ua-parser-js "^0.7.19"
-"@nuxt/utils@2.4.5":
- version "2.4.5"
- resolved "https://registry.yarnpkg.com/@nuxt/utils/-/utils-2.4.5.tgz#2f1b5ed9ecdfc356e7901cc5337c31e94dbf1a40"
- integrity sha512-/FLBP1KFwBKIaq7ht7YBrhdHG9l1uSg2B3egZdVoVLbK+Uj10uZ+XeaU+IIpC4S+hLc1FY3WTjdCb2GHp91oIw==
+"@nuxt/utils@2.5.1":
+ version "2.5.1"
+ resolved "https://registry.yarnpkg.com/@nuxt/utils/-/utils-2.5.1.tgz#9fff3fcadb7e27b5825498974b86c0ac25d54478"
+ integrity sha512-hrpHh8fF6ir4lDXAGhl5AGF7puJEkAipnvLNGAtdbtMVPEBvSNIyynKf93ECgFpxbUU2+npmx/V1LibaiMRQog==
dependencies:
- consola "^2.3.2"
+ consola "^2.5.7"
+ fs-extra "^7.0.1"
+ hash-sum "^1.0.2"
+ proper-lockfile "^4.1.0"
serialize-javascript "^1.6.1"
+ signal-exit "^3.0.2"
-"@nuxt/vue-app@2.4.5":
- version "2.4.5"
- resolved "https://registry.yarnpkg.com/@nuxt/vue-app/-/vue-app-2.4.5.tgz#c164f6ab269c54dc6f892aedff9ce769f80eb3c2"
- integrity sha512-dtcT7KDrZEAc3imCc+JEeJ4Lqgbf5ZfjKLXjzUCj3tk16OG7wR4H4bKcDLcHv63S+DTHuCaYOtzcHn44p6jTCQ==
+"@nuxt/vue-app@2.5.1":
+ version "2.5.1"
+ resolved "https://registry.yarnpkg.com/@nuxt/vue-app/-/vue-app-2.5.1.tgz#8a683661bed83ddc5f3e1cfdd2a72ba27a508e6d"
+ integrity sha512-gbtYQTNP0n+vZF2ISM5NJvxG09qUN2OEZ1z1qqQqRygRtbz8NSbh1EaB2UT1lUjTpZBAFuwuez3GAqTkZVXZSQ==
dependencies:
- vue "^2.5.22"
+ node-fetch "^2.3.0"
+ unfetch "^4.1.0"
+ vue "^2.6.10"
vue-meta "^1.5.8"
vue-no-ssr "^1.1.1"
vue-router "^3.0.2"
- vue-template-compiler "^2.5.22"
+ vue-template-compiler "^2.6.10"
vuex "^3.1.0"
-"@nuxt/vue-renderer@2.4.5":
- version "2.4.5"
- resolved "https://registry.yarnpkg.com/@nuxt/vue-renderer/-/vue-renderer-2.4.5.tgz#11c6fab292a944de19b7108baed8f92a5cdc37ce"
- integrity sha512-NsS0ZHV/HEWAbzOBXiwbhcdb1KJFj8ucma+gnbfw/rIh5hgufqAxs4btt3U0ma/i3Bm0nQo+doZAWtl/HJX6mQ==
+"@nuxt/vue-renderer@2.5.1":
+ version "2.5.1"
+ resolved "https://registry.yarnpkg.com/@nuxt/vue-renderer/-/vue-renderer-2.5.1.tgz#b3a04992d0666ee322360748f716e4c6708e5e6f"
+ integrity sha512-XQfXMVT1wla+ugwKITqIvQryBcfPxZGvhWcv2uCOu1yQexUrjvr+jDvY3MWagJ/YnErRoIWof0RHHhiE/yfy5g==
dependencies:
- "@nuxt/devalue" "^1.2.0"
- "@nuxt/utils" "2.4.5"
- consola "^2.3.2"
+ "@nuxt/devalue" "^1.2.2"
+ "@nuxt/utils" "2.5.1"
+ consola "^2.5.7"
fs-extra "^7.0.1"
lru-cache "^5.1.1"
- vue "^2.5.22"
+ vue "^2.6.10"
vue-meta "^1.5.8"
- vue-server-renderer "^2.5.22"
+ vue-server-renderer "^2.6.10"
-"@nuxt/webpack@2.4.5":
- version "2.4.5"
- resolved "https://registry.yarnpkg.com/@nuxt/webpack/-/webpack-2.4.5.tgz#c7981deb8a1e2fb499f918b80b966443f982e277"
- integrity sha512-UXC9Yw4PMIBDqGR9eB11G6v7YpahgJq4llz4ybDnWMVxOJR+yAOw5jD+8AGSBDDo/apSJ/LgzJX2TIOtopx+LA==
+"@nuxt/webpack@2.5.1":
+ version "2.5.1"
+ resolved "https://registry.yarnpkg.com/@nuxt/webpack/-/webpack-2.5.1.tgz#6bb51c6a90ac064472f5ec162ca6edbb99e07729"
+ integrity sha512-FjAzphxBQKAtwJLNJhUynD/qOq2LxfuXUr2GRlvaBg8N0GSV33tCeMRirwdBGU7QgKNBIIEA7XXOTWbe0/wc5Q==
dependencies:
- "@babel/core" "^7.2.2"
- "@babel/polyfill" "^7.2.5"
- "@nuxt/babel-preset-app" "2.4.5"
+ "@babel/core" "^7.4.0"
+ "@nuxt/babel-preset-app" "2.5.1"
"@nuxt/friendly-errors-webpack-plugin" "^2.4.0"
- "@nuxt/utils" "2.4.5"
+ "@nuxt/utils" "2.5.1"
babel-loader "^8.0.5"
cache-loader "^2.0.1"
- caniuse-lite "^1.0.30000932"
+ caniuse-lite "^1.0.30000951"
chalk "^2.4.2"
- consola "^2.3.2"
- css-loader "^2.1.0"
- cssnano "^4.1.8"
+ consola "^2.5.7"
+ css-loader "^2.1.1"
+ cssnano "^4.1.10"
eventsource-polyfill "^0.9.6"
- extract-css-chunks-webpack-plugin "^3.3.2"
+ extract-css-chunks-webpack-plugin "^4.0.1"
file-loader "^3.0.1"
fs-extra "^7.0.1"
glob "^7.1.3"
@@ -1308,18 +1329,18 @@
postcss-import "^12.0.1"
postcss-import-resolver "^1.1.0"
postcss-loader "^3.0.0"
- postcss-preset-env "^6.5.0"
+ postcss-preset-env "^6.6.0"
postcss-url "^8.0.0"
std-env "^2.2.1"
style-resources-loader "^1.2.1"
- terser-webpack-plugin "^1.2.2"
+ terser-webpack-plugin "^1.2.3"
thread-loader "^1.2.0"
time-fix-plugin "^2.0.5"
url-loader "^1.1.2"
- vue-loader "^15.6.2"
- webpack "^4.29.2"
- webpack-bundle-analyzer "^3.0.3"
- webpack-dev-middleware "^3.5.1"
+ vue-loader "^15.7.0"
+ webpack "^4.29.6"
+ webpack-bundle-analyzer "^3.1.0"
+ webpack-dev-middleware "^3.6.1"
webpack-hot-middleware "^2.24.3"
webpack-node-externals "^1.7.2"
webpackbar "^3.1.5"
@@ -1451,10 +1472,15 @@
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
-"@types/node@*", "@types/node@^11.9.5":
- version "11.11.3"
- resolved "https://registry.yarnpkg.com/@types/node/-/node-11.11.3.tgz#7c6b0f8eaf16ae530795de2ad1b85d34bf2f5c58"
- integrity sha512-wp6IOGu1lxsfnrD+5mX6qwSwWuqsdkKKxTN4aQc4wByHAKZJf9/D4KXPQ1POUjEbnCP5LMggB0OEFNY9OTsMqg==
+"@types/node@*":
+ version "11.11.4"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-11.11.4.tgz#8808bd5a82bbf6f5d412eff1c228d178e7c24bb3"
+ integrity sha512-02tIL+QIi/RW4E5xILdoAMjeJ9kYq5t5S2vciUdFPXv/ikFTb0zK8q9vXkg4+WAJuYXGiVT1H28AkD2C+IkXVw==
+
+"@types/node@^11.11.6":
+ version "11.11.6"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-11.11.6.tgz#df929d1bb2eee5afdda598a41930fe50b43eaa6a"
+ integrity sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ==
"@types/q@^1.5.1":
version "1.5.2"
@@ -1477,9 +1503,9 @@
integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==
"@types/yargs@^12.0.2", "@types/yargs@^12.0.9":
- version "12.0.9"
- resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.9.tgz#693e76a52f61a2f1e7fb48c0eef167b95ea4ffd0"
- integrity sha512-sCZy4SxP9rN2w30Hlmg5dtdRwgYQfYRiLo9usw8X9cxlf+H4FqM1xX7+sNH7NNKVdbXMJWqva7iyy+fxh/V7fA==
+ version "12.0.10"
+ resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.10.tgz#17a8ec65cd8e88f51b418ceb271af18d3137df67"
+ integrity sha512-WsVzTPshvCSbHThUduGGxbmnwcpkgSctHGHTqzWyFg4lYAuV5qXlyFPOsP3OWqCINfmg/8VXP+zJaa4OxEsBQQ==
"@vue/babel-helper-vue-jsx-merge-props@^1.0.0-beta.2":
version "1.0.0-beta.2"
@@ -2064,7 +2090,7 @@ async-limiter@~1.0.0:
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==
-async@^2.3.0, async@^2.5.0, async@^2.6.1:
+async@^2.3.0, async@^2.6.1:
version "2.6.2"
resolved "https://registry.yarnpkg.com/async/-/async-2.6.2.tgz#18330ea7e6e313887f5d2f2a904bac6fe4dd5381"
integrity sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==
@@ -2250,6 +2276,11 @@ babylon@^6.18.0:
resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3"
integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==
+bail@^1.0.0:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.3.tgz#63cfb9ddbac829b02a3128cd53224be78e6c21a3"
+ integrity sha512-1X8CnjFVQ+a+KW36uBNMTU5s8+v5FzeqrP7hTG5aTb4aPreSbZJlhwPon9VKMuEVgV++JM+SQrALY3kr7eswdg==
+
balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
@@ -2475,14 +2506,14 @@ browserify-zlib@^0.2.0:
dependencies:
pako "~1.0.5"
-browserslist@^4.0.0, browserslist@^4.3.4, browserslist@^4.4.2:
- version "4.4.2"
- resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.4.2.tgz#6ea8a74d6464bb0bd549105f659b41197d8f0ba2"
- integrity sha512-ISS/AIAiHERJ3d45Fz0AVYKkgcy+F/eJHzKEvv1j0wwKGKD9T3BrwKr/5g45L+Y4XIK5PlTqefHciRFcfE1Jxg==
+browserslist@^4.0.0, browserslist@^4.4.2, browserslist@^4.5.1:
+ version "4.5.2"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.5.2.tgz#36ad281f040af684555a23c780f5c2081c752df0"
+ integrity sha512-zmJVLiKLrzko0iszd/V4SsjTaomFeoVzQGYYOYgRgsbh7WNh95RgDB0CmBdFWYs/3MyFSt69NypjL/h3iaddKQ==
dependencies:
- caniuse-lite "^1.0.30000939"
- electron-to-chromium "^1.3.113"
- node-releases "^1.1.8"
+ caniuse-lite "^1.0.30000951"
+ electron-to-chromium "^1.3.116"
+ node-releases "^1.1.11"
bser@^2.0.0:
version "2.0.0"
@@ -2668,10 +2699,10 @@ caniuse-api@^3.0.0:
lodash.memoize "^4.1.2"
lodash.uniq "^4.5.0"
-caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000932, caniuse-lite@^1.0.30000939, caniuse-lite@^1.0.30000947:
- version "1.0.30000948"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000948.tgz#793ed7c28fe664856beb92b43fc013fc22b81633"
- integrity sha512-Lw4y7oz1X5MOMZm+2IFaSISqVVQvUuD+ZUSfeYK/SlYiMjkHN/eJ2PDfJehW5NA6JjrxYSSnIWfwjeObQMEjFQ==
+caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000939, caniuse-lite@^1.0.30000947, caniuse-lite@^1.0.30000951:
+ version "1.0.30000951"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000951.tgz#c7c2fd4d71080284c8677dd410368df8d83688fe"
+ integrity sha512-eRhP+nQ6YUkIcNQ6hnvdhMkdc7n3zadog0KXNRxAZTT2kHjUb1yGn71OrPhSn8MOvlX97g5CR97kGVj8fMsXWg==
capture-exit@^2.0.0:
version "2.0.0"
@@ -2705,6 +2736,21 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.2, chalk@^2.4
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
+character-entities-legacy@^1.0.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.2.tgz#7c6defb81648498222c9855309953d05f4d63a9c"
+ integrity sha512-9NB2VbXtXYWdXzqrvAHykE/f0QJxzaKIpZ5QzNZrrgQ7Iyxr2vnfS8fCBNVW9nUEZE0lo57nxKRqnzY/dKrwlA==
+
+character-entities@^1.0.0:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.2.tgz#58c8f371c0774ef0ba9b2aca5f00d8f100e6e363"
+ integrity sha512-sMoHX6/nBiy3KKfC78dnEalnpn0Az0oSNvqUWYTtYrhRI5iUIYsROU48G+E+kMFQzqXaJ8kHJZ85n7y6/PHgwQ==
+
+character-reference-invalid@^1.0.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.2.tgz#21e421ad3d84055952dab4a43a04e73cd425d3ed"
+ integrity sha512-7I/xceXfKyUJmSAn/jw8ve/9DyOP7XxufNYLI9Px7CmsKgEUaZLUTax6nZxGQtaoiZCjpu6cHPj20xC/vqRReQ==
+
chardet@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
@@ -2731,7 +2777,7 @@ chokidar@^1.7.0:
optionalDependencies:
fsevents "^1.0.0"
-chokidar@^2.0.0, chokidar@^2.0.2, chokidar@^2.0.3, chokidar@^2.0.4:
+chokidar@^2.0.0, chokidar@^2.0.2, chokidar@^2.0.3, chokidar@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.2.tgz#9c23ea40b01638439e0513864d362aeacc5ad058"
integrity sha512-IwXUx0FXc5ibYmPC2XeEj5mpXoV66sR+t3jqu2NS2GYwCktt3KF1/Qqjws/NkegajBA4RbZ5+DDwlOiJsxDHEg==
@@ -2886,10 +2932,15 @@ codecov@^3.2.0:
teeny-request "^3.7.0"
urlgrey "^0.4.4"
-codemirror@^5.44.0:
- version "5.44.0"
- resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.44.0.tgz#80dc2a231eeb7aab25ec2405cdca37e693ccf9cc"
- integrity sha512-3l42syTNakCdCQuYeZJXTyxina6Y9i4V0ighSJXNCQtRbaCN76smKKLu1ZHPHQon3rnzC7l4i/0r4gp809K1wg==
+codemirror@^5.45.0:
+ version "5.45.0"
+ resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.45.0.tgz#db5ebbb3bf44028c684053f3954d011efcec27ad"
+ integrity sha512-c19j644usCE8gQaXa0jqn2B/HN9MnB2u6qPIrrhrMkB+QAP42y8G4QnTwuwbVSoUS1jEl7JU9HZMGhCDL0nsAw==
+
+collapse-white-space@^1.0.2:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.4.tgz#ce05cf49e54c3277ae573036a26851ba430a0091"
+ integrity sha512-YfQ1tAUZm561vpYD+5eyWN8+UsceQbSrqqlc/6zDY2gtAE+uZLSdkkovhnGpmCThsvKBFakq4EdY/FF93E8XIw==
collection-visit@^1.0.0:
version "1.0.0"
@@ -2939,12 +2990,12 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
-commander@2.17.x, commander@~2.17.1:
+commander@2.17.x:
version "2.17.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==
-commander@2.x, commander@^2.18.0, commander@^2.19.0, commander@^2.8.1:
+commander@2.x, commander@^2.18.0, commander@^2.19.0, commander@^2.8.1, commander@~2.19.0:
version "2.19.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a"
integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==
@@ -2972,23 +3023,23 @@ component-emitter@^1.2.1:
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6"
integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=
-compressible@~2.0.14:
+compressible@~2.0.16:
version "2.0.16"
resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.16.tgz#a49bf9858f3821b64ce1be0296afc7380466a77f"
integrity sha512-JQfEOdnI7dASwCuSPWIeVYwc/zMsu/+tRhoUvEfXz2gxOA2DNjmG5vhtFdBlhWPPGo+RdT9S3tgc/uH5qgDiiA==
dependencies:
mime-db ">= 1.38.0 < 2"
-compression@^1.7.3:
- version "1.7.3"
- resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.3.tgz#27e0e176aaf260f7f2c2813c3e440adb9f1993db"
- integrity sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==
+compression@^1.7.4:
+ version "1.7.4"
+ resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f"
+ integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==
dependencies:
accepts "~1.3.5"
bytes "3.0.0"
- compressible "~2.0.14"
+ compressible "~2.0.16"
debug "2.6.9"
- on-headers "~1.0.1"
+ on-headers "~1.0.2"
safe-buffer "5.1.2"
vary "~1.1.2"
@@ -3025,10 +3076,10 @@ connect@^3.6.6:
parseurl "~1.3.2"
utils-merge "1.0.1"
-consola@^2.0.0-1, consola@^2.3.0, consola@^2.3.2, consola@^2.5.6:
- version "2.5.6"
- resolved "https://registry.yarnpkg.com/consola/-/consola-2.5.6.tgz#5ce14dbaf6f5b589c8a258ef80ed97b752fa57d5"
- integrity sha512-DN0j6ewiNWkT09G3ZoyyzN3pSYrjxWcx49+mHu+oDI5dvW5vzmyuzYsqGS79+yQserH9ymJQbGzeqUejfssr8w==
+consola@^2.0.0-1, consola@^2.3.0, consola@^2.5.6, consola@^2.5.7:
+ version "2.5.7"
+ resolved "https://registry.yarnpkg.com/consola/-/consola-2.5.7.tgz#72b313ac9039b181c8adc065c1c092effa417122"
+ integrity sha512-KZteEB71fuSoSDgJoYEo/dIvwofWMU/bI/n+wusLYHPp+c7KcxBGZ0P8CzTCko2Jp0xsrbLjmLuUo4jyIWa6vQ==
console-browserify@^1.1.0:
version "1.1.0"
@@ -3252,7 +3303,27 @@ copy-descriptor@^0.1.0:
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
-core-js@^2.4.0, core-js@^2.5.7:
+core-js-compat@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.0.0.tgz#cd9810b8000742535a4a43773866185e310bd4f7"
+ integrity sha512-W/Ppz34uUme3LmXWjMgFlYyGnbo1hd9JvA0LNQ4EmieqVjg2GPYbj3H6tcdP2QGPGWdRKUqZVbVKLNIFVs/HiA==
+ dependencies:
+ browserslist "^4.5.1"
+ core-js "3.0.0"
+ core-js-pure "3.0.0"
+ semver "^5.6.0"
+
+core-js-pure@3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.0.0.tgz#a5679adb4875427c8c0488afc93e6f5b7125859b"
+ integrity sha512-yPiS3fQd842RZDgo/TAKGgS0f3p2nxssF1H65DIZvZv0Od5CygP8puHXn3IQiM/39VAvgCbdaMQpresrbGgt9g==
+
+core-js@3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.0.0.tgz#a8dbfa978d29bfc263bfb66c556d0ca924c28957"
+ integrity sha512-WBmxlgH2122EzEJ6GH8o9L/FeoUKxxxZ6q6VUxoTlsE4EvbTWKJb447eyVxTEuq0LpXjlq/kCB2qgBvsYRkLvQ==
+
+core-js@^2.4.0, core-js@^2.5.7, core-js@^2.6.5:
version "2.6.5"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.5.tgz#44bc8d249e7fb2ff5d00e0341a7ffb94fbf67895"
integrity sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A==
@@ -3395,7 +3466,7 @@ css-has-pseudo@^0.10.0:
postcss "^7.0.6"
postcss-selector-parser "^5.0.0-rc.4"
-css-loader@^2.1.0:
+css-loader@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-2.1.1.tgz#d8254f72e412bb2238bb44dd674ffbef497333ea"
integrity sha512-OcKJU/lt232vl1P9EEDamhoO9iKY3tIjY5GU+XDLblAykTdgs6Ux9P1hTHve8nFKy5KPpOXOsVI/hIwi3841+w==
@@ -3558,7 +3629,7 @@ cssnano-util-same-parent@^4.0.0:
resolved "https://registry.yarnpkg.com/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz#574082fb2859d2db433855835d9a8456ea18bbf3"
integrity sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==
-cssnano@^4.1.0, cssnano@^4.1.8:
+cssnano@^4.1.0, cssnano@^4.1.10:
version "4.1.10"
resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-4.1.10.tgz#0ac41f0b13d13d465487e111b778d42da631b8b2"
integrity sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ==
@@ -3951,10 +4022,10 @@ ejs@^2.6.1:
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0"
integrity sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==
-electron-to-chromium@^1.3.113:
- version "1.3.116"
- resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.116.tgz#1dbfee6a592a0c14ade77dbdfe54fef86387d702"
- integrity sha512-NKwKAXzur5vFCZYBHpdWjTMO8QptNLNP80nItkSIgUOapPAo9Uia+RvkCaZJtO7fhQaVElSvBPWEc2ku6cKsPA==
+electron-to-chromium@^1.3.116:
+ version "1.3.118"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.118.tgz#5c82b0445a40934e6cae9c2f40bfaaa986ea44a3"
+ integrity sha512-/1FpHvmKmKo2Z6CCza2HfkrKvKhU7Rq4nvyX1FOherdTrdTufhVrJbCrcrIqgqUCI+BG6JC2rlY4z5QA1G0NOw==
elliptic@^6.0.0:
version "6.4.1"
@@ -4166,6 +4237,15 @@ eslint-plugin-jest@^22.4.1:
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.4.1.tgz#a5fd6f7a2a41388d16f527073b778013c5189a9c"
integrity sha512-gcLfn6P2PrFAVx3AobaOzlIEevpAEf9chTpFZz7bYfc7pz8XRv7vuKTIE4hxPKZSha6XWKKplDQ0x9Pq8xX2mg==
+eslint-plugin-markdown@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-markdown/-/eslint-plugin-markdown-1.0.0.tgz#2d381b44fcf367f1bb53ae166eccf111cd4e1174"
+ integrity sha512-YIrClt3yLgyGov+rInjIoC/05zMxb/c6YXQZkyI9UKuBRFLgCrL37cxthj0JYWiTYtiHq0p8O0Nt0/HrvO48iQ==
+ dependencies:
+ object-assign "^4.0.1"
+ remark-parse "^5.0.0"
+ unified "^6.1.2"
+
eslint-plugin-node@^8.0.1:
version "8.0.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-8.0.1.tgz#55ae3560022863d141fa7a11799532340a685964"
@@ -4210,15 +4290,7 @@ eslint-scope@3.7.1:
esrecurse "^4.1.0"
estraverse "^4.1.1"
-eslint-scope@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.0.tgz#50bf3071e9338bcdc43331794a0cb533f0136172"
- integrity sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==
- dependencies:
- esrecurse "^4.1.0"
- estraverse "^4.1.1"
-
-eslint-scope@^4.0.3:
+eslint-scope@^4.0.0, eslint-scope@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848"
integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==
@@ -4236,10 +4308,10 @@ eslint-visitor-keys@^1.0.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d"
integrity sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==
-eslint@^5.15.2:
- version "5.15.2"
- resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.15.2.tgz#0237bbb2362f89f4effef2f191eb0fea5279c0a5"
- integrity sha512-I8VM4SILpMwUvsRt83bQVwIRQAJ2iPMXun1FVZ/lV1OHklH2tJaXqoDnNzdiFc6bnCtGKXvQIQNP3kj1eMskSw==
+eslint@^5.15.3:
+ version "5.15.3"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.15.3.tgz#c79c3909dc8a7fa3714fb340c11e30fd2526b8b5"
+ integrity sha512-vMGi0PjCHSokZxE0NLp2VneGw5sio7SSiDNgIUn2tC0XkWJRNOIoHIg3CliLVfXnJsiHxGAYrkw0PieAu8+KYQ==
dependencies:
"@babel/code-frame" "^7.0.0"
ajv "^6.9.1"
@@ -4278,10 +4350,10 @@ eslint@^5.15.2:
table "^5.2.3"
text-table "^0.2.0"
-esm@^3.2.18, esm@^3.2.3:
- version "3.2.18"
- resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.18.tgz#54e9276449ab832b01240069ae66bf245785c767"
- integrity sha512-1UENjnnI37UDp7KuOqKYjfqdaMim06eBWnDv37smaxTIzDl0ZWnlgoXwsVwD9+Lidw+q/f1gUf2diVMDCycoVw==
+esm@^3.2.20:
+ version "3.2.20"
+ resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.20.tgz#44f125117863427cdece7223baa411fc739c1939"
+ integrity sha512-NA92qDA8C/qGX/xMinDGa3+cSPs4wQoFxskRrSnDo/9UloifhONFm4sl4G+JsyCqM007z2K+BfQlH5rMta4K1Q==
espree@^4.1.0:
version "4.1.0"
@@ -4330,11 +4402,6 @@ estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0:
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=
-estree-walker@^0.5.2:
- version "0.5.2"
- resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.5.2.tgz#d3850be7529c9580d815600b53126515e146dd39"
- integrity sha512-XpCnW/AE10ws/kDAs37cngSkvgIR8aN3G0MS85m7dUpuK2EREo9VJ00uvw6Dg/hXEpfsE1I1TvJOJr+Z+TL+ig==
-
estree-walker@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.0.tgz#5d865327c44a618dde5699f763891ae31f257dae"
@@ -4499,7 +4566,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2:
assign-symbols "^1.0.0"
is-extendable "^1.0.1"
-extend@~3.0.2:
+extend@^3.0.0, extend@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
@@ -4534,14 +4601,14 @@ extglob@^2.0.4:
snapdragon "^0.8.1"
to-regex "^3.0.1"
-extract-css-chunks-webpack-plugin@^3.3.2:
- version "3.3.3"
- resolved "https://registry.yarnpkg.com/extract-css-chunks-webpack-plugin/-/extract-css-chunks-webpack-plugin-3.3.3.tgz#d550be32b93dad5d290e9d979d37dd317bdaec9b"
- integrity sha512-4DYo3jna9ov81rdKtE1U2cirb3ERoWhHldzRxZWx3Q5i5Dm6U+mmfon7PmaKDuh6+xySVOqtlXrZyJY2V4tc+g==
+extract-css-chunks-webpack-plugin@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/extract-css-chunks-webpack-plugin/-/extract-css-chunks-webpack-plugin-4.0.1.tgz#8a25ecf89e4a31d74351e487e616ecc79e873052"
+ integrity sha512-axin0Qz65T4ZQl8FEGW1ZN31kwUJOO7CoiWv0aTupYjmmQdtr56qsW061MuLglzTNwDKTcSe7KbtuzMZ5uc3Cw==
dependencies:
loader-utils "^1.1.0"
lodash "^4.17.11"
- normalize-url "^3.3.0"
+ normalize-url "^3.0.0"
schema-utils "^1.0.0"
webpack-sources "^1.1.0"
@@ -5162,11 +5229,11 @@ gzip-size@^5.0.0:
pify "^3.0.0"
handlebars@^4.1.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.0.tgz#0d6a6f34ff1f63cecec8423aa4169827bf787c3a"
- integrity sha512-l2jRuU1NAWK6AW5qqcTATWQJvNPEwkM7NEKSiv/gqOsoSQbVoWyqVEY5GS+XPQ88zLNmqASRpzfdm8d79hJS+w==
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.1.tgz#6e4e41c18ebe7719ae4d38e5aca3d32fa3dd23d3"
+ integrity sha512-3Zhi6C0euYZL5sM0Zcy7lInLXKQ+YLcF/olbN010mzGQ4XVm50JeyBnMqofHh696GrciGruC7kCcApPDJvVgwA==
dependencies:
- async "^2.5.0"
+ neo-async "^2.6.0"
optimist "^0.6.1"
source-map "^0.6.1"
optionalDependencies:
@@ -5488,10 +5555,10 @@ ignore@^4.0.3, ignore@^4.0.6:
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
-ignore@^5.0.2:
- version "5.0.5"
- resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.0.5.tgz#c663c548d6ce186fb33616a8ccb5d46e56bdbbf9"
- integrity sha512-kOC8IUb8HSDMVcYrDVezCxpJkzSQWTAzf3olpKM6o9rM5zpojx23O0Fl8Wr4+qJ6ZbPEHqf1fdwev/DS7v7pmA==
+ignore@^5.0.2, ignore@^5.0.6:
+ version "5.0.6"
+ resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.0.6.tgz#562dacc7ec27d672dde433aa683c543b24c17694"
+ integrity sha512-/+hp3kUf/Csa32ktIaj0OlRqQxrgs30n62M90UBpNd9k+ENEch5S+hmbW3DtcJGz3sYFTh4F3A6fQ0q7KWsp4w==
import-cwd@^2.0.0:
version "2.1.0"
@@ -5651,6 +5718,19 @@ is-accessor-descriptor@^1.0.0:
dependencies:
kind-of "^6.0.0"
+is-alphabetical@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.2.tgz#1fa6e49213cb7885b75d15862fb3f3d96c884f41"
+ integrity sha512-V0xN4BYezDHcBSKb1QHUFMlR4as/XEuCZBzMJUU4n7+Cbt33SmUnSol+pnXFvLxSHNq2CemUXNdaXV6Flg7+xg==
+
+is-alphanumerical@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.2.tgz#1138e9ae5040158dc6ff76b820acd6b7a181fd40"
+ integrity sha512-pyfU/0kHdISIgslFfZN9nfY1Gk3MquQgUm1mJTjdkEPpkAKNWuBTSqFwewOpR7N351VkErCiyV71zX7mlQQqsg==
+ dependencies:
+ is-alphabetical "^1.0.0"
+ is-decimal "^1.0.0"
+
is-arrayish@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
@@ -5668,7 +5748,7 @@ is-binary-path@^1.0.0:
dependencies:
binary-extensions "^1.0.0"
-is-buffer@^1.1.5:
+is-buffer@^1.1.4, is-buffer@^1.1.5:
version "1.1.6"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
@@ -5716,6 +5796,11 @@ is-date-object@^1.0.1:
resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16"
integrity sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=
+is-decimal@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.2.tgz#894662d6a8709d307f3a276ca4339c8fa5dff0ff"
+ integrity sha512-TRzl7mOCchnhchN+f3ICUCzYvL9ul7R+TYOsZ8xia++knyZAJfv/uA1FvQXsAnYIl1T3B2X5E/J7Wb1QXiIBXg==
+
is-descriptor@^0.1.0:
version "0.1.6"
resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca"
@@ -5823,6 +5908,11 @@ is-glob@^4.0.0:
dependencies:
is-extglob "^2.1.1"
+is-hexadecimal@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.2.tgz#b6e710d7d07bb66b98cb8cece5c9b4921deeb835"
+ integrity sha512-but/G3sapV3MNyqiDBLrOi4x8uCIw0RY3o/Vb5GT0sMFHrVV7731wFSVy41T5FO1og7G0gXLJh0MkgPRouko/A==
+
is-module@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591"
@@ -5932,11 +6022,21 @@ is-utf8@^0.2.0:
resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=
+is-whitespace-character@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.2.tgz#ede53b4c6f6fb3874533751ec9280d01928d03ed"
+ integrity sha512-SzM+T5GKUCtLhlHFKt2SDAX2RFzfS6joT91F2/WSi9LxgFdsnhfPK/UIA+JhRR2xuyLdrCys2PiFDrtn1fU5hQ==
+
is-windows@^1.0.0, is-windows@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
+is-word-character@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/is-word-character/-/is-word-character-1.0.2.tgz#46a5dac3f2a1840898b91e576cd40d493f3ae553"
+ integrity sha512-T3FlsX8rCHAH8e7RE7PfOPZVFQlcV3XRF9eOOBQ1uf70OxO7CjjSOjeImMPCADBdYWcStAbVbYvJ1m2D3tb+EA==
+
is-wsl@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
@@ -6438,9 +6538,9 @@ js-tokens@^3.0.2:
integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls=
js-yaml@^3.12.0, js-yaml@^3.9.0:
- version "3.12.2"
- resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.2.tgz#ef1d067c5a9d9cb65bd72f285b5d8105c77f14fc"
- integrity sha512-QHn/Lh/7HhZ/Twc7vJYQTkjuCa0kaCcDcjK5Zlk2rvnUpy7DxMJ23+Jc2dcyvltwQVg1nygAVlB2oRDFHoRS5Q==
+ version "3.13.0"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.0.tgz#38ee7178ac0eea2c97ff6d96fff4b18c7d8cf98e"
+ integrity sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ==
dependencies:
argparse "^1.0.7"
esprima "^4.0.0"
@@ -6854,7 +6954,7 @@ lru-cache@^5.1.1:
dependencies:
yallist "^3.0.2"
-magic-string@^0.25.1:
+magic-string@^0.25.2:
version "0.25.2"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.2.tgz#139c3a729515ec55e96e69e82a11fe890a293ad9"
integrity sha512-iLs9mPjh9IuTtRsqqhNGYcZXGei0Nh/A4xirrsqW7c+QhKVFL2vm7U09ru6cHRD22azaP/wMDgI+HCqbETMTtg==
@@ -6917,6 +7017,11 @@ map-visit@^1.0.0:
dependencies:
object-visit "^1.0.0"
+markdown-escapes@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.2.tgz#e639cbde7b99c841c0bacc8a07982873b46d2122"
+ integrity sha512-lbRZ2mE3Q9RtLjxZBZ9+IMl68DKIXaVAhwvwn9pmjnPLS0h/6kyBMgNhqi1xFJ/2yv6cSyv0jbiZavZv93JkkA==
+
marked@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/marked/-/marked-0.6.1.tgz#a63addde477bca9613028de4b2bc3629e53a0562"
@@ -7404,10 +7509,10 @@ node-pre-gyp@^0.10.0:
semver "^5.3.0"
tar "^4"
-node-releases@^1.1.8:
- version "1.1.10"
- resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.10.tgz#5dbeb6bc7f4e9c85b899e2e7adcc0635c9b2adf7"
- integrity sha512-KbUPCpfoBvb3oBkej9+nrU0/7xPlVhmhhUJ1PZqwIP5/1dJkRWKWD3OONjo6M2J7tSCBtDCumLwwqeI+DWWaLQ==
+node-releases@^1.1.11:
+ version "1.1.11"
+ resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.11.tgz#9a0841a4b0d92b7d5141ed179e764f42ad22724a"
+ integrity sha512-8v1j5KfP+s5WOTa1spNUAOfreajQPN12JXbRR0oDE+YrJBQCXBnNqUDj27EKpPLOoSiU3tKi3xGPB+JaOdUEQQ==
dependencies:
semver "^5.3.0"
@@ -7488,7 +7593,7 @@ normalize-url@^1.0.0:
query-string "^4.1.0"
sort-keys "^1.0.0"
-normalize-url@^3.0.0, normalize-url@^3.3.0:
+normalize-url@^3.0.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559"
integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==
@@ -7545,17 +7650,18 @@ number-is-nan@^1.0.0:
resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=
-nuxt@^2.4.5:
- version "2.4.5"
- resolved "https://registry.yarnpkg.com/nuxt/-/nuxt-2.4.5.tgz#3f3256c47e78038ef8081e522181aa2578bddcea"
- integrity sha512-y2p0q58C8yyNr8zg9wEx5ZNhAYe0sbMXHeproGiCKXc2GW7TR6KtZ9/9IBeVlz7HwvoZW+VXIt2m/oecI9IbqQ==
- dependencies:
- "@nuxt/builder" "2.4.5"
- "@nuxt/cli" "2.4.5"
- "@nuxt/core" "2.4.5"
- "@nuxt/generator" "2.4.5"
+nuxt@^2.5.1:
+ version "2.5.1"
+ resolved "https://registry.yarnpkg.com/nuxt/-/nuxt-2.5.1.tgz#57e03386004f62e499d5cdc18fd380d129cb017f"
+ integrity sha512-tmj8czAaFyyb5eDuOlNHqR5vqoru1BKadinrwmO543G1hJS22oeB5LpWXuLbVrXIWWQhMWfwjznDGok0wM1nwQ==
+ dependencies:
+ "@nuxt/builder" "2.5.1"
+ "@nuxt/cli" "2.5.1"
+ "@nuxt/core" "2.5.1"
+ "@nuxt/generator" "2.5.1"
+ "@nuxt/loading-screen" "^0.1.2"
"@nuxt/opencollective" "^0.2.1"
- "@nuxt/webpack" "2.4.5"
+ "@nuxt/webpack" "2.5.1"
nwsapi@^2.0.7:
version "2.1.1"
@@ -7638,7 +7744,7 @@ on-finished@~2.3.0:
dependencies:
ee-first "1.1.1"
-on-headers@^1.0.1, on-headers@~1.0.1:
+on-headers@^1.0.2, on-headers@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f"
integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
@@ -7657,7 +7763,7 @@ onetime@^2.0.0:
dependencies:
mimic-fn "^1.0.0"
-opener@^1.5.1:
+opener@1.5.1, opener@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed"
integrity sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==
@@ -7799,9 +7905,9 @@ p-try@^1.0.0:
integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=
p-try@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.0.0.tgz#85080bb87c64688fa47996fe8f7dfbe8211760b1"
- integrity sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.1.0.tgz#c1a0f1030e97de018bb2c718929d2af59463e505"
+ integrity sha512-H2RyIJ7+A3rjkwKC2l5GGtU4H1vkxKCAGsWasNVd0Set+6i4znxbWy6/j16YDPJDWxhsgZiKAstMEP8wCdSpjA==
pako@^1.0.5, pako@~1.0.5:
version "1.0.10"
@@ -7861,6 +7967,18 @@ parse-bmfont-xml@^1.1.4:
xml-parse-from-string "^1.0.0"
xml2js "^0.4.5"
+parse-entities@^1.1.0:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-1.2.1.tgz#2c761ced065ba7dc68148580b5a225e4918cdd69"
+ integrity sha512-NBWYLQm1KSoDKk7GAHyioLTvCZ5QjdH/ASBBQTD3iLiAWJXS5bg1jEWI8nIJ+vgVvsceBVBcDGRWSo0KVQBvvg==
+ dependencies:
+ character-entities "^1.0.0"
+ character-entities-legacy "^1.0.0"
+ character-reference-invalid "^1.0.0"
+ is-alphanumerical "^1.0.0"
+ is-decimal "^1.0.0"
+ is-hexadecimal "^1.0.0"
+
parse-github-repo-url@^1.3.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/parse-github-repo-url/-/parse-github-repo-url-1.4.1.tgz#9e7d8bb252a6cb6ba42595060b7bf6df3dbc1f50"
@@ -8567,7 +8685,7 @@ postcss-place@^4.0.1:
postcss "^7.0.2"
postcss-values-parser "^2.0.0"
-postcss-preset-env@^6.5.0:
+postcss-preset-env@^6.6.0:
version "6.6.0"
resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-6.6.0.tgz#642e7d962e2bdc2e355db117c1eb63952690ed5b"
integrity sha512-I3zAiycfqXpPIFD6HXhLfWXIewAWO8emOKz+QSsxaUZb9Dp8HbF5kUf+4Wy/AxR33o+LRoO8blEWCHth0ZsCLA==
@@ -8847,13 +8965,22 @@ promise-inflight@^1.0.1:
integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM=
prompts@^2.0.1:
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.0.3.tgz#c5ccb324010b2e8f74752aadceeb57134c1d2522"
- integrity sha512-H8oWEoRZpybm6NV4to9/1limhttEo13xK62pNvn2JzY0MA03p7s0OjtmhXyon3uJmxiJJVSuUwEJFFssI3eBiQ==
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.0.4.tgz#179f9d4db3128b9933aa35f93a800d8fce76a682"
+ integrity sha512-HTzM3UWp/99A0gk51gAegwo1QRYA7xjcZufMNe33rCclFszUYAuHe1fIN/3ZmiHeGPkUsNaRyQm1hHOfM0PKxA==
dependencies:
kleur "^3.0.2"
sisteransi "^1.0.0"
+proper-lockfile@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.0.tgz#e5e21d68ea6938cbfb04171ac7f04dd0cba6fe92"
+ integrity sha512-5FGLP4Dehcwd1bOPyQhWKUosdIbL9r7F6uvBYhlsJAsGSwFk4nGtrS1Poqj6cKU2XXgqkqfDw2h0JdNjd8IgIQ==
+ dependencies:
+ graceful-fs "^4.1.11"
+ retry "^0.12.0"
+ signal-exit "^3.0.2"
+
proto-list@~1.2.1:
version "1.2.4"
resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
@@ -9168,10 +9295,10 @@ regenerator-runtime@^0.11.0:
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
-regenerator-runtime@^0.12.0:
- version "0.12.1"
- resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de"
- integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==
+regenerator-runtime@^0.13.2:
+ version "0.13.2"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz#32e59c9a6fb9b1a4aff09b4930ca2d4477343447"
+ integrity sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==
regenerator-transform@^0.13.4:
version "0.13.4"
@@ -9205,7 +9332,7 @@ regexpp@^2.0.1:
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f"
integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==
-regexpu-core@^4.1.3, regexpu-core@^4.2.0:
+regexpu-core@^4.1.3, regexpu-core@^4.5.4:
version "4.5.4"
resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.5.4.tgz#080d9d02289aa87fe1667a4f5136bc98a6aebaae"
integrity sha512-BtizvGtFQKGPUcTy56o3nk1bGRp4SZOTYrDtGNlqCQufptV5IkkLN6Emw+yunAJjzf+C9FQFtvq7IoA3+oMYHQ==
@@ -9234,6 +9361,27 @@ relateurl@0.2.x:
resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=
+remark-parse@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-5.0.0.tgz#4c077f9e499044d1d5c13f80d7a98cf7b9285d95"
+ integrity sha512-b3iXszZLH1TLoyUzrATcTQUZrwNl1rE70rVdSruJFlDaJ9z5aMkhrG43Pp68OgfHndL/ADz6V69Zow8cTQu+JA==
+ dependencies:
+ collapse-white-space "^1.0.2"
+ is-alphabetical "^1.0.0"
+ is-decimal "^1.0.0"
+ is-whitespace-character "^1.0.0"
+ is-word-character "^1.0.0"
+ markdown-escapes "^1.0.0"
+ parse-entities "^1.1.0"
+ repeat-string "^1.5.4"
+ state-toggle "^1.0.0"
+ trim "0.0.1"
+ trim-trailing-lines "^1.0.0"
+ unherit "^1.0.4"
+ unist-util-remove-position "^1.0.0"
+ vfile-location "^2.0.0"
+ xtend "^4.0.1"
+
remove-trailing-separator@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
@@ -9255,7 +9403,7 @@ repeat-element@^1.1.2:
resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce"
integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==
-repeat-string@^1.5.2, repeat-string@^1.6.1:
+repeat-string@^1.5.2, repeat-string@^1.5.4, repeat-string@^1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
@@ -9267,6 +9415,11 @@ repeating@^2.0.0:
dependencies:
is-finite "^1.0.0"
+replace-ext@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb"
+ integrity sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=
+
request-promise-core@1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.2.tgz#339f6aababcafdb31c799ff158700336301d3346"
@@ -9376,6 +9529,11 @@ ret@~0.1.10:
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
+retry@^0.12.0:
+ version "0.12.0"
+ resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
+ integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=
+
rgb-regex@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1"
@@ -9409,15 +9567,15 @@ rollup-plugin-babel@^4.3.2:
"@babel/helper-module-imports" "^7.0.0"
rollup-pluginutils "^2.3.0"
-rollup-plugin-commonjs@^9.2.1:
- version "9.2.1"
- resolved "https://registry.yarnpkg.com/rollup-plugin-commonjs/-/rollup-plugin-commonjs-9.2.1.tgz#bb151ca8fa23600c7a03e25f9f0a45b1ee922dac"
- integrity sha512-X0A/Cp/t+zbONFinBhiTZrfuUaVwRIp4xsbKq/2ohA2CDULa/7ONSJTelqxon+Vds2R2t2qJTqJQucKUC8GKkw==
+rollup-plugin-commonjs@^9.2.2:
+ version "9.2.2"
+ resolved "https://registry.yarnpkg.com/rollup-plugin-commonjs/-/rollup-plugin-commonjs-9.2.2.tgz#4959f3ff0d9706c132e5247b47ab385f11d9aae6"
+ integrity sha512-FXBgY+IvZIV2AZVT/0CPMsP+b1dKkxE+F6SHI9wddqKDV9KCGDA2cV5e/VsJLwXKFgrtliqMr7Rq3QBfPiJ8Xg==
dependencies:
- estree-walker "^0.5.2"
- magic-string "^0.25.1"
+ estree-walker "^0.6.0"
+ magic-string "^0.25.2"
resolve "^1.10.0"
- rollup-pluginutils "^2.3.3"
+ rollup-pluginutils "^2.5.0"
rollup-plugin-node-resolve@^4.0.1:
version "4.0.1"
@@ -9428,10 +9586,10 @@ rollup-plugin-node-resolve@^4.0.1:
is-module "^1.0.0"
resolve "^1.10.0"
-rollup-pluginutils@^2.0.1, rollup-pluginutils@^2.3.0, rollup-pluginutils@^2.3.3:
- version "2.4.1"
- resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.4.1.tgz#de43ab54965bbf47843599a7f3adceb723de38db"
- integrity sha512-wesMQ9/172IJDIW/lYWm0vW0LiKe5Ekjws481R7z9WTRtmO59cqyM/2uUlxvf6yzm/fElFmHUobeQOYz46dZJw==
+rollup-pluginutils@^2.0.1, rollup-pluginutils@^2.3.0, rollup-pluginutils@^2.5.0:
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.5.0.tgz#23be0f05ac3972ea7b08fc7870cb91fde5b23a09"
+ integrity sha512-9Muh1H+XB5f5ONmKMayUoTYR1EZwHbwJJ9oZLrKT5yuTf/RLIQ5mYIGsrERquVucJmjmaAW0Y7+6Qo1Ep+5w3Q==
dependencies:
estree-walker "^0.6.0"
micromatch "^3.1.10"
@@ -9445,13 +9603,13 @@ rollup-watch@^4.3.1:
require-relative "0.8.7"
rollup-pluginutils "^2.0.1"
-rollup@^1.6.0:
- version "1.6.0"
- resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.6.0.tgz#4329f4634718197c678d18491724d50d8b7ee76c"
- integrity sha512-qu9iWyuiOxAuBM8cAwLuqPclYdarIpayrkfQB7aTGTiyYPbvx+qVF33sIznfq4bxZCiytQux/FvZieUBAXivCw==
+rollup@^1.7.3:
+ version "1.7.3"
+ resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.7.3.tgz#cade518b92e23efa72026e264e29d9a56cbf8eb9"
+ integrity sha512-U3/HaZujvGofNZQldfIknKoaNFNRS+j8/uCS/jSy3FrxF9t0FBsgZW4+VXLHG7l1daTgE6+jEy0Dv7cVCB2NPg==
dependencies:
"@types/estree" "0.0.39"
- "@types/node" "^11.9.5"
+ "@types/node" "^11.11.6"
acorn "^6.1.1"
rsvp@^4.8.4:
@@ -9590,7 +9748,7 @@ serialize-javascript@^1.3.0, serialize-javascript@^1.4.0, serialize-javascript@^
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.6.1.tgz#4d1f697ec49429a847ca6f442a2a755126c4d879"
integrity sha512-A5MOagrPFga4YaKQSWHryl7AXvbQkEqpw4NNYMTNYUNV51bA8ABHgYFpqKx+YFFrw59xMV1qGH1R4AgoNIVgCw==
-serve-placeholder@^1.1.1:
+serve-placeholder@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/serve-placeholder/-/serve-placeholder-1.2.1.tgz#3659fca99b0f15fb3bdf0a72917a6d1848786e9c"
integrity sha512-qyVsP+xA/Sh4cWB/QJzz0tTD52AWIXqxAs/ceEu4HwDnAWXWIYuhwesr1/KPD1GWdE9y7xN8eUI9nW8hfpUniA==
@@ -9939,6 +10097,11 @@ standard-version@^5.0.2:
stringify-package "^1.0.0"
yargs "^12.0.2"
+state-toggle@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.1.tgz#c3cb0974f40a6a0f8e905b96789eb41afa1cde3a"
+ integrity sha512-Qe8QntFrrpWTnHwvwj2FZTgv+PKIsp0B9VxLzLLbSpPXWOgRgc5LVj/aTiSfK1RqIeF9jeC1UeOH8Q8y60A7og==
+
static-extend@^0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
@@ -10095,9 +10258,9 @@ strip-ansi@^4.0.0:
ansi-regex "^3.0.0"
strip-ansi@^5.0.0, strip-ansi@^5.1.0:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.1.0.tgz#55aaa54e33b4c0649a7338a43437b1887d153ec4"
- integrity sha512-TjxrkPONqO2Z8QDCpeE2j6n0M6EwxzyDgzEeGp+FbdvaJAt//ClYi6W5my+3ROlC/hZX2KACUwDfK49Ka5eDvg==
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
+ integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
dependencies:
ansi-regex "^4.1.0"
@@ -10274,7 +10437,7 @@ term-size@^1.2.0:
dependencies:
execa "^0.7.0"
-terser-webpack-plugin@^1.1.0, terser-webpack-plugin@^1.2.2:
+terser-webpack-plugin@^1.1.0, terser-webpack-plugin@^1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.2.3.tgz#3f98bc902fac3e5d0de730869f50668561262ec8"
integrity sha512-GOK7q85oAb/5kE12fMuLdn2btOS9OBZn4VsecpHDywoUC/jLhSAKOiYo0ezx7ss2EXPMzyEWFoE0s1WLE+4+oA==
@@ -10478,6 +10641,21 @@ trim-right@^1.0.1:
resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=
+trim-trailing-lines@^1.0.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.1.tgz#e0ec0810fd3c3f1730516b45f49083caaf2774d9"
+ integrity sha512-bWLv9BbWbbd7mlqqs2oQYnLD/U/ZqeJeJwbO0FG2zA1aTq+HTvxfHNKFa/HGCVyJpDiioUYaBhfiT6rgk+l4mg==
+
+trim@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd"
+ integrity sha1-WFhUf2spB1fulczMZm+1AITEYN0=
+
+trough@^1.0.0:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.3.tgz#e29bd1614c6458d44869fc28b255ab7857ef7c24"
+ integrity sha512-fwkLWH+DimvA4YCy+/nvJd61nWQQ2liO/nF/RjkTpiOGi+zxZzVkhb1mvbHIIW4b/8nDsYI8uTmAlc0nNkRMOw==
+
"true-case-path@^1.0.2":
version "1.0.3"
resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-1.0.3.tgz#f813b5a8c86b40da59606722b144e3225799f47d"
@@ -10547,14 +10725,35 @@ ua-parser-js@^0.7.19:
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.19.tgz#94151be4c0a7fb1d001af7022fdaca4642659e4b"
integrity sha512-T3PVJ6uz8i0HzPxOF9SWzWAlfN/DavlpQqepn22xgve/5QecC+XMCAtmUNnY7C9StehaV6exjUCI801lOI7QlQ==
-uglify-js@3.4.x, uglify-js@^3.1.4:
- version "3.4.9"
- resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3"
- integrity sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==
+uglify-js@3.4.x:
+ version "3.4.10"
+ resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.10.tgz#9ad9563d8eb3acdfb8d38597d2af1d815f6a755f"
+ integrity sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==
dependencies:
- commander "~2.17.1"
+ commander "~2.19.0"
source-map "~0.6.1"
+uglify-js@^3.1.4:
+ version "3.5.1"
+ resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.5.1.tgz#29cb91e76c9941899bc74b075ad0e6da9250abd5"
+ integrity sha512-kI+3c+KphOAKIikQsZoT2oDsVYH5qvhpTtFObfMCdhPAYnjSvmW4oTWMhvDD4jtAGHJwztlBXQgozGcq3Xw9oQ==
+ dependencies:
+ commander "~2.19.0"
+ source-map "~0.6.1"
+
+unfetch@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.1.0.tgz#6ec2dd0de887e58a4dee83a050ded80ffc4137db"
+ integrity sha512-crP/n3eAPUJxZXM9T80/yv0YhkTEx2K1D3h7D1AJM6fzsWZrxdyRuLN0JH/dkZh1LNH8LxCnBzoPFCPbb2iGpg==
+
+unherit@^1.0.4:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.1.tgz#132748da3e88eab767e08fabfbb89c5e9d28628c"
+ integrity sha512-+XZuV691Cn4zHsK0vkKYwBEwB74T3IZIcxrgn2E4rKwTfFyI1zCh7X7grwh9Re08fdPlarIdyWgI8aVB3F5A5g==
+ dependencies:
+ inherits "^2.0.1"
+ xtend "^4.0.1"
+
unicode-canonical-property-names-ecmascript@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"
@@ -10578,6 +10777,18 @@ unicode-property-aliases-ecmascript@^1.0.4:
resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz#a9cc6cc7ce63a0a3023fc99e341b94431d405a57"
integrity sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==
+unified@^6.1.2:
+ version "6.2.0"
+ resolved "https://registry.yarnpkg.com/unified/-/unified-6.2.0.tgz#7fbd630f719126d67d40c644b7e3f617035f6dba"
+ integrity sha512-1k+KPhlVtqmG99RaTbAv/usu85fcSRu3wY8X+vnsEhIxNP5VbVIDiXnLqyKIG+UMdyTg0ZX9EI6k2AfjJkHPtA==
+ dependencies:
+ bail "^1.0.0"
+ extend "^3.0.0"
+ is-plain-obj "^1.1.0"
+ trough "^1.0.0"
+ vfile "^2.0.0"
+ x-is-string "^0.1.0"
+
union-value@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4"
@@ -10612,6 +10823,37 @@ unique-slug@^2.0.0:
dependencies:
imurmurhash "^0.1.4"
+unist-util-is@^2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-2.1.2.tgz#1193fa8f2bfbbb82150633f3a8d2eb9a1c1d55db"
+ integrity sha512-YkXBK/H9raAmG7KXck+UUpnKiNmUdB+aBGrknfQ4EreE1banuzrKABx3jP6Z5Z3fMSPMQQmeXBlKpCbMwBkxVw==
+
+unist-util-remove-position@^1.0.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-1.1.2.tgz#86b5dad104d0bbfbeb1db5f5c92f3570575c12cb"
+ integrity sha512-XxoNOBvq1WXRKXxgnSYbtCF76TJrRoe5++pD4cCBsssSiWSnPEktyFrFLE8LTk3JW5mt9hB0Sk5zn4x/JeWY7Q==
+ dependencies:
+ unist-util-visit "^1.1.0"
+
+unist-util-stringify-position@^1.0.0, unist-util-stringify-position@^1.1.1:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz#3f37fcf351279dcbca7480ab5889bb8a832ee1c6"
+ integrity sha512-pNCVrk64LZv1kElr0N1wPiHEUoXNVFERp+mlTg/s9R5Lwg87f9bM/3sQB99w+N9D/qnM9ar3+AKDBwo/gm/iQQ==
+
+unist-util-visit-parents@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-2.0.1.tgz#63fffc8929027bee04bfef7d2cce474f71cb6217"
+ integrity sha512-6B0UTiMfdWql4cQ03gDTCSns+64Zkfo2OCbK31Ov0uMizEz+CJeAp0cgZVb5Fhmcd7Bct2iRNywejT0orpbqUA==
+ dependencies:
+ unist-util-is "^2.1.2"
+
+unist-util-visit@^1.1.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-1.4.0.tgz#1cb763647186dc26f5e1df5db6bd1e48b3cc2fb1"
+ integrity sha512-FiGu34ziNsZA3ZUteZxSFaczIjGmksfSgdKqBfOejrrfzyUy5b7YrlzT1Bcvi+djkYDituJDy2XB7tGTeBieKw==
+ dependencies:
+ unist-util-visit-parents "^2.0.0"
+
universalify@^0.1.0:
version "0.1.2"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
@@ -10635,7 +10877,7 @@ unset-value@^1.0.0:
has-value "^0.3.1"
isobject "^3.0.0"
-upath@^1.1.0:
+upath@^1.1.0, upath@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.2.tgz#3db658600edaeeccbe6db5e684d67ee8c2acd068"
integrity sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==
@@ -10760,6 +11002,28 @@ verror@1.10.0:
core-util-is "1.0.2"
extsprintf "^1.2.0"
+vfile-location@^2.0.0:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-2.0.4.tgz#2a5e7297dd0d9e2da4381464d04acc6b834d3e55"
+ integrity sha512-KRL5uXQPoUKu+NGvQVL4XLORw45W62v4U4gxJ3vRlDfI9QsT4ZN1PNXn/zQpKUulqGDpYuT0XDfp5q9O87/y/w==
+
+vfile-message@^1.0.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-1.1.1.tgz#5833ae078a1dfa2d96e9647886cd32993ab313e1"
+ integrity sha512-1WmsopSGhWt5laNir+633LszXvZ+Z/lxveBf6yhGsqnQIhlhzooZae7zV6YVM1Sdkw68dtAW3ow0pOdPANugvA==
+ dependencies:
+ unist-util-stringify-position "^1.1.1"
+
+vfile@^2.0.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/vfile/-/vfile-2.3.0.tgz#e62d8e72b20e83c324bc6c67278ee272488bf84a"
+ integrity sha512-ASt4mBUHcTpMKD/l5Q+WJXNtshlWxOogYyGYYrg4lt/vuRjC1EFQtlAofL5VmtVNIZJzWYFJjzGWZ0Gw8pzW1w==
+ dependencies:
+ is-buffer "^1.1.4"
+ replace-ext "1.0.0"
+ unist-util-stringify-position "^1.0.0"
+ vfile-message "^1.0.0"
+
vm-browserify@0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73"
@@ -10810,7 +11074,7 @@ vue-jest@^3.0.4:
tsconfig "^7.0.0"
vue-template-es2015-compiler "^1.6.0"
-vue-loader@^15.6.2:
+vue-loader@^15.7.0:
version "15.7.0"
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.7.0.tgz#27275aa5a3ef4958c5379c006dd1436ad04b25b3"
integrity sha512-x+NZ4RIthQOxcFclEcs8sXGEWqnZHodL2J9Vq+hUz+TDZzBaDIh1j3d9M2IUlTjtrHTZy4uMuRdTi8BGws7jLA==
@@ -10841,10 +11105,10 @@ vue-router@^3.0.2:
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.0.2.tgz#dedc67afe6c4e2bc25682c8b1c2a8c0d7c7e56be"
integrity sha512-opKtsxjp9eOcFWdp6xLQPLmRGgfM932Tl56U9chYTnoWqKxQ8M20N7AkdEbM5beUh6wICoFGYugAX9vQjyJLFg==
-vue-server-renderer@^2.5.22, vue-server-renderer@^2.6.9:
- version "2.6.9"
- resolved "https://registry.yarnpkg.com/vue-server-renderer/-/vue-server-renderer-2.6.9.tgz#74b970be513887ad255b2132daa1720a16af69ed"
- integrity sha512-UAwI9R+H9oh6YIG9xmS4uU1X8MD9bBzDLGIhqB8UHX9tJPrWQTrBijfXfnytDpefIisfz3qLa27qFOKuX4vnsw==
+vue-server-renderer@^2.6.10:
+ version "2.6.10"
+ resolved "https://registry.yarnpkg.com/vue-server-renderer/-/vue-server-renderer-2.6.10.tgz#cb2558842ead360ae2ec1f3719b75564a805b375"
+ integrity sha512-UYoCEutBpKzL2fKCwx8zlRtRtwxbPZXKTqbl2iIF4yRZUNO/ovrHyDAJDljft0kd+K0tZhN53XRHkgvCZoIhug==
dependencies:
chalk "^1.1.3"
hash-sum "^1.0.2"
@@ -10863,10 +11127,10 @@ vue-style-loader@^4.1.0:
hash-sum "^1.0.2"
loader-utils "^1.0.2"
-vue-template-compiler@^2.5.22, vue-template-compiler@^2.6.9:
- version "2.6.9"
- resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.9.tgz#26600415ff81a7a241aebc2d4e0abaa0f1a07915"
- integrity sha512-QgO0LSCdeH6zUMSgtqel+yDWsZWQPXiWBdFg9qzOhWfQL8vZ+ywinAzE04rm1XrWc+3SU0YAdWISlEgs/i8WWA==
+vue-template-compiler@^2.6.10:
+ version "2.6.10"
+ resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.10.tgz#323b4f3495f04faa3503337a82f5d6507799c9cc"
+ integrity sha512-jVZkw4/I/HT5ZMvRnhv78okGusqe0+qH2A0Em0Cp8aq78+NK9TII263CDVz2QXZsIT+yyV/gZc/j/vlwa+Epyg==
dependencies:
de-indent "^1.0.2"
he "^1.1.0"
@@ -10876,10 +11140,10 @@ vue-template-es2015-compiler@^1.6.0, vue-template-es2015-compiler@^1.9.0:
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
-vue@^2.5.22, vue@^2.6.9:
- version "2.6.9"
- resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.9.tgz#415c1cc1a5ed00c8f0acdd0a948139d12b7ea6b3"
- integrity sha512-t1+tvH8hybPM86oNne3ZozCD02zj/VoZIiojOBPJLjwBn7hxYU5e1gBObFpq8ts1NEn1VhPf/hVXBDAJ3X5ljg==
+vue@^2.6.10:
+ version "2.6.10"
+ resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.10.tgz#a72b1a42a4d82a721ea438d1b6bf55e66195c637"
+ integrity sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ==
vuex@^3.1.0:
version "3.1.0"
@@ -10914,7 +11178,7 @@ webidl-conversions@^4.0.2:
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==
-webpack-bundle-analyzer@^3.0.3:
+webpack-bundle-analyzer@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.1.0.tgz#2f19cbb87bb6d4f3cb4e59cb67c837bd9436e89d"
integrity sha512-nyDyWEs7C6DZlgvu1pR1zzJfIWSiGPbtaByZr8q+Fd2xp70FuM/8ngCJzj3Er1TYRLSFmp1F1OInbEm4DZH8NA==
@@ -10933,7 +11197,7 @@ webpack-bundle-analyzer@^3.0.3:
opener "^1.5.1"
ws "^6.0.0"
-webpack-dev-middleware@^3.5.1:
+webpack-dev-middleware@^3.6.1:
version "3.6.1"
resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.6.1.tgz#91f2531218a633a99189f7de36045a331a4b9cd4"
integrity sha512-XQmemun8QJexMEvNFbD2BIg4eSKrmSIMrTfnl2nql2Sc6OGAYFyb8rwuYrCjl/IiEYYuyTEiimMscu7EXji/Dw==
@@ -10974,7 +11238,7 @@ webpack-sources@^1.0.1, webpack-sources@^1.1.0, webpack-sources@^1.3.0:
source-list-map "^2.0.0"
source-map "~0.6.1"
-webpack@^4.29.2:
+webpack@^4.29.6:
version "4.29.6"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.29.6.tgz#66bf0ec8beee4d469f8b598d3988ff9d8d90e955"
integrity sha512-MwBwpiE1BQpMDkbnUUaW6K8RFZjljJHArC6tWQJoFm0oQtfoSebtg4Y7/QHnJ/SddtjYLHaKGX64CFjG5rehJw==
@@ -11118,6 +11382,15 @@ wrap-ansi@^4.0.0:
string-width "^2.1.1"
strip-ansi "^4.0.0"
+wrap-ansi@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.0.0.tgz#c3838a85fbac6a647558ca97024d41d7631721dc"
+ integrity sha512-3ThemJUfTTju0SKG2gjGExzGRHxT5l/KEM5sff3TQReaVWe/bFTiF1GEr8DKr/j0LxGt8qPzx0yhd2RLyqgy2Q==
+ dependencies:
+ ansi-styles "^3.2.0"
+ string-width "^3.0.0"
+ strip-ansi "^5.0.0"
+
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
@@ -11167,13 +11440,18 @@ ws@^5.2.0:
dependencies:
async-limiter "~1.0.0"
-ws@^6.0.0:
+ws@^6.0.0, ws@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.0.tgz#13806d9913b2a5f3cbb9ba47b563c002cbc7c526"
integrity sha512-deZYUNlt2O4buFCa3t5bKLf8A7FPP/TVjwOeVNpw818Ma5nk4MLXls2eoEGS39o8119QIYxTrTDoPQ5B/gTD6w==
dependencies:
async-limiter "~1.0.0"
+x-is-string@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/x-is-string/-/x-is-string-0.1.0.tgz#474b50865af3a49a9c4657f05acd145458f77d82"
+ integrity sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI=
+
xhr@^2.0.1:
version "2.5.0"
resolved "https://registry.yarnpkg.com/xhr/-/xhr-2.5.0.tgz#bed8d1676d5ca36108667692b74b316c496e49dd"
@@ -11207,7 +11485,7 @@ xmlbuilder@~9.0.1:
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"
integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=
-xtend@^4.0.0, xtend@~4.0.1:
+xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68=