github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ui/src/redux/cachedDataReducer.spec.ts (about)

     1  // Copyright 2018 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  import { assert } from "chai";
    12  import _ from "lodash";
    13  import { Action } from "redux";
    14  import moment from "moment";
    15  import { CachedDataReducer, CachedDataReducerState, KeyedCachedDataReducer, KeyedCachedDataReducerState } from "./cachedDataReducer";
    16  
    17  describe("basic cachedDataReducer", function () {
    18    class Request {
    19      constructor(public request: string) { }
    20    }
    21  
    22    class Response {
    23      constructor(public response: string) { }
    24    }
    25  
    26    const apiEndpointMock: (req: Request) => Promise<Response> = (req = new Request(null)) => new Promise((resolve, _reject) => resolve(new Response(req.request)));
    27  
    28    let expected: CachedDataReducerState<Response>;
    29  
    30    describe("reducerObj", function () {
    31      const actionNamespace = "test";
    32      const testReducerObj = new CachedDataReducer<Request, Response>(apiEndpointMock, actionNamespace);
    33  
    34      describe("actions", function () {
    35        it("requestData() creates the correct action type.", function () {
    36          assert.equal(testReducerObj.requestData().type, testReducerObj.REQUEST);
    37        });
    38  
    39        it("receiveData() creates the correct action type.", function () {
    40          assert.equal(testReducerObj.receiveData(null).type, testReducerObj.RECEIVE);
    41        });
    42  
    43        it("errorData() creates the correct action type.", function () {
    44          assert.equal(testReducerObj.errorData(null).type, testReducerObj.ERROR);
    45        });
    46  
    47        it("invalidateData() creates the correct action type.", function () {
    48          assert.equal(testReducerObj.invalidateData().type, testReducerObj.INVALIDATE);
    49        });
    50      });
    51  
    52      const reducer = testReducerObj.reducer;
    53      const testMoment = moment();
    54      testReducerObj.setTimeSource(() => testMoment);
    55  
    56      describe("reducer", function () {
    57        let state: CachedDataReducerState<Response>;
    58        beforeEach(() => {
    59          state = reducer(undefined, { type: "unknown" });
    60        });
    61  
    62        it("should have the correct default value.", function () {
    63          expected = new CachedDataReducerState<Response>();
    64          assert.deepEqual(state, expected);
    65        });
    66  
    67        it("should correctly dispatch requestData", function () {
    68          state = reducer(state, testReducerObj.requestData());
    69          expected = new CachedDataReducerState<Response>();
    70          expected.inFlight = true;
    71          expected.requestedAt = testMoment;
    72          assert.deepEqual(state, expected);
    73        });
    74  
    75        it("should correctly dispatch receiveData", function () {
    76          const expectedResponse = new Response(null);
    77          state = reducer(state, testReducerObj.receiveData(expectedResponse, null));
    78          expected = new CachedDataReducerState<Response>();
    79          expected.valid = true;
    80          expected.data = expectedResponse;
    81          expected.setAt = testMoment;
    82          expected.lastError = null;
    83          assert.deepEqual(state, expected);
    84        });
    85  
    86        it("should correctly dispatch errorData", function () {
    87          const e = new Error();
    88  
    89          state = reducer(state, testReducerObj.errorData(e, null));
    90          expected = new CachedDataReducerState<Response>();
    91          expected.lastError = e;
    92          assert.deepEqual(state, expected);
    93        });
    94  
    95        it("should correctly dispatch invalidateData", function () {
    96          state = reducer(state, testReducerObj.invalidateData());
    97          expected = new CachedDataReducerState<Response>();
    98          assert.deepEqual(state, expected);
    99        });
   100      });
   101  
   102      describe("refresh", function () {
   103        let state: {
   104          cachedData: {
   105            test: CachedDataReducerState<Response>;
   106          };
   107        };
   108  
   109        const mockDispatch = <A extends Action>(action: A): A => {
   110          state.cachedData.test = testReducerObj.reducer(state.cachedData.test, action);
   111          return undefined;
   112        };
   113  
   114        it("correctly dispatches refresh", function () {
   115          state = {
   116            cachedData: {
   117              test: new CachedDataReducerState<Response>(),
   118            },
   119          };
   120  
   121          const testString = "refresh test string";
   122  
   123          return testReducerObj.refresh(new Request(testString))(mockDispatch, () => state, undefined).then(() => {
   124            expected = new CachedDataReducerState<Response>();
   125            expected.valid = true;
   126            expected.data = new Response(testString);
   127            expected.requestedAt = testMoment;
   128            expected.setAt = testMoment;
   129            expected.lastError = null;
   130            assert.deepEqual(state.cachedData.test, expected);
   131          });
   132        });
   133      });
   134    });
   135  
   136    describe("multiple reducer objects", function () {
   137      it("should throw an error if the same actionNamespace is used twice", function () {
   138        // tslint:disable-next-line:no-unused-expression
   139        new CachedDataReducer<Request, Response>(apiEndpointMock, "duplicatenamespace");
   140        try {
   141          // tslint:disable-next-line:no-unused-expression
   142          new CachedDataReducer<Request, Response>(apiEndpointMock, "duplicatenamespace");
   143        } catch (e) {
   144          assert(_.isError(e));
   145          return;
   146        }
   147        assert.fail("Expected to fail after registering a duplicate actionNamespace.");
   148      });
   149    });
   150  });
   151  
   152  describe("keyed cachedDataReducer", function () {
   153    class Request {
   154      constructor(public request: string) { }
   155    }
   156  
   157    class Response {
   158      constructor(public response: string) { }
   159    }
   160  
   161    const apiEndpointMock: (req: Request) => Promise<Response> = (req = new Request(null)) => new Promise((resolve, _reject) => resolve(new Response(req.request)));
   162  
   163    const requestToID = (req: Request) => req.request;
   164  
   165    let expected: KeyedCachedDataReducerState<Response>;
   166  
   167    describe("reducerObj", function () {
   168      const actionNamespace = "keyedTest";
   169      const testReducerObj = new KeyedCachedDataReducer<Request, Response>(apiEndpointMock, actionNamespace, requestToID);
   170  
   171      describe("actions", function () {
   172        it("requestData() creates the correct action type.", function () {
   173          const request = new Request("testRequestRequest");
   174          const requestAction = testReducerObj.cachedDataReducer.requestData(request);
   175          assert.equal(requestAction.type, testReducerObj.cachedDataReducer.REQUEST);
   176          assert.deepEqual(requestAction.payload, {request});
   177        });
   178  
   179        it("receiveData() creates the correct action type.", function () {
   180          const response = new Response("testResponse");
   181          const request = new Request("testResponseRequest");
   182          const receiveAction = testReducerObj.cachedDataReducer.receiveData(response, request);
   183          assert.equal(receiveAction.type, testReducerObj.cachedDataReducer.RECEIVE);
   184          assert.deepEqual(receiveAction.payload, {request, data: response});
   185        });
   186  
   187        it("errorData() creates the correct action type.", function () {
   188          const error = new Error();
   189          const request = new Request("testErrorRequest");
   190          const errorAction = testReducerObj.cachedDataReducer.errorData(error, request);
   191          assert.equal(errorAction.type, testReducerObj.cachedDataReducer.ERROR);
   192          assert.deepEqual(errorAction.payload, {request, data: error});
   193        });
   194  
   195        it("invalidateData() creates the correct action type.", function () {
   196          const request = new Request("testInvalidateRequest");
   197          const invalidateAction = testReducerObj.cachedDataReducer.invalidateData(request);
   198          assert.equal(invalidateAction.type, testReducerObj.cachedDataReducer.INVALIDATE);
   199          assert.deepEqual(invalidateAction.payload, {request});
   200        });
   201      });
   202  
   203      const reducer = testReducerObj.reducer;
   204      const testMoment = moment();
   205      testReducerObj.setTimeSource(() => testMoment);
   206  
   207      describe("keyed reducer", function () {
   208        let state: KeyedCachedDataReducerState<Response>;
   209        let id: string;
   210        let request: Request;
   211        beforeEach(() => {
   212          state = reducer(undefined, { type: "unknown" });
   213          id = Math.random().toString();
   214          request = new Request(id);
   215        });
   216  
   217        it("should have the correct default value.", function() {
   218          expected = new KeyedCachedDataReducerState<Response>();
   219          assert.deepEqual(state, expected);
   220        });
   221  
   222        it("should correctly dispatch requestData", function () {
   223          state = reducer(state, testReducerObj.cachedDataReducer.requestData(request));
   224          expected = new KeyedCachedDataReducerState<Response>();
   225          expected[id] = new CachedDataReducerState<Response>();
   226          expected[id].requestedAt = testMoment;
   227          expected[id].inFlight = true;
   228          assert.deepEqual(state, expected);
   229        });
   230  
   231        it("should correctly dispatch receiveData", function () {
   232          const expectedResponse = new Response(null);
   233  
   234          state = reducer(state, testReducerObj.cachedDataReducer.receiveData(expectedResponse, request));
   235          expected = new KeyedCachedDataReducerState<Response>();
   236          expected[id] = new CachedDataReducerState<Response>();
   237          expected[id].valid = true;
   238          expected[id].data = expectedResponse;
   239          expected[id].lastError = null;
   240          expected[id].setAt = testMoment;
   241          assert.deepEqual(state, expected);
   242        });
   243  
   244        it("should correctly dispatch errorData", function() {
   245          const e = new Error();
   246  
   247          state = reducer(state, testReducerObj.cachedDataReducer.errorData(e, request));
   248          expected = new KeyedCachedDataReducerState<Response>();
   249          expected[id] = new CachedDataReducerState<Response>();
   250          expected[id].lastError = e;
   251          assert.deepEqual(state, expected);
   252        });
   253  
   254        it("should correctly dispatch invalidateData", function() {
   255          state = reducer(state, testReducerObj.cachedDataReducer.invalidateData(request));
   256          expected = new KeyedCachedDataReducerState<Response>();
   257          expected[id] = new CachedDataReducerState<Response>();
   258          assert.deepEqual(state, expected);
   259        });
   260      });
   261    });
   262  });