diff --git a/frontend/src/styles.css b/frontend/src/styles.css
index 147b26b..768a470 100644
--- a/frontend/src/styles.css
+++ b/frontend/src/styles.css
@@ -234,12 +234,6 @@ body {
min-width: 0;
}
-.tree-item .foldername {
- flex: 1;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
.tree-item:hover {
background: rgba(27, 27, 24, 0.045);
}
@@ -278,10 +272,43 @@ body {
padding-left: 20px;
}
-.file-row .filename {
+/* ---------- Sidebar label + tooltip ---------- */
+.sidebar-label {
flex: 1;
+ min-width: 0;
+ position: relative;
+}
+
+.label-text {
+ display: block;
overflow: hidden;
text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.sidebar-label.has-tip::after {
+ content: attr(data-tip);
+ position: fixed;
+ left: 282px;
+ transform: translateY(-50%);
+ top: var(--tip-y, 50%);
+ background: var(--pine-deep);
+ color: var(--paper);
+ font-family: 'IBM Plex Mono', monospace;
+ font-size: 11px;
+ font-weight: 400;
+ white-space: nowrap;
+ padding: 5px 9px;
+ border-radius: 5px;
+ pointer-events: none;
+ opacity: 0;
+ box-shadow: 0 4px 14px rgba(27, 27, 24, 0.18);
+ z-index: 200;
+ transition: opacity 0.08s;
+}
+
+.sidebar-label.has-tip:hover::after {
+ opacity: 1;
}
.file-icon {
diff --git a/frontend/tests/App.test.tsx b/frontend/tests/App.test.tsx
index ff3c9a6..fe8ec53 100644
--- a/frontend/tests/App.test.tsx
+++ b/frontend/tests/App.test.tsx
@@ -1,4 +1,4 @@
-import { render, screen, fireEvent } from '@testing-library/react';
+import { render, screen, fireEvent, act } from '@testing-library/react';
import { test, expect, beforeEach, afterEach, vi } from 'vitest';
import App from '../src/App';
import userEvent from '@testing-library/user-event';
@@ -39,8 +39,10 @@ afterEach(() => {
vi.restoreAllMocks();
});
-test('renders the DocHub heading', () => {
- render(
);
+test('renders the DocHub heading', async () => {
+ await act(async () => {
+ render(
);
+ });
expect(screen.getByRole('heading', { name: 'DocHub' })).toBeInTheDocument();
});
@@ -149,18 +151,25 @@ test('jumping to a search result clears the search input so it does not show sta
});
test('typing a query does not show a false "no results" message before the debounced search resolves', async () => {
- vi.useFakeTimers();
vi.mocked(listDocuments).mockResolvedValue({ folders: [], files: [] });
vi.mocked(search).mockResolvedValue({ results: [] });
- render(
);
+ // Settle the initial FileExplorer fetch before switching to fake timers.
+ await act(async () => {
+ render(
);
+ });
+
+ vi.useFakeTimers();
const searchInput = screen.getByRole('textbox', { name: '搜索文档' });
fireEvent.change(searchInput, { target: { value: '检索' } });
expect(screen.queryByTestId('search-no-results')).not.toBeInTheDocument();
- await vi.advanceTimersByTimeAsync(300);
+ // Advance the debounce and flush the resulting state updates inside act.
+ await act(async () => {
+ await vi.advanceTimersByTimeAsync(300);
+ });
expect(screen.getByTestId('search-no-results')).toBeInTheDocument();
diff --git a/frontend/tests/setup.ts b/frontend/tests/setup.ts
index bb02c60..2d2e71b 100644
--- a/frontend/tests/setup.ts
+++ b/frontend/tests/setup.ts
@@ -1 +1,8 @@
import '@testing-library/jest-dom/vitest';
+
+// jsdom doesn't implement ResizeObserver; provide a no-op stub for tests.
+global.ResizeObserver = class ResizeObserver {
+ observe() {}
+ unobserve() {}
+ disconnect() {}
+};