diff --git a/src/PivotTableUI.jsx b/src/PivotTableUI.jsx
index 0de055f..0010c0f 100644
--- a/src/PivotTableUI.jsx
+++ b/src/PivotTableUI.jsx
@@ -210,6 +210,7 @@ export class Dropdown extends React.PureComponent {
this.props.toggle();
} else {
this.props.setValue(r);
+ this.props.toggle(); // Close the dropdown after selection
}
}}
className={
@@ -284,12 +285,38 @@ class PivotTableUI extends React.PureComponent {
this.setState(newState);
}
+ handleDuplicates(newAttributes, existingAttributes) {
+ if (!newAttributes || !existingAttributes) {
+ return existingAttributes || [];
+ }
+ const duplicates = newAttributes.filter(item => existingAttributes.includes(item));
+ return duplicates.length > 0
+ ? existingAttributes.filter(item => !duplicates.includes(item))
+ : existingAttributes;
+ }
+
sendPropUpdate(command) {
this.props.onChange(update(this.props, command));
}
propUpdater(key) {
- return value => this.sendPropUpdate({[key]: {$set: value}});
+ return value => {
+ const update = {[key]: {$set: value}};
+
+ if (key === 'rows') {
+ const updatedCols = this.handleDuplicates(value, this.props.cols);
+ if (updatedCols.length !== this.props.cols.length) {
+ update.cols = {$set: updatedCols};
+ }
+ } else if (key === 'cols') {
+ const updatedRows = this.handleDuplicates(value, this.props.rows);
+ if (updatedRows.length !== this.props.rows.length) {
+ update.rows = {$set: updatedRows};
+ }
+ }
+
+ this.sendPropUpdate(update);
+ };
}
setValuesInFilter(attribute, values) {
@@ -502,7 +529,6 @@ class PivotTableUI extends React.PureComponent {
!this.props.hiddenAttributes.includes(e) &&
!this.props.hiddenFromDragDrop.includes(e)
);
-
const colAttrsCell = this.makeDnDCell(
colAttrs,
this.propUpdater('cols'),
@@ -519,6 +545,7 @@ class PivotTableUI extends React.PureComponent {
this.propUpdater('rows'),
'pvtAxisContainer pvtVertList pvtRows'
);
+
const outputCell = (
{
+ describe('handleDuplicates', () => {
+ // Create a minimal instance of PivotTableUI for testing
+ const getInstance = () => {
+ const pivotTableUI = new PivotTableUI({
+ onChange: () => {},
+ renderers: {},
+ aggregators: {},
+ rows: [],
+ cols: [],
+ rendererName: '',
+ aggregatorName: '',
+ vals: [],
+ valueFilter: {},
+ rowOrder: 'key_a_to_z',
+ colOrder: 'key_a_to_z',
+ derivedAttributes: {},
+ data: []
+ });
+ return pivotTableUI;
+ };
+
+ it('returns existingAttributes when newAttributes is null or undefined', () => {
+ const instance = getInstance();
+ expect(instance.handleDuplicates(null, ['a', 'b'])).toEqual(['a', 'b']);
+ expect(instance.handleDuplicates(undefined, ['a', 'b'])).toEqual(['a', 'b']);
+ });
+
+ it('returns empty array when both inputs are null or undefined', () => {
+ const instance = getInstance();
+ expect(instance.handleDuplicates(null, null)).toEqual([]);
+ expect(instance.handleDuplicates(undefined, undefined)).toEqual([]);
+ });
+
+ it('returns existingAttributes when there are no duplicates', () => {
+ const instance = getInstance();
+ const newAttributes = ['a', 'b', 'c'];
+ const existingAttributes = ['d', 'e', 'f'];
+ expect(instance.handleDuplicates(newAttributes, existingAttributes))
+ .toEqual(existingAttributes);
+ });
+
+ it('removes duplicates from existingAttributes', () => {
+ const instance = getInstance();
+ const newAttributes = ['a', 'b', 'c'];
+ const existingAttributes = ['b', 'c', 'd'];
+ // 'b' and 'c' are duplicates and should be removed
+ expect(instance.handleDuplicates(newAttributes, existingAttributes))
+ .toEqual(['d']);
+ });
+
+ it('handles empty newAttributes', () => {
+ const instance = getInstance();
+ const newAttributes = [];
+ const existingAttributes = ['a', 'b', 'c'];
+ expect(instance.handleDuplicates(newAttributes, existingAttributes))
+ .toEqual(existingAttributes);
+ });
+
+ it('handles empty existingAttributes', () => {
+ const instance = getInstance();
+ const newAttributes = ['a', 'b', 'c'];
+ const existingAttributes = [];
+ expect(instance.handleDuplicates(newAttributes, existingAttributes))
+ .toEqual([]);
+ });
+
+ it('handles case with all attributes being duplicates', () => {
+ const instance = getInstance();
+ const newAttributes = ['a', 'b', 'c'];
+ const existingAttributes = ['a', 'b', 'c'];
+ expect(instance.handleDuplicates(newAttributes, existingAttributes))
+ .toEqual([]);
+ });
+ });
+
+ describe('propUpdater', () => {
+ // We'll use a mock to check if sendPropUpdate is called with the right arguments
+ let mockSendPropUpdate;
+ let instance;
+
+ beforeEach(() => {
+ instance = new PivotTableUI({
+ onChange: () => {},
+ renderers: {},
+ aggregators: {},
+ rows: ['gender', 'age'],
+ cols: ['country', 'year'],
+ rendererName: '',
+ aggregatorName: '',
+ vals: [],
+ valueFilter: {},
+ rowOrder: 'key_a_to_z',
+ colOrder: 'key_a_to_z',
+ derivedAttributes: {},
+ data: []
+ });
+ // Mock the sendPropUpdate method
+ mockSendPropUpdate = jest.spyOn(instance, 'sendPropUpdate').mockImplementation(() => {});
+ // Mock the handleDuplicates method to control its return value
+ jest.spyOn(instance, 'handleDuplicates');
+ });
+
+ afterEach(() => {
+ mockSendPropUpdate.mockRestore();
+ instance.handleDuplicates.mockRestore();
+ });
+
+ it('calls handleDuplicates when key is "rows"', () => {
+ const newRows = ['gender', 'name'];
+ const updater = instance.propUpdater('rows');
+
+ // Set up the mock to return the same cols (no duplicates found)
+ instance.handleDuplicates.mockReturnValueOnce(instance.props.cols);
+
+ updater(newRows);
+
+ expect(instance.handleDuplicates).toHaveBeenCalledWith(newRows, instance.props.cols);
+ expect(mockSendPropUpdate).toHaveBeenCalledWith({
+ rows: { $set: newRows }
+ });
+ });
+
+ it('calls handleDuplicates when key is "cols"', () => {
+ const newCols = ['country', 'city'];
+ const updater = instance.propUpdater('cols');
+
+ // Set up the mock to return the same rows (no duplicates found)
+ instance.handleDuplicates.mockReturnValueOnce(instance.props.rows);
+
+ updater(newCols);
+
+ expect(instance.handleDuplicates).toHaveBeenCalledWith(newCols, instance.props.rows);
+ expect(mockSendPropUpdate).toHaveBeenCalledWith({
+ cols: { $set: newCols }
+ });
+ });
+
+ it('updates cols when duplicate is found in rows update', () => {
+ const newRows = ['gender', 'country']; // 'country' is duplicate
+ const updater = instance.propUpdater('rows');
+
+ // 'country' is removed from cols
+ const updatedCols = ['year'];
+ instance.handleDuplicates.mockReturnValueOnce(updatedCols);
+
+ updater(newRows);
+
+ expect(mockSendPropUpdate).toHaveBeenCalledWith({
+ rows: { $set: newRows },
+ cols: { $set: updatedCols }
+ });
+ });
+
+ it('updates rows when duplicate is found in cols update', () => {
+ const newCols = ['country', 'gender']; // 'gender' is duplicate
+ const updater = instance.propUpdater('cols');
+
+ // 'gender' is removed from rows
+ const updatedRows = ['age'];
+ instance.handleDuplicates.mockReturnValueOnce(updatedRows);
+
+ updater(newCols);
+
+ expect(mockSendPropUpdate).toHaveBeenCalledWith({
+ cols: { $set: newCols },
+ rows: { $set: updatedRows }
+ });
+ });
+
+ it('does not update the other attribute if no duplicates found', () => {
+ const newRows = ['gender', 'name'];
+ const updater = instance.propUpdater('rows');
+
+ // No change to cols (same array reference)
+ instance.handleDuplicates.mockReturnValueOnce(instance.props.cols);
+
+ updater(newRows);
+
+ expect(mockSendPropUpdate).toHaveBeenCalledWith({
+ rows: { $set: newRows }
+ });
+ // We shouldn't have cols in the update
+ expect(mockSendPropUpdate.mock.calls[0][0].cols).toBeUndefined();
+ });
+ });
+});
|