nestjs-paginate
Version:
Pagination and filtering helper method for TypeORM repositories or query builders using Nest.js framework.
153 lines • 7.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const filter_1 = require("./filter");
const helper_1 = require("./helper");
function createQueryBuilderMock(columns) {
return {
expressionMap: {
mainAlias: {
metadata: {
columns,
relations: [],
findColumnWithPropertyPath: () => undefined,
findColumnWithPropertyName: (propertyName) => columns.find((column) => column.propertyName === propertyName),
},
},
},
};
}
describe('parseFilter', () => {
it('casts $in values to numbers for numeric columns', () => {
const qb = createQueryBuilderMock([{ propertyName: 'age', type: Number }]);
const result = (0, filter_1.parseFilter)({ path: '', filter: { age: '$in:1, 2,3' } }, {
age: true,
}, qb);
expect(result.age[0].findOperator.value).toEqual([1, 2, 3]);
});
});
describe('isISODate', () => {
it('returns false when a valid ISO date is only part of the input', () => {
expect((0, helper_1.isISODate)('prefix 2024-01-01T12:30:45Z suffix')).toBe(false);
});
});
describe('parseFilterToken', () => {
it('parses a plain value with default AND comparator', () => {
const token = (0, filter_1.parseFilterToken)('Ball');
expect(token).not.toBeNull();
expect(token.comparator).toBe(filter_1.FilterComparator.AND);
expect(token.value).toBe('Ball');
});
it('parses $and comparator explicitly', () => {
const token = (0, filter_1.parseFilterToken)('$and:Ball');
expect(token).not.toBeNull();
expect(token.comparator).toBe(filter_1.FilterComparator.AND);
expect(token.value).toBe('Ball');
});
it('parses $and with an explicit operator', () => {
const token = (0, filter_1.parseFilterToken)('$and:$eq:Ball');
expect(token).not.toBeNull();
expect(token.comparator).toBe(filter_1.FilterComparator.AND);
expect(token.operator).toBe(filter_1.FilterOperator.EQ);
expect(token.value).toBe('Ball');
});
it('parses $and combined with a quantifier', () => {
const token = (0, filter_1.parseFilterToken)('$none:$and:$eq:Ball');
expect(token).not.toBeNull();
expect(token.quantifier).toBe(filter_1.FilterQuantifier.NONE);
expect(token.comparator).toBe(filter_1.FilterComparator.AND);
expect(token.value).toBe('Ball');
});
});
describe('parseFilter $and gating', () => {
const qb = {
expressionMap: {
mainAlias: {
metadata: {
columns: [{ propertyName: 'name', type: String }],
relations: [],
findColumnWithPropertyPath: () => undefined,
findColumnWithPropertyName: (p) => p === 'name' ? { propertyName: 'name', type: String } : undefined,
},
},
},
};
it('allows $and when FilterComparator.AND is in filterableColumns', () => {
const result = (0, filter_1.parseFilter)({ path: '', filter: { name: '$and:Ball' } }, { name: [filter_1.FilterOperator.EQ, filter_1.FilterComparator.AND] }, qb);
expect(result.name).toBeDefined();
expect(result.name.length).toBe(1);
});
it('blocks $and when FilterComparator.AND is not in filterableColumns', () => {
const result = (0, filter_1.parseFilter)({ path: '', filter: { name: '$and:Ball' } }, { name: [filter_1.FilterOperator.EQ] }, qb);
// The $and token should be filtered out
expect(!result.name || result.name.length === 0).toBe(true);
});
it('allows a plain filter (no explicit $and) even without FilterComparator.AND in filterableColumns', () => {
const result = (0, filter_1.parseFilter)({ path: '', filter: { name: 'Ball' } }, { name: [filter_1.FilterOperator.EQ] }, qb);
expect(result.name).toBeDefined();
expect(result.name.length).toBe(1);
});
it('allows $and when filterableColumns is true (all allowed)', () => {
const result = (0, filter_1.parseFilter)({ path: '', filter: { name: '$and:Ball' } }, { name: true }, qb);
expect(result.name).toBeDefined();
expect(result.name.length).toBe(1);
});
});
describe('parameter rename regex (TYPEORM_PARAM_REGEX)', () => {
// Helper that applies the module-level regex (must reset lastIndex before each use).
const rename = (s, suffix) => {
filter_1.TYPEORM_PARAM_REGEX.lastIndex = 0;
return s.replace(filter_1.TYPEORM_PARAM_REGEX, (_, spread, name) => `:${spread !== null && spread !== void 0 ? spread : ''}${name}${suffix}`);
};
it('renames a simple named parameter', () => {
expect(rename(':name_1', '_e0')).toBe(':name_1_e0');
});
it('renames multiple parameters in one string', () => {
expect(rename(':foo = :bar', '_e1')).toBe(':foo_e1 = :bar_e1');
});
it('does not rename PostgreSQL :: cast syntax', () => {
expect(rename('col::text = :value', '_e0')).toBe('col::text = :value_e0');
});
it('does not rename :: in the middle of an expression', () => {
expect(rename(':param::integer > 0', '_e0')).toBe(':param_e0::integer > 0');
});
it('renames embedded-path parameters (containing dots)', () => {
expect(rename(':size.height0', '_e0')).toBe(':size.height0_e0');
});
it('renames :...spread parameters (used by $in)', () => {
expect(rename(':...vals', '_e0')).toBe(':...vals_e0');
});
it('renames :...spread alongside a cast', () => {
expect(rename('col IN (:...vals) AND col2::text = :other', '_e1')).toBe('col IN (:...vals_e1) AND col2::text = :other_e1');
});
it('handles jsonb cast: :param::jsonb is renamed correctly', () => {
expect(rename('col @> :json::jsonb', '_e0')).toBe('col @> :json_e0::jsonb');
});
});
describe('hasExplicitAndComparator', () => {
it('returns true for $and:Ball', () => {
expect((0, filter_1.hasExplicitAndComparator)('$and:Ball')).toBe(true);
});
it('returns true for $and:$eq:Ball', () => {
expect((0, filter_1.hasExplicitAndComparator)('$and:$eq:Ball')).toBe(true);
});
it('returns true for $none:$and:Ball', () => {
expect((0, filter_1.hasExplicitAndComparator)('$none:$and:Ball')).toBe(true);
});
it('returns false for a plain value (no $and prefix)', () => {
expect((0, filter_1.hasExplicitAndComparator)('Ball')).toBe(false);
});
it('returns false for $eq:Ball (default AND comparator, not explicit)', () => {
expect((0, filter_1.hasExplicitAndComparator)('$eq:Ball')).toBe(false);
});
it('returns false for $eq:$and (value is the literal string $and, not the comparator)', () => {
// parseFilterToken sees $eq as operator, value = '$and' — comparator stays default AND
// but $and is NOT in the consumed prefix as a comparator token.
expect((0, filter_1.hasExplicitAndComparator)('$eq:$and')).toBe(false);
});
it('returns true for bare $and (no value after colon — user explicitly wrote $and)', () => {
// parseFilterToken: $and is consumed as comparator, value = undefined.
// hasExplicitAndComparator should still return true (the user wrote $and explicitly).
expect((0, filter_1.hasExplicitAndComparator)('$and')).toBe(true);
});
});
//# sourceMappingURL=filter.spec.js.map