mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-29 14:02:19 +00:00
fix(settings): resolve overlapping text in Memory Data panel (#1305)
This commit is contained in:
@@ -100,7 +100,7 @@ function SourceCard({ status }: SourceCardProps) {
|
||||
{FRESHNESS_LABEL[status.freshness]}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-1 flex items-center gap-3 text-xs text-stone-500">
|
||||
<div className="mt-1 flex flex-wrap items-center gap-x-3 gap-y-0.5 text-xs text-stone-500">
|
||||
<span data-testid={`memory-sync-chunks-${status.provider}`}>
|
||||
{lifetime.toLocaleString()} chunks
|
||||
</span>
|
||||
@@ -187,7 +187,7 @@ export function MemorySyncConnections({ pollIntervalMs }: MemorySyncConnectionsP
|
||||
return (
|
||||
<section className="memory-sync-connections" data-testid="memory-sync-connections">
|
||||
<h3 className="text-sm font-semibold text-stone-700">Memory sources</h3>
|
||||
<p className="mt-2 rounded-md bg-coral-50 p-2 text-xs text-coral-800">
|
||||
<p className="mt-2 rounded-md bg-coral-50 p-2 text-xs text-coral-800 break-words">
|
||||
Failed to load sync status: {loadError}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
@@ -137,7 +137,7 @@ const MemoryWindowControl = ({ onError, onSaved }: Props) => {
|
||||
<div
|
||||
role="radiogroup"
|
||||
aria-label="Long-term memory window"
|
||||
className="grid grid-cols-1 sm:grid-cols-4 gap-2">
|
||||
className="grid grid-cols-2 gap-2">
|
||||
{MEMORY_CONTEXT_WINDOWS.map(option => {
|
||||
const optionMeta = MEMORY_WINDOW_PRESET_META[option];
|
||||
const isActive = activeForUi === option;
|
||||
@@ -154,9 +154,9 @@ const MemoryWindowControl = ({ onError, onSaved }: Props) => {
|
||||
className={`text-left rounded-md border px-3 py-2 transition-colors ${
|
||||
isActive ? 'border-primary bg-primary/10' : 'border-border hover:bg-accent/40'
|
||||
} disabled:opacity-60 disabled:cursor-not-allowed`}>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="font-medium">{optionMeta.label}</span>
|
||||
<span className="text-[10px] uppercase tracking-wide text-muted-foreground">
|
||||
<div className="flex items-center justify-between gap-1 min-w-0">
|
||||
<span className="font-medium truncate">{optionMeta.label}</span>
|
||||
<span className="text-[10px] uppercase tracking-wide text-muted-foreground shrink-0 whitespace-nowrap">
|
||||
{optionMeta.badge}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* Tests for the Settings → Memory Data panel.
|
||||
*
|
||||
* Verifies that all four memory-window preset buttons render, the memory
|
||||
* sources section is present, and that a sync-connection error does not
|
||||
* hide or disable the memory-window controls.
|
||||
*/
|
||||
import { screen, waitFor } from '@testing-library/react';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { renderWithProviders } from '../../../../test/test-utils';
|
||||
import MemoryDataPanel from '../MemoryDataPanel';
|
||||
|
||||
// ── Mocks ────────────────────────────────────────────────────────────────────
|
||||
|
||||
const hoisted = vi.hoisted(() => ({
|
||||
mockGetConfig: vi.fn(),
|
||||
mockUpdateMemorySettings: vi.fn(),
|
||||
mockIsTauri: vi.fn(() => false),
|
||||
mockMemoryTreeListChunks: vi.fn(),
|
||||
mockMemoryTreeListSources: vi.fn(),
|
||||
mockMemoryTreeTopEntities: vi.fn(),
|
||||
mockMemoryTreeEntityIndexFor: vi.fn(),
|
||||
mockMemoryTreeChunkScore: vi.fn(),
|
||||
mockMemoryTreeChunksForEntity: vi.fn(),
|
||||
mockStatusList: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../../../utils/tauriCommands', () => ({
|
||||
isTauri: hoisted.mockIsTauri,
|
||||
openhumanGetConfig: hoisted.mockGetConfig,
|
||||
openhumanUpdateMemorySettings: hoisted.mockUpdateMemorySettings,
|
||||
MEMORY_CONTEXT_WINDOWS: ['minimal', 'balanced', 'extended', 'maximum'],
|
||||
memoryTreeListChunks: hoisted.mockMemoryTreeListChunks,
|
||||
memoryTreeListSources: hoisted.mockMemoryTreeListSources,
|
||||
memoryTreeTopEntities: hoisted.mockMemoryTreeTopEntities,
|
||||
memoryTreeEntityIndexFor: hoisted.mockMemoryTreeEntityIndexFor,
|
||||
memoryTreeChunkScore: hoisted.mockMemoryTreeChunkScore,
|
||||
memoryTreeChunksForEntity: hoisted.mockMemoryTreeChunksForEntity,
|
||||
}));
|
||||
|
||||
vi.mock('../../../../services/memorySyncService', async () => {
|
||||
const actual = await vi.importActual<typeof import('../../../../services/memorySyncService')>(
|
||||
'../../../../services/memorySyncService'
|
||||
);
|
||||
return {
|
||||
...actual,
|
||||
memorySyncStatusList: (...args: unknown[]) => hoisted.mockStatusList(...args),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('../../hooks/useSettingsNavigation', () => ({
|
||||
useSettingsNavigation: () => ({ navigateBack: vi.fn(), breadcrumbs: [] }),
|
||||
}));
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
const resolveConfigWith = (memory_window = 'balanced') => {
|
||||
hoisted.mockGetConfig.mockResolvedValue({
|
||||
result: {
|
||||
config: { agent: { memory_window } },
|
||||
workspace_dir: '/tmp/ws',
|
||||
config_path: '/tmp/cfg.toml',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// ── Tests ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
describe('MemoryDataPanel', () => {
|
||||
beforeEach(() => {
|
||||
hoisted.mockGetConfig.mockReset();
|
||||
hoisted.mockUpdateMemorySettings.mockReset();
|
||||
hoisted.mockIsTauri.mockReturnValue(false);
|
||||
hoisted.mockStatusList.mockReset();
|
||||
hoisted.mockMemoryTreeListChunks.mockReset();
|
||||
hoisted.mockMemoryTreeListSources.mockReset();
|
||||
hoisted.mockMemoryTreeTopEntities.mockReset();
|
||||
hoisted.mockMemoryTreeEntityIndexFor.mockReset();
|
||||
hoisted.mockMemoryTreeChunkScore.mockReset();
|
||||
hoisted.mockMemoryTreeChunksForEntity.mockReset();
|
||||
|
||||
// Default: no sources yet, no errors
|
||||
hoisted.mockStatusList.mockResolvedValue([]);
|
||||
hoisted.mockMemoryTreeListChunks.mockResolvedValue({ chunks: [], total: 0, cursor: null });
|
||||
hoisted.mockMemoryTreeListSources.mockResolvedValue([]);
|
||||
hoisted.mockMemoryTreeTopEntities.mockResolvedValue([]);
|
||||
});
|
||||
|
||||
it('renders all four preset buttons', async () => {
|
||||
resolveConfigWith('balanced');
|
||||
renderWithProviders(<MemoryDataPanel />);
|
||||
|
||||
for (const label of ['Minimal', 'Balanced', 'Extended', 'Maximum']) {
|
||||
expect(screen.getByText(label)).toBeInTheDocument();
|
||||
}
|
||||
});
|
||||
|
||||
it('renders the memory sources section', async () => {
|
||||
resolveConfigWith('balanced');
|
||||
renderWithProviders(<MemoryDataPanel />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('memory-sync-connections')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps all preset buttons accessible when sync connections returns an error', async () => {
|
||||
resolveConfigWith('balanced');
|
||||
hoisted.mockStatusList.mockRejectedValue(new Error('network timeout'));
|
||||
|
||||
renderWithProviders(<MemoryDataPanel />);
|
||||
|
||||
// Wait for the error state to appear in the sync connections section
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/network timeout/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// All four preset buttons must still be in the DOM and not disabled
|
||||
for (const preset of ['minimal', 'balanced', 'extended', 'maximum']) {
|
||||
const btn = screen.getByTestId(`memory-window-option-${preset}`);
|
||||
expect(btn).toBeInTheDocument();
|
||||
expect(btn).not.toBeDisabled();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user