package jtalk import ( "reflect" "testing" ) func TestIsJapaneseChar(t *testing.T) { tests := []struct { name string r rune want bool }{ {"ascii letter", 'a', true}, {"ascii digit", '5', true}, {"hiragana", 'お', true}, {"katakana", 'ー', true}, // U+30FC, within ぀-ヿ {"kanji", '様', true}, {"iteration mark", '々', true}, {"fullwidth digit 1-9", '1', true}, {"fullwidth digit 0 excluded (quirk in the original regex)", '0', false}, {"fullwidth letter", 'A', true}, {"halfwidth katakana", 'ヲ', true}, {"japanese comma is a mark, not a char", '、', false}, {"japanese period is a mark", '。', false}, {"ascii punctuation is a mark", '!', false}, {"space is a mark", ' ', false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := isJapaneseChar(tt.r); got != tt.want { t.Errorf("isJapaneseChar(%q) = %v, want %v", tt.r, got, tt.want) } }) } } func TestSplitByMarks(t *testing.T) { tests := []struct { name string text string wantSpans []string wantMarks []rune }{ { name: "no marks", text: "おはよう", wantSpans: []string{"おはよう"}, wantMarks: nil, }, { name: "one internal mark", text: "おにー様、すきです", wantSpans: []string{"おにー様", "すきです"}, wantMarks: []rune{'、'}, }, { name: "consecutive marks produce an empty span between them", text: "すごい!!すごい", wantSpans: []string{"すごい", "", "すごい"}, wantMarks: []rune{'!', '!'}, }, { name: "trailing mark leaves an empty final span", text: "ありがとう。", wantSpans: []string{"ありがとう", ""}, wantMarks: []rune{'。'}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gotSpans, gotMarks := splitByMarks(tt.text) if !reflect.DeepEqual(gotSpans, tt.wantSpans) { t.Errorf("splitByMarks() spans = %#v, want %#v", gotSpans, tt.wantSpans) } if !reflect.DeepEqual(gotMarks, tt.wantMarks) { t.Errorf("splitByMarks() marks = %#v, want %#v", gotMarks, tt.wantMarks) } }) } } func TestMarkToASCII(t *testing.T) { tests := []struct { r rune want string }{ {'、', ","}, {'。', "."}, {'!', "!"}, {'!', "!"}, // ASCII passthrough {'(', "("}, // ASCII passthrough {'鳥', ""}, // unmapped non-ASCII rune falls back to empty, not a guess } for _, tt := range tests { if got := markToASCII(tt.r); got != tt.want { t.Errorf("markToASCII(%q) = %q, want %q", tt.r, got, tt.want) } } } func TestParseOutputLabelPhonemes(t *testing.T) { t.Run("strips boundary sil and ignores content after the section", func(t *testing.T) { raw := "[Text analysis result]\n" + "some,morphological,analysis,-1\n" + "\n[Output label]\n" + "0 100 xx^xx-sil+o=n/A:xx+xx+xx\n" + "100 200 xx^sil-o+n=i/A:0+1+5\n" + "200 300 sil^o-n+i=i/A:1+2+4\n" + "300 400 o^n-i+sil=xx/A:1+2+4\n" + "\n[Global parameter]\n" + "Some parameter -> value+with+plusses\n" got, err := parseOutputLabelPhonemes(raw) if err != nil { t.Fatalf("parseOutputLabelPhonemes() error = %v", err) } want := []string{"o", "n", "i"} if !reflect.DeepEqual(got, want) { t.Fatalf("parseOutputLabelPhonemes() = %#v, want %#v (must not include [Global parameter] content)", got, want) } }) t.Run("missing section returns an error", func(t *testing.T) { _, err := parseOutputLabelPhonemes("no label section here") if err == nil { t.Fatal("parseOutputLabelPhonemes() error = nil, want non-nil") } }) }