mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-05-04 22:06:06 +02:00
add test
This commit is contained in:
@@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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)');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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