Skip to content
Closed
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
3 changes: 0 additions & 3 deletions tests/components/DevInfraGateHydration.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,7 @@ describe('DevInfraGate SSR & Hydration Integration', () => {
});

const ssrHtml = renderToString(<DevOnlyBridgeOverlay />);
// O HTML gerado pelo servidor DEVE ser absolutamente vazio
// Não deve conter nem wrappers de Suspense (comentários <!--$-->) nem qualquer rastro do overlay
expect(ssrHtml).toBe('');
expect(ssrHtml).not.toContain('<!--');

// --- FASE 2: Hidratação simulada (Cliente) ---
// Em vez de hydrateRoot manual (complexo com mocks), usamos render do RTL
Expand Down
71 changes: 40 additions & 31 deletions tests/components/DevInfraGateStability.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, it, expect, vi } from 'vitest';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, act } from '@testing-library/react';
import React, { useState, useEffect } from 'react';
import React, { useEffect } from 'react';
import { useDevGate } from '@/hooks/useDevGate';
import { DevOnlyBridgeOverlay } from '@/components/dev/DevOnlyBridgeOverlay';

Expand All @@ -9,61 +9,70 @@ vi.mock('@/hooks/useDevGate', () => ({
useDevGate: vi.fn(),
}));

// Mock do overlay real com contador de montagem para detectar re-renders excessivos ou "flashes"
let renderCount = 0;
// Rastreamento de eventos de ciclo de vida
let lifecycleEvents: string[] = [];

vi.mock('@/components/dev/BridgeMetricsOverlay', () => ({
default: () => {
renderCount++;
return <div data-testid="bridge-metrics-overlay-real">Overlay v{renderCount}</div>;
useEffect(() => {
lifecycleEvents.push('mount');
return () => {
lifecycleEvents.push('unmount');
};
}, []);
return <div data-testid="bridge-metrics-overlay-real">Overlay Active</div>;
},
}));

describe('DevInfraGate Stability — Loading Transition & Anti-Flicker', () => {
it('transita de isLoading para carregado e renderiza o overlay exatamente uma vez sem piscar', async () => {
renderCount = 0;

// 1. Estado inicial: Carregando
// No useDevGate real, isso retornaria isAllowed: false
describe('DevInfraGate Anti-Flicker — Lifecycle Integrity', () => {
beforeEach(() => {
lifecycleEvents = [];
vi.clearAllMocks();
});

it('garante que o overlay monta exatamente uma vez e não sofre unmount/remount durante transições de estado estáveis', async () => {
// 1. Estado inicial: Bloqueado (isLoading ou !mounted)
vi.mocked(useDevGate).mockReturnValue({
isAllowed: false,
isDev: false
});

const { rerender } = render(<DevOnlyBridgeOverlay />);

// Garantimos que não renderizou nada no HTML (nem wrappers, nem placeholders)
expect(document.body.innerHTML).not.toContain('bridge-metrics-overlay');
expect(document.body.innerHTML).not.toContain('<!--'); // Sem Suspense tags
expect(screen.queryByTestId('bridge-metrics-overlay-real')).not.toBeInTheDocument();
expect(renderCount).toBe(0);
expect(lifecycleEvents).toEqual([]);

// 2. Transição para estado Permitido (Simula Auth finalizado + permissão dev)
// 2. Transição para Permitido
vi.mocked(useDevGate).mockReturnValue({
isAllowed: true,
isDev: true
});

// Disparamos o re-render que o hook real dispararia após isLoading -> false e mounted -> true
await act(async () => {
rerender(<DevOnlyBridgeOverlay />);
});

// O overlay deve aparecer
const overlay = await screen.findByTestId('bridge-metrics-overlay-real');
expect(overlay).toBeInTheDocument();
expect(await screen.findByTestId('bridge-metrics-overlay-real')).toBeInTheDocument();
expect(lifecycleEvents).toEqual(['mount']);

// 3. Simula mudança rápida no AuthContext que NÃO altera o isAllowed final (ex: refresh de token background)
// O useDevGate deve manter a referência estável e o DevOnlyBridgeOverlay não deve desmontar o overlay.
await act(async () => {
rerender(<DevOnlyBridgeOverlay />);
});

// Se houve flicker, teríamos ['mount', 'unmount', 'mount']
expect(lifecycleEvents).toEqual(['mount']);

// Render count deve ser exatamente 1 (sem flashes de montagem/desmontagem)
expect(renderCount).toBe(1);
// 4. Somente desmonta se isAllowed virar false
vi.mocked(useDevGate).mockReturnValue({
isAllowed: false,
isDev: false
});

// 3. Simula um update trivial no Gate (ex: mudança em outra role irrelevante que não altera isAllowed)
// Se o hook for estável (useMemo), o overlay não deve re-renderizar o componente lazy.
await act(async () => {
rerender(<DevOnlyBridgeOverlay />);
});

// O contador de render do COMPONENTE DEFAULT (o mock) pode subir se o React decidir re-renderizar,
// mas o importante é que ele não foi desmontado e remontado (o que causaria flicker visual).
// No caso do lazy, se isAllowed for mantido estável pelo useMemo no useDevGate, o Suspense não deve entrar em fallback.
expect(screen.getByTestId('bridge-metrics-overlay-real')).toBeInTheDocument();
expect(screen.queryByTestId('bridge-metrics-overlay-real')).not.toBeInTheDocument();
expect(lifecycleEvents).toEqual(['mount', 'unmount']);
});
});