mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
feat(mcp): i18n + a11y McpStatusBadge status labels (#2635)
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Tests for McpStatusBadge — renders the i18n'd label and a11y role
|
||||
* for each ServerStatus, and forwards custom className.
|
||||
*/
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import McpStatusBadge from './McpStatusBadge';
|
||||
import type { ServerStatus } from './types';
|
||||
|
||||
describe('McpStatusBadge', () => {
|
||||
it.each<[ServerStatus, string]>([
|
||||
['connected', 'Connected'],
|
||||
['connecting', 'Connecting'],
|
||||
['disconnected', 'Disconnected'],
|
||||
['error', 'Error'],
|
||||
])('renders i18n label for status=%s', (status, expectedLabel) => {
|
||||
render(<McpStatusBadge status={status} />);
|
||||
expect(screen.getByRole('status')).toHaveTextContent(expectedLabel);
|
||||
});
|
||||
|
||||
it('exposes role="status" and aria-live="polite" for assistive tech', () => {
|
||||
render(<McpStatusBadge status="connecting" />);
|
||||
const badge = screen.getByRole('status');
|
||||
expect(badge).toHaveAttribute('aria-live', 'polite');
|
||||
});
|
||||
|
||||
it('falls back to the disconnected style for an unknown status value', () => {
|
||||
// ServerStatus is a closed union, but the runtime fallback exists for
|
||||
// forward-compat with possible future Rust-side variants — exercise it.
|
||||
render(<McpStatusBadge status={'spawning' as ServerStatus} />);
|
||||
expect(screen.getByRole('status')).toHaveTextContent('Disconnected');
|
||||
});
|
||||
|
||||
it('appends the optional className without dropping the built-in classes', () => {
|
||||
render(<McpStatusBadge status="connected" className="ml-2 my-custom-class" />);
|
||||
const badge = screen.getByRole('status');
|
||||
expect(badge.className).toContain('my-custom-class');
|
||||
expect(badge.className).toContain('ml-2');
|
||||
// Built-in look-and-feel preserved.
|
||||
expect(badge.className).toContain('rounded-full');
|
||||
expect(badge.className).toContain('bg-sage-500/10');
|
||||
});
|
||||
});
|
||||
@@ -1,25 +1,28 @@
|
||||
/**
|
||||
* Status badge for MCP server connection states.
|
||||
* Mirrors ChannelStatusBadge but uses ServerStatus values.
|
||||
* Mirrors ChannelStatusBadge but uses ServerStatus values; reuses the
|
||||
* shared `channels.status.*` i18n keys since the label vocabulary is
|
||||
* identical (Connected / Connecting / Disconnected / Error).
|
||||
*/
|
||||
import { useT } from '../../../lib/i18n/I18nContext';
|
||||
import type { ServerStatus } from './types';
|
||||
|
||||
const STATUS_STYLES: Record<ServerStatus, { label: string; className: string }> = {
|
||||
const STATUS_META: Record<ServerStatus, { i18nKey: string; className: string }> = {
|
||||
connected: {
|
||||
label: 'Connected',
|
||||
i18nKey: 'channels.status.connected',
|
||||
className: 'bg-sage-500/10 text-sage-700 border-sage-500/30 dark:text-sage-300',
|
||||
},
|
||||
connecting: {
|
||||
label: 'Connecting',
|
||||
i18nKey: 'channels.status.connecting',
|
||||
className: 'bg-amber-500/10 text-amber-700 border-amber-500/30 dark:text-amber-300',
|
||||
},
|
||||
disconnected: {
|
||||
label: 'Disconnected',
|
||||
i18nKey: 'channels.status.disconnected',
|
||||
className:
|
||||
'bg-stone-100 dark:bg-neutral-800 text-stone-500 dark:text-neutral-400 border-stone-200 dark:border-neutral-700',
|
||||
},
|
||||
error: {
|
||||
label: 'Error',
|
||||
i18nKey: 'channels.status.error',
|
||||
className: 'bg-coral-500/10 text-coral-700 border-coral-500/30 dark:text-coral-300',
|
||||
},
|
||||
};
|
||||
@@ -30,11 +33,14 @@ interface McpStatusBadgeProps {
|
||||
}
|
||||
|
||||
const McpStatusBadge = ({ status, className = '' }: McpStatusBadgeProps) => {
|
||||
const style = STATUS_STYLES[status] ?? STATUS_STYLES.disconnected;
|
||||
const { t } = useT();
|
||||
const meta = STATUS_META[status] ?? STATUS_META.disconnected;
|
||||
return (
|
||||
<span
|
||||
className={`shrink-0 px-2 py-1 text-[11px] border rounded-full ${style.className} ${className}`}>
|
||||
{style.label}
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
className={`shrink-0 px-2 py-1 text-[11px] border rounded-full ${meta.className} ${className}`}>
|
||||
{t(meta.i18nKey)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user