diff --git a/ui/tests/unit/components/account/flow-viz/account-card.test.tsx b/ui/tests/unit/components/account/flow-viz/account-card.test.tsx index 539e48e3..be9c28d4 100644 --- a/ui/tests/unit/components/account/flow-viz/account-card.test.tsx +++ b/ui/tests/unit/components/account/flow-viz/account-card.test.tsx @@ -20,7 +20,7 @@ vi.mock('@/hooks/use-cliproxy-stats', async () => { const mockedUseAccountQuota = vi.mocked(useAccountQuota); const mockedUseAccountQuotas = vi.mocked(useAccountQuotas); -function makeCodexQuota(planType: 'plus' | 'team', fiveHour: number, weekly: number) { +function makeCodexQuota(planType: 'free' | 'plus' | 'team', fiveHour: number, weekly: number) { return { success: true, planType, @@ -76,7 +76,9 @@ const groupedAccount: AccountData = { failureCount: 0, audience: 'business', audienceLabel: 'Business', - detailLabel: null, + detailLabel: 'Workspace 04a0f049', + compactDetailLabel: '04a0f049', + inlineLabel: 'Business · Workspace 04a0f049', }, { id: 'personal@example.com', @@ -87,11 +89,27 @@ const groupedAccount: AccountData = { failureCount: 1, audience: 'personal', audienceLabel: 'Personal', - detailLabel: null, + detailLabel: 'Free', + compactDetailLabel: 'Free', + inlineLabel: 'Personal · Free', }, ], }; +const groupedAccountWithProPersonal: AccountData = { + ...groupedAccount, + variants: groupedAccount.variants?.map((variant) => + variant.audience === 'personal' + ? { + ...variant, + detailLabel: 'Pro', + compactDetailLabel: 'Pro', + inlineLabel: 'Personal · Pro', + } + : variant + ), +}; + describe('AccountCard grouped quota tooltip', () => { beforeEach(() => { mockedUseAccountQuota.mockReturnValue({ @@ -105,13 +123,13 @@ describe('AccountCard grouped quota tooltip', () => { isLoading: false, }, { - data: makeCodexQuota('plus', 64, 42), + data: makeCodexQuota('free', 64, 42), isLoading: false, }, ] as ReturnType); }); - it('shows provider quota tooltip content for each grouped personal/business row on hover', async () => { + it('keeps grouped Codex account labels distinct and shows quota tooltips for each variant', async () => { render( { /> ); - await userEvent.hover(screen.getByText('Business')); + expect( + screen.getByTitle('Business · Workspace 04a0f049 • Personal · Free') + ).toBeInTheDocument(); + expect(screen.getByText('Biz')).toBeInTheDocument(); + + await userEvent.hover(screen.getByText('Business · Workspace 04a0f049')); const businessPlan = (await screen.findAllByText('Plan: team')).find((node) => node.closest('[data-slot="tooltip-content"]') ); @@ -141,11 +164,35 @@ describe('AccountCard grouped quota tooltip', () => { expect(tooltipContent?.className).toContain('text-popover-foreground'); expect(tooltipContent?.className).toContain('max-w-[calc(100vw-2rem)]'); - await userEvent.hover(screen.getByText('Personal')); - const personalPlan = (await screen.findAllByText('Plan: plus')).find((node) => + await userEvent.hover(screen.getByText('Personal · Free')); + const personalPlan = (await screen.findAllByText('Plan: free')).find((node) => node.closest('[data-slot="tooltip-content"]') ); expect(personalPlan).toBeInTheDocument(); expect(screen.getAllByText('Weekly usage limit').length).toBeGreaterThan(0); }); + + it('keeps richer grouped personal detail when quota planType is coarser runtime evidence', () => { + render( + undefined} + onMouseLeave={() => undefined} + onPointerDown={() => undefined} + onPointerMove={() => undefined} + onPointerUp={() => undefined} + /> + ); + + expect(screen.getByTitle('Business · Workspace 04a0f049 • Personal · Pro')).toBeInTheDocument(); + expect(screen.getByText('Personal · Pro')).toBeInTheDocument(); + expect(screen.queryByText('Personal · Free')).not.toBeInTheDocument(); + }); }); diff --git a/ui/tests/unit/ui/components/account/shared/account-surface-card.test.tsx b/ui/tests/unit/ui/components/account/shared/account-surface-card.test.tsx index 6a0a823e..56079208 100644 --- a/ui/tests/unit/ui/components/account/shared/account-surface-card.test.tsx +++ b/ui/tests/unit/ui/components/account/shared/account-surface-card.test.tsx @@ -1,7 +1,7 @@ import { render, screen } from '@tests/setup/test-utils'; import { describe, expect, it } from 'vitest'; import { AccountSurfaceCard } from '@/components/account/shared/account-surface-card'; -import type { GeminiCliQuotaResult } from '@/lib/api-client'; +import type { CodexQuotaResult, GeminiCliQuotaResult } from '@/lib/api-client'; function createGeminiQuotaResult( overrides: Partial = {} @@ -29,6 +29,16 @@ function createGeminiQuotaResult( }; } +function createCodexQuotaResult(overrides: Partial = {}): CodexQuotaResult { + return { + success: true, + windows: [], + planType: 'free', + lastUpdated: Date.now(), + ...overrides, + }; +} + describe('AccountSurfaceCard', () => { it('prefers live quota entitlement tier over a stale account tier for Gemini badges', () => { render( @@ -46,4 +56,40 @@ describe('AccountSurfaceCard', () => { expect(screen.getByText('pro')).toBeInTheDocument(); }); + + it('shows both personal identity and free-tier detail for compact Codex cards', () => { + render( + + ); + + expect(screen.getByText('Pers')).toBeInTheDocument(); + expect(screen.getByTitle('Personal')).toBeInTheDocument(); + expect(screen.getByText('Free')).toBeInTheDocument(); + }); + + it('keeps richer token-derived Codex personal detail when live quota planType is coarser', () => { + render( + + ); + + expect(screen.getByText('Pro')).toBeInTheDocument(); + expect(screen.queryByText('Free')).not.toBeInTheDocument(); + }); }); diff --git a/ui/tests/unit/ui/lib/account-visual-groups.test.ts b/ui/tests/unit/ui/lib/account-visual-groups.test.ts index 2c654071..cba2b9c2 100644 --- a/ui/tests/unit/ui/lib/account-visual-groups.test.ts +++ b/ui/tests/unit/ui/lib/account-visual-groups.test.ts @@ -16,7 +16,7 @@ function makeAccount(overrides: Partial & Pick { - it('orders grouped codex variants by audience consistently', () => { + it('preserves grouped codex variant identity details while ordering by audience', () => { const groups = buildAccountVisualGroups([ makeAccount({ id: 'kaidu.kd@gmail.com#free', @@ -33,9 +33,36 @@ describe('buildAccountVisualGroups', () => { 'business', 'personal', ]); + expect(groups[0]?.variants?.map((variant) => variant.inlineLabel)).toEqual([ + 'Business · Workspace 04a0f049', + 'Personal · Free', + ]); + expect(groups[0]?.variants?.map((variant) => variant.compactDetailLabel)).toEqual([ + '04a0f049', + 'Free', + ]); expect(groups[0]?.memberIds).toEqual([ 'kaidu.kd@gmail.com#04a0f049-team', 'kaidu.kd@gmail.com#free', ]); }); + + it('keeps multiple personal codex plans distinct inside the same grouped card', () => { + const groups = buildAccountVisualGroups([ + makeAccount({ + id: 'kaidu.kd@gmail.com#plus', + tokenFile: 'codex-kaidu.kd@gmail.com-plus.json', + }), + makeAccount({ + id: 'kaidu.kd@gmail.com#free', + tokenFile: 'codex-kaidu.kd@gmail.com-free.json', + }), + ]); + + expect(groups).toHaveLength(1); + expect(groups[0]?.variants?.map((variant) => variant.inlineLabel)).toEqual([ + 'Personal · Free', + 'Personal · Plus', + ]); + }); });