From 05cc5f62ddcd7f28567e6d64799a01c18dda4721 Mon Sep 17 00:00:00 2001 From: Kerry Date: Mon, 1 Aug 2022 08:47:13 +0200 Subject: [PATCH] test UserSettingsDialog (#9118) --- src/components/structures/TabbedView.tsx | 7 +- .../views/dialogs/UserSettingsDialog.tsx | 6 +- .../views/dialogs/UserSettingsDialog-test.tsx | 157 ++++++++++++++++++ .../UserSettingsDialog-test.tsx.snap | 141 ++++++++++++++++ test/test-utils/client.ts | 14 ++ 5 files changed, 321 insertions(+), 4 deletions(-) create mode 100644 test/components/views/dialogs/UserSettingsDialog-test.tsx create mode 100644 test/components/views/dialogs/__snapshots__/UserSettingsDialog-test.tsx.snap diff --git a/src/components/structures/TabbedView.tsx b/src/components/structures/TabbedView.tsx index 91e64946ad..a55e514073 100644 --- a/src/components/structures/TabbedView.tsx +++ b/src/components/structures/TabbedView.tsx @@ -117,7 +117,12 @@ export default class TabbedView extends React.Component { const label = _t(tab.label); return ( - + { tabIcon } { label } diff --git a/src/components/views/dialogs/UserSettingsDialog.tsx b/src/components/views/dialogs/UserSettingsDialog.tsx index 8f5e1a6108..6d4fe15fcc 100644 --- a/src/components/views/dialogs/UserSettingsDialog.tsx +++ b/src/components/views/dialogs/UserSettingsDialog.tsx @@ -46,7 +46,7 @@ interface IState { } export default class UserSettingsDialog extends React.Component { - private mjolnirWatcher: string; + private mjolnirWatcher: string | undefined; constructor(props) { super(props); @@ -61,7 +61,7 @@ export default class UserSettingsDialog extends React.Component } public componentWillUnmount(): void { - SettingsStore.unwatchSetting(this.mjolnirWatcher); + this.mjolnirWatcher && SettingsStore.unwatchSetting(this.mjolnirWatcher); } private mjolnirChanged: CallbackFn = (settingName, roomId, atLevel, newValue) => { @@ -70,7 +70,7 @@ export default class UserSettingsDialog extends React.Component }; private getTabs() { - const tabs = []; + const tabs: Tab[] = []; tabs.push(new Tab( UserTab.General, diff --git a/test/components/views/dialogs/UserSettingsDialog-test.tsx b/test/components/views/dialogs/UserSettingsDialog-test.tsx new file mode 100644 index 0000000000..50dc3f97aa --- /dev/null +++ b/test/components/views/dialogs/UserSettingsDialog-test.tsx @@ -0,0 +1,157 @@ +/* +Copyright 2022 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, { ReactElement } from 'react'; +import { render } from '@testing-library/react'; +import { mocked } from 'jest-mock'; + +import SettingsStore, { CallbackFn } from '../../../../src/settings/SettingsStore'; +import SdkConfig from '../../../../src/SdkConfig'; +import { UserTab } from '../../../../src/components/views/dialogs/UserTab'; +import UserSettingsDialog from '../../../../src/components/views/dialogs/UserSettingsDialog'; +import { IDialogProps } from '../../../../src/components/views/dialogs/IDialogProps'; +import { + getMockClientWithEventEmitter, + mockClientMethodsUser, + mockClientMethodsServer, + mockPlatformPeg, +} from '../../../test-utils'; +import { UIFeature } from '../../../../src/settings/UIFeature'; +import { SettingLevel } from '../../../../src/settings/SettingLevel'; + +mockPlatformPeg({ + supportsSpellCheckSettings: jest.fn().mockReturnValue(false), + getAppVersion: jest.fn().mockResolvedValue('1'), +}); + +jest.mock('../../../../src/settings/SettingsStore', () => ({ + getValue: jest.fn(), + getValueAt: jest.fn(), + canSetValue: jest.fn(), + monitorSetting: jest.fn(), + watchSetting: jest.fn(), + unwatchSetting: jest.fn(), + getFeatureSettingNames: jest.fn(), + getBetaInfo: jest.fn(), +})); + +jest.mock('../../../../src/SdkConfig', () => ({ + get: jest.fn(), +})); + +describe('', () => { + const userId = '@alice:server.org'; + const mockSettingsStore = mocked(SettingsStore); + const mockSdkConfig = mocked(SdkConfig); + getMockClientWithEventEmitter({ + ...mockClientMethodsUser(userId), + ...mockClientMethodsServer(), + }); + + const defaultProps = { onFinished: jest.fn() }; + const getComponent = (props: Partial = {}): ReactElement => ( + + ); + + beforeEach(() => { + jest.clearAllMocks(); + mockSettingsStore.getValue.mockReturnValue(false); + mockSettingsStore.getFeatureSettingNames.mockReturnValue([]); + mockSdkConfig.get.mockReturnValue({ brand: 'Test' }); + }); + + const getActiveTabLabel = (container) => container.querySelector('.mx_TabbedView_tabLabel_active').textContent; + const getActiveTabHeading = (container) => container.querySelector('.mx_SettingsTab_heading').textContent; + + it('should render general settings tab when no initialTabId', () => { + const { container } = render(getComponent()); + + expect(getActiveTabLabel(container)).toEqual('General'); + expect(getActiveTabHeading(container)).toEqual('General'); + }); + + it('should render initial tab when initialTabId is set', () => { + const { container } = render(getComponent({ initialTabId: UserTab.Help })); + + expect(getActiveTabLabel(container)).toEqual('Help & About'); + expect(getActiveTabHeading(container)).toEqual('Help & About'); + }); + + it('should render general tab if initialTabId tab cannot be rendered', () => { + // mjolnir tab is only rendered in some configs + const { container } = render(getComponent({ initialTabId: UserTab.Mjolnir })); + + expect(getActiveTabLabel(container)).toEqual('General'); + expect(getActiveTabHeading(container)).toEqual('General'); + }); + + it('renders tabs correctly', () => { + const { container } = render(getComponent()); + expect(container.querySelectorAll('.mx_TabbedView_tabLabel')).toMatchSnapshot(); + }); + + it('renders ignored users tab when feature_mjolnir is enabled', () => { + mockSettingsStore.getValue.mockImplementation((settingName) => settingName === "feature_mjolnir"); + const { getByTestId } = render(getComponent()); + expect(getByTestId(`settings-tab-${UserTab.Mjolnir}`)).toBeTruthy(); + }); + + it('renders voip tab when voip is enabled', () => { + mockSettingsStore.getValue.mockImplementation((settingName) => settingName === UIFeature.Voip); + const { getByTestId } = render(getComponent()); + expect(getByTestId(`settings-tab-${UserTab.Voice}`)).toBeTruthy(); + }); + + it('renders labs tab when show_labs_settings is enabled in config', () => { + mockSdkConfig.get.mockImplementation((configName) => configName === "show_labs_settings"); + const { getByTestId } = render(getComponent()); + expect(getByTestId(`settings-tab-${UserTab.Labs}`)).toBeTruthy(); + }); + + it('renders labs tab when some feature is in beta', () => { + mockSettingsStore.getFeatureSettingNames.mockReturnValue(['feature_beta_setting', 'feature_just_normal_labs']); + mockSettingsStore.getBetaInfo.mockImplementation( + (settingName) => settingName === 'feature_beta_setting' ? {} as any : undefined, + ); + const { getByTestId } = render(getComponent()); + expect(getByTestId(`settings-tab-${UserTab.Labs}`)).toBeTruthy(); + }); + + it('watches feature_mjolnir setting', () => { + let watchSettingCallback: CallbackFn = jest.fn(); + + mockSettingsStore.watchSetting.mockImplementation((settingName, roomId, callback) => { + watchSettingCallback = callback; + return `mock-watcher-id-${settingName}`; + }); + + const { queryByTestId, unmount } = render(getComponent()); + expect(queryByTestId(`settings-tab-${UserTab.Mjolnir}`)).toBeFalsy(); + + expect(mockSettingsStore.watchSetting.mock.calls[0][0]).toEqual('feature_mjolnir'); + + // call the watch setting callback + watchSettingCallback("feature_mjolnir", '', SettingLevel.ACCOUNT, true, true); + + // tab is rendered now + expect(queryByTestId(`settings-tab-${UserTab.Mjolnir}`)).toBeTruthy(); + + unmount(); + + // unwatches mjolnir after unmount + expect(mockSettingsStore.unwatchSetting).toHaveBeenCalledWith('mock-watcher-id-feature_mjolnir'); + }); +}); diff --git a/test/components/views/dialogs/__snapshots__/UserSettingsDialog-test.tsx.snap b/test/components/views/dialogs/__snapshots__/UserSettingsDialog-test.tsx.snap new file mode 100644 index 0000000000..fb887a304d --- /dev/null +++ b/test/components/views/dialogs/__snapshots__/UserSettingsDialog-test.tsx.snap @@ -0,0 +1,141 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders tabs correctly 1`] = ` +NodeList [ +
+ + + General + +
, +
+ + + Appearance + +
, +
+ + + Notifications + +
, +
+ + + Preferences + +
, +
+ + + Keyboard + +
, +
+ + + Sidebar + +
, +
+ + + Security & Privacy + +
, +
+ + + Labs + +
, +
+ + + Help & About + +
, +] +`; diff --git a/test/test-utils/client.ts b/test/test-utils/client.ts index ea21b81dd8..453856eb26 100644 --- a/test/test-utils/client.ts +++ b/test/test-utils/client.ts @@ -68,6 +68,8 @@ export const mockClientMethodsUser = (userId = '@alice:domain') => ({ isGuest: jest.fn().mockReturnValue(false), mxcUrlToHttp: jest.fn().mockReturnValue('mock-mxcUrlToHttp'), credentials: { userId }, + getThreePids: jest.fn().mockResolvedValue({ threepids: [] }), + getAccessToken: jest.fn(), }); /** @@ -82,3 +84,15 @@ export const mockClientMethodsEvents = () => ({ decryptEventIfNeeded: jest.fn(), getPushActionsForEvent: jest.fn(), }); + +/** + * Returns basic mocked client methods related to server support + */ +export const mockClientMethodsServer = (): Partial, unknown>> => ({ + doesServerSupportSeparateAddAndBind: jest.fn(), + getIdentityServerUrl: jest.fn(), + getHomeserverUrl: jest.fn(), + getCapabilities: jest.fn().mockReturnValue({}), + doesServerSupportUnstableFeature: jest.fn().mockResolvedValue(false), +}); +