react / react-0.13.3 / examples / basic-commonjs / node_modules / reactify / node_modules / react-tools / src / browser / ui / __tests__ / ReactDOMComponent-test.js
81159 views/**1* Copyright 2013-2014, Facebook, Inc.2* All rights reserved.3*4* This source code is licensed under the BSD-style license found in the5* LICENSE file in the root directory of this source tree. An additional grant6* of patent rights can be found in the PATENTS file in the same directory.7*8* @emails react-core9*/1011/*jslint evil: true */1213"use strict";1415var assign = require('Object.assign');16var mocks = require('mocks');1718describe('ReactDOMComponent', function() {1920describe('updateDOM', function() {21var React;22var ReactTestUtils;23var transaction;2425beforeEach(function() {26React = require('React');27ReactTestUtils = require('ReactTestUtils');2829var ReactReconcileTransaction = require('ReactReconcileTransaction');30transaction = new ReactReconcileTransaction();31});3233it("should handle className", function() {34var stub = ReactTestUtils.renderIntoDocument(<div style={{}} />);3536stub.receiveComponent({props: { className: 'foo' }}, transaction);37expect(stub.getDOMNode().className).toEqual('foo');38stub.receiveComponent({props: { className: 'bar' }}, transaction);39expect(stub.getDOMNode().className).toEqual('bar');40stub.receiveComponent({props: { className: null }}, transaction);41expect(stub.getDOMNode().className).toEqual('');42});4344it("should gracefully handle various style value types", function() {45var stub = ReactTestUtils.renderIntoDocument(<div style={{}} />);46var stubStyle = stub.getDOMNode().style;4748// set initial style49var setup = { display: 'block', left: '1', top: 2, fontFamily: 'Arial' };50stub.receiveComponent({props: { style: setup }}, transaction);51expect(stubStyle.display).toEqual('block');52expect(stubStyle.left).toEqual('1px');53expect(stubStyle.fontFamily).toEqual('Arial');5455// reset the style to their default state56var reset = { display: '', left: null, top: false, fontFamily: true };57stub.receiveComponent({props: { style: reset }}, transaction);58expect(stubStyle.display).toEqual('');59expect(stubStyle.left).toEqual('');60expect(stubStyle.top).toEqual('');61expect(stubStyle.fontFamily).toEqual('');62});6364it("should update styles when mutating style object", function() {65var styles = { display: 'none', fontFamily: 'Arial', lineHeight: 1.2 };66var stub = ReactTestUtils.renderIntoDocument(<div style={styles} />);6768var stubStyle = stub.getDOMNode().style;69stubStyle.display = styles.display;70stubStyle.fontFamily = styles.fontFamily;7172styles.display = 'block';7374stub.receiveComponent({props: { style: styles }}, transaction);75expect(stubStyle.display).toEqual('block');76expect(stubStyle.fontFamily).toEqual('Arial');77expect(stubStyle.lineHeight).toEqual('1.2');7879styles.fontFamily = 'Helvetica';8081stub.receiveComponent({props: { style: styles }}, transaction);82expect(stubStyle.display).toEqual('block');83expect(stubStyle.fontFamily).toEqual('Helvetica');84expect(stubStyle.lineHeight).toEqual('1.2');8586styles.lineHeight = 0.5;8788stub.receiveComponent({props: { style: styles }}, transaction);89expect(stubStyle.display).toEqual('block');90expect(stubStyle.fontFamily).toEqual('Helvetica');91expect(stubStyle.lineHeight).toEqual('0.5');9293stub.receiveComponent({props: { style: undefined }}, transaction);94expect(stubStyle.display).toBe('');95expect(stubStyle.fontFamily).toBe('');96expect(stubStyle.lineHeight).toBe('');97});9899it("should update styles if initially null", function() {100var styles = null;101var stub = ReactTestUtils.renderIntoDocument(<div style={styles} />);102103var stubStyle = stub.getDOMNode().style;104105styles = {display: 'block'};106107stub.receiveComponent({props: { style: styles }}, transaction);108expect(stubStyle.display).toEqual('block');109});110111it("should remove attributes", function() {112var stub = ReactTestUtils.renderIntoDocument(<img height='17' />);113114expect(stub.getDOMNode().hasAttribute('height')).toBe(true);115stub.receiveComponent({props: {}}, transaction);116expect(stub.getDOMNode().hasAttribute('height')).toBe(false);117});118119it("should remove properties", function() {120var stub = ReactTestUtils.renderIntoDocument(<div className='monkey' />);121122expect(stub.getDOMNode().className).toEqual('monkey');123stub.receiveComponent({props: {}}, transaction);124expect(stub.getDOMNode().className).toEqual('');125});126127it("should clear a single style prop when changing 'style'", function() {128var styles = {display: 'none', color: 'red'};129var stub = ReactTestUtils.renderIntoDocument(<div style={styles} />);130131var stubStyle = stub.getDOMNode().style;132133styles = {color: 'green'};134stub.receiveComponent({props: { style: styles }}, transaction);135expect(stubStyle.display).toEqual('');136expect(stubStyle.color).toEqual('green');137});138139it("should clear all the styles when removing 'style'", function() {140var styles = {display: 'none', color: 'red'};141var stub = ReactTestUtils.renderIntoDocument(<div style={styles} />);142143var stubStyle = stub.getDOMNode().style;144145stub.receiveComponent({props: {}}, transaction);146expect(stubStyle.display).toEqual('');147expect(stubStyle.color).toEqual('');148});149150it("should empty element when removing innerHTML", function() {151var stub = ReactTestUtils.renderIntoDocument(152<div dangerouslySetInnerHTML={{__html: ':)'}} />153);154155expect(stub.getDOMNode().innerHTML).toEqual(':)');156stub.receiveComponent({props: {}}, transaction);157expect(stub.getDOMNode().innerHTML).toEqual('');158});159160it("should transition from string content to innerHTML", function() {161var stub = ReactTestUtils.renderIntoDocument(162<div>hello</div>163);164165expect(stub.getDOMNode().innerHTML).toEqual('hello');166stub.receiveComponent(167{props: {dangerouslySetInnerHTML: {__html: 'goodbye'}}},168transaction169);170expect(stub.getDOMNode().innerHTML).toEqual('goodbye');171});172173it("should transition from innerHTML to string content", function() {174var stub = ReactTestUtils.renderIntoDocument(175<div dangerouslySetInnerHTML={{__html: 'bonjour'}} />176);177178expect(stub.getDOMNode().innerHTML).toEqual('bonjour');179stub.receiveComponent({props: {children: 'adieu'}}, transaction);180expect(stub.getDOMNode().innerHTML).toEqual('adieu');181});182183it("should not incur unnecessary DOM mutations", function() {184var stub = ReactTestUtils.renderIntoDocument(<div value="" />);185186var node = stub.getDOMNode();187var nodeValue = ''; // node.value always returns undefined188var nodeValueSetter = mocks.getMockFunction();189Object.defineProperty(node, 'value', {190get: function() {191return nodeValue;192},193set: nodeValueSetter.mockImplementation(function(newValue) {194nodeValue = newValue;195})196});197198stub.receiveComponent({props: {value: ''}}, transaction);199expect(nodeValueSetter.mock.calls.length).toBe(0);200201stub.receiveComponent({props: {}}, transaction);202expect(nodeValueSetter.mock.calls.length).toBe(1);203});204});205206describe('createOpenTagMarkup', function() {207var genMarkup;208209function quoteRegexp(str) {210return (str+'').replace(/([.?*+\^$\[\]\\(){}|-])/g, "\\$1");211}212213beforeEach(function() {214require('mock-modules').dumpCache();215216var ReactDefaultInjection = require('ReactDefaultInjection');217ReactDefaultInjection.inject();218219var ReactDOMComponent = require('ReactDOMComponent');220var ReactReconcileTransaction = require('ReactReconcileTransaction');221222var NodeStub = function(initialProps) {223this.props = initialProps || {};224this._rootNodeID = 'test';225};226assign(NodeStub.prototype, ReactDOMComponent.Mixin);227228genMarkup = function(props) {229var transaction = new ReactReconcileTransaction();230return (new NodeStub(props))._createOpenTagMarkupAndPutListeners(231transaction232);233};234235this.addMatchers({236toHaveAttribute: function(attr, value) {237var expected = '(?:^|\\s)' + attr + '=[\\\'"]';238if (typeof value != 'undefined') {239expected += quoteRegexp(value) + '[\\\'"]';240}241return this.actual.match(new RegExp(expected));242}243});244});245246it("should generate the correct markup with className", function() {247expect(genMarkup({ className: 'a' })).toHaveAttribute('class', 'a');248expect(genMarkup({ className: 'a b' })).toHaveAttribute('class', 'a b');249expect(genMarkup({ className: '' })).toHaveAttribute('class', '');250});251252it("should escape style names and values", function() {253expect(genMarkup({254style: {'b&ckground': '<3'}255})).toHaveAttribute('style', 'b&ckground:<3;');256});257});258259describe('createContentMarkup', function() {260var genMarkup;261262function quoteRegexp(str) {263return (str+'').replace(/([.?*+\^$\[\]\\(){}|-])/g, "\\$1");264}265266beforeEach(function() {267require('mock-modules').dumpCache();268269var ReactDOMComponent = require('ReactDOMComponent');270var ReactReconcileTransaction = require('ReactReconcileTransaction');271272var NodeStub = function(initialProps) {273this.props = initialProps || {};274this._rootNodeID = 'test';275};276assign(NodeStub.prototype, ReactDOMComponent.Mixin);277278genMarkup = function(props) {279var transaction = new ReactReconcileTransaction();280return (new NodeStub(props))._createContentMarkup(transaction);281};282283this.addMatchers({284toHaveInnerhtml: function(html) {285var expected = '^' + quoteRegexp(html) + '$';286return this.actual.match(new RegExp(expected));287}288});289});290291it("should handle dangerouslySetInnerHTML", function() {292var innerHTML = {__html: 'testContent'};293expect(294genMarkup({ dangerouslySetInnerHTML: innerHTML })295).toHaveInnerhtml('testContent');296});297});298299describe('mountComponent', function() {300var mountComponent;301302beforeEach(function() {303require('mock-modules').dumpCache();304305var ReactComponent = require('ReactComponent');306var ReactMultiChild = require('ReactMultiChild');307var ReactDOMComponent = require('ReactDOMComponent');308var ReactReconcileTransaction = require('ReactReconcileTransaction');309310var StubNativeComponent = function(element) {311ReactComponent.Mixin.construct.call(this, element);312};313assign(StubNativeComponent.prototype, ReactComponent.Mixin);314assign(StubNativeComponent.prototype, ReactDOMComponent.Mixin);315assign(StubNativeComponent.prototype, ReactMultiChild.Mixin);316317mountComponent = function(props) {318var transaction = new ReactReconcileTransaction();319var stubComponent = new StubNativeComponent({320type: StubNativeComponent,321props: props,322_owner: null,323_context: null324});325return stubComponent.mountComponent('test', transaction, 0);326};327});328329it("should validate against multiple children props", function() {330expect(function() {331mountComponent({ children: '', dangerouslySetInnerHTML: '' });332}).toThrow(333'Invariant Violation: Can only set one of `children` or ' +334'`props.dangerouslySetInnerHTML`.'335);336});337338it("should warn about contentEditable and children", function() {339spyOn(console, 'warn');340mountComponent({ contentEditable: true, children: '' });341expect(console.warn.argsForCall.length).toBe(1);342expect(console.warn.argsForCall[0][0]).toContain('contentEditable');343});344345it("should validate against invalid styles", function() {346expect(function() {347mountComponent({ style: 'display: none' });348}).toThrow(349'Invariant Violation: The `style` prop expects a mapping from style ' +350'properties to values, not a string.'351);352});353});354355describe('updateComponent', function() {356var React;357var container;358359beforeEach(function() {360React = require('React');361container = document.createElement('div');362});363364it("should validate against multiple children props", function() {365React.render(<div></div>, container);366367expect(function() {368React.render(369<div children="" dangerouslySetInnerHTML={{__html: ''}}></div>,370container371);372}).toThrow(373'Invariant Violation: Can only set one of `children` or ' +374'`props.dangerouslySetInnerHTML`.'375);376});377378it("should warn about contentEditable and children", function() {379spyOn(console, 'warn');380React.render(381<div contentEditable><div /></div>,382container383);384expect(console.warn.argsForCall.length).toBe(1);385expect(console.warn.argsForCall[0][0]).toContain('contentEditable');386});387388it("should validate against invalid styles", function() {389React.render(<div></div>, container);390391expect(function() {392React.render(<div style={1}></div>, container);393}).toThrow(394'Invariant Violation: The `style` prop expects a mapping from style ' +395'properties to values, not a string.'396);397});398});399400describe('unmountComponent', function() {401it("should clean up listeners", function() {402var React = require('React');403var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');404var ReactMount = require('ReactMount');405406var container = document.createElement('div');407document.documentElement.appendChild(container);408409var callback = function() {};410var instance = <div onClick={callback} />;411instance = React.render(instance, container);412413var rootNode = instance.getDOMNode();414var rootNodeID = ReactMount.getID(rootNode);415expect(416ReactBrowserEventEmitter.getListener(rootNodeID, 'onClick')417).toBe(callback);418419React.unmountComponentAtNode(container);420421expect(422ReactBrowserEventEmitter.getListener(rootNodeID, 'onClick')423).toBe(undefined);424});425});426427describe('onScroll warning', function() {428it('should warn about the `onScroll` issue when unsupported (IE8)', () => {429// Mock this here so we can mimic IE8 support. We require isEventSupported430// before React so it's pre-mocked before React qould require it.431require('mock-modules')432.dumpCache()433.mock('isEventSupported');434var isEventSupported = require('isEventSupported');435isEventSupported.mockReturnValueOnce(false);436437var React = require('React');438var ReactTestUtils = require('ReactTestUtils');439440spyOn(console, 'warn');441ReactTestUtils.renderIntoDocument(<div onScroll={function(){}} />);442expect(console.warn.callCount).toBe(1);443expect(console.warn.mostRecentCall.args[0]).toBe(444'This browser doesn\'t support the `onScroll` event'445);446});447});448449describe('tag sanitization', function() {450it('should throw when an invalid tag name is used', () => {451var React = require('React');452var ReactTestUtils = require('ReactTestUtils');453var hackzor = React.createElement('script tag');454expect(455() => ReactTestUtils.renderIntoDocument(hackzor)456).toThrow(457'Invariant Violation: Invalid tag: script tag'458);459});460461it('should throw when an attack vector is used', () => {462var React = require('React');463var ReactTestUtils = require('ReactTestUtils');464var hackzor = React.createElement('div><img /><div');465expect(466() => ReactTestUtils.renderIntoDocument(hackzor)467).toThrow(468'Invariant Violation: Invalid tag: div><img /><div'469);470});471472});473});474475476