diff --git a/playwright/e2e/audio-player/audio-player.spec.ts b/playwright/e2e/audio-player/audio-player.spec.ts index 282440f74e..01b0f23bb4 100644 --- a/playwright/e2e/audio-player/audio-player.spec.ts +++ b/playwright/e2e/audio-player/audio-player.spec.ts @@ -351,7 +351,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => { const composer = thread.locator(".mx_MessageComposer--compact"); // Assert that the reply preview contains audio ReplyTile the file info button 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(); // 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"); // 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(); }); }); diff --git a/playwright/e2e/crypto/crypto.spec.ts b/playwright/e2e/crypto/crypto.spec.ts index ae4db1b0c3..d03fa1454e 100644 --- a/playwright/e2e/crypto/crypto.spec.ts +++ b/playwright/e2e/crypto/crypto.spec.ts @@ -31,15 +31,11 @@ const startDMWithBob = async (page: Page, bob: Bot) => { const testMessages = async (page: Page, bob: Bot, bobRoomId: string) => { // check the invite message - await expect( - page.locator(".mx_EventTile", { hasText: "Hey!" }).locator(".mx_EventTile_e2eIcon_warning"), - ).not.toBeVisible(); + await expect(page.locator(".mx_EventTile", { hasText: "Hey!" }).locator(".mx_EventTile_e2eIcon")).not.toBeVisible(); // Bob sends a response await bob.sendMessage(bobRoomId, "Hoo!"); - await expect( - page.locator(".mx_EventTile", { hasText: "Hoo!" }).locator(".mx_EventTile_e2eIcon_warning"), - ).not.toBeVisible(); + await expect(page.locator(".mx_EventTile", { hasText: "Hoo!" }).locator(".mx_EventTile_e2eIcon")).not.toBeVisible(); }; const bobJoin = async (page: Page, bob: Bot) => { diff --git a/playwright/e2e/crypto/decryption-failure-messages.spec.ts b/playwright/e2e/crypto/decryption-failure-messages.spec.ts index 306e073c00..e0343a8005 100644 --- a/playwright/e2e/crypto/decryption-failure-messages.spec.ts +++ b/playwright/e2e/crypto/decryption-failure-messages.spec.ts @@ -30,69 +30,80 @@ test.describe("Cryptography", function () { test.describe("decryption failure messages", () => { test.skip(isDendrite, "Dendrite lacks support for MSC3967 so requires additional auth here"); - test("should handle device-relative historical messages", async ({ - homeserver, - page, - app, - credentials, - user, - }) => { - test.setTimeout(60000); + test( + "should handle device-relative historical messages", + { tag: "@screenshot" }, + async ({ homeserver, page, app, credentials, user }) => { + test.setTimeout(60000); - // Start with a logged-in session, without key backup, and send a message. - await createRoom(page, "Test room", true); - await sendMessageInCurrentRoom(page, "test test"); + // Start with a logged-in session, without key backup, and send a message. + await createRoom(page, "Test room", true); + await sendMessageInCurrentRoom(page, "test test"); - // Log out, discarding the key for the sent message. - await logOutOfElement(page, true); + // Log out, discarding the key for the sent message. + await logOutOfElement(page, true); - // Log in again, and see how the message looks. - await logIntoElement(page, credentials); - await app.viewRoomByName("Test room"); - const lastTile = page.locator(".mx_EventTile").last(); - await expect(lastTile).toContainText("Historical messages are not available on this device"); - await expect(lastTile.locator(".mx_EventTile_e2eIcon_decryption_failure")).toBeVisible(); + // Log in again, and see how the message looks. + await logIntoElement(page, credentials); + await app.viewRoomByName("Test room"); + const lastTile = page.locator(".mx_EventTile").last(); + await expect(lastTile).toContainText("Historical messages are not available on this device"); + 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. - const secretStorageKey = await enableKeyBackup(app); - await app.viewRoomByName("Test room"); - await sendMessageInCurrentRoom(page, "test2 test2"); + // Now, we set up key backup, and then send another message. + const secretStorageKey = await enableKeyBackup(app); + await app.viewRoomByName("Test room"); + await sendMessageInCurrentRoom(page, "test2 test2"); - // 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. - await page.waitForTimeout(10000); + // 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. + await page.waitForTimeout(10000); - // Finally, log out again, and back in, skipping verification for now, and see what we see. - await logOutOfElement(page); - 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: "I'll verify later" }).click(); - await app.viewRoomByName("Test room"); + // Finally, log out again, and back in, skipping verification for now, and see what we see. + await logOutOfElement(page); + 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: "I'll verify later" }).click(); + await app.viewRoomByName("Test room"); - // In this case, the call to cryptoApi.isEncryptionEnabledInRoom is taking a long time to resolve - await page.waitForTimeout(1000); + // In this case, the call to cryptoApi.isEncryptionEnabledInRoom is taking a long time to resolve + await page.waitForTimeout(1000); - // There should be two historical events in the timeline - const tiles = await page.locator(".mx_EventTile").all(); - expect(tiles.length).toBeGreaterThanOrEqual(2); - // look at the last two tiles only - 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.locator(".mx_EventTile_e2eIcon_decryption_failure")).toBeVisible(); - } + // There should be two historical events in the timeline + const tiles = await page.locator(".mx_EventTile").all(); + expect(tiles.length).toBeGreaterThanOrEqual(2); + // look at the last two tiles only + 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.locator(".mx_EventTile_e2eIcon")).toHaveAccessibleName( + "This message could not be decrypted", + ); + } - // Now verify our device (setting up key backup), and check what happens - await verifySession(app, secretStorageKey); - const tilesAfterVerify = (await page.locator(".mx_EventTile").all()).slice(-2); + // Now verify our device (setting up key backup), and check what happens + await verifySession(app, secretStorageKey); + 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. - await expect(tilesAfterVerify[0]).toContainText("Unable to decrypt message"); - await expect(tilesAfterVerify[0].locator(".mx_EventTile_e2eIcon_decryption_failure")).toBeVisible(); + // 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].locator(".mx_EventTile_e2eIcon")).toHaveAccessibleName( + "This message could not be decrypted", + ); - // The second message should now be decrypted, with a grey shield - await expect(tilesAfterVerify[1]).toContainText("test2 test2"); - await expect(tilesAfterVerify[1].locator(".mx_EventTile_e2eIcon_normal")).toBeVisible(); - }); + // The second message should now be decrypted, with a grey shield + await expect(tilesAfterVerify[1]).toContainText("test2 test2"); + 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.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 // 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].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 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 // standard UTD 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 ({ diff --git a/playwright/e2e/crypto/device-verification.spec.ts b/playwright/e2e/crypto/device-verification.spec.ts index eb43d4dc78..6beda36a98 100644 --- a/playwright/e2e/crypto/device-verification.spec.ts +++ b/playwright/e2e/crypto/device-verification.spec.ts @@ -68,7 +68,7 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => { await doTwoWaySasVerification(page, verifier); 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(); // Check that our device is now cross-signed diff --git a/playwright/e2e/crypto/event-shields.spec.ts b/playwright/e2e/crypto/event-shields.spec.ts index 722a30a0d2..688b81b5b1 100644 --- a/playwright/e2e/crypto/event-shields.spec.ts +++ b/playwright/e2e/crypto/event-shields.spec.ts @@ -77,11 +77,8 @@ test.describe("Cryptography", function () { const last = page.locator(".mx_EventTile_last"); await expect(last).toContainText("Unable to decrypt message"); const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon"); - await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_decryption_failure/); - await lastE2eIcon.focus(); - await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText( - "This message could not be decrypted", - ); + await expect(lastE2eIcon).toHaveAccessibleName("This message could not be decrypted"); + await expect(lastE2eIcon).toMatchScreenshot("event-shield-utd.png"); /* Should show a red padlock for an unencrypted message in an e2e room */ await bob.evaluate( @@ -99,10 +96,8 @@ test.describe("Cryptography", function () { ); 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 lastE2eIcon.focus(); - await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText("Not encrypted"); /* Should show no padlock for an unverified user */ // bob sends a valid event @@ -133,11 +128,8 @@ test.describe("Cryptography", function () { /* should show red padlock for a message from an unverified device */ await bobSecondDevice.sendMessage(testRoomId, "test encrypted from unverified"); await expect(lastTile).toContainText("test encrypted from unverified"); - await expect(lastTileE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/); - await lastTileE2eIcon.focus(); - await expect(await app.getTooltipForElement(lastTileE2eIcon)).toContainText( - "Encrypted by a device not verified by its owner.", - ); + await expect(lastTileE2eIcon).toHaveAccessibleName("Encrypted by a device not verified by its owner."); + await expect(lastE2eIcon).toMatchScreenshot("event-shield-not-verified.png"); /* 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 @@ -153,64 +145,58 @@ test.describe("Cryptography", function () { await app.viewRoomByName("TestRoom"); await expect(last).toContainText("test encrypted from unverified"); - await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/); - await lastE2eIcon.focus(); - await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText( - "Encrypted by a device not verified by its owner.", - ); + await expect(lastE2eIcon).toHaveAccessibleName("Encrypted by a device not verified by its owner."); + await expect(lastE2eIcon).toMatchScreenshot("event-shield-not-verified.png"); }, ); - test("Should show a grey padlock for a key restored from backup", async ({ - page, - app, - bot: bob, - homeserver, - user: aliceCredentials, - }) => { - test.slow(); - const securityKey = await enableKeyBackup(app); + test( + "Should show a grey padlock for a key restored from backup", + { tag: "@screenshot" }, + async ({ page, app, bot: bob, homeserver, user: aliceCredentials }) => { + test.slow(); + const securityKey = await enableKeyBackup(app); - // bob sends a valid event - await bob.sendMessage(testRoomId, "test encrypted 1"); + // bob sends a valid event + await bob.sendMessage(testRoomId, "test encrypted 1"); - const lastTile = page.locator(".mx_EventTile_last"); - const lastTileE2eIcon = lastTile.locator(".mx_EventTile_e2eIcon"); - await expect(lastTile).toContainText("test encrypted 1"); - // no e2e icon - await expect(lastTileE2eIcon).not.toBeVisible(); + const lastTile = page.locator(".mx_EventTile_last"); + const lastTileE2eIcon = lastTile.locator(".mx_EventTile_e2eIcon"); + await expect(lastTile).toContainText("test encrypted 1"); + // no e2e icon + await expect(lastTileE2eIcon).not.toBeVisible(); - // 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. - await page.waitForTimeout(10000); + // 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. + await page.waitForTimeout(10000); - /* log out, and back in */ - await logOutOfElement(page); - // 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 - await page.addInitScript(() => { - // When we reload, the initScript created by the `user`/`pageWithCredentials` fixtures - // 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. - window.localStorage.clear(); - }); - await page.reload(); - await logIntoElementAndVerify(page, aliceCredentials, securityKey); + /* log out, and back in */ + await logOutOfElement(page); + // 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 + await page.addInitScript(() => { + // When we reload, the initScript created by the `user`/`pageWithCredentials` fixtures + // 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. + window.localStorage.clear(); + }); + await page.reload(); + await logIntoElementAndVerify(page, aliceCredentials, securityKey); - /* go back to the test room and find Bob's message again */ - await app.viewRoomById(testRoomId); - 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. - // No shield would have no div mx_EventTile_e2eIcon at all. - await expect(lastTileE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_normal/); - await lastTileE2eIcon.hover(); - // The key is coming from backup, so it is not anymore possible to establish if the claimed device - // creator of this key is authentic. The tooltip should be "The authenticity of this encrypted message can't be guaranteed on this device." - // It is not "Encrypted by an unknown or deleted device." even if the claimed device is actually deleted. - await expect(await app.getTooltipForElement(lastTileE2eIcon)).toContainText( - "The authenticity of this encrypted message can't be guaranteed on this device.", - ); - }); + /* go back to the test room and find Bob's message again */ + await app.viewRoomById(testRoomId); + await expect(lastTile).toContainText("test encrypted 1"); + // 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. + // The key is coming from backup, so it is not anymore possible to establish if the claimed device + // creator of this key is authentic. The tooltip should be "The authenticity of this encrypted message can't be guaranteed on this device." + // It is not "Encrypted by an unknown or deleted device." even if the claimed device is actually deleted. + await expect(lastTileE2eIcon).toHaveAccessibleName( + "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 }) => { // bob has a second, not cross-signed, device @@ -224,7 +210,7 @@ test.describe("Cryptography", function () { // the message should appear, decrypted, with no warning 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(); // 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 await expect( - page.locator(".mx_EventTile", { hasText: "Haa!" }).locator(".mx_EventTile_e2eIcon_warning"), + page.locator(".mx_EventTile", { hasText: "Haa!" }).locator(".mx_EventTile_e2eIcon"), ).toBeVisible(); // a second edit from the verified device should be ok @@ -257,77 +243,69 @@ test.describe("Cryptography", function () { }); 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(); }); - test("should show correct shields on events sent by devices which have since been deleted", async ({ - page, - app, - bot: bob, - 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 - // his user info. - await waitForDevices(app, bob.credentials.userId, 1); + test( + "should show correct shields on events sent by devices which have since been deleted", + { tag: "@screenshot" }, + async ({ page, app, bot: bob, 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 + // his user info. + await waitForDevices(app, bob.credentials.userId, 1); - // Our app is blocked from syncing while Bob sends his messages. - await app.client.network.goOffline(); + // Our app is blocked from syncing while Bob sends his messages. + await app.client.network.goOffline(); - // Bob sends a message from his verified device - await bob.sendMessage(testRoomId, "test encrypted from verified"); + // Bob sends a message from his verified device + await bob.sendMessage(testRoomId, "test encrypted from verified"); - // And one from a second, not cross-signed, device - const bobSecondDevice = await createSecondBotDevice(page, homeserver, bob); - await bobSecondDevice.waitForNextSync(); // make sure the client knows the room is encrypted - await bobSecondDevice.sendMessage(testRoomId, "test encrypted from unverified"); + // And one from a second, not cross-signed, device + const bobSecondDevice = await createSecondBotDevice(page, homeserver, bob); + await bobSecondDevice.waitForNextSync(); // make sure the client knows the room is encrypted + await bobSecondDevice.sendMessage(testRoomId, "test encrypted from unverified"); - // ... and then logs out both devices. - await bob.evaluate((cli) => cli.logout(true)); - await bobSecondDevice.evaluate((cli) => cli.logout(true)); + // ... and then logs out both devices. + await bob.evaluate((cli) => cli.logout(true)); + await bobSecondDevice.evaluate((cli) => cli.logout(true)); - // Let our app start syncing again - await app.client.network.goOnline(); + // Let our app start syncing again + await app.client.network.goOnline(); - // 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"); - await expect(last).toContainText("test encrypted from unverified", { timeout: 20000 }); - const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon"); - await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/); - await lastE2eIcon.focus(); - await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText( - "Encrypted by a device not verified by its owner.", - ); + // 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"); + await expect(last).toContainText("test encrypted from unverified", { timeout: 20000 }); + const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon"); + await expect(lastE2eIcon).toHaveAccessibleName("Encrypted by a device not verified by its owner."); + await expect(lastE2eIcon).toMatchScreenshot("event-shield-not-verified.png"); - const penultimate = page.locator(".mx_EventTile").filter({ hasText: "test encrypted from verified" }); - await assertNoE2EIcon(penultimate, app); - }); + const penultimate = page.locator(".mx_EventTile").filter({ hasText: "test encrypted from verified" }); + await assertNoE2EIcon(penultimate, app); + }, + ); - test("should show correct shields on events sent by users with changed identity", async ({ - page, - app, - bot: bob, - homeserver, - }) => { - // Verify Bob - await verify(app, bob); + test( + "should show correct shields on events sent by users with changed identity", + { tag: "@screenshot" }, + async ({ page, app, bot: bob, homeserver }) => { + // Verify Bob + await verify(app, bob); - // Bob logs in a new device and resets cross-signing - const bobSecondDevice = await createSecondBotDevice(page, homeserver, bob); - await bootstrapCrossSigningForClient(await bobSecondDevice.prepareClient(), bob.credentials, true); + // Bob logs in a new device and resets cross-signing + const bobSecondDevice = await createSecondBotDevice(page, homeserver, bob); + await bootstrapCrossSigningForClient(await bobSecondDevice.prepareClient(), bob.credentials, true); - /* should show an error for a message from a previously verified device */ - await bobSecondDevice.sendMessage(testRoomId, "test encrypted from user that was previously verified"); - const last = page.locator(".mx_EventTile_last"); - await expect(last).toContainText("test encrypted from user that was previously verified"); - const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon"); - await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/); - await lastE2eIcon.focus(); - await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText( - "Sender's verified identity was reset", - ); - }); + /* should show an error for a message from a previously verified device */ + await bobSecondDevice.sendMessage(testRoomId, "test encrypted from user that was previously verified"); + const last = page.locator(".mx_EventTile_last"); + await expect(last).toContainText("test encrypted from user that was previously verified"); + const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon"); + await expect(lastE2eIcon).toHaveAccessibleName("Sender's verified identity was reset"); + await expect(lastE2eIcon).toMatchScreenshot("event-shield-identity-reset.png"); + }, + ); }); }); @@ -343,8 +321,6 @@ async function assertNoE2EIcon(messageLocator: Locator, app: ElementAppPage) { const e2eIcon = messageLocator.locator(".mx_EventTile_e2eIcon"); 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. - await e2eIcon.focus(); - const tooltip = await app.getTooltipForElement(e2eIcon); - throw new Error(`Found an unexpected e2eIcon with tooltip '${await tooltip.textContent()}'`); + await expect(e2eIcon).toHaveAccessibleName("None"); } } diff --git a/playwright/pages/ElementAppPage.ts b/playwright/pages/ElementAppPage.ts index e51ed7b5d4..e5a1aab31c 100644 --- a/playwright/pages/ElementAppPage.ts +++ b/playwright/pages/ElementAppPage.ts @@ -244,24 +244,6 @@ export class ElementAppPage { 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 { - 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 */ diff --git a/playwright/snapshots/crypto/decryption-failure-messages.spec.ts/history-not-available-linux.png b/playwright/snapshots/crypto/decryption-failure-messages.spec.ts/history-not-available-linux.png new file mode 100644 index 0000000000..351e2c5d02 Binary files /dev/null and b/playwright/snapshots/crypto/decryption-failure-messages.spec.ts/history-not-available-linux.png differ diff --git a/playwright/snapshots/crypto/event-shields.spec.ts/event-shield-authenticity-linux.png b/playwright/snapshots/crypto/event-shields.spec.ts/event-shield-authenticity-linux.png new file mode 100644 index 0000000000..5980ff3ca0 Binary files /dev/null and b/playwright/snapshots/crypto/event-shields.spec.ts/event-shield-authenticity-linux.png differ diff --git a/playwright/snapshots/crypto/event-shields.spec.ts/event-shield-identity-reset-linux.png b/playwright/snapshots/crypto/event-shields.spec.ts/event-shield-identity-reset-linux.png new file mode 100644 index 0000000000..3499e8f704 Binary files /dev/null and b/playwright/snapshots/crypto/event-shields.spec.ts/event-shield-identity-reset-linux.png differ diff --git a/playwright/snapshots/crypto/event-shields.spec.ts/event-shield-not-verified-linux.png b/playwright/snapshots/crypto/event-shields.spec.ts/event-shield-not-verified-linux.png new file mode 100644 index 0000000000..3499e8f704 Binary files /dev/null and b/playwright/snapshots/crypto/event-shields.spec.ts/event-shield-not-verified-linux.png differ diff --git a/playwright/snapshots/crypto/event-shields.spec.ts/event-shield-utd-linux.png b/playwright/snapshots/crypto/event-shields.spec.ts/event-shield-utd-linux.png new file mode 100644 index 0000000000..3d80c319c8 Binary files /dev/null and b/playwright/snapshots/crypto/event-shields.spec.ts/event-shield-utd-linux.png differ diff --git a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-emote-irc-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-emote-irc-layout-linux.png index 39fffefc70..330524a42b 100644 Binary files a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-emote-irc-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-emote-irc-layout-linux.png differ diff --git a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-redaction-placeholder-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-redaction-placeholder-linux.png index 39c40e0b13..b36f3662eb 100644 Binary files a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-redaction-placeholder-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-redaction-placeholder-linux.png differ diff --git a/res/css/views/dialogs/_ForwardDialog.pcss b/res/css/views/dialogs/_ForwardDialog.pcss index 68132f0bbb..2e5b190487 100644 --- a/res/css/views/dialogs/_ForwardDialog.pcss +++ b/res/css/views/dialogs/_ForwardDialog.pcss @@ -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 */ /* We also hide download links to not encourage users to try interacting */ .mx_EventTile_msgOption, - .mx_EventTile_e2eIcon_unencrypted, - .mx_EventTile_e2eIcon_warning, + .mx_EventTile_e2eIcon, .mx_MFileBody_download { display: none; } diff --git a/res/css/views/dialogs/security/_AccessSecretStorageDialog.pcss b/res/css/views/dialogs/security/_AccessSecretStorageDialog.pcss index 2c78a62f8d..ec4d58edaf 100644 --- a/res/css/views/dialogs/security/_AccessSecretStorageDialog.pcss +++ b/res/css/views/dialogs/security/_AccessSecretStorageDialog.pcss @@ -33,14 +33,11 @@ Please see LICENSE files in the repository root for full details. } .mx_AccessSecretStorageDialog_recoveryKeyFeedback { - &::before { - content: ""; + svg { display: inline-block; vertical-align: bottom; width: 20px; height: 20px; - mask-repeat: no-repeat; - mask-position: center; mask-size: 20px; margin-inline-end: 5px; } @@ -48,9 +45,8 @@ Please see LICENSE files in the repository root for full details. &.mx_AccessSecretStorageDialog_recoveryKeyFeedback--invalid { color: $alert; - &::before { - mask-image: url("@vector-im/compound-design-tokens/icons/error-solid.svg"); - background-color: $alert; + svg { + color: $alert; } } } diff --git a/res/css/views/elements/_AccessibleButton.pcss b/res/css/views/elements/_AccessibleButton.pcss index d9d78912f0..fc43b112b8 100644 --- a/res/css/views/elements/_AccessibleButton.pcss +++ b/res/css/views/elements/_AccessibleButton.pcss @@ -63,22 +63,6 @@ Please see LICENSE files in the repository root for full details. 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_primary, &.mx_AccessibleButton_kind_icon_primary_outline { @@ -114,10 +98,6 @@ Please see LICENSE files in the repository root for full details. text-decoration: underline; } - &.mx_AccessibleButton_kind_secondary_content { - color: $secondary-content; - } - &.mx_AccessibleButton_kind_danger { color: var(--cpd-color-text-on-solid-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 { 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%; - } - } } diff --git a/res/css/views/messages/_CreateEvent.pcss b/res/css/views/messages/_CreateEvent.pcss index 2f4d753436..710ca01be1 100644 --- a/res/css/views/messages/_CreateEvent.pcss +++ b/res/css/views/messages/_CreateEvent.pcss @@ -9,8 +9,7 @@ Please see LICENSE files in the repository root for full details. .mx_EventTileBubble.mx_CreateEvent { margin: var(--EventTileBubble_margin-block) auto; - &::before { - background-color: $header-panel-text-primary-color; - mask-image: url("@vector-im/compound-design-tokens/icons/chat-solid.svg"); + svg { + color: $header-panel-text-primary-color; } } diff --git a/res/css/views/messages/_EventTileBubble.pcss b/res/css/views/messages/_EventTileBubble.pcss index 4b47c8b78e..403a4b7c9b 100644 --- a/res/css/views/messages/_EventTileBubble.pcss +++ b/res/css/views/messages/_EventTileBubble.pcss @@ -18,8 +18,7 @@ Please see LICENSE files in the repository root for full details. display: grid; grid-template-columns: 24px minmax(0, 1fr) min-content min-content; - &::before, - &::after { + svg { position: relative; grid-column: 1; grid-row: 1 / 3; diff --git a/res/css/views/messages/_HiddenBody.pcss b/res/css/views/messages/_HiddenBody.pcss index 30e3100dbb..3644db16e6 100644 --- a/res/css/views/messages/_HiddenBody.pcss +++ b/res/css/views/messages/_HiddenBody.pcss @@ -11,21 +11,12 @@ Please see LICENSE files in the repository root for full details. color: $muted-fg-color; vertical-align: middle; - padding-left: 20px; - position: relative; - - &::before { + svg { height: 14px; width: 14px; - background-color: $muted-fg-color; - mask-image: url("@vector-im/compound-design-tokens/icons/visibility-off.svg"); - - mask-repeat: no-repeat; - mask-position: center; - mask-size: contain; - content: ""; - position: absolute; - top: 1px; - left: 0; + display: inline-block; + margin-right: var(--cpd-space-1-5x); + color: $muted-fg-color; + vertical-align: -2px; } } diff --git a/res/css/views/messages/_MFileBody.pcss b/res/css/views/messages/_MFileBody.pcss index 51ba681140..6293270c68 100644 --- a/res/css/views/messages/_MFileBody.pcss +++ b/res/css/views/messages/_MFileBody.pcss @@ -34,25 +34,17 @@ Please see LICENSE files in the repository root for full details. background-color: $system; border-radius: 20px; display: inline-block; - width: 32px; - height: 32px; - position: relative; + width: 16px; + height: 16px; + padding: var(--cpd-space-2x); vertical-align: middle; margin-right: 12px; - &::before { - content: ""; - mask-repeat: no-repeat; - mask-position: center; - mask-size: cover; - 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; + svg { + color: $secondary-content; + width: inherit; + height: inherit; + display: block; } } diff --git a/res/css/views/messages/_MJitsiWidgetEvent.pcss b/res/css/views/messages/_MJitsiWidgetEvent.pcss index 9884d8951f..7813354366 100644 --- a/res/css/views/messages/_MJitsiWidgetEvent.pcss +++ b/res/css/views/messages/_MJitsiWidgetEvent.pcss @@ -7,8 +7,7 @@ Please see LICENSE files in the repository root for full details. */ .mx_EventTileBubble.mx_MJitsiWidgetEvent { - &::before { - background-color: $header-panel-text-primary-color; /* XXX: Variable abuse */ - mask-image: url("@vector-im/compound-design-tokens/icons/video-call-solid.svg"); + svg { + color: $header-panel-text-primary-color; /* XXX: Variable abuse */ } } diff --git a/res/css/views/messages/_ReactionsRow.pcss b/res/css/views/messages/_ReactionsRow.pcss index 833dee9a35..0c16480bd5 100644 --- a/res/css/views/messages/_ReactionsRow.pcss +++ b/res/css/views/messages/_ReactionsRow.pcss @@ -9,25 +9,19 @@ Please see LICENSE files in the repository root for full details. color: var(--cpd-color-text-primary); .mx_ReactionsRow_addReactionButton { - position: relative; display: inline-block; visibility: hidden; /* show on hover of the .mx_EventTile */ - width: 24px; - height: 24px; + width: 16px; + height: 16px; + padding: var(--cpd-space-1x); vertical-align: middle; margin-left: 4px; margin-right: 4px; - &::before { - content: ""; - position: absolute; - height: 100%; - 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"); + svg { + height: inherit; + width: inherit; + color: $tertiary-content; } &.mx_ReactionsRow_addReactionButton_active { @@ -36,8 +30,8 @@ Please see LICENSE files in the repository root for full details. &:hover, &.mx_ReactionsRow_addReactionButton_active { - &::before { - background-color: $primary-content; + svg { + color: $primary-content; } } } diff --git a/res/css/views/messages/_RedactedBody.pcss b/res/css/views/messages/_RedactedBody.pcss index 7b114b1866..417048c7f5 100644 --- a/res/css/views/messages/_RedactedBody.pcss +++ b/res/css/views/messages/_RedactedBody.pcss @@ -11,20 +11,11 @@ Please see LICENSE files in the repository root for full details. color: $secondary-content; vertical-align: middle; - padding-left: 20px; - position: relative; - - &::before { + svg { + margin-right: 6px; height: 14px; width: 14px; - background-color: $icon-button-color; - mask-image: url("@vector-im/compound-design-tokens/icons/delete.svg"); - mask-repeat: no-repeat; - mask-position: center; - mask-size: contain; - content: ""; - position: absolute; - top: 1px; - left: 0; + color: $icon-button-color; + vertical-align: -2px; } } diff --git a/res/css/views/messages/_common_CryptoEvent.pcss b/res/css/views/messages/_common_CryptoEvent.pcss index 5f2e26fd1c..affbd26d64 100644 --- a/res/css/views/messages/_common_CryptoEvent.pcss +++ b/res/css/views/messages/_common_CryptoEvent.pcss @@ -9,28 +9,8 @@ Please see LICENSE files in the repository root for full details. .mx_EventTileBubble.mx_cryptoEvent { margin: var(--EventTileBubble_margin-block) auto; - /* white infill for the transparency */ - &.mx_cryptoEvent_icon::before { - 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_icon svg { + color: $header-panel-text-primary-color; } .mx_cryptoEvent_state, diff --git a/res/css/views/right_panel/_BaseCard.pcss b/res/css/views/right_panel/_BaseCard.pcss index 035485428a..a5864c4e2a 100644 --- a/res/css/views/right_panel/_BaseCard.pcss +++ b/res/css/views/right_panel/_BaseCard.pcss @@ -61,24 +61,19 @@ Please see LICENSE files in the repository root for full details. .mx_BaseCard_header_title_button--option { position: relative; - width: var(--BaseCard_header-button-size); - height: var(--BaseCard_header-button-size); + width: calc(var(--BaseCard_header-button-size) - 4px); + height: calc(var(--BaseCard_header-button-size) - 4px); + padding: 2px; - &::after { - content: ""; - position: absolute; - inset-block-start: 0; - inset-inline-start: 0; - 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; + svg { + width: inherit; + height: inherit; + display: block; + color: $secondary-content; } - &:hover::after { - background-color: $primary-content; + &:hover svg { + color: $primary-content; } } } diff --git a/res/css/views/rooms/_E2EIcon.pcss b/res/css/views/rooms/_E2EIcon.pcss index efa8b1faff..55e3e02748 100644 --- a/res/css/views/rooms/_E2EIcon.pcss +++ b/res/css/views/rooms/_E2EIcon.pcss @@ -21,22 +21,3 @@ Please see LICENSE files in the repository root for full details. .mx_E2EIcon.mx_E2EIcon_inline { 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; -} diff --git a/res/css/views/rooms/_EventTile.pcss b/res/css/views/rooms/_EventTile.pcss index ca3fb537dd..155d520edf 100644 --- a/res/css/views/rooms/_EventTile.pcss +++ b/res/css/views/rooms/_EventTile.pcss @@ -836,32 +836,11 @@ $left-gutter: 64px; width: 14px; height: 14px; display: block; - background-repeat: no-repeat; - background-size: contain; - &::after { - content: ""; + svg { + height: inherit; + width: inherit; 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); } } diff --git a/res/css/views/rooms/_HistoryTile.pcss b/res/css/views/rooms/_HistoryTile.pcss index 53b1ca2765..adfa20a43a 100644 --- a/res/css/views/rooms/_HistoryTile.pcss +++ b/res/css/views/rooms/_HistoryTile.pcss @@ -9,8 +9,7 @@ Please see LICENSE files in the repository root for full details. .mx_EventTileBubble.mx_HistoryTile { margin: var(--EventTileBubble_margin-block) auto; - &::before { - background-color: $header-panel-text-primary-color; - mask-image: url("@vector-im/compound-design-tokens/icons/visibility-off.svg"); + svg { + color: $header-panel-text-primary-color; } } diff --git a/res/css/views/rooms/_ReplyTile.pcss b/res/css/views/rooms/_ReplyTile.pcss index 05665887d3..64492762ad 100644 --- a/res/css/views/rooms/_ReplyTile.pcss +++ b/res/css/views/rooms/_ReplyTile.pcss @@ -11,14 +11,6 @@ Please see LICENSE files in the repository root for full details. padding: 2px 0; 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 { display: grid; grid-template: diff --git a/res/css/views/voip/_DialPad.pcss b/res/css/views/voip/_DialPad.pcss index 717ec335b8..42b46600e0 100644 --- a/res/css/views/voip/_DialPad.pcss +++ b/res/css/views/voip/_DialPad.pcss @@ -44,16 +44,11 @@ Please see LICENSE files in the repository root for full details. grid-column: 2; background-color: $accent; - &::before { - content: ""; + svg { display: inline-block; - height: 40px; - width: 40px; - vertical-align: middle; - mask-repeat: no-repeat; - 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"); + height: 20px; + width: 20px; + padding: 10px; + color: #fff; /* on all themes */ } } diff --git a/src/components/structures/WaitingForThirdPartyRoomView.tsx b/src/components/structures/WaitingForThirdPartyRoomView.tsx index a02a807c9b..278af8c6c4 100644 --- a/src/components/structures/WaitingForThirdPartyRoomView.tsx +++ b/src/components/structures/WaitingForThirdPartyRoomView.tsx @@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details. import React, { type RefObject } from "react"; 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 ErrorBoundary from "../views/elements/ErrorBoundary"; @@ -43,6 +44,7 @@ export const WaitingForThirdPartyRoomView: React.FC = ({ roomView, resize
} className="mx_cryptoEvent mx_cryptoEvent_icon" title={_t("room|waiting_for_join_title", { brand })} subtitle={_t("room|waiting_for_join_subtitle", { brand })} diff --git a/src/components/views/context_menus/ThreadListContextMenu.tsx b/src/components/views/context_menus/ThreadListContextMenu.tsx index 11292f2a4f..4820c2a9e9 100644 --- a/src/components/views/context_menus/ThreadListContextMenu.tsx +++ b/src/components/views/context_menus/ThreadListContextMenu.tsx @@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details. import React, { useCallback, useEffect } from "react"; 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 dis from "../../../dispatcher/dispatcher"; @@ -90,7 +90,9 @@ const ThreadListContextMenu: React.FC = ({ isExpanded={menuDisplayed} ref={button} data-testid="threadlist-dropdown-button" - /> + > + + {menuDisplayed && ( + + {_t("encryption|access_secret_storage_dialog|key_validation_text|wrong_security_key")} + + ); + classes = "mx_AccessSecretStorageDialog_recoveryKeyFeedback--invalid"; } return ( - - {validationText} + + {content} ); } diff --git a/src/components/views/elements/AccessibleButton.tsx b/src/components/views/elements/AccessibleButton.tsx index a3e8a57c47..a2018b2211 100644 --- a/src/components/views/elements/AccessibleButton.tsx +++ b/src/components/views/elements/AccessibleButton.tsx @@ -30,7 +30,6 @@ export type AccessibleButtonKind = | "primary_outline" | "primary_sm" | "secondary" - | "secondary_content" | "content_inline" | "danger" | "danger_outline" @@ -39,8 +38,6 @@ export type AccessibleButtonKind = | "link" | "link_inline" | "link_sm" - | "confirm_sm" - | "cancel_sm" | "icon" | "icon_primary" | "icon_primary_outline"; diff --git a/src/components/views/messages/EncryptionEvent.tsx b/src/components/views/messages/EncryptionEvent.tsx index cf8b00db39..c931f19176 100644 --- a/src/components/views/messages/EncryptionEvent.tsx +++ b/src/components/views/messages/EncryptionEvent.tsx @@ -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 { 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 { _t } from "../../../languageHandler"; @@ -58,6 +59,7 @@ const EncryptionEvent = ({ mxEvent, timestamp, ref }: IProps): ReactNode => { return ( } className="mx_cryptoEvent mx_cryptoEvent_icon" title={stateEncrypted ? _t("common|state_encryption_enabled") : _t("common|encryption_enabled")} subtitle={subtitle} @@ -69,6 +71,7 @@ const EncryptionEvent = ({ mxEvent, timestamp, ref }: IProps): ReactNode => { if (isRoomEncrypted) { return ( } className="mx_cryptoEvent mx_cryptoEvent_icon" title={_t("common|encryption_enabled")} subtitle={_t("timeline|m.room.encryption|disable_attempt")} @@ -79,7 +82,8 @@ const EncryptionEvent = ({ mxEvent, timestamp, ref }: IProps): ReactNode => { return ( } + className="mx_cryptoEvent" title={_t("timeline|m.room.encryption|disabled")} subtitle={_t("timeline|m.room.encryption|unsupported")} ref={ref} diff --git a/src/components/views/messages/EventTileBubble.tsx b/src/components/views/messages/EventTileBubble.tsx index 4569115c0d..9a75f69d54 100644 --- a/src/components/views/messages/EventTileBubble.tsx +++ b/src/components/views/messages/EventTileBubble.tsx @@ -11,6 +11,7 @@ import classNames from "classnames"; interface IProps { className: string; + icon: JSX.Element; title: string; timestamp?: JSX.Element; subtitle?: ReactNode; @@ -18,9 +19,10 @@ interface IProps { ref?: Ref; } -const EventTileBubble = ({ className, title, timestamp, subtitle, children, ref }: IProps): JSX.Element => { +const EventTileBubble = ({ className, icon, title, timestamp, subtitle, children, ref }: IProps): JSX.Element => { return (
+ {icon}
{title}
{subtitle &&
{subtitle}
} {children} diff --git a/src/components/views/messages/HiddenBody.tsx b/src/components/views/messages/HiddenBody.tsx index 20410017be..aeca8e7ea7 100644 --- a/src/components/views/messages/HiddenBody.tsx +++ b/src/components/views/messages/HiddenBody.tsx @@ -7,6 +7,7 @@ Please see LICENSE files in the repository root for full details. */ import React, { type JSX } from "react"; +import { VisibilityOffIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; import { _t } from "../../../languageHandler"; import { type IBodyProps } from "./IBodyProps"; @@ -34,6 +35,7 @@ const HiddenBody = ({ mxEvent, ref }: IBodyProps): JSX.Element => { return ( + {text} ); diff --git a/src/components/views/messages/MFileBody.tsx b/src/components/views/messages/MFileBody.tsx index 1c55385d2c..b6efbed1cf 100644 --- a/src/components/views/messages/MFileBody.tsx +++ b/src/components/views/messages/MFileBody.tsx @@ -9,8 +9,14 @@ Please see LICENSE files in the repository root for full details. import React, { type AllHTMLAttributes, createRef } from "react"; import { logger } from "matrix-js-sdk/src/logger"; 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 { 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 Modal from "../../../Modal"; @@ -190,9 +196,17 @@ export default class MFileBody extends React.Component { let placeholder: React.ReactNode = null; if (showGenericPlaceholder) { + let icon = ; + // MFileBody is not generally used for Audio/Video but can be as part of ReplyTile + if (this.content.msgtype === MsgType.Audio) { + icon = ; + } else if (this.content.msgtype === MsgType.Video) { + icon = ; + } + placeholder = ( - + {icon} {presentableTextForFile(this.content, _t("common|attachment"), true, true)} diff --git a/src/components/views/messages/MJitsiWidgetEvent.tsx b/src/components/views/messages/MJitsiWidgetEvent.tsx index 4bd85f2176..1f751af94a 100644 --- a/src/components/views/messages/MJitsiWidgetEvent.tsx +++ b/src/components/views/messages/MJitsiWidgetEvent.tsx @@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details. import React, { type JSX } from "react"; 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 WidgetStore from "../../../stores/WidgetStore"; @@ -41,6 +42,7 @@ export default class MJitsiWidgetEvent extends React.PureComponent { // removed return ( } className="mx_MJitsiWidgetEvent" title={_t("timeline|m.widget|jitsi_ended", { senderName })} timestamp={this.props.timestamp} @@ -50,6 +52,7 @@ export default class MJitsiWidgetEvent extends React.PureComponent { // modified return ( } className="mx_MJitsiWidgetEvent" title={_t("timeline|m.widget|jitsi_updated", { senderName })} subtitle={joinCopy} @@ -60,6 +63,7 @@ export default class MJitsiWidgetEvent extends React.PureComponent { // assume added return ( } className="mx_MJitsiWidgetEvent" title={_t("timeline|m.widget|jitsi_started", { senderName })} subtitle={joinCopy} diff --git a/src/components/views/messages/MKeyVerificationRequest.tsx b/src/components/views/messages/MKeyVerificationRequest.tsx index 8f1f8a6962..aa993fa542 100644 --- a/src/components/views/messages/MKeyVerificationRequest.tsx +++ b/src/components/views/messages/MKeyVerificationRequest.tsx @@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details. import React, { type JSX } from "react"; 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 { getNameForEventRoom, userLabelForEventRoom } from "../../../utils/KeyVerificationStateObserver"; @@ -73,6 +74,7 @@ const MKeyVerificationRequest: React.FC = ({ mxEvent, timestamp }) => { return ( } className="mx_cryptoEvent mx_cryptoEvent_icon" title={title} subtitle={subtitle} diff --git a/src/components/views/messages/ReactionsRow.tsx b/src/components/views/messages/ReactionsRow.tsx index 32f37f4c24..4ac68ede3e 100644 --- a/src/components/views/messages/ReactionsRow.tsx +++ b/src/components/views/messages/ReactionsRow.tsx @@ -11,6 +11,7 @@ import classNames from "classnames"; import { type MatrixEvent, MatrixEventEvent, type Relations, RelationsEvent } from "matrix-js-sdk/src/matrix"; import { uniqBy } from "lodash"; import { UnstableValue } from "matrix-js-sdk/src/NamespacedValue"; +import { ReactionAddIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; import { _t } from "../../../languageHandler"; import { isContentActionable } from "../../../utils/EventUtils"; @@ -54,7 +55,9 @@ const ReactButton: React.FC = ({ mxEvent, reactions }) => { }} isExpanded={menuDisplayed} ref={button} - /> + > + + {contextMenu} diff --git a/src/components/views/messages/RedactedBody.tsx b/src/components/views/messages/RedactedBody.tsx index ddad4d2e4d..a3a2c475dd 100644 --- a/src/components/views/messages/RedactedBody.tsx +++ b/src/components/views/messages/RedactedBody.tsx @@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details. import React, { useContext, type JSX } from "react"; 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 MatrixClientContext from "../../../contexts/MatrixClientContext"; @@ -34,6 +35,7 @@ const RedactedBody = ({ mxEvent, ref }: IBodyProps): JSX.Element => { return ( + {text} ); diff --git a/src/components/views/messages/RoomPredecessorTile.tsx b/src/components/views/messages/RoomPredecessorTile.tsx index e35728e4b8..01e1ac831b 100644 --- a/src/components/views/messages/RoomPredecessorTile.tsx +++ b/src/components/views/messages/RoomPredecessorTile.tsx @@ -10,6 +10,7 @@ Please see LICENSE files in the repository root for full details. import React, { type JSX, useCallback } from "react"; import { logger } from "matrix-js-sdk/src/logger"; 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 { Action } from "../../../dispatcher/actions"; @@ -89,6 +90,7 @@ export const RoomPredecessorTile: React.FC = ({ mxEvent, timestamp }) => return ( } className="mx_CreateEvent" title={_t("timeline|m.room.create|continuation")} timestamp={timestamp} @@ -128,6 +130,7 @@ export const RoomPredecessorTile: React.FC = ({ mxEvent, timestamp }) => return ( } className="mx_CreateEvent" title={_t("timeline|m.room.create|continuation")} subtitle={link} diff --git a/src/components/views/right_panel/WidgetCard.tsx b/src/components/views/right_panel/WidgetCard.tsx index 29a713fdd4..87f334c1aa 100644 --- a/src/components/views/right_panel/WidgetCard.tsx +++ b/src/components/views/right_panel/WidgetCard.tsx @@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details. import React, { type JSX, useContext, useEffect } from "react"; 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 BaseCard from "./BaseCard"; @@ -73,7 +74,9 @@ const WidgetCard: React.FC = ({ room, widgetId, onClose }) => { onClick={openMenu} isExpanded={menuDisplayed} label={_t("common|options")} - /> + > + + {contextMenu}
); diff --git a/src/components/views/rooms/E2EIcon.tsx b/src/components/views/rooms/E2EIcon.tsx index 72b4b36bd3..2598584aaa 100644 --- a/src/components/views/rooms/E2EIcon.tsx +++ b/src/components/views/rooms/E2EIcon.tsx @@ -38,9 +38,9 @@ interface Props { } const icons: Record = { - [E2EStatus.Warning]: , - [E2EStatus.Normal]: , - [E2EStatus.Verified]: , + [E2EStatus.Warning]: , + [E2EStatus.Normal]: , + [E2EStatus.Verified]: , }; const E2EIcon: React.FC = ({ isUser, status, className, size, onClick, hideTooltip, tooltipPlacement }) => { diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 35b6a57ffd..af7b0daa53 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -35,6 +35,7 @@ import { } from "matrix-js-sdk/src/crypto-api"; import { Tooltip } from "@vector-im/compound-web"; import { uniqueId } from "lodash"; +import { ErrorSolidIcon, InfoIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; import ReplyChain from "../elements/ReplyChain"; import { _t } from "../../../languageHandler"; @@ -1518,13 +1519,13 @@ function E2ePadlockDecryptionFailure(props: Omit { + private static icons: Record = { + [E2ePadlockIcon.Normal]: , + [E2ePadlockIcon.Warning]: , + [E2ePadlockIcon.DecryptionFailure]: , + }; + public constructor(props: IE2ePadlockProps) { super(props); @@ -1543,12 +1550,13 @@ class E2ePadlock extends React.Component { } 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 // https://github.com/element-hq/compound/issues/294 return ( -
+
+ {E2ePadlock.icons[this.props.icon]} +
); } diff --git a/src/components/views/rooms/HistoryTile.tsx b/src/components/views/rooms/HistoryTile.tsx index a004aaeab9..0701122c56 100644 --- a/src/components/views/rooms/HistoryTile.tsx +++ b/src/components/views/rooms/HistoryTile.tsx @@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details. import React from "react"; 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 { _t } from "../../../languageHandler"; @@ -28,6 +29,7 @@ const HistoryTile: React.FC = () => { return ( } className="mx_HistoryTile" title={_t("timeline|historical_messages_unavailable")} subtitle={subtitle} diff --git a/src/components/views/rooms/NewRoomIntro.tsx b/src/components/views/rooms/NewRoomIntro.tsx index 67daec115d..d41bb3ecc0 100644 --- a/src/components/views/rooms/NewRoomIntro.tsx +++ b/src/components/views/rooms/NewRoomIntro.tsx @@ -9,6 +9,7 @@ Please see LICENSE files in the repository root for full details. import React, { type JSX, useContext } from "react"; import { EventType, type Room, type User, type MatrixClient } from "matrix-js-sdk/src/matrix"; 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 DMRoomMap from "../../../utils/DMRoomMap"; @@ -291,7 +292,8 @@ const NewRoomIntro: React.FC = () => {
  • {!hasExpectedEncryptionSettings(cli, room) && ( } + className="mx_cryptoEvent" title={_t("room|intro|unencrypted_warning")} subtitle={subtitle} /> diff --git a/src/components/views/rooms/ReplyTile.tsx b/src/components/views/rooms/ReplyTile.tsx index cf80b38272..115dc6088c 100644 --- a/src/components/views/rooms/ReplyTile.tsx +++ b/src/components/views/rooms/ReplyTile.tsx @@ -112,8 +112,6 @@ export default class ReplyTile extends React.PureComponent { const classes = classNames("mx_ReplyTile", { mx_ReplyTile_inline: msgType === MsgType.Emote, mx_ReplyTile_info: isInfoMessage && !mxEvent.isRedacted(), - mx_ReplyTile_audio: msgType === MsgType.Audio, - mx_ReplyTile_video: msgType === MsgType.Video, }); let permalink = "#"; diff --git a/src/components/views/voip/DialPad.tsx b/src/components/views/voip/DialPad.tsx index d84b050152..90907987a5 100644 --- a/src/components/views/voip/DialPad.tsx +++ b/src/components/views/voip/DialPad.tsx @@ -7,6 +7,7 @@ Please see LICENSE files in the repository root for full details. */ 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 { _t } from "../../../languageHandler"; @@ -59,7 +60,9 @@ class DialPadButton extends React.PureComponent + > + + ); } } diff --git a/test/unit-tests/components/structures/RoomView-test.tsx b/test/unit-tests/components/structures/RoomView-test.tsx index 1abdd91f20..d14e9ba971 100644 --- a/test/unit-tests/components/structures/RoomView-test.tsx +++ b/test/unit-tests/components/structures/RoomView-test.tsx @@ -594,12 +594,14 @@ describe("RoomView", () => { const { container } = await renderRoomView(); // 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); jest.spyOn(cli.getCrypto()!, "getUserVerificationStatus").mockResolvedValue(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", () => { diff --git a/test/unit-tests/components/structures/__snapshots__/MessagePanel-test.tsx.snap b/test/unit-tests/components/structures/__snapshots__/MessagePanel-test.tsx.snap index 91a7bfb531..77540f0b86 100644 --- a/test/unit-tests/components/structures/__snapshots__/MessagePanel-test.tsx.snap +++ b/test/unit-tests/components/structures/__snapshots__/MessagePanel-test.tsx.snap @@ -63,6 +63,17 @@ exports[`MessagePanel should handle lots of membership events quickly 1`] = `
    + + +
    diff --git a/test/unit-tests/components/structures/__snapshots__/RoomView-test.tsx.snap b/test/unit-tests/components/structures/__snapshots__/RoomView-test.tsx.snap index 1b0165991b..9003fbbd6d 100644 --- a/test/unit-tests/components/structures/__snapshots__/RoomView-test.tsx.snap +++ b/test/unit-tests/components/structures/__snapshots__/RoomView-test.tsx.snap @@ -152,8 +152,20 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`] class="mx_NewRoomIntro" >
    + + +
    @@ -322,8 +334,20 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] = class="mx_NewRoomIntro" >
    + + +
    @@ -687,6 +711,17 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
    + + +
    @@ -2985,7 +3020,7 @@ exports[`RoomView should not display the timeline when the room encryption is lo style="width: 12px; height: 12px;" > should display the dialog for the device of a style="width: 24px; height: 24px;" > should display the dialog for the device of t style="width: 24px; height: 24px;" > should render a single device - signed by owner 1`] = ` data-testid="e2e-icon" > should render a single device - unsigned 1`] = ` data-testid="e2e-icon" > should render a single device - verified by cross-signing 1`] data-testid="e2e-icon" > should render a single user 1`] = ` data-testid="e2e-icon" > should render a single user 1`] = ` data-testid="e2e-icon" > should render a single user 1`] = ` data-testid="e2e-icon" > should render a single user 1`] = ` data-testid="e2e-icon" > + > + + + +
    `; + +exports[` should show m.audio generic placeholder 1`] = ` +
    + +
    + + + + + + + + + alt + + +
    +
    +
    +`; + +exports[` should show m.file generic placeholder 1`] = ` +
    + +
    + + + + + + + + alt + + +
    +
    +
    +`; + +exports[` should show m.video generic placeholder 1`] = ` +
    + +
    + + + + + + + + alt + + +
    +
    +
    +`; diff --git a/test/unit-tests/components/views/messages/__snapshots__/MImageBody-test.tsx.snap b/test/unit-tests/components/views/messages/__snapshots__/MImageBody-test.tsx.snap index 7f54e73f9b..c22901a747 100644 --- a/test/unit-tests/components/views/messages/__snapshots__/MImageBody-test.tsx.snap +++ b/test/unit-tests/components/views/messages/__snapshots__/MImageBody-test.tsx.snap @@ -183,7 +183,19 @@ exports[` should render MFileBody for svg with no thumbnail 1`] = ` > + > + + + + Renders as expected 1`] = `
    + + +
    diff --git a/test/unit-tests/components/views/rooms/EventTile-test.tsx b/test/unit-tests/components/views/rooms/EventTile-test.tsx index db8e2c44c3..bae1719058 100644 --- a/test/unit-tests/components/views/rooms/EventTile-test.tsx +++ b/test/unit-tests/components/views/rooms/EventTile-test.tsx @@ -269,8 +269,8 @@ describe("EventTile", () => { // there should be a warning shield expect(container.getElementsByClassName("mx_EventTile_e2eIcon")).toHaveLength(1); - expect(container.getElementsByClassName("mx_EventTile_e2eIcon")[0].classList).toContain( - "mx_EventTile_e2eIcon_warning", + expect(container.getElementsByClassName("mx_EventTile_e2eIcon")[0]).toHaveAccessibleName( + "Encrypted by a device not verified by its owner.", ); }); @@ -298,10 +298,13 @@ describe("EventTile", () => { it.each([ [EventShieldReason.UNKNOWN, "Unknown error"], - [EventShieldReason.UNVERIFIED_IDENTITY, "unverified user"], - [EventShieldReason.UNSIGNED_DEVICE, "device not verified by its owner"], - [EventShieldReason.UNKNOWN_DEVICE, "unknown or deleted device"], - [EventShieldReason.AUTHENTICITY_NOT_GUARANTEED, "can't be guaranteed"], + [EventShieldReason.UNVERIFIED_IDENTITY, "Encrypted by an unverified user."], + [EventShieldReason.UNSIGNED_DEVICE, "Encrypted by a device not verified by its owner."], + [EventShieldReason.UNKNOWN_DEVICE, "Encrypted by an unknown or deleted device."], + [ + 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.SENT_IN_CLEAR, "Not encrypted"], [EventShieldReason.VERIFICATION_VIOLATION, "Sender's verified identity was reset"], @@ -326,12 +329,7 @@ describe("EventTile", () => { const e2eIcons = container.getElementsByClassName("mx_EventTile_e2eIcon"); expect(e2eIcons).toHaveLength(1); - expect(e2eIcons[0].classList).toContain("mx_EventTile_e2eIcon_normal"); - fireEvent.focus(e2eIcons[0]); - expect(e2eIcons[0].getAttribute("aria-labelledby")).toBeTruthy(); - expect(document.getElementById(e2eIcons[0].getAttribute("aria-labelledby")!)).toHaveTextContent( - expectedText, - ); + expect(e2eIcons[0]).toHaveAccessibleName(expectedText); }); describe("undecryptable event", () => { @@ -360,8 +358,8 @@ describe("EventTile", () => { expect(eventTiles).toHaveLength(1); expect(container.getElementsByClassName("mx_EventTile_e2eIcon")).toHaveLength(1); - expect(container.getElementsByClassName("mx_EventTile_e2eIcon")[0].classList).toContain( - "mx_EventTile_e2eIcon_decryption_failure", + expect(container.getElementsByClassName("mx_EventTile_e2eIcon")[0]).toHaveAccessibleName( + "This message could not be decrypted", ); }); @@ -436,8 +434,8 @@ describe("EventTile", () => { // check it was updated expect(container.getElementsByClassName("mx_EventTile_e2eIcon")).toHaveLength(1); - expect(container.getElementsByClassName("mx_EventTile_e2eIcon")[0].classList).toContain( - "mx_EventTile_e2eIcon_warning", + expect(container.getElementsByClassName("mx_EventTile_e2eIcon")[0]).toHaveAccessibleName( + "Encrypted by a device not verified by its owner.", ); }); @@ -481,9 +479,7 @@ describe("EventTile", () => { // check it was updated expect(container.getElementsByClassName("mx_EventTile_e2eIcon")).toHaveLength(1); - expect(container.getElementsByClassName("mx_EventTile_e2eIcon")[0].classList).toContain( - "mx_EventTile_e2eIcon_warning", - ); + expect(container.getElementsByClassName("mx_EventTile_e2eIcon")[0]).toHaveAccessibleName("Not encrypted"); }); }); diff --git a/test/unit-tests/components/views/rooms/wysiwyg_composer/SendWysiwygComposer-test.tsx b/test/unit-tests/components/views/rooms/wysiwyg_composer/SendWysiwygComposer-test.tsx index c38b95a1a5..54224c6452 100644 --- a/test/unit-tests/components/views/rooms/wysiwyg_composer/SendWysiwygComposer-test.tsx +++ b/test/unit-tests/components/views/rooms/wysiwyg_composer/SendWysiwygComposer-test.tsx @@ -330,10 +330,10 @@ describe("SendWysiwygComposer", () => { "Left icon when %s", ({ isRichTextEnabled }) => { it.each([ - [E2EStatus.Verified, "mx_E2EIcon_verified"], - [E2EStatus.Warning, "mx_E2EIcon_warning"], + [E2EStatus.Verified, "Everyone in this room is verified"], + [E2EStatus.Warning, "Someone is using an unknown session"], [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 customRender(jest.fn(), jest.fn(), false, isRichTextEnabled, undefined, e2eStatus); await waitFor(() => expect(screen.getByRole("textbox")).toHaveAttribute("contentEditable", "true")); @@ -341,9 +341,9 @@ describe("SendWysiwygComposer", () => { // Then expect(leftIcon).toBeInTheDocument(); expect(leftIcon).toHaveClass("mx_E2EIcon"); - if (expectedClass) { + if (expectedLabel) { // eslint-disable-next-line jest/no-conditional-expect - expect(leftIcon.querySelector("svg")).toHaveClass(expectedClass); + expect(leftIcon).toHaveAccessibleName(expectedLabel); } else { // eslint-disable-next-line jest/no-conditional-expect expect(leftIcon.querySelector("svg")).not.toBeInTheDocument();