Compare commits

...

33 Commits

Author SHA1 Message Date
Michael Telatynski
20be92ab7e Merge branch 'develop' of ssh://github.com/element-hq/element-web into t3chguy/css-masks-again
# Conflicts:
#	res/css/views/rooms/_EventTile.pcss
2025-12-16 14:47:08 +00:00
Michael Telatynski
1afa202ef5 Add tests
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 14:02:16 +00:00
Michael Telatynski
9d3cba1621 Add missing screenshots
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 13:38:18 +00:00
Michael Telatynski
e6d572c413 Merge branch 't3chguy/a11y-icons-dec15' of ssh://github.com/element-hq/element-web into t3chguy/css-masks-again 2025-12-16 13:31:30 +00:00
Michael Telatynski
ced4456c16 Merge branch 'develop' into t3chguy/a11y-icons-dec15 2025-12-16 13:31:06 +00:00
Michael Telatynski
654a9eeb3a Merge branch 'develop' of ssh://github.com/element-hq/element-web into t3chguy/css-masks-again
# Conflicts:
#	res/css/views/rooms/_EventTile.pcss
2025-12-16 13:03:13 +00:00
Michael Telatynski
dc0763f58a Fix overflow in base card size
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 13:02:38 +00:00
Michael Telatynski
19d8798dc0 Update tests
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 13:02:30 +00:00
Michael Telatynski
d9f6355693 Iterate
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 12:58:50 +00:00
Michael Telatynski
4613c44f47 Update tests
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 12:31:01 +00:00
Michael Telatynski
0961e6f6e4 Switch to rendering svg icons rather than masking them in HiddenBody
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 12:17:12 +00:00
Michael Telatynski
5c2d89a0d7 Switch to rendering svg icons rather than masking them in AccessSecretStorageDialog
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 12:16:01 +00:00
Michael Telatynski
40120cc226 Merge branch 't3chguy/a11y-icons-dec15' of ssh://github.com/element-hq/element-web into t3chguy/css-masks-again
# Conflicts:
#	res/css/views/rooms/_EventTile.pcss
2025-12-16 12:12:59 +00:00
Michael Telatynski
8fef5c5ec6 Switch to rendering svg icons rather than masking them in DialPad
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 12:08:35 +00:00
Michael Telatynski
9864294575 Tidy up E2EIcon
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 12:07:23 +00:00
Michael Telatynski
d1a0875d7f Switch to rendering svg icons rather than masking them in E2ePadlock
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 12:06:46 +00:00
Michael Telatynski
523fba271f Switch to rendering svg icons rather than masking them in RedactedBody
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 12:06:27 +00:00
Michael Telatynski
99690f6314 Switch to rendering svg icons rather than masking them in BaseCard
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 12:06:14 +00:00
Michael Telatynski
58b75e0fe3 Switch to rendering svg icons rather than masking them in MFileBody
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 12:05:54 +00:00
Michael Telatynski
ed59a53734 Switch to rendering svg icons rather than masking them in ReactionsRow
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 12:05:46 +00:00
Michael Telatynski
b243c94093 Switch to rendering svg icons rather than masking them in EventTileBubble
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 12:04:11 +00:00
Michael Telatynski
7cd9ec4fbd Remove unused AccessibleButton kinds
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 12:01:54 +00:00
Michael Telatynski
9fa23d3c19 Add missing snapshot
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 10:45:15 +00:00
Michael Telatynski
05c64bd6bc Add test
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 10:34:04 +00:00
Michael Telatynski
c4d8c3e85f Update screenshot
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 09:44:26 +00:00
Michael Telatynski
82390d6a23 Remove unused icon underfill
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 09:42:54 +00:00
Michael Telatynski
2c2b607d3c Update screenshot
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-15 18:03:24 +00:00
Michael Telatynski
60e574f37d Update snapshots
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-15 17:55:37 +00:00
Michael Telatynski
d2ba05a3d8 Fix badly rendered icon in ThreadPanel
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-15 17:54:27 +00:00
Michael Telatynski
5c28ed6738 Fix badly rendered icon in Space menus
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-15 17:40:47 +00:00
Michael Telatynski
1808a16ed3 Fix badly rendered icon in RoomPreviewCard
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-15 17:17:58 +00:00
Michael Telatynski
bff234749f Fix badly rendered icon in JoinRuleDropdown
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-15 17:09:53 +00:00
Michael Telatynski
0733bebc38 Switch to rendering svg icons rather than masking them in SpacePanel
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-15 17:09:38 +00:00
63 changed files with 594 additions and 500 deletions

View File

@@ -351,7 +351,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
const composer = thread.locator(".mx_MessageComposer--compact"); const composer = thread.locator(".mx_MessageComposer--compact");
// Assert that the reply preview contains audio ReplyTile the file info button // Assert that the reply preview contains audio ReplyTile the file info button
await expect( await expect(
composer.locator(".mx_ReplyPreview .mx_ReplyTile_audio .mx_MFileBody_info[role='button']"), composer.locator(".mx_ReplyPreview .mx_ReplyTile .mx_MFileBody_info[role='button']"),
).toBeVisible(); ).toBeVisible();
// Select :smile: emoji and send it // Select :smile: emoji and send it
@@ -360,6 +360,6 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
await composer.getByTestId("basicmessagecomposer").press("Enter"); await composer.getByTestId("basicmessagecomposer").press("Enter");
// Assert that the file name is rendered on the file button // Assert that the file name is rendered on the file button
await expect(threadTile.locator(".mx_ReplyTile_audio .mx_MFileBody_info[role='button']")).toBeVisible(); await expect(threadTile.locator(".mx_ReplyTile .mx_MFileBody_info[role='button']")).toBeVisible();
}); });
}); });

View File

