Add tokenizer mode tests and fix formatting

This commit is contained in:
Hiroshi Shinaoka 2025-12-21 15:15:10 +09:00
parent 3ea2396dbe
commit 34c765ea62
8 changed files with 208 additions and 9 deletions

View File

@ -150,11 +150,21 @@ export default class ManageEventIndexDialog extends React.Component<IProps, ISta
onFinished: async (confirmed?: boolean) => {
if (confirmed) {
// Save the tokenizer mode setting
SettingsStore.setValue("tokenizerMode", null, SettingLevel.DEVICE, this.state.tokenizerMode);
SettingsStore.setValue(
"tokenizerMode",
null,
SettingLevel.DEVICE,
this.state.tokenizerMode,
);
} else {
// User cancelled - revert tokenizer mode to initial value
this.setState({ tokenizerMode: this.state.initialTokenizerMode });
SettingsStore.setValue("tokenizerMode", null, SettingLevel.DEVICE, this.state.initialTokenizerMode);
SettingsStore.setValue(
"tokenizerMode",
null,
SettingLevel.DEVICE,
this.state.initialTokenizerMode,
);
}
this.props.onFinished();
},

View File

@ -92,7 +92,11 @@ export default abstract class BaseEventIndexManager {
* @return {Promise} A promise that will resolve when the event index is
* initialized. Returns { wasRecreated: true } if the database was recreated.
*/
public async initEventIndex(userId: string, deviceId: string, tokenizerMode?: string): Promise<{ wasRecreated?: boolean } | void> {
public async initEventIndex(
userId: string,
deviceId: string,
tokenizerMode?: string,
): Promise<{ wasRecreated?: boolean } | void> {
throw new Error("Unimplemented");
}

View File

@ -28,7 +28,11 @@ export class SeshatIndexManager extends BaseEventIndexManager {
return this.ipc.call("supportsEventIndexing");
}
public async initEventIndex(userId: string, deviceId: string, tokenizerMode?: string): Promise<{ wasRecreated?: boolean } | void> {
public async initEventIndex(
userId: string,
deviceId: string,
tokenizerMode?: string,
): Promise<{ wasRecreated?: boolean } | void> {
return this.ipc.call("initEventIndex", userId, deviceId, tokenizerMode);
}

View File

@ -0,0 +1,33 @@
/*
Copyright 2025 New Vector Ltd.
Copyright 2020, 2021 The Matrix.org Foundation C.I.C.
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 { fireEvent, render, screen } from "jest-matrix-react";
import ConfirmTokenizerChangeDialog from "../../../../../src/async-components/views/dialogs/eventindex/ConfirmTokenizerChangeDialog";
import EventIndexPeg from "../../../../../src/indexing/EventIndexPeg";
import { flushPromises } from "../../../../test-utils";
describe("<ConfirmTokenizerChangeDialog />", () => {
afterEach(() => {
jest.restoreAllMocks();
});
it("deletes the event index and finishes when confirmed", async () => {
const onFinished = jest.fn();
jest.spyOn(EventIndexPeg, "deleteEventIndex").mockResolvedValue(undefined);
render(<ConfirmTokenizerChangeDialog onFinished={onFinished} />);
fireEvent.click(screen.getByRole("button", { name: /ok/i }));
await flushPromises();
expect(EventIndexPeg.deleteEventIndex).toHaveBeenCalled();
expect(onFinished).toHaveBeenCalledWith(true);
});
});

View File

@ -0,0 +1,102 @@
/*
Copyright 2025 New Vector Ltd.
Copyright 2020, 2021 The Matrix.org Foundation C.I.C.
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 { fireEvent, render, screen } from "jest-matrix-react";
import ManageEventIndexDialog from "../../../../../src/async-components/views/dialogs/eventindex/ManageEventIndexDialog";
import Modal from "../../../../../src/Modal";
import EventIndexPeg from "../../../../../src/indexing/EventIndexPeg";
import SettingsStore from "../../../../../src/settings/SettingsStore";
import { SettingLevel } from "../../../../../src/settings/SettingLevel";
import SdkConfig from "../../../../../src/SdkConfig";
import { flushPromises } from "../../../../test-utils";
describe("<ManageEventIndexDialog />", () => {
afterEach(() => {
jest.restoreAllMocks();
});
const mockEventIndex = {
getStats: jest.fn().mockResolvedValue({ size: 1234, eventCount: 12, roomCount: 2 }),
crawlingRooms: jest.fn().mockReturnValue({
crawlingRooms: new Set(["!room1:example.org"]),
totalRooms: new Set(["!room1:example.org", "!room2:example.org"]),
}),
currentRoom: jest.fn().mockReturnValue({ name: "Room A" }),
on: jest.fn(),
removeListener: jest.fn(),
};
function setUpDefaults(tokenizerMode: "ngram" | "language" = "language"): void {
jest.spyOn(SdkConfig, "get").mockReturnValue({ brand: "Element" } as any);
jest.spyOn(EventIndexPeg, "get").mockReturnValue(mockEventIndex as any);
jest.spyOn(SettingsStore, "getValueAt").mockImplementation((_level, settingName): any => {
if (settingName === "tokenizerMode") return tokenizerMode;
if (settingName === "crawlerSleepTime") return 3000;
return undefined;
});
jest.spyOn(SettingsStore, "setValue").mockResolvedValue(undefined as any);
}
it("closes directly when tokenizer mode is unchanged", async () => {
setUpDefaults("language");
const onFinished = jest.fn();
const createDialogSpy = jest.spyOn(Modal, "createDialog").mockReturnValue({} as any);
render(<ManageEventIndexDialog onFinished={onFinished} />);
await flushPromises();
fireEvent.click(screen.getByRole("button", { name: /done/i }));
expect(createDialogSpy).not.toHaveBeenCalled();
expect(onFinished).toHaveBeenCalled();
});
it("opens confirm dialog and saves tokenizer mode when confirmed", async () => {
setUpDefaults("language");
const onFinished = jest.fn();
const setValueSpy = jest.spyOn(SettingsStore, "setValue");
jest.spyOn(Modal, "createDialog").mockImplementation((_Component, props?: any) => {
props?.onFinished(true);
return {} as any;
});
render(<ManageEventIndexDialog onFinished={onFinished} />);
await flushPromises();
fireEvent.change(screen.getByRole("combobox"), { target: { value: "ngram" } });
fireEvent.click(screen.getByRole("button", { name: /done/i }));
await flushPromises();
expect(setValueSpy).toHaveBeenCalledWith("tokenizerMode", null, SettingLevel.DEVICE, "ngram");
expect(onFinished).toHaveBeenCalled();
});
it("opens confirm dialog and reverts tokenizer mode when cancelled", async () => {
setUpDefaults("language");
const onFinished = jest.fn();
const setValueSpy = jest.spyOn(SettingsStore, "setValue");
jest.spyOn(Modal, "createDialog").mockImplementation((_Component, props?: any) => {
props?.onFinished(false);
return {} as any;
});
render(<ManageEventIndexDialog onFinished={onFinished} />);
await flushPromises();
fireEvent.change(screen.getByRole("combobox"), { target: { value: "ngram" } });
fireEvent.click(screen.getByRole("button", { name: /done/i }));
await flushPromises();
expect(setValueSpy).toHaveBeenCalledWith("tokenizerMode", null, SettingLevel.DEVICE, "language");
expect(onFinished).toHaveBeenCalled();
});
});

View File

@ -0,0 +1,19 @@
/*
Copyright 2025 New Vector Ltd.
Copyright 2020, 2021 The Matrix.org Foundation C.I.C.
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 BaseEventIndexManager from "../../../src/indexing/BaseEventIndexManager";
describe("BaseEventIndexManager", () => {
it("initEventIndex throws unimplemented error", async () => {
// BaseEventIndexManager is abstract but has no abstract methods, so we can instantiate a trivial subclass.
class TestManager extends BaseEventIndexManager {}
const mgr = new TestManager();
await expect(mgr.initEventIndex("@user:example.org", "DEVICE", "ngram")).rejects.toThrow("Unimplemented");
});
});

View File

@ -43,11 +43,7 @@ describe("EventIndexPeg", () => {
const peg = new EventIndexPeg();
await peg.initEventIndex();
expect(mockIndexingManager.initEventIndex).toHaveBeenCalledWith(
"@user:example.org",
"DEVICE123",
"ngram",
);
expect(mockIndexingManager.initEventIndex).toHaveBeenCalledWith("@user:example.org", "DEVICE123", "ngram");
});
it("passes language tokenizer mode by default", async () => {

View File

@ -0,0 +1,31 @@
/*
Copyright 2025 New Vector Ltd.
Copyright 2020, 2021 The Matrix.org Foundation C.I.C.
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 { IPCManager } from "../../../../src/vector/platform/IPCManager";
import { SeshatIndexManager } from "../../../../src/vector/platform/SeshatIndexManager";
describe("SeshatIndexManager", () => {
afterEach(() => {
jest.restoreAllMocks();
});
it("passes tokenizerMode to initEventIndex IPC call", async () => {
// IPCManager requires window.electron to exist.
window.electron = {
on: jest.fn(),
send: jest.fn(),
} as unknown as Electron;
const ipcCallSpy = jest.spyOn(IPCManager.prototype, "call").mockResolvedValue(undefined);
const mgr = new SeshatIndexManager();
await mgr.initEventIndex("@user:example.org", "DEVICE123", "ngram");
expect(ipcCallSpy).toHaveBeenCalledWith("initEventIndex", "@user:example.org", "DEVICE123", "ngram");
});
});