Skip to content

Commit 3582ed2

Browse files
Dylan Stanfielddoug-martin
authored andcommitted
feat(core): refactor null compares and improve tests
1 parent 1ee8dbf commit 3582ed2

File tree

2 files changed

+174
-25
lines changed

2 files changed

+174
-25
lines changed

packages/core/__tests__/helpers.spec.ts

Lines changed: 164 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -335,36 +335,177 @@ describe('applyFilter', () => {
335335
expect(applyFilter({ first: 'fo', last: 'ba' }, filter)).toBe(false);
336336
});
337337

338-
it('should handle nested objects', () => {
338+
describe('nested objects', () => {
339339
type ParentDTO = TestDTO & { child: TestDTO };
340-
const parentFilter: Filter<ParentDTO> = {
341-
child: { or: [{ first: { eq: 'foo' } }, { last: { like: '%bar' } }] },
342-
};
343340
const withChild = (child: TestDTO): ParentDTO => ({
344-
first: 'baz',
345-
last: 'qux',
341+
first: 'bar',
346342
child,
347343
});
348-
expect(applyFilter(withChild({ first: 'foo', last: 'bar' }), parentFilter)).toBe(true);
349-
expect(applyFilter(withChild({ first: 'foo', last: 'foobar' }), parentFilter)).toBe(true);
350-
expect(applyFilter(withChild({ first: 'oo', last: 'foobar' }), parentFilter)).toBe(true);
351-
expect(applyFilter(withChild({ first: 'foo', last: 'baz' }), parentFilter)).toBe(true);
352-
expect(applyFilter(withChild({ first: 'oo', last: 'baz' }), parentFilter)).toBe(false);
353-
354344
type GrandParentDTO = TestDTO & { child: ParentDTO };
355-
const grandParentFilter: Filter<GrandParentDTO> = {
356-
child: { child: { or: [{ first: { eq: 'foo' } }, { last: { like: '%bar' } }] } },
357-
};
358345
const withGrandChild = (child: TestDTO): GrandParentDTO => ({
359-
first: 'baz',
360-
last: 'qux',
361-
child: { first: 'baz', last: 'qux', child },
346+
first: 'bar',
347+
child: { first: 'baz', child },
348+
});
349+
350+
it('should handle like comparisons', () => {
351+
const parentFilter: Filter<ParentDTO> = { child: { first: { like: '%foo' } } };
352+
const grandParentFilter: Filter<GrandParentDTO> = { child: { child: { first: { like: '%foo' } } } };
353+
expect(applyFilter(withChild({ first: 'afoo' }), parentFilter)).toBe(true);
354+
expect(applyFilter(withChild({ first: 'bar' }), parentFilter)).toBe(false);
355+
expect(applyFilter(withGrandChild({ first: 'afoo' }), grandParentFilter)).toBe(true);
356+
expect(applyFilter(withGrandChild({ first: 'bar' }), grandParentFilter)).toBe(false);
357+
});
358+
359+
it('should handle notLike comparisons', () => {
360+
const parentFilter: Filter<ParentDTO> = { child: { first: { notLike: '%foo' } } };
361+
const grandParentFilter: Filter<GrandParentDTO> = { child: { child: { first: { notLike: '%foo' } } } };
362+
expect(applyFilter(withChild({ first: 'bar' }), parentFilter)).toBe(true);
363+
expect(applyFilter(withChild({ first: 'afoo' }), parentFilter)).toBe(false);
364+
expect(applyFilter(withGrandChild({ first: 'bar' }), grandParentFilter)).toBe(true);
365+
expect(applyFilter(withGrandChild({ first: 'afoo' }), grandParentFilter)).toBe(false);
366+
});
367+
368+
it('should handle iLike comparisons', () => {
369+
const parentFilter: Filter<ParentDTO> = { child: { first: { iLike: '%foo' } } };
370+
const grandParentFilter: Filter<GrandParentDTO> = { child: { child: { first: { iLike: '%foo' } } } };
371+
expect(applyFilter(withChild({ first: 'AFOO' }), parentFilter)).toBe(true);
372+
expect(applyFilter(withChild({ first: 'bar' }), parentFilter)).toBe(false);
373+
expect(applyFilter(withGrandChild({ first: 'AFOO' }), grandParentFilter)).toBe(true);
374+
expect(applyFilter(withGrandChild({ first: 'bar' }), grandParentFilter)).toBe(false);
375+
});
376+
377+
it('should handle notILike comparisons', () => {
378+
const parentFilter: Filter<ParentDTO> = { child: { first: { notILike: '%foo' } } };
379+
const grandParentFilter: Filter<GrandParentDTO> = { child: { child: { first: { notILike: '%foo' } } } };
380+
expect(applyFilter(withChild({ first: 'bar' }), parentFilter)).toBe(true);
381+
expect(applyFilter(withChild({ first: 'AFOO' }), parentFilter)).toBe(false);
382+
expect(applyFilter(withGrandChild({ first: 'bar' }), grandParentFilter)).toBe(true);
383+
expect(applyFilter(withGrandChild({ first: 'AFOO' }), grandParentFilter)).toBe(false);
384+
});
385+
386+
it('should handle in comparisons', () => {
387+
const parentFilter: Filter<ParentDTO> = { child: { first: { in: ['foo'] } } };
388+
const grandParentFilter: Filter<GrandParentDTO> = { child: { child: { first: { in: ['foo'] } } } };
389+
expect(applyFilter(withChild({ first: 'foo' }), parentFilter)).toBe(true);
390+
expect(applyFilter(withChild({ first: 'bar' }), parentFilter)).toBe(false);
391+
expect(applyFilter(withGrandChild({ first: 'foo' }), grandParentFilter)).toBe(true);
392+
expect(applyFilter(withGrandChild({ first: 'bar' }), grandParentFilter)).toBe(false);
393+
});
394+
395+
it('should handle notIn comparisons', () => {
396+
const parentFilter: Filter<ParentDTO> = { child: { first: { notIn: ['foo'] } } };
397+
const grandParentFilter: Filter<GrandParentDTO> = { child: { child: { first: { notIn: ['foo'] } } } };
398+
expect(applyFilter(withChild({ first: 'bar' }), parentFilter)).toBe(true);
399+
expect(applyFilter(withChild({ first: 'foo' }), parentFilter)).toBe(false);
400+
expect(applyFilter(withGrandChild({ first: 'bar' }), grandParentFilter)).toBe(true);
401+
expect(applyFilter(withGrandChild({ first: 'foo' }), grandParentFilter)).toBe(false);
402+
});
403+
404+
it('should handle between comparisons', () => {
405+
const parentFilter: Filter<ParentDTO> = { child: { first: { between: { lower: 'a', upper: 'c' } } } };
406+
const grandParentFilter: Filter<GrandParentDTO> = {
407+
child: { child: { first: { between: { lower: 'a', upper: 'c' } } } },
408+
};
409+
expect(applyFilter(withChild({ first: 'b' }), parentFilter)).toBe(true);
410+
expect(applyFilter(withChild({ first: 'd' }), parentFilter)).toBe(false);
411+
expect(applyFilter(withGrandChild({ first: 'b' }), grandParentFilter)).toBe(true);
412+
expect(applyFilter(withGrandChild({ first: 'd' }), grandParentFilter)).toBe(false);
413+
});
414+
415+
it('should handle notBetween comparisons', () => {
416+
const parentFilter: Filter<ParentDTO> = { child: { first: { notBetween: { lower: 'a', upper: 'c' } } } };
417+
const grandParentFilter: Filter<GrandParentDTO> = {
418+
child: { child: { first: { notBetween: { lower: 'a', upper: 'c' } } } },
419+
};
420+
expect(applyFilter(withChild({ first: 'd' }), parentFilter)).toBe(true);
421+
expect(applyFilter(withChild({ first: 'b' }), parentFilter)).toBe(false);
422+
expect(applyFilter(withGrandChild({ first: 'd' }), grandParentFilter)).toBe(true);
423+
expect(applyFilter(withGrandChild({ first: 'b' }), grandParentFilter)).toBe(false);
424+
});
425+
426+
it('should handle gt comparisons', () => {
427+
const parentFilter: Filter<ParentDTO> = { child: { first: { gt: 'c' } } };
428+
const grandParentFilter: Filter<GrandParentDTO> = { child: { child: { first: { gt: 'c' } } } };
429+
expect(applyFilter(withChild({ first: 'd' }), parentFilter)).toBe(true);
430+
expect(applyFilter(withChild({ first: 'b' }), parentFilter)).toBe(false);
431+
expect(applyFilter(withChild({ first: 'c' }), parentFilter)).toBe(false);
432+
expect(applyFilter(withGrandChild({ first: 'd' }), grandParentFilter)).toBe(true);
433+
expect(applyFilter(withGrandChild({ first: 'b' }), grandParentFilter)).toBe(false);
434+
expect(applyFilter(withGrandChild({ first: 'c' }), grandParentFilter)).toBe(false);
435+
});
436+
437+
it('should handle gte comparisons', () => {
438+
const parentFilter: Filter<ParentDTO> = { child: { first: { gte: 'c' } } };
439+
const grandParentFilter: Filter<GrandParentDTO> = { child: { child: { first: { gte: 'c' } } } };
440+
expect(applyFilter(withChild({ first: 'c' }), parentFilter)).toBe(true);
441+
expect(applyFilter(withChild({ first: 'd' }), parentFilter)).toBe(true);
442+
expect(applyFilter(withChild({ first: 'b' }), parentFilter)).toBe(false);
443+
expect(applyFilter(withGrandChild({ first: 'c' }), grandParentFilter)).toBe(true);
444+
expect(applyFilter(withGrandChild({ first: 'd' }), grandParentFilter)).toBe(true);
445+
expect(applyFilter(withGrandChild({ first: 'b' }), grandParentFilter)).toBe(false);
446+
});
447+
448+
it('should handle lt comparisons', () => {
449+
const parentFilter: Filter<ParentDTO> = { child: { first: { lt: 'c' } } };
450+
const grandParentFilter: Filter<GrandParentDTO> = { child: { child: { first: { lt: 'c' } } } };
451+
expect(applyFilter(withChild({ first: 'b' }), parentFilter)).toBe(true);
452+
expect(applyFilter(withChild({ first: 'd' }), parentFilter)).toBe(false);
453+
expect(applyFilter(withChild({ first: 'c' }), parentFilter)).toBe(false);
454+
expect(applyFilter(withGrandChild({ first: 'b' }), grandParentFilter)).toBe(true);
455+
expect(applyFilter(withGrandChild({ first: 'd' }), grandParentFilter)).toBe(false);
456+
expect(applyFilter(withGrandChild({ first: 'c' }), grandParentFilter)).toBe(false);
457+
});
458+
459+
it('should handle lte comparisons', () => {
460+
const parentFilter: Filter<ParentDTO> = { child: { first: { lte: 'c' } } };
461+
const grandParentFilter: Filter<GrandParentDTO> = { child: { child: { first: { lte: 'c' } } } };
462+
expect(applyFilter(withChild({ first: 'c' }), parentFilter)).toBe(true);
463+
expect(applyFilter(withChild({ first: 'b' }), parentFilter)).toBe(true);
464+
expect(applyFilter(withChild({ first: 'd' }), parentFilter)).toBe(false);
465+
expect(applyFilter(withGrandChild({ first: 'c' }), grandParentFilter)).toBe(true);
466+
expect(applyFilter(withGrandChild({ first: 'b' }), grandParentFilter)).toBe(true);
467+
expect(applyFilter(withGrandChild({ first: 'd' }), grandParentFilter)).toBe(false);
468+
});
469+
470+
it('should handle eq comparisons', () => {
471+
const parentFilter: Filter<ParentDTO> = { child: { first: { eq: 'foo' } } };
472+
const grandParentFilter: Filter<GrandParentDTO> = { child: { child: { first: { eq: 'foo' } } } };
473+
expect(applyFilter(withChild({ first: 'foo' }), parentFilter)).toBe(true);
474+
expect(applyFilter(withChild({ first: 'bar' }), parentFilter)).toBe(false);
475+
expect(applyFilter(withGrandChild({ first: 'foo' }), grandParentFilter)).toBe(true);
476+
expect(applyFilter(withGrandChild({ first: 'bar' }), grandParentFilter)).toBe(false);
477+
});
478+
479+
it('should handle neq comparisons', () => {
480+
const parentFilter: Filter<ParentDTO> = { child: { first: { neq: 'foo' } } };
481+
const grandParentFilter: Filter<GrandParentDTO> = { child: { child: { first: { neq: 'foo' } } } };
482+
expect(applyFilter(withChild({ first: 'bar' }), parentFilter)).toBe(true);
483+
expect(applyFilter(withChild({ first: 'foo' }), parentFilter)).toBe(false);
484+
expect(applyFilter(withGrandChild({ first: 'bar' }), grandParentFilter)).toBe(true);
485+
expect(applyFilter(withGrandChild({ first: 'foo' }), grandParentFilter)).toBe(false);
486+
});
487+
488+
it('should handle is comparisons', () => {
489+
const parentFilter: Filter<ParentDTO> = { child: { first: { is: null } } };
490+
const grandParentFilter: Filter<GrandParentDTO> = { child: { child: { first: { is: null } } } };
491+
expect(applyFilter(withChild({ first: null }), parentFilter)).toBe(true);
492+
expect(applyFilter(withChild({}), parentFilter)).toBe(true); // undefined
493+
expect(applyFilter(withChild({ first: 'foo' }), parentFilter)).toBe(false);
494+
expect(applyFilter(withGrandChild({ first: null }), grandParentFilter)).toBe(true);
495+
expect(applyFilter(withGrandChild({}), grandParentFilter)).toBe(true); // undefined
496+
expect(applyFilter(withGrandChild({ first: 'foo' }), grandParentFilter)).toBe(false);
497+
});
498+
499+
it('should handle isNot comparisons', () => {
500+
const parentFilter: Filter<ParentDTO> = { child: { first: { isNot: null } } };
501+
const grandParentFilter: Filter<GrandParentDTO> = { child: { child: { first: { isNot: null } } } };
502+
expect(applyFilter(withChild({ first: 'foo' }), parentFilter)).toBe(true);
503+
expect(applyFilter(withChild({ first: null }), parentFilter)).toBe(false);
504+
expect(applyFilter(withChild({}), parentFilter)).toBe(false); // undefined
505+
expect(applyFilter(withGrandChild({ first: 'foo' }), grandParentFilter)).toBe(true);
506+
expect(applyFilter(withGrandChild({ first: null }), grandParentFilter)).toBe(false);
507+
expect(applyFilter(withGrandChild({}), grandParentFilter)).toBe(false); // undefined
362508
});
363-
expect(applyFilter(withGrandChild({ first: 'foo', last: 'bar' }), grandParentFilter)).toBe(true);
364-
expect(applyFilter(withGrandChild({ first: 'foo', last: 'foobar' }), grandParentFilter)).toBe(true);
365-
expect(applyFilter(withGrandChild({ first: 'oo', last: 'foobar' }), grandParentFilter)).toBe(true);
366-
expect(applyFilter(withGrandChild({ first: 'foo', last: 'baz' }), grandParentFilter)).toBe(true);
367-
expect(applyFilter(withGrandChild({ first: 'oo', last: 'baz' }), grandParentFilter)).toBe(false);
368509
});
369510

370511
describe('nested nulls', () => {

packages/core/src/helpers/comparison.builder.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,18 @@ export class ComparisonBuilder {
7979
field: F,
8080
val: DTO[F],
8181
): FilterFn<DTO> {
82-
if (cmp === 'neq' || cmp === 'isNot') {
82+
if (cmp === 'neq') {
8383
return (dto?: DTO): boolean => (dto ? dto[field] : null) !== val;
8484
}
85-
return (dto?: DTO): boolean => (dto ? dto[field] : null) === val;
85+
if (cmp === 'isNot') {
86+
// eslint-disable-next-line eqeqeq
87+
return (dto?: DTO): boolean => (dto ? dto[field] : null) != val;
88+
}
89+
if (cmp === 'eq') {
90+
return (dto?: DTO): boolean => (dto ? dto[field] : null) === val;
91+
}
92+
// eslint-disable-next-line eqeqeq
93+
return (dto?: DTO): boolean => (dto ? dto[field] : null) == val;
8694
}
8795

8896
private static rangeComparison<DTO, F extends keyof DTO>(

0 commit comments

Comments
 (0)