mirror of
https://github.com/re-rxjs/react-rxjs.git
synced 2025-12-08 18:01:51 +00:00
More tests
This commit is contained in:
parent
b6c66c6597
commit
8c56b0a80b
159
package-lock.json
generated
159
package-lock.json
generated
@ -2464,6 +2464,159 @@
|
||||
"micromatch": "^4.0.2"
|
||||
}
|
||||
},
|
||||
"@testing-library/dom": {
|
||||
"version": "7.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.16.2.tgz",
|
||||
"integrity": "sha512-4fT5l5L+5gfNhUZVCg0wnSszbRJ7A1ZHEz32v7OzH3mcY5lUsK++brI3IB2L9F5zO4kSDc2TRGEVa8v2hgl9vA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.10.2",
|
||||
"aria-query": "^4.0.2",
|
||||
"dom-accessibility-api": "^0.4.5",
|
||||
"pretty-format": "^25.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.10.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.3.tgz",
|
||||
"integrity": "sha512-RzGO0RLSdokm9Ipe/YD+7ww8X2Ro79qiXZF3HU9ljrM+qnJmH1Vqth+hbiQZy761LnMJTMitHDuKVYTk3k4dLw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
},
|
||||
"@babel/runtime-corejs3": {
|
||||
"version": "7.10.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.10.3.tgz",
|
||||
"integrity": "sha512-HA7RPj5xvJxQl429r5Cxr2trJwOfPjKiqhCXcdQPSqO2G0RHPZpXu4fkYmBaTKCp2c/jRaMK9GB/lN+7zvvFPw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"core-js-pure": "^3.0.0",
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
},
|
||||
"@jest/types": {
|
||||
"version": "25.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz",
|
||||
"integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/istanbul-lib-coverage": "^2.0.0",
|
||||
"@types/istanbul-reports": "^1.1.1",
|
||||
"@types/yargs": "^15.0.0",
|
||||
"chalk": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"@types/yargs": {
|
||||
"version": "15.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz",
|
||||
"integrity": "sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/yargs-parser": "*"
|
||||
}
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
|
||||
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
|
||||
"integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/color-name": "^1.1.1",
|
||||
"color-convert": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"aria-query": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz",
|
||||
"integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.10.2",
|
||||
"@babel/runtime-corejs3": "^7.10.2"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
|
||||
"integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-name": "~1.1.4"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true
|
||||
},
|
||||
"pretty-format": {
|
||||
"version": "25.5.0",
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz",
|
||||
"integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@jest/types": "^25.5.0",
|
||||
"ansi-regex": "^5.0.0",
|
||||
"ansi-styles": "^4.0.0",
|
||||
"react-is": "^16.12.0"
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
|
||||
"integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^4.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@testing-library/react": {
|
||||
"version": "10.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/react/-/react-10.3.0.tgz",
|
||||
"integrity": "sha512-Rhn5uJK6lYHWzlGVbK6uAvheAW8AUoFYxTLGdDxgsJDaK/PYy5drWfW/6YpMMOKMw+u6jHHl4MNHlt5qLHnm0Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.10.2",
|
||||
"@testing-library/dom": "^7.14.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.10.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.3.tgz",
|
||||
"integrity": "sha512-RzGO0RLSdokm9Ipe/YD+7ww8X2Ro79qiXZF3HU9ljrM+qnJmH1Vqth+hbiQZy761LnMJTMitHDuKVYTk3k4dLw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@testing-library/react-hooks": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-3.3.0.tgz",
|
||||
@ -4509,6 +4662,12 @@
|
||||
"esutils": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"dom-accessibility-api": {
|
||||
"version": "0.4.5",
|
||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.4.5.tgz",
|
||||
"integrity": "sha512-HcPDilI95nKztbVikaN2vzwvmv0sE8Y2ZJFODy/m15n7mGXLeOKGiys9qWVbFbh+aq/KYj2lqMLybBOkYAEXqg==",
|
||||
"dev": true
|
||||
},
|
||||
"domexception": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz",
|
||||
|
||||
@ -37,6 +37,7 @@
|
||||
"module": "dist/re-rxjs.esm.js",
|
||||
"devDependencies": {
|
||||
"@josepot/rxjs-utils": "^0.12.0",
|
||||
"@testing-library/react": "^10.3.0",
|
||||
"@testing-library/react-hooks": "^3.3.0",
|
||||
"@types/jest": "^26.0.0",
|
||||
"@types/react": "^16.9.38",
|
||||
|
||||
@ -26,9 +26,6 @@ const reactEnhancer = <T>(
|
||||
error(e) {
|
||||
subscriber.error(e)
|
||||
},
|
||||
complete() {
|
||||
subscriber.complete()
|
||||
},
|
||||
})
|
||||
onSubscribe.next()
|
||||
finalizeLastUnsubscription()
|
||||
|
||||
@ -1,71 +1,129 @@
|
||||
import { connectFactoryObservable } from "../src"
|
||||
import { connectFactoryObservable } from "../src/connectFactoryObservable"
|
||||
import { from, of, defer, concat, BehaviorSubject } from "rxjs"
|
||||
import { renderHook, act } from "@testing-library/react-hooks"
|
||||
|
||||
const wait = (ms: number) => new Promise(res => setTimeout(res, ms))
|
||||
|
||||
describe("connectObservable", () => {
|
||||
it("returns the latest emitted value", async () => {
|
||||
const [useNumber] = connectFactoryObservable((id: number) => of(id))
|
||||
const { result } = renderHook(() => useNumber(1))
|
||||
expect(result.current).toBe(1)
|
||||
describe("connectFactoryObservable", () => {
|
||||
const originalError = console.error
|
||||
beforeAll(() => {
|
||||
console.error = (...args: any) => {
|
||||
if (/Consider adding an error/.test(args[0])) {
|
||||
return
|
||||
}
|
||||
originalError.call(console, ...args)
|
||||
}
|
||||
})
|
||||
|
||||
it("shares the source subscription until the refCount has stayed at zero for the grace-period", async () => {
|
||||
let nInitCount = 0
|
||||
const observable$ = defer(() => {
|
||||
nInitCount += 1
|
||||
return from([1, 2, 3, 4, 5])
|
||||
afterAll(() => {
|
||||
console.error = originalError
|
||||
})
|
||||
describe("hook", () => {
|
||||
it("returns the latest emitted value", async () => {
|
||||
const [useNumber] = connectFactoryObservable((id: number) => of(id))
|
||||
const { result } = renderHook(() => useNumber(1))
|
||||
expect(result.current).toBe(1)
|
||||
})
|
||||
|
||||
const [useLatestNumber] = connectFactoryObservable(
|
||||
(id: number) => concat(observable$, of(id)),
|
||||
{
|
||||
unsubscribeGraceTime: 100,
|
||||
},
|
||||
)
|
||||
const { unmount } = renderHook(() => useLatestNumber(6))
|
||||
const { unmount: unmount2 } = renderHook(() => useLatestNumber(6))
|
||||
const { unmount: unmount3 } = renderHook(() => useLatestNumber(6))
|
||||
expect(nInitCount).toBe(1)
|
||||
unmount()
|
||||
unmount2()
|
||||
unmount3()
|
||||
it("shares the source subscription until the refCount has stayed at zero for the grace-period", async () => {
|
||||
let nInitCount = 0
|
||||
const observable$ = defer(() => {
|
||||
nInitCount += 1
|
||||
return from([1, 2, 3, 4, 5])
|
||||
})
|
||||
|
||||
await wait(90)
|
||||
const { unmount: unmount4 } = renderHook(() => useLatestNumber(6))
|
||||
expect(nInitCount).toBe(1)
|
||||
unmount4()
|
||||
const [useLatestNumber] = connectFactoryObservable(
|
||||
(id: number) => concat(observable$, of(id)),
|
||||
{
|
||||
unsubscribeGraceTime: 100,
|
||||
},
|
||||
)
|
||||
const { unmount } = renderHook(() => useLatestNumber(6))
|
||||
const { unmount: unmount2 } = renderHook(() => useLatestNumber(6))
|
||||
const { unmount: unmount3 } = renderHook(() => useLatestNumber(6))
|
||||
expect(nInitCount).toBe(1)
|
||||
unmount()
|
||||
unmount2()
|
||||
unmount3()
|
||||
|
||||
await wait(110)
|
||||
renderHook(() => useLatestNumber(6))
|
||||
expect(nInitCount).toBe(2)
|
||||
await wait(90)
|
||||
const { unmount: unmount4 } = renderHook(() => useLatestNumber(6))
|
||||
expect(nInitCount).toBe(1)
|
||||
unmount4()
|
||||
|
||||
await wait(110)
|
||||
renderHook(() => useLatestNumber(6))
|
||||
expect(nInitCount).toBe(2)
|
||||
})
|
||||
|
||||
it("allows errors to be caught in error boundaries", () => {
|
||||
const errStream = new BehaviorSubject(1)
|
||||
const [useError] = connectFactoryObservable(() => errStream)
|
||||
|
||||
renderHook(() => useError())
|
||||
|
||||
expect(() =>
|
||||
act(() => {
|
||||
errStream.error("error")
|
||||
}),
|
||||
).toThrow()
|
||||
})
|
||||
|
||||
it("doesn't throw errors on components that will get unmounted on the next cycle", () => {
|
||||
const errStream = new BehaviorSubject(1)
|
||||
const [useError] = connectFactoryObservable(() => errStream)
|
||||
|
||||
const { unmount } = renderHook(() => useError())
|
||||
|
||||
expect(() =>
|
||||
act(() => {
|
||||
errStream.error("error")
|
||||
unmount()
|
||||
}),
|
||||
).not.toThrow()
|
||||
})
|
||||
})
|
||||
describe("observable", () => {
|
||||
it("returns a factory of BehaviorObservables", () => {
|
||||
const [, getShared] = connectFactoryObservable((x: number) => of(x))
|
||||
expect((getShared(0) as any).getValue).toBeInstanceOf(Function)
|
||||
})
|
||||
|
||||
it("allows errors to be caught in error boundaries", () => {
|
||||
const errStream = new BehaviorSubject(1)
|
||||
const [useError] = connectFactoryObservable(() => errStream)
|
||||
it("if the source observable completes it keeps emitting the latest value until there are no more subscriptions", () => {
|
||||
let diff = -1
|
||||
const [, getShared] = connectFactoryObservable((_: number) => {
|
||||
diff++
|
||||
return from([1, 2, 3, 4].map(val => val + diff))
|
||||
})
|
||||
|
||||
renderHook(() => useError())
|
||||
let latestValue1: number = 0
|
||||
let nUpdates = 0
|
||||
const sub1 = getShared(0).subscribe(x => {
|
||||
latestValue1 = x
|
||||
nUpdates += 1
|
||||
})
|
||||
expect(latestValue1).toBe(4)
|
||||
expect(nUpdates).toBe(1)
|
||||
|
||||
expect(() =>
|
||||
act(() => {
|
||||
errStream.error("error")
|
||||
}),
|
||||
).toThrow()
|
||||
})
|
||||
let latestValue2: number = 0
|
||||
const sub2 = getShared(0).subscribe(x => {
|
||||
latestValue2 = x
|
||||
nUpdates += 1
|
||||
})
|
||||
expect(latestValue2).toBe(4)
|
||||
expect(nUpdates).toBe(2)
|
||||
|
||||
it("doesn't throw errors on components that will get unmounted on the next cycle", () => {
|
||||
const errStream = new BehaviorSubject(1)
|
||||
const [useError] = connectFactoryObservable(() => errStream)
|
||||
sub1.unsubscribe()
|
||||
sub2.unsubscribe()
|
||||
|
||||
const { unmount } = renderHook(() => useError())
|
||||
|
||||
expect(() =>
|
||||
act(() => {
|
||||
errStream.error("error")
|
||||
unmount()
|
||||
}),
|
||||
).not.toThrow()
|
||||
let latestValue3: number = 0
|
||||
const sub3 = getShared(0).subscribe(x => {
|
||||
latestValue3 = x
|
||||
nUpdates += 1
|
||||
})
|
||||
expect(latestValue3).toBe(5)
|
||||
expect(nUpdates).toBe(3)
|
||||
sub3.unsubscribe()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,10 +1,29 @@
|
||||
import { connectObservable } from "../src"
|
||||
import { from, of, defer } from "rxjs"
|
||||
import React, { Suspense } from "react"
|
||||
import { render, fireEvent, screen } from "@testing-library/react"
|
||||
import { connectObservable } from "../src/connectObservable"
|
||||
import { switchMapSuspended } from "../src/operators/switchMapSuspended"
|
||||
import { suspended } from "../src/operators/suspended"
|
||||
import { from, of, defer, Subject, concat } from "rxjs"
|
||||
import { renderHook } from "@testing-library/react-hooks"
|
||||
import { delay, scan, take, mergeMap, mergeMapTo } from "rxjs/operators"
|
||||
|
||||
const wait = (ms: number) => new Promise(res => setTimeout(res, ms))
|
||||
|
||||
describe("connectObservable", () => {
|
||||
const originalError = console.error
|
||||
beforeAll(() => {
|
||||
console.error = (...args: any) => {
|
||||
if (/Warning.*not wrapped in act/.test(args[0])) {
|
||||
return
|
||||
}
|
||||
originalError.call(console, ...args)
|
||||
}
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
console.error = originalError
|
||||
})
|
||||
|
||||
it("sets the initial state synchronously if it's available", async () => {
|
||||
const observable$ = of(1)
|
||||
const [useLatestNumber] = connectObservable(observable$)
|
||||
@ -40,4 +59,63 @@ describe("connectObservable", () => {
|
||||
renderHook(() => useLatestNumber())
|
||||
expect(nInitCount).toBe(2)
|
||||
})
|
||||
|
||||
it("works with suspense", async () => {
|
||||
const subject$ = new Subject()
|
||||
const source$ = concat(
|
||||
subject$.pipe(
|
||||
take(2),
|
||||
scan(a => a + 1, 0),
|
||||
switchMapSuspended(x => of(x).pipe(delay(100))),
|
||||
),
|
||||
subject$.pipe(take(1), mergeMapTo(of(3).pipe(delay(100), suspended()))),
|
||||
)
|
||||
const [useDelayedNumber] = connectObservable(source$)
|
||||
const Result: React.FC = () => <div>Result {useDelayedNumber()}</div>
|
||||
const TestSuspense: React.FC = () => {
|
||||
return (
|
||||
<div>
|
||||
<button onClick={() => subject$.next()}>Next</button>
|
||||
<Suspense fallback={<span>Waiting</span>}>
|
||||
<Result />
|
||||
</Suspense>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render(<TestSuspense />)
|
||||
|
||||
expect(screen.queryByText("Result")).toBeNull()
|
||||
expect(screen.queryByText("Waiting")).not.toBeNull()
|
||||
|
||||
fireEvent.click(screen.getByText(/Next/i))
|
||||
|
||||
expect(screen.queryByText("Result")).toBeNull()
|
||||
expect(screen.queryByText("Waiting")).not.toBeNull()
|
||||
|
||||
await wait(110)
|
||||
|
||||
expect(screen.queryByText("Result 1")).not.toBeNull()
|
||||
expect(screen.queryByText("Waiting")).toBeNull()
|
||||
|
||||
fireEvent.click(screen.getByText(/Next/i))
|
||||
|
||||
expect(screen.queryByText("Result")).toBeNull()
|
||||
expect(screen.queryByText("Waiting")).not.toBeNull()
|
||||
|
||||
await wait(110)
|
||||
|
||||
expect(screen.queryByText("Result 2")).not.toBeNull()
|
||||
expect(screen.queryByText("Waiting")).toBeNull()
|
||||
|
||||
fireEvent.click(screen.getByText(/Next/i))
|
||||
|
||||
expect(screen.queryByText("Result")).toBeNull()
|
||||
expect(screen.queryByText("Waiting")).not.toBeNull()
|
||||
|
||||
await wait(110)
|
||||
|
||||
expect(screen.queryByText("Result 3")).not.toBeNull()
|
||||
expect(screen.queryByText("Waiting")).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { createInput } from "../"
|
||||
import { createInput } from "../src/createInput"
|
||||
|
||||
describe("createInput", () => {
|
||||
test("it returns the default value", () => {
|
||||
|
||||
@ -1,112 +0,0 @@
|
||||
import {} from "react/experimental"
|
||||
import { defer, of, Subject, Observable, NEVER, concat } from "rxjs"
|
||||
import { SUSPENSE } from "../src"
|
||||
import { useObservable } from "../src/useObservable"
|
||||
import { renderHook, act } from "@testing-library/react-hooks"
|
||||
import { delay, switchMap, startWith } from "rxjs/operators"
|
||||
import { unstable_useTransition as useTransition, useEffect } from "react"
|
||||
import { BehaviorObservable, distinctShareReplay } from "../src"
|
||||
import reactEnhancer from "../src/operators/react-enhancer"
|
||||
|
||||
const wait = (ms: number) => new Promise(res => setTimeout(res, ms))
|
||||
|
||||
const enhancer = <T>(source$: Observable<T>) =>
|
||||
reactEnhancer(concat(source$, NEVER).pipe(distinctShareReplay()), 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)
|
||||
})
|
||||
|
||||
const userId$ = new Subject<number>()
|
||||
const getUser = (id: number) =>
|
||||
of({ id, name: `name_${id}` }).pipe(delay(200))
|
||||
|
||||
const user$ = userId$
|
||||
.pipe(
|
||||
switchMap(id => getUser(id).pipe(startWith(SUSPENSE))),
|
||||
startWith({ id: 1, name: "name_1" }),
|
||||
)
|
||||
.pipe(enhancer) as BehaviorObservable<any>
|
||||
|
||||
const useUserTransition = () => {
|
||||
const [startTransition, isPending] = useTransition({ timeoutMs: 500 })
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
startTransition(() => {
|
||||
userId$.next(8)
|
||||
})
|
||||
}, 100)
|
||||
}, [])
|
||||
// console.log("isPending", isPending)
|
||||
return isPending
|
||||
}
|
||||
|
||||
it.skip("works with useTransition", async () => {
|
||||
const { result: userResult } = renderHook(() => useObservable(user$))
|
||||
const { result: transitionResult } = renderHook(() => useUserTransition())
|
||||
|
||||
expect(userResult.current).toEqual({ id: 1, name: "name_1" })
|
||||
expect(transitionResult.current).toBe(false)
|
||||
|
||||
await act(async () => {
|
||||
await wait(120)
|
||||
})
|
||||
|
||||
expect(userResult.current).toEqual({ id: 1, name: "name_1" })
|
||||
expect(transitionResult.current).toBe(true)
|
||||
|
||||
await act(async () => {
|
||||
await wait(220)
|
||||
})
|
||||
|
||||
expect(userResult.current).toEqual({ id: 8, name: "name_8" })
|
||||
expect(transitionResult.current).toBe(false)
|
||||
})
|
||||
})
|
||||
128
test/useObservable.test.tsx
Normal file
128
test/useObservable.test.tsx
Normal file
@ -0,0 +1,128 @@
|
||||
import React, { useState, Suspense } from "react"
|
||||
import { render, fireEvent, screen } from "@testing-library/react"
|
||||
import { defer, of, Subject, NEVER, concat } from "rxjs"
|
||||
import { useObservable } from "../src/useObservable"
|
||||
import { renderHook, act } from "@testing-library/react-hooks"
|
||||
import { BehaviorObservable, distinctShareReplay } from "../src"
|
||||
import reactEnhancer from "../src/operators/react-enhancer"
|
||||
import { SUSPENSE } from "../src/SUSPENSE"
|
||||
|
||||
const wait = (ms: number) => new Promise(res => setTimeout(res, ms))
|
||||
|
||||
const enhancer = (source$: any) =>
|
||||
reactEnhancer(concat(source$, NEVER).pipe(distinctShareReplay()), 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)
|
||||
})
|
||||
|
||||
const observables: any = [NEVER, of(10), of(SUSPENSE), of(20)].map(enhancer)
|
||||
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()
|
||||
})
|
||||
})
|
||||
Loading…
x
Reference in New Issue
Block a user