175 lines
5.2 KiB
Go
175 lines
5.2 KiB
Go
package screens
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
|
|
"github.com/santifer/career-ops/dashboard/internal/model"
|
|
"github.com/santifer/career-ops/dashboard/internal/theme"
|
|
)
|
|
|
|
func keyMsg(s string) tea.KeyMsg {
|
|
return tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune(s)}
|
|
}
|
|
|
|
func newPDFTestModel(t *testing.T, careerOpsPath string, apps []model.CareerApplication) PipelineModel {
|
|
t.Helper()
|
|
pm := NewPipelineModel(
|
|
theme.NewTheme("catppuccin-mocha"),
|
|
apps,
|
|
model.PipelineMetrics{Total: len(apps)},
|
|
careerOpsPath,
|
|
120,
|
|
40,
|
|
)
|
|
pm.viewMode = "flat"
|
|
pm.applyFilterAndSort()
|
|
return pm
|
|
}
|
|
|
|
func writePDFFixture(t *testing.T, root, rel string) {
|
|
t.Helper()
|
|
full := filepath.Join(root, filepath.FromSlash(rel))
|
|
if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil {
|
|
t.Fatalf("mkdir: %v", err)
|
|
}
|
|
if err := os.WriteFile(full, []byte("pdf"), 0o644); err != nil {
|
|
t.Fatalf("write: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestPDFKeyFlashesWhenNoPDFExists(t *testing.T) {
|
|
root := t.TempDir()
|
|
apps := []model.CareerApplication{
|
|
{Company: "Globex", Role: "Engineer", Status: "Evaluated", Score: 4.0},
|
|
}
|
|
|
|
pm := newPDFTestModel(t, root, apps)
|
|
updated, cmd := pm.Update(keyMsg("d"))
|
|
|
|
if cmd != nil {
|
|
t.Fatal("expected no command when no PDF matches")
|
|
}
|
|
if updated.flash == "" {
|
|
t.Fatal("expected a flash notice when no PDF matches")
|
|
}
|
|
if updated.pdfPicker {
|
|
t.Fatal("expected no picker when no PDF matches")
|
|
}
|
|
}
|
|
|
|
func TestPDFKeyOpensSingleMatchDirectly(t *testing.T) {
|
|
root := t.TempDir()
|
|
writePDFFixture(t, root, "output/cv-jane-doe-globex-2026-06-05.pdf")
|
|
apps := []model.CareerApplication{
|
|
{Company: "Globex", Role: "Engineer", Status: "Evaluated", Score: 4.0},
|
|
}
|
|
|
|
pm := newPDFTestModel(t, root, apps)
|
|
updated, cmd := pm.Update(keyMsg("d"))
|
|
|
|
if updated.pdfPicker {
|
|
t.Fatal("expected no picker for a single match")
|
|
}
|
|
if cmd == nil {
|
|
t.Fatal("expected an open command for a single match")
|
|
}
|
|
msg, ok := cmd().(PipelineOpenPDFMsg)
|
|
if !ok {
|
|
t.Fatalf("expected PipelineOpenPDFMsg, got %T", cmd())
|
|
}
|
|
if !strings.HasSuffix(msg.Path, "cv-jane-doe-globex-2026-06-05.pdf") {
|
|
t.Fatalf("unexpected PDF path %q", msg.Path)
|
|
}
|
|
}
|
|
|
|
func TestPDFKeyOpensNewestForMultipleMatches(t *testing.T) {
|
|
root := t.TempDir()
|
|
// Write two PDFs for the same company with distinct dates so ordering is predictable.
|
|
writePDFFixture(t, root, "output/cv-jane-doe-anthropic-2026-06-05.pdf")
|
|
writePDFFixture(t, root, "output/cv-jane-doe-anthropic-2026-06-10.pdf")
|
|
apps := []model.CareerApplication{
|
|
{Company: "Anthropic", Role: "Staff UI Engineer", Status: "Evaluated", Score: 4.6},
|
|
}
|
|
|
|
pm := newPDFTestModel(t, root, apps)
|
|
updated, cmd := pm.Update(keyMsg("d"))
|
|
|
|
// Multiple candidates → open the newest one immediately, no picker.
|
|
if updated.pdfPicker {
|
|
t.Fatal("expected no picker — newest match should be opened directly")
|
|
}
|
|
if cmd == nil {
|
|
t.Fatal("expected an open command for the newest match")
|
|
}
|
|
msg, ok := cmd().(PipelineOpenPDFMsg)
|
|
if !ok {
|
|
t.Fatalf("expected PipelineOpenPDFMsg, got %T", cmd())
|
|
}
|
|
if !strings.HasSuffix(msg.Path, "cv-jane-doe-anthropic-2026-06-10.pdf") {
|
|
t.Fatalf("expected newest PDF to be opened, got %q", msg.Path)
|
|
}
|
|
}
|
|
|
|
func TestRegenerateKeyFlashesWithoutManifestEntry(t *testing.T) {
|
|
root := t.TempDir()
|
|
apps := []model.CareerApplication{
|
|
{Company: "Globex", Role: "Engineer", Status: "Evaluated", Score: 4.0, ReportNumber: "001"},
|
|
}
|
|
|
|
pm := newPDFTestModel(t, root, apps)
|
|
updated, cmd := pm.Update(keyMsg("D"))
|
|
|
|
if cmd != nil {
|
|
t.Fatal("expected no command without a manifest entry")
|
|
}
|
|
if updated.flash == "" {
|
|
t.Fatal("expected a flash notice without a manifest entry")
|
|
}
|
|
}
|
|
|
|
func TestRegenerateKeyEmitsGenerateMsgFromManifest(t *testing.T) {
|
|
root := t.TempDir()
|
|
writePDFFixture(t, root, "output/cv-jane-doe-globex.html")
|
|
writePDFFixture(t, root, "data/pdf-index.tsv") // placeholder, overwritten below
|
|
manifest := "001\toutput/cv-jane-doe-globex-2026-06-05.pdf\toutput/cv-jane-doe-globex.html\tletter\t2026-06-05\n"
|
|
if err := os.WriteFile(filepath.Join(root, "data", "pdf-index.tsv"), []byte(manifest), 0o644); err != nil {
|
|
t.Fatalf("write manifest: %v", err)
|
|
}
|
|
apps := []model.CareerApplication{
|
|
{Company: "Globex", Role: "Engineer", Status: "Evaluated", Score: 4.0, ReportNumber: "001"},
|
|
}
|
|
|
|
pm := newPDFTestModel(t, root, apps)
|
|
updated, cmd := pm.Update(keyMsg("D"))
|
|
|
|
if cmd == nil {
|
|
t.Fatal("expected a generate command")
|
|
}
|
|
if updated.flash == "" {
|
|
t.Fatal("expected an in-progress flash while regenerating")
|
|
}
|
|
msg, ok := cmd().(PipelineGeneratePDFMsg)
|
|
if !ok {
|
|
t.Fatalf("expected PipelineGeneratePDFMsg, got %T", cmd())
|
|
}
|
|
if msg.ReportNumber != "001" || msg.HTMLPath != "output/cv-jane-doe-globex.html" || msg.Format != "letter" {
|
|
t.Fatalf("unexpected generate request: %+v", msg)
|
|
}
|
|
|
|
// Outcome message updates the flash.
|
|
done, _ := updated.Update(PipelinePDFGeneratedMsg{Path: "/abs/cv.pdf"})
|
|
if !strings.Contains(done.flash, "cv.pdf") {
|
|
t.Fatalf("expected success flash to name the PDF, got %q", done.flash)
|
|
}
|
|
failed, _ := updated.Update(PipelinePDFGeneratedMsg{Err: "node not found"})
|
|
if !strings.Contains(failed.flash, "node not found") {
|
|
t.Fatalf("expected failure flash to carry the error, got %q", failed.flash)
|
|
}
|
|
}
|
|
|