package lo import ( "errors" "fmt" "math" "sort" "strconv" "strings" "testing" "github.com/stretchr/testify/assert" ) func TestFilter(t *testing.T) { t.Parallel() is := assert.New(t) r1 := Filter([]int{1, 2, 3, 4}, func(x, _ int) bool { return x%2 == 0 }) is.Equal([]int{2, 4}, r1) r2 := Filter([]string{"", "foo", "", "bar", ""}, func(x string, _ int) bool { return len(x) > 0 }) is.Equal([]string{"foo", "bar"}, r2) type myStrings []string allStrings := myStrings{"", "foo", "bar"} nonempty := Filter(allStrings, func(x string, _ int) bool { return len(x) > 0 }) is.IsType(nonempty, allStrings, "type preserved") } func TestFilterErr(t *testing.T) { t.Parallel() is := assert.New(t) tests := []struct { name string input []int predicate func(item, index int) (bool, error) want []int wantErr string callbacks int // Number of predicates called before error/finish }{ { name: "filter even numbers", input: []int{1, 2, 3, 4}, predicate: func(x, _ int) (bool, error) { return x%2 == 0, nil }, want: []int{2, 4}, callbacks: 4, }, { name: "empty slice", input: []int{}, predicate: func(x, _ int) (bool, error) { return true, nil }, want: []int{}, callbacks: 0, }, { name: "filter all out", input: []int{1, 2, 3, 4}, predicate: func(x, _ int) (bool, error) { return false, nil }, want: []int{}, callbacks: 4, }, { name: "filter all in", input: []int{1, 2, 3, 4}, predicate: func(x, _ int) (bool, error) { return true, nil }, want: []int{1, 2, 3, 4}, callbacks: 4, }, { name: "error on specific index", input: []int{1, 2, 3, 4}, predicate: func(x, _ int) (bool, error) { if x == 3 { return false, errors.New("number 3 is not allowed") } return x%2 == 0, nil }, callbacks: 3, wantErr: "number 3 is not allowed", }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() var callbacks int wrappedPredicate := func(item, index int) (bool, error) { callbacks++ return tt.predicate(item, index) } got, err := FilterErr(tt.input, wrappedPredicate) if tt.wantErr != "" { is.Error(err) is.Equal(tt.wantErr, err.Error()) is.Nil(got) is.Equal(tt.callbacks, callbacks, "callback count should match expected early return") } else { is.NoError(err) is.Equal(tt.want, got) is.Equal(tt.callbacks, callbacks) } }) } // Test type preservation type myStrings []string allStrings := myStrings{"", "foo", "bar"} nonempty, err := FilterErr(allStrings, func(x string, _ int) (bool, error) { return len(x) > 0, nil }) is.NoError(err) is.IsType(nonempty, allStrings, "type preserved") is.Equal(myStrings{"foo", "bar"}, nonempty) } func TestMap(t *testing.T) { t.Parallel() is := assert.New(t) result1 := Map([]int{1, 2, 3, 4}, func(x, _ int) string { return "Hello" }) result2 := Map([]int64{1, 2, 3, 4}, func(x int64, _ int) string { return strconv.FormatInt(x, 10) }) is.Equal([]string{"Hello", "Hello", "Hello", "Hello"}, result1) is.Equal([]string{"1", "2", "3", "4"}, result2) } func TestMapErr(t *testing.T) { t.Parallel() is := assert.New(t) tests := []struct { name string input []int transform func(item, index int) (string, error) wantResult []string wantErr bool errMsg string expectedCallbackCount int }{ { name: "successful transformation", input: []int{1, 2, 3, 4}, transform: func(x, _ int) (string, error) { return strconv.Itoa(x), nil }, wantResult: []string{"1", "2", "3", "4"}, wantErr: false, expectedCallbackCount: 4, }, { name: "error at third element stops iteration", input: []int{1, 2, 3, 4}, transform: func(x, _ int) (string, error) { if x == 3 { return "", errors.New("number 3 is not allowed") } return strconv.Itoa(x), nil }, wantResult: nil, wantErr: true, errMsg: "number 3 is not allowed", expectedCallbackCount: 3, }, { name: "error at first element stops iteration immediately", input: []int{1, 2, 3, 4}, transform: func(x, _ int) (string, error) { if x == 1 { return "", errors.New("number 1 is not allowed") } return strconv.Itoa(x), nil }, wantResult: nil, wantErr: true, errMsg: "number 1 is not allowed", expectedCallbackCount: 1, }, { name: "error at last element", input: []int{1, 2, 3, 4}, transform: func(x, _ int) (string, error) { if x == 4 { return "", errors.New("number 4 is not allowed") } return strconv.Itoa(x), nil }, wantResult: nil, wantErr: true, errMsg: "number 4 is not allowed", expectedCallbackCount: 4, }, { name: "empty input slice", input: []int{}, transform: func(x, _ int) (string, error) { return strconv.Itoa(x), nil }, wantResult: []string{}, wantErr: false, expectedCallbackCount: 0, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() // Track callback count to test early return callbackCount := 0 wrappedTransform := func(item, index int) (string, error) { callbackCount++ return tt.transform(item, index) } result, err := MapErr(tt.input, wrappedTransform) if tt.wantErr { is.Error(err) is.Equal(tt.errMsg, err.Error()) is.Nil(result) } else { is.NoError(err) is.Equal(tt.wantResult, result) } // Verify callback count matches expected is.Equal(tt.expectedCallbackCount, callbackCount, "callback count should match expected") }) } } func TestUniqMap(t *testing.T) { t.Parallel() is := assert.New(t) type User struct { Name string age int } users := []User{{Name: "Alice", age: 20}, {Name: "Alex", age: 21}, {Name: "Alex", age: 22}} result := UniqMap(users, func(item User, index int) string { return item.Name }) sort.Strings(result) is.Equal([]string{"Alex", "Alice"}, result) } func TestFilterMap(t *testing.T) { t.Parallel() is := assert.New(t) r1 := FilterMap([]int64{1, 2, 3, 4}, func(x int64, _ int) (string, bool) { if x%2 == 0 { return strconv.FormatInt(x, 10), true } return "", false }) r2 := FilterMap([]string{"cpu", "gpu", "mouse", "keyboard"}, func(x string, _ int) (string, bool) { if strings.HasSuffix(x, "pu") { return "xpu", true } return "", false }) is.Equal([]string{"2", "4"}, r1) is.Equal([]string{"xpu", "xpu"}, r2) } func TestFlatMap(t *testing.T) { t.Parallel() is := assert.New(t) result1 := FlatMap([]int{0, 1, 2, 3, 4}, func(x, _ int) []string { return []string{"Hello"} }) result2 := FlatMap([]int64{0, 1, 2, 3, 4}, func(x int64, _ int) []string { result := make([]string, 0, x) for i := int64(0); i < x; i++ { result = append(result, strconv.FormatInt(x, 10)) } return result }) is.Equal([]string{"Hello", "Hello", "Hello", "Hello", "Hello"}, result1) is.Equal([]string{"1", "2", "2", "3", "3", "3", "4", "4", "4", "4"}, result2) } func TestFlatMapErr(t *testing.T) { t.Parallel() is := assert.New(t) tests := []struct { name string input []int64 transform func(item int64, index int) ([]string, error) wantResult []string wantErr bool errMsg string expectedCallbackCount int }{ { name: "successful transformation", input: []int64{0, 1, 2}, transform: func(x int64, _ int) ([]string, error) { return []string{strconv.FormatInt(x, 10), strconv.FormatInt(x, 10)}, nil }, wantResult: []string{"0", "0", "1", "1", "2", "2"}, wantErr: false, expectedCallbackCount: 3, }, { name: "error at second element stops iteration", input: []int64{0, 1, 2, 3}, transform: func(x int64, _ int) ([]string, error) { if x == 1 { return nil, errors.New("number 1 is not allowed") } return []string{strconv.FormatInt(x, 10)}, nil }, wantResult: nil, wantErr: true, errMsg: "number 1 is not allowed", expectedCallbackCount: 2, }, { name: "error at first element stops iteration immediately", input: []int64{0, 1, 2, 3}, transform: func(x int64, _ int) ([]string, error) { if x == 0 { return nil, errors.New("number 0 is not allowed") } return []string{strconv.FormatInt(x, 10)}, nil }, wantResult: nil, wantErr: true, errMsg: "number 0 is not allowed", expectedCallbackCount: 1, }, { name: "error at last element", input: []int64{0, 1, 2}, transform: func(x int64, _ int) ([]string, error) { if x == 2 { return nil, errors.New("number 2 is not allowed") } return []string{strconv.FormatInt(x, 10)}, nil }, wantResult: nil, wantErr: true, errMsg: "number 2 is not allowed", expectedCallbackCount: 3, }, { name: "empty input slice", input: []int64{}, transform: func(x int64, _ int) ([]string, error) { return []string{strconv.FormatInt(x, 10)}, nil }, wantResult: []string{}, wantErr: false, expectedCallbackCount: 0, }, { name: "returns empty slice for each element", input: []int64{1, 2, 3}, transform: func(x int64, _ int) ([]string, error) { return []string{}, nil }, wantResult: []string{}, wantErr: false, expectedCallbackCount: 3, }, { name: "returns nil for some elements", input: []int64{1, 2, 3}, transform: func(x int64, _ int) ([]string, error) { if x == 2 { return nil, nil } return []string{strconv.FormatInt(x, 10)}, nil }, wantResult: []string{"1", "3"}, wantErr: false, expectedCallbackCount: 3, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() // Track callback count to test early return callbackCount := 0 wrappedTransform := func(item int64, index int) ([]string, error) { callbackCount++ return tt.transform(item, index) } result, err := FlatMapErr(tt.input, wrappedTransform) if tt.wantErr { is.Error(err) is.Equal(tt.errMsg, err.Error()) is.Nil(result) } else { is.NoError(err) is.Equal(tt.wantResult, result) } // Verify callback count matches expected is.Equal(tt.expectedCallbackCount, callbackCount, "callback count should match expected") }) } } func TestTimes(t *testing.T) { t.Parallel() is := assert.New(t) result1 := Times(3, func(i int) string { return strconv.FormatInt(int64(i), 10) }) is.Equal([]string{"0", "1", "2"}, result1) } func TestReduce(t *testing.T) { t.Parallel() is := assert.New(t) result1 := Reduce([]int{1, 2, 3, 4}, func(agg, item, _ int) int { return agg + item }, 0) result2 := Reduce([]int{1, 2, 3, 4}, func(agg, item, _ int) int { return agg + item }, 10) is.Equal(10, result1) is.Equal(20, result2) } func TestReduceErr(t *testing.T) { t.Parallel() is := assert.New(t) tests := []struct { name string input []int accumulator func(agg, item, index int) (int, error) initial int wantResult int wantErr bool errMsg string expectedCallbackCount int }{ { name: "successful reduction", input: []int{1, 2, 3, 4}, accumulator: func(agg, item, _ int) (int, error) { return agg + item, nil }, initial: 0, wantResult: 10, wantErr: false, expectedCallbackCount: 4, }, { name: "error at third element stops iteration", input: []int{1, 2, 3, 4}, accumulator: func(agg, item, _ int) (int, error) { if item == 3 { return 0, errors.New("number 3 is not allowed") } return agg + item, nil }, initial: 0, wantResult: 0, wantErr: true, errMsg: "number 3 is not allowed", expectedCallbackCount: 3, }, { name: "error at first element stops iteration immediately", input: []int{1, 2, 3, 4}, accumulator: func(agg, item, _ int) (int, error) { if item == 1 { return 0, errors.New("number 1 is not allowed") } return agg + item, nil }, initial: 0, wantResult: 0, wantErr: true, errMsg: "number 1 is not allowed", expectedCallbackCount: 1, }, { name: "error at last element", input: []int{1, 2, 3, 4}, accumulator: func(agg, item, _ int) (int, error) { if item == 4 { return 0, errors.New("number 4 is not allowed") } return agg + item, nil }, initial: 0, wantResult: 0, wantErr: true, errMsg: "number 4 is not allowed", expectedCallbackCount: 4, }, { name: "empty input slice", input: []int{}, accumulator: func(agg, item, _ int) (int, error) { return agg + item, nil }, initial: 10, wantResult: 10, wantErr: false, expectedCallbackCount: 0, }, { name: "with non-zero initial value", input: []int{1, 2, 3, 4}, accumulator: func(agg, item, _ int) (int, error) { return agg + item, nil }, initial: 10, wantResult: 20, wantErr: false, expectedCallbackCount: 4, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() // Track callback count to test early return callbackCount := 0 wrappedAccumulator := func(agg, item, index int) (int, error) { callbackCount++ return tt.accumulator(agg, item, index) } result, err := ReduceErr(tt.input, wrappedAccumulator, tt.initial) if tt.wantErr { is.Error(err) is.Equal(tt.errMsg, err.Error()) } else { is.NoError(err) is.Equal(tt.wantResult, result) } // Verify callback count matches expected is.Equal(tt.expectedCallbackCount, callbackCount, "callback count should match expected") }) } } func TestReduceRight(t *testing.T) { t.Parallel() is := assert.New(t) result1 := ReduceRight([][]int{{0, 1}, {2, 3}, {4, 5}}, func(agg, item []int, _ int) []int { return append(agg, item...) }, []int{}) is.Equal([]int{4, 5, 2, 3, 0, 1}, result1) type collection []int result3 := ReduceRight(collection{1, 2, 3, 4}, func(agg, item, _ int) int { return agg + item }, 10) is.Equal(20, result3) } func TestReduceRightErr(t *testing.T) { t.Parallel() is := assert.New(t) tests := []struct { name string input []int accumulator func(agg, item, index int) (int, error) initial int wantResult int wantErr bool errMsg string expectedCallbackCount int }{ { name: "successful reduction", input: []int{1, 2, 3, 4}, accumulator: func(agg, item, _ int) (int, error) { return agg + item, nil }, initial: 0, wantResult: 10, wantErr: false, expectedCallbackCount: 4, }, { name: "error at second element from right stops iteration", input: []int{1, 2, 3, 4}, accumulator: func(agg, item, _ int) (int, error) { if item == 3 { return 0, errors.New("number 3 is not allowed") } return agg + item, nil }, initial: 0, wantResult: 0, wantErr: true, errMsg: "number 3 is not allowed", expectedCallbackCount: 2, }, { name: "error at first element from right stops iteration immediately", input: []int{1, 2, 3, 4}, accumulator: func(agg, item, _ int) (int, error) { if item == 4 { return 0, errors.New("number 4 is not allowed") } return agg + item, nil }, initial: 0, wantResult: 0, wantErr: true, errMsg: "number 4 is not allowed", expectedCallbackCount: 1, }, { name: "error at last element from left", input: []int{1, 2, 3, 4}, accumulator: func(agg, item, _ int) (int, error) { if item == 1 { return 0, errors.New("number 1 is not allowed") } return agg + item, nil }, initial: 0, wantResult: 0, wantErr: true, errMsg: "number 1 is not allowed", expectedCallbackCount: 4, }, { name: "empty input slice", input: []int{}, accumulator: func(agg, item, _ int) (int, error) { return agg + item, nil }, initial: 10, wantResult: 10, wantErr: false, expectedCallbackCount: 0, }, { name: "with non-zero initial value", input: []int{1, 2, 3, 4}, accumulator: func(agg, item, _ int) (int, error) { return agg + item, nil }, initial: 10, wantResult: 20, wantErr: false, expectedCallbackCount: 4, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() // Track callback count to test early return callbackCount := 0 wrappedAccumulator := func(agg, item, index int) (int, error) { callbackCount++ return tt.accumulator(agg, item, index) } result, err := ReduceRightErr(tt.input, wrappedAccumulator, tt.initial) if tt.wantErr { is.Error(err) is.Equal(tt.errMsg, err.Error()) } else { is.NoError(err) is.Equal(tt.wantResult, result) } // Verify callback count matches expected is.Equal(tt.expectedCallbackCount, callbackCount, "callback count should match expected") }) } } func TestForEach(t *testing.T) { t.Parallel() is := assert.New(t) // check of callback is called for every element and in proper order callParams1 := []string{} callParams2 := []int{} ForEach([]string{"a", "b", "c"}, func(item string, i int) { callParams1 = append(callParams1, item) callParams2 = append(callParams2, i) }) is.Equal([]string{"a", "b", "c"}, callParams1) is.Equal([]int{0, 1, 2}, callParams2) is.IsIncreasing(callParams2) } func TestForEachWhile(t *testing.T) { t.Parallel() is := assert.New(t) // check of callback is called for every element and in proper order var callParams1 []string var callParams2 []int ForEachWhile([]string{"a", "b", "c"}, func(item string, i int) bool { if item == "c" { return false } callParams1 = append(callParams1, item) callParams2 = append(callParams2, i) return true }) is.Equal([]string{"a", "b"}, callParams1) is.Equal([]int{0, 1}, callParams2) is.IsIncreasing(callParams2) } func TestUniq(t *testing.T) { t.Parallel() is := assert.New(t) result1 := Uniq([]int{1, 2, 2, 1}) is.Equal([]int{1, 2}, result1) type myStrings []string allStrings := myStrings{"", "foo", "bar"} nonempty := Uniq(allStrings) is.IsType(nonempty, allStrings, "type preserved") } func TestUniqBy(t *testing.T) { t.Parallel() is := assert.New(t) result1 := UniqBy([]int{0, 1, 2, 3, 4, 5}, func(i int) int { return i % 3 }) is.Equal([]int{0, 1, 2}, result1) type myStrings []string allStrings := myStrings{"", "foo", "bar"} nonempty := UniqBy(allStrings, func(i string) string { return i }) is.IsType(nonempty, allStrings, "type preserved") } func TestUniqByErr(t *testing.T) { t.Parallel() is := assert.New(t) tests := []struct { name string input []int iteratee func(item int) (int, error) wantResult []int wantErr bool errMsg string expectedCallbackCount int }{ { name: "successful uniq", input: []int{0, 1, 2, 3, 4, 5}, iteratee: func(i int) (int, error) { return i % 3, nil }, wantResult: []int{0, 1, 2}, wantErr: false, expectedCallbackCount: 6, }, { name: "error at fourth element stops iteration", input: []int{0, 1, 2, 3, 4, 5}, iteratee: func(i int) (int, error) { if i == 3 { return 0, errors.New("number 3 is not allowed") } return i % 3, nil }, wantResult: nil, wantErr: true, errMsg: "number 3 is not allowed", expectedCallbackCount: 4, }, { name: "error at first element stops iteration immediately", input: []int{0, 1, 2, 3, 4, 5}, iteratee: func(i int) (int, error) { if i == 0 { return 0, errors.New("number 0 is not allowed") } return i % 3, nil }, wantResult: nil, wantErr: true, errMsg: "number 0 is not allowed", expectedCallbackCount: 1, }, { name: "error at last element", input: []int{0, 1, 2, 3, 4, 5}, iteratee: func(i int) (int, error) { if i == 5 { return 0, errors.New("number 5 is not allowed") } return i % 3, nil }, wantResult: nil, wantErr: true, errMsg: "number 5 is not allowed", expectedCallbackCount: 6, }, { name: "empty input slice", input: []int{}, iteratee: func(i int) (int, error) { return i % 3, nil }, wantResult: []int{}, wantErr: false, expectedCallbackCount: 0, }, { name: "all duplicates", input: []int{1, 1, 1, 1}, iteratee: func(i int) (int, error) { return i % 3, nil }, wantResult: []int{1}, wantErr: false, expectedCallbackCount: 4, }, { name: "no duplicates", input: []int{0, 1, 2, 3}, iteratee: func(i int) (int, error) { return i, nil }, wantResult: []int{0, 1, 2, 3}, wantErr: false, expectedCallbackCount: 4, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() // Track callback count to test early return callbackCount := 0 wrappedIteratee := func(item int) (int, error) { callbackCount++ return tt.iteratee(item) } result, err := UniqByErr(tt.input, wrappedIteratee) if tt.wantErr { is.Error(err) is.Equal(tt.errMsg, err.Error()) is.Nil(result) } else { is.NoError(err) is.Equal(tt.wantResult, result) } // Verify callback count matches expected is.Equal(tt.expectedCallbackCount, callbackCount, "callback count should match expected") }) } } func TestGroupBy(t *testing.T) { t.Parallel() is := assert.New(t) result1 := GroupBy([]int{0, 1, 2, 3, 4, 5}, func(i int) int { return i % 3 }) is.Equal(map[int][]int{ 0: {0, 3}, 1: {1, 4}, 2: {2, 5}, }, result1) type myStrings []string allStrings := myStrings{"", "foo", "bar"} nonempty := GroupBy(allStrings, func(i string) int { return 42 }) is.IsType(nonempty[42], allStrings, "type preserved") } func TestGroupByErr(t *testing.T) { t.Parallel() is := assert.New(t) tests := []struct { name string input []int iteratee func(item int) (int, error) wantResult map[int][]int wantErr bool errMsg string expectedCallbackCount int }{ { name: "successful grouping", input: []int{0, 1, 2, 3, 4, 5}, iteratee: func(i int) (int, error) { return i % 3, nil }, wantResult: map[int][]int{ 0: {0, 3}, 1: {1, 4}, 2: {2, 5}, }, wantErr: false, expectedCallbackCount: 6, }, { name: "error at fourth element stops iteration", input: []int{0, 1, 2, 3, 4, 5}, iteratee: func(i int) (int, error) { if i == 3 { return 0, errors.New("number 3 is not allowed") } return i % 3, nil }, wantResult: nil, wantErr: true, errMsg: "number 3 is not allowed", expectedCallbackCount: 4, }, { name: "error at first element stops iteration immediately", input: []int{0, 1, 2, 3, 4, 5}, iteratee: func(i int) (int, error) { if i == 0 { return 0, errors.New("number 0 is not allowed") } return i % 3, nil }, wantResult: nil, wantErr: true, errMsg: "number 0 is not allowed", expectedCallbackCount: 1, }, { name: "error at last element", input: []int{0, 1, 2, 3, 4, 5}, iteratee: func(i int) (int, error) { if i == 5 { return 0, errors.New("number 5 is not allowed") } return i % 3, nil }, wantResult: nil, wantErr: true, errMsg: "number 5 is not allowed", expectedCallbackCount: 6, }, { name: "empty input slice", input: []int{}, iteratee: func(i int) (int, error) { return i % 3, nil }, wantResult: map[int][]int{}, wantErr: false, expectedCallbackCount: 0, }, { name: "all elements in same group", input: []int{3, 6, 9, 12}, iteratee: func(i int) (int, error) { return 0, nil }, wantResult: map[int][]int{ 0: {3, 6, 9, 12}, }, wantErr: false, expectedCallbackCount: 4, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() // Track callback count to test early return callbackCount := 0 wrappedIteratee := func(item int) (int, error) { callbackCount++ return tt.iteratee(item) } result, err := GroupByErr(tt.input, wrappedIteratee) if tt.wantErr { is.Error(err) is.Equal(tt.errMsg, err.Error()) is.Nil(result) } else { is.NoError(err) is.Equal(tt.wantResult, result) } // Verify callback count matches expected is.Equal(tt.expectedCallbackCount, callbackCount, "callback count should match expected") }) } } func TestGroupByMap(t *testing.T) { t.Parallel() is := assert.New(t) result1 := GroupByMap([]int{0, 1, 2, 3, 4, 5}, func(i int) (int, string) { return i % 3, strconv.Itoa(i) }) is.Equal(map[int][]string{ 0: {"0", "3"}, 1: {"1", "4"}, 2: {"2", "5"}, }, result1) type myInt int type myInts []myInt result2 := GroupByMap(myInts{1, 0, 2, 3, 4, 5}, func(i myInt) (int, string) { return int(i % 3), strconv.Itoa(int(i)) }) is.Equal(map[int][]string{ 0: {"0", "3"}, 1: {"1", "4"}, 2: {"2", "5"}, }, result2) type product struct { ID int64 CategoryID int64 } products := []product{ {ID: 1, CategoryID: 1}, {ID: 2, CategoryID: 1}, {ID: 3, CategoryID: 2}, {ID: 4, CategoryID: 3}, {ID: 5, CategoryID: 3}, } result3 := GroupByMap(products, func(item product) (int64, string) { return item.CategoryID, "Product " + strconv.FormatInt(item.ID, 10) }) is.Equal(map[int64][]string{ 1: {"Product 1", "Product 2"}, 2: {"Product 3"}, 3: {"Product 4", "Product 5"}, }, result3) } func TestGroupByMapErr(t *testing.T) { t.Parallel() is := assert.New(t) tests := []struct { name string input []int transform func(item int) (int, int, error) wantResult map[int][]int wantErr bool errMsg string expectedCallbackCount int }{ { name: "successful grouping", input: []int{0, 1, 2, 3, 4, 5}, transform: func(i int) (int, int, error) { return i % 3, i * 2, nil }, wantResult: map[int][]int{ 0: {0, 6}, 1: {2, 8}, 2: {4, 10}, }, wantErr: false, expectedCallbackCount: 6, }, { name: "error at fourth element stops iteration", input: []int{0, 1, 2, 3, 4, 5}, transform: func(i int) (int, int, error) { if i == 3 { return 0, 0, errors.New("number 3 is not allowed") } return i % 3, i * 2, nil }, wantResult: nil, wantErr: true, errMsg: "number 3 is not allowed", expectedCallbackCount: 4, }, { name: "error at first element stops iteration immediately", input: []int{0, 1, 2, 3, 4, 5}, transform: func(i int) (int, int, error) { if i == 0 { return 0, 0, errors.New("number 0 is not allowed") } return i % 3, i * 2, nil }, wantResult: nil, wantErr: true, errMsg: "number 0 is not allowed", expectedCallbackCount: 1, }, { name: "error at last element", input: []int{0, 1, 2, 3, 4, 5}, transform: func(i int) (int, int, error) { if i == 5 { return 0, 0, errors.New("number 5 is not allowed") } return i % 3, i * 2, nil }, wantResult: nil, wantErr: true, errMsg: "number 5 is not allowed", expectedCallbackCount: 6, }, { name: "empty input slice", input: []int{}, transform: func(i int) (int, int, error) { return i % 3, i * 2, nil }, wantResult: map[int][]int{}, wantErr: false, expectedCallbackCount: 0, }, { name: "all elements in same group", input: []int{3, 6, 9, 12}, transform: func(i int) (int, int, error) { return 0, i, nil }, wantResult: map[int][]int{ 0: {3, 6, 9, 12}, }, wantErr: false, expectedCallbackCount: 4, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() // Track callback count to test early return callbackCount := 0 wrappedTransform := func(item int) (int, int, error) { callbackCount++ return tt.transform(item) } result, err := GroupByMapErr(tt.input, wrappedTransform) if tt.wantErr { is.Error(err) is.Equal(tt.errMsg, err.Error()) is.Nil(result) } else { is.NoError(err) is.Equal(tt.wantResult, result) } // Verify callback count matches expected is.Equal(tt.expectedCallbackCount, callbackCount, "callback count should match expected") }) } } func TestChunk(t *testing.T) { t.Parallel() is := assert.New(t) result1 := Chunk([]int{0, 1, 2, 3, 4, 5}, 2) result2 := Chunk([]int{0, 1, 2, 3, 4, 5, 6}, 2) result3 := Chunk([]int{}, 2) result4 := Chunk([]int{0}, 2) is.Equal([][]int{{0, 1}, {2, 3}, {4, 5}}, result1) is.Equal([][]int{{0, 1}, {2, 3}, {4, 5}, {6}}, result2) is.Empty(result3) is.Equal([][]int{{0}}, result4) is.PanicsWithValue("lo.Chunk: size must be greater than 0", func() { Chunk([]int{0}, 0) }) type myStrings []string allStrings := myStrings{"", "foo", "bar"} nonempty := Chunk(allStrings, 2) is.IsType(nonempty[0], allStrings, "type preserved") // appending to a chunk should not affect original slice original := []int{0, 1, 2, 3, 4, 5} result5 := Chunk(original, 2) result5[0] = append(result5[0], 6) is.Equal([]int{0, 1, 2, 3, 4, 5}, original) } func TestWindow(t *testing.T) { t.Parallel() is := assert.New(t) result1 := Window([]int{1, 2, 3, 4, 5}, 3) result2 := Window([]int{1, 2, 3, 4, 5, 6}, 3) result3 := Window([]int{1, 2}, 3) result4 := Window([]int{1, 2, 3}, 3) result5 := Window([]int{1, 2, 3, 4}, 1) is.Equal([][]int{{1, 2, 3}, {2, 3, 4}, {3, 4, 5}}, result1) is.Equal([][]int{{1, 2, 3}, {2, 3, 4}, {3, 4, 5}, {4, 5, 6}}, result2) is.Empty(result3) is.Equal([][]int{{1, 2, 3}}, result4) is.Equal([][]int{{1}, {2}, {3}, {4}}, result5) is.PanicsWithValue("lo.Window: size must be greater than 0", func() { Window([]int{1, 2, 3}, 0) }) is.PanicsWithValue("lo.Window: size must be greater than 0", func() { Window([]int{1, 2, 3}, -1) }) type myStrings []string allStrings := myStrings{"a", "b", "c", "d"} windows := Window(allStrings, 2) is.IsType(windows[0], allStrings, "type preserved") is.Equal(myStrings{"a", "b"}, windows[0]) // appending to a window should not affect original slice original := []int{1, 2, 3, 4, 5} windows2 := Window(original, 3) windows2[0] = append(windows2[0], 6) is.Equal([]int{1, 2, 3, 4, 5}, original) } func TestSliding(t *testing.T) { t.Parallel() is := assert.New(t) // Overlapping windows (step < size) result1 := Sliding([]int{1, 2, 3, 4, 5, 6}, 3, 1) is.Equal([][]int{{1, 2, 3}, {2, 3, 4}, {3, 4, 5}, {4, 5, 6}}, result1) // Non-overlapping windows (step == size, like Chunk) result2 := Sliding([]int{1, 2, 3, 4, 5, 6}, 3, 3) is.Equal([][]int{{1, 2, 3}, {4, 5, 6}}, result2) // Step > size (skipping elements) result3 := Sliding([]int{1, 2, 3, 4, 5, 6, 7, 8}, 2, 3) is.Equal([][]int{{1, 2}, {4, 5}, {7, 8}}, result3) // Single element windows result4 := Sliding([]int{1, 2, 3, 4}, 1, 1) is.Equal([][]int{{1}, {2}, {3}, {4}}, result4) // Empty result when collection is too small result5 := Sliding([]int{1, 2}, 3, 1) is.Empty(result5) // Step 2, size 2 result6 := Sliding([]int{1, 2, 3, 4, 5, 6}, 2, 2) is.Equal([][]int{{1, 2}, {3, 4}, {5, 6}}, result6) is.PanicsWithValue("lo.Sliding: size must be greater than 0", func() { Sliding([]int{1, 2, 3}, 0, 1) }) is.PanicsWithValue("lo.Sliding: step must be greater than 0", func() { Sliding([]int{1, 2, 3}, 2, 0) }) is.PanicsWithValue("lo.Sliding: step must be greater than 0", func() { Sliding([]int{1, 2, 3}, 2, -1) }) type myStrings []string allStrings := myStrings{"a", "b", "c", "d", "e"} windows := Sliding(allStrings, 2, 2) is.IsType(windows[0], allStrings, "type preserved") is.Equal(myStrings{"a", "b"}, windows[0]) // appending to a window should not affect original slice original := []int{1, 2, 3, 4, 5, 6} windows2 := Sliding(original, 2, 2) windows2[0] = append(windows2[0], 7) is.Equal([]int{1, 2, 3, 4, 5, 6}, original) } func TestPartitionBy(t *testing.T) { t.Parallel() is := assert.New(t) oddEven := func(x int) string { if x < 0 { return "negative" } else if x%2 == 0 { return "even" } return "odd" } result1 := PartitionBy([]int{-2, -1, 0, 1, 2, 3, 4, 5}, oddEven) result2 := PartitionBy([]int{}, oddEven) is.Equal([][]int{{-2, -1}, {0, 2, 4}, {1, 3, 5}}, result1) is.Empty(result2) type myStrings []string allStrings := myStrings{"", "foo", "bar"} nonempty := PartitionBy(allStrings, func(item string) int { return len(item) }) is.IsType(nonempty[0], allStrings, "type preserved") } func TestPartitionByErr(t *testing.T) { t.Parallel() is := assert.New(t) oddEven := func(x int) (string, error) { if x < 0 { return "negative", nil } else if x%2 == 0 { return "even", nil } return "odd", nil } tests := []struct { name string input []int iteratee func(item int) (string, error) wantResult [][]int wantErr bool errMsg string expectedCallbackCount int }{ { name: "successful partition", input: []int{-2, -1, 0, 1, 2, 3, 4, 5}, iteratee: oddEven, wantResult: [][]int{{-2, -1}, {0, 2, 4}, {1, 3, 5}}, wantErr: false, expectedCallbackCount: 8, }, { name: "error at fifth element stops iteration", input: []int{-2, -1, 0, 1, 2, 3}, iteratee: func(x int) (string, error) { if x == 2 { return "", errors.New("number 2 is not allowed") } return oddEven(x) }, wantResult: nil, wantErr: true, errMsg: "number 2 is not allowed", expectedCallbackCount: 5, }, { name: "error at first element stops iteration immediately", input: []int{-2, -1, 0, 1}, iteratee: func(x int) (string, error) { if x == -2 { return "", errors.New("number -2 is not allowed") } return oddEven(x) }, wantResult: nil, wantErr: true, errMsg: "number -2 is not allowed", expectedCallbackCount: 1, }, { name: "error at last element", input: []int{-2, -1, 0, 1, 2}, iteratee: func(x int) (string, error) { if x == 2 { return "", errors.New("number 2 is not allowed") } return oddEven(x) }, wantResult: nil, wantErr: true, errMsg: "number 2 is not allowed", expectedCallbackCount: 5, }, { name: "empty input slice", input: []int{}, iteratee: oddEven, wantResult: [][]int{}, wantErr: false, expectedCallbackCount: 0, }, { name: "all elements in same partition", input: []int{1, 3, 5}, iteratee: func(x int) (string, error) { return "odd", nil }, wantResult: [][]int{{1, 3, 5}}, wantErr: false, expectedCallbackCount: 3, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() // Track callback count to test early return callbackCount := 0 wrappedIteratee := func(item int) (string, error) { callbackCount++ return tt.iteratee(item) } result, err := PartitionByErr(tt.input, wrappedIteratee) if tt.wantErr { is.Error(err) is.Equal(tt.errMsg, err.Error()) is.Nil(result) } else { is.NoError(err) is.Equal(tt.wantResult, result) } // Verify callback count matches expected is.Equal(tt.expectedCallbackCount, callbackCount, "callback count should match expected") }) } } func TestFlatten(t *testing.T) { t.Parallel() is := assert.New(t) result1 := Flatten([][]int{{0, 1}, {2, 3, 4, 5}}) is.Equal([]int{0, 1, 2, 3, 4, 5}, result1) type myStrings []string allStrings := myStrings{"", "foo", "bar"} nonempty := Flatten([]myStrings{allStrings}) is.IsType(nonempty, allStrings, "type preserved") } func TestConcat(t *testing.T) { t.Parallel() is := assert.New(t) result1 := Concat([][]int{{0, 1}, {2, 3, 4, 5}}...) is.Equal([]int{0, 1, 2, 3, 4, 5}, result1) type myStrings []string allStrings := myStrings{"", "foo", "bar"} nonempty := Concat([]myStrings{allStrings}...) is.IsType(nonempty, allStrings, "type preserved") } func TestInterleave(t *testing.T) { t.Parallel() is := assert.New(t) testCases := []struct { name string in [][]int want []int }{ { "nil", [][]int{nil}, []int{}, }, { "empty", [][]int{}, []int{}, }, { "empties", [][]int{{}, {}}, []int{}, }, { "same length", [][]int{{1, 3, 5}, {2, 4, 6}}, []int{1, 2, 3, 4, 5, 6}, }, { "different length", [][]int{{1, 3, 5, 6}, {2, 4}}, []int{1, 2, 3, 4, 5, 6}, }, { "many slices", [][]int{{1}, {2, 5, 8}, {3, 6}, {4, 7, 9, 10}}, []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, }, } for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() assert.Equal(t, tc.want, Interleave(tc.in...)) }) } type myStrings []string allStrings := myStrings{"", "foo", "bar"} nonempty := Interleave(allStrings) is.IsType(nonempty, allStrings, "type preserved") } func TestShuffle(t *testing.T) { t.Parallel() is := assert.New(t) result1 := Shuffle([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) result2 := Shuffle([]int{}) is.NotEqual([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, result1) is.ElementsMatch([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, result1) is.Empty(result2) type myStrings []string allStrings := myStrings{"", "foo", "bar"} nonempty := Shuffle(allStrings) is.IsType(nonempty, allStrings, "type preserved") } func TestReverse(t *testing.T) { t.Parallel() is := assert.New(t) result1 := Reverse([]int{0, 1, 2, 3, 4, 5}) result2 := Reverse([]int{0, 1, 2, 3, 4, 5, 6}) result3 := Reverse([]int{}) is.Equal([]int{5, 4, 3, 2, 1, 0}, result1) is.Equal([]int{6, 5, 4, 3, 2, 1, 0}, result2) is.Empty(result3) type myStrings []string allStrings := myStrings{"", "foo", "bar"} nonempty := Reverse(allStrings) is.IsType(nonempty, allStrings, "type preserved") } func TestFill(t *testing.T) { t.Parallel() is := assert.New(t) result1 := Fill([]foo{{"a"}, {"a"}}, foo{"b"}) result2 := Fill([]foo{}, foo{"a"}) is.Equal([]foo{{"b"}, {"b"}}, result1) is.Empty(result2) } func TestRepeat(t *testing.T) { t.Parallel() is := assert.New(t) result1 := Repeat(2, foo{"a"}) result2 := Repeat(0, foo{"a"}) is.Equal([]foo{{"a"}, {"a"}}, result1) is.Empty(result2) } func TestRepeatBy(t *testing.T) { t.Parallel() is := assert.New(t) cb := func(i int) int { return int(math.Pow(float64(i), 2)) } result1 := RepeatBy(0, cb) result2 := RepeatBy(2, cb) result3 := RepeatBy(5, cb) is.Empty(result1) is.Equal([]int{0, 1}, result2) is.Equal([]int{0, 1, 4, 9, 16}, result3) } func TestRepeatByErr(t *testing.T) { t.Parallel() is := assert.New(t) testErr := errors.New("test error") // Table-driven tests tests := []struct { name string count int callback func(index int) (int, error) wantResult []int wantErr bool expectedCallbackCount int }{ { name: "successful completion", count: 5, callback: func(i int) (int, error) { return i * i, nil }, wantResult: []int{0, 1, 4, 9, 16}, wantErr: false, expectedCallbackCount: 5, }, { name: "error at first iteration", count: 5, callback: func(i int) (int, error) { if i == 0 { return 0, testErr } return i * i, nil }, wantResult: nil, wantErr: true, expectedCallbackCount: 1, }, { name: "error at third iteration", count: 5, callback: func(i int) (int, error) { if i == 2 { return 0, testErr } return i * i, nil }, wantResult: nil, wantErr: true, expectedCallbackCount: 3, }, { name: "error at last iteration", count: 5, callback: func(i int) (int, error) { if i == 4 { return 0, testErr } return i * i, nil }, wantResult: nil, wantErr: true, expectedCallbackCount: 5, }, { name: "zero count - empty result", count: 0, callback: func(i int) (int, error) { return i * i, nil }, wantResult: []int{}, wantErr: false, expectedCallbackCount: 0, }, { name: "single item success", count: 1, callback: func(i int) (int, error) { return 42, nil }, wantResult: []int{42}, wantErr: false, expectedCallbackCount: 1, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() // Track callback count to verify early return callbackCount := 0 wrappedCallback := func(i int) (int, error) { callbackCount++ return tt.callback(i) } result, err := RepeatByErr(tt.count, wrappedCallback) if tt.wantErr { is.ErrorIs(err, testErr) is.Nil(result) } else { is.NoError(err) is.Equal(tt.wantResult, result) } // Verify callback count matches expected (tests early return) is.Equal(tt.expectedCallbackCount, callbackCount, "callback count should match expected") }) } } func TestKeyBy(t *testing.T) { t.Parallel() is := assert.New(t) result1 := KeyBy([]string{"a", "aa", "aaa"}, func(str string) int { return len(str) }) is.Equal(map[int]string{1: "a", 2: "aa", 3: "aaa"}, result1) } func TestKeyByErr(t *testing.T) { t.Parallel() tests := []struct { name string input []string iteratee func(item string) (int, error) wantResult map[int]string wantErr bool errMsg string expectedCallbackCount int }{ { name: "empty slice", input: []string{}, iteratee: func(s string) (int, error) { return len(s), nil }, wantResult: map[int]string{}, wantErr: false, expectedCallbackCount: 0, }, { name: "success case", input: []string{"a", "aa", "aaa"}, iteratee: func(s string) (int, error) { return len(s), nil }, wantResult: map[int]string{1: "a", 2: "aa", 3: "aaa"}, wantErr: false, expectedCallbackCount: 3, }, { name: "error stops iteration - first item", input: []string{"a", "aa", "aaa"}, iteratee: func(s string) (int, error) { return 0, fmt.Errorf("error on %s", s) }, wantResult: nil, wantErr: true, errMsg: "error on a", expectedCallbackCount: 1, }, { name: "error stops iteration - middle item", input: []string{"a", "aa", "aaa"}, iteratee: func(s string) (int, error) { if s == "aa" { return 0, errors.New("middle error") } return len(s), nil }, wantResult: nil, wantErr: true, errMsg: "middle error", expectedCallbackCount: 2, }, { name: "error stops iteration - last item", input: []string{"a", "aa", "aaa"}, iteratee: func(s string) (int, error) { if s == "aaa" { return 0, errors.New("last error") } return len(s), nil }, wantResult: nil, wantErr: true, errMsg: "last error", expectedCallbackCount: 3, }, { name: "duplicate keys", input: []string{"a", "b", "c"}, iteratee: func(s string) (int, error) { return 1, nil }, wantResult: map[int]string{1: "c"}, wantErr: false, expectedCallbackCount: 3, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() callbackCount := 0 wrappedIteratee := func(s string) (int, error) { callbackCount++ return tt.iteratee(s) } result, err := KeyByErr(tt.input, wrappedIteratee) if tt.wantErr { assert.Error(t, err) if tt.errMsg != "" { assert.Equal(t, tt.errMsg, err.Error()) } assert.Nil(t, result) } else { assert.NoError(t, err) assert.Equal(t, tt.wantResult, result) } assert.Equal(t, tt.expectedCallbackCount, callbackCount, "callback count mismatch") }) } } func TestAssociate(t *testing.T) { t.Parallel() type foo struct { baz string bar int } transform := func(f *foo) (string, int) { return f.baz, f.bar } testCases := []struct { in []*foo want map[string]int }{ { in: []*foo{{baz: "apple", bar: 1}}, want: map[string]int{"apple": 1}, }, { in: []*foo{{baz: "apple", bar: 1}, {baz: "banana", bar: 2}}, want: map[string]int{"apple": 1, "banana": 2}, }, { in: []*foo{{baz: "apple", bar: 1}, {baz: "apple", bar: 2}}, want: map[string]int{"apple": 2}, }, } for i, tc := range testCases { tc := tc t.Run(fmt.Sprintf("test_%d", i), func(t *testing.T) { t.Parallel() assert.Equal(t, tc.want, Associate(tc.in, transform)) }) } } func TestAssociateI(t *testing.T) { t.Parallel() transform := func(s string, i int) (int, string) { return i % 2, s } testCases := []struct { in []string want map[int]string }{ { in: []string{"zero"}, want: map[int]string{0: "zero"}, }, { in: []string{"zero", "one"}, want: map[int]string{0: "zero", 1: "one"}, }, { in: []string{"two", "one", "zero"}, want: map[int]string{0: "zero", 1: "one"}, }, } for i, tc := range testCases { tc := tc t.Run(fmt.Sprintf("test_%d", i), func(t *testing.T) { t.Parallel() assert.Equal(t, tc.want, AssociateI(tc.in, transform)) }) } } func TestSliceToMap(t *testing.T) { t.Parallel() type foo struct { baz string bar int } transform := func(f *foo) (string, int) { return f.baz, f.bar } testCases := []struct { in []*foo want map[string]int }{ { in: []*foo{{baz: "apple", bar: 1}}, want: map[string]int{"apple": 1}, }, { in: []*foo{{baz: "apple", bar: 1}, {baz: "banana", bar: 2}}, want: map[string]int{"apple": 1, "banana": 2}, }, { in: []*foo{{baz: "apple", bar: 1}, {baz: "apple", bar: 2}}, want: map[string]int{"apple": 2}, }, } for i, tc := range testCases { tc := tc t.Run(fmt.Sprintf("test_%d", i), func(t *testing.T) { t.Parallel() assert.Equal(t, tc.want, SliceToMap(tc.in, transform)) }) } } func TestSliceToMapI(t *testing.T) { t.Parallel() transform := func(s string, i int) (int, string) { return i % 2, s } testCases := []struct { in []string want map[int]string }{ { in: []string{"zero"}, want: map[int]string{0: "zero"}, }, { in: []string{"zero", "one"}, want: map[int]string{0: "zero", 1: "one"}, }, { in: []string{"two", "one", "zero"}, want: map[int]string{0: "zero", 1: "one"}, }, } for i, tc := range testCases { tc := tc t.Run(fmt.Sprintf("test_%d", i), func(t *testing.T) { t.Parallel() assert.Equal(t, tc.want, SliceToMapI(tc.in, transform)) }) } } func TestFilterSliceToMap(t *testing.T) { t.Parallel() type foo struct { baz string bar int } transform := func(f *foo) (string, int, bool) { return f.baz, f.bar, f.bar > 1 } testCases := []struct { in []*foo want map[string]int }{ { in: []*foo{{baz: "apple", bar: 1}}, want: map[string]int{}, }, { in: []*foo{{baz: "apple", bar: 1}, {baz: "banana", bar: 2}}, want: map[string]int{"banana": 2}, }, { in: []*foo{{baz: "apple", bar: 1}, {baz: "apple", bar: 2}}, want: map[string]int{"apple": 2}, }, } for i, tc := range testCases { tc := tc t.Run(fmt.Sprintf("test_%d", i), func(t *testing.T) { t.Parallel() assert.Equal(t, tc.want, FilterSliceToMap(tc.in, transform)) }) } } func TestFilterSliceToMapI(t *testing.T) { t.Parallel() transform := func(s string, i int) (int, string, bool) { return i % 5, s, i%2 == 0 } testCases := []struct { in []string want map[int]string }{ { in: []string{"zero"}, want: map[int]string{0: "zero"}, }, { in: []string{"zero", "one", "two", "three", "four"}, want: map[int]string{0: "zero", 2: "two", 4: "four"}, }, { in: []string{"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"}, want: map[int]string{0: "ten", 1: "six", 2: "two", 3: "eight", 4: "four"}, }, } for i, tc := range testCases { tc := tc t.Run(fmt.Sprintf("test_%d", i), func(t *testing.T) { t.Parallel() assert.Equal(t, tc.want, FilterSliceToMapI(tc.in, transform)) }) } } func TestKeyify(t *testing.T) { t.Parallel() is := assert.New(t) result1 := Keyify([]int{1, 2, 3, 4}) result2 := Keyify([]int{1, 1, 1, 2}) result3 := Keyify([]int{}) is.Equal(map[int]struct{}{1: {}, 2: {}, 3: {}, 4: {}}, result1) is.Equal(map[int]struct{}{1: {}, 2: {}}, result2) is.Empty(result3) } func TestDrop(t *testing.T) { t.Parallel() is := assert.New(t) is.Equal([]int{0, 1, 2, 3, 4}, Drop([]int{0, 1, 2, 3, 4}, 0)) is.Equal([]int{1, 2, 3, 4}, Drop([]int{0, 1, 2, 3, 4}, 1)) is.Equal([]int{2, 3, 4}, Drop([]int{0, 1, 2, 3, 4}, 2)) is.Equal([]int{3, 4}, Drop([]int{0, 1, 2, 3, 4}, 3)) is.Equal([]int{4}, Drop([]int{0, 1, 2, 3, 4}, 4)) is.Empty(Drop([]int{0, 1, 2, 3, 4}, 5)) is.Empty(Drop([]int{0, 1, 2, 3, 4}, 6)) is.PanicsWithValue("lo.Drop: n must not be negative", func() { Drop([]int{0, 1, 2, 3, 4}, -1) }) type myStrings []string allStrings := myStrings{"", "foo", "bar"} nonempty := Drop(allStrings, 2) is.IsType(nonempty, allStrings, "type preserved") } func TestDropRight(t *testing.T) { t.Parallel() is := assert.New(t) is.Equal([]int{0, 1, 2, 3, 4}, DropRight([]int{0, 1, 2, 3, 4}, 0)) is.Equal([]int{0, 1, 2, 3}, DropRight([]int{0, 1, 2, 3, 4}, 1)) is.Equal([]int{0, 1, 2}, DropRight([]int{0, 1, 2, 3, 4}, 2)) is.Equal([]int{0, 1}, DropRight([]int{0, 1, 2, 3, 4}, 3)) is.Equal([]int{0}, DropRight([]int{0, 1, 2, 3, 4}, 4)) is.Empty(DropRight([]int{0, 1, 2, 3, 4}, 5)) is.Empty(DropRight([]int{0, 1, 2, 3, 4}, 6)) is.PanicsWithValue("lo.DropRight: n must not be negative", func() { DropRight([]int{0, 1, 2, 3, 4}, -1) }) type myStrings []string allStrings := myStrings{"", "foo", "bar"} nonempty := DropRight(allStrings, 2) is.IsType(nonempty, allStrings, "type preserved") } func TestDropWhile(t *testing.T) { t.Parallel() is := assert.New(t) is.Equal([]int{4, 5, 6}, DropWhile([]int{0, 1, 2, 3, 4, 5, 6}, func(t int) bool { return t != 4 })) is.Empty(DropWhile([]int{0, 1, 2, 3, 4, 5, 6}, func(t int) bool { return true })) is.Equal([]int{0, 1, 2, 3, 4, 5, 6}, DropWhile([]int{0, 1, 2, 3, 4, 5, 6}, func(t int) bool { return t == 10 })) type myStrings []string allStrings := myStrings{"", "foo", "bar"} nonempty := DropWhile(allStrings, func(t string) bool { return t != "foo" }) is.IsType(nonempty, allStrings, "type preserved") } func TestDropRightWhile(t *testing.T) { t.Parallel() is := assert.New(t) is.Equal([]int{0, 1, 2, 3}, DropRightWhile([]int{0, 1, 2, 3, 4, 5, 6}, func(t int) bool { return t != 3 })) is.Equal([]int{0, 1}, DropRightWhile([]int{0, 1, 2, 3, 4, 5, 6}, func(t int) bool { return t != 1 })) is.Equal([]int{0, 1, 2, 3, 4, 5, 6}, DropRightWhile([]int{0, 1, 2, 3, 4, 5, 6}, func(t int) bool { return t == 10 })) is.Empty(DropRightWhile([]int{0, 1, 2, 3, 4, 5, 6}, func(t int) bool { return t != 10 })) type myStrings []string allStrings := myStrings{"", "foo", "bar"} nonempty := DropRightWhile(allStrings, func(t string) bool { return t != "foo" }) is.IsType(nonempty, allStrings, "type preserved") } func TestTake(t *testing.T) { t.Parallel() is := assert.New(t) is.Equal([]int{0, 1, 2}, Take([]int{0, 1, 2, 3, 4}, 3)) is.Equal([]int{0, 1}, Take([]int{0, 1, 2, 3, 4}, 2)) is.Equal([]int{0}, Take([]int{0, 1, 2, 3, 4}, 1)) is.Empty(Take([]int{0, 1, 2, 3, 4}, 0)) is.Equal([]int{0, 1, 2, 3, 4}, Take([]int{0, 1, 2, 3, 4}, 5)) is.Equal([]int{0, 1, 2, 3, 4}, Take([]int{0, 1, 2, 3, 4}, 10)) is.PanicsWithValue("lo.Take: n must not be negative", func() { Take([]int{0, 1, 2, 3, 4}, -1) }) type myStrings []string allStrings := myStrings{"foo", "bar", "baz"} taken := Take(allStrings, 2) is.IsType(taken, allStrings, "type preserved") is.Equal(myStrings{"foo", "bar"}, taken) } func TestTakeWhile(t *testing.T) { t.Parallel() is := assert.New(t) is.Equal([]int{0, 1, 2, 3}, TakeWhile([]int{0, 1, 2, 3, 4, 5, 6}, func(t int) bool { return t < 4 })) is.Equal([]int{0, 1, 2, 3, 4, 5, 6}, TakeWhile([]int{0, 1, 2, 3, 4, 5, 6}, func(t int) bool { return t < 10 })) is.Empty(TakeWhile([]int{0, 1, 2, 3, 4, 5, 6}, func(t int) bool { return t < 0 })) is.Equal([]int{0, 1, 2}, TakeWhile([]int{0, 1, 2, 3, 4, 5, 6}, func(t int) bool { return t != 3 })) type myStrings []string allStrings := myStrings{"foo", "bar", "baz", "qux"} taken := TakeWhile(allStrings, func(t string) bool { return t != "baz" }) is.IsType(taken, allStrings, "type preserved") is.Equal(myStrings{"foo", "bar"}, taken) } func TestTakeFilter(t *testing.T) { t.Parallel() is := assert.New(t) is.Equal( []int{2, 4}, TakeFilter([]int{1, 2, 3, 4, 5, 6}, 2, func(item, index int) bool { return item%2 == 0 }), ) is.Equal([]int{2, 4, 6}, TakeFilter([]int{1, 2, 3, 4, 5, 6}, 10, func(item, index int) bool { return item%2 == 0 })) is.Empty(TakeFilter([]int{1, 2, 3, 4, 5, 6}, 0, func(item, index int) bool { return item%2 == 0 })) is.Empty(TakeFilter([]int{1, 3, 5}, 2, func(item, index int) bool { return item%2 == 0 })) is.Equal([]int{1}, TakeFilter([]int{1, 2, 3, 4, 5}, 1, func(item, index int) bool { return item%2 != 0 })) is.PanicsWithValue("lo.TakeFilter: n must not be negative", func() { TakeFilter([]int{1, 2, 3}, -1, func(item, index int) bool { return true }) }) type myStrings []string allStrings := myStrings{"foo", "bar", "baz", "qux"} filtered := TakeFilter(allStrings, 2, func(item string, index int) bool { return len(item) == 3 }) is.IsType(filtered, allStrings, "type preserved") is.Equal(myStrings{"foo", "bar"}, filtered) } func TestDropByIndex(t *testing.T) { t.Parallel() is := assert.New(t) is.Equal([]int{1, 2, 3, 4}, DropByIndex([]int{0, 1, 2, 3, 4}, 0)) is.Equal([]int{3, 4}, DropByIndex([]int{0, 1, 2, 3, 4}, 0, 1, 2)) is.Equal([]int{0, 4}, DropByIndex([]int{0, 1, 2, 3, 4}, -4, -2, -3)) is.Equal([]int{0, 2, 3, 4}, DropByIndex([]int{0, 1, 2, 3, 4}, -4, -4)) is.Equal([]int{2, 4}, DropByIndex([]int{0, 1, 2, 3, 4}, 3, 1, 0)) is.Equal([]int{0, 1, 3, 4}, DropByIndex([]int{0, 1, 2, 3, 4}, 2)) is.Equal([]int{0, 1, 2, 3}, DropByIndex([]int{0, 1, 2, 3, 4}, 4)) is.Equal([]int{0, 1, 2, 3, 4}, DropByIndex([]int{0, 1, 2, 3, 4})) is.Equal([]int{0, 1, 2, 3, 4}, DropByIndex([]int{0, 1, 2, 3, 4}, 5)) is.Equal([]int{0, 1, 2, 3, 4}, DropByIndex([]int{0, 1, 2, 3, 4}, 100)) is.Equal([]int{0, 1, 2, 3, 4}, DropByIndex([]int{0, 1, 2, 3, 4}, -100)) is.Equal([]int{0, 1, 2, 3}, DropByIndex([]int{0, 1, 2, 3, 4}, -1)) is.Equal([]int{0, 1, 2, 3}, DropByIndex([]int{0, 1, 2, 3, 4}, -1, 4)) is.Equal([]int{0, 1, 2, 3}, DropByIndex([]int{0, 1, 2, 3, 4}, -100, 4)) is.Empty(DropByIndex([]int{}, 0, 1)) is.Empty(DropByIndex([]int{42}, 0, 1)) is.Empty(DropByIndex([]int{42}, 1, 0)) is.Empty(DropByIndex([]int{}, 1)) is.Empty(DropByIndex([]int{1}, 0)) type myStrings []string allStrings := myStrings{"", "foo", "bar"} nonempty := DropByIndex(allStrings, 0) is.IsType(nonempty, allStrings, "type preserved") } func TestReject(t *testing.T) { t.Parallel() is := assert.New(t) r1 := Reject([]int{1, 2, 3, 4}, func(x, _ int) bool { return x%2 == 0 }) is.Equal([]int{1, 3}, r1) r2 := Reject([]string{"Smith", "foo", "Domin", "bar", "Olivia"}, func(x string, _ int) bool { return len(x) > 3 }) is.Equal([]string{"foo", "bar"}, r2) type myStrings []string allStrings := myStrings{"", "foo", "bar"} nonempty := Reject(allStrings, func(x string, _ int) bool { return len(x) > 0 }) is.IsType(nonempty, allStrings, "type preserved") } func TestRejectErr(t *testing.T) { t.Parallel() is := assert.New(t) tests := []struct { name string input []int predicate func(item, index int) (bool, error) want []int wantErr string callbacks int // Number of predicates called before error/finish }{ { name: "reject even numbers", input: []int{1, 2, 3, 4}, predicate: func(x, _ int) (bool, error) { return x%2 == 0, nil }, want: []int{1, 3}, callbacks: 4, }, { name: "empty slice", input: []int{}, predicate: func(x, _ int) (bool, error) { return true, nil }, want: []int{}, callbacks: 0, }, { name: "reject all out", input: []int{1, 2, 3, 4}, predicate: func(x, _ int) (bool, error) { return false, nil }, want: []int{1, 2, 3, 4}, callbacks: 4, }, { name: "reject all in", input: []int{1, 2, 3, 4}, predicate: func(x, _ int) (bool, error) { return true, nil }, want: []int{}, callbacks: 4, }, { name: "error on specific index", input: []int{1, 2, 3, 4}, predicate: func(x, _ int) (bool, error) { if x == 3 { return false, errors.New("number 3 is not allowed") } return x%2 == 0, nil }, callbacks: 3, wantErr: "number 3 is not allowed", }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() var callbacks int wrappedPredicate := func(item, index int) (bool, error) { callbacks++ return tt.predicate(item, index) } got, err := RejectErr(tt.input, wrappedPredicate) if tt.wantErr != "" { is.Error(err) is.Equal(tt.wantErr, err.Error()) is.Nil(got) is.Equal(tt.callbacks, callbacks, "callback count should match expected early return") } else { is.NoError(err) is.Equal(tt.want, got) is.Equal(tt.callbacks, callbacks) } }) } // Test type preservation type myStrings []string allStrings := myStrings{"", "foo", "bar"} nonempty, err := RejectErr(allStrings, func(x string, _ int) (bool, error) { return len(x) > 0, nil }) is.NoError(err) is.IsType(nonempty, allStrings, "type preserved") is.Equal(myStrings{""}, nonempty) } func TestRejectMap(t *testing.T) { t.Parallel() is := assert.New(t) r1 := RejectMap([]int64{1, 2, 3, 4}, func(x int64, _ int) (string, bool) { if x%2 == 0 { return strconv.FormatInt(x, 10), false } return "", true }) r2 := RejectMap([]string{"cpu", "gpu", "mouse", "keyboard"}, func(x string, _ int) (string, bool) { if strings.HasSuffix(x, "pu") { return "xpu", false } return "", true }) is.Equal([]string{"2", "4"}, r1) is.Equal([]string{"xpu", "xpu"}, r2) } func TestFilterReject(t *testing.T) { t.Parallel() is := assert.New(t) left1, right1 := FilterReject([]int{1, 2, 3, 4}, func(x, _ int) bool { return x%2 == 0 }) is.Equal([]int{2, 4}, left1) is.Equal([]int{1, 3}, right1) left2, right2 := FilterReject([]string{"Smith", "foo", "Domin", "bar", "Olivia"}, func(x string, _ int) bool { return len(x) > 3 }) is.Equal([]string{"Smith", "Domin", "Olivia"}, left2) is.Equal([]string{"foo", "bar"}, right2) type myStrings []string allStrings := myStrings{"", "foo", "bar"} a, b := FilterReject(allStrings, func(x string, _ int) bool { return len(x) > 0 }) is.IsType(a, allStrings, "type preserved") is.IsType(b, allStrings, "type preserved") } func TestCount(t *testing.T) { t.Parallel() is := assert.New(t) count1 := Count([]int{1, 2, 1}, 1) count2 := Count([]int{1, 2, 1}, 3) count3 := Count([]int{}, 1) is.Equal(2, count1) is.Zero(count2) is.Zero(count3) } func TestCountBy(t *testing.T) { t.Parallel() is := assert.New(t) count1 := CountBy([]int{1, 2, 1}, func(i int) bool { return i < 2 }) count2 := CountBy([]int{1, 2, 1}, func(i int) bool { return i > 2 }) count3 := CountBy([]int{}, func(i int) bool { return i <= 2 }) is.Equal(2, count1) is.Zero(count2) is.Zero(count3) } func TestCountByErr(t *testing.T) { t.Parallel() tests := []struct { name string input []int predicate func(int) (bool, error) want int wantErr string wantCallCount int }{ { name: "count elements less than 2", input: []int{1, 2, 1}, predicate: func(i int) (bool, error) { return i < 2, nil }, want: 2, wantErr: "", wantCallCount: 3, }, { name: "count elements greater than 2", input: []int{1, 2, 1}, predicate: func(i int) (bool, error) { return i > 2, nil }, want: 0, wantErr: "", wantCallCount: 3, }, { name: "empty slice", input: []int{}, predicate: func(i int) (bool, error) { return i <= 2, nil }, want: 0, wantErr: "", wantCallCount: 0, }, { name: "error on third element", input: []int{1, 2, 3, 4, 5}, predicate: func(i int) (bool, error) { if i == 3 { return false, fmt.Errorf("error at %d", i) } return i < 3, nil }, want: 0, wantErr: "error at 3", wantCallCount: 3, // stops early at error }, { name: "error on first element", input: []int{1, 2, 3}, predicate: func(i int) (bool, error) { return false, errors.New("first element error") }, want: 0, wantErr: "first element error", wantCallCount: 1, }, { name: "all match", input: []int{1, 2, 3}, predicate: func(i int) (bool, error) { return i > 0, nil }, want: 3, wantErr: "", wantCallCount: 3, }, } for _, tt := range tests { tt := tt // capture range variable t.Run(tt.name, func(t *testing.T) { t.Parallel() is := assert.New(t) callCount := 0 wrappedPredicate := func(i int) (bool, error) { callCount++ return tt.predicate(i) } got, err := CountByErr(tt.input, wrappedPredicate) if tt.wantErr != "" { is.Error(err) is.Equal(tt.wantErr, err.Error()) is.Equal(tt.want, got) if tt.wantCallCount > 0 { is.Equal(tt.wantCallCount, callCount, "should stop early on error") } } else { is.NoError(err) is.Equal(tt.want, got) is.Equal(tt.wantCallCount, callCount) } }) } } func TestCountValues(t *testing.T) { t.Parallel() is := assert.New(t) is.Empty(CountValues([]int{})) is.Equal(map[int]int{1: 1, 2: 1}, CountValues([]int{1, 2})) is.Equal(map[int]int{1: 1, 2: 2}, CountValues([]int{1, 2, 2})) is.Equal(map[string]int{"": 1, "foo": 1, "bar": 1}, CountValues([]string{"foo", "bar", ""})) is.Equal(map[string]int{"foo": 1, "bar": 2}, CountValues([]string{"foo", "bar", "bar"})) } func TestCountValuesBy(t *testing.T) { t.Parallel() is := assert.New(t) oddEven := func(v int) bool { return v%2 == 0 } length := func(v string) int { return len(v) } result1 := CountValuesBy([]int{}, oddEven) result2 := CountValuesBy([]int{1, 2}, oddEven) result3 := CountValuesBy([]int{1, 2, 2}, oddEven) result4 := CountValuesBy([]string{"foo", "bar", ""}, length) result5 := CountValuesBy([]string{"foo", "bar", "bar"}, length) is.Empty(result1) is.Equal(map[bool]int{true: 1, false: 1}, result2) is.Equal(map[bool]int{true: 2, false: 1}, result3) is.Equal(map[int]int{0: 1, 3: 2}, result4) is.Equal(map[int]int{3: 3}, result5) } func TestSubset(t *testing.T) { t.Parallel() is := assert.New(t) in := []int{0, 1, 2, 3, 4} out1 := Subset(in, 0, 0) out2 := Subset(in, 10, 2) out3 := Subset(in, -10, 2) out4 := Subset(in, 0, 10) out5 := Subset(in, 0, 2) out6 := Subset(in, 2, 2) out7 := Subset(in, 2, 5) out8 := Subset(in, 2, 3) out9 := Subset(in, 2, 4) out10 := Subset(in, -2, 4) out11 := Subset(in, -4, 1) out12 := Subset(in, -4, math.MaxUint) is.Empty(out1) is.Empty(out2) is.Equal([]int{0, 1}, out3) is.Equal([]int{0, 1, 2, 3, 4}, out4) is.Equal([]int{0, 1}, out5) is.Equal([]int{2, 3}, out6) is.Equal([]int{2, 3, 4}, out7) is.Equal([]int{2, 3, 4}, out8) is.Equal([]int{2, 3, 4}, out9) is.Equal([]int{3, 4}, out10) is.Equal([]int{1}, out11) is.Equal([]int{1, 2, 3, 4}, out12) type myStrings []string allStrings := myStrings{"", "foo", "bar"} nonempty := Subset(allStrings, 0, 2) is.IsType(nonempty, allStrings, "type preserved") } func TestSlice(t *testing.T) { t.Parallel() is := assert.New(t) in := []int{0, 1, 2, 3, 4} out1 := Slice(in, 0, 0) out2 := Slice(in, 0, 1) out3 := Slice(in, 0, 5) out4 := Slice(in, 0, 6) out5 := Slice(in, 1, 1) out6 := Slice(in, 1, 5) out7 := Slice(in, 1, 6) out8 := Slice(in, 4, 5) out9 := Slice(in, 5, 5) out10 := Slice(in, 6, 5) out11 := Slice(in, 6, 6) out12 := Slice(in, 1, 0) out13 := Slice(in, 5, 0) out14 := Slice(in, 6, 4) out15 := Slice(in, 6, 7) out16 := Slice(in, -10, 1) out17 := Slice(in, -1, 3) out18 := Slice(in, -10, 7) out19 := Slice(in, -10, -1) is.Empty(out1) is.Equal([]int{0}, out2) is.Equal([]int{0, 1, 2, 3, 4}, out3) is.Equal([]int{0, 1, 2, 3, 4}, out4) is.Empty(out5) is.Equal([]int{1, 2, 3, 4}, out6) is.Equal([]int{1, 2, 3, 4}, out7) is.Equal([]int{4}, out8) is.Empty(out9) is.Empty(out10) is.Empty(out11) is.Empty(out12) is.Empty(out13) is.Empty(out14) is.Empty(out15) is.Equal([]int{0}, out16) is.Equal([]int{0, 1, 2}, out17) is.Equal([]int{0, 1, 2, 3, 4}, out18) is.Empty(out19) type myStrings []string allStrings := myStrings{"", "foo", "bar"} nonempty := Slice(allStrings, 0, 2) is.IsType(nonempty, allStrings, "type preserved") } func TestReplace(t *testing.T) { t.Parallel() is := assert.New(t) in := []int{0, 1, 0, 1, 2, 3, 0} out1 := Replace(in, 0, 42, 2) out2 := Replace(in, 0, 42, 1) out3 := Replace(in, 0, 42, 0) out4 := Replace(in, 0, 42, -1) out5 := Replace(in, 0, 42, -1) out6 := Replace(in, -1, 42, 2) out7 := Replace(in, -1, 42, 1) out8 := Replace(in, -1, 42, 0) out9 := Replace(in, -1, 42, -1) out10 := Replace(in, -1, 42, -1) is.Equal([]int{42, 1, 42, 1, 2, 3, 0}, out1) is.Equal([]int{42, 1, 0, 1, 2, 3, 0}, out2) is.Equal([]int{0, 1, 0, 1, 2, 3, 0}, out3) is.Equal([]int{42, 1, 42, 1, 2, 3, 42}, out4) is.Equal([]int{42, 1, 42, 1, 2, 3, 42}, out5) is.Equal([]int{0, 1, 0, 1, 2, 3, 0}, out6) is.Equal([]int{0, 1, 0, 1, 2, 3, 0}, out7) is.Equal([]int{0, 1, 0, 1, 2, 3, 0}, out8) is.Equal([]int{0, 1, 0, 1, 2, 3, 0}, out9) is.Equal([]int{0, 1, 0, 1, 2, 3, 0}, out10) type myStrings []string allStrings := myStrings{"", "foo", "bar"} nonempty := Replace(allStrings, "0", "2", 1) is.IsType(nonempty, allStrings, "type preserved") } func TestReplaceAll(t *testing.T) { t.Parallel() is := assert.New(t) in := []int{0, 1, 0, 1, 2, 3, 0} out1 := ReplaceAll(in, 0, 42) out2 := ReplaceAll(in, -1, 42) is.Equal([]int{42, 1, 42, 1, 2, 3, 42}, out1) is.Equal([]int{0, 1, 0, 1, 2, 3, 0}, out2) type myStrings []string allStrings := myStrings{"", "foo", "bar"} nonempty := ReplaceAll(allStrings, "0", "2") is.IsType(nonempty, allStrings, "type preserved") } func TestClone(t *testing.T) { t.Parallel() is := assert.New(t) // Test with int slice original1 := []int{1, 2, 3, 4, 5} result1 := Clone(original1) is.Equal([]int{1, 2, 3, 4, 5}, result1) // Verify it's a different slice by checking that modifying one doesn't affect the other original1[0] = 99 is.Equal([]int{99, 2, 3, 4, 5}, original1) is.Equal([]int{1, 2, 3, 4, 5}, result1) // Test with string slice original2 := []string{"a", "b", "c"} result2 := Clone(original2) is.Equal([]string{"a", "b", "c"}, result2) // Test with empty slice original3 := []int{} result3 := Clone(original3) is.Equal([]int{}, result3) is.Empty(result3) // Test with nil slice var original4 []int result4 := Clone(original4) is.Nil(result4) // Verify shallow copy behavior - modifying clone doesn't affect original original5 := []int{1, 2, 3} result5 := Clone(original5) result5[0] = 99 is.Equal([]int{1, 2, 3}, original5) // Original unchanged is.Equal([]int{99, 2, 3}, result5) // Clone changed type myStrings []string original6 := myStrings{"", "foo", "bar"} result6 := Clone(original6) result6[0] = "baz" is.Equal(myStrings{"", "foo", "bar"}, original6) // Original unchanged is.Equal(myStrings{"baz", "foo", "bar"}, result6) // Clone changed } func TestCompact(t *testing.T) { t.Parallel() is := assert.New(t) r1 := Compact([]int{2, 0, 4, 0}) is.Equal([]int{2, 4}, r1) r2 := Compact([]string{"", "foo", "", "bar", ""}) is.Equal([]string{"foo", "bar"}, r2) r3 := Compact([]bool{true, false, true, false}) is.Equal([]bool{true, true}, r3) type foo struct { bar int baz string } // slice of structs // If all fields of an element are zero values, Compact removes it. r4 := Compact([]foo{ {bar: 1, baz: "a"}, // all fields are non-zero values {bar: 0, baz: ""}, // all fields are zero values {bar: 2, baz: ""}, // bar is non-zero }) is.Equal([]foo{{bar: 1, baz: "a"}, {bar: 2, baz: ""}}, r4) // slice of pointers to structs // If an element is nil, Compact removes it. e1, e2, e3 := foo{bar: 1, baz: "a"}, foo{bar: 0, baz: ""}, foo{bar: 2, baz: ""} // NOTE: e2 is a zero value of foo, but its pointer &e2 is not a zero value of *foo. r5 := Compact([]*foo{&e1, &e2, nil, &e3}) is.Equal([]*foo{&e1, &e2, &e3}, r5) type myStrings []string allStrings := myStrings{"", "foo", "bar"} nonempty := Compact(allStrings) is.IsType(nonempty, allStrings, "type preserved") } func TestIsSorted(t *testing.T) { t.Parallel() is := assert.New(t) is.True(IsSorted([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})) is.True(IsSorted([]string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"})) is.False(IsSorted([]int{0, 1, 4, 3, 2, 5, 6, 7, 8, 9, 10})) is.False(IsSorted([]string{"a", "b", "d", "c", "e", "f", "g", "h", "i", "j"})) } func TestIsSortedBy(t *testing.T) { t.Parallel() is := assert.New(t) is.True(IsSortedBy([]string{"a", "bb", "ccc"}, func(s string) int { return len(s) })) is.False(IsSortedBy([]string{"aa", "b", "ccc"}, func(s string) int { return len(s) })) is.True(IsSortedBy([]string{"1", "2", "3", "11"}, func(s string) int { ret, _ := strconv.Atoi(s) return ret })) } func TestSplice(t *testing.T) { t.Parallel() is := assert.New(t) sample := []string{"a", "b", "c", "d", "e", "f", "g"} // normal case results := Splice(sample, 1, "1", "2") is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, sample) is.Equal([]string{"a", "1", "2", "b", "c", "d", "e", "f", "g"}, results) // check there is no side effect results = Splice(sample, 1) results[0] = "b" is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, sample) // positive overflow results = Splice(sample, 42, "1", "2") is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, sample) is.Equal([]string{"a", "b", "c", "d", "e", "f", "g", "1", "2"}, results) // negative overflow results = Splice(sample, -42, "1", "2") is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, sample) is.Equal([]string{"1", "2", "a", "b", "c", "d", "e", "f", "g"}, results) // backward results = Splice(sample, -2, "1", "2") is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, sample) is.Equal([]string{"a", "b", "c", "d", "e", "1", "2", "f", "g"}, results) results = Splice(sample, -7, "1", "2") is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, sample) is.Equal([]string{"1", "2", "a", "b", "c", "d", "e", "f", "g"}, results) // other is.Equal([]string{"1", "2"}, Splice([]string{}, 0, "1", "2")) is.Equal([]string{"1", "2"}, Splice([]string{}, 1, "1", "2")) is.Equal([]string{"1", "2"}, Splice([]string{}, -1, "1", "2")) is.Equal([]string{"1", "2", "0"}, Splice([]string{"0"}, 0, "1", "2")) is.Equal([]string{"0", "1", "2"}, Splice([]string{"0"}, 1, "1", "2")) is.Equal([]string{"1", "2", "0"}, Splice([]string{"0"}, -1, "1", "2")) // type preserved type myStrings []string allStrings := myStrings{"", "foo", "bar"} nonempty := Splice(allStrings, 1, "1", "2") is.IsType(nonempty, allStrings, "type preserved") } func TestCutSuccess(t *testing.T) { t.Parallel() is := assert.New(t) // case 1 actualLeft, actualRight, result := Cut([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"a", "b"}) is.True(result) is.Equal([]string{}, actualLeft) is.Equal([]string{"c", "d", "e", "f", "g"}, actualRight) // case 2 actualLeft, actualRight, result = Cut([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"f", "g"}) is.True(result) is.Equal([]string{"a", "b", "c", "d", "e"}, actualLeft) is.Equal([]string{}, actualRight) // case 3 actualLeft, actualRight, result = Cut([]string{"g"}, []string{"g"}) is.True(result) is.Equal([]string{}, actualLeft) is.Equal([]string{}, actualRight) // case 4 actualLeft, actualRight, result = Cut([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"b", "c"}) is.True(result) is.Equal([]string{"a"}, actualLeft) is.Equal([]string{"d", "e", "f", "g"}, actualRight) // case 5 actualLeft, actualRight, result = Cut([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"e", "f"}) is.True(result) is.Equal([]string{"a", "b", "c", "d"}, actualLeft) is.Equal([]string{"g"}, actualRight) // case 6 actualLeft, actualRight, result = Cut([]string{"a", "b"}, []string{"b"}) is.True(result) is.Equal([]string{"a"}, actualLeft) is.Equal([]string{}, actualRight) // case 7 actualLeft, actualRight, result = Cut([]string{"a", "b"}, []string{"a"}) is.True(result) is.Equal([]string{}, actualLeft) is.Equal([]string{"b"}, actualRight) } func TestCutFail(t *testing.T) { t.Parallel() is := assert.New(t) // case 1 actualLeft, actualRight, result := Cut([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"z"}) is.False(result) is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, actualLeft) is.Equal([]string{}, actualRight) // case 2 actualLeft, actualRight, result = Cut([]string{}, []string{"z"}) is.False(result) is.Equal([]string{}, actualLeft) is.Equal([]string{}, actualRight) // case 3 actualLeft, actualRight, result = Cut([]string{"a"}, []string{"z"}) is.False(result) is.Equal([]string{"a"}, actualLeft) is.Equal([]string{}, actualRight) } type TestCutStruct struct { id int data string } func TestCutPrefix(t *testing.T) { t.Parallel() is := assert.New(t) // case 1 actualAfter, result := CutPrefix( []TestCutStruct{{id: 1, data: "a"}, {id: 2, data: "a"}, {id: 2, data: "b"}}, []TestCutStruct{{id: 1, data: "a"}}, ) is.True(result) is.Equal([]TestCutStruct{{id: 2, data: "a"}, {id: 2, data: "b"}}, actualAfter) // case 2 actualAfter, result = CutPrefix( []TestCutStruct{{id: 1, data: "a"}, {id: 2, data: "a"}, {id: 2, data: "b"}}, []TestCutStruct{}, ) is.True(result) is.Equal([]TestCutStruct{{id: 1, data: "a"}, {id: 2, data: "a"}, {id: 2, data: "b"}}, actualAfter) // case 3 actualAfter, result = CutPrefix( []TestCutStruct{{id: 1, data: "a"}, {id: 2, data: "a"}, {id: 2, data: "b"}}, []TestCutStruct{{id: 2, data: "b"}}, ) is.False(result) is.Equal([]TestCutStruct{{id: 1, data: "a"}, {id: 2, data: "a"}, {id: 2, data: "b"}}, actualAfter) // case 4 actualAfter, result = CutPrefix( []TestCutStruct{}, []TestCutStruct{{id: 2, data: "b"}}, ) is.False(result) is.Equal([]TestCutStruct{}, actualAfter) // case 5 actualAfterS, result := CutPrefix([]string{"a", "a", "b"}, []string{}) is.True(result) is.Equal([]string{"a", "a", "b"}, actualAfterS) } func TestCutSuffix(t *testing.T) { t.Parallel() is := assert.New(t) // case 1 actualBefore, result := CutSuffix( []TestCutStruct{{id: 1, data: "a"}, {id: 2, data: "a"}, {id: 2, data: "b"}}, []TestCutStruct{{id: 3, data: "b"}}, ) is.False(result) is.Equal([]TestCutStruct{{id: 1, data: "a"}, {id: 2, data: "a"}, {id: 2, data: "b"}}, actualBefore) // case 2 actualBefore, result = CutSuffix( []TestCutStruct{{id: 1, data: "a"}, {id: 2, data: "a"}, {id: 2, data: "b"}}, []TestCutStruct{{id: 2, data: "b"}}, ) is.True(result) is.Equal([]TestCutStruct{{id: 1, data: "a"}, {id: 2, data: "a"}}, actualBefore) // case 3 actualBefore, result = CutSuffix( []TestCutStruct{{id: 1, data: "a"}, {id: 2, data: "a"}, {id: 2, data: "b"}}, []TestCutStruct{}, ) is.True(result) is.Equal([]TestCutStruct{{id: 1, data: "a"}, {id: 2, data: "a"}, {id: 2, data: "b"}}, actualBefore) // case 4 actualBefore, result = CutSuffix( []TestCutStruct{{id: 1, data: "a"}, {id: 2, data: "a"}, {id: 2, data: "b"}}, []TestCutStruct{{id: 2, data: "a"}}, ) is.False(result) is.Equal([]TestCutStruct{{id: 1, data: "a"}, {id: 2, data: "a"}, {id: 2, data: "b"}}, actualBefore) // case 5 actualAfterS, result := CutSuffix([]string{"a", "a", "b"}, []string{}) is.True(result) is.Equal([]string{"a", "a", "b"}, actualAfterS) } func TestTrim(t *testing.T) { t.Parallel() is := assert.New(t) actual := Trim([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"a", "b"}) is.Equal([]string{"c", "d", "e", "f", "g"}, actual) actual = Trim([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"g", "f"}) is.Equal([]string{"a", "b", "c", "d", "e"}, actual) actual = Trim([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"a", "b", "c", "d", "e", "f", "g"}) is.Equal([]string{}, actual) actual = Trim([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"a", "b", "c", "d", "e", "f", "g", "h"}) is.Equal([]string{}, actual) actual = Trim([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{}) is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, actual) } func TestTrimLeft(t *testing.T) { t.Parallel() is := assert.New(t) actual := TrimLeft([]string{"a", "a", "b", "c", "d", "e", "f", "g"}, []string{"a", "b"}) is.Equal([]string{"c", "d", "e", "f", "g"}, actual) actual = TrimLeft([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"b", "a"}) is.Equal([]string{"c", "d", "e", "f", "g"}, actual) actual = TrimLeft([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"g", "f"}) is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, actual) actual = TrimLeft([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"a", "b", "c", "d", "e", "f", "g"}) is.Equal([]string{}, actual) actual = TrimLeft([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"a", "b", "c", "d", "e", "f", "g", "h"}) is.Equal([]string{}, actual) actual = TrimLeft([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{}) is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, actual) } func TestTrimPrefix(t *testing.T) { t.Parallel() is := assert.New(t) actual := TrimPrefix([]string{"a", "b", "a", "b", "c", "d", "e", "f", "g"}, []string{"a", "b"}) is.Equal([]string{"c", "d", "e", "f", "g"}, actual) actual = TrimPrefix([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"b", "a"}) is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, actual) actual = TrimPrefix([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"g", "f"}) is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, actual) actual = TrimPrefix([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"a", "b", "c", "d", "e", "f", "g"}) is.Equal([]string{}, actual) actual = TrimPrefix([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"a", "b", "c", "d", "e", "f", "g", "h"}) is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, actual) actual = TrimPrefix([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{}) is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, actual) } func TestTrimRight(t *testing.T) { t.Parallel() is := assert.New(t) actual := TrimRight([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"a", "b"}) is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, actual) actual = TrimRight([]string{"a", "b", "c", "d", "e", "f", "g", "g"}, []string{"g", "f"}) is.Equal([]string{"a", "b", "c", "d", "e"}, actual) actual = TrimRight([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"a", "b", "c", "d", "e", "f", "g"}) is.Equal([]string{}, actual) actual = TrimRight([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"a", "b", "c", "d", "e", "f", "g", "h"}) is.Equal([]string{}, actual) actual = TrimRight([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{}) is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, actual) } func TestTrimSuffix(t *testing.T) { t.Parallel() is := assert.New(t) actual := TrimSuffix([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"a", "b"}) is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, actual) actual = TrimSuffix([]string{"a", "b", "c", "d", "e", "f", "g", "f", "g"}, []string{"f", "g"}) is.Equal([]string{"a", "b", "c", "d", "e"}, actual) actual = TrimSuffix([]string{"a", "b", "c", "d", "e", "f", "g", "f", "g"}, []string{"g", "f"}) is.Equal([]string{"a", "b", "c", "d", "e", "f", "g", "f", "g"}, actual) actual = TrimSuffix([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"a", "b", "c", "d", "e", "f", "g"}) is.Equal([]string{}, actual) actual = TrimSuffix([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"a", "b", "c", "d", "e", "f", "g", "h"}) is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, actual) actual = TrimSuffix([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{}) is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, actual) }