Add ability to expose derived state apis (#39)
* Add ability to expose derived state apis * rename API and arguments based on code review suggestions Co-authored-by: Eric Alas <alas.eric@gmail.com>
This commit is contained in:
Родитель
8b74c0d392
Коммит
5f7f5b864b
|
@ -21,5 +21,8 @@ export interface IGlobalStore {
|
|||
SubscribeToPartnerState(source: string, partner: string, callback: (state: any) => void, eager: boolean): () => void;
|
||||
SubscribeToGlobalState(source: string, callback: (state: any) => void): () => void;
|
||||
|
||||
AddSelectors(source: string, selectors: Record<string, any>, mergeSelectors?: boolean): void;
|
||||
SelectPartnerState(partner: string, selector: string, defaultReturn?: any): any;
|
||||
|
||||
SetLogger(logger: ILogger): void;
|
||||
};
|
|
@ -22,6 +22,7 @@ export class GlobalStore implements IGlobalStore {
|
|||
private _eagerPartnerStoreSubscribers: { [key: string]: { [key: string]: (state) => void } }
|
||||
private _eagerUnsubscribers: { [key: string]: { [key: string]: () => void } }
|
||||
private _actionLogger: ActionLogger = null;
|
||||
private _selectors: { [key: string]: any };
|
||||
|
||||
private constructor(private _logger: ILogger = null) {
|
||||
this._stores = {};
|
||||
|
@ -30,6 +31,7 @@ export class GlobalStore implements IGlobalStore {
|
|||
this._eagerPartnerStoreSubscribers = {};
|
||||
this._eagerUnsubscribers = {};
|
||||
this._actionLogger = new ActionLogger(_logger);
|
||||
this._selectors = {};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -334,6 +336,48 @@ export class GlobalStore implements IGlobalStore {
|
|||
this._actionLogger.SetLogger(logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* Summary: Expose a collection of Selecotrs from a Partner-level that other partners can later consume. This allows partners to derive data without forcing partners to know the state structure.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param {string} source Name of the application exposing an derived state API
|
||||
* @param {Record<string, any>} selectors The collection of APIs of derived state selectors.
|
||||
* @param {boolean} mergeSelectors If the source application already exposed an API set, merge the new API being passed in.
|
||||
*
|
||||
*/
|
||||
AddSelectors(source: string, selectors: Record<string, any>, mergeSelectors = false) {
|
||||
if (this._selectors[source] == undefined) {
|
||||
this._selectors[source] = selectors;
|
||||
}
|
||||
|
||||
if (this._selectors[source] != undefined && mergeSelectors) {
|
||||
this._selectors[source] = Object.assign({}, this._selectors[source], selectors);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Summary: Select derived state from a partner app using the selector name
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param {string} partner Name of the partner application to select derived data from
|
||||
* @param {string} selector The name of the API to select
|
||||
* @param {any} defaultReturn If the partner app does not have that API exposed, return this default value instead of undefined.
|
||||
*
|
||||
*/
|
||||
SelectPartnerState(partner: string, selector: string, defaultReturn?: any) {
|
||||
if (this._selectors[partner] == undefined) {
|
||||
throw new Error(`ERROR: ${partner} not exposed any selectors.`);
|
||||
}
|
||||
if (this._selectors[partner][selector] == undefined) {
|
||||
console.warn(`${partner} has not exposed a selector with the name: ${selector}`);
|
||||
return defaultReturn;
|
||||
}
|
||||
|
||||
return this._selectors[partner][selector]();
|
||||
}
|
||||
|
||||
private RegisterEagerSubscriptions(appName: string) {
|
||||
let eagerCallbacksRegistrations = this._eagerPartnerStoreSubscribers[appName];
|
||||
if (eagerCallbacksRegistrations === undefined || eagerCallbacksRegistrations === undefined)
|
||||
|
|
|
@ -480,4 +480,169 @@ describe("Global Store", () => {
|
|||
expect(isGlobalStateChanged).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("AddSelectors", () => {
|
||||
let dummyPartnerReducer: Reducer<any, any> = (state: string = "Default", action: IAction<any>) => {
|
||||
switch (action.type) {
|
||||
case "Local": return "Local";
|
||||
case "Global": return "Global";
|
||||
}
|
||||
};
|
||||
|
||||
it("Should successfully expose derived state API", () => {
|
||||
let partnerAppName = "SamplePartner-2022";
|
||||
const partnerStore = globalStore.CreateStore(partnerAppName, dummyPartnerReducer, [], ["Global"], false, false);
|
||||
|
||||
// Arrange
|
||||
globalStore.AddSelectors(partnerAppName, {
|
||||
selectStateUpperCased: () => {
|
||||
const state = partnerStore.getState();
|
||||
return state.toUpperCase()
|
||||
},
|
||||
selectStateLowerCased: () => {
|
||||
const state = partnerStore.getState();
|
||||
return state.toLowerCase()
|
||||
}
|
||||
});
|
||||
|
||||
// Assert
|
||||
const api = (<any>globalStore)._selectors[partnerAppName];
|
||||
expect(api.selectStateUpperCased).toBeDefined();
|
||||
expect(api.selectStateLowerCased).toBeDefined();
|
||||
});
|
||||
|
||||
it("Should successfully merge derived state API", () => {
|
||||
let partnerAppName = "SamplePartner-2023";
|
||||
const partnerStore = globalStore.CreateStore(partnerAppName, dummyPartnerReducer, [], ["Global"], false, false);
|
||||
|
||||
// Arrange
|
||||
globalStore.AddSelectors(partnerAppName, {
|
||||
selectStateUpperCased: () => {
|
||||
const state = partnerStore.getState();
|
||||
return state.toUpperCase()
|
||||
},
|
||||
});
|
||||
|
||||
globalStore.AddSelectors(partnerAppName, {
|
||||
selectStateLowerCased: () => {
|
||||
const state = partnerStore.getState();
|
||||
return state.toLowerCase()
|
||||
}
|
||||
}, true);
|
||||
|
||||
// Assert
|
||||
const api = (<any>globalStore)._selectors[partnerAppName];
|
||||
expect(api.selectStateUpperCased).toBeDefined();
|
||||
expect(api.selectStateLowerCased).toBeDefined();
|
||||
});
|
||||
|
||||
it("Should not merge derived state API", () => {
|
||||
let partnerAppName = "SamplePartner-2024";
|
||||
const partnerStore = globalStore.CreateStore(partnerAppName, dummyPartnerReducer, [], ["Global"], false, false);
|
||||
|
||||
// Arrange
|
||||
globalStore.AddSelectors(partnerAppName, {
|
||||
selectStateUpperCased: () => {
|
||||
const state = partnerStore.getState();
|
||||
return state.toUpperCase()
|
||||
},
|
||||
});
|
||||
|
||||
globalStore.AddSelectors(partnerAppName, {
|
||||
selectStateLowerCased: () => {
|
||||
const state = partnerStore.getState();
|
||||
return state.toLowerCase()
|
||||
}
|
||||
});
|
||||
|
||||
// Assert
|
||||
const api = (<any>globalStore)._selectors[partnerAppName];
|
||||
expect(api.selectStateUpperCased).toBeDefined();
|
||||
expect(api.selectStateLowerCased).toBeUndefined();
|
||||
})
|
||||
})
|
||||
|
||||
describe("SelectPartnerState", () => {
|
||||
let dummyPartnerReducer: Reducer<any, any> = (state: string = "Default", action: IAction<any>) => {
|
||||
switch (action.type) {
|
||||
case "Local": return "Local";
|
||||
case "Global": return "Global";
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
it("Should be able to request a piece of derived state with valid key", () => {
|
||||
// Arrange
|
||||
let partnerAppName = "SamplePartner-2012";
|
||||
const partnerStore = globalStore.CreateStore(partnerAppName, dummyPartnerReducer, [], ["Global"], false, false);
|
||||
|
||||
globalStore.AddSelectors(partnerAppName, {
|
||||
selectStateUpperCased: () => {
|
||||
const state = partnerStore.getState();
|
||||
return state.toUpperCase()
|
||||
},
|
||||
selectStateLowerCased: () => {
|
||||
const state = partnerStore.getState();
|
||||
return state.toLowerCase()
|
||||
}
|
||||
});
|
||||
|
||||
// Act
|
||||
const partnerStateComputedUpperCase = globalStore.SelectPartnerState(partnerAppName, "selectStateUpperCased");
|
||||
expect(partnerStateComputedUpperCase).toEqual("DEFAULT");
|
||||
const partnerStateComputedLowerCase = globalStore.SelectPartnerState(partnerAppName, "selectStateLowerCased");
|
||||
expect(partnerStateComputedLowerCase).toEqual("default");
|
||||
});
|
||||
|
||||
it("Should be return undefined if derived state key is not defined", () => {
|
||||
// Arrange
|
||||
let partnerAppName = "SamplePartner-2013";
|
||||
const partnerStore = globalStore.CreateStore(partnerAppName, dummyPartnerReducer, [], ["Global"], false, false);
|
||||
|
||||
globalStore.AddSelectors(partnerAppName, {
|
||||
selectStateUpperCased: () => {
|
||||
const state = partnerStore.getState();
|
||||
return state.toUpperCase()
|
||||
},
|
||||
});
|
||||
|
||||
// Act
|
||||
const partnerStateComputedLowerCase = globalStore.SelectPartnerState(partnerAppName, "selectStateLowerCased");
|
||||
expect(partnerStateComputedLowerCase).toEqual(undefined);
|
||||
});
|
||||
|
||||
it("Should be return default value if derived state key is not defined", () => {
|
||||
// Arrange
|
||||
let partnerAppName = "SamplePartner-2013";
|
||||
const partnerStore = globalStore.CreateStore(partnerAppName, dummyPartnerReducer, [], ["Global"], false, false);
|
||||
|
||||
globalStore.AddSelectors(partnerAppName, {
|
||||
selectStateUpperCased: () => {
|
||||
const state = partnerStore.getState();
|
||||
return state.toUpperCase()
|
||||
},
|
||||
});
|
||||
|
||||
// Act
|
||||
const partnerStateComputedLowerCase = globalStore.SelectPartnerState(partnerAppName, "selectStateLowerCased", "I am a default value");
|
||||
expect(partnerStateComputedLowerCase).toEqual("I am a default value");
|
||||
});
|
||||
|
||||
it("Should throw error if partner has not exposed any derived", () => {
|
||||
// Arrange
|
||||
let partnerAppName = "SamplePartner-2014";
|
||||
let exceptionThrown = false;
|
||||
globalStore.CreateStore(partnerAppName, dummyPartnerReducer, [], ["Global"], false, false);
|
||||
|
||||
try {
|
||||
globalStore.SelectPartnerState(partnerAppName, "selectStateLowerCased");
|
||||
} catch {
|
||||
exceptionThrown = true;
|
||||
}
|
||||
|
||||
// Assert
|
||||
expect(exceptionThrown).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
Загрузка…
Ссылка в новой задаче