eigent/test/unit/components/SpaceDetailLoadingSkeleton.test.tsx
Douglas Lai ed9614486a
Refactor dashboard (#1854)
Co-authored-by: 4pmtong <web_chentong@163.com>
2026-08-19 19:02:56 +08:00

170 lines
5.5 KiB
TypeScript

// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
import { SpaceDetailSuspenseContent } from '@/components/Home/SpaceDetail';
import {
SpaceDetailListSkeleton,
SpaceDetailTabSkeleton,
} from '@/components/Home/SpaceDetailLoadingSkeleton';
import { act, render, screen, waitFor } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
const motionMocks = vi.hoisted(() => ({ reduced: false }));
vi.mock('framer-motion', async (importOriginal) => {
const actual = await importOriginal<typeof import('framer-motion')>();
return {
...actual,
useReducedMotion: () => motionMocks.reduced,
};
});
describe('SpaceDetail loading skeletons', () => {
beforeEach(() => {
motionMocks.reduced = false;
});
it.each(['project', 'task', 'trigger'] as const)(
'preserves the %s table layout while rows load',
(kind) => {
const { container } = render(<SpaceDetailListSkeleton kind={kind} />);
expect(
screen.getByRole('status', { name: `Loading ${kind} list` })
).toBeInTheDocument();
expect(
container.querySelectorAll('[data-space-detail-skeleton-row]')
).toHaveLength(5);
}
);
it.each([
['context', '[data-space-detail-tab-skeleton="context"]'],
['memory', '[data-memory-settings-skeleton]'],
['workspace-profile', '[data-workspace-settings-skeleton]'],
] as const)(
'shows the %s layout before its content resolves',
(tab, marker) => {
const { container } = render(<SpaceDetailTabSkeleton tab={tab} />);
expect(container.querySelector(marker)).toBeInTheDocument();
expect(
screen.getByRole('status', { name: `Loading ${tab} content` })
).toBeInTheDocument();
}
);
it('reveals only content that committed a Suspense fallback', async () => {
let ready = false;
let resolveContent: () => void = () => undefined;
const pendingContent = new Promise<void>((resolve) => {
resolveContent = resolve;
});
const DeferredContent = () => {
if (!ready) throw pendingContent;
return <span>Resolved memory content</span>;
};
const { rerender } = render(
<SpaceDetailSuspenseContent activeTab="memory" contextLikeTab={false}>
<DeferredContent />
</SpaceDetailSuspenseContent>
);
expect(
screen.getByRole('status', { name: 'Loading memory content' })
).toBeInTheDocument();
await act(async () => {
ready = true;
resolveContent();
await pendingContent;
});
const resolved = await screen.findByText('Resolved memory content');
const revealedContent = resolved.closest(
'[data-space-detail-resolved-content="memory"]'
);
expect(revealedContent).toHaveAttribute(
'data-space-detail-content-reveal',
'true'
);
expect(revealedContent).toHaveAttribute(
'data-space-detail-reveal-duration',
'0.2'
);
await waitFor(() =>
expect(revealedContent).toHaveAttribute(
'data-space-detail-content-reveal',
'false'
)
);
rerender(
<SpaceDetailSuspenseContent activeTab="projects" contextLikeTab={false}>
<span>Resolved project content</span>
</SpaceDetailSuspenseContent>
);
expect(
screen
.getByText('Resolved project content')
.closest('[data-space-detail-resolved-content="projects"]')
).toHaveAttribute('data-space-detail-reveal-duration', '0');
rerender(
<SpaceDetailSuspenseContent activeTab="memory" contextLikeTab={false}>
<span>Resolved memory revisit</span>
</SpaceDetailSuspenseContent>
);
expect(
screen
.getByText('Resolved memory revisit')
.closest('[data-space-detail-resolved-content="memory"]')
).toHaveAttribute('data-space-detail-reveal-duration', '0');
});
it('keeps the fallback reveal opacity-only and shorter for reduced motion', async () => {
motionMocks.reduced = true;
let ready = false;
let resolveContent: () => void = () => undefined;
const pendingContent = new Promise<void>((resolve) => {
resolveContent = resolve;
});
const DeferredContent = () => {
if (!ready) throw pendingContent;
return <span>Resolved reduced-motion content</span>;
};
render(
<SpaceDetailSuspenseContent activeTab="context" contextLikeTab={true}>
<DeferredContent />
</SpaceDetailSuspenseContent>
);
await act(async () => {
ready = true;
resolveContent();
await pendingContent;
});
const content = (
await screen.findByText('Resolved reduced-motion content')
).closest('[data-space-detail-resolved-content="context"]');
expect(content).toHaveClass('h-full');
expect(content).toHaveAttribute(
'data-space-detail-reveal-duration',
'0.12'
);
expect((content as HTMLElement).style.transform).toBe('');
});
});