Skip to content

Commit 2b1b47f

Browse files
Merge branch 'selected-tab-history'
2 parents 6d05eef + 4f66ec6 commit 2b1b47f

File tree

6 files changed

+183
-39
lines changed

6 files changed

+183
-39
lines changed

src/utils/api/activedTabsHistory.js

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
const HistoryActiveTabs = function () {
22
this.tabsId = [];
33
};
4-
HistoryActiveTabs.prototype.getTab = function () {
4+
HistoryActiveTabs.prototype.popLastTabID = function () {
55
return this.tabsId.pop();
66
};
77
HistoryActiveTabs.prototype.reset = function () {
88
this.tabsId = [];
99
};
1010
HistoryActiveTabs.prototype.add = function (id) {
11-
const tabsId = this.tabsId;
12-
tabsId[tabsId.length - 1] === id || tabsId.push(id);
11+
this.tabsId.push(id);
12+
};
13+
HistoryActiveTabs.prototype.remove = function (id) {
14+
const tabIDs = this.tabsId;
15+
while (tabIDs.indexOf(id) >= 0) {
16+
tabIDs.splice(tabIDs.indexOf(id), 1);
17+
}
18+
return this;
1319
};
1420
export default HistoryActiveTabs;
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import SelectedTabsHistory from './activedTabsHistory.js';
2+
let obj = null;
3+
beforeEach(() => {
4+
obj = new SelectedTabsHistory();
5+
});
6+
afterEach(() => {
7+
obj = null;
8+
});
9+
describe('SelectedTabsHistory.prototype.remove : ', () => {
10+
test('it should remove all occurrences of an element in the array', () => {
11+
obj.tabsId = ['1', '2', '1', '3', '4', '2', '6'];
12+
obj.remove('1').remove('2');
13+
expect(obj.tabsId.indexOf('1')).toBe(-1);
14+
expect(obj.tabsId.indexOf('2')).toBe(-1);
15+
});
16+
test('it should not work with number as a parameter', () => {
17+
obj.tabsId = ['1', '2', '1', '3', '4', '2', '6'];
18+
obj.remove(1);
19+
expect(obj.tabsId.indexOf('1')).toBe(0);
20+
});
21+
});

src/utils/api/api.factory.js

+37-33
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,13 @@ const _apiProps = {
5151
return this;
5252
},
5353
_subscribeSelectedTabsHistory: function () {
54-
this.on('onChange', ({currentData, perviousData}) => {
54+
this.on('onChange', ({currentData, perviousData, closedTabIDs}) => {
55+
for (let i = 0, l = closedTabIDs.length; i < l; i++) {
56+
this.activedTabsHistory.remove(closedTabIDs[i]);
57+
}
5558
const isSwitched = perviousData.selectedTabID !== currentData.selectedTabID;
56-
isSwitched && this.activedTabsHistory.add(perviousData.selectedTabID);
59+
if (isSwitched && this.isOpen(perviousData.selectedTabID) && !this.isSelected(perviousData.selectedTabID))
60+
this.activedTabsHistory.add(perviousData.selectedTabID);
5761
});
5862
return this;
5963
},
@@ -98,37 +102,37 @@ const _apiProps = {
98102
this._select(id);
99103
return result;
100104
},
101-
_findTabIdForSwitching: (function () {
102-
const _findOpenedAndNoneDisableTabId = function (tabsIdArr, isRightToLeft) {
103-
return (
104-
this.helper.arrFilterUntilFirstValue(
105-
tabsIdArr,
106-
(id) => this.isOpen(id) && !this.getTab(id).disable && !this.isSelected(id),
107-
isRightToLeft,
108-
) || ''
109-
);
110-
},
111-
_getPreSelectedTabId = function () {
112-
return _findOpenedAndNoneDisableTabId.call(this, [...this.activedTabsHistory.tabsId], true);
113-
},
114-
_getPreSiblingTabId = function () {
115-
const data = this.stateRef,
116-
arr = data.openTabIDs;
117-
return _findOpenedAndNoneDisableTabId.call(this, arr.slice(0, arr.indexOf(data.selectedTabID)), true);
118-
},
119-
_getNextSiblingTabId = function () {
120-
const data = this.stateRef,
121-
arr = data.openTabIDs;
122-
return _findOpenedAndNoneDisableTabId.call(this, arr.slice(arr.indexOf(data.selectedTabID) + 1));
123-
};
124-
return function () {
125-
let tabId = '';
126-
tabId = _getPreSelectedTabId.call(this);
127-
tabId = tabId || _getPreSiblingTabId.call(this);
128-
tabId = tabId || _getNextSiblingTabId.call(this);
129-
return tabId;
130-
};
131-
})(),
105+
_getPreSelectedTabId: function () {
106+
const selectedTabHistory = this.activedTabsHistory;
107+
let tabID = '';
108+
while (!tabID && selectedTabHistory.tabsId.length) {
109+
const _tabID = selectedTabHistory.popLastTabID();
110+
if (_tabID) {
111+
const _tabData = this.getTab(_tabID);
112+
if (_tabData && !_tabData.disable && this.isOpen(_tabID) && !this.isSelected(_tabID)) tabID = _tabID;
113+
}
114+
}
115+
return tabID;
116+
},
117+
_getPreSiblingTabId: function () {
118+
const {selectedTabID, openTabIDs} = this.stateRef;
119+
const isRightToLeft = true;
120+
const arr = openTabIDs.slice(0, openTabIDs.indexOf(selectedTabID));
121+
return this.helper.filterArrayUntilFirstValue(arr, (id) => !this.getTab(id).disable, isRightToLeft);
122+
},
123+
_getNextSiblingTabId: function () {
124+
const {selectedTabID, openTabIDs} = this.stateRef;
125+
const isRightToLeft = false;
126+
const arr = openTabIDs.slice(openTabIDs.indexOf(selectedTabID) + 1);
127+
return this.helper.filterArrayUntilFirstValue(arr, (id) => !this.getTab(id).disable, isRightToLeft);
128+
},
129+
_findTabIdForSwitching: function () {
130+
let tabId = '';
131+
tabId = this._getPreSelectedTabId();
132+
tabId = tabId || this._getPreSiblingTabId();
133+
tabId = tabId || this._getNextSiblingTabId();
134+
return tabId || '';
135+
},
132136
setTab: function (id, newData = {}) {
133137
this.optionsManager.validateObjectiveTabData(newData).validatePanelComponent(newData);
134138
this._setTab(id, newData);

src/utils/api/api.factory.test.js

+85-1
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ describe('context of callback options should be userProxy object : ', () => {
323323
};
324324
const obj = new apiConstructor(getDeps, {options});
325325
expect(options.onChange.mock.calls.length === 0).toBe(true);
326-
obj.trigger('onChange', obj.userProxy, {currentData: {}, perviousData: {}});
326+
obj.trigger('onChange', obj.userProxy, {currentData: {}, perviousData: {}, closedTabIDs: [], openTabIDs: []});
327327
expect(options.onChange.mock.calls.length === 1).toBe(true);
328328
});
329329
});
@@ -464,3 +464,87 @@ describe('Api.prototype.setTab : ', () => {
464464
);
465465
});
466466
});
467+
describe('Api.prototype._subscribeSelectedTabsHistory : ', () => {
468+
test('it should call "on" method', () => {
469+
obj.on = jest.fn(() => {});
470+
obj._subscribeSelectedTabsHistory();
471+
expect(obj.on.mock.calls.length).toBe(1);
472+
expect(obj.on.mock.calls[0][0]).toBe('onChange');
473+
});
474+
test('subscribed function should call activedTabsHistory.remove per each closed tabID when onChange event is triggered', () => {
475+
obj.activedTabsHistory.remove = jest.fn(() => {});
476+
const currentData = {selectedTabID: '2', openTabIDs: ['2']};
477+
const perviousData = {selectedTabID: '2', openTabIDs: ['2']};
478+
const closedTabIDs = ['1', '3'];
479+
obj.trigger('onChange', obj.userProxy, {currentData, perviousData, closedTabIDs, openTabIDs: []});
480+
expect(obj.activedTabsHistory.remove.mock.calls.length).toBe(2);
481+
expect(obj.activedTabsHistory.remove.mock.calls[0][0]).toBe('1');
482+
expect(obj.activedTabsHistory.remove.mock.calls[1][0]).toBe('3');
483+
});
484+
test(`when onChange event is triggered, subscribed function should call activedTabsHistory.add with
485+
previously selectedTabID as a parameter if it is open, not selected and none disable.`, () => {
486+
obj.activedTabsHistory.add = jest.fn(() => {});
487+
obj.isOpen = jest.fn((id) => {
488+
if (id === '1' || id === '2') return true;
489+
return false;
490+
});
491+
obj.isSelected = jest.fn((id) => {
492+
if (id === '2') return true;
493+
return false;
494+
});
495+
const currentData = {selectedTabID: '2', openTabIDs: ['1', '2']};
496+
const perviousData = {selectedTabID: '1', openTabIDs: ['1', '2']};
497+
obj.trigger('onChange', obj.userProxy, {currentData, perviousData, closedTabIDs: [], openTabIDs: []});
498+
expect(obj.activedTabsHistory.add.mock.calls.length).toBe(1);
499+
expect(obj.activedTabsHistory.add.mock.calls[0][0]).toBe('1');
500+
});
501+
});
502+
describe('Api.prototype._getPreSelectedTabId : ', () => {
503+
test(`it calls activeTabsHistory.popLastTabID repeatedly until it returns a tabID
504+
which is opened, not selected and none disable`, () => {
505+
const obj = new apiConstructor(getDeps, {
506+
options: {
507+
tabs: [{id: '1'}, {id: '2'}, {id: '3'}, {id: '4'}, {id: '5'}],
508+
selectedTabID: 'tab1',
509+
},
510+
});
511+
obj.stateRef = {selectedTabID: '1', openTabIDs: ['1', '2', '3', '4']};
512+
obj.activedTabsHistory.tabsId = ['3', '2', '1', '3', '1', '3', '4', '5'];
513+
obj.setTab('3', {disable: true}).setTab('4', {disable: true});
514+
const tabID = obj._getPreSelectedTabId();
515+
expect(tabID).toBe('2');
516+
expect(obj.activedTabsHistory.tabsId).toEqual(['3']);
517+
});
518+
test('it should return an empty string if activedTabsHistory.tabsId is empty or does not contain any valid tabID', () => {
519+
obj.stateRef = {selectedTabID: 'tab1', openTabIDs: ['tab1', 'tab2']};
520+
expect(obj._getPreSelectedTabId()).toBe('');
521+
obj.activedTabsHistory.tabsId = ['tab1'];
522+
expect(obj._getPreSelectedTabId()).toBe('');
523+
obj.activedTabsHistory.tabsId = ['tab1', ' ', '', null, 'tab2'];
524+
obj.setTab('tab2', {disable: true});
525+
expect(obj._getPreSelectedTabId()).toBe('');
526+
obj.activedTabsHistory.tabsId = ['tab1', 'tab2'];
527+
obj.setTab('tab2', {disable: false});
528+
expect(obj._getPreSelectedTabId()).toBe('tab2');
529+
});
530+
});
531+
describe('Api.prototype._getPreSiblingTabId : ', () => {
532+
test('it should call helper.filterArrayUntilFirstValue with appropriate parameters', () => {
533+
obj.helper.filterArrayUntilFirstValue = jest.fn(() => {});
534+
obj.stateRef = {selectedTabID: '2', openTabIDs: ['1', '2', '3']};
535+
obj._getPreSiblingTabId();
536+
expect(obj.helper.filterArrayUntilFirstValue.mock.calls.length).toBe(1);
537+
expect(obj.helper.filterArrayUntilFirstValue.mock.calls[0][0]).toEqual(['1']);
538+
expect(obj.helper.filterArrayUntilFirstValue.mock.calls[0][2]).toBe(true);
539+
});
540+
});
541+
describe('Api.prototype._getNextSiblingTabId : ', () => {
542+
test('it should call helper.filterArrayUntilFirstValue with appropriate parameters', () => {
543+
obj.helper.filterArrayUntilFirstValue = jest.fn(() => {});
544+
obj.stateRef = {selectedTabID: '2', openTabIDs: ['1', '2', '3']};
545+
obj._getNextSiblingTabId();
546+
expect(obj.helper.filterArrayUntilFirstValue.mock.calls.length).toBe(1);
547+
expect(obj.helper.filterArrayUntilFirstValue.mock.calls[0][0]).toEqual(['3']);
548+
expect(!obj.helper.filterArrayUntilFirstValue.mock.calls[0][2]).toBe(true);
549+
});
550+
});