@@ -31,15 +31,11 @@ const startDMWithBob = async (page: Page, bob: Bot) => {
const testMessages = async (page: Page, bob: Bot, bobRoomId: string) => { const testMessages = async (page: Page, bob: Bot, bobRoomId: string) => {
// check the invite message // check the invite message
await expect( await expect(page.locator(".mx_EventTile", { hasText: "Hey!" }).locator(".mx_EventTile_e2eIcon")).not.toBeVisible();
page.locator(".mx_EventTile", { hasText: "Hey!" }).locator(".mx_EventTile_e2eIcon_warning"),
).not.toBeVisible();
// Bob sends a response // Bob sends a response
await bob.sendMessage(bobRoomId, "Hoo!"); await bob.sendMessage(bobRoomId, "Hoo!");
await expect( await expect(page.locator(".mx_EventTile", { hasText: "Hoo!" }).locator(".mx_EventTile_e2eIcon")).not.toBeVisible();
page.locator(".mx_EventTile", { hasText: "Hoo!" }).locator(".mx_EventTile_e2eIcon_warning"),
).not.toBeVisible();
}; };
const bobJoin = async (page: Page, bob: Bot) => { const bobJoin = async (page: Page, bob: Bot) => {

View File

@@ -30,69 +30,80 @@ test.describe("Cryptography", function () {
test.describe("decryption failure messages", () => { test.describe("decryption failure messages", () => {
test.skip(isDendrite, "Dendrite lacks support for MSC3967 so requires additional auth here"); test.skip(isDendrite, "Dendrite lacks support for MSC3967 so requires additional auth here");
test("should handle device-relative historical messages", async ({ test(
homeserver, "should handle device-relative historical messages",
page, { tag: "@screenshot" },
app, async ({ homeserver, page, app, credentials, user }) => {
credentials, test.setTimeout(60000);
user,
}) => {
test.setTimeout(60000);
// Start with a logged-in session, without key backup, and send a message. // Start with a logged-in session, without key backup, and send a message.
await createRoom(page, "Test room", true); await createRoom(page, "Test room", true);
await sendMessageInCurrentRoom(page, "test test"); await sendMessageInCurrentRoom(page, "test test");
// Log out, discarding the key for the sent message. // Log out, discarding the key for the sent message.
await logOutOfElement(page, true); await logOutOfElement(page, true);
// Log in again, and see how the message looks. // Log in again, and see how the message looks.
await logIntoElement(page, credentials); await logIntoElement(page, credentials);
await app.viewRoomByName("Test room"); await app.viewRoomByName("Test room");
const lastTile = page.locator(".mx_EventTile").last(); const lastTile = page.locator(".mx_EventTile").last();
await expect(lastTile).toContainText("Historical messages are not available on this device"); await expect(lastTile).toContainText("Historical messages are not available on this device");
await expect(lastTile.locator(".mx_EventTile_e2eIcon_decryption_failure")).toBeVisible(); await expect(lastTile.locator(".mx_EventTile_e2eIcon")).toHaveAccessibleName(
"This message could not be decrypted",
);
await expect(lastTile).toMatchScreenshot("history-not-available.png", {
mask: [page.locator(".mx_MessageTimestamp")],
});
// Now, we set up key backup, and then send another message. // Now, we set up key backup, and then send another message.
const secretStorageKey = await enableKeyBackup(app); const secretStorageKey = await enableKeyBackup(app);
await app.viewRoomByName("Test room"); await app.viewRoomByName("Test room");
await sendMessageInCurrentRoom(page, "test2 test2"); await sendMessageInCurrentRoom(page, "test2 test2");
// Workaround for https://github.com/element-hq/element-web/issues/27267. It can take up to 10 seconds for // Workaround for https://github.com/element-hq/element-web/issues/27267. It can take up to 10 seconds for
// the key to be backed up. // the key to be backed up.
await page.waitForTimeout(10000); await page.waitForTimeout(10000);
// Finally, log out again, and back in, skipping verification for now, and see what we see. // Finally, log out again, and back in, skipping verification for now, and see what we see.
await logOutOfElement(page); await logOutOfElement(page);
await logIntoElement(page, credentials); await logIntoElement(page, credentials);
await page.locator(".mx_AuthPage").getByRole("button", { name: "Skip verification for now" }).click(); await page.locator(".mx_AuthPage").getByRole("button", { name: "Skip verification for now" }).click();
await page.locator(".mx_AuthPage").getByRole("button", { name: "I'll verify later" }).click(); await page.locator(".mx_AuthPage").getByRole("button", { name: "I'll verify later" }).click();
await app.viewRoomByName("Test room"); await app.viewRoomByName("Test room");
// In this case, the call to cryptoApi.isEncryptionEnabledInRoom is taking a long time to resolve // In this case, the call to cryptoApi.isEncryptionEnabledInRoom is taking a long time to resolve
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
// There should be two historical events in the timeline // There should be two historical events in the timeline
const tiles = await page.locator(".mx_EventTile").all(); const tiles = await page.locator(".mx_EventTile").all();
expect(tiles.length).toBeGreaterThanOrEqual(2); expect(tiles.length).toBeGreaterThanOrEqual(2);
// look at the last two tiles only // look at the last two tiles only
for (const tile of tiles.slice(-2)) { for (const tile of tiles.slice(-2)) {
await expect(tile).toContainText("You need to verify this device for access to historical messages"); await expect(tile).toContainText(
await expect(tile.locator(".mx_EventTile_e2eIcon_decryption_failure")).toBeVisible(); "You need to verify this device for access to historical messages",
} );
await expect(tile.locator(".mx_EventTile_e2eIcon")).toHaveAccessibleName(
"This message could not be decrypted",
);
}
// Now verify our device (setting up key backup), and check what happens // Now verify our device (setting up key backup), and check what happens
await verifySession(app, secretStorageKey); await verifySession(app, secretStorageKey);
const tilesAfterVerify = (await page.locator(".mx_EventTile").all()).slice(-2); const tilesAfterVerify = (await page.locator(".mx_EventTile").all()).slice(-2);
// The first message still cannot be decrypted, because it was never backed up. It's now a regular UTD though. // The first message still cannot be decrypted, because it was never backed up. It's now a regular UTD though.
await expect(tilesAfterVerify[0]).toContainText("Unable to decrypt message"); await expect(tilesAfterVerify[0]).toContainText("Unable to decrypt message");
await expect(tilesAfterVerify[0].locator(".mx_EventTile_e2eIcon_decryption_failure")).toBeVisible(); await expect(tilesAfterVerify[0].locator(".mx_EventTile_e2eIcon")).toHaveAccessibleName(
"This message could not be decrypted",
);
// The second message should now be decrypted, with a grey shield // The second message should now be decrypted, with a grey shield
await expect(tilesAfterVerify[1]).toContainText("test2 test2"); await expect(tilesAfterVerify[1]).toContainText("test2 test2");
await expect(tilesAfterVerify[1].locator(".mx_EventTile_e2eIcon_normal")).toBeVisible(); await expect(tilesAfterVerify[1].locator(".mx_EventTile_e2eIcon")).toHaveAccessibleName(
}); "The authenticity of this encrypted message can't be guaranteed on this device.",
);
},
);
test.describe("non-joined historical messages", () => { test.describe("non-joined historical messages", () => {
test.skip(isDendrite, "does not yet support membership on events"); test.skip(isDendrite, "does not yet support membership on events");
@@ -186,7 +197,9 @@ test.describe("Cryptography", function () {
// The first message from Bob was sent before Alice was in the room, so should // The first message from Bob was sent before Alice was in the room, so should
// be different from the standard UTD message // be different from the standard UTD message
await expect(tiles[tiles.length - 5]).toContainText("You don't have access to this message"); await expect(tiles[tiles.length - 5]).toContainText("You don't have access to this message");
await expect(tiles[tiles.length - 5].locator(".mx_EventTile_e2eIcon_decryption_failure")).toBeVisible(); await expect(tiles[tiles.length - 5].locator(".mx_EventTile_e2eIcon")).toHaveAccessibleName(
"This message could not be decrypted",
);
// The second message from Bob should be decryptable // The second message from Bob should be decryptable
await expect(tiles[tiles.length - 2]).toContainText("This should be decryptable"); await expect(tiles[tiles.length - 2]).toContainText("This should be decryptable");
@@ -196,7 +209,9 @@ test.describe("Cryptography", function () {
// in the room and is expected to be decryptable, so this should have the // in the room and is expected to be decryptable, so this should have the
// standard UTD message // standard UTD message
await expect(tiles[tiles.length - 1]).toContainText("Unable to decrypt message"); await expect(tiles[tiles.length - 1]).toContainText("Unable to decrypt message");
await expect(tiles[tiles.length - 1].locator(".mx_EventTile_e2eIcon_decryption_failure")).toBeVisible(); await expect(tiles[tiles.length - 1].locator(".mx_EventTile_e2eIcon")).toHaveAccessibleName(
"This message could not be decrypted",
);
}); });
test("should be able to jump to a message sent before our last join event", async ({ test("should be able to jump to a message sent before our last join event", async ({

View File

@@ -68,7 +68,7 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
await doTwoWaySasVerification(page, verifier); await doTwoWaySasVerification(page, verifier);
await infoDialog.getByRole("button", { name: "They match" }).click(); await infoDialog.getByRole("button", { name: "They match" }).click();
await expect(page.locator(".mx_E2EIcon_verified")).toMatchScreenshot("device-verified-e2eIcon.png"); await expect(page.locator(".mx_E2EIcon")).toMatchScreenshot("device-verified-e2eIcon.png");
await infoDialog.getByRole("button", { name: "Got it" }).click(); await infoDialog.getByRole("button", { name: "Got it" }).click();
// Check that our device is now cross-signed // Check that our device is now cross-signed

View File

@@ -77,11 +77,8 @@ test.describe("Cryptography", function () {
const last = page.locator(".mx_EventTile_last"); const last = page.locator(".mx_EventTile_last");
await expect(last).toContainText("Unable to decrypt message"); await expect(last).toContainText("Unable to decrypt message");
const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon"); const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon");
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_decryption_failure/); await expect(lastE2eIcon).toHaveAccessibleName("This message could not be decrypted");
await lastE2eIcon.focus(); await expect(lastE2eIcon).toMatchScreenshot("event-shield-utd.png");
await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText(
"This message could not be decrypted",
);
/* Should show a red padlock for an unencrypted message in an e2e room */ /* Should show a red padlock for an unencrypted message in an e2e room */
await bob.evaluate( await bob.evaluate(
@@ -99,10 +96,8 @@ test.describe("Cryptography", function () {
); );
await expect(last).toContainText("test unencrypted"); await expect(last).toContainText("test unencrypted");
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/); await expect(lastE2eIcon).toHaveAccessibleName("Not encrypted");
await expect(lastE2eIcon).toMatchScreenshot("event-shield-warning.png"); await expect(lastE2eIcon).toMatchScreenshot("event-shield-warning.png");
await lastE2eIcon.focus();
await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText("Not encrypted");
/* Should show no padlock for an unverified user */ /* Should show no padlock for an unverified user */
// bob sends a valid event // bob sends a valid event
@@ -133,11 +128,8 @@ test.describe("Cryptography", function () {
/* should show red padlock for a message from an unverified device */ /* should show red padlock for a message from an unverified device */
await bobSecondDevice.sendMessage(testRoomId, "test encrypted from unverified"); await bobSecondDevice.sendMessage(testRoomId, "test encrypted from unverified");
await expect(lastTile).toContainText("test encrypted from unverified"); await expect(lastTile).toContainText("test encrypted from unverified");
await expect(lastTileE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/); await expect(lastTileE2eIcon).toHaveAccessibleName("Encrypted by a device not verified by its owner.");
await lastTileE2eIcon.focus(); await expect(lastE2eIcon).toMatchScreenshot("event-shield-not-verified.png");
await expect(await app.getTooltipForElement(lastTileE2eIcon)).toContainText(
"Encrypted by a device not verified by its owner.",
);
/* Should show a red padlock for a message from an unverified device. /* Should show a red padlock for a message from an unverified device.
* Rust crypto remembers the verification state of the sending device, so it will know that the device was * Rust crypto remembers the verification state of the sending device, so it will know that the device was
@@ -153,64 +145,58 @@ test.describe("Cryptography", function () {
await app.viewRoomByName("TestRoom"); await app.viewRoomByName("TestRoom");
await expect(last).toContainText("test encrypted from unverified"); await expect(last).toContainText("test encrypted from unverified");
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/); await expect(lastE2eIcon).toHaveAccessibleName("Encrypted by a device not verified by its owner.");
await lastE2eIcon.focus(); await expect(lastE2eIcon).toMatchScreenshot("event-shield-not-verified.png");
await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText(
"Encrypted by a device not verified by its owner.",
);
}, },
); );
test("Should show a grey padlock for a key restored from backup", async ({ test(
page, "Should show a grey padlock for a key restored from backup",
app, { tag: "@screenshot" },
bot: bob, async ({ page, app, bot: bob, homeserver, user: aliceCredentials }) => {
homeserver, test.slow();
user: aliceCredentials, const securityKey = await enableKeyBackup(app);
}) => {
test.slow();
const securityKey = await enableKeyBackup(app);
// bob sends a valid event // bob sends a valid event
await bob.sendMessage(testRoomId, "test encrypted 1"); await bob.sendMessage(testRoomId, "test encrypted 1");
const lastTile = page.locator(".mx_EventTile_last"); const lastTile = page.locator(".mx_EventTile_last");
const lastTileE2eIcon = lastTile.locator(".mx_EventTile_e2eIcon"); const lastTileE2eIcon = lastTile.locator(".mx_EventTile_e2eIcon");
await expect(lastTile).toContainText("test encrypted 1"); await expect(lastTile).toContainText("test encrypted 1");
// no e2e icon // no e2e icon
await expect(lastTileE2eIcon).not.toBeVisible(); await expect(lastTileE2eIcon).not.toBeVisible();
// Workaround for https://github.com/element-hq/element-web/issues/27267. It can take up to 10 seconds for // Workaround for https://github.com/element-hq/element-web/issues/27267. It can take up to 10 seconds for
// the key to be backed up. // the key to be backed up.
await page.waitForTimeout(10000); await page.waitForTimeout(10000);
/* log out, and back in */ /* log out, and back in */
await logOutOfElement(page); await logOutOfElement(page);
// Reload to work around a Rust crypto bug where it can hold onto the indexeddb even after logout // Reload to work around a Rust crypto bug where it can hold onto the indexeddb even after logout
// https://github.com/element-hq/element-web/issues/25779 // https://github.com/element-hq/element-web/issues/25779
await page.addInitScript(() => { await page.addInitScript(() => {
// When we reload, the initScript created by the `user`/`pageWithCredentials` fixtures // When we reload, the initScript created by the `user`/`pageWithCredentials` fixtures
// will re-inject the original credentials into localStorage, which we don't want. // will re-inject the original credentials into localStorage, which we don't want.
// To work around, we add a second initScript which will clear localStorage again. // To work around, we add a second initScript which will clear localStorage again.
window.localStorage.clear(); window.localStorage.clear();
}); });
await page.reload(); await page.reload();
await logIntoElementAndVerify(page, aliceCredentials, securityKey); await logIntoElementAndVerify(page, aliceCredentials, securityKey);
/* go back to the test room and find Bob's message again */ /* go back to the test room and find Bob's message again */
await app.viewRoomById(testRoomId); await app.viewRoomById(testRoomId);
await expect(lastTile).toContainText("test encrypted 1"); await expect(lastTile).toContainText("test encrypted 1");
// The gray shield would be a mx_EventTile_e2eIcon_normal. The red shield would be a mx_EventTile_e2eIcon_warning. // The gray shield would be a Compound info icon. The red shield would be a Compound error solid icon.
// No shield would have no div mx_EventTile_e2eIcon at all. // No shield would have no div mx_EventTile_e2eIcon at all.
await expect(lastTileE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_normal/); // The key is coming from backup, so it is not anymore possible to establish if the claimed device
await lastTileE2eIcon.hover(); // creator of this key is authentic. The tooltip should be "The authenticity of this encrypted message can't be guaranteed on this device."
// The key is coming from backup, so it is not anymore possible to establish if the claimed device // It is not "Encrypted by an unknown or deleted device." even if the claimed device is actually deleted.
// creator of this key is authentic. The tooltip should be "The authenticity of this encrypted message can't be guaranteed on this device." await expect(lastTileE2eIcon).toHaveAccessibleName(
// It is not "Encrypted by an unknown or deleted device." even if the claimed device is actually deleted. "The authenticity of this encrypted message can't be guaranteed on this device.",
await expect(await app.getTooltipForElement(lastTileE2eIcon)).toContainText( );
"The authenticity of this encrypted message can't be guaranteed on this device.", await expect(lastTileE2eIcon).toMatchScreenshot("event-shield-authenticity.png");
); },
}); );
test("should show the correct shield on edited e2e events", async ({ page, app, bot: bob, homeserver }) => { test("should show the correct shield on edited e2e events", async ({ page, app, bot: bob, homeserver }) => {
// bob has a second, not cross-signed, device // bob has a second, not cross-signed, device
@@ -224,7 +210,7 @@ test.describe("Cryptography", function () {
// the message should appear, decrypted, with no warning // the message should appear, decrypted, with no warning
await expect( await expect(
page.locator(".mx_EventTile", { hasText: "Hoo!" }).locator(".mx_EventTile_e2eIcon_warning"), page.locator(".mx_EventTile", { hasText: "Hoo!" }).locator(".mx_EventTile_e2eIcon"),
).not.toBeVisible(); ).not.toBeVisible();
// bob sends an edit to the first message with his unverified device // bob sends an edit to the first message with his unverified device
@@ -241,7 +227,7 @@ test.describe("Cryptography", function () {
// the edit should have a warning // the edit should have a warning
await expect( await expect(
page.locator(".mx_EventTile", { hasText: "Haa!" }).locator(".mx_EventTile_e2eIcon_warning"), page.locator(".mx_EventTile", { hasText: "Haa!" }).locator(".mx_EventTile_e2eIcon"),
).toBeVisible(); ).toBeVisible();
// a second edit from the verified device should be ok // a second edit from the verified device should be ok
@@ -257,77 +243,69 @@ test.describe("Cryptography", function () {
}); });
await expect( await expect(
page.locator(".mx_EventTile", { hasText: "Hee!" }).locator(".mx_EventTile_e2eIcon_warning"), page.locator(".mx_EventTile", { hasText: "Hee!" }).locator(".mx_EventTile_e2eIcon"),
).not.toBeVisible(); ).not.toBeVisible();
}); });
test("should show correct shields on events sent by devices which have since been deleted", async ({ test(
page, "should show correct shields on events sent by devices which have since been deleted",
app, { tag: "@screenshot" },
bot: bob, async ({ page, app, bot: bob, homeserver }) => {
homeserver, // Workaround for https://github.com/element-hq/element-web/issues/28640:
}) => { // make sure that Alice has seen Bob's identity before she goes offline. We do this by opening
// Workaround for https://github.com/element-hq/element-web/issues/28640: // his user info.
// make sure that Alice has seen Bob's identity before she goes offline. We do this by opening await waitForDevices(app, bob.credentials.userId, 1);
// his user info.
await waitForDevices(app, bob.credentials.userId, 1);
// Our app is blocked from syncing while Bob sends his messages. // Our app is blocked from syncing while Bob sends his messages.
await app.client.network.goOffline(); await app.client.network.goOffline();
// Bob sends a message from his verified device // Bob sends a message from his verified device
await bob.sendMessage(testRoomId, "test encrypted from verified"); await bob.sendMessage(testRoomId, "test encrypted from verified");
// And one from a second, not cross-signed, device // And one from a second, not cross-signed, device
const bobSecondDevice = await createSecondBotDevice(page, homeserver, bob); const bobSecondDevice = await createSecondBotDevice(page, homeserver, bob);
await bobSecondDevice.waitForNextSync(); // make sure the client knows the room is encrypted await bobSecondDevice.waitForNextSync(); // make sure the client knows the room is encrypted
await bobSecondDevice.sendMessage(testRoomId, "test encrypted from unverified"); await bobSecondDevice.sendMessage(testRoomId, "test encrypted from unverified");
// ... and then logs out both devices. // ... and then logs out both devices.
await bob.evaluate((cli) => cli.logout(true)); await bob.evaluate((cli) => cli.logout(true));
await bobSecondDevice.evaluate((cli) => cli.logout(true)); await bobSecondDevice.evaluate((cli) => cli.logout(true));
// Let our app start syncing again // Let our app start syncing again
await app.client.network.goOnline(); await app.client.network.goOnline();
// Wait for the messages to arrive. It can take quite a while for the sync to wake up. // Wait for the messages to arrive. It can take quite a while for the sync to wake up.
const last = page.locator(".mx_EventTile_last"); const last = page.locator(".mx_EventTile_last");
await expect(last).toContainText("test encrypted from unverified", { timeout: 20000 }); await expect(last).toContainText("test encrypted from unverified", { timeout: 20000 });
const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon"); const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon");
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/); await expect(lastE2eIcon).toHaveAccessibleName("Encrypted by a device not verified by its owner.");
await lastE2eIcon.focus(); await expect(lastE2eIcon).toMatchScreenshot("event-shield-not-verified.png");
await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText(
"Encrypted by a device not verified by its owner.",
);
const penultimate = page.locator(".mx_EventTile").filter({ hasText: "test encrypted from verified" }); const penultimate = page.locator(".mx_EventTile").filter({ hasText: "test encrypted from verified" });
await assertNoE2EIcon(penultimate, app); await assertNoE2EIcon(penultimate, app);
}); },
);
test("should show correct shields on events sent by users with changed identity", async ({ test(
page, "should show correct shields on events sent by users with changed identity",
app, { tag: "@screenshot" },
bot: bob, async ({ page, app, bot: bob, homeserver }) => {
homeserver, // Verify Bob
}) => { await verify(app, bob);
// Verify Bob
await verify(app, bob);
// Bob logs in a new device and resets cross-signing // Bob logs in a new device and resets cross-signing
const bobSecondDevice = await createSecondBotDevice(page, homeserver, bob); const bobSecondDevice = await createSecondBotDevice(page, homeserver, bob);
await bootstrapCrossSigningForClient(await bobSecondDevice.prepareClient(), bob.credentials, true); await bootstrapCrossSigningForClient(await bobSecondDevice.prepareClient(), bob.credentials, true);
/* should show an error for a message from a previously verified device */ /* should show an error for a message from a previously verified device */
await bobSecondDevice.sendMessage(testRoomId, "test encrypted from user that was previously verified"); await bobSecondDevice.sendMessage(testRoomId, "test encrypted from user that was previously verified");
const last = page.locator(".mx_EventTile_last"); const last = page.locator(".mx_EventTile_last");
await expect(last).toContainText("test encrypted from user that was previously verified"); await expect(last).toContainText("test encrypted from user that was previously verified");
const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon"); const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon");
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/); await expect(lastE2eIcon).toHaveAccessibleName("Sender's verified identity was reset");
await lastE2eIcon.focus(); await expect(lastE2eIcon).toMatchScreenshot("event-shield-identity-reset.png");
await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText( },
"Sender's verified identity was reset", );
);
});
}); });
}); });
@@ -343,8 +321,6 @@ async function assertNoE2EIcon(messageLocator: Locator, app: ElementAppPage) {
const e2eIcon = messageLocator.locator(".mx_EventTile_e2eIcon"); const e2eIcon = messageLocator.locator(".mx_EventTile_e2eIcon");
if ((await e2eIcon.count()) > 0) { if ((await e2eIcon.count()) > 0) {
// uh-oh, there is an e2e icon. Let's find out what it's about so that we can throw a helpful error. // uh-oh, there is an e2e icon. Let's find out what it's about so that we can throw a helpful error.
await e2eIcon.focus(); await expect(e2eIcon).toHaveAccessibleName("None");
const tooltip = await app.getTooltipForElement(e2eIcon);
throw new Error(`Found an unexpected e2eIcon with tooltip '${await tooltip.textContent()}'`);
} }
} }

View File

@@ -244,24 +244,6 @@ export class ElementAppPage {
await this.page.getByRole("dialog").getByRole("button", { name: "Invite" }).click(); await this.page.getByRole("dialog").getByRole("button", { name: "Invite" }).click();
} }
/**
* Get a locator for the tooltip associated with an element
* @param e The element with the tooltip
* @returns Locator to the tooltip
*/
public async getTooltipForElement(e: Locator): Promise<Locator> {
const [labelledById, describedById] = await Promise.all([
e.getAttribute("aria-labelledby"),
e.getAttribute("aria-describedby"),
]);
if (!labelledById && !describedById) {
throw new Error(
"Element has no aria-labelledby or aria-describedy attributes! The tooltip should have added either one of these.",
);
}
return this.page.locator(`id=${labelledById ?? describedById}`);
}
/** /**
* Close the notification toast * Close the notification toast
*/ */

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

View File

@@ -40,8 +40,7 @@ Please see LICENSE files in the repository root for full details.
/* that our preview is unencrypted, which doesn't actually matter */ /* that our preview is unencrypted, which doesn't actually matter */
/* We also hide download links to not encourage users to try interacting */ /* We also hide download links to not encourage users to try interacting */
.mx_EventTile_msgOption, .mx_EventTile_msgOption,
.mx_EventTile_e2eIcon_unencrypted, .mx_EventTile_e2eIcon,
.mx_EventTile_e2eIcon_warning,
.mx_MFileBody_download { .mx_MFileBody_download {
display: none; display: none;
} }

View File

@@ -33,14 +33,11 @@ Please see LICENSE files in the repository root for full details.
} }
.mx_AccessSecretStorageDialog_recoveryKeyFeedback { .mx_AccessSecretStorageDialog_recoveryKeyFeedback {
&::before { svg {
content: "";
display: inline-block; display: inline-block;
vertical-align: bottom; vertical-align: bottom;
width: 20px; width: 20px;
height: 20px; height: 20px;
mask-repeat: no-repeat;
mask-position: center;
mask-size: 20px; mask-size: 20px;
margin-inline-end: 5px; margin-inline-end: 5px;
} }
@@ -48,9 +45,8 @@ Please see LICENSE files in the repository root for full details.
&.mx_AccessSecretStorageDialog_recoveryKeyFeedback--invalid { &.mx_AccessSecretStorageDialog_recoveryKeyFeedback--invalid {
color: $alert; color: $alert;
&::before { svg {
mask-image: url("@vector-im/compound-design-tokens/icons/error-solid.svg"); color: $alert;
background-color: $alert;
} }
} }
} }

View File

@@ -63,22 +63,6 @@ Please see LICENSE files in the repository root for full details.
font-weight: var(--cpd-font-weight-semibold); font-weight: var(--cpd-font-weight-semibold);
} }
&.mx_AccessibleButton_kind_confirm_sm {
background-color: var(--cpd-color-bg-action-primary-rest);
&::before {
mask-image: url("@vector-im/compound-design-tokens/icons/check.svg");
}
}
&.mx_AccessibleButton_kind_cancel_sm {
background-color: var(--cpd-color-bg-critical-primary);
&::before {
mask-image: url("@vector-im/compound-design-tokens/icons/close.svg");
}
}
&.mx_AccessibleButton_kind_icon, &.mx_AccessibleButton_kind_icon,
&.mx_AccessibleButton_kind_icon_primary, &.mx_AccessibleButton_kind_icon_primary,
&.mx_AccessibleButton_kind_icon_primary_outline { &.mx_AccessibleButton_kind_icon_primary_outline {
@@ -114,10 +98,6 @@ Please see LICENSE files in the repository root for full details.
text-decoration: underline; text-decoration: underline;
} }
&.mx_AccessibleButton_kind_secondary_content {
color: $secondary-content;
}
&.mx_AccessibleButton_kind_danger { &.mx_AccessibleButton_kind_danger {
color: var(--cpd-color-text-on-solid-primary); color: var(--cpd-color-text-on-solid-primary);
background-color: var(--cpd-color-bg-critical-primary); background-color: var(--cpd-color-bg-critical-primary);
@@ -175,25 +155,4 @@ Please see LICENSE files in the repository root for full details.
&.mx_AccessibleButton_kind_content_inline { &.mx_AccessibleButton_kind_content_inline {
display: inline; display: inline;
} }
&.mx_AccessibleButton_kind_confirm_sm,
&.mx_AccessibleButton_kind_cancel_sm {
padding: 0px;
width: 16px;
height: 16px;
border-radius: 100%;
position: relative;
display: block;
&::before {
content: "";
display: block;
position: absolute;
inset: 0;
background-color: #ffffff;
mask-repeat: no-repeat;
mask-position: center;
mask-size: 80%;
}
}
} }

View File

@@ -9,8 +9,7 @@ Please see LICENSE files in the repository root for full details.
.mx_EventTileBubble.mx_CreateEvent { .mx_EventTileBubble.mx_CreateEvent {
margin: var(--EventTileBubble_margin-block) auto; margin: var(--EventTileBubble_margin-block) auto;
&::before { svg {
background-color: $header-panel-text-primary-color; color: $header-panel-text-primary-color;
mask-image: url("@vector-im/compound-design-tokens/icons/chat-solid.svg");
} }
} }

View File

@@ -18,8 +18,7 @@ Please see LICENSE files in the repository root for full details.
display: grid; display: grid;
grid-template-columns: 24px minmax(0, 1fr) min-content min-content; grid-template-columns: 24px minmax(0, 1fr) min-content min-content;
&::before, svg {
&::after {
position: relative; position: relative;
grid-column: 1; grid-column: 1;
grid-row: 1 / 3; grid-row: 1 / 3;

View File

@@ -11,21 +11,12 @@ Please see LICENSE files in the repository root for full details.
color: $muted-fg-color; color: $muted-fg-color;
vertical-align: middle; vertical-align: middle;
padding-left: 20px; svg {
position: relative;
&::before {
height: 14px; height: 14px;
width: 14px; width: 14px;
background-color: $muted-fg-color; display: inline-block;
mask-image: url("@vector-im/compound-design-tokens/icons/visibility-off.svg"); margin-right: var(--cpd-space-1-5x);
color: $muted-fg-color;
mask-repeat: no-repeat; vertical-align: -2px;
mask-position: center;
mask-size: contain;
content: "";
position: absolute;
top: 1px;
left: 0;
} }
} }

View File

@@ -34,25 +34,17 @@ Please see LICENSE files in the repository root for full details.
background-color: $system; background-color: $system;
border-radius: 20px; border-radius: 20px;
display: inline-block; display: inline-block;
width: 32px; width: 16px;
height: 32px; height: 16px;
position: relative; padding: var(--cpd-space-2x);
vertical-align: middle; vertical-align: middle;
margin-right: 12px; margin-right: 12px;
&::before { svg {
content: ""; color: $secondary-content;
mask-repeat: no-repeat; width: inherit;
mask-position: center; height: inherit;
mask-size: cover; display: block;
mask-image: url("@vector-im/compound-design-tokens/icons/attachment.svg");
background-color: $secondary-content;
width: 16px;
height: 16px;
position: absolute;
top: 8px;
left: 8px;
} }
} }

