feat(web): wire TickerStrip + status bar role into DashboardLayout (TEC-3047)

- Import TickerStrip vào dashboard layout, truyền vào DashboardLayout.ticker
- Thêm placeholder top-8 quận với TODO comment chờ /analytics/districts API
- Thêm role="status" aria-live="polite" vào status bar div trong DashboardLayout
- 8 Vitest unit tests cho DashboardLayout: role=banner, role=status, ticker,
  sidebar collapse/expand width, main content (tất cả pass)

Note: listings.spec.tsx failure là pre-existing trên HEAD, không liên quan TEC-3047.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-21 01:47:25 +07:00
parent d07f39b864
commit d6d7584677
3 changed files with 132 additions and 1 deletions

View File

@@ -0,0 +1,110 @@
/* eslint-disable import-x/order */
/**
* Kiểm thử layout dashboard: header role=banner, status bar role=status,
* ticker hiển thị, sidebar và main content.
*/
import { render, screen } from '@testing-library/react';
import * as React from 'react';
import { describe, expect, it } from 'vitest';
import { DashboardLayout } from '@/components/design-system/dashboard-layout';
describe('DashboardLayout', () => {
it('renders header với role=banner', () => {
render(
<DashboardLayout
header={<header role="banner">Header</header>}
statusBar={<span>Online</span>}
>
<div>Content</div>
</DashboardLayout>,
);
expect(screen.getByRole('banner')).toBeInTheDocument();
});
it('renders status bar với role=status', () => {
render(
<DashboardLayout
header={<header role="banner">Header</header>}
statusBar={<span>Đã kết nối</span>}
>
<div>Content</div>
</DashboardLayout>,
);
expect(screen.getByRole('status')).toBeInTheDocument();
expect(screen.getByRole('status')).toHaveTextContent('Đã kết nối');
});
it('renders ticker strip khi được truyền prop ticker', () => {
render(
<DashboardLayout
ticker={<div data-testid="ticker">Ticker</div>}
header={<header role="banner">Header</header>}
>
<div>Content</div>
</DashboardLayout>,
);
expect(screen.getByTestId('ticker')).toBeInTheDocument();
});
it('không render ticker khi không truyền prop', () => {
render(
<DashboardLayout header={<header role="banner">Header</header>}>
<div>Content</div>
</DashboardLayout>,
);
// Không có vùng ticker
expect(screen.queryByTestId('ticker')).not.toBeInTheDocument();
});
it('renders sidebar khi được truyền prop', () => {
render(
<DashboardLayout
sidebar={<nav aria-label="sidebar-nav">Sidebar</nav>}
header={<header role="banner">Header</header>}
>
<div>Nội dung chính</div>
</DashboardLayout>,
);
expect(screen.getByRole('navigation', { name: 'sidebar-nav' })).toBeInTheDocument();
});
it('renders children trong main content', () => {
render(
<DashboardLayout header={<header role="banner">Header</header>}>
<p>Nội dung con</p>
</DashboardLayout>,
);
expect(screen.getByText('Nội dung con')).toBeInTheDocument();
});
it('sidebar collapsed có width 56px', () => {
const { container } = render(
<DashboardLayout
sidebar={<nav>Nav</nav>}
sidebarCollapsed
header={<header role="banner">Header</header>}
>
<div>Content</div>
</DashboardLayout>,
);
// aside phải có inline style width: 56px
const aside = container.querySelector('aside');
expect(aside).toHaveStyle({ width: '56px' });
});
it('sidebar expanded sử dụng sidebarWidth prop', () => {
const { container } = render(
<DashboardLayout
sidebar={<nav>Nav</nav>}
sidebarCollapsed={false}
sidebarWidth={240}
header={<header role="banner">Header</header>}
>
<div>Content</div>
</DashboardLayout>,
);
const aside = container.querySelector('aside');
expect(aside).toHaveStyle({ width: '240px' });
});
});