pages_sort_test.go (7833B)
1 // Copyright 2019 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 page
15
16 import (
17 "fmt"
18 "testing"
19 "time"
20
21 "github.com/gohugoio/hugo/resources/resource"
22 "github.com/google/go-cmp/cmp"
23
24 qt "github.com/frankban/quicktest"
25 )
26
27 var eq = qt.CmpEquals(
28 cmp.Comparer(func(p1, p2 testPage) bool {
29 return p1.path == p2.path && p1.weight == p2.weight
30 }),
31 )
32
33 func TestDefaultSort(t *testing.T) {
34 t.Parallel()
35 c := qt.New(t)
36 d1 := time.Now()
37 d2 := d1.Add(-1 * time.Hour)
38 d3 := d1.Add(-2 * time.Hour)
39 d4 := d1.Add(-3 * time.Hour)
40
41 p := createSortTestPages(4)
42
43 // first by weight
44 setSortVals([4]time.Time{d1, d2, d3, d4}, [4]string{"b", "a", "c", "d"}, [4]int{4, 3, 2, 1}, p)
45 SortByDefault(p)
46
47 c.Assert(p[0].Weight(), qt.Equals, 1)
48
49 // Consider zero weight, issue #2673
50 setSortVals([4]time.Time{d1, d2, d3, d4}, [4]string{"b", "a", "d", "c"}, [4]int{0, 0, 0, 1}, p)
51 SortByDefault(p)
52
53 c.Assert(p[0].Weight(), qt.Equals, 1)
54
55 // next by date
56 setSortVals([4]time.Time{d3, d4, d1, d2}, [4]string{"a", "b", "c", "d"}, [4]int{1, 1, 1, 1}, p)
57 SortByDefault(p)
58 c.Assert(p[0].Date(), qt.Equals, d1)
59
60 // finally by link title
61 setSortVals([4]time.Time{d3, d3, d3, d3}, [4]string{"b", "c", "a", "d"}, [4]int{1, 1, 1, 1}, p)
62 SortByDefault(p)
63 c.Assert(p[0].LinkTitle(), qt.Equals, "al")
64 c.Assert(p[1].LinkTitle(), qt.Equals, "bl")
65 c.Assert(p[2].LinkTitle(), qt.Equals, "cl")
66 }
67
68 // https://github.com/gohugoio/hugo/issues/4953
69 func TestSortByLinkTitle(t *testing.T) {
70 t.Parallel()
71 c := qt.New(t)
72 pages := createSortTestPages(6)
73
74 for i, p := range pages {
75 pp := p.(*testPage)
76 if i < 5 {
77 pp.title = fmt.Sprintf("title%d", i)
78 }
79
80 if i > 2 {
81 pp.linkTitle = fmt.Sprintf("linkTitle%d", i)
82 }
83
84 }
85
86 pages.shuffle()
87
88 bylt := pages.ByLinkTitle()
89
90 for i, p := range bylt {
91 if i < 3 {
92 c.Assert(p.LinkTitle(), qt.Equals, fmt.Sprintf("linkTitle%d", i+3))
93 } else {
94 c.Assert(p.LinkTitle(), qt.Equals, fmt.Sprintf("title%d", i-3))
95 }
96 }
97 }
98
99 func TestSortByN(t *testing.T) {
100 t.Parallel()
101 d1 := time.Now()
102 d2 := d1.Add(-2 * time.Hour)
103 d3 := d1.Add(-10 * time.Hour)
104 d4 := d1.Add(-20 * time.Hour)
105
106 p := createSortTestPages(4)
107
108 for i, this := range []struct {
109 sortFunc func(p Pages) Pages
110 assertFunc func(p Pages) bool
111 }{
112 {(Pages).ByWeight, func(p Pages) bool { return p[0].Weight() == 1 }},
113 {(Pages).ByTitle, func(p Pages) bool { return p[0].Title() == "ab" }},
114 {(Pages).ByLinkTitle, func(p Pages) bool { return p[0].LinkTitle() == "abl" }},
115 {(Pages).ByDate, func(p Pages) bool { return p[0].Date() == d4 }},
116 {(Pages).ByPublishDate, func(p Pages) bool { return p[0].PublishDate() == d4 }},
117 {(Pages).ByExpiryDate, func(p Pages) bool { return p[0].ExpiryDate() == d4 }},
118 {(Pages).ByLastmod, func(p Pages) bool { return p[1].Lastmod() == d3 }},
119 {(Pages).ByLength, func(p Pages) bool { return p[0].(resource.LengthProvider).Len() == len(p[0].(*testPage).content) }},
120 } {
121 setSortVals([4]time.Time{d1, d2, d3, d4}, [4]string{"b", "ab", "cde", "fg"}, [4]int{0, 3, 2, 1}, p)
122
123 sorted := this.sortFunc(p)
124 if !this.assertFunc(sorted) {
125 t.Errorf("[%d] sort error", i)
126 }
127 }
128 }
129
130 func TestLimit(t *testing.T) {
131 t.Parallel()
132 c := qt.New(t)
133 p := createSortTestPages(10)
134 firstFive := p.Limit(5)
135 c.Assert(len(firstFive), qt.Equals, 5)
136 for i := 0; i < 5; i++ {
137 c.Assert(firstFive[i], qt.Equals, p[i])
138 }
139 c.Assert(p.Limit(10), eq, p)
140 c.Assert(p.Limit(11), eq, p)
141 }
142
143 func TestPageSortReverse(t *testing.T) {
144 t.Parallel()
145 c := qt.New(t)
146 p1 := createSortTestPages(10)
147 c.Assert(p1[0].(*testPage).fuzzyWordCount, qt.Equals, 0)
148 c.Assert(p1[9].(*testPage).fuzzyWordCount, qt.Equals, 9)
149 p2 := p1.Reverse()
150 c.Assert(p2[0].(*testPage).fuzzyWordCount, qt.Equals, 9)
151 c.Assert(p2[9].(*testPage).fuzzyWordCount, qt.Equals, 0)
152 // cached
153 c.Assert(pagesEqual(p2, p1.Reverse()), qt.Equals, true)
154 }
155
156 func TestPageSortByParam(t *testing.T) {
157 t.Parallel()
158 c := qt.New(t)
159 var k any = "arbitrarily.nested"
160
161 unsorted := createSortTestPages(10)
162 delete(unsorted[9].Params(), "arbitrarily")
163
164 firstSetValue, _ := unsorted[0].Param(k)
165 secondSetValue, _ := unsorted[1].Param(k)
166 lastSetValue, _ := unsorted[8].Param(k)
167 unsetValue, _ := unsorted[9].Param(k)
168
169 c.Assert(firstSetValue, qt.Equals, "xyz100")
170 c.Assert(secondSetValue, qt.Equals, "xyz99")
171 c.Assert(lastSetValue, qt.Equals, "xyz92")
172 c.Assert(unsetValue, qt.Equals, nil)
173
174 sorted := unsorted.ByParam("arbitrarily.nested")
175 firstSetSortedValue, _ := sorted[0].Param(k)
176 secondSetSortedValue, _ := sorted[1].Param(k)
177 lastSetSortedValue, _ := sorted[8].Param(k)
178 unsetSortedValue, _ := sorted[9].Param(k)
179
180 c.Assert(firstSetSortedValue, qt.Equals, firstSetValue)
181 c.Assert(lastSetSortedValue, qt.Equals, secondSetValue)
182 c.Assert(secondSetSortedValue, qt.Equals, lastSetValue)
183 c.Assert(unsetSortedValue, qt.Equals, unsetValue)
184 }
185
186 func TestPageSortByParamNumeric(t *testing.T) {
187 t.Parallel()
188 c := qt.New(t)
189
190 var k any = "arbitrarily.nested"
191
192 n := 10
193 unsorted := createSortTestPages(n)
194 for i := 0; i < n; i++ {
195 v := 100 - i
196 if i%2 == 0 {
197 v = 100.0 - i
198 }
199
200 unsorted[i].(*testPage).params = map[string]any{
201 "arbitrarily": map[string]any{
202 "nested": v,
203 },
204 }
205 }
206 delete(unsorted[9].Params(), "arbitrarily")
207
208 firstSetValue, _ := unsorted[0].Param(k)
209 secondSetValue, _ := unsorted[1].Param(k)
210 lastSetValue, _ := unsorted[8].Param(k)
211 unsetValue, _ := unsorted[9].Param(k)
212
213 c.Assert(firstSetValue, qt.Equals, 100)
214 c.Assert(secondSetValue, qt.Equals, 99)
215 c.Assert(lastSetValue, qt.Equals, 92)
216 c.Assert(unsetValue, qt.Equals, nil)
217
218 sorted := unsorted.ByParam("arbitrarily.nested")
219 firstSetSortedValue, _ := sorted[0].Param(k)
220 secondSetSortedValue, _ := sorted[1].Param(k)
221 lastSetSortedValue, _ := sorted[8].Param(k)
222 unsetSortedValue, _ := sorted[9].Param(k)
223
224 c.Assert(firstSetSortedValue, qt.Equals, 92)
225 c.Assert(secondSetSortedValue, qt.Equals, 93)
226 c.Assert(lastSetSortedValue, qt.Equals, 100)
227 c.Assert(unsetSortedValue, qt.Equals, unsetValue)
228 }
229
230 func BenchmarkSortByWeightAndReverse(b *testing.B) {
231 p := createSortTestPages(300)
232
233 b.ResetTimer()
234 for i := 0; i < b.N; i++ {
235 p = p.ByWeight().Reverse()
236 }
237 }
238
239 func setSortVals(dates [4]time.Time, titles [4]string, weights [4]int, pages Pages) {
240 for i := range dates {
241 this := pages[i].(*testPage)
242 other := pages[len(dates)-1-i].(*testPage)
243
244 this.date = dates[i]
245 this.lastMod = dates[i]
246 this.weight = weights[i]
247 this.title = titles[i]
248 // make sure we compare apples and ... apples ...
249 other.linkTitle = this.Title() + "l"
250 other.pubDate = dates[i]
251 other.expiryDate = dates[i]
252 other.content = titles[i] + "_content"
253 }
254 lastLastMod := pages[2].Lastmod()
255 pages[2].(*testPage).lastMod = pages[1].Lastmod()
256 pages[1].(*testPage).lastMod = lastLastMod
257
258 for _, p := range pages {
259 p.(*testPage).content = ""
260 }
261 }
262
263 func createSortTestPages(num int) Pages {
264 pages := make(Pages, num)
265
266 for i := 0; i < num; i++ {
267 p := newTestPage()
268 p.path = fmt.Sprintf("/x/y/p%d.md", i)
269 p.title = fmt.Sprintf("Title %d", i%(num+1/2))
270 p.params = map[string]any{
271 "arbitrarily": map[string]any{
272 "nested": ("xyz" + fmt.Sprintf("%v", 100-i)),
273 },
274 }
275
276 w := 5
277
278 if i%2 == 0 {
279 w = 10
280 }
281 p.fuzzyWordCount = i
282 p.weight = w
283 p.description = "initial"
284
285 pages[i] = p
286 }
287
288 return pages
289 }