Skip to content

Commit b3dbe04

Browse files
authored
Merge pull request #44 from Macrox/fix/unexpected-context-overriding
fix(ConfigProvider): Isolate multiple context data
2 parents 0af98fd + 557597c commit b3dbe04

File tree

5 files changed

+106
-124
lines changed

5 files changed

+106
-124
lines changed

docs/dialog/demo/quick.md

+11-6
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ The Dialog provides quick methods called alert and confirm, as well as a lower-l
1515
---
1616

1717
````jsx
18-
import { Button, Dialog } from '@alifd/next';
18+
import { Button, Dialog, ConfigProvider } from '@alifd/next';
1919

2020

2121
const popupAlert = () => {
@@ -47,9 +47,14 @@ const popupCustom = () => {
4747
});
4848
};
4949

50-
ReactDOM.render(<span>
51-
<Button onClick={popupAlert}>Alert</Button> &nbsp;
52-
<Button onClick={popupConfirm}>Confirm</Button> &nbsp;
53-
<Button onClick={popupCustom}>Custom</Button>
54-
</span>, mountNode);
50+
ReactDOM.render(
51+
<ConfigProvider locale={{ Dialog: { ok: 'OK', cancel: 'Cancel' } }}>
52+
<span>
53+
<Button onClick={popupAlert}>Alert</Button> &nbsp;
54+
<Button onClick={popupConfirm}>Confirm</Button> &nbsp;
55+
<Button onClick={popupCustom}>Custom</Button>
56+
</span>
57+
</ConfigProvider>,
58+
mountNode
59+
);
5560
````

src/config-provider/cache.js

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
class Cache {
2+
constructor() {
3+
this._root = null;
4+
this._store = new Map();
5+
}
6+
7+
empty() {
8+
return this._store.size === 0;
9+
}
10+
11+
has(key) {
12+
return this._store.has(key)
13+
}
14+
15+
get(key, defaultValue) {
16+
const res = this.has(key) ? this._store.get(key) : this.root()
17+
return (typeof res === 'undefined' || res === null) ?
18+
defaultValue : res
19+
}
20+
21+
add(key, value) {
22+
const store = this._store;
23+
if (this.empty()) {
24+
this._root = key;
25+
}
26+
this._store.set(key, value);
27+
}
28+
29+
update(key, value) {
30+
if (this.has(key)) {
31+
this._store.set(key, value)
32+
}
33+
}
34+
35+
remove(key) {
36+
this._store.delete(key);
37+
}
38+
39+
root() {
40+
return this._store.get(this._root)
41+
}
42+
}
43+
44+
export default Cache;

src/config-provider/index.jsx

+17-9
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import PropTypes from 'prop-types';
33
import getContextProps from './get-context-props';
44
import { config, initLocales, setLanguage, setLocale, getLocale, getLanguage } from './config';
55
import Consumer from './consumer';
6+
import Cache from './cache'
67

7-
let childContextCache = {};
8+
let childContextCache = new Cache();
89

910
/**
1011
* ConfigProvider
@@ -62,7 +63,7 @@ class ConfigProvider extends Component {
6263
* @returns {Object} 新的 context props
6364
*/
6465
static getContextProps = (props, displayName) => {
65-
return getContextProps(props, childContextCache, displayName);
66+
return getContextProps(props, childContextCache.root() || {}, displayName);
6667
};
6768

