diff --git a/addon/components/ember-thead/component.js b/addon/components/ember-thead/component.js index 5f1541d20..006b86bba 100644 --- a/addon/components/ember-thead/component.js +++ b/addon/components/ember-thead/component.js @@ -8,6 +8,7 @@ import { notEmpty, or } from '@ember/object/computed'; import { closest } from '../../-private/utils/element'; import { sortMultiple, compareValues } from '../../-private/utils/sort'; +import { scheduleOnce } from '@ember/runloop'; import ColumnTree, { RESIZE_MODE, FILL_MODE, WIDTH_CONSTRAINT } from '../../-private/column-tree'; @@ -195,7 +196,7 @@ export default Component.extend({ this.addObserver('reorderFunction', this._updateApi); this.addObserver('sorts', this._updateColumnTree); - this.addObserver('columns', this._updateColumnTree); + this.addObserver('columns.[]', this._onColumnsChange); this.addObserver('fillMode', this._updateColumnTree); this.addObserver('resizeMode', this._updateColumnTree); this.addObserver('widthConstraint', this._updateColumnTree); @@ -225,6 +226,14 @@ export default Component.extend({ this.columnTree.set('enableReorder', this.get('enableReorder')); }, + _onColumnsChange() { + if (this.get('columns.length') === 0) { + return; + } + this._updateColumnTree(); + scheduleOnce('actions', this, this.fillupHandler); + }, + didInsertElement() { this._super(...arguments); @@ -232,10 +241,7 @@ export default Component.extend({ this.columnTree.registerContainer(this._container); - this._tableResizeSensor = new ResizeSensor( - this._container, - bind(this.fillupHandler.bind(this)) - ); + this._tableResizeSensor = new ResizeSensor(this._container, bind(this, this.fillupHandler)); }, willDestroyElement() { diff --git a/tests/integration/components/headers/ember-thead-test.js b/tests/integration/components/headers/ember-thead-test.js new file mode 100644 index 000000000..29bb79c84 --- /dev/null +++ b/tests/integration/components/headers/ember-thead-test.js @@ -0,0 +1,191 @@ +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; +import { click } from 'ember-native-dom-helpers'; +import TablePage from 'ember-table/test-support/pages/ember-table'; +import { A } from '@ember/array'; +import RSVP from 'rsvp'; + +// This is a "waiter"-style helper to use to ensure that +// the interior of the ember-table has finished all of its +// layout-related logic has finished. If we don't use this to +// wait after `render`, the column widths will not have been +// set yet and the tests in this module are moot. +async function rafFinished() { + return new RSVP.Promise(resolve => { + requestAnimationFrame(() => requestAnimationFrame(resolve)); + }); +} + +function tableData() { + return { + rows: [ + { A: 'A', B: 'B', C: 'C', D: 'D', E: 'E' }, + { A: 'A', B: 'B', C: 'C', D: 'D', E: 'E' }, + { A: 'A', B: 'B', C: 'C', D: 'D', E: 'E' }, + ], + columns: [ + { name: 'A', valuePath: 'A', width: 180 }, + { name: 'B', valuePath: 'B', width: 180 }, + { name: 'C', valuePath: 'C', width: 180 }, + { name: 'D', valuePath: 'D', width: 180 }, + ], + newColumn: { name: 'E', valuePath: 'E', width: 180 }, + }; +} + +function sumHeaderWidths(table) { + return table.headers.map(h => h.width).reduce((sum, w) => sum + w, 0); +} + +async function renderTable(context) { + await context.render(hbs` + + + {{#ember-table data-test-ember-table=true as |t|}} + {{#ember-thead + api=t + widthConstraint='eq-container' + columns=columns as |h|}} + {{#ember-tr api=h as |r|}} + {{ember-th api=r}} + {{/ember-tr}} + {{/ember-thead}} + + {{#ember-tbody api=t rows=rows as |b|}} + {{#ember-tr api=b as |r|}} + {{#ember-td api=r as |cellValue|}} + {{cellValue}} + {{/ember-td}} + {{/ember-tr}} + {{/ember-tbody}} + {{/ember-table}} + `); + + await rafFinished(); +} + +async function testColumnRemovals(assert, table) { + let originalWidth = table.width; + let originalContainerWidth = table.containerWidth; + + let currentColumnCount = table.headers.length; + assert.equal(currentColumnCount, 4, 'precond - 4 columns'); + assert.equal(sumHeaderWidths(table), originalWidth, 'precond - headers sum to table width'); + assert.equal( + originalWidth, + originalContainerWidth, + 'precond - table is as wide as its container' + ); + + while (currentColumnCount > 1) { + await click('button#remove-column'); + assert.equal( + table.headers.length, + currentColumnCount - 1, + `column count changes from ${currentColumnCount} -> ${currentColumnCount - 1}` + ); + currentColumnCount -= 1; + + assert.equal( + table.width, + originalWidth, + `table width is same after removal of column #${currentColumnCount}.` + ); + assert.equal( + table.containerWidth, + originalContainerWidth, + `new table container is same size as original container after removal of column #${currentColumnCount}.` + ); + assert.equal( + sumHeaderWidths(table), + originalWidth, + `headers sum to table width after removal of column #${currentColumnCount}` + ); + assert.equal( + table.width, + table.containerWidth, + `new table width is as wide as its container after removal of column #${currentColumnCount}.` + ); + } +} + +async function testColumnAddition(assert, table) { + let originalWidth = table.width; + let originalContainerWidth = table.containerWidth; + + let currentColumnCount = table.headers.length; + assert.equal(currentColumnCount, 4, 'precond - 4 columns'); + assert.equal(sumHeaderWidths(table), originalWidth, 'precond - headers sum to table width'); + assert.equal( + originalWidth, + originalContainerWidth, + 'precond - table is as wide as its container' + ); + + await click('#add-column'); + assert.equal(table.headers.length, 5, 'column is added'); + assert.equal(sumHeaderWidths(table), originalWidth, 'headers sum to table width after adding'); + assert.equal(table.width, originalWidth, 'table width is unchanged'); + assert.equal(table.containerWidth, originalContainerWidth, 'table container width is unchanged'); +} + +// In Ember-1.12, the `this` context has a `context` property itself that is used to `get` or `set`. +// This function provides Ember-1.12 compatibility for `this.get` +function compatGet(context, propName) { + return context.get ? context.get(propName) : context.context.get(propName); +} + +// This function provides Ember-1.12 compatibility for `this.set` +function compatSet(context, propName, value) { + return context.set ? context.set(propName, value) : context.context.set(propName, value); +} + +moduleForComponent('ember-thead', '[Unit] ember-thead', { integration: true }); + +test('table resizes when columns are removed', async function(assert) { + let data = tableData(); + this.set('rows', data.rows); + this.set('columns', data.columns); + this.on('removeColumn', function() { + compatSet(this, 'columns', compatGet(this, 'columns').slice(0, -1)); + }); + + await renderTable(this); + await testColumnRemovals(assert, new TablePage()); +}); + +test('table resizes when columns are removed via mutation', async function(assert) { + let data = tableData(); + this.set('rows', data.rows); + this.set('columns', A(data.columns)); + this.on('removeColumn', function() { + compatGet(this, 'columns').popObject(); + }); + + await renderTable(this); + await testColumnRemovals(assert, new TablePage()); +}); + +test('table resizes when columns are added', async function(assert) { + let data = tableData(); + this.set('rows', data.rows); + this.set('columns', data.columns); + this.on('addColumn', function() { + compatSet(this, 'columns', [...data.columns, data.newColumn]); + }); + + await renderTable(this); + await testColumnAddition(assert, new TablePage()); +}); + +test('table resizes when columns are added via mutation', async function(assert) { + let data = tableData(); + this.set('rows', data.rows); + this.set('columns', A(data.columns)); + this.on('addColumn', function() { + compatGet(this, 'columns').pushObject(data.newColumn); + }); + + await renderTable(this); + await testColumnAddition(assert, new TablePage()); +});