Skip to content

Commit a67a9e4

Browse files
committed
add selector
1 parent e3b1928 commit a67a9e4

File tree

2 files changed

+293
-0
lines changed

2 files changed

+293
-0
lines changed
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import { TestScheduler } from 'rxjs/testing'
2+
import { Controller, Selector } from '../../src'
3+
import { act, getByTestId, render, waitFor } from '@testing-library/react'
4+
import { tap } from 'rxjs'
5+
6+
type CounterData = {
7+
counter: number
8+
}
9+
10+
class TestController extends Controller<CounterData> {
11+
constructor(initialState?: CounterData) {
12+
super(initialState ?? { counter: 0 })
13+
}
14+
inc() {
15+
this.emit({ counter: this.state.counter + 1 })
16+
}
17+
protected compareState(): boolean {
18+
return false
19+
}
20+
reEmit() {
21+
this.emit(this.state)
22+
}
23+
}
24+
25+
it('emit object values every reEmit', () => {
26+
const testScheduler = new TestScheduler((actual, expected) => {
27+
return expect(actual).toEqual(expected)
28+
})
29+
const testController = new TestController()
30+
const triggerMarbles = '-aaaa'
31+
const triggerValues = {
32+
a: () => testController.reEmit(),
33+
}
34+
35+
const expectedMarbles = '-aaaa'
36+
const expectedValues = {
37+
a: { counter: 0 },
38+
}
39+
const eventObservable = testController.observable.pipe()
40+
testScheduler.run(({ expectObservable, cold }) => {
41+
expectObservable(eventObservable).toBe(expectedMarbles, expectedValues)
42+
expectObservable(cold(triggerMarbles, triggerValues).pipe(tap((fn) => fn())))
43+
})
44+
})
45+
46+
type DisplayRenderedProps = {
47+
source: TestController
48+
stateComp?: (prev: number, curr: number) => boolean
49+
}
50+
const DisplayRendered = ({ stateComp, source }: DisplayRenderedProps) => {
51+
return (
52+
<Selector source={source} selector={(state) => state.counter} stateCompare={stateComp}>
53+
{(state) => {
54+
return (
55+
<>
56+
<h1 data-testid='text'>{state}</h1>
57+
<h1 data-testid='controller'>{source.constructor.name}</h1>
58+
</>
59+
)
60+
}}
61+
</Selector>
62+
)
63+
}
64+
65+
it('rerender on state changed', async () => {
66+
const instance = new TestController()
67+
const { container } = render(<DisplayRendered source={instance} />)
68+
const rendered = getByTestId(container, 'text')
69+
const renderedText = rendered.textContent
70+
const expectedText = '0'
71+
expect(renderedText).toBe(expectedText)
72+
act(() => {
73+
instance.inc()
74+
instance.inc()
75+
instance.inc()
76+
instance.inc()
77+
instance.inc()
78+
})
79+
await waitFor(() => {
80+
const rendered = getByTestId(container, 'text')
81+
const renderedText = rendered.textContent
82+
const expectedText = '5'
83+
expect(renderedText).toBe(expectedText)
84+
})
85+
})
86+
87+
it('specified state compare respected', async () => {
88+
const stateCompareFn = jest.fn((prev, curr) => prev !== curr)
89+
const instance = new TestController()
90+
const { container } = render(<DisplayRendered source={instance} stateComp={stateCompareFn} />)
91+
const rendered = getByTestId(container, 'text')
92+
const renderedText = rendered.textContent
93+
const expectedText = '0'
94+
expect(renderedText).toBe(expectedText)
95+
96+
act(() => {
97+
instance.inc()
98+
instance.inc()
99+
instance.inc()
100+
instance.inc()
101+
})
102+
await waitFor(() => {
103+
expect(stateCompareFn).toHaveBeenCalledTimes(4)
104+
expect(stateCompareFn.mock.calls[0][0]).toBe(0)
105+
expect(stateCompareFn.mock.calls[0][1]).toBe(1)
106+
expect(stateCompareFn.mock.calls[1][0]).toBe(0)
107+
expect(stateCompareFn.mock.calls[1][1]).toBe(2)
108+
expect(stateCompareFn.mock.calls[2][0]).toBe(0)
109+
expect(stateCompareFn.mock.calls[2][1]).toBe(3)
110+
expect(stateCompareFn.mock.calls[3][0]).toBe(0)
111+
expect(stateCompareFn.mock.calls[3][1]).toBe(4)
112+
const rendered = getByTestId(container, 'text')
113+
const renderedText = rendered.textContent
114+
const expectedText = '0'
115+
expect(renderedText).toBe(expectedText)
116+
})
117+
})
118+
119+
it('render info with [state, controller]', () => {
120+
const instance = new TestController({ counter: 5 })
121+
const { container } = render(<DisplayRendered source={instance} />)
122+
const renderedCount = getByTestId(container, 'text')
123+
const renderedText = renderedCount.textContent
124+
const expectedText = '5'
125+
expect(renderedText).toBe(expectedText)
126+
127+
const renderedController = getByTestId(container, 'controller')
128+
const renderedControllerText = renderedController.textContent
129+
const expectedControllerText = 'TestController'
130+
expect(renderedControllerText).toBe(expectedControllerText)
131+
})

