1.0.0
This commit is contained in:
parent
00cac68d44
commit
4d2ca8a28a
10 changed files with 602 additions and 1 deletions
2
.analysis-options
Normal file
2
.analysis-options
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
analyzer:
|
||||||
|
strong-mode: true
|
44
.gitignore
vendored
44
.gitignore
vendored
|
@ -10,3 +10,47 @@ pubspec.lock
|
||||||
# Directory created by dartdoc
|
# Directory created by dartdoc
|
||||||
# If you don't generate documentation locally you can remove this line.
|
# If you don't generate documentation locally you can remove this line.
|
||||||
doc/api/
|
doc/api/
|
||||||
|
### JetBrains template
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
# User-specific stuff:
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/dictionaries
|
||||||
|
|
||||||
|
# Sensitive or high-churn files:
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.xml
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
|
||||||
|
# Gradle:
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
|
||||||
|
# Mongo Explorer plugin:
|
||||||
|
.idea/**/mongoSettings.xml
|
||||||
|
|
||||||
|
## File-based project format:
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
## Plugin-specific files:
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
1
.travis.yml
Normal file
1
.travis.yml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
language: dart
|
78
README.md
78
README.md
|
@ -1,2 +1,78 @@
|
||||||
# paginate
|
# paginate
|
||||||
Platform-agnostic pagination library, with custom support for the Angel framework.
|
[![version 1.0.0](https://img.shields.io/badge/pub-v1.0.0-brightgreen.svg)](https://pub.dartlang.org/packages/angel_paginate)
|
||||||
|
[![build status](https://travis-ci.org/angel-dart/paginate.svg)](https://travis-ci.org/angel-dart/paginate)
|
||||||
|
|
||||||
|
Platform-agnostic pagination library, with custom support for the
|
||||||
|
[Angel framework](https://github.com/angel-dart/angel).
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
In your `pubspec.yaml` file:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
dependencies:
|
||||||
|
angel_paginate: ^1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
This library exports a `Paginator<T>`, which can be used to efficiently produce
|
||||||
|
instances of `PaginationResult<T>`. Pagination results, when serialized to JSON, look like
|
||||||
|
this:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"total" : 75,
|
||||||
|
"items_per_page" : 10,
|
||||||
|
"previous_page" : 3,
|
||||||
|
"current_page" : 4,
|
||||||
|
"next_page" : 5,
|
||||||
|
"start_index" : 30,
|
||||||
|
"end_index" : 39,
|
||||||
|
"data" : ["<items...>"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Results can be parsed from Maps using the `PaginationResult<T>.fromMap` constructor, and
|
||||||
|
serialized via their `toJson()` method.
|
||||||
|
|
||||||
|
To create a paginator:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'package:angel_paginate/angel_paginate.dart';
|
||||||
|
|
||||||
|
main() {
|
||||||
|
var p = new Paginator(iterable);
|
||||||
|
|
||||||
|
// Get the current page (default: page 1)
|
||||||
|
var page = p.current;
|
||||||
|
print(page.total);
|
||||||
|
print(page.startIndex);
|
||||||
|
print(page.data); // The actual items on this page.
|
||||||
|
p.next(); // Advance a page
|
||||||
|
p.goBack(); // Back one page
|
||||||
|
p.goToPage(10); // Go to page number (1-based, not a 0-based index)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The entire Paginator API is documented, so check out the DartDocs.
|
||||||
|
|
||||||
|
Paginators by default cache paginations, to improve performance as you shift through pages.
|
||||||
|
This can be especially helpful in a client-side application where your UX involves a fast
|
||||||
|
response time, i.e. a search page.
|
||||||
|
|
||||||
|
## Use With Angel
|
||||||
|
Naturally, a library named `angel_paginate` has special provisions for the
|
||||||
|
[Angel framework](https://github.com/angel-dart/angel).
|
||||||
|
|
||||||
|
In `package:angel_paginate/server.dart`, a function called `paginate` generates
|
||||||
|
pagination service hooks for you. If the result of a hooked service event is an `Iterable`,
|
||||||
|
it will be paginated. This is convenient because it works with any data store, whether it
|
||||||
|
be MongoDB, RethinkDB, an in-memory store, or something else entirely.
|
||||||
|
|
||||||
|
```dart
|
||||||
|
configureServer(Angel app) {
|
||||||
|
var service = app.service('api/foo') as HookedService;
|
||||||
|
service.afterIndexed.listen(paginate(itemsPerPage: 10));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
See `test/server_test.dart` for examples of usage with Angel.
|
190
lib/angel_paginate.dart
Normal file
190
lib/angel_paginate.dart
Normal file
|
@ -0,0 +1,190 @@
|
||||||
|
/// Efficiently paginates collections of items in an object-oriented manner.
|
||||||
|
class Paginator<T> {
|
||||||
|
final Map<int, PaginationResult<T>> _cache = {};
|
||||||
|
PaginationResult<T> _current;
|
||||||
|
int _page = 0;
|
||||||
|
|
||||||
|
/// The collection of items to be paginated.
|
||||||
|
final Iterable<T> _items;
|
||||||
|
|
||||||
|
/// The maximum number of items to be shown per page.
|
||||||
|
final int itemsPerPage;
|
||||||
|
|
||||||
|
/// If `true` (default), then the results of paginations will be saved by page number.
|
||||||
|
///
|
||||||
|
/// For example, you would only have to paginate page 1 once. Future calls would return a cached version.
|
||||||
|
final bool useCache;
|
||||||
|
|
||||||
|
Paginator(this._items, {this.itemsPerPage: 5, this.useCache: true});
|
||||||
|
|
||||||
|
/// Returns `true` if there are more items at lesser page indices than the current one.
|
||||||
|
bool get canGoBack => _page > 0;
|
||||||
|
|
||||||
|
/// Returns `true` if there are more items at greater page indices than the current one.
|
||||||
|
bool get canGoForward => _page < _lastPage();
|
||||||
|
|
||||||
|
/// The current page index.
|
||||||
|
int get index => _page;
|
||||||
|
|
||||||
|
/// Returns the greatest possible page number for this collection, given the number of [itemsPerPage].
|
||||||
|
int get lastPageNumber => _lastPage();
|
||||||
|
|
||||||
|
/// The current page number. This is not the same as [index].
|
||||||
|
///
|
||||||
|
/// This getter will return user-friendly numbers. The lowest value it will ever return is `1`.
|
||||||
|
int get pageNumber => _page < 1 ? 1 : (_page + 1);
|
||||||
|
|
||||||
|
/// Fetches the current page. This will be cached until [back] or [next] is called.
|
||||||
|
///
|
||||||
|
/// If [useCache] is `true` (default), then computations will be cached even after the page changes.
|
||||||
|
PaginationResult<T> get current {
|
||||||
|
if (_current != null)
|
||||||
|
return _current;
|
||||||
|
else
|
||||||
|
return _current = _getPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
PaginationResult<T> _computePage() {
|
||||||
|
var len = _items.length;
|
||||||
|
var it = _items.skip(_page * (itemsPerPage ?? 5));
|
||||||
|
var offset = len - it.length;
|
||||||
|
it = it.take(itemsPerPage);
|
||||||
|
var last = _lastPage();
|
||||||
|
// print('cur: $_page, last: $last');
|
||||||
|
return new _PaginationResultImpl(it,
|
||||||
|
currentPage: _page + 1,
|
||||||
|
previousPage: _page <= 0 ? -1 : _page,
|
||||||
|
nextPage: _page >= last - 1 ? -1 : _page + 2,
|
||||||
|
startIndex: offset,
|
||||||
|
endIndex: offset + it.length - 1,
|
||||||
|
itemsPerPage: itemsPerPage,
|
||||||
|
total: len);
|
||||||
|
}
|
||||||
|
|
||||||
|
PaginationResult<T> _getPage() {
|
||||||
|
if (useCache != false)
|
||||||
|
return _cache.putIfAbsent(_page, () => _computePage());
|
||||||
|
else
|
||||||
|
return _computePage();
|
||||||
|
}
|
||||||
|
|
||||||
|
int _lastPage() {
|
||||||
|
var n = (_items.length / itemsPerPage).round();
|
||||||
|
// print('items: ${_items.length}');
|
||||||
|
// print('per page: $itemsPerPage');
|
||||||
|
// print('quotient: $n');
|
||||||
|
var remainder = _items.length - (n * itemsPerPage);
|
||||||
|
// print('remainder: $remainder');
|
||||||
|
return (remainder <= 0) ? n : n + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempts to go the specified page. If it fails, then it will remain on the current page.
|
||||||
|
///
|
||||||
|
/// Keep in mind - this just not be a zero-based index, but a one-based page number. The lowest
|
||||||
|
/// allowed value is `1`.
|
||||||
|
void goToPage(int page) {
|
||||||
|
if (page > 0 && page <= _lastPage()) {
|
||||||
|
_page = page - 1;
|
||||||
|
_current = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Moves the paginator back one page, if possible.
|
||||||
|
void back() {
|
||||||
|
if (_page > 0) {
|
||||||
|
_page--;
|
||||||
|
_current = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Advances the paginator one page, if possible.
|
||||||
|
void next() {
|
||||||
|
if (_page < _lastPage()) {
|
||||||
|
_page++;
|
||||||
|
_current = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stores the result of a pagination.
|
||||||
|
abstract class PaginationResult<T> {
|
||||||
|
factory PaginationResult.fromMap(Map<String, dynamic> map) =>
|
||||||
|
new _PaginationResultImpl(map['data'],
|
||||||
|
currentPage: map['current_page'],
|
||||||
|
endIndex: map['end_index'],
|
||||||
|
itemsPerPage: map['items_per_page'],
|
||||||
|
nextPage: map['next_page'],
|
||||||
|
previousPage: map['previous_page'],
|
||||||
|
startIndex: map['start_index'],
|
||||||
|
total: map['total']);
|
||||||
|
|
||||||
|
List<T> get data;
|
||||||
|
|
||||||
|
int get currentPage;
|
||||||
|
|
||||||
|
int get previousPage;
|
||||||
|
|
||||||
|
int get nextPage;
|
||||||
|
|
||||||
|
int get itemsPerPage;
|
||||||
|
|
||||||
|
int get total;
|
||||||
|
|
||||||
|
int get startIndex;
|
||||||
|
|
||||||
|
int get endIndex;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PaginationResultImpl<T> implements PaginationResult<T> {
|
||||||
|
final Iterable<T> _data;
|
||||||
|
Iterable<T> _cachedData;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final int currentPage;
|
||||||
|
|
||||||
|
_PaginationResultImpl(this._data,
|
||||||
|
{this.currentPage,
|
||||||
|
this.endIndex,
|
||||||
|
this.itemsPerPage,
|
||||||
|
this.nextPage,
|
||||||
|
this.previousPage,
|
||||||
|
this.startIndex,
|
||||||
|
this.total});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<T> get data => _cachedData ?? (_cachedData = new List<T>.from(_data));
|
||||||
|
|
||||||
|
@override
|
||||||
|
final int endIndex;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final int itemsPerPage;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final int nextPage;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final int previousPage;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final int startIndex;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final int total;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'total': total,
|
||||||
|
'items_per_page': itemsPerPage,
|
||||||
|
'previous_page': previousPage,
|
||||||
|
'current_page': currentPage,
|
||||||
|
'next_page': nextPage,
|
||||||
|
'start_index': startIndex,
|
||||||
|
'end_index': endIndex,
|
||||||
|
'data': data
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
49
lib/server.dart
Normal file
49
lib/server.dart
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
import 'angel_paginate.dart';
|
||||||
|
export 'angel_paginate.dart';
|
||||||
|
|
||||||
|
/// Paginates the results of service events.
|
||||||
|
///
|
||||||
|
/// Users can add a `page` to the query to display a certain page, i.e. `http://foo.com/api/todos?page=5`.
|
||||||
|
///
|
||||||
|
/// Users can also add a `$limit` to the query to display more or less items than specified in [itemsPerPage] (default: `5`).
|
||||||
|
/// If [maxItemsPerPage] is set, then even if the query contains a `$limit` parameter, it will be limited to the maximum.
|
||||||
|
HookedServiceEventListener paginate<T>(
|
||||||
|
{int itemsPerPage, int maxItemsPerPage}) {
|
||||||
|
return (HookedServiceEvent e) {
|
||||||
|
if (e.isBefore) throw new UnsupportedError(
|
||||||
|
'`package:angel_paginate` can only be run as an after hook.');
|
||||||
|
if (e.result is! Iterable) return;
|
||||||
|
|
||||||
|
int page = 0,
|
||||||
|
nItems = itemsPerPage;
|
||||||
|
|
||||||
|
if (e.params.containsKey('query') && e.params['query'] is Map) {
|
||||||
|
var query = e.params['query'] as Map;
|
||||||
|
|
||||||
|
if (query.containsKey('page')) {
|
||||||
|
try {
|
||||||
|
page = int.parse(query['page']?.toString());
|
||||||
|
} catch (e) {
|
||||||
|
// Fail silently...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.containsKey(r'$limit')) {
|
||||||
|
try {
|
||||||
|
var lim = int.parse(query[r'$limit']?.toString());
|
||||||
|
if (lim > 0 && (maxItemsPerPage == null || lim <= maxItemsPerPage))
|
||||||
|
nItems = lim;
|
||||||
|
} catch (e) {
|
||||||
|
// Fail silently...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var paginator = new Paginator(
|
||||||
|
e.result, itemsPerPage: nItems)
|
||||||
|
..goToPage(page);
|
||||||
|
e.result = paginator.current;
|
||||||
|
};
|
||||||
|
}
|
13
pubspec.yaml
Normal file
13
pubspec.yaml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
name: angel_paginate
|
||||||
|
version: 1.0.0
|
||||||
|
description: Platform-agnostic pagination library, with custom support for the Angel framework.
|
||||||
|
author: Tobe O <thosakwe@gmail.com>
|
||||||
|
homepage: https://github.com/angel-dart/paginate
|
||||||
|
environment:
|
||||||
|
sdk: ">=1.19.0"
|
||||||
|
dependencies:
|
||||||
|
angel_framework: ^1.0.0
|
||||||
|
dev_dependencies:
|
||||||
|
angel_diagnostics: ^1.0.0
|
||||||
|
angel_test: ^1.0.0
|
||||||
|
test: ^0.12.15
|
105
test/paginate_test.dart
Normal file
105
test/paginate_test.dart
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
import 'package:angel_paginate/angel_paginate.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
// Count-down from 100, then 101 at the end...
|
||||||
|
final List<int> DATA = new List<int>.generate(100, (i) => 100 - i)
|
||||||
|
..add(101);
|
||||||
|
|
||||||
|
main() {
|
||||||
|
group('cache', () {
|
||||||
|
var cached = new Paginator<int>(DATA),
|
||||||
|
uncached = new Paginator<int>(DATA, useCache: false);
|
||||||
|
|
||||||
|
test('always cache current', () {
|
||||||
|
expect(cached.current, cached.current);
|
||||||
|
expect(uncached.current, uncached.current);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('only cache prev/next if useCache != false', () {
|
||||||
|
var cached1 = cached.current;
|
||||||
|
cached.goToPage(4);
|
||||||
|
var cached4 = cached.current;
|
||||||
|
cached.goToPage(1);
|
||||||
|
expect(cached.current, cached1);
|
||||||
|
cached.goToPage(4);
|
||||||
|
expect(cached.current, cached4);
|
||||||
|
|
||||||
|
var uncached1 = uncached.current;
|
||||||
|
uncached.goToPage(4);
|
||||||
|
var uncached4 = uncached.current;
|
||||||
|
uncached.goToPage(1);
|
||||||
|
expect(uncached.current, isNot(uncached1));
|
||||||
|
uncached.goToPage(4);
|
||||||
|
expect(uncached.current, isNot(uncached4));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('default state', () {
|
||||||
|
var paginator = new Paginator<int>(DATA);
|
||||||
|
expect(paginator.index, 0);
|
||||||
|
expect(paginator.pageNumber, 1);
|
||||||
|
expect(paginator.itemsPerPage, 5);
|
||||||
|
expect(paginator.useCache, true);
|
||||||
|
expect(paginator.canGoBack, false);
|
||||||
|
expect(paginator.canGoForward, true);
|
||||||
|
expect(paginator.lastPageNumber, 21);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('paginate', () {
|
||||||
|
test('first page', () {
|
||||||
|
var paginator = new Paginator<int>(DATA);
|
||||||
|
expect(paginator.pageNumber, 1);
|
||||||
|
var r = paginator.current;
|
||||||
|
print(r.toJson());
|
||||||
|
expect(r.total, DATA.length);
|
||||||
|
expect(r.itemsPerPage, 5);
|
||||||
|
expect(r.previousPage, -1);
|
||||||
|
expect(r.currentPage, 1);
|
||||||
|
expect(r.nextPage, 2);
|
||||||
|
expect(r.startIndex, 0);
|
||||||
|
expect(r.endIndex, 4);
|
||||||
|
expect(r.data, DATA.skip(r.startIndex).take(r.itemsPerPage).toList());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('third page', () {
|
||||||
|
var paginator = new Paginator<int>(DATA)
|
||||||
|
..goToPage(3);
|
||||||
|
expect(paginator.pageNumber, 3);
|
||||||
|
var r = paginator.current;
|
||||||
|
print(r.toJson());
|
||||||
|
expect(r.total, DATA.length);
|
||||||
|
expect(r.itemsPerPage, 5);
|
||||||
|
expect(r.previousPage, 2);
|
||||||
|
expect(r.currentPage, 3);
|
||||||
|
expect(r.nextPage, 4);
|
||||||
|
expect(r.startIndex, 10);
|
||||||
|
expect(r.endIndex, 14);
|
||||||
|
expect(r.data, DATA.skip(r.startIndex).take(r.itemsPerPage).toList());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('last page', () {
|
||||||
|
var paginator = new Paginator<int>(DATA);
|
||||||
|
paginator.goToPage(paginator.lastPageNumber);
|
||||||
|
var r = paginator.current;
|
||||||
|
expect(r.total, DATA.length);
|
||||||
|
expect(r.itemsPerPage, 5);
|
||||||
|
expect(r.previousPage, paginator.lastPageNumber - 1);
|
||||||
|
expect(r.currentPage, paginator.lastPageNumber);
|
||||||
|
expect(r.nextPage, -1);
|
||||||
|
expect(r.startIndex, (paginator.lastPageNumber - 1) * 5);
|
||||||
|
expect(r.endIndex, r.startIndex);
|
||||||
|
expect(r.data, [DATA.last]);
|
||||||
|
expect(r.data, DATA.skip(r.startIndex).take(r.itemsPerPage).toList());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('dump pages', () {
|
||||||
|
var paginator = new Paginator<int>(DATA);
|
||||||
|
print('${paginator.lastPageNumber} page(s) of data:');
|
||||||
|
|
||||||
|
do {
|
||||||
|
print(' * Page #${paginator.pageNumber}: ${paginator.current.data}');
|
||||||
|
paginator.next();
|
||||||
|
} while(paginator.canGoForward);
|
||||||
|
});
|
||||||
|
}
|
115
test/server_test.dart
Normal file
115
test/server_test.dart
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
import 'package:angel_client/angel_client.dart' as c;
|
||||||
|
import 'package:angel_diagnostics/angel_diagnostics.dart';
|
||||||
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
import 'package:angel_paginate/server.dart';
|
||||||
|
import 'package:angel_test/angel_test.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
final List DATA = new List.filled(50, {'foo': 'bar'}, growable: true)
|
||||||
|
..addAll(new List.filled(25, {'bar': 'baz'}));
|
||||||
|
|
||||||
|
main() {
|
||||||
|
group('no max', () {
|
||||||
|
Angel app;
|
||||||
|
TestClient client;
|
||||||
|
c.Service dataService;
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
app = new Angel()
|
||||||
|
..use('/data', new AnonymousService(index: ([p]) async => DATA));
|
||||||
|
await app.configure(logRequests());
|
||||||
|
var service = app.service('data') as HookedService;
|
||||||
|
service.afterIndexed.listen(paginate(itemsPerPage: 10));
|
||||||
|
|
||||||
|
client = await connectTo(app);
|
||||||
|
dataService = client.service('data');
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() => client.close());
|
||||||
|
|
||||||
|
test('first page', () async {
|
||||||
|
var response = await dataService
|
||||||
|
.index()
|
||||||
|
.then((m) => new PaginationResult.fromMap(m));
|
||||||
|
print(response.toJson());
|
||||||
|
|
||||||
|
expect(response.total, DATA.length);
|
||||||
|
expect(response.itemsPerPage, 10);
|
||||||
|
expect(response.startIndex, 0);
|
||||||
|
expect(response.endIndex, 9);
|
||||||
|
expect(response.previousPage, -1);
|
||||||
|
expect(response.currentPage, 1);
|
||||||
|
expect(response.nextPage, 2);
|
||||||
|
expect(response.data, DATA.take(10).toList());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('third page', () async {
|
||||||
|
var response = await dataService.index({
|
||||||
|
'query': {'page': 3}
|
||||||
|
}).then((m) => new PaginationResult.fromMap(m));
|
||||||
|
print(response.toJson());
|
||||||
|
|
||||||
|
expect(response.total, DATA.length);
|
||||||
|
expect(response.itemsPerPage, 10);
|
||||||
|
expect(response.startIndex, 20);
|
||||||
|
expect(response.endIndex, 29);
|
||||||
|
expect(response.previousPage, 2);
|
||||||
|
expect(response.currentPage, 3);
|
||||||
|
expect(response.nextPage, 4);
|
||||||
|
expect(response.data, DATA.skip(20).take(10).toList());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('custom limit', () async {
|
||||||
|
var response = await dataService.index({
|
||||||
|
'query': {'page': 4, r'$limit': 5}
|
||||||
|
}).then((m) => new PaginationResult.fromMap(m));
|
||||||
|
print(response.toJson());
|
||||||
|
|
||||||
|
expect(response.total, DATA.length);
|
||||||
|
expect(response.itemsPerPage, 5);
|
||||||
|
expect(response.startIndex, 15);
|
||||||
|
expect(response.endIndex, 19);
|
||||||
|
expect(response.previousPage, 3);
|
||||||
|
expect(response.currentPage, 4);
|
||||||
|
expect(response.nextPage, 5);
|
||||||
|
expect(response.data, DATA.skip(15).take(5).toList());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('max 15', () {
|
||||||
|
Angel app;
|
||||||
|
TestClient client;
|
||||||
|
c.Service dataService;
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
app = new Angel()
|
||||||
|
..use('/data', new AnonymousService(index: ([p]) async => DATA));
|
||||||
|
await app.configure(logRequests());
|
||||||
|
var service = app.service('data') as HookedService;
|
||||||
|
service.afterIndexed.listen(
|
||||||
|
paginate(itemsPerPage: 10, maxItemsPerPage: 15));
|
||||||
|
|
||||||
|
client = await connectTo(app);
|
||||||
|
dataService = client.service('data');
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() => client.close());
|
||||||
|
|
||||||
|
test('exceed max', () async {
|
||||||
|
var response = await dataService.index({
|
||||||
|
'query': {'page': 4, r'$limit': 30}
|
||||||
|
}).then((m) => new PaginationResult.fromMap(m));
|
||||||
|
print(response.toJson());
|
||||||
|
|
||||||
|
// Should default to 10 items per page :)
|
||||||
|
expect(response.total, DATA.length);
|
||||||
|
expect(response.itemsPerPage, 10);
|
||||||
|
expect(response.startIndex, 30);
|
||||||
|
expect(response.endIndex, 39);
|
||||||
|
expect(response.previousPage, 3);
|
||||||
|
expect(response.currentPage, 4);
|
||||||
|
expect(response.nextPage, 5);
|
||||||
|
expect(response.data, DATA.skip(30).take(10).toList());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in a new issue