fix(12): satisfy CI fast-checks — lint unused vars, typed contract-test body, prettier
CI / changes (pull_request) Successful in 3s
CI / fast-checks (pull_request) Successful in 2m16s
CI / api (pull_request) Failing after 1m37s
CI / harness (pull_request) Failing after 1h3m45s
CI / security (pull_request) Failing after 11s
CI / gate (pull_request) Failing after 1s

- Remove unused 'res'/'container' assignments (no-unused-vars)
- setupClient.contract.test.ts: typed parseSentBody helper + non-async json mock
  (no-unsafe-*/require-await)
- Prettier format 7 setup files

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lucas Berger
2026-06-16 16:53:14 -04:00
co-authored by Claude Opus 4.8
parent f485b38324
commit a193bc8236
7 changed files with 172 additions and 132 deletions
+34 -13
View File
@@ -51,7 +51,10 @@ function makeQueryClient() {
});
}
function renderSetupPage(queryClient: QueryClient, props: React.ComponentProps<typeof SetupPage> = {}) {
function renderSetupPage(
queryClient: QueryClient,
props: React.ComponentProps<typeof SetupPage> = {},
) {
return render(
<QueryClientProvider client={queryClient}>
<MemoryRouter>
@@ -149,7 +152,7 @@ describe('SetupPage — Welcome step', () => {
});
it('has aria-live region for validation status', async () => {
const { container } = renderSetupPage(queryClient);
renderSetupPage(queryClient);
// The step 2 config form has aria-live; step 1 welcome step does not yet show validation
// but the structure has it via the general error block. We advance to step 2 to test.
// For now we verify that once rendered, the component tree has the correct aria-live
@@ -239,15 +242,20 @@ describe('SetupPage — Step 2 VAPID validation (CR-01 gap)', () => {
const { postSetupConfig } = await import('../api/client.js');
(postSetupConfig as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
fireEvent.change(screen.getByLabelText('App URL'), { target: { value: 'https://app.example.com' } });
fireEvent.change(screen.getByLabelText('OIDC issuer URL'), { target: { value: 'https://auth.example.com' } });
fireEvent.change(screen.getByLabelText('App URL'), {
target: { value: 'https://app.example.com' },
});
fireEvent.change(screen.getByLabelText('OIDC issuer URL'), {
target: { value: 'https://auth.example.com' },
});
fireEvent.change(screen.getByLabelText('OIDC client ID'), { target: { value: 'familysync' } });
fireEvent.change(screen.getByLabelText('VAPID public key'), { target: { value: 'BHtest123' } });
fireEvent.click(screen.getByRole('button', { name: 'Save & Validate' }));
}
it('calls validateSetupVapid after DB and OIDC pass in step 2', async () => {
const { validateSetupDb, validateSetupOidc, validateSetupVapid } = await import('../api/client.js');
const { validateSetupDb, validateSetupOidc, validateSetupVapid } =
await import('../api/client.js');
(validateSetupDb as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
(validateSetupOidc as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
(validateSetupVapid as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
@@ -261,7 +269,8 @@ describe('SetupPage — Step 2 VAPID validation (CR-01 gap)', () => {
});
it('renders a VAPID validation row after step 2 validation completes', async () => {
const { validateSetupDb, validateSetupOidc, validateSetupVapid } = await import('../api/client.js');
const { validateSetupDb, validateSetupOidc, validateSetupVapid } =
await import('../api/client.js');
(validateSetupDb as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
(validateSetupOidc as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
(validateSetupVapid as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
@@ -276,11 +285,14 @@ describe('SetupPage — Step 2 VAPID validation (CR-01 gap)', () => {
});
it('does NOT show Continue when VAPID validation fails', async () => {
const { validateSetupDb, validateSetupOidc, validateSetupVapid } = await import('../api/client.js');
const { validateSetupDb, validateSetupOidc, validateSetupVapid } =
await import('../api/client.js');
(validateSetupDb as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
(validateSetupOidc as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
(validateSetupVapid as ReturnType<typeof vi.fn>).mockRejectedValue(
new Error('VAPID validation failed. Check that your VAPID keys were generated with `npm run generate-secrets`.')
new Error(
'VAPID validation failed. Check that your VAPID keys were generated with `npm run generate-secrets`.',
),
);
await advanceToStep2();
@@ -296,7 +308,8 @@ describe('SetupPage — Step 2 VAPID validation (CR-01 gap)', () => {
});
it('shows Continue only when db, oidc, AND vapid all pass', async () => {
const { validateSetupDb, validateSetupOidc, validateSetupVapid } = await import('../api/client.js');
const { validateSetupDb, validateSetupOidc, validateSetupVapid } =
await import('../api/client.js');
(validateSetupDb as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
(validateSetupOidc as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
(validateSetupVapid as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
@@ -360,8 +373,12 @@ describe('SetupPage — Step 2 Instance copy + DB-name field (gaps 1, 3)', () =>
(validateSetupVapid as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
await advanceToStep2();
fireEvent.change(screen.getByLabelText('App URL'), { target: { value: 'https://app.example.com' } });
fireEvent.change(screen.getByLabelText('OIDC issuer URL'), { target: { value: 'https://auth.example.com' } });
fireEvent.change(screen.getByLabelText('App URL'), {
target: { value: 'https://app.example.com' },
});
fireEvent.change(screen.getByLabelText('OIDC issuer URL'), {
target: { value: 'https://auth.example.com' },
});
fireEvent.change(screen.getByLabelText('OIDC client ID'), { target: { value: 'familysync' } });
fireEvent.change(screen.getByLabelText('VAPID public key'), { target: { value: 'BHtest123' } });
fireEvent.click(screen.getByRole('button', { name: 'Save & Validate' }));
@@ -403,8 +420,12 @@ describe('SetupPage — Back navigation preserves Instance fields (gap 4)', () =
fireEvent.click(await screen.findByRole('button', { name: 'Continue' }));
await screen.findByText('Instance Configuration');
fireEvent.change(screen.getByLabelText('App URL'), { target: { value: 'https://app.example.com' } });
fireEvent.change(screen.getByLabelText('OIDC issuer URL'), { target: { value: 'https://auth.example.com' } });
fireEvent.change(screen.getByLabelText('App URL'), {
target: { value: 'https://app.example.com' },
});
fireEvent.change(screen.getByLabelText('OIDC issuer URL'), {
target: { value: 'https://auth.example.com' },
});
fireEvent.change(screen.getByLabelText('OIDC client ID'), { target: { value: 'familysync' } });
fireEvent.change(screen.getByLabelText('VAPID public key'), { target: { value: 'BHtest123' } });
fireEvent.click(screen.getByRole('button', { name: 'Save & Validate' }));
+34 -53
View File
@@ -175,14 +175,8 @@ function StepIndicator({ currentStep }: StepIndicatorProps) {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background:
isCompleted || isActive
? 'var(--color-member-0, #4a90d9)'
: 'transparent',
border:
isCompleted || isActive
? 'none'
: '2px solid var(--color-border, #e2e4e9)',
background: isCompleted || isActive ? 'var(--color-member-0, #4a90d9)' : 'transparent',
border: isCompleted || isActive ? 'none' : '2px solid var(--color-border, #e2e4e9)',
color: isCompleted || isActive ? '#ffffff' : 'var(--color-text-muted, #9ca3af)',
fontSize: 'var(--text-label-size, 13px)',
fontWeight: 600,
@@ -220,11 +214,7 @@ function StepIndicator({ currentStep }: StepIndicatorProps) {
)}
<div style={{ ...circleStyle, zIndex: 1, position: 'relative' }}>
{isCompleted ? (
<CheckCircle size={16} aria-hidden="true" />
) : (
<span>{stepNum}</span>
)}
{isCompleted ? <CheckCircle size={16} aria-hidden="true" /> : <span>{stepNum}</span>}
</div>
<span
@@ -383,12 +373,10 @@ function Step1Welcome({ onContinue, stepHeadingRef }: Step1Props) {
lineHeight: 1.5,
}}
>
This wizard will guide you through configuring your self-hosted instance. Before
continuing, run{' '}
<code>npm run generate-secrets</code>{' '}
from the repo to generate your instance secrets and add them to your Docker environment.
You&apos;ll also need: your OIDC client credentials (Authelia) and a Fastmail account
with an app password. This takes about 5 minutes.
This wizard will guide you through configuring your self-hosted instance. Before continuing,
run <code>npm run generate-secrets</code> from the repo to generate your instance secrets
and add them to your Docker environment. You&apos;ll also need: your OIDC client credentials
(Authelia) and a Fastmail account with an app password. This takes about 5 minutes.
</p>
{/* Before you start note block */}
@@ -465,7 +453,9 @@ function Step2Config({ onBack, onSuccess, stepHeadingRef, fields, setFields }: S
});
const dbName = setupStatus?.dbName ?? '';
const [validationRows, setValidationRows] = useState<Pick<ValidationRowStatus, 'db' | 'oidc' | 'vapid'>>({
const [validationRows, setValidationRows] = useState<
Pick<ValidationRowStatus, 'db' | 'oidc' | 'vapid'>
>({
db: 'idle',
oidc: 'idle',
vapid: 'idle',
@@ -518,9 +508,7 @@ function Step2Config({ onBack, onSuccess, stepHeadingRef, fields, setFields }: S
}
},
onError: (err) => {
setFieldError(
err instanceof Error ? err.message : 'Something went wrong. Please try again.',
);
setFieldError(err instanceof Error ? err.message : 'Something went wrong. Please try again.');
},
});
@@ -654,9 +642,7 @@ function Step2Config({ onBack, onSuccess, stepHeadingRef, fields, setFields }: S
placeholder="familysync"
style={inputStyle(false)}
/>
<div style={helperStyle}>
The client ID registered in Authelia for this application.
</div>
<div style={helperStyle}>The client ID registered in Authelia for this application.</div>
</div>
{/* VAPID Public Key */}
@@ -673,8 +659,7 @@ function Step2Config({ onBack, onSuccess, stepHeadingRef, fields, setFields }: S
style={inputStyle(false)}
/>
<div style={helperStyle}>
Paste the <code>VAPID_PUBLIC_KEY</code> value from{' '}
<code>npm run generate-secrets</code>.
Paste the <code>VAPID_PUBLIC_KEY</code> value from <code>npm run generate-secrets</code>.
</div>
</div>
@@ -721,19 +706,22 @@ function Step2Config({ onBack, onSuccess, stepHeadingRef, fields, setFields }: S
/>
{/* General field error (before validation rows show) */}
{fieldError && validationRows.db === 'idle' && validationRows.oidc === 'idle' && validationRows.vapid === 'idle' && (
<div
role="status"
aria-live="polite"
style={{
color: 'var(--color-destructive, #dc2626)',
fontSize: 'var(--text-body-size, 15px)',
marginTop: 'var(--space-3, 12px)',
}}
>
{fieldError}
</div>
)}
{fieldError &&
validationRows.db === 'idle' &&
validationRows.oidc === 'idle' &&
validationRows.vapid === 'idle' && (
<div
role="status"
aria-live="polite"
style={{
color: 'var(--color-destructive, #dc2626)',
fontSize: 'var(--text-body-size, 15px)',
marginTop: 'var(--space-3, 12px)',
}}
>
{fieldError}
</div>
)}
{/* Continue + Back row — appears only when both validations pass */}
{bothPassed && configSaved && (
@@ -806,15 +794,12 @@ function Step3Credential({ onBack, onSuccess, onLocked, stepHeadingRef }: Step3P
onLocked();
return;
}
setErrorText(
err instanceof Error ? err.message : 'Something went wrong. Please try again.',
);
setErrorText(err instanceof Error ? err.message : 'Something went wrong. Please try again.');
},
});
const isPending = completeMutation.isPending || finalMutation.isPending;
const saveDisabled =
isPending || email.trim().length === 0 || password.trim().length === 0;
const saveDisabled = isPending || email.trim().length === 0 || password.trim().length === 0;
function handleValidate() {
if (saveDisabled) return;
@@ -853,9 +838,8 @@ function Step3Credential({ onBack, onSuccess, onLocked, stepHeadingRef }: Step3P
lineHeight: 1.5,
}}
>
Add the Fastmail app password for the first household member. This credential is
validated against Fastmail CalDAV before saving. The password is never stored in plain
text.
Add the Fastmail app password for the first household member. This credential is validated
against Fastmail CalDAV before saving. The password is never stored in plain text.
</p>
{/* Fastmail email */}
@@ -1177,10 +1161,7 @@ export function SetupPage({ alreadyLocked = false }: SetupPageProps) {
{/* Step card (Surface 3) */}
{step === 1 && (
<Step1Welcome
onContinue={() => setStep(2)}
stepHeadingRef={stepHeadingRef}
/>
<Step1Welcome onContinue={() => setStep(2)} stepHeadingRef={stepHeadingRef} />
)}
{step === 2 && (
<Step2Config