From 6b740c3f61d41771d199f7e50148fdb8bdfecf79 Mon Sep 17 00:00:00 2001 From: Jinwoo-H Date: Thu, 17 Sep 2026 00:51:34 -0400 Subject: [PATCH] test(mobile): assert the reply-schema pins without type assertions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The changed-code casting gate counts a `as` in a test like any other, and eight of them had crept into the new schema pins. Each is replaced by an assertion that reads the same fact off the typed value: the schema already declares `worktreeCreateIdempotency`, `glab`, `status` and `error`, so the narrowing was never needed, and the two "is this key present" checks are JSON comparisons, which is the honest way to ask — `JSON.stringify` drops an absent key and keeps an explicit null, which is the whole distinction a tri-state pin is making. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb --- .../tasks/task-project-board-reply-schema.test.ts | 13 ++++--------- mobile/src/tasks/task-runtime-reply-schema.test.ts | 9 ++++----- .../tasks/task-source-search-reply-schema.test.ts | 4 ++-- .../src/tasks/workspace-source-reply-schema.test.ts | 4 ++-- 4 files changed, 12 insertions(+), 18 deletions(-) diff --git a/mobile/src/tasks/task-project-board-reply-schema.test.ts b/mobile/src/tasks/task-project-board-reply-schema.test.ts index 5515ed999aa..0416d2ac482 100644 --- a/mobile/src/tasks/task-project-board-reply-schema.test.ts +++ b/mobile/src/tasks/task-project-board-reply-schema.test.ts @@ -131,18 +131,13 @@ describe('the row detail pane preserves reviewDecision as a tri-state', () => { expect(parsed.success && parsed.data).toMatchObject({ details: { item: { reviewDecision: null } } }) - expect( - parsed.success && - 'reviewDecision' in (parsed.data as { details: { item: object } }).details.item - ).toBe(true) + // JSON drops an absent key and keeps an explicit null, which is the whole distinction here. + expect(JSON.stringify(parsed)).toContain('"reviewDecision":null') }) it('keeps absence absent rather than collapsing it to null', () => { const parsed = detail({ labels: [] }) - expect( - parsed.success && - 'reviewDecision' in (parsed.data as { details: { item: object } }).details.item - ).toBe(false) + expect(JSON.stringify(parsed)).not.toContain('reviewDecision') }) it('keeps a decision the host reports', () => { @@ -189,7 +184,7 @@ describe('the comment replies', () => { it('drops a comment with no id rather than appending an unkeyed row', () => { const parsed = taskProjectCommentWriteSchema.safeParse({ ok: true, comment: { body: 'hi' } }) - expect(parsed.success && (parsed.data as { comment?: unknown }).comment).toBeUndefined() + expect(parsed.success && parsed.data).toEqual({ ok: true }) }) it('keeps a bare-string error, which both mutation call sites branch on', () => { diff --git a/mobile/src/tasks/task-runtime-reply-schema.test.ts b/mobile/src/tasks/task-runtime-reply-schema.test.ts index 4313620ac32..429938c629d 100644 --- a/mobile/src/tasks/task-runtime-reply-schema.test.ts +++ b/mobile/src/tasks/task-runtime-reply-schema.test.ts @@ -32,10 +32,9 @@ describe('the runtime status', () => { for (const advertised of [undefined, null, 'nonsense', { dedupeTtlMs: 45_000 }]) { const parsed = taskRuntimeStatusSchema.safeParse({ worktreeCreateIdempotency: advertised }) expect(parsed.success).toBe(true) - expect( - parsed.success && - (parsed.data as { worktreeCreateIdempotency?: unknown }).worktreeCreateIdempotency - ).toEqual(advertised) + expect(parsed.success ? parsed.data.worktreeCreateIdempotency : 'unparsed').toEqual( + advertised + ) } }) }) @@ -79,7 +78,7 @@ describe('the two advisory probes', () => { it('drops a malformed glab rather than reading it as installed', () => { const parsed = taskPreflightSchema.safeParse({ glab: 'yes' }) - expect(parsed.success && (parsed.data as { glab?: unknown }).glab).toBeUndefined() + expect(parsed.success ? parsed.data.glab : 'unparsed').toBeUndefined() }) it('passes a host member no mobile consumer reads straight through', () => { diff --git a/mobile/src/tasks/task-source-search-reply-schema.test.ts b/mobile/src/tasks/task-source-search-reply-schema.test.ts index d8a89045537..71008b6988b 100644 --- a/mobile/src/tasks/task-source-search-reply-schema.test.ts +++ b/mobile/src/tasks/task-source-search-reply-schema.test.ts @@ -62,8 +62,8 @@ describe('a work-item row preserves author as a tri-state', () => { it('keeps absence absent rather than collapsing it to null', () => { const parsed = taskGitHubWorkItemListSchema.safeParse({ items: [{ number: 1 }] }) - const rows = parsed.success ? (parsed.data.items as object[]) : [] - expect('author' in rows[0]!).toBe(false) + const row = parsed.success ? parsed.data.items[0] : undefined + expect(row && Object.keys(row)).toEqual(['number']) }) }) diff --git a/mobile/src/tasks/workspace-source-reply-schema.test.ts b/mobile/src/tasks/workspace-source-reply-schema.test.ts index 22d5924eb34..b2dcd12e7d8 100644 --- a/mobile/src/tasks/workspace-source-reply-schema.test.ts +++ b/mobile/src/tasks/workspace-source-reply-schema.test.ts @@ -85,7 +85,7 @@ describe('status is an open enum that degrades to disconnected', () => { const parsed = sshConnectionStateSchema.safeParse({ state: { ...connected, status: 'whatever' } }) - expect(parsed.success && (parsed.data as { status: string }).status).not.toBe('connected') + expect(parsed.success ? parsed.data?.status : 'unparsed').not.toBe('connected') }) it('stays fatal for a non-string status, which is the wrong type and not a newer arm', () => { @@ -97,7 +97,7 @@ describe('status is an open enum that degrades to disconnected', () => { describe('error is a tri-state the drawer renders', () => { it('keeps an explicit null', () => { const parsed = sshConnectionStateSchema.safeParse({ state: connected }) - expect(parsed.success && (parsed.data as { error: unknown }).error).toBeNull() + expect(parsed.success ? parsed.data?.error : 'unparsed').toBeNull() }) it('keeps the host message', () => {