Apply strictNullChecks
to src/components/views/spaces/*
(#10517)
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
parent
209f5bdf33
commit
c0db739d81
7 changed files with 161 additions and 20 deletions
|
@ -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",
|
||||||
);
|
);
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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,
|
||||||
|
|
25
src/theme.ts
25
src/theme.ts
|
@ -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) {
|
||||||
|
|
96
test/components/views/spaces/QuickSettingsButton-test.tsx
Normal file
96
test/components/views/spaces/QuickSettingsButton-test.tsx
Normal 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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -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",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue