Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions src/functions/assert/Collection/Should-ContainCollection.ps1
Original file line number Diff line number Diff line change
@@ -1,36 +1,39 @@
function Should-ContainCollection {
<#
.SYNOPSIS
Compares collections to see if the expected collection is present in the provided collection. It does not compare the types of the input collections.
Checks that the expected collection is present in the actual collection as an ordered subsequence. It does not compare the types of the input collections.

.DESCRIPTION
This assertion uses PowerShell containment to check whether the actual collection contains the expected value. The comparison uses the contained value's own equality semantics.
The items of the expected collection must appear in the actual collection in the same order. Gaps between the matched items are allowed, but each actual item is used at most once, so repeated expected items need at least as many matching items in the actual collection. A single value is treated as a one-item collection. Items are compared using PowerShell equality, the same as the `-contains` operator.

.PARAMETER Expected
A collection of items.
One or more items to look for as an ordered subsequence. A single value is treated as a one-item collection.

.PARAMETER Actual
A collection of items.
The collection to search in.

.PARAMETER Because
The reason why the input should be the expected value.

.EXAMPLE
```powershell
1, 2, 3 | Should-ContainCollection @(1, 2)
1, 2, 3 | Should-ContainCollection @(1, 3)
@(1) | Should-ContainCollection @(1)
1, 2, 3 | Should-ContainCollection 2
```

This assertion will pass, because all items are present in the collection, in the right order.
These assertions pass, because the expected items are present in the same order. Gaps between them, as in `@(1, 3)`, are allowed, and a single value is treated as a one-item collection.

.EXAMPLE
```powershell
1, 2, 3 | Should-ContainCollection @(3, 4)
1, 2, 3 | Should-ContainCollection @(3, 2, 1)
1, 2 | Should-ContainCollection @(1, 1)
@(1) | Should-ContainCollection @(2)
```

This assertion will fail, because not all items are present in the collection, or are not in the right order.
These assertions fail, because an expected item is missing (`@(3, 4)`), the items are not in the right order (`@(3, 2, 1)`), or the actual collection does not have enough matching items (`@(1, 1)` needs two 1s).

.LINK
https://pester.dev/docs/commands/Should-ContainCollection
Expand Down Expand Up @@ -63,7 +66,7 @@
Invoke-AssertionFailed -Message $Message -CallerCmdlet $PSCmdlet
}

