Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion playwright/e2e/crypto/history-sharing.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ test.describe("History sharing", function () {
// Bob should now be able to decrypt the event
await expect(bobPage.getByText("A message from Alice")).toBeVisible();

const mask = [bobPage.locator(".mx_MessageTimestamp")];
// Exclude message timestamps and RR avatars from the screenshot. Bob sometimes sees Alice's RR on the
// previous event, which is surprising but not what we're testing here.
Copy link
Member

@t3chguy t3chguy Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds like you're introducing a flaky test as per my comment c1d5f1a

css is what you want rather than mask to hide things you want excluded from a screenshot due to inconsistent position, rather than inconsistent content which is what a mask helps with

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm afraid I can't see your comment there.

It seems like it was already flaky, because it passed in CI and failed on my machine. Given it still passes in CI, having updated the screenshot on my machine, I hope this has made it less flaky.

Copy link
Member

@t3chguy t3chguy Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if anything it'd make it more flaky, as it makes it vary more rather than less, by the size difference between the circle and the square mask. Though, likely both are enough to trigger the min pixels difference check

const mask = [bobPage.locator(".mx_MessageTimestamp"), bobPage.locator(".mx_ReadReceiptGroup_container")];
await expect(bobPage.locator(".mx_RoomView_body")).toMatchScreenshot("shared-history-invite-accepted.png", {
mask,
});
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 21 additions & 1 deletion src/components/views/rooms/RoomHeader/RoomHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ import NotificationsIcon from "@vector-im/compound-design-tokens/assets/web/icon
import VerifiedIcon from "@vector-im/compound-design-tokens/assets/web/icons/verified";
import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error-solid";
import PublicIcon from "@vector-im/compound-design-tokens/assets/web/icons/public";
import { JoinRule, type Room } from "matrix-js-sdk/src/matrix";
import { HistoryVisibility, JoinRule, type Room } from "matrix-js-sdk/src/matrix";
import { type ViewRoomOpts } from "@matrix-org/react-sdk-module-api/lib/lifecycles/RoomViewLifecycle";
import { Flex, Box } from "@element-hq/web-shared-components";
import { CallType } from "matrix-js-sdk/src/webrtc/call";
import { HistoryIcon } from "@vector-im/compound-design-tokens/assets/web/icons";

import { useRoomName } from "../../../../hooks/useRoomName.ts";
import { RightPanelPhases } from "../../../../stores/right-panel/RightPanelStorePhases.ts";
Expand Down Expand Up @@ -55,6 +56,7 @@ import { useScopedRoomContext } from "../../../../contexts/ScopedRoomContext.tsx
import { ToggleableIcon } from "./toggle/ToggleableIcon.tsx";
import { CurrentRightPanelPhaseContextProvider } from "../../../../contexts/CurrentRightPanelPhaseContext.tsx";
import { LocalRoom } from "../../../../models/LocalRoom.ts";
import { useIsEncrypted } from "../../../../hooks/useIsEncrypted.ts";

function RoomHeaderButtons({
room,
Expand Down Expand Up @@ -401,8 +403,11 @@ export default function RoomHeader({
const client = useMatrixClientContext();
const roomName = useRoomName(room);
const joinRule = useRoomState(room, (state) => state.getJoinRule());
const historyVisibility = useRoomState(room, (state) => state.getHistoryVisibility());
const historySharingEnabled = useFeatureEnabled("feature_share_history_on_invite");
const dmMember = useDmMember(room);
const isDirectMessage = !!dmMember;
const isRoomEncrypted = useIsEncrypted(client, room);
const e2eStatus = useEncryptionStatus(client, room);
const askToJoinEnabled = useFeatureEnabled("feature_ask_to_join");
const onAvatarClick = (): void => {
Expand Down Expand Up @@ -484,6 +489,21 @@ export default function RoomHeader({
/>
</Tooltip>
)}

{isRoomEncrypted &&
historySharingEnabled &&
(historyVisibility === HistoryVisibility.Shared ||
historyVisibility === HistoryVisibility.WorldReadable) && (
<Tooltip label={_t("room|header|shared_history_tooltip")} placement="right">
<HistoryIcon
width="16px"
height="16px"
className="mx_RoomHeader_icon"
color="var(--cpd-color-icon-info-primary)"
aria-label={_t("room|header|shared_history_tooltip")}
/>
</Tooltip>
)}
</Text>
</Box>
</button>
Expand Down
3 changes: 2 additions & 1 deletion src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -2031,7 +2031,8 @@
"one": "Asking to join",
"other": "%(count)s people asking to join"
},
"room_is_public": "This room is public"
"room_is_public": "This room is public",
"shared_history_tooltip": "New members see history"
},
"header_avatar_open_settings_label": "Open room settings",
"header_face_pile_tooltip": "People",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import WidgetStore, { type IApp } from "../../../../../../src/stores/WidgetStore
import { UIFeature } from "../../../../../../src/settings/UIFeature";
import { SettingLevel } from "../../../../../../src/settings/SettingLevel";
import { ElementCallMemberEventType } from "../../../../../../src/call-types";
import { defaultWatchManager } from "../../../../../../src/settings/Settings.tsx";

jest.mock("../../../../../../src/utils/ShieldUtils");
jest.mock("../../../../../../src/hooks/right-panel/useCurrentPhase", () => ({
Expand Down Expand Up @@ -100,7 +101,7 @@ describe("RoomHeader", () => {
};
}

beforeEach(async () => {
beforeEach(() => {
client = stubClient();
room = new Room(ROOM_ID, client, "@alice:example.org", {
pendingEventOrdering: PendingEventOrdering.Detached,
Expand Down Expand Up @@ -708,6 +709,41 @@ describe("RoomHeader", () => {
});
});

it("shows a history icon if the room is encrypted and has shared history", async () => {
mocked(client.getCrypto()!).isEncryptionEnabledInRoom.mockResolvedValue(true);
await room.addLiveEvents(
[
new MatrixEvent({
type: "m.room.history_visibility",
content: { history_visibility: "shared" },
sender: MatrixClientPeg.get()!.getSafeUserId(),
state_key: "",
room_id: room.roomId,
}),
],
{ addToState: true },
);
let featureEnabled = true;
jest.spyOn(SettingsStore, "getValue").mockImplementation(
(flag) => flag === "feature_share_history_on_invite" && featureEnabled,
);

render(<RoomHeader room={room} />, getWrapper());
await waitFor(() => getByLabelText(document.body, "New members see history"));

// Disable the labs flag and check the icon disappears
featureEnabled = false;
act(() =>
defaultWatchManager.notifyUpdate(
"feature_share_history_on_invite",
null,
SettingLevel.DEVICE,
featureEnabled,
),
);
expect(queryByLabelText(document.body, "New members see history")).not.toBeInTheDocument();
});

describe("dm", () => {
beforeEach(() => {
// Make the mocked room a DM
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
style="--cpd-icon-button-size: 100%;"
>
<svg
aria-labelledby="_r_12c_"
aria-labelledby="_r_134_"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
Expand All @@ -83,7 +83,7 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
style="--cpd-icon-button-size: 100%;"
>
<svg
aria-labelledby="_r_12h_"
aria-labelledby="_r_139_"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
Expand All @@ -98,7 +98,7 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
</button>
<button
aria-label="Threads"
aria-labelledby="_r_12m_"
aria-labelledby="_r_13e_"
class="_icon-button_1215g_8"
data-kind="primary"
role="button"
Expand All @@ -125,7 +125,7 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
</button>
<button
aria-label="Room info"
aria-labelledby="_r_12r_"
aria-labelledby="_r_13j_"
class="_icon-button_1215g_8"
data-kind="primary"
role="button"
Expand Down
Loading