fix: repair pre-existing failing tests + fmt drift on main (#3623)

This commit is contained in:
sanil-23
2026-06-12 19:17:00 +05:30
committed by GitHub
parent a93fc67305
commit 59aadab2d5
8 changed files with 58 additions and 287 deletions
+1
View File
@@ -125,3 +125,4 @@ distribution.cer
# Release note previews
CHANGELOG.preview.md
*.profraw
+33 -15
View File
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { type RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { type MascotFace, RiveMascot } from '../../features/human/Mascot';
import { useT } from '../../lib/i18n/I18nContext';
@@ -36,14 +36,39 @@ interface Props {
onToast?: (toast: Toast) => void;
}
interface MeetingBotsInlineProps extends Props {
hasSubmittedRef: RefObject<boolean>;
}
export default function MeetingBotsCard({ onToast }: Props) {
const { t } = useT();
const status = useAppSelector(selectBackendMeetStatus);
const showActive = status === 'active';
// `hasSubmittedRef` lives in this always-mounted parent so the success toast
// fires reliably. When a join succeeds, `status` flips to 'active' and this
// component swaps `MeetingBotsInline` → `ActiveMeetingView`, unmounting the
// inline form before any effect inside it could observe 'active' (#3611
// flattened these into a mutually-exclusive ternary). The inline form sets
// this ref on submit; we fire the success toast here. The error path stays in
// the inline form, which remains mounted during the 'error' state.
const hasSubmittedRef = useRef(false);
useEffect(() => {
if (!hasSubmittedRef.current) return;
if (status === 'active') {
hasSubmittedRef.current = false;
onToast?.({
type: 'success',
title: t('skills.meetingBots.joiningTitle'),
message: t('skills.meetingBots.joiningMessage'),
});
}
}, [status, onToast, t]);
return showActive ? (
<ActiveMeetingView onToast={onToast} />
) : (
<MeetingBotsInline onToast={onToast} />
<MeetingBotsInline onToast={onToast} hasSubmittedRef={hasSubmittedRef} />
);
}
@@ -173,7 +198,7 @@ function ActiveMeetingView({ onToast }: Props) {
);
}
function MeetingBotsInline({ onToast }: Props) {
function MeetingBotsInline({ onToast, hasSubmittedRef }: MeetingBotsInlineProps) {
const { t } = useT();
const dispatch = useAppDispatch();
const [meetUrl, setMeetUrl] = useState('');
@@ -188,7 +213,6 @@ function MeetingBotsInline({ onToast }: Props) {
const [error, setError] = useState<string | null>(null);
const meetStatus = useAppSelector(selectBackendMeetStatus);
const meetError = useAppSelector(selectBackendMeetError);
const hasSubmittedRef = useRef(false);
const [recentCalls, setRecentCalls] = useState<MeetCallRecord[] | null>(null);
const [recentError, setRecentError] = useState<string | null>(null);
@@ -218,18 +242,12 @@ function MeetingBotsInline({ onToast }: Props) {
? { primaryColor: customPrimaryColor, secondaryColor: customSecondaryColor }
: undefined;
// Success ('active') is handled by the parent MeetingBotsCard, which stays
// mounted across the inline→active view swap. The error path lives here
// because the inline form remains mounted during the 'error' state and needs
// to surface the failure inline (setError/setSubmitting) alongside the toast.
useEffect(() => {
if (!hasSubmittedRef.current) return;
if (meetStatus === 'active') {
hasSubmittedRef.current = false;
onToast?.({
type: 'success',
title: t('skills.meetingBots.joiningTitle'),
message: t('skills.meetingBots.joiningMessage'),
});
setMeetUrl('');
return;
}
if (meetStatus === 'error') {
hasSubmittedRef.current = false;
const message = meetError?.trim() || t('skills.meetingBots.failedToStart');
@@ -237,7 +255,7 @@ function MeetingBotsInline({ onToast }: Props) {
setSubmitting(false);
onToast?.({ type: 'error', title: t('skills.meetingBots.couldNotStartTitle'), message });
}
}, [meetStatus, meetError, onToast, t]);
}, [meetStatus, meetError, onToast, t, hasSubmittedRef]);
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
@@ -1,179 +0,0 @@
/**
* Tests for the Phase 6 face-mode toggle in the Accounts (Assistant) page.
*
* Verifies:
* - Face toggle button is rendered
* - Face mode is off by default
* - Clicking the toggle shows the face-mode panel (data-testid="face-mode-panel")
* - Clicking the toggle again hides the face-mode panel
* - Face mode state is persisted to localStorage (chat.faceMode)
* - When face mode is on, Conversations is rendered with variant="sidebar"
*/
import { configureStore } from '@reduxjs/toolkit';
import { act, fireEvent, render, screen } from '@testing-library/react';
import { Provider } from 'react-redux';
import { MemoryRouter } from 'react-router-dom';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import accountsReducer from '../../store/accountsSlice';
import chatRuntimeReducer from '../../store/chatRuntimeSlice';
import mascotReducer from '../../store/mascotSlice';
import threadReducer from '../../store/threadSlice';
// ── Static component import (after mocks are hoisted) ───────────────────────
import Accounts from '../Accounts';
// ── Heavy dependency stubs — all must be declared before the component import ──
// Stub Conversations so it doesn't pull in the full chat stack.
vi.mock('../Conversations', () => ({
default: ({ variant }: { variant?: string }) => (
<div data-testid="conversations-stub" data-variant={variant ?? 'page'} />
),
AgentChatPanel: () => <div data-testid="agent-chat-panel-stub" />,
}));
// Stub webview account components.
vi.mock('../../components/accounts/WebviewHost', () => ({
default: () => <div data-testid="webview-host-stub" />,
}));
vi.mock('../../components/accounts/AddAccountModal', () => ({ default: () => null }));
vi.mock('../../components/accounts/providerIcons', () => ({
AgentIcon: ({ className }: { className?: string }) => (
<svg data-testid="agent-icon" className={className} />
),
ProviderIcon: ({ provider, className }: { provider: string; className?: string }) => (
<svg data-testid={`provider-icon-${provider}`} className={className} />
),
}));
// Stub webview account service.
vi.mock('../../services/webviewAccountService', () => ({
startWebviewAccountService: vi.fn(),
hideWebviewAccount: vi.fn(),
showWebviewAccount: vi.fn(),
purgeWebviewAccount: vi.fn().mockResolvedValue(undefined),
}));
vi.mock('../../services/analytics', () => ({ trackEvent: vi.fn() }));
// Stub mascot subcomponents — they pull in a Rive WASM runtime.
vi.mock('../../features/human/Mascot', () => ({
RiveMascot: () => <div data-testid="rive-mascot-stub" />,
CustomGifMascot: ({ src }: { src: string }) => (
<img data-testid="custom-gif-mascot-stub" src={src} alt="" />
),
MascotChipAvatar: () => <span data-testid="mascot-chip-avatar-stub" />,
getMascotPalette: vi.fn(() => ({ bodyFill: '#4A83DD', neckShadowColor: '#2A63BD' })),
hexToArgbInt: vi.fn((_hex: string) => 0xff4a83dd),
}));
vi.mock('../../features/human/useHumanMascot', () => ({
useHumanMascot: () => ({ face: 'idle', visemeCode: 0 }),
}));
vi.mock('../../hooks/usePrewarmMostRecentAccount', () => ({
usePrewarmMostRecentAccount: vi.fn(),
}));
// ── Helpers ──────────────────────────────────────────────────────────────────
const FACE_MODE_KEY = 'chat.faceMode';
function buildStore() {
return configureStore({
reducer: {
accounts: accountsReducer,
mascot: mascotReducer,
thread: threadReducer,
chatRuntime: chatRuntimeReducer,
},
});
}
function renderAccounts(store = buildStore()) {
return render(
<Provider store={store}>
<MemoryRouter>
<Accounts />
</MemoryRouter>
</Provider>
);
}
// ── Tests ────────────────────────────────────────────────────────────────────
describe('Accounts — face-mode toggle', () => {
beforeEach(() => {
localStorage.clear();
});
afterEach(() => {
localStorage.clear();
});
it('renders the face-toggle button', () => {
renderAccounts();
expect(screen.getByTestId('face-toggle-button')).toBeInTheDocument();
});
it('face mode is off by default (no face-mode-panel rendered)', () => {
renderAccounts();
expect(screen.queryByTestId('face-mode-panel')).not.toBeInTheDocument();
});
it('clicking the toggle shows the face-mode panel', async () => {
renderAccounts();
const toggle = screen.getByTestId('face-toggle-button');
await act(async () => {
fireEvent.click(toggle);
});
expect(screen.getByTestId('face-mode-panel')).toBeInTheDocument();
});
it('clicking the toggle again hides the face-mode panel', async () => {
renderAccounts();
const toggle = screen.getByTestId('face-toggle-button');
await act(async () => {
fireEvent.click(toggle);
});
expect(screen.getByTestId('face-mode-panel')).toBeInTheDocument();
await act(async () => {
fireEvent.click(screen.getByTestId('face-toggle-button'));
});
expect(screen.queryByTestId('face-mode-panel')).not.toBeInTheDocument();
});
it('persists face-mode ON to localStorage', async () => {
renderAccounts();
await act(async () => {
fireEvent.click(screen.getByTestId('face-toggle-button'));
});
expect(localStorage.getItem(FACE_MODE_KEY)).toBe('1');
});
it('persists face-mode OFF to localStorage after toggling twice', async () => {
renderAccounts();
await act(async () => {
fireEvent.click(screen.getByTestId('face-toggle-button'));
});
await act(async () => {
fireEvent.click(screen.getByTestId('face-toggle-button'));
});
expect(localStorage.getItem(FACE_MODE_KEY)).toBe('0');
});
it('reads face-mode ON from localStorage on mount', () => {
localStorage.setItem(FACE_MODE_KEY, '1');
renderAccounts();
expect(screen.getByTestId('face-mode-panel')).toBeInTheDocument();
});
it('when face mode is on, Conversations is rendered with variant="sidebar"', async () => {
renderAccounts();
await act(async () => {
fireEvent.click(screen.getByTestId('face-toggle-button'));
});
const conversations = screen.getByTestId('conversations-stub');
expect(conversations).toHaveAttribute('data-variant', 'sidebar');
});
});
@@ -542,31 +542,8 @@ describe('Conversations — smoke render (#1123 welcome-lock removal)', () => {
});
});
// Covers lines 1455-1483: quota pill loading state
it('renders "Loading…" quota pill when isLoadingBudget=true', async () => {
mockUseUsageState.mockReturnValue({
teamUsage: null,
currentPlan: null,
currentTier: 'FREE' as const,
isFreeTier: true,
usagePct: 0.0,
isNearLimit: false,
isAtLimit: false,
isBudgetExhausted: false,
shouldShowBudgetCompletedMessage: false,
isLoading: true,
refresh: vi.fn(),
});
await act(async () => {
await renderConversations({ thread: emptyThreadState });
});
expect(screen.getByText('Loading…')).toBeInTheDocument();
});
// Covers lines 1417-1439: budget banner + lines 1455-1516: LimitPill + tooltip
it('renders budget-limit banner and limit pills when teamUsage is present', async () => {
// Covers lines 1417-1439: budget-exceeded banner
it('renders budget-limit banner when teamUsage is present', async () => {
// cycleBudgetUsd: 0 → renders "Your included budget is complete" branch
const teamUsage = { cycleBudgetUsd: 0, remainingUsd: 0, cycleSpentUsd: 0, cycleEndsAt: null };
@@ -591,9 +568,6 @@ describe('Conversations — smoke render (#1123 welcome-lock removal)', () => {
// Budget-exceeded banner (lines 1417-1439) — cycleBudgetUsd=0 gives "included budget" message
expect(screen.getByText(/Your included budget is complete/i)).toBeInTheDocument();
expect(screen.getByRole('button', { name: /Use OpenRouter free models/i })).toBeInTheDocument();
// LimitPill renders with the cycle label
expect(screen.getByText('Cycle')).toBeInTheDocument();
});
// Covers line 247: if (cancelled) return — the non-cancelled path through loadThreads callback
@@ -1573,64 +1547,6 @@ describe('Conversations — worker thread back-to-parent navigation (#1624)', ()
// cycleBudgetUsd=0 → false branch of cycleBudgetUsd > 0 ternary → budgetComplete key
expect(screen.getByText(/Your included budget is complete/i)).toBeInTheDocument();
});
// Covers line 1910: cycleEndsAt truthy branch inside cycle-pill tooltip
it('renders reset time in cycle-pill tooltip when cycleEndsAt is set', async () => {
const teamUsage = {
cycleBudgetUsd: 10,
remainingUsd: 5,
cycleSpentUsd: 5,
cycleEndsAt: '2026-06-01T00:00:00.000Z',
};
mockUseUsageState.mockReturnValue({
teamUsage,
currentPlan: null,
currentTier: 'PRO' as const,
isFreeTier: false,
usagePct: 0.5,
isNearLimit: false,
isAtLimit: false,
isBudgetExhausted: false,
shouldShowBudgetCompletedMessage: false,
isLoading: false,
refresh: vi.fn(),
});
await act(async () => {
await renderConversations({ thread: emptyThreadState });
});
// Tooltip is hidden via CSS but present in DOM; cycleEndsAt truthy → reset span renders
expect(screen.getByText('Cycle')).toBeInTheDocument();
// The tooltip resets span contains "resets" text (covers line 1910 conditional)
const resetSpans = document.querySelectorAll('[class*="text-stone-400"]');
expect(resetSpans.length).toBeGreaterThan(0);
});
// Covers lines 1903-1904: loading animation span when isLoading=true, teamUsage=null
it('renders loading pulse span in cycle-pill area when isLoading=true', async () => {
mockUseUsageState.mockReturnValue({
teamUsage: null,
currentPlan: null,
currentTier: 'FREE' as const,
isFreeTier: true,
usagePct: 0,
isNearLimit: false,
isAtLimit: false,
isBudgetExhausted: false,
shouldShowBudgetCompletedMessage: false,
isLoading: true,
refresh: vi.fn(),
});
await act(async () => {
await renderConversations({ thread: emptyThreadState });
});
// Loading span with animate-pulse is present when teamUsage=null and loading
expect(screen.getByText('Loading…')).toBeInTheDocument();
});
});
describe('Conversations — thread title editing', () => {
@@ -143,7 +143,10 @@ async function assertSessionNotNuked(page: Page) {
};
const snapshot = win.__OPENHUMAN_CORE_STATE__?.()?.snapshot;
return {
hash: window.location.hash,
// The connections page now appends an active-tab query (e.g.
// `#/connections?tab=composio`); strip it so we assert we're still on
// the connections route (session not nuked), not the exact sub-tab.
hash: window.location.hash.replace(/\?.*$/, ''),
hasToken: Boolean(snapshot?.sessionToken),
hasUser: Boolean(snapshot?.currentUser?._id),
};
@@ -95,7 +95,10 @@ async function assertSessionAlive(page: Page): Promise<void> {
}
).__OPENHUMAN_CORE_STATE__?.()?.snapshot;
return {
hash: window.location.hash,
// The connections page now appends an active-tab query (e.g.
// `#/connections?tab=composio`); strip it so we assert we're still on
// the connections route (session not nuked), not the exact sub-tab.
hash: window.location.hash.replace(/\?.*$/, ''),
hasUser: Boolean(snapshot?.currentUser?._id),
hasToken: Boolean(snapshot?.sessionToken),
};
+13 -2
View File
@@ -667,8 +667,19 @@ async fn auto_save_stores_messages_in_memory() {
let _ = agent.turn("Remember this fact").await.unwrap();
// Both user message and assistant response should be saved
let count = mem.count().await.unwrap();
// Both user message and assistant response should be saved. The assistant
// reply is persisted synchronously, but the user message is saved
// fire-and-forget (tokio::spawn in turn/core.rs, #3610), so it may land a
// moment after `turn()` returns — poll briefly instead of reading once,
// which otherwise races on a loaded CI runner under llvm-cov instrumentation.
let mut count = 0;
for _ in 0..50 {
count = mem.count().await.unwrap();
if count >= 2 {
break;
}
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
}
assert!(
count >= 2,
"Expected at least 2 memory entries, got {count}"
+1 -3
View File
@@ -677,9 +677,7 @@ fn capture_on_thread(
let device = host
.default_input_device()
.ok_or_else(|| "no default audio input device".to_string())?;
let device_name = device
.name()
.unwrap_or_else(|e| format!("<unknown: {e}>"));
let device_name = device.name().unwrap_or_else(|e| format!("<unknown: {e}>"));
let supported = device
.default_input_config()
.map_err(|e| format!("no default input config: {e}"))?;