feat(10-02): create RemoteNamePreview component with TDD

- Stateless component accepting value prop (string)
- Empty/whitespace state: shows grayed [my-remote] placeholder + guidance text
- Value state: shows [typed-value] in on-surface-variant color
- Renders only div/span elements — no form controls
- 9 tests covering empty state, value state, and element type constraints
This commit is contained in:
2026-04-01 12:48:50 +02:00
parent 8078664f05
commit 3c9c5fd7d8
2 changed files with 80 additions and 0 deletions
@@ -0,0 +1,57 @@
// @vitest-environment jsdom
import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
import { RemoteNamePreview } from './RemoteNamePreview';
describe('RemoteNamePreview', () => {
describe('empty state', () => {
it('shows placeholder [my-remote] when value is empty string', () => {
render(<RemoteNamePreview value="" />);
expect(screen.getByText('[my-remote]')).toBeDefined();
});
it('shows placeholder [my-remote] when value is whitespace-only', () => {
render(<RemoteNamePreview value=" " />);
expect(screen.getByText('[my-remote]')).toBeDefined();
});
it('shows guidance text when value is empty', () => {
render(<RemoteNamePreview value="" />);
expect(screen.getByText('Type a name to see how it appears in your config')).toBeDefined();
});
});
describe('value state', () => {
it('shows [my-backup] when value is "my-backup"', () => {
render(<RemoteNamePreview value="my-backup" />);
expect(screen.getByText('[my-backup]')).toBeDefined();
});
it('shows [azure-prod] when value is "azure-prod"', () => {
render(<RemoteNamePreview value="azure-prod" />);
expect(screen.getByText('[azure-prod]')).toBeDefined();
});
it('does NOT show guidance text when value is provided', () => {
render(<RemoteNamePreview value="my-backup" />);
expect(screen.queryByText('Type a name to see how it appears in your config')).toBeNull();
});
});
describe('element types', () => {
it('renders no form controls (no textbox role)', () => {
render(<RemoteNamePreview value="" />);
expect(screen.queryByRole('textbox')).toBeNull();
});
it('renders no input elements', () => {
const { container } = render(<RemoteNamePreview value="test" />);
expect(container.querySelector('input')).toBeNull();
});
it('renders no textarea elements', () => {
const { container } = render(<RemoteNamePreview value="test" />);
expect(container.querySelector('textarea')).toBeNull();
});
});
});
+23
View File
@@ -0,0 +1,23 @@
interface RemoteNamePreviewProps {
value: string;
}
export function RemoteNamePreview({ value }: RemoteNamePreviewProps) {
const trimmed = value.trim();
const isEmpty = trimmed.length === 0;
return (
<div className="mt-2 rounded-md bg-surface-variant px-3 py-2 text-xs font-mono">
{isEmpty ? (
<>
<span className="text-on-surface-variant/50">[my-remote]</span>
<p className="mt-1 text-on-surface-variant/40 font-sans">
Type a name to see how it appears in your config
</p>
</>
) : (
<span className="text-on-surface-variant">[{trimmed}]</span>
)}
</div>
);
}