@taraai/read-write
Version:
Synchronous NoSQL/Firestore for React
413 lines (354 loc) • 14.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = setCache;
exports.shouldPass = shouldPass;
var _shouldPassFail = require("./__mock__/shouldPassFail");
var _app = _interopRequireDefault(require("firebase/compat/app"));
require("firebase/compat/firestore");
require("firebase/auth");
var _createFirestoreInstance = _interopRequireWildcard(require("../createFirestoreInstance"));
var _reducer = _interopRequireDefault(require("../reducer"));
var _mutate = _interopRequireDefault(require("../utils/mutate"));
var _reduxThunk = _interopRequireDefault(require("redux-thunk"));
var _toolkit = require("@reduxjs/toolkit");
var _isEmpty = _interopRequireDefault(require("lodash/isEmpty"));
var _isFunction = _interopRequireDefault(require("lodash/isFunction"));
var _merge = _interopRequireDefault(require("lodash/merge"));
var _pick = _interopRequireDefault(require("lodash/pick"));
var _kebabCase = _interopRequireDefault(require("lodash/kebabCase"));
var _startCase = _interopRequireDefault(require("lodash/startCase"));
var _reactRedux = require("react-redux");
var _react = _interopRequireDefault(require("react"));
var _react2 = require("@testing-library/react");
var _query = require("../utils/query");
var _constants = require("../constants");
var _fs = require("fs");
var _perf_hooks = require("perf_hooks");
var _debug = _interopRequireDefault(require("debug"));
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const {
useFirestore
} = require('../redux-firebase/useFirebase');
const {
mutationWriteOutput
} = require('../reducers/utils/mutate');
const {
wrapInDispatch
} = require('../utils/actions');
const info = (0, _debug.default)('readwrite:*');
const verbose = (0, _debug.default)('readwrite:debug');
const removeColors = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
const noop = () => null;
function setupFirestore(databaseURL, enhancers, sideEffects) {
let preload = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : [];
const store = (0, _toolkit.configureStore)({
reducer: {
firestore: _reducer.default
},
middleware: [_reduxThunk.default.withExtraArgument({
getFirestore: _createFirestoreInstance.getFirestore,
...enhancers
})],
preloadedState: {
firestore: {
cache: {
database: preload.reduce((normalized, data) => (0, _merge.default)(normalized, {
[data.path]: {
[data.id]: data
}
}), {}),
databaseOverrides: {}
}
}
}
});
const wasStarted = _app.default.apps.length > 0;
const app = function () {
return wasStarted ? _app.default.apps[0] : _app.default.initializeApp({
projectId: 'demo-read-write',
...(databaseURL ? {
authDomain: 'localhost:9099',
databaseURL
} : {})
});
}.bind(this)();
const extendedFirestoreInstance = (0, _createFirestoreInstance.default)(app, {
userProfile: 'users',
useFirestoreForProfile: true
}, store.dispatch);
extendedFirestoreInstance.setListeners = queryOpts => {
const unsubscribe = () => null;
queryOpts.forEach(query => {
const meta = (0, _query.getQueryConfig)(query);
store.dispatch({
type: _constants.actionTypes.SET_LISTENER,
meta,
payload: {
name: (0, _query.getQueryName)(meta)
}
});
});
return unsubscribe;
};
useFirestore.mockReturnValue(extendedFirestoreInstance);
if (!wasStarted) _app.default.firestore().useEmulator('localhost', 8080);
wrapInDispatch.mockImplementation((dispatcher, action) => {
return sideEffects(dispatcher, store.getState, action);
});
return [extendedFirestoreInstance, store, app];
}
async function loadCollection(firestore, messages) {
let fragment = {};
await Promise.all(messages.map(_ref => {
let {
path
} = _ref;
return firestore.collection(path).get();
})).then(collection => {
const frag = collection.map(snap => {
return snap.docs.reduce((db, doc) => ({ ...db,
[doc.ref.parent.path]: {
[doc.id]: {
id: doc.id,
path: doc.ref.parent.path,
...doc.data()
}
}
}), {});
});
fragment = { ...fragment,
...frag[0]
};
});
return fragment;
}
async function cleanFirestore(firestore, messages) {
const batch = firestore.batch();
messages.filter(Boolean).forEach(_ref2 => {
let {
path,
id
} = _ref2;
batch.delete(firestore.doc(`${path}/${id}`));
});
return batch.commit();
}
function shouldPass(actionCreatorFn) {
let useEmulator = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
const isIntegration = undefined || (typeof useEmulator === 'boolean' ? useEmulator : arguments[2] || false);
const type = isIntegration ? '[integration]' : '[unit]';
const testSuiteName = typeof actionCreatorFn === 'string' ? `${type}: ${actionCreatorFn}` : `${type}: ${actionCreatorFn.typePrefix || ''} %# $testname should pass.`;
const actionCreator = (0, _isFunction.default)(useEmulator) ? useEmulator : actionCreatorFn;
return [testSuiteName, async _ref3 => {
let {
payload,
writes: writesExpected,
results: resultsExpected,
returned: returnExpected,
component,
globals,
setup,
testname
} = _ref3;
const profiles = [{
name: 'start',
time: _perf_hooks.performance.now(),
delta: 0
}];
if (setup && !(Array.isArray(setup) && setup.every(_ref4 => {
let {
path,
id
} = _ref4;
return !(0, _isEmpty.default)(path) && !(0, _isEmpty.default)(id);
}))) {
throw new Error(`'setup' must be an { path:string; id: string; ...any}[] but received ${JSON.stringify(setup)}.`);
}
const databaseURL = typeof isIntegration === 'string' ? isIntegration : isIntegration && 'localhost:8080' || null;
const log = [];
let cache = {};
let customDispatcher = (dispatcher, getState, action) => {
const mutationPromise = (0, _shouldPassFail.dispatchActual)(dispatcher, action, {
extras: globals
});
cache = getState().firestore.cache;
log.push({
event: 'cache',
data: getState().firestore.cache
});
return mutationPromise;
};
const [firestore, store, firebaseApp] = setupFirestore(databaseURL, globals, customDispatcher, setup);
profiles.push({
name: 'firestore-init',
time: _perf_hooks.performance.now(),
delta: _perf_hooks.performance.now() - profiles[profiles.length - 1].time
});
if (setup) {
await (0, _mutate.default)({
firestore: () => firestore
}, setup);
}
profiles.push({
name: 'firestore-preload',
time: _perf_hooks.performance.now(),
delta: _perf_hooks.performance.now() - profiles[profiles.length - 1].time
});
let element;
let elementName;
let preComponent;
if (component) {}
let writeReceived = null;
mutationWriteOutput.mockImplementation((writes, db) => {
writeReceived = Array.isArray(writesExpected) ? writes : writes[0];
verbose.enabled && verbose(`test-name: "${testname}"\nwrite: ${JSON.stringify(writeReceived, null, 2)}`);
return (0, _shouldPassFail.mutationWriteOutputActual)(writes, db);
});
verbose.enabled && verbose(`test-name: "${testname}"\npayload: ${JSON.stringify(payload, null, 2)}`);
const dispatched = store.dispatch(actionCreator(payload)).then(_toolkit.unwrapResult);
const returnRevived = await expect(dispatched).resolves.not.toThrow();
profiles.push({
name: 'action-dispatched',
time: _perf_hooks.performance.now(),
delta: _perf_hooks.performance.now() - profiles[profiles.length - 1].time
});
const postComponent = element && element.container && (0, _react2.prettyDOM)(element.container).replace(removeColors, '');
(0, _fs.writeFile)(`stories/${(0, _startCase.default)(elementName)}_${(0, _kebabCase.default)(testname)}.stories.tsx`, `import React from 'react';\nexport default {\n\t` + `title: '${(0, _startCase.default)(elementName)}',\n};\n\n` + `export const Default = () => (${preComponent});\n\n` + `export const After = () => (${postComponent});`, (done, err) => null);
if (element && info.enabled) {
info(`\nstories/${snakeCase(testname)}_mutation.stories.tsx`, `${prettyFormat.format(element.toJSON(), {
plugins: [prettyFormat.plugins.ReactTestComponent],
printFunctionName: false,
highlight: false
})}`, (done, err) => null);
}
if (writesExpected !== undefined) {
expect(writeReceived).toStrictEqual(writesExpected);
}
if (resultsExpected !== undefined) {
const {
firestore: diskExpected = resultsExpected,
cache: memoryExpected = resultsExpected
} = resultsExpected;
verbose.enabled && verbose(`test-name: "${testname}"\nstore: ${JSON.stringify(memoryExpected, null, 2)}`);
Object.keys(memoryExpected).forEach(path => Object.keys(memoryExpected[path]).forEach(id => {
const documentExpected = memoryExpected[path][id];
const keys = Object.keys(documentExpected);
const optimistic = { ...(cache.database && cache.database[path] && cache.database[path][id] || {}),
...(cache.databaseOverrides && cache.databaseOverrides[path] && cache.databaseOverrides[path][id] || {})
};
const documentCached = (0, _pick.default)(optimistic, keys);
expect(documentCached).toStrictEqual(documentExpected);
}));
profiles.push({
name: 'cache-validated',
time: _perf_hooks.performance.now(),
delta: _perf_hooks.performance.now() - profiles[profiles.length - 1].time
});
if (databaseURL) {
const queries = [];
Object.keys(diskExpected).forEach(path => {
const collection = diskExpected[path];
Object.keys(collection).forEach(id => {
queries.push({
path,
id
});
});
});
const database = await loadCollection(firestore, queries);
Object.keys(diskExpected).forEach(path => Object.keys(diskExpected[path]).forEach(id => {
const keys = Object.keys(diskExpected[path][id]);
const documentSavedToFirestore = (0, _pick.default)(database[path][id] || {}, keys);
const documentExpected = (0, _pick.default)(diskExpected[path][id] || {}, keys);
expect(documentExpected).toStrictEqual(documentSavedToFirestore);
}));
}
profiles.push({
name: 'firestore-validated',
time: _perf_hooks.performance.now(),
delta: _perf_hooks.performance.now() - profiles[profiles.length - 1].time
});
}
if (returnExpected !== undefined) {
expect(returnExpected).toStrictEqual(returnRevived);
}
await cleanFirestore(firestore, [...(setup || []), ...(Array.isArray(writeReceived) ? writeReceived : [writeReceived])]);
profiles.push({
name: 'firestore-cleaned',
time: _perf_hooks.performance.now(),
delta: _perf_hooks.performance.now() - profiles[profiles.length - 1].time
});
await firebaseApp.delete();
for (var i in firestore) {
firestore[i] = null;
}
if (info.enabled) {
info(`\ntest-name: "${testname}"\n`, profiles.slice(1).map(_ref5 => {
let {
name,
delta
} = _ref5;
return `${name}: ${delta.toFixed(2)}ms `;
}).join('\n'), '\n');
}
}];
}
function setCache(_ref6) {
let {
firebaseAuth = {
isEmpty: true,
isLoaded: false
},
firebaseProfile = {
isEmpty: true,
isLoaded: false
},
...documents
} = _ref6;
let middlewares = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
const keys = Object.keys(aliases);
documents.map();
const normalizedDocuments = keys.reduce((obj, key) => {
const list = aliases[key];
const {
path
} = list[0];
list.forEach(item => {
obj[list[0].path] = {
[item.id]: item
};
});
return obj;
}, {});
const initialState = {
firebase: {
auth: firebaseAuth,
profile: firebaseProfile
},
firestore: {
cache: {
database: normalizedDocuments,
databaseOverrides: {},
...keys.reduce((obj, alias) => ({ ...obj,
[alias]: {
ordered: aliases[alias].map(_ref7 => {
let {
path,
id
} = _ref7;
return [path, id];
}),
path: aliases[alias] && aliases[alias][0] && aliases[alias][0].path || 'unset',
via: 'memory'
}
}), {})
}
}
};
const store = (0, _toolkit.configureStore)(middlewares);
return store(initialState);
}