// ========= 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(); 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(); 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(); 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((resolve) => { resolveContent = resolve; }); const DeferredContent = () => { if (!ready) throw pendingContent; return Resolved memory content; }; const { rerender } = render( ); 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( Resolved project content ); expect( screen .getByText('Resolved project content') .closest('[data-space-detail-resolved-content="projects"]') ).toHaveAttribute('data-space-detail-reveal-duration', '0'); rerender( Resolved memory revisit ); 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((resolve) => { resolveContent = resolve; }); const DeferredContent = () => { if (!ready) throw pendingContent; return Resolved reduced-motion content; }; render( ); 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(''); }); });