Skip to content

Commit

Permalink
feat: add none() (#60)
Browse files Browse the repository at this point in the history
Add `none()` for ensuring no entry in an array satisfy the expectation.

Remove deprecation for `some()`.
While it is equivalent to `has()` with one parameter, there is no harm in using it.
  • Loading branch information
unional authored Aug 5, 2018
1 parent 60c3138 commit 7f150e9
Show file tree
Hide file tree
Showing 19 changed files with 5,923 additions and 3,640 deletions.
9,375 changes: 5,808 additions & 3,567 deletions package-lock.json

Large diffs are not rendered by default.

27 changes: 14 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,30 +48,31 @@
"setupFiles": [
"<rootDir>/scripts/setup-test-env.js"
],
"testEnvironment": "node",
"testRegex": ".*\\.spec.ts$",
"transform": {
".(ts|tsx)": "<rootDir>/node_modules/ts-jest/preprocessor.js"
".(ts|tsx)": "ts-jest"
}
},
"author": {
"name": "Homa Wong",
"email": "[email protected]"
},
"devDependencies": {
"@types/jest": "^22.2.3",
"@types/node": "^9.6.21",
"assertron": "^5.1.1",
"dependency-check": "^3.1.0",
"eslint": "^4.19.1",
"@types/jest": "^23.3.1",
"@types/node": "^10.5.6",
"assertron": "^5.1.3",
"dependency-check": "^3.2.0",
"eslint": "^5.3.0",
"eslint-plugin-unional": "^1.0.0",
"jest": "^22.4.4",
"power-assert": "^1.5.0",
"jest": "^23.4.2",
"power-assert": "^1.6.0",
"rimraf": "^2.6.2",
"semantic-release": "^15.5.1",
"ts-jest": "^22.4.6",
"tslint": "^5.10.0",
"tslint-config-unional": "^0.9.4",
"typescript": "^2.8.4"
"semantic-release": "^15.9.5",
"ts-jest": "^23.1.2",
"tslint": "^5.11.0",
"tslint-config-unional": "^0.9.6",
"typescript": "^3.0.1"
},
"dependencies": {
"tersify": "^1.2.6"
Expand Down
2 changes: 1 addition & 1 deletion scripts/watch.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ cp.spawn('tsc', ['-w'], { shell: true })
.stdout.on('data', (data) => {
const text = data.toString()
process.stdout.write(text)
if (/.*Compilation complete/.test(text)) {
if (/.*Found 0 errors/.test(text)) {
if (!runner) {
runner = cp.spawn('jest', ['--watch'], {
stdio: 'inherit',
Expand Down
2 changes: 1 addition & 1 deletion src/And.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ test('fail when not passing any expectations', () => {

test('pass when passing all expectations', () => {
const s = createSatisfier([new And({ a: 1 }, { b: 2 })])
t.equal(s.exec([{ a: 1, b: 2 }]), undefined)
t.strictEqual(s.exec([{ a: 1, b: 2 }]), undefined)
})
2 changes: 1 addition & 1 deletion src/AtLeastOnce.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ test('pass when subject contains at least one element satisfying the expectation

test('match one follow with one miss', () => {
const actual = createSatisfier([new AtLeastOnce(1), 3]).exec([1, 2, 3])
t.equal(actual, undefined)
t.strictEqual(actual, undefined)
})

test('match one follow with two misses', () => {
Expand Down
4 changes: 2 additions & 2 deletions src/Or.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ test('fail when not passing all expectations', () => {

test('pass when passing any expectations', () => {
const s = createSatisfier([new Or({ a: 1 }, { b: 2 })])
t.equal(s.exec([{ a: 1 }]), undefined)
t.equal(s.exec([{ b: 2 }]), undefined)
t.strictEqual(s.exec([{ a: 1 }]), undefined)
t.strictEqual(s.exec([{ b: 2 }]), undefined)
})
72 changes: 36 additions & 36 deletions src/createSatisfier.exec.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,29 @@ import { createSatisfier } from './index'
import { assertExec, assertRegExp } from './testUtil'

test('undefined should match anything', () => {
t.equal(createSatisfier(undefined).exec(undefined), undefined)
t.equal(createSatisfier(undefined).exec({}), undefined)
t.equal(createSatisfier({ a: undefined }).exec({}), undefined)
t.equal(createSatisfier([undefined]).exec([]), undefined)
t.strictEqual(createSatisfier(undefined).exec(undefined), undefined)
t.strictEqual(createSatisfier(undefined).exec({}), undefined)
t.strictEqual(createSatisfier({ a: undefined }).exec({}), undefined)
t.strictEqual(createSatisfier([undefined]).exec([]), undefined)
})

test('primitive types without specifing generic will work without issue.', () => {
t.equal(createSatisfier(1).exec(1), undefined)
t.equal(createSatisfier(true).exec(true), undefined)
t.equal(createSatisfier('a').exec('a'), undefined)
t.strictEqual(createSatisfier(1).exec(1), undefined)
t.strictEqual(createSatisfier(true).exec(true), undefined)
t.strictEqual(createSatisfier('a').exec('a'), undefined)
})

test('can use generic to specify the data structure', () => {
t.equal(createSatisfier<number>(1).exec(1), undefined)
t.equal(createSatisfier<{ a: number }>({ a: /1/ }).exec({ a: 1 }), undefined)
t.strictEqual(createSatisfier<number>(1).exec(1), undefined)
t.strictEqual(createSatisfier<{ a: number }>({ a: /1/ }).exec({ a: 1 }), undefined)
})

test('empty object expectation passes all objects', () => {
t.equal(createSatisfier({}).exec({}), undefined)
t.equal(createSatisfier({}).exec({ a: 1 }), undefined)
t.equal(createSatisfier({}).exec({ a: { b: 'a' } }), undefined)
t.equal(createSatisfier({}).exec({ a: true }), undefined)
t.equal(createSatisfier({}).exec({ a: [1] }), undefined)
t.strictEqual(createSatisfier({}).exec({}), undefined)
t.strictEqual(createSatisfier({}).exec({ a: 1 }), undefined)
t.strictEqual(createSatisfier({}).exec({ a: { b: 'a' } }), undefined)
t.strictEqual(createSatisfier({}).exec({ a: true }), undefined)
t.strictEqual(createSatisfier({}).exec({ a: [1] }), undefined)
})

test('empty object expectation fails primitive', () => {
Expand All @@ -37,24 +37,24 @@ test('empty object expectation fails primitive', () => {

test('mismatch value gets path, expected, and actual', () => {
const actual = createSatisfier({ a: 1 }).exec({ a: 2 })!
t.equal(actual.length, 1)
t.strictEqual(actual.length, 1)
assertExec(actual[0], ['a'], 1, 2)
})

test('missing property get actual as undefined', () => {
const actual = createSatisfier({ a: 1 }).exec({})!
t.equal(actual.length, 1)
t.strictEqual(actual.length, 1)
assertExec(actual[0], ['a'], 1, undefined)
})

test('missing property get deeper level', () => {
const actual = createSatisfier({ a: { b: 1 } }).exec({ a: {} })!
t.equal(actual.length, 1)
t.strictEqual(actual.length, 1)
assertExec(actual[0], ['a', 'b'], 1, undefined)
})

test('passing regex gets undefined', () => {
t.equal(createSatisfier({ foo: /foo/ }).exec({ foo: 'foo' }), undefined)
t.strictEqual(createSatisfier({ foo: /foo/ }).exec({ foo: 'foo' }), undefined)
})

test('failed regex will be in expected property', () => {
Expand Down Expand Up @@ -82,54 +82,54 @@ test('regex on non-string will fail as normal', () => {
})

test('predicate receives actual value', () => {
t.equal(createSatisfier({ a: a => a === 1 }).exec({ a: 1 }), undefined)
t.strictEqual(createSatisfier({ a: a => a === 1 }).exec({ a: 1 }), undefined)
})

test('passing predicate gets undefined', () => {
t.equal(createSatisfier({ a: () => true }).exec({}), undefined)
t.equal(createSatisfier({ a: () => true }).exec({ a: 1 }), undefined)
t.strictEqual(createSatisfier({ a: () => true }).exec({}), undefined)
t.strictEqual(createSatisfier({ a: () => true }).exec({ a: 1 }), undefined)
})

test('failing predicate', () => {
const actual = createSatisfier({ a: /*istanbul ignore next*/function () { return false } }).exec({ a: 1 })!
t.equal(actual.length, 1)
t.strictEqual(actual.length, 1)
assertExec(actual[0], ['a'], /*istanbul ignore next*/function () { return false; }, 1)
})

test('against each element in array', () => {
t.equal(createSatisfier({ a: 1 }).exec([{ a: 1 }, { b: 1, a: 1 }]), undefined)
t.strictEqual(createSatisfier({ a: 1 }).exec([{ a: 1 }, { b: 1, a: 1 }]), undefined)
})

test('against each element in array in deep level', () => {
const actual = createSatisfier({ a: { b: { c: /foo/ } } }).exec([{ a: {} }, { a: { b: {} } }, { a: { b: { c: 'boo' } } }])!
t.equal(actual.length, 3)
t.strictEqual(actual.length, 3)
assertExec(actual[0], ['[0]', 'a', 'b'], { c: /foo/ }, undefined)
assertExec(actual[1], ['[1]', 'a', 'b', 'c'], /foo/, undefined)
assertExec(actual[2], ['[2]', 'a', 'b', 'c'], /foo/, 'boo')
})

test('when apply against array, will have indices in the path', () => {
const actual = createSatisfier({ a: 1 }).exec([{ a: 1 }, {}])!
t.equal(actual.length, 1)
t.strictEqual(actual.length, 1)
assertExec(actual[0], ['[1]', 'a'], 1, undefined)
})

test('when expectation is an array, apply to each entry in the actual array', () => {
t.equal(createSatisfier([{ a: 1 }, { b: 2 }]).exec([{ a: 1 }, { b: 2 }, { c: 3 }]), undefined)
t.strictEqual(createSatisfier([{ a: 1 }, { b: 2 }]).exec([{ a: 1 }, { b: 2 }, { c: 3 }]), undefined)
const actual = createSatisfier([{ a: 1 }, { b: 2 }]).exec([{ a: true }, { b: 'b' }, { c: 3 }])!
t.equal(actual.length, 2)
t.strictEqual(actual.length, 2)
assertExec(actual[0], ['[0]', 'a'], 1, true)
assertExec(actual[1], ['[1]', 'b'], 2, 'b')
})

test.skip('when expectation is an array and actual is not, the behavior is not defined yet', () => {
const actual = createSatisfier([{ a: 1 }, { b: 2 }]).exec({ a: 1 })!
t.equal(actual.length, 1)
t.strictEqual(actual.length, 1)
})

test('deep object checking', () => {
const actual = createSatisfier({ a: { b: 1 } }).exec({ a: { b: 2 } })!
t.equal(actual.length, 1)
t.strictEqual(actual.length, 1)
assertExec(actual[0], ['a', 'b'], 1, 2)
})

Expand All @@ -141,21 +141,21 @@ test('can check parent property', () => {
boo = 'boo'
}
const boo = new Boo()
t.equal(createSatisfier({ foo: 'foo' }).exec(boo), undefined)
t.strictEqual(createSatisfier({ foo: 'foo' }).exec(boo), undefined)
})

test('actual of type any should not have type checking error', () => {
let actual: any = { a: 1 }
t.equal(createSatisfier({ a: 1 }).exec(actual), undefined)
t.strictEqual(createSatisfier({ a: 1 }).exec(actual), undefined)
})

test('expect array in hash', () => {
t.equal(createSatisfier({ a: [1, true, 'a'] }).exec({ a: [1, true, 'a'] }), undefined)
t.strictEqual(createSatisfier({ a: [1, true, 'a'] }).exec({ a: [1, true, 'a'] }), undefined)
})

test('failing array in hash', () => {
const actual = createSatisfier({ a: [1, true, 'a'] }).exec({ a: [1, true, 'b'] })!
t.equal(actual.length, 1)
t.strictEqual(actual.length, 1)
assertExec(actual[0], ['a', '[2]'], 'a', 'b')
})

Expand All @@ -164,7 +164,7 @@ test('apply property predicate to array', () => {
data: e => e && e.every(x => x.login)
});

t.equal(satisfier.exec({ data: [{ login: 'a' }] }), undefined)
t.notEqual(satisfier.exec([{ data: [{ foo: 'a' }] }]), undefined)
t.notEqual(satisfier.exec([{ foo: 'b' }]), undefined)
t.strictEqual(satisfier.exec({ data: [{ login: 'a' }] }), undefined)
t.notStrictEqual(satisfier.exec([{ data: [{ foo: 'a' }] }]), undefined)
t.notStrictEqual(satisfier.exec([{ foo: 'b' }]), undefined)
})
2 changes: 1 addition & 1 deletion src/createSatisfier.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ test('Expecter can be specify partial of the data structure', () => {
test('nested {} checks for non undefined', () => {
const s = createSatisfier<{ a: { c: number, d: string }, b: string }>({ a: {} })
const actual = s.exec({} as any)!
t.equal(actual.length, 1)
t.strictEqual(actual.length, 1)
assertExec(actual[0], ['a'], {}, undefined)
})

Expand Down
2 changes: 1 addition & 1 deletion src/every.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,5 @@ test('array with all match returns true', () => {
})

test('tersify()', () => {
t.equal(every({ a: 1 }).tersify(), 'every({ a: 1 })')
t.strictEqual(every({ a: 1 }).tersify(), 'every({ a: 1 })')
})
2 changes: 1 addition & 1 deletion src/has.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,5 @@ test('pass when there are unmatched entry between matched one', () => {
})

test('tersify()', () => {
t.equal(has({ a: 1 }, { b: 2 }).tersify(), 'has({ a: 1 }, { b: 2 })')
t.strictEqual(has({ a: 1 }, { b: 2 }).tersify(), 'has({ a: 1 }, { b: 2 })')
})
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export * from './interfaces'
export * from './isInRange'
export * from './isInInterval'
export * from './isTypeOf'
export * from './none'
export * from './some'
8 changes: 4 additions & 4 deletions src/isInInterval.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ test('right closed', () => {
})

test('tersify()', () => {
t.equal(isInOpenInterval(1, 3).tersify(), '(1...3)')
t.equal(isInClosedInterval(1, 3).tersify(), '[1...3]')
t.equal(isInLeftClosedInterval(1, 3).tersify(), '[1...3)')
t.equal(isInRightClosedInterval(1, 3).tersify(), '(1...3]')
t.strictEqual(isInOpenInterval(1, 3).tersify(), '(1...3)')
t.strictEqual(isInClosedInterval(1, 3).tersify(), '[1...3]')
t.strictEqual(isInLeftClosedInterval(1, 3).tersify(), '[1...3)')
t.strictEqual(isInRightClosedInterval(1, 3).tersify(), '(1...3]')
})
2 changes: 1 addition & 1 deletion src/isInRange.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ test('check number in range', () => {
})

test('tersify()', () => {
t.equal(isInRange(1, 3).tersify(), '[1...3]')
t.strictEqual(isInRange(1, 3).tersify(), '[1...3]')
})
2 changes: 1 addition & 1 deletion src/isTypeOf.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ test('check type of property', () => {
})

test('tersify()', () => {
t.equal(isTypeOf('number').tersify(), 'typeof number')
t.strictEqual(isTypeOf('number').tersify(), 'typeof number')
})
24 changes: 24 additions & 0 deletions src/none.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import t from 'assert'
import a from 'assertron'

import { createSatisfier, none } from './index'

test('non array returns false', () => {
a.false(createSatisfier(none({ a: 1 })).test(true))
a.false(createSatisfier(none({ a: 1 })).test(1))
a.false(createSatisfier(none({ a: 1 })).test('{ a: 1 }'))
a.false(createSatisfier(none({ a: 1 })).test({ a: 1 }))
})

test('array with no match returns true', () => {
t(createSatisfier(none({ a: 1 })).test([{ b: 1 }]))
})

test('array with one match returns false', () => {
a.false(createSatisfier(none({ a: 1 })).test([{ a: 1 }]))
a.false(createSatisfier(none({ a: 1 })).test([{ b: 1 }, { a: 1 }]))
})

test('tersify()', () => {
t.strictEqual(none({ a: 1 }).tersify(), 'none({ a: 1 })')
})
17 changes: 17 additions & 0 deletions src/none.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {
tersible,
tersify,
// @ts-ignore
Tersible
} from 'tersify'

import { createSatisfier } from './createSatisfier'

/**
* Check if an array have no entry satisfying the expectation.
* @param expectation expectation
*/
export function none(expectation) {
const s = createSatisfier(expectation)
return tersible(e => e && Array.isArray(e) && !e.some(v => s.test(v)), () => `none(${tersify(expectation)})`)
}
2 changes: 1 addition & 1 deletion src/some.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@ test('array with more than one match returns true', () => {
})

test('tersify()', () => {
t.equal(some({ a: 1 }).tersify(), 'some({ a: 1 })')
t.strictEqual(some({ a: 1 }).tersify(), 'some({ a: 1 })')
})
1 change: 0 additions & 1 deletion src/some.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { createSatisfier } from './createSatisfier'

/**
* Check if an array have at least one entry satisfying the expectation.
* @deprecated please use `has()` instead
* @param expectation expectation
*/
export function some(expectation) {
Expand Down
16 changes: 8 additions & 8 deletions src/testUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ import t from 'assert'
import { tersify } from 'tersify'

export function assertRegExp(actual, path, regex, actualValue) {
t.equal(actual.length, 1)
t.deepEqual(actual[0].path, path)
t.equal(actual[0].expected.source, regex.source)
t.deepEqual(actual[0].actual, actualValue)
t.strictEqual(actual.length, 1)
t.deepStrictEqual(actual[0].path, path)
t.strictEqual(actual[0].expected.source, regex.source)
t.deepStrictEqual(actual[0].actual, actualValue)
}

export function assertExec(entry, path, expected, actual) {
t.deepEqual(entry.path, path)
t.deepStrictEqual(entry.path, path)
if (typeof entry.expected === 'function')
t.equal(tersify(entry.expected), tersify(expected))
t.strictEqual(tersify(entry.expected), tersify(expected))
else
t.deepEqual(entry.expected, expected)
t.deepEqual(entry.actual, actual)
t.deepStrictEqual(entry.expected, expected)
t.deepStrictEqual(entry.actual, actual)
}

0 comments on commit 7f150e9

Please sign in to comment.