1
0
mirror of https://github.com/matrix-org/matrix-react-sdk.git synced 2025-08-07 21:23:00 +03:00

Merge branch 'develop' into johannes/find-myself

This commit is contained in:
Johannes Marbach
2023-02-13 20:16:04 +01:00
committed by GitHub
612 changed files with 3608 additions and 2769 deletions

View File

@@ -40,7 +40,7 @@ describe("DateSeparator", () => {
};
const RealDate = global.Date;
class MockDate extends Date {
constructor(date) {
constructor(date: string | number | Date) {
super(date || now);
}
}

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import React, { ComponentProps } from "react";
// eslint-disable-next-line deprecate/import
import { mount } from "enzyme";
import { act } from "react-dom/test-utils";
@@ -63,7 +63,7 @@ describe("<MBeaconBody />", () => {
const defaultEvent = makeBeaconInfoEvent(aliceId, roomId, { isLive: true }, "$alice-room1-1");
const defaultProps = {
const defaultProps: ComponentProps<typeof MBeaconBody> = {
mxEvent: defaultEvent,
highlights: [],
highlightLink: "",
@@ -80,7 +80,10 @@ describe("<MBeaconBody />", () => {
wrappingComponentProps: { value: mockClient },
});
const modalSpy = jest.spyOn(Modal, "createDialog").mockReturnValue(undefined);
const modalSpy = jest.spyOn(Modal, "createDialog").mockReturnValue({
finished: Promise.resolve([true]),
close: () => {},
});
beforeEach(() => {
jest.clearAllMocks();
@@ -169,7 +172,7 @@ describe("<MBeaconBody />", () => {
const room = makeRoomWithStateEvents([aliceBeaconInfo1], { roomId, mockClient });
const component = getComponent({ mxEvent: aliceBeaconInfo1 });
const beaconInstance = room.currentState.beacons.get(getBeaconInfoIdentifier(aliceBeaconInfo1));
const beaconInstance = room.currentState.beacons.get(getBeaconInfoIdentifier(aliceBeaconInfo1))!;
// update alice's beacon with a new edition
// beacon instance emits
act(() => {
@@ -190,7 +193,7 @@ describe("<MBeaconBody />", () => {
const aliceBeaconInfo = makeBeaconInfoEvent(aliceId, roomId, { isLive: true }, "$alice-room1-1");
const room = makeRoomWithStateEvents([aliceBeaconInfo], { roomId, mockClient });
const beaconInstance = room.currentState.beacons.get(getBeaconInfoIdentifier(aliceBeaconInfo));
const beaconInstance = room.currentState.beacons.get(getBeaconInfoIdentifier(aliceBeaconInfo))!;
const component = getComponent({ mxEvent: aliceBeaconInfo });
act(() => {
@@ -240,7 +243,7 @@ describe("<MBeaconBody />", () => {
it("renders a live beacon with a location correctly", () => {
const room = makeRoomWithStateEvents([aliceBeaconInfo], { roomId, mockClient });
const beaconInstance = room.currentState.beacons.get(getBeaconInfoIdentifier(aliceBeaconInfo));
const beaconInstance = room.currentState.beacons.get(getBeaconInfoIdentifier(aliceBeaconInfo))!;
beaconInstance.addLocations([location1]);
const component = getComponent({ mxEvent: aliceBeaconInfo });
@@ -249,7 +252,7 @@ describe("<MBeaconBody />", () => {
it("opens maximised map view on click when beacon has a live location", () => {
const room = makeRoomWithStateEvents([aliceBeaconInfo], { roomId, mockClient });
const beaconInstance = room.currentState.beacons.get(getBeaconInfoIdentifier(aliceBeaconInfo));
const beaconInstance = room.currentState.beacons.get(getBeaconInfoIdentifier(aliceBeaconInfo))!;
beaconInstance.addLocations([location1]);
const component = getComponent({ mxEvent: aliceBeaconInfo });
@@ -274,7 +277,7 @@ describe("<MBeaconBody />", () => {
it("renders a live beacon with a location correctly", () => {
const room = makeRoomWithStateEvents([aliceBeaconInfo], { roomId, mockClient });
const beaconInstance = room.currentState.beacons.get(getBeaconInfoIdentifier(aliceBeaconInfo));
const beaconInstance = room.currentState.beacons.get(getBeaconInfoIdentifier(aliceBeaconInfo))!;
beaconInstance.addLocations([location1]);
const component = getComponent({ mxEvent: aliceBeaconInfo });
@@ -283,7 +286,7 @@ describe("<MBeaconBody />", () => {
it("opens maximised map view on click when beacon has a live location", () => {
const room = makeRoomWithStateEvents([aliceBeaconInfo], { roomId, mockClient });
const beaconInstance = room.currentState.beacons.get(getBeaconInfoIdentifier(aliceBeaconInfo));
const beaconInstance = room.currentState.beacons.get(getBeaconInfoIdentifier(aliceBeaconInfo))!;
beaconInstance.addLocations([location1]);
const component = getComponent({ mxEvent: aliceBeaconInfo });
@@ -299,7 +302,7 @@ describe("<MBeaconBody />", () => {
const room = makeRoomWithStateEvents([aliceBeaconInfo], { roomId, mockClient });
const component = getComponent({ mxEvent: aliceBeaconInfo });
const beaconInstance = room.currentState.beacons.get(getBeaconInfoIdentifier(aliceBeaconInfo));
const beaconInstance = room.currentState.beacons.get(getBeaconInfoIdentifier(aliceBeaconInfo))!;
act(() => {
beaconInstance.addLocations([location1]);
component.setProps({});
@@ -343,9 +346,9 @@ describe("<MBeaconBody />", () => {
const redactionEvent = new MatrixEvent({ type: EventType.RoomRedaction, content: { reason: "test reason" } });
const setupRoomWithBeacon = (beaconInfoEvent, locationEvents: MatrixEvent[] = []) => {
const setupRoomWithBeacon = (beaconInfoEvent: MatrixEvent, locationEvents: MatrixEvent[] = []) => {
const room = makeRoomWithStateEvents([beaconInfoEvent], { roomId, mockClient });
const beaconInstance = room.currentState.beacons.get(getBeaconInfoIdentifier(beaconInfoEvent));
const beaconInstance = room.currentState.beacons.get(getBeaconInfoIdentifier(beaconInfoEvent))!;
beaconInstance.addLocations(locationEvents);
};
const mockGetRelationsForEvent = (locationEvents: MatrixEvent[] = []) => {

View File

@@ -69,7 +69,7 @@ describe("<MImageBody/>", () => {
const props = {
onHeightChanged: jest.fn(),
onMessageAllowed: jest.fn(),
permalinkCreator: new RoomPermalinkCreator(new Room(encryptedMediaEvent.getRoomId(), cli, cli.getUserId())),
permalinkCreator: new RoomPermalinkCreator(new Room(encryptedMediaEvent.getRoomId()!, cli, cli.getUserId()!)),
};
it("should show error when encrypted media cannot be downloaded", async () => {

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import React, { ComponentProps } from "react";
// eslint-disable-next-line deprecate/import
import { mount } from "enzyme";
import { LocationAssetType } from "matrix-js-sdk/src/@types/location";
@@ -46,7 +46,7 @@ describe("MLocationBody", () => {
isGuest: jest.fn().mockReturnValue(false),
});
const defaultEvent = makeLocationEvent("geo:51.5076,-0.1276", LocationAssetType.Pin);
const defaultProps = {
const defaultProps: ComponentProps<typeof MLocationBody> = {
mxEvent: defaultEvent,
highlights: [],
highlightLink: "",
@@ -79,7 +79,7 @@ describe("MLocationBody", () => {
});
describe("with error", () => {
let sdkConfigSpy;
let sdkConfigSpy: jest.SpyInstance<any>;
beforeEach(() => {
// eat expected errors to keep console clean
@@ -171,7 +171,7 @@ describe("MLocationBody", () => {
const component = getComponent({ mxEvent: selfShareEvent });
// render self locations with user avatars
expect(component.find("SmartMarker").at(0).props()["roomMember"]).toEqual(member);
expect(component.find("SmartMarker").at(0).prop("roomMember")).toEqual(member);
});
});
});

View File

@@ -42,7 +42,7 @@ import MPollBody from "../../../../src/components/views/messages/MPollBody";
import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks";
import { MediaEventHelper } from "../../../../src/utils/MediaEventHelper";
const CHECKED = "mx_MPollBody_option_checked";
const CHECKED = "mx_PollOption_checked";
const userId = "@me:example.com";
const mockClient = getMockClientWithEventEmitter({
@@ -227,7 +227,7 @@ describe("MPollBody", () => {
content: newPollStart(undefined, undefined, true),
});
const props = getMPollBodyPropsFromEvent(mxEvent);
const room = await setupRoomWithPollEvents(mxEvent, votes, [], mockClient);
const room = await setupRoomWithPollEvents([mxEvent], votes, [], mockClient);
const renderResult = renderMPollBodyWithWrapper(props);
// wait for /relations promise to resolve
await flushPromises();
@@ -255,7 +255,7 @@ describe("MPollBody", () => {
content: newPollStart(undefined, undefined, true),
});
const props = getMPollBodyPropsFromEvent(mxEvent);
const room = await setupRoomWithPollEvents(mxEvent, votes, [], mockClient);
const room = await setupRoomWithPollEvents([mxEvent], votes, [], mockClient);
const renderResult = renderMPollBodyWithWrapper(props);
// wait for /relations promise to resolve
await flushPromises();
@@ -383,7 +383,7 @@ describe("MPollBody", () => {
const votes: MatrixEvent[] = [];
const ends: MatrixEvent[] = [];
const { container } = await newMPollBody(votes, ends, answers);
expect(container.querySelectorAll(".mx_MPollBody_option").length).toBe(20);
expect(container.querySelectorAll(".mx_PollOption").length).toBe(20);
});
it("hides scores if I voted but the poll is undisclosed", async () => {
@@ -429,7 +429,7 @@ describe("MPollBody", () => {
];
const ends = [newPollEndEvent("@me:example.com", 12)];
const renderResult = await newMPollBody(votes, ends, undefined, false);
expect(endedVotesCount(renderResult, "pizza")).toBe("3 votes");
expect(endedVotesCount(renderResult, "pizza")).toBe('<div class="mx_PollOption_winnerIcon"></div>3 votes');
expect(endedVotesCount(renderResult, "poutine")).toBe("1 vote");
expect(endedVotesCount(renderResult, "italian")).toBe("0 votes");
expect(endedVotesCount(renderResult, "wings")).toBe("1 vote");
@@ -531,9 +531,9 @@ describe("MPollBody", () => {
const ends = [newPollEndEvent("@me:example.com", 25)];
const renderResult = await newMPollBody(votes, ends);
expect(endedVotesCount(renderResult, "pizza")).toBe("0 votes");
expect(endedVotesCount(renderResult, "poutine")).toBe("1 vote");
expect(endedVotesCount(renderResult, "poutine")).toBe('<div class="mx_PollOption_winnerIcon"></div>1 vote');
expect(endedVotesCount(renderResult, "italian")).toBe("0 votes");
expect(endedVotesCount(renderResult, "wings")).toBe("1 vote");
expect(endedVotesCount(renderResult, "wings")).toBe('<div class="mx_PollOption_winnerIcon"></div>1 vote');
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Final result based on 2 votes");
});
@@ -542,7 +542,7 @@ describe("MPollBody", () => {
const ends = [newPollEndEvent("@me:example.com", 25)];
const renderResult = await newMPollBody(votes, ends);
expect(endedVotesCount(renderResult, "pizza")).toBe("0 votes");
expect(endedVotesCount(renderResult, "poutine")).toBe("1 vote");
expect(endedVotesCount(renderResult, "poutine")).toBe('<div class="mx_PollOption_winnerIcon"></div>1 vote');
expect(endedVotesCount(renderResult, "italian")).toBe("0 votes");
expect(endedVotesCount(renderResult, "wings")).toBe("0 votes");
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Final result based on 1 vote");
@@ -564,7 +564,7 @@ describe("MPollBody", () => {
expect(endedVotesCount(renderResult, "pizza")).toBe("2 votes");
expect(endedVotesCount(renderResult, "poutine")).toBe("0 votes");
expect(endedVotesCount(renderResult, "italian")).toBe("0 votes");
expect(endedVotesCount(renderResult, "wings")).toBe("3 votes");
expect(endedVotesCount(renderResult, "wings")).toBe('<div class="mx_PollOption_winnerIcon"></div>3 votes');
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Final result based on 5 votes");
});
@@ -584,7 +584,7 @@ describe("MPollBody", () => {
expect(endedVotesCount(renderResult, "pizza")).toBe("2 votes");
expect(endedVotesCount(renderResult, "poutine")).toBe("0 votes");
expect(endedVotesCount(renderResult, "italian")).toBe("0 votes");
expect(endedVotesCount(renderResult, "wings")).toBe("3 votes");
expect(endedVotesCount(renderResult, "wings")).toBe('<div class="mx_PollOption_winnerIcon"></div>3 votes');
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Final result based on 5 votes");
});
@@ -607,7 +607,7 @@ describe("MPollBody", () => {
expect(endedVotesCount(renderResult, "pizza")).toBe("2 votes");
expect(endedVotesCount(renderResult, "poutine")).toBe("0 votes");
expect(endedVotesCount(renderResult, "italian")).toBe("0 votes");
expect(endedVotesCount(renderResult, "wings")).toBe("3 votes");
expect(endedVotesCount(renderResult, "wings")).toBe('<div class="mx_PollOption_winnerIcon"></div>3 votes');
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Final result based on 5 votes");
});
@@ -634,7 +634,7 @@ describe("MPollBody", () => {
expect(endedVotesCount(renderResult, "pizza")).toBe("2 votes");
expect(endedVotesCount(renderResult, "poutine")).toBe("0 votes");
expect(endedVotesCount(renderResult, "italian")).toBe("0 votes");
expect(endedVotesCount(renderResult, "wings")).toBe("3 votes");
expect(endedVotesCount(renderResult, "wings")).toBe('<div class="mx_PollOption_winnerIcon"></div>3 votes');
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Final result based on 5 votes");
});
@@ -653,8 +653,8 @@ describe("MPollBody", () => {
expect(endedVoteChecked(renderResult, "pizza")).toBe(false);
// Double-check by looking for the endedOptionWinner class
expect(endedVoteDiv(renderResult, "wings").className.includes("mx_MPollBody_endedOptionWinner")).toBe(true);
expect(endedVoteDiv(renderResult, "pizza").className.includes("mx_MPollBody_endedOptionWinner")).toBe(false);
expect(endedVoteDiv(renderResult, "wings").className.includes("mx_PollOption_endedOptionWinner")).toBe(true);
expect(endedVoteDiv(renderResult, "pizza").className.includes("mx_PollOption_endedOptionWinner")).toBe(false);
});
it("highlights multiple winning votes", async () => {
@@ -670,13 +670,13 @@ describe("MPollBody", () => {
expect(endedVoteChecked(renderResult, "wings")).toBe(true);
expect(endedVoteChecked(renderResult, "poutine")).toBe(true);
expect(endedVoteChecked(renderResult, "italian")).toBe(false);
expect(renderResult.container.getElementsByClassName("mx_MPollBody_option_checked")).toHaveLength(3);
expect(renderResult.container.getElementsByClassName(CHECKED)).toHaveLength(3);
});
it("highlights nothing if poll has no votes", async () => {
const ends = [newPollEndEvent("@me:example.com", 25)];
const renderResult = await newMPollBody([], ends);
expect(renderResult.container.getElementsByClassName("mx_MPollBody_option_checked")).toHaveLength(0);
expect(renderResult.container.getElementsByClassName(CHECKED)).toHaveLength(0);
});
it("says poll is not ended if there is no end event", async () => {
@@ -700,7 +700,7 @@ describe("MPollBody", () => {
});
const ends = [newPollEndEvent("@me:example.com", 25)];
await setupRoomWithPollEvents(pollEvent, [], ends, mockClient);
await setupRoomWithPollEvents([pollEvent], [], ends, mockClient);
const poll = mockClient.getRoom(pollEvent.getRoomId()!)!.polls.get(pollEvent.getId()!)!;
// start fetching, dont await
poll.getResponses();
@@ -745,7 +745,7 @@ describe("MPollBody", () => {
expect(inputs[0].getAttribute("value")).toEqual("n1");
expect(inputs[1].getAttribute("value")).toEqual("n2");
expect(inputs[2].getAttribute("value")).toEqual("n3");
const options = container.querySelectorAll(".mx_MPollBody_optionText");
const options = container.querySelectorAll(".mx_PollOption_optionText");
expect(options).toHaveLength(3);
expect(options[0].innerHTML).toEqual("new answer 1");
expect(options[1].innerHTML).toEqual("new answer 2");
@@ -920,7 +920,7 @@ async function newMPollBodyFromEvent(
): Promise<RenderResult> {
const props = getMPollBodyPropsFromEvent(mxEvent);
await setupRoomWithPollEvents(mxEvent, relationEvents, endEvents, mockClient);
await setupRoomWithPollEvents([mxEvent], relationEvents, endEvents, mockClient);
return renderMPollBodyWithWrapper(props);
}
@@ -934,11 +934,11 @@ function voteButton({ getByTestId }: RenderResult, value: string): Element {
}
function votesCount({ getByTestId }: RenderResult, value: string): string {
return getByTestId(`pollOption-${value}`).querySelector(".mx_MPollBody_optionVoteCount")!.innerHTML;
return getByTestId(`pollOption-${value}`).querySelector(".mx_PollOption_optionVoteCount")!.innerHTML;
}
function endedVoteChecked({ getByTestId }: RenderResult, value: string): boolean {
return getByTestId(`pollOption-${value}`).className.includes("mx_MPollBody_option_checked");
return getByTestId(`pollOption-${value}`).className.includes(CHECKED);
}
function endedVoteDiv({ getByTestId }: RenderResult, value: string): Element {
@@ -1036,7 +1036,7 @@ async function runIsPollEnded(ends: MatrixEvent[]) {
content: newPollStart(),
});
await setupRoomWithPollEvents(pollEvent, [], ends, mockClient);
await setupRoomWithPollEvents([pollEvent], [], ends, mockClient);
return isPollEnded(pollEvent, mockClient);
}

View File

@@ -50,7 +50,7 @@ describe("<MPollEndBody />", () => {
const setupRoomWithEventsTimeline = async (pollEnd: MatrixEvent, pollStart?: MatrixEvent): Promise<Room> => {
if (pollStart) {
await setupRoomWithPollEvents(pollStart, [], [pollEnd], mockClient);
await setupRoomWithPollEvents([pollStart], [], [pollEnd], mockClient);
}
const room = mockClient.getRoom(roomId) || new Room(roomId, mockClient, userId);

View File

@@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
import React, { ComponentProps } from "react";
import { IContent, MatrixEvent } from "matrix-js-sdk/src/matrix";
import { render, RenderResult } from "@testing-library/react";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
@@ -43,7 +43,7 @@ describe("MVideoBody", () => {
});
function makeMVideoBody(w: number, h: number): RenderResult {
const content = {
const content: IContent = {
info: {
"w": w,
"h": h,
@@ -66,7 +66,7 @@ function makeMVideoBody(w: number, h: number): RenderResult {
content,
});
const defaultProps = {
const defaultProps: ComponentProps<typeof MVideoBody> = {
mxEvent: event,
highlights: [],
highlightLink: "",

View File

@@ -76,7 +76,7 @@ describe("<MessageActionBar />", () => {
});
const localStorageMock = (() => {
let store = {};
let store: Record<string, any> = {};
return {
getItem: jest.fn().mockImplementation((key) => store[key] ?? null),
setItem: jest.fn().mockImplementation((key, value) => {

View File

@@ -15,7 +15,7 @@ limitations under the License.
*/
import React from "react";
import { MatrixClient } from "matrix-js-sdk/src/matrix";
import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
import { MockedObject } from "jest-mock";
import { render } from "@testing-library/react";
@@ -38,9 +38,9 @@ describe("<TextualBody />", () => {
beforeEach(() => {
defaultMatrixClient = getMockClientWithEventEmitter({
getRoom: () => defaultRoom,
getAccountData: () => undefined,
getAccountData: (): MatrixEvent | undefined => undefined,
isGuest: () => false,
mxcUrlToHttp: (s) => s,
mxcUrlToHttp: (s: string) => s,
});
});
@@ -56,7 +56,7 @@ describe("<TextualBody />", () => {
});
const defaultProps = {
mxEvent: defaultEvent,
highlights: [],
highlights: [] as string[],
highlightLink: "",
onMessageAllowed: jest.fn(),
onHeightChanged: jest.fn(),
@@ -158,17 +158,17 @@ describe("<TextualBody />", () => {
});
describe("renders formatted m.text correctly", () => {
let matrixClient;
let matrixClient: MatrixClient;
beforeEach(() => {
matrixClient = getMockClientWithEventEmitter({
getRoom: () => mkStubRoom("room_id", "room name", undefined),
getAccountData: () => undefined,
getAccountData: (): MatrixEvent | undefined => undefined,
getUserId: () => "@me:my_server",
getHomeserverUrl: () => "https://my_server/",
on: () => undefined,
removeListener: () => undefined,
on: (): void => undefined,
removeListener: (): void => undefined,
isGuest: () => false,
mxcUrlToHttp: (s) => s,
mxcUrlToHttp: (s: string) => s,
});
DMRoomMap.makeShared();
});
@@ -375,10 +375,10 @@ describe("<TextualBody />", () => {
const matrixClient = getMockClientWithEventEmitter({
getRoom: () => mkStubRoom("room_id", "room name", undefined),
getAccountData: () => undefined,
getUrlPreview: (url) => new Promise(() => {}),
getAccountData: (): MatrixClient | undefined => undefined,
getUrlPreview: (url: string) => new Promise(() => {}),
isGuest: () => false,
mxcUrlToHttp: (s) => s,
mxcUrlToHttp: (s: string) => s,
});
DMRoomMap.makeShared();

View File

@@ -22,65 +22,65 @@ exports[`<MPollEndBody /> when poll start event exists in current timeline rende
class="mx_MPollBody_allOptions"
>
<div
class="mx_MPollBody_option mx_MPollBody_option_ended"
class="mx_PollOption mx_PollOption_ended"
data-testid="pollOption-socks"
>
<div
class="mx_MPollBody_endedOption"
class="mx_PollOption_endedOption"
data-value="socks"
>
<div
class="mx_MPollBody_optionDescription"
class="mx_PollOption_content"
>
<div
class="mx_MPollBody_optionText"
class="mx_PollOption_optionText"
>
Socks
</div>
<div
class="mx_MPollBody_optionVoteCount"
class="mx_PollOption_optionVoteCount"
>
0 votes
</div>
</div>
</div>
<div
class="mx_MPollBody_popularityBackground"
class="mx_PollOption_popularityBackground"
>
<div
class="mx_MPollBody_popularityAmount"
class="mx_PollOption_popularityAmount"
style="width: 0%;"
/>
</div>
</div>
<div
class="mx_MPollBody_option mx_MPollBody_option_ended"
class="mx_PollOption mx_PollOption_ended"
data-testid="pollOption-shoes"
>
<div
class="mx_MPollBody_endedOption"
class="mx_PollOption_endedOption"
data-value="shoes"
>
<div
class="mx_MPollBody_optionDescription"
class="mx_PollOption_content"
>
<div
class="mx_MPollBody_optionText"
class="mx_PollOption_optionText"
>
Shoes
</div>
<div
class="mx_MPollBody_optionVoteCount"
class="mx_PollOption_optionVoteCount"
>
0 votes
</div>
</div>
</div>
<div
class="mx_MPollBody_popularityBackground"
class="mx_PollOption_popularityBackground"
>
<div
class="mx_MPollBody_popularityAmount"
class="mx_PollOption_popularityAmount"
style="width: 0%;"
/>
</div>