test(web): drop the double cast from the CRLF SSE stream mocks
Both new #2006 regressions faked the events response with an object cast `as unknown as Response`, which AGENTS.md rejects — it erases the check that the mock actually satisfies the API the hook consumes. Each now builds a real `ReadableStream` inside a real `Response`. `pull` runs once per chunk the hook's reader consumes, so awaiting a promise inside it gates delivery exactly the way the hand-rolled reader did, while the mock stays type-checked against the Response contract. The chunk counter replaces the read counter as the "the reader has consumed the previous chunk" signal. Both still fail against the pre-fix parser, verified. Raised by Copilot on PR #2036. Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
07a7b0beb5
commit
7a29a21618
1 changed files with 33 additions and 39 deletions
|
|
@ -987,7 +987,6 @@ describe("useServers", () => {
|
||||||
const framed = new Promise<void>((r) => {
|
const framed = new Promise<void>((r) => {
|
||||||
releaseFrame = r;
|
releaseFrame = r;
|
||||||
});
|
});
|
||||||
let reads = 0;
|
|
||||||
const encoder = new TextEncoder();
|
const encoder = new TextEncoder();
|
||||||
// Referentially stable: an inline arrow re-subscribes on every render, and
|
// Referentially stable: an inline arrow re-subscribes on every render, and
|
||||||
// the re-subscription's own mount refresh would pick the edit up on its
|
// the re-subscription's own mount refresh would pick the edit up on its
|
||||||
|
|
@ -995,22 +994,18 @@ describe("useServers", () => {
|
||||||
const fetchFn: typeof fetch = async (input, init) => {
|
const fetchFn: typeof fetch = async (input, init) => {
|
||||||
const url = input instanceof Request ? input.url : String(input);
|
const url = input instanceof Request ? input.url : String(input);
|
||||||
if (url.endsWith("/api/servers/events")) {
|
if (url.endsWith("/api/servers/events")) {
|
||||||
const body = {
|
// A real ReadableStream in a real Response — `pull` is only invoked
|
||||||
getReader: () => ({
|
// when the hook's reader asks for a chunk, so awaiting inside it
|
||||||
read: async () => {
|
// gates delivery exactly the way a hand-rolled reader double would,
|
||||||
reads += 1;
|
// while keeping the mock type-checked against the Response API.
|
||||||
if (reads === 1) {
|
const stream = new ReadableStream<Uint8Array>({
|
||||||
await framed;
|
async pull(controller) {
|
||||||
return {
|
await framed;
|
||||||
done: false,
|
controller.enqueue(encoder.encode("event: change\r\n\r\n"));
|
||||||
value: encoder.encode("event: change\r\n\r\n"),
|
controller.close();
|
||||||
};
|
},
|
||||||
}
|
});
|
||||||
return { done: true, value: undefined };
|
return new Response(stream, { status: 200 });
|
||||||
},
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
return { ok: true, body } as unknown as Response;
|
|
||||||
}
|
}
|
||||||
return h.fetchFn(url, init);
|
return h.fetchFn(url, init);
|
||||||
};
|
};
|
||||||
|
|
@ -1057,31 +1052,30 @@ describe("useServers", () => {
|
||||||
releaseSecondHalf = r;
|
releaseSecondHalf = r;
|
||||||
});
|
});
|
||||||
let listGets = 0;
|
let listGets = 0;
|
||||||
let reads = 0;
|
let chunks = 0;
|
||||||
const encoder = new TextEncoder();
|
const encoder = new TextEncoder();
|
||||||
const fetchFn: typeof fetch = async (input, init) => {
|
const fetchFn: typeof fetch = async (input, init) => {
|
||||||
const url = input instanceof Request ? input.url : String(input);
|
const url = input instanceof Request ? input.url : String(input);
|
||||||
if (url.endsWith("/api/servers/events")) {
|
if (url.endsWith("/api/servers/events")) {
|
||||||
const body = {
|
// `pull` runs once per chunk the hook's reader consumes, so `chunks`
|
||||||
getReader: () => ({
|
// reaching 2 proves the half-separator chunk was read and parsed.
|
||||||
read: async () => {
|
const stream = new ReadableStream<Uint8Array>({
|
||||||
reads += 1;
|
async pull(controller) {
|
||||||
if (reads === 1) {
|
chunks += 1;
|
||||||
await firstHalf;
|
if (chunks === 1) {
|
||||||
return {
|
await firstHalf;
|
||||||
done: false,
|
controller.enqueue(encoder.encode("event: change\r\n\r"));
|
||||||
value: encoder.encode("event: change\r\n\r"),
|
return;
|
||||||
};
|
}
|
||||||
}
|
if (chunks === 2) {
|
||||||
if (reads === 2) {
|
await secondHalf;
|
||||||
await secondHalf;
|
controller.enqueue(encoder.encode("\n"));
|
||||||
return { done: false, value: encoder.encode("\n") };
|
return;
|
||||||
}
|
}
|
||||||
return { done: true, value: undefined };
|
controller.close();
|
||||||
},
|
},
|
||||||
}),
|
});
|
||||||
};
|
return new Response(stream, { status: 200 });
|
||||||
return { ok: true, body } as unknown as Response;
|
|
||||||
}
|
}
|
||||||
if (url.endsWith("/api/servers")) listGets += 1;
|
if (url.endsWith("/api/servers")) listGets += 1;
|
||||||
return h.fetchFn(url, init);
|
return h.fetchFn(url, init);
|
||||||
|
|
@ -1094,7 +1088,7 @@ describe("useServers", () => {
|
||||||
|
|
||||||
// Half a separator is not a frame: no refresh yet.
|
// Half a separator is not a frame: no refresh yet.
|
||||||
releaseFirstHalf?.();
|
releaseFirstHalf?.();
|
||||||
await waitFor(() => expect(reads).toBeGreaterThanOrEqual(2));
|
await waitFor(() => expect(chunks).toBeGreaterThanOrEqual(2));
|
||||||
await new Promise((r) => setTimeout(r, 100));
|
await new Promise((r) => setTimeout(r, 100));
|
||||||
expect(listGets).toBe(1);
|
expect(listGets).toBe(1);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue