summaryrefslogtreecommitdiff
path: root/internal/perc
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-23 21:11:32 +0300
committerPaul Buetow <paul@buetow.org>2026-05-23 21:11:32 +0300
commit5ea6e73fda9fc12dbccf472b116a69eed0871a37 (patch)
treee62b1b0b1abbd3f4f1b43ab60ebfca7cfcbab105 /internal/perc
parent440cd6adf44324584ab784389a373058fa93c3c8 (diff)
test: add unit tests for ParseCalculation() and Calculation.Format()
Add tests for ParseCalculation() exported API and Calculation struct: - ParseCalculation returns correct *Calculation for all 3 types (PercentOfY, IsWhatPercentOfY, IsYPercentOfWhat) - Correct field values: Type, Percent, Base, Result, Steps - 'what is' prefix stripping via ParseCalculation - Case insensitivity via ParseCalculation - Division by zero edge cases via ParseCalculation - Invalid input returns error via ParseCalculation - Calculation.Format() for each CalculationType - Calculation.Format() with and without Steps - Decimal and large value handling
Diffstat (limited to 'internal/perc')
-rw-r--r--internal/perc/perc_test.go247
1 files changed, 247 insertions, 0 deletions
diff --git a/internal/perc/perc_test.go b/internal/perc/perc_test.go
index d96fabb..f8d6f97 100644
--- a/internal/perc/perc_test.go
+++ b/internal/perc/perc_test.go
@@ -280,3 +280,250 @@ func TestParseWhitespace(t *testing.T) {
})
}
}
+
+// TestParseCalculationPercentOfY tests ParseCalculation for "X% of Y" type
+func TestParseCalculationPercentOfY(t *testing.T) {
+ calc, err := ParseCalculation("20% of 150")
+ if err != nil {
+ t.Fatalf("ParseCalculation returned error: %v", err)
+ }
+ if calc == nil {
+ t.Fatal("ParseCalculation returned nil")
+ }
+ if calc.Type != PercentOfY {
+ t.Errorf("Type = %v, want PercentOfY", calc.Type)
+ }
+ if calc.Percent != 20 {
+ t.Errorf("Percent = %v, want 20", calc.Percent)
+ }
+ if calc.Base != 150 {
+ t.Errorf("Base = %v, want 150", calc.Base)
+ }
+ if calc.Result != 30 {
+ t.Errorf("Result = %v, want 30", calc.Result)
+ }
+ if calc.Steps == "" {
+ t.Error("Steps should not be empty")
+ }
+}
+
+// TestParseCalculationIsWhatPercentOfY tests ParseCalculation for "X is what % of Y" type
+func TestParseCalculationIsWhatPercentOfY(t *testing.T) {
+ calc, err := ParseCalculation("30 is what % of 150")
+ if err != nil {
+ t.Fatalf("ParseCalculation returned error: %v", err)
+ }
+ if calc == nil {
+ t.Fatal("ParseCalculation returned nil")
+ }
+ if calc.Type != IsWhatPercentOfY {
+ t.Errorf("Type = %v, want IsWhatPercentOfY", calc.Type)
+ }
+ if calc.Percent != 20 {
+ t.Errorf("Percent = %v, want 20", calc.Percent)
+ }
+ if calc.Base != 150 {
+ t.Errorf("Base = %v, want 150", calc.Base)
+ }
+ if calc.Result != 30 {
+ t.Errorf("Result = %v, want 30", calc.Result)
+ }
+}
+
+// TestParseCalculationIsYPercentOfWhat tests ParseCalculation for "X is Y% of what" type
+func TestParseCalculationIsYPercentOfWhat(t *testing.T) {
+ calc, err := ParseCalculation("30 is 20% of what")
+ if err != nil {
+ t.Fatalf("ParseCalculation returned error: %v", err)
+ }
+ if calc == nil {
+ t.Fatal("ParseCalculation returned nil")
+ }
+ if calc.Type != IsYPercentOfWhat {
+ t.Errorf("Type = %v, want IsYPercentOfWhat", calc.Type)
+ }
+ if calc.Percent != 20 {
+ t.Errorf("Percent = %v, want 20", calc.Percent)
+ }
+ if calc.Base != 150 {
+ t.Errorf("Base = %v, want 150", calc.Base)
+ }
+ if calc.Result != 30 {
+ t.Errorf("Result = %v, want 30", calc.Result)
+ }
+}
+
+// TestParseCalculationWhatIsPrefix tests "what is" prefix stripping
+func TestParseCalculationWhatIsPrefix(t *testing.T) {
+ calc, err := ParseCalculation("what is 20% of 150")
+ if err != nil {
+ t.Fatalf("ParseCalculation returned error: %v", err)
+ }
+ if calc.Percent != 20 || calc.Base != 150 || calc.Result != 30 {
+ t.Errorf("ParseCalculation with 'what is' prefix: Percent=%v, Base=%v, Result=%v",
+ calc.Percent, calc.Base, calc.Result)
+ }
+}
+
+// TestParseCalculationCaseInsensitive tests case insensitivity
+func TestParseCalculationCaseInsensitive(t *testing.T) {
+ tests := []struct {
+ input string
+ wantType CalculationType
+ }{
+ {"20% OF 150", PercentOfY},
+ {"WHAT IS 20% OF 150", PercentOfY},
+ {"30 IS WHAT % OF 150", IsWhatPercentOfY},
+ {"30 IS 20% OF WHAT", IsYPercentOfWhat},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.input, func(t *testing.T) {
+ calc, err := ParseCalculation(tt.input)
+ if err != nil {
+ t.Fatalf("ParseCalculation(%q) returned error: %v", tt.input, err)
+ }
+ if calc.Type != tt.wantType {
+ t.Errorf("Type = %v, want %v", calc.Type, tt.wantType)
+ }
+ })
+ }
+}
+
+// TestParseCalculationDivisionByZero tests division by zero edge cases
+func TestParseCalculationDivisionByZero(t *testing.T) {
+ tests := []struct {
+ name string
+ input string
+ }{
+ {"X is what % of 0", "30 is what % of 0"},
+ {"X is 0% of what", "30 is 0% of what"},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ _, err := ParseCalculation(tt.input)
+ if err == nil {
+ t.Errorf("ParseCalculation(%q) should return error for division by zero", tt.input)
+ }
+ })
+ }
+}
+
+// TestParseCalculationInvalidInput tests invalid inputs return errors
+func TestParseCalculationInvalidInput(t *testing.T) {
+ tests := []string{
+ "hello world",
+ "20%",
+ "% of",
+ "calculate percentage",
+ "",
+ }
+
+ for _, input := range tests {
+ t.Run(input, func(t *testing.T) {
+ _, err := ParseCalculation(input)
+ if err == nil {
+ t.Errorf("ParseCalculation(%q) should return error", input)
+ }
+ })
+ }
+}
+
+// TestCalculationFormatPercentOfY tests Format() for PercentOfY type
+func TestCalculationFormatPercentOfY(t *testing.T) {
+ calc := &Calculation{
+ Type: PercentOfY,
+ Percent: 20,
+ Base: 150,
+ Result: 30,
+ Steps: "(20 / 100) * 150 = 0.20 * 150 = 30",
+ }
+
+ formatted := calc.Format()
+ if !strings.HasPrefix(formatted, "20.00% of 150.00 = 30.00") {
+ t.Errorf("Format() = %q, expected to start with '20.00%% of 150.00 = 30.00'", formatted)
+ }
+ if !strings.Contains(formatted, "Steps:") {
+ t.Error("Format() should contain Steps")
+ }
+}
+
+// TestCalculationFormatIsWhatPercentOfY tests Format() for IsWhatPercentOfY type
+func TestCalculationFormatIsWhatPercentOfY(t *testing.T) {
+ calc := &Calculation{
+ Type: IsWhatPercentOfY,
+ Percent: 20,
+ Base: 150,
+ Result: 30,
+ Steps: "",
+ }
+
+ formatted := calc.Format()
+ if !strings.HasPrefix(formatted, "30.00 is 20.00% of 150.00") {
+ t.Errorf("Format() = %q, expected to start with '30.00 is 20.00%% of 150.00'", formatted)
+ }
+}
+
+// TestCalculationFormatIsYPercentOfWhat tests Format() for IsYPercentOfWhat type
+func TestCalculationFormatIsYPercentOfWhat(t *testing.T) {
+ calc := &Calculation{
+ Type: IsYPercentOfWhat,
+ Percent: 20,
+ Base: 150,
+ Result: 30,
+ Steps: "",
+ }
+
+ formatted := calc.Format()
+ if !strings.HasPrefix(formatted, "30.00 is 20.00% of 150.00") {
+ t.Errorf("Format() = %q, expected to start with '30.00 is 20.00%% of 150.00'", formatted)
+ }
+}
+
+// TestCalculationFormatWithoutSteps tests Format() without Steps field
+func TestCalculationFormatWithoutSteps(t *testing.T) {
+ calc := &Calculation{
+ Type: PercentOfY,
+ Percent: 25,
+ Base: 100,
+ Result: 25,
+ Steps: "",
+ }
+
+ formatted := calc.Format()
+ if formatted != "25.00% of 100.00 = 25.00" {
+ t.Errorf("Format() without steps = %q, want '25.00%% of 100.00 = 25.00'", formatted)
+ }
+ if strings.Contains(formatted, "Steps") {
+ t.Error("Format() should not contain 'Steps' when Steps is empty")
+ }
+}
+
+// TestParseCalculationDecimalValues tests decimal values in ParseCalculation
+func TestParseCalculationDecimalValues(t *testing.T) {
+ calc, err := ParseCalculation("12.5% of 80")
+ if err != nil {
+ t.Fatalf("ParseCalculation returned error: %v", err)
+ }
+ if calc.Percent != 12.5 {
+ t.Errorf("Percent = %v, want 12.5", calc.Percent)
+ }
+ if calc.Result != 10 {
+ t.Errorf("Result = %v, want 10", calc.Result)
+ }
+}
+
+// TestParseCalculationLargeValues tests large values in ParseCalculation
+func TestParseCalculationLargeValues(t *testing.T) {
+ calc, err := ParseCalculation("50% of 1000000")
+ if err != nil {
+ t.Fatalf("ParseCalculation returned error: %v", err)
+ }
+ if calc.Percent != 50 {
+ t.Errorf("Percent = %v, want 50", calc.Percent)
+ }
+ if calc.Result != 500000 {
+ t.Errorf("Result = %v, want 500000", calc.Result)
+ }
+}