Skip to content

Commit 621c086

Browse files
add initialState prop and validateTabData into
optionManager.factory
1 parent 6b0d78f commit 621c086

File tree

12 files changed

+416
-149
lines changed

12 files changed

+416
-149
lines changed

package-lock.json

Lines changed: 5 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/index.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ describe("apply multiple actions : ", () => {
135135
}
136136
});
137137
expect(document.getElementById('updatedPanel1') != null).toBe(true);
138-
expect(document.querySelector('li[tab-id="1"] .rc-dyn-tabs-close') == null).toBe(true);
138+
expect(document.querySelector('li[tab-id="1"] .rc-dyn-tabs-close')).toBe(null);
139139
expect(document.querySelector('ul').className.includes('rc-dyn-tabs-rtl')).toBe(true);
140140
expect(document.querySelectorAll('a.rc-dyn-tabs-title').length === 2).toBe(true);
141141
expect(op.onChange.mock.calls.length).toBe(0);

src/useDynamicTabs/useDynamicTabs.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,16 @@ function useDynamicTabs(getDeps, options = {}) {
77
ref.current = { api: getApiInstance(options), TabListComponent: null, PanelListComponent: null };
88
const { current: { api } } = ref
99
, _ref = ref.current
10-
, [state, dispatch] = useReducer(reducer, api.getInitialState())
10+
, [state, dispatch] = useReducer(reducer, api.optionsManager.initialState)
1111
, [flushState, setFlushState] = useState({});
1212
api.updateStateRef(state, dispatch).updateFlushState(setFlushState);
13+
useLayoutEffect(() => {
14+
api.updateState(state);
15+
}, [state]);
1316
useLayoutEffect(() => {
1417
api.trigger('onLoad', api.userProxy);
1518
return () => { api.trigger('onDestroy', api.userProxy); };
1619
}, []);
17-
useLayoutEffect(() => {
18-
api.updateState(state);
19-
}, [state]);
2020
useLayoutEffect(() => {
2121
api.trigger('onInit', api.userProxy);
2222
});

src/utils/api/api.factory.js

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,9 @@ const _apiProps = {
6565
},
6666
getOption: function (name) { return this.optionsManager.getOption(name); },
6767
setOption: function (name, value) { return this.optionsManager.setOption(name, value); },
68-
getCopyPerviousData: function () { return this.helper.getCopyState(this._perviousState); },
68+
getCopyPerviousData: function () {
69+
return this.helper.getCopyState(this._perviousState);
70+
},
6971
getCopyData: function () { return this.helper.getCopyState(this.stateRef); },
7072
isSelected: function (id = missingParamEr('isSelected')) { return this.stateRef.selectedTabID == id; },
7173
isOpen: function (id = missingParamEr('isOpen')) { return this.stateRef.openTabIDs.indexOf(id) >= 0; },
@@ -103,13 +105,15 @@ const _apiProps = {
103105
return tabId;
104106
};
105107
})(),
106-
setTab: function (id, newData) {
107-
this._setTab(id, newData, this.getOption('defaultPanelComponent'));
108+
setTab: function (id, newData = {}) {
109+
this.optionsManager.validateObjectiveTabData(newData).validatePanelComponent(newData);
110+
this._setTab(id, newData);
108111
return this;
109112
},
110113
open: function (tabObj = missingParamEr('open')) {
111-
const newTabObj = this._addTab(tabObj, { defaultPanelComponent: this.getOption('defaultPanelComponent') });
114+
const newTabObj = this.optionsManager.validateTabData(tabObj);
112115
const result = this._getFlushEffectsPromise();
116+
this._addTab(newTabObj);
113117
this._open(newTabObj.id);
114118
return result;
115119
},
@@ -138,20 +142,20 @@ const _apiProps = {
138142
}
139143
};
140144
Helper.setNoneEnumProps(_apiProps, {
141-
getInitialState: function () {
142-
if (!this._initialState) {
143-
const { selectedTabID, tabs, defaultPanelComponent } = this.optionsManager.options, openTabIDs = [];
144-
tabs.map(tab => {
145-
const newTab = this._addTab(tab, { defaultPanelComponent });
146-
openTabIDs.push(newTab.id);
147-
});
148-
this._initialState = {
149-
selectedTabID: selectedTabID + '', //make sure it is type of string
150-
openTabIDs
151-
};
152-
}
153-
return this._initialState;
154-
},
145+
// getInitialState: function () {
146+
// if (!this._initialState) {
147+
// const { selectedTabID, tabs, defaultPanelComponent } = this.optionsManager.options, openTabIDs = [];
148+
// tabs.map(tab => {
149+
// const newTab = this._addTab(tab, { defaultPanelComponent });
150+
// openTabIDs.push(newTab.id);
151+
// });
152+
// this._initialState = {
153+
// selectedTabID: selectedTabID + '', //make sure it is type of string
154+
// openTabIDs
155+
// };
156+
// }
157+
// return this._initialState;
158+
// },
155159
onChange: function ({ newState, oldState, closedTabsId, openedTabsId, isSwitched }) {
156160
if (isSwitched || openedTabsId.length || closedTabsId.length) {
157161
this.trigger('onChange', this.userProxy, {

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

Lines changed: 65 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,43 +14,41 @@ beforeAll(() => {
1414
beforeEach(() => {
1515
getDeps = function (options = {}) {
1616
const activedTabsHistory = new (ActivedTabsHistory)(), optionsManager = new (OptionManager)({ options });
17-
BaseApi.call(this, helper);
18-
Tabs.call(this);
17+
BaseApi.call(this, {
18+
helper,
19+
initialState: optionsManager.initialState
20+
});
21+
Tabs.call(this, { initialTabs: optionsManager.initialTabs, });
1922
Pub_Sub.call(this);
2023
return { activedTabsHistory, helper, optionsManager };
2124
};
22-
obj = new (apiConstructor)(getDeps, { options: {} });
25+
obj = new (apiConstructor)(getDeps, {
26+
options: {
27+
tabs: [{
28+
id: 'tab1',
29+
title: 'tabTitle1'
30+
}, {
31+
id: 'tab2',
32+
title: 'tabTitle2'
33+
}],
34+
selectedTabID: 'tab1'
35+
}
36+
});
2337
});
2438
afterEach(() => {
2539
getDeps = null;
2640
obj = null;
2741
});
28-
describe('Api.prototype.getInitialState : ', () => {
29-
test('it should call _addTab internally per each tabData option', () => {
30-
const obj = new (apiConstructor)(getDeps, {
31-
options: {
32-
tabs: [
33-
{ id: '1', title: 'tab1' },
34-
{ id: '2', title: 'tab2' },
35-
{ id: '3', title: 'tab3' }
36-
]
37-
}
38-
});
39-
obj._addTab = jest.fn(() => ({ id: '2' }));
40-
obj.getInitialState();
41-
expect(obj._addTab.mock.calls.length === 3).toBe(true);
42-
});
43-
});
4442
describe('Api.prototype.open : ', () => {
45-
test('it should call _addTab internally', () => {
43+
test('it should call optionsManager.validateTabData internally', () => {
4644
Object.assign(obj, {
47-
_addTab: jest.fn(() => ({ id: '2' })),
4845
_getFlushEffectsPromise: jest.fn(() => Promise),
4946
_open: jest.fn(() => { })
5047
});
48+
obj.optionsManager.validateTabData = jest.fn(data => data);
5149
obj.open({ id: '2' });
52-
expect(obj._addTab.mock.calls.length === 1).toBe(true);
53-
expect(obj._addTab).toHaveBeenCalledBefore(obj._getFlushEffectsPromise);
50+
expect(obj.optionsManager.validateTabData.mock.calls.length === 1).toBe(true);
51+
expect(obj.optionsManager.validateTabData).toHaveBeenCalledBefore(obj._getFlushEffectsPromise);
5452
expect(obj._getFlushEffectsPromise).toHaveBeenCalledBefore(obj._open);
5553
});
5654
test('it throws an error if is called with falsy value of id parameter', () => {
@@ -394,4 +392,48 @@ describe('Api.prototype.eventHandlerFactory : ', () => {
394392
expect(beforeClose.mock.calls[0][1]).toBe(id);
395393
}
396394
});
395+
});
396+
describe('Api.prototype.getCopyPerviouseData', () => {
397+
test('In the onLoad event, return data is equal to getInitialState() and getCopyData()', () => {
398+
expect.assertions(3);
399+
const _state = { selectedTabID: 'tab1', openTabIDs: ['tab1', 'tab2'] };
400+
obj.setOption('onLoad', function () {
401+
const perviousData = this.getCopyPerviousData();
402+
const data = this.getCopyData();
403+
expect(perviousData).toEqual(_state);
404+
expect(perviousData).toEqual(data);
405+
expect(perviousData !== null).toBe(true);
406+
});
407+
obj.updateStateRef(_state, () => { });
408+
obj.trigger('onLoad', obj.userProxy);
409+
});
410+
});
411+
describe('Api.prototype.getCopyPerviousData', () => {
412+
test('returned data should be equal to optionsManager.initialState in onLoad event', () => {
413+
expect.assertions(2);
414+
obj.setOption('onLoad', function () {
415+
const perviousData = this.getCopyPerviousData();
416+
const data = this.getCopyData();
417+
expect(perviousData).toEqual(obj.optionsManager.initialState);
418+
expect(perviousData).not.toEqual(data);
419+
});
420+
obj.updateState({ selectedTabID: 'tab1', openTabIDs: ['tab1', 'tab2'] });
421+
obj.trigger('onLoad', obj.userProxy);
422+
});
423+
test('returned data should be equal to currentData in onLoad event', () => {
424+
expect.assertions(1);
425+
obj.setOption('onLoad', function () {
426+
expect(this.getCopyPerviousData()).toEqual(this.getCopyData());
427+
});
428+
obj.updateStateRef({ selectedTabID: 'tab1', openTabIDs: ['tab1', 'tab2'] }, () => { });
429+
obj.trigger('onLoad', obj.userProxy);
430+
});
431+
});
432+
describe('Api.prototype.setTab : ', () => {
433+
test('it should call optionsManager.validateObjectiveTabData and validatePanelComponent internally', () => {
434+
obj.optionsManager.validateObjectiveTabData = jest.fn(() => obj.optionsManager);
435+
obj.optionsManager.validatePanelComponent = jest.fn(() => obj.optionsManager);
436+
obj.setTab('1', { title: 'c' });
437+
expect(obj.optionsManager.validateObjectiveTabData).toHaveBeenCalledBefore(obj.optionsManager.validatePanelComponent);
438+
});
397439
});

src/utils/api/api.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ import Tabs from './tabs.js';
77
import BaseApi from './baseApi.js';
88
const getDeps = function (options = {}) {
99
const activedTabsHistory = new (ActivedTabsHistory)(), optionsManager = new (OptionManager)({ options });
10-
BaseApi.call(this, helper);
11-
Tabs.call(this);
10+
BaseApi.call(this, {
11+
helper,
12+
initialState: optionsManager.initialState
13+
});
14+
Tabs.call(this, { initialTabs: optionsManager.initialTabs, });
1215
Pub_Sub.call(this);
1316
return { activedTabsHistory, helper, optionsManager };
1417
};

src/utils/api/baseApi.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import actions from '../stateManagement/actions';
22
import Helper from '../helper.js';
3-
function BaseApi(helper) {
3+
function BaseApi({ helper, initialState }) {
44
this._helper = helper;
5-
this._state = null;// it will be updated after each render
6-
this._initialState = null;
7-
this._perviousState = null;// it is a pervious value of this._state
5+
this._state = this._helper.getCopyState(initialState);// it will be updated after each render
6+
this._perviousState = this._helper.getCopyState(initialState);// it is a pervious value of this._state
87
this._dispatch = null;
98
this._setFlushState = null;
109
this._isReady = false;
@@ -27,7 +26,7 @@ BaseApi.prototype.__flushEffects = function () {
2726
Helper.setNoneEnumProps(BaseApi.prototype, {
2827
updateStateRef: function (state, dispatch) { this.stateRef = state; this._dispatch = dispatch; return this; },
2928
updateState: function (state) {
30-
this._perviousState = this._helper.getCopyState(this._state || state);
29+
this._perviousState = this._helper.getCopyState(this._state);
3130
this._state = this._helper.getCopyState(state);
3231
return this;
3332
},

src/utils/api/optionManager/optionManager.factory.js

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1+
import React from 'react';
12
import Helper from '../../helper.js';
23
const { throwMissingParam: missingParamEr } = Helper;
34
function OptionManager(getDeps, { options }) {
45
const { globalDefaultOptions } = getDeps();
56
this._defaultOptions = globalDefaultOptions;
67
this._validateOptions(options);
78
this.options = Object.assign({}, this._defaultOptions, options);
8-
this._setSetting();
9+
this.setting = {};
10+
this.initialState = {};
11+
this.initialTabs = [];
12+
this._setSetting()._setInitialData();
913
};
1014
OptionManager.prototype.getOption = function (OptionName) {
1115
return this.options[OptionName];
@@ -17,11 +21,46 @@ OptionManager.prototype.setOption = function (name = missingParamEr('setOption')
1721
this.options[name] = value;
1822
return this;
1923
};
24+
OptionManager.prototype.validatePanelComponent = function (tabData) {
25+
// convert panel element into a function component.
26+
if (tabData.panelComponent && typeof tabData.panelComponent !== 'function' && React.isValidElement(tabData.panelComponent)) {
27+
tabData.panelComponent = function (props) {
28+
const PanelElement = tabData.panelComponent;
29+
return PanelElement;
30+
};
31+
}
32+
return this;
33+
};
34+
OptionManager.prototype.validateObjectiveTabData = function (tabData) {
35+
if (Object.prototype.toString.call(tabData) !== '[object Object]')
36+
throw new Error('tabData must be type of Object');
37+
return this;
38+
};
39+
OptionManager.prototype.validateTabData = function (tabData) {
40+
this.validateObjectiveTabData(tabData).validatePanelComponent(tabData);
41+
tabData = Object.assign(this.setting.getDefaultTabData(), tabData);
42+
tabData.id = tabData.id + '';//make sure id is string
43+
return tabData;
44+
};
2045
OptionManager.prototype._validateOptions = function (options) {
2146
if (Object.prototype.toString.call(options) !== '[object Object]')
2247
throw 'Invalid argument in "useDynamicTabs" function. Argument must be type of an object';
2348
return this;
2449
};
50+
OptionManager.prototype._setInitialData = function () {
51+
// set this.initialTabs and this.initialState
52+
const { selectedTabID, tabs } = this.options, openTabIDs = [];
53+
tabs.map(tab => {
54+
const newTab = this.validateTabData(tab);
55+
this.initialTabs.push(newTab);
56+
openTabIDs.push(newTab.id);
57+
});
58+
this.initialState = {
59+
selectedTabID: selectedTabID + '', //make sure it is type of string
60+
openTabIDs
61+
};
62+
return this;
63+
};
2564
OptionManager.prototype._setSetting = function () {
2665
this.setting = {
2766
tabClass: 'rc-dyn-tabs-tab',
@@ -37,7 +76,18 @@ OptionManager.prototype._setSetting = function () {
3776
ltrClass: 'rc-dyn-tabs-ltr',
3877
rtlClass: 'rc-dyn-tabs-rtl',
3978
panelIdTemplate: id => `rc-dyn-tabs-p-${id}`,
40-
ariaLabelledbyIdTemplate: id => `rc-dyn-tabs-l-${id}`
79+
ariaLabelledbyIdTemplate: id => `rc-dyn-tabs-l-${id}`,
80+
getDefaultTabData: () => {
81+
return {
82+
title: "",
83+
tooltip: "",
84+
panelComponent: this.options.defaultPanelComponent,
85+
closable: true,
86+
iconClass: "",
87+
disable: false,
88+
id: `tab_${(new (Date)()).getTime()}`
89+
};
90+
}
4191
};
4292
return this;
4393
};

0 commit comments

Comments
 (0)