Rules Hub
Coding Rules Library
Rule priority, scope & exceptions
Use this to align rules with the senior-level structure (P0/P1/P2, scope, exceptions/tradeoffs).
Test SSE by streaming events through an MSW ReadableStream controller
Mock the SSE endpoint with MSW returning an open ReadableStream, capture its controller, and enqueue 'data:' frames mid-test to drive a real EventSource.
Bad example
| 1 | // Replacing EventSource with a hand-rolled fake bypasses the real parsing, |
| 2 | // reconnection and error paths — you end up testing the mock, not the code. |
| 3 | class FakeEventSource { |
| 4 | onmessage: ((e: { data: string }) => void) | null = null; |
| 5 | emit(data: unknown) { this.onmessage?.({ data: JSON.stringify(data) }); } |
| 6 | } |
| 7 | vi.stubGlobal('EventSource', FakeEventSource); |
| 8 | // No coverage of how the browser frames/parses 'data:' lines, onopen, onerror. |
Explanation (EN)
Stubbing EventSource with a fake object means the real EventSource wire-format parsing, onopen/onerror lifecycle, and connection teardown are never exercised. The test passes against your mock's behaviour rather than the browser's, so frame-formatting or lifecycle bugs slip through.
Objašnjenje (HR)
Zamjena EventSourcea lažnim objektom znači da se stvarno parsiranje žičanog formata EventSourcea, onopen/onerror životni ciklus i prekid veze nikad ne izvršavaju. Test prolazi prema ponašanju tvog mocka, a ne preglednika, pa bugovi u formatiranju okvira ili životnom ciklusu prođu neprimijećeno.
Good example
| 1 | let sseController: ReadableStreamDefaultController<Uint8Array> | null = null; |
| 2 | const encoder = new TextEncoder(); |
| 3 |
|
| 4 | const worker = setupWorker( |
| 5 | http.get('/events/stream', () => { |
| 6 | const stream = new ReadableStream<Uint8Array>({ start(c) { sseController = c; } }); |
| 7 | return new HttpResponse(stream, { |
| 8 | headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache' }, |
| 9 | }); |
| 10 | }) |
| 11 | ); |
| 12 |
|
| 13 | const pushSseMessage = (payload: unknown) => |
| 14 | sseController?.enqueue(encoder.encode(`data: ${JSON.stringify(payload)}\n\n`)); |
| 15 |
|
| 16 | test('prepends a pushed event', async () => { |
| 17 | await render(<Component />); |
| 18 | await expect.poll(() => sseController).not.toBeNull(); // wait for connection |
| 19 | pushSseMessage({ id: 'x', title: 'Breaking' }); |
| 20 | await expect.element(getByTestId('item-x')).toBeInTheDocument(); |
| 21 | }); |
Explanation (EN)
Returning a real, open ReadableStream from an MSW handler lets the genuine EventSource connect, and capturing the stream controller lets the test enqueue properly-framed 'data:' events at any moment. The component runs through its real parsing and state-update path, and onopen/onerror can be exercised by closing or erroring the response.
Objašnjenje (HR)
Vraćanje stvarnog, otvorenog ReadableStreama iz MSW handlera dopušta da se pravi EventSource spoji, a hvatanje kontrolera streama omogućuje testu da u bilo kojem trenutku ubaci ispravno uokvirene 'data:' događaje. Komponenta prolazi kroz svoj stvarni put parsiranja i ažuriranja stanja, a onopen/onerror se mogu testirati zatvaranjem ili greškom u odgovoru.
Notes (EN)
Pair with expect.poll to wait for the connection before pushing. To test the error/fallback path, override the same handler to return status 503.
Bilješke (HR)
Kombiniraj s expect.poll da pričekaš vezu prije slanja. Za testiranje error/fallback puta, prebriši isti handler da vrati status 503.