Fix theoretical bug where VerificationRequestDialog uses an outdated request (#30870)
* Tests for VerificationRequestDialog * Fix theoretical bug where VerificationRequestDialog uses an outdated request We were passing on `this.props.verificationRequest` to `EncryptionPanel` but we should be passing on the request in `this.state`. This would not cause a problem in practice because the `EncryptionPanel` immediately overwrites the request if you supply a `verificationRequestPromise`.
This commit is contained in:
@@ -60,7 +60,7 @@ export default class VerificationRequestDialog extends React.Component<IProps, I
|
||||
>
|
||||
<EncryptionPanel
|
||||
layout="dialog"
|
||||
verificationRequest={this.props.verificationRequest}
|
||||
verificationRequest={this.state.verificationRequest}
|
||||
verificationRequestPromise={this.props.verificationRequestPromise}
|
||||
onClose={this.props.onFinished}
|
||||
member={member}
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { act, render, screen } from "jest-matrix-react";
|
||||
import { User } from "matrix-js-sdk/src/matrix";
|
||||
import {
|
||||
type ShowSasCallbacks,
|
||||
VerificationPhase,
|
||||
type Verifier,
|
||||
type VerificationRequest,
|
||||
type ShowQrCodeCallbacks,
|
||||
} from "matrix-js-sdk/src/crypto-api";
|
||||
import { VerificationMethod } from "matrix-js-sdk/src/types";
|
||||
|
||||
import VerificationRequestDialog from "../../../../../src/components/views/dialogs/VerificationRequestDialog";
|
||||
import { stubClient } from "../../../../test-utils";
|
||||
|
||||
describe("VerificationRequestDialog", () => {
|
||||
function renderComponent(phase: VerificationPhase, method?: "emoji" | "qr"): ReturnType<typeof render> {
|
||||
const member = User.createUser("@alice:example.org", stubClient());
|
||||
const request = createRequest(phase, method);
|
||||
|
||||
return render(
|
||||
<VerificationRequestDialog onFinished={jest.fn()} member={member} verificationRequest={request} />,
|
||||
);
|
||||
}
|
||||
|
||||
it("Initially, asks how you would like to verify this device", async () => {
|
||||
const dialog = renderComponent(VerificationPhase.Ready);
|
||||
|
||||
expect(screen.getByRole("heading", { name: "Verify other device" })).toBeInTheDocument();
|
||||
expect(screen.getByText("Verify this device by completing one of the following:")).toBeInTheDocument();
|
||||
|
||||
expect(dialog.asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("After we started verification here, says we are waiting for the other device", async () => {
|
||||
const dialog = renderComponent(VerificationPhase.Requested);
|
||||
|
||||
expect(screen.getByRole("heading", { name: "Verify other device" })).toBeInTheDocument();
|
||||
|
||||
expect(
|
||||
screen.getByText("To proceed, please accept the verification request on your other device."),
|
||||
).toBeInTheDocument();
|
||||
|
||||
expect(dialog.asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("When other device accepted emoji, displays emojis and asks for confirmation", async () => {
|
||||
const dialog = renderComponent(VerificationPhase.Started, "emoji");
|
||||
|
||||
expect(screen.getByRole("heading", { name: "Verify other device" })).toBeInTheDocument();
|
||||
|
||||
expect(
|
||||
screen.getByText("Confirm the emoji below are displayed on both devices, in the same order:"),
|
||||
).toBeInTheDocument();
|
||||
|
||||
expect(dialog.asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("After scanning QR, shows confirmation dialog", async () => {
|
||||
const dialog = renderComponent(VerificationPhase.Started, "qr");
|
||||
|
||||
expect(screen.getByRole("heading", { name: "Verify other device" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("heading", { name: "Verify by scanning" })).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText("Almost there! Is your other device showing the same shield?")).toBeInTheDocument();
|
||||
|
||||
expect(dialog.asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("Shows a successful message if verification finished normally", async () => {
|
||||
const dialog = renderComponent(VerificationPhase.Done);
|
||||
|
||||
expect(screen.getByRole("heading", { name: "Verify other device" })).toBeInTheDocument();
|
||||
expect(screen.getByText("You've successfully verified your device!")).toBeInTheDocument();
|
||||
|
||||
expect(dialog.asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("Shows a failure message if verification was cancelled", async () => {
|
||||
const dialog = renderComponent(VerificationPhase.Cancelled);
|
||||
|
||||
expect(screen.getByRole("heading", { name: "Verify other device" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("heading", { name: "Verification cancelled" })).toBeInTheDocument();
|
||||
|
||||
expect(
|
||||
screen.getByText(
|
||||
"You cancelled verification on your other device. Start verification again from the notification.",
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
|
||||
expect(dialog.asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("Renders correctly if the request is supplied later via a promise", async () => {
|
||||
// Given we supply a promise of a request instead of a request
|
||||
const member = User.createUser("@alice:example.org", stubClient());
|
||||
const requestPromise = Promise.resolve(createRequest(VerificationPhase.Cancelled));
|
||||
|
||||
// When we render the dialog
|
||||
render(
|
||||
<VerificationRequestDialog
|
||||
onFinished={jest.fn()}
|
||||
member={member}
|
||||
verificationRequestPromise={requestPromise}
|
||||
/>,
|
||||
);
|
||||
|
||||
// And wait for the component to mount, the promise to resolve and the component state to update
|
||||
await act(async () => await new Promise(process.nextTick));
|
||||
|
||||
// Then it renders the resolved information
|
||||
expect(screen.getByRole("heading", { name: "Verify other device" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("heading", { name: "Verification cancelled" })).toBeInTheDocument();
|
||||
|
||||
expect(
|
||||
screen.getByText(
|
||||
"You cancelled verification on your other device. Start verification again from the notification.",
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("Renders the later promise request if both immediate and promise are supplied", async () => {
|
||||
// Given we supply a promise of a request as well as a request
|
||||
const member = User.createUser("@alice:example.org", stubClient());
|
||||
const request = createRequest(VerificationPhase.Ready);
|
||||
const requestPromise = Promise.resolve(createRequest(VerificationPhase.Cancelled));
|
||||
|
||||
// When we render the dialog
|
||||
render(
|
||||
<VerificationRequestDialog
|
||||
onFinished={jest.fn()}
|
||||
member={member}
|
||||
verificationRequest={request}
|
||||
verificationRequestPromise={requestPromise}
|
||||
/>,
|
||||
);
|
||||
|
||||
// And wait for the component to mount, the promise to resolve and the component state to update
|
||||
await act(async () => await new Promise(process.nextTick));
|
||||
|
||||
// Then it renders the information from the request in the promise
|
||||
expect(screen.getByRole("heading", { name: "Verify other device" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("heading", { name: "Verification cancelled" })).toBeInTheDocument();
|
||||
|
||||
expect(
|
||||
screen.getByText(
|
||||
"You cancelled verification on your other device. Start verification again from the notification.",
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
function createRequest(phase: VerificationPhase, method?: "emoji" | "qr"): VerificationRequest {
|
||||
let verifier = undefined;
|
||||
let chosenMethod = undefined;
|
||||
|
||||
switch (method) {
|
||||
case "emoji":
|
||||
chosenMethod = VerificationMethod.Sas;
|
||||
verifier = createEmojiVerifier();
|
||||
break;
|
||||
case "qr":
|
||||
chosenMethod = VerificationMethod.Reciprocate;
|
||||
verifier = createQrVerifier();
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
phase: jest.fn().mockReturnValue(phase),
|
||||
|
||||
// VerificationRequest is an emitter - ignore any events that are emitted.
|
||||
on: jest.fn(),
|
||||
off: jest.fn(),
|
||||
|
||||
// These tests (so far) only check for when we are initiating a verificiation of our own device.
|
||||
isSelfVerification: jest.fn().mockReturnValue(true),
|
||||
initiatedByMe: jest.fn().mockReturnValue(true),
|
||||
|
||||
// Always returning true means we can support QR code and emoji verification.
|
||||
otherPartySupportsMethod: jest.fn().mockReturnValue(true),
|
||||
|
||||
// If we asked for emoji, these are populated.
|
||||
verifier,
|
||||
chosenMethod,
|
||||
} as unknown as VerificationRequest;
|
||||
}
|
||||
|
||||
function createEmojiVerifier(): Verifier {
|
||||
const showSasCallbacks = {
|
||||
sas: {
|
||||
emoji: [
|
||||
// Example set of emoji to display.
|
||||
["🐶", "Dog"],
|
||||
["🐱", "Cat"],
|
||||
],
|
||||
},
|
||||
} as ShowSasCallbacks;
|
||||
|
||||
return {
|
||||
getShowSasCallbacks: jest.fn().mockReturnValue(showSasCallbacks),
|
||||
getReciprocateQrCodeCallbacks: jest.fn(),
|
||||
on: jest.fn(),
|
||||
off: jest.fn(),
|
||||
verify: jest.fn(),
|
||||
} as unknown as Verifier;
|
||||
}
|
||||
|
||||
function createQrVerifier(): Verifier {
|
||||
const reciprocateQrCodeCallbacks = {
|
||||
confirm: jest.fn(),
|
||||
cancel: jest.fn(),
|
||||
} as ShowQrCodeCallbacks;
|
||||
|
||||
return {
|
||||
getShowSasCallbacks: jest.fn(),
|
||||
getReciprocateQrCodeCallbacks: jest.fn().mockReturnValue(reciprocateQrCodeCallbacks),
|
||||
on: jest.fn(),
|
||||
off: jest.fn(),
|
||||
verify: jest.fn(),
|
||||
} as unknown as Verifier;
|
||||
}
|
||||
@@ -0,0 +1,440 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`VerificationRequestDialog After scanning QR, shows confirmation dialog 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
<div
|
||||
aria-describedby="mx_Dialog_content"
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_InfoDialog mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Verify other device
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_UserInfo_container mx_VerificationPanel_reciprocate_section"
|
||||
>
|
||||
<h3>
|
||||
Verify by scanning
|
||||
</h3>
|
||||
<p>
|
||||
Almost there! Is your other device showing the same shield?
|
||||
</p>
|
||||
<div
|
||||
class="mx_E2EIcon mx_E2EIcon_verified"
|
||||
data-testid="e2e-icon"
|
||||
style="width: 128px; height: 128px;"
|
||||
>
|
||||
<div
|
||||
class="mx_E2EIcon_normal"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VerificationPanel_reciprocateButtons"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_danger"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
No
|
||||
</div>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Yes
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`VerificationRequestDialog After we started verification here, says we are waiting for the other device 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
<div
|
||||
aria-describedby="mx_Dialog_content"
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_InfoDialog mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Verify other device
|
||||
</h1>
|
||||
</div>
|
||||
<div>
|
||||
To proceed, please accept the verification request on your other device.
|
||||
</div>
|
||||
<div
|
||||
aria-describedby="«r6»"
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`VerificationRequestDialog Initially, asks how you would like to verify this device 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
<div
|
||||
aria-describedby="mx_Dialog_content"
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_InfoDialog mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Verify other device
|
||||
</h1>
|
||||
</div>
|
||||
<div>
|
||||
Verify this device by completing one of the following:
|
||||
<div
|
||||
class="mx_VerificationPanel_QRPhase_startOptions"
|
||||
>
|
||||
<div
|
||||
class="mx_VerificationPanel_QRPhase_startOption"
|
||||
>
|
||||
<p>
|
||||
Scan this unique code
|
||||
</p>
|
||||
<div
|
||||
class="mx_QRCode mx_VerificationQRCode"
|
||||
>
|
||||
<div
|
||||
class="mx_Spinner"
|
||||
>
|
||||
<div
|
||||
aria-label="Loading…"
|
||||
class="mx_Spinner_icon"
|
||||
data-testid="spinner"
|
||||
role="progressbar"
|
||||
style="width: 32px; height: 32px;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VerificationPanel_QRPhase_betweenText"
|
||||
>
|
||||
or
|
||||
</div>
|
||||
<div
|
||||
class="mx_VerificationPanel_QRPhase_startOption"
|
||||
>
|
||||
<p>
|
||||
Compare unique emoji
|
||||
</p>
|
||||
<span
|
||||
class="mx_VerificationPanel_QRPhase_helpText"
|
||||
>
|
||||
Compare a unique set of emoji if you don't have a camera on either device
|
||||
</span>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Start
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`VerificationRequestDialog Shows a failure message if verification was cancelled 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
<div
|
||||
aria-describedby="mx_Dialog_content"
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_InfoDialog mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Verify other device
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_UserInfo_container"
|
||||
>
|
||||
<h3>
|
||||
Verification cancelled
|
||||
</h3>
|
||||
<p>
|
||||
You cancelled verification on your other device. Start verification again from the notification.
|
||||
</p>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_UserInfo_wideButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Got it
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`VerificationRequestDialog Shows a successful message if verification finished normally 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
<div
|
||||
aria-describedby="mx_Dialog_content"
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_InfoDialog mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Verify other device
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_UserInfo_container mx_VerificationPanel_verified_section"
|
||||
>
|
||||
<p>
|
||||
You've successfully verified your device!
|
||||
</p>
|
||||
<div
|
||||
class="mx_E2EIcon mx_E2EIcon_verified"
|
||||
data-testid="e2e-icon"
|
||||
style="width: 128px; height: 128px;"
|
||||
>
|
||||
<div
|
||||
class="mx_E2EIcon_normal"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_UserInfo_wideButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Got it
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`VerificationRequestDialog When other device accepted emoji, displays emojis and asks for confirmation 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
<div
|
||||
aria-describedby="mx_Dialog_content"
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_InfoDialog mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Verify other device
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_UserInfo_container"
|
||||
>
|
||||
<div
|
||||
class="mx_VerificationShowSas"
|
||||
>
|
||||
<p>
|
||||
Confirm the emoji below are displayed on both devices, in the same order:
|
||||
</p>
|
||||
<div
|
||||
class="mx_VerificationShowSas_emojiSas"
|
||||
>
|
||||
<div
|
||||
class="mx_VerificationShowSas_emojiSas_block"
|
||||
>
|
||||
<div
|
||||
class="mx_VerificationShowSas_emojiSas_emoji"
|
||||
>
|
||||
🐶
|
||||
</div>
|
||||
<div
|
||||
class="mx_VerificationShowSas_emojiSas_label"
|
||||
>
|
||||
Dog
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VerificationShowSas_emojiSas_block"
|
||||
>
|
||||
<div
|
||||
class="mx_VerificationShowSas_emojiSas_emoji"
|
||||
>
|
||||
🐱
|
||||
</div>
|
||||
<div
|
||||
class="mx_VerificationShowSas_emojiSas_label"
|
||||
>
|
||||
Cat
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VerificationShowSas_emojiSas_break"
|
||||
/>
|
||||
</div>
|
||||
<p />
|
||||
<div
|
||||
class="mx_VerificationShowSas_buttonRow"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
They match
|
||||
</div>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
They don't match
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
Reference in New Issue
Block a user