feat(admin): E2E mock-сьюты inbox/reports/reviews + testids. Refs EventHub/EventHubFrontAdmin#33 #36

This commit is contained in:
2026-07-14 22:35:21 +03:00
parent 87c6b55e81
commit a3468518b3
9 changed files with 501 additions and 24 deletions
+3 -1
View File
@@ -28,6 +28,7 @@ interface ExploreListShellProps {
onViewModeChange: (mode: ExploreViewMode) => void;
children: ReactNode;
className?: string;
'data-testid'?: string;
}
export function ExploreListShell({
@@ -41,10 +42,11 @@ export function ExploreListShell({
onViewModeChange,
children,
className,
'data-testid': dataTestId,
}: ExploreListShellProps) {
const { t } = useTranslation();
return (
<div className={cn('space-y-4', className)}>
<div className={cn('space-y-4', className)} data-testid={dataTestId}>
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
<div className="min-w-0 space-y-1">
<h1 className="text-2xl font-semibold tracking-tight">{title}</h1>
+20 -4
View File
@@ -150,7 +150,7 @@ const ReportInboxPage: React.FC = () => {
});
return (
<div className="flex h-[calc(100vh-7rem)] min-h-[480px] flex-col gap-4">
<div data-testid="page-inbox-reports" className="flex h-[calc(100vh-7rem)] min-h-[480px] flex-col gap-4">
<div className="flex flex-wrap items-center gap-3">
<div>
<h1 className="text-2xl font-semibold tracking-tight">{t('inbox.reportsTitle')}</h1>
@@ -164,6 +164,7 @@ const ReportInboxPage: React.FC = () => {
</span>
<Button
size="sm"
data-testid="btn-bulk-reviewed"
disabled={bulkPending}
onClick={() => void handleBulk('reviewed')}
>
@@ -172,6 +173,7 @@ const ReportInboxPage: React.FC = () => {
<Button
size="sm"
variant="secondary"
data-testid="btn-bulk-dismissed"
disabled={bulkPending}
onClick={() => void handleBulk('dismissed')}
>
@@ -182,6 +184,7 @@ const ReportInboxPage: React.FC = () => {
<Button
variant={filter === 'pending' ? 'default' : 'outline'}
size="sm"
data-testid="filter-pending"
onClick={() => {
setFilter('pending');
setCheckedIds(new Set());
@@ -192,6 +195,7 @@ const ReportInboxPage: React.FC = () => {
<Button
variant={filter === 'all' ? 'default' : 'outline'}
size="sm"
data-testid="filter-all"
onClick={() => {
setFilter('all');
setCheckedIds(new Set());
@@ -233,6 +237,7 @@ const ReportInboxPage: React.FC = () => {
{items.map((item) => (
<div
key={item.id}
data-testid={`report-row-${item.id}`}
className={cn(
'flex w-full items-center gap-2 px-2 py-1.5 transition-colors hover:bg-muted/60',
selectedId === item.id && 'bg-muted'
@@ -240,6 +245,7 @@ const ReportInboxPage: React.FC = () => {
>
<input
type="checkbox"
data-testid={`report-check-${item.id}`}
className="size-3.5 shrink-0 accent-primary"
checked={checkedIds.has(item.id)}
onChange={(e) => {
@@ -251,6 +257,7 @@ const ReportInboxPage: React.FC = () => {
/>
<button
type="button"
data-testid={`report-select-${item.id}`}
onClick={() => selectReport(item.id)}
className="flex min-w-0 flex-1 items-center gap-2 text-left"
>
@@ -284,7 +291,7 @@ const ReportInboxPage: React.FC = () => {
</CardContent>
) : report ? (
<>
<CardHeader>
<CardHeader data-testid="inbox-report-detail">
<div className="flex items-start justify-between gap-2">
<CardTitle className="text-lg">{t('inbox.reportCard', { id: report.id })}</CardTitle>
<Badge variant={reportBadgeVariant(report.status)}>
@@ -346,10 +353,19 @@ const ReportInboxPage: React.FC = () => {
</CardContent>
{report.status === 'pending' && (
<CardFooter className="gap-2 border-t bg-muted/30 p-4">
<Button onClick={() => handleStatus('reviewed')} disabled={updateReport.isPending}>
<Button
data-testid="btn-report-reviewed"
onClick={() => handleStatus('reviewed')}
disabled={updateReport.isPending}
>
{t('common.reviewed')} <kbd className="ml-1 rounded border px-1 text-[10px] opacity-70">1</kbd>
</Button>
<Button variant="secondary" onClick={() => handleStatus('dismissed')} disabled={updateReport.isPending}>
<Button
data-testid="btn-report-dismissed"
variant="secondary"
onClick={() => handleStatus('dismissed')}
disabled={updateReport.isPending}
>
{t('common.dismissed')} <kbd className="ml-1 rounded border px-1 text-[10px] opacity-70">2</kbd>
</Button>
</CardFooter>
+31 -6
View File
@@ -127,20 +127,35 @@ const TicketInboxPage: React.FC = () => {
});
return (
<div className="flex h-[calc(100vh-7rem)] min-h-[480px] flex-col gap-4">
<div data-testid="page-inbox-tickets" className="flex h-[calc(100vh-7rem)] min-h-[480px] flex-col gap-4">
<div className="flex flex-wrap items-center gap-3">
<div>
<h1 className="text-2xl font-semibold tracking-tight">{t('inbox.ticketsTitle')}</h1>
<p className="text-sm text-muted-foreground">{t('inbox.ticketsHints')}</p>
</div>
<div className="ml-auto flex flex-wrap gap-2">
<Button variant={filter === 'active' ? 'default' : 'outline'} size="sm" onClick={() => setFilter('active')}>
<Button
variant={filter === 'active' ? 'default' : 'outline'}
size="sm"
data-testid="filter-active"
onClick={() => setFilter('active')}
>
{t('inbox.active')}
</Button>
<Button variant={filter === 'mine' ? 'default' : 'outline'} size="sm" onClick={() => setFilter('mine')}>
<Button
variant={filter === 'mine' ? 'default' : 'outline'}
size="sm"
data-testid="filter-mine"
onClick={() => setFilter('mine')}
>
{t('inbox.mine')}
</Button>
<Button variant={filter === 'all' ? 'default' : 'outline'} size="sm" onClick={() => setFilter('all')}>
<Button
variant={filter === 'all' ? 'default' : 'outline'}
size="sm"
data-testid="filter-all"
onClick={() => setFilter('all')}
>
{t('inbox.all')}
</Button>
</div>
@@ -167,6 +182,7 @@ const TicketInboxPage: React.FC = () => {
<button
key={item.id}
type="button"
data-testid={`ticket-row-${item.id}`}
onClick={() => selectTicket(item.id)}
className={cn(
'flex w-full items-center gap-2 px-3 py-1.5 text-left transition-colors hover:bg-muted/60',
@@ -253,11 +269,20 @@ const TicketInboxPage: React.FC = () => {
{(ticket.status === 'open' || ticket.status === 'in_progress') && (
<CardFooter className="gap-2 border-t bg-muted/30 p-4">
{ticket.status === 'open' && (
<Button variant="secondary" onClick={handleInProgress} disabled={updateTicket.isPending}>
<Button
data-testid="btn-ticket-in-progress"
variant="secondary"
onClick={handleInProgress}
disabled={updateTicket.isPending}
>
{t('common.inProgress')} <kbd className="ml-1 rounded border px-1 text-[10px] opacity-70">2</kbd>
</Button>
)}
<Button onClick={handleResolve} disabled={updateTicket.isPending}>
<Button
data-testid="btn-ticket-resolved"
onClick={handleResolve}
disabled={updateTicket.isPending}
>
{t('common.resolved')} <kbd className="ml-1 rounded border px-1 text-[10px] opacity-70">1</kbd>
</Button>
</CardFooter>
+15 -6
View File
@@ -86,7 +86,7 @@ const ReportDetailPage: React.FC = () => {
: t('common.dismissed');
return (
<>
<div data-testid="page-report-detail">
<PageHeader
title={t('reports.detailTitle', { id: report.id })}
breadcrumbs={[
@@ -171,10 +171,19 @@ const ReportDetailPage: React.FC = () => {
{report.status === 'pending' && (
<div className="flex flex-wrap gap-2">
<Button onClick={() => setConfirmStatus('reviewed')} disabled={updateReport.isPending}>
<Button
data-testid="btn-report-reviewed"
onClick={() => setConfirmStatus('reviewed')}
disabled={updateReport.isPending}
>
{t('common.reviewed')}
</Button>
<Button variant="destructive" onClick={() => setConfirmStatus('dismissed')} disabled={updateReport.isPending}>
<Button
data-testid="btn-report-dismissed"
variant="destructive"
onClick={() => setConfirmStatus('dismissed')}
disabled={updateReport.isPending}
>
{t('common.dismissed')}
</Button>
</div>
@@ -184,7 +193,7 @@ const ReportDetailPage: React.FC = () => {
</Card>
<Dialog open={confirmStatus !== null} onOpenChange={(open) => !open && setConfirmStatus(null)}>
<DialogContent>
<DialogContent data-testid="dialog-confirm-report-status">
<DialogHeader>
<DialogTitle>{t('common.confirmMarkAs', { status: statusLabel })}</DialogTitle>
</DialogHeader>
@@ -192,13 +201,13 @@ const ReportDetailPage: React.FC = () => {
<Button variant="outline" onClick={() => setConfirmStatus(null)}>
{t('common.cancel')}
</Button>
<Button onClick={handleConfirm} disabled={updateReport.isPending}>
<Button data-testid="btn-confirm-status" onClick={handleConfirm} disabled={updateReport.isPending}>
{updateReport.isPending ? t('common.saving') : t('common.yes')}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</>
</div>
);
};
+3
View File
@@ -250,6 +250,7 @@ const ReportListPage: React.FC = () => {
return (
<>
<ExploreListShell
data-testid="page-reports"
title={t('reports.title')}
stats={exploreStats}
viewMode={viewMode}
@@ -342,6 +343,7 @@ const ReportListPage: React.FC = () => {
{detailModal.report.status === 'pending' && (
<DialogFooter className="mt-4 sm:justify-start">
<Button
data-testid="btn-report-reviewed"
onClick={() => {
updateReport.mutate(
{ id: detailModal.report!.id, data: { status: 'reviewed' } },
@@ -353,6 +355,7 @@ const ReportListPage: React.FC = () => {
{t('common.reviewed')}
</Button>
<Button
data-testid="btn-report-dismissed"
variant="destructive"
onClick={() => {
updateReport.mutate(
+4
View File
@@ -367,6 +367,7 @@ const ReviewListPage: React.FC = () => {
return (
<>
<ExploreListShell
data-testid="page-reviews"
title={t('reviews.title')}
stats={exploreStats}
viewMode={viewMode}
@@ -418,6 +419,7 @@ const ReviewListPage: React.FC = () => {
<div className="flex flex-wrap gap-2">
<Button
data-testid="btn-reviews-bulk-hidden"
variant="outline"
onClick={() => openBulkStatusModal('hidden')}
disabled={selectedRowKeys.length === 0}
@@ -425,6 +427,7 @@ const ReviewListPage: React.FC = () => {
{t('reviews.hideSelected')}
</Button>
<Button
data-testid="btn-reviews-bulk-visible"
variant="outline"
onClick={() => openBulkStatusModal('visible')}
disabled={selectedRowKeys.length === 0}
@@ -432,6 +435,7 @@ const ReviewListPage: React.FC = () => {
{t('reviews.showSelected')}
</Button>
<Button
data-testid="btn-reviews-bulk-deleted"
variant="destructive"
onClick={() => openBulkStatusModal('deleted')}
disabled={selectedRowKeys.length === 0}