A modern vue admin. It is based on Vue3, vite and TypeScript. It's fast!
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

142 lines
3.7 KiB

import type { RequestClient } from '../request-client';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { SSE } from './sse';
// 模拟 TextDecoder
const OriginalTextDecoder = globalThis.TextDecoder;
beforeEach(() => {
vi.stubGlobal(
'TextDecoder',
class {
private decoder = new OriginalTextDecoder();
decode(value: Uint8Array, opts?: any) {
return this.decoder.decode(value, opts);
}
},
);
});
// 创建 fetch mock
const createFetchMock = (chunks: string[], ok = true) => {
const encoder = new TextEncoder();
let index = 0;
return vi.fn().mockResolvedValue({
ok,
status: ok ? 200 : 500,
body: {
getReader: () => ({
read: async () => {
if (index < chunks.length) {
return { done: false, value: encoder.encode(chunks[index++]) };
}
return { done: true, value: undefined };
},
}),
},
});
};
describe('sSE', () => {
let client: RequestClient;
let sse: SSE;
beforeEach(() => {
vi.restoreAllMocks();
client = {
getBaseUrl: () => 'http://localhost',
instance: {
interceptors: {
request: {
handlers: [],
},
},
},
} as unknown as RequestClient;
sse = new SSE(client);
});
it('should call requestSSE when postSSE is used', async () => {
const spy = vi.spyOn(sse, 'requestSSE').mockResolvedValue(undefined);
await sse.postSSE('/test', { foo: 'bar' }, { headers: { a: '1' } });
expect(spy).toHaveBeenCalledWith(
'/test',
{ foo: 'bar' },
{
headers: { a: '1' },
method: 'POST',
},
);
});
it('should throw error if fetch response not ok', async () => {
vi.stubGlobal('fetch', createFetchMock([], false));
await expect(sse.requestSSE('/bad')).rejects.toThrow(
'HTTP error! status: 500',
);
});
it('should trigger onMessage and onEnd callbacks', async () => {
const messages: string[] = [];
const onMessage = vi.fn((msg: string) => messages.push(msg));
const onEnd = vi.fn();
vi.stubGlobal('fetch', createFetchMock(['hello', ' world']));
await sse.requestSSE('/sse', undefined, { onMessage, onEnd });
expect(onMessage).toHaveBeenCalledTimes(2);
expect(messages.join('')).toBe('hello world');
// onEnd 不再带参数
expect(onEnd).toHaveBeenCalled();
});
it('should apply request interceptors', async () => {
const interceptor = vi.fn(async (config) => {
config.headers['x-test'] = 'intercepted';
return config;
});
(client.instance.interceptors.request as any).handlers.push({
fulfilled: interceptor,
});
// 创建 fetch mock,并挂到全局
const fetchMock = createFetchMock(['data']);
vi.stubGlobal('fetch', fetchMock);
await sse.requestSSE('/sse', undefined, {});
expect(interceptor).toHaveBeenCalled();
expect(fetchMock).toHaveBeenCalledWith(
'http://localhost/sse',
expect.objectContaining({
headers: expect.any(Headers),
}),
);
const calls = fetchMock.mock?.calls;
expect(calls).toBeDefined();
expect(calls?.length).toBeGreaterThan(0);
const init = calls?.[0]?.[1] as RequestInit;
expect(init).toBeDefined();
const headers = init?.headers as Headers;
expect(headers?.get('x-test')).toBe('intercepted');
expect(headers?.get('accept')).toBe('text/event-stream');
});
it('should throw error when no reader', async () => {
vi.stubGlobal(
'fetch',
vi.fn().mockResolvedValue({
ok: true,
status: 200,
body: null,
}),
);
await expect(sse.requestSSE('/sse')).rejects.toThrow('No reader');
});
});