Apply strictNullChecks to src/components/views/spaces/* (#10517)

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Weimann 2023-06-28 16:39:19 +02:00 committed by GitHub
parent 209f5bdf33
commit c0db739d81
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 161 additions and 20 deletions

View file

@ -1,5 +1,5 @@
/* /*
Copyright 2021 The Matrix.org Foundation C.I.C. Copyright 2021 - 2023 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -33,7 +33,6 @@ import { Icon as PinUprightIcon } from "../../../../res/img/element-icons/room/p
import { Icon as EllipsisIcon } from "../../../../res/img/element-icons/room/ellipsis.svg"; import { Icon as EllipsisIcon } from "../../../../res/img/element-icons/room/ellipsis.svg";
import { Icon as MembersIcon } from "../../../../res/img/element-icons/room/members.svg"; import { Icon as MembersIcon } from "../../../../res/img/element-icons/room/members.svg";
import { Icon as FavoriteIcon } from "../../../../res/img/element-icons/roomlist/favorite.svg"; import { Icon as FavoriteIcon } from "../../../../res/img/element-icons/roomlist/favorite.svg";
import SettingsStore from "../../../settings/SettingsStore";
import Modal from "../../../Modal"; import Modal from "../../../Modal";
import DevtoolsDialog from "../dialogs/DevtoolsDialog"; import DevtoolsDialog from "../dialogs/DevtoolsDialog";
import { SdkContextClass } from "../../../contexts/SDKContext"; import { SdkContextClass } from "../../../contexts/SDKContext";
@ -46,6 +45,9 @@ const QuickSettingsButton: React.FC<{
const { [MetaSpace.Favourites]: favouritesEnabled, [MetaSpace.People]: peopleEnabled } = const { [MetaSpace.Favourites]: favouritesEnabled, [MetaSpace.People]: peopleEnabled } =
useSettingValue<Record<MetaSpace, boolean>>("Spaces.enabledMetaSpaces"); useSettingValue<Record<MetaSpace, boolean>>("Spaces.enabledMetaSpaces");
const currentRoomId = SdkContextClass.instance.roomViewStore.getRoomId();
const developerModeEnabled = useSettingValue("developerMode");
let contextMenu: JSX.Element | undefined; let contextMenu: JSX.Element | undefined;
if (menuDisplayed && handle.current) { if (menuDisplayed && handle.current) {
contextMenu = ( contextMenu = (
@ -68,14 +70,14 @@ const QuickSettingsButton: React.FC<{
{_t("All settings")} {_t("All settings")}
</AccessibleButton> </AccessibleButton>
{SettingsStore.getValue("developerMode") && SdkContextClass.instance.roomViewStore.getRoomId() && ( {currentRoomId && developerModeEnabled && (
<AccessibleButton <AccessibleButton
onClick={() => { onClick={() => {
closeMenu(); closeMenu();
Modal.createDialog( Modal.createDialog(
DevtoolsDialog, DevtoolsDialog,
{ {
roomId: SdkContextClass.instance.roomViewStore.getRoomId()!, roomId: currentRoomId,
}, },
"mx_DevtoolsDialog_wrapper", "mx_DevtoolsDialog_wrapper",
); );

View file

@ -1,5 +1,5 @@
/* /*
Copyright 2021 The Matrix.org Foundation C.I.C. Copyright 2021 - 2023 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View file

@ -1,5 +1,5 @@
/* /*
Copyright 2021 The Matrix.org Foundation C.I.C. Copyright 2021 - 2023 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -95,9 +95,9 @@ export const SpaceButton = forwardRef<HTMLElement, IButtonProps>(
} }
let notifBadge; let notifBadge;
if (notificationState) { if (space && notificationState) {
let ariaLabel = _t("Jump to first unread room."); let ariaLabel = _t("Jump to first unread room.");
if (space?.getMyMembership() === "invite") { if (space.getMyMembership() === "invite") {
ariaLabel = _t("Jump to first invite."); ariaLabel = _t("Jump to first invite.");
} }
@ -133,8 +133,9 @@ export const SpaceButton = forwardRef<HTMLElement, IButtonProps>(
} }
const viewSpaceHome = (): void => const viewSpaceHome = (): void =>
defaultDispatcher.dispatch({ action: Action.ViewRoom, room_id: space.roomId }); // space is set here because of the assignment condition of onClick
const activateSpace = (): void => SpaceStore.instance.setActiveSpace(spaceKey ?? space.roomId); defaultDispatcher.dispatch({ action: Action.ViewRoom, room_id: space!.roomId });
const activateSpace = (): void => SpaceStore.instance.setActiveSpace(spaceKey ?? space?.roomId ?? "");
const onClick = props.onClick ?? (selected && space ? viewSpaceHome : activateSpace); const onClick = props.onClick ?? (selected && space ? viewSpaceHome : activateSpace);
return ( return (

View file

@ -46,6 +46,7 @@ import { FontWatcher } from "./watchers/FontWatcher";
import RustCryptoSdkController from "./controllers/RustCryptoSdkController"; import RustCryptoSdkController from "./controllers/RustCryptoSdkController";
import ServerSupportUnstableFeatureController from "./controllers/ServerSupportUnstableFeatureController"; import ServerSupportUnstableFeatureController from "./controllers/ServerSupportUnstableFeatureController";
import { WatchManager } from "./WatchManager"; import { WatchManager } from "./WatchManager";
import { CustomTheme } from "../theme";
export const defaultWatchManager = new WatchManager(); export const defaultWatchManager = new WatchManager();
@ -111,7 +112,15 @@ export const labGroupNames: Record<LabGroup, string> = {
[LabGroup.Developer]: _td("Developer"), [LabGroup.Developer]: _td("Developer"),
}; };
export type SettingValueType = boolean | number | string | number[] | string[] | Record<string, unknown> | null; export type SettingValueType =
| boolean
| number
| string
| number[]
| string[]
| Record<string, unknown>
| Record<string, unknown>[]
| null;
export interface IBaseSetting<T extends SettingValueType = SettingValueType> { export interface IBaseSetting<T extends SettingValueType = SettingValueType> {
isFeature?: false | undefined; isFeature?: false | undefined;
@ -653,7 +662,7 @@ export const SETTINGS: { [setting: string]: ISetting } = {
}, },
"custom_themes": { "custom_themes": {
supportedLevels: LEVELS_ACCOUNT_SETTINGS, supportedLevels: LEVELS_ACCOUNT_SETTINGS,
default: [], default: [] as CustomTheme[],
}, },
"use_system_theme": { "use_system_theme": {
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS,

View file

@ -16,6 +16,7 @@ limitations under the License.
*/ */
import { compare } from "matrix-js-sdk/src/utils"; import { compare } from "matrix-js-sdk/src/utils";
import { logger } from "matrix-js-sdk/src/logger";
import { _t } from "./languageHandler"; import { _t } from "./languageHandler";
import SettingsStore from "./settings/SettingsStore"; import SettingsStore from "./settings/SettingsStore";
@ -34,7 +35,8 @@ interface IFontFaces extends Omit<Record<(typeof allowedFontFaceProps)[number],
}[]; }[];
} }
interface ICustomTheme { export type CustomTheme = {
name: string;
colors: { colors: {
[key: string]: string; [key: string]: string;
}; };
@ -44,7 +46,7 @@ interface ICustomTheme {
monospace: string; monospace: string;
}; };
is_dark?: boolean; // eslint-disable-line camelcase is_dark?: boolean; // eslint-disable-line camelcase
} };
/** /**
* Given a non-high-contrast theme, find the corresponding high-contrast one * Given a non-high-contrast theme, find the corresponding high-contrast one
@ -79,11 +81,20 @@ export function enumerateThemes(): { [key: string]: string } {
"light-high-contrast": _t("Light high contrast"), "light-high-contrast": _t("Light high contrast"),
"dark": _t("Dark"), "dark": _t("Dark"),
}; };
const customThemes = SettingsStore.getValue("custom_themes"); const customThemes = SettingsStore.getValue("custom_themes") || [];
const customThemeNames: Record<string, string> = {}; const customThemeNames: Record<string, string> = {};
for (const { name } of customThemes) {
customThemeNames[`custom-${name}`] = name; try {
for (const { name } of customThemes) {
customThemeNames[`custom-${name}`] = name;
}
} catch (err) {
logger.warn("Error loading custom themes", {
err,
customThemes,
});
} }
return Object.assign({}, customThemeNames, BUILTIN_THEMES); return Object.assign({}, customThemeNames, BUILTIN_THEMES);
} }
@ -166,7 +177,7 @@ function generateCustomFontFaceCSS(faces: IFontFaces[]): string {
.join("\n"); .join("\n");
} }
function setCustomThemeVars(customTheme: ICustomTheme): void { function setCustomThemeVars(customTheme: CustomTheme): void {
const { style } = document.body; const { style } = document.body;
function setCSSColorVariable(name: string, hexColor: string, doPct = true): void { function setCSSColorVariable(name: string, hexColor: string, doPct = true): void {
@ -209,7 +220,7 @@ function setCustomThemeVars(customTheme: ICustomTheme): void {
} }
} }
export function getCustomTheme(themeName: string): ICustomTheme { export function getCustomTheme(themeName: string): CustomTheme {
// set css variables // set css variables
const customThemes = SettingsStore.getValue("custom_themes"); const customThemes = SettingsStore.getValue("custom_themes");
if (!customThemes) { if (!customThemes) {

View file

@ -0,0 +1,96 @@
/*
Copyright 2023 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { mocked } from "jest-mock";
import QuickSettingsButton from "../../../../src/components/views/spaces/QuickSettingsButton";
import SettingsStore from "../../../../src/settings/SettingsStore";
import { SdkContextClass } from "../../../../src/contexts/SDKContext";
describe("QuickSettingsButton", () => {
const roomId = "!room:example.com";
const renderQuickSettingsButton = () => {
render(<QuickSettingsButton isPanelCollapsed={true} />);
};
const getQuickSettingsButton = () => {
return screen.getByRole("button", { name: "Quick settings" });
};
const openQuickSettings = async () => {
await userEvent.click(getQuickSettingsButton());
await screen.findByText("Quick settings");
};
it("should render the quick settings button", () => {
renderQuickSettingsButton();
expect(getQuickSettingsButton()).toBeInTheDocument();
});
describe("when the quick settings are open", () => {
beforeEach(async () => {
renderQuickSettingsButton();
await openQuickSettings();
});
it("should not render the »Developer tools« button", () => {
renderQuickSettingsButton();
expect(screen.queryByText("Developer tools")).not.toBeInTheDocument();
});
});
describe("when developer mode is enabled", () => {
beforeEach(() => {
jest.spyOn(SettingsStore, "getValue").mockImplementation((setting) => setting === "developerMode");
renderQuickSettingsButton();
});
afterEach(() => {
mocked(SettingsStore.getValue).mockRestore();
});
describe("and no room is viewed", () => {
it("should not render the »Developer tools« button", () => {
renderQuickSettingsButton();
expect(screen.queryByText("Developer tools")).not.toBeInTheDocument();
});
});
describe("and a room is viewed", () => {
beforeEach(() => {
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue(roomId);
});
afterEach(() => {
mocked(SdkContextClass.instance.roomViewStore.getRoomId).mockRestore();
});
describe("and the quick settings are open", () => {
beforeEach(async () => {
await openQuickSettings();
});
it("should render the »Developer tools« button", () => {
expect(screen.getByRole("button", { name: "Developer tools" })).toBeInTheDocument();
});
});
});
});
});

View file

@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { setTheme } from "../src/theme"; import SettingsStore from "../src/settings/SettingsStore";
import { enumerateThemes, setTheme } from "../src/theme";
describe("theme", () => { describe("theme", () => {
describe("setTheme", () => { describe("setTheme", () => {
@ -124,4 +125,25 @@ describe("theme", () => {
}); });
}); });
}); });
describe("enumerateThemes", () => {
it("should return a list of themes", () => {
jest.spyOn(SettingsStore, "getValue").mockReturnValue([{ name: "pink" }]);
expect(enumerateThemes()).toEqual({
"light": "Light",
"light-high-contrast": "Light high contrast",
"dark": "Dark",
"custom-pink": "pink",
});
});
it("should be robust to malformed custom_themes values", () => {
jest.spyOn(SettingsStore, "getValue").mockReturnValue([23]);
expect(enumerateThemes()).toEqual({
"light": "Light",
"light-high-contrast": "Light high contrast",
"dark": "Dark",
});
});
});
}); });