github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/tpl/collections/sort_test.go (about)

     1  // Copyright 2017 The Hugo Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package collections
    15  
    16  import (
    17  	"fmt"
    18  	"reflect"
    19  	"testing"
    20  
    21  	"github.com/gohugoio/hugo/common/maps"
    22  	"github.com/gohugoio/hugo/config"
    23  	"github.com/gohugoio/hugo/langs"
    24  
    25  	"github.com/gohugoio/hugo/deps"
    26  )
    27  
    28  type stringsSlice []string
    29  
    30  func TestSort(t *testing.T) {
    31  	t.Parallel()
    32  
    33  	ns := New(&deps.Deps{
    34  		Language: langs.NewDefaultLanguage(config.New()),
    35  	})
    36  
    37  	type ts struct {
    38  		MyInt    int
    39  		MyFloat  float64
    40  		MyString string
    41  	}
    42  	type mid struct {
    43  		Tst TstX
    44  	}
    45  
    46  	for i, test := range []struct {
    47  		seq         any
    48  		sortByField any
    49  		sortAsc     string
    50  		expect      any
    51  	}{
    52  		{[]string{"class1", "class2", "class3"}, nil, "asc", []string{"class1", "class2", "class3"}},
    53  		{[]string{"class3", "class1", "class2"}, nil, "asc", []string{"class1", "class2", "class3"}},
    54  		{[]string{"CLASS3", "class1", "class2"}, nil, "asc", []string{"class1", "class2", "CLASS3"}},
    55  		// Issue 6023
    56  		{stringsSlice{"class3", "class1", "class2"}, nil, "asc", stringsSlice{"class1", "class2", "class3"}},
    57  
    58  		{[]int{1, 2, 3, 4, 5}, nil, "asc", []int{1, 2, 3, 4, 5}},
    59  		{[]int{5, 4, 3, 1, 2}, nil, "asc", []int{1, 2, 3, 4, 5}},
    60  		// test sort key parameter is forcibly set empty
    61  		{[]string{"class3", "class1", "class2"}, map[int]string{1: "a"}, "asc", []string{"class1", "class2", "class3"}},
    62  		// test map sorting by keys
    63  		{map[string]int{"1": 10, "2": 20, "3": 30, "4": 40, "5": 50}, nil, "asc", []int{10, 20, 30, 40, 50}},
    64  		{map[string]int{"3": 10, "2": 20, "1": 30, "4": 40, "5": 50}, nil, "asc", []int{30, 20, 10, 40, 50}},
    65  		{map[string]string{"1": "10", "2": "20", "3": "30", "4": "40", "5": "50"}, nil, "asc", []string{"10", "20", "30", "40", "50"}},
    66  		{map[string]string{"3": "10", "2": "20", "1": "30", "4": "40", "5": "50"}, nil, "asc", []string{"30", "20", "10", "40", "50"}},
    67  		{map[string]string{"one": "10", "two": "20", "three": "30", "four": "40", "five": "50"}, nil, "asc", []string{"50", "40", "10", "30", "20"}},
    68  		{map[int]string{1: "10", 2: "20", 3: "30", 4: "40", 5: "50"}, nil, "asc", []string{"10", "20", "30", "40", "50"}},
    69  		{map[int]string{3: "10", 2: "20", 1: "30", 4: "40", 5: "50"}, nil, "asc", []string{"30", "20", "10", "40", "50"}},
    70  		{map[float64]string{3.3: "10", 2.3: "20", 1.3: "30", 4.3: "40", 5.3: "50"}, nil, "asc", []string{"30", "20", "10", "40", "50"}},
    71  		// test map sorting by value
    72  		{map[string]int{"1": 10, "2": 20, "3": 30, "4": 40, "5": 50}, "value", "asc", []int{10, 20, 30, 40, 50}},
    73  		{map[string]int{"3": 10, "2": 20, "1": 30, "4": 40, "5": 50}, "value", "asc", []int{10, 20, 30, 40, 50}},
    74  		// test map sorting by field value
    75  		{
    76  			map[string]ts{"1": {10, 10.5, "ten"}, "2": {20, 20.5, "twenty"}, "3": {30, 30.5, "thirty"}, "4": {40, 40.5, "forty"}, "5": {50, 50.5, "fifty"}},
    77  			"MyInt",
    78  			"asc",
    79  			[]ts{{10, 10.5, "ten"}, {20, 20.5, "twenty"}, {30, 30.5, "thirty"}, {40, 40.5, "forty"}, {50, 50.5, "fifty"}},
    80  		},
    81  		{
    82  			map[string]ts{"1": {10, 10.5, "ten"}, "2": {20, 20.5, "twenty"}, "3": {30, 30.5, "thirty"}, "4": {40, 40.5, "forty"}, "5": {50, 50.5, "fifty"}},
    83  			"MyFloat",
    84  			"asc",
    85  			[]ts{{10, 10.5, "ten"}, {20, 20.5, "twenty"}, {30, 30.5, "thirty"}, {40, 40.5, "forty"}, {50, 50.5, "fifty"}},
    86  		},
    87  		{
    88  			map[string]ts{"1": {10, 10.5, "ten"}, "2": {20, 20.5, "twenty"}, "3": {30, 30.5, "thirty"}, "4": {40, 40.5, "forty"}, "5": {50, 50.5, "fifty"}},
    89  			"MyString",
    90  			"asc",
    91  			[]ts{{50, 50.5, "fifty"}, {40, 40.5, "forty"}, {10, 10.5, "ten"}, {30, 30.5, "thirty"}, {20, 20.5, "twenty"}},
    92  		},
    93  		// test sort desc
    94  		{[]string{"class1", "class2", "class3"}, "value", "desc", []string{"class3", "class2", "class1"}},
    95  		{[]string{"class3", "class1", "class2"}, "value", "desc", []string{"class3", "class2", "class1"}},
    96  		// test sort by struct's method
    97  		{
    98  			[]TstX{{A: "i", B: "j"}, {A: "e", B: "f"}, {A: "c", B: "d"}, {A: "g", B: "h"}, {A: "a", B: "b"}},
    99  			"TstRv",
   100  			"asc",
   101  			[]TstX{{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}, {A: "g", B: "h"}, {A: "i", B: "j"}},
   102  		},
   103  		{
   104  			[]*TstX{{A: "i", B: "j"}, {A: "e", B: "f"}, {A: "c", B: "d"}, {A: "g", B: "h"}, {A: "a", B: "b"}},
   105  			"TstRp",
   106  			"asc",
   107  			[]*TstX{{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}, {A: "g", B: "h"}, {A: "i", B: "j"}},
   108  		},
   109  		// Lower case Params, slice
   110  		{
   111  			[]TstParams{{params: maps.Params{"color": "indigo"}}, {params: maps.Params{"color": "blue"}}, {params: maps.Params{"color": "green"}}},
   112  			".Params.COLOR",
   113  			"asc",
   114  			[]TstParams{{params: maps.Params{"color": "blue"}}, {params: maps.Params{"color": "green"}}, {params: maps.Params{"color": "indigo"}}},
   115  		},
   116  		// Lower case Params, map
   117  		{
   118  			map[string]TstParams{"1": {params: maps.Params{"color": "indigo"}}, "2": {params: maps.Params{"color": "blue"}}, "3": {params: maps.Params{"color": "green"}}},
   119  			".Params.CoLoR",
   120  			"asc",
   121  			[]TstParams{{params: maps.Params{"color": "blue"}}, {params: maps.Params{"color": "green"}}, {params: maps.Params{"color": "indigo"}}},
   122  		},
   123  		// test map sorting by struct's method
   124  		{
   125  			map[string]TstX{"1": {A: "i", B: "j"}, "2": {A: "e", B: "f"}, "3": {A: "c", B: "d"}, "4": {A: "g", B: "h"}, "5": {A: "a", B: "b"}},
   126  			"TstRv",
   127  			"asc",
   128  			[]TstX{{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}, {A: "g", B: "h"}, {A: "i", B: "j"}},
   129  		},
   130  		{
   131  			map[string]*TstX{"1": {A: "i", B: "j"}, "2": {A: "e", B: "f"}, "3": {A: "c", B: "d"}, "4": {A: "g", B: "h"}, "5": {A: "a", B: "b"}},
   132  			"TstRp",
   133  			"asc",
   134  			[]*TstX{{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}, {A: "g", B: "h"}, {A: "i", B: "j"}},
   135  		},
   136  		// test sort by dot chaining key argument
   137  		{
   138  			[]map[string]TstX{{"foo": TstX{A: "e", B: "f"}}, {"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}},
   139  			"foo.A",
   140  			"asc",
   141  			[]map[string]TstX{{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}},
   142  		},
   143  		{
   144  			[]map[string]TstX{{"foo": TstX{A: "e", B: "f"}}, {"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}},
   145  			".foo.A",
   146  			"asc",
   147  			[]map[string]TstX{{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}},
   148  		},
   149  		{
   150  			[]map[string]TstX{{"foo": TstX{A: "e", B: "f"}}, {"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}},
   151  			"foo.TstRv",
   152  			"asc",
   153  			[]map[string]TstX{{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}},
   154  		},
   155  		{
   156  			[]map[string]*TstX{{"foo": &TstX{A: "e", B: "f"}}, {"foo": &TstX{A: "a", B: "b"}}, {"foo": &TstX{A: "c", B: "d"}}},
   157  			"foo.TstRp",
   158  			"asc",
   159  			[]map[string]*TstX{{"foo": &TstX{A: "a", B: "b"}}, {"foo": &TstX{A: "c", B: "d"}}, {"foo": &TstX{A: "e", B: "f"}}},
   160  		},
   161  		{
   162  			[]map[string]mid{{"foo": mid{Tst: TstX{A: "e", B: "f"}}}, {"foo": mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": mid{Tst: TstX{A: "c", B: "d"}}}},
   163  			"foo.Tst.A",
   164  			"asc",
   165  			[]map[string]mid{{"foo": mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": mid{Tst: TstX{A: "c", B: "d"}}}, {"foo": mid{Tst: TstX{A: "e", B: "f"}}}},
   166  		},
   167  		{
   168  			[]map[string]mid{{"foo": mid{Tst: TstX{A: "e", B: "f"}}}, {"foo": mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": mid{Tst: TstX{A: "c", B: "d"}}}},
   169  			"foo.Tst.TstRv",
   170  			"asc",
   171  			[]map[string]mid{{"foo": mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": mid{Tst: TstX{A: "c", B: "d"}}}, {"foo": mid{Tst: TstX{A: "e", B: "f"}}}},
   172  		},
   173  		// test map sorting by dot chaining key argument
   174  		{
   175  			map[string]map[string]TstX{"1": {"foo": TstX{A: "e", B: "f"}}, "2": {"foo": TstX{A: "a", B: "b"}}, "3": {"foo": TstX{A: "c", B: "d"}}},
   176  			"foo.A",
   177  			"asc",
   178  			[]map[string]TstX{{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}},
   179  		},
   180  		{
   181  			map[string]map[string]TstX{"1": {"foo": TstX{A: "e", B: "f"}}, "2": {"foo": TstX{A: "a", B: "b"}}, "3": {"foo": TstX{A: "c", B: "d"}}},
   182  			".foo.A",
   183  			"asc",
   184  			[]map[string]TstX{{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}},
   185  		},
   186  		{
   187  			map[string]map[string]TstX{"1": {"foo": TstX{A: "e", B: "f"}}, "2": {"foo": TstX{A: "a", B: "b"}}, "3": {"foo": TstX{A: "c", B: "d"}}},
   188  			"foo.TstRv",
   189  			"asc",
   190  			[]map[string]TstX{{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}},
   191  		},
   192  		{
   193  			map[string]map[string]*TstX{"1": {"foo": &TstX{A: "e", B: "f"}}, "2": {"foo": &TstX{A: "a", B: "b"}}, "3": {"foo": &TstX{A: "c", B: "d"}}},
   194  			"foo.TstRp",
   195  			"asc",
   196  			[]map[string]*TstX{{"foo": &TstX{A: "a", B: "b"}}, {"foo": &TstX{A: "c", B: "d"}}, {"foo": &TstX{A: "e", B: "f"}}},
   197  		},
   198  		{
   199  			map[string]map[string]mid{"1": {"foo": mid{Tst: TstX{A: "e", B: "f"}}}, "2": {"foo": mid{Tst: TstX{A: "a", B: "b"}}}, "3": {"foo": mid{Tst: TstX{A: "c", B: "d"}}}},
   200  			"foo.Tst.A",
   201  			"asc",
   202  			[]map[string]mid{{"foo": mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": mid{Tst: TstX{A: "c", B: "d"}}}, {"foo": mid{Tst: TstX{A: "e", B: "f"}}}},
   203  		},
   204  		{
   205  			map[string]map[string]mid{"1": {"foo": mid{Tst: TstX{A: "e", B: "f"}}}, "2": {"foo": mid{Tst: TstX{A: "a", B: "b"}}}, "3": {"foo": mid{Tst: TstX{A: "c", B: "d"}}}},
   206  			"foo.Tst.TstRv",
   207  			"asc",
   208  			[]map[string]mid{{"foo": mid{Tst: TstX{A: "a", B: "b"}}}, {"foo": mid{Tst: TstX{A: "c", B: "d"}}}, {"foo": mid{Tst: TstX{A: "e", B: "f"}}}},
   209  		},
   210  		// interface slice with missing elements
   211  		{
   212  			[]any{
   213  				map[any]any{"Title": "Foo", "Weight": 10},
   214  				map[any]any{"Title": "Bar"},
   215  				map[any]any{"Title": "Zap", "Weight": 5},
   216  			},
   217  			"Weight",
   218  			"asc",
   219  			[]any{
   220  				map[any]any{"Title": "Bar"},
   221  				map[any]any{"Title": "Zap", "Weight": 5},
   222  				map[any]any{"Title": "Foo", "Weight": 10},
   223  			},
   224  		},
   225  		// test boolean values
   226  		{[]bool{false, true, false}, "value", "asc", []bool{false, false, true}},
   227  		{[]bool{false, true, false}, "value", "desc", []bool{true, false, false}},
   228  		// test error cases
   229  		{(*[]TstX)(nil), nil, "asc", false},
   230  		{TstX{A: "a", B: "b"}, nil, "asc", false},
   231  		{
   232  			[]map[string]TstX{{"foo": TstX{A: "e", B: "f"}}, {"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}},
   233  			"foo.NotAvailable",
   234  			"asc",
   235  			false,
   236  		},
   237  		{
   238  			map[string]map[string]TstX{"1": {"foo": TstX{A: "e", B: "f"}}, "2": {"foo": TstX{A: "a", B: "b"}}, "3": {"foo": TstX{A: "c", B: "d"}}},
   239  			"foo.NotAvailable",
   240  			"asc",
   241  			false,
   242  		},
   243  		{nil, nil, "asc", false},
   244  	} {
   245  		t.Run(fmt.Sprintf("test%d", i), func(t *testing.T) {
   246  			var result any
   247  			var err error
   248  			if test.sortByField == nil {
   249  				result, err = ns.Sort(test.seq)
   250  			} else {
   251  				result, err = ns.Sort(test.seq, test.sortByField, test.sortAsc)
   252  			}
   253  
   254  			if b, ok := test.expect.(bool); ok && !b {
   255  				if err == nil {
   256  					t.Fatal("Sort didn't return an expected error")
   257  				}
   258  			} else {
   259  				if err != nil {
   260  					t.Fatalf("failed: %s", err)
   261  				}
   262  				if !reflect.DeepEqual(result, test.expect) {
   263  					t.Fatalf("Sort called on sequence: %#v | sortByField: `%v` | got\n%#v but expected\n%#v", test.seq, test.sortByField, result, test.expect)
   264  				}
   265  			}
   266  		})
   267  	}
   268  }