| 1 | ### |
| 2 | Copyright (c) Meta Platforms, Inc. and affiliates. |
| 3 | |
| 4 | This source code is licensed under the MIT license found in the |
| 5 | LICENSE file in the root directory of this source tree. |
| 6 | ### |
| 7 | |
| 8 | PropTypes = null |
| 9 | React = null |
| 10 | ReactDOM = null |
| 11 | ReactDOMClient = null |
| 12 | assertConsoleErrorDev = null |
| 13 | assertConsoleWarnDev = null |
| 14 | |
| 15 | featureFlags = require 'shared/ReactFeatureFlags' |
| 16 | |
| 17 | describe 'ReactCoffeeScriptClass', -> |
| 18 | container = null |
| 19 | root = null |
| 20 | InnerComponent = null |
| 21 | attachedListener = null; |
| 22 | renderedName = null; |
| 23 | |
| 24 | beforeEach -> |
| 25 | React = require 'react' |
| 26 | ReactDOM = require 'react-dom' |
| 27 | ReactDOMClient = require 'react-dom/client' |
| 28 | PropTypes = require 'prop-types' |
| 29 | container = document.createElement 'div' |
| 30 | root = ReactDOMClient.createRoot container |
| 31 | attachedListener = null |
| 32 | renderedName = null |
| 33 | TestUtils = require 'internal-test-utils' |
| 34 | assertConsoleErrorDev = TestUtils.assertConsoleErrorDev |
| 35 | assertConsoleWarnDev = TestUtils.assertConsoleWarnDev |
| 36 | InnerComponent = class extends React.Component |
| 37 | getName: -> this.props.name |
| 38 | render: -> |
| 39 | attachedListener = this.props.onClick |
| 40 | renderedName = this.props.name |
| 41 | return React.createElement('div', className: this.props.name) |
| 42 | |
| 43 | test = (element, expectedTag, expectedClassName) -> |
| 44 | ReactDOM.flushSync -> |
| 45 | root.render(element) |
| 46 | expect(container.firstChild).not.toBeNull() |
| 47 | expect(container.firstChild.tagName).toBe(expectedTag) |
| 48 | expect(container.firstChild.className).toBe(expectedClassName) |
| 49 | |
| 50 | it 'preserves the name of the class for use in error messages', -> |
| 51 | class Foo extends React.Component |
| 52 | expect(Foo.name).toBe 'Foo' |
| 53 | |
| 54 | it 'throws if no render function is defined', -> |
| 55 | class Foo extends React.Component |
| 56 | caughtErrors = [] |
| 57 | errorHandler = (event) -> |
| 58 | event.preventDefault() |
| 59 | caughtErrors.push(event.error) |
| 60 | window.addEventListener 'error', errorHandler; |
| 61 | ReactDOM.flushSync -> |
| 62 | root.render React.createElement(Foo) |
| 63 | assertConsoleErrorDev [ |
| 64 | # A failed component renders twice in DEV in concurrent mode |
| 65 | 'No `render` method found on the Foo instance: you may have forgotten to define `render`.\n' + |
| 66 | ' in Foo (at **)', |
| 67 | 'No `render` method found on the Foo instance: you may have forgotten to define `render`.\n' + |
| 68 | ' in Foo (at **)', |
| 69 | ] |
| 70 | window.removeEventListener 'error', errorHandler; |
| 71 | expect(caughtErrors).toEqual([ |
| 72 | expect.objectContaining( |
| 73 | message: expect.stringContaining('is not a function') |
| 74 | ) |
| 75 | ]) |
| 76 | |
| 77 | it 'renders a simple stateless component with prop', -> |
| 78 | class Foo extends React.Component |
| 79 | render: -> |
| 80 | React.createElement(InnerComponent, |
| 81 | name: @props.bar |
| 82 | ) |
| 83 | |
| 84 | test React.createElement(Foo, bar: 'foo'), 'DIV', 'foo' |
| 85 | test React.createElement(Foo, bar: 'bar'), 'DIV', 'bar' |
| 86 | |
| 87 | it 'renders based on state using initial values in this.props', -> |
| 88 | class Foo extends React.Component |
| 89 | constructor: (props) -> |
| 90 | super props |
| 91 | @state = bar: @props.initialValue |
| 92 | |
| 93 | render: -> |
| 94 | React.createElement('span', |
| 95 | className: @state.bar |
| 96 | ) |
| 97 | |
| 98 | test React.createElement(Foo, initialValue: 'foo'), 'SPAN', 'foo' |
| 99 | |
| 100 | it 'renders based on state using props in the constructor', -> |
| 101 | class Foo extends React.Component |
| 102 | constructor: (props) -> |
| 103 | @state = bar: props.initialValue |
| 104 | |
| 105 | changeState: -> |
| 106 | @setState bar: 'bar' |
| 107 | |
| 108 | render: -> |
| 109 | if @state.bar is 'foo' |
| 110 | return React.createElement('div', |
| 111 | className: 'foo' |
| 112 | ) |
| 113 | React.createElement('span', |
| 114 | className: @state.bar |
| 115 | ) |
| 116 | |
| 117 | ref = React.createRef() |
| 118 | test React.createElement(Foo, initialValue: 'foo', ref: ref), 'DIV', 'foo' |
| 119 | ReactDOM.flushSync -> |
| 120 | ref.current.changeState() |
| 121 | test React.createElement(Foo), 'SPAN', 'bar' |
| 122 | |
| 123 | it 'sets initial state with value returned by static getDerivedStateFromProps', -> |
| 124 | class Foo extends React.Component |
| 125 | constructor: (props) -> |
| 126 | super props |
| 127 | @state = foo: null |
| 128 | render: -> |
| 129 | React.createElement('div', |
| 130 | className: "#{@state.foo} #{@state.bar}" |
| 131 | ) |
| 132 | Foo.getDerivedStateFromProps = (nextProps, prevState) -> |
| 133 | { |
| 134 | foo: nextProps.foo |
| 135 | bar: 'bar' |
| 136 | } |
| 137 | test React.createElement(Foo, foo: 'foo'), 'DIV', 'foo bar' |
| 138 | |
| 139 | it 'warns if getDerivedStateFromProps is not static', -> |
| 140 | class Foo extends React.Component |
| 141 | render: -> |
| 142 | React.createElement('div') |
| 143 | getDerivedStateFromProps: -> |
| 144 | {} |
| 145 | ReactDOM.flushSync -> |
| 146 | root.render React.createElement(Foo, foo: 'foo') |
| 147 | assertConsoleErrorDev [ |
| 148 | 'Foo: getDerivedStateFromProps() is defined as an instance method and will be ignored. Instead, declare it as a static method.\n' + |
| 149 | ' in Foo (at **)'] |
| 150 | |
| 151 | it 'warns if getDerivedStateFromError is not static', -> |
| 152 | class Foo extends React.Component |
| 153 | render: -> |
| 154 | React.createElement('div') |
| 155 | getDerivedStateFromError: -> |
| 156 | {} |
| 157 | ReactDOM.flushSync -> |
| 158 | root.render React.createElement(Foo, foo: 'foo') |
| 159 | |
| 160 | assertConsoleErrorDev [ |
| 161 | 'Foo: getDerivedStateFromError() is defined as an instance method and will be ignored. Instead, declare it as a static method.\n' + |
| 162 | ' in Foo (at **)' |
| 163 | ] |
| 164 | |
| 165 | it 'warns if getSnapshotBeforeUpdate is static', -> |
| 166 | class Foo extends React.Component |
| 167 | render: -> |
| 168 | React.createElement('div') |
| 169 | Foo.getSnapshotBeforeUpdate = () -> |
| 170 | {} |
| 171 | ReactDOM.flushSync -> |
| 172 | root.render React.createElement(Foo, foo: 'foo') |
| 173 | |
| 174 | assertConsoleErrorDev [ |
| 175 | 'Foo: getSnapshotBeforeUpdate() is defined as a static method and will be ignored. Instead, declare it as an instance method.\n' + |
| 176 | ' in Foo (at **)' |
| 177 | ] |
| 178 | |
| 179 | it 'warns if state not initialized before static getDerivedStateFromProps', -> |
| 180 | class Foo extends React.Component |
| 181 | render: -> |
| 182 | React.createElement('div', |
| 183 | className: "#{@state.foo} #{@state.bar}" |
| 184 | ) |
| 185 | Foo.getDerivedStateFromProps = (nextProps, prevState) -> |
| 186 | { |
| 187 | foo: nextProps.foo |
| 188 | bar: 'bar' |
| 189 | } |
| 190 | ReactDOM.flushSync -> |
| 191 | root.render React.createElement(Foo, foo: 'foo') |
| 192 | |
| 193 | assertConsoleErrorDev [ |
| 194 | '`Foo` uses `getDerivedStateFromProps` but its initial state is |
| 195 | undefined. This is not recommended. Instead, define the initial state by |
| 196 | assigning an object to `this.state` in the constructor of `Foo`. |
| 197 | This ensures that `getDerivedStateFromProps` arguments have a consistent shape.\n' + |
| 198 | ' in Foo (at **)' |
| 199 | ] |
| 200 | |
| 201 | it 'updates initial state with values returned by static getDerivedStateFromProps', -> |
| 202 | class Foo extends React.Component |
| 203 | constructor: (props, context) -> |
| 204 | super props, context |
| 205 | @state = |
| 206 | foo: 'foo' |
| 207 | bar: 'bar' |
| 208 | render: -> |
| 209 | React.createElement('div', |
| 210 | className: "#{@state.foo} #{@state.bar}" |
| 211 | ) |
| 212 | Foo.getDerivedStateFromProps = (nextProps, prevState) -> |
| 213 | { |
| 214 | foo: "not-#{prevState.foo}" |
| 215 | } |
| 216 | test React.createElement(Foo), 'DIV', 'not-foo bar' |
| 217 | |
| 218 | it 'renders updated state with values returned by static getDerivedStateFromProps', -> |
| 219 | class Foo extends React.Component |
| 220 | constructor: (props, context) -> |
| 221 | super props, context |
| 222 | @state = |
| 223 | value: 'initial' |
| 224 | render: -> |
| 225 | React.createElement('div', |
| 226 | className: @state.value |
| 227 | ) |
| 228 | Foo.getDerivedStateFromProps = (nextProps, prevState) -> |
| 229 | if nextProps.update |
| 230 | return { |
| 231 | value: 'updated' |
| 232 | } |
| 233 | return null |
| 234 | test React.createElement(Foo, update: false), 'DIV', 'initial' |
| 235 | test React.createElement(Foo, update: true), 'DIV', 'updated' |
| 236 | |
| 237 | if !featureFlags.disableLegacyContext |
| 238 | it 'renders based on context in the constructor', -> |
| 239 | class Foo extends React.Component |
| 240 | @contextTypes: |
| 241 | tag: PropTypes.string |
| 242 | className: PropTypes.string |
| 243 | |
| 244 | constructor: (props, context) -> |
| 245 | super props, context |
| 246 | @state = |
| 247 | tag: context.tag |
| 248 | className: @context.className |
| 249 | |
| 250 | render: -> |
| 251 | Tag = @state.tag |
| 252 | React.createElement Tag, |
| 253 | className: @state.className |
| 254 | |
| 255 | class Outer extends React.Component |
| 256 | @childContextTypes: |
| 257 | tag: PropTypes.string |
| 258 | className: PropTypes.string |
| 259 | |
| 260 | getChildContext: -> |
| 261 | tag: 'span' |
| 262 | className: 'foo' |
| 263 | |
| 264 | render: -> |
| 265 | React.createElement Foo |
| 266 | |
| 267 | test React.createElement(Outer), 'SPAN', 'foo' |
| 268 | |
| 269 | assertConsoleErrorDev([ |
| 270 | 'Outer uses the legacy childContextTypes API which will soon be removed. |
| 271 | Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' + |
| 272 | ' in Outer (at **)', |
| 273 | 'Foo uses the legacy contextTypes API which will soon be removed. |
| 274 | Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' + |
| 275 | ' in Outer (at **)', |
| 276 | ]); |
| 277 | |
| 278 | it 'renders only once when setting state in componentWillMount', -> |
| 279 | renderCount = 0 |
| 280 | class Foo extends React.Component |
| 281 | constructor: (props) -> |
| 282 | @state = bar: props.initialValue |
| 283 | |
| 284 | UNSAFE_componentWillMount: -> |
| 285 | @setState bar: 'bar' |
| 286 | |
| 287 | render: -> |
| 288 | renderCount++ |
| 289 | React.createElement('span', className: @state.bar) |
| 290 | |
| 291 | test React.createElement(Foo, initialValue: 'foo'), 'SPAN', 'bar' |
| 292 | expect(renderCount).toBe(1) |
| 293 | |
| 294 | it 'should warn with non-object in the initial state property', -> |
| 295 | [['an array'], 'a string', 1234].forEach (state) -> |
| 296 | class Foo extends React.Component |
| 297 | constructor: -> |
| 298 | @state = state |
| 299 | |
| 300 | render: -> |
| 301 | React.createElement('span') |
| 302 | |
| 303 | test React.createElement(Foo), 'SPAN', '' |
| 304 | assertConsoleErrorDev [ |
| 305 | 'Foo.state: must be set to an object or null\n' + |
| 306 | ' in Foo (at **)' |
| 307 | ] |
| 308 | |
| 309 | it 'should render with null in the initial state property', -> |
| 310 | class Foo extends React.Component |
| 311 | constructor: -> |
| 312 | @state = null |
| 313 | |
| 314 | render: -> |
| 315 | React.createElement('span') |
| 316 | |
| 317 | test React.createElement(Foo), 'SPAN', '' |
| 318 | |
| 319 | it 'setState through an event handler', -> |
| 320 | class Foo extends React.Component |
| 321 | constructor: (props) -> |
| 322 | @state = bar: props.initialValue |
| 323 | |
| 324 | handleClick: => |
| 325 | @setState bar: 'bar' |
| 326 | |
| 327 | render: -> |
| 328 | React.createElement(InnerComponent, |
| 329 | name: @state.bar |
| 330 | onClick: @handleClick |
| 331 | ) |
| 332 | |
| 333 | test React.createElement(Foo, initialValue: 'foo'), 'DIV', 'foo' |
| 334 | ReactDOM.flushSync -> |
| 335 | attachedListener() |
| 336 | expect(renderedName).toBe 'bar' |
| 337 | |
| 338 | it 'should not implicitly bind event handlers', -> |
| 339 | class Foo extends React.Component |
| 340 | constructor: (props) -> |
| 341 | @state = bar: props.initialValue |
| 342 | |
| 343 | handleClick: -> # needs double arrow |
| 344 | @setState bar: 'bar' |
| 345 | |
| 346 | render: -> |
| 347 | React.createElement(InnerComponent, |
| 348 | name: @state.bar |
| 349 | onClick: @handleClick |
| 350 | ) |
| 351 | |
| 352 | test React.createElement(Foo, initialValue: 'foo'), 'DIV', 'foo' |
| 353 | expect(attachedListener).toThrow() |
| 354 | |
| 355 | it 'renders using forceUpdate even when there is no state', -> |
| 356 | class Foo extends React.Component |
| 357 | constructor: (props) -> |
| 358 | @mutativeValue = props.initialValue |
| 359 | |
| 360 | handleClick: => |
| 361 | @mutativeValue = 'bar' |
| 362 | @forceUpdate() |
| 363 | |
| 364 | render: -> |
| 365 | React.createElement(InnerComponent, |
| 366 | name: @mutativeValue |
| 367 | onClick: @handleClick |
| 368 | ) |
| 369 | |
| 370 | test React.createElement(Foo, initialValue: 'foo'), 'DIV', 'foo' |
| 371 | ReactDOM.flushSync -> |
| 372 | attachedListener() |
| 373 | expect(renderedName).toBe 'bar' |
| 374 | |
| 375 | it 'will call all the normal life cycle methods', -> |
| 376 | lifeCycles = [] |
| 377 | class Foo extends React.Component |
| 378 | constructor: -> |
| 379 | @state = {} |
| 380 | |
| 381 | UNSAFE_componentWillMount: -> |
| 382 | lifeCycles.push 'will-mount' |
| 383 | |
| 384 | componentDidMount: -> |
| 385 | lifeCycles.push 'did-mount' |
| 386 | |
| 387 | UNSAFE_componentWillReceiveProps: (nextProps) -> |
| 388 | lifeCycles.push 'receive-props', nextProps |
| 389 | |
| 390 | shouldComponentUpdate: (nextProps, nextState) -> |
| 391 | lifeCycles.push 'should-update', nextProps, nextState |
| 392 | true |
| 393 | |
| 394 | UNSAFE_componentWillUpdate: (nextProps, nextState) -> |
| 395 | lifeCycles.push 'will-update', nextProps, nextState |
| 396 | |
| 397 | componentDidUpdate: (prevProps, prevState) -> |
| 398 | lifeCycles.push 'did-update', prevProps, prevState |
| 399 | |
| 400 | componentWillUnmount: -> |
| 401 | lifeCycles.push 'will-unmount' |
| 402 | |
| 403 | render: -> |
| 404 | React.createElement('span', |
| 405 | className: @props.value |
| 406 | ) |
| 407 | |
| 408 | test React.createElement(Foo, value: 'foo'), 'SPAN', 'foo' |
| 409 | expect(lifeCycles).toEqual [ |
| 410 | 'will-mount' |
| 411 | 'did-mount' |
| 412 | ] |
| 413 | lifeCycles = [] # reset |
| 414 | test React.createElement(Foo, value: 'bar'), 'SPAN', 'bar' |
| 415 | expect(lifeCycles).toEqual [ |
| 416 | 'receive-props', { value: 'bar' } |
| 417 | 'should-update', { value: 'bar' }, {} |
| 418 | 'will-update', { value: 'bar' }, {} |
| 419 | 'did-update', { value: 'foo' }, {} |
| 420 | ] |
| 421 | lifeCycles = [] # reset |
| 422 | ReactDOM.flushSync -> |
| 423 | root.unmount() |
| 424 | expect(lifeCycles).toEqual ['will-unmount'] |
| 425 | |
| 426 | if !featureFlags.disableLegacyContext |
| 427 | it 'warns when classic properties are defined on the instance, |
| 428 | but does not invoke them.', -> |
| 429 | getInitialStateWasCalled = false |
| 430 | getDefaultPropsWasCalled = false |
| 431 | class Foo extends React.Component |
| 432 | constructor: -> |
| 433 | @contextTypes = {} |
| 434 | @contextType = {} |
| 435 | |
| 436 | getInitialState: -> |
| 437 | getInitialStateWasCalled = true |
| 438 | {} |
| 439 | |
| 440 | getDefaultProps: -> |
| 441 | getDefaultPropsWasCalled = true |
| 442 | {} |
| 443 | |
| 444 | render: -> |
| 445 | React.createElement('span', |
| 446 | className: 'foo' |
| 447 | ) |
| 448 | |
| 449 | test React.createElement(Foo), 'SPAN', 'foo' |
| 450 | assertConsoleErrorDev [ |
| 451 | 'getInitialState was defined on Foo, a plain JavaScript class. |
| 452 | This is only supported for classes created using React.createClass. |
| 453 | Did you mean to define a state property instead?\n' + |
| 454 | ' in Foo (at **)', |
| 455 | 'getDefaultProps was defined on Foo, a plain JavaScript class. |
| 456 | This is only supported for classes created using React.createClass. |
| 457 | Use a static property to define defaultProps instead.\n' + |
| 458 | ' in Foo (at **)', |
| 459 | 'contextType was defined as an instance property on Foo. Use a static property to define contextType instead.\n' + |
| 460 | ' in Foo (at **)', |
| 461 | 'contextTypes was defined as an instance property on Foo. Use a static property to define contextTypes instead.\n' + |
| 462 | ' in Foo (at **)', |
| 463 | ] |
| 464 | expect(getInitialStateWasCalled).toBe false |
| 465 | expect(getDefaultPropsWasCalled).toBe false |
| 466 | |
| 467 | it 'does not warn about getInitialState() on class components |
| 468 | if state is also defined.', -> |
| 469 | class Foo extends React.Component |
| 470 | constructor: (props) -> |
| 471 | super props |
| 472 | @state = bar: @props.initialValue |
| 473 | |
| 474 | getInitialState: -> |
| 475 | {} |
| 476 | |
| 477 | render: -> |
| 478 | React.createElement('span', |
| 479 | className: 'foo' |
| 480 | ) |
| 481 | |
| 482 | test React.createElement(Foo), 'SPAN', 'foo' |
| 483 | |
| 484 | it 'should warn when misspelling shouldComponentUpdate', -> |
| 485 | class NamedComponent extends React.Component |
| 486 | componentShouldUpdate: -> |
| 487 | false |
| 488 | |
| 489 | render: -> |
| 490 | React.createElement('span', |
| 491 | className: 'foo' |
| 492 | ) |
| 493 | |
| 494 | test React.createElement(NamedComponent), 'SPAN', 'foo' |
| 495 | assertConsoleErrorDev [ |
| 496 | 'NamedComponent has a method called componentShouldUpdate(). |
| 497 | Did you mean shouldComponentUpdate()? The name is phrased as a |
| 498 | question because the function is expected to return a value.\n' + |
| 499 | ' in NamedComponent (at **)' |
| 500 | ] |
| 501 | |
| 502 | it 'should warn when misspelling componentWillReceiveProps', -> |
| 503 | class NamedComponent extends React.Component |
| 504 | componentWillRecieveProps: -> |
| 505 | false |
| 506 | |
| 507 | render: -> |
| 508 | React.createElement('span', |
| 509 | className: 'foo' |
| 510 | ) |
| 511 | |
| 512 | test React.createElement(NamedComponent), 'SPAN', 'foo' |
| 513 | assertConsoleErrorDev [ |
| 514 | 'NamedComponent has a method called componentWillRecieveProps(). |
| 515 | Did you mean componentWillReceiveProps()?\n' + |
| 516 | ' in NamedComponent (at **)' |
| 517 | ] |
| 518 | |
| 519 | it 'should warn when misspelling UNSAFE_componentWillReceiveProps', -> |
| 520 | class NamedComponent extends React.Component |
| 521 | UNSAFE_componentWillRecieveProps: -> |
| 522 | false |
| 523 | |
| 524 | render: -> |
| 525 | React.createElement('span', |
| 526 | className: 'foo' |
| 527 | ) |
| 528 | |
| 529 | test React.createElement(NamedComponent), 'SPAN', 'foo' |
| 530 | assertConsoleErrorDev [ |
| 531 | 'NamedComponent has a method called UNSAFE_componentWillRecieveProps(). |
| 532 | Did you mean UNSAFE_componentWillReceiveProps()?\n' + |
| 533 | ' in NamedComponent (at **)' |
| 534 | ] |
| 535 | |
| 536 | it 'should throw AND warn when trying to access classic APIs', -> |
| 537 | ref = React.createRef() |
| 538 | test React.createElement(InnerComponent, name: 'foo', ref: ref), 'DIV', 'foo' |
| 539 | |
| 540 | expect(-> ref.current.replaceState {}).toThrow() |
| 541 | assertConsoleWarnDev([ |
| 542 | 'replaceState(...) is deprecated in plain JavaScript React classes. Refactor your code to use setState instead (see https://github.com/facebook/react/issues/3236).']) |
| 543 | |
| 544 | expect(-> ref.current.isMounted()).toThrow() |
| 545 | assertConsoleWarnDev([ |
| 546 | 'isMounted(...) is deprecated in plain JavaScript React classes. Instead, make sure to clean up subscriptions and pending requests in componentWillUnmount to prevent memory leaks.']) |
| 547 | |
| 548 | if !featureFlags.disableLegacyContext |
| 549 | it 'supports this.context passed via getChildContext', -> |
| 550 | class Bar extends React.Component |
| 551 | @contextTypes: |
| 552 | bar: PropTypes.string |
| 553 | render: -> |
| 554 | React.createElement('div', className: @context.bar) |
| 555 | |
| 556 | class Foo extends React.Component |
| 557 | @childContextTypes: |
| 558 | bar: PropTypes.string |
| 559 | getChildContext: -> |
| 560 | bar: 'bar-through-context' |
| 561 | render: -> |
| 562 | React.createElement Bar |
| 563 | |
| 564 | test React.createElement(Foo), 'DIV', 'bar-through-context' |
| 565 | assertConsoleErrorDev [ |
| 566 | 'Foo uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead. |
| 567 | (https://react.dev/link/legacy-context)\n' + |
| 568 | ' in Foo (at **)', |
| 569 | 'Bar uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead. |
| 570 | (https://react.dev/link/legacy-context)\n' + |
| 571 | ' in Foo (at **)' |
| 572 | ] |
| 573 | |
| 574 | undefined |