Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/src/components/settings/SettingsHome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ const SettingsHome = () => {
title={item.title}
description={item.description}
onClick={item.onClick}
testId={`settings-nav-${item.id}`}
dangerous={item.dangerous}
isFirst={index === 0}
isLast={index === flatItems.length - 1}
Expand Down
1 change: 1 addition & 0 deletions app/src/components/settings/SettingsSectionPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const SettingsSectionPage = ({ title, description, items }: SettingsSectionPageP
title={item.title}
description={item.description}
onClick={() => navigateToSettings(item.route)}
testId={`settings-nav-${item.id}`}
isFirst={index === 0}
isLast={index === items.length - 1}
/>
Expand Down
2 changes: 2 additions & 0 deletions app/src/components/settings/__tests__/SettingsHome.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ describe('SettingsHome', () => {
expect(screen.getByText('Advanced')).toBeInTheDocument();
expect(screen.getByText('Clear App Data')).toBeInTheDocument();
expect(screen.getByText('Log out')).toBeInTheDocument();
expect(screen.getByTestId('settings-nav-account')).toBeInTheDocument();
expect(screen.getByTestId('settings-nav-notifications')).toBeInTheDocument();
});

it('localizes Appearance and Mascot menu items', () => {
Expand Down
4 changes: 4 additions & 0 deletions app/src/components/settings/components/SettingsMenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ interface SettingsMenuItemProps {
title: string;
description?: string;
onClick?: () => void;
testId?: string;
dangerous?: boolean;
isFirst?: boolean;
isLast?: boolean;
Expand All @@ -16,6 +17,7 @@ const SettingsMenuItem = ({
title,
description,
onClick,
testId,
dangerous = false,
isFirst = false,
isLast = false,
Expand Down Expand Up @@ -49,6 +51,7 @@ const SettingsMenuItem = ({
return (
<button
type="button"
data-testid={testId}
onClick={onClick}
className={`w-full flex items-center justify-between py-3 px-4 bg-white dark:bg-neutral-900 text-stone-900 dark:text-neutral-100 ${borderClasses} hover:bg-stone-50 dark:hover:bg-neutral-800/60 dark:bg-neutral-800/60 dark:hover:bg-neutral-800/60 transition-all duration-200 text-left ${roundedClasses} focus:outline-none focus:ring-0 focus:border-inherit`}>
{content}
Expand All @@ -58,6 +61,7 @@ const SettingsMenuItem = ({

return (
<div
data-testid={testId}
className={`w-full flex items-center justify-between py-3 px-4 bg-white dark:bg-neutral-900 text-stone-900 dark:text-neutral-100 ${borderClasses} ${roundedClasses}`}>
{content}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,7 @@ const DeveloperOptionsPanel = () => {
title={t(item.titleKey)}
description={t(item.descriptionKey)}
onClick={() => navigateToSettings(item.route)}
testId={`settings-nav-${item.id}`}
isFirst={index === 0}
isLast={false}
/>
Expand All @@ -513,6 +514,7 @@ const DeveloperOptionsPanel = () => {
title={item.title}
description={item.description}
onClick={item.onClick}
testId={`settings-nav-${item.id}`}
isFirst={false}
isLast={index === trailingItems.length - 1}
/>
Expand Down
2 changes: 1 addition & 1 deletion app/src/components/settings/panels/MemoryDebugPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ const MemoryDebugPanel = () => {
}, [clearNamespaceInput, refreshAll]);

return (
<div>
<div data-testid="memory-debug-panel">
<SettingsHeader
title={t('memory.debugTitle')}
showBackButton={true}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ const WebhooksDebugPanel = () => {
}, [loadData]);

return (
<div>
<div data-testid="webhooks-debug-panel">
<SettingsHeader
title={t('webhooks.debugTitle')}
showBackButton={true}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { render, screen, waitFor } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';

vi.mock('../../../../lib/i18n/I18nContext', () => ({ useT: () => ({ t: (key: string) => key }) }));

vi.mock('../../hooks/useSettingsNavigation', () => ({
useSettingsNavigation: () => ({ navigateBack: vi.fn(), breadcrumbs: [] }),
}));

vi.mock('../components/SettingsHeader', () => ({ default: () => null }));

vi.mock('../../../intelligence/MemoryTextWithEntities', () => ({
MemoryTextWithEntities: ({ text }: { text: string }) => <span>{text}</span>,
}));

vi.mock('../../../../utils/tauriCommands', () => ({
memoryClearNamespace: vi.fn().mockResolvedValue({}),
memoryDeleteDocument: vi.fn().mockResolvedValue({}),
memoryListDocuments: vi.fn().mockResolvedValue({ data: { documents: [] } }),
memoryListNamespaces: vi.fn().mockResolvedValue([]),
memoryQueryNamespace: vi.fn().mockResolvedValue({ matches: [] }),
memoryRecallNamespace: vi.fn().mockResolvedValue({ matches: [] }),
}));

describe('MemoryDebugPanel stable test hooks', () => {
it('renders the panel-level test id', async () => {
const { default: MemoryDebugPanel } = await import('../MemoryDebugPanel');

render(<MemoryDebugPanel />);

expect(screen.getByTestId('memory-debug-panel')).toBeInTheDocument();
await waitFor(() => expect(screen.getByText('Documents')).toBeInTheDocument());
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* SSE-side observable behaviour (constructor URL, skip-on-null,
* webhooks_debug event handling).
*/
import { render, waitFor } from '@testing-library/react';
import { render, screen, waitFor } from '@testing-library/react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

// Recording EventSource stub — jsdom has no native impl.
Expand Down Expand Up @@ -112,6 +112,7 @@ describe('WebhooksDebugPanel — SSE auth wiring (#1922)', () => {

render(<WebhooksDebugPanel />);

expect(screen.getByTestId('webhooks-debug-panel')).toBeInTheDocument();
await waitFor(() => expect(MockEventSource.instances).toHaveLength(1));
expect(MockEventSource.instances[0].url).toBe(
'http://localhost:7788/events/webhooks?token=rpc-token-debug-1'
Expand Down
56 changes: 56 additions & 0 deletions app/src/components/skills/SkillCard.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';

import UnifiedSkillCard from './SkillCard';

vi.mock('../../lib/i18n/I18nContext', () => ({
useT: () => ({ t: (key: string) => key }),
}));

describe('UnifiedSkillCard stable test hooks', () => {
it('renders row and primary action test ids', () => {
const onCtaClick = vi.fn();

render(
<UnifiedSkillCard
icon={<span />}
title="Calendar"
description="Connect calendar context"
ctaLabel="Install"
testId="skill-row-calendar"
ctaTestId="skill-install-calendar"
onCtaClick={onCtaClick}
/>
);

expect(screen.getByTestId('skill-row-calendar')).toBeInTheDocument();
fireEvent.click(screen.getByTestId('skill-install-calendar'));
expect(onCtaClick).toHaveBeenCalledTimes(1);
});

it('renders secondary action test ids', () => {
const onUninstall = vi.fn();

render(
<UnifiedSkillCard
icon={<span />}
title="Calendar"
description="Connect calendar context"
ctaLabel="Open"
onCtaClick={vi.fn()}
secondaryActions={[
{
label: 'Uninstall',
icon: <span />,
testId: 'skill-uninstall-calendar',
onClick: onUninstall,
},
]}
/>
);

fireEvent.click(screen.getByTitle('skills.card.moreActions'));
fireEvent.click(screen.getByTestId('skill-uninstall-calendar'));
expect(onUninstall).toHaveBeenCalledTimes(1);
});
});
9 changes: 8 additions & 1 deletion app/src/components/skills/SkillCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export interface UnifiedSkillCardProps {
ctaLabel: string;
ctaVariant?: 'primary' | 'sage' | 'amber';
onCtaClick: () => void;
testId?: string;
ctaTestId?: string;
badge?: ReactNode;
secondaryActions?: Array<{
label: string;
Expand Down Expand Up @@ -46,6 +48,8 @@ export function UnifiedSkillCard({
ctaLabel,
ctaVariant = 'primary',
onCtaClick,
testId,
ctaTestId,
secondaryActions,
syncProgress,
syncSummaryText,
Expand All @@ -69,7 +73,9 @@ export function UnifiedSkillCard({
const ctaStyle = CTA_STYLES[ctaVariant] ?? CTA_STYLES.primary;

return (
<div className="flex items-center gap-3 rounded-xl border border-stone-100 dark:border-neutral-800 bg-white dark:bg-neutral-900 p-3 transition-colors hover:bg-stone-50 dark:hover:bg-neutral-800/60">
<div
data-testid={testId}
className="flex items-center gap-3 rounded-xl border border-stone-100 dark:border-neutral-800 bg-white dark:bg-neutral-900 p-3 transition-colors hover:bg-stone-50 dark:hover:bg-neutral-800/60">
<div className="flex h-8 w-8 flex-shrink-0 items-center justify-center text-stone-600 dark:text-neutral-300">
{icon}
</div>
Expand Down Expand Up @@ -155,6 +161,7 @@ export function UnifiedSkillCard({
)}
<button
type="button"
data-testid={ctaTestId}
disabled={ctaDisabled}
onClick={e => {
e.stopPropagation();
Expand Down
4 changes: 4 additions & 0 deletions app/src/pages/Conversations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1244,6 +1244,7 @@ const Conversations = ({ variant = 'page', composer = 'text' }: ConversationsPro
</h2>
{/* [#1123] welcomeLocked guard removed — always show new thread button */}
<button
data-testid="new-thread-sidebar-button"
onClick={() => void handleCreateNewThread()}
className="w-7 h-7 flex items-center justify-center rounded-lg hover:bg-stone-100 dark:hover:bg-neutral-800 dark:bg-neutral-800 dark:hover:bg-neutral-800/60 text-stone-500 dark:text-neutral-400 hover:text-stone-700 dark:hover:text-neutral-200 dark:text-neutral-200 dark:hover:text-neutral-200 transition-colors"
title={t('chat.newThread')}>
Expand Down Expand Up @@ -1279,6 +1280,7 @@ const Conversations = ({ variant = 'page', composer = 'text' }: ConversationsPro
sortedThreads.map(thread => (
<div
key={thread.id}
data-testid={`thread-row-${thread.id}`}
role="button"
tabIndex={0}
onClick={() => {
Expand Down Expand Up @@ -1433,6 +1435,7 @@ const Conversations = ({ variant = 'page', composer = 'text' }: ConversationsPro
</div>
<TokenUsagePill />
<button
data-testid="new-thread-button"
onClick={() => void handleCreateNewThread()}
className="px-2.5 py-1 rounded-lg text-xs font-medium text-primary-600 hover:bg-primary-50 transition-colors"
title={t('chat.newThreadShortcut')}>
Expand Down Expand Up @@ -2027,6 +2030,7 @@ const Conversations = ({ variant = 'page', composer = 'text' }: ConversationsPro
{/* Voice input mic hidden per #717 (inputMode='voice' path retained). */}
</div>
<button
data-testid="send-message-button"
aria-label={t('chat.send')}
title={t('chat.send')}
onClick={() => {
Expand Down
Loading
Loading