tests/Selector/selector_ctor.test.tsx

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import { TestScheduler } from 'rxjs/testing'
2+
import { Controller, ControllerProvider, ResourcesNotProvidedError, Selector } from '../../src'
3+
import { act, getByTestId, render, waitFor } from '@testing-library/react'
4+
import { tap } from 'rxjs'
5+
6+
type CounterData = {
7+
counter: number
8+
}
9+
10+
class TestController extends Controller<CounterData> {
11+
constructor(initialState?: CounterData) {
12+
super(initialState ?? { counter: 0 })
13+
}
14+
inc() {
15+
this.emit({ counter: this.state.counter + 1 })
16+
}
17+
protected compareState(): boolean {
18+
return false
19+
}
20+
reEmit() {
21+
this.emit(this.state)
22+
}
23+
}
24+
25+
it('emit object values every reEmit', () => {
26+
const testScheduler = new TestScheduler((actual, expected) => {
27+
return expect(actual).toEqual(expected)
28+
})
29+
const testController = new TestController()
30+
const triggerMarbles = '-aaaa'
31+
const triggerValues = {
32+
a: () => testController.reEmit(),
33+
}
34+
35+
const expectedMarbles = '-aaaa'
36+
const expectedValues = {
37+
a: { counter: 0 },
38+
}
39+
const eventObservable = testController.observable.pipe()
40+
testScheduler.run(({ expectObservable, cold }) => {
41+
expectObservable(eventObservable).toBe(expectedMarbles, expectedValues)
42+
expectObservable(cold(triggerMarbles, triggerValues).pipe(tap((fn) => fn())))
43+
})
44+
})
45+
46+
type DisplayRenderedProps = {
47+
stateComp?: (prev: number, curr: number) => boolean
48+
}
49+
const DisplayRendered = ({ stateComp }: DisplayRenderedProps) => {
50+
return (
51+
<Selector source={TestController} selector={(state) => state.counter} stateCompare={stateComp}>
52+
{(state, controller) => {
53+
return (
54+
<>
55+
<h1 data-testid='text'>{state}</h1>
56+
<h1 data-testid='controller'>{controller.constructor.name}</h1>
57+
</>
58+
)
59+
}}
60+
</Selector>
61+
)
62+
}
63+
64+
it('rerender on state changed', async () => {
65+
const instance = new TestController()
66+
const { container } = render(
67+
<ControllerProvider ctor={TestController} source={instance}>
68+
<DisplayRendered />
69+
</ControllerProvider>,
70+
)
71+
const rendered = getByTestId(container, 'text')
72+
const renderedText = rendered.textContent
73+
const expectedText = '0'
74+
expect(renderedText).toBe(expectedText)
75+
act(() => {
76+
instance.inc()
77+
instance.inc()
78+
instance.inc()
79+
instance.inc()
80+
instance.inc()
81+
})
82+
await waitFor(() => {
83+
const rendered = getByTestId(container, 'text')
84+
const renderedText = rendered.textContent
85+
const expectedText = '5'
86+
expect(renderedText).toBe(expectedText)
87+
})
88+
})
89+
90+
it('specified state compare respected', async () => {
91+
const stateCompareFn = jest.fn((prev, curr) => prev !== curr)
92+
const instance = new TestController()
93+
const { container } = render(
94+
<ControllerProvider ctor={TestController} source={instance}>
95+
<DisplayRendered stateComp={stateCompareFn} />
96+
</ControllerProvider>,
97+
)
98+
const rendered = getByTestId(container, 'text')
99+
const renderedText = rendered.textContent
100+
const expectedText = '0'
101+
expect(renderedText).toBe(expectedText)
102+
103+
act(() => {
104+
instance.inc()
105+
instance.inc()
106+
instance.inc()
107+
instance.inc()
108+
})
109+
await waitFor(() => {
110+
expect(stateCompareFn).toHaveBeenCalledTimes(4)
111+
expect(stateCompareFn.mock.calls[0][0]).toBe(0)
112+
expect(stateCompareFn.mock.calls[0][1]).toBe(1)
113+
expect(stateCompareFn.mock.calls[1][0]).toBe(0)
114+
expect(stateCompareFn.mock.calls[1][1]).toBe(2)
115+
expect(stateCompareFn.mock.calls[2][0]).toBe(0)
116+
expect(stateCompareFn.mock.calls[2][1]).toBe(3)
117+
expect(stateCompareFn.mock.calls[3][0]).toBe(0)
118+
expect(stateCompareFn.mock.calls[3][1]).toBe(4)
119+
const rendered = getByTestId(container, 'text')
120+
const renderedText = rendered.textContent
121+
const expectedText = '0'
122+
expect(renderedText).toBe(expectedText)
123+
})
124+
})
125+
126+
it('render with [state, controller]', () => {
127+
const instance = new TestController({ counter: 5 })
128+
const { container } = render(
129+
<ControllerProvider ctor={TestController} source={instance}>
130+
<DisplayRendered />
131+
</ControllerProvider>,
132+
)
133+
const renderedCount = getByTestId(container, 'text')
134+
const renderedText = renderedCount.textContent
135+
const expectedText = '5'
136+
expect(renderedText).toBe(expectedText)
137+
138+
const renderedController = getByTestId(container, 'controller')
139+
const renderedControllerText = renderedController.textContent
140+
const expectedControllerText = 'TestController'
141+
expect(renderedControllerText).toBe(expectedControllerText)
142+
})
143+
144+
it('throw error when not provided ctor', async () => {
145+
jest.spyOn(console, 'error').mockImplementation(() => jest.fn())
146+
147+
const renderer = () => {
148+
render(<DisplayRendered />)
149+
}
150+
await waitFor(() => expect(renderer).toThrow(ResourcesNotProvidedError))
151+
jest.restoreAllMocks()
152+
})
153+
154+
it('throw error when not provided ctor', async () => {
155+
jest.spyOn(console, 'error').mockImplementation(() => jest.fn())
156+
157+
const renderer = () => {
158+
render(<DisplayRendered />)
159+
}
160+
await waitFor(() => expect(renderer).toThrow(ResourcesNotProvidedError))
161+
jest.restoreAllMocks()
162+
})

0 commit comments

Comments
 (0)