UNPKG

firestore-jest-mock

Version:

Jest helper for mocking Google Cloud Firestore

262 lines 9.71 kB
"use strict"; const buildDocFromHash = require('./buildDocFromHash'); module.exports = function buildQuerySnapShot(requestedRecords, filters, selectFields) { const definiteRecords = requestedRecords.filter(rec => !!rec); const results = _filteredDocuments(definiteRecords, filters); const docs = results.map(doc => buildDocFromHash(doc, 'abc123', selectFields)); return { empty: results.length < 1, size: results.length, docs, forEach(callback) { docs.forEach(callback); }, docChanges() { return []; }, }; }; /** * @typedef DocumentHash * @type {import('./buildDocFromHash').DocumentHash} */ /** * @typedef Comparator * @type {import('./buildQuerySnapShot').Comparator} */ /** * Applies query filters to an array of mock document data. * * @param {Array<DocumentHash>} records The array of records to filter. * @param {Array<{ key: string; comp: Comparator; value: unknown }>=} filters The filters to apply. * If no filters are provided, then the records array is returned as-is. * * @returns {Array<import('./buildDocFromHash').DocumentHash>} The filtered documents. */ function _filteredDocuments(records, filters) { if (!filters || !Array.isArray(filters) || filters.length === 0) { return records; } filters.forEach(({ key, comp, value }) => { // https://firebase.google.com/docs/reference/js/firebase.firestore#wherefilterop // Convert values to string to make Array comparisons work // See https://jsbin.com/bibawaf/edit?js,console switch (comp) { // https://firebase.google.com/docs/firestore/query-data/queries#query_operators case '<': records = _recordsLessThanValue(records, key, value); break; case '<=': records = _recordsLessThanOrEqualToValue(records, key, value); break; case '==': records = _recordsEqualToValue(records, key, value); break; case '!=': records = _recordsNotEqualToValue(records, key, value); break; case '>=': records = _recordsGreaterThanOrEqualToValue(records, key, value); break; case '>': records = _recordsGreaterThanValue(records, key, value); break; case 'array-contains': records = _recordsArrayContainsValue(records, key, value); break; case 'in': records = _recordsWithValueInList(records, key, value); break; case 'not-in': records = _recordsWithValueNotInList(records, key, value); break; case 'array-contains-any': records = _recordsWithOneOfValues(records, key, value); break; } }); return records; } function _recordsWithKey(records, key) { return records.filter(record => record && getValueByPath(record, key) !== undefined); } function _recordsWithNonNullKey(records, key) { return records.filter(record => record && getValueByPath(record, key) !== undefined && getValueByPath(record, key) !== null); } function _shouldCompareNumerically(a, b) { return typeof a === 'number' && typeof b === 'number'; } function _shouldCompareTimestamp(a, b) { //We check whether toMillis method exists to support both Timestamp mock and Firestore Timestamp object //B is expected to be Date, not Timestamp, just like Firestore does return (typeof a === 'object' && a !== null && typeof a.toMillis === 'function' && b instanceof Date); } /** * @param {Array<DocumentHash>} records * @param {string} key * @param {unknown} value * @returns {Array<DocumentHash>} */ function _recordsLessThanValue(records, key, value) { return _recordsWithNonNullKey(records, key).filter(record => { const recordValue = getValueByPath(record, key); if (_shouldCompareNumerically(recordValue, value)) { return recordValue < value; } if (_shouldCompareTimestamp(recordValue, value)) { return recordValue.toMillis() < value; } return String(recordValue) < String(value); }); } /** * @param {Array<DocumentHash>} records * @param {string} key * @param {unknown} value * @returns {Array<DocumentHash>} */ function _recordsLessThanOrEqualToValue(records, key, value) { return _recordsWithNonNullKey(records, key).filter(record => { const recordValue = getValueByPath(record, key); if (_shouldCompareNumerically(recordValue, value)) { return recordValue <= value; } if (_shouldCompareTimestamp(recordValue, value)) { return recordValue.toMillis() <= value; } return String(recordValue) <= String(value); }); } /** * @param {Array<DocumentHash>} records * @param {string} key * @param {unknown} value * @returns {Array<DocumentHash>} */ function _recordsEqualToValue(records, key, value) { return _recordsWithKey(records, key).filter(record => { const recordValue = getValueByPath(record, key); if (_shouldCompareTimestamp(recordValue, value)) { //NOTE: for equality, we must compare numbers! return recordValue.toMillis() === value.getTime(); } return String(recordValue) === String(value); }); } /** * @param {Array<DocumentHash>} records * @param {string} key * @param {unknown} value * @returns {Array<DocumentHash>} */ function _recordsNotEqualToValue(records, key, value) { return _recordsWithKey(records, key).filter(record => { const recordValue = getValueByPath(record, key); if (_shouldCompareTimestamp(recordValue, value)) { //NOTE: for equality, we must compare numbers! return recordValue.toMillis() !== value.getTime(); } return String(recordValue) !== String(value); }); } /** * @param {Array<DocumentHash>} records * @param {string} key * @param {unknown} value * @returns {Array<DocumentHash>} */ function _recordsGreaterThanOrEqualToValue(records, key, value) { return _recordsWithNonNullKey(records, key).filter(record => { const recordValue = getValueByPath(record, key); if (_shouldCompareNumerically(recordValue, value)) { return recordValue >= value; } if (_shouldCompareTimestamp(recordValue, value)) { return recordValue.toMillis() >= value; } return String(recordValue) >= String(value); }); } /** * @param {Array<DocumentHash>} records * @param {string} key * @param {unknown} value * @returns {Array<DocumentHash>} */ function _recordsGreaterThanValue(records, key, value) { return _recordsWithNonNullKey(records, key).filter(record => { const recordValue = getValueByPath(record, key); if (_shouldCompareNumerically(recordValue, value)) { return recordValue > value; } if (_shouldCompareTimestamp(recordValue, value)) { return recordValue.toMillis() > value; } return String(recordValue) > String(value); }); } /** * @see https://firebase.google.com/docs/firestore/query-data/queries#array_membership * * @param {Array<DocumentHash>} records * @param {string} key * @param {unknown} value * @returns {Array<DocumentHash>} */ function _recordsArrayContainsValue(records, key, value) { return records.filter(record => record && getValueByPath(record, key) && Array.isArray(getValueByPath(record, key)) && getValueByPath(record, key).includes(value)); } /** * @see https://firebase.google.com/docs/firestore/query-data/queries#in_not-in_and_array-contains-any * * @param {Array<DocumentHash>} records * @param {string} key * @param {unknown} value * @returns {Array<DocumentHash>} */ function _recordsWithValueInList(records, key, value) { // TODO: Throw an error when a value is passed that contains more than 10 values return records.filter(record => { if (!record || getValueByPath(record, key) === undefined) { return false; } return value && Array.isArray(value) && value.includes(getValueByPath(record, key)); }); } /** * @see https://firebase.google.com/docs/firestore/query-data/queries#not-in * * @param {Array<DocumentHash>} records * @param {string} key * @param {unknown} value * @returns {Array<DocumentHash>} */ function _recordsWithValueNotInList(records, key, value) { // TODO: Throw an error when a value is passed that contains more than 10 values return _recordsWithKey(records, key).filter(record => value && Array.isArray(value) && !value.includes(getValueByPath(record, key))); } /** * @see https://firebase.google.com/docs/firestore/query-data/queries#in_not-in_and_array-contains-any * * @param {Array<DocumentHash>} records * @param {string} key * @param {unknown} value * @returns {Array<DocumentHash>} */ function _recordsWithOneOfValues(records, key, value) { // TODO: Throw an error when a value is passed that contains more than 10 values return records.filter(record => record && getValueByPath(record, key) && Array.isArray(getValueByPath(record, key)) && value && Array.isArray(value) && getValueByPath(record, key).some(v => value.includes(v))); } function getValueByPath(record, path) { const keys = path.split('.'); return keys.reduce((nestedObject = {}, key) => nestedObject[key], record); } //# sourceMappingURL=buildQuerySnapShot.js.map