View File

@@ -7,8 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/ */
.mx_EventTileBubble.mx_MJitsiWidgetEvent { .mx_EventTileBubble.mx_MJitsiWidgetEvent {
&::before { svg {
background-color: $header-panel-text-primary-color; /* XXX: Variable abuse */ color: $header-panel-text-primary-color; /* XXX: Variable abuse */
mask-image: url("@vector-im/compound-design-tokens/icons/video-call-solid.svg");
} }
} }

View File

@@ -9,25 +9,19 @@ Please see LICENSE files in the repository root for full details.
color: var(--cpd-color-text-primary); color: var(--cpd-color-text-primary);
.mx_ReactionsRow_addReactionButton { .mx_ReactionsRow_addReactionButton {
position: relative;
display: inline-block; display: inline-block;
visibility: hidden; /* show on hover of the .mx_EventTile */ visibility: hidden; /* show on hover of the .mx_EventTile */
width: 24px; width: 16px;
height: 24px; height: 16px;
padding: var(--cpd-space-1x);
vertical-align: middle; vertical-align: middle;
margin-left: 4px; margin-left: 4px;
margin-right: 4px; margin-right: 4px;
&::before { svg {
content: ""; height: inherit;
position: absolute; width: inherit;
height: 100%; color: $tertiary-content;
width: 100%;
mask-size: 16px;
mask-repeat: no-repeat;
mask-position: center;
background-color: $tertiary-content;
mask-image: url("@vector-im/compound-design-tokens/icons/reaction-add.svg");
} }
&.mx_ReactionsRow_addReactionButton_active { &.mx_ReactionsRow_addReactionButton_active {
@@ -36,8 +30,8 @@ Please see LICENSE files in the repository root for full details.
&:hover, &:hover,
&.mx_ReactionsRow_addReactionButton_active { &.mx_ReactionsRow_addReactionButton_active {
&::before { svg {
background-color: $primary-content; color: $primary-content;
} }
} }
} }

View File

@@ -11,20 +11,11 @@ Please see LICENSE files in the repository root for full details.
color: $secondary-content; color: $secondary-content;
vertical-align: middle; vertical-align: middle;
padding-left: 20px; svg {
position: relative; margin-right: 6px;
&::before {
height: 14px; height: 14px;
width: 14px; width: 14px;
background-color: $icon-button-color; color: $icon-button-color;
mask-image: url("@vector-im/compound-design-tokens/icons/delete.svg"); vertical-align: -2px;
mask-repeat: no-repeat;
mask-position: center;
mask-size: contain;
content: "";
position: absolute;
top: 1px;
left: 0;
} }
} }

