From e64ee3a58ebe346bcdba290f2809a648418d7e65 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 18 Nov 2018 16:43:07 -0400 Subject: [PATCH 01/17] feat(table): Add table-busy slot for loading status. Fixes #1859 Adds a new slot `table-busy` which allows users to display a loading message when the table is in the busy state. If no `table-busy` slot is present, the current rows are displayed (as was the behaviour before) Closes #1859 --- src/components/table/table.js | 224 +++++++++++++++++++--------------- 1 file changed, 124 insertions(+), 100 deletions(-) diff --git a/src/components/table/table.js b/src/components/table/table.js index 0c3acda9933..54824a23204 100644 --- a/src/components/table/table.js +++ b/src/components/table/table.js @@ -222,122 +222,146 @@ export default { rows.push(h(false)) } - // Add the item data rows - items.forEach((item, rowIndex) => { - const detailsSlot = $scoped['row-details'] - const rowShowDetails = Boolean(item._showDetails && detailsSlot) - // Details ID needed for aria-describedby when details showing - const detailsId = rowShowDetails - ? this.safeId(`_details_${rowIndex}_`) - : null - const toggleDetailsFn = () => { - if (detailsSlot) { - this.$set(item, '_showDetails', !item._showDetails) - } - } - // For each item data field in row - const tds = fields.map((field, colIndex) => { - const formatted = this.getFormattedValue(item, field) - const data = { - key: `row-${rowIndex}-cell-${colIndex}`, - class: this.tdClasses(field, item), - attrs: this.tdAttrs(field, item, colIndex), - domProps: {} - } - let childNodes - if ($scoped[field.key]) { - childNodes = [ - $scoped[field.key]({ - item: item, - index: rowIndex, - field: field, - unformatted: _get(item, field.key, ''), - value: formatted, - toggleDetails: toggleDetailsFn, - detailsShowing: Boolean(item._showDetails) - }) - ] - if (this.isStacked) { - // We wrap in a DIV to ensure rendered as a single cell when visually stacked! - childNodes = [h('div', {}, [childNodes])] - } - } else { - if (this.isStacked) { - // We wrap in a DIV to ensure rendered as a single cell when visually stacked! - childNodes = [h('div', formatted)] - } else { - // Non stacked - childNodes = formatted - } - } - // Render either a td or th cell - return h(field.isRowHeader ? 'th' : 'td', data, childNodes) - }) - // Calculate the row number in the dataset (indexed from 1) - let ariaRowIndex = null - if (this.currentPage && this.perPage && this.perPage > 0) { - ariaRowIndex = String((this.currentPage - 1) * this.perPage + rowIndex + 1) + // Add the item data rows or the busy slot + if (this.computedBusy && $slots['table-busy']) { + // show the busy slot + const tdAttrs = { colspan: String(fields.length) } + const trAttrs = { } + if (this.isStacked) { + tdAttrs['role'] = 'cell' + trAttrs['role'] = 'row' } - // Assemble and add the row + const busyContent = h('td', { attrs: tdAttrs }, $slots['table-busy']) rows.push( h( 'tr', { - key: `row-${rowIndex}`, - class: [ - this.rowClasses(item), - { 'b-table-has-details': rowShowDetails } - ], - attrs: { - 'aria-describedby': detailsId, - 'aria-owns': detailsId, - 'aria-rowindex': ariaRowIndex, - role: this.isStacked ? 'row' : null - }, - on: { - click: evt => { this.rowClicked(evt, item, rowIndex) }, - contextmenu: evt => { this.rowContextmenu(evt, item, rowIndex) }, - dblclick: evt => { this.rowDblClicked(evt, item, rowIndex) }, - mouseenter: evt => { this.rowHovered(evt, item, rowIndex) }, - mouseleave: evt => { this.rowUnhovered(evt, item, rowIndex) } - } + key: 'table-busy-slot', + staticClass: 'b-table-busy-slot', + class: [typeof this.tbodyTrClass === 'function' ? this.tbodyTrClass(item, 'table-busy') : this.tbodyTrClass], + attrs: trAttrs }, - tds + [busyContent] ) ) - // Row Details slot - if (rowShowDetails) { - const tdAttrs = { colspan: String(fields.length) } - const trAttrs = { id: detailsId } - if (this.isStacked) { - tdAttrs['role'] = 'cell' - trAttrs['role'] = 'row' + } else { + // Show the rows + items.forEach((item, rowIndex) => { + const detailsSlot = $scoped['row-details'] + const rowShowDetails = Boolean(item._showDetails && detailsSlot) + // Details ID needed for aria-describedby when details showing + const detailsId = rowShowDetails + ? this.safeId(`_details_${rowIndex}_`) + : null + const toggleDetailsFn = () => { + if (detailsSlot) { + this.$set(item, '_showDetails', !item._showDetails) + } + } + // For each item data field in row + const tds = fields.map((field, colIndex) => { + const formatted = this.getFormattedValue(item, field) + const data = { + key: `row-${rowIndex}-cell-${colIndex}`, + class: this.tdClasses(field, item), + attrs: this.tdAttrs(field, item, colIndex), + domProps: {} + } + let childNodes + if ($scoped[field.key]) { + childNodes = [ + $scoped[field.key]({ + item: item, + index: rowIndex, + field: field, + unformatted: _get(item, field.key, ''), + value: formatted, + toggleDetails: toggleDetailsFn, + detailsShowing: Boolean(item._showDetails) + }) + ] + if (this.isStacked) { + // We wrap in a DIV to ensure rendered as a single cell when visually stacked! + childNodes = [h('div', {}, [childNodes])] + } + } else { + if (this.isStacked) { + // We wrap in a DIV to ensure rendered as a single cell when visually stacked! + childNodes = [h('div', formatted)] + } else { + // Non stacked + childNodes = formatted + } + } + // Render either a td or th cell + return h(field.isRowHeader ? 'th' : 'td', data, childNodes) + }) + // Calculate the row number in the dataset (indexed from 1) + let ariaRowIndex = null + if (this.currentPage && this.perPage && this.perPage > 0) { + ariaRowIndex = String((this.currentPage - 1) * this.perPage + rowIndex + 1) } - const details = h('td', { attrs: tdAttrs }, [ - detailsSlot({ - item: item, - index: rowIndex, - fields: fields, - toggleDetails: toggleDetailsFn - }) - ]) + // Assemble and add the row rows.push( h( 'tr', { - key: `details-${rowIndex}`, - staticClass: 'b-table-details', - class: [typeof this.tbodyTrClass === 'function' ? this.tbodyTrClass(item, 'row-details') : this.tbodyTrClass], - attrs: trAttrs + key: `row-${rowIndex}`, + class: [ + this.rowClasses(item), + { 'b-table-has-details': rowShowDetails } + ], + attrs: { + 'aria-describedby': detailsId, + 'aria-owns': detailsId, + 'aria-rowindex': ariaRowIndex, + role: this.isStacked ? 'row' : null + }, + on: { + click: evt => { this.rowClicked(evt, item, rowIndex) }, + contextmenu: evt => { this.rowContextmenu(evt, item, rowIndex) }, + dblclick: evt => { this.rowDblClicked(evt, item, rowIndex) }, + mouseenter: evt => { this.rowHovered(evt, item, rowIndex) }, + mouseleave: evt => { this.rowUnhovered(evt, item, rowIndex) } + } }, - [details] + tds ) ) - } else if (detailsSlot) { - // Only add the placeholder if a the table has a row-details slot defined (but not shown) - rows.push(h(false)) - } - }) + // Row Details slot + if (rowShowDetails) { + const tdAttrs = { colspan: String(fields.length) } + const trAttrs = { id: detailsId } + if (this.isStacked) { + tdAttrs['role'] = 'cell' + trAttrs['role'] = 'row' + } + const details = h('td', { attrs: tdAttrs }, [ + detailsSlot({ + item: item, + index: rowIndex, + fields: fields, + toggleDetails: toggleDetailsFn + }) + ]) + rows.push( + h( + 'tr', + { + key: `details-${rowIndex}`, + staticClass: 'b-table-details', + class: [typeof this.tbodyTrClass === 'function' ? this.tbodyTrClass(item, 'row-details') : this.tbodyTrClass], + attrs: trAttrs + }, + [details] + ) + ) + } else if (detailsSlot) { + // Only add the placeholder if a the table has a row-details slot defined (but not shown) + rows.push(h(false)) + } + }) + } // Empty Items / Empty Filtered Row slot if (this.showEmpty && (!items || items.length === 0)) { From a02c4e989c0543243fdf66ab98904c17d384c2f8 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 18 Nov 2018 16:46:09 -0400 Subject: [PATCH 02/17] lint --- src/components/table/table.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/table/table.js b/src/components/table/table.js index 54824a23204..3f38c49267a 100644 --- a/src/components/table/table.js +++ b/src/components/table/table.js @@ -238,7 +238,7 @@ export default { { key: 'table-busy-slot', staticClass: 'b-table-busy-slot', - class: [typeof this.tbodyTrClass === 'function' ? this.tbodyTrClass(item, 'table-busy') : this.tbodyTrClass], + class: [typeof this.tbodyTrClass === 'function' ? this.tbodyTrClass(null, 'table-busy') : this.tbodyTrClass], attrs: trAttrs }, [busyContent] From a8ed4ba2437baa110db5a8018db90afbe95ba6e0 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 18 Nov 2018 16:52:08 -0400 Subject: [PATCH 03/17] document new slot in package.json --- src/components/table/package.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/table/package.json b/src/components/table/package.json index a54af6cef23..c684895dc83 100755 --- a/src/components/table/package.json +++ b/src/components/table/package.json @@ -157,6 +157,10 @@ "name": "table-colgroup", "description": "Slot to place custom colgroup and col elements" }, + { + "name": "table-busy", + "description": "Optional slot to place loading message when table is in the busy state" + }, { "name": "[field]", "description": "Scoped slot for custom data rendering of field data. See docs for scoped data" From eccd7d8f329d2b62a04eb51d38305071159ade5c Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 18 Nov 2018 17:39:54 -0400 Subject: [PATCH 04/17] Update table.js --- src/components/table/table.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/table/table.js b/src/components/table/table.js index 3f38c49267a..ef495e7730e 100644 --- a/src/components/table/table.js +++ b/src/components/table/table.js @@ -223,15 +223,15 @@ export default { } // Add the item data rows or the busy slot - if (this.computedBusy && $slots['table-busy']) { - // show the busy slot - const tdAttrs = { colspan: String(fields.length) } - const trAttrs = { } - if (this.isStacked) { - tdAttrs['role'] = 'cell' - trAttrs['role'] = 'row' + if ($slots['table-busy'] && this.computedBusy) { + // Show the busy slot + const trAttrs = { + role: this.isStacked ? 'row' : null + } + const tdAttrs = { + colspan: String(fields.length), + role: this.isStacked ? 'cell' : null } - const busyContent = h('td', { attrs: tdAttrs }, $slots['table-busy']) rows.push( h( 'tr', @@ -241,7 +241,7 @@ export default { class: [typeof this.tbodyTrClass === 'function' ? this.tbodyTrClass(null, 'table-busy') : this.tbodyTrClass], attrs: trAttrs }, - [busyContent] + [h('td', { attrs: tdAttrs }, [$slots['table-busy']])] ) ) } else { From 1bdf1b54d9cd3947f9cc7c6e6a6388263120e292 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 18 Nov 2018 18:17:26 -0400 Subject: [PATCH 05/17] Update README.md --- src/components/table/README.md | 93 +++++++++++++++++++++++++++++----- 1 file changed, 81 insertions(+), 12 deletions(-) diff --git a/src/components/table/README.md b/src/components/table/README.md index 5b9b85aca1d..290c3828ba1 100755 --- a/src/components/table/README.md +++ b/src/components/table/README.md @@ -609,6 +609,73 @@ elements are limited. Refer to [MDN](https://developer.mozilla.org/en-US/docs/We for details and usage of `` +## 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 hte busy state, the tabe 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 `` 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 +informaton 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 [scoped slots](http://vuejs.org/v2/guide/components.html#Scoped-Slots) @@ -1226,22 +1293,23 @@ function myProvider (ctx) { } ``` -`` automatically tracks/controls it's `busy` state, however it provides -a `busy` prop that can be used either to override inner `busy`state, or to monitor -``'s current busy state in your application using the 2-way `.sync` modifier. +### Automated table busy state +`` automatically tracks/controls it's `busy` state when items provider functions are +used, however it also provides a `busy` prop that can be used either to override the inner `busy` +state, or to monitor ``'s current busy state in your application using the 2-way `.sync` modifier. -**Note:** in order to allow `` fully track it's `busy` state, custom items +**Note:** in order to allow `` fully track it's `busy` state, the custom items provider function should handle errors from data sources and return an empty array to ``. -`` provides a `busy` prop that will flag the table as busy, which you can -set to `true` just before your async fetch, and then set it to `false` once you have -your data, and just before you send it to the table for display. Example: - +**Example: usage of busy state** ```html - + + ``` - ```js data () { return { @@ -1250,8 +1318,8 @@ data () { } methods: { myProvider (ctx) { - // Here we don't set isBusy prop, so busy state will be handled by table itself - // this.isBusy = true + // Here we don't set isBusy prop, so busy state will be handled by table itself + // this.isBusy = true let promise = axios.get('/some/url') return promise.then((data) => { @@ -1276,6 +1344,7 @@ __not__ be called/refreshed until the `busy` state has been set to `false`. emitted when in the `busy` state (either set automatically during provider update, or when manually set). + ### Provider Paging, Filtering, and Sorting By default, the items provider function is responsible for **all paging, filtering, and sorting** of the data, before passing it to `b-table` for display. From 77cead9145d3e3d1bcf626ec6b370f5ce497cecd Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 18 Nov 2018 18:41:05 -0400 Subject: [PATCH 06/17] Create table-busy.spec.js Add test suite for b-table busy state --- src/components/table/table-busy.spec.js | 95 +++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 src/components/table/table-busy.spec.js diff --git a/src/components/table/table-busy.spec.js b/src/components/table/table-busy.spec.js new file mode 100644 index 00000000000..6954e6bf4f8 --- /dev/null +++ b/src/components/table/table-busy.spec.js @@ -0,0 +1,95 @@ +import Table from './table' +import { mount } from '@vue/test-utils' + +const testItems = [ + {a: 1, b: 2, c: 3 }, + {a: 1, b: 2, c: 3 }, + {a: 1, b: 2, c: 3 } +] + +describe('b-table busy state', async () => { + it('default should have attribute aria-busy=false', async () => { + const wrapper = mount(Table, { + propsData: { + items: testItems + } + }) + expect(wrapper.attributes('aria-busy')).toBeDefined() + expect(wrapper.attributes('aria-busy')).toEqual('false') + }) + + it('default should have rows rendered', async () => { + const wrapper = mount(Table, { + propsData: { + items: testItems + } + }) + 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) + }) + + it('should have attribute aria-busy=true when busy=true', async () => { + const wrapper = mount(Table, { + propsData: { + busy: true, + items: testItems + } + }) + expect(wrapper.attributes('aria-busy')).toBeDefined() + expect(wrapper.attributes('aria-busy')).toEqual('true') + }) + + it('should render table-busy slot when busy=true and slot provided', async () => { + const wrapper = mount(Table, { + propsData: { + busy: true + items: testItems + }, + slots: { + 'table-busy': 'busy slot content' + } + }) + expect(wrapper.attributes('aria-busy')).toBeDefined() + expect(wrapper.attributes('aria-busy')).toEqual('true') + 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) + expect(wrapper.find('tbody').text()).toContain('busy slot content') + }) + + it('should not render table-busy slot when busy=false and slot provided', async () => { + const wrapper = mount(Table, { + propsData: { + busy: false + items: testItems + }, + slots: { + 'table-busy': 'busy slot content' + } + }) + expect(wrapper.attributes('aria-busy')).toBeDefined() + expect(wrapper.attributes('aria-busy')).toEqual('true') + 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) + expect(wrapper.find('tbody').text()).not.toContain('busy slot content') + }) + + it('should set class b-table-busy-slot on tbody>tr when busy=true and slot provided', async () => { + const wrapper = mount(Table, { + propsData: { + busy: true + items: testItems + }, + slots: { + 'table-busy': 'busy slot content' + } + }) + expect(wrapper.attributes('aria-busy')).toBeDefined() + expect(wrapper.attributes('aria-busy')).toEqual('true') + expect(wrapper.find('tbody').exists()).toBe(true) + expect(wrapper.find('tbody').find('tr').exists()).toBe(true) + expect(wrapper.find('tbody').find('tr').classes()).toContain('b-table-busy-slot') + }) +}) From 1710956feb1974e52f4dcdae999bc46984dbe493 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 18 Nov 2018 18:44:31 -0400 Subject: [PATCH 07/17] lint --- src/components/table/table-busy.spec.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/table/table-busy.spec.js b/src/components/table/table-busy.spec.js index 6954e6bf4f8..8a99a2e9ef8 100644 --- a/src/components/table/table-busy.spec.js +++ b/src/components/table/table-busy.spec.js @@ -43,7 +43,7 @@ describe('b-table busy state', async () => { it('should render table-busy slot when busy=true and slot provided', async () => { const wrapper = mount(Table, { propsData: { - busy: true + busy: true, items: testItems }, slots: { @@ -61,7 +61,7 @@ describe('b-table busy state', async () => { it('should not render table-busy slot when busy=false and slot provided', async () => { const wrapper = mount(Table, { propsData: { - busy: false + busy: false, items: testItems }, slots: { @@ -79,7 +79,7 @@ describe('b-table busy state', async () => { it('should set class b-table-busy-slot on tbody>tr when busy=true and slot provided', async () => { const wrapper = mount(Table, { propsData: { - busy: true + busy: true, items: testItems }, slots: { From 155ceb17a614f24f4b35189002c4292e787398a2 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 18 Nov 2018 18:46:18 -0400 Subject: [PATCH 08/17] lint --- src/components/table/table-busy.spec.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/table/table-busy.spec.js b/src/components/table/table-busy.spec.js index 8a99a2e9ef8..3633e123494 100644 --- a/src/components/table/table-busy.spec.js +++ b/src/components/table/table-busy.spec.js @@ -2,9 +2,9 @@ import Table from './table' import { mount } from '@vue/test-utils' const testItems = [ - {a: 1, b: 2, c: 3 }, - {a: 1, b: 2, c: 3 }, - {a: 1, b: 2, c: 3 } + { a: 1, b: 2, c: 3 }, + { a: 5, b: 5, c: 6 }, + { a: 7, b: 8, c: 9 } ] describe('b-table busy state', async () => { From f3f27a108d63a84681c986126c47f50ad400dbb5 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 18 Nov 2018 19:01:08 -0400 Subject: [PATCH 09/17] Update table-busy.spec.js --- src/components/table/table-busy.spec.js | 39 ++++++++++--------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/src/components/table/table-busy.spec.js b/src/components/table/table-busy.spec.js index 3633e123494..3d104a23849 100644 --- a/src/components/table/table-busy.spec.js +++ b/src/components/table/table-busy.spec.js @@ -40,25 +40,24 @@ describe('b-table busy state', async () => { expect(wrapper.attributes('aria-busy')).toEqual('true') }) - it('should render table-busy slot when busy=true and slot provided', async () => { + it('should have attribute aria-busy=true when data localBusy=true', async () => { const wrapper = mount(Table, { propsData: { - busy: true, items: testItems - }, - slots: { - 'table-busy': 'busy slot content' } }) + expect(wrapper.attributes('aria-busy')).toBeDefined() + expect(wrapper.attributes('aria-busy')).toEqual('false') + + wrapper.setData({ + localBusy: true + }) + expect(wrapper.attributes('aria-busy')).toBeDefined() expect(wrapper.attributes('aria-busy')).toEqual('true') - 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) - expect(wrapper.find('tbody').text()).toContain('busy slot content') }) - it('should not render table-busy slot when busy=false and slot provided', async () => { + it('should render table-busy slot when busy=true and slot provided', async () => { const wrapper = mount(Table, { propsData: { busy: false, @@ -69,27 +68,21 @@ describe('b-table busy state', async () => { } }) expect(wrapper.attributes('aria-busy')).toBeDefined() - expect(wrapper.attributes('aria-busy')).toEqual('true') + expect(wrapper.attributes('aria-busy')).toEqual('false') 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) - expect(wrapper.find('tbody').text()).not.toContain('busy slot content') - }) - it('should set class b-table-busy-slot on tbody>tr when busy=true and slot provided', async () => { - const wrapper = mount(Table, { - propsData: { - busy: true, - items: testItems - }, - slots: { - 'table-busy': 'busy slot content' - } + wrapper.setProps({ + busy: true }) + expect(wrapper.attributes('aria-busy')).toBeDefined() expect(wrapper.attributes('aria-busy')).toEqual('true') expect(wrapper.find('tbody').exists()).toBe(true) - expect(wrapper.find('tbody').find('tr').exists()).toBe(true) + expect(wrapper.find('tbody').findAll('tr').exists()).toBe(true) + expect(wrapper.find('tbody').findAll('tr').length).toBe(1) + expect(wrapper.find('tbody').text()).toContain('busy slot content') expect(wrapper.find('tbody').find('tr').classes()).toContain('b-table-busy-slot') }) }) From 09097d74a3133dc6f1d82a3141440f8b8a403b54 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 18 Nov 2018 19:09:28 -0400 Subject: [PATCH 10/17] Update table-busy.spec.js --- src/components/table/table-busy.spec.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/components/table/table-busy.spec.js b/src/components/table/table-busy.spec.js index 3d104a23849..094a1eb1d32 100644 --- a/src/components/table/table-busy.spec.js +++ b/src/components/table/table-busy.spec.js @@ -77,12 +77,15 @@ describe('b-table busy state', async () => { busy: true }) - expect(wrapper.attributes('aria-busy')).toBeDefined() - expect(wrapper.attributes('aria-busy')).toEqual('true') - 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) - expect(wrapper.find('tbody').text()).toContain('busy slot content') - expect(wrapper.find('tbody').find('tr').classes()).toContain('b-table-busy-slot') + // Await until next tick to check rendered DOM + return Vue.nextTick().then(function() { + expect(wrapper.attributes('aria-busy')).toBeDefined() + expect(wrapper.attributes('aria-busy')).toEqual('true') + 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) + expect(wrapper.find('tbody').text()).toContain('busy slot content') + expect(wrapper.find('tbody').find('tr').classes()).toContain('b-table-busy-slot') + }) }) }) From a0f958ddde45c169d653a6504e0f00d95dbf3782 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 18 Nov 2018 19:12:35 -0400 Subject: [PATCH 11/17] Update table-busy.spec.js --- src/components/table/table-busy.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/table/table-busy.spec.js b/src/components/table/table-busy.spec.js index 094a1eb1d32..47a079ae54a 100644 --- a/src/components/table/table-busy.spec.js +++ b/src/components/table/table-busy.spec.js @@ -78,7 +78,7 @@ describe('b-table busy state', async () => { }) // Await until next tick to check rendered DOM - return Vue.nextTick().then(function() { + return wrapper.vm.nextTick().then(function () { expect(wrapper.attributes('aria-busy')).toBeDefined() expect(wrapper.attributes('aria-busy')).toEqual('true') expect(wrapper.find('tbody').exists()).toBe(true) From 8ef528cbcbb537dc12d5bc415d08f78baba18627 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 18 Nov 2018 19:15:39 -0400 Subject: [PATCH 12/17] Update table-busy.spec.js --- src/components/table/table-busy.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/table/table-busy.spec.js b/src/components/table/table-busy.spec.js index 47a079ae54a..9a6f7fec5e9 100644 --- a/src/components/table/table-busy.spec.js +++ b/src/components/table/table-busy.spec.js @@ -78,7 +78,7 @@ describe('b-table busy state', async () => { }) // Await until next tick to check rendered DOM - return wrapper.vm.nextTick().then(function () { + return wrapper.vm.$nextTick().then(function () { expect(wrapper.attributes('aria-busy')).toBeDefined() expect(wrapper.attributes('aria-busy')).toEqual('true') expect(wrapper.find('tbody').exists()).toBe(true) From d1b2d01ecc10b2df4e249a73c002628b35ccb98a Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 18 Nov 2018 19:40:19 -0400 Subject: [PATCH 13/17] Update table-busy.spec.js --- src/components/table/table-busy.spec.js | 34 ++++++++++++++++--------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/components/table/table-busy.spec.js b/src/components/table/table-busy.spec.js index 9a6f7fec5e9..10633f519e8 100644 --- a/src/components/table/table-busy.spec.js +++ b/src/components/table/table-busy.spec.js @@ -1,5 +1,5 @@ import Table from './table' -import { mount } from '@vue/test-utils' +import { mount, shallowMount } from '@vue/test-utils' const testItems = [ { a: 1, b: 2, c: 3 }, @@ -58,13 +58,16 @@ describe('b-table busy state', async () => { }) it('should render table-busy slot when busy=true and slot provided', async () => { - const wrapper = mount(Table, { + const wrapper = shallowMount(Table, { propsData: { busy: false, items: testItems }, slots: { - 'table-busy': 'busy slot content' + // Note slot data needs to be wrapped in an element. + // https://github.com/vue/vue-test-utils/issues:992 + // Will be fixed in v1.0.0-beta.26 + 'table-busy': 'busy slot content' } }) expect(wrapper.attributes('aria-busy')).toBeDefined() @@ -77,15 +80,22 @@ describe('b-table busy state', async () => { busy: true }) - // Await until next tick to check rendered DOM - return wrapper.vm.$nextTick().then(function () { - expect(wrapper.attributes('aria-busy')).toBeDefined() - expect(wrapper.attributes('aria-busy')).toEqual('true') - 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) - expect(wrapper.find('tbody').text()).toContain('busy slot content') - expect(wrapper.find('tbody').find('tr').classes()).toContain('b-table-busy-slot') + expect(wrapper.attributes('aria-busy')).toBeDefined() + expect(wrapper.attributes('aria-busy')).toEqual('true') + 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) + expect(wrapper.find('tbody').text()).toContain('busy slot content') + expect(wrapper.find('tbody').find('tr').classes()).toContain('b-table-busy-slot') + + wrapper.setProps({ + busy: false }) + + expect(wrapper.attributes('aria-busy')).toBeDefined() + expect(wrapper.attributes('aria-busy')).toEqual('false') + 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) }) }) From d8f901d16df9716f396695c96b24b8a91dead373 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 18 Nov 2018 20:41:40 -0400 Subject: [PATCH 14/17] Update table-busy.spec.js Add provider function busy testing --- src/components/table/table-busy.spec.js | 67 +++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/components/table/table-busy.spec.js b/src/components/table/table-busy.spec.js index 10633f519e8..276e955d5e7 100644 --- a/src/components/table/table-busy.spec.js +++ b/src/components/table/table-busy.spec.js @@ -44,6 +44,9 @@ describe('b-table busy state', async () => { const wrapper = mount(Table, { propsData: { items: testItems + }, + listeners: { + busy } }) expect(wrapper.attributes('aria-busy')).toBeDefined() @@ -57,6 +60,22 @@ describe('b-table busy state', async () => { expect(wrapper.attributes('aria-busy')).toEqual('true') }) + it('should emit update:busy event when data localBusy is set to true', async () => { + const wrapper = mount(Table, { + propsData: { + items: testItems + } + }) + expect(wrapper.emitted('update:busy')).not.toBeDefined() + + wrapper.setData({ + localBusy: true + }) + + expect(wrapper.emitted('update:busy')).toBeDefined() + expect(wrapper.emitted('update:busy')[0][0]).toEqual(true) + }) + it('should render table-busy slot when busy=true and slot provided', async () => { const wrapper = shallowMount(Table, { propsData: { @@ -98,4 +117,52 @@ describe('b-table busy state', async () => { expect(wrapper.find('tbody').findAll('tr').exists()).toBe(true) expect(wrapper.find('tbody').findAll('tr').length).toBe(testItems.length) }) + + it('table-busy slot works with async provider function', async () => { + let callback = null + const providerFn = (ctx, cb) => { + // Simulate async function by letting us call calback manually + callback = cb + } + const wrapper = shallowMount(Table, { + propsData: { + fields: Object.keys(testItems[0]), + items: providerFn + }, + slots: { + 'table-busy': 'busy slot content' + } + }) + + // WHen items is a provider function, localBusy is immediately set to true + expect(wrapper.attributes('aria-busy')).toBeDefined() + expect(wrapper.attributes('aria-busy')).toEqual('true') + 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) + expect(wrapper.find('tbody').text()).toContain('busy slot content') + expect(wrapper.emitted('update:busy')).toBeDefined() + expect(wrapper.emitted('update:busy').length).toBe(1) + expect(wrapper.emitted('update:busy')[0][0]).toEqual(true) + + // Provider function is called after nextTick on mount, so we wait and call the callback + return wrapper.vm.$nextTick(() => { + // callback should now be set + expect(typeof callback).toBe('function') + + // Send the items array to b-table + callback(testItems) + + // b-table should immediately clear busy state and populate items + expect(wrapper.attributes('aria-busy')).toBeDefined() + expect(wrapper.attributes('aria-busy')).toEqual('false') + 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) + expect(wrapper.emitted('update:busy').length).toBe(2) + expect(wrapper.emitted('update:busy')[1][0]).toEqual(false) + expect(wrapper.emitted('refreshed')).toBeDefined() + expect(wrapper.emitted('refreshed').length).toBe(1) + }) + }) }) From 619b8db8f049ab537810fb67334d1be6c67663f5 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 18 Nov 2018 20:44:15 -0400 Subject: [PATCH 15/17] lint --- src/components/table/table-busy.spec.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/table/table-busy.spec.js b/src/components/table/table-busy.spec.js index 276e955d5e7..9af75a3e594 100644 --- a/src/components/table/table-busy.spec.js +++ b/src/components/table/table-busy.spec.js @@ -44,9 +44,6 @@ describe('b-table busy state', async () => { const wrapper = mount(Table, { propsData: { items: testItems - }, - listeners: { - busy } }) expect(wrapper.attributes('aria-busy')).toBeDefined() From b75e7186964943131f00929172ad70e14988d76a Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 18 Nov 2018 21:29:44 -0400 Subject: [PATCH 16/17] Update table-busy.spec.js --- src/components/table/table-busy.spec.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/components/table/table-busy.spec.js b/src/components/table/table-busy.spec.js index 9af75a3e594..595882e724a 100644 --- a/src/components/table/table-busy.spec.js +++ b/src/components/table/table-busy.spec.js @@ -1,5 +1,5 @@ import Table from './table' -import { mount, shallowMount } from '@vue/test-utils' +import { mount } from '@vue/test-utils' const testItems = [ { a: 1, b: 2, c: 3 }, @@ -74,7 +74,7 @@ describe('b-table busy state', async () => { }) it('should render table-busy slot when busy=true and slot provided', async () => { - const wrapper = shallowMount(Table, { + const wrapper = mount(Table, { propsData: { busy: false, items: testItems @@ -117,21 +117,30 @@ describe('b-table busy state', async () => { it('table-busy slot works with async provider function', async () => { let callback = null + let called = false const providerFn = (ctx, cb) => { // Simulate async function by letting us call calback manually callback = cb + called = true } - const wrapper = shallowMount(Table, { + const wrapper = mount(Table, { propsData: { fields: Object.keys(testItems[0]), - items: providerFn + items: providerFn, + busy: false }, slots: { 'table-busy': 'busy slot content' } }) - // WHen items is a provider function, localBusy is immediately set to true + expect(callback).toBe(null) + expect(called).toBe(false) + expect(typeof wrapper.props('items')).toBe('function') + expect(wrapper.vm.localBusy).toBe(true) + expect(wrapper.vm.computedBusy).toBe(true) + + // When items is a provider function, localBusy is immediately set to true expect(wrapper.attributes('aria-busy')).toBeDefined() expect(wrapper.attributes('aria-busy')).toEqual('true') expect(wrapper.find('tbody').exists()).toBe(true) From d4117b9156371d09228fa8150c9920d8674f432f Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 18 Nov 2018 22:16:34 -0400 Subject: [PATCH 17/17] Update table-busy.spec.js --- src/components/table/table-busy.spec.js | 65 ++----------------------- 1 file changed, 4 insertions(+), 61 deletions(-) diff --git a/src/components/table/table-busy.spec.js b/src/components/table/table-busy.spec.js index 595882e724a..53bf984c1d8 100644 --- a/src/components/table/table-busy.spec.js +++ b/src/components/table/table-busy.spec.js @@ -18,7 +18,7 @@ describe('b-table busy state', async () => { expect(wrapper.attributes('aria-busy')).toEqual('false') }) - it('default should have rows rendered', async () => { + it('default should have item rows rendered', async () => { const wrapper = mount(Table, { propsData: { items: testItems @@ -29,7 +29,7 @@ describe('b-table busy state', async () => { expect(wrapper.find('tbody').findAll('tr').length).toBe(testItems.length) }) - it('should have attribute aria-busy=true when busy=true', async () => { + it('should have attribute aria-busy=true when prop busy=true', async () => { const wrapper = mount(Table, { propsData: { busy: true, @@ -57,7 +57,7 @@ describe('b-table busy state', async () => { expect(wrapper.attributes('aria-busy')).toEqual('true') }) - it('should emit update:busy event when data localBusy is set to true', async () => { + it('should emit update:busy event when data localBusy is toggled', async () => { const wrapper = mount(Table, { propsData: { items: testItems @@ -73,7 +73,7 @@ describe('b-table busy state', async () => { expect(wrapper.emitted('update:busy')[0][0]).toEqual(true) }) - it('should render table-busy slot when busy=true and slot provided', async () => { + it('should render table-busy slot when prop busy=true and slot provided', async () => { const wrapper = mount(Table, { propsData: { busy: false, @@ -114,61 +114,4 @@ describe('b-table busy state', async () => { expect(wrapper.find('tbody').findAll('tr').exists()).toBe(true) expect(wrapper.find('tbody').findAll('tr').length).toBe(testItems.length) }) - - it('table-busy slot works with async provider function', async () => { - let callback = null - let called = false - const providerFn = (ctx, cb) => { - // Simulate async function by letting us call calback manually - callback = cb - called = true - } - const wrapper = mount(Table, { - propsData: { - fields: Object.keys(testItems[0]), - items: providerFn, - busy: false - }, - slots: { - 'table-busy': 'busy slot content' - } - }) - - expect(callback).toBe(null) - expect(called).toBe(false) - expect(typeof wrapper.props('items')).toBe('function') - expect(wrapper.vm.localBusy).toBe(true) - expect(wrapper.vm.computedBusy).toBe(true) - - // When items is a provider function, localBusy is immediately set to true - expect(wrapper.attributes('aria-busy')).toBeDefined() - expect(wrapper.attributes('aria-busy')).toEqual('true') - 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) - expect(wrapper.find('tbody').text()).toContain('busy slot content') - expect(wrapper.emitted('update:busy')).toBeDefined() - expect(wrapper.emitted('update:busy').length).toBe(1) - expect(wrapper.emitted('update:busy')[0][0]).toEqual(true) - - // Provider function is called after nextTick on mount, so we wait and call the callback - return wrapper.vm.$nextTick(() => { - // callback should now be set - expect(typeof callback).toBe('function') - - // Send the items array to b-table - callback(testItems) - - // b-table should immediately clear busy state and populate items - expect(wrapper.attributes('aria-busy')).toBeDefined() - expect(wrapper.attributes('aria-busy')).toEqual('false') - 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) - expect(wrapper.emitted('update:busy').length).toBe(2) - expect(wrapper.emitted('update:busy')[1][0]).toEqual(false) - expect(wrapper.emitted('refreshed')).toBeDefined() - expect(wrapper.emitted('refreshed').length).toBe(1) - }) - }) })