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 });