generated from storybookjs/addon-kit
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into norbert/cpc
- Loading branch information
Showing
11 changed files
with
299 additions
and
73 deletions.
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
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
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
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
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
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,96 @@ | ||
import { beforeEach, describe, expect, it, vi } from "vitest"; | ||
|
||
import { FETCH_ABORTED, FETCH_REQUEST, FETCH_RESPONSE } from "../constants"; | ||
import { ChannelFetch } from "./ChannelFetch"; | ||
import { MockChannel } from "./MockChannel"; | ||
|
||
const resolveAfter = (ms: number, value: any) => | ||
new Promise((resolve) => setTimeout(resolve, ms, value)); | ||
|
||
const rejectAfter = (ms: number, reason: any) => | ||
new Promise((_, reject) => setTimeout(reject, ms, reason)); | ||
|
||
describe("ChannelFetch", () => { | ||
let channel: MockChannel; | ||
|
||
beforeEach(() => { | ||
channel = new MockChannel(); | ||
}); | ||
|
||
it("should handle fetch requests", async () => { | ||
const fetch = vi.fn(() => resolveAfter(100, { headers: [], text: async () => "data" })); | ||
ChannelFetch.subscribe("req", channel, fetch as any); | ||
|
||
channel.emit(FETCH_REQUEST, { | ||
requestId: "req", | ||
input: "https://example.com", | ||
init: { headers: { foo: "bar" } }, | ||
}); | ||
|
||
await vi.waitFor(() => { | ||
expect(fetch).toHaveBeenCalledWith("https://example.com", { | ||
headers: { foo: "bar" }, | ||
signal: expect.any(AbortSignal), | ||
}); | ||
}); | ||
}); | ||
|
||
it("should send fetch responses", async () => { | ||
const fetch = vi.fn(() => resolveAfter(100, { headers: [], text: async () => "data" })); | ||
const instance = ChannelFetch.subscribe("res", channel, fetch as any); | ||
|
||
const promise = new Promise<void>((resolve) => { | ||
channel.on(FETCH_RESPONSE, ({ response, error }) => { | ||
expect(response.body).toBe("data"); | ||
expect(error).toBeUndefined(); | ||
resolve(); | ||
}); | ||
}); | ||
|
||
channel.emit(FETCH_REQUEST, { requestId: "res", input: "https://example.com" }); | ||
await vi.waitFor(() => { | ||
expect(instance.abortControllers.size).toBe(1); | ||
}); | ||
|
||
await promise; | ||
|
||
expect(instance.abortControllers.size).toBe(0); | ||
}); | ||
|
||
it("should send fetch error responses", async () => { | ||
const fetch = vi.fn(() => rejectAfter(100, new Error("oops"))); | ||
const instance = ChannelFetch.subscribe("err", channel, fetch as any); | ||
|
||
const promise = new Promise<void>((resolve) => { | ||
channel.on(FETCH_RESPONSE, ({ response, error }) => { | ||
expect(response).toBeUndefined(); | ||
expect(error).toMatch(/oops/); | ||
resolve(); | ||
}); | ||
}); | ||
|
||
channel.emit(FETCH_REQUEST, { requestId: "err", input: "https://example.com" }); | ||
await vi.waitFor(() => { | ||
expect(instance.abortControllers.size).toBe(1); | ||
}); | ||
|
||
await promise; | ||
expect(instance.abortControllers.size).toBe(0); | ||
}); | ||
|
||
it("should abort fetch requests", async () => { | ||
const fetch = vi.fn((input, init) => new Promise<Response>(() => {})); | ||
const instance = ChannelFetch.subscribe("abort", channel, fetch); | ||
|
||
channel.emit(FETCH_REQUEST, { requestId: "abort", input: "https://example.com" }); | ||
await vi.waitFor(() => { | ||
expect(instance.abortControllers.size).toBe(1); | ||
}); | ||
|
||
channel.emit(FETCH_ABORTED, { requestId: "abort" }); | ||
await vi.waitFor(() => { | ||
expect(fetch.mock.lastCall?.[1].signal.aborted).toBe(true); | ||
expect(instance.abortControllers.size).toBe(0); | ||
}); | ||
}); | ||
}); |
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,47 @@ | ||
import type { Channel } from "@storybook/channels"; | ||
|
||
import { FETCH_ABORTED, FETCH_REQUEST, FETCH_RESPONSE } from "../constants"; | ||
|
||
type ChannelLike = Pick<Channel, "emit" | "on" | "off">; | ||
|
||
const instances = new Map<string, ChannelFetch>(); | ||
|
||
export class ChannelFetch { | ||
channel: ChannelLike; | ||
|
||
abortControllers: Map<string, AbortController>; | ||
|
||
constructor(channel: ChannelLike, _fetch = fetch) { | ||
this.channel = channel; | ||
this.abortControllers = new Map<string, AbortController>(); | ||
|
||
this.channel.on(FETCH_ABORTED, ({ requestId }) => { | ||
this.abortControllers.get(requestId)?.abort(); | ||
this.abortControllers.delete(requestId); | ||
}); | ||
|
||
this.channel.on(FETCH_REQUEST, async ({ requestId, input, init }) => { | ||
const controller = new AbortController(); | ||
this.abortControllers.set(requestId, controller); | ||
|
||
try { | ||
const res = await _fetch(input as RequestInfo, { ...init, signal: controller.signal }); | ||
const body = await res.text(); | ||
const headers = Array.from(res.headers as any); | ||
const response = { body, headers, status: res.status, statusText: res.statusText }; | ||
this.channel.emit(FETCH_RESPONSE, { requestId, response }); | ||
} catch (err) { | ||
const error = err instanceof Error ? err.message : String(err); | ||
this.channel.emit(FETCH_RESPONSE, { requestId, error }); | ||
} finally { | ||
this.abortControllers.delete(requestId); | ||
} | ||
}); | ||
} | ||
|
||
static subscribe(key: string, channel: ChannelLike, _fetch = fetch) { | ||
const instance = instances.get(key) || new ChannelFetch(channel, _fetch); | ||
if (!instances.has(key)) instances.set(key, instance); | ||
return instance; | ||
} | ||
} |
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,16 @@ | ||
export class MockChannel { | ||
private listeners: Record<string, ((...args: any[]) => void)[]> = {}; | ||
|
||
on(event: string, listener: (...args: any[]) => void) { | ||
this.listeners[event] = [...(this.listeners[event] ?? []), listener]; | ||
} | ||
|
||
off(event: string, listener: (...args: any[]) => void) { | ||
this.listeners[event] = (this.listeners[event] ?? []).filter((l) => l !== listener); | ||
} | ||
|
||
emit(event: string, ...args: any[]) { | ||
// setTimeout is used to simulate the asynchronous nature of the real channel | ||
(this.listeners[event] || []).forEach((listener) => setTimeout(() => listener(...args))); | ||
} | ||
} |
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
Oops, something went wrong.