if ($Actual -notcontains $Expected) {
if (-not (Is-CollectionSubsequence -Expected $Expected -Actual $Actual)) {
$Message = Get-AssertionMessage -Expected $Expected -Actual $Actual -Because $Because -DefaultMessage "Expected <expectedType> <expected> to be present in <actualType> <actual>,<because> but it was not there."
& $reportFailure $Message
}
Expand Down
21 changes: 11 additions & 10 deletions src/functions/assert/Collection/Should-NotContainCollection.ps1
Original file line number Diff line number Diff line change
@@ -1,36 +1,37 @@
function Should-NotContainCollection {
<#
.SYNOPSIS
Compares collections to ensure that the expected collection is not present in the provided collection. It does not compare the types of the input collections.
Checks that the expected collection is not present in the actual collection as an ordered subsequence. It does not compare the types of the input collections.

.DESCRIPTION
This assertion uses PowerShell containment to check that the actual collection does not contain the expected value. The comparison uses the contained value's own equality semantics.
Passes when the items of the expected collection do not appear in the actual collection in the same order. The subsequence is matched the same way as `Should-ContainCollection`: gaps between matched items are allowed, but each actual item is used at most once. A single value is treated as a one-item collection. Items are compared using PowerShell equality, the same as the `-contains` operator.

.PARAMETER Expected
A collection of items.
One or more items to look for as an ordered subsequence. A single value is treated as a one-item collection.

.PARAMETER Actual
A collection of items.
The collection to search in.

.PARAMETER Because
The reason why the input should be the expected value.

.EXAMPLE
```powershell
1, 2, 3 | Should-ContainCollection @(3, 4)
1, 2, 3 | Should-ContainCollection @(3, 2, 1)
@(1) | Should-ContainCollection @(2)
1, 2, 3 | Should-NotContainCollection @(3, 4)
1, 2, 3 | Should-NotContainCollection @(3, 2, 1)
@(1) | Should-NotContainCollection @(2)
```

This assertion will pass, because the collections are different, or the items are not in the right order.
These assertions pass, because the expected items are not present as an ordered subsequence.

.EXAMPLE
```powershell
1, 2, 3 | Should-NotContainCollection @(1, 2)
1, 2, 3 | Should-NotContainCollection @(1, 3)
@(1) | Should-NotContainCollection @(1)
```

This assertion will fail, because all items are present in the collection and are in the right order.
These assertions fail, because the expected items are present in the same order. Gaps between them, as in `@(1, 3)`, are allowed.

.LINK
https://pester.dev/docs/commands/Should-NotContainCollection
Expand Down Expand Up @@ -63,7 +64,7 @@
Invoke-AssertionFailed -Message $Message -CallerCmdlet $PSCmdlet
}

if ($Actual -contains $Expected) {
if (Is-CollectionSubsequence -Expected $Expected -Actual $Actual) {
$Message = Get-AssertionMessage -Expected $Expected -Actual $Actual -Because $Because -DefaultMessage "Expected <expectedType> <expected> to not be present in <actualType> <actual>,<because> but it was there."
& $reportFailure $Message
}
Expand Down
35 changes: 35 additions & 0 deletions src/functions/assert/Common/Is-CollectionSubsequence.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
function Is-CollectionSubsequence ($Expected, $Actual) {
# Returns $true when every item of $Expected appears within $Actual in the same
# relative order. Gaps between the matched items are allowed, but each $Actual item is
# consumed at most once, so repeated items in $Expected need at least as many matching
# items in $Actual (e.g. @(1, 1) needs two 1s in $Actual, not one reused twice).
#
# A single, non-collection value is treated as a one-item collection, which keeps the
# original single-item containment behaviour as the one-item special case.

# Materialise the expected items so we can index them by position. Adding them one by
# one preserves each item as a single element, even when it is itself a collection,
# which @(...) would otherwise flatten.
$expectedItems = [System.Collections.Generic.List[object]]::new()
foreach ($item in $Expected) { $expectedItems.Add($item) }

$expectedCount = $expectedItems.Count
# An empty expected collection is vacuously present in any collection.
if (0 -eq $expectedCount) { return $true }

# Greedy two-pointer subsequence match: walk $Actual once and advance through the
# expected items whenever the current actual item matches the next one we need. Matching
# the earliest occurrence is always safe for a subsequence, so a single pass is enough.
$matchIndex = 0
foreach ($actualItem in $Actual) {
# Compare with -eq, actual item on the left, to match the equality semantics of
# PowerShell's -contains operator. Cast to [bool] so an actual item that is itself a
# collection collapses to a single truthy/falsy result instead of a filtered array.
if ([bool]($actualItem -eq $expectedItems[$matchIndex])) {
$matchIndex++
if ($matchIndex -eq $expectedCount) { return $true }
}
}

return $false
}
32 changes: 32 additions & 0 deletions tst/functions/assert/Collection/Should-ContainCollection.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,38 @@ InPesterModuleScope {
@(1, 2, 3) | Should-ContainCollection 1
}

It "Passes when the expected items appear as a contiguous block" {
1, 2, 3 | Should-ContainCollection @(1, 2)
1, 2, 3 | Should-ContainCollection @(2, 3)
}

It "Passes when the expected items appear in order with gaps" {
1, 2, 3 | Should-ContainCollection @(1, 3)
}

It "Passes when an expected collection of one item is present" {
@(1) | Should-ContainCollection @(1)
}

It "Passes when there are enough duplicate items to match repeated expected items" {
1, 1, 2 | Should-ContainCollection @(1, 1)
}

It "Fails when the expected items are not in the right order" {
$err = { 1, 2, 3 | Should-ContainCollection @(3, 2, 1) } | Verify-AssertionFailed
$err.Exception.Message | Verify-Equal "Expected [Object[]] @(3, 2, 1) to be present in [Object[]] @(1, 2, 3), but it was not there."
}

It "Fails when an expected item is missing" {
$err = { 1, 2, 3 | Should-ContainCollection @(3, 4) } | Verify-AssertionFailed
$err.Exception.Message | Verify-Equal "Expected [Object[]] @(3, 4) to be present in [Object[]] @(1, 2, 3), but it was not there."
}

It "Fails when a repeated expected item cannot be matched by a single actual item" {
{ 1, 2 | Should-ContainCollection @(1, 1) } | Verify-AssertionFailed
{ @(1) | Should-ContainCollection @(1, 1) } | Verify-AssertionFailed
}

It "Fails when collection of multiple items does not contain the expected item" {
$err = { @(5, 6, 7) | Should-ContainCollection 1 } | Verify-AssertionFailed
$err.Exception.Message | Verify-Equal "Expected [int] 1 to be present in [Object[]] @(5, 6, 7), but it was not there."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,28 @@ InPesterModuleScope {
@(5, 6, 7) | Should-NotContainCollection 1
}

It "Passes when the expected items are not in the right order" {
1, 2, 3 | Should-NotContainCollection @(3, 2, 1)
}

It "Passes when an expected item is missing" {
1, 2, 3 | Should-NotContainCollection @(3, 4)
}

It "Passes when a repeated expected item cannot be matched by a single actual item" {
1, 2 | Should-NotContainCollection @(1, 1)
}

It "Fails when the expected items appear as a contiguous block" {
$err = { 1, 2, 3 | Should-NotContainCollection @(1, 2) } | Verify-AssertionFailed
$err.Exception.Message | Verify-Equal "Expected [Object[]] @(1, 2) to not be present in [Object[]] @(1, 2, 3), but it was there."
}

It "Fails when the expected items appear in order with gaps" {
$err = { 1, 2, 3 | Should-NotContainCollection @(1, 3) } | Verify-AssertionFailed
$err.Exception.Message | Verify-Equal "Expected [Object[]] @(1, 3) to not be present in [Object[]] @(1, 2, 3), but it was there."
}

It "Can be called with positional parameters" {
{ Should-NotContainCollection 1 1, 2, 3 } | Verify-AssertionFailed
}
Expand Down
Loading