src/utils/helper.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,11 @@ helper.getArraysDiff = function (arr1, arr2) {
4747
});
4848
return [arr1Copy, arr2Copy];
4949
};
50-
helper.arrFilterUntilFirstValue = (arr, callback, isRightToLeft) => {
50+
helper.filterArrayUntilFirstValue = (arr, callback, isRightToLeft) => {
5151
isRightToLeft && arr.reverse();
52-
for (let i = 0, l = arr.length; i < l; i++) if (callback(arr[i], i, arr)) return arr[i];
52+
for (let i = 0, l = arr.length; i < l; i++) {
53+
if (callback(arr[i], i, arr)) return arr[i];
54+
}
5355
return null;
5456
};
5557
helper.throwMissingParam = (FnName) => {

src/utils/helper.test.js

+27
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,30 @@ describe('helper : ', () => {
3030
expect(copyState2.openTabIDs != state2.openTabIDs).toBe(true);
3131
});
3232
});
33+
describe('helper.filterArrayUntilFirstValue : ', () => {
34+
test('filterArrayUntilFirstValue function should work correctly and it may change the original array', () => {
35+
expect.assertions(3);
36+
const arr = ['1', '33', '2', '3', '22', '4'];
37+
const result1 = helper.filterArrayUntilFirstValue(arr, (item, index, _arr) => {
38+
if (item.includes('3')) {
39+
expect(_arr).toEqual(arr);
40+
}
41+
return item.includes('3');
42+
});
43+
expect(result1).toBe('33');
44+
const result2 = helper.filterArrayUntilFirstValue(arr, (item) => item.includes('2'), true);
45+
expect(result2).toBe('22');
46+
});
47+
test('it may change the original array', () => {
48+
const arr = ['1', '33', '2', '3', '22', '4'];
49+
const originalArr = [...arr];
50+
expect(originalArr).toEqual(arr);
51+
helper.filterArrayUntilFirstValue(arr, (item) => item.includes('2'), true);
52+
expect(originalArr).not.toEqual(arr);
53+
});
54+
test('it should return null if there is not desired element in the array', () => {
55+
const arr = ['1', '2'];
56+
const result = helper.filterArrayUntilFirstValue(arr, (item) => item > 5);
57+
expect(result).toBe(null);
58+
});
59+
});

0 commit comments

Comments
 (0)