Store refactor: use non-global stores in components (#9293)
* Add Stores and StoresContext and use it in MatrixChat and RoomView Added a new kind of class: - Add God object `Stores` which will hold refs to all known stores and the `MatrixClient`. This object is NOT a singleton. - Add `StoresContext` to hold onto a ref of `Stores` for use inside components. `StoresContext` is created via: - Create `Stores` in `MatrixChat`, assigning the `MatrixClient` when we have one set. Currently sets the RVS to `RoomViewStore.instance`. - Wrap `MatrixChat`s `render()` function in a `StoresContext.Provider` so it can be used anywhere. `StoresContext` is currently only used in `RoomView` via the following changes: - Remove the HOC, which redundantly set `mxClient` as a prop. We don't need this as `RoomView` was using the client from `this.context`. - Change the type of context accepted from `MatrixClientContext` to `StoresContext`. - Modify alllll the places where `this.context` is used to interact with the client and suffix `.client`. - Modify places where we use `RoomViewStore.instance` and replace them with `this.context.roomViewStore`. This makes `RoomView` use a non-global instance of RVS. * Linting * SDKContext and make client an optional constructor arg * Move SDKContext to /src/contexts * Inject all RVS deps * Linting * Remove reset calls; deep copy the INITIAL_STATE to avoid test pollution * DI singletons used in RoomView; DI them in RoomView-test too * Initial RoomViewStore.instance after all files are imported to avoid cyclical deps * Lazily init stores to allow for circular dependencies Rather than stores accepting a list of other stores in their constructors, which doesn't work when A needs B and B needs A, make new-style stores simply accept Stores. When a store needs another store, they access it via `Stores` which then lazily constructs that store if it needs it. This breaks the circular dependency at constructor time, without needing to introduce wiring diagrams or any complex DI framework. * Delete RoomViewStore.instance Replaced with Stores.instance.roomViewStore * Linting * Move OverridableStores to test/TestStores * Rejig how eager stores get made; don't automatically do it else tests break * Linting * Linting and review comments * Fix new code to use Stores.instance * s/Stores/SdkContextClass/g * Update docs * Remove unused imports * Update src/stores/RoomViewStore.tsx Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> * Remove empty c'tor to make sonar happy Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
parent
84f2974b57
commit
e946674df3
36 changed files with 467 additions and 275 deletions
|
@ -32,17 +32,16 @@ import { defaultDispatcher } from "../../../src/dispatcher/dispatcher";
|
|||
import { ViewRoomPayload } from "../../../src/dispatcher/payloads/ViewRoomPayload";
|
||||
import { RoomView as _RoomView } from "../../../src/components/structures/RoomView";
|
||||
import ResizeNotifier from "../../../src/utils/ResizeNotifier";
|
||||
import { RoomViewStore } from "../../../src/stores/RoomViewStore";
|
||||
import SettingsStore from "../../../src/settings/SettingsStore";
|
||||
import { SettingLevel } from "../../../src/settings/SettingLevel";
|
||||
import DMRoomMap from "../../../src/utils/DMRoomMap";
|
||||
import { NotificationState } from "../../../src/stores/notifications/NotificationState";
|
||||
import RightPanelStore from "../../../src/stores/right-panel/RightPanelStore";
|
||||
import { RightPanelPhases } from "../../../src/stores/right-panel/RightPanelStorePhases";
|
||||
import { LocalRoom, LocalRoomState } from "../../../src/models/LocalRoom";
|
||||
import { DirectoryMember } from "../../../src/utils/direct-messages";
|
||||
import { createDmLocalRoom } from "../../../src/utils/dm/createDmLocalRoom";
|
||||
import { UPDATE_EVENT } from "../../../src/stores/AsyncStore";
|
||||
import { SdkContextClass, SDKContext } from "../../../src/contexts/SDKContext";
|
||||
|
||||
const RoomView = wrapInMatrixClientContext(_RoomView);
|
||||
|
||||
|
@ -50,6 +49,7 @@ describe("RoomView", () => {
|
|||
let cli: MockedObject<MatrixClient>;
|
||||
let room: Room;
|
||||
let roomCount = 0;
|
||||
let stores: SdkContextClass;
|
||||
|
||||
beforeEach(async () => {
|
||||
mockPlatformPeg({ reload: () => {} });
|
||||
|
@ -64,7 +64,9 @@ describe("RoomView", () => {
|
|||
room.on(RoomEvent.TimelineReset, (...args) => cli.emit(RoomEvent.TimelineReset, ...args));
|
||||
|
||||
DMRoomMap.makeShared();
|
||||
RightPanelStore.instance.useUnitTestClient(cli);
|
||||
stores = new SdkContextClass();
|
||||
stores.client = cli;
|
||||
stores.rightPanelStore.useUnitTestClient(cli);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
|
@ -73,15 +75,15 @@ describe("RoomView", () => {
|
|||
});
|
||||
|
||||
const mountRoomView = async (): Promise<ReactWrapper> => {
|
||||
if (RoomViewStore.instance.getRoomId() !== room.roomId) {
|
||||
if (stores.roomViewStore.getRoomId() !== room.roomId) {
|
||||
const switchedRoom = new Promise<void>(resolve => {
|
||||
const subFn = () => {
|
||||
if (RoomViewStore.instance.getRoomId()) {
|
||||
RoomViewStore.instance.off(UPDATE_EVENT, subFn);
|
||||
if (stores.roomViewStore.getRoomId()) {
|
||||
stores.roomViewStore.off(UPDATE_EVENT, subFn);
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
RoomViewStore.instance.on(UPDATE_EVENT, subFn);
|
||||
stores.roomViewStore.on(UPDATE_EVENT, subFn);
|
||||
});
|
||||
|
||||
defaultDispatcher.dispatch<ViewRoomPayload>({
|
||||
|
@ -94,15 +96,16 @@ describe("RoomView", () => {
|
|||
}
|
||||
|
||||
const roomView = mount(
|
||||
<RoomView
|
||||
mxClient={cli}
|
||||
threepidInvite={null}
|
||||
oobData={null}
|
||||
resizeNotifier={new ResizeNotifier()}
|
||||
justCreatedOpts={null}
|
||||
forceTimeline={false}
|
||||
onRegistered={null}
|
||||
/>,
|
||||
<SDKContext.Provider value={stores}>
|
||||
<RoomView
|
||||
threepidInvite={null}
|
||||
oobData={null}
|
||||
resizeNotifier={new ResizeNotifier()}
|
||||
justCreatedOpts={null}
|
||||
forceTimeline={false}
|
||||
onRegistered={null}
|
||||
/>
|
||||
</SDKContext.Provider>,
|
||||
);
|
||||
await act(() => Promise.resolve()); // Allow state to settle
|
||||
return roomView;
|
||||
|
@ -162,14 +165,14 @@ describe("RoomView", () => {
|
|||
it("normally doesn't open the chat panel", async () => {
|
||||
jest.spyOn(NotificationState.prototype, "isUnread", "get").mockReturnValue(false);
|
||||
await mountRoomView();
|
||||
expect(RightPanelStore.instance.isOpen).toEqual(false);
|
||||
expect(stores.rightPanelStore.isOpen).toEqual(false);
|
||||
});
|
||||
|
||||
it("opens the chat panel if there are unread messages", async () => {
|
||||
jest.spyOn(NotificationState.prototype, "isUnread", "get").mockReturnValue(true);
|
||||
await mountRoomView();
|
||||
expect(RightPanelStore.instance.isOpen).toEqual(true);
|
||||
expect(RightPanelStore.instance.currentCard.phase).toEqual(RightPanelPhases.Timeline);
|
||||
expect(stores.rightPanelStore.isOpen).toEqual(true);
|
||||
expect(stores.rightPanelStore.currentCard.phase).toEqual(RightPanelPhases.Timeline);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue