Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
81159 views
1
/**
2
* Copyright 2013-2014, Facebook, Inc.
3
* All rights reserved.
4
*
5
* This source code is licensed under the BSD-style license found in the
6
* LICENSE file in the root directory of this source tree. An additional grant
7
* of patent rights can be found in the PATENTS file in the same directory.
8
*
9
* @emails react-core
10
*/
11
12
/*jslint evil: true */
13
14
"use strict";
15
16
var assign = require('Object.assign');
17
var mocks = require('mocks');
18
19
describe('ReactDOMComponent', function() {
20
21
describe('updateDOM', function() {
22
var React;
23
var ReactTestUtils;
24
var transaction;
25
26
beforeEach(function() {
27
React = require('React');
28
ReactTestUtils = require('ReactTestUtils');
29
30
var ReactReconcileTransaction = require('ReactReconcileTransaction');
31
transaction = new ReactReconcileTransaction();
32
});
33
34
it("should handle className", function() {
35
var stub = ReactTestUtils.renderIntoDocument(<div style={{}} />);
36
37
stub.receiveComponent({props: { className: 'foo' }}, transaction);
38
expect(stub.getDOMNode().className).toEqual('foo');
39
stub.receiveComponent({props: { className: 'bar' }}, transaction);
40
expect(stub.getDOMNode().className).toEqual('bar');
41
stub.receiveComponent({props: { className: null }}, transaction);
42
expect(stub.getDOMNode().className).toEqual('');
43
});
44
45
it("should gracefully handle various style value types", function() {
46
var stub = ReactTestUtils.renderIntoDocument(<div style={{}} />);
47
var stubStyle = stub.getDOMNode().style;
48
49
// set initial style
50
var setup = { display: 'block', left: '1', top: 2, fontFamily: 'Arial' };
51
stub.receiveComponent({props: { style: setup }}, transaction);
52
expect(stubStyle.display).toEqual('block');
53
expect(stubStyle.left).toEqual('1px');
54
expect(stubStyle.fontFamily).toEqual('Arial');
55
56
// reset the style to their default state
57
var reset = { display: '', left: null, top: false, fontFamily: true };
58
stub.receiveComponent({props: { style: reset }}, transaction);
59
expect(stubStyle.display).toEqual('');
60
expect(stubStyle.left).toEqual('');
61
expect(stubStyle.top).toEqual('');
62
expect(stubStyle.fontFamily).toEqual('');
63
});
64
65
it("should update styles when mutating style object", function() {
66
var styles = { display: 'none', fontFamily: 'Arial', lineHeight: 1.2 };
67
var stub = ReactTestUtils.renderIntoDocument(<div style={styles} />);
68
69
var stubStyle = stub.getDOMNode().style;
70
stubStyle.display = styles.display;
71
stubStyle.fontFamily = styles.fontFamily;
72
73
styles.display = 'block';
74
75
stub.receiveComponent({props: { style: styles }}, transaction);
76
expect(stubStyle.display).toEqual('block');
77
expect(stubStyle.fontFamily).toEqual('Arial');
78
expect(stubStyle.lineHeight).toEqual('1.2');
79
80
styles.fontFamily = 'Helvetica';
81
82
stub.receiveComponent({props: { style: styles }}, transaction);
83
expect(stubStyle.display).toEqual('block');
84
expect(stubStyle.fontFamily).toEqual('Helvetica');
85
expect(stubStyle.lineHeight).toEqual('1.2');
86
87
styles.lineHeight = 0.5;
88
89
stub.receiveComponent({props: { style: styles }}, transaction);
90
expect(stubStyle.display).toEqual('block');
91
expect(stubStyle.fontFamily).toEqual('Helvetica');
92
expect(stubStyle.lineHeight).toEqual('0.5');
93
94
stub.receiveComponent({props: { style: undefined }}, transaction);
95
expect(stubStyle.display).toBe('');
96
expect(stubStyle.fontFamily).toBe('');
97
expect(stubStyle.lineHeight).toBe('');
98
});
99
100
it("should update styles if initially null", function() {
101
var styles = null;
102
var stub = ReactTestUtils.renderIntoDocument(<div style={styles} />);
103
104
var stubStyle = stub.getDOMNode().style;
105
106
styles = {display: 'block'};
107
108
stub.receiveComponent({props: { style: styles }}, transaction);
109
expect(stubStyle.display).toEqual('block');
110
});
111
112
it("should remove attributes", function() {
113
var stub = ReactTestUtils.renderIntoDocument(<img height='17' />);
114
115
expect(stub.getDOMNode().hasAttribute('height')).toBe(true);
116
stub.receiveComponent({props: {}}, transaction);
117
expect(stub.getDOMNode().hasAttribute('height')).toBe(false);
118
});
119
120
it("should remove properties", function() {
121
var stub = ReactTestUtils.renderIntoDocument(<div className='monkey' />);
122
123
expect(stub.getDOMNode().className).toEqual('monkey');
124
stub.receiveComponent({props: {}}, transaction);
125
expect(stub.getDOMNode().className).toEqual('');
126
});
127
128
it("should clear a single style prop when changing 'style'", function() {
129
var styles = {display: 'none', color: 'red'};
130
var stub = ReactTestUtils.renderIntoDocument(<div style={styles} />);
131
132
var stubStyle = stub.getDOMNode().style;
133
134
styles = {color: 'green'};
135
stub.receiveComponent({props: { style: styles }}, transaction);
136
expect(stubStyle.display).toEqual('');
137
expect(stubStyle.color).toEqual('green');
138
});
139
140
it("should clear all the styles when removing 'style'", function() {
141
var styles = {display: 'none', color: 'red'};
142
var stub = ReactTestUtils.renderIntoDocument(<div style={styles} />);
143
144
var stubStyle = stub.getDOMNode().style;
145
146
stub.receiveComponent({props: {}}, transaction);
147
expect(stubStyle.display).toEqual('');
148
expect(stubStyle.color).toEqual('');
149
});
150
151
it("should empty element when removing innerHTML", function() {
152
var stub = ReactTestUtils.renderIntoDocument(
153
<div dangerouslySetInnerHTML={{__html: ':)'}} />
154
);
155
156
expect(stub.getDOMNode().innerHTML).toEqual(':)');
157
stub.receiveComponent({props: {}}, transaction);
158
expect(stub.getDOMNode().innerHTML).toEqual('');
159
});
160
161
it("should transition from string content to innerHTML", function() {
162
var stub = ReactTestUtils.renderIntoDocument(
163
<div>hello</div>
164
);
165
166
expect(stub.getDOMNode().innerHTML).toEqual('hello');
167
stub.receiveComponent(
168
{props: {dangerouslySetInnerHTML: {__html: 'goodbye'}}},
169
transaction
170
);
171
expect(stub.getDOMNode().innerHTML).toEqual('goodbye');
172
});
173
174
it("should transition from innerHTML to string content", function() {
175
var stub = ReactTestUtils.renderIntoDocument(
176
<div dangerouslySetInnerHTML={{__html: 'bonjour'}} />
177
);
178
179
expect(stub.getDOMNode().innerHTML).toEqual('bonjour');
180
stub.receiveComponent({props: {children: 'adieu'}}, transaction);
181
expect(stub.getDOMNode().innerHTML).toEqual('adieu');
182
});
183
184
it("should not incur unnecessary DOM mutations", function() {
185
var stub = ReactTestUtils.renderIntoDocument(<div value="" />);
186
187
var node = stub.getDOMNode();
188
var nodeValue = ''; // node.value always returns undefined
189
var nodeValueSetter = mocks.getMockFunction();
190
Object.defineProperty(node, 'value', {
191
get: function() {
192
return nodeValue;
193
},
194
set: nodeValueSetter.mockImplementation(function(newValue) {
195
nodeValue = newValue;
196
})
197
});
198
199
stub.receiveComponent({props: {value: ''}}, transaction);
200
expect(nodeValueSetter.mock.calls.length).toBe(0);
201
202
stub.receiveComponent({props: {}}, transaction);
203
expect(nodeValueSetter.mock.calls.length).toBe(1);
204
});
205
});
206
207
describe('createOpenTagMarkup', function() {
208
var genMarkup;
209
210
function quoteRegexp(str) {
211
return (str+'').replace(/([.?*+\^$\[\]\\(){}|-])/g, "\\$1");
212
}
213
214
beforeEach(function() {
215
require('mock-modules').dumpCache();
216
217
var ReactDefaultInjection = require('ReactDefaultInjection');
218
ReactDefaultInjection.inject();
219
220
var ReactDOMComponent = require('ReactDOMComponent');
221
var ReactReconcileTransaction = require('ReactReconcileTransaction');
222
223
var NodeStub = function(initialProps) {
224
this.props = initialProps || {};
225
this._rootNodeID = 'test';
226
};
227
assign(NodeStub.prototype, ReactDOMComponent.Mixin);
228
229
genMarkup = function(props) {
230
var transaction = new ReactReconcileTransaction();
231
return (new NodeStub(props))._createOpenTagMarkupAndPutListeners(
232
transaction
233
);
234
};
235
236
this.addMatchers({
237
toHaveAttribute: function(attr, value) {
238
var expected = '(?:^|\\s)' + attr + '=[\\\'"]';
239
if (typeof value != 'undefined') {
240
expected += quoteRegexp(value) + '[\\\'"]';
241
}
242
return this.actual.match(new RegExp(expected));
243
}
244
});
245
});
246
247
it("should generate the correct markup with className", function() {
248
expect(genMarkup({ className: 'a' })).toHaveAttribute('class', 'a');
249
expect(genMarkup({ className: 'a b' })).toHaveAttribute('class', 'a b');
250
expect(genMarkup({ className: '' })).toHaveAttribute('class', '');
251
});
252
253
it("should escape style names and values", function() {
254
expect(genMarkup({
255
style: {'b&ckground': '<3'}
256
})).toHaveAttribute('style', 'b&amp;ckground:&lt;3;');
257
});
258
});
259
260
describe('createContentMarkup', function() {
261
var genMarkup;
262
263
function quoteRegexp(str) {
264
return (str+'').replace(/([.?*+\^$\[\]\\(){}|-])/g, "\\$1");
265
}
266
267
beforeEach(function() {
268
require('mock-modules').dumpCache();
269
270
var ReactDOMComponent = require('ReactDOMComponent');
271
var ReactReconcileTransaction = require('ReactReconcileTransaction');
272
273
var NodeStub = function(initialProps) {
274
this.props = initialProps || {};
275
this._rootNodeID = 'test';
276
};
277
assign(NodeStub.prototype, ReactDOMComponent.Mixin);
278
279
genMarkup = function(props) {
280
var transaction = new ReactReconcileTransaction();
281
return (new NodeStub(props))._createContentMarkup(transaction);
282
};
283
284
this.addMatchers({
285
toHaveInnerhtml: function(html) {
286
var expected = '^' + quoteRegexp(html) + '$';
287
return this.actual.match(new RegExp(expected));
288
}
289
});
290
});
291
292
it("should handle dangerouslySetInnerHTML", function() {
293
var innerHTML = {__html: 'testContent'};
294
expect(
295
genMarkup({ dangerouslySetInnerHTML: innerHTML })
296
).toHaveInnerhtml('testContent');
297
});
298
});
299
300
describe('mountComponent', function() {
301
var mountComponent;
302
303
beforeEach(function() {
304
require('mock-modules').dumpCache();
305
306
var ReactComponent = require('ReactComponent');
307
var ReactMultiChild = require('ReactMultiChild');
308
var ReactDOMComponent = require('ReactDOMComponent');
309
var ReactReconcileTransaction = require('ReactReconcileTransaction');
310
311
var StubNativeComponent = function(element) {
312
ReactComponent.Mixin.construct.call(this, element);
313
};
314
assign(StubNativeComponent.prototype, ReactComponent.Mixin);
315
assign(StubNativeComponent.prototype, ReactDOMComponent.Mixin);
316
assign(StubNativeComponent.prototype, ReactMultiChild.Mixin);
317
318
mountComponent = function(props) {
319
var transaction = new ReactReconcileTransaction();
320
var stubComponent = new StubNativeComponent({
321
type: StubNativeComponent,
322
props: props,
323
_owner: null,
324
_context: null
325
});
326
return stubComponent.mountComponent('test', transaction, 0);
327
};
328
});
329
330
it("should validate against multiple children props", function() {
331
expect(function() {
332
mountComponent({ children: '', dangerouslySetInnerHTML: '' });
333
}).toThrow(
334
'Invariant Violation: Can only set one of `children` or ' +
335
'`props.dangerouslySetInnerHTML`.'
336
);
337
});
338
339
it("should warn about contentEditable and children", function() {
340
spyOn(console, 'warn');
341
mountComponent({ contentEditable: true, children: '' });
342
expect(console.warn.argsForCall.length).toBe(1);
343
expect(console.warn.argsForCall[0][0]).toContain('contentEditable');
344
});
345
346
it("should validate against invalid styles", function() {
347
expect(function() {
348
mountComponent({ style: 'display: none' });
349
}).toThrow(
350
'Invariant Violation: The `style` prop expects a mapping from style ' +
351
'properties to values, not a string.'
352
);
353
});
354
});
355
356
describe('updateComponent', function() {
357
var React;
358
var container;
359
360
beforeEach(function() {
361
React = require('React');
362
container = document.createElement('div');
363
});
364
365
it("should validate against multiple children props", function() {
366
React.render(<div></div>, container);
367
368
expect(function() {
369
React.render(
370
<div children="" dangerouslySetInnerHTML={{__html: ''}}></div>,
371
container
372
);
373
}).toThrow(
374
'Invariant Violation: Can only set one of `children` or ' +
375
'`props.dangerouslySetInnerHTML`.'
376
);
377
});
378
379
it("should warn about contentEditable and children", function() {
380
spyOn(console, 'warn');
381
React.render(
382
<div contentEditable><div /></div>,
383
container
384
);
385
expect(console.warn.argsForCall.length).toBe(1);
386
expect(console.warn.argsForCall[0][0]).toContain('contentEditable');
387
});
388
389
it("should validate against invalid styles", function() {
390
React.render(<div></div>, container);
391
392
expect(function() {
393
React.render(<div style={1}></div>, container);
394
}).toThrow(
395
'Invariant Violation: The `style` prop expects a mapping from style ' +
396
'properties to values, not a string.'
397
);
398
});
399
});
400
401
describe('unmountComponent', function() {
402
it("should clean up listeners", function() {
403
var React = require('React');
404
var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
405
var ReactMount = require('ReactMount');
406
407
var container = document.createElement('div');
408
document.documentElement.appendChild(container);
409
410
var callback = function() {};
411
var instance = <div onClick={callback} />;
412
instance = React.render(instance, container);
413
414
var rootNode = instance.getDOMNode();
415
var rootNodeID = ReactMount.getID(rootNode);
416
expect(
417
ReactBrowserEventEmitter.getListener(rootNodeID, 'onClick')
418
).toBe(callback);
419
420
React.unmountComponentAtNode(container);
421
422
expect(
423
ReactBrowserEventEmitter.getListener(rootNodeID, 'onClick')
424
).toBe(undefined);
425
});
426
});
427
428
describe('onScroll warning', function() {
429
it('should warn about the `onScroll` issue when unsupported (IE8)', () => {
430
// Mock this here so we can mimic IE8 support. We require isEventSupported
431
// before React so it's pre-mocked before React qould require it.
432
require('mock-modules')
433
.dumpCache()
434
.mock('isEventSupported');
435
var isEventSupported = require('isEventSupported');
436
isEventSupported.mockReturnValueOnce(false);
437
438
var React = require('React');
439
var ReactTestUtils = require('ReactTestUtils');
440
441
spyOn(console, 'warn');
442
ReactTestUtils.renderIntoDocument(<div onScroll={function(){}} />);
443
expect(console.warn.callCount).toBe(1);
444
expect(console.warn.mostRecentCall.args[0]).toBe(
445
'This browser doesn\'t support the `onScroll` event'
446
);
447
});
448
});
449
450
describe('tag sanitization', function() {
451
it('should throw when an invalid tag name is used', () => {
452
var React = require('React');
453
var ReactTestUtils = require('ReactTestUtils');
454
var hackzor = React.createElement('script tag');
455
expect(
456
() => ReactTestUtils.renderIntoDocument(hackzor)
457
).toThrow(
458
'Invariant Violation: Invalid tag: script tag'
459
);
460
});
461
462
it('should throw when an attack vector is used', () => {
463
var React = require('React');
464
var ReactTestUtils = require('ReactTestUtils');
465
var hackzor = React.createElement('div><img /><div');
466
expect(
467
() => ReactTestUtils.renderIntoDocument(hackzor)
468
).toThrow(
469
'Invariant Violation: Invalid tag: div><img /><div'
470
);
471
});
472
473
});
474
});
475
476