Add E2E tests for internationalization across multiple pages

- Implemented navigation.spec.ts to test language switching and translation of navigation elements.
- Created sessions-page.spec.ts to verify translations on the sessions page, including headers, status badges, and date formatting.
- Developed settings-page.spec.ts to ensure settings page content is translated and persists across sessions.
- Added skills-page.spec.ts to validate translations for skill categories, action buttons, and empty states.
This commit is contained in:
catlog22
2026-01-30 22:54:21 +08:00
parent e78e95049b
commit 81725c94b1
150 changed files with 25341 additions and 1448 deletions

View File

@@ -4,6 +4,7 @@
// Card component for displaying issues with actions
import { useState } from 'react';
import { useIntl } from 'react-intl';
import {
AlertCircle,
AlertTriangle,
@@ -41,31 +42,64 @@ export interface IssueCardProps {
// ========== Priority Helpers ==========
const priorityConfig: Record<Issue['priority'], { icon: React.ElementType; color: string; label: string }> = {
critical: { icon: AlertCircle, color: 'destructive', label: 'Critical' },
high: { icon: AlertTriangle, color: 'warning', label: 'High' },
medium: { icon: Info, color: 'info', label: 'Medium' },
low: { icon: Info, color: 'secondary', label: 'Low' },
// Priority icon and color configuration (without labels for i18n)
const priorityVariantConfig: Record<Issue['priority'], { icon: React.ElementType; color: string }> = {
critical: { icon: AlertCircle, color: 'destructive' },
high: { icon: AlertTriangle, color: 'warning' },
medium: { icon: Info, color: 'info' },
low: { icon: Info, color: 'secondary' },
};
const statusConfig: Record<Issue['status'], { icon: React.ElementType; color: string; label: string }> = {
open: { icon: AlertCircle, color: 'info', label: 'Open' },
in_progress: { icon: Clock, color: 'warning', label: 'In Progress' },
resolved: { icon: CheckCircle, color: 'success', label: 'Resolved' },
closed: { icon: XCircle, color: 'muted', label: 'Closed' },
completed: { icon: CheckCircle, color: 'success', label: 'Completed' },
// Priority label keys for i18n
const priorityLabelKeys: Record<Issue['priority'], string> = {
critical: 'issues.priority.critical',
high: 'issues.priority.high',
medium: 'issues.priority.medium',
low: 'issues.priority.low',
};
// Status icon and color configuration (without labels for i18n)
const statusVariantConfig: Record<Issue['status'], { icon: React.ElementType; color: string }> = {
open: { icon: AlertCircle, color: 'info' },
in_progress: { icon: Clock, color: 'warning' },
resolved: { icon: CheckCircle, color: 'success' },
closed: { icon: XCircle, color: 'muted' },
completed: { icon: CheckCircle, color: 'success' },
};
// Status label keys for i18n
const statusLabelKeys: Record<Issue['status'], string> = {
open: 'issues.status.open',
in_progress: 'issues.status.inProgress',
resolved: 'issues.status.resolved',
closed: 'issues.status.closed',
completed: 'issues.status.completed',
};
// ========== Priority Badge ==========
export function PriorityBadge({ priority }: { priority: Issue['priority'] }) {
const config = priorityConfig[priority];
const { formatMessage } = useIntl();
const config = priorityVariantConfig[priority];
// Defensive check: handle unknown priority values
if (!config) {
return (
<Badge variant="secondary" className="gap-1">
{priority}
</Badge>
);
}
const Icon = config.icon;
const label = priorityLabelKeys[priority]
? formatMessage({ id: priorityLabelKeys[priority] })
: priority;
return (
<Badge variant={config.color as 'default' | 'secondary' | 'destructive' | 'outline'} className="gap-1">
<Icon className="w-3 h-3" />
{config.label}
{label}
</Badge>
);
}
@@ -73,13 +107,27 @@ export function PriorityBadge({ priority }: { priority: Issue['priority'] }) {
// ========== Status Badge ==========
export function StatusBadge({ status }: { status: Issue['status'] }) {
const config = statusConfig[status];
const { formatMessage } = useIntl();
const config = statusVariantConfig[status];
// Defensive check: handle unknown status values
if (!config) {
return (
<Badge variant="outline" className="gap-1">
{status}
</Badge>
);
}
const Icon = config.icon;
const label = statusLabelKeys[status]
? formatMessage({ id: statusLabelKeys[status] })
: status;
return (
<Badge variant="outline" className="gap-1">
<Icon className="w-3 h-3" />
{config.label}
{label}
</Badge>
);
}
@@ -99,6 +147,7 @@ export function IssueCard({
dragHandleProps,
innerRef,
}: IssueCardProps) {
const { formatMessage } = useIntl();
const [isMenuOpen, setIsMenuOpen] = useState(false);
const handleClick = () => {
@@ -176,19 +225,19 @@ export function IssueCard({
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={handleEdit}>
<Edit className="w-4 h-4 mr-2" />
Edit
{formatMessage({ id: 'issues.actions.edit' })}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => onStatusChange?.(issue, 'in_progress')}>
<Clock className="w-4 h-4 mr-2" />
Start Progress
{formatMessage({ id: 'issues.actions.startProgress' })}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => onStatusChange?.(issue, 'resolved')}>
<CheckCircle className="w-4 h-4 mr-2" />
Mark Resolved
{formatMessage({ id: 'issues.actions.markResolved' })}
</DropdownMenuItem>
<DropdownMenuItem onClick={handleDelete} className="text-destructive">
<Trash2 className="w-4 h-4 mr-2" />
Delete
{formatMessage({ id: 'issues.actions.delete' })}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
@@ -228,7 +277,10 @@ export function IssueCard({
{issue.solutions && issue.solutions.length > 0 && (
<div className="flex items-center gap-1 mt-2 text-xs text-muted-foreground">
<ExternalLink className="w-3 h-3" />
{issue.solutions.length} solution{issue.solutions.length !== 1 ? 's' : ''}
{issue.solutions.length} {formatMessage(
{ id: 'issues.card.solutions' },
{ count: issue.solutions.length }
)}
</div>
)}
</Card>