You've already forked matrix-react-sdk
mirror of
https://github.com/matrix-org/matrix-react-sdk.git
synced 2025-07-28 15:22:05 +03:00
TAC: Release Announcement (#12380)
* WIP * Store the release announcements in the account settings * Update TAC release announcement description * Fix settings content comparison * Add logging in case of failure * Watch settings changes * I add release announcement settings to disable it * Disable release announcement in e2e test * Add release announcement in e2e test * Add tests for ReleaseAnnouncementStore.ts * Update compound-web to `3.3.0` * Update TAC tests * Update Labs tests * Nits * Add test for ReleaseAnnouncement.tsx * Update `@vector-im/compound-web` * Add playwright snapshot * Delete false playwright screenshot * Wait for EW to be displayed after reload * Add screenshot * Clean util file * Renaming and comments fixing * Use second store instead of looking in the store. --------- Co-authored-by: R Midhun Suresh <hi@midhun.dev>
This commit is contained in:
48
test/components/structures/ReleaseAnnouncement-test.tsx
Normal file
48
test/components/structures/ReleaseAnnouncement-test.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2024 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, waitFor } from "@testing-library/react";
|
||||
|
||||
import { ReleaseAnnouncement } from "../../../src/components/structures/ReleaseAnnouncement";
|
||||
|
||||
describe("ReleaseAnnouncement", () => {
|
||||
function renderReleaseAnnouncement() {
|
||||
return render(
|
||||
<ReleaseAnnouncement
|
||||
feature="threadsActivityCentre"
|
||||
header="header"
|
||||
description="description"
|
||||
closeLabel="close"
|
||||
>
|
||||
<div>content</div>
|
||||
</ReleaseAnnouncement>,
|
||||
);
|
||||
}
|
||||
|
||||
test("render the release announcement and close it", async () => {
|
||||
renderReleaseAnnouncement();
|
||||
|
||||
// The release announcement is displayed
|
||||
expect(screen.queryByRole("dialog", { name: "header" })).toBeDefined();
|
||||
// Click on the close button in the release announcement
|
||||
screen.getByRole("button", { name: "close" }).click();
|
||||
// The release announcement should be hidden after the close button is clicked
|
||||
await waitFor(() => expect(screen.queryByRole("dialog", { name: "header" })).toBeNull());
|
||||
});
|
||||
});
|
@ -60,7 +60,7 @@ describe("<LabsUserSettingsTab />", () => {
|
||||
// non-beta labs section
|
||||
expect(screen.getByText("Early previews")).toBeInTheDocument();
|
||||
const labsSections = container.getElementsByClassName("mx_SettingsSubsection");
|
||||
expect(labsSections).toHaveLength(10);
|
||||
expect(labsSections).toHaveLength(11);
|
||||
});
|
||||
|
||||
describe("Rust crypto setting", () => {
|
||||
|
@ -28,6 +28,8 @@ import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
|
||||
import { stubClient } from "../../../test-utils";
|
||||
import { populateThread } from "../../../test-utils/threads";
|
||||
import DMRoomMap from "../../../../src/utils/DMRoomMap";
|
||||
import SettingsStore from "../../../../src/settings/SettingsStore";
|
||||
import { SettingLevel } from "../../../../src/settings/SettingLevel";
|
||||
|
||||
describe("ThreadsActivityCentre", () => {
|
||||
const getTACButton = () => {
|
||||
@ -101,11 +103,23 @@ describe("ThreadsActivityCentre", () => {
|
||||
);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await SettingsStore.setValue("feature_release_announcement", null, SettingLevel.DEVICE, false);
|
||||
});
|
||||
|
||||
it("should render the threads activity centre button", async () => {
|
||||
renderTAC();
|
||||
expect(getTACButton()).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render the release announcement", async () => {
|
||||
// Enable release announcement
|
||||
await SettingsStore.setValue("feature_release_announcement", null, SettingLevel.DEVICE, true);
|
||||
|
||||
renderTAC();
|
||||
expect(document.body).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should render the threads activity centre button and the display label", async () => {
|
||||
renderTAC({ displayButtonLabel: true });
|
||||
expect(getTACButton()).toBeInTheDocument();
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
exports[`ThreadsActivityCentre renders notifications matching the snapshot 1`] = `
|
||||
<div
|
||||
aria-labelledby="radix-20"
|
||||
aria-labelledby="radix-21"
|
||||
aria-orientation="vertical"
|
||||
class="_menu_1x5h1_17"
|
||||
data-align="end"
|
||||
@ -11,7 +11,7 @@ exports[`ThreadsActivityCentre renders notifications matching the snapshot 1`] =
|
||||
data-side="right"
|
||||
data-state="open"
|
||||
dir="ltr"
|
||||
id="radix-21"
|
||||
id="radix-22"
|
||||
role="menu"
|
||||
style="outline: none; --radix-dropdown-menu-content-transform-origin: var(--radix-popper-transform-origin); --radix-dropdown-menu-content-available-width: var(--radix-popper-available-width); --radix-dropdown-menu-content-available-height: var(--radix-popper-available-height); --radix-dropdown-menu-trigger-width: var(--radix-popper-anchor-width); --radix-dropdown-menu-trigger-height: var(--radix-popper-anchor-height); pointer-events: auto;"
|
||||
tabindex="-1"
|
||||
@ -127,7 +127,7 @@ exports[`ThreadsActivityCentre renders notifications matching the snapshot 1`] =
|
||||
|
||||
exports[`ThreadsActivityCentre should match snapshot when empty 1`] = `
|
||||
<div
|
||||
aria-labelledby="radix-28"
|
||||
aria-labelledby="radix-29"
|
||||
aria-orientation="vertical"
|
||||
class="_menu_1x5h1_17"
|
||||
data-align="end"
|
||||
@ -136,7 +136,7 @@ exports[`ThreadsActivityCentre should match snapshot when empty 1`] = `
|
||||
data-side="right"
|
||||
data-state="open"
|
||||
dir="ltr"
|
||||
id="radix-29"
|
||||
id="radix-30"
|
||||
role="menu"
|
||||
style="outline: none; --radix-dropdown-menu-content-transform-origin: var(--radix-popper-transform-origin); --radix-dropdown-menu-content-available-width: var(--radix-popper-available-width); --radix-dropdown-menu-content-available-height: var(--radix-popper-available-height); --radix-dropdown-menu-trigger-width: var(--radix-popper-anchor-width); --radix-dropdown-menu-trigger-height: var(--radix-popper-anchor-height); pointer-events: auto;"
|
||||
tabindex="-1"
|
||||
@ -161,7 +161,7 @@ exports[`ThreadsActivityCentre should match snapshot when empty 1`] = `
|
||||
|
||||
exports[`ThreadsActivityCentre should order the room with the same notification level by most recent 1`] = `
|
||||
<div
|
||||
aria-labelledby="radix-31"
|
||||
aria-labelledby="radix-32"
|
||||
aria-orientation="vertical"
|
||||
class="_menu_1x5h1_17"
|
||||
data-align="end"
|
||||
@ -170,7 +170,7 @@ exports[`ThreadsActivityCentre should order the room with the same notification
|
||||
data-side="right"
|
||||
data-state="open"
|
||||
dir="ltr"
|
||||
id="radix-32"
|
||||
id="radix-33"
|
||||
role="menu"
|
||||
style="outline: none; --radix-dropdown-menu-content-transform-origin: var(--radix-popper-transform-origin); --radix-dropdown-menu-content-available-width: var(--radix-popper-available-width); --radix-dropdown-menu-content-available-height: var(--radix-popper-available-height); --radix-dropdown-menu-trigger-width: var(--radix-popper-anchor-width); --radix-dropdown-menu-trigger-height: var(--radix-popper-anchor-height); pointer-events: auto;"
|
||||
tabindex="-1"
|
||||
@ -331,3 +331,129 @@ exports[`ThreadsActivityCentre should order the room with the same notification
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`ThreadsActivityCentre should render the release announcement 1`] = `
|
||||
<body>
|
||||
<div
|
||||
data-floating-ui-inert=""
|
||||
>
|
||||
<div
|
||||
class="mx_ThreadsActivityCentre_container"
|
||||
>
|
||||
<button
|
||||
aria-controls="floating-ui-3"
|
||||
aria-expanded="true"
|
||||
aria-haspopup="dialog"
|
||||
aria-label="Threads"
|
||||
class="_icon-button_16nk7_17 mx_ThreadsActivityCentreButton"
|
||||
data-state="open"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="_indicator-icon_133tf_26"
|
||||
style="--cpd-icon-button-size: 100%;"
|
||||
>
|
||||
<div
|
||||
class="mx_ThreadsActivityCentreButton_Icon"
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
<span
|
||||
data-floating-ui-focus-guard=""
|
||||
data-type="outside"
|
||||
role="button"
|
||||
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
<span
|
||||
aria-owns="undefined"
|
||||
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
|
||||
/>
|
||||
<span
|
||||
data-floating-ui-focus-guard=""
|
||||
data-type="outside"
|
||||
role="button"
|
||||
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
data-floating-ui-portal=""
|
||||
id="undefined"
|
||||
>
|
||||
<span
|
||||
data-floating-ui-focus-guard=""
|
||||
data-floating-ui-inert=""
|
||||
data-type="inside"
|
||||
role="button"
|
||||
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
<div
|
||||
aria-describedby="floating-ui-2"
|
||||
aria-labelledby="floating-ui-1"
|
||||
class="_content_1oa1y_17"
|
||||
id="floating-ui-3"
|
||||
role="dialog"
|
||||
style="position: absolute; left: 0px; top: 0px; transform: translate(0px, 0px);"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_arrow_1oa1y_62"
|
||||
height="20"
|
||||
style="position: absolute; pointer-events: none; right: calc(100% - 0px); transform: rotate(90deg);"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
>
|
||||
<path
|
||||
d="M0,0 H20 L10,12 Q10,12 10,12 Z"
|
||||
stroke="none"
|
||||
/>
|
||||
<clippath
|
||||
id="floating-ui-5"
|
||||
>
|
||||
<rect
|
||||
height="20"
|
||||
width="20"
|
||||
x="0"
|
||||
y="0"
|
||||
/>
|
||||
</clippath>
|
||||
</svg>
|
||||
<h3
|
||||
class="_typography_yh5dq_162 _font-body-lg-semibold_yh5dq_83 _header_1oa1y_46"
|
||||
id="floating-ui-1"
|
||||
>
|
||||
Threads Activity Centre
|
||||
</h3>
|
||||
<span
|
||||
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 _description_1oa1y_52"
|
||||
id="floating-ui-2"
|
||||
>
|
||||
Threads notifications have moved, find them here from now on.
|
||||
</span>
|
||||
<button
|
||||
class="_button_dyfp8_17 _button_1oa1y_57"
|
||||
data-kind="secondary"
|
||||
data-size="sm"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
OK
|
||||
</button>
|
||||
</div>
|
||||
<span
|
||||
data-floating-ui-focus-guard=""
|
||||
data-floating-ui-inert=""
|
||||
data-type="inside"
|
||||
role="button"
|
||||
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
</body>
|
||||
`;
|
||||
|
125
test/stores/ReleaseAnnouncementStore-test.tsx
Normal file
125
test/stores/ReleaseAnnouncementStore-test.tsx
Normal file
@ -0,0 +1,125 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2024 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 { mocked } from "jest-mock";
|
||||
|
||||
import SettingsStore, { CallbackFn } from "../../src/settings/SettingsStore";
|
||||
import { Feature, ReleaseAnnouncementStore } from "../../src/stores/ReleaseAnnouncementStore";
|
||||
import { SettingLevel } from "../../src/settings/SettingLevel";
|
||||
|
||||
jest.mock("../../src/settings/SettingsStore");
|
||||
|
||||
describe("ReleaseAnnouncementStore", () => {
|
||||
let releaseAnnouncementStore: ReleaseAnnouncementStore;
|
||||
// Local settings
|
||||
// Instead of using the real SettingsStore, we use a local settings object
|
||||
// to avoid side effects between tests
|
||||
let settings: Record<string, any> = {};
|
||||
|
||||
beforeEach(() => {
|
||||
// Default settings
|
||||
settings = {
|
||||
feature_release_announcement: true,
|
||||
releaseAnnouncementData: {},
|
||||
};
|
||||
const watchCallbacks: Array<CallbackFn> = [];
|
||||
|
||||
mocked(SettingsStore.getValue).mockImplementation((setting: string) => {
|
||||
return settings[setting];
|
||||
});
|
||||
mocked(SettingsStore.setValue).mockImplementation(
|
||||
(settingName: string, roomId: string | null, level: SettingLevel, value: any): Promise<void> => {
|
||||
settings[settingName] = value;
|
||||
// we don't care about the parameters, just call the callbacks
|
||||
// @ts-ignore
|
||||
watchCallbacks.forEach((cb) => cb());
|
||||
return Promise.resolve();
|
||||
},
|
||||
);
|
||||
mocked(SettingsStore.isLevelSupported).mockReturnValue(true);
|
||||
mocked(SettingsStore.canSetValue).mockReturnValue(true);
|
||||
mocked(SettingsStore.watchSetting).mockImplementation((settingName: string, roomId: null, callback: any) => {
|
||||
watchCallbacks.push(callback);
|
||||
return "watcherId";
|
||||
});
|
||||
|
||||
releaseAnnouncementStore = new ReleaseAnnouncementStore();
|
||||
});
|
||||
|
||||
/**
|
||||
* Disables the release announcement feature.
|
||||
*/
|
||||
function disableReleaseAnnouncement() {
|
||||
settings["feature_release_announcement"] = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens to the next release announcement change event.
|
||||
*/
|
||||
function listenReleaseAnnouncementChanged() {
|
||||
return new Promise<Feature | null>((resolve) =>
|
||||
releaseAnnouncementStore.once("releaseAnnouncementChanged", resolve),
|
||||
);
|
||||
}
|
||||
|
||||
it("should be a singleton", () => {
|
||||
expect(ReleaseAnnouncementStore.instance).toBeDefined();
|
||||
});
|
||||
|
||||
it("should return null when the release announcement is disabled", async () => {
|
||||
disableReleaseAnnouncement();
|
||||
|
||||
expect(releaseAnnouncementStore.getReleaseAnnouncement()).toBeNull();
|
||||
|
||||
// Wait for the next release announcement change event
|
||||
const promise = listenReleaseAnnouncementChanged();
|
||||
// Call the next release announcement
|
||||
// because the release announcement is disabled, the next release announcement should be null
|
||||
await releaseAnnouncementStore.nextReleaseAnnouncement();
|
||||
expect(await promise).toBeNull();
|
||||
expect(releaseAnnouncementStore.getReleaseAnnouncement()).toBeNull();
|
||||
});
|
||||
|
||||
it("should return the next feature when the next release announcement is called", async () => {
|
||||
// Sanity check
|
||||
expect(releaseAnnouncementStore.getReleaseAnnouncement()).toBe("threadsActivityCentre");
|
||||
|
||||
const promise = listenReleaseAnnouncementChanged();
|
||||
await releaseAnnouncementStore.nextReleaseAnnouncement();
|
||||
// Currently there is only one feature, so the next feature should be null
|
||||
expect(await promise).toBeNull();
|
||||
expect(releaseAnnouncementStore.getReleaseAnnouncement()).toBeNull();
|
||||
|
||||
const secondStore = new ReleaseAnnouncementStore();
|
||||
// The TAC release announcement has been viewed, so it should be updated in the store account
|
||||
// The release announcement viewing states should be share among all instances (devices in the same account)
|
||||
expect(secondStore.getReleaseAnnouncement()).toBeNull();
|
||||
});
|
||||
|
||||
it("should listen to release announcement data changes in the store", async () => {
|
||||
const secondStore = new ReleaseAnnouncementStore();
|
||||
expect(secondStore.getReleaseAnnouncement()).toBe("threadsActivityCentre");
|
||||
|
||||
const promise = listenReleaseAnnouncementChanged();
|
||||
await secondStore.nextReleaseAnnouncement();
|
||||
|
||||
// Currently there is only one feature, so the next feature should be null
|
||||
expect(await promise).toBeNull();
|
||||
expect(releaseAnnouncementStore.getReleaseAnnouncement()).toBeNull();
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user