Passed
Push — master ( 3c9c42...019716 )
by Alexander
02:06 queued 34s
created

KeysetPaginatorTest.php$0 ➔ rewind()   A

Complexity

Conditions 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
c 0
b 0
f 0
dl 0
loc 4
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Data\Tests\Paginator;
6
7
use Yiisoft\Data\Paginator\KeysetPaginator;
8
use Yiisoft\Data\Reader\Filter\FilterProcessorInterface;
9
use Yiisoft\Data\Reader\Iterable\IterableDataReader;
10
use Yiisoft\Data\Reader\Filter\FilterInterface;
11
use Yiisoft\Data\Reader\DataReaderInterface;
12
use Yiisoft\Data\Reader\FilterableDataInterface;
13
use Yiisoft\Data\Reader\Sort;
14
use Yiisoft\Data\Reader\SortableDataInterface;
15
use Yiisoft\Data\Tests\TestCase;
16
17
final class KeysetPaginatorTest extends Testcase
18
{
19
    public function testDataReaderWithoutFilterableInterface(): void
20
    {
21
        $this->expectException(\InvalidArgumentException::class);
22
        $this->expectExceptionMessage(
23
            'Data reader should implement FilterableDataInterface to be used with keyset paginator.'
24
        );
25
26
        new KeysetPaginator($this->getNonFilterableDataReader());
27
    }
28
29
    public function testDataReaderWithoutSortableInterface(): void
30
    {
31
        $this->expectException(\InvalidArgumentException::class);
32
        $this->expectExceptionMessage(
33
            'Data reader should implement SortableDataInterface to be used with keyset paginator.'
34
        );
35
36
        new KeysetPaginator($this->getNonSortableDataReader());
37
    }
38
39
    public function testPageSizeCannotBeLessThanOne(): void
40
    {
41
        $sort = (new Sort(['id', 'name']))->withOrderString('id');
42
        $dataReader = (new IterableDataReader($this->getDataSet()))
43
            ->withSort($sort);
44
        $paginator = new KeysetPaginator($dataReader);
45
46
        $this->expectException(\InvalidArgumentException::class);
47
        $paginator->withPageSize(0);
48
    }
49
50
    public function testThrowsExceptionWhenReaderHasNoSort(): void
51
    {
52
        $dataReader = new IterableDataReader($this->getDataSet());
53
54
        $this->expectException(\RuntimeException::class);
55
56
        new KeysetPaginator($dataReader);
57
    }
58
59
    public function testThrowsExceptionWhenNotSorted(): void
60
    {
61
        $sort = new Sort(['id', 'name']);
62
63
        $dataReader = (new IterableDataReader($this->getDataSet()))
64
            ->withSort($sort);
65
66
        $this->expectException(\RuntimeException::class);
67
68
        new KeysetPaginator($dataReader);
69
    }
70
71
    /**
72
     * @dataProvider onePageDataProvider
73
     */
74
    public function testOnePage(array $dataSet, int $pageSize): void
75
    {
76
        $sort = (new Sort(['id', 'name']))->withOrderString('id');
77
78
        $dataReader = (new IterableDataReader($dataSet))
79
            ->withSort($sort);
80
        $paginator = (new KeysetPaginator($dataReader))
81
            ->withPageSize($pageSize);
82
        $this->assertTrue($paginator->isOnFirstPage());
83
        $this->assertTrue($paginator->isOnLastPage());
84
    }
85
86
    public function testEmptyData(): void
87
    {
88
        $sort = (new Sort(['id', 'name']))->withOrderString('id');
89
        $dataReader = (new IterableDataReader([]))
90
            ->withSort($sort);
91
        $paginator = (new KeysetPaginator($dataReader))
92
            ->withPageSize(1)
93
            ->withNextPageToken('1');
94
95
        $this->assertTrue($paginator->isOnFirstPage());
96
        $this->assertTrue($paginator->isOnLastPage());
97
        $this->assertEmpty($paginator->read());
98
    }
99
100
    public function onePageDataProvider(): array
101
    {
102
        return [
103
            [[], 1],
104
            [[], 2],
105
            [[], 3],
106
107
            [array_slice($this->getDataSet(), 0, 1), 1],
108
109
            [array_slice($this->getDataSet(), 0, 1), 2],
110
            [array_slice($this->getDataSet(), 0, 2), 2],
111
112
            [array_slice($this->getDataSet(), 0, 1), 3],
113
            [array_slice($this->getDataSet(), 0, 2), 3],
114
            [array_slice($this->getDataSet(), 0, 3), 3],
115
116
            [array_slice($this->getDataSet(), 0, 1), 4],
117
            [array_slice($this->getDataSet(), 0, 2), 4],
118
            [array_slice($this->getDataSet(), 0, 3), 4],
119
            [array_slice($this->getDataSet(), 0, 4), 4],
120
        ];
121
    }
122
123
    public function testReadFirstPage(): void
124
    {
125
        $sort = (new Sort(['id', 'name']))->withOrderString('id');
126
127
        $dataReader = (new IterableDataReader($this->getDataSet()))
128
            ->withSort($sort);
129
130
131
        $paginator = (new KeysetPaginator($dataReader))
132
            ->withPageSize(2);
133
134
        $expected = [
135
            [
136
                'id' => 1,
137
                'name' => 'Codename Boris',
138
            ],
139
            [
140
                'id' => 2,
141
                'name' => 'Codename Doris',
142
            ],
143
        ];
144
145
        $this->assertSame($expected, $this->iterableToArray($paginator->read()));
146
        $last = end($expected);
147
        $this->assertSame((string)$last['id'], $paginator->getNextPageToken());
148
        $this->assertTrue($paginator->isOnFirstPage());
149
    }
150
151
    public function testReadSecondPage(): void
152
    {
153
        $sort = (new Sort(['id', 'name']))->withOrderString('id');
154
155
        $dataReader = (new IterableDataReader($this->getDataSet()))
156
            ->withSort($sort);
157
158
        $paginator = (new KeysetPaginator($dataReader))
159
            ->withPageSize(2)
160
            ->withNextPageToken("2");
161
162
        $expected = [
163
            [
164
                'id' => 3,
165
                'name' => 'Agent K',
166
            ],
167
            [
168
                'id' => 5,
169
                'name' => 'Agent J',
170
            ],
171
        ];
172
173
        $this->assertSame($expected, $this->iterableToArray($paginator->read()));
174
        $last = end($expected);
175
        $this->assertSame((string)$last['id'], $paginator->getNextPageToken());
176
    }
177
178
    public function testReadSecondPageOrderedByName(): void
179
    {
180
        $sort = (new Sort(['id', 'name']))->withOrderString('name');
181
182
        $dataReader = (new IterableDataReader($this->getDataSet()))
183
            ->withSort($sort);
184
185
        $paginator = (new KeysetPaginator($dataReader))
186
            ->withPageSize(2)
187
            ->withNextPageToken('Agent J');
188
189
        $expected = [
190
            [
191
                'id' => 3,
192
                'name' => 'Agent K',
193
            ],
194
            [
195
                'id' => 1,
196
                'name' => 'Codename Boris',
197
            ],
198
        ];
199
200
        $this->assertSame($expected, $this->iterableToArray($paginator->read()));
201
        $last = end($expected);
202
        $this->assertSame((string)$last['name'], $paginator->getNextPageToken());
203
    }
204
205
    public function testBackwardPagination(): void
206
    {
207
        $sort = (new Sort(['id', 'name']))->withOrderString('id');
208
209
        $dataReader = (new IterableDataReader($this->getDataSet()))
210
            ->withSort($sort);
211
212
        $paginator = (new KeysetPaginator($dataReader))
213
            ->withPageSize(2)
214
            ->withPreviousPageToken('5');
215
216
        $expected = [
217
            [
218
                'id' => 2,
219
                'name' => 'Codename Doris',
220
            ],
221
            [
222
                'id' => 3,
223
                'name' => 'Agent K',
224
            ],
225
        ];
226
        $this->assertSame($expected, $this->iterableToArray($paginator->read()));
227
        $first = reset($expected);
228
        $last = end($expected);
229
        $this->assertSame((string)$last['id'], $paginator->getNextPageToken(), 'Last value fail!');
230
        $this->assertSame((string)$first['id'], $paginator->getPreviousPageToken(), 'First value fail!');
231
    }
232
233
    public function testForwardAndBackwardPagination(): void
234
    {
235
        $sort = (new Sort(['id', 'name']))->withOrderString('id');
236
237
        $dataReader = (new IterableDataReader($this->getDataSet()))
238
            ->withSort($sort);
239
240
        $paginator = (new KeysetPaginator($dataReader))
241
            ->withPageSize(2)
242
            ->withNextPageToken("2");
243
244
        $expected = [
245
            [
246
                'id' => 3,
247
                'name' => 'Agent K',
248
            ],
249
            [
250
                'id' => 5,
251
                'name' => 'Agent J',
252
            ],
253
        ];
254
        $this->assertSame($expected, $this->iterableToArray($paginator->read()));
255
        $first = reset($expected);
256
        $last = end($expected);
257
        $this->assertSame((string)$last['id'], $paginator->getNextPageToken(), 'Last value fail!');
258
        $this->assertSame((string)$first['id'], $paginator->getPreviousPageToken(), 'First value fail!');
259
260
        $expected = [
261
            [
262
                'id' => 1,
263
                'name' => 'Codename Boris',
264
            ],
265
            [
266
                'id' => 2,
267
                'name' => 'Codename Doris',
268
            ],
269
        ];
270
271
        $paginator = (new KeysetPaginator($dataReader))
272
            ->withPageSize(2)
273
            ->withPreviousPageToken($paginator->getPreviousPageToken());
274
275
        $this->assertSame($expected, $this->iterableToArray($paginator->read()));
276
        $last = end($expected);
277
        $this->assertSame((string)$last['id'], $paginator->getNextPageToken(), 'Last value fail!');
278
        $this->assertNull($paginator->getPreviousPageToken(), 'First value fail!');
279
    }
280
281
    public function testIsOnFirstPage(): void
282
    {
283
        $sort = (new Sort(['id']))->withOrderString('id');
284
        $dataReader = (new IterableDataReader($this->getDataSet()))
285
            ->withSort($sort);
286
        $paginator = (new KeysetPaginator($dataReader))
287
            ->withPageSize(2);
288
        $this->assertTrue($paginator->isOnFirstPage());
289
        $this->assertTrue($paginator->isRequired());
290
    }
291
292
    public function testIsOnLastPage(): void
293
    {
294
        $sort = (new Sort(['id']))->withOrderString('id');
295
        $dataReader = (new IterableDataReader($this->getDataSet()))
296
            ->withSort($sort);
297
        $paginator = (new KeysetPaginator($dataReader))
298
            ->withPageSize(2);
299
300
        $paginator = $paginator->withNextPageToken('6');
301
        $this->assertTrue($paginator->isOnLastPage());
302
        $paginator = $paginator->withNextPageToken('5');
303
        $this->assertTrue($paginator->isOnLastPage());
304
        $paginator = $paginator->withNextPageToken('4');
305
        $this->assertTrue($paginator->isOnLastPage());
306
        $paginator = $paginator->withNextPageToken('3');
307
        $this->assertTrue($paginator->isOnLastPage());
308
        $paginator = $paginator->withNextPageToken('2');
309
        $this->assertFalse($paginator->isOnLastPage());
310
        $this->assertTrue($paginator->isRequired());
311
    }
312
313
    public function testCurrentPageSize(): void
314
    {
315
        $sort = (new Sort(['id']))->withOrderString('id');
316
        $dataReader = (new IterableDataReader($this->getDataSet()))
317
            ->withSort($sort);
318
        $paginator = (new KeysetPaginator($dataReader))
319
            ->withPageSize(2);
320
        $this->assertSame(2, $paginator->getCurrentPageSize());
321
        $paginator = $paginator->withPreviousPageToken('1');
322
        $this->assertSame(0, $paginator->getCurrentPageSize());
323
        $paginator = $paginator->withPreviousPageToken('2');
324
        $this->assertSame(1, $paginator->getCurrentPageSize());
325
        $paginator = $paginator->withPreviousPageToken('3');
326
        $this->assertSame(2, $paginator->getCurrentPageSize());
327
328
        $paginator = $paginator->withNextPageToken('6');
329
        $this->assertSame(0, $paginator->getCurrentPageSize());
330
        $paginator = $paginator->withNextPageToken('5');
331
        $this->assertSame(1, $paginator->getCurrentPageSize());
332
        $paginator = $paginator->withNextPageToken('4');
333
        $this->assertSame(2, $paginator->getCurrentPageSize());
334
    }
335
336
    public function testReadCache(): void
337
    {
338
        $sort = (new Sort(['id']))->withOrderString('id');
339
        $dataSet = new class($this->getDataSet()) extends \ArrayIterator {
340
            private int $rewindCounter = 0;
341
342
            public function rewind()
343
            {
344
                $this->rewindCounter++;
345
                parent::rewind();
346
            }
347
348
            public function getRewindCounter(): int
349
            {
350
                return $this->rewindCounter;
351
            }
352
        };
353
        $dataReader = (new IterableDataReader($dataSet))->withSort($sort);
354
        $paginator = (new KeysetPaginator($dataReader))
355
            ->withPageSize(2);
356
        // not use datase test
357
        $this->assertSame(0, $dataSet->getRewindCounter());
358
        // first use dataset test
359
        $paginator->getCurrentPageSize();
360
        $this->assertSame(1, $dataSet->getRewindCounter());
361
        // repeated use dataset test
362
        $paginator->getCurrentPageSize();
363
        $this->assertSame(1, $dataSet->getRewindCounter());
364
        $paginator->isOnFirstPage();
365
        $this->assertSame(1, $dataSet->getRewindCounter());
366
        $paginator->isOnLastPage();
367
        $this->assertSame(1, $dataSet->getRewindCounter());
368
        foreach ($paginator->read() as $void) {
369
        }
370
        $this->assertSame(1, $dataSet->getRewindCounter());
371
        // clear cache test
372
        $paginator = (new KeysetPaginator($dataReader))
373
            ->withPageSize(3);
374
        $this->assertSame(1, $dataSet->getRewindCounter());
375
        // recreate cache
376
        $paginator->getCurrentPageSize();
377
        $this->assertSame(2, $dataSet->getRewindCounter());
378
        $paginator->getCurrentPageSize();
379
        $this->assertSame(2, $dataSet->getRewindCounter());
380
    }
381
382
    public function testTokenResults(): void
383
    {
384
        $sort = (new Sort(['id']))->withOrderString('id');
385
        $dataReader = (new IterableDataReader($this->getDataSet()))
386
            ->withSort($sort);
387
        $paginator = (new KeysetPaginator($dataReader))
388
            ->withPageSize(2);
389
        $this->assertNotNull($paginator->getNextPageToken());
390
        $paginator = $paginator->withPreviousPageToken('1');
391
        try {
392
            $paginator->getNextPageToken();
393
            $this->assertTrue(false);
394
        } catch (\RuntimeException $e) {
395
            $this->assertTrue(true);
396
        }
397
        $this->assertNull($paginator->getPreviousPageToken());
398
        $paginator = $paginator->withPreviousPageToken('2');
399
        $this->assertNotNull($paginator->getNextPageToken());
400
        $this->assertSame('1', $paginator->getNextPageToken());
401
        $this->assertNull($paginator->getPreviousPageToken());
402
        $paginator = $paginator->withPreviousPageToken('3');
403
        $this->assertNotNull($paginator->getNextPageToken());
404
        $this->assertSame('2', $paginator->getNextPageToken());
405
        $this->assertNull($paginator->getPreviousPageToken());
406
407
        $paginator = $paginator->withNextPageToken('6');
408
        try {
409
            $paginator->getPreviousPageToken();
410
            $this->assertTrue(false);
411
        } catch (\RuntimeException $e) {
412
            $this->assertTrue(true);
413
        }
414
        $this->assertNull($paginator->getNextPageToken());
415
        $paginator = $paginator->withNextPageToken('5');
416
        $this->assertNotNull($paginator->getPreviousPageToken());
417
        $this->assertSame('6', $paginator->getPreviousPageToken());
418
        $this->assertNull($paginator->getNextPageToken());
419
        $paginator = $paginator->withNextPageToken('4');
420
        $this->assertNull($paginator->getNextPageToken());
421
        $this->assertNotNull($paginator->getPreviousPageToken());
422
        $this->assertSame('5', $paginator->getPreviousPageToken());
423
    }
424
425
    public function testDefaultPageSize(): void
426
    {
427
        $sort = (new Sort(['id']))->withOrderString('id');
428
        $dataReader = (new IterableDataReader($this->getDataSet()))->withSort($sort);
429
        $paginator = new KeysetPaginator($dataReader);
430
        $this->assertSame(10, $paginator->getPageSize());
431
        $this->assertCount(5, $paginator->read());
432
    }
433
434
    public function testCustomPageSize(): void
435
    {
436
        $sort = (new Sort(['id']))->withOrderString('id');
437
        $dataReader = (new IterableDataReader($this->getDataSet()))->withSort($sort);
438
        $paginator = (new KeysetPaginator($dataReader))->withPageSize(2);
439
        $this->assertSame(2, $paginator->getPageSize());
440
        $this->assertCount(2, $paginator->read());
441
    }
442
443
    private function getDataSet(): array
444
    {
445
        return [
446
            [
447
                'id' => 1,
448
                'name' => 'Codename Boris',
449
            ],
450
            [
451
                'id' => 2,
452
                'name' => 'Codename Doris',
453
            ],
454
            [
455
                'id' => 3,
456
                'name' => 'Agent K',
457
            ],
458
            [
459
                'id' => 5,
460
                'name' => 'Agent J',
461
            ],
462
            [
463
                'id' => 6,
464
                'name' => '007',
465
            ],
466
        ];
467
    }
468
469
    private function getNonSortableDataReader()
470
    {
471
        return new class() implements DataReaderInterface, FilterableDataInterface {
472
            public function withLimit(int $limit)
473
            {
474
                // do nothing
475
            }
476
477
            public function read(): iterable
478
            {
479
                return [];
480
            }
481
482
            public function withFilter(FilterInterface $filter)
483
            {
484
                // do nothing
485
            }
486
487
            public function withFilterProcessors(FilterProcessorInterface ...$filterUnits)
488
            {
489
                // do nothing
490
            }
491
        };
492
    }
493
494
    private function getNonFilterableDataReader()
495
    {
496
        return new class() implements DataReaderInterface, SortableDataInterface {
497
            public function withLimit(int $limit)
498
            {
499
                // do nothing
500
            }
501
502
            public function read(): iterable
503
            {
504
                return [];
505
            }
506
507
            public function withSort(?Sort $sorting)
508
            {
509
                // do nothing
510
            }
511
512
            public function getSort(): ?Sort
513
            {
514
                return new Sort([]);
515
            }
516
        };
517
    }
518
}
519