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

Commit 8a97e5f

Browse files
turt2livet3chguy
andauthored
Expose and pre-populate thread ID in devtools dialog (#10953)
Co-authored-by: Michael Telatynski <[email protected]>
1 parent cfd48b3 commit 8a97e5f

File tree

8 files changed

+286
-48
lines changed

8 files changed

+286
-48
lines changed

src/SlashCommands.tsx

+49-43
Large diffs are not rendered by default.

src/components/views/dialogs/DevtoolsDialog.tsx

+12-2
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,13 @@ const Tools: Record<Category, [label: string, tool: Tool][]> = {
6565

6666
interface IProps {
6767
roomId: string;
68+
threadRootId?: string | null;
6869
onFinished(finished?: boolean): void;
6970
}
7071

7172
type ToolInfo = [label: string, tool: Tool];
7273

73-
const DevtoolsDialog: React.FC<IProps> = ({ roomId, onFinished }) => {
74+
const DevtoolsDialog: React.FC<IProps> = ({ roomId, threadRootId, onFinished }) => {
7475
const [tool, setTool] = useState<ToolInfo | null>(null);
7576

7677
let body: JSX.Element;
@@ -125,9 +126,18 @@ const DevtoolsDialog: React.FC<IProps> = ({ roomId, onFinished }) => {
125126
<CopyableText className="mx_DevTools_label_right" getTextToCopy={() => roomId} border={false}>
126127
{_t("Room ID: %(roomId)s", { roomId })}
127128
</CopyableText>
129+
{!threadRootId ? null : (
130+
<CopyableText
131+
className="mx_DevTools_label_right"
132+
getTextToCopy={() => threadRootId}
133+
border={false}
134+
>
135+
{_t("Thread Root ID: %(threadRootId)s", { threadRootId })}
136+
</CopyableText>
137+
)}
128138
<div className="mx_DevTools_label_bottom" />
129139
{cli.getRoom(roomId) && (
130-
<DevtoolsContext.Provider value={{ room: cli.getRoom(roomId)! }}>
140+
<DevtoolsContext.Provider value={{ room: cli.getRoom(roomId)!, threadRootId }}>
131141
{body}
132142
</DevtoolsContext.Provider>
133143
)}

src/components/views/dialogs/devtools/BaseTool.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ export default BaseTool;
8888

8989
interface IContext {
9090
room: Room;
91+
threadRootId?: string | null;
9192
}
9293

9394
export const DevtoolsContext = createContext<IContext>({} as IContext);

src/components/views/dialogs/devtools/Event.tsx

+7
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,13 @@ export const TimelineEventEditor: React.FC<IEditorProps> = ({ mxEvent, onBack })
204204
};
205205

206206
defaultContent = stringify(newContent);
207+
} else if (context.threadRootId) {
208+
defaultContent = stringify({
209+
"m.relates_to": {
210+
rel_type: "m.thread",
211+
event_id: context.threadRootId,
212+
},
213+
});
207214
}
208215

209216
return <EventEditor fieldDefs={fields} defaultContent={defaultContent} onSend={onSend} onBack={onBack} />;

src/i18n/strings/en_EN.json

+1
Original file line numberDiff line numberDiff line change
@@ -2841,6 +2841,7 @@
28412841
"Toolbox": "Toolbox",
28422842
"Developer Tools": "Developer Tools",
28432843
"Room ID: %(roomId)s": "Room ID: %(roomId)s",
2844+
"Thread Root ID: %(threadRootId)s": "Thread Root ID: %(threadRootId)s",
28442845
"The poll has ended. No votes were cast.": "The poll has ended. No votes were cast.",
28452846
"The poll has ended. Top answer: %(topAnswer)s": "The poll has ended. Top answer: %(topAnswer)s",
28462847
"Failed to end poll": "Failed to end poll",

test/components/views/dialogs/DevtoolsDialog-test.tsx

+19-3
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ limitations under the License.
1515
*/
1616

1717
import React from "react";
18-
import { getByLabelText, render } from "@testing-library/react";
18+
import { getByLabelText, getAllByLabelText, render } from "@testing-library/react";
1919
import { Room } from "matrix-js-sdk/src/models/room";
2020
import { MatrixClient } from "matrix-js-sdk/src/client";
2121
import userEvent from "@testing-library/user-event";
@@ -29,10 +29,10 @@ describe("DevtoolsDialog", () => {
2929
let cli: MatrixClient;
3030
let room: Room;
3131

32-
function getComponent(roomId: string, onFinished = () => true) {
32+
function getComponent(roomId: string, threadRootId: string | null = null, onFinished = () => true) {
3333
return render(
3434
<MatrixClientContext.Provider value={cli}>
35-
<DevtoolsDialog roomId={roomId} onFinished={onFinished} />
35+
<DevtoolsDialog roomId={roomId} threadRootId={threadRootId} onFinished={onFinished} />
3636
</MatrixClientContext.Provider>,
3737
);
3838
}
@@ -68,4 +68,20 @@ describe("DevtoolsDialog", () => {
6868
expect(navigator.clipboard.writeText).toHaveBeenCalled();
6969
expect(navigator.clipboard.readText()).resolves.toBe(room.roomId);
7070
});
71+
72+
it("copies the thread root id when provided", async () => {
73+
const user = userEvent.setup();
74+
jest.spyOn(navigator.clipboard, "writeText");
75+
76+
const threadRootId = "$test_event_id_goes_here";
77+
const { container } = getComponent(room.roomId, threadRootId);
78+
79+
const copyBtn = getAllByLabelText(container, "Copy")[1];
80+
await user.click(copyBtn);
81+
const copiedBtn = getByLabelText(container, "Copied!");
82+
83+
expect(copiedBtn).toBeInTheDocument();
84+
expect(navigator.clipboard.writeText).toHaveBeenCalled();
85+
expect(navigator.clipboard.readText()).resolves.toBe(threadRootId);
86+
});
7187
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
Copyright 2023 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import React from "react";
18+
import { render } from "@testing-library/react";
19+
import { Room } from "matrix-js-sdk/src/models/room";
20+
import { PendingEventOrdering } from "matrix-js-sdk/src/client";
21+
22+
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
23+
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
24+
import { stubClient } from "../../../../test-utils";
25+
import { DevtoolsContext } from "../../../../../src/components/views/dialogs/devtools/BaseTool";
26+
import { TimelineEventEditor } from "../../../../../src/components/views/dialogs/devtools/Event";
27+
28+
describe("<EventEditor />", () => {
29+
beforeEach(() => {
30+
stubClient();
31+
});
32+
33+
it("should render", () => {
34+
const cli = MatrixClientPeg.safeGet();
35+
const { asFragment } = render(
36+
<MatrixClientContext.Provider value={cli}>
37+
<DevtoolsContext.Provider
38+
value={{
39+
room: new Room("!roomId", cli, "@alice:example.com", {
40+
pendingEventOrdering: PendingEventOrdering.Detached,
41+
}),
42+
}}
43+
>
44+
<TimelineEventEditor onBack={() => {}} />
45+
</DevtoolsContext.Provider>
46+
</MatrixClientContext.Provider>,
47+
);
48+
expect(asFragment()).toMatchSnapshot();
49+
});
50+
51+
describe("thread context", () => {
52+
it("should pre-populate a thread relationship", () => {
53+
const cli = MatrixClientPeg.safeGet();
54+
const { asFragment } = render(
55+
<MatrixClientContext.Provider value={cli}>
56+
<DevtoolsContext.Provider
57+
value={{
58+
room: new Room("!roomId", cli, "@alice:example.com", {
59+
pendingEventOrdering: PendingEventOrdering.Detached,
60+
}),
61+
threadRootId: "$this_is_a_thread_id",
62+
}}
63+
>
64+
<TimelineEventEditor onBack={() => {}} />
65+
</DevtoolsContext.Provider>
66+
</MatrixClientContext.Provider>,
67+
);
68+
expect(asFragment()).toMatchSnapshot();
69+
});
70+
});
71+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`<EventEditor /> should render 1`] = `
4+
<DocumentFragment>
5+
<div
6+
class="mx_DevTools_content"
7+
>
8+
<div
9+
class="mx_DevTools_eventTypeStateKeyGroup"
10+
>
11+
<div
12+
class="mx_Field mx_Field_input"
13+
>
14+
<input
15+
autocomplete="on"
16+
id="eventType"
17+
label="Event Type"
18+
placeholder="Event Type"
19+
size="42"
20+
type="text"
21+
value=""
22+
/>
23+
<label
24+
for="eventType"
25+
>
26+
Event Type
27+
</label>
28+
</div>
29+
</div>
30+
<div
31+
class="mx_Field mx_Field_textarea mx_DevTools_textarea"
32+
>
33+
<textarea
34+
autocomplete="off"
35+
id="evContent"
36+
label="Event Content"
37+
placeholder="Event Content"
38+
type="text"
39+
>
40+
{
41+
42+
}
43+
</textarea>
44+
<label
45+
for="evContent"
46+
>
47+
Event Content
48+
</label>
49+
</div>
50+
</div>
51+
<div
52+
class="mx_Dialog_buttons"
53+
>
54+
<button>
55+
Back
56+
</button>
57+
<button>
58+
Send
59+
</button>
60+
</div>
61+
</DocumentFragment>
62+
`;
63+
64+
exports[`<EventEditor /> thread context should pre-populate a thread relationship 1`] = `
65+
<DocumentFragment>
66+
<div
67+
class="mx_DevTools_content"
68+
>
69+
<div
70+
class="mx_DevTools_eventTypeStateKeyGroup"
71+
>
72+
<div
73+
class="mx_Field mx_Field_input"
74+
>
75+
<input
76+
autocomplete="on"
77+
id="eventType"
78+
label="Event Type"
79+
placeholder="Event Type"
80+
size="42"
81+
type="text"
82+
value=""
83+
/>
84+
<label
85+
for="eventType"
86+
>
87+
Event Type
88+
</label>
89+
</div>
90+
</div>
91+
<div
92+
class="mx_Field mx_Field_textarea mx_DevTools_textarea"
93+
>
94+
<textarea
95+
autocomplete="off"
96+
id="evContent"
97+
label="Event Content"
98+
placeholder="Event Content"
99+
type="text"
100+
>
101+
{
102+
"m.relates_to": {
103+
"rel_type": "m.thread",
104+
"event_id": "$this_is_a_thread_id"
105+
}
106+
}
107+
</textarea>
108+
<label
109+
for="evContent"
110+
>
111+
Event Content
112+
</label>
113+
</div>
114+
</div>
115+
<div
116+
class="mx_Dialog_buttons"
117+
>
118+
<button>
119+
Back
120+
</button>
121+
<button>
122+
Send
123+
</button>
124+
</div>
125+
</DocumentFragment>
126+
`;

0 commit comments

Comments
 (0)