UNPKG

@gravity-ui/data-source

Version:
659 lines (651 loc) 28.7 kB
"use strict"; var _regeneratorRuntime2 = _interopRequireDefault(require("@babel/runtime/helpers/regeneratorRuntime")); var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); var _react = _interopRequireDefault(require("react")); var _react2 = require("@testing-library/react"); var _ClientDataManager = require("../ClientDataManager"); var _DataSourceProvider = require("../DataSourceProvider"); var _useQueryData = require("../hooks/useQueryData"); var _factory = require("../impl/plain/factory"); var _jsxRuntime = require("react/jsx-runtime"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } describe('Normalization Configuration Integration', function () { var queryClient; var dataManager; beforeEach(function () { dataManager = new _ClientDataManager.ClientDataManager({ normalizerConfig: { devLogging: false } }); queryClient = dataManager.queryClient; }); afterEach(function () { var _dataManager$queryNor; (_dataManager$queryNor = dataManager.queryNormalizer) === null || _dataManager$queryNor === void 0 || _dataManager$queryNor.unsubscribe(); queryClient.clear(); }); describe('ClientDataManager configuration', function () { it('should use custom getNormalizationObjectKey from config', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/(0, _regeneratorRuntime2.default)().mark(function _callee2() { var customGetKey, customDataManager, queryKey, normalized; return (0, _regeneratorRuntime2.default)().wrap(function _callee2$(_context2) { while (1) switch (_context2.prev = _context2.next) { case 0: customGetKey = jest.fn(function (obj) { return "custom:".concat(obj.id); }); customDataManager = new _ClientDataManager.ClientDataManager({ normalizerConfig: { getNormalizationObjectKey: customGetKey, devLogging: false } }); customDataManager.queryNormalizer.subscribe(); queryKey = ['users']; _context2.next = 6; return customDataManager.queryClient.fetchQuery({ queryKey: queryKey, queryFn: function () { var _queryFn = (0, _asyncToGenerator2.default)(/*#__PURE__*/(0, _regeneratorRuntime2.default)().mark(function _callee() { return (0, _regeneratorRuntime2.default)().wrap(function _callee$(_context) { while (1) switch (_context.prev = _context.next) { case 0: return _context.abrupt("return", [{ id: '1', name: 'User 1' }]); case 1: case "end": return _context.stop(); } }, _callee); })); function queryFn() { return _queryFn.apply(this, arguments); } return queryFn; }(), normalize: true }); case 6: normalized = customDataManager.queryNormalizer.getNormalizedData(); expect(normalized.objects['@@custom:1']).toBeDefined(); expect(customGetKey).toHaveBeenCalled(); customDataManager.queryNormalizer.unsubscribe(); customDataManager.queryClient.clear(); case 11: case "end": return _context2.stop(); } }, _callee2); }))); it('should use custom getArrayType from config', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/(0, _regeneratorRuntime2.default)().mark(function _callee4() { var customGetArrayType, customDataManager, queryKey; return (0, _regeneratorRuntime2.default)().wrap(function _callee4$(_context4) { while (1) switch (_context4.prev = _context4.next) { case 0: customGetArrayType = jest.fn(function (_ref3) { var arrayKey = _ref3.arrayKey; return "custom:".concat(arrayKey); }); customDataManager = new _ClientDataManager.ClientDataManager({ normalizerConfig: { getArrayType: customGetArrayType, devLogging: false } }); customDataManager.queryNormalizer.subscribe(); queryKey = ['items']; _context4.next = 6; return customDataManager.queryClient.fetchQuery({ queryKey: queryKey, queryFn: function () { var _queryFn2 = (0, _asyncToGenerator2.default)(/*#__PURE__*/(0, _regeneratorRuntime2.default)().mark(function _callee3() { return (0, _regeneratorRuntime2.default)().wrap(function _callee3$(_context3) { while (1) switch (_context3.prev = _context3.next) { case 0: return _context3.abrupt("return", { items: [{ id: '1', name: 'Item 1' }] }); case 1: case "end": return _context3.stop(); } }, _callee3); })); function queryFn() { return _queryFn2.apply(this, arguments); } return queryFn; }(), normalize: true }); case 6: expect(customGetArrayType).toHaveBeenCalled(); customDataManager.queryNormalizer.unsubscribe(); customDataManager.queryClient.clear(); case 9: case "end": return _context4.stop(); } }, _callee4); }))); }); describe('Query-level normalization control', function () { it('should normalize when query has normalize: true', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/(0, _regeneratorRuntime2.default)().mark(function _callee6() { var queryKey, normalized; return (0, _regeneratorRuntime2.default)().wrap(function _callee6$(_context6) { while (1) switch (_context6.prev = _context6.next) { case 0: dataManager.queryNormalizer.subscribe(); queryKey = ['users-normalized']; _context6.next = 4; return dataManager.queryClient.fetchQuery({ queryKey: queryKey, queryFn: function () { var _queryFn3 = (0, _asyncToGenerator2.default)(/*#__PURE__*/(0, _regeneratorRuntime2.default)().mark(function _callee5() { return (0, _regeneratorRuntime2.default)().wrap(function _callee5$(_context5) { while (1) switch (_context5.prev = _context5.next) { case 0: return _context5.abrupt("return", [{ id: '1', name: 'User 1' }]); case 1: case "end": return _context5.stop(); } }, _callee5); })); function queryFn() { return _queryFn3.apply(this, arguments); } return queryFn; }(), normalize: true }); case 4: normalized = dataManager.queryNormalizer.getNormalizedData(); // Data SHOULD be normalized expect(normalized.objects['@@1']).toBeDefined(); case 6: case "end": return _context6.stop(); } }, _callee6); }))); it('should NOT normalize when query has normalize: false', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/(0, _regeneratorRuntime2.default)().mark(function _callee8() { var queryKey, normalized; return (0, _regeneratorRuntime2.default)().wrap(function _callee8$(_context8) { while (1) switch (_context8.prev = _context8.next) { case 0: dataManager.queryNormalizer.subscribe(); queryKey = ['users-not-normalized']; _context8.next = 4; return dataManager.queryClient.fetchQuery({ queryKey: queryKey, queryFn: function () { var _queryFn4 = (0, _asyncToGenerator2.default)(/*#__PURE__*/(0, _regeneratorRuntime2.default)().mark(function _callee7() { return (0, _regeneratorRuntime2.default)().wrap(function _callee7$(_context7) { while (1) switch (_context7.prev = _context7.next) { case 0: return _context7.abrupt("return", [{ id: '2', name: 'User 2' }]); case 1: case "end": return _context7.stop(); } }, _callee7); })); function queryFn() { return _queryFn4.apply(this, arguments); } return queryFn; }(), normalize: false }); case 4: normalized = dataManager.queryNormalizer.getNormalizedData(); // Data should NOT be normalized expect(normalized.objects['@@2']).toBeUndefined(); case 6: case "end": return _context8.stop(); } }, _callee8); }))); it('should normalize by default when normalize option is not provided', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/(0, _regeneratorRuntime2.default)().mark(function _callee10() { var queryKey, normalized; return (0, _regeneratorRuntime2.default)().wrap(function _callee10$(_context10) { while (1) switch (_context10.prev = _context10.next) { case 0: dataManager.queryNormalizer.subscribe(); queryKey = ['users-default']; _context10.next = 4; return dataManager.queryClient.fetchQuery({ queryKey: queryKey, queryFn: function () { var _queryFn5 = (0, _asyncToGenerator2.default)(/*#__PURE__*/(0, _regeneratorRuntime2.default)().mark(function _callee9() { return (0, _regeneratorRuntime2.default)().wrap(function _callee9$(_context9) { while (1) switch (_context9.prev = _context9.next) { case 0: return _context9.abrupt("return", [{ id: '3', name: 'User 3' }]); case 1: case "end": return _context9.stop(); } }, _callee9); })); function queryFn() { return _queryFn5.apply(this, arguments); } return queryFn; }() }); case 4: normalized = dataManager.queryNormalizer.getNormalizedData(); // Data SHOULD be normalized (default behavior is now true) expect(normalized.objects['@@3']).toBeDefined(); case 6: case "end": return _context10.stop(); } }, _callee10); }))); }); describe('DataSourceProvider integration', function () { it('should auto-subscribe queryNormalizer on mount', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/(0, _regeneratorRuntime2.default)().mark(function _callee12() { var wrapper, dataSource, _renderHook, result, normalized; return (0, _regeneratorRuntime2.default)().wrap(function _callee12$(_context12) { while (1) switch (_context12.prev = _context12.next) { case 0: wrapper = function wrapper(_ref8) { var children = _ref8.children; return /*#__PURE__*/(0, _jsxRuntime.jsx)(_DataSourceProvider.DataSourceProvider, { dataManager: dataManager, children: children }); }; dataSource = (0, _factory.makePlainQueryDataSource)({ name: 'users', fetch: function () { var _fetch = (0, _asyncToGenerator2.default)(/*#__PURE__*/(0, _regeneratorRuntime2.default)().mark(function _callee11() { return (0, _regeneratorRuntime2.default)().wrap(function _callee11$(_context11) { while (1) switch (_context11.prev = _context11.next) { case 0: return _context11.abrupt("return", [{ id: '1', name: 'User 1' }]); case 1: case "end": return _context11.stop(); } }, _callee11); })); function fetch() { return _fetch.apply(this, arguments); } return fetch; }(), options: { normalize: true } }); _renderHook = (0, _react2.renderHook)(function () { return (0, _useQueryData.useQueryData)(dataSource, {}); }, { wrapper: wrapper }), result = _renderHook.result; _context12.next = 5; return (0, _react2.waitFor)(function () { return expect(result.current.status).toBe('success'); }); case 5: normalized = dataManager.queryNormalizer.getNormalizedData(); // Data should be normalized because DataSourceProvider subscribes automatically expect(normalized.objects['@@1']).toBeDefined(); case 7: case "end": return _context12.stop(); } }, _callee12); }))); it('should work with multiple queries', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/(0, _regeneratorRuntime2.default)().mark(function _callee15() { var wrapper, dataSource1, dataSource2, _renderHook2, result1, _renderHook3, result2, normalized; return (0, _regeneratorRuntime2.default)().wrap(function _callee15$(_context15) { while (1) switch (_context15.prev = _context15.next) { case 0: wrapper = function wrapper(_ref10) { var children = _ref10.children; return /*#__PURE__*/(0, _jsxRuntime.jsx)(_DataSourceProvider.DataSourceProvider, { dataManager: dataManager, children: children }); }; dataSource1 = (0, _factory.makePlainQueryDataSource)({ name: 'users', fetch: function () { var _fetch2 = (0, _asyncToGenerator2.default)(/*#__PURE__*/(0, _regeneratorRuntime2.default)().mark(function _callee13() { return (0, _regeneratorRuntime2.default)().wrap(function _callee13$(_context13) { while (1) switch (_context13.prev = _context13.next) { case 0: return _context13.abrupt("return", [{ id: '1', name: 'User 1' }]); case 1: case "end": return _context13.stop(); } }, _callee13); })); function fetch() { return _fetch2.apply(this, arguments); } return fetch; }(), options: { normalize: true } }); dataSource2 = (0, _factory.makePlainQueryDataSource)({ name: 'posts', fetch: function () { var _fetch3 = (0, _asyncToGenerator2.default)(/*#__PURE__*/(0, _regeneratorRuntime2.default)().mark(function _callee14() { return (0, _regeneratorRuntime2.default)().wrap(function _callee14$(_context14) { while (1) switch (_context14.prev = _context14.next) { case 0: return _context14.abrupt("return", [{ id: '2', title: 'Post 1' }]); case 1: case "end": return _context14.stop(); } }, _callee14); })); function fetch() { return _fetch3.apply(this, arguments); } return fetch; }(), options: { normalize: true } }); _renderHook2 = (0, _react2.renderHook)(function () { return (0, _useQueryData.useQueryData)(dataSource1, {}); }, { wrapper: wrapper }), result1 = _renderHook2.result; _renderHook3 = (0, _react2.renderHook)(function () { return (0, _useQueryData.useQueryData)(dataSource2, {}); }, { wrapper: wrapper }), result2 = _renderHook3.result; _context15.next = 7; return (0, _react2.waitFor)(function () { expect(result1.current.status).toBe('success'); expect(result2.current.status).toBe('success'); }); case 7: normalized = dataManager.queryNormalizer.getNormalizedData(); expect(normalized.objects['@@1']).toBeDefined(); expect(normalized.objects['@@2']).toBeDefined(); case 10: case "end": return _context15.stop(); } }, _callee15); }))); }); describe('Optimistic updates configuration', function () { it('should enable optimistic updates when configured', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/(0, _regeneratorRuntime2.default)().mark(function _callee16() { var dmWithOptimistic, queryKey, initialData, data; return (0, _regeneratorRuntime2.default)().wrap(function _callee16$(_context16) { while (1) switch (_context16.prev = _context16.next) { case 0: dmWithOptimistic = new _ClientDataManager.ClientDataManager({ normalizerConfig: { devLogging: false, optimistic: true } }); expect(dmWithOptimistic.queryNormalizer).toBeDefined(); dmWithOptimistic.queryNormalizer.subscribe(); queryKey = ['users']; initialData = [{ id: '1', name: 'Old' }]; dmWithOptimistic.queryClient.setQueryData(queryKey, initialData); dmWithOptimistic.normalizer.setQuery(JSON.stringify(queryKey), initialData); // Manual optimistic update via setNormalizedData dmWithOptimistic.queryNormalizer.setNormalizedData({ id: '1', name: 'New' }); data = dmWithOptimistic.queryClient.getQueryData(queryKey); expect(data[0].name).toBe('New'); dmWithOptimistic.queryNormalizer.unsubscribe(); dmWithOptimistic.queryClient.clear(); case 12: case "end": return _context16.stop(); } }, _callee16); }))); it('should work with optimistic config object', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/(0, _regeneratorRuntime2.default)().mark(function _callee17() { var dmWithOptimisticConfig; return (0, _regeneratorRuntime2.default)().wrap(function _callee17$(_context17) { while (1) switch (_context17.prev = _context17.next) { case 0: dmWithOptimisticConfig = new _ClientDataManager.ClientDataManager({ normalizerConfig: { devLogging: false, optimistic: { autoCalculateRollback: true, devLogging: false } } }); expect(dmWithOptimisticConfig.queryNormalizer).toBeDefined(); expect(dmWithOptimisticConfig.normalizer).toBeDefined(); dmWithOptimisticConfig.queryClient.clear(); case 4: case "end": return _context17.stop(); } }, _callee17); }))); }); describe('Invalidate configuration', function () { it('should support invalidate option in global config', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/(0, _regeneratorRuntime2.default)().mark(function _callee18() { var dmWithInvalidate; return (0, _regeneratorRuntime2.default)().wrap(function _callee18$(_context18) { while (1) switch (_context18.prev = _context18.next) { case 0: dmWithInvalidate = new _ClientDataManager.ClientDataManager({ normalizerConfig: { devLogging: false, invalidate: true } }); expect(dmWithInvalidate.queryNormalizer).toBeDefined(); expect(dmWithInvalidate.normalizer).toBeDefined(); dmWithInvalidate.queryClient.clear(); case 4: case "end": return _context18.stop(); } }, _callee18); }))); it('should support both optimistic and invalidate options', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/(0, _regeneratorRuntime2.default)().mark(function _callee19() { var dmWithBoth; return (0, _regeneratorRuntime2.default)().wrap(function _callee19$(_context19) { while (1) switch (_context19.prev = _context19.next) { case 0: dmWithBoth = new _ClientDataManager.ClientDataManager({ normalizerConfig: { devLogging: false, optimistic: true, invalidate: true } }); expect(dmWithBoth.queryNormalizer).toBeDefined(); expect(dmWithBoth.normalizer).toBeDefined(); dmWithBoth.queryClient.clear(); case 4: case "end": return _context19.stop(); } }, _callee19); }))); }); describe('ClientDataManager.update()', function () { it('should work with array of objects for optimistic update', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/(0, _regeneratorRuntime2.default)().mark(function _callee20() { var dm, queryKey, initialData, data; return (0, _regeneratorRuntime2.default)().wrap(function _callee20$(_context20) { while (1) switch (_context20.prev = _context20.next) { case 0: dm = new _ClientDataManager.ClientDataManager({ normalizerConfig: { devLogging: false, optimistic: true } }); dm.queryNormalizer.subscribe(); queryKey = ['users']; initialData = [{ id: '1', name: 'User 1' }, { id: '2', name: 'User 2' }]; dm.queryClient.setQueryData(queryKey, initialData); dm.normalizer.setQuery(JSON.stringify(queryKey), initialData); // Update both objects dm.update([{ id: '1', name: 'Updated 1' }, { id: '2', name: 'Updated 2' }]); data = dm.queryClient.getQueryData(queryKey); expect(data[0].name).toBe('Updated 1'); expect(data[1].name).toBe('Updated 2'); dm.queryNormalizer.unsubscribe(); dm.queryClient.clear(); case 12: case "end": return _context20.stop(); } }, _callee20); }))); it('should call invalidateData when invalidate option is enabled', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/(0, _regeneratorRuntime2.default)().mark(function _callee21() { var dm, queryKey, initialData, cache, invalidateSpy; return (0, _regeneratorRuntime2.default)().wrap(function _callee21$(_context21) { while (1) switch (_context21.prev = _context21.next) { case 0: dm = new _ClientDataManager.ClientDataManager({ normalizerConfig: { devLogging: false, invalidate: true } }); dm.queryNormalizer.subscribe(); queryKey = ['users']; initialData = [{ id: '1', name: 'User 1' }]; dm.queryClient.setQueryData(queryKey, initialData); dm.normalizer.setQuery(JSON.stringify(queryKey), initialData); // Set query state to success so it can be invalidated cache = dm.queryClient.getQueryCache().find({ queryKey: queryKey }); cache === null || cache === void 0 || cache.setState({ status: 'success', fetchStatus: 'idle', isInvalidated: false }); invalidateSpy = jest.spyOn(dm, 'invalidateData'); dm.update({ id: '1', name: 'Updated' }); expect(invalidateSpy).toHaveBeenCalled(); invalidateSpy.mockRestore(); dm.queryNormalizer.unsubscribe(); dm.queryClient.clear(); case 14: case "end": return _context21.stop(); } }, _callee21); }))); it('should trigger refetch when mutation has fewer keys and normy returns empty queriesToUpdate', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/(0, _regeneratorRuntime2.default)().mark(function _callee22() { var dm, queryKey, initialData, cache, originalGetQueriesToUpdate, invalidateSpy; return (0, _regeneratorRuntime2.default)().wrap(function _callee22$(_context22) { while (1) switch (_context22.prev = _context22.next) { case 0: // checkMutationObjectsKeys is called only when getQueriesToUpdate returns [] // This happens when normy can't compute a diff (e.g., structure mismatch) dm = new _ClientDataManager.ClientDataManager({ normalizerConfig: { devLogging: false, invalidate: true // Need to enable invalidate for refetch to work } }); dm.queryNormalizer.subscribe(); queryKey = ['users']; // Store data with more keys initialData = [{ id: '1', name: 'User 1', email: 'user@test.com', age: 25 }]; dm.queryClient.setQueryData(queryKey, initialData); dm.normalizer.setQuery(JSON.stringify(queryKey), initialData); // Set query state to success so it can be invalidated cache = dm.queryClient.getQueryCache().find({ queryKey: queryKey }); cache === null || cache === void 0 || cache.setState({ status: 'success', fetchStatus: 'idle', isInvalidated: false }); // Mock getQueriesToUpdate to return empty array (simulating normy can't compute diff) originalGetQueriesToUpdate = dm.normalizer.getQueriesToUpdate; dm.normalizer.getQueriesToUpdate = jest.fn().mockReturnValue([]); invalidateSpy = jest.spyOn(dm.queryClient, 'invalidateQueries'); // Update with fewer keys - should trigger refetch via checkMutationObjectsKeys dm.update({ id: '1', name: 'Updated' }); // Check if invalidation was triggered due to fewer keys expect(invalidateSpy).toHaveBeenCalled(); // Restore mocks dm.normalizer.getQueriesToUpdate = originalGetQueriesToUpdate; invalidateSpy.mockRestore(); dm.queryNormalizer.unsubscribe(); dm.queryClient.clear(); case 17: case "end": return _context22.stop(); } }, _callee22); }))); }); }); // #sourceMappingURL=threeLevelIntegration.test.js.map