github.com/kovansky/hugo@v0.92.3-0.20220224232819-63076e4ff19f/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 23 "github.com/gohugoio/hugo/deps" 24 ) 25 26 type stringsSlice []string 27 28 func TestSort(t *testing.T) { 29 t.Parallel() 30 31 ns := New(&deps.Deps{}) 32 33 type ts struct { 34 MyInt int 35 MyFloat float64 36 MyString string 37 } 38 type mid struct { 39 Tst TstX 40 } 41 42 for i, test := range []struct { 43 seq interface{} 44 sortByField interface{} 45 sortAsc string 46 expect interface{} 47 }{ 48 {[]string{"class1", "class2", "class3"}, nil, "asc", []string{"class1", "class2", "class3"}}, 49 {[]string{"class3", "class1", "class2"}, nil, "asc", []string{"class1", "class2", "class3"}}, 50 {[]string{"CLASS3", "class1", "class2"}, nil, "asc", []string{"class1", "class2", "CLASS3"}}, 51 // Issue 6023 52 {stringsSlice{"class3", "class1", "class2"}, nil, "asc", stringsSlice{"class1", "class2", "class3"}}, 53 54 {[]int{1, 2, 3, 4, 5}, nil, "asc", []int{1, 2, 3, 4, 5}}, 55 {[]int{5, 4, 3, 1, 2}, nil, "asc", []int{1, 2, 3, 4, 5}}, 56 // test sort key parameter is forcibly set empty 57 {[]string{"class3", "class1", "class2"}, map[int]string{1: "a"}, "asc", []string{"class1", "class2", "class3"}}, 58 // test map sorting by keys 59 {map[string]int{"1": 10, "2": 20, "3": 30, "4": 40, "5": 50}, nil, "asc", []int{10, 20, 30, 40, 50}}, 60 {map[string]int{"3": 10, "2": 20, "1": 30, "4": 40, "5": 50}, nil, "asc", []int{30, 20, 10, 40, 50}}, 61 {map[string]string{"1": "10", "2": "20", "3": "30", "4": "40", "5": "50"}, nil, "asc", []string{"10", "20", "30", "40", "50"}}, 62 {map[string]string{"3": "10", "2": "20", "1": "30", "4": "40", "5": "50"}, nil, "asc", []string{"30", "20", "10", "40", "50"}}, 63 {map[string]string{"one": "10", "two": "20", "three": "30", "four": "40", "five": "50"}, nil, "asc", []string{"50", "40", "10", "30", "20"}}, 64 {map[int]string{1: "10", 2: "20", 3: "30", 4: "40", 5: "50"}, nil, "asc", []string{"10", "20", "30", "40", "50"}}, 65 {map[int]string{3: "10", 2: "20", 1: "30", 4: "40", 5: "50"}, nil, "asc", []string{"30", "20", "10", "40", "50"}}, 66 {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"}}, 67 // test map sorting by value 68 {map[string]int{"1": 10, "2": 20, "3": 30, "4": 40, "5": 50}, "value", "asc", []int{10, 20, 30, 40, 50}}, 69 {map[string]int{"3": 10, "2": 20, "1": 30, "4": 40, "5": 50}, "value", "asc", []int{10, 20, 30, 40, 50}}, 70 // test map sorting by field value 71 { 72 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"}}, 73 "MyInt", 74 "asc", 75 []ts{{10, 10.5, "ten"}, {20, 20.5, "twenty"}, {30, 30.5, "thirty"}, {40, 40.5, "forty"}, {50, 50.5, "fifty"}}, 76 }, 77 { 78 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"}}, 79 "MyFloat", 80 "asc", 81 []ts{{10, 10.5, "ten"}, {20, 20.5, "twenty"}, {30, 30.5, "thirty"}, {40, 40.5, "forty"}, {50, 50.5, "fifty"}}, 82 }, 83 { 84 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"}}, 85 "MyString", 86 "asc", 87 []ts{{50, 50.5, "fifty"}, {40, 40.5, "forty"}, {10, 10.5, "ten"}, {30, 30.5, "thirty"}, {20, 20.5, "twenty"}}, 88 }, 89 // test sort desc 90 {[]string{"class1", "class2", "class3"}, "value", "desc", []string{"class3", "class2", "class1"}}, 91 {[]string{"class3", "class1", "class2"}, "value", "desc", []string{"class3", "class2", "class1"}}, 92 // test sort by struct's method 93 { 94 []TstX{{A: "i", B: "j"}, {A: "e", B: "f"}, {A: "c", B: "d"}, {A: "g", B: "h"}, {A: "a", B: "b"}}, 95 "TstRv", 96 "asc", 97 []TstX{{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}, {A: "g", B: "h"}, {A: "i", B: "j"}}, 98 }, 99 { 100 []*TstX{{A: "i", B: "j"}, {A: "e", B: "f"}, {A: "c", B: "d"}, {A: "g", B: "h"}, {A: "a", B: "b"}}, 101 "TstRp", 102 "asc", 103 []*TstX{{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}, {A: "g", B: "h"}, {A: "i", B: "j"}}, 104 }, 105 // Lower case Params, slice 106 { 107 []TstParams{{params: maps.Params{"color": "indigo"}}, {params: maps.Params{"color": "blue"}}, {params: maps.Params{"color": "green"}}}, 108 ".Params.COLOR", 109 "asc", 110 []TstParams{{params: maps.Params{"color": "blue"}}, {params: maps.Params{"color": "green"}}, {params: maps.Params{"color": "indigo"}}}, 111 }, 112 // Lower case Params, map 113 { 114 map[string]TstParams{"1": {params: maps.Params{"color": "indigo"}}, "2": {params: maps.Params{"color": "blue"}}, "3": {params: maps.Params{"color": "green"}}}, 115 ".Params.CoLoR", 116 "asc", 117 []TstParams{{params: maps.Params{"color": "blue"}}, {params: maps.Params{"color": "green"}}, {params: maps.Params{"color": "indigo"}}}, 118 }, 119 // test map sorting by struct's method 120 { 121 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"}}, 122 "TstRv", 123 "asc", 124 []TstX{{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}, {A: "g", B: "h"}, {A: "i", B: "j"}}, 125 }, 126 { 127 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"}}, 128 "TstRp", 129 "asc", 130 []*TstX{{A: "a", B: "b"}, {A: "c", B: "d"}, {A: "e", B: "f"}, {A: "g", B: "h"}, {A: "i", B: "j"}}, 131 }, 132 // test sort by dot chaining key argument 133 { 134 []map[string]TstX{{"foo": TstX{A: "e", B: "f"}}, {"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}}, 135 "foo.A", 136 "asc", 137 []map[string]TstX{{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}}, 138 }, 139 { 140 []map[string]TstX{{"foo": TstX{A: "e", B: "f"}}, {"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}}, 141 ".foo.A", 142 "asc", 143 []map[string]TstX{{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}}, 144 }, 145 { 146 []map[string]TstX{{"foo": TstX{A: "e", B: "f"}}, {"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}}, 147 "foo.TstRv", 148 "asc", 149 []map[string]TstX{{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}}, 150 }, 151 { 152 []map[string]*TstX{{"foo": &TstX{A: "e", B: "f"}}, {"foo": &TstX{A: "a", B: "b"}}, {"foo": &TstX{A: "c", B: "d"}}}, 153 "foo.TstRp", 154 "asc", 155 []map[string]*TstX{{"foo": &TstX{A: "a", B: "b"}}, {"foo": &TstX{A: "c", B: "d"}}, {"foo": &TstX{A: "e", B: "f"}}}, 156 }, 157 { 158 []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"}}}}, 159 "foo.Tst.A", 160 "asc", 161 []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"}}}}, 162 }, 163 { 164 []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"}}}}, 165 "foo.Tst.TstRv", 166 "asc", 167 []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"}}}}, 168 }, 169 // test map sorting by dot chaining key argument 170 { 171 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"}}}, 172 "foo.A", 173 "asc", 174 []map[string]TstX{{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}}, 175 }, 176 { 177 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"}}}, 178 ".foo.A", 179 "asc", 180 []map[string]TstX{{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}}, 181 }, 182 { 183 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"}}}, 184 "foo.TstRv", 185 "asc", 186 []map[string]TstX{{"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}, {"foo": TstX{A: "e", B: "f"}}}, 187 }, 188 { 189 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"}}}, 190 "foo.TstRp", 191 "asc", 192 []map[string]*TstX{{"foo": &TstX{A: "a", B: "b"}}, {"foo": &TstX{A: "c", B: "d"}}, {"foo": &TstX{A: "e", B: "f"}}}, 193 }, 194 { 195 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"}}}}, 196 "foo.Tst.A", 197 "asc", 198 []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"}}}}, 199 }, 200 { 201 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"}}}}, 202 "foo.Tst.TstRv", 203 "asc", 204 []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"}}}}, 205 }, 206 // interface slice with missing elements 207 { 208 []interface{}{ 209 map[interface{}]interface{}{"Title": "Foo", "Weight": 10}, 210 map[interface{}]interface{}{"Title": "Bar"}, 211 map[interface{}]interface{}{"Title": "Zap", "Weight": 5}, 212 }, 213 "Weight", 214 "asc", 215 []interface{}{ 216 map[interface{}]interface{}{"Title": "Bar"}, 217 map[interface{}]interface{}{"Title": "Zap", "Weight": 5}, 218 map[interface{}]interface{}{"Title": "Foo", "Weight": 10}, 219 }, 220 }, 221 // test boolean values 222 {[]bool{false, true, false}, "value", "asc", []bool{false, false, true}}, 223 {[]bool{false, true, false}, "value", "desc", []bool{true, false, false}}, 224 // test error cases 225 {(*[]TstX)(nil), nil, "asc", false}, 226 {TstX{A: "a", B: "b"}, nil, "asc", false}, 227 { 228 []map[string]TstX{{"foo": TstX{A: "e", B: "f"}}, {"foo": TstX{A: "a", B: "b"}}, {"foo": TstX{A: "c", B: "d"}}}, 229 "foo.NotAvailable", 230 "asc", 231 false, 232 }, 233 { 234 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"}}}, 235 "foo.NotAvailable", 236 "asc", 237 false, 238 }, 239 {nil, nil, "asc", false}, 240 } { 241 t.Run(fmt.Sprintf("test%d", i), func(t *testing.T) { 242 var result interface{} 243 var err error 244 if test.sortByField == nil { 245 result, err = ns.Sort(test.seq) 246 } else { 247 result, err = ns.Sort(test.seq, test.sortByField, test.sortAsc) 248 } 249 250 if b, ok := test.expect.(bool); ok && !b { 251 if err == nil { 252 t.Fatal("Sort didn't return an expected error") 253 } 254 } else { 255 if err != nil { 256 t.Fatalf("failed: %s", err) 257 } 258 if !reflect.DeepEqual(result, test.expect) { 259 t.Fatalf("Sort called on sequence: %#v | sortByField: `%v` | got\n%#v but expected\n%#v", test.seq, test.sortByField, result, test.expect) 260 } 261 } 262 }) 263 } 264 }