Migrate more tests to vitest (#34349)
This commit is contained in:
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type ChangeEvent, type KeyboardEvent as ReactKeyboardEvent } from "react";
|
||||
import { waitFor } from "@testing-library/dom";
|
||||
|
||||
import { type Playback, PlaybackState } from "../../../src/audio/Playback";
|
||||
import { AudioPlayerViewModel } from "../../../src/viewmodels/room/timeline/event-tile/body/AudioPlayerViewModel";
|
||||
import { MockedPlayback } from "../../unit-tests/audio/MockedPlayback";
|
||||
|
||||
describe("AudioPlayerViewModel", () => {
|
||||
let playback: Playback;
|
||||
beforeEach(() => {
|
||||
playback = new MockedPlayback(PlaybackState.Decoding, 50, 10) as unknown as Playback;
|
||||
});
|
||||
|
||||
it("should return the snapshot", () => {
|
||||
const vm = new AudioPlayerViewModel({ playback, mediaName: "mediaName" });
|
||||
expect(vm.getSnapshot()).toMatchObject({
|
||||
mediaName: "mediaName",
|
||||
sizeBytes: 8000,
|
||||
playbackState: "decoding",
|
||||
durationSeconds: 50,
|
||||
playedSeconds: 10,
|
||||
percentComplete: 20,
|
||||
error: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("should toggle the playback state", async () => {
|
||||
const vm = new AudioPlayerViewModel({ playback, mediaName: "mediaName" });
|
||||
|
||||
await vm.togglePlay();
|
||||
expect(playback.toggle).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should move the playback on seekbar change", async () => {
|
||||
const vm = new AudioPlayerViewModel({ playback, mediaName: "mediaName" });
|
||||
await vm.onSeekbarChange({ target: { value: "20" } } as ChangeEvent<HTMLInputElement>);
|
||||
expect(playback.skipTo).toHaveBeenCalledWith(10); // 20% of 50 seconds
|
||||
});
|
||||
|
||||
it("should has error=true when playback.prepare fails", async () => {
|
||||
jest.spyOn(playback, "prepare").mockRejectedValue(new Error("Failed to prepare playback"));
|
||||
const vm = new AudioPlayerViewModel({ playback, mediaName: "mediaName" });
|
||||
await waitFor(() => expect(vm.getSnapshot().error).toBe(true));
|
||||
});
|
||||
|
||||
it("should handle key down events", () => {
|
||||
const vm = new AudioPlayerViewModel({ playback, mediaName: "mediaName" });
|
||||
let event = new KeyboardEvent("keydown", { key: " " }) as unknown as ReactKeyboardEvent<HTMLDivElement>;
|
||||
vm.onKeyDown(event);
|
||||
expect(playback.toggle).toHaveBeenCalled();
|
||||
|
||||
event = new KeyboardEvent("keydown", { key: "ArrowLeft" }) as unknown as ReactKeyboardEvent<HTMLDivElement>;
|
||||
vm.onKeyDown(event);
|
||||
expect(playback.skipTo).toHaveBeenCalledWith(10 - 5); // 5 seconds back
|
||||
|
||||
event = new KeyboardEvent("keydown", { key: "ArrowRight" }) as unknown as ReactKeyboardEvent<HTMLDivElement>;
|
||||
vm.onKeyDown(event);
|
||||
expect(playback.skipTo).toHaveBeenCalledWith(10 + 5); // 5 seconds forward
|
||||
});
|
||||
|
||||
it("does not stop propagation for unhandled key down events", () => {
|
||||
const vm = new AudioPlayerViewModel({ playback, mediaName: "mediaName" });
|
||||
const event = new KeyboardEvent("keydown", { key: "a" });
|
||||
const stopPropagationSpy = jest.spyOn(event, "stopPropagation");
|
||||
|
||||
vm.onKeyDown(event as unknown as ReactKeyboardEvent<HTMLDivElement>);
|
||||
|
||||
expect(stopPropagationSpy).not.toHaveBeenCalled();
|
||||
expect(playback.toggle).not.toHaveBeenCalled();
|
||||
expect(playback.skipTo).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should update snapshot when setProps is called with new mediaName", () => {
|
||||
const vm = new AudioPlayerViewModel({ playback, mediaName: "oldName" });
|
||||
expect(vm.getSnapshot().mediaName).toBe("oldName");
|
||||
|
||||
vm.setProps({ mediaName: "newName" });
|
||||
expect(vm.getSnapshot().mediaName).toBe("newName");
|
||||
});
|
||||
});
|
||||
@@ -1,160 +0,0 @@
|
||||
/*
|
||||
* Copyright 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { DisambiguatedProfileViewModel } from "../../../src/viewmodels/room/timeline/event-tile/DisambiguatedProfileViewModel";
|
||||
|
||||
describe("DisambiguatedProfileViewModel", () => {
|
||||
const member = {
|
||||
userId: "@alice:example.org",
|
||||
roomId: "!room:example.org",
|
||||
rawDisplayName: "Alice",
|
||||
disambiguate: true,
|
||||
};
|
||||
const nonDisambiguatedMember = {
|
||||
...member,
|
||||
disambiguate: false,
|
||||
};
|
||||
|
||||
it("should return the snapshot from props", () => {
|
||||
const vm = new DisambiguatedProfileViewModel({
|
||||
member,
|
||||
fallbackName: "Fallback",
|
||||
colored: true,
|
||||
emphasizeDisplayName: true,
|
||||
withTooltip: true,
|
||||
});
|
||||
|
||||
expect(vm.getSnapshot()).toEqual({
|
||||
displayName: "Alice",
|
||||
colorClass: "mx_Username_color3",
|
||||
displayIdentifier: "@alice:example.org",
|
||||
title: "Alice (@alice:example.org)",
|
||||
emphasizeDisplayName: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("should default member fields when member is null", () => {
|
||||
const vm = new DisambiguatedProfileViewModel({
|
||||
member: null,
|
||||
fallbackName: "Fallback",
|
||||
});
|
||||
|
||||
expect(vm.getSnapshot()).toMatchObject({
|
||||
displayName: "Fallback",
|
||||
colorClass: undefined,
|
||||
displayIdentifier: undefined,
|
||||
title: undefined,
|
||||
emphasizeDisplayName: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it("should delegate onClick without emitting a snapshot update", () => {
|
||||
const onClick = jest.fn();
|
||||
const vm = new DisambiguatedProfileViewModel({
|
||||
member,
|
||||
fallbackName: "Fallback",
|
||||
onClick,
|
||||
});
|
||||
const prevSnapshot = vm.getSnapshot();
|
||||
const subscriber = jest.fn();
|
||||
|
||||
vm.subscribe(subscriber);
|
||||
vm.onClick?.({} as never);
|
||||
|
||||
expect(onClick).toHaveBeenCalledTimes(1);
|
||||
expect(subscriber).not.toHaveBeenCalled();
|
||||
expect(vm.getSnapshot()).toBe(prevSnapshot);
|
||||
});
|
||||
|
||||
it("should keep onClick bound when extracted as a callback", () => {
|
||||
const onClick = jest.fn();
|
||||
const vm = new DisambiguatedProfileViewModel({
|
||||
member,
|
||||
fallbackName: "Fallback",
|
||||
onClick,
|
||||
});
|
||||
|
||||
const clickHandler = vm.onClick;
|
||||
|
||||
expect(() => clickHandler?.({} as never)).not.toThrow();
|
||||
expect(onClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should emit snapshot update when fallbackName changes", () => {
|
||||
const vm = new DisambiguatedProfileViewModel({
|
||||
member: null,
|
||||
fallbackName: "Fallback",
|
||||
});
|
||||
const subscriber = jest.fn();
|
||||
|
||||
vm.subscribe(subscriber);
|
||||
vm.setMember("Updated");
|
||||
|
||||
expect(subscriber).toHaveBeenCalledTimes(1);
|
||||
expect(vm.getSnapshot().displayName).toBe("Updated");
|
||||
});
|
||||
|
||||
it("should emit snapshot update when setMember is called even if fallbackName is unchanged", () => {
|
||||
const vm = new DisambiguatedProfileViewModel({
|
||||
member: null,
|
||||
fallbackName: "Fallback",
|
||||
});
|
||||
const subscriber = jest.fn();
|
||||
|
||||
vm.subscribe(subscriber);
|
||||
vm.setMember("Fallback");
|
||||
|
||||
expect(subscriber).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should compute tooltip title from constructor props when withTooltip is true", () => {
|
||||
const vm = new DisambiguatedProfileViewModel({
|
||||
member,
|
||||
fallbackName: "Fallback",
|
||||
withTooltip: true,
|
||||
});
|
||||
|
||||
expect(vm.getSnapshot().title).toBe("Alice (@alice:example.org)");
|
||||
});
|
||||
|
||||
it("should compute tooltip title even when disambiguation is not needed", () => {
|
||||
const vm = new DisambiguatedProfileViewModel({
|
||||
member: nonDisambiguatedMember,
|
||||
fallbackName: "Fallback",
|
||||
withTooltip: true,
|
||||
});
|
||||
|
||||
expect(vm.getSnapshot().title).toBe("Alice (@alice:example.org)");
|
||||
});
|
||||
|
||||
it("should emit snapshot update when member changes via setMember", () => {
|
||||
const vm = new DisambiguatedProfileViewModel({
|
||||
member: null,
|
||||
fallbackName: "Fallback",
|
||||
});
|
||||
const subscriber = jest.fn();
|
||||
|
||||
vm.subscribe(subscriber);
|
||||
vm.setMember("Fallback", member);
|
||||
|
||||
expect(subscriber).toHaveBeenCalledTimes(1);
|
||||
expect(vm.getSnapshot().displayName).toBe("Alice");
|
||||
});
|
||||
|
||||
it("should emit snapshot update when setMember is called with unchanged member", () => {
|
||||
const vm = new DisambiguatedProfileViewModel({
|
||||
member,
|
||||
fallbackName: "Fallback",
|
||||
});
|
||||
const subscriber = jest.fn();
|
||||
|
||||
vm.subscribe(subscriber);
|
||||
vm.setMember("Fallback", member);
|
||||
|
||||
expect(subscriber).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -1,139 +0,0 @@
|
||||
/*
|
||||
* Copyright 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { waitFor } from "jest-matrix-react";
|
||||
import { type PanelImperativeHandle } from "@element-hq/web-shared-components";
|
||||
|
||||
import { ResizerViewModel } from "../../../src/viewmodels/structures/ResizerViewModel";
|
||||
import SettingsStore from "../../../src/settings/SettingsStore";
|
||||
import { SettingLevel } from "../../../src/settings/SettingLevel";
|
||||
|
||||
jest.mock("what-input");
|
||||
|
||||
describe("LeftPanelResizerViewModel", () => {
|
||||
afterEach(() => {
|
||||
SettingsStore.reset();
|
||||
});
|
||||
|
||||
describe("Initial state is correct", () => {
|
||||
it("should have correct initial state when panel was previously collapsed", () => {
|
||||
SettingsStore.setValue("RoomList.isPanelCollapsed", null, SettingLevel.DEVICE, true);
|
||||
const vm = new ResizerViewModel();
|
||||
expect(vm.getSnapshot()).toStrictEqual({
|
||||
isCollapsed: true,
|
||||
initialSize: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it("should have correct initial state when panel was previously resized", () => {
|
||||
SettingsStore.setValue("RoomList.panelSize", null, SettingLevel.DEVICE, 34);
|
||||
const vm = new ResizerViewModel();
|
||||
expect(vm.getSnapshot()).toStrictEqual({
|
||||
isCollapsed: false,
|
||||
initialSize: 34,
|
||||
});
|
||||
});
|
||||
|
||||
it("should have correct initial state when panel was neither resized nor collapsed", () => {
|
||||
const vm = new ResizerViewModel();
|
||||
expect(vm.getSnapshot()).toStrictEqual({
|
||||
isCollapsed: false,
|
||||
initialSize: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should update isCollapsed on onLeftPanelResized()", async () => {
|
||||
const vm = new ResizerViewModel();
|
||||
vm.onLeftPanelResize({ inPixels: 100, asPercentage: 6 });
|
||||
await waitFor(() => {
|
||||
expect(vm.getSnapshot().isCollapsed).toStrictEqual(false);
|
||||
});
|
||||
vm.onLeftPanelResize({ inPixels: 0, asPercentage: 6 });
|
||||
await waitFor(() => {
|
||||
expect(vm.getSnapshot().isCollapsed).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("should noop on click when handle is not yet set", () => {
|
||||
const vm = new ResizerViewModel();
|
||||
expect(() => {
|
||||
// Click
|
||||
vm.onPointerDown();
|
||||
vm.onPointerUp();
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it("should noop on mouse drag", () => {
|
||||
const vm = new ResizerViewModel();
|
||||
SettingsStore.setValue("RoomList.panelSize", null, SettingLevel.DEVICE, 34);
|
||||
const mockHandle = {
|
||||
resize: jest.fn(),
|
||||
isCollapsed: jest.fn().mockReturnValue(true),
|
||||
} as unknown as PanelImperativeHandle;
|
||||
vm.setPanelHandle(mockHandle);
|
||||
|
||||
// Simulate drag
|
||||
vm.onPointerDown();
|
||||
vm.onPointerMove();
|
||||
vm.onPointerUp();
|
||||
|
||||
expect(mockHandle.resize).not.toHaveBeenCalledWith("34%");
|
||||
});
|
||||
|
||||
describe("should expand panel on double click when panel is collapsed", () => {
|
||||
it("to last non-zero width that the user set", () => {
|
||||
const vm = new ResizerViewModel();
|
||||
SettingsStore.setValue("RoomList.panelSize", null, SettingLevel.DEVICE, 34);
|
||||
const mockHandle = {
|
||||
resize: jest.fn(),
|
||||
isCollapsed: jest.fn().mockReturnValue(true),
|
||||
} as unknown as PanelImperativeHandle;
|
||||
vm.setPanelHandle(mockHandle);
|
||||
// Simulate click
|
||||
vm.onPointerDown();
|
||||
vm.onPointerUp();
|
||||
expect(mockHandle.resize).toHaveBeenCalledWith("34%");
|
||||
});
|
||||
|
||||
it("to maximum size of the panel", () => {
|
||||
const vm = new ResizerViewModel();
|
||||
const mockHandle = {
|
||||
resize: jest.fn(),
|
||||
isCollapsed: jest.fn().mockReturnValue(true),
|
||||
} as unknown as PanelImperativeHandle;
|
||||
vm.setPanelHandle(mockHandle);
|
||||
// Simulate click
|
||||
vm.onPointerDown();
|
||||
vm.onPointerUp();
|
||||
expect(mockHandle.resize).toHaveBeenCalledWith("100%");
|
||||
});
|
||||
});
|
||||
|
||||
it("should collapse panel on click when panel is expanded", () => {
|
||||
const vm = new ResizerViewModel();
|
||||
const mockHandle = {
|
||||
collapse: jest.fn(),
|
||||
isCollapsed: jest.fn().mockReturnValue(false),
|
||||
} as unknown as PanelImperativeHandle;
|
||||
vm.setPanelHandle(mockHandle);
|
||||
|
||||
vm.onDoubleClick();
|
||||
expect(mockHandle.collapse).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should resize to nearest whole number", () => {
|
||||
const vm = new ResizerViewModel();
|
||||
const mockHandle = {
|
||||
resize: jest.fn(),
|
||||
} as unknown as PanelImperativeHandle;
|
||||
vm.setPanelHandle(mockHandle);
|
||||
|
||||
vm.onLeftPanelResized(25.515);
|
||||
expect(mockHandle.resize).toHaveBeenCalledWith("26%");
|
||||
});
|
||||
});
|
||||
@@ -1,354 +0,0 @@
|
||||
/*
|
||||
* Copyright 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { mocked } from "jest-mock";
|
||||
import { ConnectionError, Direction } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import dispatcher from "../../../src/dispatcher/dispatcher";
|
||||
import { Action } from "../../../src/dispatcher/actions";
|
||||
import { formatFullDateNoTime } from "../../../src/DateUtils";
|
||||
import Modal from "../../../src/Modal";
|
||||
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
|
||||
import SettingsStore from "../../../src/settings/SettingsStore";
|
||||
import { UIFeature } from "../../../src/settings/UIFeature";
|
||||
import { SDKContextClass } from "../../../src/contexts/SDKContextClass";
|
||||
import { DateSeparatorViewModel } from "../../../src/viewmodels/room/timeline/DateSeparatorViewModel";
|
||||
import { flushPromisesWithFakeTimers } from "../../test-utils/utilities";
|
||||
|
||||
jest.mock("../../../src/settings/SettingsStore");
|
||||
jest.mock("../../../src/contexts/SDKContextClass", () => ({
|
||||
SDKContextClass: {
|
||||
instance: {
|
||||
roomViewStore: {
|
||||
getRoomId: jest.fn(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
describe("DateSeparatorViewModel", () => {
|
||||
const HOUR_MS = 3600000;
|
||||
const DAY_MS = HOUR_MS * 24;
|
||||
// Friday Dec 17 2021, 9:09am
|
||||
const nowDate = new Date("2021-12-17T08:09:00.000Z");
|
||||
const roomId = "!room:example.org";
|
||||
const defaultProps = {
|
||||
roomId,
|
||||
ts: nowDate.getTime(),
|
||||
};
|
||||
type TestCase = [string, number, string];
|
||||
const testCases: TestCase[] = [
|
||||
["the exact same moment", nowDate.getTime(), "today"],
|
||||
["same day as current day", nowDate.getTime() - HOUR_MS, "today"],
|
||||
["day before the current day", nowDate.getTime() - HOUR_MS * 12, "yesterday"],
|
||||
["2 days ago", nowDate.getTime() - DAY_MS * 2, "Wednesday"],
|
||||
["144 hours ago", nowDate.getTime() - HOUR_MS * 144, "Sat, Dec 11, 2021"],
|
||||
[
|
||||
"6 days ago, but less than 144h",
|
||||
new Date("Saturday Dec 11 2021 23:59:00 GMT+0100 (Central European Standard Time)").getTime(),
|
||||
"Saturday",
|
||||
],
|
||||
];
|
||||
|
||||
const watchCallbacks = new Map<string, (...args: any[]) => void>();
|
||||
const mockTimestampToEvent = jest.fn();
|
||||
|
||||
const hasTestId = (node: React.ReactNode, testId: string): boolean => {
|
||||
if (!React.isValidElement<{ children?: React.ReactNode }>(node)) return false;
|
||||
const props = node.props as { "children"?: React.ReactNode; "data-testid"?: string };
|
||||
if (props["data-testid"] === testId) return true;
|
||||
|
||||
const children = React.Children.toArray(props.children);
|
||||
return children.some((child) => hasTestId(child, testId));
|
||||
};
|
||||
|
||||
const createViewModel = (
|
||||
props: Partial<typeof defaultProps> & { forExport?: boolean } = {},
|
||||
): DateSeparatorViewModel => {
|
||||
return new DateSeparatorViewModel({
|
||||
...defaultProps,
|
||||
...props,
|
||||
roomViewStore: SDKContextClass.instance.roomViewStore,
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(nowDate.getTime());
|
||||
watchCallbacks.clear();
|
||||
|
||||
mocked(SettingsStore).getValue.mockImplementation((key): any => {
|
||||
if (String(key) === UIFeature.TimelineEnableRelativeDates) return true;
|
||||
if (key === "feature_jump_to_date") return false;
|
||||
return undefined;
|
||||
});
|
||||
mocked(SettingsStore).watchSetting.mockImplementation((settingName, _roomId, cb): any => {
|
||||
watchCallbacks.set(String(settingName), cb);
|
||||
return `${String(settingName)}-watch-ref`;
|
||||
});
|
||||
mocked(SettingsStore).unwatchSetting.mockImplementation(() => {});
|
||||
|
||||
mockTimestampToEvent.mockReset();
|
||||
jest.spyOn(MatrixClientPeg, "safeGet").mockReturnValue({
|
||||
timestampToEvent: mockTimestampToEvent,
|
||||
} as any);
|
||||
|
||||
jest.spyOn(dispatcher, "dispatch").mockImplementation(() => {});
|
||||
jest.spyOn(Modal, "createDialog").mockImplementation(() => ({ close: jest.fn() }) as any);
|
||||
|
||||
mocked(SDKContextClass.instance.roomViewStore.getRoomId).mockReturnValue(roomId);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it("computes relative label for today", () => {
|
||||
const vm = createViewModel();
|
||||
|
||||
expect(vm.getSnapshot().label).toBe("today");
|
||||
});
|
||||
|
||||
it("uses full date when exporting", () => {
|
||||
const vm = createViewModel({ forExport: true });
|
||||
|
||||
expect(vm.getSnapshot().label).toBe(formatFullDateNoTime(nowDate));
|
||||
});
|
||||
|
||||
it("updates label when relative dates setting changes at runtime", () => {
|
||||
const vm = createViewModel();
|
||||
expect(vm.getSnapshot().label).toBe("today");
|
||||
|
||||
const callback = watchCallbacks.get(UIFeature.TimelineEnableRelativeDates);
|
||||
expect(callback).toBeDefined();
|
||||
callback?.(UIFeature.TimelineEnableRelativeDates, null, null, null, false);
|
||||
|
||||
expect(vm.getSnapshot().label).toBe(formatFullDateNoTime(nowDate));
|
||||
});
|
||||
|
||||
it("exposes jumpToDateMenu when feature is enabled", () => {
|
||||
mocked(SettingsStore).getValue.mockImplementation((key): any => {
|
||||
if (String(key) === UIFeature.TimelineEnableRelativeDates) return true;
|
||||
if (key === "feature_jump_to_date") return true;
|
||||
return undefined;
|
||||
});
|
||||
const vm = createViewModel();
|
||||
|
||||
expect(vm.getSnapshot().jumpToEnabled).toBeTruthy();
|
||||
});
|
||||
|
||||
it("exposes jumpFromDate in snapshot", () => {
|
||||
const vm = createViewModel();
|
||||
|
||||
expect(vm.getSnapshot().jumpFromDate).toBe("2021-12-17");
|
||||
});
|
||||
|
||||
it("does not expose jumpToDateMenu when exporting", () => {
|
||||
mocked(SettingsStore).getValue.mockImplementation((key): any => {
|
||||
if (String(key) === UIFeature.TimelineEnableRelativeDates) return true;
|
||||
if (key === "feature_jump_to_date") return true;
|
||||
return undefined;
|
||||
});
|
||||
const vm = createViewModel({ forExport: true });
|
||||
|
||||
expect(vm.getSnapshot().jumpToEnabled).toBeFalsy();
|
||||
});
|
||||
|
||||
it("updates jumpToEnabled when feature_jump_to_date changes at runtime", () => {
|
||||
const vm = createViewModel();
|
||||
expect(vm.getSnapshot().jumpToEnabled).toBeFalsy();
|
||||
|
||||
const callback = watchCallbacks.get("feature_jump_to_date");
|
||||
expect(callback).toBeDefined();
|
||||
callback?.("feature_jump_to_date", null, null, null, true);
|
||||
|
||||
expect(vm.getSnapshot().jumpToEnabled).toBeTruthy();
|
||||
});
|
||||
|
||||
it("dispatches ViewRoom when pickDate resolves in active room", async () => {
|
||||
const eventId = "$event";
|
||||
const unixTimestamp = nowDate.getTime() - DAY_MS;
|
||||
mockTimestampToEvent.mockResolvedValue({
|
||||
event_id: eventId,
|
||||
origin_server_ts: unixTimestamp,
|
||||
});
|
||||
const vm = createViewModel();
|
||||
|
||||
await vm.pickDate(unixTimestamp);
|
||||
|
||||
expect(mockTimestampToEvent).toHaveBeenCalledWith(roomId, unixTimestamp, Direction.Forward);
|
||||
expect(dispatcher.dispatch).toHaveBeenCalledWith({
|
||||
action: Action.ViewRoom,
|
||||
event_id: eventId,
|
||||
highlighted: true,
|
||||
room_id: roomId,
|
||||
metricsTrigger: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it("does not dispatch ViewRoom when room changed before pickDate resolves", async () => {
|
||||
mockTimestampToEvent.mockResolvedValue({
|
||||
event_id: "$event",
|
||||
origin_server_ts: nowDate.getTime(),
|
||||
});
|
||||
mocked(SDKContextClass.instance.roomViewStore.getRoomId).mockReturnValue("!other:example.org");
|
||||
const vm = createViewModel();
|
||||
|
||||
await vm.pickDate(nowDate.getTime() - HOUR_MS);
|
||||
|
||||
expect(dispatcher.dispatch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("shows submit debug logs option for generic errors", async () => {
|
||||
mockTimestampToEvent.mockRejectedValue(new Error("Boom"));
|
||||
const vm = createViewModel();
|
||||
|
||||
await vm.pickDate(nowDate.getTime() - HOUR_MS);
|
||||
|
||||
expect(Modal.createDialog).toHaveBeenCalled();
|
||||
const [, params] = mocked(Modal.createDialog).mock.calls.at(-1)!;
|
||||
expect(hasTestId((params as any).description, "jump-to-date-error-submit-debug-logs-button")).toBe(true);
|
||||
});
|
||||
|
||||
it("does not show submit debug logs option for connection errors", async () => {
|
||||
mockTimestampToEvent.mockRejectedValue(new ConnectionError("offline"));
|
||||
const vm = createViewModel();
|
||||
|
||||
await vm.pickDate(nowDate.getTime() - HOUR_MS);
|
||||
|
||||
expect(Modal.createDialog).toHaveBeenCalled();
|
||||
const [, params] = mocked(Modal.createDialog).mock.calls.at(-1)!;
|
||||
expect(hasTestId((params as any).description, "jump-to-date-error-submit-debug-logs-button")).toBe(false);
|
||||
});
|
||||
|
||||
describe("snapshot labels", () => {
|
||||
it.each(testCases)("formats date correctly when current time is %s", (_d, ts, result) => {
|
||||
expect(createViewModel({ ts }).getSnapshot().label).toContain(result);
|
||||
});
|
||||
|
||||
describe("when forExport is true", () => {
|
||||
it.each(testCases)("formats date in full when current time is %s", (_d, ts) => {
|
||||
expect(createViewModel({ ts, forExport: true }).getSnapshot().label).toContain(
|
||||
formatFullDateNoTime(new Date(ts)),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when TimelineEnableRelativeDates is false", () => {
|
||||
beforeEach(() => {
|
||||
mocked(SettingsStore).getValue.mockImplementation((key): any => {
|
||||
if (String(key) === UIFeature.TimelineEnableRelativeDates) return false;
|
||||
if (key === "feature_jump_to_date") return false;
|
||||
return undefined;
|
||||
});
|
||||
});
|
||||
|
||||
it.each(testCases)("formats date in full when current time is %s", (_d, ts) => {
|
||||
expect(createViewModel({ ts }).getSnapshot().label).toContain(formatFullDateNoTime(new Date(ts)));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("jump actions", () => {
|
||||
beforeEach(() => {
|
||||
mocked(SettingsStore).getValue.mockImplementation((key): any => {
|
||||
if (String(key) === UIFeature.TimelineEnableRelativeDates) return true;
|
||||
if (key === "feature_jump_to_date") return true;
|
||||
return undefined;
|
||||
});
|
||||
});
|
||||
|
||||
[
|
||||
{
|
||||
timeDescriptor: "last week",
|
||||
run: (vm: DateSeparatorViewModel): Promise<void> => vm.onLastWeekPicked(),
|
||||
},
|
||||
{
|
||||
timeDescriptor: "last month",
|
||||
run: (vm: DateSeparatorViewModel): Promise<void> => vm.onLastMonthPicked(),
|
||||
},
|
||||
{
|
||||
timeDescriptor: "the beginning",
|
||||
run: (vm: DateSeparatorViewModel): Promise<void> => vm.onBeginningPicked(),
|
||||
},
|
||||
].forEach((testCase) => {
|
||||
it(`can jump to ${testCase.timeDescriptor}`, async () => {
|
||||
const returnedDate = new Date();
|
||||
returnedDate.setDate(nowDate.getDate() - 100);
|
||||
const returnedEventId = "$abc";
|
||||
mockTimestampToEvent.mockResolvedValue({
|
||||
event_id: returnedEventId,
|
||||
origin_server_ts: returnedDate.getTime(),
|
||||
});
|
||||
const vm = createViewModel();
|
||||
|
||||
await testCase.run(vm);
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
expect(mockTimestampToEvent).toHaveBeenCalledWith(roomId, expect.any(Number), Direction.Forward);
|
||||
expect(dispatcher.dispatch).toHaveBeenCalledWith({
|
||||
action: Action.ViewRoom,
|
||||
event_id: returnedEventId,
|
||||
highlighted: true,
|
||||
room_id: roomId,
|
||||
metricsTrigger: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("does not jump when room changed before request resolves", async () => {
|
||||
mocked(SDKContextClass.instance.roomViewStore.getRoomId).mockReturnValue("!some-other-room");
|
||||
mockTimestampToEvent.mockResolvedValue({
|
||||
event_id: "$abc",
|
||||
origin_server_ts: 0,
|
||||
});
|
||||
const vm = createViewModel();
|
||||
|
||||
await vm.onLastWeekPicked();
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
expect(dispatcher.dispatch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not show jump to date error if user switched room", async () => {
|
||||
mocked(SDKContextClass.instance.roomViewStore.getRoomId).mockReturnValue("!some-other-room");
|
||||
mockTimestampToEvent.mockRejectedValue(new Error("Fake error in test"));
|
||||
const vm = createViewModel();
|
||||
|
||||
await vm.onLastWeekPicked();
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
expect(Modal.createDialog).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("shows error dialog with submit debug logs option when non-networking error occurs", async () => {
|
||||
mockTimestampToEvent.mockRejectedValue(new Error("Fake error in test"));
|
||||
const vm = createViewModel();
|
||||
|
||||
await vm.onLastWeekPicked();
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
expect(Modal.createDialog).toHaveBeenCalled();
|
||||
const [, params] = mocked(Modal.createDialog).mock.calls.at(-1)!;
|
||||
expect(hasTestId((params as any).description, "jump-to-date-error-submit-debug-logs-button")).toBe(true);
|
||||
});
|
||||
|
||||
it("shows error dialog without submit debug logs option when networking error occurs", async () => {
|
||||
mockTimestampToEvent.mockRejectedValue(new ConnectionError("Fake connection error in test"));
|
||||
const vm = createViewModel();
|
||||
|
||||
await vm.onLastWeekPicked();
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
expect(Modal.createDialog).toHaveBeenCalled();
|
||||
const [, params] = mocked(Modal.createDialog).mock.calls.at(-1)!;
|
||||
expect(hasTestId((params as any).description, "jump-to-date-error-submit-debug-logs-button")).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user