View File

@@ -9,28 +9,8 @@ Please see LICENSE files in the repository root for full details.
.mx_EventTileBubble.mx_cryptoEvent { .mx_EventTileBubble.mx_cryptoEvent {
margin: var(--EventTileBubble_margin-block) auto; margin: var(--EventTileBubble_margin-block) auto;
/* white infill for the transparency */ &.mx_cryptoEvent_icon svg {
&.mx_cryptoEvent_icon::before { color: $header-panel-text-primary-color;
background-color: #ffffff;
mask-image: url("@vector-im/compound-design-tokens/icons/lock-solid.svg");
mask-repeat: no-repeat;
mask-position: center;
mask-size: 80%;
}
&.mx_cryptoEvent_icon::after {
mask-image: url("@vector-im/compound-design-tokens/icons/lock-solid.svg");
background-color: $header-panel-text-primary-color;
}
&.mx_cryptoEvent_icon_verified::after {
mask-image: url("@vector-im/compound-design-tokens/icons/shield.svg");
background-color: $accent;
}
&.mx_cryptoEvent_icon_warning::after {
mask-image: url("@vector-im/compound-design-tokens/icons/error-solid.svg");
background-color: $e2e-warning-color;
} }
.mx_cryptoEvent_state, .mx_cryptoEvent_state,

View File

@@ -61,24 +61,19 @@ Please see LICENSE files in the repository root for full details.
.mx_BaseCard_header_title_button--option { .mx_BaseCard_header_title_button--option {
position: relative; position: relative;
width: var(--BaseCard_header-button-size); width: calc(var(--BaseCard_header-button-size) - 4px);
height: var(--BaseCard_header-button-size); height: calc(var(--BaseCard_header-button-size) - 4px);
padding: 2px;
&::after { svg {
content: ""; width: inherit;
position: absolute; height: inherit;
inset-block-start: 0; display: block;
inset-inline-start: 0; color: $secondary-content;
height: 100%;
width: 100%;
mask-repeat: no-repeat;
mask-position: center;
mask-image: url("@vector-im/compound-design-tokens/icons/overflow-horizontal.svg");
background-color: $secondary-content;
} }
&:hover::after { &:hover svg {
background-color: $primary-content; color: $primary-content;
} }
} }
} }

View File

@@ -21,22 +21,3 @@ Please see LICENSE files in the repository root for full details.
.mx_E2EIcon.mx_E2EIcon_inline { .mx_E2EIcon.mx_E2EIcon_inline {
display: inline-block; display: inline-block;
} }
.mx_E2EIcon_warning {
color: $e2e-warning-color;
}
.mx_E2EIcon_normal {
color: var(--cpd-color-icon-tertiary);
}
.mx_E2EIcon_verified,
.mx_E2EIcon_warning {
.mx_E2EIcon_normal::after {
background-color: white;
}
}
.mx_E2EIcon_verified {
color: $e2e-verified-color;
}

View File

@@ -830,32 +830,11 @@ $left-gutter: 64px;
width: 14px; width: 14px;
height: 14px; height: 14px;
display: block; display: block;
background-repeat: no-repeat;
background-size: contain;
&::after { svg {
content: ""; height: inherit;
width: inherit;
display: block; display: block;
position: absolute;
inset: 0;
mask-repeat: no-repeat;
mask-position: center;
mask-size: contain;
}
&.mx_EventTile_e2eIcon_warning::after {
mask-image: url("@vector-im/compound-design-tokens/icons/error-solid.svg");
background-color: $e2e-warning-color; /* red */
}
&.mx_EventTile_e2eIcon_normal::after {
mask-image: url("@vector-im/compound-design-tokens/icons/info.svg");
background-color: var(--cpd-color-icon-tertiary); /* grey */
}
&.mx_EventTile_e2eIcon_decryption_failure::after {
mask-image: url("@vector-im/compound-design-tokens/icons/error-solid.svg");
background-color: var(--cpd-color-icon-tertiary);
} }
} }

View File

@@ -9,8 +9,7 @@ Please see LICENSE files in the repository root for full details.
.mx_EventTileBubble.mx_HistoryTile { .mx_EventTileBubble.mx_HistoryTile {
margin: var(--EventTileBubble_margin-block) auto; margin: var(--EventTileBubble_margin-block) auto;
&::before { svg {
background-color: $header-panel-text-primary-color; color: $header-panel-text-primary-color;
mask-image: url("@vector-im/compound-design-tokens/icons/visibility-off.svg");
} }
} }

View File

@@ -11,14 +11,6 @@ Please see LICENSE files in the repository root for full details.
padding: 2px 0; padding: 2px 0;
font: var(--cpd-font-body-md-regular); font: var(--cpd-font-body-md-regular);
&.mx_ReplyTile_audio .mx_MFileBody_info_icon::before {
mask-image: url("@vector-im/compound-design-tokens/icons/volume-on-solid.svg");
}
&.mx_ReplyTile_video .mx_MFileBody_info_icon::before {
mask-image: url("@vector-im/compound-design-tokens/icons/video-call-solid.svg");
}
> a { > a {
display: grid; display: grid;
grid-template: grid-template:

View File

@@ -44,16 +44,11 @@ Please see LICENSE files in the repository root for full details.
grid-column: 2; grid-column: 2;
background-color: $accent; background-color: $accent;
&::before { svg {
content: "";
display: inline-block; display: inline-block;
height: 40px; height: 20px;
width: 40px; width: 20px;
vertical-align: middle; padding: 10px;
mask-repeat: no-repeat; color: #fff; /* on all themes */
mask-size: 20px;
mask-position: center;
background-color: #fff; /* on all themes */
mask-image: url("@vector-im/compound-design-tokens/icons/voice-call-solid.svg");
} }
} }

View File

