diff --git a/backend/internal/web/ui/ui_test.go b/backend/internal/web/ui/ui_test.go
index 4000ad1..6bab7f7 100644
--- a/backend/internal/web/ui/ui_test.go
+++ b/backend/internal/web/ui/ui_test.go
@@ -346,3 +346,54 @@ func TestFormField_NoErrorWhenEmpty(t *testing.T) {
t.Errorf("expected NO ui-form-error in output when Error is empty; got: %s", out)
}
}
+
+// Phase 13 Plan 04 — Modal, EmptyState, Table component tests (TDD RED)
+
+func TestModal_RendersBackdropAndPanel(t *testing.T) {
+ out := render(t, context.Background(), Modal(ModalProps{Title: "Confirm action"}))
+ for _, want := range []string{"ui-modal-backdrop", "ui-modal-panel"} {
+ if !strings.Contains(out, want) {
+ t.Errorf("expected %q in output; got: %s", want, out)
+ }
+ }
+}
+
+func TestModal_RendersTitle(t *testing.T) {
+ out := render(t, context.Background(), Modal(ModalProps{Title: "Confirm action"}))
+ // Title must appear inside a heading element
+ if !strings.Contains(out, "
") || !strings.Contains(out, "Confirm action") {
+ t.Errorf("expected with title text in output; got: %s", out)
+ }
+}
+
+func TestModal_NilBodyOmitted(t *testing.T) {
+ out := render(t, context.Background(), Modal(ModalProps{Title: "Confirm action"}))
+ if strings.Contains(out, "ui-modal-body") {
+ t.Errorf("expected NO ui-modal-body when Body is nil; got: %s", out)
+ }
+}
+
+func TestEmptyState_RendersTitle(t *testing.T) {
+ out := render(t, context.Background(), EmptyState(EmptyStateProps{Title: "Nothing here yet"}))
+ for _, want := range []string{"ui-empty-state", "Nothing here yet"} {
+ if !strings.Contains(out, want) {
+ t.Errorf("expected %q in output; got: %s", want, out)
+ }
+ }
+}
+
+func TestEmptyState_NilIconOmitted(t *testing.T) {
+ out := render(t, context.Background(), EmptyState(EmptyStateProps{Title: "Nothing here yet"}))
+ if strings.Contains(out, "ui-empty-state-icon") {
+ t.Errorf("expected NO ui-empty-state-icon when Icon is nil; got: %s", out)
+ }
+}
+
+func TestTable_RendersShell(t *testing.T) {
+ out := render(t, context.Background(), Table(TableProps{}))
+ for _, want := range []string{"ui-table-shell", "ui-table"} {
+ if !strings.Contains(out, want) {
+ t.Errorf("expected %q in output; got: %s", want, out)
+ }
+ }
+}