Remove tooltip

This commit is contained in:
Jeremie Pardou-Piquemal 2022-12-18 18:42:54 +01:00
parent 218fbfbfd1
commit 8fe8abdcfc
10 changed files with 163 additions and 175 deletions

View File

@ -15,7 +15,6 @@
"dependencies": {
"bootstrap": "^4.6.2",
"classnames": "^2.3.2",
"clipboard": "^2.0.11",
"jquery": "3",
"moment": "^2.29.4",
"nanoid": "^4.0.0",
@ -29,6 +28,7 @@
"react-router": "^6.4.4",
"react-router-dom": "^6.4.4",
"react-simple-dropdown": "^3.2.3",
"react-tooltip": "^5.2.0",
"redux": "^4.2.0",
"redux-thunk": "^2.4.2",
"sanitize-html": "^2.7.3",

View File

@ -62,10 +62,7 @@ exports[`Connected Home component > should display 1`] = `
/>
<button
class="btn btn-plain btn-link clipboard-trigger room-id ellipsis"
data-clipboard-text="http://localhost:3000/"
data-placement="bottom"
data-toggle="tooltip"
title="Copied"
id="copy-room-url-button"
>
/
</button>
@ -74,9 +71,8 @@ exports[`Connected Home component > should display 1`] = `
>
<button
class="lock-room btn btn-link btn-plain"
data-placement="bottom"
data-toggle="tooltip"
title="You must be the owner to lock or unlock the room"
data-testid="lock-room-button"
id="lock-room-button"
>
<svg
class="muted"
@ -164,8 +160,6 @@ exports[`Connected Home component > should display 1`] = `
>
<span
class="me-icon-wrap"
data-placement="bottom"
data-toggle="tooltip"
title="Me"
>
<svg

View File

@ -1,7 +1,7 @@
import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react';
import mock$ from 'jquery';
import { test, expect, vi } from 'vitest';
import { act } from 'react-dom/test-utils';
import Nav from '.';
@ -35,7 +35,7 @@ vi.mock('nanoid', () => {
};
});
vi.useFakeTimers();
const mockClipboardWriteTest = vi.fn();
const mockTranslations = {
newRoomButton: 'new room',
@ -43,6 +43,8 @@ const mockTranslations = {
aboutButton: 'about',
};
vi.useFakeTimers();
test('Nav component is displaying', async () => {
const { asFragment } = render(
<Nav
@ -58,10 +60,6 @@ test('Nav component is displaying', async () => {
);
expect(asFragment()).toMatchSnapshot();
expect(mock$).toHaveBeenCalledWith('.room-id');
expect(mock$).toHaveBeenLastCalledWith('.lock-room');
expect(mockTooltip).toHaveBeenLastCalledWith({ trigger: 'manual' });
});
test('Nav component is displaying with another configuration and can rerender', async () => {
@ -103,11 +101,11 @@ test('Nav component is displaying with another configuration and can rerender',
});
test('Can copy room url', async () => {
document.execCommand = vi.fn(() => true);
navigator.clipboard = { writeText: mockClipboardWriteTest };
const toggleLockRoom = vi.fn();
const { getByText } = render(
const { getByText, queryByText } = render(
<Nav
members={[
{ id: 'id1', username: 'alan', isOwner: true },
@ -119,27 +117,28 @@ test('Can copy room url', async () => {
toggleLockRoom={toggleLockRoom}
openModal={() => {}}
iAmOwner={false}
translations={{}}
translations={{ copyButtonTooltip: 'Copied' }}
/>,
);
fireEvent.click(getByText(`/testRoom`));
await act(async () => {
await fireEvent.click(getByText('/testRoom'));
});
expect(document.execCommand).toHaveBeenLastCalledWith('copy');
expect(mock$).toHaveBeenCalledTimes(12);
expect(mockTooltip).toHaveBeenLastCalledWith('show');
expect(mockClipboardWriteTest).toHaveBeenLastCalledWith('http://localhost:3000/testRoom');
await getByText('Copied');
// Wait tooltip closing
vi.runAllTimers();
await act(() => vi.runAllTimers());
expect(mock$).toHaveBeenCalledTimes(15);
expect(mockTooltip).toHaveBeenLastCalledWith('hide');
expect(queryByText('Copied')).not.toBeInTheDocument();
});
test('Can lock/unlock room is room owner only', async () => {
const toggleLockRoom = vi.fn();
const { rerender, getByTitle } = render(
const { rerender, getByTestId, getByText, queryByText } = render(
<Nav
members={[
{ id: 'id1', username: 'alan', isOwner: true },
@ -155,13 +154,13 @@ test('Can lock/unlock room is room owner only', async () => {
/>,
);
const toggleLockRoomButton = getByTitle('You must be the owner to lock or unlock the room');
const toggleLockRoomButton = getByTestId('lock-room-button');
fireEvent.click(toggleLockRoomButton);
await fireEvent.click(toggleLockRoomButton);
expect(toggleLockRoom).toHaveBeenCalledWith();
fireEvent.click(toggleLockRoomButton);
await fireEvent.click(toggleLockRoomButton);
expect(toggleLockRoom).toHaveBeenCalledTimes(2);
@ -182,11 +181,16 @@ test('Can lock/unlock room is room owner only', async () => {
/>,
);
fireEvent.click(toggleLockRoomButton);
await fireEvent.click(toggleLockRoomButton);
expect(toggleLockRoom).toHaveBeenCalledTimes(2);
expect(mock$).toHaveBeenLastCalledWith('.lock-room');
expect(mockTooltip).toHaveBeenLastCalledWith('show');
await getByText('You must be the owner to lock or unlock the room');
// Wait tooltip closing
await act(() => vi.runAllTimers());
expect(queryByText('You must be the owner to lock or unlock the room')).not.toBeInTheDocument();
});
test('Can show user list', async () => {
@ -226,8 +230,10 @@ test('Can show user list', async () => {
translations={{}}
/>,
);
await waitFor(() => expect(getByText('alan')).toBeInTheDocument());
await waitFor(() => expect(getByText('dan')).toBeInTheDocument());
expect(queryByTitle('Owner')).not.toBeInTheDocument();
expect(queryByTitle('Me')).not.toBeInTheDocument();
});

View File

@ -15,9 +15,7 @@ exports[`Nav component is displaying 1`] = `
/>
<button
class="btn btn-plain btn-link clipboard-trigger room-id ellipsis"
data-clipboard-text="http://localhost:3000/testRoom"
data-placement="bottom"
data-toggle="tooltip"
id="copy-room-url-button"
>
/testRoom
</button>
@ -26,9 +24,8 @@ exports[`Nav component is displaying 1`] = `
>
<button
class="lock-room btn btn-link btn-plain"
data-placement="bottom"
data-toggle="tooltip"
title="You must be the owner to lock or unlock the room"
data-testid="lock-room-button"
id="lock-room-button"
>
<svg
class="muted"
@ -257,9 +254,7 @@ exports[`Nav component is displaying with another configuration and can rerender
/>
<button
class="btn btn-plain btn-link clipboard-trigger room-id ellipsis"
data-clipboard-text="http://localhost:3000/testRoom_2"
data-placement="bottom"
data-toggle="tooltip"
id="copy-room-url-button"
>
/testRoom_2
</button>
@ -268,9 +263,8 @@ exports[`Nav component is displaying with another configuration and can rerender
>
<button
class="lock-room btn btn-link btn-plain"
data-placement="bottom"
data-toggle="tooltip"
title="You must be the owner to lock or unlock the room"
data-testid="lock-room-button"
id="lock-room-button"
>
<svg
fill="none"
@ -357,9 +351,6 @@ exports[`Nav component is displaying with another configuration and can rerender
>
<span
class="owner-icon-wrap"
data-placement="bottom"
data-toggle="tooltip"
title="Owner"
>
<svg
class="owner-icon"
@ -369,6 +360,7 @@ exports[`Nav component is displaying with another configuration and can rerender
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
title="Owner"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
@ -545,9 +537,7 @@ exports[`Nav component is displaying with another configuration and can rerender
/>
<button
class="btn btn-plain btn-link clipboard-trigger room-id ellipsis"
data-clipboard-text="http://localhost:3000/testRoom_3"
data-placement="bottom"
data-toggle="tooltip"
id="copy-room-url-button"
>
/testRoom_3
</button>
@ -556,9 +546,8 @@ exports[`Nav component is displaying with another configuration and can rerender
>
<button
class="lock-room btn btn-link btn-plain"
data-placement="bottom"
data-toggle="tooltip"
title="You must be the owner to lock or unlock the room"
data-testid="lock-room-button"
id="lock-room-button"
>
<svg
fill="none"
@ -645,9 +634,6 @@ exports[`Nav component is displaying with another configuration and can rerender
>
<span
class="owner-icon-wrap"
data-placement="bottom"
data-toggle="tooltip"
title="Owner"
>
<svg
class="owner-icon"
@ -657,6 +643,7 @@ exports[`Nav component is displaying with another configuration and can rerender
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
title="Owner"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"

View File

@ -3,31 +3,23 @@ import PropTypes from 'prop-types';
import { nanoid } from 'nanoid';
import { Info, Settings, PlusCircle, User, Users, Lock, Unlock, Star } from 'react-feather';
import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown';
import Clipboard from 'clipboard';
import $ from 'jquery';
import { Tooltip } from 'react-tooltip';
import logoImg from '@/img/logo.png';
import Username from '@/components/Username';
const Nav = ({ members, roomId, userId, roomLocked, toggleLockRoom, openModal, iAmOwner, translations }) => {
const [showCopyTooltip, setShowCopyTooltip] = React.useState(false);
const [showLockedTooltip, setShowLockedTooltip] = React.useState(false);
const mountedRef = React.useRef(true);
const roomUrl = `${window.location.origin}/${roomId}`;
React.useEffect(() => {
const clip = new Clipboard('.clipboard-trigger');
clip.on('success', () => {
$('.room-id').tooltip('show');
setTimeout(() => {
$('.room-id').tooltip('hide');
}, 3000);
});
$(() => {
$('.room-id').tooltip({
trigger: 'manual',
});
$('.lock-room').tooltip({
trigger: 'manual',
});
});
mountedRef.current = true;
return () => {
mountedRef.current = false;
};
}, []);
const newRoom = () => {
@ -47,39 +39,67 @@ const Nav = ({ members, roomId, userId, roomLocked, toggleLockRoom, openModal, i
const handleToggleLock = () => {
if (!iAmOwner) {
$('.lock-room').tooltip('show');
setTimeout(() => $('.lock-room').tooltip('hide'), 3000);
setShowLockedTooltip(true);
setTimeout(() => {
if (mountedRef.current) {
setShowLockedTooltip(false);
}
}, 2000);
return;
}
toggleLockRoom();
};
const handleCopy = async () => {
await navigator.clipboard.writeText(roomUrl);
setShowCopyTooltip(true);
setTimeout(() => {
if (mountedRef.current) {
setShowCopyTooltip(false);
}
}, 2000);
};
return (
<nav className="navbar navbar-expand-md navbar-dark">
<div className="meta">
<img src={logoImg} alt="Darkwire" className="logo" />
<button
data-toggle="tooltip"
data-placement="bottom"
title={translations.copyButtonTooltip}
data-clipboard-text={`${window.location.origin}/${roomId}`}
id="copy-room-url-button"
className="btn btn-plain btn-link clipboard-trigger room-id ellipsis"
onClick={handleCopy}
>
{`/${roomId}`}
</button>
{showCopyTooltip && (
<Tooltip
anchorId="copy-room-url-button"
content={translations.copyButtonTooltip}
place="bottom"
events={[]}
isOpen={true}
/>
)}
<span className="lock-room-container">
<button
id="lock-room-button"
data-testid="lock-room-button"
onClick={handleToggleLock}
className="lock-room btn btn-link btn-plain"
data-toggle="tooltip"
data-placement="bottom"
title="You must be the owner to lock or unlock the room"
>
{roomLocked && <Lock />}
{!roomLocked && <Unlock className="muted" />}
</button>
{showLockedTooltip && (
<Tooltip
anchorId="lock-room-button"
content="You must be the owner to lock or unlock the room"
place="bottom"
events={[]}
isOpen={true}
/>
)}
</span>
<Dropdown className="members-dropdown">
@ -96,13 +116,13 @@ const Nav = ({ members, roomId, userId, roomLocked, toggleLockRoom, openModal, i
<Username username={member.username} />
<span className="icon-container">
{member.id === userId && (
<span data-toggle="tooltip" data-placement="bottom" title="Me" className="me-icon-wrap">
<span className="me-icon-wrap" title="Me">
<User className="me-icon" />
</span>
)}
{member.isOwner && (
<span data-toggle="tooltip" data-placement="bottom" title="Owner" className="owner-icon-wrap">
<Star className="owner-icon" />
<span className="owner-icon-wrap">
<Star className="owner-icon" title="Owner" />
</span>
)}
</span>

View File

@ -1,8 +1,7 @@
import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import RoomLink from '.';
import mock$ from 'jquery';
import { describe, it, expect, vi, afterEach } from 'vitest';
import { describe, it, expect, vi } from 'vitest';
import { act } from 'react-dom/test-utils';
const mockTooltip = vi.fn().mockImplementation(param => {});
@ -26,43 +25,31 @@ const mockTranslations = {
};
describe('RoomLink', () => {
afterEach(() => {
mock$.mockClear();
});
it('should display', async () => {
const { asFragment, unmount } = render(<RoomLink roomId="roomId" translations={mockTranslations} />);
const { asFragment } = render(<RoomLink roomId="roomId" translations={mockTranslations} />);
expect(asFragment()).toMatchSnapshot();
expect(mock$).toHaveBeenLastCalledWith('.copy-room');
expect(mockTooltip).toHaveBeenLastCalledWith({ trigger: 'manual' });
mock$.mockClear();
unmount();
expect(mock$).toHaveBeenLastCalledWith('.copy-room');
expect(mockTooltip).toHaveBeenLastCalledWith('hide');
});
it('should copy link', async () => {
// Mock execCommand for paste
document.execCommand = vi.fn(() => true);
const mockClipboardWriteTest = vi.fn();
navigator.clipboard = { writeText: mockClipboardWriteTest };
const { getByTitle } = render(<RoomLink roomId="roomId" translations={mockTranslations} />);
const { getByTestId, queryByText, getByText } = render(
<RoomLink roomId="roomId" translations={mockTranslations} />,
);
await fireEvent.click(getByTitle(mockTranslations.copyButtonTooltip));
await act(async () => {
await fireEvent.click(getByTestId('copy-room-button'));
});
expect(document.execCommand).toHaveBeenLastCalledWith('copy');
expect(mock$).toHaveBeenCalledTimes(4);
expect(mock$).toHaveBeenLastCalledWith('.copy-room');
expect(mockTooltip).toHaveBeenLastCalledWith('show');
expect(mockClipboardWriteTest).toHaveBeenLastCalledWith('http://localhost:3000/roomId');
// Wait for tooltip to close
vi.runAllTimers();
await getByText(mockTranslations.copyButtonTooltip);
expect(mock$).toHaveBeenCalledTimes(6);
expect(mock$).toHaveBeenLastCalledWith('.copy-room');
expect(mockTooltip).toHaveBeenLastCalledWith('hide');
// Wait tooltip closing
await act(() => vi.runAllTimers());
expect(queryByText('Copied')).not.toBeInTheDocument();
});
});

View File

@ -21,10 +21,8 @@ exports[`RoomLink > should display 1`] = `
>
<button
class="copy-room btn btn-secondary"
data-clipboard-text="http://localhost:3000/roomId"
data-placement="bottom"
data-toggle="tooltip"
title="copyButton"
data-testid="copy-room-button"
id="copy-room"
type="button"
>
<svg

View File

@ -1,33 +1,31 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Copy } from 'react-feather';
import Clipboard from 'clipboard';
import $ from 'jquery';
import { Tooltip } from 'react-tooltip';
const RoomLink = ({ roomId, translations }) => {
const [showTooltip, setShowTooltip] = React.useState(false);
const mountedRef = React.useRef(true);
const roomUrl = `${window.location.origin}/${roomId}`;
React.useEffect(() => {
const clip = new Clipboard('.copy-room');
clip.on('success', () => {
$('.copy-room').tooltip('show');
setTimeout(() => {
$('.copy-room').tooltip('hide');
}, 3000);
});
$(() => {
$('.copy-room').tooltip({
trigger: 'manual',
});
});
mountedRef.current = true;
return () => {
if ($('.copy-room').tooltip) $('.copy-room').tooltip('hide');
mountedRef.current = false;
};
}, []);
const handleClick = async () => {
await navigator.clipboard.writeText(roomUrl);
setShowTooltip(true);
setTimeout(() => {
if (mountedRef.current) {
setShowTooltip(false);
}
}, 2000);
};
return (
<form>
<div className="form-group">
@ -35,16 +33,24 @@ const RoomLink = ({ roomId, translations }) => {
<input id="room-url" className="form-control" type="text" readOnly value={roomUrl} />
<div className="input-group-append">
<button
id="copy-room"
data-testid="copy-room-button"
className="copy-room btn btn-secondary"
type="button"
data-toggle="tooltip"
data-placement="bottom"
data-clipboard-text={roomUrl}
title={translations.copyButtonTooltip}
onClick={handleClick}
>
<Copy />
</button>
</div>
{showTooltip && (
<Tooltip
anchorId="copy-room"
content={translations.copyButtonTooltip}
place="top"
events={[]}
isOpen={true}
/>
)}
</div>
</div>
</form>

View File

@ -8,6 +8,7 @@ import 'bootstrap/dist/css/bootstrap.min.css';
import 'react-simple-dropdown/styles/Dropdown.css';
import './stylesheets/app.sass';
import 'bootstrap/dist/js/bootstrap.bundle.min.js';
import 'react-tooltip/dist/react-tooltip.css';
import configureStore from '@/store/';
import Home from '@/components/Home/';

View File

@ -345,6 +345,18 @@
minimatch "^3.1.2"
strip-json-comments "^3.1.1"
"@floating-ui/core@^1.0.4":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.0.4.tgz#03066eaea8e9b2a2cd3f5aaa60f1e0f580ebe88e"
integrity sha512-FPFLbg2b06MIw1dqk2SOEMAMX3xlrreGjcui5OTxfBDtaKTmh0kioOVjT8gcfl58juawL/yF+S+gnq8aUYQx/Q==
"@floating-ui/dom@^1.0.4":
version "1.0.12"
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.0.12.tgz#07c870a05d9b825a6d7657524f48fe6761722800"
integrity sha512-HeG/wHoa2laUHlDX3xkzqlUqliAfa+zqV04LaKIwNCmCNaW2p0fQi4/Kd0LB4GdFoJ2UllLFq5gWnXAd67lg7w==
dependencies:
"@floating-ui/core" "^1.0.4"
"@humanwhocodes/config-array@^0.11.6":
version "0.11.7"
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.7.tgz#38aec044c6c828f6ed51d5d7ae3d9b9faf6dbb0f"
@ -1083,15 +1095,6 @@ classnames@^2.1.2, classnames@^2.3.2:
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924"
integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==
clipboard@^2.0.11:
version "2.0.11"
resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.11.tgz#62180360b97dd668b6b3a84ec226975762a70be5"
integrity sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==
dependencies:
good-listener "^1.2.2"
select "^1.1.2"
tiny-emitter "^2.0.0"
color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
@ -1248,11 +1251,6 @@ delayed-stream@~1.0.0:
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
delegate@^3.1.2:
version "3.2.0"
resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166"
integrity sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==
diff-sequences@^29.3.1:
version "29.3.1"
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.3.1.tgz#104b5b95fe725932421a9c6e5b4bef84c3f2249e"
@ -1846,13 +1844,6 @@ globby@^11.1.0:
merge2 "^1.4.1"
slash "^3.0.0"
good-listener@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50"
integrity sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==
dependencies:
delegate "^3.1.2"
gopd@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
@ -2962,6 +2953,14 @@ react-simple-dropdown@^3.2.3:
classnames "^2.1.2"
prop-types "^15.5.8"
react-tooltip@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-5.2.0.tgz#e10e7de2385e8fe6bf3438739c574558b455de3b"
integrity sha512-EH6XIg2MDbMTEElSAZQVXMVeFoOhTgQuea2or0iwyzsr9v8rJf3ImMhOtq7Xe/BPlougxC+PmOibazodLdaRoA==
dependencies:
"@floating-ui/dom" "^1.0.4"
classnames "^2.3.2"
react@^18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
@ -3118,11 +3117,6 @@ scheduler@^0.23.0:
dependencies:
loose-envify "^1.1.0"
select@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d"
integrity sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==
semver@^6.0.0, semver@^6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
@ -3299,11 +3293,6 @@ text-table@^0.2.0:
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
tiny-emitter@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423"
integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==
tinybench@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.3.1.tgz#14f64e6b77d7ef0b1f6ab850c7a808c6760b414d"