Skip to content

Commit

Permalink
Validators/matchers improvements (#167)
Browse files Browse the repository at this point in the history
  • Loading branch information
krzkaczor authored Jan 9, 2022
1 parent 7dca1a2 commit 05013cd
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 11 deletions.
6 changes: 6 additions & 0 deletions .changeset/fast-penguins-hammer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'earljs': patch
---

Make `toBeAnArrayWith` and `toBeAContainerWith` handle repeated items as
expected
25 changes: 22 additions & 3 deletions packages/earljs/src/matchers/ArrayWith.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Dictionary } from 'ts-essentials'

import { smartEq } from '../validators/smartEq'
import { Matcher } from './Base'

Expand All @@ -11,9 +13,7 @@ export class ArrayWithMatcher<T> extends Matcher {
return false
}

return this.expectedItems.every((expectedItem) =>
actualItems.some((actualItem) => smartEq(actualItem, expectedItem).result === 'success'),
)
return contains(this.expectedItems, actualItems)
}

toString() {
Expand All @@ -24,3 +24,22 @@ export class ArrayWithMatcher<T> extends Matcher {
return new ArrayWithMatcher(items) as any
}
}

/** @internal */
export function contains(expectedItems: ReadonlyArray<any>, actualItems: ReadonlyArray<any>): boolean {
const matchedIndexes: Dictionary<boolean, number> = {}

return expectedItems.every((expectedItem) => {
const foundIndex = actualItems.findIndex(
(actualItem, index) => smartEq(actualItem, expectedItem).result === 'success' && !matchedIndexes[index],
)

if (foundIndex !== -1) {
matchedIndexes[foundIndex] = true

return true
} else {
return false
}
})
}
6 changes: 2 additions & 4 deletions packages/earljs/src/matchers/ContainerWith.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { isIterableAndNotString } from '../validators/common'
import { smartEq } from '../validators/smartEq'
import { contains } from './ArrayWith'
import { Matcher } from './Base'

export class ContainerWithMatcher<T> extends Matcher {
Expand All @@ -14,9 +14,7 @@ export class ContainerWithMatcher<T> extends Matcher {

const items = Array.from(actualItems)

return this.expectedItems.every((expectedItem) =>
items.some((actualItem) => smartEq(actualItem, expectedItem).result === 'success'),
)
return contains(this.expectedItems, items)
}

toString() {
Expand Down
3 changes: 2 additions & 1 deletion packages/earljs/src/matchers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export interface Matchers {
anything(): any

/**
* Matches an instance of the provided class or primitive type. Examples:
* Matches an instance of a provided class or a primitive type. Works as expected with builtin types like strings, numbers, dates.
*
* 1. `expect.a(MyClass)` - matches `new MyClass`, but not `new Other()`
* 2. `expect.a(String)` - matches `"foo"`, but not `123`
Expand All @@ -22,6 +22,7 @@ export interface Matchers {
*
* @example
* ```ts
* expect(something).toEqual(expect.a(MyClass)) // matches any object of instance MyClass but not `other`
* expect(something).toEqual(expect.a(String)) // matches any string
* expect(something).toEqual(expect.a(Object)) // matches any object (not null)
* ```
Expand Down
11 changes: 8 additions & 3 deletions packages/earljs/src/validators/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,17 @@ export interface CommonValidators<T> extends BooleanValidators, OptionalValidato
*/
toReferentiallyEqual(value: T): void
/**
* Checks if the value is an instance of the provided class or primitive type. Examples:
* Checks if the value is an instance of a provided class or a primitive type. Works as expected with builtin types like strings, numbers, dates.
*
* 1. `expect.a(MyClass)` - matches `new MyClass`, but not `new Other()`
* 2. `expect.a(String)` - matches `"foo"`, but not `123`
*
* @param clazz - type class or primitive constructor to match against.
* @example
* ```ts
* expect(object).toBeA(MyClass) // checks if object is instance of `MyClass`, but not `Other`
* expect(foo).toBeA(String) // checks if foo is instance of string
* expect(foo).toBeA(Object) // matches any object (not null)
* ```
*/
toBeA(clazz: any): void
Expand Down Expand Up @@ -343,20 +347,21 @@ export interface ArrayValidators {
*/
toBeAnArrayOfLength(length: number): void
/**
* Checks if the value is an array containing all of the provided items.
* Checks if the value is an array containing all of the provided items. Each expected item must be matched uniquely.
*
* @param expectedItems - values or matchers to look for in the matched array. Order of the items doesn't matter.
* @example
* ```ts
* expect([1, 2, 3]).toBeAnArrayWith(1)
* expect([1]).toBeAnArrayWith(1, 1) // throws b/c a second "1" is missing
* ```
*/
toBeAnArrayWith(...expectedItems: ReadonlyArray<any>): void
}

export interface IterableValidators<T> {
/**
* Checks if the value is an iterable containing all of the provided items.
* Checks if the value is an iterable containing all of the provided items. Internally, container is first turned into array and `toBeAnArrayWith` is used for final comparison.
*
* @param expectedItems - values or matchers to look for in the matched iterable. Order of the items doesn't matter.
* @example
Expand Down
9 changes: 9 additions & 0 deletions packages/earljs/test/matchers/ArrayWith.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ describe('ArrayWith matcher', () => {
expect(m.check(1)).to.be.false
})

it('matches repeated items', () => {
const m = new ArrayWithMatcher([1, 1])

expect(m.check([1, 1])).to.be.true
expect(m.check([1, 1, 1])).to.be.true
expect(m.check([1, 0, 1])).to.be.true
expect(m.check([1])).to.be.false
})

describe('in expectation', () => {
it('works', () => {
earlExpect([1, 2, 3]).toEqual(earlExpect.arrayWith(3))
Expand Down
9 changes: 9 additions & 0 deletions packages/earljs/test/matchers/ContainerWith.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ describe('ContainerWith matcher', () => {
expect(m.check(['a'])).to.be.false
})

it('matches arrays with repeated items', () => {
const m = new ContainerWithMatcher([1, 1])

expect(m.check([1, 1])).to.be.true
expect(m.check([1, 1, 1])).to.be.true
expect(m.check([1, 0, 1])).to.be.true
expect(m.check([1])).to.be.false
})

it('matches sets', () => {
const m = new ContainerWithMatcher([1])

Expand Down

0 comments on commit 05013cd

Please sign in to comment.