From 992846855da578d1b9737b39cd2bcb88f914f27f Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Sat, 18 Apr 2026 22:44:25 +0700 Subject: [PATCH] test: meaningful escape check, single-slice donut, utcOffsetLabel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - I4 — TestRenderAll now seeds Name/Company with XML-significant chars that actually hit the render pipeline via cardTitle(). Previous test checked Bio which is no longer rendered, so the assertion was vacuous. - New TestDonutSingleSlice guards against the I1 empty-arc regression: asserts the single-slice path emits primitives and not the degenerate A-command path. - New TestDonutEmpty covers the zero-stats fallback. - New TestUTCOffsetLabel pins the UTC±N.NN format across UTC, Asia/Saigon, Asia/Kolkata (half-hour), Asia/Kathmandu (quarter-hour). Catches %+.2f regressions. --- internal/card/card_test.go | 44 +++++++++++++++++++++++++++++++++----- main_test.go | 31 +++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 5 deletions(-) create mode 100644 main_test.go diff --git a/internal/card/card_test.go b/internal/card/card_test.go index 5cb52ee6..91be3e37 100644 --- a/internal/card/card_test.go +++ b/internal/card/card_test.go @@ -11,10 +11,13 @@ import ( ) func TestRenderAll(t *testing.T) { + // Name is rendered in every card's title via cardTitle(). Putting + // XML-significant chars here exercises escapeXML through the real + // rendering pipeline, not just through the unit test below. p := &github.Profile{ Login: "tiennm99", - Name: "Minh Tien", - Bio: "Test & ", + Name: `Alice & "quoted"`, + Company: "VNG & ", Followers: 12, Following: 7, PublicRepos: 42, @@ -57,15 +60,46 @@ func TestRenderAll(t *testing.T) { if err != nil { t.Fatalf("read %s: %v", name, err) } - if !strings.HasPrefix(string(data), "") { - t.Errorf("%s: raw XML special characters leaked through escape", name) + // The raw Name string contains `&`, `<`, `>`, `"`. None should + // appear unescaped in the final markup. Presence of the escaped + // forms also verifies the title actually got rendered. + for _, leak := range []string{`Alice & `, `VNG & `, `"quoted"`} { + if strings.Contains(content, leak) { + t.Errorf("%s: raw XML special characters leaked through escape (%q)", name, leak) + } } } } +// TestDonutSingleSlice verifies the donut renderer handles a single-slice +// case (100%) with visible geometry. Regression guard against the empty-arc +// bug where start == end degenerates the SVG A command. +func TestDonutSingleSlice(t *testing.T) { + th, _ := theme.Lookup("dracula") + stats := []github.LangStat{{Name: "Go", Color: "#00ADD8", Value: 100}} + svg := string(renderDonutCard("Test", stats, th)) + + if !strings.Contains(svg, " primitives; got:\n%s", svg) + } + if strings.Contains(svg, `A70.00,70.00 0 1 1 380.00,50.00`) { + t.Error("single-slice donut still emits degenerate arc") + } +} + +// TestDonutEmpty verifies the zero-stats fallback path. +func TestDonutEmpty(t *testing.T) { + th, _ := theme.Lookup("dracula") + svg := string(renderDonutCard("Test", nil, th)) + if !strings.Contains(svg, "No data available") { + t.Errorf("empty donut should render the no-data fallback; got:\n%s", svg) + } +} + func TestFormatInt(t *testing.T) { cases := map[int]string{ 0: "0", diff --git a/main_test.go b/main_test.go new file mode 100644 index 00000000..eb0b812f --- /dev/null +++ b/main_test.go @@ -0,0 +1,31 @@ +package main + +import ( + "strings" + "testing" + "time" +) + +// TestUTCOffsetLabel checks that half-hour and quarter-hour zones render +// with a decimal, matching github-profile-summary-cards' "UTC+X.NN" style. +func TestUTCOffsetLabel(t *testing.T) { + cases := []struct { + zone string + want string // must appear in the label; exact value varies by DST + }{ + {"UTC", "UTC+0.00"}, + {"Asia/Saigon", "UTC+7.00"}, + {"Asia/Kolkata", "UTC+5.50"}, // half-hour zone + {"Asia/Kathmandu", "UTC+5.75"}, // quarter-hour zone + } + for _, tc := range cases { + loc, err := time.LoadLocation(tc.zone) + if err != nil { + t.Skipf("%s unavailable: %v", tc.zone, err) + } + got := utcOffsetLabel(loc) + if !strings.Contains(got, tc.want) { + t.Errorf("utcOffsetLabel(%q) = %q, want prefix %q", tc.zone, got, tc.want) + } + } +}