313 lines
6.3 KiB
Go
313 lines
6.3 KiB
Go
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("<tr><th>Projet</th></tr>"),
|
|
Body: textComponent("<tr><td>Hello</td></tr>"),
|
|
})
|
|
|
|
html := renderToString(t, component)
|
|
|
|
for _, want := range []string{
|
|
`ui-table-shell`,
|
|
`class="ui-table"`,
|
|
`<thead>`,
|
|
`<tbody>`,
|
|
`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
|
|
})
|
|
}
|