-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
282 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import type { Observable } from "../../utilities/index.js"; | ||
|
||
interface PeekOptions { | ||
timeout?: number; | ||
} | ||
type ObservableEvent<T> = | ||
| { type: "next"; value: T } | ||
| { type: "error"; error: any } | ||
| { type: "complete" }; | ||
|
||
async function* observableToAsyncEventIterator<T>(observable: Observable<T>) { | ||
let resolveNext: undefined | ((value: ObservableEvent<T>) => void); | ||
const promises: Promise<ObservableEvent<T>>[] = []; | ||
pushPromise(); | ||
|
||
function pushPromise() { | ||
promises.push( | ||
new Promise<ObservableEvent<T>>((resolve) => { | ||
resolveNext = resolve; | ||
}) | ||
); | ||
} | ||
|
||
function onValue(value: ObservableEvent<T>) { | ||
resolveNext!(value); | ||
pushPromise(); | ||
} | ||
observable.subscribe( | ||
(value) => onValue({ type: "next", value }), | ||
(error) => onValue({ type: "error", error }), | ||
() => onValue({ type: "complete" }) | ||
); | ||
|
||
while (true) { | ||
yield promises.shift()!; | ||
} | ||
} | ||
|
||
class IteratorTaker<T> { | ||
constructor(private iterator: AsyncGenerator<T, void, unknown>) {} | ||
|
||
async take({ timeout = 100 }: PeekOptions = {}): Promise<T> { | ||
return Promise.race([ | ||
this.iterator.next().then((result) => result.value!), | ||
new Promise<T>((_, reject) => { | ||
setTimeout( | ||
reject, | ||
timeout, | ||
new Error("Timeout waiting for next event") | ||
); | ||
}), | ||
]); | ||
} | ||
} | ||
|
||
export class ObservableTaker<T> extends IteratorTaker<ObservableEvent<T>> { | ||
constructor(observable: Observable<T>) { | ||
super(observableToAsyncEventIterator(observable)); | ||
} | ||
|
||
async takeNext(options?: PeekOptions): Promise<T> { | ||
const event = await this.take(options); | ||
expect(event).toEqual({ type: "next", value: expect.anything() }); | ||
return (event as ObservableEvent<T> & { type: "next" }).value; | ||
} | ||
|
||
async takeError(options?: PeekOptions): Promise<any> { | ||
const event = await this.take(options); | ||
expect(event).toEqual({ type: "error", error: expect.anything() }); | ||
return (event as ObservableEvent<T> & { type: "error" }).error; | ||
} | ||
|
||
async takeComplete(options?: PeekOptions): Promise<void> { | ||
const event = await this.take(options); | ||
expect(event).toEqual({ type: "complete" }); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import { Observable } from "../../../utilities"; | ||
import { ObservableTaker } from "../ObservableTaker"; | ||
|
||
it("allows to step through an observable until completion", async () => { | ||
const taker = new ObservableTaker( | ||
new Observable<number>((observer) => { | ||
observer.next(1); | ||
observer.next(2); | ||
observer.next(3); | ||
observer.complete(); | ||
}) | ||
); | ||
await expect(taker.takeNext()).resolves.toBe(1); | ||
await expect(taker.takeNext()).resolves.toBe(2); | ||
await expect(taker.takeNext()).resolves.toBe(3); | ||
await expect(taker.takeComplete()).resolves.toBeUndefined(); | ||
}); | ||
|
||
it("allows to step through an observable until error", async () => { | ||
const taker = new ObservableTaker( | ||
new Observable<number>((observer) => { | ||
observer.next(1); | ||
observer.next(2); | ||
observer.next(3); | ||
observer.error(new Error("expected")); | ||
}) | ||
); | ||
await expect(taker.takeNext()).resolves.toBe(1); | ||
await expect(taker.takeNext()).resolves.toBe(2); | ||
await expect(taker.takeNext()).resolves.toBe(3); | ||
await expect(taker.takeError()).resolves.toEqual(expect.any(Error)); | ||
}); | ||
|
||
it("will time out if no more value is omitted", async () => { | ||
const taker = new ObservableTaker( | ||
new Observable<number>((observer) => { | ||
observer.next(1); | ||
observer.next(2); | ||
}) | ||
); | ||
await expect(taker.takeNext()).resolves.toBe(1); | ||
await expect(taker.takeNext()).resolves.toBe(2); | ||
await expect(taker.takeNext()).rejects.toEqual(expect.any(Error)); | ||
}); | ||
|
||
it.each([ | ||
["takeNext", "complete"], | ||
["takeNext", "error"], | ||
["takeError", "complete"], | ||
["takeError", "next"], | ||
["takeComplete", "next"], | ||
["takeComplete", "error"], | ||
])("errors when %s receives %s instead", async (expected, gotten) => { | ||
const taker = new ObservableTaker( | ||
new Observable<number>((observer) => { | ||
observer.next(1); | ||
observer.next(2); | ||
// @ts-ignore | ||
observer[gotten](3); | ||
}) | ||
); | ||
await expect(taker.takeNext()).resolves.toBe(1); | ||
await expect(taker.takeNext()).resolves.toBe(2); | ||
// @ts-ignore | ||
await expect(taker[expected]()).rejects.toEqual(expect.any(Error)); | ||
}); | ||
|
||
it.each([ | ||
["takeNext", "next"], | ||
["takeError", "error"], | ||
["takeComplete", "complete"], | ||
])("succeeds when %s, receives %s", async (expected, gotten) => { | ||
const taker = new ObservableTaker( | ||
new Observable<number>((observer) => { | ||
observer.next(1); | ||
observer.next(2); | ||
// @ts-ignore | ||
observer[gotten](3); | ||
}) | ||
); | ||
await expect(taker.takeNext()).resolves.toBe(1); | ||
await expect(taker.takeNext()).resolves.toBe(2); | ||
// @ts-ignore this should just not throw | ||
await taker[expected](); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
export * from "./profile/index.js"; | ||
export * from "./disposables/index.js"; | ||
export { ObservableTaker } from "./ObservableTaker.js"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters