diff --git a/packages/inferno-create-element/__tests__/components1.spec.tsx b/packages/inferno-create-element/__tests__/components1.spec.tsx new file mode 100644 index 000000000..ef1f7ea2d --- /dev/null +++ b/packages/inferno-create-element/__tests__/components1.spec.tsx @@ -0,0 +1,681 @@ +import {Component, render} from "inferno"; + +describe('Components 1 (JSX)', () => { + let container; + let attachedListener: () => void = () => {}; + let renderedName: (string | null) = null; + + + interface InnerProps { + onClick: () => void, + name: null | string + } + + class Inner extends Component { + render() { + attachedListener = this.props.onClick; + renderedName = this.props.name; + return
; + } + } + + beforeEach(function () { + attachedListener = () => {}; + renderedName = null; + container = document.createElement('div'); + document.body.appendChild(container); + }); + + afterEach(function () { + render(null, container); + document.body.removeChild(container); + }); + + class BasicComponent1 extends Component<{ name: any, title: any }> { + render() { + return ( +
+ The title is {this.props.title} +
+ ); + } + } + + it('should render a basic component jsx', () => { + render( +
+ +
, + container + ); + + expect(container.innerHTML).toBe('
The title is abc
'); + + render( +
+ +
, + container + ); + + expect(container.innerHTML).toBe('
The title is abc
'); + + const attrs = {title: 'abc', name: 'basic-render2', foo: 'bar'}; + + // JSX Spread Attribute + render( +
+ +
, + container + ); + + expect(container.innerHTML).toBe('
The title is abc
'); + }); + + class BasicComponent1b extends Component<{ isChecked: boolean | null, title: string }> { + render() { + return ( +
+ +
+ ); + } + } + + it('should render a basic component with inputs', () => { + render( +
+ +
, + container + ); + + expect(container.innerHTML).toBe('
'); + expect(container.querySelector('input').checked).toBe(true); + + render( +
+ +
, + container + ); + expect(container.innerHTML).toBe('
'); + expect(container.querySelector('input').checked).toBe(false); + + render( +
+ +
, + container + ); + + render(
, container); + + render( +
+ +
, + container + ); + expect(container.querySelector('input').checked).toBe(true); + }); + + it('should render a basic component and remove property if null', () => { + render( +
+ +
, + container + ); + + expect(container.innerHTML).toBe('
The title is abc
'); + + render(
, container); + render( +
+ +
, + container + ); + + expect(container.innerHTML).toBe('
The title is Hello, World!
'); + + render( +
+ +
, + container + ); + expect(container.innerHTML).toBe('
The title is 123
'); + render( +
+ +
, + container + ); + expect(container.innerHTML).toBe('
The title is
'); + + render( +
+ +
, + container + ); + + expect(container.innerHTML).toBe('
The title is
'); + + render( +
+ +
, + container + ); + + expect(container.innerHTML).toBe('
The title is abc
'); + + render( +
+ +
, + container + ); + expect(container.innerHTML).toBe('
The title is 123
'); + }); + + it('should render a basic root component', () => { + render(, container); + + expect(container.firstChild.getAttribute('class')).toBe('basic'); + + render(, container); + + expect(container.firstChild.getAttribute('class')).toBe('basic'); + + render(, container); + expect(container.innerHTML).toBe('
The title is 123
'); + }); + + class BasicComponent2 extends Component<{ name: string; title: string }> { + render() { + return ( +
+ The title is {this.props.title} + {this.props.children} +
+ ); + } + } + + it('should render a basic component with children', () => { + render( +
+ + Im a child + +
, + container + ); + + expect(container.innerHTML).toBe('
The title is abcIm a child
'); + + render( +
+ + Im a child + +
, + container + ); + expect(container.innerHTML).toBe('
The title is 123Im a child
'); + }); + + it('should render multiple components', () => { + render( +
+ + +
, + container + ); + + expect(container.innerHTML).toBe( + '
The title is component 1
' + + '
The title is component 2
' + ); + + render( +
+ +
, + container + ); + expect(container.innerHTML).toBe('
The title is component 1
'); + }); + + class BasicComponent3 extends Component<{ title?: string; styles?: any }> { + render() { + return ( +
+ The title is {this.props.title} +
+ ); + } + } + + it('should render a basic component with styling', () => { + render(, container); + + expect(container.innerHTML).toBe( + '
The title is styled!
' + ); + + render(, container); + + render(, container); + + expect(container.innerHTML).toBe( + '
The title is styled (again)!
' + ); + }); + + it('should render a basic component and remove styling', () => { + render(, container); + + expect(container.innerHTML).toBe( + '
The title is styled!
' + ); + + render(, container); + + expect([null, '']).toContain(container.firstChild.getAttribute('style')); + expect(container.firstChild.tagName).toEqual('DIV'); + expect(container.firstChild.firstChild.innerHTML).toEqual('The title is styles are removed!'); + }); + + interface SuperState { + organizations: { name: string, key: string }[] + } + + class SuperComponent extends Component<{}, SuperState> { + state: SuperState; + + constructor(props) { + super(props); + this.state = { + organizations: [ + {name: 'test1', key: '1'}, + {name: 'test2', key: '2'}, + {name: 'test3', key: '3'}, + {name: 'test4', key: '4'}, + {name: 'test5', key: '5'}, + {name: 'test6', key: '6'} + ] + }; + } + + render() { + return ( + + ); + } + } + + it('should render a basic component with a list of values from state', () => { + render(, container); + expect(container.innerHTML).toBe( + '' + ); + }); + + it('should render a basic component with an element and components as children', () => { + class Navbar extends Component { + render() { + return ( +
    +
  • Nav1
  • +
+ ); + } + } + + class Main extends Component { + render() { + return ( +
+ +
+
+ ); + } + } + + render(
, container); + }); + + function test(element, expectedTag, expectedClassName, callback) { + render(element, container, () => { + expect(container.firstChild).not.toBe(null); + expect(container.firstChild.tagName).toBe(expectedTag); + expect(container.firstChild.className).toBe(expectedClassName); + callback(); + }); + } + + it('should only render once when setting state in componentWillMount', function (done) { + let renderCount = 0; + + interface FooState { + bar: string | null + } + + class Foo extends Component<{ initialValue: string | null }, FooState> { + state: FooState; + + constructor(props) { + super(props); + this.state = {bar: props.initialValue}; + } + + componentWillMount() { + this.setState({bar: 'bar'}); + } + + render() { + renderCount++; + return ; + } + } + + test(, 'SPAN', 'bar', () => { + test(, 'SPAN', 'bar', () => { + expect(renderCount).toBe(2); + done(); + }); + }); + }); + + it('should render with null in the initial state property', function (done) { + class Foo extends Component { + constructor(props) { + super(props); + this.state = null; + } + + render() { + return ; + } + } + + test(, 'SPAN', '', done); + }); + + it('should setState through an event handler', (done) => { + interface FooState { + bar: string | null + } + + class Foo extends Component<{ initialValue: string | null }, FooState> { + state: FooState; + + constructor(props) { + super(props); + this.state = {bar: props.initialValue}; + } + + handleClick() { + this.setState({bar: 'bar'}); + } + + render() { + return ; + } + } + + test(, 'DIV', 'foo', () => { + expect(renderedName).toBe('foo'); + attachedListener(); + setTimeout(() => { + expect(renderedName).toBe('bar'); + done(); + }, 10); + }); + }); + + it('should render using forceUpdate even when there is no state', (done) => { + class Foo extends Component<{ initialValue: string | null }> { + private mutativeValue: string; + + constructor(props) { + super(props); + this.mutativeValue = props.initialValue; + } + + handleClick() { + this.mutativeValue = 'bar'; + this.forceUpdate(); + } + + render() { + return ; + } + } + + test(, 'DIV', 'foo', function () { + attachedListener(); + expect(renderedName).toBe('bar'); + done(); + }); + }); + + describe('should render a component with a list of children that dynamically update via setState', () => { + interface CounterState { + count: number + } + + interface CounterProps { + car: string + } + + class Counter extends Component { + state: CounterState; + + constructor(props) { + super(props); + this.state = { + count: 0 + }; + this.incrementCount = this.incrementCount.bind(this); + } + + incrementCount() { + this.setState({ + count: this.state.count + 1 + }); + } + + render() { + return ( +
+

+ {this.props.car} {this.state.count} +

+ +
+ ); + } + } + + class Wrapper extends Component { + constructor(props) { + super(props); + } + + render() { + return ( +
+ {['Saab', 'Volvo', 'BMW'].map(function (c) { + return ; + })} +
+ ); + } + } + + it('Initial render (creation)', () => { + render(, container); + + expect(container.innerHTML).toBe( + '

Saab 0

Volvo 0

BMW 0

' + ); + }); + + it('Second render (update) #1', (done) => { + render(, container); + const buttons = container.querySelectorAll('button'); + for (const button of buttons) { + button.click(); + } + + setTimeout(() => { + expect(container.innerHTML).toBe( + '

Saab 1

Volvo 1

BMW 1

' + ); + done(); + }, 25); + }); + }); + + describe('should render a component with a conditional state item', () => { + interface SomeErrorState { + show: boolean + } + + class SomeError extends Component<{}, SomeErrorState> { + state: SomeErrorState; + + constructor(props) { + super(props); + + this.state = { + show: false + }; + + this.toggle = this.toggle.bind(this); + } + + toggle() { + this.setState({ + show: !this.state.show + }); + } + + render() { + return ( +
+ +
+ {function () { + if (this.state.show === true) { + return

This is cool!

; + } else { + return

Not so cool

; + } + }.call(this)} +
+ ); + } + } + + it('Initial render (creation)', () => { + render(, container); + + expect(container.innerHTML).toBe(''); + }); + + it('Second render (update with state change) #2', () => { + render(, container); + const buttons = container.querySelectorAll('button'); + for (const button of buttons) { + button.click(); + } + + expect(container.innerHTML).toBe(''); + }); + }); + + describe('should render a stateless component with a conditional state item', () => { + interface TestingState { + show: boolean + } + + const StatelessComponent = (props) =>

{props.name}

; + + class Testing extends Component<{}, TestingState> { + state: TestingState; + // @ts-ignore + private name: string = 'Kalle'; + + constructor(props) { + super(props); + + this.state = { + show: false + }; + + this.toggle = this.toggle.bind(this); + } + + toggle() { + this.setState({ + show: !this.state.show + }); + } + + render() { + return ( +
+ {function () { + if (this.state.show === true) { + return ; + } else { + return

Hello folks

; + } + }.call(this)} + +
+ ); + } + } + + it('Initial render (creation)', () => { + render(null, container); + + render(, container); + + expect(container.innerHTML).toBe('

Hello folks

'); + }); + + it('Second render (update with state change) #3', (done) => { + render(, container); + const buttons = container.querySelectorAll('button'); + for (const button of buttons) { + button.click(); + } + + setTimeout(() => { + expect(container.innerHTML).toBe('

Kalle

'); + done(); + }, 25); + }); + }); +}); diff --git a/packages/inferno-create-element/__tests__/components.spec.tsx b/packages/inferno-create-element/__tests__/components2b.spec.tsx similarity index 55% rename from packages/inferno-create-element/__tests__/components.spec.tsx rename to packages/inferno-create-element/__tests__/components2b.spec.tsx index d75938531..919895112 100644 --- a/packages/inferno-create-element/__tests__/components.spec.tsx +++ b/packages/inferno-create-element/__tests__/components2b.spec.tsx @@ -1,1229 +1,33 @@ -import { Component, render, rerender } from 'inferno'; +import {Component, render} from 'inferno'; import { createElement } from 'inferno-create-element'; -describe('Components (JSX)', () => { +describe('Components 2 (TSX)', () => { let container; - let Inner; - let attachedListener = null; - let renderedName = null; beforeEach(function () { - attachedListener = null; - renderedName = null; container = document.createElement('div'); document.body.appendChild(container); - - Inner = class extends Component { - render() { - attachedListener = this.props.onClick; - renderedName = this.props.name; - return
; - } - }; - }); - - afterEach(function () { - render(null, container); - document.body.removeChild(container); - }); - - class BasicComponent1 extends Component { - render() { - return ( -
- The title is {this.props.title} -
- ); - } - } - - it('should render a basic component jsx', () => { - render( -
- -
, - container - ); - - expect(container.innerHTML).toBe('
The title is abc
'); - - render( -
- -
, - container - ); - - expect(container.innerHTML).toBe('
The title is abc
'); - - const attrs = { title: 'abc', name: 'basic-render2', foo: 'bar' }; - - // JSX Spread Attribute - render( -
- -
, - container - ); - - expect(container.innerHTML).toBe('
The title is abc
'); - }); - - class BasicComponent1b extends Component { - render() { - return ( -
- -
- ); - } - } - - it('should render a basic component with inputs', () => { - render( -
- -
, - container - ); - - expect(container.innerHTML).toBe('
'); - expect(container.querySelector('input').checked).toBe(true); - - render( -
- -
, - container - ); - expect(container.innerHTML).toBe('
'); - expect(container.querySelector('input').checked).toBe(false); - - render( -
- -
, - container - ); - - render(
, container); - - render( -
- -
, - container - ); - expect(container.querySelector('input').checked).toBe(true); - }); - - it('should render a basic component and remove property if null', () => { - render( -
- -
, - container - ); - - expect(container.innerHTML).toBe('
The title is abc
'); - - render(
, container); - render( -
- -
, - container - ); - - expect(container.innerHTML).toBe('
The title is Hello, World!
'); - - render( -
- -
, - container - ); - expect(container.innerHTML).toBe('
The title is 123
'); - render( -
- -
, - container - ); - expect(container.innerHTML).toBe('
The title is
'); - - render( -
- -
, - container - ); - - expect(container.innerHTML).toBe('
The title is
'); - - render( -
- -
, - container - ); - - expect(container.innerHTML).toBe('
The title is abc
'); - - render( -
- -
, - container - ); - expect(container.innerHTML).toBe('
The title is 123
'); - }); - - it('should render a basic root component', () => { - render(, container); - - expect(container.firstChild.getAttribute('class')).toBe('basic'); - - render(, container); - - expect(container.firstChild.getAttribute('class')).toBe('basic'); - - render(, container); - expect(container.innerHTML).toBe('
The title is 123
'); - }); - - class BasicComponent2 extends Component { - render() { - return ( -
- The title is {this.props.title} - {this.props.children} -
- ); - } - } - - it('should render a basic component with children', () => { - render( -
- - Im a child - -
, - container - ); - - expect(container.innerHTML).toBe('
The title is abcIm a child
'); - - render( -
- - Im a child - -
, - container - ); - expect(container.innerHTML).toBe('
The title is 123Im a child
'); - }); - - it('should render multiple components', () => { - render( -
- - -
, - container - ); - - expect(container.innerHTML).toBe( - '
The title is component 1
' + - '
The title is component 2
' - ); - - render( -
- -
, - container - ); - expect(container.innerHTML).toBe('
The title is component 1
'); - }); - - class BasicComponent3 extends Component { - render() { - return ( -
- The title is {this.props.title} -
- ); - } - } - - it('should render a basic component with styling', () => { - render(, container); - - expect(container.innerHTML).toBe( - '
The title is styled!
' - ); - - render(, container); - - render(, container); - - expect(container.innerHTML).toBe( - '
The title is styled (again)!
' - ); - }); - - it('should render a basic component and remove styling', () => { - render(, container); - - expect(container.innerHTML).toBe( - '
The title is styled!
' - ); - - render(, container); - - expect([null, '']).toContain(container.firstChild.getAttribute('style')); - expect(container.firstChild.tagName).toEqual('DIV'); - expect(container.firstChild.firstChild.innerHTML).toEqual('The title is styles are removed!'); - }); - - class SuperComponent extends Component { - constructor(props) { - super(props); - this.state = { - organizations: [ - { name: 'test1', key: '1' }, - { name: 'test2', key: '2' }, - { name: 'test3', key: '3' }, - { name: 'test4', key: '4' }, - { name: 'test5', key: '5' }, - { name: 'test6', key: '6' } - ] - }; - } - - render() { - return ( - - ); - } - } - it('should render a basic component with a list of values from state', () => { - render(, container); - expect(container.innerHTML).toBe( - '' - ); - }); - - it('should render a basic component with an element and components as children', () => { - class Navbar extends Component { - render() { - return ( -
    -
  • Nav1
  • -
- ); - } - } - - class Main extends Component { - render() { - return ( -
- -
-
- ); - } - } - - render(
, container); - }); - - function test(element, expectedTag, expectedClassName, callback) { - render(element, container, () => { - expect(container.firstChild).not.toBe(null); - expect(container.firstChild.tagName).toBe(expectedTag); - expect(container.firstChild.className).toBe(expectedClassName); - callback(); - }); - } - - it('should only render once when setting state in componentWillMount', function (done) { - let renderCount = 0; - class Foo extends Component { - constructor(props) { - super(props); - this.state = { bar: props.initialValue }; - } - - componentWillMount() { - this.setState({ bar: 'bar' }); - } - - render() { - renderCount++; - return ; - } - } - test(, 'SPAN', 'bar', () => { - test(, 'SPAN', 'bar', () => { - expect(renderCount).toBe(2); - done(); - }); - }); - }); - - it('should render with null in the initial state property', function (done) { - class Foo extends Component { - constructor(props) { - super(props); - this.state = null; - } - - render() { - return ; - } - } - test(, 'SPAN', '', done); - }); - - it('should setState through an event handler', (done) => { - class Foo extends Component { - constructor(props) { - super(props); - this.state = { bar: props.initialValue }; - } - - handleClick() { - this.setState({ bar: 'bar' }); - } - - render() { - return ; - } - } - test(, 'DIV', 'foo', () => { - expect(renderedName).toBe('foo'); - attachedListener(); - setTimeout(() => { - expect(renderedName).toBe('bar'); - done(); - }, 10); - }); - }); - - it('should render using forceUpdate even when there is no state', (done) => { - class Foo extends Component { - constructor(props) { - super(props); - this.mutativeValue = props.initialValue; - } - - handleClick() { - this.mutativeValue = 'bar'; - this.forceUpdate(); - } - - render() { - return ; - } - } - test(, 'DIV', 'foo', function () { - attachedListener(); - expect(renderedName).toBe('bar'); - done(); - }); - }); - - describe('should render a component with a list of children that dynamically update via setState', () => { - class Counter extends Component { - constructor(props) { - super(props); - this.state = { - count: 0 - }; - this.incrementCount = this.incrementCount.bind(this); - } - - incrementCount() { - this.setState({ - count: this.state.count + 1 - }); - } - - render() { - return ( -
-

- {this.props.car} {this.state.count} -

- -
- ); - } - } - - class Wrapper extends Component { - constructor(props) { - super(props); - } - - render() { - return ( -
- {['Saab', 'Volvo', 'BMW'].map(function (c) { - return ; - })} -
- ); - } - } - - it('Initial render (creation)', () => { - render(, container); - - expect(container.innerHTML).toBe( - '

Saab 0

Volvo 0

BMW 0

' - ); - }); - - it('Second render (update) #1', (done) => { - render(, container); - const buttons = container.querySelectorAll('button'); - for (const button of buttons) { - button.click(); - } - - setTimeout(() => { - expect(container.innerHTML).toBe( - '

Saab 1

Volvo 1

BMW 1

' - ); - done(); - }, 25); - }); - }); - - describe('should render a component with a conditional state item', () => { - class SomeError extends Component { - constructor(props) { - super(props); - - this.state = { - show: false - }; - - this.toggle = this.toggle.bind(this); - } - - toggle() { - this.setState({ - show: !this.state.show - }); - } - - render() { - return ( -
- -
- {function () { - if (this.state.show === true) { - return

This is cool!

; - } else { - return

Not so cool

; - } - }.call(this)} -
- ); - } - } - - it('Initial render (creation)', () => { - render(, container); - - expect(container.innerHTML).toBe(''); - }); - - it('Second render (update with state change) #2', () => { - render(, container); - const buttons = container.querySelectorAll('button'); - for (const button of buttons) { - button.click(); - } - - expect(container.innerHTML).toBe(''); - }); - }); - - describe('should render a stateless component with a conditional state item', () => { - const StatelessComponent = (props) =>

{props.name}

; - - class Testing extends Component { - constructor(props) { - super(props); - this.name = 'Kalle'; - - this.state = { - show: false - }; - - this.toggle = this.toggle.bind(this); - } - - toggle() { - this.setState({ - show: !this.state.show - }); - } - - render() { - return ( -
- {function () { - if (this.state.show === true) { - return ; - } else { - return

Hello folks

; - } - }.call(this)} - -
- ); - } - } - - it('Initial render (creation)', () => { - render(null, container); - - render(, container); - - expect(container.innerHTML).toBe('

Hello folks

'); - }); - - it('Second render (update with state change) #3', (done) => { - render(, container); - const buttons = container.querySelectorAll('button'); - for (const button of buttons) { - button.click(); - } - - setTimeout(() => { - expect(container.innerHTML).toBe('

Kalle

'); - done(); - }, 25); - }); - }); - - describe('should render a repeating counter component with component children', () => { - let id = 0; - - class Value extends Component { - constructor(props) { - super(props); - this.id = ++id; - } - - render() { - return
{this.props.value}
; - } - } - - class Repeater extends Component { - render() { - const children = []; - for (let i = 0; i < 3; i++) { - children.push(); - } - - return
{children}
; - } - } - - it('should correctly render as values increase', () => { - let value = 0; - - render(, container); - expect(container.innerHTML).toBe('
0
0
0
'); - - value++; - render(, container); - expect(container.innerHTML).toBe('
1
1
1
'); - - value++; - render(, container); - expect(container.innerHTML).toBe('
2
2
2
'); - }); - }); - - describe('should render a component with component children as the only child', () => { - class Jaska extends Component { - constructor(props) { - super(props); - } - - render() { - return ( -
-

Okdokfwoe

-

odkodwq

-
- ); - } - } - - class Container extends Component { - constructor(props) { - super(props); - } - - render() { - return
{this.props.children}
; - } - } - - class TestingProps extends Component { - constructor(props) { - super(props); - } - - render() { - return ( -
- - - -
- ); - } - } - - it('should correctly render', () => { - render(, container); - expect(container.innerHTML).toBe('

Okdokfwoe

odkodwq

'); - }); - }); - - describe('should render a component with with mapped text nodes', () => { - class MyComponent98 extends Component { - constructor(props) { - super(props); - this.state = { - isok: false - }; - } - - componentDidMount() { - this.setState({ isok: true }); - } - - render() { - return ; - } - } - - class MyComponent99 extends Component { - constructor(props) { - super(props); - } - - render() { - return ( -
- isok= - {this.props.isok ? 'true' : 'false'} -
- {this.props.isok && - ['a', 'b'].map((x) => { - return {x}; - })} -
-
- ); - } - } - - it('should correctly render', () => { - render(, container); - - expect(container.innerHTML).toBe('
isok=false
'); - - rerender(); - - expect(container.innerHTML).toBe('
isok=true
ab
'); - }); - }); - - describe('should render a component with conditional boolean text nodes', () => { - class MyComponent98 extends Component { - constructor(props) { - super(props); - this.state = { - isok: false - }; - } - - componentDidMount() { - this.setState({ isok: true }); - } - - render() { - return ; - } - } - - class MyComponent99 extends Component { - constructor(props) { - super(props); - } - - render() { - const z = function (v) { - if (v) { - return a; - } else { - return b; - } - }; - - return ( -
-
{z(this.props.isok)}
-
- ); - } - } - - it('should correctly render', (done) => { - render(, container); - setTimeout(() => { - expect(container.innerHTML).toBe('
a
'); - done(); - }, 25); - }); - }); - - const StatelessComponent2 = (props) =>
{props.name}
; - - it('should render stateless component', () => { - render(, container); - expect(container.textContent).toBe('A'); - }); - - it('should unmount stateless component', function () { - render(, container); - expect(container.textContent).toBe('A'); - - render(null, container); - expect(container.textContent).toBe(''); - }); - - it('should support module pattern components', function () { - function Child({ test }) { - return
{test}
; - } - - render(, container); - - expect(container.textContent).toBe('test'); - }); - - describe('should render a component with a conditional list that changes upon toggle', () => { - class BuggyRender extends Component { - constructor(props) { - super(props); - - this.state = { - empty: true - }; - - this.toggle = this.toggle.bind(this); - } - - toggle() { - this.setState({ - empty: !this.state.empty - }); - } - - render() { - return ( -
- -
    - {(() => { - if (this.state.empty === true) { - return
  • No cars!
  • ; - } else { - return ['BMW', 'Volvo', 'Saab'].map(function (car) { - return
  • {car}
  • ; - }); - } - })()} -
-
- ); - } - } - - it('should correctly render', () => { - render(, container); - expect(container.innerHTML).toBe('
  • No cars!
'); - }); - - it('should handle update upon click', () => { - render(, container); - const buttons = container.querySelectorAll('button'); - - for (const button of buttons) { - button.click(); - } - - expect(container.innerHTML).toBe('
  • BMW
  • Volvo
  • Saab
'); - }); - }); - - describe('should render a component with a list that instantly changes', () => { - class ChangeChildrenCount extends Component { - constructor(props) { - super(props); - - this.state = { - list: ['1', '2', '3', '4'] - }; - - this.handleClick = this.handleClick.bind(this); - } - - handleClick() { - this.setState({ - list: ['1'] - }); - } - - render() { - return ( -
- - {this.state.list.map(function (x, i) { - return
{i}
; - })} -
- ); - } - } - - it('should correctly render', () => { - render(, container); - expect(container.innerHTML).toBe('
0
1
2
3
'); - }); - - it('should handle update upon click', (done) => { - render(, container); - const buttons = container.querySelectorAll('button'); - - for (const button of buttons) { - button.click(); - } - - setTimeout(() => { - expect(container.innerHTML).toBe('
0
'); - done(); - }, 10); - }); - }); - - describe('should render a stateless component with context', () => { - const StatelessComponent3 = ({ value }, { fortyTwo }) => ( -

- {value}-{fortyTwo || 'ERROR'} -

- ); - - class First extends Component { - constructor(props, context) { - super(props, context); - - this.state = { - counter: 0 - }; - - this._onClick = this._onClick.bind(this); - } - - _onClick() { - this.setState({ - counter: 1 - }); - } - - getChildContext() { - return { - fortyTwo: 42 - }; - } - - render() { - return ( -
- - -
- ); - } - } - - it('should correctly render', () => { - render(, container); - expect(container.innerHTML).toBe('

0-42

'); - }); - - it('should handle update upon click', (done) => { - render(, container); - const buttons = container.querySelectorAll('button'); - - for (const button of buttons) { - button.click(); - } - - setTimeout(() => { - expect(container.innerHTML).toBe('

1-42

'); - done(); - }, 10); - }); - }); - - describe('should render a conditional stateless component', () => { - const StatelessComponent4 = ({ value }) =>

{value}

; - - class First extends Component { - constructor(props) { - super(props); - - this.state = { - counter: 0 - }; - - this.condition = true; - this._onClick = this._onClick.bind(this); - } - - _onClick() { - this.setState({ - counter: 1 - }); - } - - render() { - return ( -
- - {this.condition ? : null} -
- ); - } - } - - it('should correctly render', () => { - render(, container); - expect(container.innerHTML).toBe('

0

'); - }); - - it('should handle update upon click', (done) => { - render(, container); - const buttons = container.querySelectorAll('button'); - - for (const button of buttons) { - button.click(); - } - - setTimeout(() => { - expect(container.innerHTML).toBe('

1

'); - done(); - }, 10); - }); - }); - - describe('should render stateless component correctly when changing states', () => { - let firstDiv, secondDiv; - - beforeEach(function () { - firstDiv = document.createElement('div'); - secondDiv = document.createElement('div'); - - container.appendChild(firstDiv); - container.appendChild(secondDiv); - }); - - afterEach(function () { - render(null, firstDiv); - render(null, secondDiv); - }); - - const StatelessComponent = ({ value }) =>

{value}

; - - class First extends Component { - constructor(props) { - super(props); - - this.state = { - counter: 0 - }; - - this.condition = true; - this._onClick = this._onClick.bind(this); - } - - _onClick() { - this.setState({ - counter: 1 - }); - } - - render() { - return ( -
- - {this.condition ? : null} -
- ); - } - } - - it('should correctly render', () => { - render(, firstDiv); - render(, secondDiv); - - expect(container.innerHTML).toBe('

0

0

'); - }); - - it('should handle update when changing first component', (done) => { - render(, firstDiv); - render(, secondDiv); - - const buttons = firstDiv.querySelectorAll('button'); - for (const button of buttons) { - button.click(); - } - - setTimeout(() => { - expect(container.innerHTML).toBe('

1

0

'); - done(); - }, 10); - }); - - it('should handle update when changing second component', (done) => { - render(, firstDiv); - render(, secondDiv); - - const buttons = secondDiv.querySelectorAll('button'); - for (const button of buttons) { - button.click(); - } - - setTimeout(() => { - expect(container.innerHTML).toBe('

0

1

'); - done(); - }, 10); - }); }); - describe('updating child should not cause rendering parent to fail', () => { - it('should render parent correctly after child changes', (done) => { - let updateParent, updateChild; - - class Parent extends Component { - constructor(props) { - super(props); - this.state = { x: false }; - - updateParent = () => { - this.setState({ x: true }); - }; - } - - render() { - return ( -
-

parent

- {!this.state.x ? : } -
- ); - } - } - - class ChildB extends Component { - constructor(props) { - super(props); - } - - render() { - return
Y
; - } - } - - class ChildA extends Component { - constructor(props) { - super(props); - this.state = { z: false }; - - updateChild = () => { - this.setState({ z: true }); - }; - } - - render() { - if (!this.state.z) { - return
A
; - } - return ; - } - } - - class SubChild extends Component { - constructor(props) { - super(props); - } - - render() { - return
B
; - } - } - - render(, container); - expect(container.innerHTML).toBe('

parent

A
'); - updateChild(); - setTimeout(() => { - expect(container.innerHTML).toBe('

parent

B
'); - updateParent(); - setTimeout(() => { - expect(container.innerHTML).toBe('

parent

Y
'); - done(); - }, 10); - }, 10); - }); + afterEach(function () { + render(null, container); + document.body.removeChild(container); }); describe('recursive component', () => { it('Should be possible to pass props recursively', () => { - class List extends Component { + interface ListProps { + data: Array<{ key: string; data: string | Array<{ key: string; data: string }> }>; + } + class List extends Component { render() { const children = this.props.data.map((entity) => { const { key, data } = entity; - const child = Array.isArray(data) ? : ; + const child = (Array.isArray(data) ? + : + + ); + return
  • {child}
  • ; }); @@ -1231,7 +35,11 @@ describe('Components (JSX)', () => { } } - class Text extends Component { + interface TextProps { + data: string; + } + + class Text extends Component { render() { return {this.props.data}; } @@ -1253,11 +61,18 @@ describe('Components (JSX)', () => { }); it('Should be possible to pass props recursively AT BEGINNING (JSX plugin change required)', () => { - class List extends Component { + interface ListProps { + data: Array<{ key: string; data: string | Array<{ key: string; data: string }> }>; + } + class List extends Component { render() { const children = this.props.data.map((entity) => { const { key, data } = entity; - const child = Array.isArray(data) ? : ; + const child = (Array.isArray(data) ? + : + + ); + return
  • {child}
  • ; }); @@ -1265,7 +80,11 @@ describe('Components (JSX)', () => { } } - class Text extends Component { + interface TextProps { + data: string; + } + + class Text extends Component { render() { return {this.props.data}; } @@ -1288,7 +107,13 @@ describe('Components (JSX)', () => { }); it('Should render (github #117)', (done) => { - class MakeX extends Component { + interface MakeXState { + x: boolean; + } + + class MakeX extends Component<{}, MakeXState> { + state: MakeXState; + constructor(props) { super(props); this.state = { x: false }; @@ -1315,7 +140,13 @@ describe('Components (JSX)', () => { } } - class MakeA extends Component { + interface MakeAState { + z: boolean; + } + + class MakeA extends Component<{}, MakeAState> { + state: MakeAState; + constructor(props) { super(props); this.state = { z: false }; @@ -1353,7 +184,10 @@ describe('Components (JSX)', () => { }); it('Events should propagate between components (github #135)', (done) => { - class Label extends Component { + interface LabelProps { + text: string + } + class Label extends Component { render() { const style = { 'background-color': 'red', @@ -1367,8 +201,9 @@ describe('Components (JSX)', () => { let btnFlag = false; let containerFlag = false; - class Button extends Component { - onClick(event) { + + class Button extends Component { + onClick(_event) { btnFlag = !btnFlag; } @@ -1383,7 +218,7 @@ describe('Components (JSX)', () => { } class Container extends Component { - onClick(event) { + onClick(_event) { containerFlag = !containerFlag; } @@ -1412,7 +247,11 @@ describe('Components (JSX)', () => { }); it('Should be possible to stop propagation', (done) => { - class Label extends Component { + interface LabelProps { + text: string + } + + class Label extends Component { render() { const style = { 'background-color': 'red', @@ -1426,7 +265,7 @@ describe('Components (JSX)', () => { let btnFlag = false; let containerFlag = false; - class Button extends Component { + class Button extends Component { onClick(event) { event.stopPropagation(); btnFlag = !btnFlag; @@ -1443,7 +282,7 @@ describe('Components (JSX)', () => { } class Container extends Component { - onClick(event) { + onClick(_event) { containerFlag = !containerFlag; } @@ -1503,13 +342,10 @@ describe('Components (JSX)', () => { describe('A component rendering a component should work as expected', () => { let forceUpdate; let forceUpdate2; - let foo; - let bar; class Bar extends Component { constructor() { super(); - bar = this; forceUpdate = this.forceUpdate.bind(this); } @@ -1520,7 +356,6 @@ describe('Components (JSX)', () => { class Foo extends Component { constructor() { super(); - foo = this; forceUpdate2 = this.forceUpdate.bind(this); } @@ -1559,7 +394,13 @@ describe('Components (JSX)', () => { spyOn(obj, 'fn'); - class Bar extends Component { + interface BarState { + bool: boolean; + } + + class Bar extends Component<{}, BarState> { + state: BarState; + constructor(props) { super(props); @@ -1606,7 +447,13 @@ describe('Components (JSX)', () => { it('Should be able to swap between invalid node and valid node', () => { let updater; - class Bar extends Component { + interface BarState { + bool: boolean; + } + + class Bar extends Component<{}, BarState> { + state: BarState; + constructor(props) { super(props); @@ -1656,7 +503,13 @@ describe('Components (JSX)', () => { it('Should be able to swap between text node and html node', () => { let updater; - class Bar extends Component { + interface BarState { + bool: boolean; + } + + class Bar extends Component<{}, BarState> { + state: BarState; + constructor(props) { super(props); @@ -1700,7 +553,13 @@ describe('Components (JSX)', () => { it('Should be able to swap between text node and html node #2', (done) => { let updater; - class Bar extends Component { + interface BarState { + bool: boolean; + } + + class Bar extends Component<{}, BarState> { + state: BarState; + constructor(props) { super(props); @@ -1750,7 +609,11 @@ describe('Components (JSX)', () => { let instance; let shouldUpdate = false; - class Test extends Component { + interface Test2Props { + foo: string; + } + + class Test extends Component { shouldComponentUpdate() { return shouldUpdate; } @@ -1761,7 +624,7 @@ describe('Components (JSX)', () => { } } - class Test2 extends Component { + class Test2 extends Component { shouldComponentUpdate() { return shouldUpdate; } @@ -1860,7 +723,7 @@ describe('Components (JSX)', () => { this.handleBlur = this.handleBlur.bind(this); } - handleBlur(event) {} + handleBlur(_event) {} render() { const props = { @@ -1880,7 +743,7 @@ describe('Components (JSX)', () => { describe('Swapping Component to DOM node', () => { it('Should be able to swap statefull component to DOM list when doing setState', () => { - let change1 = null; + let change1; let unMountCalled = false; class FooBar extends Component { @@ -1904,7 +767,13 @@ describe('Components (JSX)', () => { } } - class Tester extends Component { + interface TesterState { + toggle1: boolean; + } + + class Tester extends Component { + state: TesterState; + constructor(props) { super(props); @@ -1950,7 +819,7 @@ describe('Components (JSX)', () => { }); it('Should be able to swap stateless component to DOM list when doing setState', () => { - let change1 = null; + let change1; const FooBar = () => (
    @@ -1961,7 +830,13 @@ describe('Components (JSX)', () => {
    ); - class Tester extends Component { + interface TesterState { + toggle1: boolean; + } + + class Tester extends Component<{}, TesterState> { + state: TesterState; + constructor(props) { super(props); @@ -2007,7 +882,13 @@ describe('Components (JSX)', () => { describe('handling componentWillReceiveProps lifecycle event', () => { it('should correctly handle setState within the lifecycle function', () => { let renderCount = 0; - class Comp1 extends Component { + interface Comp1State { + foo: number; + } + + class Comp1 extends Component<{}, Comp1State> { + state: Comp1State; + constructor(props) { super(props); this.state = { @@ -2072,7 +953,13 @@ describe('Components (JSX)', () => { }); describe('components should be able to use defaultProps', () => { - class Comp1 extends Component { + interface Comp1Props { + a?: string; + b?: string; + c?: string; + } + + class Comp1 extends Component { constructor(props) { super(props); } @@ -2091,7 +978,7 @@ describe('Components (JSX)', () => { } } - class Comp2 extends Component { + class Comp2 extends Component { constructor(props) { super(props); } @@ -2143,7 +1030,7 @@ describe('Components (JSX)', () => { let childrenPropertABeforeMount = 'A'; class Parent extends Component { render() { - expect(this.props.children.props.a).toEqual(childrenPropertABeforeMount); + expect((this.props.children as any).props.a).toBe(childrenPropertABeforeMount); return
    {this.props.children}
    ; } @@ -2174,7 +1061,13 @@ describe('Components (JSX)', () => { describe('when calling setState with a function', () => { let reference; - class Comp1 extends Component { + interface Comp1State { + foo: string; + } + + class Comp1 extends Component<{}, Comp1State> { + state: Comp1State; + constructor(props) { super(props); this.state = { @@ -2207,7 +1100,11 @@ describe('Components (JSX)', () => { describe('node change in updateComponent', () => { it('Should not crash when invalid node returned - statefull', () => { - class Comp1 extends Component { + interface Comp1Props { + foo?: boolean; + } + + class Comp1 extends Component { constructor(props) { super(props); } @@ -2228,7 +1125,11 @@ describe('Components (JSX)', () => { }); it('Should not crash when invalid node returned - stateless', () => { - const Comp1 = ({ foo }) => { + interface Comp1Props { + foo?: boolean; + } + + const Comp1 = ({ foo }: Comp1Props) => { if (foo) { return null; } @@ -2246,7 +1147,13 @@ describe('Components (JSX)', () => { describe('Root handling issues #1', () => { let div; - class A extends Component { + interface AState { + n: boolean; + } + + class A extends Component<{}, AState> { + state: AState; + private onClick: () => void; constructor(props) { super(props); this.state = { n: false }; @@ -2279,7 +1186,12 @@ describe('Components (JSX)', () => { } } - class Test extends Component { + interface TestState { + reverse: boolean + } + + class Test extends Component<{}, TestState> { + state: TestState; constructor(props) { super(props); this.state = { @@ -2335,7 +1247,13 @@ describe('Components (JSX)', () => { describe('Root handling issues #2', () => { let div; - class A extends Component { + interface AState { + n: boolean; + } + + class A extends Component<{}, AState> { + state: AState; + private onClick: () => void; constructor(props) { super(props); this.state = { n: false }; @@ -2372,7 +1290,12 @@ describe('Components (JSX)', () => { } } - class Test extends Component { + interface TestState { + reverse: boolean + } + + class Test extends Component<{}, TestState> { + state: TestState; constructor(props) { super(props); this.state = { @@ -2428,7 +1351,13 @@ describe('Components (JSX)', () => { describe('Root handling issues #3', () => { let div; - class A extends Component { + interface AState { + n: boolean; + } + + class A extends Component<{}, AState> { + state: AState; + private onClick: () => void; constructor(props) { super(props); this.state = { n: false }; @@ -2459,7 +1388,12 @@ describe('Components (JSX)', () => { return false} />; } - class Test extends Component { + interface TestState { + reverse: boolean + } + + class Test extends Component<{}, TestState> { + state: TestState; constructor(props) { super(props); this.state = { @@ -2516,7 +1450,13 @@ describe('Components (JSX)', () => { }); describe('Root handling issues #4', () => { - class A extends Component { + interface AState { + n: boolean; + } + + class A extends Component<{}, AState> { + state: AState; + private onClick: () => void; constructor(props) { super(props); this.state = { n: false }; @@ -2544,7 +1484,13 @@ describe('Components (JSX)', () => { } } - class Test extends Component { + interface TestState { + reverse: boolean + } + + class Test extends Component<{}, TestState> { + state: TestState; + constructor(props) { super(props); this.state = { @@ -2598,7 +1544,14 @@ describe('Components (JSX)', () => { }); describe('Root handling issues #5', () => { - class A extends Component { + interface AState { + n: boolean; + } + + class A extends Component<{}, AState> { + state: AState; + private onClick: () => void; + constructor(props) { super(props); this.state = { n: false }; @@ -2628,7 +1581,12 @@ describe('Components (JSX)', () => { } } - class Test extends Component { + interface TestState { + reverse: boolean; + } + + class Test extends Component<{}, TestState> { + state: TestState; constructor(props) { super(props); this.state = { @@ -2728,7 +1686,13 @@ describe('Components (JSX)', () => { }); describe('Cloned children issues #1', () => { - class Test extends Component { + interface TestState { + reverse: boolean; + } + + class Test extends Component<{}, TestState> { + state: TestState; + constructor(props) { super(props); this.state = { @@ -2765,7 +1729,13 @@ describe('Components (JSX)', () => { }); }); describe('Cloned children issues #2', () => { - class Test extends Component { + interface TestState { + reverse: boolean; + } + + class Test extends Component<{}, TestState> { + state: TestState; + constructor(props) { super(props); this.state = { @@ -2806,7 +1776,17 @@ describe('Components (JSX)', () => { describe('Asynchronous setStates', () => { it('Should not fail when parent component calls setState on unmounting children', (done) => { - class Parent extends Component { + interface ParentProps { + toggle: boolean; + } + + interface ParentState { + text: string; + } + + class Parent extends Component { + state: ParentState; + constructor(props) { super(props); @@ -2837,7 +1817,7 @@ describe('Components (JSX)', () => { } } - class Tester extends Component { + class Tester extends Component<{call: () => void, toggle: boolean}> { constructor(props) { super(props); } @@ -2867,20 +1847,4 @@ describe('Components (JSX)', () => { }, 40); }); }); - - // Crappy test, not all browsers throw exception if frozen object is assigned value - // describe('Context', () => { - // it('Should be the same object always (dev frozen)', () => { - // class ContextClass extends Component { - // constructor(props, context) { - // super(props, context); - // context.foo = 'bar'; - // } - // } - // - // expect(() => { - // render(, container); - // }).toThrowError(); - // }); - // }); }); diff --git a/packages/inferno-create-element/__tests__/components3.spec.tsx b/packages/inferno-create-element/__tests__/components3.spec.tsx new file mode 100644 index 000000000..a255bfbca --- /dev/null +++ b/packages/inferno-create-element/__tests__/components3.spec.tsx @@ -0,0 +1,672 @@ +import {Component, InfernoChild, render, rerender} from "inferno"; + +describe('Components 3 (TSX)', () => { + let container; + + beforeEach(function () { + container = document.createElement('div'); + document.body.appendChild(container); + }); + + afterEach(function () { + render(null, container); + document.body.removeChild(container); + }); + + + describe('should render a repeating counter component with component children', () => { + interface ValueProps { + value: number + } + + class Value extends Component { + constructor(props) { + super(props); + } + + render() { + return
    {this.props.value}
    ; + } + } + + interface RepeaterProps { + value: number; + } + + class Repeater extends Component { + render() { + const children: InfernoChild[] = []; + for (let i = 0; i < 3; i++) { + children.push(); + } + + return
    {children}
    ; + } + } + + it('should correctly render as values increase', () => { + let value = 0; + + render(, container); + expect(container.innerHTML).toBe('
    0
    0
    0
    '); + + value++; + render(, container); + expect(container.innerHTML).toBe('
    1
    1
    1
    '); + + value++; + render(, container); + expect(container.innerHTML).toBe('
    2
    2
    2
    '); + }); + }); + + describe('should render a component with component children as the only child', () => { + class Jaska extends Component { + constructor(props) { + super(props); + } + + render() { + return ( +
    +

    Okdokfwoe

    +

    odkodwq

    +
    + ); + } + } + + class Container extends Component { + constructor(props) { + super(props); + } + + render() { + return
    {this.props.children}
    ; + } + } + + class TestingProps extends Component { + constructor(props) { + super(props); + } + + render() { + return ( +
    + + + +
    + ); + } + } + + it('should correctly render', () => { + render(, container); + expect(container.innerHTML).toBe('

    Okdokfwoe

    odkodwq

    '); + }); + }); + + describe('should render a component with with mapped text nodes', () => { + interface MyComponent99Props { + isok: boolean + } + + interface MyComponent98State { + isok: boolean + } + + class MyComponent98 extends Component<{}, MyComponent98State> { + state: MyComponent98State; + + constructor(props) { + super(props); + this.state = { + isok: false + }; + } + + componentDidMount() { + this.setState({ isok: true }); + } + + render() { + return ; + } + } + + class MyComponent99 extends Component { + constructor(props) { + super(props); + } + + render() { + return ( +
    + isok= + {this.props.isok ? 'true' : 'false'} +
    + {this.props.isok && + ['a', 'b'].map((x) => { + return {x}; + })} +
    +
    + ); + } + } + + it('should correctly render', () => { + render(, container); + + expect(container.innerHTML).toBe('
    isok=false
    '); + + rerender(); + + expect(container.innerHTML).toBe('
    isok=true
    ab
    '); + }); + }); + + describe('should render a component with conditional boolean text nodes', () => { + interface MyComponent98State { + isok: boolean + } + + interface MyComponent99Props { + isok: boolean + } + + class MyComponent98 extends Component<{}, MyComponent98State> { + state: MyComponent98State; + + constructor(props) { + super(props); + this.state = { + isok: false + }; + } + + componentDidMount() { + this.setState({ isok: true }); + } + + render() { + return ; + } + } + + class MyComponent99 extends Component { + constructor(props) { + super(props); + } + + render() { + const z = function (v) { + if (v) { + return a; + } else { + return b; + } + }; + + return ( +
    +
    {z(this.props.isok)}
    +
    + ); + } + } + + it('should correctly render', (done) => { + render(, container); + setTimeout(() => { + expect(container.innerHTML).toBe('
    a
    '); + done(); + }, 25); + }); + }); + + const StatelessComponent2 = (props) =>
    {props.name}
    ; + + it('should render stateless component', () => { + render(, container); + expect(container.textContent).toBe('A'); + }); + + it('should unmount stateless component', function () { + render(, container); + expect(container.textContent).toBe('A'); + + render(null, container); + expect(container.textContent).toBe(''); + }); + + it('should support module pattern components', function () { + function Child({ test }) { + return
    {test}
    ; + } + + render(, container); + + expect(container.textContent).toBe('test'); + }); + + describe('should render a component with a conditional list that changes upon toggle', () => { + class BuggyRender extends Component<{}, {empty: boolean}> { + state: {empty: boolean}; + + constructor(props) { + super(props); + + this.state = { + empty: true + }; + + this.toggle = this.toggle.bind(this); + } + + toggle() { + this.setState({ + empty: !this.state.empty + }); + } + + render() { + return ( +
    + +
      + {(() => { + if (this.state.empty === true) { + return
    • No cars!
    • ; + } else { + return ['BMW', 'Volvo', 'Saab'].map(function (car) { + return
    • {car}
    • ; + }); + } + })()} +
    +
    + ); + } + } + + it('should correctly render', () => { + render(, container); + expect(container.innerHTML).toBe('
    • No cars!
    '); + }); + + it('should handle update upon click', () => { + render(, container); + const buttons = container.querySelectorAll('button'); + + for (const button of buttons) { + button.click(); + } + + expect(container.innerHTML).toBe('
    • BMW
    • Volvo
    • Saab
    '); + }); + }); + + describe('should render a component with a list that instantly changes', () => { + interface ChangeChildrenCountState { + list: string[] + } + class ChangeChildrenCount extends Component<{}, ChangeChildrenCountState> { + state: ChangeChildrenCountState; + + constructor(props) { + super(props); + + this.state = { + list: ['1', '2', '3', '4'] + }; + + this.handleClick = this.handleClick.bind(this); + } + + handleClick() { + this.setState({ + list: ['1'] + }); + } + + render() { + return ( +
    + + {this.state.list.map(function (_x, i) { + return
    {i}
    ; + })} +
    + ); + } + } + + it('should correctly render', () => { + render(, container); + expect(container.innerHTML).toBe('
    0
    1
    2
    3
    '); + }); + + it('should handle update upon click', (done) => { + render(, container); + const buttons = container.querySelectorAll('button'); + + for (const button of buttons) { + button.click(); + } + + setTimeout(() => { + expect(container.innerHTML).toBe('
    0
    '); + done(); + }, 10); + }); + }); + + describe('should render a stateless component with context', () => { + const StatelessComponent3 = ({ value }, { fortyTwo }) => ( +

    + {value}-{fortyTwo || 'ERROR'} +

    + ); + + interface FirstState { + counter: number + } + + class First extends Component<{}, FirstState> { + state: FirstState; + + constructor(props, context) { + super(props, context); + + this.state = { + counter: 0 + }; + + this._onClick = this._onClick.bind(this); + } + + _onClick() { + this.setState({ + counter: 1 + }); + } + + getChildContext() { + return { + fortyTwo: 42 + }; + } + + render() { + return ( +
    + + +
    + ); + } + } + + it('should correctly render', () => { + render(, container); + expect(container.innerHTML).toBe('

    0-42

    '); + }); + + it('should handle update upon click', (done) => { + render(, container); + const buttons = container.querySelectorAll('button'); + + for (const button of buttons) { + button.click(); + } + + setTimeout(() => { + expect(container.innerHTML).toBe('

    1-42

    '); + done(); + }, 10); + }); + }); + + describe('should render a conditional stateless component', () => { + const StatelessComponent4 = ({ value }) =>

    {value}

    ; + + interface FirstState { + counter: number + } + + class First extends Component<{}, FirstState> { + state: FirstState; + private condition: boolean; + + constructor(props) { + super(props); + + this.state = { + counter: 0 + }; + + this.condition = true; + this._onClick = this._onClick.bind(this); + } + + _onClick() { + this.setState({ + counter: 1 + }); + } + + render() { + return ( +
    + + {this.condition ? : null} +
    + ); + } + } + + it('should correctly render', () => { + render(, container); + expect(container.innerHTML).toBe('

    0

    '); + }); + + it('should handle update upon click', (done) => { + render(, container); + const buttons = container.querySelectorAll('button'); + + for (const button of buttons) { + button.click(); + } + + setTimeout(() => { + expect(container.innerHTML).toBe('

    1

    '); + done(); + }, 10); + }); + }); + + describe('should render stateless component correctly when changing states', () => { + let firstDiv, secondDiv; + + beforeEach(function () { + firstDiv = document.createElement('div'); + secondDiv = document.createElement('div'); + + container.appendChild(firstDiv); + container.appendChild(secondDiv); + }); + + afterEach(function () { + render(null, firstDiv); + render(null, secondDiv); + }); + + const StatelessComponent = ({ value }) =>

    {value}

    ; + + interface FirstState { + counter: number + } + + class First extends Component<{name: string}, FirstState> { + state: FirstState; + private condition: boolean; + + constructor(props) { + super(props); + + this.state = { + counter: 0 + }; + + this.condition = true; + this._onClick = this._onClick.bind(this); + } + + _onClick() { + this.setState({ + counter: 1 + }); + } + + render() { + return ( +
    + + {this.condition ? : null} +
    + ); + } + } + + it('should correctly render', () => { + render(, firstDiv); + render(, secondDiv); + + expect(container.innerHTML).toBe('

    0

    0

    '); + }); + + it('should handle update when changing first component', (done) => { + render(, firstDiv); + render(, secondDiv); + + const buttons = firstDiv.querySelectorAll('button'); + for (const button of buttons) { + button.click(); + } + + setTimeout(() => { + expect(container.innerHTML).toBe('

    1

    0

    '); + done(); + }, 10); + }); + + it('should handle update when changing second component', (done) => { + render(, firstDiv); + render(, secondDiv); + + const buttons = secondDiv.querySelectorAll('button'); + for (const button of buttons) { + button.click(); + } + + setTimeout(() => { + expect(container.innerHTML).toBe('

    0

    1

    '); + done(); + }, 10); + }); + }); + + describe('updating child should not cause rendering parent to fail', () => { + it('should render parent correctly after child changes', (done) => { + let updateParent, updateChild; + + interface ParentState { + x: boolean + } + + class Parent extends Component<{}, ParentState> { + state: ParentState; + + constructor(props) { + super(props); + this.state = { x: false }; + + updateParent = () => { + this.setState({ x: true }); + }; + } + + render() { + return ( +
    +

    parent

    + {!this.state.x ? : } +
    + ); + } + } + + + class ChildB extends Component { + constructor(props) { + super(props); + } + + render() { + return
    Y
    ; + } + } + + interface ChildAState { + z: boolean + } + + class ChildA extends Component<{}, ChildAState> { + state: ChildAState; + + constructor(props) { + super(props); + this.state = { z: false }; + + updateChild = () => { + this.setState({ z: true }); + }; + } + + render() { + if (!this.state.z) { + return
    A
    ; + } + return ; + } + } + + class SubChild extends Component { + constructor(props) { + super(props); + } + + render() { + return
    B
    ; + } + } + + render(, container); + expect(container.innerHTML).toBe('

    parent

    A
    '); + updateChild(); + setTimeout(() => { + expect(container.innerHTML).toBe('

    parent

    B
    '); + updateParent(); + setTimeout(() => { + expect(container.innerHTML).toBe('

    parent

    Y
    '); + done(); + }, 10); + }, 10); + }); + }); +})