@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React, { type RefObject } from "react"; import React, { type RefObject } from "react";
import { type MatrixEvent } from "matrix-js-sdk/src/matrix"; import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
import { LockSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import type ResizeNotifier from "../../utils/ResizeNotifier"; import type ResizeNotifier from "../../utils/ResizeNotifier";
import ErrorBoundary from "../views/elements/ErrorBoundary"; import ErrorBoundary from "../views/elements/ErrorBoundary";
@@ -43,6 +44,7 @@ export const WaitingForThirdPartyRoomView: React.FC<Props> = ({ roomView, resize
<div className="mx_RoomView_timeline"> <div className="mx_RoomView_timeline">
<ScrollPanel className="mx_RoomView_messagePanel"> <ScrollPanel className="mx_RoomView_messagePanel">
<EventTileBubble <EventTileBubble
icon={<LockSolidIcon />}
className="mx_cryptoEvent mx_cryptoEvent_icon" className="mx_cryptoEvent mx_cryptoEvent_icon"
title={_t("room|waiting_for_join_title", { brand })} title={_t("room|waiting_for_join_title", { brand })}
subtitle={_t("room|waiting_for_join_subtitle", { brand })} subtitle={_t("room|waiting_for_join_subtitle", { brand })}

View File

@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React, { useCallback, useEffect } from "react"; import React, { useCallback, useEffect } from "react";
import { type MatrixEvent } from "matrix-js-sdk/src/matrix"; import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
import { LinkIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; import { LinkIcon, OverflowHorizontalIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { type ButtonEvent } from "../elements/AccessibleButton"; import { type ButtonEvent } from "../elements/AccessibleButton";
import dis from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher";
@@ -90,7 +90,9 @@ const ThreadListContextMenu: React.FC<ThreadListContextMenuProps> = ({
isExpanded={menuDisplayed} isExpanded={menuDisplayed}
ref={button} ref={button}
data-testid="threadlist-dropdown-button" data-testid="threadlist-dropdown-button"
/> >
<OverflowHorizontalIcon />
</ContextMenuTooltipButton>
{menuDisplayed && ( {menuDisplayed && (
<IconizedContextMenu <IconizedContextMenu
onFinished={closeThreadOptions} onFinished={closeThreadOptions}

View File

@@ -10,9 +10,10 @@ import { Button, PasswordInput } from "@vector-im/compound-web";
import LockSolidIcon from "@vector-im/compound-design-tokens/assets/web/icons/lock-solid"; import LockSolidIcon from "@vector-im/compound-design-tokens/assets/web/icons/lock-solid";
import { debounce } from "lodash"; import { debounce } from "lodash";
import classNames from "classnames"; import classNames from "classnames";
import React, { type ChangeEvent, type FormEvent } from "react"; import React, { type ChangeEvent, type FormEvent, type ReactNode } from "react";
import { type SecretStorage } from "matrix-js-sdk/src/matrix"; import { type SecretStorage } from "matrix-js-sdk/src/matrix";
import { Flex } from "@element-hq/web-shared-components"; import { Flex } from "@element-hq/web-shared-components";
import { ErrorSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { _t } from "../../../../languageHandler"; import { _t } from "../../../../languageHandler";
import { EncryptionCard } from "../../settings/encryption/EncryptionCard"; import { EncryptionCard } from "../../settings/encryption/EncryptionCard";
@@ -142,27 +143,29 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
}; };
private getRecoveryKeyFeedback(): React.ReactNode | null { private getRecoveryKeyFeedback(): React.ReactNode | null {
let validationText: string; let content: ReactNode;
let classes: string | undefined; let classes: string | undefined;
if (this.state.recoveryKeyCorrect) { if (this.state.recoveryKeyCorrect) {
// The recovery key is good. Empty feedback. // The recovery key is good. Empty feedback.
validationText = "\xA0"; // &nbsp; content = "\xA0"; // &nbsp;
} else if (this.state.recoveryKeyCorrect === null) { } else if (this.state.recoveryKeyCorrect === null) {
// The input element is empty. Tell the user they can also use a passphrase. // The input element is empty. Tell the user they can also use a passphrase.
validationText = _t("encryption|access_secret_storage_dialog|alternatives"); content = _t("encryption|access_secret_storage_dialog|alternatives");
} else { } else {
// The entered key is not (yet) correct. Tell them so. // The entered key is not (yet) correct. Tell them so.
validationText = _t("encryption|access_secret_storage_dialog|key_validation_text|wrong_security_key"); content = (
classes = classNames({ <>
"mx_AccessSecretStorageDialog_recoveryKeyFeedback": true, <ErrorSolidIcon />
"mx_AccessSecretStorageDialog_recoveryKeyFeedback--invalid": true, {_t("encryption|access_secret_storage_dialog|key_validation_text|wrong_security_key")}
}); </>
);
classes = "mx_AccessSecretStorageDialog_recoveryKeyFeedback--invalid";
} }
return ( return (
<Flex align="center" className={classes}> <Flex align="center" className={classNames("mx_AccessSecretStorageDialog_recoveryKeyFeedback", classes)}>
{validationText} {content}
</Flex> </Flex>
); );
} }

View File

@@ -30,7 +30,6 @@ export type AccessibleButtonKind =
| "primary_outline" | "primary_outline"
| "primary_sm" | "primary_sm"
| "secondary" | "secondary"
| "secondary_content"
| "content_inline" | "content_inline"
| "danger" | "danger"
| "danger_outline" | "danger_outline"
@@ -39,8 +38,6 @@ export type AccessibleButtonKind =
| "link" | "link"
| "link_inline" | "link_inline"
| "link_sm" | "link_sm"
| "confirm_sm"
| "cancel_sm"
| "icon" | "icon"
| "icon_primary" | "icon_primary"
| "icon_primary_outline"; | "icon_primary_outline";

View File

@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React, { type JSX, type Ref, type ReactNode } from "react"; import React, { type JSX, type Ref, type ReactNode } from "react";
import { type MatrixEvent } from "matrix-js-sdk/src/matrix"; import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
import { ErrorSolidIcon, LockSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import type { RoomEncryptionEventContent } from "matrix-js-sdk/src/types"; import type { RoomEncryptionEventContent } from "matrix-js-sdk/src/types";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
@@ -53,6 +54,7 @@ const EncryptionEvent = ({ mxEvent, timestamp, ref }: IProps): ReactNode => {
return ( return (
<EventTileBubble <EventTileBubble
icon={<LockSolidIcon />}
className="mx_cryptoEvent mx_cryptoEvent_icon" className="mx_cryptoEvent mx_cryptoEvent_icon"
title={_t("common|encryption_enabled")} title={_t("common|encryption_enabled")}
subtitle={subtitle} subtitle={subtitle}
@@ -64,6 +66,7 @@ const EncryptionEvent = ({ mxEvent, timestamp, ref }: IProps): ReactNode => {
if (isRoomEncrypted) { if (isRoomEncrypted) {
return ( return (
<EventTileBubble <EventTileBubble
icon={<LockSolidIcon />}
className="mx_cryptoEvent mx_cryptoEvent_icon" className="mx_cryptoEvent mx_cryptoEvent_icon"
title={_t("common|encryption_enabled")} title={_t("common|encryption_enabled")}
subtitle={_t("timeline|m.room.encryption|disable_attempt")} subtitle={_t("timeline|m.room.encryption|disable_attempt")}
@@ -74,7 +77,8 @@ const EncryptionEvent = ({ mxEvent, timestamp, ref }: IProps): ReactNode => {
return ( return (
<EventTileBubble <EventTileBubble
className="mx_cryptoEvent mx_cryptoEvent_icon mx_cryptoEvent_icon_warning" icon={<ErrorSolidIcon color="var(--cpd-color-icon-critical-primary)" />}
className="mx_cryptoEvent"
title={_t("timeline|m.room.encryption|disabled")} title={_t("timeline|m.room.encryption|disabled")}
subtitle={_t("timeline|m.room.encryption|unsupported")} subtitle={_t("timeline|m.room.encryption|unsupported")}
ref={ref} ref={ref}

View File

@@ -11,6 +11,7 @@ import classNames from "classnames";
interface IProps { interface IProps {
className: string; className: string;
icon: JSX.Element;
title: string; title: string;
timestamp?: JSX.Element; timestamp?: JSX.Element;
subtitle?: ReactNode; subtitle?: ReactNode;
@@ -18,9 +19,10 @@ interface IProps {
ref?: Ref<HTMLDivElement>; ref?: Ref<HTMLDivElement>;
} }
const EventTileBubble = ({ className, title, timestamp, subtitle, children, ref }: IProps): JSX.Element => { const EventTileBubble = ({ className, icon, title, timestamp, subtitle, children, ref }: IProps): JSX.Element => {
return ( return (
<div className={classNames("mx_EventTileBubble", className)} ref={ref}> <div className={classNames("mx_EventTileBubble", className)} ref={ref}>
{icon}
<div className="mx_EventTileBubble_title">{title}</div> <div className="mx_EventTileBubble_title">{title}</div>
{subtitle && <div className="mx_EventTileBubble_subtitle">{subtitle}</div>} {subtitle && <div className="mx_EventTileBubble_subtitle">{subtitle}</div>}
{children} {children}

View File

@@ -7,6 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/ */
import React, { type JSX } from "react"; import React, { type JSX } from "react";
import { VisibilityOffIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import { type IBodyProps } from "./IBodyProps"; import { type IBodyProps } from "./IBodyProps";
@@ -34,6 +35,7 @@ const HiddenBody = ({ mxEvent, ref }: IBodyProps): JSX.Element => {
return ( return (
<span className="mx_HiddenBody" ref={ref}> <span className="mx_HiddenBody" ref={ref}>
<VisibilityOffIcon />
{text} {text}
</span> </span>
); );

View File

@@ -9,8 +9,14 @@ Please see LICENSE files in the repository root for full details.
import React, { type AllHTMLAttributes, createRef } from "react"; import React, { type AllHTMLAttributes, createRef } from "react";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { type MediaEventContent } from "matrix-js-sdk/src/types"; import { type MediaEventContent } from "matrix-js-sdk/src/types";
import { MsgType } from "matrix-js-sdk/src/matrix";
import { Button } from "@vector-im/compound-web"; import { Button } from "@vector-im/compound-web";
import { DownloadIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; import {
AttachmentIcon,
DownloadIcon,
VideoCallSolidIcon,
VolumeOnSolidIcon,
} from "@vector-im/compound-design-tokens/assets/web/icons";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import Modal from "../../../Modal"; import Modal from "../../../Modal";
@@ -190,9 +196,17 @@ export default class MFileBody extends React.Component<IProps, IState> {
let placeholder: React.ReactNode = null; let placeholder: React.ReactNode = null;
if (showGenericPlaceholder) { if (showGenericPlaceholder) {
let icon = <AttachmentIcon />;
// MFileBody is not generally used for Audio/Video but can be as part of ReplyTile
if (this.content.msgtype === MsgType.Audio) {
icon = <VolumeOnSolidIcon />;
} else if (this.content.msgtype === MsgType.Video) {
icon = <VideoCallSolidIcon />;
}
placeholder = ( placeholder = (
<AccessibleButton className="mx_MediaBody mx_MFileBody_info" onClick={this.onPlaceholderClick}> <AccessibleButton className="mx_MediaBody mx_MFileBody_info" onClick={this.onPlaceholderClick}>
<span className="mx_MFileBody_info_icon" /> <span className="mx_MFileBody_info_icon">{icon}</span>
<TextWithTooltip tooltip={presentableTextForFile(this.content, _t("common|attachment"), true)}> <TextWithTooltip tooltip={presentableTextForFile(this.content, _t("common|attachment"), true)}>
<span className="mx_MFileBody_info_filename"> <span className="mx_MFileBody_info_filename">
{presentableTextForFile(this.content, _t("common|attachment"), true, true)} {presentableTextForFile(this.content, _t("common|attachment"), true, true)}

View File

@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React, { type JSX } from "react"; import React, { type JSX } from "react";
import { type MatrixEvent } from "matrix-js-sdk/src/matrix"; import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
import { VideoCallSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import WidgetStore from "../../../stores/WidgetStore"; import WidgetStore from "../../../stores/WidgetStore";
@@ -41,6 +42,7 @@ export default class MJitsiWidgetEvent extends React.PureComponent<IProps> {
// removed // removed
return ( return (
<EventTileBubble <EventTileBubble
icon={<VideoCallSolidIcon />}
className="mx_MJitsiWidgetEvent" className="mx_MJitsiWidgetEvent"
title={_t("timeline|m.widget|jitsi_ended", { senderName })} title={_t("timeline|m.widget|jitsi_ended", { senderName })}
timestamp={this.props.timestamp} timestamp={this.props.timestamp}
@@ -50,6 +52,7 @@ export default class MJitsiWidgetEvent extends React.PureComponent<IProps> {
// modified // modified
return ( return (
<EventTileBubble <EventTileBubble
icon={<VideoCallSolidIcon />}
className="mx_MJitsiWidgetEvent" className="mx_MJitsiWidgetEvent"
title={_t("timeline|m.widget|jitsi_updated", { senderName })} title={_t("timeline|m.widget|jitsi_updated", { senderName })}
subtitle={joinCopy} subtitle={joinCopy}
@@ -60,6 +63,7 @@ export default class MJitsiWidgetEvent extends React.PureComponent<IProps> {
// assume added // assume added
return ( return (
<EventTileBubble <EventTileBubble
icon={<VideoCallSolidIcon />}
className="mx_MJitsiWidgetEvent" className="mx_MJitsiWidgetEvent"
title={_t("timeline|m.widget|jitsi_started", { senderName })} title={_t("timeline|m.widget|jitsi_started", { senderName })}
subtitle={joinCopy} subtitle={joinCopy}

View File

@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React, { type JSX } from "react"; import React, { type JSX } from "react";
import { type MatrixEvent } from "matrix-js-sdk/src/matrix"; import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
import { LockSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import { getNameForEventRoom, userLabelForEventRoom } from "../../../utils/KeyVerificationStateObserver"; import { getNameForEventRoom, userLabelForEventRoom } from "../../../utils/KeyVerificationStateObserver";
@@ -73,6 +74,7 @@ const MKeyVerificationRequest: React.FC<Props> = ({ mxEvent, timestamp }) => {
return ( return (
<EventTileBubble <EventTileBubble
icon={<LockSolidIcon />}
className="mx_cryptoEvent mx_cryptoEvent_icon" className="mx_cryptoEvent mx_cryptoEvent_icon"
title={title} title={title}
subtitle={subtitle} subtitle={subtitle}

View File

@@ -11,6 +11,7 @@ import classNames from "classnames";
import { type MatrixEvent, MatrixEventEvent, type Relations, RelationsEvent } from "matrix-js-sdk/src/matrix"; import { type MatrixEvent, MatrixEventEvent, type Relations, RelationsEvent } from "matrix-js-sdk/src/matrix";
import { uniqBy } from "lodash"; import { uniqBy } from "lodash";
import { UnstableValue } from "matrix-js-sdk/src/NamespacedValue"; import { UnstableValue } from "matrix-js-sdk/src/NamespacedValue";
import { ReactionAddIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import { isContentActionable } from "../../../utils/EventUtils"; import { isContentActionable } from "../../../utils/EventUtils";
@@ -54,7 +55,9 @@ const ReactButton: React.FC<IProps> = ({ mxEvent, reactions }) => {
}} }}
isExpanded={menuDisplayed} isExpanded={menuDisplayed}
ref={button} ref={button}
/> >
<ReactionAddIcon />
</ContextMenuTooltipButton>
{contextMenu} {contextMenu}
</React.Fragment> </React.Fragment>

View File

@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React, { useContext, type JSX } from "react"; import React, { useContext, type JSX } from "react";
import { type MatrixClient } from "matrix-js-sdk/src/matrix"; import { type MatrixClient } from "matrix-js-sdk/src/matrix";
import { DeleteIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
@@ -34,6 +35,7 @@ const RedactedBody = ({ mxEvent, ref }: IBodyProps): JSX.Element => {
return ( return (
<span className="mx_RedactedBody" ref={ref} title={titleText}> <span className="mx_RedactedBody" ref={ref} title={titleText}>
<DeleteIcon />
{text} {text}
</span> </span>
); );

View File

@@ -10,6 +10,7 @@ Please see LICENSE files in the repository root for full details.
import React, { type JSX, useCallback } from "react"; import React, { type JSX, useCallback } from "react";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { type MatrixEvent, type Room, type RoomState } from "matrix-js-sdk/src/matrix"; import { type MatrixEvent, type Room, type RoomState } from "matrix-js-sdk/src/matrix";
import { ChatSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import dis from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher";
import { Action } from "../../../dispatcher/actions"; import { Action } from "../../../dispatcher/actions";
@@ -89,6 +90,7 @@ export const RoomPredecessorTile: React.FC<IProps> = ({ mxEvent, timestamp }) =>
return ( return (
<EventTileBubble <EventTileBubble
icon={<ChatSolidIcon />}
className="mx_CreateEvent" className="mx_CreateEvent"
title={_t("timeline|m.room.create|continuation")} title={_t("timeline|m.room.create|continuation")}
timestamp={timestamp} timestamp={timestamp}
@@ -128,6 +130,7 @@ export const RoomPredecessorTile: React.FC<IProps> = ({ mxEvent, timestamp }) =>
return ( return (
<EventTileBubble <EventTileBubble
icon={<ChatSolidIcon />}
className="mx_CreateEvent" className="mx_CreateEvent"
title={_t("timeline|m.room.create|continuation")} title={_t("timeline|m.room.create|continuation")}
subtitle={link} subtitle={link}

View File

@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React, { type JSX, useContext, useEffect } from "react"; import React, { type JSX, useContext, useEffect } from "react";
import { type Room } from "matrix-js-sdk/src/matrix"; import { type Room } from "matrix-js-sdk/src/matrix";
import { OverflowHorizontalIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import BaseCard from "./BaseCard"; import BaseCard from "./BaseCard";
@@ -73,7 +74,9 @@ const WidgetCard: React.FC<IProps> = ({ room, widgetId, onClose }) => {
onClick={openMenu} onClick={openMenu}
isExpanded={menuDisplayed} isExpanded={menuDisplayed}
label={_t("common|options")} label={_t("common|options")}
/> >
<OverflowHorizontalIcon />
</ContextMenuButton>
{contextMenu} {contextMenu}
</div> </div>
); );

View File

@@ -38,9 +38,9 @@ interface Props {
} }
const icons: Record<E2EStatus, JSX.Element> = { const icons: Record<E2EStatus, JSX.Element> = {
[E2EStatus.Warning]: <ErrorSolidIcon className="mx_E2EIcon_warning" />, [E2EStatus.Warning]: <ErrorSolidIcon color="var(--cpd-color-icon-critical-primary)" />,
[E2EStatus.Normal]: <LockSolidIcon className="mx_E2EIcon_normal" />, [E2EStatus.Normal]: <LockSolidIcon color="var(--cpd-color-icon-tertiary)" />,
[E2EStatus.Verified]: <ShieldIcon className="mx_E2EIcon_verified" />, [E2EStatus.Verified]: <ShieldIcon color="var(--cpd-color-icon-success-primary)" />,
}; };
const E2EIcon: React.FC<Props> = ({ isUser, status, className, size, onClick, hideTooltip, tooltipPlacement }) => { const E2EIcon: React.FC<Props> = ({ isUser, status, className, size, onClick, hideTooltip, tooltipPlacement }) => {

View File

@@ -35,6 +35,7 @@ import {
} from "matrix-js-sdk/src/crypto-api"; } from "matrix-js-sdk/src/crypto-api";
import { Tooltip } from "@vector-im/compound-web"; import { Tooltip } from "@vector-im/compound-web";
import { uniqueId } from "lodash"; import { uniqueId } from "lodash";
import { ErrorSolidIcon, InfoIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import ReplyChain from "../elements/ReplyChain"; import ReplyChain from "../elements/ReplyChain";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
@@ -1518,13 +1519,13 @@ function E2ePadlockDecryptionFailure(props: Omit<IE2ePadlockProps, "title" | "ic
} }
enum E2ePadlockIcon { enum E2ePadlockIcon {
/** grey shield */ /** Compound Info icon in grey */
Normal = "normal", Normal = "normal",
/** red shield with (!) */ /** Compound ErrorSolid icon in red */
Warning = "warning", Warning = "warning",
/** key in grey circle */ /** Compound ErrorSolid icon in grey */
DecryptionFailure = "decryption_failure", DecryptionFailure = "decryption_failure",
} }
@@ -1534,6 +1535,12 @@ interface IE2ePadlockProps {
} }
class E2ePadlock extends React.Component<IE2ePadlockProps> { class E2ePadlock extends React.Component<IE2ePadlockProps> {
private static icons: Record<E2ePadlockIcon, JSX.Element> = {
[E2ePadlockIcon.Normal]: <InfoIcon color="var(--cpd-color-icon-tertiary)" />,
[E2ePadlockIcon.Warning]: <ErrorSolidIcon color="var(--cpd-color-icon-critical-primary)" />,
[E2ePadlockIcon.DecryptionFailure]: <ErrorSolidIcon color="var(--cpd-color-icon-tertiary)" />,
};
public constructor(props: IE2ePadlockProps) { public constructor(props: IE2ePadlockProps) {
super(props); super(props);
@@ -1543,12 +1550,13 @@ class E2ePadlock extends React.Component<IE2ePadlockProps> {
} }
public render(): ReactNode { public render(): ReactNode {
const classes = `mx_EventTile_e2eIcon mx_EventTile_e2eIcon_${this.props.icon}`;
// We specify isTriggerInteractive=true and make the div interactive manually as a workaround for // We specify isTriggerInteractive=true and make the div interactive manually as a workaround for
// https://github.com/element-hq/compound/issues/294 // https://github.com/element-hq/compound/issues/294
return ( return (
<Tooltip label={this.props.title} isTriggerInteractive={true}> <Tooltip label={this.props.title} isTriggerInteractive={true}>
<div className={classes} tabIndex={0} aria-label={_t("timeline|e2e_state")} /> <div className="mx_EventTile_e2eIcon" tabIndex={0} aria-label={_t("timeline|e2e_state")}>
{E2ePadlock.icons[this.props.icon]}
</div>
</Tooltip> </Tooltip>
); );
} }

View File

@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React from "react"; import React from "react";
import { EventTimeline } from "matrix-js-sdk/src/matrix"; import { EventTimeline } from "matrix-js-sdk/src/matrix";
import { VisibilityOffIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import EventTileBubble from "../messages/EventTileBubble"; import EventTileBubble from "../messages/EventTileBubble";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
@@ -28,6 +29,7 @@ const HistoryTile: React.FC = () => {
return ( return (
<EventTileBubble <EventTileBubble
icon={<VisibilityOffIcon />}
className="mx_HistoryTile" className="mx_HistoryTile"
title={_t("timeline|historical_messages_unavailable")} title={_t("timeline|historical_messages_unavailable")}
subtitle={subtitle} subtitle={subtitle}

View File

@@ -9,6 +9,7 @@ Please see LICENSE files in the repository root for full details.
import React, { type JSX, useContext } from "react"; import React, { type JSX, useContext } from "react";
import { EventType, type Room, type User, type MatrixClient } from "matrix-js-sdk/src/matrix"; import { EventType, type Room, type User, type MatrixClient } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types"; import { KnownMembership } from "matrix-js-sdk/src/types";
import { ErrorSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import DMRoomMap from "../../../utils/DMRoomMap"; import DMRoomMap from "../../../utils/DMRoomMap";
@@ -291,7 +292,8 @@ const NewRoomIntro: React.FC = () => {
<li className="mx_NewRoomIntro"> <li className="mx_NewRoomIntro">
{!hasExpectedEncryptionSettings(cli, room) && ( {!hasExpectedEncryptionSettings(cli, room) && (
<EventTileBubble <EventTileBubble
className="mx_cryptoEvent mx_cryptoEvent_icon_warning" icon={<ErrorSolidIcon color="var(--cpd-color-icon-critical-primary)" />}
className="mx_cryptoEvent"
title={_t("room|intro|unencrypted_warning")} title={_t("room|intro|unencrypted_warning")}
subtitle={subtitle} subtitle={subtitle}
/> />

View File

@@ -112,8 +112,6 @@ export default class ReplyTile extends React.PureComponent<IProps> {
const classes = classNames("mx_ReplyTile", { const classes = classNames("mx_ReplyTile", {
mx_ReplyTile_inline: msgType === MsgType.Emote, mx_ReplyTile_inline: msgType === MsgType.Emote,
mx_ReplyTile_info: isInfoMessage && !mxEvent.isRedacted(), mx_ReplyTile_info: isInfoMessage && !mxEvent.isRedacted(),
mx_ReplyTile_audio: msgType === MsgType.Audio,
mx_ReplyTile_video: msgType === MsgType.Video,
}); });
let permalink = "#"; let permalink = "#";

View File

@@ -7,6 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/ */
import React, { type JSX } from "react"; import React, { type JSX } from "react";
import { VoiceCallSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton"; import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
@@ -59,7 +60,9 @@ class DialPadButton extends React.PureComponent<DigitButtonProps | DialButtonPro
className="mx_DialPad_button mx_DialPad_dialButton" className="mx_DialPad_button mx_DialPad_dialButton"
onClick={this.onClick} onClick={this.onClick}
aria-label={_t("voip|dial")} aria-label={_t("voip|dial")}
/> >
<VoiceCallSolidIcon />
</AccessibleButton>
); );
} }
} }

View File

@@ -594,12 +594,14 @@ describe("RoomView", () => {
const { container } = await renderRoomView(); const { container } = await renderRoomView();
// We no longer show the grey shield for encrypted rooms, so it should not be there. // We no longer show the grey shield for encrypted rooms, so it should not be there.
await waitFor(() => expect(container.querySelector(".mx_E2EIcon_normal")).not.toBeInTheDocument()); await waitFor(() => expect(container.querySelector(".mx_E2EIcon")).not.toBeInTheDocument());
const verificationStatus = new UserVerificationStatus(true, true, false); const verificationStatus = new UserVerificationStatus(true, true, false);
jest.spyOn(cli.getCrypto()!, "getUserVerificationStatus").mockResolvedValue(verificationStatus); jest.spyOn(cli.getCrypto()!, "getUserVerificationStatus").mockResolvedValue(verificationStatus);
cli.emit(CryptoEvent.UserTrustStatusChanged, cli.getSafeUserId(), verificationStatus); cli.emit(CryptoEvent.UserTrustStatusChanged, cli.getSafeUserId(), verificationStatus);
await waitFor(() => expect(container.querySelector(".mx_E2EIcon_verified")).toBeInTheDocument()); await waitFor(() =>
expect(container.querySelector(".mx_E2EIcon")).toHaveAccessibleName("Everyone in this room is verified"),
);
}); });
describe("video rooms", () => { describe("video rooms", () => {

View File

@@ -63,6 +63,17 @@ exports[`MessagePanel should handle lots of membership events quickly 1`] = `
<div <div
class="mx_EventTileBubble mx_HistoryTile" class="mx_EventTileBubble mx_HistoryTile"
> >
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="m16.1 13.3-1.45-1.45q.225-1.175-.675-2.2t-2.325-.8L10.2 7.4q.424-.2.863-.3A4.2 4.2 0 0 1 12 7q1.875 0 3.188 1.312Q16.5 9.625 16.5 11.5q0 .5-.1.938t-.3.862m3.2 3.15-1.45-1.4a11 11 0 0 0 1.688-1.588A9 9 0 0 0 20.8 11.5q-1.25-2.524-3.588-4.013Q14.875 6 12 6q-.724 0-1.425.1a10 10 0 0 0-1.375.3L7.65 4.85A11.1 11.1 0 0 1 12 4q3.575 0 6.425 1.887T22.7 10.8a.8.8 0 0 1 .1.313q.025.188.025.387a2 2 0 0 1-.125.7 10.9 10.9 0 0 1-3.4 4.25m-.2 5.45-3.5-3.45q-.874.274-1.762.413Q12.95 19 12 19q-3.575 0-6.425-1.887T1.3 12.2a.8.8 0 0 1-.1-.312 3 3 0 0 1 0-.763.8.8 0 0 1 .1-.3Q1.825 9.7 2.55 8.75A13.3 13.3 0 0 1 4.15 7L2.075 4.9a.93.93 0 0 1-.275-.688q0-.412.3-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275l17 17q.275.275.288.688a.93.93 0 0 1-.288.712.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275M5.55 8.4q-.725.65-1.325 1.425A9 9 0 0 0 3.2 11.5q1.25 2.524 3.588 4.012T12 17q.5 0 .975-.062.475-.063.975-.138l-.9-.95q-.274.075-.525.113A3.5 3.5 0 0 1 12 16q-1.875 0-3.187-1.312Q7.5 13.375 7.5 11.5q0-.274.038-.525.037-.25.112-.525z"
/>
</svg>
<div <div
class="mx_EventTileBubble_title" class="mx_EventTileBubble_title"
> >

View File

@@ -141,8 +141,20 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`]
class="mx_NewRoomIntro" class="mx_NewRoomIntro"
> >
<div <div
class="mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon_warning" class="mx_EventTileBubble mx_cryptoEvent"
> >
<svg
color="var(--cpd-color-icon-critical-primary)"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
/>
</svg>
<div <div
class="mx_EventTileBubble_title" class="mx_EventTileBubble_title"
> >
@@ -311,8 +323,20 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
class="mx_NewRoomIntro" class="mx_NewRoomIntro"
> >
<div <div
class="mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon_warning" class="mx_EventTileBubble mx_cryptoEvent"
> >
<svg
color="var(--cpd-color-icon-critical-primary)"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
/>
</svg>
<div <div
class="mx_EventTileBubble_title" class="mx_EventTileBubble_title"
> >
@@ -676,6 +700,17 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
<div <div
class="mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon" class="mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon"
> >
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 22q-.824 0-1.412-.587A1.93 1.93 0 0 1 4 20V10q0-.825.588-1.412A1.93 1.93 0 0 1 6 8h1V6q0-2.075 1.463-3.537Q9.926 1 12 1q2.075 0 3.537 1.463Q17 3.925 17 6v2h1q.824 0 1.413.588Q20 9.175 20 10v10q0 .824-.587 1.413A1.93 1.93 0 0 1 18 22zM9 8h6V6q0-1.25-.875-2.125A2.9 2.9 0 0 0 12 3q-1.25 0-2.125.875A2.9 2.9 0 0 0 9 6z"
/>
</svg>
<div <div
class="mx_EventTileBubble_title" class="mx_EventTileBubble_title"
> >
@@ -2974,7 +3009,7 @@ exports[`RoomView should not display the timeline when the room encryption is lo
style="width: 12px; height: 12px;" style="width: 12px; height: 12px;"
> >
<svg <svg
class="mx_E2EIcon_verified" color="var(--cpd-color-icon-success-primary)"
fill="currentColor" fill="currentColor"
height="1em" height="1em"
viewBox="0 0 24 24" viewBox="0 0 24 24"

View File

@@ -27,7 +27,7 @@ exports[`<UntrustedDeviceDialog /> should display the dialog for the device of a
style="width: 24px; height: 24px;" style="width: 24px; height: 24px;"
> >
<svg <svg
class="mx_E2EIcon_warning" color="var(--cpd-color-icon-critical-primary)"
fill="currentColor" fill="currentColor"
height="1em" height="1em"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@@ -116,7 +116,7 @@ exports[`<UntrustedDeviceDialog /> should display the dialog for the device of t
style="width: 24px; height: 24px;" style="width: 24px; height: 24px;"
> >
<svg <svg
class="mx_E2EIcon_warning" color="var(--cpd-color-icon-critical-primary)"
fill="currentColor" fill="currentColor"
height="1em" height="1em"
viewBox="0 0 24 24" viewBox="0 0 24 24"

View File

@@ -37,7 +37,7 @@ exports[`VerificationRequestDialog After scanning QR, shows confirmation dialog
style="width: 128px; height: 128px;" style="width: 128px; height: 128px;"
> >
<svg <svg
class="mx_E2EIcon_verified" color="var(--cpd-color-icon-success-primary)"
fill="currentColor" fill="currentColor"
height="1em" height="1em"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@@ -313,7 +313,7 @@ exports[`VerificationRequestDialog Shows a successful message if verification fi
style="width: 128px; height: 128px;" style="width: 128px; height: 128px;"
> >
<svg <svg
class="mx_E2EIcon_verified" color="var(--cpd-color-icon-success-primary)"
fill="currentColor" fill="currentColor"
height="1em" height="1em"
viewBox="0 0 24 24" viewBox="0 0 24 24"

View File

@@ -73,7 +73,7 @@ exports[`<Users /> should render a single device - signed by owner 1`] = `
data-testid="e2e-icon" data-testid="e2e-icon"
> >
<svg <svg
class="mx_E2EIcon_normal" color="var(--cpd-color-icon-tertiary)"
fill="currentColor" fill="currentColor"
height="1em" height="1em"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@@ -237,7 +237,7 @@ exports[`<Users /> should render a single device - unsigned 1`] = `
data-testid="e2e-icon" data-testid="e2e-icon"
> >
<svg <svg
class="mx_E2EIcon_warning" color="var(--cpd-color-icon-critical-primary)"
fill="currentColor" fill="currentColor"
height="1em" height="1em"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@@ -401,7 +401,7 @@ exports[`<Users /> should render a single device - verified by cross-signing 1`]
data-testid="e2e-icon" data-testid="e2e-icon"
> >
<svg <svg
class="mx_E2EIcon_verified" color="var(--cpd-color-icon-success-primary)"
fill="currentColor" fill="currentColor"
height="1em" height="1em"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@@ -555,7 +555,7 @@ exports[`<Users /> should render a single user 1`] = `
data-testid="e2e-icon" data-testid="e2e-icon"
> >
<svg <svg
class="mx_E2EIcon_verified" color="var(--cpd-color-icon-success-primary)"
fill="currentColor" fill="currentColor"
height="1em" height="1em"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@@ -587,7 +587,7 @@ exports[`<Users /> should render a single user 1`] = `
data-testid="e2e-icon" data-testid="e2e-icon"
> >
<svg <svg
class="mx_E2EIcon_verified" color="var(--cpd-color-icon-success-primary)"
fill="currentColor" fill="currentColor"
height="1em" height="1em"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@@ -613,7 +613,7 @@ exports[`<Users /> should render a single user 1`] = `
data-testid="e2e-icon" data-testid="e2e-icon"
> >
<svg <svg
class="mx_E2EIcon_normal" color="var(--cpd-color-icon-tertiary)"
fill="currentColor" fill="currentColor"
height="1em" height="1em"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@@ -637,7 +637,7 @@ exports[`<Users /> should render a single user 1`] = `
data-testid="e2e-icon" data-testid="e2e-icon"
> >
<svg <svg
class="mx_E2EIcon_warning" color="var(--cpd-color-icon-critical-primary)"
fill="currentColor" fill="currentColor"
height="1em" height="1em"
viewBox="0 0 24 24" viewBox="0 0 24 24"

View File

@@ -28,7 +28,19 @@ exports[`AppTile destroys non-persisted right panel widget on room change 1`] =
class="mx_AccessibleButton mx_BaseCard_header_title_button--option" class="mx_AccessibleButton mx_BaseCard_header_title_button--option"
role="button" role="button"
tabindex="0" tabindex="0"
/> >
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 4 12q0-.825.588-1.412A1.93 1.93 0 0 1 6 10q.824 0 1.412.588Q8 11.175 8 12t-.588 1.412A1.93 1.93 0 0 1 6 14m6 0q-.825 0-1.412-.588A1.93 1.93 0 0 1 10 12q0-.825.588-1.412A1.93 1.93 0 0 1 12 10q.825 0 1.412.588Q14 11.175 14 12t-.588 1.412A1.93 1.93 0 0 1 12 14m6 0q-.824 0-1.413-.588A1.93 1.93 0 0 1 16 12q0-.825.587-1.412A1.93 1.93 0 0 1 18 10q.824 0 1.413.588Q20 11.175 20 12t-.587 1.412A1.93 1.93 0 0 1 18 14"
/>
</svg>
</div>
</div> </div>
<button <button
aria-labelledby="_r_0_" aria-labelledby="_r_0_"

View File

@@ -85,4 +85,31 @@ describe("<MFileBody/>", () => {
expect(getByRole("link", { name: "Download" })).toBeInTheDocument(); expect(getByRole("link", { name: "Download" })).toBeInTheDocument();
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });
it.each(["m.file", "m.audio", "m.video"])("should show %s generic placeholder", async (msgtype) => {
const mediaEvent = new MatrixEvent({
room_id: "!room:server",
sender: userId,
type: EventType.RoomMessage,
content: {
body: "alt",
msgtype,
url: "mxc://server/image",
},
});
const { container, getByRole } = render(
<ScopedRoomContextProvider {...({ timelineRenderingType: TimelineRenderingType.File } as any)}>
<MFileBody
{...props}
mxEvent={mediaEvent}
mediaEventHelper={new MediaEventHelper(mediaEvent)}
showGenericPlaceholder={true}
/>
</ScopedRoomContextProvider>,
);
expect(getByRole("button", { name: "alt" })).toBeInTheDocument();
expect(container).toMatchSnapshot();
});
}); });

View File

@@ -35,3 +35,126 @@ exports[`<MFileBody/> should show a download button in file rendering type 1`] =
</span> </span>
</div> </div>
`; `;
exports[`<MFileBody/> should show m.audio generic placeholder 1`] = `
<div>
<span
class="mx_MFileBody"
>
<div
class="mx_AccessibleButton mx_MediaBody mx_MFileBody_info"
role="button"
tabindex="0"
>
<span
class="mx_MFileBody_info_icon"
>
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M3 14v-4a2 2 0 0 1 2-2h2l3.293-3.293c.63-.63 1.707-.184 1.707.707v13.172c0 .89-1.077 1.337-1.707.707L7 16H5a2 2 0 0 1-2-2m11.122-5.536a1 1 0 0 1 1.414 0A5 5 0 0 1 17 12c0 1.38-.56 2.632-1.464 3.536a1 1 0 0 1-1.415-1.415 3 3 0 0 0 .88-2.121c0-.829-.335-1.577-.88-2.121a1 1 0 0 1 0-1.415"
/>
<path
d="M16.95 5.636a1 1 0 0 1 1.414 0A8.98 8.98 0 0 1 21 12a8.98 8.98 0 0 1-2.636 6.364 1 1 0 0 1-1.414-1.414A6.98 6.98 0 0 0 19 12a6.98 6.98 0 0 0-2.05-4.95 1 1 0 0 1 0-1.414"
/>
</svg>
</span>
<span
aria-labelledby="_r_6_"
tabindex="0"
>
<span
class="mx_MFileBody_info_filename"
>
alt
</span>
</span>
</div>
</span>
</div>
`;
exports[`<MFileBody/> should show m.file generic placeholder 1`] = `
<div>
<span
class="mx_MFileBody"
>
<div
class="mx_AccessibleButton mx_MediaBody mx_MFileBody_info"
role="button"
tabindex="0"
>
<span
class="mx_MFileBody_info_icon"
>
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11.5 22q-2.3 0-3.9-1.6T6 16.5V6q0-1.65 1.175-2.825T10 2t2.825 1.175T14 6v9.5q0 1.05-.725 1.775T11.5 18t-1.775-.725T9 15.5V6.75A.73.73 0 0 1 9.75 6a.73.73 0 0 1 .75.75v8.75q0 .424.287.712.288.288.713.288.424 0 .713-.288a.97.97 0 0 0 .287-.712V6q0-1.05-.725-1.775T10 3.5t-1.775.725T7.5 6v10.5q0 1.65 1.175 2.825T11.5 20.5t2.825-1.175T15.5 16.5V6.75a.73.73 0 0 1 .75-.75.73.73 0 0 1 .75.75v9.75q0 2.3-1.6 3.9T11.5 22"
/>
</svg>
</span>
<span
aria-labelledby="_r_0_"
tabindex="0"
>
<span
class="mx_MFileBody_info_filename"
>
alt
</span>
</span>
</div>
</span>
</div>
`;
exports[`<MFileBody/> should show m.video generic placeholder 1`] = `
<div>
<span
class="mx_MFileBody"
>
<div
class="mx_AccessibleButton mx_MediaBody mx_MFileBody_info"
role="button"
tabindex="0"
>
<span
class="mx_MFileBody_info_icon"
>
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 4h10a2 2 0 0 1 2 2v4.286l3.35-2.871a1 1 0 0 1 1.65.76v7.65a1 1 0 0 1-1.65.76L18 13.715V18a2 2 0 0 1-2 2H6a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4"
/>
</svg>
</span>
<span
aria-labelledby="_r_c_"
tabindex="0"
>
<span
class="mx_MFileBody_info_filename"
>
alt
</span>
</span>
</div>
</span>
</div>
`;

View File

@@ -172,7 +172,19 @@ exports[`<MImageBody/> should render MFileBody for svg with no thumbnail 1`] = `
> >
<span <span
class="mx_MFileBody_info_icon" class="mx_MFileBody_info_icon"
/> >
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11.5 22q-2.3 0-3.9-1.6T6 16.5V6q0-1.65 1.175-2.825T10 2t2.825 1.175T14 6v9.5q0 1.05-.725 1.775T11.5 18t-1.775-.725T9 15.5V6.75A.73.73 0 0 1 9.75 6a.73.73 0 0 1 .75.75v8.75q0 .424.287.712.288.288.713.288.424 0 .713-.288a.97.97 0 0 0 .287-.712V6q0-1.05-.725-1.775T10 3.5t-1.775.725T7.5 6v10.5q0 1.65 1.175 2.825T11.5 20.5t2.825-1.175T15.5 16.5V6.75a.73.73 0 0 1 .75-.75.73.73 0 0 1 .75.75v9.75q0 2.3-1.6 3.9T11.5 22"
/>
</svg>
</span>
<span <span
aria-labelledby="_r_0_" aria-labelledby="_r_0_"
tabindex="0" tabindex="0"

View File

@@ -5,6 +5,17 @@ exports[`<RoomPredecessorTile /> Renders as expected 1`] = `
<div <div
class="mx_EventTileBubble mx_CreateEvent" class="mx_EventTileBubble mx_CreateEvent"
> >
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M2.95 16.3 1.5 21.25a.94.94 0 0 0 .25 1 .94.94 0 0 0 1 .25l4.95-1.45a10.2 10.2 0 0 0 2.1.712Q10.875 22 12 22a9.7 9.7 0 0 0 3.9-.788 10.1 10.1 0 0 0 3.175-2.137q1.35-1.35 2.137-3.175A9.7 9.7 0 0 0 22 12a9.7 9.7 0 0 0-.788-3.9 10.1 10.1 0 0 0-2.137-3.175q-1.35-1.35-3.175-2.137A9.7 9.7 0 0 0 12 2a9.7 9.7 0 0 0-3.9.788 10.1 10.1 0 0 0-3.175 2.137Q3.575 6.275 2.788 8.1A9.7 9.7 0 0 0 2 12q0 1.125.238 2.2.237 1.076.712 2.1"
/>
</svg>
<div <div
class="mx_EventTileBubble_title" class="mx_EventTileBubble_title"
> >

View File

@@ -269,8 +269,8 @@ describe("EventTile", () => {
// there should be a warning shield // there should be a warning shield
expect(container.getElementsByClassName("mx_EventTile_e2eIcon")).toHaveLength(1); expect(container.getElementsByClassName("mx_EventTile_e2eIcon")).toHaveLength(1);
expect(container.getElementsByClassName("mx_EventTile_e2eIcon")[0].classList).toContain( expect(container.getElementsByClassName("mx_EventTile_e2eIcon")[0]).toHaveAccessibleName(
"mx_EventTile_e2eIcon_warning", "Encrypted by a device not verified by its owner.",
); );
}); });
@@ -298,10 +298,13 @@ describe("EventTile", () => {
it.each([ it.each([
[EventShieldReason.UNKNOWN, "Unknown error"], [EventShieldReason.UNKNOWN, "Unknown error"],
[EventShieldReason.UNVERIFIED_IDENTITY, "unverified user"], [EventShieldReason.UNVERIFIED_IDENTITY, "Encrypted by an unverified user."],
[EventShieldReason.UNSIGNED_DEVICE, "device not verified by its owner"], [EventShieldReason.UNSIGNED_DEVICE, "Encrypted by a device not verified by its owner."],
[EventShieldReason.UNKNOWN_DEVICE, "unknown or deleted device"], [EventShieldReason.UNKNOWN_DEVICE, "Encrypted by an unknown or deleted device."],
[EventShieldReason.AUTHENTICITY_NOT_GUARANTEED, "can't be guaranteed"], [
EventShieldReason.AUTHENTICITY_NOT_GUARANTEED,
"The authenticity of this encrypted message can't be guaranteed on this device.",
],
[EventShieldReason.MISMATCHED_SENDER_KEY, "Encrypted by an unverified session"], [EventShieldReason.MISMATCHED_SENDER_KEY, "Encrypted by an unverified session"],
[EventShieldReason.SENT_IN_CLEAR, "Not encrypted"], [EventShieldReason.SENT_IN_CLEAR, "Not encrypted"],
[EventShieldReason.VERIFICATION_VIOLATION, "Sender's verified identity was reset"], [EventShieldReason.VERIFICATION_VIOLATION, "Sender's verified identity was reset"],
@@ -326,12 +329,7 @@ describe("EventTile", () => {
const e2eIcons = container.getElementsByClassName("mx_EventTile_e2eIcon"); const e2eIcons = container.getElementsByClassName("mx_EventTile_e2eIcon");
expect(e2eIcons).toHaveLength(1); expect(e2eIcons).toHaveLength(1);
expect(e2eIcons[0].classList).toContain("mx_EventTile_e2eIcon_normal"); expect(e2eIcons[0]).toHaveAccessibleName(expectedText);
fireEvent.focus(e2eIcons[0]);
expect(e2eIcons[0].getAttribute("aria-labelledby")).toBeTruthy();
expect(document.getElementById(e2eIcons[0].getAttribute("aria-labelledby")!)).toHaveTextContent(
expectedText,
);
}); });
describe("undecryptable event", () => { describe("undecryptable event", () => {
@@ -360,8 +358,8 @@ describe("EventTile", () => {
expect(eventTiles).toHaveLength(1); expect(eventTiles).toHaveLength(1);
expect(container.getElementsByClassName("mx_EventTile_e2eIcon")).toHaveLength(1); expect(container.getElementsByClassName("mx_EventTile_e2eIcon")).toHaveLength(1);
expect(container.getElementsByClassName("mx_EventTile_e2eIcon")[0].classList).toContain( expect(container.getElementsByClassName("mx_EventTile_e2eIcon")[0]).toHaveAccessibleName(
"mx_EventTile_e2eIcon_decryption_failure", "This message could not be decrypted",
); );
}); });
@@ -436,8 +434,8 @@ describe("EventTile", () => {
// check it was updated // check it was updated
expect(container.getElementsByClassName("mx_EventTile_e2eIcon")).toHaveLength(1); expect(container.getElementsByClassName("mx_EventTile_e2eIcon")).toHaveLength(1);
expect(container.getElementsByClassName("mx_EventTile_e2eIcon")[0].classList).toContain( expect(container.getElementsByClassName("mx_EventTile_e2eIcon")[0]).toHaveAccessibleName(
"mx_EventTile_e2eIcon_warning", "Encrypted by a device not verified by its owner.",
); );
}); });
@@ -481,9 +479,7 @@ describe("EventTile", () => {
// check it was updated // check it was updated
expect(container.getElementsByClassName("mx_EventTile_e2eIcon")).toHaveLength(1); expect(container.getElementsByClassName("mx_EventTile_e2eIcon")).toHaveLength(1);
expect(container.getElementsByClassName("mx_EventTile_e2eIcon")[0].classList).toContain( expect(container.getElementsByClassName("mx_EventTile_e2eIcon")[0]).toHaveAccessibleName("Not encrypted");
"mx_EventTile_e2eIcon_warning",
);
}); });
}); });

View File

@@ -330,10 +330,10 @@ describe("SendWysiwygComposer", () => {
"Left icon when %s", "Left icon when %s",
({ isRichTextEnabled }) => { ({ isRichTextEnabled }) => {
it.each([ it.each([
[E2EStatus.Verified, "mx_E2EIcon_verified"], [E2EStatus.Verified, "Everyone in this room is verified"],
[E2EStatus.Warning, "mx_E2EIcon_warning"], [E2EStatus.Warning, "Someone is using an unknown session"],
[undefined, undefined], [undefined, undefined],
])("Should render left icon when e2eStatus is %s", async (e2eStatus, expectedClass) => { ])("Should render left icon when e2eStatus is %s", async (e2eStatus, expectedLabel) => {
// When // When
customRender(jest.fn(), jest.fn(), false, isRichTextEnabled, undefined, e2eStatus); customRender(jest.fn(), jest.fn(), false, isRichTextEnabled, undefined, e2eStatus);
await waitFor(() => expect(screen.getByRole("textbox")).toHaveAttribute("contentEditable", "true")); await waitFor(() => expect(screen.getByRole("textbox")).toHaveAttribute("contentEditable", "true"));
@@ -341,9 +341,9 @@ describe("SendWysiwygComposer", () => {
// Then // Then
expect(leftIcon).toBeInTheDocument(); expect(leftIcon).toBeInTheDocument();
expect(leftIcon).toHaveClass("mx_E2EIcon"); expect(leftIcon).toHaveClass("mx_E2EIcon");
if (expectedClass) { if (expectedLabel) {
// eslint-disable-next-line jest/no-conditional-expect // eslint-disable-next-line jest/no-conditional-expect
expect(leftIcon.querySelector("svg")).toHaveClass(expectedClass); expect(leftIcon).toHaveAccessibleName(expectedLabel);
} else { } else {
// eslint-disable-next-line jest/no-conditional-expect // eslint-disable-next-line jest/no-conditional-expect
expect(leftIcon.querySelector("svg")).not.toBeInTheDocument(); expect(leftIcon.querySelector("svg")).not.toBeInTheDocument();