@samitouri / QOS-React-2 / commits / 5f150822e6

Add CoffeeScript Class Test

As part of the new class effort it is now possible to define React Components using any type of generic JavaScript class syntax. This includes CoffeeScript. This test ensures that we don't regress that support, and also serves as an example for using React in CoffeeScript. This test fail trails the ReactES6Class-test file. Some manual tweaking is required when converting tests.

Sebastian Markbage committed Jan 26, 2015 at 02:19 UTC 5f150822e658b03660c4930bcf959814e47fcea6
3 files changed +376 -1
jest/preprocessor.js
+6 -1
@@ -2,8 +2,13 @@
2
3 var ReactTools = require('../main.js');
4
5 +var coffee = require('coffee-script');
6 +
7 module.exports = {
6 - process: function(src) {
8 + process: function(src, path) {
9 + if (path.match(/\.coffee$/)) {
10 + return coffee.compile(src, {'bare': true});
11 + }
12 return ReactTools.transform(src, {harmony: true});
13 }
14 };
package.json
+5
@@ -33,6 +33,7 @@
33 "benchmark": "~1.0.0",
34 "browserify": "^8.1.0",
35 "bundle-collapser": "^1.1.1",
36 + "coffee-script": "^1.8.0",
37 "coverify": "~1.0.4",
38 "derequire": "^1.2.0",
39 "envify": "^3.0.0",
@@ -79,6 +80,10 @@
80 "rootDir": "",
81 "scriptPreprocessor": "jest/preprocessor.js",
82 "setupEnvScriptFile": "jest/environment.js",
83 + "testFileExtensions": [
84 + "js",
85 + "coffee"
86 + ],
87 "modulePathIgnorePatterns": [
88 "/build/",
89 "/node_modules/",
src/modern/class/__tests__/ReactCoffeeScriptClass-test.coffee new
+365
@@ -0,0 +1,365 @@
1 +###
2 +Copyright 2015, 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 +
10 +React = null
11 +
12 +describe 'ReactCoffeeScriptClass', ->
13 + div = null
14 + span = null
15 + container = null
16 + Inner = null
17 + attachedListener = null;
18 + renderedName = null;
19 +
20 + beforeEach ->
21 + React = require 'React'
22 + container = document.createElement 'div'
23 + attachedListener = null
24 + renderedName = null
25 + div = React.createFactory 'div'
26 + span = React.createFactory 'span'
27 + class InnerComponent extends React.Component
28 + getName: -> this.props.name
29 + render: ->
30 + attachedListener = this.props.onClick
31 + renderedName = this.props.name
32 + return div className: this.props.name
33 + Inner = React.createFactory InnerComponent
34 +
35 + test = (element, expectedTag, expectedClassName) ->
36 + instance = React.render(element, container)
37 + expect(container.firstChild).not.toBeNull()
38 + expect(container.firstChild.tagName).toBe(expectedTag)
39 + expect(container.firstChild.className).toBe(expectedClassName)
40 + instance;
41 +
42 + it 'preserves the name of the class for use in error messages', ->
43 + class Foo extends React.Component
44 + expect(Foo.name).toBe 'Foo'
45 +
46 + it 'throws if no render function is defined', ->
47 + class Foo extends React.Component
48 + expect(->
49 + React.render React.createElement(Foo), container
50 + ).toThrow()
51 +
52 + it 'renders a simple stateless component with prop', ->
53 + class Foo
54 + render: ->
55 + Inner
56 + name: @props.bar
57 +
58 + test React.createElement(Foo, bar: 'foo'), 'DIV', 'foo'
59 + test React.createElement(Foo, bar: 'bar'), 'DIV', 'bar'
60 +
61 + it 'renders based on state using initial values in this.props', ->
62 + class Foo extends React.Component
63 + constructor: (props) ->
64 + super props
65 + @state = bar: @props.initialValue
66 +
67 + render: ->
68 + span
69 + className: @state.bar
70 +
71 + test React.createElement(Foo, initialValue: 'foo'), 'SPAN', 'foo'
72 +
73 + it 'renders based on state using props in the constructor', ->
74 + class Foo extends React.Component
75 + constructor: (props) ->
76 + @state = bar: props.initialValue
77 +
78 + changeState: ->
79 + @setState bar: 'bar'
80 +
81 + render: ->
82 + if @state.bar is 'foo'
83 + return div(
84 + className: 'foo'
85 + )
86 + span
87 + className: @state.bar
88 +
89 + instance = test React.createElement(Foo, initialValue: 'foo'), 'DIV', 'foo'
90 + instance.changeState()
91 + test React.createElement(Foo), 'SPAN', 'bar'
92 +
93 + it 'renders based on context in the constructor', ->
94 + class Foo extends React.Component
95 + @contextTypes:
96 + tag: React.PropTypes.string
97 + className: React.PropTypes.string
98 +
99 + constructor: (props, context) ->
100 + super props, context
101 + @state =
102 + tag: context.tag
103 + className: @context.className
104 +
105 + render: ->
106 + Tag = @state.tag
107 + React.createElement Tag,
108 + className: @state.className
109 +
110 + class Outer extends React.Component
111 + @childContextTypes:
112 + tag: React.PropTypes.string
113 + className: React.PropTypes.string
114 +
115 + getChildContext: ->
116 + tag: 'span'
117 + className: 'foo'
118 +
119 + render: ->
120 + React.createElement Foo
121 +
122 + test React.createElement(Outer), 'SPAN', 'foo'
123 +
124 + it 'renders only once when setting state in componentWillMount', ->
125 + renderCount = 0
126 + class Foo extends React.Component
127 + constructor: (props) ->
128 + @state = bar: props.initialValue
129 +
130 + componentWillMount: ->
131 + @setState bar: 'bar'
132 +
133 + render: ->
134 + renderCount++
135 + span className: @state.bar
136 +
137 + test React.createElement(Foo, initialValue: 'foo'), 'SPAN', 'bar'
138 + expect(renderCount).toBe 1
139 +
140 + it 'should throw with non-object in the initial state property', ->
141 + [['an array'], 'a string', 1234].forEach (state) ->
142 + class Foo
143 + constructor: ->
144 + @state = state
145 +
146 + render: ->
147 + span()
148 +
149 + expect(->
150 + test React.createElement(Foo), 'span', ''
151 + ).toThrow(
152 + 'Invariant Violation: Foo.state: must be set to an object or null'
153 + )
154 +
155 + it 'should render with null in the initial state property', ->
156 + class Foo extends React.Component
157 + constructor: ->
158 + @state = null
159 +
160 + render: ->
161 + span()
162 +
163 + test React.createElement(Foo), 'SPAN', ''
164 +
165 + it 'setState through an event handler', ->
166 + class Foo extends React.Component
167 + constructor: (props) ->
168 + @state = bar: props.initialValue
169 +
170 + handleClick: =>
171 + @setState bar: 'bar'
172 +
173 + render: ->
174 + Inner
175 + name: @state.bar
176 + onClick: @handleClick
177 +
178 + test React.createElement(Foo, initialValue: 'foo'), 'DIV', 'foo'
179 + attachedListener()
180 + expect(renderedName).toBe 'bar'
181 +
182 + it 'should not implicitly bind event handlers', ->
183 + class Foo extends React.Component
184 + constructor: (props) ->
185 + @state = bar: props.initialValue
186 +
187 + handleClick: -> # needs double arrow
188 + @setState bar: 'bar'
189 +
190 + render: ->
191 + Inner
192 + name: @state.bar
193 + onClick: @handleClick
194 +
195 + test React.createElement(Foo, initialValue: 'foo'), 'DIV', 'foo'
196 + expect(attachedListener).toThrow()
197 +
198 + it 'renders using forceUpdate even when there is no state', ->
199 + class Foo extends React.Component
200 + constructor: (props) ->
201 + @mutativeValue = props.initialValue
202 +
203 + handleClick: =>
204 + @mutativeValue = 'bar'
205 + @forceUpdate()
206 +
207 + render: ->
208 + Inner
209 + name: @mutativeValue
210 + onClick: @handleClick
211 +
212 + test React.createElement(Foo, initialValue: 'foo'), 'DIV', 'foo'
213 + attachedListener()
214 + expect(renderedName).toBe 'bar'
215 +
216 + it 'will call all the normal life cycle methods', ->
217 + lifeCycles = []
218 + class Foo
219 + constructor: ->
220 + @state = {}
221 +
222 + componentWillMount: ->
223 + lifeCycles.push 'will-mount'
224 +
225 + componentDidMount: ->
226 + lifeCycles.push 'did-mount'
227 +
228 + componentWillReceiveProps: (nextProps) ->
229 + lifeCycles.push 'receive-props', nextProps
230 +
231 + shouldComponentUpdate: (nextProps, nextState) ->
232 + lifeCycles.push 'should-update', nextProps, nextState
233 + true
234 +
235 + componentWillUpdate: (nextProps, nextState) ->
236 + lifeCycles.push 'will-update', nextProps, nextState
237 +
238 + componentDidUpdate: (prevProps, prevState) ->
239 + lifeCycles.push 'did-update', prevProps, prevState
240 +
241 + componentWillUnmount: ->
242 + lifeCycles.push 'will-unmount'
243 +
244 + render: ->
245 + span
246 + className: @props.value
247 +
248 + test React.createElement(Foo, value: 'foo'), 'SPAN', 'foo'
249 + expect(lifeCycles).toEqual [
250 + 'will-mount'
251 + 'did-mount'
252 + ]
253 + lifeCycles = [] # reset
254 + test React.createElement(Foo, value: 'bar'), 'SPAN', 'bar'
255 + expect(lifeCycles).toEqual [
256 + 'receive-props', { value: 'bar' }
257 + 'should-update', { value: 'bar' }, {}
258 + 'will-update', { value: 'bar' }, {}
259 + 'did-update', { value: 'foo' }, {}
260 + ]
261 + lifeCycles = [] # reset
262 + React.unmountComponentAtNode container
263 + expect(lifeCycles).toEqual ['will-unmount']
264 +
265 + it 'warns when classic properties are defined on the instance,
266 + but does not invoke them.', ->
267 + spyOn console, 'warn'
268 + getInitialStateWasCalled = false
269 + class Foo extends React.Component
270 + constructor: ->
271 + @contextTypes = {}
272 + @propTypes = {}
273 +
274 + getInitialState: ->
275 + getInitialStateWasCalled = true
276 + {}
277 +
278 + render: ->
279 + span
280 + className: 'foo'
281 +
282 + test React.createElement(Foo), 'SPAN', 'foo'
283 + expect(getInitialStateWasCalled).toBe false
284 + expect(console.warn.calls.length).toBe 3
285 + expect(console.warn.calls[0].args[0]).toContain(
286 + 'getInitialState was defined on Foo, a plain JavaScript class.'
287 + )
288 + expect(console.warn.calls[1].args[0]).toContain(
289 + 'propTypes was defined as an instance property on Foo.'
290 + )
291 + expect(console.warn.calls[2].args[0]).toContain(
292 + 'contextTypes was defined as an instance property on Foo.'
293 + )
294 +
295 + it 'should warn when mispelling shouldComponentUpdate', ->
296 + spyOn console, 'warn'
297 + class NamedComponent
298 + componentShouldUpdate: ->
299 + false
300 +
301 + render: ->
302 + span
303 + className: 'foo'
304 +
305 + test React.createElement(NamedComponent), 'SPAN', 'foo'
306 + expect(console.warn.calls.length).toBe 1
307 + expect(console.warn.calls[0].args[0]).toBe(
308 + 'Warning: NamedComponent has a method called componentShouldUpdate().
309 + Did you mean shouldComponentUpdate()? The name is phrased as a
310 + question because the function is expected to return a value.'
311 + )
312 +
313 + it 'should throw AND warn when trying to access classic APIs', ->
314 + spyOn console, 'warn'
315 + instance =
316 + test Inner(name: 'foo'), 'DIV', 'foo'
317 + expect(-> instance.getDOMNode()).toThrow()
318 + expect(-> instance.replaceState {}).toThrow()
319 + expect(-> instance.isMounted()).toThrow()
320 + expect(-> instance.setProps name: 'bar').toThrow()
321 + expect(console.warn.calls.length).toBe 4
322 + expect(console.warn.calls[0].args[0]).toContain(
323 + 'getDOMNode(...) is deprecated in plain JavaScript React classes'
324 + )
325 + expect(console.warn.calls[1].args[0]).toContain(
326 + 'replaceState(...) is deprecated in plain JavaScript React classes'
327 + )
328 + expect(console.warn.calls[2].args[0]).toContain(
329 + 'isMounted(...) is deprecated in plain JavaScript React classes'
330 + )
331 + expect(console.warn.calls[3].args[0]).toContain(
332 + 'setProps(...) is deprecated in plain JavaScript React classes'
333 + )
334 +
335 + it 'supports this.context passed via getChildContext', ->
336 + class Bar
337 + @contextTypes:
338 + bar: React.PropTypes.string
339 + render: ->
340 + div className: @context.bar
341 +
342 + class Foo
343 + @childContextTypes:
344 + bar: React.PropTypes.string
345 + getChildContext: ->
346 + bar: 'bar-through-context'
347 + render: ->
348 + React.createElement Bar
349 +
350 + test React.createElement(Foo), 'DIV', 'bar-through-context'
351 +
352 + it 'supports classic refs', ->
353 + class Foo
354 + render: ->
355 + Inner
356 + name: 'foo'
357 + ref: 'inner'
358 +
359 + instance = test(React.createElement(Foo), 'DIV', 'foo')
360 + expect(instance.refs.inner.getName()).toBe 'foo'
361 +
362 + it 'supports drilling through to the DOM using findDOMNode', ->
363 + instance = test Inner(name: 'foo'), 'DIV', 'foo'
364 + node = React.findDOMNode(instance)
365 + expect(node).toBe container.firstChild