@@ -541,20 +541,52 @@ class PatrolFinder implements MatchFinder {
541541
542542 @override
543543 PatrolFinder get first {
544- // TODO: Throw a better error (https://github.com/leancodepl/patrol/issues/548)
545- return PatrolFinder (tester: tester, finder: finder.first);
544+ return _select (
545+ description: 'first' ,
546+ selector: (candidates) => candidates.take (1 ),
547+ );
546548 }
547549
548550 @override
549551 PatrolFinder get last {
550- // TODO: Throw a better error (https://github.com/leancodepl/patrol/issues/548)
551- return PatrolFinder (tester: tester, finder: finder.last);
552+ return _select (
553+ description: 'last' ,
554+ selector: (candidates) {
555+ if (candidates.isEmpty) {
556+ return const Iterable <Element >.empty ();
557+ }
558+
559+ return [candidates.last] as Iterable <Element >;
560+ },
561+ );
552562 }
553563
554564 @override
555565 PatrolFinder at (int index) {
556- // TODO: Throw a better error (https://github.com/leancodepl/patrol/issues/548)
557- return PatrolFinder (tester: tester, finder: finder.at (index));
566+ return _select (
567+ description: 'index $index ' ,
568+ selector: (candidates) {
569+ if (index < 0 ) {
570+ return const Iterable <Element >.empty ();
571+ }
572+
573+ return candidates.skip (index).take (1 );
574+ },
575+ );
576+ }
577+
578+ PatrolFinder _select ({
579+ required String description,
580+ required Iterable <Element > Function (Iterable <Element > candidates) selector,
581+ }) {
582+ return PatrolFinder (
583+ tester: tester,
584+ finder: _PatrolSelectorFinder (
585+ finder,
586+ selectorDescription: description,
587+ selector: selector,
588+ ),
589+ );
558590 }
559591
560592 @override
@@ -611,6 +643,30 @@ class PatrolFinder implements MatchFinder {
611643 bool precache () => finder.precache ();
612644}
613645
646+ class _PatrolSelectorFinder extends ChainedFinder {
647+ _PatrolSelectorFinder (
648+ super .parent, {
649+ required this .selectorDescription,
650+ required this .selector,
651+ });
652+
653+ final String selectorDescription;
654+ final Iterable <Element > Function (Iterable <Element > candidates) selector;
655+
656+ @override
657+ Iterable <Element > filter (Iterable <Element > parentCandidates) =>
658+ selector (parentCandidates);
659+
660+ @override
661+ String describeMatch (Plurality plurality) {
662+ return '${parent .describeMatch (plurality )} '
663+ '(ignoring all but $selectorDescription )' ;
664+ }
665+
666+ @override
667+ String get description => describeMatch (Plurality .many);
668+ }
669+
614670/// Useful methods that make chained finders more readable.
615671extension ActionCombiner on Future <PatrolFinder > {
616672 /// Same as [PatrolFinder.tap] , but on a [PatrolFinder] which is not yet
0 commit comments