mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-17 13:53:52 +02:00
add test
This commit is contained in:
70
src/shared/utils/__tests__/retry.test.js
Normal file
70
src/shared/utils/__tests__/retry.test.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import { executeWithBackoff } from '../retry';
|
||||
|
||||
describe('executeWithBackoff', () => {
|
||||
test('returns result on first success', async () => {
|
||||
const fn = vi.fn().mockResolvedValue('ok');
|
||||
const result = await executeWithBackoff(fn);
|
||||
expect(result).toBe('ok');
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('retries on failure then succeeds', async () => {
|
||||
const fn = vi
|
||||
.fn()
|
||||
.mockRejectedValueOnce(new Error('fail'))
|
||||
.mockRejectedValueOnce(new Error('fail'))
|
||||
.mockResolvedValue('ok');
|
||||
|
||||
const result = await executeWithBackoff(fn, {
|
||||
maxRetries: 3,
|
||||
baseDelay: 1
|
||||
});
|
||||
expect(result).toBe('ok');
|
||||
expect(fn).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
test('throws after exhausting retries', async () => {
|
||||
const fn = vi.fn().mockRejectedValue(new Error('always fails'));
|
||||
|
||||
await expect(
|
||||
executeWithBackoff(fn, { maxRetries: 2, baseDelay: 1 })
|
||||
).rejects.toThrow('always fails');
|
||||
expect(fn).toHaveBeenCalledTimes(3); // initial + 2 retries
|
||||
});
|
||||
|
||||
test('stops retrying when shouldRetry returns false', async () => {
|
||||
const fn = vi.fn().mockRejectedValue(new Error('permanent'));
|
||||
|
||||
await expect(
|
||||
executeWithBackoff(fn, {
|
||||
maxRetries: 5,
|
||||
baseDelay: 1,
|
||||
shouldRetry: () => false
|
||||
})
|
||||
).rejects.toThrow('permanent');
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('uses exponential backoff delays', async () => {
|
||||
const delays = [];
|
||||
const originalSetTimeout = globalThis.setTimeout;
|
||||
vi.spyOn(globalThis, 'setTimeout').mockImplementation((fn, delay) => {
|
||||
delays.push(delay);
|
||||
return originalSetTimeout(fn, 0);
|
||||
});
|
||||
|
||||
const mockFn = vi
|
||||
.fn()
|
||||
.mockRejectedValueOnce(new Error('1'))
|
||||
.mockRejectedValueOnce(new Error('2'))
|
||||
.mockRejectedValueOnce(new Error('3'))
|
||||
.mockResolvedValue('done');
|
||||
|
||||
await executeWithBackoff(mockFn, { maxRetries: 5, baseDelay: 100 });
|
||||
|
||||
// Delays: 100 * 2^0, 100 * 2^1, 100 * 2^2
|
||||
expect(delays).toEqual([100, 200, 400]);
|
||||
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
});
|
||||
23
src/shared/utils/__tests__/setting.test.js
Normal file
23
src/shared/utils/__tests__/setting.test.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import { getVRChatResolution } from '../setting';
|
||||
|
||||
describe('getVRChatResolution', () => {
|
||||
test.each([
|
||||
['1280x720', '1280x720 (720p)'],
|
||||
['1920x1080', '1920x1080 (1080p)'],
|
||||
['2560x1440', '2560x1440 (1440p)'],
|
||||
['3840x2160', '3840x2160 (4K)'],
|
||||
['7680x4320', '7680x4320 (8K)']
|
||||
])('maps %s to %s', (input, expected) => {
|
||||
expect(getVRChatResolution(input)).toBe(expected);
|
||||
});
|
||||
|
||||
test('returns Custom for unknown resolutions', () => {
|
||||
expect(getVRChatResolution('1024x768')).toBe('1024x768 (Custom)');
|
||||
expect(getVRChatResolution('800x600')).toBe('800x600 (Custom)');
|
||||
});
|
||||
|
||||
test('handles empty/undefined input', () => {
|
||||
expect(getVRChatResolution('')).toBe(' (Custom)');
|
||||
expect(getVRChatResolution(undefined)).toBe('undefined (Custom)');
|
||||
});
|
||||
});
|
||||
56
src/shared/utils/__tests__/throttle.test.js
Normal file
56
src/shared/utils/__tests__/throttle.test.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import { createRateLimiter } from '../throttle';
|
||||
|
||||
describe('createRateLimiter', () => {
|
||||
test('schedule executes function and returns result', async () => {
|
||||
const limiter = createRateLimiter({
|
||||
limitPerInterval: 10,
|
||||
intervalMs: 1000
|
||||
});
|
||||
const result = await limiter.schedule(() => 42);
|
||||
expect(result).toBe(42);
|
||||
});
|
||||
|
||||
test('schedule executes async functions', async () => {
|
||||
const limiter = createRateLimiter({
|
||||
limitPerInterval: 10,
|
||||
intervalMs: 1000
|
||||
});
|
||||
const result = await limiter.schedule(
|
||||
() => new Promise((r) => setTimeout(() => r('async'), 10))
|
||||
);
|
||||
expect(result).toBe('async');
|
||||
});
|
||||
|
||||
test('allows bursts up to limit', async () => {
|
||||
const limiter = createRateLimiter({
|
||||
limitPerInterval: 3,
|
||||
intervalMs: 1000
|
||||
});
|
||||
const results = [];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
results.push(await limiter.schedule(() => i));
|
||||
}
|
||||
expect(results).toEqual([0, 1, 2]);
|
||||
});
|
||||
|
||||
test('clear resets the rate limiter', async () => {
|
||||
const limiter = createRateLimiter({
|
||||
limitPerInterval: 1,
|
||||
intervalMs: 50
|
||||
});
|
||||
|
||||
await limiter.schedule(() => 'first');
|
||||
limiter.clear();
|
||||
// After clear, should be able to schedule immediately
|
||||
const result = await limiter.schedule(() => 'second');
|
||||
expect(result).toBe('second');
|
||||
});
|
||||
|
||||
test('wait resolves without executing a function', async () => {
|
||||
const limiter = createRateLimiter({
|
||||
limitPerInterval: 10,
|
||||
intervalMs: 1000
|
||||
});
|
||||
await expect(limiter.wait()).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user