1package funk
2
3import (
4	"database/sql"
5	"fmt"
6	"reflect"
7	"testing"
8
9	"github.com/stretchr/testify/assert"
10	"github.com/stretchr/testify/require"
11)
12
13func TestMap(t *testing.T) {
14	is := assert.New(t)
15
16	r := Map([]int{1, 2, 3, 4}, func(x int) string {
17		return "Hello"
18	})
19
20	result, ok := r.([]string)
21
22	is.True(ok)
23	is.Equal(len(result), 4)
24
25	r = Map([]int{1, 2, 3, 4}, func(x int) (int, int) {
26		return x, x
27	})
28
29	resultType := reflect.TypeOf(r)
30
31	is.True(resultType.Kind() == reflect.Map)
32	is.True(resultType.Key().Kind() == reflect.Int)
33	is.True(resultType.Elem().Kind() == reflect.Int)
34
35	mapping := map[int]string{
36		1: "Florent",
37		2: "Gilles",
38	}
39
40	r = Map(mapping, func(k int, v string) int {
41		return k
42	})
43
44	is.True(reflect.TypeOf(r).Kind() == reflect.Slice)
45	is.True(reflect.TypeOf(r).Elem().Kind() == reflect.Int)
46
47	r = Map(mapping, func(k int, v string) (string, string) {
48		return fmt.Sprintf("%d", k), v
49	})
50
51	resultType = reflect.TypeOf(r)
52
53	is.True(resultType.Kind() == reflect.Map)
54	is.True(resultType.Key().Kind() == reflect.String)
55	is.True(resultType.Elem().Kind() == reflect.String)
56}
57
58func TestFlatMap(t *testing.T) {
59
60	is := assert.New(t)
61
62	x := reflect.Value{}.IsValid()
63	fmt.Println(x)
64
65	r := FlatMap([][]int{{1}, {2}, {3}, {4}}, func(x []int) []int {
66		return x
67	})
68
69	result, ok := r.([]int)
70
71	is.True(ok)
72	is.ElementsMatch(result, []int{1, 2, 3, 4})
73
74	mapping := map[string][]int{
75		"a": {1},
76		"b": {2},
77	}
78
79	r = FlatMap(mapping, func(k string, v []int) []int {
80		return v
81	})
82
83	result, ok = r.([]int)
84
85	is.True(ok)
86	is.ElementsMatch(result, []int{1, 2})
87}
88
89func TestToMap(t *testing.T) {
90	is := assert.New(t)
91
92	f := &Foo{
93		ID:        1,
94		FirstName: "Dark",
95		LastName:  "Vador",
96		Age:       30,
97		Bar: &Bar{
98			Name: "Test",
99		},
100	}
101
102	results := []*Foo{f}
103
104	instanceMap := ToMap(results, "ID")
105
106	is.True(reflect.TypeOf(instanceMap).Kind() == reflect.Map)
107
108	mapping, ok := instanceMap.(map[int]*Foo)
109
110	is.True(ok)
111
112	for _, result := range results {
113		item, ok := mapping[result.ID]
114
115		is.True(ok)
116		is.True(reflect.TypeOf(item).Kind() == reflect.Ptr)
117		is.True(reflect.TypeOf(item).Elem().Kind() == reflect.Struct)
118
119		is.Equal(item.ID, result.ID)
120	}
121}
122
123func TestChunk(t *testing.T) {
124	is := assert.New(t)
125
126	results := Chunk([]int{0, 1, 2, 3, 4}, 2).([][]int)
127
128	is.Len(results, 3)
129	is.Len(results[0], 2)
130	is.Len(results[1], 2)
131	is.Len(results[2], 1)
132
133	is.Len(Chunk([]int{}, 2), 0)
134	is.Len(Chunk([]int{1}, 2), 1)
135	is.Len(Chunk([]int{1, 2, 3}, 0), 3)
136}
137
138func TestFlatten(t *testing.T) {
139	is := assert.New(t)
140
141	is.Equal(Flatten([][][]int{{{1, 2}}, {{3, 4}}}), [][]int{{1, 2}, {3, 4}})
142}
143
144func TestFlattenDeep(t *testing.T) {
145	is := assert.New(t)
146
147	is.Equal(FlattenDeep([][][]int{{{1, 2}}, {{3, 4}}}), []int{1, 2, 3, 4})
148}
149
150func TestShuffle(t *testing.T) {
151	initial := []int{0, 1, 2, 3, 4}
152
153	results := Shuffle(initial)
154
155	is := assert.New(t)
156
157	is.Len(results, 5)
158
159	for _, entry := range initial {
160		is.True(Contains(results, entry))
161	}
162}
163
164func TestReverse(t *testing.T) {
165	results := Reverse([]int{0, 1, 2, 3, 4})
166
167	is := assert.New(t)
168
169	is.Equal(Reverse("abcdefg"), "gfedcba")
170	is.Len(results, 5)
171
172	is.Equal(results, []int{4, 3, 2, 1, 0})
173}
174
175func TestUniq(t *testing.T) {
176	is := assert.New(t)
177
178	results := Uniq([]int{0, 1, 1, 2, 3, 0, 0, 12})
179	is.Len(results, 5)
180	is.Equal(results, []int{0, 1, 2, 3, 12})
181
182	results = Uniq([]string{"foo", "bar", "foo", "bar", "bar"})
183	is.Len(results, 2)
184	is.Equal(results, []string{"foo", "bar"})
185}
186
187func TestConvertSlice(t *testing.T) {
188	instances := []*Foo{foo, foo2}
189
190	var raw []Model
191
192	ConvertSlice(instances, &raw)
193
194	is := assert.New(t)
195
196	is.Len(raw, len(instances))
197}
198
199func TestDrop(t *testing.T) {
200	results := Drop([]int{0, 1, 1, 2, 3, 0, 0, 12}, 3)
201
202	is := assert.New(t)
203
204	is.Len(results, 5)
205
206	is.Equal([]int{2, 3, 0, 0, 12}, results)
207}
208
209func TestPrune(t *testing.T) {
210
211	var testCases = []struct {
212		OriginalFoo *Foo
213		Paths       []string
214		ExpectedFoo *Foo
215	}{
216		{
217			foo,
218			[]string{"FirstName"},
219			&Foo{
220				FirstName: foo.FirstName,
221			},
222		},
223		{
224			foo,
225			[]string{"FirstName", "ID"},
226			&Foo{
227				FirstName: foo.FirstName,
228				ID:        foo.ID,
229			},
230		},
231		{
232			foo,
233			[]string{"EmptyValue.Int64"},
234			&Foo{
235				EmptyValue: sql.NullInt64{
236					Int64: foo.EmptyValue.Int64,
237				},
238			},
239		},
240		{
241			foo,
242			[]string{"FirstName", "ID", "EmptyValue.Int64"},
243			&Foo{
244				FirstName: foo.FirstName,
245				ID:        foo.ID,
246				EmptyValue: sql.NullInt64{
247					Int64: foo.EmptyValue.Int64,
248				},
249			},
250		},
251		{
252			foo,
253			[]string{"FirstName", "ID", "EmptyValue.Int64"},
254			&Foo{
255				FirstName: foo.FirstName,
256				ID:        foo.ID,
257				EmptyValue: sql.NullInt64{
258					Int64: foo.EmptyValue.Int64,
259				},
260			},
261		},
262		{
263			foo,
264			[]string{"FirstName", "ID", "Bar"},
265			&Foo{
266				FirstName: foo.FirstName,
267				ID:        foo.ID,
268				Bar:       foo.Bar,
269			},
270		},
271		{
272			foo,
273			[]string{"Bar", "Bars"},
274			&Foo{
275				Bar:  foo.Bar,
276				Bars: foo.Bars,
277			},
278		},
279		{
280			foo,
281			[]string{"FirstName", "Bars.Name"},
282			&Foo{
283				FirstName: foo.FirstName,
284				Bars: []*Bar{
285					{Name: bar.Name},
286					{Name: bar.Name},
287				},
288			},
289		},
290		{
291			foo,
292			[]string{"Bars.Name", "Bars.Bars.Name"},
293			&Foo{
294				Bars: []*Bar{
295					{Name: bar.Name, Bars: []*Bar{{Name: "Level1-1"}, {Name: "Level1-2"}}},
296					{Name: bar.Name, Bars: []*Bar{{Name: "Level1-1"}, {Name: "Level1-2"}}},
297				},
298			},
299		},
300		{
301			foo,
302			[]string{"BarInterface", "BarPointer"},
303			&Foo{
304				BarInterface: bar,
305				BarPointer:   &bar,
306			},
307		},
308	}
309
310	// pass to prune by pointer to struct
311	for idx, tc := range testCases {
312		t.Run(fmt.Sprintf("Prune pointer test case #%v", idx), func(t *testing.T) {
313			is := assert.New(t)
314			res, err := Prune(tc.OriginalFoo, tc.Paths)
315			require.NoError(t, err)
316
317			fooPrune := res.(*Foo)
318			is.Equal(tc.ExpectedFoo, fooPrune)
319		})
320	}
321
322	// pass to prune by struct directly
323	for idx, tc := range testCases {
324		t.Run(fmt.Sprintf("Prune non pointer test case #%v", idx), func(t *testing.T) {
325			is := assert.New(t)
326			fooNonPtr := *tc.OriginalFoo
327			res, err := Prune(fooNonPtr, tc.Paths)
328			require.NoError(t, err)
329
330			fooPrune := res.(Foo)
331			is.Equal(*tc.ExpectedFoo, fooPrune)
332		})
333	}
334
335	// test PruneByTag
336	var TagTestCases = []struct {
337		OriginalFoo *Foo
338		Paths       []string
339		ExpectedFoo *Foo
340		Tag         string
341	}{
342		{
343			foo,
344			[]string{"tag 1", "tag 4.BarName"},
345			&Foo{
346				FirstName: foo.FirstName,
347				Bar: &Bar{
348					Name: bar.Name,
349				},
350			},
351			"tag_name",
352		},
353	}
354
355	for idx, tc := range TagTestCases {
356		t.Run(fmt.Sprintf("PruneByTag test case #%v", idx), func(t *testing.T) {
357			is := assert.New(t)
358			fooNonPtr := *tc.OriginalFoo
359			res, err := PruneByTag(fooNonPtr, tc.Paths, tc.Tag)
360			require.NoError(t, err)
361
362			fooPrune := res.(Foo)
363			is.Equal(*tc.ExpectedFoo, fooPrune)
364		})
365	}
366
367	t.Run("Bar Slice", func(t *testing.T) {
368		barSlice := []*Bar{bar, bar}
369		barSlicePruned, err := pruneByTag(barSlice, []string{"Name"}, nil /*tag*/)
370		require.NoError(t, err)
371		assert.Equal(t, []*Bar{{Name: bar.Name}, {Name: bar.Name}}, barSlicePruned)
372	})
373
374	t.Run("Bar Array", func(t *testing.T) {
375		barArr := [2]*Bar{bar, bar}
376		barArrPruned, err := pruneByTag(barArr, []string{"Name"}, nil /*tag*/)
377		require.NoError(t, err)
378		assert.Equal(t, [2]*Bar{{Name: bar.Name}, {Name: bar.Name}}, barArrPruned)
379	})
380
381	// test values are copied and not referenced in return result
382	// NOTE: pointers at the end of path are referenced. Maybe we need to make a copy
383	t.Run("Copy Value Str", func(t *testing.T) {
384		is := assert.New(t)
385		fooTest := &Foo{
386			Bar: &Bar{
387				Name: "bar",
388			},
389		}
390		res, err := pruneByTag(fooTest, []string{"Bar.Name"}, nil)
391		require.NoError(t, err)
392		fooTestPruned := res.(*Foo)
393		is.Equal(fooTest, fooTestPruned)
394
395		// change pruned
396		fooTestPruned.Bar.Name = "changed bar"
397		// check original is unchanged
398		is.Equal(fooTest.Bar.Name, "bar")
399	})
400
401	// error cases
402	var errCases = []struct {
403		InputFoo *Foo
404		Paths    []string
405		TagName  *string
406	}{
407		{
408			foo,
409			[]string{"NotExist"},
410			nil,
411		},
412		{
413			foo,
414			[]string{"FirstName.NotExist", "LastName"},
415			nil,
416		},
417		{
418			foo,
419			[]string{"LastName", "FirstName.NotExist"},
420			nil,
421		},
422		{
423			foo,
424			[]string{"LastName", "Bars.NotExist"},
425			nil,
426		},
427		// tags
428		{
429			foo,
430			[]string{"tag 999"},
431			&[]string{"tag_name"}[0],
432		},
433		{
434			foo,
435			[]string{"tag 1.NotExist"},
436			&[]string{"tag_name"}[0],
437		},
438		{
439			foo,
440			[]string{"tag 4.NotExist"},
441			&[]string{"tag_name"}[0],
442		},
443		{
444			foo,
445			[]string{"FirstName"},
446			&[]string{"tag_name_not_exist"}[0],
447		},
448	}
449
450	for idx, errTC := range errCases {
451		t.Run(fmt.Sprintf("error test case #%v", idx), func(t *testing.T) {
452			_, err := pruneByTag(errTC.InputFoo, errTC.Paths, errTC.TagName)
453			assert.Error(t, err)
454		})
455	}
456}
457
458func ExamplePrune() {
459	type ExampleFoo struct {
460		ExampleFooPtr *ExampleFoo `json:"example_foo_ptr"`
461		Name          string      `json:"name"`
462		Number        int         `json:"number"`
463	}
464
465	exampleFoo := ExampleFoo{
466		ExampleFooPtr: &ExampleFoo{
467			Name:   "ExampleFooPtr",
468			Number: 2,
469		},
470		Name:   "ExampleFoo",
471		Number: 1,
472	}
473
474	// prune using struct field name
475	res, _ := Prune(exampleFoo, []string{"ExampleFooPtr.Name", "Number"})
476	prunedFoo := res.(ExampleFoo)
477	fmt.Println(prunedFoo.ExampleFooPtr.Name)
478	fmt.Println(prunedFoo.ExampleFooPtr.Number)
479	fmt.Println(prunedFoo.Name)
480	fmt.Println(prunedFoo.Number)
481
482	// prune using struct json tag
483	res2, _ := PruneByTag(exampleFoo, []string{"example_foo_ptr.name", "number"}, "json")
484	prunedByTagFoo := res2.(ExampleFoo)
485	fmt.Println(prunedByTagFoo.ExampleFooPtr.Name)
486	fmt.Println(prunedByTagFoo.ExampleFooPtr.Number)
487	fmt.Println(prunedByTagFoo.Name)
488	fmt.Println(prunedByTagFoo.Number)
489	// output:
490	// ExampleFooPtr
491	// 0
492	//
493	// 1
494	// ExampleFooPtr
495	// 0
496	//
497	// 1
498}
499