Files
EasyCLIProxyAPI/tests/authFileQuotaPanel.test.tsx
T
lzt404 1532bdb056 feat(quota): display all Codex reset credit expiry times
- List each expiry chronologically instead of showing only the earliest
- Update auth file panels, quota cards, and reset confirmation dialogs
- Preserve separate entries for credits with identical expiry times
- Add translations and regression coverage
2026-10-05 00:45:17 +08:00

81 lines
4.8 KiB
TypeScript

import { expect, it } from 'bun:test';
import { renderToStaticMarkup } from 'react-dom/server';
import { AuthFileQuotaPanel } from '../src/components/AuthFileQuotaPanel';
import { I18nProvider } from '../src/i18n';
import type { QuotaState } from '../src/services/quotaService';
import { QuotaCard } from '../src/pages/QuotaPage';
const render = (quota: QuotaState) => renderToStaticMarkup(<I18nProvider><AuthFileQuotaPanel file={{ name: 'test.json' }} quota={quota} disabled={false} onRefresh={() => {}} /></I18nProvider>);
it('shows every reset expiry in chronological order, including duplicates, on both surfaces', () => {
const quota: QuotaState = {
status: 'success', rows: [], resetCredits: 3,
resetCreditExpiries: ['2030-12-20T00:00:00Z', '2030-10-10T00:00:00Z', '2030-10-10T00:00:00Z'],
};
const file = { name: 'codex.json', provider: 'codex' };
for (const content of [
<AuthFileQuotaPanel file={file} quota={quota} disabled={false} compact dense onRefresh={() => {}} />,
<QuotaCard file={file} quota={quota} onRefresh={() => {}} />,
]) {
const html = renderToStaticMarkup(<I18nProvider>{content}</I18nProvider>);
expect(html.match(/<li>/g)).toHaveLength(3);
expect(html).toContain('Reset 3 expires');
expect(html).not.toContain('Earliest expiry');
expect(html.indexOf('10/10/2030')).toBeLessThan(html.indexOf('12/20/2030'));
}
});
it('renders real quota values with bounded bars and keeps unknown distinct from zero', () => {
const html = render({ status: 'success', rows: [
{ label: 'empty', remainingPercent: 0 }, { label: 'full', remainingPercent: 100 },
{ label: 'too high', remainingPercent: 140 }, { label: 'too low', remainingPercent: -20 },
{ label: 'unknown', remainingPercent: null }, { label: 'invalid', remainingPercent: NaN },
] });
expect(html.match(/role="progressbar"/g)).toHaveLength(4);
expect(html.match(/aria-valuenow="0"/g)).toHaveLength(2);
expect(html.match(/aria-valuenow="100"/g)).toHaveLength(2);
expect(html.match(/credential-quota-row unknown/g)).toHaveLength(2);
expect(html).not.toContain('NaN');
expect(html).not.toContain('140%');
});
it('does not fabricate progress percentages while loading or on failure', () => {
const loading = render({ status: 'loading', rows: [] });
expect(loading).toContain('indeterminate');
expect(loading).not.toContain('aria-valuenow');
const error = render({ status: 'error', rows: [], error: 'upstream failed' });
expect(error).toContain('upstream failed');
expect(error).not.toContain('role="progressbar"');
});
it.each([
{ credits: undefined, visible: false, disabled: false },
{ credits: 0, visible: false, disabled: false },
{ credits: 2, visible: true, disabled: false },
{ credits: 2, applicable: 0, remaining: 100, visible: true, disabled: false },
{ credits: 2, loading: true, visible: true, disabled: true },
{ credits: 2, fileDisabled: true, visible: true, disabled: true },
{ credits: 2, provider: 'claude', visible: false, disabled: false },
{ credits: 2, result: 'error', visible: true, disabled: false },
{ credits: 2, result: 'refresh-error', visible: true, disabled: false },
] as const)('matches quota-page reset visibility and availability: %j', (scenario) => {
const values = scenario as { credits?: number; applicable?: number; remaining?: number; loading?: boolean; fileDisabled?: boolean; provider?: string; result?: 'error' | 'refresh-error'; visible: boolean; disabled: boolean };
const file = { name: 'test.json', provider: values.provider ?? 'codex', disabled: values.fileDisabled ?? false };
const quota: QuotaState = {
status: values.loading ? 'loading' : 'success', rows: [{ label: '5h', remainingPercent: values.remaining ?? 0 }],
resetCredits: values.credits, resetCreditsApplicable: values.applicable,
pendingAction: values.loading ? 'reset' : undefined,
actionResult: values.result ? { action: 'reset', status: values.result, error: 'test error' } : undefined,
};
const onReset = file.provider === 'codex' ? () => {} : undefined;
const credentialHtml = renderToStaticMarkup(<I18nProvider><AuthFileQuotaPanel file={file} quota={quota} disabled={file.disabled} onRefresh={() => {}} onReset={onReset} /></I18nProvider>);
const quotaHtml = renderToStaticMarkup(<I18nProvider><QuotaCard file={file} quota={quota} onRefresh={() => {}} onReset={onReset} /></I18nProvider>);
const buttonState = (html: string) => {
const button = html.match(/<button[^>]*title="Reset Quota"[^>]*>Reset Quota<\/button>/)?.[0];
return { visible: Boolean(button), disabled: Boolean(button?.includes('disabled=""')) };
};
expect(buttonState(credentialHtml)).toEqual({ visible: values.visible, disabled: values.disabled });
expect(buttonState(credentialHtml)).toEqual(buttonState(quotaHtml));
if (values.loading) expect(credentialHtml).toContain('Submitting reset and refreshing quota');
});