package Test2::Compare::Bag; use strict; use warnings; use base 'Test2::Compare::Base'; our $VERSION = '0.000162'; use Test2::Util::HashBase qw/ending meta items for_each/; use Carp qw/croak confess/; use Scalar::Util qw/reftype looks_like_number/; sub init { my $self = shift; $self->{+ITEMS} ||= []; $self->{+FOR_EACH} ||= []; $self->SUPER::init(); } sub name { '' } sub meta_class { 'Test2::Compare::Meta' } sub verify { my $self = shift; my %params = @_; return 0 unless $params{exists}; my $got = $params{got} || return 0; return 0 unless ref($got); return 0 unless reftype($got) eq 'ARRAY'; return 1; } sub add_prop { my $self = shift; $self->{+META} = $self->meta_class->new unless defined $self->{+META}; $self->{+META}->add_prop(@_); } sub add_item { my $self = shift; my $check = pop; my ($idx) = @_; push @{$self->{+ITEMS}}, $check; } sub add_for_each { my $self = shift; push @{$self->{+FOR_EACH}} => @_; } sub deltas { my $self = shift; my %params = @_; my ($got, $convert, $seen) = @params{qw/got convert seen/}; my @deltas; my $state = 0; my @items = @{$self->{+ITEMS}}; my @for_each = @{$self->{+FOR_EACH}}; # Make a copy that we can munge as needed. my @list = @$got; my %unmatched = map { $_ => $list[$_] } 0..$#list; my $meta = $self->{+META}; push @deltas => $meta->deltas(%params) if defined $meta; while (@items) { my $item = shift @items; my $check = $convert->($item); my $match = 0; for my $idx (0..$#list) { next unless exists $unmatched{$idx}; my $val = $list[$idx]; my $deltas = $check->run( id => [ARRAY => $idx], convert => $convert, seen => $seen, exists => 1, got => $val, ); unless ($deltas) { $match++; delete $unmatched{$idx}; last; } } unless ($match) { push @deltas => $self->delta_class->new( dne => 'got', verified => undef, id => [ARRAY => '*'], got => undef, check => $check, ); } } if (@for_each) { my @checks = map { $convert->($_) } @for_each; for my $idx (0..$#list) { # All items are matched if we have conditions for all items delete $unmatched{$idx}; my $val = $list[$idx]; for my $check (@checks) { push @deltas => $check->run( id => [ARRAY => $idx], convert => $convert, seen => $seen, exists => 1, got => $val, ); } } } # if elements are left over, and ending is true, we have a problem! if($self->{+ENDING} && keys %unmatched) { for my $idx (sort keys %unmatched) { my $elem = $list[$idx]; push @deltas => $self->delta_class->new( dne => 'check', verified => undef, id => [ARRAY => $idx], got => $elem, check => undef, $self->{+ENDING} eq 'implicit' ? (note => 'implicit end') : (), ); } } return @deltas; } 1; __END__ =pod =encoding UTF-8 =head1 NAME Test2::Compare::Bag - Internal representation of a bag comparison. =head1 DESCRIPTION This module is an internal representation of a bag for comparison purposes. =head1 METHODS =over 4 =item $bool = $arr->ending =item $arr->set_ending($bool) Set this to true if you would like to fail when the array being validated has more items than the check. That is, if you check for 4 items but the array has 5 values, it will fail and list that unmatched item in the array as unexpected. If set to false then it is assumed you do not care about extra items. =item $arrayref = $arr->items() Returns the arrayref of values to be checked in the array. =item $arr->set_items($arrayref) Accepts an arrayref. B that there is no validation when using C, it is better to use the C interface. =item $name = $arr->name() Always returns the string C<< "" >>. =item $bool = $arr->verify(got => $got, exists => $bool) Check if C<$got> is an array reference or not. =item $arr->add_item($item) Push an item onto the list of values to be checked. =item @deltas = $arr->deltas(got => $got, convert => \&convert, seen => \%seen) Find the differences between the expected bag values and those in the C<$got> arrayref. =back =head1 SOURCE The source code repository for Test2-Suite can be found at F. =head1 MAINTAINERS =over 4 =item Chad Granum Eexodist@cpan.orgE =item Gianni Ceccarelli Edakkar@thenautilus.netE =back =head1 AUTHORS =over 4 =item Chad Granum Eexodist@cpan.orgE =item Gianni Ceccarelli Edakkar@thenautilus.netE =back =head1 COPYRIGHT Copyright 2018 Chad Granum Eexodist@cpan.orgE. Copyright 2018 Gianni Ceccarelli Edakkar@thenautilus.netE This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See F =cut