Merge branch 'develop' into johannes/find-myself
This commit is contained in:
commit
f842e319de
26 changed files with 707 additions and 271 deletions
2
.github/workflows/static_analysis.yaml
vendored
2
.github/workflows/static_analysis.yaml
vendored
|
@ -52,6 +52,8 @@ jobs:
|
||||||
- "--noImplicitAny"
|
- "--noImplicitAny"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
|
|
||||||
- name: Install Deps
|
- name: Install Deps
|
||||||
run: "scripts/ci/layered.sh"
|
run: "scripts/ci/layered.sh"
|
||||||
|
|
|
@ -236,6 +236,7 @@
|
||||||
@import "./views/messages/_MLocationBody.pcss";
|
@import "./views/messages/_MLocationBody.pcss";
|
||||||
@import "./views/messages/_MNoticeBody.pcss";
|
@import "./views/messages/_MNoticeBody.pcss";
|
||||||
@import "./views/messages/_MPollBody.pcss";
|
@import "./views/messages/_MPollBody.pcss";
|
||||||
|
@import "./views/messages/_MPollEndBody.pcss";
|
||||||
@import "./views/messages/_MStickerBody.pcss";
|
@import "./views/messages/_MStickerBody.pcss";
|
||||||
@import "./views/messages/_MTextBody.pcss";
|
@import "./views/messages/_MTextBody.pcss";
|
||||||
@import "./views/messages/_MVideoBody.pcss";
|
@import "./views/messages/_MVideoBody.pcss";
|
||||||
|
|
22
res/css/views/messages/_MPollEndBody.pcss
Normal file
22
res/css/views/messages/_MPollEndBody.pcss
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mx_MPollEndBody_icon {
|
||||||
|
height: 14px;
|
||||||
|
margin-right: $spacing-8;
|
||||||
|
vertical-align: middle;
|
||||||
|
color: $secondary-content;
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M3 9.5C3 9.22386 3.22386 9 3.5 9H6.5C6.77614 9 7 9.22386 7 9.5V22H3V9.5Z" fill="#C1C6CD"/>
|
<path d="M3 9.5C3 9.22386 3.22386 9 3.5 9H6.5C6.77614 9 7 9.22386 7 9.5V22H3V9.5Z" fill="currentColor"/>
|
||||||
<path d="M17 13.5C17 13.2239 17.2239 13 17.5 13H20.5C20.7761 13 21 13.2239 21 13.5V22H17V13.5Z" fill="#C1C6CD"/>
|
<path d="M17 13.5C17 13.2239 17.2239 13 17.5 13H20.5C20.7761 13 21 13.2239 21 13.5V22H17V13.5Z" fill="currentColor"/>
|
||||||
<path d="M10 2.5C10 2.22386 10.2239 2 10.5 2H13.5C13.7761 2 14 2.22386 14 2.5V22H10V2.5Z" fill="#C1C6CD"/>
|
<path d="M10 2.5C10 2.22386 10.2239 2 10.5 2H13.5C13.7761 2 14 2.22386 14 2.5V22H10V2.5Z" fill="currentColor"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 423 B After Width: | Height: | Size: 438 B |
|
@ -39,7 +39,7 @@ const ZoomButtons: React.FC<Props> = ({ map }) => {
|
||||||
<div className="mx_ZoomButtons">
|
<div className="mx_ZoomButtons">
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
onClick={onZoomIn}
|
onClick={onZoomIn}
|
||||||
data-test-id="map-zoom-in-button"
|
data-testid="map-zoom-in-button"
|
||||||
title={_t("Zoom in")}
|
title={_t("Zoom in")}
|
||||||
className="mx_ZoomButtons_button"
|
className="mx_ZoomButtons_button"
|
||||||
>
|
>
|
||||||
|
@ -47,7 +47,7 @@ const ZoomButtons: React.FC<Props> = ({ map }) => {
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
onClick={onZoomOut}
|
onClick={onZoomOut}
|
||||||
data-test-id="map-zoom-out-button"
|
data-testid="map-zoom-out-button"
|
||||||
title={_t("Zoom out")}
|
title={_t("Zoom out")}
|
||||||
className="mx_ZoomButtons_button"
|
className="mx_ZoomButtons_button"
|
||||||
>
|
>
|
||||||
|
|
109
src/components/views/messages/MPollEndBody.tsx
Normal file
109
src/components/views/messages/MPollEndBody.tsx
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useEffect, useState, useContext } from "react";
|
||||||
|
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||||
|
import { M_TEXT } from "matrix-js-sdk/src/@types/extensible_events";
|
||||||
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
|
import { Icon as PollIcon } from "../../../../res/img/element-icons/room/composer/poll.svg";
|
||||||
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
import { textForEvent } from "../../../TextForEvent";
|
||||||
|
import { IBodyProps } from "./IBodyProps";
|
||||||
|
import MPollBody from "./MPollBody";
|
||||||
|
|
||||||
|
const getRelatedPollStartEventId = (event: MatrixEvent): string | undefined => {
|
||||||
|
const relation = event.getRelation();
|
||||||
|
return relation?.event_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to retrieve the related poll start event for this end event
|
||||||
|
* If the event already exists in the rooms timeline, return it
|
||||||
|
* Otherwise try to fetch the event from the server
|
||||||
|
* @param event
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const usePollStartEvent = (event: MatrixEvent): { pollStartEvent?: MatrixEvent; isLoadingPollStartEvent: boolean } => {
|
||||||
|
const matrixClient = useContext(MatrixClientContext);
|
||||||
|
const [pollStartEvent, setPollStartEvent] = useState<MatrixEvent>();
|
||||||
|
const [isLoadingPollStartEvent, setIsLoadingPollStartEvent] = useState(false);
|
||||||
|
|
||||||
|
const pollStartEventId = getRelatedPollStartEventId(event);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const room = matrixClient.getRoom(event.getRoomId());
|
||||||
|
const fetchPollStartEvent = async (roomId: string, pollStartEventId: string): Promise<void> => {
|
||||||
|
setIsLoadingPollStartEvent(true);
|
||||||
|
try {
|
||||||
|
const startEventJson = await matrixClient.fetchRoomEvent(roomId, pollStartEventId);
|
||||||
|
const startEvent = new MatrixEvent(startEventJson);
|
||||||
|
// add the poll to the room polls state
|
||||||
|
room?.processPollEvents([startEvent, event]);
|
||||||
|
|
||||||
|
// end event is not a valid end to the related start event
|
||||||
|
// if not sent by the same user
|
||||||
|
if (startEvent.getSender() === event.getSender()) {
|
||||||
|
setPollStartEvent(startEvent);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Failed to fetch related poll start event", error);
|
||||||
|
} finally {
|
||||||
|
setIsLoadingPollStartEvent(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (pollStartEvent || !room || !pollStartEventId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const timelineSet = room.getUnfilteredTimelineSet();
|
||||||
|
const localEvent = timelineSet
|
||||||
|
?.getTimelineForEvent(pollStartEventId)
|
||||||
|
?.getEvents()
|
||||||
|
.find((e) => e.getId() === pollStartEventId);
|
||||||
|
|
||||||
|
if (localEvent) {
|
||||||
|
// end event is not a valid end to the related start event
|
||||||
|
// if not sent by the same user
|
||||||
|
if (localEvent.getSender() === event.getSender()) {
|
||||||
|
setPollStartEvent(localEvent);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// pollStartEvent is not in the current timeline,
|
||||||
|
// fetch it
|
||||||
|
fetchPollStartEvent(room.roomId, pollStartEventId);
|
||||||
|
}
|
||||||
|
}, [event, pollStartEventId, pollStartEvent, matrixClient]);
|
||||||
|
|
||||||
|
return { pollStartEvent, isLoadingPollStartEvent };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MPollEndBody = React.forwardRef<any, IBodyProps>(({ mxEvent, ...props }, ref) => {
|
||||||
|
const { pollStartEvent, isLoadingPollStartEvent } = usePollStartEvent(mxEvent);
|
||||||
|
|
||||||
|
if (!pollStartEvent) {
|
||||||
|
const pollEndFallbackMessage = M_TEXT.findIn(mxEvent.getContent()) || textForEvent(mxEvent);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PollIcon className="mx_MPollEndBody_icon" />
|
||||||
|
{!isLoadingPollStartEvent && pollEndFallbackMessage}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <MPollBody mxEvent={pollStartEvent} {...props} />;
|
||||||
|
});
|
|
@ -18,7 +18,7 @@ import React, { createRef } from "react";
|
||||||
import { EventType, MsgType } from "matrix-js-sdk/src/@types/event";
|
import { EventType, MsgType } from "matrix-js-sdk/src/@types/event";
|
||||||
import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon";
|
import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon";
|
||||||
import { M_LOCATION } from "matrix-js-sdk/src/@types/location";
|
import { M_LOCATION } from "matrix-js-sdk/src/@types/location";
|
||||||
import { M_POLL_START } from "matrix-js-sdk/src/@types/polls";
|
import { M_POLL_END, M_POLL_START } from "matrix-js-sdk/src/@types/polls";
|
||||||
import { MatrixEventEvent } from "matrix-js-sdk/src/models/event";
|
import { MatrixEventEvent } from "matrix-js-sdk/src/models/event";
|
||||||
|
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
@ -37,6 +37,7 @@ import MVoiceOrAudioBody from "./MVoiceOrAudioBody";
|
||||||
import MVideoBody from "./MVideoBody";
|
import MVideoBody from "./MVideoBody";
|
||||||
import MStickerBody from "./MStickerBody";
|
import MStickerBody from "./MStickerBody";
|
||||||
import MPollBody from "./MPollBody";
|
import MPollBody from "./MPollBody";
|
||||||
|
import { MPollEndBody } from "./MPollEndBody";
|
||||||
import MLocationBody from "./MLocationBody";
|
import MLocationBody from "./MLocationBody";
|
||||||
import MjolnirBody from "./MjolnirBody";
|
import MjolnirBody from "./MjolnirBody";
|
||||||
import MBeaconBody from "./MBeaconBody";
|
import MBeaconBody from "./MBeaconBody";
|
||||||
|
@ -73,6 +74,8 @@ const baseEvTypes = new Map<string, React.ComponentType<Partial<IBodyProps>>>([
|
||||||
[EventType.Sticker, MStickerBody],
|
[EventType.Sticker, MStickerBody],
|
||||||
[M_POLL_START.name, MPollBody],
|
[M_POLL_START.name, MPollBody],
|
||||||
[M_POLL_START.altName, MPollBody],
|
[M_POLL_START.altName, MPollBody],
|
||||||
|
[M_POLL_END.name, MPollEndBody],
|
||||||
|
[M_POLL_END.altName, MPollEndBody],
|
||||||
[M_BEACON_INFO.name, MBeaconBody],
|
[M_BEACON_INFO.name, MBeaconBody],
|
||||||
[M_BEACON_INFO.altName, MBeaconBody],
|
[M_BEACON_INFO.altName, MBeaconBody],
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2020 Nurjin Jafar
|
Copyright 2020 Nurjin Jafar
|
||||||
Copyright 2020 Nordeck IT + Consulting GmbH.
|
Copyright 2020 Nordeck IT + Consulting GmbH.
|
||||||
|
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -86,7 +87,7 @@ export default class Confetti implements ICanvasEffect {
|
||||||
private particles: Array<ConfettiParticle> = [];
|
private particles: Array<ConfettiParticle> = [];
|
||||||
private waveAngle = 0;
|
private waveAngle = 0;
|
||||||
|
|
||||||
public isRunning: boolean;
|
public isRunning = false;
|
||||||
|
|
||||||
public start = async (canvas: HTMLCanvasElement, timeout = 3000): Promise<void> => {
|
public start = async (canvas: HTMLCanvasElement, timeout = 3000): Promise<void> => {
|
||||||
if (!canvas) {
|
if (!canvas) {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2020 Nurjin Jafar
|
Copyright 2020 Nurjin Jafar
|
||||||
Copyright 2020 Nordeck IT + Consulting GmbH.
|
Copyright 2020 Nordeck IT + Consulting GmbH.
|
||||||
|
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -69,7 +70,7 @@ export default class Fireworks implements ICanvasEffect {
|
||||||
private context: CanvasRenderingContext2D | null = null;
|
private context: CanvasRenderingContext2D | null = null;
|
||||||
private supportsAnimationFrame = window.requestAnimationFrame;
|
private supportsAnimationFrame = window.requestAnimationFrame;
|
||||||
private particles: Array<FireworksParticle> = [];
|
private particles: Array<FireworksParticle> = [];
|
||||||
public isRunning: boolean;
|
public isRunning = false;
|
||||||
|
|
||||||
public start = async (canvas: HTMLCanvasElement, timeout = 3000): Promise<void> => {
|
public start = async (canvas: HTMLCanvasElement, timeout = 3000): Promise<void> => {
|
||||||
if (!canvas) {
|
if (!canvas) {
|
||||||
|
@ -94,7 +95,7 @@ export default class Fireworks implements ICanvasEffect {
|
||||||
if (this.particles.length < this.options.maxCount && this.isRunning) {
|
if (this.particles.length < this.options.maxCount && this.isRunning) {
|
||||||
this.createFirework();
|
this.createFirework();
|
||||||
}
|
}
|
||||||
const alive = [];
|
const alive: FireworksParticle[] = [];
|
||||||
for (let i = 0; i < this.particles.length; i++) {
|
for (let i = 0; i < this.particles.length; i++) {
|
||||||
if (this.move(this.particles[i])) {
|
if (this.move(this.particles[i])) {
|
||||||
alive.push(this.particles[i]);
|
alive.push(this.particles[i]);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
Copyright 2021 - 2023 The Matrix.org Foundation C.I.C.
|
||||||
Copyright 2022 Arseny Uskov
|
Copyright 2022 Arseny Uskov
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@ -65,7 +65,7 @@ export default class Hearts implements ICanvasEffect {
|
||||||
|
|
||||||
private context: CanvasRenderingContext2D | null = null;
|
private context: CanvasRenderingContext2D | null = null;
|
||||||
private particles: Array<Heart> = [];
|
private particles: Array<Heart> = [];
|
||||||
private lastAnimationTime: number;
|
private lastAnimationTime = 0;
|
||||||
|
|
||||||
private colours = [
|
private colours = [
|
||||||
"rgba(194,210,224,1)",
|
"rgba(194,210,224,1)",
|
||||||
|
@ -82,7 +82,7 @@ export default class Hearts implements ICanvasEffect {
|
||||||
"rgba(252,116,183,1)",
|
"rgba(252,116,183,1)",
|
||||||
];
|
];
|
||||||
|
|
||||||
public isRunning: boolean;
|
public isRunning = false;
|
||||||
|
|
||||||
public start = async (canvas: HTMLCanvasElement, timeout = 3000): Promise<void> => {
|
public start = async (canvas: HTMLCanvasElement, timeout = 3000): Promise<void> => {
|
||||||
if (!canvas) {
|
if (!canvas) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
Copyright 2020 - 2023 The Matrix.org Foundation C.I.C.
|
||||||
Copyright 2021 Josias Allestad
|
Copyright 2021 Josias Allestad
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@ -52,9 +52,9 @@ export default class Rainfall implements ICanvasEffect {
|
||||||
|
|
||||||
private context: CanvasRenderingContext2D | null = null;
|
private context: CanvasRenderingContext2D | null = null;
|
||||||
private particles: Array<Raindrop> = [];
|
private particles: Array<Raindrop> = [];
|
||||||
private lastAnimationTime: number;
|
private lastAnimationTime = 0;
|
||||||
|
|
||||||
public isRunning: boolean;
|
public isRunning = false;
|
||||||
|
|
||||||
public start = async (canvas: HTMLCanvasElement, timeout = 3000): Promise<void> => {
|
public start = async (canvas: HTMLCanvasElement, timeout = 3000): Promise<void> => {
|
||||||
if (!canvas) {
|
if (!canvas) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
Copyright 2020 - 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -57,9 +57,9 @@ export default class Snowfall implements ICanvasEffect {
|
||||||
|
|
||||||
private context: CanvasRenderingContext2D | null = null;
|
private context: CanvasRenderingContext2D | null = null;
|
||||||
private particles: Array<Snowflake> = [];
|
private particles: Array<Snowflake> = [];
|
||||||
private lastAnimationTime: number;
|
private lastAnimationTime = 0;
|
||||||
|
|
||||||
public isRunning: boolean;
|
public isRunning = false;
|
||||||
|
|
||||||
public start = async (canvas: HTMLCanvasElement, timeout = 3000): Promise<void> => {
|
public start = async (canvas: HTMLCanvasElement, timeout = 3000): Promise<void> => {
|
||||||
if (!canvas) {
|
if (!canvas) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
Copyright 2021 - 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -51,9 +51,9 @@ export default class SpaceInvaders implements ICanvasEffect {
|
||||||
|
|
||||||
private context: CanvasRenderingContext2D | null = null;
|
private context: CanvasRenderingContext2D | null = null;
|
||||||
private particles: Array<Invader> = [];
|
private particles: Array<Invader> = [];
|
||||||
private lastAnimationTime: number;
|
private lastAnimationTime = 0;
|
||||||
|
|
||||||
public isRunning: boolean;
|
public isRunning = false;
|
||||||
|
|
||||||
public start = async (canvas: HTMLCanvasElement, timeout = 3000): Promise<void> => {
|
public start = async (canvas: HTMLCanvasElement, timeout = 3000): Promise<void> => {
|
||||||
if (!canvas) {
|
if (!canvas) {
|
||||||
|
|
|
@ -18,7 +18,7 @@ import React from "react";
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
import { EventType, MsgType, RelationType } from "matrix-js-sdk/src/@types/event";
|
import { EventType, MsgType, RelationType } from "matrix-js-sdk/src/@types/event";
|
||||||
import { Optional } from "matrix-events-sdk";
|
import { Optional } from "matrix-events-sdk";
|
||||||
import { M_POLL_START } from "matrix-js-sdk/src/@types/polls";
|
import { M_POLL_END, M_POLL_START } from "matrix-js-sdk/src/@types/polls";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { GroupCallIntent } from "matrix-js-sdk/src/webrtc/groupCall";
|
import { GroupCallIntent } from "matrix-js-sdk/src/webrtc/groupCall";
|
||||||
|
|
||||||
|
@ -99,6 +99,8 @@ const EVENT_TILE_TYPES = new Map<string, Factory>([
|
||||||
[EventType.Sticker, MessageEventFactory],
|
[EventType.Sticker, MessageEventFactory],
|
||||||
[M_POLL_START.name, MessageEventFactory],
|
[M_POLL_START.name, MessageEventFactory],
|
||||||
[M_POLL_START.altName, MessageEventFactory],
|
[M_POLL_START.altName, MessageEventFactory],
|
||||||
|
[M_POLL_END.name, MessageEventFactory],
|
||||||
|
[M_POLL_END.altName, MessageEventFactory],
|
||||||
[EventType.KeyVerificationCancel, KeyVerificationConclFactory],
|
[EventType.KeyVerificationCancel, KeyVerificationConclFactory],
|
||||||
[EventType.KeyVerificationDone, KeyVerificationConclFactory],
|
[EventType.KeyVerificationDone, KeyVerificationConclFactory],
|
||||||
[EventType.CallInvite, LegacyCallEventFactory], // note that this requires a special factory type
|
[EventType.CallInvite, LegacyCallEventFactory], // note that this requires a special factory type
|
||||||
|
@ -412,7 +414,11 @@ export function renderReplyTile(
|
||||||
// XXX: this'll eventually be dynamic based on the fields once we have extensible event types
|
// XXX: this'll eventually be dynamic based on the fields once we have extensible event types
|
||||||
const messageTypes = [EventType.RoomMessage, EventType.Sticker];
|
const messageTypes = [EventType.RoomMessage, EventType.Sticker];
|
||||||
export function isMessageEvent(ev: MatrixEvent): boolean {
|
export function isMessageEvent(ev: MatrixEvent): boolean {
|
||||||
return messageTypes.includes(ev.getType() as EventType) || M_POLL_START.matches(ev.getType());
|
return (
|
||||||
|
messageTypes.includes(ev.getType() as EventType) ||
|
||||||
|
M_POLL_START.matches(ev.getType()) ||
|
||||||
|
M_POLL_END.matches(ev.getType())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function haveRendererForEvent(mxEvent: MatrixEvent, showHiddenEvents: boolean): boolean {
|
export function haveRendererForEvent(mxEvent: MatrixEvent, showHiddenEvents: boolean): boolean {
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { M_POLL_START } from "matrix-js-sdk/src/@types/polls";
|
import { M_POLL_END, M_POLL_START } from "matrix-js-sdk/src/@types/polls";
|
||||||
import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon";
|
import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon";
|
||||||
import { MatrixEvent, MatrixClient } from "matrix-js-sdk/src/matrix";
|
import { MatrixEvent, MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ import { VoiceBroadcastInfoEventType } from "../../voice-broadcast/types";
|
||||||
* If an event is not forwardable return null
|
* If an event is not forwardable return null
|
||||||
*/
|
*/
|
||||||
export const getForwardableEvent = (event: MatrixEvent, cli: MatrixClient): MatrixEvent | null => {
|
export const getForwardableEvent = (event: MatrixEvent, cli: MatrixClient): MatrixEvent | null => {
|
||||||
if (M_POLL_START.matches(event.getType())) {
|
if (M_POLL_START.matches(event.getType()) || M_POLL_END.matches(event.getType())) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
import { EventType, MsgType } from "matrix-js-sdk/src/@types/event";
|
import { EventType, MsgType } from "matrix-js-sdk/src/@types/event";
|
||||||
import { M_POLL_START } from "matrix-js-sdk/src/@types/polls";
|
import { M_POLL_END, M_POLL_START } from "matrix-js-sdk/src/@types/polls";
|
||||||
import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon";
|
import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon";
|
||||||
import { IContent } from "matrix-js-sdk/src/matrix";
|
import { IContent } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
|
@ -41,6 +41,7 @@ const calcIsInfoMessage = (
|
||||||
eventType !== EventType.Sticker &&
|
eventType !== EventType.Sticker &&
|
||||||
eventType !== EventType.RoomCreate &&
|
eventType !== EventType.RoomCreate &&
|
||||||
!M_POLL_START.matches(eventType) &&
|
!M_POLL_START.matches(eventType) &&
|
||||||
|
!M_POLL_END.matches(eventType) &&
|
||||||
!M_BEACON_INFO.matches(eventType) &&
|
!M_BEACON_INFO.matches(eventType) &&
|
||||||
!(eventType === VoiceBroadcastInfoEventType && content?.state === VoiceBroadcastInfoState.Started)
|
!(eventType === VoiceBroadcastInfoEventType && content?.state === VoiceBroadcastInfoState.Started)
|
||||||
);
|
);
|
||||||
|
|
|
@ -18,7 +18,7 @@ import { EventStatus, MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
import { EventType, EVENT_VISIBILITY_CHANGE_TYPE, MsgType, RelationType } from "matrix-js-sdk/src/@types/event";
|
import { EventType, EVENT_VISIBILITY_CHANGE_TYPE, MsgType, RelationType } from "matrix-js-sdk/src/@types/event";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { M_POLL_START } from "matrix-js-sdk/src/@types/polls";
|
import { M_POLL_END, M_POLL_START } from "matrix-js-sdk/src/@types/polls";
|
||||||
import { M_LOCATION } from "matrix-js-sdk/src/@types/location";
|
import { M_LOCATION } from "matrix-js-sdk/src/@types/location";
|
||||||
import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon";
|
import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon";
|
||||||
import { THREAD_RELATION_TYPE } from "matrix-js-sdk/src/models/thread";
|
import { THREAD_RELATION_TYPE } from "matrix-js-sdk/src/models/thread";
|
||||||
|
@ -57,6 +57,7 @@ export function isContentActionable(mxEvent: MatrixEvent): boolean {
|
||||||
} else if (
|
} else if (
|
||||||
mxEvent.getType() === "m.sticker" ||
|
mxEvent.getType() === "m.sticker" ||
|
||||||
M_POLL_START.matches(mxEvent.getType()) ||
|
M_POLL_START.matches(mxEvent.getType()) ||
|
||||||
|
M_POLL_END.matches(mxEvent.getType()) ||
|
||||||
M_BEACON_INFO.matches(mxEvent.getType()) ||
|
M_BEACON_INFO.matches(mxEvent.getType()) ||
|
||||||
(mxEvent.getType() === VoiceBroadcastInfoEventType &&
|
(mxEvent.getType() === VoiceBroadcastInfoEventType &&
|
||||||
mxEvent.getContent()?.state === VoiceBroadcastInfoState.Started)
|
mxEvent.getContent()?.state === VoiceBroadcastInfoState.Started)
|
||||||
|
|
|
@ -69,10 +69,10 @@ describe("<PollHistoryDialog />", () => {
|
||||||
expect(getByText("There are no polls in this room")).toBeTruthy();
|
expect(getByText("There are no polls in this room")).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders a list of polls when there are polls in the timeline", () => {
|
it("renders a list of polls when there are polls in the timeline", async () => {
|
||||||
const pollStart1 = makePollStartEvent("Question?", userId, undefined, 1675300825090, "$1");
|
const pollStart1 = makePollStartEvent("Question?", userId, undefined, { ts: 1675300825090, id: "$1" });
|
||||||
const pollStart2 = makePollStartEvent("Where?", userId, undefined, 1675300725090, "$2");
|
const pollStart2 = makePollStartEvent("Where?", userId, undefined, { ts: 1675300725090, id: "$2" });
|
||||||
const pollStart3 = makePollStartEvent("What?", userId, undefined, 1675200725090, "$3");
|
const pollStart3 = makePollStartEvent("What?", userId, undefined, { ts: 1675200725090, id: "$3" });
|
||||||
const message = new MatrixEvent({
|
const message = new MatrixEvent({
|
||||||
type: "m.room.message",
|
type: "m.room.message",
|
||||||
content: {},
|
content: {},
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
Copyright 2022, 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -15,13 +15,10 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
// eslint-disable-next-line deprecate/import
|
|
||||||
import { mount } from "enzyme";
|
|
||||||
import * as maplibregl from "maplibre-gl";
|
import * as maplibregl from "maplibre-gl";
|
||||||
import { act } from "react-dom/test-utils";
|
import { render, screen } from "@testing-library/react";
|
||||||
|
|
||||||
import ZoomButtons from "../../../../src/components/views/location/ZoomButtons";
|
import ZoomButtons from "../../../../src/components/views/location/ZoomButtons";
|
||||||
import { findByTestId } from "../../../test-utils";
|
|
||||||
|
|
||||||
describe("<ZoomButtons />", () => {
|
describe("<ZoomButtons />", () => {
|
||||||
const mapOptions = { container: {} as unknown as HTMLElement, style: "" };
|
const mapOptions = { container: {} as unknown as HTMLElement, style: "" };
|
||||||
|
@ -29,7 +26,7 @@ describe("<ZoomButtons />", () => {
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
map: mockMap,
|
map: mockMap,
|
||||||
};
|
};
|
||||||
const getComponent = (props = {}) => mount(<ZoomButtons {...defaultProps} {...props} />);
|
const getComponent = (props = {}) => render(<ZoomButtons {...defaultProps} {...props} />);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
@ -37,15 +34,12 @@ describe("<ZoomButtons />", () => {
|
||||||
|
|
||||||
it("renders buttons", () => {
|
it("renders buttons", () => {
|
||||||
const component = getComponent();
|
const component = getComponent();
|
||||||
expect(component).toMatchSnapshot();
|
expect(component.asFragment()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("calls map zoom in on zoom in click", () => {
|
it("calls map zoom in on zoom in click", () => {
|
||||||
const component = getComponent();
|
const component = getComponent();
|
||||||
|
screen.getByTestId("map-zoom-in-button").click();
|
||||||
act(() => {
|
|
||||||
findByTestId(component, "map-zoom-in-button").at(0).simulate("click");
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(mockMap.zoomIn).toHaveBeenCalled();
|
expect(mockMap.zoomIn).toHaveBeenCalled();
|
||||||
expect(component).toBeTruthy();
|
expect(component).toBeTruthy();
|
||||||
|
@ -53,10 +47,7 @@ describe("<ZoomButtons />", () => {
|
||||||
|
|
||||||
it("calls map zoom out on zoom out click", () => {
|
it("calls map zoom out on zoom out click", () => {
|
||||||
const component = getComponent();
|
const component = getComponent();
|
||||||
|
screen.getByTestId("map-zoom-out-button").click();
|
||||||
act(() => {
|
|
||||||
findByTestId(component, "map-zoom-out-button").at(0).simulate("click");
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(mockMap.zoomOut).toHaveBeenCalled();
|
expect(mockMap.zoomOut).toHaveBeenCalled();
|
||||||
expect(component).toBeTruthy();
|
expect(component).toBeTruthy();
|
||||||
|
|
|
@ -162,7 +162,7 @@ exports[`<LocationViewDialog /> renders map correctly 1`] = `
|
||||||
>
|
>
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
className="mx_ZoomButtons_button"
|
className="mx_ZoomButtons_button"
|
||||||
data-test-id="map-zoom-in-button"
|
data-testid="map-zoom-in-button"
|
||||||
element="div"
|
element="div"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
role="button"
|
role="button"
|
||||||
|
@ -171,7 +171,7 @@ exports[`<LocationViewDialog /> renders map correctly 1`] = `
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="mx_AccessibleButton mx_ZoomButtons_button"
|
className="mx_AccessibleButton mx_ZoomButtons_button"
|
||||||
data-test-id="map-zoom-in-button"
|
data-testid="map-zoom-in-button"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
onKeyUp={[Function]}
|
onKeyUp={[Function]}
|
||||||
|
@ -186,7 +186,7 @@ exports[`<LocationViewDialog /> renders map correctly 1`] = `
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
className="mx_ZoomButtons_button"
|
className="mx_ZoomButtons_button"
|
||||||
data-test-id="map-zoom-out-button"
|
data-testid="map-zoom-out-button"
|
||||||
element="div"
|
element="div"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
role="button"
|
role="button"
|
||||||
|
@ -195,7 +195,7 @@ exports[`<LocationViewDialog /> renders map correctly 1`] = `
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="mx_AccessibleButton mx_ZoomButtons_button"
|
className="mx_AccessibleButton mx_ZoomButtons_button"
|
||||||
data-test-id="map-zoom-out-button"
|
data-testid="map-zoom-out-button"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
onKeyUp={[Function]}
|
onKeyUp={[Function]}
|
||||||
|
|
|
@ -1,74 +1,32 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`<ZoomButtons /> renders buttons 1`] = `
|
exports[`<ZoomButtons /> renders buttons 1`] = `
|
||||||
<ZoomButtons
|
<DocumentFragment>
|
||||||
map={
|
|
||||||
MockMap {
|
|
||||||
"_events": {},
|
|
||||||
"_eventsCount": 0,
|
|
||||||
"_maxListeners": undefined,
|
|
||||||
"addControl": [MockFunction],
|
|
||||||
"fitBounds": [MockFunction],
|
|
||||||
"removeControl": [MockFunction],
|
|
||||||
"setCenter": [MockFunction],
|
|
||||||
"setStyle": [MockFunction],
|
|
||||||
"zoomIn": [MockFunction],
|
|
||||||
"zoomOut": [MockFunction],
|
|
||||||
Symbol(kCapture): false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
className="mx_ZoomButtons"
|
class="mx_ZoomButtons"
|
||||||
>
|
>
|
||||||
<AccessibleButton
|
<div
|
||||||
className="mx_ZoomButtons_button"
|
class="mx_AccessibleButton mx_ZoomButtons_button"
|
||||||
data-test-id="map-zoom-in-button"
|
data-testid="map-zoom-in-button"
|
||||||
element="div"
|
|
||||||
onClick={[Function]}
|
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabindex="0"
|
||||||
title="Zoom in"
|
title="Zoom in"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="mx_AccessibleButton mx_ZoomButtons_button"
|
class="mx_ZoomButtons_icon"
|
||||||
data-test-id="map-zoom-in-button"
|
/>
|
||||||
onClick={[Function]}
|
</div>
|
||||||
onKeyDown={[Function]}
|
<div
|
||||||
onKeyUp={[Function]}
|
class="mx_AccessibleButton mx_ZoomButtons_button"
|
||||||
role="button"
|
data-testid="map-zoom-out-button"
|
||||||
tabIndex={0}
|
|
||||||
title="Zoom in"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="mx_ZoomButtons_icon"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</AccessibleButton>
|
|
||||||
<AccessibleButton
|
|
||||||
className="mx_ZoomButtons_button"
|
|
||||||
data-test-id="map-zoom-out-button"
|
|
||||||
element="div"
|
|
||||||
onClick={[Function]}
|
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabindex="0"
|
||||||
title="Zoom out"
|
title="Zoom out"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="mx_AccessibleButton mx_ZoomButtons_button"
|
class="mx_ZoomButtons_icon"
|
||||||
data-test-id="map-zoom-out-button"
|
/>
|
||||||
onClick={[Function]}
|
</div>
|
||||||
onKeyDown={[Function]}
|
|
||||||
onKeyUp={[Function]}
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
title="Zoom out"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="mx_ZoomButtons_icon"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</AccessibleButton>
|
|
||||||
</div>
|
</div>
|
||||||
</ZoomButtons>
|
</DocumentFragment>
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -16,10 +16,9 @@ limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { fireEvent, render, RenderResult } from "@testing-library/react";
|
import { fireEvent, render, RenderResult } from "@testing-library/react";
|
||||||
import { MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||||
import { Relations } from "matrix-js-sdk/src/models/relations";
|
import { Relations } from "matrix-js-sdk/src/models/relations";
|
||||||
import {
|
import {
|
||||||
M_POLL_END,
|
|
||||||
M_POLL_KIND_DISCLOSED,
|
M_POLL_KIND_DISCLOSED,
|
||||||
M_POLL_KIND_UNDISCLOSED,
|
M_POLL_KIND_UNDISCLOSED,
|
||||||
M_POLL_RESPONSE,
|
M_POLL_RESPONSE,
|
||||||
|
@ -31,7 +30,13 @@ import { M_TEXT } from "matrix-js-sdk/src/@types/extensible_events";
|
||||||
|
|
||||||
import { allVotes, findTopAnswer, isPollEnded } from "../../../../src/components/views/messages/MPollBody";
|
import { allVotes, findTopAnswer, isPollEnded } from "../../../../src/components/views/messages/MPollBody";
|
||||||
import { IBodyProps } from "../../../../src/components/views/messages/IBodyProps";
|
import { IBodyProps } from "../../../../src/components/views/messages/IBodyProps";
|
||||||
import { flushPromises, getMockClientWithEventEmitter, mockClientMethodsUser } from "../../../test-utils";
|
import {
|
||||||
|
flushPromises,
|
||||||
|
getMockClientWithEventEmitter,
|
||||||
|
makePollEndEvent,
|
||||||
|
mockClientMethodsUser,
|
||||||
|
setupRoomWithPollEvents,
|
||||||
|
} from "../../../test-utils";
|
||||||
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
|
||||||
import MPollBody from "../../../../src/components/views/messages/MPollBody";
|
import MPollBody from "../../../../src/components/views/messages/MPollBody";
|
||||||
import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks";
|
import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks";
|
||||||
|
@ -112,7 +117,7 @@ describe("MPollBody", () => {
|
||||||
responseEvent("@catrd:example.com", "poutine"),
|
responseEvent("@catrd:example.com", "poutine"),
|
||||||
responseEvent("@dune2:example.com", "wings"),
|
responseEvent("@dune2:example.com", "wings"),
|
||||||
];
|
];
|
||||||
const ends = [endEvent("@notallowed:example.com", 12)];
|
const ends = [newPollEndEvent("@notallowed:example.com", 12)];
|
||||||
const renderResult = await newMPollBody(votes, ends);
|
const renderResult = await newMPollBody(votes, ends);
|
||||||
|
|
||||||
// Even though an end event was sent, we render the poll as unfinished
|
// Even though an end event was sent, we render the poll as unfinished
|
||||||
|
@ -222,7 +227,7 @@ describe("MPollBody", () => {
|
||||||
content: newPollStart(undefined, undefined, true),
|
content: newPollStart(undefined, undefined, true),
|
||||||
});
|
});
|
||||||
const props = getMPollBodyPropsFromEvent(mxEvent);
|
const props = getMPollBodyPropsFromEvent(mxEvent);
|
||||||
const room = await setupRoomWithPollEvents(mxEvent, votes);
|
const room = await setupRoomWithPollEvents(mxEvent, votes, [], mockClient);
|
||||||
const renderResult = renderMPollBodyWithWrapper(props);
|
const renderResult = renderMPollBodyWithWrapper(props);
|
||||||
// wait for /relations promise to resolve
|
// wait for /relations promise to resolve
|
||||||
await flushPromises();
|
await flushPromises();
|
||||||
|
@ -250,7 +255,7 @@ describe("MPollBody", () => {
|
||||||
content: newPollStart(undefined, undefined, true),
|
content: newPollStart(undefined, undefined, true),
|
||||||
});
|
});
|
||||||
const props = getMPollBodyPropsFromEvent(mxEvent);
|
const props = getMPollBodyPropsFromEvent(mxEvent);
|
||||||
const room = await setupRoomWithPollEvents(mxEvent, votes);
|
const room = await setupRoomWithPollEvents(mxEvent, votes, [], mockClient);
|
||||||
const renderResult = renderMPollBodyWithWrapper(props);
|
const renderResult = renderMPollBodyWithWrapper(props);
|
||||||
// wait for /relations promise to resolve
|
// wait for /relations promise to resolve
|
||||||
await flushPromises();
|
await flushPromises();
|
||||||
|
@ -422,7 +427,7 @@ describe("MPollBody", () => {
|
||||||
responseEvent("@catrd:example.com", "poutine"),
|
responseEvent("@catrd:example.com", "poutine"),
|
||||||
responseEvent("@dune2:example.com", "wings"),
|
responseEvent("@dune2:example.com", "wings"),
|
||||||
];
|
];
|
||||||
const ends = [endEvent("@me:example.com", 12)];
|
const ends = [newPollEndEvent("@me:example.com", 12)];
|
||||||
const renderResult = await newMPollBody(votes, ends, undefined, false);
|
const renderResult = await newMPollBody(votes, ends, undefined, false);
|
||||||
expect(endedVotesCount(renderResult, "pizza")).toBe("3 votes");
|
expect(endedVotesCount(renderResult, "pizza")).toBe("3 votes");
|
||||||
expect(endedVotesCount(renderResult, "poutine")).toBe("1 vote");
|
expect(endedVotesCount(renderResult, "poutine")).toBe("1 vote");
|
||||||
|
@ -471,7 +476,7 @@ describe("MPollBody", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sends no events when I click in an ended poll", async () => {
|
it("sends no events when I click in an ended poll", async () => {
|
||||||
const ends = [endEvent("@me:example.com", 25)];
|
const ends = [newPollEndEvent("@me:example.com", 25)];
|
||||||
const votes = [responseEvent("@uy:example.com", "wings", 15), responseEvent("@uy:example.com", "poutine", 15)];
|
const votes = [responseEvent("@uy:example.com", "wings", 15), responseEvent("@uy:example.com", "poutine", 15)];
|
||||||
const renderResult = await newMPollBody(votes, ends);
|
const renderResult = await newMPollBody(votes, ends);
|
||||||
clickOption(renderResult, "wings");
|
clickOption(renderResult, "wings");
|
||||||
|
@ -509,7 +514,7 @@ describe("MPollBody", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows non-radio buttons if the poll is ended", async () => {
|
it("shows non-radio buttons if the poll is ended", async () => {
|
||||||
const events = [endEvent()];
|
const events = [newPollEndEvent()];
|
||||||
const { container } = await newMPollBody([], events);
|
const { container } = await newMPollBody([], events);
|
||||||
expect(container.querySelector(".mx_StyledRadioButton")).not.toBeInTheDocument();
|
expect(container.querySelector(".mx_StyledRadioButton")).not.toBeInTheDocument();
|
||||||
expect(container.querySelector('input[type="radio"]')).not.toBeInTheDocument();
|
expect(container.querySelector('input[type="radio"]')).not.toBeInTheDocument();
|
||||||
|
@ -523,7 +528,7 @@ describe("MPollBody", () => {
|
||||||
responseEvent("@qbert:example.com", "poutine", 16), // latest qbert
|
responseEvent("@qbert:example.com", "poutine", 16), // latest qbert
|
||||||
responseEvent("@qbert:example.com", "wings", 15),
|
responseEvent("@qbert:example.com", "wings", 15),
|
||||||
];
|
];
|
||||||
const ends = [endEvent("@me:example.com", 25)];
|
const ends = [newPollEndEvent("@me:example.com", 25)];
|
||||||
const renderResult = await newMPollBody(votes, ends);
|
const renderResult = await newMPollBody(votes, ends);
|
||||||
expect(endedVotesCount(renderResult, "pizza")).toBe("0 votes");
|
expect(endedVotesCount(renderResult, "pizza")).toBe("0 votes");
|
||||||
expect(endedVotesCount(renderResult, "poutine")).toBe("1 vote");
|
expect(endedVotesCount(renderResult, "poutine")).toBe("1 vote");
|
||||||
|
@ -534,7 +539,7 @@ describe("MPollBody", () => {
|
||||||
|
|
||||||
it("counts a single vote as normal if the poll is ended", async () => {
|
it("counts a single vote as normal if the poll is ended", async () => {
|
||||||
const votes = [responseEvent("@qbert:example.com", "poutine", 16)];
|
const votes = [responseEvent("@qbert:example.com", "poutine", 16)];
|
||||||
const ends = [endEvent("@me:example.com", 25)];
|
const ends = [newPollEndEvent("@me:example.com", 25)];
|
||||||
const renderResult = await newMPollBody(votes, ends);
|
const renderResult = await newMPollBody(votes, ends);
|
||||||
expect(endedVotesCount(renderResult, "pizza")).toBe("0 votes");
|
expect(endedVotesCount(renderResult, "pizza")).toBe("0 votes");
|
||||||
expect(endedVotesCount(renderResult, "poutine")).toBe("1 vote");
|
expect(endedVotesCount(renderResult, "poutine")).toBe("1 vote");
|
||||||
|
@ -551,7 +556,7 @@ describe("MPollBody", () => {
|
||||||
responseEvent("@fg:example.com", "pizza", 15),
|
responseEvent("@fg:example.com", "pizza", 15),
|
||||||
responseEvent("@hi:example.com", "pizza", 15),
|
responseEvent("@hi:example.com", "pizza", 15),
|
||||||
];
|
];
|
||||||
const ends = [endEvent("@me:example.com", 25)];
|
const ends = [newPollEndEvent("@me:example.com", 25)];
|
||||||
const renderResult = await newMPollBody(votes, ends);
|
const renderResult = await newMPollBody(votes, ends);
|
||||||
|
|
||||||
expect(renderResult.container.querySelectorAll(".mx_StyledRadioButton")).toHaveLength(0);
|
expect(renderResult.container.querySelectorAll(".mx_StyledRadioButton")).toHaveLength(0);
|
||||||
|
@ -573,7 +578,7 @@ describe("MPollBody", () => {
|
||||||
responseEvent("@wf:example.com", "pizza", 15),
|
responseEvent("@wf:example.com", "pizza", 15),
|
||||||
responseEvent("@ld:example.com", "pizza", 15),
|
responseEvent("@ld:example.com", "pizza", 15),
|
||||||
];
|
];
|
||||||
const ends = [endEvent("@me:example.com", 25)];
|
const ends = [newPollEndEvent("@me:example.com", 25)];
|
||||||
const renderResult = await newMPollBody(votes, ends);
|
const renderResult = await newMPollBody(votes, ends);
|
||||||
|
|
||||||
expect(endedVotesCount(renderResult, "pizza")).toBe("2 votes");
|
expect(endedVotesCount(renderResult, "pizza")).toBe("2 votes");
|
||||||
|
@ -594,8 +599,8 @@ describe("MPollBody", () => {
|
||||||
responseEvent("@ld:example.com", "pizza", 15),
|
responseEvent("@ld:example.com", "pizza", 15),
|
||||||
];
|
];
|
||||||
const ends = [
|
const ends = [
|
||||||
endEvent("@unauthorised:example.com", 5), // Should be ignored
|
newPollEndEvent("@unauthorised:example.com", 5), // Should be ignored
|
||||||
endEvent("@me:example.com", 25),
|
newPollEndEvent("@me:example.com", 25),
|
||||||
];
|
];
|
||||||
const renderResult = await newMPollBody(votes, ends);
|
const renderResult = await newMPollBody(votes, ends);
|
||||||
|
|
||||||
|
@ -620,9 +625,9 @@ describe("MPollBody", () => {
|
||||||
responseEvent("@ld:example.com", "pizza", 15),
|
responseEvent("@ld:example.com", "pizza", 15),
|
||||||
];
|
];
|
||||||
const ends = [
|
const ends = [
|
||||||
endEvent("@me:example.com", 65),
|
newPollEndEvent("@me:example.com", 65),
|
||||||
endEvent("@me:example.com", 25),
|
newPollEndEvent("@me:example.com", 25),
|
||||||
endEvent("@me:example.com", 75),
|
newPollEndEvent("@me:example.com", 75),
|
||||||
];
|
];
|
||||||
const renderResult = await newMPollBody(votes, ends);
|
const renderResult = await newMPollBody(votes, ends);
|
||||||
|
|
||||||
|
@ -640,7 +645,7 @@ describe("MPollBody", () => {
|
||||||
responseEvent("@qb:example.com", "wings", 14),
|
responseEvent("@qb:example.com", "wings", 14),
|
||||||
responseEvent("@xy:example.com", "wings", 15),
|
responseEvent("@xy:example.com", "wings", 15),
|
||||||
];
|
];
|
||||||
const ends = [endEvent("@me:example.com", 25)];
|
const ends = [newPollEndEvent("@me:example.com", 25)];
|
||||||
const renderResult = await newMPollBody(votes, ends);
|
const renderResult = await newMPollBody(votes, ends);
|
||||||
|
|
||||||
// Then the winner is highlighted
|
// Then the winner is highlighted
|
||||||
|
@ -658,7 +663,7 @@ describe("MPollBody", () => {
|
||||||
responseEvent("@xy:example.com", "wings", 15),
|
responseEvent("@xy:example.com", "wings", 15),
|
||||||
responseEvent("@fg:example.com", "poutine", 15),
|
responseEvent("@fg:example.com", "poutine", 15),
|
||||||
];
|
];
|
||||||
const ends = [endEvent("@me:example.com", 25)];
|
const ends = [newPollEndEvent("@me:example.com", 25)];
|
||||||
const renderResult = await newMPollBody(votes, ends);
|
const renderResult = await newMPollBody(votes, ends);
|
||||||
|
|
||||||
expect(endedVoteChecked(renderResult, "pizza")).toBe(true);
|
expect(endedVoteChecked(renderResult, "pizza")).toBe(true);
|
||||||
|
@ -669,7 +674,7 @@ describe("MPollBody", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("highlights nothing if poll has no votes", async () => {
|
it("highlights nothing if poll has no votes", async () => {
|
||||||
const ends = [endEvent("@me:example.com", 25)];
|
const ends = [newPollEndEvent("@me:example.com", 25)];
|
||||||
const renderResult = await newMPollBody([], ends);
|
const renderResult = await newMPollBody([], ends);
|
||||||
expect(renderResult.container.getElementsByClassName("mx_MPollBody_option_checked")).toHaveLength(0);
|
expect(renderResult.container.getElementsByClassName("mx_MPollBody_option_checked")).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
@ -681,7 +686,7 @@ describe("MPollBody", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("says poll is ended if there is an end event", async () => {
|
it("says poll is ended if there is an end event", async () => {
|
||||||
const ends = [endEvent("@me:example.com", 25)];
|
const ends = [newPollEndEvent("@me:example.com", 25)];
|
||||||
const result = await runIsPollEnded(ends);
|
const result = await runIsPollEnded(ends);
|
||||||
expect(result).toBe(true);
|
expect(result).toBe(true);
|
||||||
});
|
});
|
||||||
|
@ -693,9 +698,9 @@ describe("MPollBody", () => {
|
||||||
room_id: "#myroom:example.com",
|
room_id: "#myroom:example.com",
|
||||||
content: newPollStart([]),
|
content: newPollStart([]),
|
||||||
});
|
});
|
||||||
const ends = [endEvent("@me:example.com", 25)];
|
const ends = [newPollEndEvent("@me:example.com", 25)];
|
||||||
|
|
||||||
await setupRoomWithPollEvents(pollEvent, [], ends);
|
await setupRoomWithPollEvents(pollEvent, [], ends, mockClient);
|
||||||
const poll = mockClient.getRoom(pollEvent.getRoomId()!)!.polls.get(pollEvent.getId()!)!;
|
const poll = mockClient.getRoom(pollEvent.getRoomId()!)!.polls.get(pollEvent.getId()!)!;
|
||||||
// start fetching, dont await
|
// start fetching, dont await
|
||||||
poll.getResponses();
|
poll.getResponses();
|
||||||
|
@ -793,7 +798,7 @@ describe("MPollBody", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders a finished poll with no votes", async () => {
|
it("renders a finished poll with no votes", async () => {
|
||||||
const ends = [endEvent("@me:example.com", 25)];
|
const ends = [newPollEndEvent("@me:example.com", 25)];
|
||||||
const { container } = await newMPollBody([], ends);
|
const { container } = await newMPollBody([], ends);
|
||||||
expect(container).toMatchSnapshot();
|
expect(container).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
@ -806,7 +811,7 @@ describe("MPollBody", () => {
|
||||||
responseEvent("@yo:example.com", "wings", 15),
|
responseEvent("@yo:example.com", "wings", 15),
|
||||||
responseEvent("@qr:example.com", "italian", 16),
|
responseEvent("@qr:example.com", "italian", 16),
|
||||||
];
|
];
|
||||||
const ends = [endEvent("@me:example.com", 25)];
|
const ends = [newPollEndEvent("@me:example.com", 25)];
|
||||||
const { container } = await newMPollBody(votes, ends);
|
const { container } = await newMPollBody(votes, ends);
|
||||||
expect(container).toMatchSnapshot();
|
expect(container).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
@ -820,7 +825,7 @@ describe("MPollBody", () => {
|
||||||
responseEvent("@th:example.com", "poutine", 13),
|
responseEvent("@th:example.com", "poutine", 13),
|
||||||
responseEvent("@yh:example.com", "poutine", 14),
|
responseEvent("@yh:example.com", "poutine", 14),
|
||||||
];
|
];
|
||||||
const ends = [endEvent("@me:example.com", 25)];
|
const ends = [newPollEndEvent("@me:example.com", 25)];
|
||||||
const { container } = await newMPollBody(votes, ends);
|
const { container } = await newMPollBody(votes, ends);
|
||||||
expect(container).toMatchSnapshot();
|
expect(container).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
@ -848,7 +853,7 @@ describe("MPollBody", () => {
|
||||||
responseEvent("@th:example.com", "poutine", 13),
|
responseEvent("@th:example.com", "poutine", 13),
|
||||||
responseEvent("@yh:example.com", "poutine", 14),
|
responseEvent("@yh:example.com", "poutine", 14),
|
||||||
];
|
];
|
||||||
const ends = [endEvent("@me:example.com", 25)];
|
const ends = [newPollEndEvent("@me:example.com", 25)];
|
||||||
const { container } = await newMPollBody(votes, ends, undefined, false);
|
const { container } = await newMPollBody(votes, ends, undefined, false);
|
||||||
expect(container).toMatchSnapshot();
|
expect(container).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
@ -915,28 +920,11 @@ async function newMPollBodyFromEvent(
|
||||||
): Promise<RenderResult> {
|
): Promise<RenderResult> {
|
||||||
const props = getMPollBodyPropsFromEvent(mxEvent);
|
const props = getMPollBodyPropsFromEvent(mxEvent);
|
||||||
|
|
||||||
await setupRoomWithPollEvents(mxEvent, relationEvents, endEvents);
|
await setupRoomWithPollEvents(mxEvent, relationEvents, endEvents, mockClient);
|
||||||
|
|
||||||
return renderMPollBodyWithWrapper(props);
|
return renderMPollBodyWithWrapper(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setupRoomWithPollEvents(
|
|
||||||
mxEvent: MatrixEvent,
|
|
||||||
relationEvents: Array<MatrixEvent>,
|
|
||||||
endEvents: Array<MatrixEvent> = [],
|
|
||||||
): Promise<Room> {
|
|
||||||
const room = new Room(mxEvent.getRoomId()!, mockClient, userId);
|
|
||||||
room.processPollEvents([mxEvent, ...relationEvents, ...endEvents]);
|
|
||||||
setRedactionAllowedForMeOnly(room);
|
|
||||||
// wait for events to process on room
|
|
||||||
await flushPromises();
|
|
||||||
mockClient.getRoom.mockReturnValue(room);
|
|
||||||
mockClient.relations.mockResolvedValue({
|
|
||||||
events: [...relationEvents, ...endEvents],
|
|
||||||
});
|
|
||||||
return room;
|
|
||||||
}
|
|
||||||
|
|
||||||
function clickOption({ getByTestId }: RenderResult, value: string) {
|
function clickOption({ getByTestId }: RenderResult, value: string) {
|
||||||
fireEvent.click(getByTestId(`pollOption-${value}`));
|
fireEvent.click(getByTestId(`pollOption-${value}`));
|
||||||
}
|
}
|
||||||
|
@ -961,7 +949,7 @@ function endedVotesCount(renderResult: RenderResult, value: string): string {
|
||||||
return votesCount(renderResult, value);
|
return votesCount(renderResult, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function newPollStart(answers?: PollAnswer[], question?: string, disclosed = true): PollStartEventContent {
|
export function newPollStart(answers?: PollAnswer[], question?: string, disclosed = true): PollStartEventContent {
|
||||||
if (!answers) {
|
if (!answers) {
|
||||||
answers = [
|
answers = [
|
||||||
{ id: "pizza", [M_TEXT.name]: "Pizza" },
|
{ id: "pizza", [M_TEXT.name]: "Pizza" },
|
||||||
|
@ -1036,22 +1024,8 @@ function expectedResponseEventCall(answer: string) {
|
||||||
return [roomId, eventType, content];
|
return [roomId, eventType, content];
|
||||||
}
|
}
|
||||||
|
|
||||||
function endEvent(sender = "@me:example.com", ts = 0): MatrixEvent {
|
export function newPollEndEvent(sender = "@me:example.com", ts = 0): MatrixEvent {
|
||||||
return new MatrixEvent({
|
return makePollEndEvent("$mypoll", "#myroom:example.com", sender, ts);
|
||||||
event_id: nextId(),
|
|
||||||
room_id: "#myroom:example.com",
|
|
||||||
origin_server_ts: ts,
|
|
||||||
type: M_POLL_END.name,
|
|
||||||
sender: sender,
|
|
||||||
content: {
|
|
||||||
"m.relates_to": {
|
|
||||||
rel_type: "m.reference",
|
|
||||||
event_id: "$mypoll",
|
|
||||||
},
|
|
||||||
[M_POLL_END.name]: {},
|
|
||||||
[M_TEXT.name]: "The poll has ended. Something.",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function runIsPollEnded(ends: MatrixEvent[]) {
|
async function runIsPollEnded(ends: MatrixEvent[]) {
|
||||||
|
@ -1062,7 +1036,7 @@ async function runIsPollEnded(ends: MatrixEvent[]) {
|
||||||
content: newPollStart(),
|
content: newPollStart(),
|
||||||
});
|
});
|
||||||
|
|
||||||
await setupRoomWithPollEvents(pollEvent, [], ends);
|
await setupRoomWithPollEvents(pollEvent, [], ends, mockClient);
|
||||||
|
|
||||||
return isPollEnded(pollEvent, mockClient);
|
return isPollEnded(pollEvent, mockClient);
|
||||||
}
|
}
|
||||||
|
@ -1078,12 +1052,6 @@ function runFindTopAnswer(votes: MatrixEvent[]) {
|
||||||
return findTopAnswer(pollEvent, newVoteRelations(votes));
|
return findTopAnswer(pollEvent, newVoteRelations(votes));
|
||||||
}
|
}
|
||||||
|
|
||||||
function setRedactionAllowedForMeOnly(room: Room) {
|
|
||||||
jest.spyOn(room.currentState, "maySendRedactionForEvent").mockImplementation((_evt: MatrixEvent, id: string) => {
|
|
||||||
return id === userId;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let EVENT_ID = 0;
|
let EVENT_ID = 0;
|
||||||
function nextId(): string {
|
function nextId(): string {
|
||||||
EVENT_ID++;
|
EVENT_ID++;
|
||||||
|
|
203
test/components/views/messages/MPollEndBody-test.tsx
Normal file
203
test/components/views/messages/MPollEndBody-test.tsx
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { render } from "@testing-library/react";
|
||||||
|
import { EventTimeline, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||||
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
import { M_TEXT } from "matrix-js-sdk/src/@types/extensible_events";
|
||||||
|
|
||||||
|
import { IBodyProps } from "../../../../src/components/views/messages/IBodyProps";
|
||||||
|
import { MPollEndBody } from "../../../../src/components/views/messages/MPollEndBody";
|
||||||
|
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
|
||||||
|
import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks";
|
||||||
|
import { MediaEventHelper } from "../../../../src/utils/MediaEventHelper";
|
||||||
|
import {
|
||||||
|
flushPromises,
|
||||||
|
getMockClientWithEventEmitter,
|
||||||
|
makePollEndEvent,
|
||||||
|
makePollStartEvent,
|
||||||
|
mockClientMethodsEvents,
|
||||||
|
mockClientMethodsUser,
|
||||||
|
setupRoomWithPollEvents,
|
||||||
|
} from "../../../test-utils";
|
||||||
|
|
||||||
|
describe("<MPollEndBody />", () => {
|
||||||
|
const userId = "@alice:domain.org";
|
||||||
|
const roomId = "!room:domain.org";
|
||||||
|
const mockClient = getMockClientWithEventEmitter({
|
||||||
|
...mockClientMethodsUser(userId),
|
||||||
|
...mockClientMethodsEvents(),
|
||||||
|
getRoom: jest.fn(),
|
||||||
|
relations: jest.fn(),
|
||||||
|
fetchRoomEvent: jest.fn(),
|
||||||
|
});
|
||||||
|
const pollStartEvent = makePollStartEvent("Question?", userId, undefined, { roomId });
|
||||||
|
const pollEndEvent = makePollEndEvent(pollStartEvent.getId()!, roomId, userId, 123);
|
||||||
|
|
||||||
|
const setupRoomWithEventsTimeline = async (pollEnd: MatrixEvent, pollStart?: MatrixEvent): Promise<Room> => {
|
||||||
|
if (pollStart) {
|
||||||
|
await setupRoomWithPollEvents(pollStart, [], [pollEnd], mockClient);
|
||||||
|
}
|
||||||
|
const room = mockClient.getRoom(roomId) || new Room(roomId, mockClient, userId);
|
||||||
|
|
||||||
|
// end events validate against this
|
||||||
|
jest.spyOn(room.currentState, "maySendRedactionForEvent").mockImplementation(
|
||||||
|
(_evt: MatrixEvent, id: string) => {
|
||||||
|
return id === mockClient.getSafeUserId();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const timelineSet = room.getUnfilteredTimelineSet();
|
||||||
|
const getTimelineForEventSpy = jest.spyOn(timelineSet, "getTimelineForEvent");
|
||||||
|
// if we have a pollStart, mock the room timeline to include it
|
||||||
|
if (pollStart) {
|
||||||
|
const eventTimeline = {
|
||||||
|
getEvents: jest.fn().mockReturnValue([pollEnd, pollStart]),
|
||||||
|
} as unknown as EventTimeline;
|
||||||
|
getTimelineForEventSpy.mockReturnValue(eventTimeline);
|
||||||
|
}
|
||||||
|
mockClient.getRoom.mockReturnValue(room);
|
||||||
|
|
||||||
|
return room;
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
|
mxEvent: pollEndEvent,
|
||||||
|
highlightLink: "unused",
|
||||||
|
mediaEventHelper: {} as unknown as MediaEventHelper,
|
||||||
|
onHeightChanged: () => {},
|
||||||
|
onMessageAllowed: () => {},
|
||||||
|
permalinkCreator: {} as unknown as RoomPermalinkCreator,
|
||||||
|
ref: undefined as any,
|
||||||
|
};
|
||||||
|
|
||||||
|
const getComponent = (props: Partial<IBodyProps> = {}) =>
|
||||||
|
render(<MPollEndBody {...defaultProps} {...props} />, {
|
||||||
|
wrapper: ({ children }) => (
|
||||||
|
<MatrixClientContext.Provider value={mockClient}>{children}</MatrixClientContext.Provider>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockClient.getRoom.mockReset();
|
||||||
|
mockClient.relations.mockResolvedValue({
|
||||||
|
events: [],
|
||||||
|
});
|
||||||
|
mockClient.fetchRoomEvent.mockResolvedValue(pollStartEvent.toJSON());
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.spyOn(logger, "error").mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when poll start event exists in current timeline", () => {
|
||||||
|
it("renders an ended poll", async () => {
|
||||||
|
await setupRoomWithEventsTimeline(pollEndEvent, pollStartEvent);
|
||||||
|
const { container } = getComponent();
|
||||||
|
|
||||||
|
// ended poll rendered
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
|
||||||
|
// didnt try to fetch start event while it was already in timeline
|
||||||
|
expect(mockClient.fetchRoomEvent).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not render a poll tile when end event is invalid", async () => {
|
||||||
|
// sender of end event does not match start event
|
||||||
|
const invalidEndEvent = makePollEndEvent(pollStartEvent.getId()!, roomId, "@mallory:domain.org", 123);
|
||||||
|
await setupRoomWithEventsTimeline(invalidEndEvent, pollStartEvent);
|
||||||
|
const { getByText } = getComponent({ mxEvent: invalidEndEvent });
|
||||||
|
|
||||||
|
// no poll tile rendered
|
||||||
|
expect(getByText("The poll has ended. Something.")).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when poll start event does not exist in current timeline", () => {
|
||||||
|
it("fetches the related poll start event and displays a poll tile", async () => {
|
||||||
|
await setupRoomWithEventsTimeline(pollEndEvent);
|
||||||
|
const { container, getByTestId } = getComponent();
|
||||||
|
|
||||||
|
// while fetching event, only icon is shown
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
|
||||||
|
// flush the fetch event promise
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(mockClient.fetchRoomEvent).toHaveBeenCalledWith(roomId, pollStartEvent.getId());
|
||||||
|
|
||||||
|
// quick check for poll tile
|
||||||
|
expect(getByTestId("pollQuestion").innerHTML).toEqual("Question?");
|
||||||
|
expect(getByTestId("totalVotes").innerHTML).toEqual("Final result based on 0 votes");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not render a poll tile when end event is invalid", async () => {
|
||||||
|
// sender of end event does not match start event
|
||||||
|
const invalidEndEvent = makePollEndEvent(pollStartEvent.getId()!, roomId, "@mallory:domain.org", 123);
|
||||||
|
await setupRoomWithEventsTimeline(invalidEndEvent);
|
||||||
|
const { getByText } = getComponent({ mxEvent: invalidEndEvent });
|
||||||
|
|
||||||
|
// flush the fetch event promise
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
// no poll tile rendered
|
||||||
|
expect(getByText("The poll has ended. Something.")).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("logs an error and displays the text fallback when fetching the start event fails", async () => {
|
||||||
|
await setupRoomWithEventsTimeline(pollEndEvent);
|
||||||
|
mockClient.fetchRoomEvent.mockRejectedValue({ code: 404 });
|
||||||
|
const logSpy = jest.spyOn(logger, "error").mockImplementation(() => {});
|
||||||
|
const { getByText } = getComponent();
|
||||||
|
|
||||||
|
// flush the fetch event promise
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
// poll end event fallback text used
|
||||||
|
expect(getByText("The poll has ended. Something.")).toBeTruthy();
|
||||||
|
expect(logSpy).toHaveBeenCalledWith("Failed to fetch related poll start event", { code: 404 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("logs an error and displays the extensible event text when fetching the start event fails", async () => {
|
||||||
|
await setupRoomWithEventsTimeline(pollEndEvent);
|
||||||
|
mockClient.fetchRoomEvent.mockRejectedValue({ code: 404 });
|
||||||
|
const logSpy = jest.spyOn(logger, "error").mockImplementation(() => {});
|
||||||
|
const { getByText } = getComponent();
|
||||||
|
|
||||||
|
// flush the fetch event promise
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
// poll end event fallback text used
|
||||||
|
expect(getByText("The poll has ended. Something.")).toBeTruthy();
|
||||||
|
expect(logSpy).toHaveBeenCalledWith("Failed to fetch related poll start event", { code: 404 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("displays fallback text when the poll end event does not have text", async () => {
|
||||||
|
const endWithoutText = makePollEndEvent(pollStartEvent.getId()!, roomId, userId, 123);
|
||||||
|
delete endWithoutText.getContent()[M_TEXT.name];
|
||||||
|
await setupRoomWithEventsTimeline(endWithoutText);
|
||||||
|
mockClient.fetchRoomEvent.mockRejectedValue({ code: 404 });
|
||||||
|
const { getByText } = getComponent({ mxEvent: endWithoutText });
|
||||||
|
|
||||||
|
// flush the fetch event promise
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
// default fallback text used
|
||||||
|
expect(getByText("@alice:domain.org has ended a poll")).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,108 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`<MPollEndBody /> when poll start event does not exist in current timeline fetches the related poll start event and displays a poll tile 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="mx_MPollEndBody_icon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<MPollEndBody /> when poll start event exists in current timeline renders an ended poll 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="mx_MPollBody"
|
||||||
|
>
|
||||||
|
<h2
|
||||||
|
data-testid="pollQuestion"
|
||||||
|
>
|
||||||
|
Question?
|
||||||
|
</h2>
|
||||||
|
<div
|
||||||
|
class="mx_MPollBody_allOptions"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_MPollBody_option mx_MPollBody_option_ended"
|
||||||
|
data-testid="pollOption-socks"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_MPollBody_endedOption"
|
||||||
|
data-value="socks"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_MPollBody_optionDescription"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_MPollBody_optionText"
|
||||||
|
>
|
||||||
|
Socks
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_MPollBody_optionVoteCount"
|
||||||
|
>
|
||||||
|
0 votes
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_MPollBody_popularityBackground"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_MPollBody_popularityAmount"
|
||||||
|
style="width: 0%;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_MPollBody_option mx_MPollBody_option_ended"
|
||||||
|
data-testid="pollOption-shoes"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_MPollBody_endedOption"
|
||||||
|
data-value="shoes"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_MPollBody_optionDescription"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_MPollBody_optionText"
|
||||||
|
>
|
||||||
|
Shoes
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_MPollBody_optionVoteCount"
|
||||||
|
>
|
||||||
|
0 votes
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_MPollBody_popularityBackground"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_MPollBody_popularityAmount"
|
||||||
|
style="width: 0%;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_MPollBody_totalVotes"
|
||||||
|
data-testid="totalVotes"
|
||||||
|
>
|
||||||
|
Final result based on 0 votes
|
||||||
|
<div
|
||||||
|
class="mx_Spinner"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="Loading..."
|
||||||
|
class="mx_Spinner_icon"
|
||||||
|
data-testid="spinner"
|
||||||
|
role="progressbar"
|
||||||
|
style="width: 16px; height: 16px;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
|
@ -14,16 +14,25 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
import { Mocked } from "jest-mock";
|
||||||
import { M_POLL_START, PollAnswer, M_POLL_KIND_DISCLOSED } from "matrix-js-sdk/src/@types/polls";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
|
import { MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||||
|
import { M_POLL_START, PollAnswer, M_POLL_KIND_DISCLOSED, M_POLL_END } from "matrix-js-sdk/src/@types/polls";
|
||||||
import { M_TEXT } from "matrix-js-sdk/src/@types/extensible_events";
|
import { M_TEXT } from "matrix-js-sdk/src/@types/extensible_events";
|
||||||
|
import { uuid4 } from "@sentry/utils";
|
||||||
|
|
||||||
|
import { flushPromises } from "./utilities";
|
||||||
|
|
||||||
|
type Options = {
|
||||||
|
roomId: string;
|
||||||
|
ts: number;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
export const makePollStartEvent = (
|
export const makePollStartEvent = (
|
||||||
question: string,
|
question: string,
|
||||||
sender: string,
|
sender: string,
|
||||||
answers?: PollAnswer[],
|
answers?: PollAnswer[],
|
||||||
ts?: number,
|
{ roomId, ts, id }: Partial<Options> = {},
|
||||||
id?: string,
|
|
||||||
): MatrixEvent => {
|
): MatrixEvent => {
|
||||||
if (!answers) {
|
if (!answers) {
|
||||||
answers = [
|
answers = [
|
||||||
|
@ -34,7 +43,7 @@ export const makePollStartEvent = (
|
||||||
|
|
||||||
return new MatrixEvent({
|
return new MatrixEvent({
|
||||||
event_id: id || "$mypoll",
|
event_id: id || "$mypoll",
|
||||||
room_id: "#myroom:example.com",
|
room_id: roomId || "#myroom:example.com",
|
||||||
sender: sender,
|
sender: sender,
|
||||||
type: M_POLL_START.name,
|
type: M_POLL_START.name,
|
||||||
content: {
|
content: {
|
||||||
|
@ -50,3 +59,55 @@ export const makePollStartEvent = (
|
||||||
origin_server_ts: ts || 0,
|
origin_server_ts: ts || 0,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const makePollEndEvent = (pollStartEventId: string, roomId: string, sender: string, ts = 0): MatrixEvent => {
|
||||||
|
return new MatrixEvent({
|
||||||
|
event_id: uuid4(),
|
||||||
|
room_id: roomId,
|
||||||
|
origin_server_ts: ts,
|
||||||
|
type: M_POLL_END.name,
|
||||||
|
sender: sender,
|
||||||
|
content: {
|
||||||
|
"m.relates_to": {
|
||||||
|
rel_type: "m.reference",
|
||||||
|
event_id: pollStartEventId,
|
||||||
|
},
|
||||||
|
[M_POLL_END.name]: {},
|
||||||
|
[M_TEXT.name]: "The poll has ended. Something.",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a room with attached poll events
|
||||||
|
* Returns room from mockClient
|
||||||
|
* mocks relations api
|
||||||
|
* @param mxEvent - poll start event
|
||||||
|
* @param relationEvents - returned by relations api
|
||||||
|
* @param endEvents - returned by relations api
|
||||||
|
* @param mockClient - client in use
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const setupRoomWithPollEvents = async (
|
||||||
|
mxEvent: MatrixEvent,
|
||||||
|
relationEvents: Array<MatrixEvent>,
|
||||||
|
endEvents: Array<MatrixEvent> = [],
|
||||||
|
mockClient: Mocked<MatrixClient>,
|
||||||
|
): Promise<Room> => {
|
||||||
|
const room = new Room(mxEvent.getRoomId()!, mockClient, mockClient.getSafeUserId());
|
||||||
|
room.processPollEvents([mxEvent, ...relationEvents, ...endEvents]);
|
||||||
|
|
||||||
|
// set redaction allowed for current user only
|
||||||
|
// poll end events are validated against this
|
||||||
|
jest.spyOn(room.currentState, "maySendRedactionForEvent").mockImplementation((_evt: MatrixEvent, id: string) => {
|
||||||
|
return id === mockClient.getSafeUserId();
|
||||||
|
});
|
||||||
|
|
||||||
|
// wait for events to process on room
|
||||||
|
await flushPromises();
|
||||||
|
mockClient.getRoom.mockReturnValue(room);
|
||||||
|
mockClient.relations.mockResolvedValue({
|
||||||
|
events: [...relationEvents, ...endEvents],
|
||||||
|
});
|
||||||
|
return room;
|
||||||
|
};
|
||||||
|
|
174
yarn.lock
174
yarn.lock
|
@ -1766,105 +1766,105 @@
|
||||||
tslib "^2.4.1"
|
tslib "^2.4.1"
|
||||||
webcrypto-core "^1.7.4"
|
webcrypto-core "^1.7.4"
|
||||||
|
|
||||||
"@percy/cli-app@1.17.0":
|
"@percy/cli-app@1.18.0":
|
||||||
version "1.17.0"
|
version "1.18.0"
|
||||||
resolved "https://registry.yarnpkg.com/@percy/cli-app/-/cli-app-1.17.0.tgz#1c53e83022e09b1f8fa0583edd1953fef18bc23c"
|
resolved "https://registry.yarnpkg.com/@percy/cli-app/-/cli-app-1.18.0.tgz#20c4033d4d8ed1fffebc8ef9aba3bf4da53695f8"
|
||||||
integrity sha512-8Vc/pfaKsubKXRaPNdB3Snd8h7NfGPNio5RtETgQpJHwE+hu4rbjN4WovkjntiUSsQwLGYpXmKqV9n+8Z4oskA==
|
integrity sha512-EAqD61ivuCwfl6PacXW9Wx9kTRvMPCBlQnxmrhx7jJG5tIp418p4XB3zkFOAirUa/LOdwNIVaPCHJyVAcJ1V5Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@percy/cli-command" "1.17.0"
|
"@percy/cli-command" "1.18.0"
|
||||||
"@percy/cli-exec" "1.17.0"
|
"@percy/cli-exec" "1.18.0"
|
||||||
|
|
||||||
"@percy/cli-build@1.17.0":
|
"@percy/cli-build@1.18.0":
|
||||||
version "1.17.0"
|
version "1.18.0"
|
||||||
resolved "https://registry.yarnpkg.com/@percy/cli-build/-/cli-build-1.17.0.tgz#d0eba560778d9a98c1c9950f11fc44baae84f5fd"
|
resolved "https://registry.yarnpkg.com/@percy/cli-build/-/cli-build-1.18.0.tgz#797524e857574add7f5c08192b4f5668b1540cac"
|
||||||
integrity sha512-ES5Cwwa4n7/czOObqhG/o2Lr2l4VZFG2DEH/WGarCgsG42yiKqNWUHjFZj2AYfvv24f0b1KpL/Hz7gBauagGZA==
|
integrity sha512-FuYWjXx4Wy0v27GpwxGX5qDq4xcLzlStbABNCp4H5RrNrLa1jkcViOKXTSurdjHYWlHHFWJUbk39D4g3ZRXOAA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@percy/cli-command" "1.17.0"
|
"@percy/cli-command" "1.18.0"
|
||||||
|
|
||||||
"@percy/cli-command@1.17.0":
|
"@percy/cli-command@1.18.0":
|
||||||
version "1.17.0"
|
version "1.18.0"
|
||||||
resolved "https://registry.yarnpkg.com/@percy/cli-command/-/cli-command-1.17.0.tgz#4db677d47b8468e2c02ede818f27453387c8672d"
|
resolved "https://registry.yarnpkg.com/@percy/cli-command/-/cli-command-1.18.0.tgz#6e5af91c56cad09b5b7f26e3dc95d47ed4e203ef"
|
||||||
integrity sha512-KmYorWxT+M+Ei/UVZiVx1WVJ4g87D+gvHX5tC4KS8C+oVy31Z01rJ4YChM0OSS92COMX0hCOacOxrFCNGbMgow==
|
integrity sha512-2dHJalp83IygD/FqXCFNND22z7f4r5uZxqr5GAyiJ0STkQYitTgwkUp+4IRdfK1zmi+A2dbyYU0xV9txEW5v8g==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@percy/config" "1.17.0"
|
"@percy/config" "1.18.0"
|
||||||
"@percy/core" "1.17.0"
|
"@percy/core" "1.18.0"
|
||||||
"@percy/logger" "1.17.0"
|
"@percy/logger" "1.18.0"
|
||||||
|
|
||||||
"@percy/cli-config@1.17.0":
|
"@percy/cli-config@1.18.0":
|
||||||
version "1.17.0"
|
version "1.18.0"
|
||||||
resolved "https://registry.yarnpkg.com/@percy/cli-config/-/cli-config-1.17.0.tgz#e33a7f480f2d6c76b4c0033048de8a5095c0e095"
|
resolved "https://registry.yarnpkg.com/@percy/cli-config/-/cli-config-1.18.0.tgz#0eb28a5496634d278945c19f654b055c84053380"
|
||||||
integrity sha512-dpwCLyy9e1ubsZY3QaZj1A3KNHOQOjSuvNCZ01yM+VH5hSPsh9OD50Jhie/6W4BTMbn/THMo5KyHc76ahc0nYA==
|
integrity sha512-N5I7av5SGO5n0YC61wbqVniNxFwAQFFubKyGyGaVlQkmKyRuYCmDMHlPdSv3zyvVD1ZJCvtDzi/VqQGDubEieg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@percy/cli-command" "1.17.0"
|
"@percy/cli-command" "1.18.0"
|
||||||
|
|
||||||
"@percy/cli-exec@1.17.0":
|
"@percy/cli-exec@1.18.0":
|
||||||
version "1.17.0"
|
version "1.18.0"
|
||||||
resolved "https://registry.yarnpkg.com/@percy/cli-exec/-/cli-exec-1.17.0.tgz#10ea4dea0485308d013b26af1c963c7135826d12"
|
resolved "https://registry.yarnpkg.com/@percy/cli-exec/-/cli-exec-1.18.0.tgz#edf560aad4a2a100c577d2295f57cfe43790d683"
|
||||||
integrity sha512-W3Mb3t8dZOaEdvRX7uPStpiBEWFnUGQ2S+2X5nk4aGdikIO1jVwZrXRyFRs/NU6gkDocREJ2MFEwAQGsuCB3qg==
|
integrity sha512-4y+XIsYS5KypJmTtGKyKFU4GZ6902dCcEMJNDc69grSsco9N2lBG2gsvChCrCA2gDT8nUvKng9z9KF2OiMvF3w==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@percy/cli-command" "1.17.0"
|
"@percy/cli-command" "1.18.0"
|
||||||
cross-spawn "^7.0.3"
|
cross-spawn "^7.0.3"
|
||||||
which "^2.0.2"
|
which "^2.0.2"
|
||||||
|
|
||||||
"@percy/cli-snapshot@1.17.0":
|
"@percy/cli-snapshot@1.18.0":
|
||||||
version "1.17.0"
|
version "1.18.0"
|
||||||
resolved "https://registry.yarnpkg.com/@percy/cli-snapshot/-/cli-snapshot-1.17.0.tgz#8e3e84930650229b5fc023df4c543f37125592c3"
|
resolved "https://registry.yarnpkg.com/@percy/cli-snapshot/-/cli-snapshot-1.18.0.tgz#0ca97fe7cd89d68921fce1670d68bb3626008dd4"
|
||||||
integrity sha512-QYKSqlC3bMjaz9k1ClOUCq6Jwz4FPf1q+x9IlYQsUUOQ68Sx7qBy8edw1iS09BsKkESFHqFIBi7j7laWU/al3g==
|
integrity sha512-3a7HqJU/wCT7fKraYw6S4cs8KrlUZ0MbDV9/dEbFh9nSfz0BLQjcK+kL8OzjxM+bVwZ6pf62FJl44nVLpXqrGw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@percy/cli-command" "1.17.0"
|
"@percy/cli-command" "1.18.0"
|
||||||
yaml "^2.0.0"
|
yaml "^2.0.0"
|
||||||
|
|
||||||
"@percy/cli-upload@1.17.0":
|
"@percy/cli-upload@1.18.0":
|
||||||
version "1.17.0"
|
version "1.18.0"
|
||||||
resolved "https://registry.yarnpkg.com/@percy/cli-upload/-/cli-upload-1.17.0.tgz#1662047a4032f681f814935eac4435e8d20e705d"
|
resolved "https://registry.yarnpkg.com/@percy/cli-upload/-/cli-upload-1.18.0.tgz#2ad505e6c290d54317001cfa817badf8d69920e5"
|
||||||
integrity sha512-vhfi4AXptDLKG2UdE94sI1FZLh6kR1YvDh7UlbxvJmRl6+vkkxEOHkZZ6B575b3Mzm+20LBfw4gU7maajs/16Q==
|
integrity sha512-JSRoE0aTnBH1HGNGmDJxGNkNIGor7H8pv6lO8AUiwCwQ55EHgLqtWlhwq+2AHz7QOiGD8aOHQTwJk6GcCIIa3A==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@percy/cli-command" "1.17.0"
|
"@percy/cli-command" "1.18.0"
|
||||||
fast-glob "^3.2.11"
|
fast-glob "^3.2.11"
|
||||||
image-size "^1.0.0"
|
image-size "^1.0.0"
|
||||||
|
|
||||||
"@percy/cli@^1.11.0":
|
"@percy/cli@^1.11.0":
|
||||||
version "1.17.0"
|
version "1.18.0"
|
||||||
resolved "https://registry.yarnpkg.com/@percy/cli/-/cli-1.17.0.tgz#2048914800e5a2f62591d183225d12dc50c7a844"
|
resolved "https://registry.yarnpkg.com/@percy/cli/-/cli-1.18.0.tgz#9d734be256e75a600a4de67db4506f0f92656fe2"
|
||||||
integrity sha512-uY755vWT+bx3IVvzZKTbX7UJclPXStp85M9LSaWff1K8nk47c+patB9ydLSZKQu+Qkj1L/5L46plaResn8SfsA==
|
integrity sha512-yfvVh2uwTqMGxn2wF/RCvGVsyfXkeKOt05Cil4s8PRSBQ94iDY872lMJ3al0gSq0y4GLAH9CO7ZVt3uqD2tlBg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@percy/cli-app" "1.17.0"
|
"@percy/cli-app" "1.18.0"
|
||||||
"@percy/cli-build" "1.17.0"
|
"@percy/cli-build" "1.18.0"
|
||||||
"@percy/cli-command" "1.17.0"
|
"@percy/cli-command" "1.18.0"
|
||||||
"@percy/cli-config" "1.17.0"
|
"@percy/cli-config" "1.18.0"
|
||||||
"@percy/cli-exec" "1.17.0"
|
"@percy/cli-exec" "1.18.0"
|
||||||
"@percy/cli-snapshot" "1.17.0"
|
"@percy/cli-snapshot" "1.18.0"
|
||||||
"@percy/cli-upload" "1.17.0"
|
"@percy/cli-upload" "1.18.0"
|
||||||
"@percy/client" "1.17.0"
|
"@percy/client" "1.18.0"
|
||||||
"@percy/logger" "1.17.0"
|
"@percy/logger" "1.18.0"
|
||||||
|
|
||||||
"@percy/client@1.17.0":
|
"@percy/client@1.18.0":
|
||||||
version "1.17.0"
|
version "1.18.0"
|
||||||
resolved "https://registry.yarnpkg.com/@percy/client/-/client-1.17.0.tgz#5cf732e9895417670e683ec3dd930cad11c25e20"
|
resolved "https://registry.yarnpkg.com/@percy/client/-/client-1.18.0.tgz#03e593eab84c2ba7f4fb03770f4bc9f976970b0c"
|
||||||
integrity sha512-7ur4SN/FLf7wPDcJyOMquQT3oCRVZ9Up4Ux3zbRV8eaYgdmnkpYn1+F8uRqwwXLsVngWGqB0x+ckbQ2N0oXA4Q==
|
integrity sha512-onuVIpB6TPNjEhLlPsyhJYXTY2xdv3iNx4bj8Yfk751vQ33US2z4FEWDQKIEvHJGcFT7NEIHXFT3bYcFDmREdQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@percy/env" "1.17.0"
|
"@percy/env" "1.18.0"
|
||||||
"@percy/logger" "1.17.0"
|
"@percy/logger" "1.18.0"
|
||||||
|
|
||||||
"@percy/config@1.17.0":
|
"@percy/config@1.18.0":
|
||||||
version "1.17.0"
|
version "1.18.0"
|
||||||
resolved "https://registry.yarnpkg.com/@percy/config/-/config-1.17.0.tgz#d210e8b38265f3511cd5829142e8e90ec7c64e33"
|
resolved "https://registry.yarnpkg.com/@percy/config/-/config-1.18.0.tgz#d6a753c714938ee925a4ad799a83f3f33c9b1592"
|
||||||
integrity sha512-azAdqWHC3Neb5HC4sThvobuHsawiBBoRO6taBIrB6gO66mzhG76p1avChmsgZTeuAGIUE9gnF3GYW6FJiqnXAQ==
|
integrity sha512-bjAiZuhORij3vxeolVjpf7ZU1Sjqv2Y9CgsBthoIu20f5o0a10w6JGgPkDjT3NaAIZDXgbxVsrWovlslbRC57w==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@percy/logger" "1.17.0"
|
"@percy/logger" "1.18.0"
|
||||||
ajv "^8.6.2"
|
ajv "^8.6.2"
|
||||||
cosmiconfig "^7.0.0"
|
cosmiconfig "^7.0.0"
|
||||||
yaml "^2.0.0"
|
yaml "^2.0.0"
|
||||||
|
|
||||||
"@percy/core@1.17.0":
|
"@percy/core@1.18.0":
|
||||||
version "1.17.0"
|
version "1.18.0"
|
||||||
resolved "https://registry.yarnpkg.com/@percy/core/-/core-1.17.0.tgz#0b007b16be37f26e8f8f3829713633569550a19f"
|
resolved "https://registry.yarnpkg.com/@percy/core/-/core-1.18.0.tgz#e6493d45ea1db8272141dcda850ec71b4d014b77"
|
||||||
integrity sha512-kIzSxKNEjwl+Y+86XHZfEYBFyvPvuISwGzKhmL/03lJRggvmH+eThHvB+P8zX1v/9wHEKA/YwQU8uSggZMN2yA==
|
integrity sha512-9d8mkE6bfp0nRxhnGgC8N2KBG+MRiCxfvmZGfKENEoSY8NTTjJ5LqF+ol1JRwQ+uTABPZgy9XItWLINrS3yC1Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@percy/client" "1.17.0"
|
"@percy/client" "1.18.0"
|
||||||
"@percy/config" "1.17.0"
|
"@percy/config" "1.18.0"
|
||||||
"@percy/dom" "1.17.0"
|
"@percy/dom" "1.18.0"
|
||||||
"@percy/logger" "1.17.0"
|
"@percy/logger" "1.18.0"
|
||||||
content-disposition "^0.5.4"
|
content-disposition "^0.5.4"
|
||||||
cross-spawn "^7.0.3"
|
cross-spawn "^7.0.3"
|
||||||
extract-zip "^2.0.1"
|
extract-zip "^2.0.1"
|
||||||
|
@ -1882,20 +1882,20 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@percy/sdk-utils" "^1.3.1"
|
"@percy/sdk-utils" "^1.3.1"
|
||||||
|
|
||||||
"@percy/dom@1.17.0":
|
"@percy/dom@1.18.0":
|
||||||
version "1.17.0"
|
version "1.18.0"
|
||||||
resolved "https://registry.yarnpkg.com/@percy/dom/-/dom-1.17.0.tgz#9109d35625caba0b61dc30e900eb1212fcee3f16"
|
resolved "https://registry.yarnpkg.com/@percy/dom/-/dom-1.18.0.tgz#8698486134e6d94b98b132d39d3d25cd42597dba"
|
||||||
integrity sha512-C6qaqOk4p8ax/gPruQtVy+Ml7Yb6nWpKYThk4afy4N0Jq9hZa0qBzlVE1vO4jtxHK8JaZ2lXGG4Gdxz4FOQuMA==
|
integrity sha512-FoaUgmdCaymSVV/5UQsDwPlZYpSymViODiGJxEJnfESKB4L5aNWQTL3QefFOAI67Q9lUIezW7oueLPsH2XlCNg==
|
||||||
|
|
||||||
"@percy/env@1.17.0":
|
"@percy/env@1.18.0":
|
||||||
version "1.17.0"
|
version "1.18.0"
|
||||||
resolved "https://registry.yarnpkg.com/@percy/env/-/env-1.17.0.tgz#575b98702bf191a7922bf45c85ac59e8a502aa89"
|
resolved "https://registry.yarnpkg.com/@percy/env/-/env-1.18.0.tgz#831fcedee5d9d748f307a9b1465d5e28434c69fc"
|
||||||
integrity sha512-z6O7VauoKXDq66hS8HhVmR2poJZLdM0xVAJ7XBmfdalVnnCSzub1Fjubd12eN12Gt9fZpX8WCeOI8+0PjAAspQ==
|
integrity sha512-b+RTDPst4yKk67EQMjGeBIjfAkqZy2jUXgW3SKaNCyCOzI+16IXJ1gJSrniv29TpxgqDa1y3OUyWTPkmuDVi2A==
|
||||||
|
|
||||||
"@percy/logger@1.17.0":
|
"@percy/logger@1.18.0":
|
||||||
version "1.17.0"
|
version "1.18.0"
|
||||||
resolved "https://registry.yarnpkg.com/@percy/logger/-/logger-1.17.0.tgz#f945b40d45084f16c452bac89b4c5a8a44807723"
|
resolved "https://registry.yarnpkg.com/@percy/logger/-/logger-1.18.0.tgz#d8f692625eb1725b489e796775f13a7e91650056"
|
||||||
integrity sha512-UEzXcoxwDl8SfvJc4Gy0Ib8lYKDxM7WkgVsg/C5bniUyKuqpDnJdvqiV82vzOVI9nmlYikCG09ebZbNIjnXDWg==
|
integrity sha512-ZC9OqaTVPjnndcSfbQaU0NcquC0J4KZFx7hEDznukXNsLIK4WSLiEK1QS+tGxAkIKZilHmVc/vv9q3lMvlQDaQ==
|
||||||
|
|
||||||
"@percy/sdk-utils@^1.3.1":
|
"@percy/sdk-utils@^1.3.1":
|
||||||
version "1.16.0"
|
version "1.16.0"
|
||||||
|
@ -2235,9 +2235,9 @@
|
||||||
integrity sha512-jhMOZSS0UGYTS9pqvt6q3wtT3uvOSve5piTEmTMx3zzTuBLvSIMxSIBIc3d5lajVD5h4xc41AMZD2M5orN3PxA==
|
integrity sha512-jhMOZSS0UGYTS9pqvt6q3wtT3uvOSve5piTEmTMx3zzTuBLvSIMxSIBIc3d5lajVD5h4xc41AMZD2M5orN3PxA==
|
||||||
|
|
||||||
"@types/node@*":
|
"@types/node@*":
|
||||||
version "18.11.18"
|
version "18.13.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.18.tgz#8dfb97f0da23c2293e554c5a50d61ef134d7697f"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.13.0.tgz#0400d1e6ce87e9d3032c19eb6c58205b0d3f7850"
|
||||||
integrity sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==
|
integrity sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==
|
||||||
|
|
||||||
"@types/node@^14.14.31":
|
"@types/node@^14.14.31":
|
||||||
version "14.18.34"
|
version "14.18.34"
|
||||||
|
@ -7299,9 +7299,9 @@ punycode@1.3.2:
|
||||||
integrity sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==
|
integrity sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==
|
||||||
|
|
||||||
punycode@^2.1.0:
|
punycode@^2.1.0:
|
||||||
version "2.2.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.2.0.tgz#2092cc57cd2582c38e4e7e8bb869dc8d3148bc74"
|
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f"
|
||||||
integrity sha512-LN6QV1IJ9ZhxWTNdktaPClrNfp8xdSAYS0Zk2ddX7XsXZAxckMHPCBcHRo0cTcEIgYPRiGEkmji3Idkh2yFtYw==
|
integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==
|
||||||
|
|
||||||
punycode@^2.1.1:
|
punycode@^2.1.1:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue