Skip to content
This repository was archived by the owner on Oct 22, 2024. It is now read-only.

Commit 91e84f7

Browse files
Upgrade to latest compound-web package (#84)
* Upgrade to latest compound-web package * Use a custom render function for jest tests This way we don't need to manually wrap our components with <TooltipProvider> * Pin wrap-ansi to fix broken yarn install * Add playwright helper to find tooltip from element and use it in the failing test * Exclude floating-ui divs/spans from axe testing This is rendered outside .MatrixChat by compound and contains all the tooltips. * Wrap outermost components with TooltipProvider * Remove onChange and use onSelect for toggle * Fix jest tests and update snapshots * Use vector-im/matrix-wysiwig --------- Co-authored-by: Michael Telatynski <[email protected]>
1 parent 3bc0439 commit 91e84f7

File tree

389 files changed

+1262
-1085
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

389 files changed

+1262
-1085
lines changed

.eslintrc.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ module.exports = {
5151
"error",
5252
{
5353
paths: [
54+
{
55+
name: "@testing-library/react",
56+
message: "Please use jest-matrix-react instead",
57+
},
5458
{
5559
name: "matrix-js-sdk",
5660
message: "Please use matrix-js-sdk/src/matrix instead",

jest.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const config: Config = {
3737
coverageReporters: ["text-summary", "lcov"],
3838
testResultsProcessor: "@casualbot/jest-sonar-reporter",
3939
prettierPath: null,
40+
moduleDirectories: ["node_modules", "test/test-utils"],
4041
};
4142

4243
// if we're running under GHA, enable the GHA reporter

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@
6868
"jwt-decode": "4.0.0",
6969
"@floating-ui/react": "0.26.11",
7070
"@radix-ui/react-id": "1.1.0",
71-
"caniuse-lite": "1.0.30001655"
71+
"caniuse-lite": "1.0.30001655",
72+
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0",
73+
"wrap-ansi": "npm:wrap-ansi@^7.0.0"
7274
},
7375
"dependencies": {
7476
"@babel/runtime": "^7.12.5",
@@ -80,7 +82,7 @@
8082
"@sentry/browser": "^8.0.0",
8183
"@testing-library/react-hooks": "^8.0.1",
8284
"@vector-im/compound-design-tokens": "^1.8.0",
83-
"@vector-im/compound-web": "^5.5.0",
85+
"@vector-im/compound-web": "^6.3.1",
8486
"@zxcvbn-ts/core": "^3.0.4",
8587
"@zxcvbn-ts/language-common": "^3.0.4",
8688
"@zxcvbn-ts/language-en": "^3.0.2",

playwright/e2e/crypto/event-shields.spec.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,9 @@ test.describe("Cryptography", function () {
7070
const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon");
7171
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_decryption_failure/);
7272
await lastE2eIcon.focus();
73-
await expect(page.getByRole("tooltip")).toContainText("This message could not be decrypted");
73+
await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText(
74+
"This message could not be decrypted",
75+
);
7476

7577
/* Should show a red padlock for an unencrypted message in an e2e room */
7678
await bob.evaluate(
@@ -90,7 +92,7 @@ test.describe("Cryptography", function () {
9092
await expect(last).toContainText("test unencrypted");
9193
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/);
9294
await lastE2eIcon.focus();
93-
await expect(page.getByRole("tooltip")).toContainText("Not encrypted");
95+
await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText("Not encrypted");
9496

9597
/* Should show no padlock for an unverified user */
9698
// bob sends a valid event
@@ -123,7 +125,9 @@ test.describe("Cryptography", function () {
123125
await expect(lastTile).toContainText("test encrypted from unverified");
124126
await expect(lastTileE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/);
125127
await lastTileE2eIcon.focus();
126-
await expect(page.getByRole("tooltip")).toContainText("Encrypted by a device not verified by its owner.");
128+
await expect(await app.getTooltipForElement(lastTileE2eIcon)).toContainText(
129+
"Encrypted by a device not verified by its owner.",
130+
);
127131

128132
/* In legacy crypto: should show a grey padlock for a message from a deleted device.
129133
* In rust crypto: should show a red padlock for a message from an unverified device.
@@ -159,7 +163,7 @@ test.describe("Cryptography", function () {
159163
await expect(last).toContainText("test encrypted from unverified");
160164
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/);
161165
await lastE2eIcon.focus();
162-
await expect(page.getByRole("tooltip")).toContainText(
166+
await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText(
163167
workerInfo.project.name === "Legacy Crypto"
164168
? "Encrypted by an unknown or deleted device."
165169
: "Encrypted by a device not verified by its owner.",
@@ -212,7 +216,7 @@ test.describe("Cryptography", function () {
212216
// The key is coming from backup, so it is not anymore possible to establish if the claimed device
213217
// creator of this key is authentic. The tooltip should be "The authenticity of this encrypted message can't be guaranteed on this device."
214218
// It is not "Encrypted by an unknown or deleted device." even if the claimed device is actually deleted.
215-
await expect(page.getByRole("tooltip")).toContainText(
219+
await expect(await app.getTooltipForElement(lastTileE2eIcon)).toContainText(
216220
"The authenticity of this encrypted message can't be guaranteed on this device.",
217221
);
218222
});
@@ -296,7 +300,9 @@ test.describe("Cryptography", function () {
296300
const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon");
297301
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/);
298302
await lastE2eIcon.focus();
299-
await expect(page.getByRole("tooltip")).toContainText("Encrypted by a device not verified by its owner.");
303+
await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText(
304+
"Encrypted by a device not verified by its owner.",
305+
);
300306

301307
const penultimate = page.locator(".mx_EventTile").filter({ hasText: "test encrypted from verified" });
302308
await expect(penultimate.locator(".mx_EventTile_e2eIcon")).not.toBeVisible();

playwright/element-web-test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ export const test = base.extend<{
224224
},
225225

226226
axe: async ({ page }, use) => {
227-
await use(new AxeBuilder({ page }));
227+
await use(new AxeBuilder({ page }).exclude("[id^='floating-ui-']"));
228228
},
229229
checkA11y: async ({ axe }, use, testInfo) =>
230230
use(async () => {

playwright/pages/ElementAppPage.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,4 +172,22 @@ export class ElementAppPage {
172172
await this.page.getByRole("button", { name: "Room info" }).first().click();
173173
return this.page.locator(".mx_RightPanel");
174174
}
175+
176+
/**
177+
* Get a locator for the tooltip associated with an element
178+
* @param e The element with the tooltip
179+
* @returns Locator to the tooltip
180+
*/
181+
public async getTooltipForElement(e: Locator): Promise<Locator> {
182+
const [labelledById, describedById] = await Promise.all([
183+
e.getAttribute("aria-labelledby"),
184+
e.getAttribute("aria-describedby"),
185+
]);
186+
if (!labelledById && !describedById) {
187+
throw new Error(
188+
"Element has no aria-labelledby or aria-describedy attributes! The tooltip should have added either one of these.",
189+
);
190+
}
191+
return this.page.locator(`#${labelledById ?? describedById}`);
192+
}
175193
}

src/Modal.tsx

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import ReactDOM from "react-dom";
1212
import classNames from "classnames";
1313
import { IDeferred, defer, sleep } from "matrix-js-sdk/src/utils";
1414
import { TypedEventEmitter } from "matrix-js-sdk/src/matrix";
15-
import { Glass } from "@vector-im/compound-web";
15+
import { Glass, TooltipProvider } from "@vector-im/compound-web";
1616

1717
import dis, { defaultDispatcher } from "./dispatcher/dispatcher";
1818
import AsyncWrapper from "./AsyncWrapper";
@@ -416,16 +416,18 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
416416
const classes = classNames("mx_Dialog_wrapper mx_Dialog_staticWrapper", this.staticModal.className);
417417

418418
const staticDialog = (
419-
<div className={classes}>
420-
<Glass className="mx_Dialog_border">
421-
<div className="mx_Dialog">{this.staticModal.elem}</div>
422-
</Glass>
423-
<div
424-
data-testid="dialog-background"
425-
className="mx_Dialog_background mx_Dialog_staticBackground"
426-
onClick={this.onBackgroundClick}
427-
/>
428-
</div>
419+
<TooltipProvider>
420+
<div className={classes}>
421+
<Glass className="mx_Dialog_border">
422+
<div className="mx_Dialog">{this.staticModal.elem}</div>
423+
</Glass>
424+
<div
425+
data-testid="dialog-background"
426+
className="mx_Dialog_background mx_Dialog_staticBackground"
427+
onClick={this.onBackgroundClick}
428+
/>
429+
</div>
430+
</TooltipProvider>
429431
);
430432

431433
ReactDOM.render(staticDialog, ModalManager.getOrCreateStaticContainer());
@@ -441,16 +443,18 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
441443
});
442444

443445
const dialog = (
444-
<div className={classes}>
445-
<Glass className="mx_Dialog_border">
446-
<div className="mx_Dialog">{modal.elem}</div>
447-
</Glass>
448-
<div
449-
data-testid="dialog-background"
450-
className="mx_Dialog_background"
451-
onClick={this.onBackgroundClick}
452-
/>
453-
</div>
446+
<TooltipProvider>
447+
<div className={classes}>
448+
<Glass className="mx_Dialog_border">
449+
<div className="mx_Dialog">{modal.elem}</div>
450+
</Glass>
451+
<div
452+
data-testid="dialog-background"
453+
className="mx_Dialog_background"
454+
onClick={this.onBackgroundClick}
455+
/>
456+
</div>
457+
</TooltipProvider>
454458
);
455459

456460
setTimeout(() => ReactDOM.render(dialog, ModalManager.getOrCreateContainer()), 0);

src/components/structures/ContextMenu.tsx

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import React, { CSSProperties, RefObject, SyntheticEvent, useRef, useState } fro
1212
import ReactDOM from "react-dom";
1313
import classNames from "classnames";
1414
import FocusLock from "react-focus-lock";
15+
import { TooltipProvider } from "@vector-im/compound-web";
1516

1617
import { Writeable } from "../../@types/common";
1718
import UIStore from "../../stores/UIStore";
@@ -621,15 +622,17 @@ export function createMenu(
621622
};
622623

623624
const menu = (
624-
<ContextMenu
625-
{...props}
626-
mountAsChild={true}
627-
hasBackground={false}
628-
onFinished={onFinished} // eslint-disable-line react/jsx-no-bind
629-
windowResize={onFinished} // eslint-disable-line react/jsx-no-bind
630-
>
631-
<ElementClass {...props} onFinished={onFinished} />
632-
</ContextMenu>
625+
<TooltipProvider>
626+
<ContextMenu
627+
{...props}
628+
mountAsChild={true}
629+
hasBackground={false}
630+
onFinished={onFinished} // eslint-disable-line react/jsx-no-bind
631+
windowResize={onFinished} // eslint-disable-line react/jsx-no-bind
632+
>
633+
<ElementClass {...props} onFinished={onFinished} />
634+
</ContextMenu>
635+
</TooltipProvider>
633636
);
634637

635638
ReactDOM.render(menu, getOrCreateContainer());

src/components/structures/MatrixChat.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { logger } from "matrix-js-sdk/src/logger";
2424
import { throttle } from "lodash";
2525
import { CryptoEvent } from "matrix-js-sdk/src/crypto";
2626
import { KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
27+
import { TooltipProvider } from "@vector-im/compound-web";
2728

2829
// what-input helps improve keyboard accessibility
2930
import "what-input";
@@ -2181,7 +2182,9 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
21812182

21822183
return (
21832184
<ErrorBoundary>
2184-
<SDKContext.Provider value={this.stores}>{view}</SDKContext.Provider>
2185+
<SDKContext.Provider value={this.stores}>
2186+
<TooltipProvider>{view}</TooltipProvider>
2187+
</SDKContext.Provider>
21852188
</ErrorBoundary>
21862189
);
21872190
}

src/components/structures/ThreadPanel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ export const ThreadPanelHeader: React.FC<{
130130
return (
131131
<div className="mx_BaseCard_header_title">
132132
<Tooltip label={_t("threads|mark_all_read")}>
133-
<IconButton onClick={onMarkAllThreadsReadClick} aria-label={_t("threads|mark_all_read")} size="24px">
133+
<IconButton onClick={onMarkAllThreadsReadClick} size="24px">
134134
<MarkAllThreadsReadIcon />
135135
</IconButton>
136136
</Tooltip>

src/components/structures/auth/forgot-password/CheckEmail.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export const CheckEmail: React.FC<CheckEmailProps> = ({
5858
<input onClick={onSubmitForm} type="button" className="mx_Login_submit" value={_t("action|next")} />
5959
<div className="mx_AuthBody_did-not-receive">
6060
<span className="mx_VerifyEMailDialog_text-light">{_t("auth|check_email_resend_prompt")}</span>
61-
<Tooltip label={_t("auth|check_email_resend_tooltip")} placement="top" open={tooltipVisible}>
61+
<Tooltip description={_t("auth|check_email_resend_tooltip")} placement="top" open={tooltipVisible}>
6262
<AccessibleButton className="mx_AuthBody_resend-button" kind="link" onClick={onResendClickFn}>
6363
<RetryIcon className="mx_Icon mx_Icon_16" />
6464
{_t("action|resend")}

src/components/structures/auth/forgot-password/VerifyEmailModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export const VerifyEmailModal: React.FC<Props> = ({
5757

5858
<div className="mx_AuthBody_did-not-receive">
5959
<span className="mx_VerifyEMailDialog_text-light">{_t("auth|check_email_resend_prompt")}</span>
60-
<Tooltip label={_t("auth|check_email_resend_tooltip")} placement="top" open={tooltipVisible}>
60+
<Tooltip description={_t("auth|check_email_resend_tooltip")} placement="top" open={tooltipVisible}>
6161
<AccessibleButton className="mx_AuthBody_resend-button" kind="link" onClick={onResendClickFn}>
6262
<RetryIcon className="mx_Icon mx_Icon_16" />
6363
{_t("action|resend")}

src/components/views/elements/AccessibleButton.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ const AccessibleButton = forwardRef(function <T extends keyof JSX.IntrinsicEleme
212212
if (title) {
213213
return (
214214
<Tooltip
215-
label={title}
215+
description={title}
216216
caption={caption}
217217
isTriggerInteractive={true}
218218
placement={placement}

src/components/views/elements/InfoTooltip.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export default class InfoTooltip extends React.PureComponent<TooltipProps> {
3535

3636
// Tooltip are forced on the right for a more natural feel to them on info icons
3737
return (
38-
<Tooltip label={tooltip || title} placement="right">
38+
<Tooltip description={tooltip || title} placement="right">
3939
<div className={classNames("mx_InfoTooltip", className)} tabIndex={this.props.tabIndex ?? 0}>
4040
<span className={classNames("mx_InfoTooltip_icon", iconClassName)} aria-label={title} />
4141
{children}

src/components/views/elements/PersistedElement.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
88
import React, { MutableRefObject, ReactNode } from "react";
99
import ReactDOM from "react-dom";
1010
import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
11+
import { TooltipProvider } from "@vector-im/compound-web";
1112

1213
import dis from "../../../dispatcher/dispatcher";
1314
import MatrixClientContext from "../../../contexts/MatrixClientContext";
@@ -167,9 +168,11 @@ export default class PersistedElement extends React.Component<IProps> {
167168
private renderApp(): void {
168169
const content = (
169170
<MatrixClientContext.Provider value={MatrixClientPeg.safeGet()}>
170-
<div ref={this.collectChild} style={this.props.style}>
171-
{this.props.children}
172-
</div>
171+
<TooltipProvider>
172+
<div ref={this.collectChild} style={this.props.style}>
173+
{this.props.children}
174+
</div>
175+
</TooltipProvider>
173176
</MatrixClientContext.Provider>
174177
);
175178

src/components/views/elements/Pill.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ export const Pill: React.FC<PillProps> = ({ type: propType, url, inMessage, room
141141
<bdi>
142142
<MatrixClientContext.Provider value={MatrixClientPeg.safeGet()}>
143143
<Tooltip
144-
label={resourceId ?? ""}
144+
description={resourceId ?? ""}
145145
open={resourceId ? undefined : false}
146146
placement="right"
147147
isTriggerInteractive={isAnchor}

src/components/views/elements/RoomTopic.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ export default function RoomTopic({ room, className, ...props }: IProps): JSX.El
111111
if (!body) return <div className={classNames(className, "mx_RoomTopic")} />;
112112

113113
return (
114-
<Tooltip label={_t("room|read_topic")} disabled={disableTooltip}>
114+
<Tooltip description={_t("room|read_topic")} disabled={disableTooltip}>
115115
<div
116116
{...props}
117117
tabIndex={0}

src/components/views/messages/MStickerBody.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export default class MStickerBody extends MImageBody {
6161

6262
return {
6363
placement: "right",
64-
label: content.body,
64+
description: content.body,
6565
};
6666
}
6767

src/components/views/messages/MessageTimestamp.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export default class MessageTimestamp extends React.Component<IProps> {
5353
}
5454

5555
return (
56-
<Tooltip label={label} caption={caption}>
56+
<Tooltip description={label} caption={caption}>
5757
<span className="mx_MessageTimestamp" aria-hidden={true} aria-live="off">
5858
{icon}
5959
{timestamp}

src/components/views/messages/ReactionsRowButtonTooltip.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export default class ReactionsRowButtonTooltip extends React.PureComponent<Props
5151
const caption = shortName ? _t("timeline|reactions|tooltip_caption", { shortName }) : undefined;
5252

5353
return (
54-
<Tooltip label={formattedSenders} caption={caption} placement="right">
54+
<Tooltip description={formattedSenders} caption={caption} placement="right">
5555
{children}
5656
</Tooltip>
5757
);

src/components/views/messages/TextualBody.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Please see LICENSE files in the repository root for full details.
99
import React, { createRef, SyntheticEvent, MouseEvent } from "react";
1010
import ReactDOM from "react-dom";
1111
import { MsgType } from "matrix-js-sdk/src/matrix";
12+
import { TooltipProvider } from "@vector-im/compound-web";
1213

1314
import * as HtmlUtils from "../../../HtmlUtils";
1415
import { formatDate } from "../../../DateUtils";
@@ -335,7 +336,11 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
335336

336337
const reason = node.getAttribute("data-mx-spoiler") ?? undefined;
337338
node.removeAttribute("data-mx-spoiler"); // we don't want to recurse
338-
const spoiler = <Spoiler reason={reason} contentHtml={node.outerHTML} />;
339+
const spoiler = (
340+
<TooltipProvider>
341+
<Spoiler reason={reason} contentHtml={node.outerHTML} />
342+
</TooltipProvider>
343+
);
339344

340345
ReactDOM.render(spoiler, spoilerContainer);
341346
node.parentNode?.replaceChild(spoilerContainer, node);

src/components/views/right_panel/RoomSummaryCard.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -373,9 +373,7 @@ const RoomSummaryCard: React.FC<IProps> = ({
373373
Icon={FavouriteIcon}
374374
label={_t("room|context_menu|favourite")}
375375
checked={isFavorite}
376-
onChange={() => tagRoom(room, DefaultTagID.Favourite)}
377-
// XXX: https://github.com/element-hq/compound/issues/288
378-
onSelect={() => {}}
376+
onSelect={() => tagRoom(room, DefaultTagID.Favourite)}
379377
/>
380378
<MenuItem
381379
Icon={UserAddIcon}

0 commit comments

Comments
 (0)