Merge branch 'develop' into johannes/find-myself

This commit is contained in:
Johannes Marbach 2023-02-10 08:26:16 +01:00
commit 6ee6accfc6
16 changed files with 392 additions and 774 deletions

View file

@ -152,7 +152,7 @@
"@types/escape-html": "^1.0.1",
"@types/file-saver": "^2.0.3",
"@types/flux": "^3.1.9",
"@types/fs-extra": "^9.0.13",
"@types/fs-extra": "^11.0.0",
"@types/geojson": "^7946.0.8",
"@types/jest": "^29.2.1",
"@types/katex": "^0.14.0",

View file

@ -146,7 +146,7 @@ export default class PasswordReset {
err.message = _t("Failed to verify email address: make sure you clicked the link in the email");
} else if (err.httpStatus === 404) {
err.message = _t(
"Your email address does not appear to be associated with a Matrix ID on this Homeserver.",
"Your email address does not appear to be associated with a Matrix ID on this homeserver.",
);
} else if (err.httpStatus) {
err.message += ` (Status ${err.httpStatus})`;

View file

@ -63,7 +63,7 @@ const SpaceContextMenu: React.FC<IProps> = ({ space, hideHeader, onFinished, ...
inviteOption = (
<IconizedContextMenuOption
data-test-id="invite-option"
data-testid="invite-option"
className="mx_SpacePanel_contextMenu_inviteButton"
iconClassName="mx_SpacePanel_iconInvite"
label={_t("Invite")}
@ -85,7 +85,7 @@ const SpaceContextMenu: React.FC<IProps> = ({ space, hideHeader, onFinished, ...
settingsOption = (
<IconizedContextMenuOption
data-test-id="settings-option"
data-testid="settings-option"
iconClassName="mx_SpacePanel_iconSettings"
label={_t("Settings")}
onClick={onSettingsClick}
@ -102,7 +102,7 @@ const SpaceContextMenu: React.FC<IProps> = ({ space, hideHeader, onFinished, ...
leaveOption = (
<IconizedContextMenuOption
data-test-id="leave-option"
data-testid="leave-option"
iconClassName="mx_SpacePanel_iconLeave"
className="mx_IconizedContextMenu_option_red"
label={_t("Leave space")}
@ -172,12 +172,12 @@ const SpaceContextMenu: React.FC<IProps> = ({ space, hideHeader, onFinished, ...
newRoomSection = (
<>
<div data-test-id="add-to-space-header" className="mx_SpacePanel_contextMenu_separatorLabel">
<div data-testid="add-to-space-header" className="mx_SpacePanel_contextMenu_separatorLabel">
{_t("Add")}
</div>
{canAddRooms && (
<IconizedContextMenuOption
data-test-id="new-room-option"
data-testid="new-room-option"
iconClassName="mx_SpacePanel_iconPlus"
label={_t("Room")}
onClick={onNewRoomClick}
@ -185,7 +185,7 @@ const SpaceContextMenu: React.FC<IProps> = ({ space, hideHeader, onFinished, ...
)}
{canAddVideoRooms && (
<IconizedContextMenuOption
data-test-id="new-video-room-option"
data-testid="new-video-room-option"
iconClassName="mx_SpacePanel_iconPlus"
label={_t("Video room")}
onClick={onNewVideoRoomClick}
@ -195,7 +195,7 @@ const SpaceContextMenu: React.FC<IProps> = ({ space, hideHeader, onFinished, ...
)}
{canAddSubSpaces && (
<IconizedContextMenuOption
data-test-id="new-subspace-option"
data-testid="new-subspace-option"
iconClassName="mx_SpacePanel_iconPlus"
label={_t("Space")}
onClick={onNewSubspaceClick}

View file

@ -191,16 +191,19 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
public replaceEmoticon(caretPosition: DocumentPosition, regex: RegExp): number {
const { model } = this.props;
const range = model.startRange(caretPosition);
// expand range max 8 characters backwards from caretPosition,
// expand range max 9 characters backwards from caretPosition,
// as a space to look for an emoticon
let n = 8;
let n = 9;
range.expandBackwardsWhile((index, offset) => {
const part = model.parts[index];
n -= 1;
return n >= 0 && [Type.Plain, Type.PillCandidate, Type.Newline].includes(part.type);
});
const emoticonMatch = regex.exec(range.text);
if (emoticonMatch) {
// ignore matches at start of proper substrings
// so xd will not match if the string was "mixd 123456"
// and we are lookinh at xd 123456 part of the string
if (emoticonMatch && (n >= 0 || emoticonMatch.index !== 0)) {
const query = emoticonMatch[1].replace("-", "");
// try both exact match and lower-case, this means that xd won't match xD but :P will match :p
const data = EMOTICON_TO_EMOJI.get(query) || EMOTICON_TO_EMOJI.get(query.toLowerCase());

View file

@ -73,7 +73,7 @@ const securityCardContent: Record<
title: _t("Unverified session"),
description: (
<>
<p>{_t(`This session doesn't support encryption, so it can't be verified.`)}</p>
<p>{_t(`This session doesn't support encryption and thus can't be verified.`)}</p>
<p>
{_t(
`You won't be able to participate in rooms where encryption is enabled when using this session.`,

View file

@ -117,7 +117,7 @@
"%(brand)s was not given permission to send notifications - please try again": "%(brand)s was not given permission to send notifications - please try again",
"Unable to enable Notifications": "Unable to enable Notifications",
"This email address was not found": "This email address was not found",
"Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Your email address does not appear to be associated with a Matrix ID on this Homeserver.",
"Your email address does not appear to be associated with a Matrix ID on this homeserver.": "Your email address does not appear to be associated with a Matrix ID on this homeserver.",
"United Kingdom": "United Kingdom",
"United States": "United States",
"Afghanistan": "Afghanistan",
@ -1826,7 +1826,7 @@
"Unverified sessions are sessions that have logged in with your credentials but have not been cross-verified.": "Unverified sessions are sessions that have logged in with your credentials but have not been cross-verified.",
"You should make especially certain that you recognise these sessions as they could represent an unauthorised use of your account.": "You should make especially certain that you recognise these sessions as they could represent an unauthorised use of your account.",
"Unverified session": "Unverified session",
"This session doesn't support encryption, so it can't be verified.": "This session doesn't support encryption, so it can't be verified.",
"This session doesn't support encryption and thus can't be verified.": "This session doesn't support encryption and thus can't be verified.",
"You won't be able to participate in rooms where encryption is enabled when using this session.": "You won't be able to participate in rooms where encryption is enabled when using this session.",
"For best security and privacy, it is recommended to use Matrix clients that support encryption.": "For best security and privacy, it is recommended to use Matrix clients that support encryption.",
"Inactive sessions": "Inactive sessions",
@ -1842,7 +1842,6 @@
"Your current session is ready for secure messaging.": "Your current session is ready for secure messaging.",
"This session is ready for secure messaging.": "This session is ready for secure messaging.",
"Verified session": "Verified session",
"This session doesn't support encryption and thus can't be verified.": "This session doesn't support encryption and thus can't be verified.",
"Verify your current session for enhanced secure messaging.": "Verify your current session for enhanced secure messaging.",
"Verify or sign out from this session for best security and reliability.": "Verify or sign out from this session for best security and reliability.",
"Verify session": "Verify session",

View file

@ -62,12 +62,12 @@ interface IStickyRoom {
*/
export class Algorithm extends EventEmitter {
private _cachedRooms: ITagMap = {};
private _cachedStickyRooms: ITagMap = {}; // a clone of the _cachedRooms, with the sticky room
private _stickyRoom: IStickyRoom = null;
private _lastStickyRoom: IStickyRoom = null; // only not-null when changing the sticky room
private sortAlgorithms: ITagSortingMap;
private listAlgorithms: IListOrderingMap;
private algorithms: IOrderingAlgorithmMap;
private _cachedStickyRooms: ITagMap | null = {}; // a clone of the _cachedRooms, with the sticky room
private _stickyRoom: IStickyRoom | null = null;
private _lastStickyRoom: IStickyRoom | null = null; // only not-null when changing the sticky room
private sortAlgorithms: ITagSortingMap | null = null;
private listAlgorithms: IListOrderingMap | null = null;
private algorithms: IOrderingAlgorithmMap | null = null;
private rooms: Room[] = [];
private roomIdsToTags: {
[roomId: string]: TagID[];
@ -86,7 +86,7 @@ export class Algorithm extends EventEmitter {
CallStore.instance.off(CallStoreEvent.ActiveCalls, this.onActiveCalls);
}
public get stickyRoom(): Room {
public get stickyRoom(): Room | null {
return this._stickyRoom ? this._stickyRoom.room : null;
}
@ -124,7 +124,7 @@ export class Algorithm extends EventEmitter {
}
}
public getTagSorting(tagId: TagID): SortAlgorithm {
public getTagSorting(tagId: TagID): SortAlgorithm | null {
if (!this.sortAlgorithms) return null;
return this.sortAlgorithms[tagId];
}
@ -132,6 +132,8 @@ export class Algorithm extends EventEmitter {
public setTagSorting(tagId: TagID, sort: SortAlgorithm): void {
if (!tagId) throw new Error("Tag ID must be defined");
if (!sort) throw new Error("Algorithm must be defined");
if (!this.sortAlgorithms) throw new Error("this.sortAlgorithms must be defined before calling setTagSorting");
if (!this.algorithms) throw new Error("this.algorithms must be defined before calling setTagSorting");
this.sortAlgorithms[tagId] = sort;
const algorithm: OrderingAlgorithm = this.algorithms[tagId];
@ -141,7 +143,7 @@ export class Algorithm extends EventEmitter {
this.recalculateActiveCallRooms(tagId);
}
public getListOrdering(tagId: TagID): ListAlgorithm {
public getListOrdering(tagId: TagID): ListAlgorithm | null {
if (!this.listAlgorithms) return null;
return this.listAlgorithms[tagId];
}
@ -149,6 +151,9 @@ export class Algorithm extends EventEmitter {
public setListOrdering(tagId: TagID, order: ListAlgorithm): void {
if (!tagId) throw new Error("Tag ID must be defined");
if (!order) throw new Error("Algorithm must be defined");
if (!this.sortAlgorithms) throw new Error("this.sortAlgorithms must be defined before calling setListOrdering");
if (!this.listAlgorithms) throw new Error("this.listAlgorithms must be defined before calling setListOrdering");
if (!this.algorithms) throw new Error("this.algorithms must be defined before calling setListOrdering");
this.listAlgorithms[tagId] = order;
const algorithm = getListAlgorithmInstance(order, tagId, this.sortAlgorithms[tagId]);
@ -160,12 +165,12 @@ export class Algorithm extends EventEmitter {
this.recalculateActiveCallRooms(tagId);
}
private updateStickyRoom(val: Room): void {
private updateStickyRoom(val: Room | null): void {
this.doUpdateStickyRoom(val);
this._lastStickyRoom = null; // clear to indicate we're done changing
}
private doUpdateStickyRoom(val: Room): void {
private doUpdateStickyRoom(val: Room | null): void {
if (val?.isSpaceRoom() && val.getMyMembership() !== "invite") {
// no-op sticky rooms for spaces - they're effectively virtual rooms
val = null;
@ -237,6 +242,10 @@ export class Algorithm extends EventEmitter {
// Lie to the algorithm and remove the room from it's field of view
this.handleRoomUpdate(val, RoomUpdateCause.RoomRemoved);
// handleRoomUpdate may have modified this._stickyRoom. Convince the
// compiler of this fact.
this._stickyRoom = this.stickyRoomMightBeModified();
// Check for tag & position changes while we're here. We also check the room to ensure
// it is still the same room.
if (this._stickyRoom) {
@ -284,6 +293,13 @@ export class Algorithm extends EventEmitter {
this.emit(LIST_UPDATED_EVENT);
}
/**
* Hack to prevent Typescript claiming this._stickyRoom is always null.
*/
private stickyRoomMightBeModified(): IStickyRoom | null {
return this._stickyRoom;
}
private onActiveCalls = (): void => {
// In case we're unsticking a room, sort it back into natural order
this.recalculateStickyRoom();
@ -310,7 +326,7 @@ export class Algorithm extends EventEmitter {
* the call.
* @param updatedTag The tag that was updated, if possible.
*/
protected recalculateStickyRoom(updatedTag: TagID = null): void {
protected recalculateStickyRoom(updatedTag: TagID | null = null): void {
// 🐉 Here be dragons.
// This function does far too much for what it should, and is called by many places.
// Not only is this responsible for ensuring the sticky room is held in place at all
@ -336,14 +352,16 @@ export class Algorithm extends EventEmitter {
if (updatedTag) {
// Update the tag indicated by the caller, if possible. This is mostly to ensure
// our cache is up to date.
this._cachedStickyRooms[updatedTag] = [...this.cachedRooms[updatedTag]]; // shallow clone
if (this._cachedStickyRooms) {
this._cachedStickyRooms[updatedTag] = [...this.cachedRooms[updatedTag]]; // shallow clone
}
}
// Now try to insert the sticky room, if we need to.
// We need to if there's no updated tag (we regenned the whole cache) or if the tag
// we might have updated from the cache is also our sticky room.
const sticky = this._stickyRoom;
if (!updatedTag || updatedTag === sticky.tag) {
if (sticky && (!updatedTag || updatedTag === sticky.tag) && this._cachedStickyRooms) {
this._cachedStickyRooms[sticky.tag].splice(sticky.position, 0, sticky.room);
}
@ -362,7 +380,7 @@ export class Algorithm extends EventEmitter {
*
* @param updatedTag The tag that was updated, if possible.
*/
protected recalculateActiveCallRooms(updatedTag: TagID = null): void {
protected recalculateActiveCallRooms(updatedTag: TagID | null = null): void {
if (!updatedTag) {
// Assume all tags need updating
// We're not modifying the map here, so can safely rely on the cached values
@ -379,7 +397,7 @@ export class Algorithm extends EventEmitter {
if (CallStore.instance.activeCalls.size) {
// We operate on the sticky rooms map
if (!this._cachedStickyRooms) this.initCachedStickyRooms();
const rooms = this._cachedStickyRooms[updatedTag];
const rooms = this._cachedStickyRooms![updatedTag];
const activeRoomIds = new Set([...CallStore.instance.activeCalls].map((call) => call.roomId));
const activeRooms: Room[] = [];
@ -390,7 +408,7 @@ export class Algorithm extends EventEmitter {
}
// Stick rooms with active calls to the top
this._cachedStickyRooms[updatedTag] = [...activeRooms, ...inactiveRooms];
this._cachedStickyRooms![updatedTag] = [...activeRooms, ...inactiveRooms];
}
}
@ -638,7 +656,7 @@ export class Algorithm extends EventEmitter {
}
// Like above, update the reference to the sticky room if we need to
if (hasTags && isSticky) {
if (hasTags && isSticky && this._stickyRoom) {
// Go directly in and set the sticky room's new reference, being careful not
// to trigger a sticky room update ourselves.
this._stickyRoom.room = room;

View file

@ -113,10 +113,10 @@ describe("RoomNotifs test", () => {
event: true,
type: "m.room.create",
room: ROOM_ID,
user: client.getUserId()!,
user: "@zoe:localhost",
content: {
...(predecessorId ? { predecessor: { room_id: predecessorId, event_id: "$someevent" } } : {}),
creator: client.getUserId(),
creator: "@zoe:localhost",
room_version: "5",
},
ts: Date.now(),
@ -128,7 +128,7 @@ describe("RoomNotifs test", () => {
event: true,
type: EventType.RoomPredecessor,
room: ROOM_ID,
user: client.getUserId()!,
user: "@zoe:localhost",
skey: "",
content: {
predecessor_room_id: predecessorId,

View file

@ -15,16 +15,14 @@ limitations under the License.
*/
import React from "react";
// eslint-disable-next-line deprecate/import
import { mount } from "enzyme";
import { Room } from "matrix-js-sdk/src/matrix";
import { mocked } from "jest-mock";
import { act } from "react-dom/test-utils";
import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
import { Mocked, mocked } from "jest-mock";
import "focus-visible"; // to fix context menus
import { prettyDOM, render, RenderResult, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import SpaceContextMenu from "../../../../src/components/views/context_menus/SpaceContextMenu";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
import { findByTestId } from "../../../test-utils";
import {
shouldShowSpaceSettings,
showCreateNewRoom,
@ -55,9 +53,11 @@ jest.mock("../../../../src/utils/leave-behaviour", () => ({
describe("<SpaceContextMenu />", () => {
const userId = "@test:server";
const mockClient = {
getUserId: jest.fn().mockReturnValue(userId),
};
} as unknown as Mocked<MatrixClient>;
const makeMockSpace = (props = {}) =>
({
name: "test space",
@ -70,17 +70,18 @@ describe("<SpaceContextMenu />", () => {
getMyMembership: jest.fn(),
...props,
} as unknown as Room);
const defaultProps = {
space: makeMockSpace(),
onFinished: jest.fn(),
};
const getComponent = (props = {}) =>
mount(<SpaceContextMenu {...defaultProps} {...props} />, {
wrappingComponent: MatrixClientContext.Provider,
wrappingComponentProps: {
value: mockClient,
},
});
const renderComponent = (props = {}): RenderResult =>
render(
<MatrixClientContext.Provider value={mockClient}>
<SpaceContextMenu {...defaultProps} {...props} />
</MatrixClientContext.Provider>,
);
beforeEach(() => {
jest.resetAllMocks();
@ -88,134 +89,135 @@ describe("<SpaceContextMenu />", () => {
});
it("renders menu correctly", () => {
const component = getComponent();
expect(component).toMatchSnapshot();
const { baseElement } = renderComponent();
expect(prettyDOM(baseElement)).toMatchSnapshot();
});
it("renders invite option when space is public", () => {
const space = makeMockSpace({
getJoinRule: jest.fn().mockReturnValue("public"),
});
const component = getComponent({ space });
expect(findByTestId(component, "invite-option").length).toBeTruthy();
renderComponent({ space });
expect(screen.getByTestId("invite-option")).toBeInTheDocument();
});
it("renders invite option when user is has invite rights for space", () => {
const space = makeMockSpace({
canInvite: jest.fn().mockReturnValue(true),
});
const component = getComponent({ space });
renderComponent({ space });
expect(space.canInvite).toHaveBeenCalledWith(userId);
expect(findByTestId(component, "invite-option").length).toBeTruthy();
expect(screen.getByTestId("invite-option")).toBeInTheDocument();
});
it("opens invite dialog when invite option is clicked", () => {
it("opens invite dialog when invite option is clicked", async () => {
const space = makeMockSpace({
getJoinRule: jest.fn().mockReturnValue("public"),
});
const onFinished = jest.fn();
const component = getComponent({ space, onFinished });
renderComponent({ space, onFinished });
act(() => {
findByTestId(component, "invite-option").at(0).simulate("click");
});
await userEvent.click(screen.getByTestId("invite-option"));
expect(showSpaceInvite).toHaveBeenCalledWith(space);
expect(onFinished).toHaveBeenCalled();
});
it("renders space settings option when user has rights", () => {
mocked(shouldShowSpaceSettings).mockReturnValue(true);
const component = getComponent();
renderComponent();
expect(shouldShowSpaceSettings).toHaveBeenCalledWith(defaultProps.space);
expect(findByTestId(component, "settings-option").length).toBeTruthy();
expect(screen.getByTestId("settings-option")).toBeInTheDocument();
});
it("opens space settings when space settings option is clicked", () => {
it("opens space settings when space settings option is clicked", async () => {
mocked(shouldShowSpaceSettings).mockReturnValue(true);
const onFinished = jest.fn();
const component = getComponent({ onFinished });
renderComponent({ onFinished });
act(() => {
findByTestId(component, "settings-option").at(0).simulate("click");
});
await userEvent.click(screen.getByTestId("settings-option"));
expect(showSpaceSettings).toHaveBeenCalledWith(defaultProps.space);
expect(onFinished).toHaveBeenCalled();
});
it("renders leave option when user does not have rights to see space settings", () => {
const component = getComponent();
expect(findByTestId(component, "leave-option").length).toBeTruthy();
renderComponent();
expect(screen.getByTestId("leave-option")).toBeInTheDocument();
});
it("leaves space when leave option is clicked", () => {
it("leaves space when leave option is clicked", async () => {
const onFinished = jest.fn();
const component = getComponent({ onFinished });
act(() => {
findByTestId(component, "leave-option").at(0).simulate("click");
});
renderComponent({ onFinished });
await userEvent.click(screen.getByTestId("leave-option"));
expect(leaveSpace).toHaveBeenCalledWith(defaultProps.space);
expect(onFinished).toHaveBeenCalled();
});
describe("add children section", () => {
const space = makeMockSpace();
beforeEach(() => {
// set space to allow adding children to space
mocked(space.currentState.maySendStateEvent).mockReturnValue(true);
mocked(shouldShowComponent).mockReturnValue(true);
});
it("does not render section when user does not have permission to add children", () => {
mocked(space.currentState.maySendStateEvent).mockReturnValue(false);
const component = getComponent({ space });
renderComponent({ space });
expect(findByTestId(component, "add-to-space-header").length).toBeFalsy();
expect(findByTestId(component, "new-room-option").length).toBeFalsy();
expect(findByTestId(component, "new-subspace-option").length).toBeFalsy();
expect(screen.queryByTestId("add-to-space-header")).not.toBeInTheDocument();
expect(screen.queryByTestId("new-room-option")).not.toBeInTheDocument();
expect(screen.queryByTestId("new-subspace-option")).not.toBeInTheDocument();
});
it("does not render section when UIComponent customisations disable room and space creation", () => {
mocked(shouldShowComponent).mockReturnValue(false);
const component = getComponent({ space });
renderComponent({ space });
expect(shouldShowComponent).toHaveBeenCalledWith(UIComponent.CreateRooms);
expect(shouldShowComponent).toHaveBeenCalledWith(UIComponent.CreateSpaces);
expect(findByTestId(component, "add-to-space-header").length).toBeFalsy();
expect(findByTestId(component, "new-room-option").length).toBeFalsy();
expect(findByTestId(component, "new-subspace-option").length).toBeFalsy();
expect(screen.queryByTestId("add-to-space-header")).not.toBeInTheDocument();
expect(screen.queryByTestId("new-room-option")).not.toBeInTheDocument();
expect(screen.queryByTestId("new-subspace-option")).not.toBeInTheDocument();
});
it("renders section with add room button when UIComponent customisation allows CreateRoom", () => {
// only allow CreateRoom
mocked(shouldShowComponent).mockImplementation((feature) => feature === UIComponent.CreateRooms);
const component = getComponent({ space });
renderComponent({ space });
expect(findByTestId(component, "add-to-space-header").length).toBeTruthy();
expect(findByTestId(component, "new-room-option").length).toBeTruthy();
expect(findByTestId(component, "new-subspace-option").length).toBeFalsy();
expect(screen.getByTestId("add-to-space-header")).toBeInTheDocument();
expect(screen.getByTestId("new-room-option")).toBeInTheDocument();
expect(screen.queryByTestId("new-subspace-option")).not.toBeInTheDocument();
});
it("renders section with add space button when UIComponent customisation allows CreateSpace", () => {
// only allow CreateSpaces
mocked(shouldShowComponent).mockImplementation((feature) => feature === UIComponent.CreateSpaces);
const component = getComponent({ space });
renderComponent({ space });
expect(findByTestId(component, "add-to-space-header").length).toBeTruthy();
expect(findByTestId(component, "new-room-option").length).toBeFalsy();
expect(findByTestId(component, "new-subspace-option").length).toBeTruthy();
expect(screen.getByTestId("add-to-space-header")).toBeInTheDocument();
expect(screen.queryByTestId("new-room-option")).not.toBeInTheDocument();
expect(screen.getByTestId("new-subspace-option")).toBeInTheDocument();
});
it("opens create room dialog on add room button click", () => {
it("opens create room dialog on add room button click", async () => {
const onFinished = jest.fn();
const component = getComponent({ space, onFinished });
renderComponent({ space, onFinished });
act(() => {
findByTestId(component, "new-room-option").at(0).simulate("click");
});
await userEvent.click(screen.getByTestId("new-room-option"));
expect(showCreateNewRoom).toHaveBeenCalledWith(space);
expect(onFinished).toHaveBeenCalled();
});
it("opens create space dialog on add space button click", () => {
const onFinished = jest.fn();
const component = getComponent({ space, onFinished });
act(() => {
findByTestId(component, "new-subspace-option").at(0).simulate("click");
});
it("opens create space dialog on add space button click", async () => {
const onFinished = jest.fn();
renderComponent({ space, onFinished });
await userEvent.click(screen.getByTestId("new-subspace-option"));
expect(showCreateNewSubspace).toHaveBeenCalledWith(space);
expect(onFinished).toHaveBeenCalled();
});

View file

@ -1,500 +1,98 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<SpaceContextMenu /> renders menu correctly 1`] = `
<SpaceContextMenu
onFinished={[MockFunction]}
space={
{
"canInvite": [MockFunction] {
"calls": [
[
"@test:server",
],
],
"results": [
{
"type": "return",
"value": undefined,
},
],
},
"client": {
"getUserId": [MockFunction] {
"calls": [
[],
],
"results": [
{
"type": "return",
"value": "@test:server",
},
],
},
},
"currentState": {
"maySendStateEvent": [MockFunction] {
"calls": [
[
"m.space.child",
"@test:server",
],
],
"results": [
{
"type": "return",
"value": undefined,
},
],
},
},
"getJoinRule": [MockFunction] {
"calls": [
[],
],
"results": [
{
"type": "return",
"value": undefined,
},
],
},
"getMyMembership": [MockFunction],
"name": "test space",
}
}
>
<IconizedContextMenu
className="mx_SpacePanel_contextMenu"
compact={true}
onFinished={[MockFunction]}
>
<ContextMenu
chevronFace="none"
hasBackground={true}
managed={true}
onFinished={[MockFunction]}
>
<Portal
containerInfo={
<div
id="mx_ContextualMenu_Container"
>
<div
class="mx_ContextualMenu_wrapper"
>
<div
class="mx_ContextualMenu_background"
/>
<div
class="mx_ContextualMenu"
role="menu"
>
<div
class="mx_IconizedContextMenu mx_SpacePanel_contextMenu mx_IconizedContextMenu_compact"
>
<div
class="mx_SpacePanel_contextMenu_header"
>
test space
</div>
<div
class="mx_IconizedContextMenu_optionList"
>
<div
aria-label="Space home"
class="mx_AccessibleButton mx_IconizedContextMenu_item focus-visible"
data-focus-visible-added=""
role="menuitem"
tabindex="0"
>
<span
class="mx_IconizedContextMenu_icon mx_SpacePanel_iconHome"
/>
<span
class="mx_IconizedContextMenu_label"
>
Space home
</span>
</div>
<div
aria-label="Explore rooms"
class="mx_AccessibleButton mx_IconizedContextMenu_item"
role="menuitem"
tabindex="-1"
>
<span
class="mx_IconizedContextMenu_icon mx_SpacePanel_iconExplore"
/>
<span
class="mx_IconizedContextMenu_label"
>
Explore rooms
</span>
</div>
<div
aria-label="Preferences"
class="mx_AccessibleButton mx_IconizedContextMenu_item"
role="menuitem"
tabindex="-1"
>
<span
class="mx_IconizedContextMenu_icon mx_SpacePanel_iconPreferences"
/>
<span
class="mx_IconizedContextMenu_label"
>
Preferences
</span>
</div>
<div
aria-label="Leave space"
class="mx_AccessibleButton mx_IconizedContextMenu_option_red mx_IconizedContextMenu_item"
data-test-id="leave-option"
role="menuitem"
tabindex="-1"
>
<span
class="mx_IconizedContextMenu_icon mx_SpacePanel_iconLeave"
/>
<span
class="mx_IconizedContextMenu_label"
>
Leave space
</span>
</div>
</div>
</div>
</div>
</div>
</div>
}
>
<RovingTabIndexProvider
handleHomeEnd={true}
handleUpDown={true}
onKeyDown={[Function]}
>
<div
className="mx_ContextualMenu_wrapper"
onClick={[Function]}
onContextMenu={[Function]}
onKeyDown={[Function]}
style={
{
"bottom": undefined,
"right": undefined,
}
}
>
<div
className="mx_ContextualMenu_background"
onClick={[Function]}
onContextMenu={[Function]}
style={{}}
/>
<div
className="mx_ContextualMenu"
role="menu"
style={{}}
>
<div
className="mx_IconizedContextMenu mx_SpacePanel_contextMenu mx_IconizedContextMenu_compact"
>
<div
className="mx_SpacePanel_contextMenu_header"
>
test space
</div>
<IconizedContextMenuOptionList
first={true}
>
<div
className="mx_IconizedContextMenu_optionList"
>
<IconizedContextMenuOption
iconClassName="mx_SpacePanel_iconHome"
label="Space home"
onClick={[Function]}
>
<MenuItem
className="mx_IconizedContextMenu_item"
label="Space home"
onClick={[Function]}
>
<RovingAccessibleButton
aria-label="Space home"
className="mx_IconizedContextMenu_item"
onClick={[Function]}
role="menuitem"
>
<AccessibleButton
aria-label="Space home"
className="mx_IconizedContextMenu_item"
element="div"
inputRef={
{
"current": <div
aria-label="Space home"
class="mx_AccessibleButton mx_IconizedContextMenu_item focus-visible"
data-focus-visible-added=""
role="menuitem"
tabindex="0"
>
<span
class="mx_IconizedContextMenu_icon mx_SpacePanel_iconHome"
/>
<span
class="mx_IconizedContextMenu_label"
>
Space home
</span>
</div>,
}
}
onClick={[Function]}
onFocus={[Function]}
role="menuitem"
tabIndex={0}
>
<div
aria-label="Space home"
className="mx_AccessibleButton mx_IconizedContextMenu_item"
onClick={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
role="menuitem"
tabIndex={0}
>
<span
className="mx_IconizedContextMenu_icon mx_SpacePanel_iconHome"
/>
<span
className="mx_IconizedContextMenu_label"
>
Space home
</span>
</div>
</AccessibleButton>
</RovingAccessibleButton>
</MenuItem>
</IconizedContextMenuOption>
<IconizedContextMenuOption
iconClassName="mx_SpacePanel_iconExplore"
label="Explore rooms"
onClick={[Function]}
>
<MenuItem
className="mx_IconizedContextMenu_item"
label="Explore rooms"
onClick={[Function]}
>
<RovingAccessibleButton
aria-label="Explore rooms"
className="mx_IconizedContextMenu_item"
onClick={[Function]}
role="menuitem"
>
<AccessibleButton
aria-label="Explore rooms"
className="mx_IconizedContextMenu_item"
element="div"
inputRef={
{
"current": <div
aria-label="Explore rooms"
class="mx_AccessibleButton mx_IconizedContextMenu_item"
role="menuitem"
tabindex="-1"
>
<span
class="mx_IconizedContextMenu_icon mx_SpacePanel_iconExplore"
/>
<span
class="mx_IconizedContextMenu_label"
>
Explore rooms
</span>
</div>,
}
}
onClick={[Function]}
onFocus={[Function]}
role="menuitem"
tabIndex={-1}
>
<div
aria-label="Explore rooms"
className="mx_AccessibleButton mx_IconizedContextMenu_item"
onClick={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
role="menuitem"
tabIndex={-1}
>
<span
className="mx_IconizedContextMenu_icon mx_SpacePanel_iconExplore"
/>
<span
className="mx_IconizedContextMenu_label"
>
Explore rooms
</span>
</div>
</AccessibleButton>
</RovingAccessibleButton>
</MenuItem>
</IconizedContextMenuOption>
<IconizedContextMenuOption
iconClassName="mx_SpacePanel_iconPreferences"
label="Preferences"
onClick={[Function]}
>
<MenuItem
className="mx_IconizedContextMenu_item"
label="Preferences"
onClick={[Function]}
>
<RovingAccessibleButton
aria-label="Preferences"
className="mx_IconizedContextMenu_item"
onClick={[Function]}
role="menuitem"
>
<AccessibleButton
aria-label="Preferences"
className="mx_IconizedContextMenu_item"
element="div"
inputRef={
{
"current": <div
aria-label="Preferences"
class="mx_AccessibleButton mx_IconizedContextMenu_item"
role="menuitem"
tabindex="-1"
>
<span
class="mx_IconizedContextMenu_icon mx_SpacePanel_iconPreferences"
/>
<span
class="mx_IconizedContextMenu_label"
>
Preferences
</span>
</div>,
}
}
onClick={[Function]}
onFocus={[Function]}
role="menuitem"
tabIndex={-1}
>
<div
aria-label="Preferences"
className="mx_AccessibleButton mx_IconizedContextMenu_item"
onClick={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
role="menuitem"
tabIndex={-1}
>
<span
className="mx_IconizedContextMenu_icon mx_SpacePanel_iconPreferences"
/>
<span
className="mx_IconizedContextMenu_label"
>
Preferences
</span>
</div>
</AccessibleButton>
</RovingAccessibleButton>
</MenuItem>
</IconizedContextMenuOption>
<IconizedContextMenuOption
className="mx_IconizedContextMenu_option_red"
data-test-id="leave-option"
iconClassName="mx_SpacePanel_iconLeave"
label="Leave space"
onClick={[Function]}
>
<MenuItem
className="mx_IconizedContextMenu_option_red mx_IconizedContextMenu_item"
data-test-id="leave-option"
label="Leave space"
onClick={[Function]}
>
<RovingAccessibleButton
aria-label="Leave space"
className="mx_IconizedContextMenu_option_red mx_IconizedContextMenu_item"
data-test-id="leave-option"
onClick={[Function]}
role="menuitem"
>
<AccessibleButton
aria-label="Leave space"
className="mx_IconizedContextMenu_option_red mx_IconizedContextMenu_item"
data-test-id="leave-option"
element="div"
inputRef={
{
"current": <div
aria-label="Leave space"
class="mx_AccessibleButton mx_IconizedContextMenu_option_red mx_IconizedContextMenu_item"
data-test-id="leave-option"
role="menuitem"
tabindex="-1"
>
<span
class="mx_IconizedContextMenu_icon mx_SpacePanel_iconLeave"
/>
<span
class="mx_IconizedContextMenu_label"
>
Leave space
</span>
</div>,
}
}
onClick={[Function]}
onFocus={[Function]}
role="menuitem"
tabIndex={-1}
>
<div
aria-label="Leave space"
className="mx_AccessibleButton mx_IconizedContextMenu_option_red mx_IconizedContextMenu_item"
data-test-id="leave-option"
onClick={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
role="menuitem"
tabIndex={-1}
>
<span
className="mx_IconizedContextMenu_icon mx_SpacePanel_iconLeave"
/>
<span
className="mx_IconizedContextMenu_label"
>
Leave space
</span>
</div>
</AccessibleButton>
</RovingAccessibleButton>
</MenuItem>
</IconizedContextMenuOption>
</div>
</IconizedContextMenuOptionList>
</div>
</div>
</div>
</RovingTabIndexProvider>
</Portal>
</ContextMenu>
</IconizedContextMenu>
</SpaceContextMenu>
"<body>
<div />
<div
id="mx_ContextualMenu_Container"
>
<div
class="mx_ContextualMenu_wrapper"
>
<div
class="mx_ContextualMenu_background"
/>
<div
class="mx_ContextualMenu"
role="menu"
>
<div
class="mx_IconizedContextMenu mx_SpacePanel_contextMenu mx_IconizedContextMenu_compact"
>
<div
class="mx_SpacePanel_contextMenu_header"
>
test space
</div>
<div
class="mx_IconizedContextMenu_optionList"
>
<div
aria-label="Space home"
class="mx_AccessibleButton mx_IconizedContextMenu_item focus-visible"
data-focus-visible-added=""
role="menuitem"
tabindex="0"
>
<span
class="mx_IconizedContextMenu_icon mx_SpacePanel_iconHome"
/>
<span
class="mx_IconizedContextMenu_label"
>
Space home
</span>
</div>
<div
aria-label="Explore rooms"
class="mx_AccessibleButton mx_IconizedContextMenu_item"
role="menuitem"
tabindex="-1"
>
<span
class="mx_IconizedContextMenu_icon mx_SpacePanel_iconExplore"
/>
<span
class="mx_IconizedContextMenu_label"
>
Explore rooms
</span>
</div>
<div
aria-label="Preferences"
class="mx_AccessibleButton mx_IconizedContextMenu_item"
role="menuitem"
tabindex="-1"
>
<span
class="mx_IconizedContextMenu_icon mx_SpacePanel_iconPreferences"
/>
<span
class="mx_IconizedContextMenu_label"
>
Preferences
</span>
</div>
<div
aria-label="Leave space"
class="mx_AccessibleButton mx_IconizedContextMenu_option_red mx_IconizedContextMenu_item"
data-testid="leave-option"
role="menuitem"
tabindex="-1"
>
<span
class="mx_IconizedContextMenu_icon mx_SpacePanel_iconLeave"
/>
<span
class="mx_IconizedContextMenu_label"
>
Leave space
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</body>"
`;

View file

@ -14,10 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { fireEvent, render, screen } from "@testing-library/react";
import React from "react";
// eslint-disable-next-line deprecate/import
import { mount } from "enzyme";
import { act } from "react-dom/test-utils";
import LabelledCheckbox from "../../../../src/components/views/elements/LabelledCheckbox";
@ -30,32 +28,18 @@ jest.mock("matrix-js-sdk/src/randomstring", () => {
describe("<LabelledCheckbox />", () => {
type CompProps = React.ComponentProps<typeof LabelledCheckbox>;
const getComponent = (props: CompProps) => mount(<LabelledCheckbox {...props} />);
type CompClass = ReturnType<typeof getComponent>;
const getComponent = (props: CompProps) => <LabelledCheckbox {...props} />;
const getCheckbox = (): HTMLInputElement => screen.getByRole("checkbox");
const getCheckbox = (component: CompClass) => component.find(`input[type="checkbox"]`);
const getLabel = (component: CompClass) => component.find(`.mx_LabelledCheckbox_label`);
const getByline = (component: CompClass) => component.find(`.mx_LabelledCheckbox_byline`);
const isChecked = (checkbox: ReturnType<typeof getCheckbox>) => checkbox.is(`[checked=true]`);
const isDisabled = (checkbox: ReturnType<typeof getCheckbox>) => checkbox.is(`[disabled=true]`);
const getText = (span: ReturnType<typeof getLabel>) => (span.length > 0 ? span.at(0).text() : null);
test.each([null, "this is a byline"])("should render with byline of %p", (byline) => {
it.each([undefined, "this is a byline"])("should render with byline of %p", (byline) => {
const props: CompProps = {
label: "Hello world",
value: true,
byline: byline,
onChange: jest.fn(),
};
const component = getComponent(props);
const checkbox = getCheckbox(component);
expect(component).toMatchSnapshot();
expect(isChecked(checkbox)).toBe(true);
expect(isDisabled(checkbox)).toBe(false);
expect(getText(getLabel(component))).toBe(props.label);
expect(getText(getByline(component))).toBe(byline);
const renderResult = render(getComponent(props));
expect(renderResult.asFragment()).toMatchSnapshot();
});
it("should support unchecked by default", () => {
@ -64,9 +48,8 @@ describe("<LabelledCheckbox />", () => {
value: false,
onChange: jest.fn(),
};
const component = getComponent(props);
expect(isChecked(getCheckbox(component))).toBe(false);
render(getComponent(props));
expect(getCheckbox()).not.toBeChecked();
});
it("should be possible to disable the checkbox", () => {
@ -76,9 +59,8 @@ describe("<LabelledCheckbox />", () => {
disabled: true,
onChange: jest.fn(),
};
const component = getComponent(props);
expect(isDisabled(getCheckbox(component))).toBe(true);
render(getComponent(props));
expect(getCheckbox()).toBeDisabled();
});
it("should emit onChange calls", () => {
@ -87,15 +69,11 @@ describe("<LabelledCheckbox />", () => {
value: false,
onChange: jest.fn(),
};
const component = getComponent(props);
render(getComponent(props));
expect(props.onChange).not.toHaveBeenCalled();
act(() => {
getCheckbox(component).simulate("change");
});
expect(props.onChange).toHaveBeenCalledTimes(1);
fireEvent.click(getCheckbox());
expect(props.onChange).toHaveBeenCalledWith(true);
});
it("should react to value and disabled prop changes", () => {
@ -104,16 +82,18 @@ describe("<LabelledCheckbox />", () => {
value: false,
onChange: jest.fn(),
};
const component = getComponent(props);
let checkbox = getCheckbox(component);
const { rerender } = render(getComponent(props));
expect(isChecked(checkbox)).toBe(false);
expect(isDisabled(checkbox)).toBe(false);
let checkbox = getCheckbox();
expect(checkbox).not.toBeChecked();
expect(checkbox).not.toBeDisabled();
component.setProps({ value: true, disabled: true });
checkbox = getCheckbox(component); // refresh reference to checkbox
props.disabled = true;
props.value = true;
rerender(getComponent(props));
expect(isChecked(checkbox)).toBe(true);
expect(isDisabled(checkbox)).toBe(true);
checkbox = getCheckbox();
expect(checkbox).toBeChecked();
expect(checkbox).toBeDisabled();
});
});

View file

@ -1,106 +1,82 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<LabelledCheckbox /> should render with byline of "this is a byline" 1`] = `
<LabelledCheckbox
byline="this is a byline"
label="Hello world"
onChange={[MockFunction]}
value={true}
>
<DocumentFragment>
<label
className="mx_LabelledCheckbox"
class="mx_LabelledCheckbox"
>
<StyledCheckbox
checked={true}
className=""
onChange={[Function]}
<span
class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
>
<span
className="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
<input
checked=""
id="checkbox_abdefghi"
type="checkbox"
/>
<label
for="checkbox_abdefghi"
>
<input
checked={true}
id="checkbox_abdefghi"
onChange={[Function]}
type="checkbox"
/>
<label
htmlFor="checkbox_abdefghi"
<div
class="mx_Checkbox_background"
>
<div
className="mx_Checkbox_background"
>
<div
className="mx_Checkbox_checkmark"
/>
</div>
</label>
</span>
</StyledCheckbox>
class="mx_Checkbox_checkmark"
/>
</div>
</label>
</span>
<div
className="mx_LabelledCheckbox_labels"
class="mx_LabelledCheckbox_labels"
>
<span
className="mx_LabelledCheckbox_label"
class="mx_LabelledCheckbox_label"
>
Hello world
</span>
<span
className="mx_LabelledCheckbox_byline"
class="mx_LabelledCheckbox_byline"
>
this is a byline
</span>
</div>
</label>
</LabelledCheckbox>
</DocumentFragment>
`;
exports[`<LabelledCheckbox /> should render with byline of null 1`] = `
<LabelledCheckbox
byline={null}
label="Hello world"
onChange={[MockFunction]}
value={true}
>
exports[`<LabelledCheckbox /> should render with byline of undefined 1`] = `
<DocumentFragment>
<label
className="mx_LabelledCheckbox"
class="mx_LabelledCheckbox"
>
<StyledCheckbox
checked={true}
className=""
onChange={[Function]}
<span
class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
>
<span
className="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
<input
checked=""
id="checkbox_abdefghi"
type="checkbox"
/>
<label
for="checkbox_abdefghi"
>
<input
checked={true}
id="checkbox_abdefghi"
onChange={[Function]}
type="checkbox"
/>
<label
htmlFor="checkbox_abdefghi"
<div
class="mx_Checkbox_background"
>
<div
className="mx_Checkbox_background"
>
<div
className="mx_Checkbox_checkmark"
/>
</div>
</label>
</span>
</StyledCheckbox>
class="mx_Checkbox_checkmark"
/>
</div>
</label>
</span>
<div
className="mx_LabelledCheckbox_labels"
class="mx_LabelledCheckbox_labels"
>
<span
className="mx_LabelledCheckbox_label"
class="mx_LabelledCheckbox_label"
>
Hello world
</span>
</div>
</label>
</LabelledCheckbox>
</DocumentFragment>
`;

View file

@ -24,34 +24,64 @@ import * as TestUtils from "../../../test-utils";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import EditorModel from "../../../../src/editor/model";
import { createPartCreator, createRenderer } from "../../../editor/mock";
import SettingsStore from "../../../../src/settings/SettingsStore";
describe("BasicMessageComposer", () => {
const renderer = createRenderer();
const pc = createPartCreator();
beforeEach(() => {
TestUtils.stubClient();
});
TestUtils.stubClient();
it("should allow a user to paste a URL without it being mangled", () => {
const client: MatrixClient = MatrixClientPeg.get();
const roomId = "!1234567890:domain";
const userId = client.getSafeUserId();
const room = new Room(roomId, client, userId);
it("should allow a user to paste a URL without it being mangled", async () => {
const model = new EditorModel([], pc, renderer);
const client: MatrixClient = MatrixClientPeg.get();
const roomId = "!1234567890:domain";
const userId = client.getSafeUserId();
const room = new Room(roomId, client, userId);
render(<BasicMessageComposer model={model} room={room} />);
const testUrl = "https://element.io";
const mockDataTransfer = generateMockDataTransferForString(testUrl);
render(<BasicMessageComposer model={model} room={room} />);
userEvent.paste(mockDataTransfer);
await userEvent.paste(mockDataTransfer);
expect(model.parts).toHaveLength(1);
expect(model.parts[0].text).toBe(testUrl);
expect(screen.getByText(testUrl)).toBeInTheDocument();
});
it("should replaceEmoticons properly", async () => {
jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName: string) => {
return settingName === "MessageComposerInput.autoReplaceEmoji";
});
userEvent.setup();
const model = new EditorModel([], pc, renderer);
render(<BasicMessageComposer model={model} room={room} />);
const tranformations = [
{ before: "4:3 video", after: "4:3 video" },
{ before: "regexp 12345678", after: "regexp 12345678" },
{ before: "--:--)", after: "--:--)" },
{ before: "we <3 matrix", after: "we ❤️ matrix" },
{ before: "hello world :-)", after: "hello world 🙂" },
{ before: ":) hello world", after: "🙂 hello world" },
{ before: ":D 4:3 video :)", after: "😄 4:3 video 🙂" },
{ before: ":-D", after: "😄" },
{ before: ":D", after: "😄" },
{ before: ":3", after: "😽" },
];
const input = screen.getByRole("textbox");
for (const { before, after } of tranformations) {
await userEvent.clear(input);
//add a space after the text to trigger the replacement
await userEvent.type(input, before + " ");
const transformedText = model.parts.map((part) => part.text).join("");
expect(transformedText).toBe(after + " ");
}
});
});
function generateMockDataTransferForString(string: string): DataTransfer {

View file

@ -226,6 +226,7 @@ describe("<Notifications />", () => {
setAccountData: jest.fn(),
sendReadReceipt: jest.fn(),
supportsThreads: jest.fn().mockReturnValue(true),
isInitialSyncComplete: jest.fn().mockReturnValue(false),
});
mockClient.getPushRules.mockResolvedValue(pushRules);

View file

@ -94,6 +94,8 @@ describe("RoomViewStore", function () {
getDeviceId: jest.fn().mockReturnValue("ABC123"),
sendStateEvent: jest.fn().mockResolvedValue({}),
supportsThreads: jest.fn(),
isInitialSyncComplete: jest.fn().mockResolvedValue(false),
relations: jest.fn(),
});
const room = new Room(roomId, mockClient, userId);
const room2 = new Room(roomId2, mockClient, userId);

109
yarn.lock
View file

@ -2127,11 +2127,12 @@
"@types/fbemitter" "*"
"@types/react" "*"
"@types/fs-extra@^9.0.13":
version "9.0.13"
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.13.tgz#7594fbae04fe7f1918ce8b3d213f74ff44ac1f45"
integrity sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==
"@types/fs-extra@^11.0.0":
version "11.0.1"
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-11.0.1.tgz#f542ec47810532a8a252127e6e105f487e0a6ea5"
integrity sha512-MxObHvNl4A69ofaTRU8DFqvgzzv8s9yRtaPPm5gud9HDNvpB3GPQFvNuTWAI59B9huVGV5jXYJwbCsmBsOGYWA==
dependencies:
"@types/jsonfile" "*"
"@types/node" "*"
"@types/geojson@*", "@types/geojson@^7946.0.10", "@types/geojson@^7946.0.8":
@ -2200,6 +2201,13 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
"@types/jsonfile@*":
version "6.1.1"
resolved "https://registry.yarnpkg.com/@types/jsonfile/-/jsonfile-6.1.1.tgz#ac84e9aefa74a2425a0fb3012bdea44f58970f1b"
integrity sha512-GSgiRCVeapDN+3pqA35IkQwasaCh/0YFH5dEF6S88iDvEn901DjOeH3/QPY+XYP1DFzDZPvIvfeEgk+7br5png==
dependencies:
"@types/node" "*"
"@types/katex@^0.14.0":
version "0.14.0"
resolved "https://registry.yarnpkg.com/@types/katex/-/katex-0.14.0.tgz#b84c0afc3218069a5ad64fe2a95321881021b5fe"
@ -2417,14 +2425,15 @@
integrity sha512-3NoqvZC2W5gAC5DZbTpCeJ251vGQmgcWIHQJGq2J240HY6ErQ9aWKkwfoKJlHLx+A83WPNTZ9+3cd2ILxbvr1w==
"@typescript-eslint/eslint-plugin@^5.35.1":
version "5.48.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.1.tgz#deee67e399f2cb6b4608c935777110e509d8018c"
integrity sha512-9nY5K1Rp2ppmpb9s9S2aBiF3xo5uExCehMDmYmmFqqyxgenbHJ3qbarcLt4ITgaD6r/2ypdlcFRdcuVPnks+fQ==
version "5.51.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.51.0.tgz#da3f2819633061ced84bb82c53bba45a6fe9963a"
integrity sha512-wcAwhEWm1RgNd7dxD/o+nnLW8oH+6RK1OGnmbmkj/GGoDPV1WWMVP0FXYQBivKHdwM1pwii3bt//RC62EriIUQ==
dependencies:
"@typescript-eslint/scope-manager" "5.48.1"
"@typescript-eslint/type-utils" "5.48.1"
"@typescript-eslint/utils" "5.48.1"
"@typescript-eslint/scope-manager" "5.51.0"
"@typescript-eslint/type-utils" "5.51.0"
"@typescript-eslint/utils" "5.51.0"
debug "^4.3.4"
grapheme-splitter "^1.0.4"
ignore "^5.2.0"
natural-compare-lite "^1.4.0"
regexpp "^3.2.0"
@ -2432,71 +2441,71 @@
tsutils "^3.21.0"
"@typescript-eslint/parser@^5.6.0":
version "5.48.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.48.1.tgz#d0125792dab7e232035434ab8ef0658154db2f10"
integrity sha512-4yg+FJR/V1M9Xoq56SF9Iygqm+r5LMXvheo6DQ7/yUWynQ4YfCRnsKuRgqH4EQ5Ya76rVwlEpw4Xu+TgWQUcdA==
version "5.51.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.51.0.tgz#2d74626652096d966ef107f44b9479f02f51f271"
integrity sha512-fEV0R9gGmfpDeRzJXn+fGQKcl0inIeYobmmUWijZh9zA7bxJ8clPhV9up2ZQzATxAiFAECqPQyMDB4o4B81AaA==
dependencies:
"@typescript-eslint/scope-manager" "5.48.1"
"@typescript-eslint/types" "5.48.1"
"@typescript-eslint/typescript-estree" "5.48.1"
"@typescript-eslint/scope-manager" "5.51.0"
"@typescript-eslint/types" "5.51.0"
"@typescript-eslint/typescript-estree" "5.51.0"
debug "^4.3.4"
"@typescript-eslint/scope-manager@5.48.1":
version "5.48.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.48.1.tgz#39c71e4de639f5fe08b988005beaaf6d79f9d64d"
integrity sha512-S035ueRrbxRMKvSTv9vJKIWgr86BD8s3RqoRZmsSh/s8HhIs90g6UlK8ZabUSjUZQkhVxt7nmZ63VJ9dcZhtDQ==
"@typescript-eslint/scope-manager@5.51.0":
version "5.51.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.51.0.tgz#ad3e3c2ecf762d9a4196c0fbfe19b142ac498990"
integrity sha512-gNpxRdlx5qw3yaHA0SFuTjW4rxeYhpHxt491PEcKF8Z6zpq0kMhe0Tolxt0qjlojS+/wArSDlj/LtE69xUJphQ==
dependencies:
"@typescript-eslint/types" "5.48.1"
"@typescript-eslint/visitor-keys" "5.48.1"
"@typescript-eslint/types" "5.51.0"
"@typescript-eslint/visitor-keys" "5.51.0"
"@typescript-eslint/type-utils@5.48.1":
version "5.48.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.48.1.tgz#5d94ac0c269a81a91ad77c03407cea2caf481412"
integrity sha512-Hyr8HU8Alcuva1ppmqSYtM/Gp0q4JOp1F+/JH5D1IZm/bUBrV0edoewQZiEc1r6I8L4JL21broddxK8HAcZiqQ==
"@typescript-eslint/type-utils@5.51.0":
version "5.51.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.51.0.tgz#7af48005531700b62a20963501d47dfb27095988"
integrity sha512-QHC5KKyfV8sNSyHqfNa0UbTbJ6caB8uhcx2hYcWVvJAZYJRBo5HyyZfzMdRx8nvS+GyMg56fugMzzWnojREuQQ==
dependencies:
"@typescript-eslint/typescript-estree" "5.48.1"
"@typescript-eslint/utils" "5.48.1"
"@typescript-eslint/typescript-estree" "5.51.0"
"@typescript-eslint/utils" "5.51.0"
debug "^4.3.4"
tsutils "^3.21.0"
"@typescript-eslint/types@5.48.1":
version "5.48.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.48.1.tgz#efd1913a9aaf67caf8a6e6779fd53e14e8587e14"
integrity sha512-xHyDLU6MSuEEdIlzrrAerCGS3T7AA/L8Hggd0RCYBi0w3JMvGYxlLlXHeg50JI9Tfg5MrtsfuNxbS/3zF1/ATg==
"@typescript-eslint/types@5.51.0":
version "5.51.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.51.0.tgz#e7c1622f46c7eea7e12bbf1edfb496d4dec37c90"
integrity sha512-SqOn0ANn/v6hFn0kjvLwiDi4AzR++CBZz0NV5AnusT2/3y32jdc0G4woXPWHCumWtUXZKPAS27/9vziSsC9jnw==
"@typescript-eslint/typescript-estree@5.48.1":
version "5.48.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.1.tgz#9efa8ee2aa471c6ab62e649f6e64d8d121bc2056"
integrity sha512-Hut+Osk5FYr+sgFh8J/FHjqX6HFcDzTlWLrFqGoK5kVUN3VBHF/QzZmAsIXCQ8T/W9nQNBTqalxi1P3LSqWnRA==
"@typescript-eslint/typescript-estree@5.51.0":
version "5.51.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.51.0.tgz#0ec8170d7247a892c2b21845b06c11eb0718f8de"
integrity sha512-TSkNupHvNRkoH9FMA3w7TazVFcBPveAAmb7Sz+kArY6sLT86PA5Vx80cKlYmd8m3Ha2SwofM1KwraF24lM9FvA==
dependencies:
"@typescript-eslint/types" "5.48.1"
"@typescript-eslint/visitor-keys" "5.48.1"
"@typescript-eslint/types" "5.51.0"
"@typescript-eslint/visitor-keys" "5.51.0"
debug "^4.3.4"
globby "^11.1.0"
is-glob "^4.0.3"
semver "^7.3.7"
tsutils "^3.21.0"
"@typescript-eslint/utils@5.48.1":
version "5.48.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.48.1.tgz#20f2f4e88e9e2a0961cbebcb47a1f0f7da7ba7f9"
integrity sha512-SmQuSrCGUOdmGMwivW14Z0Lj8dxG1mOFZ7soeJ0TQZEJcs3n5Ndgkg0A4bcMFzBELqLJ6GTHnEU+iIoaD6hFGA==
"@typescript-eslint/utils@5.51.0":
version "5.51.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.51.0.tgz#074f4fabd5b12afe9c8aa6fdee881c050f8b4d47"
integrity sha512-76qs+5KWcaatmwtwsDJvBk4H76RJQBFe+Gext0EfJdC3Vd2kpY2Pf//OHHzHp84Ciw0/rYoGTDnIAr3uWhhJYw==
dependencies:
"@types/json-schema" "^7.0.9"
"@types/semver" "^7.3.12"
"@typescript-eslint/scope-manager" "5.48.1"
"@typescript-eslint/types" "5.48.1"
"@typescript-eslint/typescript-estree" "5.48.1"
"@typescript-eslint/scope-manager" "5.51.0"
"@typescript-eslint/types" "5.51.0"
"@typescript-eslint/typescript-estree" "5.51.0"
eslint-scope "^5.1.1"
eslint-utils "^3.0.0"
semver "^7.3.7"
"@typescript-eslint/visitor-keys@5.48.1":
version "5.48.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.1.tgz#79fd4fb9996023ef86849bf6f904f33eb6c8fccb"
integrity sha512-Ns0XBwmfuX7ZknznfXozgnydyR8F6ev/KEGePP4i74uL3ArsKbEhJ7raeKr1JSa997DBDwol/4a0Y+At82c9dA==
"@typescript-eslint/visitor-keys@5.51.0":
version "5.51.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.51.0.tgz#c0147dd9a36c0de758aaebd5b48cae1ec59eba87"
integrity sha512-Oh2+eTdjHjOFjKA27sxESlA87YPSOJafGCR0md5oeMdh1ZcCfAGCIOL216uTBAkAIptvLIfKQhl7lHxMJet4GQ==
dependencies:
"@typescript-eslint/types" "5.48.1"
"@typescript-eslint/types" "5.51.0"
eslint-visitor-keys "^3.3.0"
"@wojtekmaj/enzyme-adapter-react-17@^0.8.0":