Skip to content

Commit

Permalink
Merge pull request #10 from ygunayer/dev
Browse files Browse the repository at this point in the history
Massively increase code coverage, fix various bugs
  • Loading branch information
ygunayer authored Jan 4, 2020
2 parents b2460b5 + d0ea437 commit 42d8b93
Show file tree
Hide file tree
Showing 9 changed files with 232 additions and 23 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ node_modules
.cache
dist
.nyc_output
coverage/
5 changes: 3 additions & 2 deletions .nycrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"extends": "@istanbuljs/nyc-config-typescript",
"all": true,
"exclude": ["src/examples", "dist/examples", "test/"]
"include": ["src/**/*.ts", "dist/**/*.js"],
"exclude": ["src/examples", "dist/examples"],
"reporter": ["html", "text"]
}
16 changes: 16 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Mocha Tests",
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
"args": [
"--timeout",
"999999",
"--colors",
"${workspaceFolder}/test/**/*.test.js"
],
"internalConsoleOptions": "openOnSessionStart",
"skipFiles": [
"<node_internals>/**"
]
},
{
"type": "node",
"request": "launch",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"scripts": {
"clean": "rimraf dist/",
"build": "npm run clean && tsc",
"test": "npm run build && nyc mocha --require ts-node/register --require source-map-support/register --recursive",
"test": "npm run build && nyc mocha --require ts-node/register --require source-map-support/register --recursive test/",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"publish": "npm test && cd dist && cp ../package*.json ./ && cp ../.npmignore ./ && npm pack && npm publish ./*.tgz",
"example:basic": "tsc && node dist/examples/basic.js",
Expand Down
4 changes: 2 additions & 2 deletions src/lib/partial-function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ export function Combined<A, B, C>(
): PartialFunction<A, C> {
return fromStub({
isDefinedAt(value: A) {
if (pf.isDefinedAt(value)) {
return true;
if (!pf.isDefinedAt(value)) {
return false;
}

const bValue = pf.apply(value);
Expand Down
26 changes: 11 additions & 15 deletions src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export function isType(x: any) {
* @param match the expression to match against
*/
export function isMatch(value: any, match: any): boolean {
if (match === _) {
if (match === _ || value === _) {
return true;
}

Expand All @@ -56,12 +56,12 @@ export function isMatch(value: any, match: any): boolean {
if (match === String) {
return isString(value);
}
if (match === Object) {
return isObject(value);
}
if (match === Number) {
return isNumber(value);
}
if (match === Object) {
return isObject(value);
}

if (isType(match)) {
const Type = match;
Expand All @@ -82,13 +82,6 @@ export function isMatch(value: any, match: any): boolean {
}
}

if (isObject(match)) {
return Object.keys(match).some(key => {
const matchAgainst = match[key];
return isMatch(value[key], matchAgainst);
});
}

if (isArray(match)) {
if (!isArray(value)) {
return false;
Expand All @@ -99,13 +92,16 @@ export function isMatch(value: any, match: any): boolean {
}

return match.every((x, idx) => {
if (x === true) {
return true;
}

return isMatch(value[idx], x);
});
}

if (isObject(match)) {
return Object.keys(match).every(key => {
const matchAgainst = match[key];
return isMatch(value[key], matchAgainst);
});
}

return value === match;
}
34 changes: 32 additions & 2 deletions test/lib/partial-function.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require('mocha');
const {expect} = require('chai');
const {fromFunction, fromStub, fromStubs, UndefinedValueError} = require('../../dist/lib/partial-function');
const {Never, fromFunction, fromStub, fromStubs, UndefinedValueError} = require('../../dist/lib/partial-function');

describe('PartialFunction', () => {

Expand All @@ -20,6 +20,22 @@ describe('PartialFunction', () => {
});


describe('Never', () => {
it('should be undefined for everything and never apply', () => {
const values = [1, 'foo', /foo/, () => 42, {a: 4}, null, undefined];
values.forEach(value => {
expect(Never.isDefinedAt(value)).to.be.false;
try {
Never.apply(value);
throw new Error('Should throw error');
} catch (err) {
expect(err, err.msg).to.be.instanceOf(UndefinedValueError);
}
});
});
});


describe('fromStub', () => {
it('should create a partial function that uses the given parameters', () => {
let isDefinedAtCalled = false;
Expand Down Expand Up @@ -219,6 +235,7 @@ describe('PartialFunction', () => {
expect(isApplyCalled1, 'isApplyCalled1 should be true').to.be.true;
expect(isDefinedAtCalled2, 'isDefinedAtCalled2 should be false').to.be.false;
expect(isApplyCalled2, 'isApplyCalled2 should be false').to.be.false;
expect(pf.isDefinedAt(42), `should be defined for 42`).to.be.true;
expect(result).to.equal(420);
});

Expand Down Expand Up @@ -248,10 +265,11 @@ describe('PartialFunction', () => {
expect(isApplyCalled1, 'isApplyCalled1 should be false').to.be.false;
expect(isDefinedAtCalled2, 'isDefinedAtCalled2 should be true').to.be.true;
expect(isApplyCalled2, 'isApplyCalled2 should be true').to.be.true;
expect(pf.isDefinedAt(100), `should be defined for 100`).to.be.true;
expect(result).to.equal(50);
});

it('should throw without applying anything when both undefined', () => {
it('should throw without applying anything when both are undefined', () => {
let isDefinedAtCalled1 = false;
let isApplyCalled1 = false;
const stub1 = {
Expand All @@ -271,6 +289,8 @@ describe('PartialFunction', () => {

const pf = pf1.orElse(pf2);

expect(pf.isDefinedAt('foo'), `should not be defined for 'foo'`).to.be.false;

try {
pf.apply('foo');
throw new Error('Should have thrown');
Expand Down Expand Up @@ -306,6 +326,8 @@ describe('PartialFunction', () => {
expect(isDefinedAtCalled, 'isDefinedAtCalled should be true').to.be.true;
expect(isApplyCalled, 'isApplyCalled should be true').to.be.true;
expect(isSecondCalled, 'isSecondCalled should be true').to.be.true;
expect(pf.isDefinedAt(42), `should be defined for 42`).to.be.true;
expect(pf.isDefinedAt('foo'), `should not be defined for 'foo'`).to.be.false;
expect(result).to.equal('foo-420');
});

Expand All @@ -330,6 +352,8 @@ describe('PartialFunction', () => {
expect(isDefinedAtCalled, 'isDefinedAtCalled should be true').to.be.true;
expect(isApplyCalled, 'isApplyCalled should be false').to.be.false;
expect(isSecondCalled, 'isSecondCalled should be false').to.be.false;
expect(pf.isDefinedAt(42), `should be defined for 42`).to.be.true;
expect(pf.isDefinedAt('foo'), `should not be defined for 'foo'`).to.be.false;
}
});
});
Expand Down Expand Up @@ -364,6 +388,7 @@ describe('PartialFunction', () => {
expect(isApplyCalled1, 'isApplyCalled1 should be true').to.be.true;
expect(isDefinedAtCalled2, 'isDefinedAtCalled2 should be true').to.be.true;
expect(isApplyCalled2, 'isApplyCalled2 should be true').to.be.true;
expect(pf.isDefinedAt(42), `should be defined for 42`).to.be.true;
expect(result).to.equal(840);
});

Expand Down Expand Up @@ -396,6 +421,8 @@ describe('PartialFunction', () => {
expect(isApplyCalled1, 'isApplyCalled1 should be false').to.be.false;
expect(isDefinedAtCalled2, 'isDefinedAtCalled2 should be false').to.be.false;
expect(isApplyCalled2, 'isApplyCalled2 should be false').to.be.false;
expect(pf.isDefinedAt(42), `should be defined for 42`).to.be.true;
expect(pf.isDefinedAt('foo'), `should not be defined for 'foo'`).to.be.false;
}
});

Expand All @@ -418,6 +445,7 @@ describe('PartialFunction', () => {
const pf2 = fromStub(stub2);

const pf = pf1.andThen(pf2);
expect(pf.isDefinedAt(42), `should not be defined for 42`).to.be.false;

try {
pf.apply(42);
Expand Down Expand Up @@ -450,6 +478,7 @@ describe('PartialFunction', () => {

expect(isDefinedAtCalled, 'isDefinedAtCalled should be true').to.be.true;
expect(isApplyCalled, 'isApplyCalled should be true').to.be.true;
expect(pf.isDefinedAt(42), `should be defined for 42`).to.be.true;
expect(result).to.equal('bar');
});

Expand All @@ -467,6 +496,7 @@ describe('PartialFunction', () => {

expect(isDefinedAtCalled, 'isDefinedAtCalled should be true').to.be.true;
expect(isApplyCalled, 'isApplyCalled should be true').to.be.true;
expect(pf.isDefinedAt(42), `should be defined for 42`).to.be.true;
expect(result).to.equal(null);
});
});
Expand Down
148 changes: 148 additions & 0 deletions test/lib/util.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
require('mocha');
const {expect} = require('chai');
const {_} = require('../../dist/index');
const {isMatch} = require('../../dist/lib/utils');

class Foo { constructor(a, b) { this.a = a; this.b = b; } }
class Bar { constructor(a, b) { this.a = a; this.b = b; } }

describe('utils/isMatch', () => {

it('should match scalar numbers', () => {
const result1 = isMatch(5, 5);
const result2 = isMatch(4, 5);
expect(result1).to.be.true;
expect(result2).to.be.false;
});

it('should match scalar numbers even if it\'s zero', () => {
const result = isMatch(0, 0);
expect(result).to.be.true;
});

it('should match scalar value with a function', () => {
const fnMatch = x => x % 2 == 0;
const result1 = isMatch(4, fnMatch);
const result2 = isMatch(5, fnMatch);
expect(result1).to.be.true;
expect(result2).to.be.false;
});

it('should match regular expressions', () => {
const result1 = isMatch('foo', /foo/);
const result2 = isMatch('bar', /foo/);
expect(result1).to.be.true;
expect(result2).to.be.false;
});

try {
eval('Symbol');

it('should match symbols', () => {
const s1 = Symbol('foo');
const s2 = Symbol('foo');

const result1 = isMatch(s1, s1);
const result2 = isMatch(s1, s2);

expect(result1).to.be.true;
expect(result2).to.be.false;
});
} catch (err) {
console.warn('Test runtime environment does not support Symbols, some tests will not be run');
}

it('should match types against their instances', () => {
const result = isMatch(new Foo(3, 4), Foo);
expect(result).to.be.true;
});

it('should match different instances of the same type if their values match exactly', () => {
const result1 = isMatch(new Foo(3, 4), new Foo(3, 4));
const result2 = isMatch(new Foo(3, 4), new Foo(4, 5));
expect(result1).to.be.true;
expect(result2).to.be.false;
});

it('should not match type instances similar but different types', () => {
const result = isMatch(new Foo(3, 4), Bar);
expect(result).to.be.false;
});

it('should match type instances with object literals with exact matching values', () => {
const result = isMatch(new Foo(3, 4), {a: 3, b: 4});
expect(result).to.be.true;
});

it('should match type instances with object literals with partially matching values', () => {
const result = isMatch(new Foo(3, 4), {a: 3});
expect(result).to.be.true;
});

it('should not match type instances with object literals if the latter has extra fields', () => {
const result = isMatch(new Foo(3, 4), {a: 3, b: 4, c: 5});
expect(result).to.be.false;
});

it('should match object literals with another with exact matching values', () => {
const result = isMatch({a: 3, b: 4}, {a: 3, b: 4});
expect(result).to.be.true;
});

it('should match object literals with another with partially matching values', () => {
const result = isMatch({a: 3, b: 4}, {a: 3});
expect(result).to.be.true;
});

it('should not match object literal with another if the latter has extra fields', () => {
const result = isMatch(new Foo(3, 4), {a: 3, b: 4, c: 5});
expect(result).to.be.false;
});

it('should recognize functional matchers in object literals', () => {
const isOdd = x => x % 2 == 1;
const isEven = x => !isOdd(x);
const result1 = isMatch(new Foo(3, 4), {a: isOdd, b: isEven});
const result2 = isMatch(new Foo(3, 4), {a: isEven});
expect(result1).to.be.true;
expect(result2).to.be.false;
});

it('should match arrays of exact length, values and order', () => {
const result1 = isMatch(['foo', 'bar'], ['foo', 'bar']);
const result2 = isMatch(['foo', 'bar'], ['foo']);
const result3 = isMatch(['foo', 'bar'], ['foo', 'baz']);
const result4 = isMatch(['foo', 'bar'], ['bar', 'foo']);
const result5 = isMatch(['foo', 'bar'], 'foo');
const result6 = isMatch('foo', ['foo']);
expect(result1).to.be.true;
expect(result2).to.be.false;
expect(result3).to.be.false;
expect(result4).to.be.false;
expect(result5).to.be.false;
expect(result6).to.be.false;
});

it('should match built-in types with values of their type', () => {
expect(isMatch(1, Number), '1 is a number').to.be.true;
expect(isMatch(1, String), '1 not a string').to.be.false;

expect(isMatch('x', String), `'x' is a string`).to.be.true;
expect(isMatch('x', Number), `'x' is not a number`).to.be.false;

expect(isMatch({a: 4}, Object), `{a: 4} is an object`).to.be.true;
expect(isMatch(new Foo(4, 5), Object), `new Foo(4, 5) is an object`).to.be.true;

expect(isMatch(/foo/, RegExp, '/foo/ is a RegExp')).to.be.true;
});

it('should always match both as value and matcher', () => {
const values = [
_, 1, 'foo', /foo/, () => 42, {a: 4}, new Foo(3, 4), null, undefined
];
values.forEach(value => {
expect(isMatch(_, value)).to.be.true;
expect(isMatch(value, _)).to.be.true;
});
});
});
Loading

0 comments on commit 42d8b93

Please sign in to comment.