From f000e85326ffe019817847343c21b542ed59842d Mon Sep 17 00:00:00 2001 From: Philippe Ndiaye Date: Mon, 2 Nov 2026 16:14:46 +0100 Subject: [PATCH 1/2] Cover the table state arrays and the list renderer before dropping the array extensions --- tests/dummy/app/controllers/application.ts | 224 +++++++++--------- tests/dummy/config/ember-cli-update.json | 4 +- .../components/hyper-table-test.js | 136 +++++++++++ .../common/ordering-test.ts | 4 +- .../hyper-table-v2/manage-columns-test.ts | 1 - tests/unit/types/table-test.js | 133 +++++++++++ 6 files changed, 386 insertions(+), 116 deletions(-) create mode 100644 tests/unit/types/table-test.js diff --git a/tests/dummy/app/controllers/application.ts b/tests/dummy/app/controllers/application.ts index 5bb67e8a..e3626c1a 100644 --- a/tests/dummy/app/controllers/application.ts +++ b/tests/dummy/app/controllers/application.ts @@ -101,116 +101,120 @@ class Manager implements TableManager { class RowsFetcher { fetch(_page: number, _perPage: number): Promise { return new Promise((resolve) => { - window.setTimeout(() => resolve({ - rows: [ - { - influencerId: Math.random(), - recordId: 12, - holderId: 57, - holderType: 'list', - foo: 'ekip', - bar: 'hello', - toto: 'toto', - code: 'code', - test: 'test', - total: 123, - time: 1344549600 // should be 2012.08.10 - }, - { - influencerId: Math.random(), - recordId: 12, - holderId: 57, - holderType: 'list', - foo: 'second', - bar: 'second bar', - total: 123.393, - time: 1643289899 - }, - { - influencerId: Math.random(), - recordId: 12, - holderId: 57, - holderType: 'list', - foo: 'ekip', - bar: 'hello', - total: 123 - }, - { - influencerId: Math.random(), - recordId: 12, - holderId: 57, - holderType: 'list', - foo: 'second', - bar: 'second bar', - total: 123000 - }, - { - influencerId: Math.random(), - recordId: 12, - holderId: 57, - holderType: 'list', - foo: 'ekip', - bar: 'hello' - }, - { - influencerId: Math.random(), - recordId: 12, - holderId: 57, - holderType: 'list', - foo: 'second', - bar: 'second bar' - }, - { - influencerId: Math.random(), - recordId: 12, - holderId: 57, - holderType: 'list', - foo: 'ekip', - bar: 'hello' - }, - { - influencerId: Math.random(), - recordId: 12, - holderId: 57, - holderType: 'list', - foo: 'second', - bar: 'second bar' - }, - { - influencerId: Math.random(), - recordId: 12, - holderId: 57, - holderType: 'list', - foo: 'ekip', - bar: 'hello' - }, - { - influencerId: Math.random(), - recordId: 12, - holderId: 57, - holderType: 'list', - foo: 'second', - bar: 'second bar' - }, - { - influencerId: Math.random(), - recordId: 12, - holderId: 57, - holderType: 'list', - foo: 'ekip', - bar: 'hello' - }, - { - influencerId: Math.random(), - recordId: 12, - holderId: 57, - holderType: 'list', - foo: 'second', - bar: 'second bar' - } - ], - meta: { total: 12 } - }), 500); + window.setTimeout( + () => + resolve({ + rows: [ + { + influencerId: Math.random(), + recordId: 12, + holderId: 57, + holderType: 'list', + foo: 'ekip', + bar: 'hello', + toto: 'toto', + code: 'code', + test: 'test', + total: 123, + time: 1344549600 // should be 2012.08.10 + }, + { + influencerId: Math.random(), + recordId: 12, + holderId: 57, + holderType: 'list', + foo: 'second', + bar: 'second bar', + total: 123.393, + time: 1643289899 + }, + { + influencerId: Math.random(), + recordId: 12, + holderId: 57, + holderType: 'list', + foo: 'ekip', + bar: 'hello', + total: 123 + }, + { + influencerId: Math.random(), + recordId: 12, + holderId: 57, + holderType: 'list', + foo: 'second', + bar: 'second bar', + total: 123000 + }, + { + influencerId: Math.random(), + recordId: 12, + holderId: 57, + holderType: 'list', + foo: 'ekip', + bar: 'hello' + }, + { + influencerId: Math.random(), + recordId: 12, + holderId: 57, + holderType: 'list', + foo: 'second', + bar: 'second bar' + }, + { + influencerId: Math.random(), + recordId: 12, + holderId: 57, + holderType: 'list', + foo: 'ekip', + bar: 'hello' + }, + { + influencerId: Math.random(), + recordId: 12, + holderId: 57, + holderType: 'list', + foo: 'second', + bar: 'second bar' + }, + { + influencerId: Math.random(), + recordId: 12, + holderId: 57, + holderType: 'list', + foo: 'ekip', + bar: 'hello' + }, + { + influencerId: Math.random(), + recordId: 12, + holderId: 57, + holderType: 'list', + foo: 'second', + bar: 'second bar' + }, + { + influencerId: Math.random(), + recordId: 12, + holderId: 57, + holderType: 'list', + foo: 'ekip', + bar: 'hello' + }, + { + influencerId: Math.random(), + recordId: 12, + holderId: 57, + holderType: 'list', + foo: 'second', + bar: 'second bar' + } + ], + meta: { total: 12 } + }), + 500 + ); }); } } diff --git a/tests/dummy/config/ember-cli-update.json b/tests/dummy/config/ember-cli-update.json index f9569f06..e2cb620a 100644 --- a/tests/dummy/config/ember-cli-update.json +++ b/tests/dummy/config/ember-cli-update.json @@ -10,9 +10,7 @@ "outputRepo": "https://github.com/ember-cli/ember-addon-output", "codemodsSource": "ember-addon-codemods-manifest@1", "isBaseBlueprint": true, - "options": [ - "--no-welcome" - ] + "options": ["--no-welcome"] } ] } diff --git a/tests/integration/components/hyper-table-test.js b/tests/integration/components/hyper-table-test.js index 3d0be5d3..998a155c 100644 --- a/tests/integration/components/hyper-table-test.js +++ b/tests/integration/components/hyper-table-test.js @@ -510,4 +510,140 @@ module('Integration | Component | hyper-table', function (hooks) { assert.dom('.hypertable__state--empty').exists(); }); }); + module('arrays', function () { + test('the field categories are sorted by label', async function (assert) { + this.table = buildTable(this.owner); + this.table.updateFieldCategories([ + { key: 'z', label: 'Zeta' }, + { key: 'a', label: 'Alpha' }, + { key: 'm', label: 'Mu' } + ]); + + await render(hbs``); + + assert.deepEqual( + [...this.element.querySelectorAll('.field-category')].map((c) => c.textContent.trim()), + ['All Fields', 'Alpha', 'Mu', 'Zeta'] + ); + }); + + test('the first column filters feed the search input and clear with the search', async function (assert) { + this.table = buildTable(this.owner, { hooks: { onColumnsChange() {}, onSearchQueryChange() {} } }); + this.table.columns[0].set('filters', [{ key: 'value', value: 'foo' }]); + + await render(hbs``); + + assert.dom('.hypertable__upper-header [data-control-name="table_search_input"] input').hasValue('foo'); + + this.table.columns[0].set('filters', [{ key: 'value', value: 'bar' }]); + await settled(); + + assert.dom('.hypertable__upper-header [data-control-name="table_search_input"] input').hasValue('bar'); + }); + + test('reordering the columns keeps the first one and notifies the hook', async function (assert) { + const onColumnsChange = sinon.spy(); + this.table = buildTable(this.owner, { hooks: { onColumnsChange } }); + this.table.fields.find((f) => f.key === 'extra').set('visible', true); + this.table.updateColumns([ + ...this.table.columns, + { key: 'extra', type: 'text', orderable: true, filterable: true } + ]); + + await render(hbs``); + const component = + this.owner.lookup('-view-registry:main')[this.element.querySelector('.hypertable-container').id]; + const [first, score, extra] = this.table.columns; + + component.reorderColumns([score, extra]); + assert.false(onColumnsChange.called); + + component.reorderColumns([extra, score]); + assert.true(onColumnsChange.calledWith('columns:reorder')); + assert.deepEqual( + this.table.columns.map((c) => c.key), + [first.key, 'extra', 'score'] + ); + }); + + test('removing then adding a column updates the rendered columns', async function (assert) { + this.table = buildTable(this.owner, { hooks: { onColumnsChange() {} } }); + + await render(hbs``); + assert.dom('.hypertable > .hypertable__column').exists({ count: 1 }); + + await this.table.toggleColumnVisibility(this.table.fields.find((f) => f.key === 'score')); + await settled(); + assert.dom('.hypertable > .hypertable__column').doesNotExist(); + + await this.table.toggleColumnVisibility(this.table.fields.find((f) => f.key === 'score')); + await settled(); + assert.dom('.hypertable > .hypertable__column').exists({ count: 1 }); + }); + + test('a predefined view disappears from the list once added', async function (assert) { + this.view = { name: 'predef', table: {} }; + this.noop = () => {}; + this.manager = EmberObject.create({ + availableTableViews: true, + views: [], + predefinedViews: [this.view], + hooks: { onAddView() {}, onSelectView() {} } + }); + + await render(hbs``); + assert.dom('.views-container hr').exists(); + + await click('.views-container > div'); + + assert.dom('.views-container hr').doesNotExist(); + }); + + test('the editing status of a cell is rendered once the edition starts', async function (assert) { + this.table = buildTable(this.owner, { hooks: { onLiveEdit() {} } }); + this.item = this.table.data[0]; + this.column = this.table.columns[0]; + this.column.set('upsertable', true); + this.item.set('name', null); + + await render( + hbs`` + ); + await click('span'); + + assert.dom('.cell-container').hasClass('editing'); + assert.strictEqual(this.table.editStatus.length, 1); + }); + + test('list: it renders the first element of a list of strings', async function (assert) { + this.table = buildTable(this.owner); + this.item = this.table.data[0]; + this.column = this.table.columns[0]; + this.item.set('name', ['alpha', 'beta', 'gamma']); + + await render( + hbs`` + ); + + assert.dom('.list-container > div').hasText('alpha'); + assert.dom('.list-elements').includesText('+2 more'); + }); + + test('list: it renders the first name of a list of objects', async function (assert) { + this.table = buildTable(this.owner); + this.item = this.table.data[0]; + this.column = this.table.columns[0]; + this.item.set('name', [ + { id: 1, name: 'one' }, + { id: 2, name: 'two' } + ]); + + await render( + hbs`` + ); + + assert.dom('.list-container > div').hasText('one'); + assert.dom('.list-elements').includesText('+1 more'); + }); + }); }); diff --git a/tests/integration/components/hyper-table-v2/filtering-renderers/common/ordering-test.ts b/tests/integration/components/hyper-table-v2/filtering-renderers/common/ordering-test.ts index b7b816dd..a62be5ba 100644 --- a/tests/integration/components/hyper-table-v2/filtering-renderers/common/ordering-test.ts +++ b/tests/integration/components/hyper-table-v2/filtering-renderers/common/ordering-test.ts @@ -72,14 +72,14 @@ module('Integration | Component | hyper-table-v2/filtering-renderers/common/orde assert.strictEqual(this.column.order, undefined); await click('.oss-toggle-buttons-container .oss-toggle-buttons-btn:nth-child(1)'); - assert.ok(applyOrderSpy.calledWith(this.column, 'asc')); + assert.ok(applyOrderSpy.calledWith(this.column, 'asc')); assert.deepEqual(this.column.order, { direction: 'asc', key: 'date' }); await click('.oss-toggle-buttons-container .oss-toggle-buttons-btn:nth-child(2)'); - assert.ok(applyOrderSpy.calledWith(this.column, 'desc')); + assert.ok(applyOrderSpy.calledWith(this.column, 'desc')); assert.deepEqual(this.column.order, { direction: 'desc', key: 'date' diff --git a/tests/integration/components/hyper-table-v2/manage-columns-test.ts b/tests/integration/components/hyper-table-v2/manage-columns-test.ts index 9683a8b1..6297bf04 100644 --- a/tests/integration/components/hyper-table-v2/manage-columns-test.ts +++ b/tests/integration/components/hyper-table-v2/manage-columns-test.ts @@ -118,7 +118,6 @@ module('Integration | Component | hyper-table-v2/manage-columns', function (hook assert.strictEqual(clusterNames[0].textContent?.trim(), 'instagram'); assert.strictEqual(clusterNames[1].textContent?.trim(), 'x'); assert.strictEqual(clusterNames[2].textContent?.trim(), 'youtube'); - }); test('it sets the category as active when user select a category', async function (assert) { diff --git a/tests/unit/types/table-test.js b/tests/unit/types/table-test.js new file mode 100644 index 00000000..050f9fe7 --- /dev/null +++ b/tests/unit/types/table-test.js @@ -0,0 +1,133 @@ +import EmberObject from '@ember/object'; + +import { extendArrayPrototype } from 'dummy/tests/helpers/array-prototype-extensions'; +import { setupTest } from 'ember-qunit'; +import { module, test } from 'qunit'; + +import Column from '@upfluence/hypertable/types/column'; + +const field = (key, props = {}) => EmberObject.create({ key, type: 'text', ...props }); + +module('Unit | Type | table', function (hooks) { + setupTest(hooks); + extendArrayPrototype(hooks); + + hooks.beforeEach(function () { + this.table = this.owner.lookup('service:hypertable-manager').createTable({}, { onColumnsChange() {} }); + }); + + module('updateColumns', function () { + test('it keeps the columns that have a field and decorates them', function (assert) { + const fields = [field('name', { size: 'L' }), field('score'), field('other')]; + this.table.updateFields(fields); + + this.table.updateColumns([ + { key: 'name', orderable: true, filterable: true }, + { key: 'unknown' }, + { key: 'score', orderable: true, filterable: true, filters: [{ key: 'lower_bound', value: '1' }] } + ]); + + assert.deepEqual( + this.table.columns.map((c) => c.key), + ['name', 'score'] + ); + assert.strictEqual(this.table.columns[0].size, 'L'); + assert.false(this.table.columns[0].orderable); + assert.true(this.table.columns[1].orderable); + assert.strictEqual(this.table.columns[1].filters[0].value, '1'); + assert.strictEqual(this.table.columns[1].field, fields[1]); + assert.true(fields[0].visible); + assert.false(fields[0].toggleable); + assert.true(fields[1].toggleable); + assert.strictEqual(fields[2].visible, undefined); + }); + }); + + module('updateData', function () { + test('it initializes the selection of the new rows', function (assert) { + this.table.updateData([EmberObject.create({ id: 1 }), EmberObject.create({ id: 2 })]); + + assert.deepEqual( + this.table.data.map((row) => row.selected), + [false, false] + ); + }); + + test('it selects the new rows when every row is selected', function (assert) { + this.table.set('_allRowsSelected', true); + + this.table.updateData([EmberObject.create({ id: 1 })]); + + assert.true(this.table.data[0].selected); + }); + + test('it keeps the selection of the rows that are already loaded', function (assert) { + this.table.updateData([EmberObject.create({ id: 1 })]); + this.table.data[0].set('selected', true); + + this.table.updateData([EmberObject.create({ id: 1 }), EmberObject.create({ id: 2 })]); + + assert.deepEqual( + this.table.data.map((row) => row.selected), + [true, false] + ); + }); + + test('it accepts an empty list', function (assert) { + this.table.updateData([]); + + assert.strictEqual(this.table.data.length, 0); + }); + }); + + module('updateViews', function () { + test('it only keeps the predefined views that are not already saved', function (assert) { + this.table.updateViews([{ name: 'a' }], [{ name: 'a' }, { name: 'b' }]); + + assert.deepEqual( + this.table.predefinedViews.map((v) => v.name), + ['b'] + ); + }); + }); + + module('toggleColumnVisibility', function () { + test('it removes a visible column and adds it back', async function (assert) { + const fields = [field('name'), field('score')]; + this.table.updateFields(fields); + this.table.updateColumns([{ key: 'name' }, { key: 'score' }]); + const notified = []; + this.table.addObserver('columns.[]', () => notified.push(this.table.columns.length)); + + assert.strictEqual(await this.table.toggleColumnVisibility(fields[1]), 'removal'); + assert.deepEqual( + this.table.columns.map((c) => c.key), + ['name'] + ); + assert.false(fields[1].visible); + + assert.strictEqual(await this.table.toggleColumnVisibility(fields[1]), 'addition'); + assert.deepEqual( + this.table.columns.map((c) => c.key), + ['name', 'score'] + ); + assert.true(fields[1].visible); + assert.deepEqual(notified, [1, 2]); + }); + }); + + module('column', function () { + test('applyFacets joins the identifiers of the facets', function (assert) { + const onColumnsChange = (key) => assert.step(key); + const column = Column.create({ manager: { hooks: { onColumnsChange } } }); + + column.applyFacets('brand', [{ identifier: 'a' }, { identifier: 'b' }]); + + assert.deepEqual(column.filters, [{ key: 'brand', value: 'a,b' }]); + + column.applyFacets('brand', []); + assert.deepEqual(column.filters, []); + assert.verifySteps(['columns:change', 'columns:change']); + }); + }); +}); From ebded8b30bfe2b3e2a60aea25c02036fc571c479 Mon Sep 17 00:00:00 2001 From: Philippe Ndiaye Date: Mon, 2 Nov 2026 16:16:35 +0100 Subject: [PATCH 2/2] Replace the Ember array extensions with native array code in the classic hyper-table --- .../hyper-table-v2/manage-columns.ts | 9 ++-- .../hyper-table/cell-renderers/list.hbs | 4 +- .../hyper-table/cell-renderers/list.js | 10 ++-- .../hyper-table/filters-renderers/date.js | 4 +- .../hyper-table/filters-renderers/money.js | 4 +- .../hyper-table/filters-renderers/numeric.js | 6 +-- .../hyper-table/filters-renderers/text.js | 4 +- addon/components/hyper-table/index.hbs | 5 +- addon/components/hyper-table/index.js | 50 ++++++++++--------- addon/components/hyper-table/views.hbs | 6 +-- addon/components/hyper-table/views.js | 8 ++- addon/mixins/editable.js | 14 +++--- addon/types/column.js | 2 +- addon/types/table.js | 23 +++++---- tests/helpers/array-prototype-extensions.js | 25 ---------- .../components/hyper-table-test.js | 2 - tests/unit/types/table-test.js | 2 - 17 files changed, 82 insertions(+), 96 deletions(-) delete mode 100644 tests/helpers/array-prototype-extensions.js diff --git a/addon/components/hyper-table-v2/manage-columns.ts b/addon/components/hyper-table-v2/manage-columns.ts index 770c44f4..86c7d59e 100644 --- a/addon/components/hyper-table-v2/manage-columns.ts +++ b/addon/components/hyper-table-v2/manage-columns.ts @@ -1,7 +1,6 @@ -import { A } from '@ember/array'; import { set, action } from '@ember/object'; import { later, next } from '@ember/runloop'; -import { isEmpty } from '@ember/utils'; +import { compare, isEmpty } from '@ember/utils'; import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; @@ -37,15 +36,15 @@ export default class HyperTableV2ManageColumns extends Component { - const fields = A( - (this.args.handler.columnDefinitions || []).filter((columnDefinition) => { + const fields = (this.args.handler.columnDefinitions || []) + .filter((columnDefinition) => { const search = (this.searchColumnDefinitionKeyword || '').toLowerCase(); const hasSearched = !this.searchColumnDefinitionKeyword || columnDefinition.name.toLowerCase().indexOf(search) >= 0; const hasActiveGroup = !this.activeColumnCategory || columnDefinition.category === this.activeColumnCategory; return hasActiveGroup && hasSearched; }) - ).sortBy('name'); + .sort((a, b) => compare(a.name, b.name)); return this.groupByClusteringKey(fields); } diff --git a/addon/components/hyper-table/cell-renderers/list.hbs b/addon/components/hyper-table/cell-renderers/list.hbs index 2086a779..5b3e5517 100644 --- a/addon/components/hyper-table/cell-renderers/list.hbs +++ b/addon/components/hyper-table/cell-renderers/list.hbs @@ -2,9 +2,9 @@ — {{else}}
- {{#if this.value.firstObject.name}} + {{#if this.firstItem.name}}
- {{this.value.firstObject.name}} + {{this.firstItem.name}}
{{#if (eq this.manager.tetherOn this.elementId)}}
diff --git a/addon/components/hyper-table/cell-renderers/list.js b/addon/components/hyper-table/cell-renderers/list.js index 5a35c480..fafaded2 100644 --- a/addon/components/hyper-table/cell-renderers/list.js +++ b/addon/components/hyper-table/cell-renderers/list.js @@ -10,9 +10,13 @@ export default Component.extend(CellRendererMixin, { isEditing: false, - formattedList: computed('value', 'value.firstObject.name', function () { - if (this.value.firstObject.name) { - return this.value.mapBy('name').join('
'); + firstItem: computed('value.[]', function () { + return this.value.slice()[0]; + }), + + formattedList: computed('value.[]', 'firstItem.name', function () { + if (this.firstItem.name) { + return this.value.map((item) => item.name).join('
'); } return this.value.slice().join('
'); }), diff --git a/addon/components/hyper-table/filters-renderers/date.js b/addon/components/hyper-table/filters-renderers/date.js index 3fe3e558..c6e62a5e 100644 --- a/addon/components/hyper-table/filters-renderers/date.js +++ b/addon/components/hyper-table/filters-renderers/date.js @@ -19,8 +19,8 @@ export default class DateFiltersRenderer extends FiltersRenderer { this._currentMovingDateOption = filter ? filter.value : null; this.filterOption = this._currentMovingDateOption ? 'moving' : 'fixed'; - let lowerBoundFilter = this.args.column.filters.findBy('key', 'lower_bound'); - let upperBoundFilter = this.args.column.filters.findBy('key', 'upper_bound'); + let lowerBoundFilter = this.args.column.filters.find((filter) => filter.key === 'lower_bound'); + let upperBoundFilter = this.args.column.filters.find((filter) => filter.key === 'upper_bound'); if (lowerBoundFilter && upperBoundFilter) { this._currentDateValue = [ moment.unix(lowerBoundFilter.value).toDate(), diff --git a/addon/components/hyper-table/filters-renderers/money.js b/addon/components/hyper-table/filters-renderers/money.js index 8ec446d5..fe95a5ee 100644 --- a/addon/components/hyper-table/filters-renderers/money.js +++ b/addon/components/hyper-table/filters-renderers/money.js @@ -20,8 +20,8 @@ export default NumericFilterRenderer.extend({ didReceiveAttrs() { this._super(); if (this.column) { - let lowerBound = this.column.filters.findBy('key', 'lower_bound'); - let upperBound = this.column.filters.findBy('key', 'upper_bound'); + let lowerBound = this.column.filters.find((filter) => filter.key === 'lower_bound'); + let upperBound = this.column.filters.find((filter) => filter.key === 'upper_bound'); if (!isBlank(lowerBound)) { this.setProperties({ lowerBoundFilter: lowerBound.value / 100 }); diff --git a/addon/components/hyper-table/filters-renderers/numeric.js b/addon/components/hyper-table/filters-renderers/numeric.js index 6754576e..4ea38a38 100644 --- a/addon/components/hyper-table/filters-renderers/numeric.js +++ b/addon/components/hyper-table/filters-renderers/numeric.js @@ -22,7 +22,7 @@ export default Component.extend(FiltersRendererMixin, { }), currentExistenceFilter: computed('column.filters.[]', 'lowerBoundFilter', 'upperBoundFilter', function () { - let _existenceFilter = this.column.filters.findBy('key', 'existence'); + let _existenceFilter = this.column.filters.find((filter) => filter.key === 'existence'); if (_existenceFilter) { return _existenceFilter.value; @@ -58,8 +58,8 @@ export default Component.extend(FiltersRendererMixin, { didReceiveAttrs() { this._super(); if (this.column) { - let lowerBound = this.column.filters.findBy('key', 'lower_bound'); - let upperBound = this.column.filters.findBy('key', 'upper_bound'); + let lowerBound = this.column.filters.find((filter) => filter.key === 'lower_bound'); + let upperBound = this.column.filters.find((filter) => filter.key === 'upper_bound'); this.setProperties({ lowerBoundFilter: lowerBound?.value, diff --git a/addon/components/hyper-table/filters-renderers/text.js b/addon/components/hyper-table/filters-renderers/text.js index feb38c84..4168ce99 100644 --- a/addon/components/hyper-table/filters-renderers/text.js +++ b/addon/components/hyper-table/filters-renderers/text.js @@ -21,7 +21,7 @@ export default Component.extend(FiltersRendererMixin, { }, currentExistenceFilter: computed('column.filters.[]', '_searchQuery', function () { - let _existenceFilter = this.column.filters.findBy('key', 'existence'); + let _existenceFilter = this.column.filters.find((filter) => filter.key === 'existence'); if (_existenceFilter) { return _existenceFilter.value; @@ -47,7 +47,7 @@ export default Component.extend(FiltersRendererMixin, { init() { this._super(); - const searchTerm = this.column.filters.findBy('key', this.searchKey || 'value'); + const searchTerm = this.column.filters.find((filter) => filter.key === (this.searchKey || 'value')); this.set('_searchQuery', searchTerm?.value); }, diff --git a/addon/components/hyper-table/index.hbs b/addon/components/hyper-table/index.hbs index 3ebb4ad2..c9b41925 100644 --- a/addon/components/hyper-table/index.hbs +++ b/addon/components/hyper-table/index.hbs @@ -118,7 +118,10 @@

{{/if}} {{#each fields as |field|}} -
+
filter.key === 'value'); return searchTerm ? searchTerm.value : null; }, set: function (k, v) { - this._columns.firstObject.set('filters', isEmpty(v) ? [] : [{ key: 'value', value: v }]); + this._firstColumn.set('filters', isEmpty(v) ? [] : [{ key: 'value', value: v }]); debounce(this, this._doSearch, 1000); return v; } @@ -56,7 +59,7 @@ export default Component.extend({ _collection: alias('manager.data'), _columns: alias('manager.columns'), _fieldCategories: computed('manager.fieldCategories', function () { - return this.manager.fieldCategories.sortBy('label'); + return [...this.manager.fieldCategories].sort((a, b) => compare(a.label, b.label)); }), metaObserver: observer('meta.total', function () { @@ -91,20 +94,17 @@ export default Component.extend({ _orderedFilteredClusters: computed('manager.fields', '_availableFieldsKeyword', '_activeFieldCategory', function () { let _keyword = this._availableFieldsKeyword.toLowerCase(); - let fields = A( - this.manager.fields.filter((x) => { - const hasKeyword = - !this._availableFieldsKeyword || - (x.name || '').toLowerCase().indexOf(_keyword) >= 0 || - (x.title || '').toLowerCase().indexOf(_keyword) >= 0; - const hasActiveGroup = !this._activeFieldCategory || x.categories.indexOf(this._activeFieldCategory) >= 0; - const isToggleable = x.toggleable === true; - - return hasKeyword && hasActiveGroup && isToggleable; - }) - ); + let fields = this.manager.fields.filter((x) => { + const hasKeyword = + !this._availableFieldsKeyword || + (x.name || '').toLowerCase().indexOf(_keyword) >= 0 || + (x.title || '').toLowerCase().indexOf(_keyword) >= 0; + const hasActiveGroup = !this._activeFieldCategory || x.categories.indexOf(this._activeFieldCategory) >= 0; + const isToggleable = x.toggleable === true; + + return hasKeyword && hasActiveGroup && isToggleable; + }); - fields.sortBy('name'); return this.groupByClusteringKey(fields); }), @@ -117,7 +117,7 @@ export default Component.extend({ _allRowSelectedManager(value) { this._setAllRowSelected(value); - this._collection.setEach('selected', value); + this._collection.forEach((item) => set(item, 'selected', value)); this.manager.set('excludedItems', []); }, @@ -224,7 +224,11 @@ export default Component.extend({ reorderColumns: action(function (itemModels) { let _cs = [this._columns[0]].concat(itemModels); - let hasSameOrder = compare(_cs.mapBy('key'), this.manager.columns.mapBy('key')) === 0; + let hasSameOrder = + compare( + _cs.map((column) => column.key), + this.manager.columns.map((column) => column.key) + ) === 0; if (!hasSameOrder) { this.manager.updateColumns(_cs); @@ -318,9 +322,9 @@ export default Component.extend({ }), resetAllFilters: action(function () { - this._columns.setEach('filters', []); + this._columns.forEach((column) => set(column, 'filters', [])); this.manager.resetSelection(); - this._collection.setEach('selected', false); + this._collection.forEach((item) => set(item, 'selected', false)); if (this.manager.hooks.onColumnsChange) { this.manager.hooks.onColumnsChange('columns:change'); @@ -340,7 +344,7 @@ export default Component.extend({ toggleSelectAll: action(function (value) { this.manager.set('_selectAllChecked', value); if (this.manager._selectAllChecked) { - this._collection.setEach('selected', true); + this._collection.forEach((item) => set(item, 'selected', true)); if (this._selectedCount === this.manager.meta.total) { this._allRowSelectedManager(true); } diff --git a/addon/components/hyper-table/views.hbs b/addon/components/hyper-table/views.hbs index c10286d5..52e9a866 100644 --- a/addon/components/hyper-table/views.hbs +++ b/addon/components/hyper-table/views.hbs @@ -65,11 +65,7 @@ <:footer>
- + predefinedView !== view) + ); this.selectView(view); } } diff --git a/addon/mixins/editable.js b/addon/mixins/editable.js index 5eb380cb..9cd8e873 100644 --- a/addon/mixins/editable.js +++ b/addon/mixins/editable.js @@ -7,7 +7,7 @@ export default Mixin.create({ classNameBindings: ['editStatus.status', 'isUpsertable'], editStatus: computed('manager.{editStatus.[],editStatus.@each.status}', 'element.id', function () { - return this.manager.get('editStatus').findBy('id', this.element.id); + return this.manager.get('editStatus').find((status) => status.id === this.element.id); }), isSuccess: equal('editStatus.status', 'success'), @@ -26,19 +26,20 @@ export default Mixin.create({ } let editStatus = this.manager.get('editStatus'); - let elementEditStatus = editStatus.findBy('id', this.element.id); + let elementEditStatus = editStatus.find((status) => status.id === this.element.id); // check if autosave is set to true // will initialize the item's edit status if (autosave) { - this.manager.editStatus.pushObject( + this.manager.set('editStatus', [ + ...this.manager.editStatus, EmberObject.create({ key: this.column.key, status: 'editing', item: this.item, id: this.element.id }) - ); + ]); } // no editing status -> user hasn't started modification @@ -62,14 +63,15 @@ export default Mixin.create({ // sets the global editing status to let the table know of any on going editing if (!elementEditStatus) { - this.manager.editStatus.pushObject( + this.manager.set('editStatus', [ + ...this.manager.editStatus, EmberObject.create({ key: this.column.key, status: 'editing', item: this.item, id: this.element.id }) - ); + ]); } else { elementEditStatus.set('status', 'editing'); } diff --git a/addon/types/column.js b/addon/types/column.js index c585d403..deb1cb6b 100644 --- a/addon/types/column.js +++ b/addon/types/column.js @@ -40,7 +40,7 @@ export default EmberObject.extend({ }, applyFacets(key, facets) { - this.set('filters', isEmpty(facets) ? [] : [{ key, value: facets.mapBy('identifier').join(',') }]); + this.set('filters', isEmpty(facets) ? [] : [{ key, value: facets.map((facet) => facet.identifier).join(',') }]); this.manager.hooks.onColumnsChange('columns:change'); }, diff --git a/addon/types/table.js b/addon/types/table.js index 63150ab0..f157c6f8 100644 --- a/addon/types/table.js +++ b/addon/types/table.js @@ -1,4 +1,4 @@ -import EmberObject from '@ember/object'; +import EmberObject, { get, set } from '@ember/object'; import { or } from '@ember/object/computed'; import { scheduleOnce } from '@ember/runloop'; import { dasherize } from '@ember/string'; @@ -86,13 +86,13 @@ export default EmberObject.extend({ this.set( 'columns', columns - .filter((column) => this.fields.findBy('key', column.key)) + .filter((column) => this.fields.find((field) => field.key === column.key)) .map((column, index) => { if (typeOf(column) !== 'instance') { column = Column.create(column); } - let field = this.fields.findBy('key', column.key); + let field = this.fields.find((field) => field.key === column.key); column.setProperties({ manager: this, @@ -120,15 +120,15 @@ export default EmberObject.extend({ updateData(data) { if (data.length > 0) { - let undefinedSelectedData = data.filterBy('selected', undefined); + let undefinedSelectedData = data.filter((row) => get(row, 'selected') === undefined); undefinedSelectedData.forEach((undefinedSelectedRow) => { let existingRow = this.data.find((row) => row.id === undefinedSelectedRow.id); if (!existingRow) return; undefinedSelectedRow.selected = existingRow.selected; }); - let newData = data.filterBy('selected', undefined); - this._allRowsSelected ? newData.setEach('selected', true) : newData.setEach('selected', false); + let newData = data.filter((row) => get(row, 'selected') === undefined); + newData.forEach((row) => set(row, 'selected', this._allRowsSelected)); } this.set('data', data); @@ -159,7 +159,7 @@ export default EmberObject.extend({ if (predefinedViews) { predefinedViews = predefinedViews.filter((view) => { - return views.filterBy('name', view.name).length === 0; + return !views.some((v) => get(v, 'name') === view.name); }); this.set('predefinedViews', predefinedViews); } @@ -193,12 +193,15 @@ export default EmberObject.extend({ toggleColumnVisibility(field, column) { return new Promise((resolve) => { - let _c = this.columns.findBy('key', field.key); + let _c = this.columns.find((column) => column.key === field.key); let _action; if (_c) { field.set('visible', false); - this.columns.removeObject(_c); + this.set( + 'columns', + this.columns.filter((column) => column !== _c) + ); _action = 'removal'; } else { this.formatField(field); @@ -225,7 +228,7 @@ export default EmberObject.extend({ newColumn.filters = column.filters; } - this.columns.pushObject(newColumn); + this.set('columns', [...this.columns, newColumn]); _action = 'addition'; } diff --git a/tests/helpers/array-prototype-extensions.js b/tests/helpers/array-prototype-extensions.js deleted file mode 100644 index c1c48045..00000000 --- a/tests/helpers/array-prototype-extensions.js +++ /dev/null @@ -1,25 +0,0 @@ -import { A } from '@ember/array'; - -// The classic hyper-table code relies on the Ember array prototype extensions that apps enable; the dummy app disables them. -// The mixin is not applied to Array.prototype itself so that it leaves no trace on A() once the tests are done. -export function extendArrayPrototype(hooks) { - const added = []; - - hooks.before(function () { - const sample = A([]); - - Object.getOwnPropertyNames(sample) - .filter((name) => name !== 'length' && !(name in Array.prototype)) - .forEach((name) => { - Object.defineProperty(Array.prototype, name, { - ...Object.getOwnPropertyDescriptor(sample, name), - configurable: true - }); - added.push(name); - }); - }); - - hooks.after(function () { - added.forEach((name) => delete Array.prototype[name]); - }); -} diff --git a/tests/integration/components/hyper-table-test.js b/tests/integration/components/hyper-table-test.js index 998a155c..5cf2d6e5 100644 --- a/tests/integration/components/hyper-table-test.js +++ b/tests/integration/components/hyper-table-test.js @@ -2,7 +2,6 @@ import { A } from '@ember/array'; import EmberObject from '@ember/object'; import { click, fillIn, render, settled, triggerEvent } from '@ember/test-helpers'; -import { extendArrayPrototype } from 'dummy/tests/helpers/array-prototype-extensions'; import { hbs } from 'ember-cli-htmlbars'; import { setupRenderingTest } from 'ember-qunit'; import { module, test } from 'qunit'; @@ -63,7 +62,6 @@ const buildTable = (owner, { type = 'text', hooks = {}, features = FEATURES } = module('Integration | Component | hyper-table', function (hooks) { setupRenderingTest(hooks); - extendArrayPrototype(hooks); hooks.beforeEach(function () { this.documentClick = sinon.spy(); diff --git a/tests/unit/types/table-test.js b/tests/unit/types/table-test.js index 050f9fe7..0cf7045e 100644 --- a/tests/unit/types/table-test.js +++ b/tests/unit/types/table-test.js @@ -1,6 +1,5 @@ import EmberObject from '@ember/object'; -import { extendArrayPrototype } from 'dummy/tests/helpers/array-prototype-extensions'; import { setupTest } from 'ember-qunit'; import { module, test } from 'qunit'; @@ -10,7 +9,6 @@ const field = (key, props = {}) => EmberObject.create({ key, type: 'text', ...pr module('Unit | Type | table', function (hooks) { setupTest(hooks); - extendArrayPrototype(hooks); hooks.beforeEach(function () { this.table = this.owner.lookup('service:hypertable-manager').createTable({}, { onColumnsChange() {} });