mirror of
https://github.com/re-rxjs/react-rxjs.git
synced 2025-12-08 18:01:51 +00:00
165 lines
4.7 KiB
TypeScript
165 lines
4.7 KiB
TypeScript
import React, { useState, Suspense, useRef, useEffect } from "react"
|
|
import { render, fireEvent, screen } from "@testing-library/react"
|
|
import { defer, of, Subject, NEVER, BehaviorSubject, Observable } from "rxjs"
|
|
import { renderHook, act } from "@testing-library/react-hooks"
|
|
import { useObservable } from "../src/internal/useObservable"
|
|
import shareLatest from "../src/internal/share-latest"
|
|
import reactEnhancer from "../src/internal/react-enhancer"
|
|
import { SUSPENSE } from "../src"
|
|
import { BehaviorObservable } from "../src/internal/BehaviorObservable"
|
|
|
|
const wait = (ms: number) => new Promise(res => setTimeout(res, ms))
|
|
|
|
const enhancer = <T extends any>(source$: Observable<T>) =>
|
|
reactEnhancer<T>(source$.pipe(shareLatest(false)), 20)
|
|
|
|
describe("useObservable", () => {
|
|
it("works", async () => {
|
|
let counter = 0
|
|
let subject = new Subject<number>()
|
|
const source$ = defer(() => {
|
|
subject = new Subject<number>()
|
|
counter++
|
|
return subject.asObservable()
|
|
}).pipe(enhancer) as BehaviorObservable<number>
|
|
|
|
const { result, unmount } = renderHook(() => useObservable(source$))
|
|
|
|
expect(result.current).toBe(null)
|
|
|
|
await act(async () => {
|
|
subject.next(1)
|
|
await wait(10)
|
|
})
|
|
expect(counter).toBe(1)
|
|
expect(result.current).toEqual(1)
|
|
|
|
act(() => {
|
|
subject.next(4)
|
|
subject.complete()
|
|
})
|
|
|
|
expect(result.current).toEqual(4)
|
|
expect(counter).toBe(1)
|
|
unmount()
|
|
|
|
const secondMount = renderHook(() => useObservable(source$))
|
|
|
|
expect(counter).toBe(1)
|
|
expect(secondMount.result.current).toEqual(4)
|
|
secondMount.unmount()
|
|
|
|
await wait(40)
|
|
|
|
const thirdMount = renderHook(() => useObservable(source$))
|
|
|
|
expect(thirdMount.result.current).toEqual(null)
|
|
expect(counter).toBe(2)
|
|
|
|
await act(async () => {
|
|
subject.next(1)
|
|
await wait(10)
|
|
})
|
|
expect(thirdMount.result.current).toEqual(1)
|
|
expect(counter).toBe(2)
|
|
})
|
|
|
|
it("doesn't trigger react updates when the observable emits the same value", () => {
|
|
const subject$ = new BehaviorSubject(1)
|
|
const src$ = subject$.pipe(enhancer) as BehaviorObservable<number>
|
|
const useLatestNumber = () => {
|
|
const result = useObservable(src$)
|
|
const nUpdatesRef = useRef(0)
|
|
useEffect(() => {
|
|
nUpdatesRef.current++
|
|
})
|
|
return { result, nUpdatesRef }
|
|
}
|
|
|
|
const { result } = renderHook(() => useLatestNumber())
|
|
|
|
expect(result.current.result).toBe(1)
|
|
expect(result.current.nUpdatesRef.current).toBe(1)
|
|
|
|
act(() => {
|
|
subject$.next(1)
|
|
subject$.next(1)
|
|
subject$.next(1)
|
|
subject$.next(1)
|
|
})
|
|
|
|
expect(result.current.nUpdatesRef.current).toBe(1)
|
|
|
|
act(() => {
|
|
subject$.next(20)
|
|
})
|
|
expect(result.current.result).toBe(20)
|
|
expect(result.current.nUpdatesRef.current).toBe(2)
|
|
})
|
|
|
|
const observables: any = [NEVER, of(10), of(SUSPENSE), of(20)].map((x: any) =>
|
|
enhancer(x),
|
|
)
|
|
const Result: React.FC<{ idx: number }> = ({ idx }) => {
|
|
const result = useObservable(observables[idx % observables.length])
|
|
return <div>Result {result}</div>
|
|
}
|
|
|
|
const TestSuspense: React.FC = () => {
|
|
const [currentIdx, setCurrentIdx] = useState(0)
|
|
|
|
return (
|
|
<div>
|
|
<button onClick={() => setCurrentIdx(x => x + 1)}>Next</button>
|
|
<Suspense fallback={<span>Waiting</span>}>
|
|
<Result idx={currentIdx} />
|
|
</Suspense>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
it("supports suspense", () => {
|
|
const subs = observables[2].subscribe()
|
|
render(<TestSuspense />)
|
|
|
|
expect(screen.queryByText("Result")).toBeNull()
|
|
expect(screen.queryByText("Waiting")).not.toBeNull()
|
|
|
|
fireEvent.click(screen.getByText(/Next/i))
|
|
|
|
expect(screen.queryByText("Result 10")).not.toBeNull()
|
|
expect(screen.queryByText("Waiting")).toBeNull()
|
|
|
|
fireEvent.click(screen.getByText(/Next/i))
|
|
|
|
expect(screen.queryByText("Result")).toBeNull()
|
|
expect(screen.queryByText("Waiting")).not.toBeNull()
|
|
|
|
fireEvent.click(screen.getByText(/Next/i))
|
|
|
|
expect(screen.queryByText("Result 20")).not.toBeNull()
|
|
expect(screen.queryByText("Waiting")).toBeNull()
|
|
|
|
fireEvent.click(screen.getByText(/Next/i))
|
|
|
|
expect(screen.queryByText("Result")).toBeNull()
|
|
expect(screen.queryByText("Waiting")).not.toBeNull()
|
|
|
|
fireEvent.click(screen.getByText(/Next/i))
|
|
|
|
expect(screen.queryByText("Result 10")).not.toBeNull()
|
|
expect(screen.queryByText("Waiting")).toBeNull()
|
|
|
|
fireEvent.click(screen.getByText(/Next/i))
|
|
|
|
expect(screen.queryByText("Result")).toBeNull()
|
|
expect(screen.queryByText("Waiting")).not.toBeNull()
|
|
|
|
fireEvent.click(screen.getByText(/Next/i))
|
|
|
|
expect(screen.queryByText("Result 20")).not.toBeNull()
|
|
expect(screen.queryByText("Waiting")).toBeNull()
|
|
subs.unsubscribe()
|
|
})
|
|
})
|