Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit b352a25

Browse files
committed
Add some unit tests around module stuff
Needs end-to-end tests still.
1 parent 69e36a7 commit b352a25

File tree

9 files changed

+336
-3
lines changed

9 files changed

+336
-3
lines changed

src/modules/ModuleComponents.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,12 @@ import React from "react";
2121
import Field from "../components/views/elements/Field";
2222
import Spinner from "../components/views/elements/Spinner";
2323

24-
// TODO: @@ Is this future-proof enough? Will we remember to do this for new components?
24+
// Here we define all the render factories for the module API components. This file should be
25+
// imported by the ModuleRunner to load them into the call stack at runtime.
26+
//
27+
// If a new component is added to the module API, it should be added here too.
28+
//
29+
// Don't forget to add a test to ensure the renderFactory is overridden! See ModuleComponents-test.tsx
2530

2631
TextInputField.renderFactory = (props) => (
2732
<Field

src/modules/ModuleRunner.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ export class ModuleRunner {
3333
// we only want one instance
3434
}
3535

36+
/**
37+
* Resets the runner, clearing all known modules.
38+
*
39+
* Intended for test usage only.
40+
*/
41+
public reset() {
42+
this.modules = [];
43+
}
44+
3645
/**
3746
* All custom translations from all registered modules.
3847
*/

src/modules/ProxiedModuleApi.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { Optional } from "matrix-events-sdk";
2020
import { DialogProps } from "@matrix-org/react-sdk-module-api/lib/components/DialogContent";
2121
import React from "react";
2222
import { AccountAuthInfo } from "@matrix-org/react-sdk-module-api/lib/types/AccountAuthInfo";
23-
import { PlainSubstitution } from "@matrix-org/react-sdk-module-api/src/types/translations";
23+
import { PlainSubstitution } from "@matrix-org/react-sdk-module-api/lib/types/translations";
2424
import * as Matrix from "matrix-js-sdk/src/matrix";
2525

2626
import Modal from "../Modal";
@@ -137,7 +137,6 @@ export class ProxiedModuleApi implements ModuleApi {
137137
navigateToPermalink(uri);
138138

139139
const parts = parsePermalink(uri);
140-
if (parts.roomIdOrAlias)
141140
if (parts.roomIdOrAlias && andJoin) {
142141
let roomId = parts.roomIdOrAlias;
143142
let servers = parts.viaServers;

test/modules/AppModule-test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { MockModule } from "./MockModule";
18+
import { AppModule } from "../../src/modules/AppModule";
19+
20+
describe("AppModule", () => {
21+
describe("constructor", () => {
22+
it("should call the factory immediately", () => {
23+
let module: MockModule;
24+
const appModule = new AppModule((api) => {
25+
if (module) {
26+
throw new Error("State machine error: Factory called twice");
27+
}
28+
module = new MockModule(api);
29+
return module;
30+
});
31+
expect(appModule.module).toBeDefined();
32+
expect(appModule.module).toBe(module);
33+
expect(appModule.api).toBeDefined();
34+
});
35+
});
36+
});

test/modules/MockModule.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { RuntimeModule } from "@matrix-org/react-sdk-module-api/lib/RuntimeModule";
18+
import { ModuleApi } from "@matrix-org/react-sdk-module-api/lib/ModuleApi";
19+
import { ModuleRunner } from "../../src/modules/ModuleRunner";
20+
21+
export class MockModule extends RuntimeModule {
22+
public get apiInstance(): ModuleApi {
23+
return this.moduleApi;
24+
}
25+
26+
public constructor(moduleApi: ModuleApi) {
27+
super(moduleApi);
28+
}
29+
}
30+
31+
export function registerMockModule(): MockModule {
32+
let module: MockModule;
33+
ModuleRunner.instance.registerModule(api => {
34+
if (module) {
35+
throw new Error("State machine error: ModuleRunner created the module twice");
36+
}
37+
module = new MockModule(api);
38+
return module;
39+
});
40+
if (!module) {
41+
throw new Error("State machine error: ModuleRunner did not create module");
42+
}
43+
return module;
44+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import React from "react";
18+
import { mount } from "enzyme";
19+
import { TextInputField } from "@matrix-org/react-sdk-module-api/lib/components/TextInputField";
20+
import { Spinner as ModuleSpinner } from "@matrix-org/react-sdk-module-api/lib/components/Spinner";
21+
22+
import "../../src/modules/ModuleRunner";
23+
24+
describe("Module Components", () => {
25+
// Note: we're not testing to see if there's components that are missing a renderFactory()
26+
// but rather that the renderFactory() for components we do know about is actually defined
27+
// and working.
28+
//
29+
// We do this by deliberately not importing the ModuleComponents file itself, relying on the
30+
// ModuleRunner import to do its job (as per documentation in ModuleComponents).
31+
32+
it("should override the factory for a TextInputField", () => {
33+
const component = mount(<TextInputField label="My Label" value="My Value" onChange={() => {}} />);
34+
expect(component).toMatchSnapshot();
35+
});
36+
37+
it("should override the factory for a ModuleSpinner", () => {
38+
const component = mount(<ModuleSpinner />);
39+
expect(component).toMatchSnapshot();
40+
});
41+
});

test/modules/ModuleRunner-test.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { RoomPreviewOpts, RoomViewLifecycle } from "@matrix-org/react-sdk-module-api/lib/lifecycles/RoomViewLifecycle";
18+
19+
import { MockModule, registerMockModule } from "./MockModule";
20+
import { ModuleRunner } from "../../src/modules/ModuleRunner";
21+
22+
describe("ModuleRunner", () => {
23+
afterEach(() => {
24+
ModuleRunner.instance.reset();
25+
});
26+
27+
// Translations implicitly tested by ProxiedModuleApi integration tests.
28+
29+
describe("invoke", () => {
30+
it("should invoke to every registered module", async () => {
31+
const module1 = registerMockModule();
32+
const module2 = registerMockModule();
33+
34+
const wrapEmit = (module: MockModule) => new Promise((resolve) => {
35+
module.on(RoomViewLifecycle.PreviewRoomNotLoggedIn, (val1, val2) => {
36+
resolve([val1, val2]);
37+
});
38+
});
39+
const promises = Promise.all([
40+
wrapEmit(module1),
41+
wrapEmit(module2),
42+
]);
43+
44+
const roomId = "!room:example.org";
45+
const opts: RoomPreviewOpts = { canJoin: false };
46+
ModuleRunner.instance.invoke(RoomViewLifecycle.PreviewRoomNotLoggedIn, opts, roomId);
47+
const results = await promises;
48+
expect(results).toEqual([
49+
[opts, roomId], // module 1
50+
[opts, roomId], // module 2
51+
]);
52+
});
53+
});
54+
});
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { TranslationStringsObject } from "@matrix-org/react-sdk-module-api/lib/types/translations";
18+
19+
import { ProxiedModuleApi } from "../../src/modules/ProxiedModuleApi";
20+
import { stubClient } from "../test-utils";
21+
import { setLanguage } from "../../src/languageHandler";
22+
import { ModuleRunner } from "../../src/modules/ModuleRunner";
23+
import { registerMockModule } from "./MockModule";
24+
25+
describe("ProxiedApiModule", () => {
26+
afterEach(() => {
27+
ModuleRunner.instance.reset();
28+
});
29+
30+
// Note: Remainder is implicitly tested from end-to-end tests of modules.
31+
32+
describe("translations", () => {
33+
it("should cache translations", () => {
34+
const api = new ProxiedModuleApi();
35+
expect(api.translations).toBeFalsy();
36+
37+
const translations: TranslationStringsObject = {
38+
["custom string"]: {
39+
"en": "custom string",
40+
"fr": "custom french string",
41+
},
42+
};
43+
api.registerTranslations(translations);
44+
expect(api.translations).toBe(translations);
45+
});
46+
47+
describe("integration", () => {
48+
it("should translate strings using translation system", async () => {
49+
// Test setup
50+
stubClient();
51+
52+
// Set up a module to pull translations through
53+
const module = registerMockModule();
54+
const en = "custom string";
55+
const de = "custom german string";
56+
const enVars = "custom variable %(var)s";
57+
const varVal = "string";
58+
const deVars = "custom german variable %(var)s";
59+
const deFull = `custom german variable ${varVal}`;
60+
expect(module.apiInstance).toBeInstanceOf(ProxiedModuleApi);
61+
module.apiInstance.registerTranslations({
62+
[en]: {
63+
"en": en,
64+
"de": de,
65+
},
66+
[enVars]: {
67+
"en": enVars,
68+
"de": deVars,
69+
},
70+
});
71+
await setLanguage("de"); // calls `registerCustomTranslations()` for us
72+
73+
// See if we can pull the German string out
74+
expect(module.apiInstance.translateString(en)).toEqual(de);
75+
expect(module.apiInstance.translateString(enVars, { var: varVal })).toEqual(deFull);
76+
});
77+
});
78+
});
79+
});
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`Module Components should override the factory for a ModuleSpinner 1`] = `
4+
<Spinner>
5+
<Spinner
6+
h={32}
7+
w={32}
8+
>
9+
<div
10+
className="mx_Spinner"
11+
>
12+
<div
13+
aria-label="Loading..."
14+
className="mx_Spinner_icon"
15+
style={
16+
Object {
17+
"height": 32,
18+
"width": 32,
19+
}
20+
}
21+
/>
22+
</div>
23+
</Spinner>
24+
</Spinner>
25+
`;
26+
27+
exports[`Module Components should override the factory for a TextInputField 1`] = `
28+
<TextInputField
29+
label="My Label"
30+
onChange={[Function]}
31+
value="My Value"
32+
>
33+
<Field
34+
autoComplete="off"
35+
element="input"
36+
label="My Label"
37+
onChange={[Function]}
38+
type="text"
39+
validateOnBlur={true}
40+
validateOnChange={true}
41+
validateOnFocus={true}
42+
value="My Value"
43+
>
44+
<div
45+
className="mx_Field mx_Field_input"
46+
>
47+
<input
48+
autoComplete="off"
49+
id="mx_Field_1"
50+
label="My Label"
51+
onBlur={[Function]}
52+
onChange={[Function]}
53+
onFocus={[Function]}
54+
placeholder="My Label"
55+
type="text"
56+
value="My Value"
57+
/>
58+
<label
59+
htmlFor="mx_Field_1"
60+
>
61+
My Label
62+
</label>
63+
</div>
64+
</Field>
65+
</TextInputField>
66+
`;

0 commit comments

Comments
 (0)