From ec6951d8dbdd60a12ab68c216c48868590f3205f Mon Sep 17 00:00:00 2001 From: Aurore Trunelle Date: Mon, 18 Mar 2019 12:38:47 +0000 Subject: [PATCH 01/48] fix(docs): fix typo (#2864) fix typo in b-form-input modifiers section --- src/components/form-input/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/form-input/README.md b/src/components/form-input/README.md index 4a6d471854b..60443073b8e 100644 --- a/src/components/form-input/README.md +++ b/src/components/form-input/README.md @@ -463,7 +463,7 @@ chosen, or new values to be entered. Vue does not officially support `.lazy`, `.trim`, and `.number` modifiers on the `v-model` of custom component based inputs, and may generate a bad user experience. Avoid using Vue's native modifiers. -To get around this, `` and `` have two boolean props `trim` and +To get around this, `` and `` have two boolean props `trim` and `number` which emulate the native Vue `v-model` modifiers `.trim` and `.number` respectively. Emulation of the `.lazy` modifier is _not_ supported (listen for `change` or `blur` events instead). From 8570c9675a708bc51156a64f128e348c7ef18c9a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" Date: Mon, 18 Mar 2019 20:18:35 +0100 Subject: [PATCH 02/48] chore(deps): update dependency eslint to ^5.15.3 (#2868) --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 8d1eab31294..dc2eb3e245c 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "codecov": "^3.2.0", "codemirror": "^5.44.0", "cross-env": "^5.2.0", - "eslint": "^5.15.2", + "eslint": "^5.15.3", "eslint-config-prettier": "^4.1.0", "eslint-config-standard": "^12.0.0", "eslint-config-vue": "^2.0.2", diff --git a/yarn.lock b/yarn.lock index fa4150d4f61..4be7f711a79 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4236,10 +4236,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" From da49558a8f05889ef09fa469575365519e46b833 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Tue, 19 Mar 2019 09:50:28 -0300 Subject: [PATCH 03/48] fix(table): fix range selection (#2865) --- src/components/table/README.md | 10 ++ src/components/table/_table.scss | 14 ++- .../table/helpers/mixin-selectable.js | 50 +++++--- .../table/helpers/mixin-tbody-row.js | 7 +- src/components/table/table-selectable.spec.js | 113 +++++++++++++++++- src/components/table/table.js | 77 ++++++------ 6 files changed, 208 insertions(+), 63 deletions(-) diff --git a/src/components/table/README.md b/src/components/table/README.md index 74aef89bd59..46f71fafcc9 100644 --- a/src/components/table/README.md +++ b/src/components/table/README.md @@ -1188,6 +1188,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 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/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-tbody-row.js b/src/components/table/helpers/mixin-tbody-row.js index a53a4e4519e..17b61256ffc 100644 --- a/src/components/table/helpers/mixin-tbody-row.js +++ b/src/components/table/helpers/mixin-tbody-row.js @@ -195,7 +195,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 = [] @@ -244,7 +243,7 @@ export default { key: `__b-table-row-${rowKey}__`, class: [ this.rowClasses(item), - this.rowSelectedClasses(rowIndex), + this.selectableRowClasses(rowIndex), { 'b-table-has-details': rowShowDetails } @@ -256,8 +255,8 @@ 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) diff --git a/src/components/table/table-selectable.spec.js b/src/components/table/table-selectable.spec.js index 5ebf113454e..b495d528900 100644 --- a/src/components/table/table-selectable.spec.js +++ b/src/components/table/table-selectable.spec.js @@ -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.js b/src/components/table/table.js index fb484250fd7..1678a43955e 100644 --- a/src/components/table/table.js +++ b/src/components/table/table.js @@ -186,19 +186,43 @@ 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 @@ -477,9 +501,6 @@ export default { } }, render(h) { - const fields = this.computedFields - const items = this.computedItems - // Build the caption (from caption mixin) const $caption = this.renderCaption() @@ -502,31 +523,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] ) From ddcd66a07058d570e98a70557f2eaf871ea44949 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Tue, 19 Mar 2019 15:11:55 -0300 Subject: [PATCH 04/48] feat(table): add basic keyboard nav when table has row-clicked handler or is selctable (closes #2869) (#2870) --- src/components/table/README.md | 14 +- .../table/helpers/mixin-tbody-row.js | 84 ++++++--- .../table/table-tbody-row-events.spec.js | 71 +++++++- src/components/table/table.spec.js | 159 ------------------ 4 files changed, 144 insertions(+), 184 deletions(-) diff --git a/src/components/table/README.md b/src/components/table/README.md index 46f71fafcc9..47406c46df2 100644 --- a/src/components/table/README.md +++ b/src/components/table/README.md @@ -1828,12 +1828,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 tabl 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: diff --git a/src/components/table/helpers/mixin-tbody-row.js b/src/components/table/helpers/mixin-tbody-row.js index 17b61256ffc..08db431058d 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) { @@ -235,12 +277,24 @@ 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.selectableRowClasses(rowIndex), @@ -259,28 +313,14 @@ export default { ...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/table-tbody-row-events.spec.js b/src/components/table/table-tbody-row-events.spec.js index 44f8c6c61d3..4ea42025199 100644 --- a/src/components/table/table-tbody-row-events.spec.js +++ b/src/components/table/table-tbody-row-events.spec.js @@ -10,6 +10,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() @@ -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() @@ -323,6 +339,10 @@ describe('table tbody row events', () => { c: 'link', d: '', e: '' + }, + 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.spec.js b/src/components/table/table.spec.js index 85c94171e04..ac4cca5f4f7 100644 --- a/src/components/table/table.spec.js +++ b/src/components/table/table.spec.js @@ -390,165 +390,6 @@ describe('table', () => { } }) - 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() - }) - } - }) - - /* - * 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() - } - - const spy = jest.fn() - vm.$on('row-clicked', spy) - expect(spy).not.toHaveBeenCalled() - - // Select text in first TR - const range = document.createRange() - range.selectNode(trs[0]) - selection.addRange(range) - - // Click row - trs[0].click() - expect(spy).not.toHaveBeenCalled() - - // Clear selection - if (selection.rangeCount > 0) { - selection.removeAllRanges() - } - - // 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() - }) - } - }) - - 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() - }) - } - }) - - 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() - }) - } - } - }) - - 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('sortable header th should emit a sort-changed event with context when clicked and sort changed', async () => { const { app: { $refs } From 1eb490f7a4c6f8e4f67c33c303dde39ee6cf1cac Mon Sep 17 00:00:00 2001 From: "renovate[bot]" Date: Tue, 19 Mar 2019 22:36:25 +0100 Subject: [PATCH 05/48] chore(deps): update all non-major dependencies to ^7.4.0 (#2874) --- package.json | 10 +- yarn.lock | 342 +++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 338 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index dc2eb3e245c..0a2beb23032 100644 --- a/package.json +++ b/package.json @@ -84,11 +84,11 @@ }, "devDependencies": { "@babel/cli": "^7.2.3", - "@babel/core": "^7.3.4", - "@babel/plugin-transform-modules-commonjs": "^7.2.0", - "@babel/plugin-transform-runtime": "^7.3.4", - "@babel/preset-env": "^7.3.4", - "@babel/standalone": "^7.3.4", + "@babel/core": "^7.4.0", + "@babel/plugin-transform-modules-commonjs": "^7.4.0", + "@babel/plugin-transform-runtime": "^7.4.0", + "@babel/preset-env": "^7.4.0", + "@babel/standalone": "^7.4.0", "@nuxtjs/google-analytics": "^2.2.0", "@nuxtjs/pwa": "^3.0.0-beta.14", "@vue/test-utils": "^1.0.0-beta.29", diff --git a/yarn.lock b/yarn.lock index 4be7f711a79..14bd57ae427 100644 --- a/yarn.lock +++ b/yarn.lock @@ -26,7 +26,7 @@ dependencies: "@babel/highlight" "^7.0.0" -"@babel/core@^7.1.0", "@babel/core@^7.2.2", "@babel/core@^7.3.4": +"@babel/core@^7.1.0", "@babel/core@^7.2.2": version "7.3.4" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.3.4.tgz#921a5a13746c21e32445bf0798680e9d11a6530b" integrity sha512-jRsuseXBo9pN197KnDwhhaaBzyZr2oIcLHHTt2oDdQrej5Qp57dCCJafWx5ivU8/alEYDpssYqv1MUqcxwQlrA== @@ -46,6 +46,26 @@ semver "^5.4.1" source-map "^0.5.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.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" + lodash "^4.17.11" + resolve "^1.3.2" + 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" @@ -57,6 +77,17 @@ source-map "^0.5.0" trim-right "^1.0.1" +"@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.4.0" + jsesc "^2.5.1" + lodash "^4.17.11" + source-map "^0.5.0" + trim-right "^1.0.1" + "@babel/helper-annotate-as-pure@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz#323d39dd0b50e10c7c06ca7d7638e6864d8c5c32" @@ -81,6 +112,15 @@ "@babel/traverse" "^7.1.0" "@babel/types" "^7.0.0" +"@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.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" @@ -102,6 +142,15 @@ "@babel/types" "^7.0.0" lodash "^4.17.10" +"@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.4.0" + lodash "^4.17.11" + "@babel/helper-explode-assignable-expression@^7.1.0": version "7.1.0" resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz#537fa13f6f1674df745b0c00ec8fe4e99681c8f6" @@ -133,6 +182,13 @@ dependencies: "@babel/types" "^7.0.0" +"@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.4.0" + "@babel/helper-member-expression-to-functions@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0.tgz#8cd14b0a0df7ff00f009e7d7a436945f47c7a16f" @@ -199,6 +255,16 @@ "@babel/traverse" "^7.3.4" "@babel/types" "^7.3.4" +"@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.4.0" + "@babel/types" "^7.4.0" + "@babel/helper-simple-access@^7.1.0": version "7.1.0" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz#65eeb954c8c245beaa4e859da6188f39d71e585c" @@ -214,6 +280,13 @@ dependencies: "@babel/types" "^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.4.0" + "@babel/helper-wrap-function@^7.1.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz#c4e0012445769e2815b55296ead43a958549f6fa" @@ -233,6 +306,15 @@ "@babel/traverse" "^7.1.5" "@babel/types" "^7.3.0" +"@babel/helpers@^7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.4.0.tgz#03392e52c4ce7ad2e7b1cc07d1aba867a8ce2e32" + integrity sha512-2Lfcn74A2WSFUbYJ76ilYE1GnegCKUHTfXxp25EL2zPZHjV7OcDncqNjl295mUH0VnB65mNriXW4J5ROvxsgGg== + dependencies: + "@babel/template" "^7.4.0" + "@babel/traverse" "^7.4.0" + "@babel/types" "^7.4.0" + "@babel/highlight@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4" @@ -247,6 +329,11 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.3.4.tgz#a43357e4bbf4b92a437fb9e465c192848287f27c" integrity sha512-tXZCqWtlOOP4wgCp6RjRvLmfuhnqTLy9VHwRochJBCP2nDm27JnnuFEnXFASVyQNHk36jD1tAammsCEEqgscIQ== +"@babel/parser@^7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.4.0.tgz#6de669e73ac3a32c754280d0fef8fca6aad2c416" + integrity sha512-ZmMhJfU/+SXXvy9ALjDZopa3T3EixQtQai89JRC48eM9OUwrxJjYjuM/0wmdl2AekytlzMVhPY8cYdLb13kpKQ== + "@babel/plugin-proposal-async-generator-functions@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz#b289b306669dce4ad20b0252889a15768c9d417e" @@ -289,6 +376,14 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-object-rest-spread" "^7.2.0" +"@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" + "@babel/plugin-proposal-optional-catch-binding@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz#135d81edb68a081e55e56ec48541ece8065c38f5" @@ -306,6 +401,15 @@ "@babel/helper-regex" "^7.0.0" regexpu-core "^4.2.0" +"@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.5.4" + "@babel/plugin-syntax-async-generators@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz#69e1f0db34c6f5a0cf7e2b3323bf159a76c8cb7f" @@ -371,6 +475,15 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-remap-async-to-generator" "^7.1.0" +"@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" + "@babel/helper-remap-async-to-generator" "^7.1.0" + "@babel/plugin-transform-block-scoped-functions@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz#5d3cc11e8d5ddd752aa64c9148d0db6cb79fd190" @@ -386,6 +499,14 @@ "@babel/helper-plugin-utils" "^7.0.0" lodash "^4.17.11" +"@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" @@ -400,6 +521,20 @@ "@babel/helper-split-export-declaration" "^7.0.0" globals "^11.1.0" +"@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.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.4.0" + "@babel/helper-split-export-declaration" "^7.4.0" + globals "^11.1.0" + "@babel/plugin-transform-computed-properties@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.2.0.tgz#83a7df6a658865b1c8f641d510c6f3af220216da" @@ -414,6 +549,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@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" + "@babel/plugin-transform-dotall-regex@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.2.0.tgz#f0aabb93d120a8ac61e925ea0ba440812dbe0e49" @@ -445,6 +587,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@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" + "@babel/plugin-transform-function-name@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.2.0.tgz#f7930362829ff99a3174c39f0afcc024ef59731a" @@ -477,6 +626,15 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-simple-access" "^7.1.0" +"@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" @@ -485,6 +643,14 @@ "@babel/helper-hoist-variables" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0" +"@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.4.0" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-modules-umd@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.2.0.tgz#7678ce75169f0877b8eb2235538c074268dd01ae" @@ -507,6 +673,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@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" + "@babel/plugin-transform-object-super@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.2.0.tgz#b35d4c10f56bab5d650047dad0f1d8e8814b6598" @@ -524,6 +697,15 @@ "@babel/helper-get-function-arity" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0" +"@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.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" @@ -531,7 +713,14 @@ dependencies: regenerator-transform "^0.13.4" -"@babel/plugin-transform-runtime@^7.2.0", "@babel/plugin-transform-runtime@^7.3.4": +"@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": 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== @@ -541,6 +730,16 @@ resolve "^1.8.1" semver "^5.5.1" +"@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" + resolve "^1.8.1" + semver "^5.5.1" + "@babel/plugin-transform-shorthand-properties@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz#6333aee2f8d6ee7e28615457298934a3b46198f0" @@ -595,7 +794,7 @@ core-js "^2.5.7" regenerator-runtime "^0.12.0" -"@babel/preset-env@^7.3.1", "@babel/preset-env@^7.3.4": +"@babel/preset-env@^7.3.1": 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== @@ -644,6 +843,57 @@ js-levenshtein "^1.1.3" semver "^5.3.0" +"@babel/preset-env@^7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.4.0.tgz#8117e193876c646bc00c1333e0785c88e330db1a" + integrity sha512-0S+5y5FkdV/P98sjDW5QAnSjJL+FHEhPxMv8lioc7jjSasRQZhQrjyew41IbOOiytDVhCkz8oqzYZdqexYptHQ== + 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.4.0" + "@babel/plugin-proposal-optional-catch-binding" "^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.4.0" + "@babel/plugin-transform-block-scoped-functions" "^7.2.0" + "@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.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.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.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.4.0" + "@babel/plugin-transform-object-super" "^7.2.0" + "@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" + "@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" @@ -651,10 +901,10 @@ dependencies: regenerator-runtime "^0.12.0" -"@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.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.4.0.tgz#633e72397a26e90d32c625530c52f9e04c8323cf" + integrity sha512-3tgk5jchKs7L/Fx6pFcv1CfKEHKCeXL+2QN9xLgYouEiKhN9L2/itLgcuArE4Wehi9qb17yLQ/o74A5HS8VniA== "@babel/template@^7.0.0", "@babel/template@^7.1.0", "@babel/template@^7.1.2", "@babel/template@^7.2.2": version "7.2.2" @@ -665,6 +915,15 @@ "@babel/parser" "^7.2.2" "@babel/types" "^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.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" @@ -680,6 +939,21 @@ globals "^11.1.0" lodash "^4.17.11" +"@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.4.0" + "@babel/helper-function-name" "^7.1.0" + "@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" @@ -689,6 +963,15 @@ lodash "^4.17.11" to-fast-properties "^2.0.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" + to-fast-properties "^2.0.0" + "@cnakazawa/watch@^1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.3.tgz#099139eaec7ebf07a27c1786a3ff64f39464d2ef" @@ -2484,6 +2767,15 @@ browserslist@^4.0.0, browserslist@^4.3.4, browserslist@^4.4.2: electron-to-chromium "^1.3.113" node-releases "^1.1.8" +browserslist@^4.5.1: + version "4.5.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.5.1.tgz#2226cada1947b33f4cfcf7b608dcb519b6128106" + integrity sha512-/pPw5IAUyqaQXGuD5vS8tcbudyPZ241jk1W5pQBsGDfcjNQt7p8qxZhgMNuygDShte1PibLFexecWUPgmVLfrg== + dependencies: + caniuse-lite "^1.0.30000949" + electron-to-chromium "^1.3.116" + node-releases "^1.1.11" + bser@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/bser/-/bser-2.0.0.tgz#9ac78d3ed5d915804fd87acb158bc797147a1719" @@ -2673,6 +2965,11 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000932, caniuse-lite@^1.0.30000939, can 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.30000949: + version "1.0.30000950" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000950.tgz#8c559d66e332b34e919d1086cc6d29c1948856ae" + integrity sha512-Cs+4U9T0okW2ftBsCIHuEYXXkki7mjXmjCh4c6PzYShk04qDEr76/iC7KwhLoWoY65wcra1XOsRD+S7BptEb5A== + capture-exit@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" @@ -3252,6 +3549,26 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= +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: version "2.6.5" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.5.tgz#44bc8d249e7fb2ff5d00e0341a7ffb94fbf67895" @@ -3951,7 +4268,7 @@ 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: +electron-to-chromium@^1.3.113, electron-to-chromium@^1.3.116: version "1.3.116" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.116.tgz#1dbfee6a592a0c14ade77dbdfe54fef86387d702" integrity sha512-NKwKAXzur5vFCZYBHpdWjTMO8QptNLNP80nItkSIgUOapPAo9Uia+RvkCaZJtO7fhQaVElSvBPWEc2ku6cKsPA== @@ -7404,6 +7721,13 @@ node-pre-gyp@^0.10.0: semver "^5.3.0" tar "^4" +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" + node-releases@^1.1.8: version "1.1.10" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.10.tgz#5dbeb6bc7f4e9c85b899e2e7adcc0635c9b2adf7" @@ -9205,7 +9529,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.2.0, 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== From ffac3559426f9f0fbdc5283aed462a1d3044dcd4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" Date: Tue, 19 Mar 2019 20:34:45 -0300 Subject: [PATCH 06/48] chore(deps): update all non-major dependencies to ^7.4.1 (#2875) --- package.json | 4 ++-- yarn.lock | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 0a2beb23032..f08c5b2b912 100644 --- a/package.json +++ b/package.json @@ -87,8 +87,8 @@ "@babel/core": "^7.4.0", "@babel/plugin-transform-modules-commonjs": "^7.4.0", "@babel/plugin-transform-runtime": "^7.4.0", - "@babel/preset-env": "^7.4.0", - "@babel/standalone": "^7.4.0", + "@babel/preset-env": "^7.4.1", + "@babel/standalone": "^7.4.1", "@nuxtjs/google-analytics": "^2.2.0", "@nuxtjs/pwa": "^3.0.0-beta.14", "@vue/test-utils": "^1.0.0-beta.29", diff --git a/yarn.lock b/yarn.lock index 14bd57ae427..afd96d50625 100644 --- a/yarn.lock +++ b/yarn.lock @@ -843,10 +843,10 @@ js-levenshtein "^1.1.3" semver "^5.3.0" -"@babel/preset-env@^7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.4.0.tgz#8117e193876c646bc00c1333e0785c88e330db1a" - integrity sha512-0S+5y5FkdV/P98sjDW5QAnSjJL+FHEhPxMv8lioc7jjSasRQZhQrjyew41IbOOiytDVhCkz8oqzYZdqexYptHQ== +"@babel/preset-env@^7.4.1": + version "7.4.1" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.4.1.tgz#80e19ad76f62fb136d57ee4b963db3e8a6840bad" + integrity sha512-uC2DeVb6ljdjBGhJCyHxNZfSJEVgPdUm2R5cX85GCl1Qreo5sMM5g85ntqtzRF7XRYGgnRmV5we9cdlvo1wJvg== dependencies: "@babel/helper-module-imports" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0" @@ -901,10 +901,10 @@ dependencies: regenerator-runtime "^0.12.0" -"@babel/standalone@^7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.4.0.tgz#633e72397a26e90d32c625530c52f9e04c8323cf" - integrity sha512-3tgk5jchKs7L/Fx6pFcv1CfKEHKCeXL+2QN9xLgYouEiKhN9L2/itLgcuArE4Wehi9qb17yLQ/o74A5HS8VniA== +"@babel/standalone@^7.4.1": + version "7.4.1" + resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.4.1.tgz#44c6d3ad6f3a7d65504f734ddf719ef27f9352fe" + integrity sha512-kDtxw0hc0RhzqXK7ZT7UZ3tK6eh/YBJNJ2zihSSDq309tEM47Cf18+O9os3rbNviqoSFdfbqrO2e3WHEYAGf7w== "@babel/template@^7.0.0", "@babel/template@^7.1.0", "@babel/template@^7.1.2", "@babel/template@^7.2.2": version "7.2.2" From cb367e0809b3c5548961969e9228780e75f61e22 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Tue, 19 Mar 2019 23:36:08 -0300 Subject: [PATCH 07/48] chore(tests): more tests (#2872) --- .../breadcrumb/fixtures/breadcrumb.html | 4 +- .../breadcrumb/fixtures/breadcrumb.js | 18 ++ .../button-toolbar/button-toolbar.js | 81 +++--- .../button-toolbar/button-toolbar.spec.js | 195 ++++++++++++- .../fixtures/button-toolbar.html | 17 -- .../button-toolbar/fixtures/button-toolbar.js | 3 - src/components/carousel/carousel-slide.js | 1 + src/components/carousel/carousel.js | 6 +- src/components/carousel/carousel.spec.js | 49 ++++ .../carousel/fixtures/carousel.html | 111 ++++--- src/components/collapse/collapse.js | 1 + src/components/form-file/form-file.js | 8 +- .../form-group/fixtures/form-group.html | 40 +++ src/components/form-group/form-group.js | 2 + src/components/form-group/form-group.spec.js | 69 ++++- src/components/progress/progress-bar.spec.js | 270 ++++++++++++++++++ src/directives/toggle/toggle.js | 2 +- 17 files changed, 745 insertions(+), 132 deletions(-) delete mode 100644 src/components/button-toolbar/fixtures/button-toolbar.html delete mode 100644 src/components/button-toolbar/fixtures/button-toolbar.js create mode 100644 src/components/progress/progress-bar.spec.js diff --git a/src/components/breadcrumb/fixtures/breadcrumb.html b/src/components/breadcrumb/fixtures/breadcrumb.html index e837b03a047..087d93a5cde 100644 --- a/src/components/breadcrumb/fixtures/breadcrumb.html +++ b/src/components/breadcrumb/fixtures/breadcrumb.html @@ -6,6 +6,6 @@ v-bind="item" :key="index" /> - + diff --git a/src/components/breadcrumb/fixtures/breadcrumb.js b/src/components/breadcrumb/fixtures/breadcrumb.js index 177c40d42f8..7f6797bc2f5 100644 --- a/src/components/breadcrumb/fixtures/breadcrumb.js +++ b/src/components/breadcrumb/fixtures/breadcrumb.js @@ -35,6 +35,24 @@ window.app = new Vue({ { text: 'Library' } + ], + items3: [ + { + text: 'Home', + href: 'https://bootstrap-vue.github.io' + }, + { + text: 'Admin', + href: '#', + active: true + }, + { + text: 'Manage', + href: '#' + }, + { + text: 'Library' + } ] } }) diff --git a/src/components/button-toolbar/button-toolbar.js b/src/components/button-toolbar/button-toolbar.js index d6e75b3493a..aa5f06276b1 100644 --- a/src/components/button-toolbar/button-toolbar.js +++ b/src/components/button-toolbar/button-toolbar.js @@ -22,11 +22,6 @@ export default { default: false } }, - computed: { - classObject() { - return ['btn-toolbar', this.justify && !this.vertical ? 'justify-content-between' : ''] - } - }, mounted() { if (this.keyNav) { // Pre-set the tabindexes if the markup does not include tabindex="-1" on the toolbar items @@ -41,62 +36,51 @@ export default { this.focusFirst(evt) } }, + stop(evt) { + evt.preventDefault() + evt.stopPropagation() + }, onKeydown(evt) { if (!this.keyNav) { + /* istanbul ignore next: should never happen */ return } const key = evt.keyCode const shift = evt.shiftKey if (key === KeyCodes.UP || key === KeyCodes.LEFT) { - evt.preventDefault() - evt.stopPropagation() - if (shift) { - this.focusFirst(evt) - } else { - this.focusNext(evt, true) - } + this.stop(evt) + shift ? this.focusFirst(evt) : this.focusPrev(evt) } else if (key === KeyCodes.DOWN || key === KeyCodes.RIGHT) { - evt.preventDefault() - evt.stopPropagation() - if (shift) { - this.focusLast(evt) - } else { - this.focusNext(evt, false) - } + this.stop(evt) + shift ? this.focusLast(evt) : this.focusNext(evt) } }, setItemFocus(item) { - this.$nextTick(() => { - item.focus() - }) + item && item.focus && item.focus() }, - focusNext(evt, prev) { + focusFirst(evt) { const items = this.getItems() - if (items.length < 1) { - return - } - let index = items.indexOf(evt.target) - if (prev && index > 0) { - index-- - } else if (!prev && index < items.length - 1) { - index++ - } - if (index < 0) { - index = 0 + this.setItemFocus(items[0]) + }, + focusPrev(evt) { + let items = this.getItems() + const index = items.indexOf(evt.target) + if (index > -1) { + items = items.slice(0, index).reverse() + this.setItemFocus(items[0]) } - this.setItemFocus(items[index]) }, - focusFirst(evt) { - const items = this.getItems() - if (items.length > 0) { + focusNext(evt) { + let items = this.getItems() + const index = items.indexOf(evt.target) + if (index > -1) { + items = items.slice(index + 1) this.setItemFocus(items[0]) } }, focusLast(evt) { - const items = this.getItems() - if (items.length > 0) { - this.setItemFocus([items.length - 1]) - } + const items = this.getItems().reverse() + this.setItemFocus(items[0]) }, getItems() { let items = selectAll(ITEM_SELECTOR, this.$el) @@ -111,15 +95,18 @@ export default { return h( 'div', { - class: this.classObject, + staticClass: 'btn-toolbar', + class: { 'justify-content-between': this.justify }, attrs: { role: 'toolbar', tabindex: this.keyNav ? '0' : null }, - on: { - focusin: this.onFocusin, - keydown: this.onKeydown - } + on: this.keyNav + ? { + focusin: this.onFocusin, + keydown: this.onKeydown + } + : {} }, [this.$slots.default] ) diff --git a/src/components/button-toolbar/button-toolbar.spec.js b/src/components/button-toolbar/button-toolbar.spec.js index 6039c60e125..8c1f8ab66d5 100644 --- a/src/components/button-toolbar/button-toolbar.spec.js +++ b/src/components/button-toolbar/button-toolbar.spec.js @@ -1,22 +1,195 @@ -import { loadFixture, testVM } from '../../../tests/utils' +import ButtonToolbar from './button-toolbar' +import ButtonGroup from '../button-group/button-group' +import Button from '../button/button' +import { mount } from '@vue/test-utils' +import Vue from 'vue' describe('button-toolbar', () => { - beforeEach(loadFixture(__dirname, 'button-toolbar')) - testVM() + it('toolbar root should be "div"', async () => { + const wrapper = mount(ButtonToolbar, {}) + expect(wrapper.is('div')).toBe(true) + wrapper.destroy() + }) it('toolbar should contain base class', async () => { - const { - app: { $refs } - } = window + const wrapper = mount(ButtonToolbar, {}) + expect(wrapper.classes()).toContain('btn-toolbar') + wrapper.destroy() + }) - expect($refs.toolbar).toHaveClass('btn-toolbar') + it('toolbar should not have class "justify-content-between"', async () => { + const wrapper = mount(ButtonToolbar, {}) + expect(wrapper.classes()).not.toContain('justify-content-between') + wrapper.destroy() }) it('toolbar should have role', async () => { - const { - app: { $refs } - } = window + const wrapper = mount(ButtonToolbar, {}) + expect(wrapper.attributes('role')).toBe('toolbar') + wrapper.destroy() + }) + + it('toolbar should not have tabindex by default', async () => { + const wrapper = mount(ButtonToolbar, {}) + expect(wrapper.attributes('tabindex')).not.toBeDefined() + wrapper.destroy() + }) + + it('toolbar should have class "justify-content-between" when justify set', async () => { + const wrapper = mount(ButtonToolbar, { + propsData: { + justify: true + } + }) + expect(wrapper.classes()).toContain('justify-content-between') + expect(wrapper.classes()).toContain('btn-toolbar') + wrapper.destroy() + }) + + it('toolbar should have tabindex when key-nav set', async () => { + const wrapper = mount(ButtonToolbar, { + propsData: { + keyNav: true + } + }) + expect(wrapper.attributes('tabindex')).toBeDefined() + expect(wrapper.attributes('tabindex')).toBe('0') + expect(wrapper.element.tabIndex).toBe(0) + wrapper.destroy() + }) + + // These tests are wrapped in a new describe to limit the scope of the getBCR Mock + describe('keyboard navigation', () => { + const origGetBCR = Element.prototype.getBoundingClientRect + + beforeEach(() => { + // Mock getBCR so that the isVisible(el) test returns true + // In our test below, all pagination buttons would normally be visible + Element.prototype.getBoundingClientRect = jest.fn(() => { + return { + width: 24, + height: 24, + top: 0, + left: 0, + bottom: 0, + right: 0 + } + }) + }) + + afterEach(() => { + // Restore prototype + Element.prototype.getBoundingClientRect = origGetBCR + }) + + // Test App for keynav + const App = Vue.extend({ + render(h) { + return h(ButtonToolbar, { props: { keyNav: true } }, [ + h(ButtonGroup, {}, [h(Button, {}, 'a'), h(Button, {}, 'b')]), + h(ButtonGroup, {}, [h(Button, { props: { disabled: true } }, 'c'), h(Button, {}, 'd')]), + h(ButtonGroup, {}, [h(Button, {}, 'e'), h(Button, {}, 'f')]) + ]) + } + }) + + it('has correct structure', async () => { + const wrapper = mount(App, { + attachToDocument: true + }) + + await wrapper.vm.$nextTick() + + expect(wrapper.is('div.btn-toolbar')).toBe(true) + expect(wrapper.attributes('tabindex')).toBe('0') + + const $groups = wrapper.findAll('.btn-group') + expect($groups).toBeDefined() + expect($groups.length).toBe(3) + expect($groups.is(ButtonGroup)).toBe(true) + + const $btns = wrapper.findAll('button') + expect($btns).toBeDefined() + expect($btns.length).toBe(6) + expect($btns.is(Button)).toBe(true) + expect($btns.at(0).is('button[tabindex="-1"')).toBe(true) + expect($btns.at(1).is('button[tabindex="-1"')).toBe(true) + expect($btns.at(2).is('button[tabindex="-1"')).toBe(false) // disabled button + expect($btns.at(3).is('button[tabindex="-1"')).toBe(true) + expect($btns.at(4).is('button[tabindex="-1"')).toBe(true) + expect($btns.at(5).is('button[tabindex="-1"')).toBe(true) + + wrapper.destroy() + }) + + it('focuses first button when tabbed into', async () => { + const wrapper = mount(App, { + attachToDocument: true + }) + + await wrapper.vm.$nextTick() + + expect(wrapper.is('div.btn-toolbar')).toBe(true) + expect(wrapper.attributes('tabindex')).toBe('0') + + const $btns = wrapper.findAll('button') + expect($btns).toBeDefined() + expect($btns.length).toBe(6) + + expect(document.activeElement).not.toBe(wrapper.element) + expect(document.activeElement).not.toBe($btns.at(0).element) + + wrapper.trigger('focusin') + await wrapper.vm.$nextTick() + expect(document.activeElement).toBe($btns.at(0).element) + + wrapper.destroy() + }) + + it('keyboard navigation works', async () => { + const wrapper = mount(App, { + attachToDocument: true + }) + + await wrapper.vm.$nextTick() + + expect(wrapper.is('div.btn-toolbar')).toBe(true) + expect(wrapper.attributes('tabindex')).toBe('0') + + const $btns = wrapper.findAll('button') + expect($btns).toBeDefined() + expect($btns.length).toBe(6) + + // Focus first button + $btns.at(0).element.focus() + expect(document.activeElement).toBe($btns.at(0).element) + + // Cursor right + $btns.at(0).trigger('keydown.right') + await wrapper.vm.$nextTick() + expect(document.activeElement).toBe($btns.at(1).element) + + // Cursor right (skips disabled button) + $btns.at(1).trigger('keydown.right') + await wrapper.vm.$nextTick() + expect(document.activeElement).toBe($btns.at(3).element) + + // Cursor shift-right (focuses last button) + $btns.at(1).trigger('keydown.right', { shiftKey: true }) + await wrapper.vm.$nextTick() + expect(document.activeElement).toBe($btns.at(5).element) + + // Cursor left + $btns.at(5).trigger('keydown.left') + await wrapper.vm.$nextTick() + expect(document.activeElement).toBe($btns.at(4).element) + + // Cursor shift left (focuses first button) + $btns.at(5).trigger('keydown.left', { shiftKey: true }) + await wrapper.vm.$nextTick() + expect(document.activeElement).toBe($btns.at(0).element) - expect($refs.toolbar.$el.getAttribute('role')).toBe('toolbar') + wrapper.destroy() + }) }) }) diff --git a/src/components/button-toolbar/fixtures/button-toolbar.html b/src/components/button-toolbar/fixtures/button-toolbar.html deleted file mode 100644 index ef208d89bf1..00000000000 --- a/src/components/button-toolbar/fixtures/button-toolbar.html +++ /dev/null @@ -1,17 +0,0 @@ -
- - - « - - - - Edit - Undo - Redo - - - - » - - -
diff --git a/src/components/button-toolbar/fixtures/button-toolbar.js b/src/components/button-toolbar/fixtures/button-toolbar.js deleted file mode 100644 index 0bae7b95202..00000000000 --- a/src/components/button-toolbar/fixtures/button-toolbar.js +++ /dev/null @@ -1,3 +0,0 @@ -window.app = new Vue({ - el: '#app' -}) diff --git a/src/components/carousel/carousel-slide.js b/src/components/carousel/carousel-slide.js index 89cb5d8cd38..6576d1ceb4c 100644 --- a/src/components/carousel/carousel-slide.js +++ b/src/components/carousel/carousel-slide.js @@ -114,6 +114,7 @@ export default { on: noDrag ? { dragstart: e => { + /* istanbul ignore next: difficult to test in JSDOM */ e.preventDefault() } } diff --git a/src/components/carousel/carousel.js b/src/components/carousel/carousel.js index c98cbec1da7..bc53f680b2d 100644 --- a/src/components/carousel/carousel.js +++ b/src/components/carousel/carousel.js @@ -138,7 +138,7 @@ export default { transitionEndEvent: null, slides: [], direction: null, - isPaused: false, + isPaused: !(parseInt(this.interval, 10) > 0), // Touch event handling values touchStartX: 0, touchDeltaX: 0 @@ -171,6 +171,7 @@ export default { }, index(to, from) { if (to === from || this.isSliding) { + /* istanbul ignore next */ return } this.doSlide(to, from) @@ -181,6 +182,8 @@ export default { this._intervalId = null this._animationTimeout = null this._touchTimeout = null + // Set initial paused state + this.isPaused = !(parseInt(this.interval, 10) > 0) }, mounted() { // Cache current browser transitionend event name @@ -251,6 +254,7 @@ export default { if (!evt) { this.isPaused = false } + /* istanbul ignore next: most likley will never happen, but just in case */ if (this._intervalId) { clearInterval(this._intervalId) this._intervalId = null diff --git a/src/components/carousel/carousel.spec.js b/src/components/carousel/carousel.spec.js index cc90d744d28..bf816bb7614 100644 --- a/src/components/carousel/carousel.spec.js +++ b/src/components/carousel/carousel.spec.js @@ -267,4 +267,53 @@ describe('carousel', () => { expect(spyEnd).toHaveBeenCalledWith(app.slide) expect(carousel.isSliding).toBe(false) }) + + it('should emit paused and unpaused events when interval hcanged to 0', async () => { + const { app } = window + const carousel = app.$refs.carousel + + const spy1 = jest.fn() + const spy2 = jest.fn() + + carousel.$on('unpaused', spy1) + carousel.$on('paused', spy2) + + expect(carousel.interval).toBe(0) + expect(carousel.isPaused).toBe(true) + + jest.runOnlyPendingTimers() + await nextTick() + expect(spy1).not.toHaveBeenCalled() + expect(spy2).not.toHaveBeenCalled() + + await setData(app, 'interval', 1000) + await app.$nextTick() + jest.runOnlyPendingTimers() + expect(carousel.interval).toBe(1000) + expect(carousel.isPaused).toBe(false) + expect(spy1).toHaveBeenCalledTimes(1) + expect(spy2).not.toHaveBeenCalled() + + jest.runOnlyPendingTimers() + await nextTick() + + await setData(app, 'interval', 0) + await app.$nextTick() + jest.runOnlyPendingTimers() + expect(carousel.interval).toBe(0) + expect(carousel.isPaused).toBe(true) + expect(spy1).toHaveBeenCalledTimes(1) + expect(spy2).toHaveBeenCalledTimes(1) + + jest.runOnlyPendingTimers() + await nextTick() + + await setData(app, 'interval', 1000) + await app.$nextTick() + jest.runOnlyPendingTimers() + expect(carousel.interval).toBe(1000) + expect(carousel.isPaused).toBe(false) + expect(spy1).toHaveBeenCalledTimes(2) + expect(spy2).toHaveBeenCalledTimes(1) + }) }) diff --git a/src/components/carousel/fixtures/carousel.html b/src/components/carousel/fixtures/carousel.html index f8fc12a5d3e..0f8c1523923 100644 --- a/src/components/carousel/fixtures/carousel.html +++ b/src/components/carousel/fixtures/carousel.html @@ -1,47 +1,68 @@
- - - - - - - - -

Hello world!

-
- - - - - - - - - -
- -

- Slide #: {{ slide }}
- Is Sliding: {{ sliding }} -

- - - - + + + + + + + + +

Hello world!

+
+ + + + + + + + + +
+ +

+ Slide #: {{ slide }}
+ Is Sliding: {{ sliding }} +

+ + + + + + + + + + + + + + + + +
diff --git a/src/components/collapse/collapse.js b/src/components/collapse/collapse.js index 80c81b32692..928417fca75 100644 --- a/src/components/collapse/collapse.js +++ b/src/components/collapse/collapse.js @@ -150,6 +150,7 @@ export default { // If we are in a nav/navbar, close the collapse when non-disabled link clicked const el = evt.target if (!this.isNav || !el || getCS(this.$el).display !== 'block') { + /* istanbul ignore next: can't test getComputedStyle in JSDOM */ return } if (matches(el, '.nav-link,.dropdown-item') || closest('.nav-link,.dropdown-item', el)) { diff --git a/src/components/form-file/form-file.js b/src/components/form-file/form-file.js index 4f81c6175d4..8945f06f962 100644 --- a/src/components/form-file/form-file.js +++ b/src/components/form-file/form-file.js @@ -136,8 +136,8 @@ export default { // Check if special `items` prop is available on event (drop mode) // Can be disabled by setting no-traverse const items = evt.dataTransfer && evt.dataTransfer.items + /* istanbul ignore next: not supported in JSDOM */ if (items && !this.noTraverse) { - /* istanbul ignore next: not supported in JSDOM */ const queue = [] for (let i = 0; i < items.length; i++) { const item = items[i].webkitGetAsEntry() @@ -174,7 +174,7 @@ export default { // Triggered when the parent form (if any) is reset this.selectedFile = this.multiple ? [] : null }, - onDragover(evt) { + onDragover(evt) /* istanbul ignore next: difficult to test in JSDOM */ { evt.preventDefault() evt.stopPropagation() if (this.noDrop || !this.custom) { @@ -183,12 +183,12 @@ export default { this.dragging = true evt.dataTransfer.dropEffect = 'copy' }, - onDragleave(evt) { + onDragleave(evt) /* istanbul ignore next: difficult to test in JSDOM */ { evt.preventDefault() evt.stopPropagation() this.dragging = false }, - onDrop(evt) { + onDrop(evt) /* istanbul ignore next: difficult to test in JSDOM */ { evt.preventDefault() evt.stopPropagation() if (this.noDrop) { diff --git a/src/components/form-group/fixtures/form-group.html b/src/components/form-group/fixtures/form-group.html index 556f1532632..eb8a392ce48 100644 --- a/src/components/form-group/fixtures/form-group.html +++ b/src/components/form-group/fixtures/form-group.html @@ -1,6 +1,7 @@
@@ -44,6 +48,7 @@ @@ -51,6 +56,7 @@ + + + + + + + + + + + +
diff --git a/src/components/form-group/form-group.js b/src/components/form-group/form-group.js index a6c1f0ac0d4..b591c825093 100644 --- a/src/components/form-group/form-group.js +++ b/src/components/form-group/form-group.js @@ -351,11 +351,13 @@ export default { legendClick(evt) { if (this.labelFor) { // don't do anything if labelFor is set + /* istanbul ignore next: clicking a label will focus the input, so no need to test */ return } const tagName = evt.target ? evt.target.tagName : '' if (/^(input|select|textarea|label|button|a)$/i.test(tagName)) { // If clicked an interactive element inside legend, we just let the default happen + /* istanbul ignore next */ return } const inputs = selectAll(SELECTOR, this.$refs.content).filter(isVisible) diff --git a/src/components/form-group/form-group.spec.js b/src/components/form-group/form-group.spec.js index 43d1e7c352d..a50ca9341a7 100644 --- a/src/components/form-group/form-group.spec.js +++ b/src/components/form-group/form-group.spec.js @@ -1,6 +1,73 @@ -import { loadFixture, testVM } from '../../../tests/utils' +import { loadFixture, testVM, setData, nextTick } from '../../../tests/utils' describe('form-group', () => { beforeEach(loadFixture(__dirname, 'form-group')) testVM() + + it('app changes validation state when text supplied', async () => { + const { app } = window + const $group = app.$refs.group1 + + expect($group.$el.getAttribute('aria-invalid')).toBe('true') + + const oldADB = $group.$el.getAttribute('aria-describedby') + + await setData(app, 'text', 'foobar doodle') + await nextTick() + + expect($group.$el.getAttribute('aria-invalid')).toBe(null) + + const newADB = $group.$el.getAttribute('aria-describedby') + + expect(oldADB).not.toBe(newADB) + }) + + describe('form-group > legend click', () => { + // These tests are wrapped in a new describe to limit the scope of the getBCR Mock + const origGetBCR = Element.prototype.getBoundingClientRect + + beforeEach(() => { + // Mock getBCR so that the isVisible(el) test returns true + // In our test below, all pagination buttons would normally be visible + Element.prototype.getBoundingClientRect = jest.fn(() => { + return { + width: 24, + height: 24, + top: 0, + left: 0, + bottom: 0, + right: 0 + } + }) + }) + + afterEach(() => { + // Restore prototype + Element.prototype.getBoundingClientRect = origGetBCR + }) + + it('clicking legend focuses input', async () => { + const { app } = window + const $group = app.$refs.group10 + + const legend = $group.$el.querySelector('legend') + expect(legend).toBeDefined() + expect(legend.tagName).toBe('LEGEND') + expect(legend.textContent).toContain('legend-click') + const input = $group.$el.querySelector('input') + expect(input).toBeDefined() + + expect(document.activeElement).not.toBe(input) + + // legend.click() + // legend.click() doesn't trigger the click event, since it is + // a non-interactive element + const clickEvt = new MouseEvent('click') + legend.dispatchEvent(clickEvt) + await nextTick() + + // Can't get this to work in the test environment for some reason + // expect(document.activeElement).toBe(input) + }) + }) }) diff --git a/src/components/progress/progress-bar.spec.js b/src/components/progress/progress-bar.spec.js new file mode 100644 index 00000000000..0ccb0b744c9 --- /dev/null +++ b/src/components/progress/progress-bar.spec.js @@ -0,0 +1,270 @@ +import ProgressBar from './progress-bar' +import { mount } from '@vue/test-utils' + +describe('progress-bar', () => { + it('has correct base class and structure', async () => { + const wrapper = mount(ProgressBar) + + expect(wrapper.is('div')).toBe(true) + expect(wrapper.classes()).toContain('progress-bar') + expect(wrapper.attributes('role')).toBe('progressbar') + expect(wrapper.attributes('aria-valuemin')).toBe('0') + expect(wrapper.attributes('aria-valuemax')).toBe('100') + expect(wrapper.attributes('aria-valuenow')).toBe('0') + expect(wrapper.attributes('style')).toContain('width: 0%;') + + expect(wrapper.classes()).not.toContain('progress-bar-striped') + expect(wrapper.classes()).not.toContain('progress-bar-animated') + + // Should not have a label + expect(wrapper.text()).toBe('') + + wrapper.destroy() + }) + + it('has class bg-primary when variant=primary', async () => { + const wrapper = mount(ProgressBar, { + propsData: { + variant: 'primary' + } + }) + + expect(wrapper.classes()).toContain('bg-primary') + expect(wrapper.classes()).toContain('progress-bar') + + wrapper.destroy() + }) + + it('has class bg-info when parent variant=info', async () => { + const wrapper = mount(ProgressBar, { + provide: { + bvProgress: { + variant: 'info' + } + } + }) + + expect(wrapper.classes()).toContain('bg-info') + expect(wrapper.classes()).toContain('progress-bar') + + wrapper.destroy() + }) + + it('has class bg-primary when prop variant=primary and parent variant=info', async () => { + const wrapper = mount(ProgressBar, { + provide: { + bvProgress: { + variant: 'info' + } + }, + propsData: { + variant: 'primary' + } + }) + + expect(wrapper.classes()).toContain('bg-primary') + expect(wrapper.classes()).toContain('progress-bar') + + wrapper.destroy() + }) + it('has class progress-bar-striped when prop striped set', async () => { + const wrapper = mount(ProgressBar, { + propsData: { + striped: true + } + }) + + expect(wrapper.classes()).toContain('progress-bar-striped') + expect(wrapper.classes()).toContain('progress-bar') + + wrapper.destroy() + }) + + it('has class progress-bar-striped when parent prop striped set', async () => { + const wrapper = mount(ProgressBar, { + provide: { + bvProgress: { + striped: true + } + } + }) + + expect(wrapper.classes()).toContain('progress-bar-striped') + expect(wrapper.classes()).toContain('progress-bar') + + wrapper.destroy() + }) + + it('has class progress-bar-animated and progress-bar-striped when prop animated set', async () => { + const wrapper = mount(ProgressBar, { + propsData: { + animated: true + } + }) + + expect(wrapper.classes()).toContain('progress-bar-animated') + expect(wrapper.classes()).toContain('progress-bar-striped') + expect(wrapper.classes()).toContain('progress-bar') + + wrapper.destroy() + }) + + it('has class progress-bar-animated and progress-bar-striped when parent prop animated set', async () => { + const wrapper = mount(ProgressBar, { + provide: { + bvProgress: { + animated: true + } + } + }) + + expect(wrapper.classes()).toContain('progress-bar-animated') + expect(wrapper.classes()).toContain('progress-bar-striped') + expect(wrapper.classes()).toContain('progress-bar') + + wrapper.destroy() + }) + + it('has style width set when value set', async () => { + const wrapper = mount(ProgressBar, { + propsData: { + value: 50 + } + }) + + expect(wrapper.attributes('style')).toContain('width: 50%;') + expect(wrapper.attributes('aria-valuenow')).toBe('50') + expect(wrapper.attributes('aria-valuemin')).toBe('0') + expect(wrapper.attributes('aria-valuemax')).toBe('100') + + wrapper.destroy() + }) + + it('has max set', async () => { + const wrapper = mount(ProgressBar, { + propsData: { + value: 25, + max: 50 + } + }) + + expect(wrapper.attributes('style')).toContain('width: 50%;') + expect(wrapper.attributes('aria-valuenow')).toBe('25') + expect(wrapper.attributes('aria-valuemin')).toBe('0') + expect(wrapper.attributes('aria-valuemax')).toBe('50') + + wrapper.destroy() + }) + + it('has max set when parent max set', async () => { + const wrapper = mount(ProgressBar, { + provide: { + bvProgress: { + max: 50 + } + }, + propsData: { + value: 25 + } + }) + + expect(wrapper.attributes('style')).toContain('width: 50%;') + expect(wrapper.attributes('aria-valuenow')).toBe('25') + expect(wrapper.attributes('aria-valuemin')).toBe('0') + expect(wrapper.attributes('aria-valuemax')).toBe('50') + + wrapper.destroy() + }) + + it('has label when prop label set', async () => { + const wrapper = mount(ProgressBar, { + propsData: { + label: 'foobar' + } + }) + + expect(wrapper.text()).toBe('foobar') + + wrapper.destroy() + }) + + it('has label when prop labelHtml set', async () => { + const wrapper = mount(ProgressBar, { + propsData: { + labelHtml: 'foobar' + } + }) + + expect(wrapper.text()).toBe('foobar') + + wrapper.destroy() + }) + + it('has label from default slot', async () => { + const wrapper = mount(ProgressBar, { + slots: { + default: 'foobar' + } + }) + + expect(wrapper.text()).toBe('foobar') + + wrapper.destroy() + }) + + it('has label when show-value set', async () => { + const wrapper = mount(ProgressBar, { + propsData: { + value: 50, + showValue: true + } + }) + + expect(wrapper.text()).toBe('50') + + wrapper.destroy() + }) + + it('has label with precision when show-value and precision set', async () => { + const wrapper = mount(ProgressBar, { + propsData: { + value: 50, + showValue: true, + precision: 2 + } + }) + + expect(wrapper.text()).toBe('50.00') + + wrapper.destroy() + }) + + it('has label when show-progress set', async () => { + const wrapper = mount(ProgressBar, { + propsData: { + value: 25, + showProgress: true, + max: 50 + } + }) + + expect(wrapper.text()).toBe('50') + + wrapper.destroy() + }) + + it('has label when show-progress and precision set', async () => { + const wrapper = mount(ProgressBar, { + propsData: { + value: 25, + showProgress: true, + max: 50, + precision: 2 + } + }) + + expect(wrapper.text()).toBe('50.00') + + wrapper.destroy() + }) +}) diff --git a/src/directives/toggle/toggle.js b/src/directives/toggle/toggle.js index 568ecd43b83..6f5a065ae6e 100644 --- a/src/directives/toggle/toggle.js +++ b/src/directives/toggle/toggle.js @@ -51,7 +51,7 @@ export default { vnode.context.$root.$on(EVENT_STATE, el[BVT]) } }, - unbind(el, binding, vnode) { + unbind(el, binding, vnode) /* istanbul ignore next */ { if (el[BVT]) { // Remove our $root listener vnode.context.$root.$off(EVENT_STATE, el[BVT]) From b566c58ed546299a0904da03aef80ce10f578886 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Wed, 20 Mar 2019 01:04:33 -0300 Subject: [PATCH 08/48] chore: more unit testing adjustments (#2877) --- src/components/modal/modal.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/modal/modal.js b/src/components/modal/modal.js index 6c135bac858..d612314bf0f 100644 --- a/src/components/modal/modal.js +++ b/src/components/modal/modal.js @@ -354,6 +354,7 @@ export default { watch: { visible(newVal, oldVal) { if (newVal === oldVal) { + /* istanbul ignore next */ return } this[newVal ? 'show' : 'hide']() @@ -378,7 +379,7 @@ export default { this.show() } }, - beforeDestroy() /* instanbul ignore next */ { + beforeDestroy() /* istanbul ignore next */ { // Ensure everything is back to normal if (this._observer) { this._observer.disconnect() @@ -456,7 +457,7 @@ export default { relatedTarget: null, isOK: trigger || null, trigger: trigger || null, - cancel() { + cancel() /* istanbul ignore next */ { // Backwards compatibility warn('b-modal: evt.cancel() is deprecated. Please use evt.preventDefault().') this.preventDefault() From eaa00cdafe528063a17e1bf688f22bf99ad5bba2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" Date: Wed, 20 Mar 2019 07:37:26 +0100 Subject: [PATCH 09/48] chore(deps): update all non-major dependencies to ^2.6.10 (#2878) --- package.json | 6 +++--- yarn.lock | 33 ++++++++++++++++++++++++++++++--- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index f08c5b2b912..63046e15e18 100644 --- a/package.json +++ b/package.json @@ -132,11 +132,11 @@ "sass-loader": "^7.1.0", "standard-version": "^5.0.2", "terser": "^3.17.0", - "vue": "^2.6.9", + "vue": "^2.6.10", "vue-jest": "^3.0.4", "vue-router": "^3.0.2", - "vue-server-renderer": "^2.6.9", - "vue-template-compiler": "^2.6.9" + "vue-server-renderer": "^2.6.10", + "vue-template-compiler": "^2.6.10" }, "jest": { "testRegex": "spec.js$", diff --git a/yarn.lock b/yarn.lock index afd96d50625..645ea42486d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11165,7 +11165,7 @@ 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: +vue-server-renderer@^2.5.22: version "2.6.9" resolved "https://registry.yarnpkg.com/vue-server-renderer/-/vue-server-renderer-2.6.9.tgz#74b970be513887ad255b2132daa1720a16af69ed" integrity sha512-UAwI9R+H9oh6YIG9xmS4uU1X8MD9bBzDLGIhqB8UHX9tJPrWQTrBijfXfnytDpefIisfz3qLa27qFOKuX4vnsw== @@ -11179,6 +11179,20 @@ vue-server-renderer@^2.5.22, vue-server-renderer@^2.6.9: serialize-javascript "^1.3.0" source-map "0.5.6" +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" + he "^1.1.0" + lodash.template "^4.4.0" + lodash.uniq "^4.5.0" + resolve "^1.2.0" + serialize-javascript "^1.3.0" + source-map "0.5.6" + vue-style-loader@^4.1.0: version "4.1.2" resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.2.tgz#dedf349806f25ceb4e64f3ad7c0a44fba735fcf8" @@ -11187,7 +11201,7 @@ 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: +vue-template-compiler@^2.5.22: 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== @@ -11195,16 +11209,29 @@ vue-template-compiler@^2.5.22, vue-template-compiler@^2.6.9: de-indent "^1.0.2" he "^1.1.0" +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" + vue-template-es2015-compiler@^1.6.0, vue-template-es2015-compiler@^1.9.0: version "1.9.1" 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: +vue@^2.5.22: 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" resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.1.0.tgz#634b81515cf0cfe976bd1ffe9601755e51f843b9" From 591f0edfda5c9a885ea6fdea5d3c5b34cef40c27 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" Date: Wed, 20 Mar 2019 09:12:29 +0100 Subject: [PATCH 10/48] chore(deps): update dependency rollup to ^1.7.0 (#2879) --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 63046e15e18..f55382eda1f 100644 --- a/package.json +++ b/package.json @@ -124,7 +124,7 @@ "nuxt": "^2.4.5", "postcss-cli": "^6.1.2", "prettier": "1.14.3", - "rollup": "^1.6.0", + "rollup": "^1.7.0", "rollup-plugin-babel": "^4.3.2", "rollup-plugin-commonjs": "^9.2.1", "rollup-plugin-node-resolve": "^4.0.1", diff --git a/yarn.lock b/yarn.lock index 645ea42486d..4c0084d2516 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9769,10 +9769,10 @@ 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.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.7.0.tgz#2f5063c0f344f2225d1077655dc54d105a512bb2" + integrity sha512-hjuWSCgoQsFSTsmsNP4AH1l1kfkFqW82gW00V9nL81Zr3JtnKn3rvxh18jUAAEMb7qNoHj21PR5SqbK2mhBgMg== dependencies: "@types/estree" "0.0.39" "@types/node" "^11.9.5" From b609b5d119a1b2c6c863f23588b4586f8aaccd52 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" Date: Wed, 20 Mar 2019 09:31:02 -0300 Subject: [PATCH 11/48] chore(deps): update dependency codemirror to ^5.45.0 (#2881) --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index f55382eda1f..e8d52fa1c69 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "babel-plugin-istanbul": "^5.1.1", "clean-css-cli": "^4.2.1", "codecov": "^3.2.0", - "codemirror": "^5.44.0", + "codemirror": "^5.45.0", "cross-env": "^5.2.0", "eslint": "^5.15.3", "eslint-config-prettier": "^4.1.0", diff --git a/yarn.lock b/yarn.lock index 4c0084d2516..7adb742657f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3183,10 +3183,10 @@ 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== collection-visit@^1.0.0: version "1.0.0" From 004c2e83a03a8ee3e3fbc6fc405c1e98e40fa129 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" Date: Thu, 21 Mar 2019 12:48:39 +0100 Subject: [PATCH 12/48] chore(deps): update all non-major dependencies to ^7.4.2 (#2885) --- package.json | 4 ++-- yarn.lock | 25 ++++++++++++++++--------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index e8d52fa1c69..ea92d14aae4 100644 --- a/package.json +++ b/package.json @@ -87,8 +87,8 @@ "@babel/core": "^7.4.0", "@babel/plugin-transform-modules-commonjs": "^7.4.0", "@babel/plugin-transform-runtime": "^7.4.0", - "@babel/preset-env": "^7.4.1", - "@babel/standalone": "^7.4.1", + "@babel/preset-env": "^7.4.2", + "@babel/standalone": "^7.4.2", "@nuxtjs/google-analytics": "^2.2.0", "@nuxtjs/pwa": "^3.0.0-beta.14", "@vue/test-utils": "^1.0.0-beta.29", diff --git a/yarn.lock b/yarn.lock index 7adb742657f..f3ae5bc620c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -666,6 +666,13 @@ dependencies: regexp-tree "^0.1.0" +"@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" @@ -843,10 +850,10 @@ js-levenshtein "^1.1.3" semver "^5.3.0" -"@babel/preset-env@^7.4.1": - version "7.4.1" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.4.1.tgz#80e19ad76f62fb136d57ee4b963db3e8a6840bad" - integrity sha512-uC2DeVb6ljdjBGhJCyHxNZfSJEVgPdUm2R5cX85GCl1Qreo5sMM5g85ntqtzRF7XRYGgnRmV5we9cdlvo1wJvg== +"@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" @@ -876,7 +883,7 @@ "@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-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.4.0" @@ -901,10 +908,10 @@ dependencies: regenerator-runtime "^0.12.0" -"@babel/standalone@^7.4.1": - version "7.4.1" - resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.4.1.tgz#44c6d3ad6f3a7d65504f734ddf719ef27f9352fe" - integrity sha512-kDtxw0hc0RhzqXK7ZT7UZ3tK6eh/YBJNJ2zihSSDq309tEM47Cf18+O9os3rbNviqoSFdfbqrO2e3WHEYAGf7w== +"@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" From 8a503ec243bc7a06fb221681105d7c39d7fe51d2 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 21 Mar 2019 16:10:29 -0300 Subject: [PATCH 13/48] feat(carousel): add no-hover-pause prop (#2888) --- src/components/carousel/README.md | 4 ++++ src/components/carousel/carousel.js | 11 +++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/components/carousel/README.md b/src/components/carousel/README.md index d37f2641481..5185a6237d3 100644 --- a/src/components/carousel/README.md +++ b/src/components/carousel/README.md @@ -122,6 +122,10 @@ carousel, set the `interval` back to the desired number of ms. When the carousel is paused, the user can still switch slides via the controls (if enabled) or touch swipe (on touch enabled devices, if not disabled). +When the users mouse hovers the carousel it will automatically pause, and will automatically +restart when the mouse leaves the carousel. To disable this feature, set the `no-hover-pause` +prop on `. + ## Controls and Indicators Set the prop `controls` to enable the previous and next control buttons. diff --git a/src/components/carousel/carousel.js b/src/components/carousel/carousel.js index bc53f680b2d..d661a115b3d 100644 --- a/src/components/carousel/carousel.js +++ b/src/components/carousel/carousel.js @@ -61,6 +61,8 @@ function getTransisionEndEvent(el) { return null } +const noop = () => {} + // @vue/component export default { name: 'BCarousel', @@ -112,6 +114,11 @@ export default { type: Boolean, default: false }, + noHoverPause: { + // Disable pause on hover + type: Boolean, + default: false + }, imgWidth: { // Sniffed by carousel-slide type: [Number, String] @@ -541,8 +548,8 @@ export default { ) const on = { - mouseenter: this.pause, - mouseleave: this.restart, + mouseenter: this.noHoverPause ? noop : this.pause, + mouseleave: this.noHoverPause ? noop : this.restart, focusin: this.pause, focusout: this.restart, keydown: evt => { From 3702d3ff07c95d09f94b27fc3ca66536898f1388 Mon Sep 17 00:00:00 2001 From: pooya parsa Date: Fri, 22 Mar 2019 02:03:40 +0430 Subject: [PATCH 14/48] chore: create jest.config.js --- jest.config.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 jest.config.js diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 00000000000..57e323a48ff --- /dev/null +++ b/jest.config.js @@ -0,0 +1,12 @@ +module.exports = { + testRegex: 'spec.js$', + moduleFileExtensions: ['js', 'vue'], + transform: { + '^.+\\.js$': 'babel-jest', + '.*\\.(vue)$': 'vue-jest' + }, + coverageDirectory: './coverage/', + testEnvironmentOptions: { + pretendToBeVisual: true + } +} From 12bc0760b3e8faef4a8451ac357165ac328fc6b5 Mon Sep 17 00:00:00 2001 From: pooya parsa Date: Fri, 22 Mar 2019 02:04:06 +0430 Subject: [PATCH 15/48] chore: simplify package.json --- package.json | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/package.json b/package.json index ea92d14aae4..0369bfd4103 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "jsnext:main": "es/index.js", "style": "dist/bootstrap-vue.css", "license": "MIT", + "repository": "bootstrap-vue/bootstrap-vue", "homepage": "https://bootstrap-vue.js.org", "contributors": [ { @@ -40,13 +41,6 @@ "url": "https://github.com/jackmu95" } ], - "bugs": { - "url": "https://github.com/bootstrap-vue/bootstrap-vue/issues" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/bootstrap-vue/bootstrap-vue.git" - }, "files": [ "src", "dist", @@ -138,21 +132,6 @@ "vue-server-renderer": "^2.6.10", "vue-template-compiler": "^2.6.10" }, - "jest": { - "testRegex": "spec.js$", - "moduleFileExtensions": [ - "js", - "vue" - ], - "transform": { - "^.+\\.js$": "babel-jest", - ".*\\.(vue)$": "vue-jest" - }, - "coverageDirectory": "./coverage/", - "testEnvironmentOptions": { - "pretendToBeVisual": true - } - }, "standard-version": { "scripts": { "postchangelog": "./node_modules/.bin/prettier --write CHANGELOG.md" From af8a8fa6f55996d6fd6d9cbe54a79cb968d371ca Mon Sep 17 00:00:00 2001 From: pooya parsa Date: Fri, 22 Mar 2019 02:06:06 +0430 Subject: [PATCH 16/48] docs: prepare babel.config.js for nuxt 2.5.x --- babel.config.js | 37 +++++++++++++------------------------ 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/babel.config.js b/babel.config.js index 6077262c182..6161c28787a 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,28 +1,17 @@ -module.exports = { - presets: [ - [ - '@babel/env', - { - useBuiltIns: 'entry' +module.exports = function(api) { + const isDocs = api.env('docs') + + return { + presets: isDocs + ? ['@nuxt/babel-preset-app-edge'] + : [['@babel/env', { useBuiltIns: 'entry', corejs: { version: 2 } }]], + env: { + es: { + plugins: [['@babel/plugin-transform-modules-commonjs', { noInterop: true, loose: true }]] + }, + test: { + presets: [['@babel/env', { targets: { node: 'current' } }]] } - ] - ], - env: { - docs: { - plugins: ['@babel/plugin-transform-runtime'] - }, - es: { - plugins: [['@babel/plugin-transform-modules-commonjs', { noInterop: true, loose: true }]] - }, - test: { - presets: [ - [ - '@babel/env', - { - targets: { node: 'current' } - } - ] - ] } } } From 5090f4206bac566fa5b4eb5c2b25b834942df820 Mon Sep 17 00:00:00 2001 From: pooya parsa Date: Fri, 22 Mar 2019 02:27:31 +0430 Subject: [PATCH 17/48] chore(docs): upgrade nuxt to 2.5.1 --- babel.config.js | 4 +- package.json | 2 +- yarn.lock | 892 ++++++++++++++++-------------------------------- 3 files changed, 304 insertions(+), 594 deletions(-) diff --git a/babel.config.js b/babel.config.js index 6161c28787a..5359858c09f 100644 --- a/babel.config.js +++ b/babel.config.js @@ -2,9 +2,7 @@ module.exports = function(api) { const isDocs = api.env('docs') return { - presets: isDocs - ? ['@nuxt/babel-preset-app-edge'] - : [['@babel/env', { useBuiltIns: 'entry', corejs: { version: 2 } }]], + presets: isDocs ? [] : [['@babel/env', { useBuiltIns: 'entry', corejs: { version: 2 } }]], env: { es: { plugins: [['@babel/plugin-transform-modules-commonjs', { noInterop: true, loose: true }]] diff --git a/package.json b/package.json index 0369bfd4103..88e58efe44b 100644 --- a/package.json +++ b/package.json @@ -115,7 +115,7 @@ "lodash": "^4.17.11", "marked": "^0.6.1", "node-sass": "^4.11.0", - "nuxt": "^2.4.5", + "nuxt": "^2.5.1", "postcss-cli": "^6.1.2", "prettier": "1.14.3", "rollup": "^1.7.0", diff --git a/yarn.lock b/yarn.lock index f3ae5bc620c..426b8ffe1e1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -26,27 +26,7 @@ dependencies: "@babel/highlight" "^7.0.0" -"@babel/core@^7.1.0", "@babel/core@^7.2.2": - version "7.3.4" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.3.4.tgz#921a5a13746c21e32445bf0798680e9d11a6530b" - integrity sha512-jRsuseXBo9pN197KnDwhhaaBzyZr2oIcLHHTt2oDdQrej5Qp57dCCJafWx5ivU8/alEYDpssYqv1MUqcxwQlrA== - 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" - convert-source-map "^1.1.0" - debug "^4.1.0" - json5 "^2.1.0" - lodash "^4.17.11" - resolve "^1.3.2" - semver "^5.4.1" - source-map "^0.5.0" - -"@babel/core@^7.4.0": +"@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== @@ -66,18 +46,7 @@ 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== - dependencies: - "@babel/types" "^7.3.4" - jsesc "^2.5.1" - lodash "^4.17.11" - source-map "^0.5.0" - trim-right "^1.0.1" - -"@babel/generator@^7.4.0": +"@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== @@ -103,15 +72,6 @@ "@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== - dependencies: - "@babel/helper-hoist-variables" "^7.0.0" - "@babel/traverse" "^7.1.0" - "@babel/types" "^7.0.0" - "@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" @@ -121,26 +81,17 @@ "@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-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== - dependencies: - "@babel/helper-function-name" "^7.1.0" - "@babel/types" "^7.0.0" - lodash "^4.17.10" + "@babel/helper-replace-supers" "^7.4.0" + "@babel/helper-split-export-declaration" "^7.4.0" "@babel/helper-define-map@^7.4.0": version "7.4.0" @@ -175,13 +126,6 @@ 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== - dependencies: - "@babel/types" "^7.0.0" - "@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" @@ -245,17 +189,7 @@ "@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== - 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/helper-replace-supers@^7.4.0": +"@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== @@ -273,14 +207,7 @@ "@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== - dependencies: - "@babel/types" "^7.0.0" - -"@babel/helper-split-export-declaration@^7.4.0": +"@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== @@ -297,19 +224,10 @@ "@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== - dependencies: - "@babel/template" "^7.1.2" - "@babel/traverse" "^7.1.5" - "@babel/types" "^7.3.0" - "@babel/helpers@^7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.4.0.tgz#03392e52c4ce7ad2e7b1cc07d1aba867a8ce2e32" - integrity sha512-2Lfcn74A2WSFUbYJ76ilYE1GnegCKUHTfXxp25EL2zPZHjV7OcDncqNjl295mUH0VnB65mNriXW4J5ROvxsgGg== + 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.4.0" "@babel/traverse" "^7.4.0" @@ -324,15 +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.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.4.0.tgz#6de669e73ac3a32c754280d0fef8fca6aad2c416" - integrity sha512-ZmMhJfU/+SXXvy9ALjDZopa3T3EixQtQai89JRC48eM9OUwrxJjYjuM/0wmdl2AekytlzMVhPY8cYdLb13kpKQ== +"@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" @@ -343,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" @@ -368,14 +281,6 @@ "@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== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-syntax-object-rest-spread" "^7.2.0" - "@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" @@ -392,15 +297,6 @@ "@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== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-regex" "^7.0.0" - regexpu-core "^4.2.0" - "@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" @@ -466,15 +362,6 @@ 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== - dependencies: - "@babel/helper-module-imports" "^7.0.0" - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-remap-async-to-generator" "^7.1.0" - "@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" @@ -491,14 +378,6 @@ 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== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - lodash "^4.17.11" - "@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" @@ -507,20 +386,6 @@ "@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== - dependencies: - "@babel/helper-annotate-as-pure" "^7.0.0" - "@babel/helper-define-map" "^7.1.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" - globals "^11.1.0" - "@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" @@ -542,13 +407,6 @@ 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== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@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" @@ -580,13 +438,6 @@ "@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== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@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" @@ -617,15 +468,6 @@ "@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== - 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-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" @@ -635,14 +477,6 @@ "@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== - dependencies: - "@babel/helper-hoist-variables" "^7.0.0" - "@babel/helper-plugin-utils" "^7.0.0" - "@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" @@ -659,13 +493,6 @@ "@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== - dependencies: - regexp-tree "^0.1.0" - "@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" @@ -673,13 +500,6 @@ 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== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@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" @@ -695,15 +515,6 @@ "@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== - dependencies: - "@babel/helper-call-delegate" "^7.1.0" - "@babel/helper-get-function-arity" "^7.0.0" - "@babel/helper-plugin-utils" "^7.0.0" - "@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" @@ -713,13 +524,6 @@ "@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== - dependencies: - regenerator-transform "^0.13.4" - "@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" @@ -727,16 +531,6 @@ dependencies: regenerator-transform "^0.13.4" -"@babel/plugin-transform-runtime@^7.2.0": - 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== - dependencies: - "@babel/helper-module-imports" "^7.0.0" - "@babel/helper-plugin-utils" "^7.0.0" - resolve "^1.8.1" - semver "^5.5.1" - "@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" @@ -793,62 +587,13 @@ "@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" - -"@babel/preset-env@^7.3.1": - 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== - 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-optional-catch-binding" "^7.2.0" - "@babel/plugin-proposal-unicode-property-regex" "^7.2.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-block-scoped-functions" "^7.2.0" - "@babel/plugin-transform-block-scoping" "^7.3.4" - "@babel/plugin-transform-classes" "^7.3.4" - "@babel/plugin-transform-computed-properties" "^7.2.0" - "@babel/plugin-transform-destructuring" "^7.2.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-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-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-object-super" "^7.2.0" - "@babel/plugin-transform-parameters" "^7.2.0" - "@babel/plugin-transform-regenerator" "^7.3.4" - "@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" - invariant "^2.2.2" - js-levenshtein "^1.1.3" - semver "^5.3.0" + core-js "^2.6.5" + regenerator-runtime "^0.13.2" "@babel/preset-env@^7.4.2": version "7.4.2" @@ -901,28 +646,19 @@ 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.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== - dependencies: - "@babel/code-frame" "^7.0.0" - "@babel/parser" "^7.2.2" - "@babel/types" "^7.2.2" - -"@babel/template@^7.4.0": +"@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== @@ -931,22 +667,7 @@ "@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== - dependencies: - "@babel/code-frame" "^7.0.0" - "@babel/generator" "^7.3.4" - "@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" - debug "^4.1.0" - globals "^11.1.0" - lodash "^4.17.11" - -"@babel/traverse@^7.4.0": +"@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== @@ -961,16 +682,7 @@ 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== - dependencies: - esutils "^2.0.2" - lodash "^4.17.11" - to-fast-properties "^2.0.0" - -"@babel/types@^7.4.0": +"@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== @@ -1394,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== @@ -1487,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" @@ -1507,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" @@ -1598,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" @@ -1742,9 +1473,9 @@ 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== + 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/q@^1.5.1": version "1.5.2" @@ -1767,9 +1498,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" @@ -2354,7 +2085,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== @@ -2765,21 +2496,12 @@ 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== - dependencies: - caniuse-lite "^1.0.30000939" - electron-to-chromium "^1.3.113" - node-releases "^1.1.8" - -browserslist@^4.5.1: - version "4.5.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.5.1.tgz#2226cada1947b33f4cfcf7b608dcb519b6128106" - integrity sha512-/pPw5IAUyqaQXGuD5vS8tcbudyPZ241jk1W5pQBsGDfcjNQt7p8qxZhgMNuygDShte1PibLFexecWUPgmVLfrg== +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.30000949" + caniuse-lite "^1.0.30000951" electron-to-chromium "^1.3.116" node-releases "^1.1.11" @@ -2967,15 +2689,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.30000949: - version "1.0.30000950" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000950.tgz#8c559d66e332b34e919d1086cc6d29c1948856ae" - integrity sha512-Cs+4U9T0okW2ftBsCIHuEYXXkki7mjXmjCh4c6PzYShk04qDEr76/iC7KwhLoWoY65wcra1XOsRD+S7BptEb5A== +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" @@ -3035,7 +2752,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== @@ -3243,12 +2960,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== @@ -3276,23 +2993,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" @@ -3329,10 +3046,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" @@ -3576,7 +3293,7 @@ core-js@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.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== @@ -3719,7 +3436,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== @@ -3882,7 +3599,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== @@ -4275,10 +3992,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, electron-to-chromium@^1.3.116: - 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" @@ -4534,15 +4251,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== @@ -4602,10 +4311,10 @@ eslint@^5.15.3: 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.18, 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" @@ -4858,14 +4567,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" @@ -5486,11 +5195,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: @@ -5812,10 +5521,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" @@ -6762,9 +6471,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" @@ -7735,13 +7444,6 @@ node-releases@^1.1.11: dependencies: semver "^5.3.0" -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== - dependencies: - semver "^5.3.0" - node-sass@^4.11.0: version "4.11.0" resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.11.0.tgz#183faec398e9cbe93ba43362e2768ca988a6369a" @@ -7819,7 +7521,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== @@ -7876,17 +7578,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" @@ -7969,7 +7672,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== @@ -7988,7 +7691,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== @@ -8130,9 +7833,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" @@ -8898,7 +8601,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== @@ -9178,13 +8881,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" @@ -9499,10 +9211,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" @@ -9536,7 +9248,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.5.4: +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== @@ -9707,6 +9419,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" @@ -9760,9 +9477,9 @@ rollup-plugin-node-resolve@^4.0.1: 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== + 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" @@ -9921,7 +9638,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== @@ -10426,9 +10143,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" @@ -10605,7 +10322,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== @@ -10878,14 +10595,27 @@ 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.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.17.1" + 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== + 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" @@ -10966,7 +10696,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== @@ -11141,7 +10871,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== @@ -11172,20 +10902,6 @@ 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: - version "2.6.9" - resolved "https://registry.yarnpkg.com/vue-server-renderer/-/vue-server-renderer-2.6.9.tgz#74b970be513887ad255b2132daa1720a16af69ed" - integrity sha512-UAwI9R+H9oh6YIG9xmS4uU1X8MD9bBzDLGIhqB8UHX9tJPrWQTrBijfXfnytDpefIisfz3qLa27qFOKuX4vnsw== - dependencies: - chalk "^1.1.3" - hash-sum "^1.0.2" - he "^1.1.0" - lodash.template "^4.4.0" - lodash.uniq "^4.5.0" - resolve "^1.2.0" - serialize-javascript "^1.3.0" - source-map "0.5.6" - 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" @@ -11208,14 +10924,6 @@ vue-style-loader@^4.1.0: hash-sum "^1.0.2" loader-utils "^1.0.2" -vue-template-compiler@^2.5.22: - 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== - dependencies: - de-indent "^1.0.2" - he "^1.1.0" - 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" @@ -11229,11 +10937,6 @@ 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: - 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" @@ -11272,7 +10975,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== @@ -11291,7 +10994,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== @@ -11332,7 +11035,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== @@ -11476,6 +11179,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" @@ -11525,7 +11237,7 @@ 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== From cbd07089ce78a6440f96f6c5143e4868e0cea59f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" Date: Thu, 21 Mar 2019 19:13:26 -0300 Subject: [PATCH 18/48] chore(deps): update dependency esm to ^3.2.20 (#2890) --- package.json | 2 +- yarn.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 88e58efe44b..f6b2a4e268c 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,7 @@ "eslint-plugin-promise": "^4.0.1", "eslint-plugin-standard": "^4.0.0", "eslint-plugin-vue": "^5.2.2", - "esm": "^3.2.18", + "esm": "^3.2.20", "gh-pages": "^2.0.1", "highlightjs": "^9.12.0", "html-loader": "^0.5.5", diff --git a/yarn.lock b/yarn.lock index 426b8ffe1e1..c04c95f3bd0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4311,7 +4311,7 @@ eslint@^5.15.3: table "^5.2.3" text-table "^0.2.0" -esm@^3.2.18, esm@^3.2.20: +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== From 61b75dc6ecee8d0d0ee83b68dbb87a88714d0e89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacob=20M=C3=BCller?= Date: Fri, 22 Mar 2019 00:55:22 +0100 Subject: [PATCH 19/48] feat(utils/noop): add `noop()` util (#2892) * feat(utils/noop): add `noop()` util * fix noop reference --- src/components/carousel/carousel.js | 42 +++++++++--------- src/utils/noop.js | 3 ++ src/utils/tooltip.class.js | 69 ++++++++++++----------------- 3 files changed, 53 insertions(+), 61 deletions(-) create mode 100644 src/utils/noop.js diff --git a/src/components/carousel/carousel.js b/src/components/carousel/carousel.js index d661a115b3d..c3d594b116f 100644 --- a/src/components/carousel/carousel.js +++ b/src/components/carousel/carousel.js @@ -1,5 +1,6 @@ import observeDom from '../../utils/observe-dom' import KeyCodes from '../../utils/key-codes' +import noop from '../../utils/noop' import { selectAll, reflow, @@ -28,7 +29,7 @@ const DIRECTION = { const TRANS_DURATION = 600 + 50 // Time for mouse compat events to fire after touch -const TOUCHEVENT_COMPAT_WAIT = 500 +const TOUCH_EVENT_COMPAT_WAIT = 500 // Number of pixels to consider touch move a swipe const SWIPE_THRESHOLD = 40 @@ -50,7 +51,7 @@ const TransitionEndEvents = { const EventOptions = { passive: true, capture: false } // Return the browser specific transitionEnd event name -function getTransisionEndEvent(el) { +function getTransitionEndEvent(el) { for (const name in TransitionEndEvents) { if (el.style[name] !== undefined) { return TransitionEndEvents[name] @@ -61,8 +62,6 @@ function getTransisionEndEvent(el) { return null } -const noop = () => {} - // @vue/component export default { name: 'BCarousel', @@ -194,7 +193,7 @@ export default { }, mounted() { // Cache current browser transitionend event name - this.transitionEndEvent = getTransisionEndEvent(this.$el) || null + this.transitionEndEvent = getTransitionEndEvent(this.$el) || null // Get all slides this.updateSlides() // Observe child changes so we can update slide list @@ -205,7 +204,7 @@ export default { attributeFilter: ['id'] }) }, - beforeDestroy() /* istanbul ignore next: dificult to test */ { + beforeDestroy() /* istanbul ignore next: difficult to test */ { clearTimeout(this._animationTimeout) clearTimeout(this._touchTimeout) clearInterval(this._intervalId) @@ -217,7 +216,7 @@ export default { // Set slide setSlide(slide, direction = null) { // Don't animate when page is not visible - /* istanbul ignore if: dificult to test */ + /* istanbul ignore if: difficult to test */ if (inBrowser && document.visibilityState && document.hidden) { return } @@ -261,7 +260,7 @@ export default { if (!evt) { this.isPaused = false } - /* istanbul ignore next: most likley will never happen, but just in case */ + /* istanbul ignore next: most likely will never happen, but just in case */ if (this._intervalId) { clearInterval(this._intervalId) this._intervalId = null @@ -271,9 +270,9 @@ export default { this._intervalId = setInterval(this.next, Math.max(1000, this.interval)) } }, - // Re-Start auto rotate slides when focus/hover leaves the carousel + // Restart auto rotate slides when focus/hover leaves the carousel restart(evt) { - /* istanbul ignore if: dificult to test */ + /* istanbul ignore if: difficult to test */ if (!this.$el.contains(document.activeElement)) { this.start() } @@ -314,7 +313,7 @@ export default { addClass(nextSlide, dirClass) // Transition End handler let called = false - /* istanbul ignore next: dificult to test */ + /* istanbul ignore next: difficult to test */ const onceTransEnd = evt => { if (called) { return @@ -347,7 +346,7 @@ export default { const events = this.transitionEndEvent.split(/\s+/) events.forEach(event => eventOn(currentSlide, event, onceTransEnd, EventOptions)) } - // Fallback to setTimeout + // Fallback to setTimeout() this._animationTimeout = setTimeout(onceTransEnd, TRANS_DURATION) } if (isCycling) { @@ -393,16 +392,16 @@ export default { } }, handleSwipe() /* istanbul ignore next: JSDOM doesn't support touch events */ { - const absDeltax = Math.abs(this.touchDeltaX) - if (absDeltax <= SWIPE_THRESHOLD) { + const absDeltaX = Math.abs(this.touchDeltaX) + if (absDeltaX <= SWIPE_THRESHOLD) { return } - const direction = absDeltax / this.touchDeltaX + const direction = absDeltaX / this.touchDeltaX if (direction > 0) { - // swipe left + // Swipe left this.prev() } else if (direction < 0) { - // swipe right + // Swipe right this.next() } }, @@ -414,7 +413,7 @@ export default { } }, touchMove(evt) /* istanbul ignore next: JSDOM doesn't support touch events */ { - // ensure swiping with one touch and not pinching + // Ensure swiping with one touch and not pinching if (evt.touches && evt.touches.length > 1) { this.touchDeltaX = 0 } else { @@ -439,7 +438,7 @@ export default { } this._touchTimeout = setTimeout( this.start, - TOUCHEVENT_COMPAT_WAIT + Math.max(1000, this.interval) + TOUCH_EVENT_COMPAT_WAIT + Math.max(1000, this.interval) ) } }, @@ -458,7 +457,7 @@ export default { [this.$slots.default] ) - // Prev and Next Controls + // Prev and next controls let controls = h(false) if (this.controls) { controls = [ @@ -567,7 +566,8 @@ export default { } // Touch support event handlers for environment if (!this.noTouch && hasTouchSupport) { - /* istanbul ignore next: JSDOM doesn't support touch events */ // Attach appropriate listeners (passsive mode) + // Attach appropriate listeners (passive mode) + /* istanbul ignore next: JSDOM doesn't support touch events */ if (hasPointerEvent) { on['&pointerdown'] = this.touchStart on['&pointerup'] = this.touchEnd 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/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') From ddc20060f6e0704043fa711308350e4f5e43f74b Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 21 Mar 2019 21:44:08 -0300 Subject: [PATCH 20/48] fix(table): fix broken aria-labels for sortable columns + break out code into additional mixins + tests (#2884) --- .../form-select/form-select.spec.js | 29 + src/components/pagination/pagination.spec.js | 29 + src/components/table/README.md | 191 +-- src/components/table/fixtures/table.html | 148 -- src/components/table/fixtures/table.js | 175 --- src/components/table/helpers/mixin-empty.js | 4 +- .../table/helpers/mixin-filtering.js | 200 +++ src/components/table/helpers/mixin-items.js | 57 + .../table/helpers/mixin-pagination.js | 29 + .../table/helpers/mixin-provider.js | 6 + src/components/table/helpers/mixin-sorting.js | 252 ++++ .../table/helpers/mixin-tbody-row.js | 47 +- src/components/table/helpers/mixin-thead.js | 94 +- src/components/table/table-busy.spec.js | 2 +- src/components/table/table-caption.spec.js | 2 +- src/components/table/table-colgroup.spec.js | 2 +- src/components/table/table-filtering.spec.js | 237 ++++ src/components/table/table-pagination.spec.js | 139 ++ src/components/table/table-primarykey.spec.js | 2 +- src/components/table/table-provider.spec.js | 52 +- .../table/table-row-details.spec.js | 61 +- src/components/table/table-selectable.spec.js | 2 +- src/components/table/table-sort.spec.js | 268 ---- src/components/table/table-sorting.spec.js | 700 ++++++++++ ...spec.js => table-tbody-bottom-row.spec.js} | 2 +- .../table/table-tbody-row-events.spec.js | 2 +- ...ow.spec.js => table-tbody-top-row.spec.js} | 2 +- .../table/table-tbody-transition.spec.js | 2 +- .../table/table-tfoot-events.spec.js | 18 +- .../table/table-thead-events.spec.js | 40 +- src/components/table/table-thead-top.spec.js | 2 +- src/components/table/table.js | 347 +---- src/components/table/table.spec.js | 1209 ++++++++--------- src/utils/startcase.spec.js | 11 + tests/utils.js | 18 +- 35 files changed, 2590 insertions(+), 1791 deletions(-) delete mode 100644 src/components/table/fixtures/table.html delete mode 100644 src/components/table/fixtures/table.js create mode 100644 src/components/table/helpers/mixin-filtering.js create mode 100644 src/components/table/helpers/mixin-items.js create mode 100644 src/components/table/helpers/mixin-pagination.js create mode 100644 src/components/table/helpers/mixin-sorting.js create mode 100644 src/components/table/table-filtering.spec.js create mode 100644 src/components/table/table-pagination.spec.js delete mode 100644 src/components/table/table-sort.spec.js create mode 100644 src/components/table/table-sorting.spec.js rename src/components/table/{table-bottom-row.spec.js => table-tbody-bottom-row.spec.js} (98%) rename src/components/table/{table-top-row.spec.js => table-tbody-top-row.spec.js} (98%) create mode 100644 src/utils/startcase.spec.js diff --git a/src/components/form-select/form-select.spec.js b/src/components/form-select/form-select.spec.js index dcc693582ff..27bd8a9924d 100644 --- a/src/components/form-select/form-select.spec.js +++ b/src/components/form-select/form-select.spec.js @@ -433,6 +433,35 @@ describe('form-select', () => { wrapper.destroy() }) + it('updating v-model (value) when selects correct option', async () => { + const wrapper = mount(Select, { + propsData: { + options: ['one', 'two', { text: 'three', value: { three: 3 } }], + value: 'one' + } + }) + const $options = wrapper.findAll('option') + expect($options.length).toBe(3) + + expect($options.at(0).element.selected).toBe(true) + + // select 2nd option + wrapper.setProps({ + value: 'two' + }) + + expect($options.at(1).element.selected).toBe(true) + + // select 3rd option + wrapper.setProps({ + value: { three: 3 } + }) + + expect($options.at(2).element.selected).toBe(true) + + wrapper.destroy() + }) + it('updates v-model when option selected in single mode with complex values', async () => { const wrapper = mount(Select, { propsData: { diff --git a/src/components/pagination/pagination.spec.js b/src/components/pagination/pagination.spec.js index 4c4f5a2c5c9..092b74f1f25 100644 --- a/src/components/pagination/pagination.spec.js +++ b/src/components/pagination/pagination.spec.js @@ -134,6 +134,35 @@ describe('pagination', () => { wrapper.destroy() }) + it('renders corerct number of elements when total-rows changes', async () => { + const wrapper = mount(Pagination, { + propsData: { + size: 'sm', + totalRows: 1, + perPage: 1, + limit: 10 + } + }) + expect(wrapper.is('ul')).toBe(true) + expect(wrapper.findAll('li').length).toBe(5) + + wrapper.setProps({ + totalRows: 4 + }) + + expect(wrapper.is('ul')).toBe(true) + expect(wrapper.findAll('li').length).toBe(8) + + wrapper.setProps({ + perPage: 2 + }) + + expect(wrapper.is('ul')).toBe(true) + expect(wrapper.findAll('li').length).toBe(6) + + wrapper.destroy() + }) + it('has class "pagination-sm" when prop size="sm"', async () => { const wrapper = mount(Pagination, { propsData: { diff --git a/src/components/table/README.md b/src/components/table/README.md index 47406c46df2..6ed400ea9fe 100644 --- a/src/components/table/README.md +++ b/src/components/table/README.md @@ -323,14 +323,14 @@ The following field properties are recognized: | Property | Type | Description | | --------------- | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `key` | String | The key for selecting data from the record in the items array. Required when setting the `fields` from as an array of objects. | +| `key` | String | The key for selecting data from the record in the items array. Required when setting the `fields` via an array of objects. | | `label` | String | Appears in the columns table header (and footer if `foot-clone` is set). Defaults to the field's key (in humanized format) if not provided. It's possible to use empty labels by assigning an empty string `""` but be sure you also set `headerTitle` to provide non-sighted users a hint about the column contents. | | `headerTitle` | String | Text to place on the fields header `
` data ``/`` heading ``/`` field `` | | `dark` | Boolean | Invert the colors — with light text on dark backgrounds (equivalent to Bootstrap V4 class `.table-dark`) | -| `fixed` | Boolean | Generate a table with equal fixed-width columns (`table-layout: fixed`) | +| `fixed` | Boolean | Generate a table with equal fixed-width columns (`table-layout: fixed;`) | | `foot-clone` | Boolean | Turns on the table footer, and defaults with the same contents a the table header | | `no-footer-sorting` | Boolean | When `foot-clone` is true and the table is sortable, disables the sorting icons and click behaviour on the footer heading cells. Refer to the [**Sorting**](#sorting) section below for more details. | | `responsive` | Boolean or String | Generate a responsive table to make it scroll horizontally. Set to `true` for an always responsive table, or set it to one of the breakpoints `'sm'`, `'md'`, `'lg'`, or `'xl'` to make the table responsive (horizontally scroll) only on screens smaller than the breakpoint. See [**Responsive tables**](#responsive-tables) below for details. | @@ -522,7 +522,7 @@ You can also style every row using the `tbody-tr-class` prop ``` -## Responsive tables +### Responsive tables Responsive tables allow tables to be scrolled horizontally with ease. Make any table responsive across all viewports by setting the prop `responsive` to `true`. Or, pick a maximum breakpoint with @@ -594,7 +594,7 @@ values: `sm`, `md`, `lg`, or `xl`. clips off any content that goes beyond the bottom or top edges of the table. In particular, this may clip off dropdown menus and other third-party widgets. -## Stacked tables +### Stacked tables An alternative to responsive tables, BootstrapVue includes the stacked table option (using custom SCSS/CSS), which allow tables to be rendered in a visually stacked format. Make any table stacked @@ -647,7 +647,78 @@ The prop `stacked` takes precedence over the `responsive` prop. - In an always stacked table, the table header and footer, and the fixed top and bottom row slots will not be rendered. -## Table caption +### Table busy state + +`` provides a `busy` prop that will flag the table as busy, which you can set to `true` +just before you update your items, and then set it to `false` once you have your items. When in the +busy state, the table will have the attribute `aria-busy="true"`. + +During the busy state, the table will be rendered in a "muted" look (`opacity: 0.6`), using the +following custom CSS: + +```css +/* Busy table styling */ +table.b-table[aria-busy='false'] { + opacity: 1; +} +table.b-table[aria-busy='true'] { + opacity: 0.6; +} +``` + +You can override this styling using your own CSS. + +You may optionally provide a `table-busy` slot to show a custom loading message or spinner whenever +the table's busy state is `true`. The slot will be placed in a `` element with class +`b-table-busy-slot`, which has one single `` and `` elements for optional grouping and styling of table columns. Note the styles available via `` elements are limited. @@ -726,77 +797,6 @@ Slot `table-colgroup` can be optionally scoped, receiving an object with the fol | `columns` | Number | The number of columns in the rendered table | | `fields` | Array | Array of field defintion objects (normalized to the array of objects format) | -## Table busy state - -`` provides a `busy` prop that will flag the table as busy, which you can set to `true` -just before you update your items, and then set it to `false` once you have your items. When in the -busy state, the table will have the attribute `aria-busy="true"`. - -During the busy state, the table will be rendered in a "muted" look (`opacity: 0.6`), using the -following custom CSS: - -```css -/* Busy table styling */ -table.b-table[aria-busy='false'] { - opacity: 1; -} -table.b-table[aria-busy='true'] { - opacity: 0.6; -} -``` - -You can override this styling using your own CSS. - -You may optionally provide a `table-busy` slot to show a custom loading message or spinner whenever -the table's busy state is `true`. The slot will be placed in a `` element with class -`b-table-busy-slot`, which has one single `
` attribute `title`. Defaults to no `title` attribute. | | `headerAbbr` | String | Text to place on the fields header `` attribute `abbr`. Set this to the unabbreviated version of the label (or title) if label (or title) is an abbreviation. Defaults to no `abbr` attribute. | | `class` | String or Array | Class name (or array of class names) to add to `` **and** `` in the column. | | `formatter` | String or Function | A formatter callback function, can be used instead of (or in conjunction with) slots for real table fields (i.e. fields, that have corresponding data at items array). Refer to [**Custom Data Rendering**](#custom-data-rendering) for more details. | | `sortable` | Boolean | Enable sorting on this column. Refer to the [**Sorting**](#sorting) Section for more details. | -| `sortDirection` | String | Change sort direction on this column. Refer to the [**Change sort direction**](#change-sort-direction) Section for more details. | +| `sortDirection` | String | Set the initial sort direction on this column when it becomes sorted. Refer to the [**Change initial sort direction**](#Change-initial-sort-direction) Section for more details. | | `tdClass` | String or Array or Function | Class name (or array of class names) to add to `
` cells in the column. If custom classes per cell are required, a callback function can be specified instead. | | `thClass` | String or Array | Class name (or array of class names) to add to `
` cell. | | `thStyle` | Object | JavaScript object representing CSS styles you would like to apply to the table `
`. | @@ -413,7 +413,7 @@ place a unique `:key` on your element/components in your custom formatted field | `small` | Boolean | To make tables more compact by cutting cell padding in half. | | `hover` | Boolean | To enable a hover highlighting state on table rows within a `
` with a `colspan` set to the number of fields. + +**Example of `table-busy` slot usage:** + +```html + + + + + +``` + +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. + +### Table caption Add an optional caption to your table via the prop `caption` or the named slot `table-caption` (the slot takes precedence over the prop). The default Bootstrap V4 styling places the caption at the @@ -712,7 +783,7 @@ You can have the caption placed at the top of the table by setting the `caption- You can also use [custom CSS](https://developer.mozilla.org/en-US/docs/Web/CSS/caption-side) to control the caption positioning. -## Table colgroup +### Table colgroup Use the named slot `table-colgroup` to specify `
` with a `colspan` set to the number of fields. - -**Example of `table-busy` slot usage:** - -```html - - - - - -``` - -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. - ## Custom Data Rendering Custom rendering for each data field in a row is possible using either @@ -915,8 +915,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 @@ -1309,10 +1315,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 @@ -1427,16 +1443,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 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

- - - - - - - - - - - - - - - - - - Table Caption - - - - - - -

Paginated Table

-
-
- - - - -
-
- - - -
-
- -
-
- - - - - - - - - -

Dark Table

-
-
- -
-
- - - - - - -

Provider Test Table

-
- - - -
- - - - - - - -
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..03c8b67a8ed --- /dev/null +++ b/src/components/table/helpers/mixin-filtering.js @@ -0,0 +1,200 @@ +import stringifyRecordValues from './stringify-record-values' +import looseEqual from '../../../utils/loose-equal' +import warn from '../../../utils/warn' + +export default { + props: { + filter: { + // Pasing a function to filter is deprecated and should be avoided + type: [String, RegExp, Object, Array, Function], + default: null + }, + 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: Supplying a function to prop "filter" is deprecated. Use "filterFn" instead.' + ) + /* 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-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 08db431058d..4be4107feb9 100644 --- a/src/components/table/helpers/mixin-tbody-row.js +++ b/src/components/table/helpers/mixin-tbody-row.js @@ -149,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) @@ -194,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) diff --git a/src/components/table/helpers/mixin-thead.js b/src/components/table/helpers/mixin-thead.js index c00ade5cb0e..b342252d77d 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 = @@ -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/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..77bc43ff967 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: { 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 b495d528900..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: { 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 4ea42025199..9ea365c71f2 100644 --- a/src/components/table/table-tbody-row-events.spec.js +++ b/src/components/table/table-tbody-row-events.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 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: { 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..c00112176d4 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,6 +96,10 @@ 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: '', diff --git a/src/components/table/table-thead-events.spec.js b/src/components/table/table-thead-events.spec.js index bd5c8de46e8..1ed2b0a2bbc 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,6 +114,10 @@ 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: '', 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 1678a43955e..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 @@ -225,203 +152,26 @@ export default { ...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)) { @@ -433,73 +183,6 @@ 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) { // Build the caption (from caption mixin) const $caption = this.renderCaption() diff --git a/src/components/table/table.spec.js b/src/components/table/table.spec.js index ac4cca5f4f7..e67e06ad135 100644 --- a/src/components/table/table.spec.js +++ b/src/components/table/table.spec.js @@ -1,764 +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 } - } + }) + + 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) + + wrapper.destroy() }) - 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') + 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 + } + }) + + expect(wrapper).toBeDefined() + expect(wrapper.findAll('tbody > tr').length).toBe(1) + expect(wrapper.find('tbody > tr').classes()).toContain('table-primary') + + wrapper.setProps({ + dark: true + }) + + expect(wrapper.findAll('tbody > tr').length).toBe(1) + expect(wrapper.find('tbody > tr').classes()).toContain('bg-primary') + + wrapper.destroy() }) - 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') + it('item _cellVariants works', async () => { + const wrapper = mount(Table, { + propsData: { + items: [{ a: 1, _cellVariants: { a: 'info' } }], + fields: ['a'], + dark: false } - } + }) + + 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') + + wrapper.setProps({ + dark: true + }) + + 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') + + 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('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') + + 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) - it('should render stacked table', async () => { - const { app } = window - const vm = app.$refs.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(vm).toHaveAllClasses(['b-table-stacked']) + 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/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/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 */ From 12d5b0e5f24c27582d3a5d12be15f7f8cbc99f85 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Fri, 22 Mar 2019 00:36:32 -0300 Subject: [PATCH 21/48] chore(docs): minor fixes (#2894) --- src/components/pagination-nav/README.md | 2 +- src/components/pagination/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/pagination-nav/README.md b/src/components/pagination-nav/README.md index f54527cc260..70dfa5b10d2 100644 --- a/src/components/pagination-nav/README.md +++ b/src/components/pagination-nav/README.md @@ -283,7 +283,7 @@ below. Prev Next Last -
+
diff --git a/src/components/pagination/README.md b/src/components/pagination/README.md index ff882e54e01..6be1ea15af5 100644 --- a/src/components/pagination/README.md +++ b/src/components/pagination/README.md @@ -132,7 +132,7 @@ For a full list of all available slots see the [Slots](#comp-ref-b-pagination-sl Prev Next Last -
+
From 04deaefb13e6d58f89992fc22f99e7f56d368e17 Mon Sep 17 00:00:00 2001 From: ruudz Date: Fri, 22 Mar 2019 10:34:53 +0100 Subject: [PATCH 22/48] docs(form-checkbox): fixed closing `` tag in contextual state and validation example (#2896) Fixed closing tag in Contextual state and validation example. --- src/components/form-checkbox/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/form-checkbox/README.md b/src/components/form-checkbox/README.md index b2899fb471c..e2d8a31ea2a 100644 --- a/src/components/form-checkbox/README.md +++ b/src/components/form-checkbox/README.md @@ -416,7 +416,7 @@ To apply one of the contextual state icons on ``, set the `stat
Please select two - Thank you + Thank you
From 7d9c3111b46e5aa2103cd11b8e0f417e70b27430 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Fri, 22 Mar 2019 09:22:39 -0300 Subject: [PATCH 23/48] Update ISSUE_TEMPLATE.md --- .github/ISSUE_TEMPLATE.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index c86f19f64be..d3c61a9669a 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,3 +1,8 @@ + + **Before opening an issue:** - [Search for duplicate or closed issues](https://github.com/bootstrap-vue/bootstrap-vue/issues?utf8=%E2%9C%93&q=is%3Aissue) From 6c1940d4a9660425fc80bea55445e047844a772a Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Fri, 22 Mar 2019 11:43:04 -0300 Subject: [PATCH 24/48] fix(table): fix SSR mismatch errors (#2897) --- src/components/table/helpers/mixin-thead.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/table/helpers/mixin-thead.js b/src/components/table/helpers/mixin-thead.js index b342252d77d..748c4e86d7e 100644 --- a/src/components/table/helpers/mixin-thead.js +++ b/src/components/table/helpers/mixin-thead.js @@ -106,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 From 9d4408d6bebcc2859b8fa1c383c59b692e126099 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Fri, 22 Mar 2019 15:47:17 -0300 Subject: [PATCH 25/48] fix(utils/dom): update closest routine to support SVG (#2901) --- src/utils/dom.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 } From e9a8e8547b1248f01705eaa981ca35afe644bc4a Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Fri, 22 Mar 2019 16:20:05 -0300 Subject: [PATCH 26/48] feat(forms): new b-form-datalist helper component (#2899) --- src/components/form-input/README.md | 4 ++ src/components/form/README.md | 36 ++++++++++++++ src/components/form/form-datalist.js | 25 ++++++++++ src/components/form/form-datalist.spec.js | 57 +++++++++++++++++++++++ src/components/form/index.js | 3 ++ src/components/form/package.json | 6 ++- 6 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 src/components/form/form-datalist.js create mode 100644 src/components/form/form-datalist.spec.js diff --git a/src/components/form-input/README.md b/src/components/form-input/README.md index 60443073b8e..fe96476374b 100644 --- a/src/components/form-input/README.md +++ b/src/components/form-input/README.md @@ -448,6 +448,10 @@ chosen, or new values to be entered. ``` +BootstrapVue provides the form helper component +[``](/docs/components/form/#datalist-helper) for quickly creating a `` +from an array of options. + **Notes:** - Datalists work in conjunction with the browser's built in auto-complete, displaying datalist diff --git a/src/components/form/README.md b/src/components/form/README.md index f8b6c15f8ce..b3ad601b177 100644 --- a/src/components/form/README.md +++ b/src/components/form/README.md @@ -177,6 +177,7 @@ See also: - `` Help text blocks for inputs - `` Invalid feedback text blocks for input `invalid` states - `` Valid feedback text blocks for input `valid` states +- `` Create `` for use with `` or plain `` See also: [``](/docs/components/form-group) Form input wrapper to generate form-groups that support labels, help text and feedback @@ -262,6 +263,41 @@ or the `force-show` prop to display the feedback. ``` +### Datalist helper + +For broswers that support +[``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/datalist) elements, +the `` helper component will allow you to quickly create a `` and +child `
From 8bad401ec3a9447e127c0163dde147b523a4a8d5 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 23 Mar 2019 21:41:58 -0300 Subject: [PATCH 29/48] Update navbar.js --- src/components/navbar/navbar.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/navbar/navbar.js b/src/components/navbar/navbar.js index d53761d2ae2..548bcbe7d7f 100644 --- a/src/components/navbar/navbar.js +++ b/src/components/navbar/navbar.js @@ -1,4 +1,5 @@ import { mergeData } from 'vue-functional-data-merge' +import { getBreakpointsAll } from '../../utils/config' export const props = { tag: { @@ -36,7 +37,8 @@ export default { props, render(h, { props, data, children }) { let breakpoint = '' - if (props.toggleable && typeof props.toggleable === 'string' && props.toggleable !== 'xs') { + let xs = getBreakpointsAll()[0] + if (props.toggleable && typeof props.toggleable === 'string' && props.toggleable !== xs) { breakpoint = `navbar-expand-${props.toggleable}` } else if (props.toggleable === false) { breakpoint = 'navbar-expand' From 66505c795daefd7258c52652dc403ab396478542 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 23 Mar 2019 21:42:38 -0300 Subject: [PATCH 30/48] Update navbar.js --- src/components/navbar/navbar.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/navbar/navbar.js b/src/components/navbar/navbar.js index 548bcbe7d7f..d53761d2ae2 100644 --- a/src/components/navbar/navbar.js +++ b/src/components/navbar/navbar.js @@ -1,5 +1,4 @@ import { mergeData } from 'vue-functional-data-merge' -import { getBreakpointsAll } from '../../utils/config' export const props = { tag: { @@ -37,8 +36,7 @@ export default { props, render(h, { props, data, children }) { let breakpoint = '' - let xs = getBreakpointsAll()[0] - if (props.toggleable && typeof props.toggleable === 'string' && props.toggleable !== xs) { + if (props.toggleable && typeof props.toggleable === 'string' && props.toggleable !== 'xs') { breakpoint = `navbar-expand-${props.toggleable}` } else if (props.toggleable === false) { breakpoint = 'navbar-expand' From a17de35db4e59d1755b771ea5b2d8ccff2bfc65c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" Date: Sun, 24 Mar 2019 14:45:51 +0100 Subject: [PATCH 31/48] Update dependency rollup to ^1.7.2 (#2906) --- package.json | 2 +- yarn.lock | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index f6b2a4e268c..c35965eb4c0 100644 --- a/package.json +++ b/package.json @@ -118,7 +118,7 @@ "nuxt": "^2.5.1", "postcss-cli": "^6.1.2", "prettier": "1.14.3", - "rollup": "^1.7.0", + "rollup": "^1.7.2", "rollup-plugin-babel": "^4.3.2", "rollup-plugin-commonjs": "^9.2.1", "rollup-plugin-node-resolve": "^4.0.1", diff --git a/yarn.lock b/yarn.lock index c04c95f3bd0..51819ab1cf0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1472,11 +1472,16 @@ 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": +"@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" resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8" @@ -9493,13 +9498,13 @@ rollup-watch@^4.3.1: require-relative "0.8.7" rollup-pluginutils "^2.0.1" -rollup@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.7.0.tgz#2f5063c0f344f2225d1077655dc54d105a512bb2" - integrity sha512-hjuWSCgoQsFSTsmsNP4AH1l1kfkFqW82gW00V9nL81Zr3JtnKn3rvxh18jUAAEMb7qNoHj21PR5SqbK2mhBgMg== +rollup@^1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.7.2.tgz#6a2c50843915cba2c1432028e741f2ccb39e8ca3" + integrity sha512-HdbjbHsvoLDwJU/lWEyktVlLdXR7mtnNxRnzaDb+fiNiboAxDYCpwLz1zLGI3+k890PacAptSGrqJLJnABMKJg== dependencies: "@types/estree" "0.0.39" - "@types/node" "^11.9.5" + "@types/node" "^11.11.6" acorn "^6.1.1" rsvp@^4.8.4: From 44d03515568ad0ab1dd5e0eb0251d25e72b4da01 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann <31919422+gitlab-winnie@users.noreply.github.com> Date: Mon, 25 Mar 2019 01:26:07 +0100 Subject: [PATCH 32/48] feat: add BOOTSTRAP_VUE_NO_WARN environment variable to hide warnings (#2826) --- docs/markdown/misc/settings/README.md | 21 +++++++++++++ docs/markdown/misc/settings/meta.json | 3 ++ src/utils/env.js | 6 ++++ src/utils/warn.js | 6 +++- src/utils/warn.spec.js | 45 +++++++++++++++++++++++++++ 5 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 docs/markdown/misc/settings/README.md create mode 100644 docs/markdown/misc/settings/meta.json create mode 100644 src/utils/warn.spec.js diff --git a/docs/markdown/misc/settings/README.md b/docs/markdown/misc/settings/README.md new file mode 100644 index 00000000000..8ddb63f3227 --- /dev/null +++ b/docs/markdown/misc/settings/README.md @@ -0,0 +1,21 @@ +# Miscellaneous Settings + +## Disabling BootstrapVue console warnings + +BootstrapVue will warn (via `console.warn`) when you try and use a depreated prop, or pass +an invalid value to certain props. These warnings are provided to help you ensure that your +application is using the correct props and values. + +In some cases, you may want to disable these warnings (not recommended). You can do so by +setting the following process envinronment variable: + + + +```js +process.env.BOOTSTRAP_VUE_NO_WARN = true +``` + +By ignoring warnings, you may find that your project fails/breaks when using future releases +of bootstrapVue where deprecated props have been removed. + +Warnings should be corrected before moving your project into production! diff --git a/docs/markdown/misc/settings/meta.json b/docs/markdown/misc/settings/meta.json new file mode 100644 index 00000000000..1ed130b5c74 --- /dev/null +++ b/docs/markdown/misc/settings/meta.json @@ -0,0 +1,3 @@ +{ + "title": "Settings" +} 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/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}`) + }) + }) +}) From 1e89e0fe410a10e9543eae94fd3fb6e037e32edd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" Date: Mon, 25 Mar 2019 07:27:32 +0100 Subject: [PATCH 33/48] chore(deps): update dependency rollup to ^1.7.3 (#2907) --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index c35965eb4c0..4ecf141d8f0 100644 --- a/package.json +++ b/package.json @@ -118,7 +118,7 @@ "nuxt": "^2.5.1", "postcss-cli": "^6.1.2", "prettier": "1.14.3", - "rollup": "^1.7.2", + "rollup": "^1.7.3", "rollup-plugin-babel": "^4.3.2", "rollup-plugin-commonjs": "^9.2.1", "rollup-plugin-node-resolve": "^4.0.1", diff --git a/yarn.lock b/yarn.lock index 51819ab1cf0..96fc41c0769 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9498,10 +9498,10 @@ rollup-watch@^4.3.1: require-relative "0.8.7" rollup-pluginutils "^2.0.1" -rollup@^1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.7.2.tgz#6a2c50843915cba2c1432028e741f2ccb39e8ca3" - integrity sha512-HdbjbHsvoLDwJU/lWEyktVlLdXR7mtnNxRnzaDb+fiNiboAxDYCpwLz1zLGI3+k890PacAptSGrqJLJnABMKJg== +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.11.6" From b184e192dad025e3b4ead56bcd44d76ae6333cb5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" Date: Mon, 25 Mar 2019 07:38:26 +0100 Subject: [PATCH 34/48] chore(deps): update dependency rollup-plugin-commonjs to ^9.2.2 (#2908) --- package.json | 2 +- yarn.lock | 23 +++++++++-------------- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 4ecf141d8f0..e49725f03a0 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,7 @@ "prettier": "1.14.3", "rollup": "^1.7.3", "rollup-plugin-babel": "^4.3.2", - "rollup-plugin-commonjs": "^9.2.1", + "rollup-plugin-commonjs": "^9.2.2", "rollup-plugin-node-resolve": "^4.0.1", "rollup-watch": "^4.3.1", "sass-loader": "^7.1.0", diff --git a/yarn.lock b/yarn.lock index 96fc41c0769..bcd04f1b2c1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4368,11 +4368,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" @@ -6892,7 +6887,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== @@ -9462,15 +9457,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" @@ -9481,7 +9476,7 @@ 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: +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== From 3bef9812f007ba4b575b7fd3936f0a19d3983856 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Mon, 25 Mar 2019 10:32:35 -0300 Subject: [PATCH 35/48] fix(dropdown): fix `no-caret` prop when dropleft (fixes #2909) (#2910) * fix(dropdown): fix hide-caret when dropleft (fixes #2909) Fixes #2909 * Update _dropdown.scss * Update _dropdown.scss --- src/components/dropdown/_dropdown.scss | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/components/dropdown/_dropdown.scss b/src/components/dropdown/_dropdown.scss index 954283006a2..de84b0999fc 100644 --- a/src/components/dropdown/_dropdown.scss +++ b/src/components/dropdown/_dropdown.scss @@ -1,8 +1,21 @@ // Hide the caret for no-caret setting // See: https://github.com/bootstrap-vue/bootstrap-vue/issues/1473 // See: https://github.com/twbs/bootstrap/issues/23724 -.dropdown-toggle { - &.dropdown-toggle-no-caret:after { - display: none !important; +.dropdown { + &:not(.dropleft) { + .dropdown-toggle { + &.dropdown-toggle-no-caret:after { + display: none !important; + } + } + } + + // See: https://github.com/bootstrap-vue/bootstrap-vue/issues/2909 + &.dropleft { + .dropdown-toggle { + &.dropdown-toggle-no-caret:before { + display: none !important; + } + } } } From 6f38d9dc91720b5552ad2596f4b003dfe47cb2ef Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Mon, 25 Mar 2019 15:03:55 -0300 Subject: [PATCH 36/48] feat(form-checkbox/radio): allow no label in plain mode (fixes #2911) (#2912) --- .../form-checkbox/form-checkbox.spec.js | 58 +++++++++++++++++++ src/mixins/form-radio-check.js | 37 ++++++++---- 2 files changed, 83 insertions(+), 12 deletions(-) diff --git a/src/components/form-checkbox/form-checkbox.spec.js b/src/components/form-checkbox/form-checkbox.spec.js index b6d0c0ce3da..0ac0492b53d 100644 --- a/src/components/form-checkbox/form-checkbox.spec.js +++ b/src/components/form-checkbox/form-checkbox.spec.js @@ -58,6 +58,35 @@ describe('form-checkbox', () => { wrapper.destroy() }) + it('default does not have aria-label attribute on input', async () => { + const wrapper = mount(Input, { + propsData: { + checked: false + }, + slots: { + default: 'foobar' + } + }) + expect(wrapper.find('input').attributes('aria-label')).not.toBeDefined() + + wrapper.destroy() + }) + + it('has aria-label attribute on input when aria-label provided', async () => { + const wrapper = mount(Input, { + propsData: { + checked: false, + ariaLabel: 'bar' + }, + slots: { + default: 'foo' + } + }) + expect(wrapper.find('input').attributes('aria-label')).toBe('bar') + + wrapper.destroy() + }) + it('default has input class custom-control-input', async () => { const wrapper = mount(Input, { propsData: { @@ -70,6 +99,7 @@ describe('form-checkbox', () => { const input = wrapper.find('input') expect(input.classes().length).toEqual(1) expect(input.classes()).toContain('custom-control-input') + expect(input.classes()).not.toContain('position-static') wrapper.destroy() }) @@ -481,6 +511,34 @@ describe('form-checkbox', () => { wrapper.destroy() }) + it('plain does not have class position-static when label provided', async () => { + const wrapper = mount(Input, { + propsData: { + plain: true, + checked: false + }, + slots: { + default: 'foobar' + } + }) + expect(wrapper.find('input').classes()).not.toContain('position-static') + + wrapper.destroy() + }) + + it('plain has no label when no default slot content', async () => { + const wrapper = mount(Input, { + propsData: { + plain: true, + checked: false + } + }) + expect(wrapper.find('label').exists()).toBe(false) + expect(wrapper.find('input').classes()).toContain('position-static') + + wrapper.destroy() + }) + it('plain has no input validation classes by default', async () => { const wrapper = mount(Input, { propsData: { 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', From 67d12a4075a0722a9078f4be3bf56a2d10c191a6 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Mon, 25 Mar 2019 18:47:59 -0300 Subject: [PATCH 37/48] chore(docs): fix typo in pagination-nav docs --- src/components/pagination-nav/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/pagination-nav/README.md b/src/components/pagination-nav/README.md index 70dfa5b10d2..639dd7116ae 100644 --- a/src/components/pagination-nav/README.md +++ b/src/components/pagination-nav/README.md @@ -398,9 +398,9 @@ be set as `active`. To disable auto active page detection, set the `no-page-detect` prop to `true`. -**Note:** Auto page detection needs to loop through all possible page links until a match is detected. -For larget `number-of-pages`, this check can take some time so you may want to manually control -which page is the active via the `v-model` and the `no-page-detect` prop. +**Note:** Auto page detection needs to loop through all possible page links until a match is +detected. For larger `number-of-pages`, this check can take some time so you may want to manually +control which page is the active via the `v-model` and the `no-page-detect` prop. ## Accessibility From 8018bdf07329e43cb4051ff2a7526176967c610f Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Mon, 25 Mar 2019 19:26:05 -0300 Subject: [PATCH 38/48] feat(core): create configurable base global configuration (#2905) --- docs/components/anchored-heading.js | 24 + docs/components/componentdoc.vue | 165 ++++-- docs/components/importdoc.vue | 103 ++-- docs/content/index.js | 3 + docs/layouts/docs.vue | 6 +- docs/markdown/intro/README.md | 19 + docs/markdown/misc/settings/README.md | 151 ++++- docs/markdown/misc/settings/meta.json | 3 +- docs/markdown/reference/theming/README.md | 7 +- docs/pages/docs/components/_slug.vue | 15 +- docs/pages/docs/directives/_slug.vue | 12 +- docs/pages/docs/index.js | 10 +- docs/pages/docs/misc/_slug.vue | 12 +- nuxt/index.js | 5 + nuxt/plugin.template.js | 8 +- src/bv-config.js | 12 + src/components/alert/alert.js | 7 +- src/components/alert/index.js | 6 +- src/components/badge/badge.js | 10 +- src/components/badge/index.js | 6 +- src/components/breadcrumb/index.js | 6 +- src/components/button-group/index.js | 6 +- src/components/button-toolbar/index.js | 6 +- src/components/button/button-close.js | 9 +- src/components/button/button.js | 9 +- src/components/button/index.js | 6 +- src/components/card/card-sub-title.js | 7 +- src/components/card/index.js | 6 +- src/components/carousel/carousel.js | 4 + src/components/carousel/index.js | 6 +- src/components/collapse/index.js | 13 +- src/components/dropdown/dropdown.js | 10 +- src/components/dropdown/index.js | 6 +- src/components/embed/index.js | 6 +- src/components/form-checkbox/index.js | 6 +- src/components/form-file/form-file.js | 15 +- src/components/form-file/index.js | 6 +- src/components/form-group/form-group.js | 554 +++++++++--------- src/components/form-group/index.js | 6 +- src/components/form-input/index.js | 6 +- src/components/form-radio/index.js | 6 +- src/components/form-select/form-select.js | 6 +- src/components/form-select/index.js | 6 +- src/components/form-textarea/index.js | 6 +- src/components/form/README.md | 3 + src/components/form/form-text.js | 7 +- src/components/form/index.js | 6 +- src/components/image/img-lazy.js | 7 +- src/components/image/img.js | 5 +- src/components/image/index.js | 6 +- src/components/input-group/index.js | 7 +- src/components/jumbotron/index.js | 6 +- src/components/layout/col.js | 215 +++---- src/components/layout/index.js | 6 +- src/components/link/index.js | 6 +- src/components/list-group/index.js | 6 +- src/components/media/index.js | 6 +- src/components/modal/index.js | 13 +- src/components/modal/modal-defaults.spec.js | 42 ++ src/components/modal/modal.js | 16 +- src/components/nav/index.js | 13 +- src/components/navbar/index.js | 21 +- src/components/navbar/navbar.js | 4 +- src/components/pagination-nav/index.js | 6 +- src/components/pagination/index.js | 6 +- src/components/popover/index.js | 13 +- src/components/progress/index.js | 6 +- src/components/spinner/index.js | 6 +- .../table/helpers/mixin-filtering.js | 10 +- src/components/table/index.js | 6 +- src/components/tabs/index.js | 6 +- src/components/tabs/tabs.js | 4 + src/components/tooltip/index.js | 13 +- src/directives/modal/index.js | 10 +- src/directives/popover/index.js | 10 +- src/directives/scrollspy/index.js | 10 +- src/directives/toggle/index.js | 10 +- src/directives/tooltip/index.js | 10 +- src/index.js | 26 +- src/mixins/pagination.js | 4 + src/utils/clone-deep.js | 17 + src/utils/clone-deep.spec.js | 70 +++ src/utils/config.js | 205 +++++++ src/utils/config.spec.js | 169 ++++++ src/utils/plugins.js | 41 +- 85 files changed, 1615 insertions(+), 739 deletions(-) create mode 100644 docs/components/anchored-heading.js create mode 100644 src/bv-config.js create mode 100644 src/components/modal/modal-defaults.spec.js create mode 100644 src/utils/clone-deep.js create mode 100644 src/utils/clone-deep.spec.js create mode 100644 src/utils/config.js create mode 100644 src/utils/config.spec.js diff --git a/docs/components/anchored-heading.js b/docs/components/anchored-heading.js new file mode 100644 index 00000000000..e391ffef630 --- /dev/null +++ b/docs/components/anchored-heading.js @@ -0,0 +1,24 @@ +export default { + props: { + level: { + type: [Number, String], + default: 2 + }, + id: { + type: String, + default: '' + } + }, + render(h) { + const $anchor = h( + 'b-link', + { + staticClass: 'anchorjs-link', + attrs: { to: { hash: `#${this.id}` }, 'aria-label': 'Anchor' } + }, + [h(false)] + ) + const $content = h('span', { staticClass: 'bd-content-title' }, [this.$slots.default, $anchor]) + return h(`h${this.level}`, { attrs: { id: this.id, tabindex: '-1' } }, [$content]) + } +} diff --git a/docs/components/componentdoc.vue b/docs/components/componentdoc.vue index 5521ae909d9..7d807db2900 100644 --- a/docs/components/componentdoc.vue +++ b/docs/components/componentdoc.vue @@ -5,7 +5,9 @@ > -

{{ tag }}

+ + {{ tag }} +
@@ -15,7 +17,9 @@
-

Component aliases

+ + Component aliases +

{{ tag }} can also be used via the following aliases:

  • <{{ kebabCase(alias) }}>
  • @@ -23,7 +27,9 @@
-

Properties

+ + Properties + -