Skip to content

Commit

Permalink
Moved ExtensionsManager to matrix-react-sdk-module-api. Added tests f…
Browse files Browse the repository at this point in the history
…or ModuleRunner use of new UserSearchExtension
  • Loading branch information
thoraj committed Aug 22, 2024
1 parent e4b4424 commit 3b1824f
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 122 deletions.
122 changes: 2 additions & 120 deletions src/modules/ModuleRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,131 +17,13 @@ limitations under the License.
import { safeSet } from "matrix-js-sdk/src/utils";
import { TranslationStringsObject } from "@matrix-org/react-sdk-module-api/lib/types/translations";
import { AnyLifecycle } from "@matrix-org/react-sdk-module-api/lib/lifecycles/types";
import {
DefaultCryptoSetupExtensions,
ProvideCryptoSetupExtensions,
} from "@matrix-org/react-sdk-module-api/lib/lifecycles/CryptoSetupExtensions";
import {
DefaultExperimentalExtensions,
ProvideExperimentalExtensions,
} from "@matrix-org/react-sdk-module-api/lib/lifecycles/ExperimentalExtensions";
import {
DefaultUserSearchExtensions,
ProvideUserSearchExtensions,
} from "@matrix-org/react-sdk-module-api/lib/lifecycles/UserSearchExtensions";
import { ExtensionsManager } from "@matrix-org/react-sdk-module-api/lib/extensions/ExtensionsManager";

import { AppModule } from "./AppModule";
import { ModuleFactory } from "./ModuleFactory";

import "./ModuleComponents";

/**
* Handles and manages extensions provided by modules.
*/
class ExtensionsManager {
// Private backing fields for extensions
private cryptoSetupExtension: ProvideCryptoSetupExtensions;
private experimentalExtension: ProvideExperimentalExtensions;
private userSearchExtension: ProvideUserSearchExtensions;

/** `true` if `cryptoSetupExtension` is the default implementation; `false` if it is implemented by a module. */
private hasDefaultCryptoSetupExtension = true;

/** `true` if `userSearchExtension` is the default implementation; `false` if it is implemented by a module. */
private hasDefaultUserSearchExtension = true;

/** `true` if `experimentalExtension` is the default implementation; `false` if it is implemented by a module. */
private hasDefaultExperimentalExtension = true;


/**
* Create a new instance.
*/
public constructor() {
// Set up defaults
this.cryptoSetupExtension = new DefaultCryptoSetupExtensions();
this.experimentalExtension = new DefaultExperimentalExtensions();
this.userSearchExtension = new DefaultUserSearchExtensions();
}

/**
* Provides a crypto setup extension.
*
* @returns The registered extension. If no module provides this extension, a default implementation is returned.
*/
public get cryptoSetup(): ProvideCryptoSetupExtensions {
return this.cryptoSetupExtension;
}

/**
* Provides a user search extension.
*
* @returns The registered extension. If no module provides this extension, a default implementation is returned.
*/
public get userSearch(): ProvideUserSearchExtensions {
return this.userSearchExtension;
}


/**
* Provides an experimental extension.
*
* @remarks
* This method extension is provided to simplify experimentation and development, and is not intended for production code.
*
* @returns The registered extension. If no module provides this extension, a default implementation is returned.
*/
public get experimental(): ProvideExperimentalExtensions {
return this.experimentalExtension;
}

/**
* Add any extensions provided by the module.
*
* @param module - The appModule to check for extensions.
*
* @throws if an extension is provided by more than one module.
*/
public addExtensions(module: AppModule): void {
const runtimeModule = module.module;

/* Add the cryptoSetup extension if any */
if (runtimeModule.extensions?.cryptoSetup) {
if (this.hasDefaultCryptoSetupExtension) {
this.cryptoSetupExtension = runtimeModule.extensions?.cryptoSetup;
this.hasDefaultCryptoSetupExtension = false;
} else {
throw new Error(
`adding cryptoSetup extension implementation from module ${runtimeModule.moduleName} but an implementation was already provided.`,
);
}
}

/* Add the userSearch extension if any */
if (runtimeModule.extensions?.userSearch) {
if (this.hasDefaultUserSearchExtension) {
this.userSearchExtension = runtimeModule.extensions?.userSearch;
this.hasDefaultCryptoSetupExtension = false;
} else {
throw new Error(
`adding userSearch extension implementation from module ${runtimeModule.moduleName} but an implementation was already provided.`,
);
}
}

/* Add the experimental extension if any */
if (runtimeModule.extensions?.experimental) {
if (this.hasDefaultExperimentalExtension) {
this.experimentalExtension = runtimeModule.extensions?.experimental;
this.hasDefaultExperimentalExtension = false;
} else {
throw new Error(
`adding experimental extension implementation from module ${runtimeModule.moduleName} but an implementation was already provided.`,
);
}
}
}
}

