This commit is contained in:
pa
2026-03-04 22:12:09 +09:00
parent db80d5e77d
commit 4df94478ba
3 changed files with 149 additions and 0 deletions

View 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();
});
});

View 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)');
});
});

View 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();
});
});