Skip to content

Commit

Permalink
feat(redlist): add red list option in settings
Browse files Browse the repository at this point in the history
  • Loading branch information
marc.sirisak committed Oct 24, 2024
1 parent a4867ec commit 55f272d
Show file tree
Hide file tree
Showing 8 changed files with 516 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import SettingsSubsection, { SettingsSubsectionText } from "../../shared/Setting
import { SDKContext } from "../../../../../contexts/SDKContext";
import UserPersonalInfoSettings from "../../UserPersonalInfoSettings";
import { useMatrixClientContext } from "../../../../../contexts/MatrixClientContext";
import TchapRedListSettings from "../../../../../../../../src/tchap/components/views/settings/tabs/user/TchapRedListSettings"; // :TCHAP: red-list-settings

interface IProps {
closeSettingsFn: () => void;
Expand Down Expand Up @@ -194,6 +195,9 @@ const AccountUserSettingsTab: React.FC<IProps> = ({ closeSettingsFn }) => {
onPasswordChanged={onPasswordChanged}
onPasswordChangeError={onPasswordChangeError}
/>
{/* :TCHAP: red-list-settings */}
<TchapRedListSettings />
{/* end :TCHAP: */}
</SettingsSection>
{accountManagementSection}
</SettingsTab>
Expand Down
12 changes: 12 additions & 0 deletions modules/tchap-translations/tchap_translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -881,5 +881,17 @@
"encryption|verification|verification_skip_warning": {
"en": "Without verifying, you won't have access to all your messages and may appear as untrusted to others.",
"fr": "Sans vérification, vous n'aurez pas accès à tous vos messages et votre compte n'apparaitra pas comme fiable pour les autres utilisateurs"
},
"settings|general|redlist_description": {
"en": "The other users won't be able to discover my account during their search",
"fr": "Les autres utilisateurs ne pourront pas découvrir mon compte lors de leurs recherches"
},
"settings|general|redlist_title": {
"en": "Register my account on the red list",
"fr": "Inscrire mon compte sur la liste rouge"
},
"settings|general|redlist": {
"en": "Red List",
"fr": "Liste rouge"
}
}
6 changes: 6 additions & 0 deletions patches/subtree-modifications.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,5 +94,11 @@
"src/components/views/right_panel/RightPanelTabs.tsx",
"src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx"
]
},
"red-list-settings": {
"issue": "https://github.com/tchapgouv/tchap-web-v4/issues/1134",
"files": [
"src/components/views/settings/tabs/user/AccountUserSettingsTab.tsx"
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React, { useEffect, useState } from 'react';
import LabelledToggleSwitch from 'matrix-react-sdk/src/components/views/elements/LabelledToggleSwitch';
import SettingsSubsection from 'matrix-react-sdk/src/components/views/settings/shared/SettingsSubsection';
import { _t } from "matrix-react-sdk/src/languageHandler";
import TchapUtils from '../../../../../util/TchapUtils';

interface TchapRedListSettingsProps {
}

/**
* A group of settings views to allow the user to set their profile information.
*/
const TchapRedListSettings: React.FC<TchapRedListSettingsProps> = () => {

const [isOnRedList, setIsOnRedList] = useState(false);
const [loading, setLoading] = useState(false);

useEffect(() => {
const fetchInitialRedListValue = async () => {
const initialValue = await TchapUtils.getUserRedListInfo();
console.log("initialRedListValue", initialValue);
setIsOnRedList(initialValue);
}
fetchInitialRedListValue();
}, []);

const _onRedlistOptionChange = async (checked: boolean) => {
try {
if (!loading) {
setLoading(true)
await TchapUtils.setUserRedListInfo(checked);
setIsOnRedList(checked);
}
setLoading(false)
} catch (err) {
console.error("Error setting AccountData 'im.vector.hide_profile': " + err);
setIsOnRedList(!checked); // return to initial state because of error
setLoading(false)
}
}

return <SettingsSubsection
heading={_t("settings|general|redlist")}
stretchContent>
<LabelledToggleSwitch value={isOnRedList}
onChange={(checked: boolean) => _onRedlistOptionChange(checked)}
label={_t('settings|general|redlist_title')}
disabled={loading}
/>
<p className="mx_SettingsTab_subsectionText">
({_t("settings|general|redlist_description")})
</p>
</SettingsSubsection>
}

export default TchapRedListSettings;
12 changes: 12 additions & 0 deletions src/tchap/util/TchapUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,4 +282,16 @@ export default class TchapUtils {
}
}

static async getUserRedListInfo() : Promise<boolean> {
const client = MatrixClientPeg.safeGet();
const accountData = await client.getAccountData('im.vector.hide_profile');
console.log('accountData', accountData?.getContent());
return !!accountData?.getContent().hide_profile;
}

static async setUserRedListInfo(isOnRedList: boolean) : Promise<void> {
const client = MatrixClientPeg.safeGet();
client.setAccountData('im.vector.hide_profile', {hide_profile: isOnRedList});
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { render } from "@testing-library/react";
import React from "react";
import { MatrixClient } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import { MockedObject } from "jest-mock";
import AccountUserSettingsTab from "matrix-react-sdk/src/components/views/settings/tabs/user/AccountUserSettingsTab";
import { SdkContextClass, SDKContext } from "matrix-react-sdk/src/contexts/SDKContext";
import SettingsStore from "matrix-react-sdk/src/settings/SettingsStore";
import { UIFeature } from "matrix-react-sdk/src/settings/UIFeature";
import { OidcClientStore } from "matrix-react-sdk/src/stores/oidc/OidcClientStore";
import MatrixClientContext from "matrix-react-sdk/src/contexts/MatrixClientContext";
import {
getMockClientWithEventEmitter,
mockClientMethodsServer,
mockClientMethodsUser,
mockPlatformPeg,
} from "matrix-react-sdk/test/test-utils";


jest.mock(
"matrix-react-sdk/src/components/views/settings/ChangePassword",
() =>
({ onError, onFinished }: { onError: (e: Error) => void; onFinished: () => void }) => {
return <button>Mock change password</button>;
},
);

describe("<AccountUserSettingsTab />", () => {
const defaultProps = {
closeSettingsFn: jest.fn(),
};

const userId = "@alice:server.org";
let mockClient: MockedObject<MatrixClient>;

let stores: SdkContextClass;

const getComponent = () => (
<MatrixClientContext.Provider value={mockClient}>
<SDKContext.Provider value={stores}>
<AccountUserSettingsTab {...defaultProps} />
</SDKContext.Provider>
</MatrixClientContext.Provider>
);

beforeEach(() => {
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
mockPlatformPeg();
jest.clearAllMocks();
jest.spyOn(SettingsStore, "getValue").mockRestore();
jest.spyOn(logger, "error").mockRestore();

mockClient = getMockClientWithEventEmitter({
...mockClientMethodsUser(userId),
...mockClientMethodsServer(),
getCapabilities: jest.fn(),
getThreePids: jest.fn(),
getIdentityServerUrl: jest.fn(),
deleteThreePid: jest.fn(),
});

mockClient.getCapabilities.mockResolvedValue({});
mockClient.getThreePids.mockResolvedValue({
threepids: [],
});
mockClient.deleteThreePid.mockResolvedValue({
id_server_unbind_result: "success",
});

stores = new SdkContextClass();
stores.client = mockClient;
// stub out this store completely to avoid mocking initialisation
const mockOidcClientStore = {} as unknown as OidcClientStore;
jest.spyOn(stores, "oidcClientStore", "get").mockReturnValue(mockOidcClientStore);
jest.spyOn(SettingsStore, "getValue").mockImplementation(
(settingName: string) => settingName === UIFeature.Deactivate,
);
});

afterEach(() => {
jest.restoreAllMocks();
});


describe("common view snapshot", () => {

it("should render section when account deactivation feature is enabled", () => {
const { container } = render(getComponent());

expect(container).toMatchSnapshot();
});
});


});
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { act, logRoles, render, screen, waitFor } from '@testing-library/react';
import React from 'react';
import { flushPromises, stubClient } from 'matrix-react-sdk/test/test-utils';
import { Mocked, mocked } from 'jest-mock';

import TchapRedListSettings from '~tchap-web/src/tchap/components/views/settings/tabs/user/TchapRedListSettings';
import { MatrixClient } from 'matrix-js-sdk/src/matrix';
import TchapUtils from '~tchap-web/src/tchap/util/TchapUtils';
import userEvent from '@testing-library/user-event';

jest.mock("~tchap-web/src/tchap/util/TchapUtils");

describe("TchapRedListSettings", () => {
let client: Mocked<MatrixClient>;

const renderComponent = () => render(<TchapRedListSettings />);

beforeEach(() => {
client = mocked(stubClient());
});


it("should red list should be activated when initial value is true", async () => {
mocked(TchapUtils.getUserRedListInfo).mockResolvedValue(true);
mocked(TchapUtils.setUserRedListInfo).mockResolvedValue();

renderComponent();

await flushPromises();

const switchElm = screen.getByRole("switch");

expect(switchElm).toHaveAttribute('aria-checked', "true");

});

it("should red list should be desactivated when initial value is false", async () => {
mocked(TchapUtils.getUserRedListInfo).mockResolvedValue(false);
mocked(TchapUtils.setUserRedListInfo).mockResolvedValue();

renderComponent();

await flushPromises();

const switchElm = screen.getByRole("switch");

expect(switchElm).toHaveAttribute('aria-checked', "false");
});


it("should remove user from red list", async () => {
// initial value
mocked(TchapUtils.getUserRedListInfo).mockResolvedValue(true);
mocked(TchapUtils.setUserRedListInfo).mockResolvedValue();

renderComponent();

await flushPromises();

const switchElm = screen.getByRole("switch");

act(() => {
userEvent.click(switchElm);
})

waitFor(() => {
expect(switchElm).toHaveAttribute('aria-checked', "false");
})
});

it("should put user on red list", async () => {
// initial value
mocked(TchapUtils.getUserRedListInfo).mockResolvedValue(false);
mocked(TchapUtils.setUserRedListInfo).mockResolvedValue();

renderComponent();

await flushPromises();

const switchElm = screen.getByRole("switch");

act(() => {
userEvent.click(switchElm);
})

waitFor(() => {
expect(switchElm).toHaveAttribute('aria-checked', "true");
})
});

it("should get back to initial value if throws an error", async () => {
// initial value
mocked(TchapUtils.getUserRedListInfo).mockResolvedValue(false);
mocked(TchapUtils.setUserRedListInfo).mockRejectedValue(new Error("error test"));

renderComponent();

await flushPromises();

const switchElm = screen.getByRole("switch");

act(() => {
userEvent.click(switchElm);
})
waitFor(() => {
expect(switchElm).toHaveAttribute('aria-checked', "false");
});
});
});
Loading

0 comments on commit 55f272d

Please sign in to comment.