// Copyright (c) 2025 IBM Corp. // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package option import ( "fmt" "slices" "strconv" "testing" F "github.com/IBM/fp-go/v2/function" "github.com/stretchr/testify/assert" ) // Helper function to create a sequence from a slice func seqFromSlice[T any](items []T) Seq[T] { return slices.Values(items) } // Helper function to collect a sequence into a slice func collectSeq[T any](seq Seq[T]) []T { return slices.Collect(seq) } func TestTraverseIter_AllSome(t *testing.T) { // Test case where all transformations succeed parse := func(s string) Option[int] { n, err := strconv.Atoi(s) if err != nil { return None[int]() } return Some(n) } input := seqFromSlice([]string{"1", "2", "3", "4", "5"}) result := TraverseIter(parse)(input) assert.True(t, IsSome(result), "Expected Some result when all transformations succeed") collected := MonadFold(result, func() []int { return nil }, collectSeq[int]) expected := []int{1, 2, 3, 4, 5} assert.Equal(t, expected, collected) } func TestTraverseIter_ContainsNone(t *testing.T) { // Test case where one transformation fails parse := func(s string) Option[int] { n, err := strconv.Atoi(s) if err != nil { return None[int]() } return Some(n) } input := seqFromSlice([]string{"1", "invalid", "3"}) result := TraverseIter(parse)(input) assert.True(t, IsNone(result), "Expected None when any transformation fails") } func TestTraverseIter_EmptySequence(t *testing.T) { // Test with empty sequence double := func(x int) Option[int] { return Some(x * 2) } input := seqFromSlice([]int{}) result := TraverseIter(double)(input) assert.True(t, IsSome(result), "Expected Some for empty sequence") collected := MonadFold(result, func() []int { return nil }, collectSeq[int]) assert.Empty(t, collected) } func TestTraverseIter_SingleElement(t *testing.T) { // Test with single element - success case validate := func(x int) Option[int] { if x > 0 { return Some(x * 2) } return None[int]() } input := seqFromSlice([]int{5}) result := TraverseIter(validate)(input) assert.True(t, IsSome(result)) collected := MonadFold(result, func() []int { return nil }, collectSeq[int]) assert.Equal(t, []int{10}, collected) } func TestTraverseIter_SingleElementFails(t *testing.T) { // Test with single element - failure case validate := func(x int) Option[int] { if x > 0 { return Some(x * 2) } return None[int]() } input := seqFromSlice([]int{-5}) result := TraverseIter(validate)(input) assert.True(t, IsNone(result)) } func TestTraverseIter_Validation(t *testing.T) { // Test validation use case validatePositive := func(x int) Option[int] { if x > 0 { return Some(x) } return None[int]() } // All positive input1 := seqFromSlice([]int{1, 2, 3, 4}) result1 := TraverseIter(validatePositive)(input1) assert.True(t, IsSome(result1)) // Contains negative input2 := seqFromSlice([]int{1, -2, 3}) result2 := TraverseIter(validatePositive)(input2) assert.True(t, IsNone(result2)) // Contains zero input3 := seqFromSlice([]int{1, 0, 3}) result3 := TraverseIter(validatePositive)(input3) assert.True(t, IsNone(result3)) } func TestTraverseIter_Transformation(t *testing.T) { // Test transformation use case safeDivide := func(x int) Option[float64] { if x != 0 { return Some(100.0 / float64(x)) } return None[float64]() } // All non-zero input1 := seqFromSlice([]int{1, 2, 4, 5}) result1 := TraverseIter(safeDivide)(input1) assert.True(t, IsSome(result1)) collected := MonadFold(result1, func() []float64 { return nil }, collectSeq[float64]) expected := []float64{100.0, 50.0, 25.0, 20.0} assert.Equal(t, expected, collected) // Contains zero input2 := seqFromSlice([]int{1, 0, 4}) result2 := TraverseIter(safeDivide)(input2) assert.True(t, IsNone(result2)) } func TestTraverseIter_ShortCircuit(t *testing.T) { // Test that traversal short-circuits on first None callCount := 0 countingFunc := func(x int) Option[int] { callCount++ if x < 0 { return None[int]() } return Some(x * 2) } // First element fails input := seqFromSlice([]int{-1, 2, 3, 4, 5}) result := TraverseIter(countingFunc)(input) assert.True(t, IsNone(result)) // Should have called the function for elements until the first failure // Note: The exact count depends on implementation details of the traverse function assert.Greater(t, callCount, 0, "Function should be called at least once") } func TestTraverseIter_LazyEvaluation(t *testing.T) { // Test that the result sequence is lazy transform := func(x int) Option[int] { return Some(x * 2) } input := seqFromSlice([]int{1, 2, 3, 4, 5}) result := TraverseIter(transform)(input) assert.True(t, IsSome(result)) // Partially consume the sequence callCount := 0 MonadFold(result, func() int { return 0 }, func(seq Seq[int]) int { for val := range seq { callCount++ _ = val if callCount == 2 { break } } return callCount }) assert.Equal(t, 2, callCount, "Should only evaluate consumed elements") } func TestTraverseIter_ComplexTransformation(t *testing.T) { // Test with more complex transformation type Person struct { Name string Age int } validatePerson := func(name string) Option[Person] { if name == "" { return None[Person]() } return Some(Person{Name: name, Age: len(name)}) } input := seqFromSlice([]string{"Alice", "Bob", "Charlie"}) result := TraverseIter(validatePerson)(input) assert.True(t, IsSome(result)) collected := MonadFold(result, func() []Person { return nil }, collectSeq[Person]) expected := []Person{ {Name: "Alice", Age: 5}, {Name: "Bob", Age: 3}, {Name: "Charlie", Age: 7}, } assert.Equal(t, expected, collected) } func TestTraverseIter_WithPipeline(t *testing.T) { // Test TraverseIter in a functional pipeline parse := func(s string) Option[int] { n, err := strconv.Atoi(s) if err != nil { return None[int]() } return Some(n) } input := seqFromSlice([]string{"1", "2", "3", "4", "5"}) result := F.Pipe2( input, TraverseIter(parse), Map(collectSeq[int]), ) collected := MonadFold(result, func() []int { return nil }, F.Identity[[]int]) expected := []int{1, 2, 3, 4, 5} assert.Equal(t, expected, collected) } func TestTraverseIter_ChainedTransformations(t *testing.T) { // Test chaining multiple transformations parseAndValidate := func(s string) Option[int] { n, err := strconv.Atoi(s) if err != nil { return None[int]() } if n > 0 { return Some(n) } return None[int]() } // All valid input1 := seqFromSlice([]string{"1", "2", "3"}) result1 := TraverseIter(parseAndValidate)(input1) assert.True(t, IsSome(result1)) // Contains invalid number input2 := seqFromSlice([]string{"1", "invalid", "3"}) result2 := TraverseIter(parseAndValidate)(input2) assert.True(t, IsNone(result2)) // Contains non-positive number input3 := seqFromSlice([]string{"1", "0", "3"}) result3 := TraverseIter(parseAndValidate)(input3) assert.True(t, IsNone(result3)) } // Example test demonstrating usage func ExampleTraverseIter() { // Parse a sequence of strings to integers parse := func(s string) Option[int] { n, err := strconv.Atoi(s) if err != nil { return None[int]() } return Some(n) } // Create a sequence of valid strings validStrings := seqFromSlice([]string{"1", "2", "3"}) result := TraverseIter(parse)(validStrings) if IsSome(result) { numbers := MonadFold(result, func() []int { return nil }, collectSeq[int]) fmt.Println(numbers) } // Create a sequence with invalid string invalidStrings := seqFromSlice([]string{"1", "invalid", "3"}) result2 := TraverseIter(parse)(invalidStrings) if IsNone(result2) { fmt.Println("Parsing failed") } // Output: // [1 2 3] // Parsing failed }