Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions internal/archdocs/pssg/schema/jsonld.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,9 +290,10 @@ func stepName(step string) string {
return step[:idx+1]
}
}
// Truncate if too long
if len(step) > 80 {
return step[:77] + "..."
// Truncate if too long (rune-aware to avoid splitting multi-byte UTF-8).
runes := []rune(step)
if len(runes) > 80 {
return string(runes[:77]) + "..."
}
return step
}
Expand Down
48 changes: 48 additions & 0 deletions internal/archdocs/pssg/schema/jsonld_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,57 @@
package schema

import (
"strings"
"testing"
"unicode/utf8"
)

func TestStepName_ShortStep(t *testing.T) {
short := "Mix well."
if got := stepName(short); got != short {
t.Errorf("stepName(%q) = %q, want unchanged", short, got)
}
}

func TestStepName_FirstSentence(t *testing.T) {
step := "Sauté the onions. Then add garlic and cook for 2 more minutes."
got := stepName(step)
if got != "Sauté the onions." {
t.Errorf("stepName first-sentence: got %q", got)
}
}

func TestStepName_TruncatesLongASCII(t *testing.T) {
long := strings.Repeat("a", 81)
got := stepName(long)
if !strings.HasSuffix(got, "...") {
t.Errorf("long step should end with '...', got %q", got)
}
if len([]rune(got)) > 80 {
t.Errorf("truncated step too long: %d runes", len([]rune(got)))
}
}

// TestStepName_MultiByteUTF8 is the regression test for the byte-based slicing
// bug: a step whose byte length exceeds 80 but rune count is near the boundary
// must produce valid UTF-8 when truncated.
func TestStepName_MultiByteUTF8(t *testing.T) {
// "é" is 2 bytes. 75 ASCII chars + 4 "é" = 75 + 8 = 83 bytes > 80,
// but only 79 runes — just under the limit.
// A step with 81 runes of "é" should be truncated to 77 "é" + "...".
long := strings.Repeat("é", 81) // 162 bytes, 81 runes
got := stepName(long)
if !utf8.ValidString(got) {
t.Errorf("stepName output contains invalid UTF-8: %q", got)
}
if !strings.HasSuffix(got, "...") {
t.Errorf("long multi-byte step should be truncated with '...', got %q", got)
}
if strings.Contains(got, strings.Repeat("é", 81)) {
t.Errorf("long multi-byte step should be truncated, not returned in full")
}
}

func TestParseDurationMinutes(t *testing.T) {
cases := []struct {
input string
Expand Down