Files
element-call-Github/src/useReactions.test.tsx
Will Hunt 5d88c52e30 Support for generic reactions (#2708)
* Initial support for Hand Raise feature

Signed-off-by: Milton Moura <miltonmoura@gmail.com>

* Refactored to use reaction and redaction events

Signed-off-by: Milton Moura <miltonmoura@gmail.com>

* Replacing button svg with raised hand emoji

Signed-off-by: Milton Moura <miltonmoura@gmail.com>

* SpotlightTile should not duplicate the raised hand

Signed-off-by: Milton Moura <miltonmoura@gmail.com>

* Update src/room/useRaisedHands.tsx

Element Call recently changed to AGPL-3.0

* Use relations to load existing reactions when joining the call

Signed-off-by: Milton Moura <miltonmoura@gmail.com>

* Links to sha commit of matrix-js-sdk that exposes the call membership event id and refactors some async code

Signed-off-by: Milton Moura <miltonmoura@gmail.com>

* Removing RaiseHand.svg

* Check for reaction & redaction capabilities in widget mode

Signed-off-by: Milton Moura <miltonmoura@gmail.com>

* Fix failing GridTile test

Signed-off-by: Milton Moura <miltonmoura@gmail.com>

* Center align hand raise.

* Add support for displaying the duration of a raised hand.

* Add a sound for when a hand is raised.

* Refactor raised hand indicator and add tests.

* lint

* Refactor into own files.

* Redact the right thing.

* Tidy up useEffect

* Lint tests

* Remove extra layer

* Add better sound. (woosh)

* Add a small mode for spotlight

* Fix timestamp calculation on relaod.

* Fix call border resizing video

* lint

* Fix and update tests

* Allow timer to be configurable.

* Add preferences tab for choosing to enable timer.

* Drop border from raised hand icon

* Handle cases when a new member event happens.

* Prevent infinite loop

* Major refactor to support various state problems.

* Tidy up and finish test rewrites

* Add some explanation comments.

* Even more comments.

* Use proper duration formatter

* Remove rerender

* Fix redactions not working because they pick up events in transit.

* More tidying

* Use deferred value

* linting

* Add tests for cases where we got a reaction from someone else.

* Be even less brittle.

* Transpose border to GridTile.

* First PoC for reactions

* hide menu by default

* Add lightbulb.

* Add reaction indicator.

* Add sounds.

* Tidy up + add support for floating emoji.

* Linting and general stability improvements.

* Subscribe to the ecall reaction event type.

* fix import

* Center emoji picker

* Overflow buttons when screen is too narrow

* lint

* Add settings for disabling animations / sounds.

* Make vertical divider more visually distinct.

* Make event listener more resillient.

* lint

* Fix some tests.

* Remove old raised hand component

* Add new icon

* Update text

* Update compound hand raised icon.

* Add deer.

* Fix case where you could send larger strings as emoji

* Const the active time.

* Document time in css.

* Add rock emoji

* Add licence file.

* Add type def for custom reaction type.

* better reaction description

* Factor out reactions test structure to utils file.

* Add tests for ReactionToggleButton

* Add keyboard shortcuts for reaction sending.

* type tidyups

* lint

* Add tests for ReactionAudioRenderer

* lint

* prettier

* i18n sort

* final lint?

* Preload reaction sounds to prevent delays.

* Update rock sounds

* add onclick back

* Fix test

* lint

* simplify

* Tweak line height

* modal impl

* Modal refactor attempts.

* Remove closed menu test since we're using Modal.

* Swap icon, make mobile view better.

* Fix mobile view for emoji picker.

* Use Intl.Segmenter

* Clear timeouts on component close.

* Remove useless useCallback

* Use prefers-reduced-motion

* Add toggle for raise hand.

* Add lower hand text

* Add lower motion mode.

* Decomplicate className system for Modal

* Add error for failured to send reaction.

* i18n

* Spacing for emoji buttons search

* Remove unrequired media query

* Fix generic sound not playing.

* Clear reactions if we're clearing timeouts.

* Fix tests

* Relabel lower hand

* More translations

* Add comments on reaction interface

* Move polyfill.

* lint

* Replace deer sound

* Another attempt to fix the sizing of the reactions

* cleanup

* fix button

* fix

---------

Signed-off-by: Milton Moura <miltonmoura@gmail.com>
Co-authored-by: Milton Moura <miltonmoura@gmail.com>
Co-authored-by: fkwp <fkwp@users.noreply.github.com>
2024-11-08 17:36:40 +00:00

174 lines
5.9 KiB
TypeScript

/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import { render } from "@testing-library/react";
import { act, FC } from "react";
import { describe, expect, test } from "vitest";
import { RoomEvent } from "matrix-js-sdk/src/matrix";
import { useReactions } from "./useReactions";
import {
createHandRaisedReaction,
createRedaction,
MockRoom,
MockRTCSession,
TestReactionsWrapper,
} from "./utils/testReactions";
const memberUserIdAlice = "@alice:example.org";
const memberEventAlice = "$membership-alice:example.org";
const memberUserIdBob = "@bob:example.org";
const memberEventBob = "$membership-bob:example.org";
const membership: Record<string, string> = {
[memberEventAlice]: memberUserIdAlice,
[memberEventBob]: memberUserIdBob,
"$membership-charlie:example.org": "@charlie:example.org",
};
/**
* Test explanation.
* This test suite checks that the useReactions hook appropriately reacts
* to new reactions, redactions and membership changesin the room. There is
* a large amount of test structure used to construct a mock environment.
*/
const TestComponent: FC = () => {
const { raisedHands } = useReactions();
return (
<div>
<ul>
{Object.entries(raisedHands).map(([userId, date]) => (
<li key={userId}>
<span>{userId}</span>
<time>{date.getTime()}</time>
</li>
))}
</ul>
</div>
);
};
describe("useReactions", () => {
test("starts with an empty list", () => {
const rtcSession = new MockRTCSession(
new MockRoom(memberUserIdAlice),
membership,
);
const { queryByRole } = render(
<TestReactionsWrapper rtcSession={rtcSession}>
<TestComponent />
</TestReactionsWrapper>,
);
expect(queryByRole("list")?.children).to.have.lengthOf(0);
});
test("handles incoming raised hand", async () => {
const room = new MockRoom(memberUserIdAlice);
const rtcSession = new MockRTCSession(room, membership);
const { queryByRole } = render(
<TestReactionsWrapper rtcSession={rtcSession}>
<TestComponent />
</TestReactionsWrapper>,
);
await act(() => room.testSendHandRaise(memberEventAlice, membership));
expect(queryByRole("list")?.children).to.have.lengthOf(1);
await act(() => room.testSendHandRaise(memberEventBob, membership));
expect(queryByRole("list")?.children).to.have.lengthOf(2);
});
test("handles incoming unraised hand", async () => {
const room = new MockRoom(memberUserIdAlice);
const rtcSession = new MockRTCSession(room, membership);
const { queryByRole } = render(
<TestReactionsWrapper rtcSession={rtcSession}>
<TestComponent />
</TestReactionsWrapper>,
);
const reactionEventId = await act(() =>
room.testSendHandRaise(memberEventAlice, membership),
);
expect(queryByRole("list")?.children).to.have.lengthOf(1);
await act(() =>
room.emit(
RoomEvent.Redaction,
createRedaction(memberUserIdAlice, reactionEventId),
room,
undefined,
),
);
expect(queryByRole("list")?.children).to.have.lengthOf(0);
});
test("handles loading prior raised hand events", () => {
const room = new MockRoom(memberUserIdAlice, [
createHandRaisedReaction(memberEventAlice, membership),
]);
const rtcSession = new MockRTCSession(room, membership);
const { queryByRole } = render(
<TestReactionsWrapper rtcSession={rtcSession}>
<TestComponent />
</TestReactionsWrapper>,
);
expect(queryByRole("list")?.children).to.have.lengthOf(1);
});
// If the membership event changes for a user, we want to remove
// the raised hand event.
test("will remove reaction when a member leaves the call", () => {
const room = new MockRoom(memberUserIdAlice, [
createHandRaisedReaction(memberEventAlice, membership),
]);
const rtcSession = new MockRTCSession(room, membership);
const { queryByRole } = render(
<TestReactionsWrapper rtcSession={rtcSession}>
<TestComponent />
</TestReactionsWrapper>,
);
expect(queryByRole("list")?.children).to.have.lengthOf(1);
act(() => rtcSession.testRemoveMember(memberUserIdAlice));
expect(queryByRole("list")?.children).to.have.lengthOf(0);
});
test("will remove reaction when a member joins via a new event", () => {
const room = new MockRoom(memberUserIdAlice, [
createHandRaisedReaction(memberEventAlice, membership),
]);
const rtcSession = new MockRTCSession(room, membership);
const { queryByRole } = render(
<TestReactionsWrapper rtcSession={rtcSession}>
<TestComponent />
</TestReactionsWrapper>,
);
expect(queryByRole("list")?.children).to.have.lengthOf(1);
// Simulate leaving and rejoining
act(() => {
rtcSession.testRemoveMember(memberUserIdAlice);
rtcSession.testAddMember(memberUserIdAlice);
});
expect(queryByRole("list")?.children).to.have.lengthOf(0);
});
test("ignores invalid sender for historic event", () => {
const room = new MockRoom(memberUserIdAlice, [
createHandRaisedReaction(memberEventAlice, memberUserIdBob),
]);
const rtcSession = new MockRTCSession(room, membership);
const { queryByRole } = render(
<TestReactionsWrapper rtcSession={rtcSession}>
<TestComponent />
</TestReactionsWrapper>,
);
expect(queryByRole("list")?.children).to.have.lengthOf(0);
});
test("ignores invalid sender for new event", async () => {
const room = new MockRoom(memberUserIdAlice);
const rtcSession = new MockRTCSession(room, membership);
const { queryByRole } = render(
<TestReactionsWrapper rtcSession={rtcSession}>
<TestComponent />
</TestReactionsWrapper>,
);
await act(() => room.testSendHandRaise(memberEventAlice, memberUserIdBob));
expect(queryByRole("list")?.children).to.have.lengthOf(0);
});
});