sort_test.go (11423B)
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 }