package ui import ( "bytes" "context" "io" "os" "path/filepath" "strings" "testing" "github.com/a-h/templ" ) func TestButtonRendersPrimaryMediumMarkup(t *testing.T) { component := Button(ButtonProps{ Label: "Nouveau projet", Variant: ButtonVariantPrimary, Size: SizeMD, Type: "button", }) html := renderToString(t, component) for _, want := range []string{ `type="button"`, `Nouveau projet`, `ui-button`, `ui-button-primary`, `ui-button-md`, } { if !strings.Contains(html, want) { t.Fatalf("expected %q in %q", want, html) } } } func TestIconButtonRendersBorderlessDestructiveMarkup(t *testing.T) { component := IconButton(IconButtonProps{ Label: "Supprimer le projet", Icon: "trash", Variant: IconButtonVariantDangerGhost, Type: "button", }) html := renderToString(t, component) for _, want := range []string{ `type="button"`, `aria-label="Supprimer le projet"`, `borderless-icon-button`, `lucide-trash2`, } { if !strings.Contains(html, want) { t.Fatalf("expected %q in %q", want, html) } } } func TestBadgeRendersSemanticStatusVariant(t *testing.T) { component := Badge(BadgeProps{ Label: "En cours", Variant: BadgeVariantWarning, }) html := renderToString(t, component) for _, want := range []string{ `ui-badge`, `ui-badge-warning`, `En cours`, } { if !strings.Contains(html, want) { t.Fatalf("expected %q in %q", want, html) } } } func TestModalRendersShellStructure(t *testing.T) { component := Modal(ModalProps{ Title: "Nouveau projet", Body: textComponent("Body copy"), Actions: textComponent("Actions"), }) html := renderToString(t, component) for _, want := range []string{ `ui-modal-backdrop`, `ui-modal-panel`, `Nouveau projet`, `Body copy`, `Actions`, } { if !strings.Contains(html, want) { t.Fatalf("expected %q in %q", want, html) } } } func TestButtonUsesSharedTokenBackedClasses(t *testing.T) { component := Button(ButtonProps{ Label: "Create", Variant: ButtonVariantPrimary, Size: SizeSM, Type: "button", }) html := renderToString(t, component) for _, want := range []string{ `ui-button`, `ui-button-primary`, `ui-button-sm`, } { if !strings.Contains(html, want) { t.Fatalf("expected %q in %q", want, html) } } } func TestSharedSemanticClassesExistInStylesheet(t *testing.T) { cssPath := filepath.Join("..", "..", "..", "static", "styles.css") body, err := os.ReadFile(cssPath) if err != nil { t.Fatalf("read stylesheet: %v", err) } css := string(body) for _, want := range []string{ `.ui-button-primary`, `.ui-button-sm`, `.ui-badge-warning`, `.ui-modal-panel`, `.borderless-icon-button`, } { if !strings.Contains(css, want) { t.Fatalf("expected stylesheet to contain %q", want) } } } func TestButtonRendersDangerLargeMarkup(t *testing.T) { component := Button(ButtonProps{ Label: "Supprimer", Variant: ButtonVariantDanger, Size: SizeLG, Type: "submit", }) html := renderToString(t, component) for _, want := range []string{ `type="submit"`, `ui-button-danger`, `ui-button-lg`, `Supprimer`, } { if !strings.Contains(html, want) { t.Fatalf("expected %q in %q", want, html) } } } func TestInputRendersSharedControlMarkup(t *testing.T) { component := Input(InputProps{ Name: "name", Value: "My project", Placeholder: "Nom du projet", Type: "text", }) html := renderToString(t, component) for _, want := range []string{ `name="name"`, `value="My project"`, `placeholder="Nom du projet"`, `class="ui-input"`, } { if !strings.Contains(html, want) { t.Fatalf("expected %q in %q", want, html) } } } func TestTextareaRendersSharedControlMarkup(t *testing.T) { component := Textarea(TextareaProps{ Name: "description", Value: "Longer copy", Placeholder: "Description", Rows: 4, }) html := renderToString(t, component) for _, want := range []string{ `name="description"`, `placeholder="Description"`, `rows="4"`, `class="ui-textarea"`, `Longer copy`, } { if !strings.Contains(html, want) { t.Fatalf("expected %q in %q", want, html) } } } func TestFormFieldRendersLabelAndError(t *testing.T) { component := FormField(FormFieldProps{ Label: "Nom", For: "tablo-name", Field: Input(InputProps{Name: "name", Type: "text"}), Error: "Le nom est requis", }) html := renderToString(t, component) for _, want := range []string{ `ui-form-field`, `for="tablo-name"`, `Nom`, `ui-form-error`, `Le nom est requis`, } { if !strings.Contains(html, want) { t.Fatalf("expected %q in %q", want, html) } } } func TestCardRendersSharedRegions(t *testing.T) { component := Card(CardProps{ Header: textComponent("Header"), Body: textComponent("Body"), Footer: textComponent("Footer"), }) html := renderToString(t, component) for _, want := range []string{ `ui-card`, `ui-card-header`, `ui-card-body`, `ui-card-footer`, `Body`, } { if !strings.Contains(html, want) { t.Fatalf("expected %q in %q", want, html) } } } func TestTableRendersSharedShell(t *testing.T) { component := Table(TableProps{ Head: textComponent("Projet"), Body: textComponent("Hello"), }) html := renderToString(t, component) for _, want := range []string{ `ui-table-shell`, `class="ui-table"`, ``, ``, `Projet`, `Hello`, } { if !strings.Contains(html, want) { t.Fatalf("expected %q in %q", want, html) } } } func TestEmptyStateRendersTitleDescriptionAndAction(t *testing.T) { component := EmptyState(EmptyStateProps{ Title: "Aucun projet", Description: "Créez votre premier projet.", Action: textComponent("Créer"), }) html := renderToString(t, component) for _, want := range []string{ `ui-empty-state`, `Aucun projet`, `Créez votre premier projet.`, `Créer`, } { if !strings.Contains(html, want) { t.Fatalf("expected %q in %q", want, html) } } } func renderToString(t *testing.T, component templ.Component) string { t.Helper() var buf bytes.Buffer if err := component.Render(context.Background(), &buf); err != nil { t.Fatalf("render component: %v", err) } return buf.String() } func textComponent(text string) templ.Component { return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error { _, err := w.Write([]byte(text)) return err }) }