Skip to content

Commit

Permalink
chore(core): remove graceTime
Browse files Browse the repository at this point in the history
  • Loading branch information
josepot committed Oct 14, 2020
1 parent 35527ed commit d4ac471
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 89 deletions.
35 changes: 26 additions & 9 deletions packages/core/src/bind/connectFactoryObservable.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
Subject,
} from "rxjs"
import { renderHook, act as actHook } from "@testing-library/react-hooks"
import { switchMap, delay, take } from "rxjs/operators"
import { switchMap, delay, take, catchError } from "rxjs/operators"
import { FC, Suspense, useState } from "react"
import React from "react"
import {
Expand Down Expand Up @@ -55,7 +55,8 @@ describe("connectFactoryObservable", () => {

it("suspends the component when the observable hasn't emitted yet.", async () => {
const source$ = of(1).pipe(delay(100))
const [useDelayedNumber] = bind(() => source$)
const [useDelayedNumber, getDelayedNumber$] = bind(() => source$)
const subs = getDelayedNumber$().subscribe()
const Result: React.FC = () => <div>Result {useDelayedNumber()}</div>
const TestSuspense: React.FC = () => {
return (
Expand All @@ -74,6 +75,7 @@ describe("connectFactoryObservable", () => {

expect(screen.queryByText("Result 1")).not.toBeNull()
expect(screen.queryByText("Waiting")).toBeNull()
subs.unsubscribe()
})

it("shares the multicasted subscription with all of the components that use the same parameters", async () => {
Expand All @@ -92,19 +94,21 @@ describe("connectFactoryObservable", () => {
expect(subscriberCount).toBe(0)

const first = { val: 1 }
latestNumber$(1, first).subscribe()
renderHook(() => useLatestNumber(1, first))
expect(subscriberCount).toBe(1)

renderHook(() => useLatestNumber(1, first))
expect(subscriberCount).toBe(1)

latestNumber$(1, first).subscribe()
expect(subscriberCount).toBe(1)

const second = { val: 2 }
latestNumber$(1, second).subscribe()
renderHook(() => useLatestNumber(1, second))
expect(subscriberCount).toBe(2)

latestNumber$(2, second).subscribe()
renderHook(() => useLatestNumber(2, second))
expect(subscriberCount).toBe(3)
})
Expand Down Expand Up @@ -135,7 +139,9 @@ describe("connectFactoryObservable", () => {
})

it("suspends the component when the factory-observable hasn't emitted yet.", async () => {
const [useDelayedNumber] = bind((x: number) => of(x).pipe(delay(50)))
const [useDelayedNumber, getDelayedNumber$] = bind((x: number) =>
of(x).pipe(delay(50)),
)
const Result: React.FC<{ input: number }> = (p) => (
<div>Result {useDelayedNumber(p.input)}</div>
)
Expand All @@ -151,6 +157,7 @@ describe("connectFactoryObservable", () => {
)
}

getDelayedNumber$(0).subscribe()
render(<TestSuspense />)
expect(screen.queryByText("Result")).toBeNull()
expect(screen.queryByText("Waiting")).not.toBeNull()
Expand All @@ -161,6 +168,7 @@ describe("connectFactoryObservable", () => {
expect(screen.queryByText("Waiting")).toBeNull()

componentAct(() => {
getDelayedNumber$(1).subscribe()
fireEvent.click(screen.getByText(/increase/i))
})
expect(screen.queryByText("Result")).toBeNull()
Expand All @@ -172,6 +180,7 @@ describe("connectFactoryObservable", () => {
expect(screen.queryByText("Waiting")).toBeNull()

componentAct(() => {
getDelayedNumber$(2).subscribe()
fireEvent.click(screen.getByText(/increase/i))
})
expect(screen.queryByText("Result")).toBeNull()
Expand All @@ -190,9 +199,10 @@ describe("connectFactoryObservable", () => {
return from([1, 2, 3, 4, 5])
})

const [useLatestNumber] = bind((id: number) =>
const [useLatestNumber, getLatestNumber$] = bind((id: number) =>
concat(observable$, of(id)),
)
let subs = getLatestNumber$(6).subscribe()
const { unmount } = renderHook(() => useLatestNumber(6))
const { unmount: unmount2 } = renderHook(() => useLatestNumber(6))
const { unmount: unmount3 } = renderHook(() => useLatestNumber(6))
Expand All @@ -201,13 +211,13 @@ describe("connectFactoryObservable", () => {
unmount2()
unmount3()

await wait(230)
const { unmount: unmount4 } = renderHook(() => useLatestNumber(6))
expect(nInitCount).toBe(1)

unmount4()
await wait(270)
subs.unsubscribe()

getLatestNumber$(6).subscribe()
renderHook(() => useLatestNumber(6))
expect(nInitCount).toBe(2)
})
Expand Down Expand Up @@ -307,7 +317,13 @@ describe("connectFactoryObservable", () => {
observer.error("controlled error")
})

const [useOkKo] = bind((ok: boolean) => (ok ? normal$ : errored$))
const [useOkKo, getObs$] = bind((ok: boolean) =>
ok ? normal$ : errored$,
)
getObs$(true).subscribe()
getObs$(false)
.pipe(catchError(() => []))
.subscribe()

const ErrorComponent = () => {
const [ok, setOk] = useState(true)
Expand Down Expand Up @@ -407,6 +423,7 @@ describe("connectFactoryObservable", () => {
expect(sub1.closed).toBe(false)
sub1.unsubscribe()

let sub = getShared(0).subscribe()
const { result, unmount } = renderHook(() => useLatestNumber(0))
expect(result.current).toBe(5)
expect(nUpdates).toBe(4)
Expand All @@ -432,7 +449,7 @@ describe("connectFactoryObservable", () => {
sub3.unsubscribe()

unmount()
await wait(260)
sub.unsubscribe()

let latestValue4: number = 0
const sub4 = getShared(0).subscribe((x) => {
Expand Down
92 changes: 45 additions & 47 deletions packages/core/src/bind/connectObservable.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,15 @@ import {
Subject,
throwError,
Observable,
concat,
} from "rxjs"
import { delay, scan, startWith, map, switchMap } from "rxjs/operators"
import {
delay,
scan,
startWith,
map,
switchMap,
catchError,
} from "rxjs/operators"
import { bind, SUSPENSE } from "../"
import { TestErrorBoundary } from "../test-helpers/TestErrorBoundary"

Expand Down Expand Up @@ -51,7 +57,33 @@ describe("connectObservable", () => {

it("suspends the component when the observable hasn't emitted yet.", async () => {
const source$ = of(1).pipe(delay(100))
const [useDelayedNumber] = bind(source$)
const [useDelayedNumber, delayedNumber$] = bind(source$)
const sub = delayedNumber$.subscribe()
const Result: React.FC = () => <div>Result {useDelayedNumber()}</div>
const TestSuspense: React.FC = () => {
return (
<Suspense fallback={<span>Waiting</span>}>
<Result />
</Suspense>
)
}

render(<TestSuspense />)

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()
sub.unsubscribe()
})

it("suspends the component when the observable starts emitting suspense", async () => {
const source$ = of(1).pipe(delay(100), startWith(SUSPENSE))
const [useDelayedNumber, delayedNumber$] = bind(source$)
const sub = delayedNumber$.subscribe()
const Result: React.FC = () => <div>Result {useDelayedNumber()}</div>
const TestSuspense: React.FC = () => {
return (
Expand All @@ -70,6 +102,7 @@ describe("connectObservable", () => {

expect(screen.queryByText("Result 1")).not.toBeNull()
expect(screen.queryByText("Waiting")).toBeNull()
sub.unsubscribe()
})

it("updates with the last emitted value", async () => {
Expand Down Expand Up @@ -157,14 +190,15 @@ describe("connectObservable", () => {
expect(updates).toHaveBeenCalledTimes(2)
})

it("shares the source subscription until the refCount has stayed at zero for the grace-period", async () => {
it("shares the source subscription until there are no more subscribers", async () => {
let nInitCount = 0
const observable$ = defer(() => {
nInitCount += 1
return from([1, 2, 3, 4, 5])
})

const [useLatestNumber] = bind(observable$)
const [useLatestNumber, latestNumber$] = bind(observable$)
let subs = latestNumber$.subscribe()
const { unmount } = renderHook(() => useLatestNumber())
const { unmount: unmount2 } = renderHook(() => useLatestNumber())
const { unmount: unmount3 } = renderHook(() => useLatestNumber())
Expand All @@ -173,12 +207,12 @@ describe("connectObservable", () => {
unmount2()
unmount3()

await wait(230)
const { unmount: unmount4 } = renderHook(() => useLatestNumber())
expect(nInitCount).toBe(1)
unmount4()

await wait(270)
subs.unsubscribe()
subs = latestNumber$.subscribe()
renderHook(() => useLatestNumber())
expect(nInitCount).toBe(2)
})
Expand Down Expand Up @@ -352,7 +386,7 @@ describe("connectObservable", () => {

it("allows to retry the errored observable after a grace period of time", async () => {
let errStream = new Subject<string>()
const [useError] = bind(
const [useError, error$] = bind(
defer(() => {
return (errStream = new Subject<string>())
}),
Expand All @@ -364,6 +398,7 @@ describe("connectObservable", () => {
}

const errorCallback = jest.fn()
error$.pipe(catchError(() => [])).subscribe()
const { unmount } = render(
<TestErrorBoundary onError={errorCallback}>
<Suspense fallback={<div>Loading...</div>}>
Expand All @@ -390,8 +425,9 @@ describe("connectObservable", () => {

errorCallback.mockReset()
await componentAct(async () => {
await wait(250)
await wait(200)
})
error$.subscribe()

render(
<TestErrorBoundary onError={errorCallback}>
Expand Down Expand Up @@ -448,42 +484,4 @@ describe("connectObservable", () => {

expect(errorCallback).not.toHaveBeenCalled()
})

it("handles combined Suspended components that resolve at different times", async () => {
let nSideEffects = 0
const fast$ = defer(() => {
nSideEffects++
return of("fast")
}).pipe(delay(5))
const slow$ = defer(() => {
nSideEffects++
return of("slow")
}).pipe(delay(2500))

const [useFast] = bind(concat(of(SUSPENSE), fast$))
const [useSlow] = bind(concat(of(SUSPENSE), slow$))

const Fast: React.FC = () => <>{useFast()}</>
const Slow: React.FC = () => <>{useSlow()}</>

expect(nSideEffects).toBe(0)

render(
<Suspense fallback={<div>Loading...</div>}>
<Slow />
<Fast />
</Suspense>,
)

expect(screen.queryByText("Loading...")).not.toBeNull()

expect(nSideEffects).toBe(2)

await componentAct(async () => {
await wait(2600)
})

expect(screen.queryByText("Loading...")).toBeNull()
expect(nSideEffects).toBe(2)
})
})
37 changes: 5 additions & 32 deletions packages/core/src/internal/react-enhancer.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,21 @@
import { Observable, noop } from "rxjs"
import { Observable } from "rxjs"
import { SUSPENSE } from "../SUSPENSE"
import { BehaviorObservable, Action } from "./BehaviorObservable"
import { EMPTY_VALUE } from "./empty-value"

const reactEnhancer = <T>(source$: Observable<T>): BehaviorObservable<T> => {
let refCount = 0
let finalizeLastUnsubscription = noop

const result = new Observable<T>((subscriber) => {
refCount++
let isActive = true
let latestValue = EMPTY_VALUE
const subscription = source$.subscribe(
return source$.subscribe(
(value) => {
if (isActive && !Object.is(latestValue, value)) {
if (!Object.is(latestValue, value)) {
subscriber.next((latestValue = value))
}
},
(e) => {
subscriber.error(e)
},
)
finalizeLastUnsubscription()
return () => {
refCount--
if (refCount > 0 || subscription.closed) {
return subscription.unsubscribe()
}

isActive = false
const timeoutToken = setTimeout(() => {
finalizeLastUnsubscription()
}, 250)

finalizeLastUnsubscription = () => {
clearTimeout(timeoutToken)
subscription.unsubscribe()
finalizeLastUnsubscription = noop
}
}
}) as BehaviorObservable<T>

let promise: undefined | { type: Action.Suspense; payload: Promise<T | void> }
Expand Down Expand Up @@ -86,7 +63,7 @@ const reactEnhancer = <T>(source$: Observable<T>): BehaviorObservable<T> => {
res()
},
)
if (value !== EMPTY_VALUE || error !== EMPTY_VALUE) {
if (value !== EMPTY_VALUE) {
subscription.unsubscribe()
}
}).finally(() => {
Expand All @@ -98,11 +75,7 @@ const reactEnhancer = <T>(source$: Observable<T>): BehaviorObservable<T> => {
return value
}

if (error !== EMPTY_VALUE) {
return error
}

return promise
return error !== EMPTY_VALUE ? error : promise
}
}
result.getValue = getValue
Expand Down
5 changes: 4 additions & 1 deletion packages/dom/src/batchUpdates.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { Component, ErrorInfo, useEffect } from "react"
import { Observable, throwError, concat, Subject } from "rxjs"
import { mergeMapTo, take, filter } from "rxjs/operators"
import { mergeMapTo, take, filter, catchError } from "rxjs/operators"
import { bind, Subscribe } from "@react-rxjs/core"
import { batchUpdates } from "./"
import { act, render, screen } from "@testing-library/react"
Expand Down Expand Up @@ -149,6 +149,9 @@ describe("batchUpdates", () => {
test("batchUpdates doesn't get in the way of Error Boundaries", async () => {
const mockFn = jest.fn()
const errorCallback = jest.fn()
latestNumber$(true, true)
.pipe(catchError(() => []))
.subscribe()
render(
<TestErrorBoundary onError={errorCallback}>
<Father batched error onRender={mockFn} />
Expand Down

0 comments on commit d4ac471

Please sign in to comment.