6869
static initLocales = initLocales;
@@ -73,7 +74,7 @@ class ConfigProvider extends Component {
7374
static Consumer = Consumer;
7475

7576
static getContext = () => {
76-
const { nextPrefix, nextLocale, nextPure, nextWarning } = childContextCache;
77+
const { nextPrefix, nextLocale, nextPure, nextWarning } = childContextCache.root() || {};
7778

7879
return {
7980
prefix: nextPrefix,
@@ -83,6 +84,14 @@ class ConfigProvider extends Component {
8384
};
8485
}
8586

87+
constructor(...args) {
88+
super(...args)
89+
childContextCache.add(
90+
this,
91+
Object.assign({}, childContextCache.get(this, {}), this.getChildContext())
92+
);
93+
}
94+
8695
getChildContext() {
8796
const { prefix, locale, pure, warning } = this.props;
8897

@@ -98,22 +107,21 @@ class ConfigProvider extends Component {
98107
this.setMomentLocale(this.props.locale);
99108
}
100109

101-
componentDidMount() {
102-
childContextCache = Object.assign({}, this.getChildContext(), childContextCache);
103-
}
104-
105110
componentWillReceiveProps(nextProps) {
106111
if (this.props.locale !== nextProps.locale) {
107112
this.setMomentLocale(nextProps.locale);
108113
}
109114
}
110115

111116
componentDidUpdate() {
112-
childContextCache = this.getChildContext();
117+
childContextCache.add(
118+
this,
119+
Object.assign({}, childContextCache.get(this, {}), this.getChildContext())
120+
);
113121
}
114122

115123
componentWillUnmount() {
116-
childContextCache = {};
124+
childContextCache.remove(this);
117125
}
118126

119127
setMomentLocale(locale) {

test/dialog/index-spec.js

+5-76
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ describe('inner', () => {
356356
assert(cancel.innerHTML === 'near cancel');
357357
});
358358

359-
it('quick should obey: self.locale > nearest ConfigProvider.locale > further ConfigProvider.locale', () => {
359+
it('quick-calling should use root context\'s state if its exists', () => {
360360
wrapper = render(
361361
<ConfigProvider prefix="far-" locale={{
362362
momentLocale: 'en',
@@ -391,90 +391,19 @@ describe('inner', () => {
391391
const btn = document.querySelector('button');
392392
ReactTestUtils.Simulate.click(btn);
393393

394-
const footer = document.querySelector('.near-dialog-footer');
395-
const overlayWrapper = document.querySelector('.near-overlay-wrapper');
394+
const footer = document.querySelector('.far-dialog-footer');
395+
const overlayWrapper = document.querySelector('.far-overlay-wrapper');
396396
const ok = footer.querySelectorAll('button')[0];
397397
const cancel = footer.querySelectorAll('button')[1];
398398

399399
assert(footer);
400400
assert(overlayWrapper);
401-
assert(ok.innerHTML === 'near ok');
401+
assert(ok.innerHTML === 'far ok');
402402
assert(cancel.innerHTML === 'my cancel');
403403

404404
document.body.removeChild(overlayWrapper);
405405

406-
assert(!document.querySelector('.near-overlay-wrapper'));
407-
});
408-
409-
it('alert should obey: self.locale > nearest ConfigProvider.locale > further ConfigProvider.locale', () => {
410-
wrapper = render(
411-
<ConfigProvider prefix="far-" locale={{
412-
momentLocale: 'en',
413-
Dialog: {
414-
ok: 'far ok',
415-
cancel: 'far cancel'
416-
}
417-
}}>
418-
<ConfigProvider prefix="near-" locale={{
419-
momentLocale: 'en',
420-
Dialog: {
421-
ok: 'near ok',
422-
cancel: 'near cancel'
423-
}
424-
}}>
425-
<div>
426-
<Button
427-
type="primary"
428-
onClick={() => {
429-
Dialog.alert({
430-
locale: {
431-
ok: 'my ok'
432-
},
433-
content: <Button type="primary">test</Button>,
434-
});
435-
}}>
436-
OK
437-
</Button>
438-
<Button
439-
type="primary"
440-
onClick={() => {
441-
Dialog.alert({
442-
content: <Button type="primary">test</Button>,
443-
});
444-
}}>
445-
OK
446-
</Button>
447-
</div>
448-
</ConfigProvider>
449-
</ConfigProvider>
450-
);
451-
452-
const btn1 = document.querySelectorAll('button')[0];
453-
const btn2 = document.querySelectorAll('button')[1];
454-
ReactTestUtils.Simulate.click(btn1);
455-
let footer = document.querySelector('.near-dialog-footer');
456-
let overlayWrapper = document.querySelector('.near-overlay-wrapper');
457-
let ok = footer.querySelectorAll('button')[0];
458-
let innerBtn = document.querySelector('.near-dialog-message button.near-btn-primary');
459-
460-
assert(footer);
461-
assert(overlayWrapper);
462-
assert(innerBtn);
463-
assert(ok.innerHTML === 'my ok');
464-
document.body.removeChild(overlayWrapper);
465-
assert(!document.querySelector('.near-overlay-wrapper'));
466-
467-
ReactTestUtils.Simulate.click(btn2);
468-
footer = document.querySelector('.near-dialog-footer');
469-
overlayWrapper = document.querySelector('.near-overlay-wrapper');
470-
ok = footer.querySelectorAll('button')[0];
471-
innerBtn = document.querySelector('.near-dialog-message button.near-btn-primary');
472-
assert(footer);
473-
assert(overlayWrapper);
474-
assert(innerBtn);
475-
assert(ok.innerHTML === 'near ok');
476-
document.body.removeChild(overlayWrapper);
477-
assert(!document.querySelector('.near-overlay-wrapper'));
406+
assert(!document.querySelector('.far-overlay-wrapper'));
478407
});
479408
});
480409

test/message/index-spec.js

+29-33
Original file line numberDiff line numberDiff line change
@@ -247,31 +247,10 @@ describe('toast', done => {
247247
});
248248
});
249249

250-
describe('toast quick', () => {
251-
it('should render type message', () => {
252-
const methods = ['success', 'warning', 'error', 'notice', 'help', 'loading'];
253-
methods.forEach(method => {
254-
Message[method](method);
255-
const nodes = document.querySelectorAll(`.next-overlay-wrapper .next-message.next-message-${method}`);
256-
assert(nodes[nodes.length - 1].innerText.trim() === method);
257-
});
258-
});
259-
});
260-
261250
describe('should support configProvider', () => {
262-
263-
let wrapper;
264-
265-
afterEach(() => {
266-
if (wrapper) {
267-
wrapper.unmount();
268-
wrapper = null;
269-
}
270-
});
271-
272251
it('normal should obey: self.locale > nearest ConfigProvider.locale > further ConfigProvider.locale', () => {
273252
const methods = ['success', 'warning', 'error', 'notice', 'help', 'loading'];
274-
wrapper = render(
253+
const wrapper = render(
275254
<ConfigProvider prefix="far-" locale={{
276255
momentLocale: 'en',
277256
Dialog: {
@@ -298,12 +277,13 @@ describe('should support configProvider', () => {
298277
);
299278
const innerBtn = document.querySelectorAll('.near-message .near-message-content .near-btn-primary');
300279
assert(innerBtn.length === methods.length);
280+
wrapper.unmount();
301281
});
302282

303-
it('quick should obey: self.locale > nearest ConfigProvider.locale > further ConfigProvider.locale', () => {
283+
it('quick-calling should use root context\'s state if its exists', () => {
304284
const methods = ['success', 'warning', 'error', 'notice', 'help', 'loading'];
305285
methods.forEach(method => {
306-
wrapper = render(
286+
const wrapper = render(
307287
<ConfigProvider prefix="far-" locale={{
308288
momentLocale: 'en',
309289
Dialog: {
@@ -337,20 +317,36 @@ describe('should support configProvider', () => {
337317

338318
const btn = document.querySelector('button');
339319
ReactTestUtils.Simulate.click(btn);
340-
const overlayWrapper = document.querySelector('.near-overlay-wrapper');
341-
const icon = document.querySelector('.near-icon.near-message-symbol');
342-
const innerBtn = document.querySelector('.near-message-content .near-btn-primary');
320+
const icon = document.querySelector('.far-icon.far-message-symbol');
321+
const innerBtn = document.querySelector('.far-message-content .far-btn-primary');
343322

344-
assert(overlayWrapper);
345323
assert(icon);
346324
assert(innerBtn);
347325

348326
wrapper.unmount();
349-
wrapper = null;
350327
});
351-
352-
const lastOverlayWrapper = document.querySelector('.near-overlay-wrapper');
353-
document.body.removeChild(lastOverlayWrapper);
354-
assert(!document.querySelector('.near-overlay-wrapper'));
355328
});
356329
});
330+
331+
describe('toast quick-calling', () => {
332+
const avaliableMethods = [
333+
'success',
334+
'warning',
335+
'error',
336+
'notice',
337+
'help',
338+
'loading',
339+
];
340+
341+
for (const method of avaliableMethods) {
342+
it(`render ${method}`, (done) => {
343+
Message.show('content');
344+
assert(document.querySelector('.next-overlay-wrapper .next-message').innerText.trim() === 'content');
345+
setTimeout(() => {
346+
Message.hide();
347+
}, 500);
348+
setTimeout(done, 1000);
349+
done()
350+
})
351+
}
352+
});

0 commit comments

Comments
 (0)