package web import ( "net/http" "net/http/httptest" "strings" "testing" "time" "gardomatic.kleiax.de/lib/client" ) func TestJournalTemplateRendersMetadataMarkdownAndMedia(t *testing.T) { app := newTestApplication(t) garden := client.Garden{ID: 3, Name: "Hinterhof", Role: "owner"} entry := client.JournalEntry{ID: 7, GardenID: 3, AuthorName: "Ada", Title: "Ernte", Body: "**Drei** Tomaten.\n\n", CreatedAt: time.Date(2026, 9, 5, 14, 30, 0, 0, time.Local), Tags: []string{"tomaten"}, Attachments: []client.JournalAttachment{{ID: 9, EntryID: 7, FileName: "foto.jpg", MediaType: "image/jpeg", Size: 2048}}} response := httptest.NewRecorder() app.render(response, http.StatusOK, "journal.tmpl", &templateData{commonTemplateData: commonTemplateData{Garden: &garden}, journalTemplateData: journalTemplateData{JournalEntries: []client.JournalEntry{entry}}}) body := response.Body.String() for _, want := range []string{"Drei", "05.09.2026 · 14:30 Uhr", "Ada", "tomaten", "/g/3/journal/attachments/7/9", "Tagebuch", "data-card-href='/g/3/journal/edit/7'"} { if !strings.Contains(body, want) { t.Errorf("journal output missing %q: %s", want, body) } } if strings.Contains(body, "") { t.Fatalf("raw HTML was not escaped: %s", body) } if strings.Contains(body, ">Bearbeiten") { t.Fatalf("journal tile still exposes a separate edit link: %s", body) } } func TestJournalFormOffersBothEditorModesAndCaptureInputs(t *testing.T) { app := newTestApplication(t) garden := client.Garden{ID: 3, Name: "Hinterhof", Role: "owner"} response := httptest.NewRecorder() app.render(response, http.StatusOK, "journal_form.tmpl", &templateData{commonTemplateData: commonTemplateData{Garden: &garden, Form: journalForm{Body: "# Notiz", Errors: map[string]string{}}, UseJournalEditor: true}}) body := response.Body.String() for _, want := range []string{"toastui-editor-3.2.2.min.js", "data-journal-editor", "data-journal-body", "data-image-camera", "data-video-record", "data-video-pause", "data-video-apply", "Mediendateien auswählen", "Audio aufnehmen", "data-audio-dialog", "data-audio-preview", "data-audio-pause", "data-audio-apply", "data-recording-indicator", "name='embedded_images'", "data-tag-editor", "data-tag-suggestions"} { if !strings.Contains(body, want) { t.Errorf("journal form missing %q: %s", want, body) } } if strings.Contains(body, "data-image-preview") || strings.Contains(body, "data-image-file") { t.Fatalf("photo capture should expose one button, not a persistent preview/file picker: %s", body) } } func TestPinboardReusesEntryAndPhotoComponents(t *testing.T) { app := newTestApplication(t) garden := client.Garden{ID: 3, Name: "Hinterhof", Role: "owner"} entry := client.JournalEntry{ID: 8, GardenID: 3, EntryType: client.JournalEntryTypePinboard, AuthorName: "Ada", Title: "Sitzecke", Body: "Bank unter den Apfelbaum", CreatedAt: time.Date(2026, 9, 6, 10, 0, 0, 0, time.Local), Attachments: []client.JournalAttachment{{ID: 11, EntryID: 8, FileName: "idee.jpg", MediaType: "image/jpeg", Size: 1024}}} listResponse := httptest.NewRecorder() app.render(listResponse, http.StatusOK, "journal.tmpl", &templateData{commonTemplateData: commonTemplateData{Garden: &garden, ContentKind: client.JournalEntryTypePinboard}, journalTemplateData: journalTemplateData{JournalEntries: []client.JournalEntry{entry}}}) listBody := listResponse.Body.String() for _, want := range []string{"Pinnwand", "pinboard-list", "data-card-href='/g/3/pinboard/edit/8'", "/g/3/pinboard/attachments/8/11", "Bank unter den Apfelbaum"} { if !strings.Contains(listBody, want) { t.Errorf("pinboard output missing %q: %s", want, listBody) } } formResponse := httptest.NewRecorder() app.render(formResponse, http.StatusOK, "journal_form.tmpl", &templateData{commonTemplateData: commonTemplateData{Garden: &garden, ContentKind: client.JournalEntryTypePinboard, Form: journalForm{DateTime: "2026-09-06T10:00", Errors: map[string]string{}}, UseJournalEditor: true}, mediaTemplateData: mediaTemplateData{Images: []client.Image{{ID: 4, FileName: "vorhanden.jpg"}}}}) formBody := formResponse.Body.String() for _, want := range []string{"Neue Notiz", "Fotos auswählen", "Foto mit Gerät aufnehmen", "Aus Bilderdatenbank", "href='/g/3/pinboard'"} { if !strings.Contains(formBody, want) { t.Errorf("pinboard form missing %q: %s", want, formBody) } } if strings.Contains(formBody, "name='title' value='' maxlength='500' required") { t.Fatalf("pinboard title is still required: %s", formBody) } if strings.Contains(formBody, "data-video-record") || strings.Contains(formBody, "data-audio-record") { t.Fatalf("simple pinboard form exposes journal recording controls: %s", formBody) } } func TestPinboardSearchCardLinksBackToPinboard(t *testing.T) { card := gardenEntrySearchCard(3, client.JournalEntry{ID: 8, EntryType: client.JournalEntryTypePinboard, Title: "Sitzecke"}) if card.Type != "Pinnwand" || card.URL != "/g/3/pinboard#entry-8" { t.Fatalf("pinboard search card: %+v", card) } } func TestDashboardShowsJournalEntryCount(t *testing.T) { app := newTestApplication(t) garden := client.Garden{ID: 3, Name: "Hinterhof", Role: "owner"} entries := []client.JournalEntry{{ID: 1}, {ID: 2}, {ID: 3}} response := httptest.NewRecorder() app.render(response, http.StatusOK, "dashboard.tmpl", &templateData{commonTemplateData: commonTemplateData{Garden: &garden}, journalTemplateData: journalTemplateData{JournalEntries: entries, PinboardEntries: []client.JournalEntry{{ID: 4}}}}) body := response.Body.String() if !strings.Contains(body, "href='/g/3/journal'") || !strings.Contains(body, "3