/**
* Handles and coordinates the operation of modules.
Expand Down Expand Up @@ -209,7 +91,7 @@ export class ModuleRunner {
this.modules.push(appModule);

// Check if the new module provides any extensions, and also ensure a given extension is only provided by a single runtime module.
this.extensionsManager.addExtensions(appModule);
this.extensionsManager.addExtensions(appModule.module);
}

/**
Expand Down
51 changes: 49 additions & 2 deletions test/modules/MockModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@ limitations under the License.
import { RuntimeModule } from "@matrix-org/react-sdk-module-api/lib/RuntimeModule";
import { ModuleApi } from "@matrix-org/react-sdk-module-api/lib/ModuleApi";
import { AllExtensions } from "@matrix-org/react-sdk-module-api/lib/types/extensions";
import { ProvideCryptoSetupExtensions } from "@matrix-org/react-sdk-module-api/lib/lifecycles/CryptoSetupExtensions";
import { ProvideExperimentalExtensions } from "@matrix-org/react-sdk-module-api/lib/lifecycles/ExperimentalExtensions";
import { ProvideCryptoSetupExtensions } from "@matrix-org/react-sdk-module-api/lib/extensions/CryptoSetupExtensions";
import {
ProvideUserSearchExtensions,
SearchContext
} from "@matrix-org/react-sdk-module-api/lib/extensions/UserSearchExtensions";
import { ProvideExperimentalExtensions } from "@matrix-org/react-sdk-module-api/lib/extensions/ExperimentalExtensions";

import { ModuleRunner } from "../../src/modules/ModuleRunner";

Expand Down Expand Up @@ -95,6 +99,28 @@ class MockModuleWithExperimentalExtension extends RuntimeModule {
}
}

class MockModuleWithUserSearchExtension extends RuntimeModule {
public get apiInstance(): ModuleApi {
return this.moduleApi;
}

moduleName: string = MockModuleWithUserSearchExtension.name;

extensions: AllExtensions = {
userSearch: {
getSearchContext: jest.fn().mockReturnValue(<SearchContext> {
extraBodyArgs: {},
extraRequestOptions: {}
}),
} as ProvideUserSearchExtensions,
};

public constructor(moduleApi: ModuleApi) {
super(moduleApi);
}
}


/**
* Register a mock module which implements the cryptoSetup extension.
*
Expand Down Expand Up @@ -136,3 +162,24 @@ export function registerMockModuleWithExperimentalExtension(): MockModuleWithExp
}
return module;
}

/**
* Register a mock module which implements the user search extension.
*
* @returns The registered module.
*/
export function registerMockModuleWithUserSearchExtension(): MockModuleWithUserSearchExtension {
let module: MockModuleWithUserSearchExtension | undefined;

ModuleRunner.instance.registerModule((api) => {
if (module) {
throw new Error("State machine error: ModuleRunner created the module twice");
}
module = new MockModuleWithUserSearchExtension(api);
return module;
});
if (!module) {
throw new Error("State machine error: ModuleRunner did not create module");
}
return module;
}
10 changes: 10 additions & 0 deletions test/modules/ModuleRunner-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
registerMockModule,
registerMockModuleWithCryptoSetupExtension,
registerMockModuleWithExperimentalExtension,
registerMockModuleWithUserSearchExtension
} from "./MockModule";
import { ModuleRunner } from "../../src/modules/ModuleRunner";

Expand Down Expand Up @@ -97,5 +98,14 @@ describe("ModuleRunner", () => {
"adding experimental extension implementation from module MockModuleWithExperimentalExtension but an implementation was already provided",
);
});

it("must not allow multiple modules to provide user search extension", async () => {
registerMockModuleWithUserSearchExtension();
const t = () => registerMockModuleWithUserSearchExtension();
expect(t).toThrow(Error);
expect(t).toThrow(
"adding userSearch extension implementation from module MockModuleWithUserSearchExtension but an implementation was already provided",
);
});
});
});

0 comments on commit 3b1824f

Please sign in to comment.