1
0
mirror of https://github.com/matrix-org/matrix-react-sdk.git synced 2025-08-09 08:42:50 +03:00

Apply prettier formatting

This commit is contained in:
Michael Weimann
2022-12-12 12:24:14 +01:00
parent 1cac306093
commit 526645c791
1576 changed files with 65385 additions and 62478 deletions

View File

@@ -63,24 +63,15 @@ describe("ContentMessages", () => {
describe("sendStickerContentToRoom", () => {
beforeEach(() => {
mocked(client.sendStickerMessage).mockReturnValue(prom);
mocked(doMaybeLocalRoomAction).mockImplementation(<T>(
roomId: string,
fn: (actualRoomId: string) => Promise<T>,
client?: MatrixClient,
) => {
return fn(roomId);
});
mocked(doMaybeLocalRoomAction).mockImplementation(
<T>(roomId: string, fn: (actualRoomId: string) => Promise<T>, client?: MatrixClient) => {
return fn(roomId);
},
);
});
it("should forward the call to doMaybeLocalRoomAction", async () => {
await contentMessages.sendStickerContentToRoom(
stickerUrl,
roomId,
null,
imageInfo,
text,
client,
);
await contentMessages.sendStickerContentToRoom(stickerUrl, roomId, null, imageInfo, text, client);
expect(client.sendStickerMessage).toHaveBeenCalledWith(roomId, null, stickerUrl, imageInfo, text);
});
});
@@ -88,22 +79,25 @@ describe("ContentMessages", () => {
describe("sendContentToRoom", () => {
const roomId = "!roomId:server";
beforeEach(() => {
Object.defineProperty(global.Image.prototype, 'src', {
Object.defineProperty(global.Image.prototype, "src", {
// Define the property setter
set(src) {
window.setTimeout(() => this.onload());
},
});
Object.defineProperty(global.Image.prototype, 'height', {
get() { return 600; },
Object.defineProperty(global.Image.prototype, "height", {
get() {
return 600;
},
});
Object.defineProperty(global.Image.prototype, 'width', {
get() { return 800; },
Object.defineProperty(global.Image.prototype, "width", {
get() {
return 800;
},
});
mocked(doMaybeLocalRoomAction).mockImplementation(<T>(
roomId: string,
fn: (actualRoomId: string) => Promise<T>,
) => fn(roomId));
mocked(doMaybeLocalRoomAction).mockImplementation(
<T>(roomId: string, fn: (actualRoomId: string) => Promise<T>) => fn(roomId),
);
mocked(BlurhashEncoder.instance.getBlurhash).mockResolvedValue(undefined);
});
@@ -111,34 +105,46 @@ describe("ContentMessages", () => {
mocked(client.uploadContent).mockResolvedValue({ content_uri: "mxc://server/file" });
const file = new File([], "fileName", { type: "image/jpeg" });
await contentMessages.sendContentToRoom(file, roomId, undefined, client, undefined);
expect(client.sendMessage).toHaveBeenCalledWith(roomId, null, expect.objectContaining({
url: "mxc://server/file",
msgtype: "m.image",
}));
expect(client.sendMessage).toHaveBeenCalledWith(
roomId,
null,
expect.objectContaining({
url: "mxc://server/file",
msgtype: "m.image",
}),
);
});
it("should fall back to m.file for invalid image files", async () => {
mocked(client.uploadContent).mockResolvedValue({ content_uri: "mxc://server/file" });
const file = new File([], "fileName", { type: "image/png" });
await contentMessages.sendContentToRoom(file, roomId, undefined, client, undefined);
expect(client.sendMessage).toHaveBeenCalledWith(roomId, null, expect.objectContaining({
url: "mxc://server/file",
msgtype: "m.file",
}));
expect(client.sendMessage).toHaveBeenCalledWith(
roomId,
null,
expect.objectContaining({
url: "mxc://server/file",
msgtype: "m.file",
}),
);
});
it("should use m.video for video files", async () => {
jest.spyOn(document, "createElement").mockImplementation(tagName => {
jest.spyOn(document, "createElement").mockImplementation((tagName) => {
const element = createElement(tagName);
if (tagName === "video") {
(<HTMLVideoElement>element).load = jest.fn();
(<HTMLVideoElement>element).play = () => element.onloadeddata(new Event("loadeddata"));
(<HTMLVideoElement>element).pause = jest.fn();
Object.defineProperty(element, 'videoHeight', {
get() { return 600; },
Object.defineProperty(element, "videoHeight", {
get() {
return 600;
},
});
Object.defineProperty(element, 'videoWidth', {
get() { return 800; },
Object.defineProperty(element, "videoWidth", {
get() {
return 800;
},
});
}
return element;
@@ -147,31 +153,43 @@ describe("ContentMessages", () => {
mocked(client.uploadContent).mockResolvedValue({ content_uri: "mxc://server/file" });
const file = new File([], "fileName", { type: "video/mp4" });
await contentMessages.sendContentToRoom(file, roomId, undefined, client, undefined);
expect(client.sendMessage).toHaveBeenCalledWith(roomId, null, expect.objectContaining({
url: "mxc://server/file",
msgtype: "m.video",
}));
expect(client.sendMessage).toHaveBeenCalledWith(
roomId,
null,
expect.objectContaining({
url: "mxc://server/file",
msgtype: "m.video",
}),
);
});
it("should use m.audio for audio files", async () => {
mocked(client.uploadContent).mockResolvedValue({ content_uri: "mxc://server/file" });
const file = new File([], "fileName", { type: "audio/mp3" });
await contentMessages.sendContentToRoom(file, roomId, undefined, client, undefined);
expect(client.sendMessage).toHaveBeenCalledWith(roomId, null, expect.objectContaining({
url: "mxc://server/file",
msgtype: "m.audio",
}));
expect(client.sendMessage).toHaveBeenCalledWith(
roomId,
null,
expect.objectContaining({
url: "mxc://server/file",
msgtype: "m.audio",
}),
);
});
it("should default to name 'Attachment' if file doesn't have a name", async () => {
mocked(client.uploadContent).mockResolvedValue({ content_uri: "mxc://server/file" });
const file = new File([], "", { type: "text/plain" });
await contentMessages.sendContentToRoom(file, roomId, undefined, client, undefined);
expect(client.sendMessage).toHaveBeenCalledWith(roomId, null, expect.objectContaining({
url: "mxc://server/file",
msgtype: "m.file",
body: "Attachment",
}));
expect(client.sendMessage).toHaveBeenCalledWith(
roomId,
null,
expect.objectContaining({
url: "mxc://server/file",
msgtype: "m.file",
body: "Attachment",
}),
);
});
it("should keep RoomUpload's total and loaded values up to date", async () => {
@@ -196,10 +214,9 @@ describe("ContentMessages", () => {
const roomId = "!roomId:server";
beforeEach(() => {
mocked(doMaybeLocalRoomAction).mockImplementation(<T>(
roomId: string,
fn: (actualRoomId: string) => Promise<T>,
) => fn(roomId));
mocked(doMaybeLocalRoomAction).mockImplementation(
<T>(roomId: string, fn: (actualRoomId: string) => Promise<T>) => fn(roomId),
);
});
it("should return only uploads for the given relation", async () => {
@@ -284,14 +301,19 @@ describe("uploadFile", () => {
const res = await uploadFile(client, "!roomId:server", file, progressHandler);
expect(res.url).toBeFalsy();
expect(res.file).toEqual(expect.objectContaining({
url: "mxc://server/file",
}));
expect(res.file).toEqual(
expect.objectContaining({
url: "mxc://server/file",
}),
);
expect(encrypt.encryptAttachment).toHaveBeenCalled();
expect(client.uploadContent).toHaveBeenCalledWith(expect.any(Blob), expect.objectContaining({
progressHandler,
includeFilename: false,
}));
expect(client.uploadContent).toHaveBeenCalledWith(
expect.any(Blob),
expect.objectContaining({
progressHandler,
includeFilename: false,
}),
);
expect(mocked(client.uploadContent).mock.calls[0][0]).not.toBe(file);
});

View File

@@ -14,15 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { MatrixEvent } from 'matrix-js-sdk/src/matrix';
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
import { DecryptionFailureTracker } from '../src/DecryptionFailureTracker';
import { DecryptionFailureTracker } from "../src/DecryptionFailureTracker";
class MockDecryptionError extends Error {
constructor(code) {
super();
this.code = code || 'MOCK_DECRYPTION_ERROR';
this.code = code || "MOCK_DECRYPTION_ERROR";
}
}
@@ -37,12 +37,15 @@ function createFailedDecryptionEvent() {
return event;
}
describe('DecryptionFailureTracker', function() {
it('tracks a failed decryption for a visible event', function(done) {
describe("DecryptionFailureTracker", function () {
it("tracks a failed decryption for a visible event", function (done) {
const failedDecryptionEvent = createFailedDecryptionEvent();
let count = 0;
const tracker = new DecryptionFailureTracker((total) => count += total, () => "UnknownError");
const tracker = new DecryptionFailureTracker(
(total) => (count += total),
() => "UnknownError",
);
tracker.addVisibleEvent(failedDecryptionEvent);
@@ -55,24 +58,27 @@ describe('DecryptionFailureTracker', function() {
// Immediately track the newest failures
tracker.trackFailures();
expect(count).not.toBe(0, 'should track a failure for an event that failed decryption');
expect(count).not.toBe(0, "should track a failure for an event that failed decryption");
done();
});
it('tracks a failed decryption with expected raw error for a visible event', function(done) {
it("tracks a failed decryption with expected raw error for a visible event", function (done) {
const failedDecryptionEvent = createFailedDecryptionEvent();
let count = 0;
let reportedRawCode = "";
const tracker = new DecryptionFailureTracker((total, errcode, rawCode) => {
count += total;
reportedRawCode = rawCode;
}, () => "UnknownError");
const tracker = new DecryptionFailureTracker(
(total, errcode, rawCode) => {
count += total;
reportedRawCode = rawCode;
},
() => "UnknownError",
);
tracker.addVisibleEvent(failedDecryptionEvent);
const err = new MockDecryptionError('INBOUND_SESSION_MISMATCH_ROOM_ID');
const err = new MockDecryptionError("INBOUND_SESSION_MISMATCH_ROOM_ID");
tracker.eventDecrypted(failedDecryptionEvent, err);
// Pretend "now" is Infinity
@@ -81,17 +87,20 @@ describe('DecryptionFailureTracker', function() {
// Immediately track the newest failures
tracker.trackFailures();
expect(count).not.toBe(0, 'should track a failure for an event that failed decryption');
expect(reportedRawCode).toBe('INBOUND_SESSION_MISMATCH_ROOM_ID', 'Should add the rawCode to the event context');
expect(count).not.toBe(0, "should track a failure for an event that failed decryption");
expect(reportedRawCode).toBe("INBOUND_SESSION_MISMATCH_ROOM_ID", "Should add the rawCode to the event context");
done();
});
it('tracks a failed decryption for an event that becomes visible later', function(done) {
it("tracks a failed decryption for an event that becomes visible later", function (done) {
const failedDecryptionEvent = createFailedDecryptionEvent();
let count = 0;
const tracker = new DecryptionFailureTracker((total) => count += total, () => "UnknownError");
const tracker = new DecryptionFailureTracker(
(total) => (count += total),
() => "UnknownError",
);
const err = new MockDecryptionError();
tracker.eventDecrypted(failedDecryptionEvent, err);
@@ -104,16 +113,19 @@ describe('DecryptionFailureTracker', function() {
// Immediately track the newest failures
tracker.trackFailures();
expect(count).not.toBe(0, 'should track a failure for an event that failed decryption');
expect(count).not.toBe(0, "should track a failure for an event that failed decryption");
done();
});
it('does not track a failed decryption for an event that never becomes visible', function(done) {
it("does not track a failed decryption for an event that never becomes visible", function (done) {
const failedDecryptionEvent = createFailedDecryptionEvent();
let count = 0;
const tracker = new DecryptionFailureTracker((total) => count += total, () => "UnknownError");
const tracker = new DecryptionFailureTracker(
(total) => (count += total),
() => "UnknownError",
);
const err = new MockDecryptionError();
tracker.eventDecrypted(failedDecryptionEvent, err);
@@ -124,16 +136,19 @@ describe('DecryptionFailureTracker', function() {
// Immediately track the newest failures
tracker.trackFailures();
expect(count).toBe(0, 'should not track a failure for an event that never became visible');
expect(count).toBe(0, "should not track a failure for an event that never became visible");
done();
});
it('does not track a failed decryption where the event is subsequently successfully decrypted', (done) => {
it("does not track a failed decryption where the event is subsequently successfully decrypted", (done) => {
const decryptedEvent = createFailedDecryptionEvent();
const tracker = new DecryptionFailureTracker((total) => {
expect(true).toBe(false, 'should not track an event that has since been decrypted correctly');
}, () => "UnknownError");
const tracker = new DecryptionFailureTracker(
(total) => {
expect(true).toBe(false, "should not track an event that has since been decrypted correctly");
},
() => "UnknownError",
);
tracker.addVisibleEvent(decryptedEvent);
@@ -152,36 +167,45 @@ describe('DecryptionFailureTracker', function() {
done();
});
it('does not track a failed decryption where the event is subsequently successfully decrypted ' +
'and later becomes visible', (done) => {
const decryptedEvent = createFailedDecryptionEvent();
const tracker = new DecryptionFailureTracker((total) => {
expect(true).toBe(false, 'should not track an event that has since been decrypted correctly');
}, () => "UnknownError");
it(
"does not track a failed decryption where the event is subsequently successfully decrypted " +
"and later becomes visible",
(done) => {
const decryptedEvent = createFailedDecryptionEvent();
const tracker = new DecryptionFailureTracker(
(total) => {
expect(true).toBe(false, "should not track an event that has since been decrypted correctly");
},
() => "UnknownError",
);
const err = new MockDecryptionError();
tracker.eventDecrypted(decryptedEvent, err);
const err = new MockDecryptionError();
tracker.eventDecrypted(decryptedEvent, err);
// Indicate successful decryption: clear data can be anything where the msgtype is not m.bad.encrypted
decryptedEvent.setClearData({});
tracker.eventDecrypted(decryptedEvent, null);
// Indicate successful decryption: clear data can be anything where the msgtype is not m.bad.encrypted
decryptedEvent.setClearData({});
tracker.eventDecrypted(decryptedEvent, null);
tracker.addVisibleEvent(decryptedEvent);
tracker.addVisibleEvent(decryptedEvent);
// Pretend "now" is Infinity
tracker.checkFailures(Infinity);
// Pretend "now" is Infinity
tracker.checkFailures(Infinity);
// Immediately track the newest failures
tracker.trackFailures();
done();
});
// Immediately track the newest failures
tracker.trackFailures();
done();
},
);
it('only tracks a single failure per event, despite multiple failed decryptions for multiple events', (done) => {
it("only tracks a single failure per event, despite multiple failed decryptions for multiple events", (done) => {
const decryptedEvent = createFailedDecryptionEvent();
const decryptedEvent2 = createFailedDecryptionEvent();
let count = 0;
const tracker = new DecryptionFailureTracker((total) => count += total, () => "UnknownError");
const tracker = new DecryptionFailureTracker(
(total) => (count += total),
() => "UnknownError",
);
tracker.addVisibleEvent(decryptedEvent);
@@ -206,16 +230,19 @@ describe('DecryptionFailureTracker', function() {
tracker.trackFailures();
tracker.trackFailures();
expect(count).toBe(2, count + ' failures tracked, should only track a single failure per event');
expect(count).toBe(2, count + " failures tracked, should only track a single failure per event");
done();
});
it('should not track a failure for an event that was tracked previously', (done) => {
it("should not track a failure for an event that was tracked previously", (done) => {
const decryptedEvent = createFailedDecryptionEvent();
let count = 0;
const tracker = new DecryptionFailureTracker((total) => count += total, () => "UnknownError");
const tracker = new DecryptionFailureTracker(
(total) => (count += total),
() => "UnknownError",
);
tracker.addVisibleEvent(decryptedEvent);
@@ -233,19 +260,22 @@ describe('DecryptionFailureTracker', function() {
tracker.trackFailures();
expect(count).toBe(1, 'should only track a single failure per event');
expect(count).toBe(1, "should only track a single failure per event");
done();
});
xit('should not track a failure for an event that was tracked in a previous session', (done) => {
xit("should not track a failure for an event that was tracked in a previous session", (done) => {
// This test uses localStorage, clear it beforehand
localStorage.clear();
const decryptedEvent = createFailedDecryptionEvent();
let count = 0;
const tracker = new DecryptionFailureTracker((total) => count += total, () => "UnknownError");
const tracker = new DecryptionFailureTracker(
(total) => (count += total),
() => "UnknownError",
);
tracker.addVisibleEvent(decryptedEvent);
@@ -260,7 +290,10 @@ describe('DecryptionFailureTracker', function() {
tracker.trackFailures();
// Simulate the browser refreshing by destroying tracker and creating a new tracker
const secondTracker = new DecryptionFailureTracker((total) => count += total, () => "UnknownError");
const secondTracker = new DecryptionFailureTracker(
(total) => (count += total),
() => "UnknownError",
);
secondTracker.addVisibleEvent(decryptedEvent);
@@ -270,24 +303,24 @@ describe('DecryptionFailureTracker', function() {
secondTracker.checkFailures(Infinity);
secondTracker.trackFailures();
expect(count).toBe(1, count + ' failures tracked, should only track a single failure per event');
expect(count).toBe(1, count + " failures tracked, should only track a single failure per event");
done();
});
it('should count different error codes separately for multiple failures with different error codes', () => {
it("should count different error codes separately for multiple failures with different error codes", () => {
const counts = {};
const tracker = new DecryptionFailureTracker(
(total, errorCode) => counts[errorCode] = (counts[errorCode] || 0) + total,
(error) => error === "UnknownError" ? "UnknownError" : "OlmKeysNotSentError",
(total, errorCode) => (counts[errorCode] = (counts[errorCode] || 0) + total),
(error) => (error === "UnknownError" ? "UnknownError" : "OlmKeysNotSentError"),
);
const decryptedEvent1 = createFailedDecryptionEvent();
const decryptedEvent2 = createFailedDecryptionEvent();
const decryptedEvent3 = createFailedDecryptionEvent();
const error1 = new MockDecryptionError('UnknownError');
const error2 = new MockDecryptionError('OlmKeysNotSentError');
const error1 = new MockDecryptionError("UnknownError");
const error2 = new MockDecryptionError("OlmKeysNotSentError");
tracker.addVisibleEvent(decryptedEvent1);
tracker.addVisibleEvent(decryptedEvent2);
@@ -305,23 +338,23 @@ describe('DecryptionFailureTracker', function() {
tracker.trackFailures();
//expect(counts['UnknownError']).toBe(1, 'should track one UnknownError');
expect(counts['OlmKeysNotSentError']).toBe(2, 'should track two OlmKeysNotSentError');
expect(counts["OlmKeysNotSentError"]).toBe(2, "should track two OlmKeysNotSentError");
});
it('should aggregate error codes correctly', () => {
it("should aggregate error codes correctly", () => {
const counts = {};
const tracker = new DecryptionFailureTracker(
(total, errorCode) => counts[errorCode] = (counts[errorCode] || 0) + total,
(errorCode) => 'OlmUnspecifiedError',
(total, errorCode) => (counts[errorCode] = (counts[errorCode] || 0) + total),
(errorCode) => "OlmUnspecifiedError",
);
const decryptedEvent1 = createFailedDecryptionEvent();
const decryptedEvent2 = createFailedDecryptionEvent();
const decryptedEvent3 = createFailedDecryptionEvent();
const error1 = new MockDecryptionError('ERROR_CODE_1');
const error2 = new MockDecryptionError('ERROR_CODE_2');
const error3 = new MockDecryptionError('ERROR_CODE_3');
const error1 = new MockDecryptionError("ERROR_CODE_1");
const error2 = new MockDecryptionError("ERROR_CODE_2");
const error3 = new MockDecryptionError("ERROR_CODE_3");
tracker.addVisibleEvent(decryptedEvent1);
tracker.addVisibleEvent(decryptedEvent2);
@@ -336,20 +369,22 @@ describe('DecryptionFailureTracker', function() {
tracker.trackFailures();
expect(counts['OlmUnspecifiedError'])
.toBe(3, 'should track three OlmUnspecifiedError, got ' + counts['OlmUnspecifiedError']);
expect(counts["OlmUnspecifiedError"]).toBe(
3,
"should track three OlmUnspecifiedError, got " + counts["OlmUnspecifiedError"],
);
});
it('should remap error codes correctly', () => {
it("should remap error codes correctly", () => {
const counts = {};
const tracker = new DecryptionFailureTracker(
(total, errorCode) => counts[errorCode] = (counts[errorCode] || 0) + total,
(errorCode) => Array.from(errorCode).reverse().join(''),
(total, errorCode) => (counts[errorCode] = (counts[errorCode] || 0) + total),
(errorCode) => Array.from(errorCode).reverse().join(""),
);
const decryptedEvent = createFailedDecryptionEvent();
const error = new MockDecryptionError('ERROR_CODE_1');
const error = new MockDecryptionError("ERROR_CODE_1");
tracker.addVisibleEvent(decryptedEvent);
@@ -360,7 +395,6 @@ describe('DecryptionFailureTracker', function() {
tracker.trackFailures();
expect(counts['1_EDOC_RORRE'])
.toBe(1, 'should track remapped error code');
expect(counts["1_EDOC_RORRE"]).toBe(1, "should track remapped error code");
});
});

View File

@@ -1,4 +1,3 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
@@ -46,34 +45,35 @@ jest.mock("../src/dispatcher/dispatcher", () => ({
}));
jest.mock("../src/SecurityManager", () => ({
isSecretStorageBeingAccessed: jest.fn(), accessSecretStorage: jest.fn(),
isSecretStorageBeingAccessed: jest.fn(),
accessSecretStorage: jest.fn(),
}));
jest.mock("../src/utils/device/snoozeBulkUnverifiedDeviceReminder", () => ({
isBulkUnverifiedDeviceReminderSnoozed: jest.fn(),
}));
const userId = '@user:server';
const deviceId = 'my-device-id';
const userId = "@user:server";
const deviceId = "my-device-id";
const mockDispatcher = mocked(dis);
const flushPromises = async () => await new Promise(process.nextTick);
describe('DeviceListener', () => {
describe("DeviceListener", () => {
let mockClient: Mocked<MatrixClient> | undefined;
// spy on various toasts' hide and show functions
// easier than mocking
jest.spyOn(SetupEncryptionToast, 'showToast');
jest.spyOn(SetupEncryptionToast, 'hideToast');
jest.spyOn(BulkUnverifiedSessionsToast, 'showToast');
jest.spyOn(BulkUnverifiedSessionsToast, 'hideToast');
jest.spyOn(UnverifiedSessionToast, 'showToast');
jest.spyOn(UnverifiedSessionToast, 'hideToast');
jest.spyOn(SetupEncryptionToast, "showToast");
jest.spyOn(SetupEncryptionToast, "hideToast");
jest.spyOn(BulkUnverifiedSessionsToast, "showToast");
jest.spyOn(BulkUnverifiedSessionsToast, "hideToast");
jest.spyOn(UnverifiedSessionToast, "showToast");
jest.spyOn(UnverifiedSessionToast, "hideToast");
beforeEach(() => {
jest.resetAllMocks();
mockPlatformPeg({
getAppVersion: jest.fn().mockResolvedValue('1.2.3'),
getAppVersion: jest.fn().mockResolvedValue("1.2.3"),
});
mockClient = getMockClientWithEventEmitter({
isGuest: jest.fn(),
@@ -98,8 +98,8 @@ describe('DeviceListener', () => {
getAccountData: jest.fn(),
checkDeviceTrust: jest.fn().mockReturnValue(new DeviceTrustLevel(false, false, false, false)),
});
jest.spyOn(MatrixClientPeg, 'get').mockReturnValue(mockClient);
jest.spyOn(SettingsStore, 'getValue').mockReturnValue(false);
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient);
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
mocked(isBulkUnverifiedDeviceReminderSnoozed).mockClear().mockReturnValue(false);
});
@@ -110,51 +110,46 @@ describe('DeviceListener', () => {
return instance;
};
describe('client information', () => {
it('watches device client information setting', async () => {
const watchSettingSpy = jest.spyOn(SettingsStore, 'watchSetting');
const unwatchSettingSpy = jest.spyOn(SettingsStore, 'unwatchSetting');
describe("client information", () => {
it("watches device client information setting", async () => {
const watchSettingSpy = jest.spyOn(SettingsStore, "watchSetting");
const unwatchSettingSpy = jest.spyOn(SettingsStore, "unwatchSetting");
const deviceListener = await createAndStart();
expect(watchSettingSpy).toHaveBeenCalledWith(
'deviceClientInformationOptIn', null, expect.any(Function),
);
expect(watchSettingSpy).toHaveBeenCalledWith("deviceClientInformationOptIn", null, expect.any(Function));
deviceListener.stop();
expect(unwatchSettingSpy).toHaveBeenCalled();
});
describe('when device client information feature is enabled', () => {
describe("when device client information feature is enabled", () => {
beforeEach(() => {
jest.spyOn(SettingsStore, 'getValue').mockImplementation(
settingName => settingName === 'deviceClientInformationOptIn',
jest.spyOn(SettingsStore, "getValue").mockImplementation(
(settingName) => settingName === "deviceClientInformationOptIn",
);
});
it('saves client information on start', async () => {
it("saves client information on start", async () => {
await createAndStart();
expect(mockClient!.setAccountData).toHaveBeenCalledWith(
`io.element.matrix_client_information.${deviceId}`,
{ name: 'Element', url: 'localhost', version: '1.2.3' },
{ name: "Element", url: "localhost", version: "1.2.3" },
);
});
it('catches error and logs when saving client information fails', async () => {
const errorLogSpy = jest.spyOn(logger, 'error');
const error = new Error('oups');
it("catches error and logs when saving client information fails", async () => {
const errorLogSpy = jest.spyOn(logger, "error");
const error = new Error("oups");
mockClient!.setAccountData.mockRejectedValue(error);
// doesn't throw
await createAndStart();
expect(errorLogSpy).toHaveBeenCalledWith(
'Failed to update client information',
error,
);
expect(errorLogSpy).toHaveBeenCalledWith("Failed to update client information", error);
});
it('saves client information on logged in action', async () => {
it("saves client information on logged in action", async () => {
const instance = await createAndStart();
mockClient!.setAccountData.mockClear();
@@ -166,29 +161,30 @@ describe('DeviceListener', () => {
expect(mockClient!.setAccountData).toHaveBeenCalledWith(
`io.element.matrix_client_information.${deviceId}`,
{ name: 'Element', url: 'localhost', version: '1.2.3' },
{ name: "Element", url: "localhost", version: "1.2.3" },
);
});
});
describe('when device client information feature is disabled', () => {
const clientInfoEvent = new MatrixEvent({ type: `io.element.matrix_client_information.${deviceId}`,
content: { name: 'hello' },
describe("when device client information feature is disabled", () => {
const clientInfoEvent = new MatrixEvent({
type: `io.element.matrix_client_information.${deviceId}`,
content: { name: "hello" },
});
const emptyClientInfoEvent = new MatrixEvent({ type: `io.element.matrix_client_information.${deviceId}` });
beforeEach(() => {
jest.spyOn(SettingsStore, 'getValue').mockReturnValue(false);
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
mockClient!.getAccountData.mockReturnValue(undefined);
});
it('does not save client information on start', async () => {
it("does not save client information on start", async () => {
await createAndStart();
expect(mockClient!.setAccountData).not.toHaveBeenCalled();
});
it('removes client information on start if it exists', async () => {
it("removes client information on start if it exists", async () => {
mockClient!.getAccountData.mockReturnValue(clientInfoEvent);
await createAndStart();
@@ -198,14 +194,14 @@ describe('DeviceListener', () => {
);
});
it('does not try to remove client info event that are already empty', async () => {
it("does not try to remove client info event that are already empty", async () => {
mockClient!.getAccountData.mockReturnValue(emptyClientInfoEvent);
await createAndStart();
expect(mockClient!.setAccountData).not.toHaveBeenCalled();
});
it('does not save client information on logged in action', async () => {
it("does not save client information on logged in action", async () => {
const instance = await createAndStart();
// @ts-ignore calling private function
@@ -216,51 +212,48 @@ describe('DeviceListener', () => {
expect(mockClient!.setAccountData).not.toHaveBeenCalled();
});
it('saves client information after setting is enabled', async () => {
const watchSettingSpy = jest.spyOn(SettingsStore, 'watchSetting');
it("saves client information after setting is enabled", async () => {
const watchSettingSpy = jest.spyOn(SettingsStore, "watchSetting");
await createAndStart();
const [settingName, roomId, callback] = watchSettingSpy.mock.calls[0];
expect(settingName).toEqual('deviceClientInformationOptIn');
expect(settingName).toEqual("deviceClientInformationOptIn");
expect(roomId).toBeNull();
callback('deviceClientInformationOptIn', null, SettingLevel.DEVICE, SettingLevel.DEVICE, true);
callback("deviceClientInformationOptIn", null, SettingLevel.DEVICE, SettingLevel.DEVICE, true);
await flushPromises();
expect(mockClient!.setAccountData).toHaveBeenCalledWith(
`io.element.matrix_client_information.${deviceId}`,
{ name: 'Element', url: 'localhost', version: '1.2.3' },
{ name: "Element", url: "localhost", version: "1.2.3" },
);
});
});
});
describe('recheck', () => {
it('does nothing when cross signing feature is not supported', async () => {
describe("recheck", () => {
it("does nothing when cross signing feature is not supported", async () => {
mockClient!.doesServerSupportUnstableFeature.mockResolvedValue(false);
await createAndStart();
expect(mockClient!.isCrossSigningReady).not.toHaveBeenCalled();
});
it('does nothing when crypto is not enabled', async () => {
it("does nothing when crypto is not enabled", async () => {
mockClient!.isCryptoEnabled.mockReturnValue(false);
await createAndStart();
expect(mockClient!.isCrossSigningReady).not.toHaveBeenCalled();
});
it('does nothing when initial sync is not complete', async () => {
it("does nothing when initial sync is not complete", async () => {
mockClient!.isInitialSyncComplete.mockReturnValue(false);
await createAndStart();
expect(mockClient!.isCrossSigningReady).not.toHaveBeenCalled();
});
describe('set up encryption', () => {
const rooms = [
{ roomId: '!room1' },
{ roomId: '!room2' },
] as unknown as Room[];
describe("set up encryption", () => {
const rooms = [{ roomId: "!room1" }, { roomId: "!room2" }] as unknown as Room[];
beforeEach(() => {
mockClient!.isCrossSigningReady.mockResolvedValue(false);
@@ -269,21 +262,21 @@ describe('DeviceListener', () => {
mockClient!.isRoomEncrypted.mockReturnValue(true);
});
it('hides setup encryption toast when cross signing and secret storage are ready', async () => {
it("hides setup encryption toast when cross signing and secret storage are ready", async () => {
mockClient!.isCrossSigningReady.mockResolvedValue(true);
mockClient!.isSecretStorageReady.mockResolvedValue(true);
await createAndStart();
expect(SetupEncryptionToast.hideToast).toHaveBeenCalled();
});
it('hides setup encryption toast when it is dismissed', async () => {
it("hides setup encryption toast when it is dismissed", async () => {
const instance = await createAndStart();
instance.dismissEncryptionSetup();
await flushPromises();
expect(SetupEncryptionToast.hideToast).toHaveBeenCalled();
});
it('does not do any checks or show any toasts when secret storage is being accessed', async () => {
it("does not do any checks or show any toasts when secret storage is being accessed", async () => {
mocked(isSecretStorageBeingAccessed).mockReturnValue(true);
await createAndStart();
@@ -291,7 +284,7 @@ describe('DeviceListener', () => {
expect(SetupEncryptionToast.showToast).not.toHaveBeenCalled();
});
it('does not do any checks or show any toasts when no rooms are encrypted', async () => {
it("does not do any checks or show any toasts when no rooms are encrypted", async () => {
mockClient!.isRoomEncrypted.mockReturnValue(false);
await createAndStart();
@@ -299,21 +292,22 @@ describe('DeviceListener', () => {
expect(SetupEncryptionToast.showToast).not.toHaveBeenCalled();
});
describe('when user does not have a cross signing id on this device', () => {
describe("when user does not have a cross signing id on this device", () => {
beforeEach(() => {
mockClient!.getCrossSigningId.mockReturnValue(null);
});
it('shows verify session toast when account has cross signing', async () => {
it("shows verify session toast when account has cross signing", async () => {
mockClient!.getStoredCrossSigningForUser.mockReturnValue(new CrossSigningInfo(userId));
await createAndStart();
expect(mockClient!.downloadKeys).toHaveBeenCalled();
expect(SetupEncryptionToast.showToast).toHaveBeenCalledWith(
SetupEncryptionToast.Kind.VERIFY_THIS_SESSION);
SetupEncryptionToast.Kind.VERIFY_THIS_SESSION,
);
});
it('checks key backup status when when account has cross signing', async () => {
it("checks key backup status when when account has cross signing", async () => {
mockClient!.getCrossSigningId.mockReturnValue(null);
mockClient!.getStoredCrossSigningForUser.mockReturnValue(new CrossSigningInfo(userId));
await createAndStart();
@@ -322,31 +316,32 @@ describe('DeviceListener', () => {
});
});
describe('when user does have a cross signing id on this device', () => {
describe("when user does have a cross signing id on this device", () => {
beforeEach(() => {
mockClient!.getCrossSigningId.mockReturnValue('abc');
mockClient!.getCrossSigningId.mockReturnValue("abc");
});
it('shows upgrade encryption toast when user has a key backup available', async () => {
it("shows upgrade encryption toast when user has a key backup available", async () => {
// non falsy response
mockClient!.getKeyBackupVersion.mockResolvedValue({} as unknown as IKeyBackupInfo);
await createAndStart();
expect(SetupEncryptionToast.showToast).toHaveBeenCalledWith(
SetupEncryptionToast.Kind.UPGRADE_ENCRYPTION);
SetupEncryptionToast.Kind.UPGRADE_ENCRYPTION,
);
});
});
});
describe('key backup status', () => {
it('checks keybackup status when cross signing and secret storage are ready', async () => {
describe("key backup status", () => {
it("checks keybackup status when cross signing and secret storage are ready", async () => {
// default mocks set cross signing and secret storage to ready
await createAndStart();
expect(mockClient!.getKeyBackupEnabled).toHaveBeenCalled();
expect(mockDispatcher.dispatch).not.toHaveBeenCalled();
});
it('checks keybackup status when setup encryption toast has been dismissed', async () => {
it("checks keybackup status when setup encryption toast has been dismissed", async () => {
mockClient!.isCrossSigningReady.mockResolvedValue(false);
const instance = await createAndStart();
@@ -356,20 +351,20 @@ describe('DeviceListener', () => {
expect(mockClient!.getKeyBackupEnabled).toHaveBeenCalled();
});
it('does not dispatch keybackup event when key backup check is not finished', async () => {
it("does not dispatch keybackup event when key backup check is not finished", async () => {
// returns null when key backup status hasn't finished being checked
mockClient!.getKeyBackupEnabled.mockReturnValue(null);
await createAndStart();
expect(mockDispatcher.dispatch).not.toHaveBeenCalled();
});
it('dispatches keybackup event when key backup is not enabled', async () => {
it("dispatches keybackup event when key backup is not enabled", async () => {
mockClient!.getKeyBackupEnabled.mockReturnValue(false);
await createAndStart();
expect(mockDispatcher.dispatch).toHaveBeenCalledWith({ action: Action.ReportKeyBackupNotEnabled });
});
it('does not check key backup status again after check is complete', async () => {
it("does not check key backup status again after check is complete", async () => {
mockClient!.getKeyBackupEnabled.mockReturnValue(null);
const instance = await createAndStart();
expect(mockClient!.getKeyBackupEnabled).toHaveBeenCalled();
@@ -390,43 +385,41 @@ describe('DeviceListener', () => {
});
});
describe('unverified sessions toasts', () => {
describe("unverified sessions toasts", () => {
const currentDevice = new DeviceInfo(deviceId);
const device2 = new DeviceInfo('d2');
const device3 = new DeviceInfo('d3');
const device2 = new DeviceInfo("d2");
const device3 = new DeviceInfo("d3");
const deviceTrustVerified = new DeviceTrustLevel(true, false, false, false);
const deviceTrustUnverified = new DeviceTrustLevel(false, false, false, false);
beforeEach(() => {
mockClient!.isCrossSigningReady.mockResolvedValue(true);
mockClient!.getStoredDevicesForUser.mockReturnValue([
currentDevice, device2, device3,
]);
mockClient!.getStoredDevicesForUser.mockReturnValue([currentDevice, device2, device3]);
// all devices verified by default
mockClient!.checkDeviceTrust.mockReturnValue(deviceTrustVerified);
mockClient!.deviceId = currentDevice.deviceId;
jest.spyOn(SettingsStore, 'getValue').mockImplementation(
settingName => settingName === UIFeature.BulkUnverifiedSessionsReminder,
jest.spyOn(SettingsStore, "getValue").mockImplementation(
(settingName) => settingName === UIFeature.BulkUnverifiedSessionsReminder,
);
});
describe('bulk unverified sessions toasts', () => {
it('hides toast when cross signing is not ready', async () => {
describe("bulk unverified sessions toasts", () => {
it("hides toast when cross signing is not ready", async () => {
mockClient!.isCrossSigningReady.mockResolvedValue(false);
await createAndStart();
expect(BulkUnverifiedSessionsToast.hideToast).toHaveBeenCalled();
expect(BulkUnverifiedSessionsToast.showToast).not.toHaveBeenCalled();
});
it('hides toast when all devices at app start are verified', async () => {
it("hides toast when all devices at app start are verified", async () => {
await createAndStart();
expect(BulkUnverifiedSessionsToast.hideToast).toHaveBeenCalled();
expect(BulkUnverifiedSessionsToast.showToast).not.toHaveBeenCalled();
});
it('hides toast when feature is disabled', async () => {
it("hides toast when feature is disabled", async () => {
// BulkUnverifiedSessionsReminder set to false
jest.spyOn(SettingsStore, 'getValue').mockReturnValue(false);
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
// currentDevice, device2 are verified, device3 is unverified
// ie if reminder was enabled it should be shown
mockClient!.checkDeviceTrust.mockImplementation((_userId, deviceId) => {
@@ -442,7 +435,7 @@ describe('DeviceListener', () => {
expect(BulkUnverifiedSessionsToast.hideToast).toHaveBeenCalled();
});
it('hides toast when current device is unverified', async () => {
it("hides toast when current device is unverified", async () => {
// device2 verified, current and device3 unverified
mockClient!.checkDeviceTrust.mockImplementation((_userId, deviceId) => {
switch (deviceId) {
@@ -457,7 +450,7 @@ describe('DeviceListener', () => {
expect(BulkUnverifiedSessionsToast.showToast).not.toHaveBeenCalled();
});
it('hides toast when reminder is snoozed', async () => {
it("hides toast when reminder is snoozed", async () => {
mocked(isBulkUnverifiedDeviceReminderSnoozed).mockReturnValue(true);
// currentDevice, device2 are verified, device3 is unverified
mockClient!.checkDeviceTrust.mockImplementation((_userId, deviceId) => {
@@ -474,7 +467,7 @@ describe('DeviceListener', () => {
expect(BulkUnverifiedSessionsToast.hideToast).toHaveBeenCalled();
});
it('shows toast with unverified devices at app start', async () => {
it("shows toast with unverified devices at app start", async () => {
// currentDevice, device2 are verified, device3 is unverified
mockClient!.checkDeviceTrust.mockImplementation((_userId, deviceId) => {
switch (deviceId) {
@@ -492,7 +485,7 @@ describe('DeviceListener', () => {
expect(BulkUnverifiedSessionsToast.hideToast).not.toHaveBeenCalled();
});
it('hides toast when unverified sessions at app start have been dismissed', async () => {
it("hides toast when unverified sessions at app start have been dismissed", async () => {
// currentDevice, device2 are verified, device3 is unverified
mockClient!.checkDeviceTrust.mockImplementation((_userId, deviceId) => {
switch (deviceId) {
@@ -514,7 +507,7 @@ describe('DeviceListener', () => {
expect(BulkUnverifiedSessionsToast.hideToast).toHaveBeenCalled();
});
it('hides toast when unverified sessions are added after app start', async () => {
it("hides toast when unverified sessions are added after app start", async () => {
// currentDevice, device2 are verified, device3 is unverified
mockClient!.checkDeviceTrust.mockImplementation((_userId, deviceId) => {
switch (deviceId) {
@@ -525,17 +518,13 @@ describe('DeviceListener', () => {
return deviceTrustUnverified;
}
});
mockClient!.getStoredDevicesForUser.mockReturnValue([
currentDevice, device2,
]);
mockClient!.getStoredDevicesForUser.mockReturnValue([currentDevice, device2]);
await createAndStart();
expect(BulkUnverifiedSessionsToast.hideToast).toHaveBeenCalled();
// add an unverified device
mockClient!.getStoredDevicesForUser.mockReturnValue([
currentDevice, device2, device3,
]);
mockClient!.getStoredDevicesForUser.mockReturnValue([currentDevice, device2, device3]);
// trigger a recheck
mockClient!.emit(CryptoEvent.DevicesUpdated, [userId], false);
await flushPromises();

View File

@@ -14,13 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
// eslint-disable-next-line deprecate/import
import { mount } from 'enzyme';
import { mocked } from 'jest-mock';
import { mount } from "enzyme";
import { mocked } from "jest-mock";
import { topicToHtml } from '../src/HtmlUtils';
import SettingsStore from '../src/settings/SettingsStore';
import { topicToHtml } from "../src/HtmlUtils";
import SettingsStore from "../src/settings/SettingsStore";
jest.mock("../src/settings/SettingsStore");
@@ -30,38 +30,39 @@ const enableHtmlTopicFeature = () => {
});
};
describe('HtmlUtils', () => {
it('converts plain text topic to HTML', () => {
const component = mount(<div>{ topicToHtml("pizza", null, null, false) }</div>);
describe("HtmlUtils", () => {
it("converts plain text topic to HTML", () => {
const component = mount(<div>{topicToHtml("pizza", null, null, false)}</div>);
const wrapper = component.render();
expect(wrapper.children().first().html()).toEqual("pizza");
});
it('converts plain text topic with emoji to HTML', () => {
const component = mount(<div>{ topicToHtml("pizza 🍕", null, null, false) }</div>);
it("converts plain text topic with emoji to HTML", () => {
const component = mount(<div>{topicToHtml("pizza 🍕", null, null, false)}</div>);
const wrapper = component.render();
expect(wrapper.children().first().html()).toEqual("pizza <span class=\"mx_Emoji\" title=\":pizza:\">🍕</span>");
expect(wrapper.children().first().html()).toEqual('pizza <span class="mx_Emoji" title=":pizza:">🍕</span>');
});
it('converts literal HTML topic to HTML', async () => {
it("converts literal HTML topic to HTML", async () => {
enableHtmlTopicFeature();
const component = mount(<div>{ topicToHtml("<b>pizza</b>", null, null, false) }</div>);
const component = mount(<div>{topicToHtml("<b>pizza</b>", null, null, false)}</div>);
const wrapper = component.render();
expect(wrapper.children().first().html()).toEqual("&lt;b&gt;pizza&lt;/b&gt;");
});
it('converts true HTML topic to HTML', async () => {
it("converts true HTML topic to HTML", async () => {
enableHtmlTopicFeature();
const component = mount(<div>{ topicToHtml("**pizza**", "<b>pizza</b>", null, false) }</div>);
const component = mount(<div>{topicToHtml("**pizza**", "<b>pizza</b>", null, false)}</div>);
const wrapper = component.render();
expect(wrapper.children().first().html()).toEqual("<b>pizza</b>");
});
it('converts true HTML topic with emoji to HTML', async () => {
it("converts true HTML topic with emoji to HTML", async () => {
enableHtmlTopicFeature();
const component = mount(<div>{ topicToHtml("**pizza** 🍕", "<b>pizza</b> 🍕", null, false) }</div>);
const component = mount(<div>{topicToHtml("**pizza** 🍕", "<b>pizza</b> 🍕", null, false)}</div>);
const wrapper = component.render();
expect(wrapper.children().first().html())
.toEqual("<b>pizza</b> <span class=\"mx_Emoji\" title=\":pizza:\">🍕</span>");
expect(wrapper.children().first().html()).toEqual(
'<b>pizza</b> <span class="mx_Emoji" title=":pizza:">🍕</span>',
);
});
});

View File

@@ -14,14 +14,17 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { isKeyComboMatch, KeyCombo } from '../src/KeyBindingsManager';
import { isKeyComboMatch, KeyCombo } from "../src/KeyBindingsManager";
function mockKeyEvent(key: string, modifiers?: {
ctrlKey?: boolean;
altKey?: boolean;
shiftKey?: boolean;
metaKey?: boolean;
}): KeyboardEvent {
function mockKeyEvent(
key: string,
modifiers?: {
ctrlKey?: boolean;
altKey?: boolean;
shiftKey?: boolean;
metaKey?: boolean;
},
): KeyboardEvent {
return {
key,
ctrlKey: modifiers?.ctrlKey ?? false,
@@ -31,121 +34,140 @@ function mockKeyEvent(key: string, modifiers?: {
} as KeyboardEvent;
}
describe('KeyBindingsManager', () => {
it('should match basic key combo', () => {
describe("KeyBindingsManager", () => {
it("should match basic key combo", () => {
const combo1: KeyCombo = {
key: 'k',
key: "k",
};
expect(isKeyComboMatch(mockKeyEvent('k'), combo1, false)).toBe(true);
expect(isKeyComboMatch(mockKeyEvent('n'), combo1, false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent("k"), combo1, false)).toBe(true);
expect(isKeyComboMatch(mockKeyEvent("n"), combo1, false)).toBe(false);
});
it('should match key + modifier key combo', () => {
it("should match key + modifier key combo", () => {
const combo: KeyCombo = {
key: 'k',
key: "k",
ctrlKey: true,
};
expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true }), combo, false)).toBe(true);
expect(isKeyComboMatch(mockKeyEvent('n', { ctrlKey: true }), combo, false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent('k'), combo, false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent('k', { shiftKey: true }), combo, false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent('k', { shiftKey: true, metaKey: true }), combo, false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent("k", { ctrlKey: true }), combo, false)).toBe(true);
expect(isKeyComboMatch(mockKeyEvent("n", { ctrlKey: true }), combo, false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent("k"), combo, false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent("k", { shiftKey: true }), combo, false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent("k", { shiftKey: true, metaKey: true }), combo, false)).toBe(false);
const combo2: KeyCombo = {
key: 'k',
key: "k",
metaKey: true,
};
expect(isKeyComboMatch(mockKeyEvent('k', { metaKey: true }), combo2, false)).toBe(true);
expect(isKeyComboMatch(mockKeyEvent('n', { metaKey: true }), combo2, false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent('k'), combo2, false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent('k', { altKey: true, metaKey: true }), combo2, false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent("k", { metaKey: true }), combo2, false)).toBe(true);
expect(isKeyComboMatch(mockKeyEvent("n", { metaKey: true }), combo2, false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent("k"), combo2, false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent("k", { altKey: true, metaKey: true }), combo2, false)).toBe(false);
const combo3: KeyCombo = {
key: 'k',
key: "k",
altKey: true,
};
expect(isKeyComboMatch(mockKeyEvent('k', { altKey: true }), combo3, false)).toBe(true);
expect(isKeyComboMatch(mockKeyEvent('n', { altKey: true }), combo3, false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent('k'), combo3, false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, metaKey: true }), combo3, false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent("k", { altKey: true }), combo3, false)).toBe(true);
expect(isKeyComboMatch(mockKeyEvent("n", { altKey: true }), combo3, false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent("k"), combo3, false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent("k", { ctrlKey: true, metaKey: true }), combo3, false)).toBe(false);
const combo4: KeyCombo = {
key: 'k',
key: "k",
shiftKey: true,
};
expect(isKeyComboMatch(mockKeyEvent('k', { shiftKey: true }), combo4, false)).toBe(true);
expect(isKeyComboMatch(mockKeyEvent('n', { shiftKey: true }), combo4, false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent('k'), combo4, false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent('k', { shiftKey: true, ctrlKey: true }), combo4, false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent("k", { shiftKey: true }), combo4, false)).toBe(true);
expect(isKeyComboMatch(mockKeyEvent("n", { shiftKey: true }), combo4, false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent("k"), combo4, false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent("k", { shiftKey: true, ctrlKey: true }), combo4, false)).toBe(false);
});
it('should match key + multiple modifiers key combo', () => {
it("should match key + multiple modifiers key combo", () => {
const combo: KeyCombo = {
key: 'k',
key: "k",
ctrlKey: true,
altKey: true,
};
expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, altKey: true }), combo, false)).toBe(true);
expect(isKeyComboMatch(mockKeyEvent('n', { ctrlKey: true, altKey: true }), combo, false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, metaKey: true }), combo, false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, metaKey: true, shiftKey: true }), combo,
false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent("k", { ctrlKey: true, altKey: true }), combo, false)).toBe(true);
expect(isKeyComboMatch(mockKeyEvent("n", { ctrlKey: true, altKey: true }), combo, false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent("k", { ctrlKey: true, metaKey: true }), combo, false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent("k", { ctrlKey: true, metaKey: true, shiftKey: true }), combo, false)).toBe(
false,
);
const combo2: KeyCombo = {
key: 'k',
key: "k",
ctrlKey: true,
shiftKey: true,
altKey: true,
};
expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, shiftKey: true, altKey: true }), combo2,
false)).toBe(true);
expect(isKeyComboMatch(mockKeyEvent('n', { ctrlKey: true, shiftKey: true, altKey: true }), combo2,
false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, metaKey: true }), combo2, false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent('k',
{ ctrlKey: true, shiftKey: true, altKey: true, metaKey: true }), combo2, false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent("k", { ctrlKey: true, shiftKey: true, altKey: true }), combo2, false)).toBe(
true,
);
expect(isKeyComboMatch(mockKeyEvent("n", { ctrlKey: true, shiftKey: true, altKey: true }), combo2, false)).toBe(
false,
);
expect(isKeyComboMatch(mockKeyEvent("k", { ctrlKey: true, metaKey: true }), combo2, false)).toBe(false);
expect(
isKeyComboMatch(
mockKeyEvent("k", { ctrlKey: true, shiftKey: true, altKey: true, metaKey: true }),
combo2,
false,
),
).toBe(false);
const combo3: KeyCombo = {
key: 'k',
key: "k",
ctrlKey: true,
shiftKey: true,
altKey: true,
metaKey: true,
};
expect(isKeyComboMatch(mockKeyEvent('k',
{ ctrlKey: true, shiftKey: true, altKey: true, metaKey: true }), combo3, false)).toBe(true);
expect(isKeyComboMatch(mockKeyEvent('n',
{ ctrlKey: true, shiftKey: true, altKey: true, metaKey: true }), combo3, false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent('k',
{ ctrlKey: true, shiftKey: true, altKey: true }), combo3, false)).toBe(false);
expect(
isKeyComboMatch(
mockKeyEvent("k", { ctrlKey: true, shiftKey: true, altKey: true, metaKey: true }),
combo3,
false,
),
).toBe(true);
expect(
isKeyComboMatch(
mockKeyEvent("n", { ctrlKey: true, shiftKey: true, altKey: true, metaKey: true }),
combo3,
false,
),
).toBe(false);
expect(isKeyComboMatch(mockKeyEvent("k", { ctrlKey: true, shiftKey: true, altKey: true }), combo3, false)).toBe(
false,
);
});
it('should match ctrlOrMeta key combo', () => {
it("should match ctrlOrMeta key combo", () => {
const combo: KeyCombo = {
key: 'k',
key: "k",
ctrlOrCmdKey: true,
};
// PC:
expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true }), combo, false)).toBe(true);
expect(isKeyComboMatch(mockKeyEvent('k', { metaKey: true }), combo, false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent('n', { ctrlKey: true }), combo, false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent("k", { ctrlKey: true }), combo, false)).toBe(true);
expect(isKeyComboMatch(mockKeyEvent("k", { metaKey: true }), combo, false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent("n", { ctrlKey: true }), combo, false)).toBe(false);
// MAC:
expect(isKeyComboMatch(mockKeyEvent('k', { metaKey: true }), combo, true)).toBe(true);
expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true }), combo, true)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent('n', { ctrlKey: true }), combo, true)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent("k", { metaKey: true }), combo, true)).toBe(true);
expect(isKeyComboMatch(mockKeyEvent("k", { ctrlKey: true }), combo, true)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent("n", { ctrlKey: true }), combo, true)).toBe(false);
});
it('should match advanced ctrlOrMeta key combo', () => {
it("should match advanced ctrlOrMeta key combo", () => {
const combo: KeyCombo = {
key: 'k',
key: "k",
ctrlOrCmdKey: true,
altKey: true,
};
// PC:
expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, altKey: true }), combo, false)).toBe(true);
expect(isKeyComboMatch(mockKeyEvent('k', { metaKey: true, altKey: true }), combo, false)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent("k", { ctrlKey: true, altKey: true }), combo, false)).toBe(true);
expect(isKeyComboMatch(mockKeyEvent("k", { metaKey: true, altKey: true }), combo, false)).toBe(false);
// MAC:
expect(isKeyComboMatch(mockKeyEvent('k', { metaKey: true, altKey: true }), combo, true)).toBe(true);
expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, altKey: true }), combo, true)).toBe(false);
expect(isKeyComboMatch(mockKeyEvent("k", { metaKey: true, altKey: true }), combo, true)).toBe(true);
expect(isKeyComboMatch(mockKeyEvent("k", { ctrlKey: true, altKey: true }), combo, true)).toBe(false);
});
});

View File

@@ -21,23 +21,28 @@ import {
PushRuleKind,
RuleId,
TweakName,
} from 'matrix-js-sdk/src/matrix';
import { CallEvent, CallState, CallType, MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
import EventEmitter from 'events';
import { mocked } from 'jest-mock';
import { CallEventHandlerEvent } from 'matrix-js-sdk/src/webrtc/callEventHandler';
} from "matrix-js-sdk/src/matrix";
import { CallEvent, CallState, CallType, MatrixCall } from "matrix-js-sdk/src/webrtc/call";
import EventEmitter from "events";
import { mocked } from "jest-mock";
import { CallEventHandlerEvent } from "matrix-js-sdk/src/webrtc/callEventHandler";
import LegacyCallHandler, {
LegacyCallHandlerEvent, AudioID, PROTOCOL_PSTN, PROTOCOL_PSTN_PREFIXED, PROTOCOL_SIP_NATIVE, PROTOCOL_SIP_VIRTUAL,
} from '../src/LegacyCallHandler';
import { stubClient, mkStubRoom, untilDispatch } from './test-utils';
import { MatrixClientPeg } from '../src/MatrixClientPeg';
import DMRoomMap from '../src/utils/DMRoomMap';
import SdkConfig from '../src/SdkConfig';
LegacyCallHandlerEvent,
AudioID,
PROTOCOL_PSTN,
PROTOCOL_PSTN_PREFIXED,
PROTOCOL_SIP_NATIVE,
PROTOCOL_SIP_VIRTUAL,
} from "../src/LegacyCallHandler";
import { stubClient, mkStubRoom, untilDispatch } from "./test-utils";
import { MatrixClientPeg } from "../src/MatrixClientPeg";
import DMRoomMap from "../src/utils/DMRoomMap";
import SdkConfig from "../src/SdkConfig";
import { Action } from "../src/dispatcher/actions";
import { getFunctionalMembers } from "../src/utils/room/getFunctionalMembers";
import SettingsStore from '../src/settings/SettingsStore';
import { UIFeature } from '../src/settings/UIFeature';
import SettingsStore from "../src/settings/SettingsStore";
import { UIFeature } from "../src/settings/UIFeature";
jest.mock("../src/utils/room/getFunctionalMembers", () => ({
getFunctionalMembers: jest.fn(),
@@ -67,34 +72,34 @@ const VIRTUAL_ROOM_BOB = "$virtual_bob_room:example.org";
const BOB_PHONE_NUMBER = "01818118181";
function mkStubDM(roomId, userId) {
const room = mkStubRoom(roomId, 'room', MatrixClientPeg.get());
const room = mkStubRoom(roomId, "room", MatrixClientPeg.get());
room.getJoinedMembers = jest.fn().mockReturnValue([
{
userId: '@me:example.org',
name: 'Member',
rawDisplayName: 'Member',
userId: "@me:example.org",
name: "Member",
rawDisplayName: "Member",
roomId: roomId,
membership: 'join',
getAvatarUrl: () => 'mxc://avatar.url/image.png',
getMxcAvatarUrl: () => 'mxc://avatar.url/image.png',
membership: "join",
getAvatarUrl: () => "mxc://avatar.url/image.png",
getMxcAvatarUrl: () => "mxc://avatar.url/image.png",
},
{
userId: userId,
name: 'Member',
rawDisplayName: 'Member',
name: "Member",
rawDisplayName: "Member",
roomId: roomId,
membership: 'join',
getAvatarUrl: () => 'mxc://avatar.url/image.png',
getMxcAvatarUrl: () => 'mxc://avatar.url/image.png',
membership: "join",
getAvatarUrl: () => "mxc://avatar.url/image.png",
getMxcAvatarUrl: () => "mxc://avatar.url/image.png",
},
{
userId: FUNCTIONAL_USER,
name: 'Bot user',
rawDisplayName: 'Bot user',
name: "Bot user",
rawDisplayName: "Bot user",
roomId: roomId,
membership: 'join',
getAvatarUrl: () => 'mxc://avatar.url/image.png',
getMxcAvatarUrl: () => 'mxc://avatar.url/image.png',
membership: "join",
getAvatarUrl: () => "mxc://avatar.url/image.png",
getMxcAvatarUrl: () => "mxc://avatar.url/image.png",
},
]);
room.currentState.getMembers = room.getJoinedMembers;
@@ -127,7 +132,7 @@ function untilCallHandlerEvent(callHandler: LegacyCallHandler, event: LegacyCall
});
}
describe('LegacyCallHandler', () => {
describe("LegacyCallHandler", () => {
let dmRoomMap;
let callHandler;
let audioElement: HTMLAudioElement;
@@ -136,11 +141,11 @@ describe('LegacyCallHandler', () => {
// what addresses the app has looked up via pstn and native lookup
let pstnLookup: string;
let nativeLookup: string;
const deviceId = 'my-device';
const deviceId = "my-device";
beforeEach(async () => {
stubClient();
MatrixClientPeg.get().createCall = roomId => {
MatrixClientPeg.get().createCall = (roomId) => {
if (fakeCall && fakeCall.roomId !== roomId) {
throw new Error("Only one call is supported!");
}
@@ -160,16 +165,14 @@ describe('LegacyCallHandler', () => {
callHandler = new LegacyCallHandler();
callHandler.start();
mocked(getFunctionalMembers).mockReturnValue([
FUNCTIONAL_USER,
]);
mocked(getFunctionalMembers).mockReturnValue([FUNCTIONAL_USER]);
const nativeRoomAlice = mkStubDM(NATIVE_ROOM_ALICE, NATIVE_ALICE);
const nativeRoomBob = mkStubDM(NATIVE_ROOM_BOB, NATIVE_BOB);
const nativeRoomCharie = mkStubDM(NATIVE_ROOM_CHARLIE, NATIVE_CHARLIE);
const virtualBobRoom = mkStubDM(VIRTUAL_ROOM_BOB, VIRTUAL_BOB);
MatrixClientPeg.get().getRoom = roomId => {
MatrixClientPeg.get().getRoom = (roomId) => {
switch (roomId) {
case NATIVE_ROOM_ALICE:
return nativeRoomAlice;
@@ -217,44 +220,50 @@ describe('LegacyCallHandler', () => {
MatrixClientPeg.get().getThirdpartyUser = (proto, params) => {
if ([PROTOCOL_PSTN, PROTOCOL_PSTN_PREFIXED].includes(proto)) {
pstnLookup = params['m.id.phone'];
return Promise.resolve([{
userid: VIRTUAL_BOB,
protocol: "m.id.phone",
fields: {
is_native: true,
lookup_success: true,
},
}]);
} else if (proto === PROTOCOL_SIP_NATIVE) {
nativeLookup = params['virtual_mxid'];
if (params['virtual_mxid'] === VIRTUAL_BOB) {
return Promise.resolve([{
userid: NATIVE_BOB,
protocol: "im.vector.protocol.sip_native",
pstnLookup = params["m.id.phone"];
return Promise.resolve([
{
userid: VIRTUAL_BOB,
protocol: "m.id.phone",
fields: {
is_native: true,
lookup_success: true,
},
}]);
},
]);
} else if (proto === PROTOCOL_SIP_NATIVE) {
nativeLookup = params["virtual_mxid"];
if (params["virtual_mxid"] === VIRTUAL_BOB) {
return Promise.resolve([
{
userid: NATIVE_BOB,
protocol: "im.vector.protocol.sip_native",
fields: {
is_native: true,
lookup_success: true,
},
},
]);
}
return Promise.resolve([]);
} else if (proto === PROTOCOL_SIP_VIRTUAL) {
if (params['native_mxid'] === NATIVE_BOB) {
return Promise.resolve([{
userid: VIRTUAL_BOB,
protocol: "im.vector.protocol.sip_virtual",
fields: {
is_virtual: true,
lookup_success: true,
if (params["native_mxid"] === NATIVE_BOB) {
return Promise.resolve([
{
userid: VIRTUAL_BOB,
protocol: "im.vector.protocol.sip_virtual",
fields: {
is_virtual: true,
lookup_success: true,
},
},
}]);
]);
}
return Promise.resolve([]);
}
};
audioElement = document.createElement('audio');
audioElement = document.createElement("audio");
audioElement.id = "remoteAudio";
document.body.appendChild(audioElement);
});
@@ -271,7 +280,7 @@ describe('LegacyCallHandler', () => {
SdkConfig.unset();
});
it('should look up the correct user and start a call in the room when a phone number is dialled', async () => {
it("should look up the correct user and start a call in the room when a phone number is dialled", async () => {
await callHandler.dialNumber(BOB_PHONE_NUMBER);
expect(pstnLookup).toEqual(BOB_PHONE_NUMBER);
@@ -289,7 +298,7 @@ describe('LegacyCallHandler', () => {
expect(callHandler.roomIdForCall(fakeCall)).toEqual(NATIVE_ROOM_BOB);
});
it('should look up the correct user and start a call in the room when a call is transferred', async () => {
it("should look up the correct user and start a call in the room when a call is transferred", async () => {
// we can pass a very minimal object as as the call since we pass consultFirst=true:
// we don't need to actually do any transferring
const mockTransferreeCall = { type: CallType.Voice };
@@ -304,7 +313,7 @@ describe('LegacyCallHandler', () => {
expect(callHandler.roomIdForCall(fakeCall)).toEqual(NATIVE_ROOM_BOB);
});
it('should move calls between rooms when remote asserted identity changes', async () => {
it("should move calls between rooms when remote asserted identity changes", async () => {
callHandler.placeCall(NATIVE_ROOM_ALICE, CallType.Voice);
await untilCallHandlerEvent(callHandler, LegacyCallHandlerEvent.CallState);
@@ -313,7 +322,7 @@ describe('LegacyCallHandler', () => {
expect(callHandler.getCallForRoom(NATIVE_ROOM_ALICE)).toBe(fakeCall);
let callRoomChangeEventCount = 0;
const roomChangePromise = new Promise<void>(resolve => {
const roomChangePromise = new Promise<void>((resolve) => {
callHandler.addListener(LegacyCallHandlerEvent.CallChangeRoom, () => {
++callRoomChangeEventCount;
resolve();
@@ -355,7 +364,7 @@ describe('LegacyCallHandler', () => {
});
});
describe('LegacyCallHandler without third party protocols', () => {
describe("LegacyCallHandler without third party protocols", () => {
let dmRoomMap;
let callHandler: LegacyCallHandler;
let audioElement: HTMLAudioElement;
@@ -363,7 +372,7 @@ describe('LegacyCallHandler without third party protocols', () => {
beforeEach(() => {
stubClient();
MatrixClientPeg.get().createCall = roomId => {
MatrixClientPeg.get().createCall = (roomId) => {
if (fakeCall && fakeCall.roomId !== roomId) {
throw new Error("Only one call is supported!");
}
@@ -380,7 +389,7 @@ describe('LegacyCallHandler without third party protocols', () => {
const nativeRoomAlice = mkStubDM(NATIVE_ROOM_ALICE, NATIVE_ALICE);
MatrixClientPeg.get().getRoom = roomId => {
MatrixClientPeg.get().getRoom = (roomId) => {
switch (roomId) {
case NATIVE_ROOM_ALICE:
return nativeRoomAlice;
@@ -409,7 +418,7 @@ describe('LegacyCallHandler without third party protocols', () => {
throw new Error("Endpoint unsupported.");
};
audioElement = document.createElement('audio');
audioElement = document.createElement("audio");
audioElement.id = "remoteAudio";
document.body.appendChild(audioElement);
});
@@ -426,7 +435,7 @@ describe('LegacyCallHandler without third party protocols', () => {
SdkConfig.unset();
});
it('should still start a native call', async () => {
it("should still start a native call", async () => {
callHandler.placeCall(NATIVE_ROOM_ALICE, CallType.Voice);
await untilCallHandlerEvent(callHandler, LegacyCallHandlerEvent.CallState);
@@ -439,8 +448,8 @@ describe('LegacyCallHandler without third party protocols', () => {
expect(callHandler.roomIdForCall(fakeCall)).toEqual(NATIVE_ROOM_ALICE);
});
describe('incoming calls', () => {
const roomId = 'test-room-id';
describe("incoming calls", () => {
const roomId = "test-room-id";
const mockAudioElement = {
play: jest.fn(),
@@ -451,35 +460,35 @@ describe('LegacyCallHandler without third party protocols', () => {
} as unknown as HTMLMediaElement;
beforeEach(() => {
jest.clearAllMocks();
jest.spyOn(SettingsStore, 'getValue').mockImplementation(setting =>
setting === UIFeature.Voip);
jest.spyOn(SettingsStore, "getValue").mockImplementation((setting) => setting === UIFeature.Voip);
jest.spyOn(MatrixClientPeg.get(), 'supportsVoip').mockReturnValue(true);
jest.spyOn(MatrixClientPeg.get(), "supportsVoip").mockReturnValue(true);
MatrixClientPeg.get().isFallbackICEServerAllowed = jest.fn();
MatrixClientPeg.get().prepareToEncrypt = jest.fn();
MatrixClientPeg.get().pushRules = {
global: {
[PushRuleKind.Override]: [{
rule_id: RuleId.IncomingCall,
default: false,
enabled: true,
actions: [
{
set_tweak: TweakName.Sound,
value: 'ring',
},
]
,
}],
[PushRuleKind.Override]: [
{
rule_id: RuleId.IncomingCall,
default: false,
enabled: true,
actions: [
{
set_tweak: TweakName.Sound,
value: "ring",
},
],
},
],
},
};
jest.spyOn(document, 'getElementById').mockReturnValue(mockAudioElement);
jest.spyOn(document, "getElementById").mockReturnValue(mockAudioElement);
// silence local notifications by default
jest.spyOn(MatrixClientPeg.get(), 'getAccountData').mockImplementation((eventType) => {
jest.spyOn(MatrixClientPeg.get(), "getAccountData").mockImplementation((eventType) => {
if (eventType.includes(LOCAL_NOTIFICATION_SETTINGS_PREFIX.name)) {
return new MatrixEvent({
type: eventType,
@@ -491,7 +500,7 @@ describe('LegacyCallHandler without third party protocols', () => {
});
});
it('should unmute <audio> before playing', () => {
it("should unmute <audio> before playing", () => {
// Test setup: set the audio element as muted
mockAudioElement.muted = true;
expect(mockAudioElement.muted).toStrictEqual(true);
@@ -504,7 +513,7 @@ describe('LegacyCallHandler without third party protocols', () => {
expect(mockAudioElement.play).toHaveBeenCalled();
});
it('listens for incoming call events when voip is enabled', () => {
it("listens for incoming call events when voip is enabled", () => {
const call = new MatrixCall({
client: MatrixClientPeg.get(),
roomId,
@@ -517,9 +526,9 @@ describe('LegacyCallHandler without third party protocols', () => {
expect(callHandler.getCallForRoom(roomId)).toEqual(call);
});
it('rings when incoming call state is ringing and notifications set to ring', () => {
it("rings when incoming call state is ringing and notifications set to ring", () => {
// remove local notification silencing mock for this test
jest.spyOn(MatrixClientPeg.get(), 'getAccountData').mockReturnValue(undefined);
jest.spyOn(MatrixClientPeg.get(), "getAccountData").mockReturnValue(undefined);
const call = new MatrixCall({
client: MatrixClientPeg.get(),
roomId,
@@ -536,7 +545,7 @@ describe('LegacyCallHandler without third party protocols', () => {
expect(mockAudioElement.play).toHaveBeenCalled();
});
it('does not ring when incoming call state is ringing but local notifications are silenced', () => {
it("does not ring when incoming call state is ringing but local notifications are silenced", () => {
const call = new MatrixCall({
client: MatrixClientPeg.get(),
roomId,
@@ -554,7 +563,7 @@ describe('LegacyCallHandler without third party protocols', () => {
expect(callHandler.isCallSilenced(call.callId)).toEqual(true);
});
it('should force calls to silent when local notifications are silenced', async () => {
it("should force calls to silent when local notifications are silenced", async () => {
const call = new MatrixCall({
client: MatrixClientPeg.get(),
roomId,
@@ -567,13 +576,13 @@ describe('LegacyCallHandler without third party protocols', () => {
expect(callHandler.isCallSilenced(call.callId)).toEqual(true);
});
it('does not unsilence calls when local notifications are silenced', async () => {
it("does not unsilence calls when local notifications are silenced", async () => {
const call = new MatrixCall({
client: MatrixClientPeg.get(),
roomId,
});
const cli = MatrixClientPeg.get();
const callHandlerEmitSpy = jest.spyOn(callHandler, 'emit');
const callHandlerEmitSpy = jest.spyOn(callHandler, "emit");
cli.emit(CallEventHandlerEvent.Incoming, call);
// reset emit call count

View File

@@ -40,7 +40,7 @@ describe("Markdown parser test", () => {
"https://riot.im/app/#/room/#_foonetic_xkcd:matrix.org",
].join("\n");
it('tests that links with markdown empasis in them are getting properly HTML formatted', () => {
it("tests that links with markdown empasis in them are getting properly HTML formatted", () => {
/* eslint-disable max-len */
const expectedResult = [
"<p>Test1:<br />#_foonetic_xkcd:matrix.org<br />http://google.com/_thing_<br />https://matrix.org/_matrix/client/foo/123_<br />#_foonetic_xkcd:matrix.org</p>",
@@ -53,7 +53,7 @@ describe("Markdown parser test", () => {
const md = new Markdown(testString);
expect(md.toHTML()).toEqual(expectedResult);
});
it('tests that links with autolinks are not touched at all and are still properly formatted', () => {
it("tests that links with autolinks are not touched at all and are still properly formatted", () => {
const test = [
"Test1:",
"<#_foonetic_xkcd:matrix.org>",
@@ -81,10 +81,10 @@ describe("Markdown parser test", () => {
* but it seems to be actually working properly
*/
const expectedResult = [
"<p>Test1:<br />&lt;#_foonetic_xkcd:matrix.org&gt;<br /><a href=\"http://google.com/_thing_\">http://google.com/_thing_</a><br /><a href=\"https://matrix.org/_matrix/client/foo/123_\">https://matrix.org/_matrix/client/foo/123_</a><br />&lt;#_foonetic_xkcd:matrix.org&gt;</p>",
"<p>Test1A:<br />&lt;#_foonetic_xkcd:matrix.org&gt;<br /><a href=\"http://google.com/_thing_\">http://google.com/_thing_</a><br /><a href=\"https://matrix.org/_matrix/client/foo/123_\">https://matrix.org/_matrix/client/foo/123_</a><br />&lt;#_foonetic_xkcd:matrix.org&gt;</p>",
"<p>Test2:<br /><a href=\"http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg\">http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg</a><br /><a href=\"http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg\">http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg</a></p>",
"<p>Test3:<br /><a href=\"https://riot.im/app/#/room/#_foonetic_xkcd:matrix.org\">https://riot.im/app/#/room/#_foonetic_xkcd:matrix.org</a><br /><a href=\"https://riot.im/app/#/room/#_foonetic_xkcd:matrix.org\">https://riot.im/app/#/room/#_foonetic_xkcd:matrix.org</a></p>",
'<p>Test1:<br />&lt;#_foonetic_xkcd:matrix.org&gt;<br /><a href="http://google.com/_thing_">http://google.com/_thing_</a><br /><a href="https://matrix.org/_matrix/client/foo/123_">https://matrix.org/_matrix/client/foo/123_</a><br />&lt;#_foonetic_xkcd:matrix.org&gt;</p>',
'<p>Test1A:<br />&lt;#_foonetic_xkcd:matrix.org&gt;<br /><a href="http://google.com/_thing_">http://google.com/_thing_</a><br /><a href="https://matrix.org/_matrix/client/foo/123_">https://matrix.org/_matrix/client/foo/123_</a><br />&lt;#_foonetic_xkcd:matrix.org&gt;</p>',
'<p>Test2:<br /><a href="http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg">http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg</a><br /><a href="http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg">http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg</a></p>',
'<p>Test3:<br /><a href="https://riot.im/app/#/room/#_foonetic_xkcd:matrix.org">https://riot.im/app/#/room/#_foonetic_xkcd:matrix.org</a><br /><a href="https://riot.im/app/#/room/#_foonetic_xkcd:matrix.org">https://riot.im/app/#/room/#_foonetic_xkcd:matrix.org</a></p>',
"",
].join("\n");
/* eslint-enable max-len */
@@ -92,29 +92,29 @@ describe("Markdown parser test", () => {
expect(md.toHTML()).toEqual(expectedResult);
});
it('expects that links in codeblock are not modified', () => {
it("expects that links in codeblock are not modified", () => {
const expectedResult = [
'<pre><code class="language-Test1:">#_foonetic_xkcd:matrix.org',
'http://google.com/_thing_',
'https://matrix.org/_matrix/client/foo/123_',
'#_foonetic_xkcd:matrix.org',
'',
'Test1A:',
'#_foonetic_xkcd:matrix.org',
'http://google.com/_thing_',
'https://matrix.org/_matrix/client/foo/123_',
'#_foonetic_xkcd:matrix.org',
'',
'Test2:',
'http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg',
'http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg',
'',
'Test3:',
'https://riot.im/app/#/room/#_foonetic_xkcd:matrix.org',
'https://riot.im/app/#/room/#_foonetic_xkcd:matrix.org```',
'</code></pre>',
'',
].join('\n');
"http://google.com/_thing_",
"https://matrix.org/_matrix/client/foo/123_",
"#_foonetic_xkcd:matrix.org",
"",
"Test1A:",
"#_foonetic_xkcd:matrix.org",
"http://google.com/_thing_",
"https://matrix.org/_matrix/client/foo/123_",
"#_foonetic_xkcd:matrix.org",
"",
"Test2:",
"http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg",
"http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg",
"",
"Test3:",
"https://riot.im/app/#/room/#_foonetic_xkcd:matrix.org",
"https://riot.im/app/#/room/#_foonetic_xkcd:matrix.org```",
"</code></pre>",
"",
].join("\n");
const md = new Markdown("```" + testString + "```");
expect(md.toHTML()).toEqual(expectedResult);
});
@@ -122,15 +122,19 @@ describe("Markdown parser test", () => {
it('expects that links with emphasis are "escaped" correctly', () => {
/* eslint-disable max-len */
const testString = [
'http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg' + " " + 'http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg',
'http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg' + " " + 'http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg',
"http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg" +
" " +
"http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg",
"http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg" +
" " +
"http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg",
"https://example.com/_test_test2_-test3",
"https://example.com/_test_test2_test3_",
"https://example.com/_test__test2_test3_",
"https://example.com/_test__test2__test3_",
"https://example.com/_test__test2_test3__",
"https://example.com/_test__test2",
].join('\n');
].join("\n");
const expectedResult = [
"http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg",
"http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg",
@@ -140,39 +144,47 @@ describe("Markdown parser test", () => {
"https://example.com/_test__test2__test3_",
"https://example.com/_test__test2_test3__",
"https://example.com/_test__test2",
].join('<br />');
].join("<br />");
/* eslint-enable max-len */
const md = new Markdown(testString);
expect(md.toHTML()).toEqual(expectedResult);
});
it('expects that the link part will not be accidentally added to <strong>', () => {
it("expects that the link part will not be accidentally added to <strong>", () => {
/* eslint-disable max-len */
const testString = `https://github.com/matrix-org/synapse/blob/develop/synapse/module_api/__init__.py`;
const expectedResult = 'https://github.com/matrix-org/synapse/blob/develop/synapse/module_api/__init__.py';
const expectedResult = "https://github.com/matrix-org/synapse/blob/develop/synapse/module_api/__init__.py";
/* eslint-enable max-len */
const md = new Markdown(testString);
expect(md.toHTML()).toEqual(expectedResult);
});
it('expects that the link part will not be accidentally added to <strong> for multiline links', () => {
it("expects that the link part will not be accidentally added to <strong> for multiline links", () => {
/* eslint-disable max-len */
const testString = [
'https://github.com/matrix-org/synapse/blob/develop/synapse/module_api/__init__.py' + " " + 'https://github.com/matrix-org/synapse/blob/develop/synapse/module_api/__init__.py',
'https://github.com/matrix-org/synapse/blob/develop/synapse/module_api/__init__.py' + " " + 'https://github.com/matrix-org/synapse/blob/develop/synapse/module_api/__init__.py',
].join('\n');
"https://github.com/matrix-org/synapse/blob/develop/synapse/module_api/__init__.py" +
" " +
"https://github.com/matrix-org/synapse/blob/develop/synapse/module_api/__init__.py",
"https://github.com/matrix-org/synapse/blob/develop/synapse/module_api/__init__.py" +
" " +
"https://github.com/matrix-org/synapse/blob/develop/synapse/module_api/__init__.py",
].join("\n");
const expectedResult = [
'https://github.com/matrix-org/synapse/blob/develop/synapse/module_api/__init__.py' + " " + 'https://github.com/matrix-org/synapse/blob/develop/synapse/module_api/__init__.py',
'https://github.com/matrix-org/synapse/blob/develop/synapse/module_api/__init__.py' + " " + 'https://github.com/matrix-org/synapse/blob/develop/synapse/module_api/__init__.py',
].join('<br />');
"https://github.com/matrix-org/synapse/blob/develop/synapse/module_api/__init__.py" +
" " +
"https://github.com/matrix-org/synapse/blob/develop/synapse/module_api/__init__.py",
"https://github.com/matrix-org/synapse/blob/develop/synapse/module_api/__init__.py" +
" " +
"https://github.com/matrix-org/synapse/blob/develop/synapse/module_api/__init__.py",
].join("<br />");
/* eslint-enable max-len */
const md = new Markdown(testString);
expect(md.toHTML()).toEqual(expectedResult);
});
it('resumes applying formatting to the rest of a message after a link', () => {
const testString = 'http://google.com/_thing_ *does* __not__ exist';
const expectedResult = 'http://google.com/_thing_ <em>does</em> <strong>not</strong> exist';
it("resumes applying formatting to the rest of a message after a link", () => {
const testString = "http://google.com/_thing_ *does* __not__ exist";
const expectedResult = "http://google.com/_thing_ <em>does</em> <strong>not</strong> exist";
const md = new Markdown(testString);
expect(md.toHTML()).toEqual(expectedResult);
});

View File

@@ -14,13 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { mocked } from 'jest-mock';
import { mocked } from "jest-mock";
import { SettingLevel } from "../src/settings/SettingLevel";
import { MatrixClientPeg } from '../src/MatrixClientPeg';
import { MatrixClientPeg } from "../src/MatrixClientPeg";
import { stubClient } from "./test-utils";
import MediaDeviceHandler from "../src/MediaDeviceHandler";
import SettingsStore from '../src/settings/SettingsStore';
import SettingsStore from "../src/settings/SettingsStore";
jest.mock("../src/settings/SettingsStore");
@@ -51,9 +51,7 @@ describe("MediaDeviceHandler", () => {
await MediaDeviceHandler.setAudioNoiseSuppression(false);
expectedAudioSettings.forEach((value, key) => {
expect(SettingsStoreMock.setValue).toHaveBeenCalledWith(
key, null, SettingLevel.DEVICE, value,
);
expect(SettingsStoreMock.setValue).toHaveBeenCalledWith(key, null, SettingLevel.DEVICE, value);
});
expect(MatrixClientPeg.get().getMediaHandler().setAudioSettings).toHaveBeenCalledWith({

View File

@@ -78,12 +78,14 @@ describe("Notifier", () => {
mockClient = getMockClientWithEventEmitter({
...mockClientMethodsUser(userId),
isGuest: jest.fn().mockReturnValue(false),
getAccountData: jest.fn().mockImplementation(eventType => accountDataStore[eventType]),
getAccountData: jest.fn().mockImplementation((eventType) => accountDataStore[eventType]),
setAccountData: jest.fn().mockImplementation((eventType, content) => {
accountDataStore[eventType] = content ? new MatrixEvent({
type: eventType,
content,
}) : undefined;
accountDataStore[eventType] = content
? new MatrixEvent({
type: eventType,
content,
})
: undefined;
}),
decryptEventIfNeeded: jest.fn(),
getRoom: jest.fn(),
@@ -107,20 +109,20 @@ describe("Notifier", () => {
Notifier.isBodyEnabled = jest.fn().mockReturnValue(true);
mockClient.getRoom.mockImplementation(id => {
mockClient.getRoom.mockImplementation((id) => {
return id === roomId ? testRoom : new Room(id, mockClient, mockClient.getUserId());
});
});
describe('triggering notification from events', () => {
describe("triggering notification from events", () => {
let hasStartedNotiferBefore = false;
const event = new MatrixEvent({
sender: '@alice:server.org',
type: 'm.room.message',
room_id: '!room:server.org',
sender: "@alice:server.org",
type: "m.room.message",
room_id: "!room:server.org",
content: {
body: 'hey',
body: "hey",
},
});
@@ -142,28 +144,28 @@ describe("Notifier", () => {
});
mockSettings = {
'notificationsEnabled': true,
'audioNotificationsEnabled': true,
notificationsEnabled: true,
audioNotificationsEnabled: true,
};
// enable notifications by default
jest.spyOn(SettingsStore, "getValue").mockReset().mockImplementation(
settingName => mockSettings[settingName] ?? false,
);
jest.spyOn(SettingsStore, "getValue")
.mockReset()
.mockImplementation((settingName) => mockSettings[settingName] ?? false);
});
afterAll(() => {
Notifier.stop();
});
it('does not create notifications before syncing has started', () => {
it("does not create notifications before syncing has started", () => {
emitLiveEvent(event);
expect(MockPlatform.displayNotification).not.toHaveBeenCalled();
expect(MockPlatform.loudNotification).not.toHaveBeenCalled();
});
it('does not create notifications for own event', () => {
it("does not create notifications for own event", () => {
const ownEvent = new MatrixEvent({ sender: userId });
mockClient!.emit(ClientEvent.Sync, SyncState.Syncing, null);
@@ -173,7 +175,7 @@ describe("Notifier", () => {
expect(MockPlatform.loudNotification).not.toHaveBeenCalled();
});
it('does not create notifications for non-live events (scrollback)', () => {
it("does not create notifications for non-live events (scrollback)", () => {
mockClient!.emit(ClientEvent.Sync, SyncState.Syncing, null);
mockClient!.emit(RoomEvent.Timeline, event, testRoom, false, false, {
liveEvent: false,
@@ -184,7 +186,7 @@ describe("Notifier", () => {
expect(MockPlatform.loudNotification).not.toHaveBeenCalled();
});
it('does not create notifications for rooms which cannot be obtained via client.getRoom', () => {
it("does not create notifications for rooms which cannot be obtained via client.getRoom", () => {
mockClient!.emit(ClientEvent.Sync, SyncState.Syncing, null);
mockClient.getRoom.mockReturnValue(null);
mockClient!.emit(RoomEvent.Timeline, event, testRoom, false, false, {
@@ -196,7 +198,7 @@ describe("Notifier", () => {
expect(MockPlatform.loudNotification).not.toHaveBeenCalled();
});
it('does not create notifications when event does not have notify push action', () => {
it("does not create notifications when event does not have notify push action", () => {
mockClient.getPushActionsForEvent.mockReturnValue({
notify: false,
tweaks: {
@@ -211,29 +213,21 @@ describe("Notifier", () => {
expect(MockPlatform.loudNotification).not.toHaveBeenCalled();
});
it('creates desktop notification when enabled', () => {
it("creates desktop notification when enabled", () => {
mockClient!.emit(ClientEvent.Sync, SyncState.Syncing, null);
emitLiveEvent(event);
expect(MockPlatform.displayNotification).toHaveBeenCalledWith(
testRoom.name,
'hey',
null,
testRoom,
event,
);
expect(MockPlatform.displayNotification).toHaveBeenCalledWith(testRoom.name, "hey", null, testRoom, event);
});
it('creates a loud notification when enabled', () => {
it("creates a loud notification when enabled", () => {
mockClient!.emit(ClientEvent.Sync, SyncState.Syncing, null);
emitLiveEvent(event);
expect(MockPlatform.loudNotification).toHaveBeenCalledWith(
event, testRoom,
);
expect(MockPlatform.loudNotification).toHaveBeenCalledWith(event, testRoom);
});
it('does not create loud notification when event does not have sound tweak in push actions', () => {
it("does not create loud notification when event does not have sound tweak in push actions", () => {
mockClient.getPushActionsForEvent.mockReturnValue({
notify: true,
tweaks: {
@@ -252,7 +246,7 @@ describe("Notifier", () => {
});
describe("_displayPopupNotification", () => {
const testCases: {event: IContent | undefined, count: number}[] = [
const testCases: { event: IContent | undefined; count: number }[] = [
{ event: { is_silenced: true }, count: 0 },
{ event: { is_silenced: false }, count: 1 },
{ event: undefined, count: 1 },
@@ -274,7 +268,7 @@ describe("Notifier", () => {
});
describe("_playAudioNotification", () => {
const testCases: {event: IContent | undefined, count: number}[] = [
const testCases: { event: IContent | undefined; count: number }[] = [
{ event: { is_silenced: true }, count: 0 },
{ event: { is_silenced: false }, count: 1 },
{ event: undefined, count: 1 },
@@ -330,13 +324,15 @@ describe("Notifier", () => {
const callEvent = callOnEvent();
expect(ToastStore.sharedInstance().addOrReplaceToast).toHaveBeenCalledWith(expect.objectContaining({
key: `call_${callEvent.getStateKey()}`,
priority: 100,
component: IncomingCallToast,
bodyClassName: "mx_IncomingCallToast",
props: { callEvent },
}));
expect(ToastStore.sharedInstance().addOrReplaceToast).toHaveBeenCalledWith(
expect.objectContaining({
key: `call_${callEvent.getStateKey()}`,
priority: 100,
component: IncomingCallToast,
bodyClassName: "mx_IncomingCallToast",
props: { callEvent },
}),
);
});
it("should not show toast when group calls are not supported", () => {
@@ -356,7 +352,7 @@ describe("Notifier", () => {
});
});
describe('local notification settings', () => {
describe("local notification settings", () => {
const createLocalNotificationSettingsIfNeededMock = mocked(createLocalNotificationSettingsIfNeeded);
let hasStartedNotiferBefore = false;
beforeEach(() => {
@@ -375,36 +371,34 @@ describe("Notifier", () => {
Notifier.stop();
});
it('does not create local notifications event after a sync error', () => {
it("does not create local notifications event after a sync error", () => {
mockClient.emit(ClientEvent.Sync, SyncState.Error, SyncState.Syncing);
expect(createLocalNotificationSettingsIfNeededMock).not.toHaveBeenCalled();
});
it('does not create local notifications event after sync stops', () => {
it("does not create local notifications event after sync stops", () => {
mockClient.emit(ClientEvent.Sync, SyncState.Stopped, SyncState.Syncing);
expect(createLocalNotificationSettingsIfNeededMock).not.toHaveBeenCalled();
});
it('does not create local notifications event after a cached sync', () => {
it("does not create local notifications event after a cached sync", () => {
mockClient.emit(ClientEvent.Sync, SyncState.Syncing, SyncState.Syncing, {
fromCache: true,
});
expect(createLocalNotificationSettingsIfNeededMock).not.toHaveBeenCalled();
});
it('creates local notifications event after a non-cached sync', () => {
it("creates local notifications event after a non-cached sync", () => {
mockClient.emit(ClientEvent.Sync, SyncState.Syncing, SyncState.Syncing, {});
expect(createLocalNotificationSettingsIfNeededMock).toHaveBeenCalled();
});
});
describe('_evaluateEvent', () => {
describe("_evaluateEvent", () => {
beforeEach(() => {
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId")
.mockReturnValue(testRoom.roomId);
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue(testRoom.roomId);
jest.spyOn(UserActivity.sharedInstance(), "userActiveRecently")
.mockReturnValue(true);
jest.spyOn(UserActivity.sharedInstance(), "userActiveRecently").mockReturnValue(true);
jest.spyOn(Modal, "hasDialogs").mockReturnValue(false);
@@ -457,9 +451,7 @@ describe("Notifier", () => {
thread_id: rootEvent.getId(),
});
await waitFor(() =>
expect(SdkContextClass.instance.roomViewStore.getThreadId()).toBe(rootEvent.getId()),
);
await waitFor(() => expect(SdkContextClass.instance.roomViewStore.getThreadId()).toBe(rootEvent.getId()));
Notifier._evaluateEvent(events[1]);
expect(Notifier._displayPopupNotification).toHaveBeenCalledTimes(1);

View File

@@ -14,25 +14,26 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { mocked } from 'jest-mock';
import { PostHog } from 'posthog-js';
import { mocked } from "jest-mock";
import { PostHog } from "posthog-js";
import { Anonymity, getRedactedCurrentLocation, IPosthogEvent, PosthogAnalytics } from '../src/PosthogAnalytics';
import SdkConfig from '../src/SdkConfig';
import { getMockClientWithEventEmitter } from './test-utils';
import { Anonymity, getRedactedCurrentLocation, IPosthogEvent, PosthogAnalytics } from "../src/PosthogAnalytics";
import SdkConfig from "../src/SdkConfig";
import { getMockClientWithEventEmitter } from "./test-utils";
import SettingsStore from "../src/settings/SettingsStore";
import { Layout } from "../src/settings/enums/Layout";
import defaultDispatcher from "../src/dispatcher/dispatcher";
import { Action } from "../src/dispatcher/actions";
import { SettingLevel } from "../src/settings/SettingLevel";
const getFakePosthog = (): PostHog => ({
capture: jest.fn(),
init: jest.fn(),
identify: jest.fn(),
reset: jest.fn(),
register: jest.fn(),
} as unknown as PostHog);
const getFakePosthog = (): PostHog =>
({
capture: jest.fn(),
init: jest.fn(),
identify: jest.fn(),
reset: jest.fn(),
register: jest.fn(),
} as unknown as PostHog);
export interface ITestEvent extends IPosthogEvent {
eventName: "JestTestEvents";
@@ -198,58 +199,70 @@ describe("PosthogAnalytics", () => {
it("should send layout IRC correctly", async () => {
await SettingsStore.setValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
defaultDispatcher.dispatch({
action: Action.SettingUpdated,
settingName: "layout",
}, true);
defaultDispatcher.dispatch(
{
action: Action.SettingUpdated,
settingName: "layout",
},
true,
);
analytics.trackEvent<ITestEvent>({
eventName: "JestTestEvents",
});
expect(mocked(fakePosthog).capture.mock.calls[0][1]["$set"]).toStrictEqual({
"WebLayout": "IRC",
WebLayout: "IRC",
});
});
it("should send layout Bubble correctly", async () => {
await SettingsStore.setValue("layout", null, SettingLevel.DEVICE, Layout.Bubble);
defaultDispatcher.dispatch({
action: Action.SettingUpdated,
settingName: "layout",
}, true);
defaultDispatcher.dispatch(
{
action: Action.SettingUpdated,
settingName: "layout",
},
true,
);
analytics.trackEvent<ITestEvent>({
eventName: "JestTestEvents",
});
expect(mocked(fakePosthog).capture.mock.calls[0][1]["$set"]).toStrictEqual({
"WebLayout": "Bubble",
WebLayout: "Bubble",
});
});
it("should send layout Group correctly", async () => {
await SettingsStore.setValue("layout", null, SettingLevel.DEVICE, Layout.Group);
defaultDispatcher.dispatch({
action: Action.SettingUpdated,
settingName: "layout",
}, true);
defaultDispatcher.dispatch(
{
action: Action.SettingUpdated,
settingName: "layout",
},
true,
);
analytics.trackEvent<ITestEvent>({
eventName: "JestTestEvents",
});
expect(mocked(fakePosthog).capture.mock.calls[0][1]["$set"]).toStrictEqual({
"WebLayout": "Group",
WebLayout: "Group",
});
});
it("should send layout Compact correctly", async () => {
await SettingsStore.setValue("layout", null, SettingLevel.DEVICE, Layout.Group);
await SettingsStore.setValue("useCompactLayout", null, SettingLevel.DEVICE, true);
defaultDispatcher.dispatch({
action: Action.SettingUpdated,
settingName: "useCompactLayout",
}, true);
defaultDispatcher.dispatch(
{
action: Action.SettingUpdated,
settingName: "useCompactLayout",
},
true,
);
analytics.trackEvent<ITestEvent>({
eventName: "JestTestEvents",
});
expect(mocked(fakePosthog).capture.mock.calls[0][1]["$set"]).toStrictEqual({
"WebLayout": "Compact",
WebLayout: "Compact",
});
});
});

View File

@@ -84,7 +84,7 @@ describe("Reply", () => {
content: {
"m.relates_to": {
"m.in_reply_to": {
"event_id": "$event1",
event_id: "$event1",
},
},
},
@@ -96,24 +96,30 @@ describe("Reply", () => {
describe("stripPlainReply", () => {
it("Removes leading quotes until the first blank line", () => {
expect(stripPlainReply(`
expect(
stripPlainReply(
`
> This is part
> of the quote
But this is not
`.trim())).toBe("But this is not");
`.trim(),
),
).toBe("But this is not");
});
});
describe("stripHTMLReply", () => {
it("Removes <mx-reply> from the input", () => {
expect(stripHTMLReply(`
expect(
stripHTMLReply(`
<mx-reply>
This is part
of the quote
</mx-reply>
But this is not
`).trim()).toBe("But this is not");
`).trim(),
).toBe("But this is not");
});
});

View File

@@ -14,17 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { mocked } from 'jest-mock';
import { mocked } from "jest-mock";
import { ConditionKind, PushRuleActionName, TweakName } from "matrix-js-sdk/src/@types/PushRules";
import { NotificationCountType, Room } from 'matrix-js-sdk/src/models/room';
import { NotificationCountType, Room } from "matrix-js-sdk/src/models/room";
import { mkEvent, stubClient } from "./test-utils";
import { MatrixClientPeg } from "../src/MatrixClientPeg";
import {
getRoomNotifsState,
RoomNotifState,
getUnreadNotificationCount,
} from "../src/RoomNotifs";
import { getRoomNotifsState, RoomNotifState, getUnreadNotificationCount } from "../src/RoomNotifs";
describe("RoomNotifs test", () => {
beforeEach(() => {
@@ -35,12 +31,14 @@ describe("RoomNotifs test", () => {
const cli = MatrixClientPeg.get();
mocked(cli).pushRules = {
global: {
override: [{
rule_id: "!roomId:server",
enabled: true,
default: false,
actions: [],
}],
override: [
{
rule_id: "!roomId:server",
enabled: true,
default: false,
actions: [],
},
],
},
};
expect(getRoomNotifsState(cli, "!roomId:server")).toBe(null);
@@ -56,17 +54,21 @@ describe("RoomNotifs test", () => {
const cli = MatrixClientPeg.get();
cli.pushRules = {
global: {
override: [{
rule_id: "!roomId:server",
enabled: true,
default: false,
conditions: [{
kind: ConditionKind.EventMatch,
key: "room_id",
pattern: "!roomId:server",
}],
actions: [PushRuleActionName.DontNotify],
}],
override: [
{
rule_id: "!roomId:server",
enabled: true,
default: false,
conditions: [
{
kind: ConditionKind.EventMatch,
key: "room_id",
pattern: "!roomId:server",
},
],
actions: [PushRuleActionName.DontNotify],
},
],
},
};
expect(getRoomNotifsState(cli, "!roomId:server")).toBe(RoomNotifState.Mute);

View File

@@ -17,14 +17,14 @@ limitations under the License.
import { mocked } from "jest-mock";
import fetchMock from "fetch-mock-jest";
import ScalarAuthClient from '../src/ScalarAuthClient';
import { stubClient } from './test-utils';
import ScalarAuthClient from "../src/ScalarAuthClient";
import { stubClient } from "./test-utils";
import SdkConfig from "../src/SdkConfig";
import { WidgetType } from "../src/widgets/WidgetType";
describe('ScalarAuthClient', function() {
const apiUrl = 'https://test.com/api';
const uiUrl = 'https:/test.com/app';
describe("ScalarAuthClient", function () {
const apiUrl = "https://test.com/api";
const uiUrl = "https:/test.com/app";
const tokenObject = {
access_token: "token",
token_type: "Bearer",
@@ -33,12 +33,12 @@ describe('ScalarAuthClient', function() {
};
let client;
beforeEach(function() {
beforeEach(function () {
jest.clearAllMocks();
client = stubClient();
});
it('should request a new token if the old one fails', async function() {
it("should request a new token if the old one fails", async function () {
const sac = new ScalarAuthClient(apiUrl + 0, uiUrl);
fetchMock.get("https://test.com/api0/account?scalar_token=brokentoken&v=1.1", {
@@ -60,7 +60,7 @@ describe('ScalarAuthClient', function() {
expect(sac.exchangeForScalarToken).toBeCalledWith(tokenObject);
expect(sac.hasCredentials).toBeTruthy();
// @ts-ignore private property
expect(sac.scalarToken).toEqual('wokentoken');
expect(sac.scalarToken).toEqual("wokentoken");
});
describe("exchangeForScalarToken", () => {
@@ -191,22 +191,29 @@ describe('ScalarAuthClient', function() {
});
it("should send state=disable to API /widgets/set_assets_state", async () => {
fetchMock.get("https://test.com/api8/widgets/set_assets_state?scalar_token=wokentoken1" +
"&widget_type=m.custom&widget_id=id1&state=disable", {
body: "OK",
});
fetchMock.get(
"https://test.com/api8/widgets/set_assets_state?scalar_token=wokentoken1" +
"&widget_type=m.custom&widget_id=id1&state=disable",
{
body: "OK",
},
);
await expect(sac.disableWidgetAssets(WidgetType.CUSTOM, "id1")).resolves.toBeUndefined();
});
it("should throw upon non-20x code", async () => {
fetchMock.get("https://test.com/api8/widgets/set_assets_state?scalar_token=wokentoken1" +
"&widget_type=m.custom&widget_id=id2&state=disable", {
status: 500,
});
fetchMock.get(
"https://test.com/api8/widgets/set_assets_state?scalar_token=wokentoken1" +
"&widget_type=m.custom&widget_id=id2&state=disable",
{
status: 500,
},
);
await expect(sac.disableWidgetAssets(WidgetType.CUSTOM, "id2"))
.rejects.toThrow("Scalar request failed: 500");
await expect(sac.disableWidgetAssets(WidgetType.CUSTOM, "id2")).rejects.toThrow(
"Scalar request failed: 500",
);
});
});
});

View File

@@ -14,18 +14,18 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { MatrixClient, Room } from 'matrix-js-sdk/src/matrix';
import { mocked } from 'jest-mock';
import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
import { mocked } from "jest-mock";
import { Command, Commands, getCommand } from '../src/SlashCommands';
import { createTestClient } from './test-utils';
import { MatrixClientPeg } from '../src/MatrixClientPeg';
import { LocalRoom, LOCAL_ROOM_ID_PREFIX } from '../src/models/LocalRoom';
import SettingsStore from '../src/settings/SettingsStore';
import LegacyCallHandler from '../src/LegacyCallHandler';
import { SdkContextClass } from '../src/contexts/SDKContext';
import { Command, Commands, getCommand } from "../src/SlashCommands";
import { createTestClient } from "./test-utils";
import { MatrixClientPeg } from "../src/MatrixClientPeg";
import { LocalRoom, LOCAL_ROOM_ID_PREFIX } from "../src/models/LocalRoom";
import SettingsStore from "../src/settings/SettingsStore";
import LegacyCallHandler from "../src/LegacyCallHandler";
import { SdkContextClass } from "../src/contexts/SDKContext";
describe('SlashCommands', () => {
describe("SlashCommands", () => {
let client: MatrixClient;
const roomId = "!room:example.com";
let room: Room;
@@ -55,7 +55,7 @@ describe('SlashCommands', () => {
jest.clearAllMocks();
client = createTestClient();
jest.spyOn(MatrixClientPeg, 'get').mockReturnValue(client);
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(client);
room = new Room(roomId, client, client.getUserId());
localRoom = new LocalRoom(localRoomId, client, client.getUserId());
@@ -63,8 +63,8 @@ describe('SlashCommands', () => {
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId");
});
describe('/topic', () => {
it('sets topic', async () => {
describe("/topic", () => {
it("sets topic", async () => {
const command = getCommand("/topic pizza");
expect(command.cmd).toBeDefined();
expect(command.args).toBeDefined();
@@ -226,10 +226,7 @@ describe('SlashCommands', () => {
});
});
describe.each([
"rainbow",
"rainbowme",
])("/%s", (commandName: string) => {
describe.each(["rainbow", "rainbowme"])("/%s", (commandName: string) => {
const command = findCommand(commandName);
it("should return usage if no args", () => {

View File

@@ -14,17 +14,17 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { SlidingSync } from 'matrix-js-sdk/src/sliding-sync';
import { mocked } from 'jest-mock';
import { MatrixClient, MatrixEvent, Room } from 'matrix-js-sdk/src/matrix';
import { SlidingSync } from "matrix-js-sdk/src/sliding-sync";
import { mocked } from "jest-mock";
import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
import { SlidingSyncManager } from '../src/SlidingSyncManager';
import { stubClient } from './test-utils';
import { SlidingSyncManager } from "../src/SlidingSyncManager";
import { stubClient } from "./test-utils";
jest.mock('matrix-js-sdk/src/sliding-sync');
const MockSlidingSync = <jest.Mock<SlidingSync>><unknown>SlidingSync;
jest.mock("matrix-js-sdk/src/sliding-sync");
const MockSlidingSync = <jest.Mock<SlidingSync>>(<unknown>SlidingSync);
describe('SlidingSyncManager', () => {
describe("SlidingSyncManager", () => {
let manager: SlidingSyncManager;
let slidingSync: SlidingSync;
let client: MatrixClient;
@@ -93,24 +93,29 @@ describe('SlidingSyncManager', () => {
await manager.startSpidering(batchSize, gapMs);
// we expect calls for 10,19 -> 20,29 -> 30,39 -> 40,49 -> 50,59 -> 60,69
const wantWindows = [
[10, 19], [20, 29], [30, 39], [40, 49], [50, 59], [60, 69],
[10, 19],
[20, 29],
[30, 39],
[40, 49],
[50, 59],
[60, 69],
];
expect(slidingSync.getListData).toBeCalledTimes(wantWindows.length);
expect(slidingSync.setList).toBeCalledTimes(1);
expect(slidingSync.setListRanges).toBeCalledTimes(wantWindows.length-1);
expect(slidingSync.setListRanges).toBeCalledTimes(wantWindows.length - 1);
wantWindows.forEach((range, i) => {
if (i === 0) {
expect(slidingSync.setList).toBeCalledWith(
manager.getOrAllocateListIndex(SlidingSyncManager.ListSearch),
expect.objectContaining({
ranges: [[0, batchSize-1], range],
ranges: [[0, batchSize - 1], range],
}),
);
return;
}
expect(slidingSync.setListRanges).toBeCalledWith(
manager.getOrAllocateListIndex(SlidingSyncManager.ListSearch),
[[0, batchSize-1], range],
[[0, batchSize - 1], range],
);
});
});
@@ -130,7 +135,10 @@ describe('SlidingSyncManager', () => {
expect(slidingSync.setList).toBeCalledWith(
manager.getOrAllocateListIndex(SlidingSyncManager.ListSearch),
expect.objectContaining({
ranges: [[0, batchSize-1], [batchSize, batchSize+batchSize-1]],
ranges: [
[0, batchSize - 1],
[batchSize, batchSize + batchSize - 1],
],
}),
);
});
@@ -150,7 +158,10 @@ describe('SlidingSyncManager', () => {
expect(slidingSync.setList).toBeCalledWith(
manager.getOrAllocateListIndex(SlidingSyncManager.ListSearch),
expect.objectContaining({
ranges: [[0, batchSize-1], [batchSize, batchSize+batchSize-1]],
ranges: [
[0, batchSize - 1],
[batchSize, batchSize + batchSize - 1],
],
}),
);
});

View File

@@ -14,15 +14,11 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {
MatrixEvent,
EventType,
SERVICE_TYPES,
} from 'matrix-js-sdk/src/matrix';
import { MatrixEvent, EventType, SERVICE_TYPES } from "matrix-js-sdk/src/matrix";
import { startTermsFlow, Service } from '../src/Terms';
import { getMockClientWithEventEmitter } from './test-utils';
import { MatrixClientPeg } from '../src/MatrixClientPeg';
import { startTermsFlow, Service } from "../src/Terms";
import { getMockClientWithEventEmitter } from "./test-utils";
import { MatrixClientPeg } from "../src/MatrixClientPeg";
const POLICY_ONE = {
version: "six",
@@ -40,10 +36,10 @@ const POLICY_TWO = {
},
};
const IM_SERVICE_ONE = new Service(SERVICE_TYPES.IM, 'https://imone.test', 'a token token');
const IM_SERVICE_TWO = new Service(SERVICE_TYPES.IM, 'https://imtwo.test', 'a token token');
const IM_SERVICE_ONE = new Service(SERVICE_TYPES.IM, "https://imone.test", "a token token");
const IM_SERVICE_TWO = new Service(SERVICE_TYPES.IM, "https://imtwo.test", "a token token");
describe('Terms', function() {
describe("Terms", function () {
const mockClient = getMockClientWithEventEmitter({
getAccountData: jest.fn(),
getTerms: jest.fn(),
@@ -51,7 +47,7 @@ describe('Terms', function() {
setAccountData: jest.fn(),
});
beforeEach(function() {
beforeEach(function () {
jest.clearAllMocks();
mockClient.getAccountData.mockReturnValue(null);
mockClient.getTerms.mockResolvedValue(null);
@@ -59,30 +55,33 @@ describe('Terms', function() {
});
afterAll(() => {
jest.spyOn(MatrixClientPeg, 'get').mockRestore();
jest.spyOn(MatrixClientPeg, "get").mockRestore();
});
it('should prompt for all terms & services if no account data', async function() {
it("should prompt for all terms & services if no account data", async function () {
mockClient.getAccountData.mockReturnValue(null);
mockClient.getTerms.mockResolvedValue({
policies: {
"policy_the_first": POLICY_ONE,
policy_the_first: POLICY_ONE,
},
});
const interactionCallback = jest.fn().mockResolvedValue([]);
await startTermsFlow([IM_SERVICE_ONE], interactionCallback);
expect(interactionCallback).toBeCalledWith([
{
service: IM_SERVICE_ONE,
policies: {
policy_the_first: POLICY_ONE,
expect(interactionCallback).toBeCalledWith(
[
{
service: IM_SERVICE_ONE,
policies: {
policy_the_first: POLICY_ONE,
},
},
},
], []);
],
[],
);
});
it('should not prompt if all policies are signed in account data', async function() {
it("should not prompt if all policies are signed in account data", async function () {
const directEvent = new MatrixEvent({
type: EventType.Direct,
content: {
@@ -92,7 +91,7 @@ describe('Terms', function() {
mockClient.getAccountData.mockReturnValue(directEvent);
mockClient.getTerms.mockResolvedValue({
policies: {
"policy_the_first": POLICY_ONE,
policy_the_first: POLICY_ONE,
},
});
mockClient.agreeToTerms;
@@ -101,15 +100,12 @@ describe('Terms', function() {
await startTermsFlow([IM_SERVICE_ONE], interactionCallback);
expect(interactionCallback).not.toHaveBeenCalled();
expect(mockClient.agreeToTerms).toBeCalledWith(
SERVICE_TYPES.IM,
'https://imone.test',
'a token token',
["http://example.com/one"],
);
expect(mockClient.agreeToTerms).toBeCalledWith(SERVICE_TYPES.IM, "https://imone.test", "a token token", [
"http://example.com/one",
]);
});
it("should prompt for only terms that aren't already signed", async function() {
it("should prompt for only terms that aren't already signed", async function () {
const directEvent = new MatrixEvent({
type: EventType.Direct,
content: {
@@ -120,31 +116,32 @@ describe('Terms', function() {
mockClient.getTerms.mockResolvedValue({
policies: {
"policy_the_first": POLICY_ONE,
"policy_the_second": POLICY_TWO,
policy_the_first: POLICY_ONE,
policy_the_second: POLICY_TWO,
},
});
const interactionCallback = jest.fn().mockResolvedValue(["http://example.com/one", "http://example.com/two"]);
await startTermsFlow([IM_SERVICE_ONE], interactionCallback);
expect(interactionCallback).toBeCalledWith([
{
service: IM_SERVICE_ONE,
policies: {
policy_the_second: POLICY_TWO,
expect(interactionCallback).toBeCalledWith(
[
{
service: IM_SERVICE_ONE,
policies: {
policy_the_second: POLICY_TWO,
},
},
},
], ["http://example.com/one"]);
expect(mockClient.agreeToTerms).toBeCalledWith(
SERVICE_TYPES.IM,
'https://imone.test',
'a token token',
["http://example.com/one", "http://example.com/two"],
],
["http://example.com/one"],
);
expect(mockClient.agreeToTerms).toBeCalledWith(SERVICE_TYPES.IM, "https://imone.test", "a token token", [
"http://example.com/one",
"http://example.com/two",
]);
});
it("should prompt for only services with un-agreed policies", async function() {
it("should prompt for only services with un-agreed policies", async function () {
const directEvent = new MatrixEvent({
type: EventType.Direct,
content: {
@@ -155,16 +152,16 @@ describe('Terms', function() {
mockClient.getTerms.mockImplementation(async (_serviceTypes: SERVICE_TYPES, baseUrl: string) => {
switch (baseUrl) {
case 'https://imone.test':
case "https://imone.test":
return {
policies: {
"policy_the_first": POLICY_ONE,
policy_the_first: POLICY_ONE,
},
};
case 'https://imtwo.test':
case "https://imtwo.test":
return {
policies: {
"policy_the_second": POLICY_TWO,
policy_the_second: POLICY_TWO,
},
};
}
@@ -173,26 +170,22 @@ describe('Terms', function() {
const interactionCallback = jest.fn().mockResolvedValue(["http://example.com/one", "http://example.com/two"]);
await startTermsFlow([IM_SERVICE_ONE, IM_SERVICE_TWO], interactionCallback);
expect(interactionCallback).toBeCalledWith([
{
service: IM_SERVICE_TWO,
policies: {
policy_the_second: POLICY_TWO,
expect(interactionCallback).toBeCalledWith(
[
{
service: IM_SERVICE_TWO,
policies: {
policy_the_second: POLICY_TWO,
},
},
},
], ["http://example.com/one"]);
expect(mockClient.agreeToTerms).toBeCalledWith(
SERVICE_TYPES.IM,
'https://imone.test',
'a token token',
],
["http://example.com/one"],
);
expect(mockClient.agreeToTerms).toBeCalledWith(
SERVICE_TYPES.IM,
'https://imtwo.test',
'a token token',
["http://example.com/two"],
);
expect(mockClient.agreeToTerms).toBeCalledWith(SERVICE_TYPES.IM, "https://imone.test", "a token token", [
"http://example.com/one",
]);
expect(mockClient.agreeToTerms).toBeCalledWith(SERVICE_TYPES.IM, "https://imtwo.test", "a token token", [
"http://example.com/two",
]);
});
});

View File

@@ -15,26 +15,23 @@ limitations under the License.
*/
import { EventType, MatrixClient, MatrixEvent, Room, RoomMember } from "matrix-js-sdk/src/matrix";
import TestRenderer from 'react-test-renderer';
import TestRenderer from "react-test-renderer";
import { ReactElement } from "react";
import { mocked } from "jest-mock";
import { getSenderName, textForEvent } from "../src/TextForEvent";
import SettingsStore from "../src/settings/SettingsStore";
import { createTestClient, stubClient } from './test-utils';
import { MatrixClientPeg } from '../src/MatrixClientPeg';
import UserIdentifierCustomisations from '../src/customisations/UserIdentifier';
import { createTestClient, stubClient } from "./test-utils";
import { MatrixClientPeg } from "../src/MatrixClientPeg";
import UserIdentifierCustomisations from "../src/customisations/UserIdentifier";
import { ElementCall } from "../src/models/Call";
jest.mock("../src/settings/SettingsStore");
jest.mock('../src/customisations/UserIdentifier', () => ({
getDisplayUserIdentifier: jest.fn().mockImplementation(userId => userId),
jest.mock("../src/customisations/UserIdentifier", () => ({
getDisplayUserIdentifier: jest.fn().mockImplementation((userId) => userId),
}));
function mockPinnedEvent(
pinnedMessageIds?: string[],
prevPinnedMessageIds?: string[],
): MatrixEvent {
function mockPinnedEvent(pinnedMessageIds?: string[], prevPinnedMessageIds?: string[]): MatrixEvent {
return new MatrixEvent({
type: "m.room.pinned_events",
state_key: "",
@@ -53,31 +50,33 @@ function mockPinnedEvent(
// and should be replaced with snapshots.
function renderComponent(component): string {
const serializeObject = (object): string => {
if (typeof object === 'string') {
return object === ' ' ? '' : object;
if (typeof object === "string") {
return object === " " ? "" : object;
}
if (Array.isArray(object) && object.length === 1 && typeof object[0] === 'string') {
if (Array.isArray(object) && object.length === 1 && typeof object[0] === "string") {
return object[0];
}
if (object['type'] !== undefined && typeof object['children'] !== undefined) {
if (object["type"] !== undefined && typeof object["children"] !== undefined) {
return serializeObject(object.children);
}
if (!Array.isArray(object)) {
return '';
return "";
}
return object.map(child => {
return serializeObject(child);
}).join('');
return object
.map((child) => {
return serializeObject(child);
})
.join("");
};
return serializeObject(component.toJSON());
}
describe('TextForEvent', () => {
describe("TextForEvent", () => {
describe("getSenderName()", () => {
it("Prefers sender.name", () => {
expect(getSenderName({ sender: { name: "Alice" } } as MatrixEvent)).toBe("Alice");
@@ -93,11 +92,11 @@ describe('TextForEvent', () => {
describe("TextForPinnedEvent", () => {
beforeAll(() => {
// enable feature_pinning setting
(SettingsStore.getValue as jest.Mock).mockImplementation(feature => feature === 'feature_pinning');
(SettingsStore.getValue as jest.Mock).mockImplementation((feature) => feature === "feature_pinning");
});
it("mentions message when a single message was pinned, with no previously pinned messages", () => {
const event = mockPinnedEvent(['message-1']);
const event = mockPinnedEvent(["message-1"]);
const plainText = textForEvent(event);
const component = TestRenderer.create(textForEvent(event, true) as ReactElement);
@@ -107,7 +106,7 @@ describe('TextForEvent', () => {
});
it("mentions message when a single message was pinned, with multiple previously pinned messages", () => {
const event = mockPinnedEvent(['message-1', 'message-2', 'message-3'], ['message-1', 'message-2']);
const event = mockPinnedEvent(["message-1", "message-2", "message-3"], ["message-1", "message-2"]);
const plainText = textForEvent(event);
const component = TestRenderer.create(textForEvent(event, true) as ReactElement);
@@ -117,7 +116,7 @@ describe('TextForEvent', () => {
});
it("mentions message when a single message was unpinned, with a single message previously pinned", () => {
const event = mockPinnedEvent([], ['message-1']);
const event = mockPinnedEvent([], ["message-1"]);
const plainText = textForEvent(event);
const component = TestRenderer.create(textForEvent(event, true) as ReactElement);
@@ -127,7 +126,7 @@ describe('TextForEvent', () => {
});
it("mentions message when a single message was unpinned, with multiple previously pinned messages", () => {
const event = mockPinnedEvent(['message-2'], ['message-1', 'message-2']);
const event = mockPinnedEvent(["message-2"], ["message-1", "message-2"]);
const plainText = textForEvent(event);
const component = TestRenderer.create(textForEvent(event, true) as ReactElement);
@@ -137,7 +136,7 @@ describe('TextForEvent', () => {
});
it("shows generic text when multiple messages were pinned", () => {
const event = mockPinnedEvent(['message-1', 'message-2', 'message-3'], ['message-1']);
const event = mockPinnedEvent(["message-1", "message-2", "message-3"], ["message-1"]);
const plainText = textForEvent(event);
const component = TestRenderer.create(textForEvent(event, true) as ReactElement);
@@ -147,7 +146,7 @@ describe('TextForEvent', () => {
});
it("shows generic text when multiple messages were unpinned", () => {
const event = mockPinnedEvent(['message-3'], ['message-1', 'message-2', 'message-3']);
const event = mockPinnedEvent(["message-3"], ["message-1", "message-2", "message-3"]);
const plainText = textForEvent(event);
const component = TestRenderer.create(textForEvent(event, true) as ReactElement);
@@ -157,7 +156,7 @@ describe('TextForEvent', () => {
});
it("shows generic text when one message was pinned, and another unpinned", () => {
const event = mockPinnedEvent(['message-2'], ['message-1']);
const event = mockPinnedEvent(["message-2"], ["message-1"]);
const plainText = textForEvent(event);
const component = TestRenderer.create(textForEvent(event, true) as ReactElement);
@@ -174,19 +173,19 @@ describe('TextForEvent', () => {
};
const userA = {
id: '@a',
name: 'Alice',
rawDisplayName: 'Alice',
id: "@a",
name: "Alice",
rawDisplayName: "Alice",
};
const userB = {
id: '@b',
name: 'Bob (@b)',
rawDisplayName: 'Bob',
id: "@b",
name: "Bob (@b)",
rawDisplayName: "Bob",
};
const userC = {
id: '@c',
name: 'Bob (@c)',
rawDisplayName: 'Bob',
id: "@c",
name: "Bob (@c)",
rawDisplayName: "Bob",
};
interface PowerEventProps {
usersDefault?: number;
@@ -194,9 +193,7 @@ describe('TextForEvent', () => {
users: Record<string, number>;
prevUsers: Record<string, number>;
}
const mockPowerEvent = ({
usersDefault, prevDefault, users, prevUsers,
}: PowerEventProps): MatrixEvent => {
const mockPowerEvent = ({ usersDefault, prevDefault, users, prevUsers }: PowerEventProps): MatrixEvent => {
const mxEvent = new MatrixEvent({
type: EventType.RoomPowerLevels,
sender: userA.id,
@@ -218,16 +215,16 @@ describe('TextForEvent', () => {
mockClient = createTestClient();
MatrixClientPeg.get = () => mockClient;
mockClient.getRoom.mockClear().mockReturnValue(mockRoom);
mockRoom.getMember.mockClear().mockImplementation(
userId => [userA, userB, userC].find(u => u.id === userId),
);
mockRoom.getMember
.mockClear()
.mockImplementation((userId) => [userA, userB, userC].find((u) => u.id === userId));
(SettingsStore.getValue as jest.Mock).mockReturnValue(true);
});
beforeEach(() => {
(UserIdentifierCustomisations.getDisplayUserIdentifier as jest.Mock)
.mockClear()
.mockImplementation(userId => userId);
.mockImplementation((userId) => userId);
});
it("returns falsy when no users have changed power level", () => {
@@ -308,80 +305,112 @@ describe('TextForEvent', () => {
[userC.id]: 101,
},
});
const expectedText = "Alice changed the power level of Bob (@b) from Moderator to Admin,"
+ " Bob (@c) from Custom (101) to Moderator.";
const expectedText =
"Alice changed the power level of Bob (@b) from Moderator to Admin," +
" Bob (@c) from Custom (101) to Moderator.";
expect(textForEvent(event)).toEqual(expectedText);
});
});
describe("textForCanonicalAliasEvent()", () => {
const userA = {
id: '@a',
name: 'Alice',
id: "@a",
name: "Alice",
};
interface AliasEventProps {
alias?: string; prevAlias?: string; altAliases?: string[]; prevAltAliases?: string[];
alias?: string;
prevAlias?: string;
altAliases?: string[];
prevAltAliases?: string[];
}
const mockEvent = ({
alias, prevAlias, altAliases, prevAltAliases,
}: AliasEventProps): MatrixEvent => new MatrixEvent({
type: EventType.RoomCanonicalAlias,
sender: userA.id,
state_key: "",
content: {
alias, alt_aliases: altAliases,
},
prev_content: {
alias: prevAlias, alt_aliases: prevAltAliases,
},
});
const mockEvent = ({ alias, prevAlias, altAliases, prevAltAliases }: AliasEventProps): MatrixEvent =>
new MatrixEvent({
type: EventType.RoomCanonicalAlias,
sender: userA.id,
state_key: "",
content: {
alias,
alt_aliases: altAliases,
},
prev_content: {
alias: prevAlias,
alt_aliases: prevAltAliases,
},
});
type TestCase = [string, AliasEventProps & { result: string }];
const testCases: TestCase[] = [
["room alias didn't change", {
result: '@a changed the addresses for this room.',
}],
["room alias changed", {
alias: 'banana',
prevAlias: 'apple',
result: '@a set the main address for this room to banana.',
}],
["room alias was added", {
alias: 'banana',
result: '@a set the main address for this room to banana.',
}],
["room alias was removed", {
prevAlias: 'apple',
result: '@a removed the main address for this room.',
}],
["added an alt alias", {
altAliases: ['canteloupe'],
result: '@a added alternative address canteloupe for this room.',
}],
["added multiple alt aliases", {
altAliases: ['canteloupe', 'date'],
result: '@a added the alternative addresses canteloupe, date for this room.',
}],
["removed an alt alias", {
altAliases: ['canteloupe'],
prevAltAliases: ['canteloupe', 'date'],
result: '@a removed alternative address date for this room.',
}],
["added and removed an alt aliases", {
altAliases: ['canteloupe', 'elderberry'],
prevAltAliases: ['canteloupe', 'date'],
result: '@a changed the alternative addresses for this room.',
}],
["changed alias and added alt alias", {
alias: 'banana',
prevAlias: 'apple',
altAliases: ['canteloupe'],
result: '@a changed the main and alternative addresses for this room.',
}],
[
"room alias didn't change",
{
result: "@a changed the addresses for this room.",
},
],
[
"room alias changed",
{
alias: "banana",
prevAlias: "apple",
result: "@a set the main address for this room to banana.",
},
],
[
"room alias was added",
{
alias: "banana",
result: "@a set the main address for this room to banana.",
},
],
[
"room alias was removed",
{
prevAlias: "apple",
result: "@a removed the main address for this room.",
},
],
[
"added an alt alias",
{
altAliases: ["canteloupe"],
result: "@a added alternative address canteloupe for this room.",
},
],
[
"added multiple alt aliases",
{
altAliases: ["canteloupe", "date"],
result: "@a added the alternative addresses canteloupe, date for this room.",
},
],
[
"removed an alt alias",
{
altAliases: ["canteloupe"],
prevAltAliases: ["canteloupe", "date"],
result: "@a removed alternative address date for this room.",
},
],
[
"added and removed an alt aliases",
{
altAliases: ["canteloupe", "elderberry"],
prevAltAliases: ["canteloupe", "date"],
result: "@a changed the alternative addresses for this room.",
},
],
[
"changed alias and added alt alias",
{
alias: "banana",
prevAlias: "apple",
altAliases: ["canteloupe"],
result: "@a changed the main and alternative addresses for this room.",
},
],
];
it.each(testCases)('returns correct message when %s', (_d, { result, ...eventProps }) => {
it.each(testCases)("returns correct message when %s", (_d, { result, ...eventProps }) => {
const event = mockEvent(eventProps);
expect(textForEvent(event)).toEqual(result);
});
@@ -392,18 +421,15 @@ describe('TextForEvent', () => {
beforeEach(() => {
pollEvent = new MatrixEvent({
type: 'org.matrix.msc3381.poll.start',
sender: '@a',
type: "org.matrix.msc3381.poll.start",
sender: "@a",
content: {
'org.matrix.msc3381.poll.start': {
answers: [
{ 'org.matrix.msc1767.text': 'option1' },
{ 'org.matrix.msc1767.text': 'option2' },
],
"org.matrix.msc3381.poll.start": {
answers: [{ "org.matrix.msc1767.text": "option1" }, { "org.matrix.msc1767.text": "option2" }],
question: {
'body': 'Test poll name',
'msgtype': 'm.text',
'org.matrix.msc1767.text': 'Test poll name',
"body": "Test poll name",
"msgtype": "m.text",
"org.matrix.msc1767.text": "Test poll name",
},
},
},
@@ -413,11 +439,11 @@ describe('TextForEvent', () => {
it("returns correct message for redacted poll start", () => {
pollEvent.makeRedacted(pollEvent);
expect(textForEvent(pollEvent)).toEqual('@a: Message deleted');
expect(textForEvent(pollEvent)).toEqual("@a: Message deleted");
});
it("returns correct message for normal poll start", () => {
expect(textForEvent(pollEvent)).toEqual('@a has started a poll - ');
expect(textForEvent(pollEvent)).toEqual("@a has started a poll - ");
});
});
@@ -426,12 +452,12 @@ describe('TextForEvent', () => {
beforeEach(() => {
messageEvent = new MatrixEvent({
type: 'm.room.message',
sender: '@a',
type: "m.room.message",
sender: "@a",
content: {
'body': 'test message',
'msgtype': 'm.text',
'org.matrix.msc1767.text': 'test message',
"body": "test message",
"msgtype": "m.text",
"org.matrix.msc1767.text": "test message",
},
});
});
@@ -439,11 +465,11 @@ describe('TextForEvent', () => {
it("returns correct message for redacted message", () => {
messageEvent.makeRedacted(messageEvent);
expect(textForEvent(messageEvent)).toEqual('@a: Message deleted');
expect(textForEvent(messageEvent)).toEqual("@a: Message deleted");
});
it("returns correct message for normal message", () => {
expect(textForEvent(messageEvent)).toEqual('@a: test message');
expect(textForEvent(messageEvent)).toEqual("@a: test message");
});
});
@@ -472,14 +498,14 @@ describe('TextForEvent', () => {
});
it("returns correct message for call event when supported", () => {
expect(textForEvent(callEvent)).toEqual('Video call started in Test room.');
expect(textForEvent(callEvent)).toEqual("Video call started in Test room.");
});
it("returns correct message for call event when supported", () => {
mocked(mockClient).supportsVoip.mockReturnValue(false);
expect(textForEvent(callEvent)).toEqual(
'Video call started in Test room. (not supported by this browser)',
"Video call started in Test room. (not supported by this browser)",
);
});
});

View File

@@ -15,11 +15,7 @@ limitations under the License.
*/
import { mocked } from "jest-mock";
import {
MatrixEvent,
EventType,
MsgType,
} from "matrix-js-sdk/src/matrix";
import { MatrixEvent, EventType, MsgType } from "matrix-js-sdk/src/matrix";
import { haveRendererForEvent } from "../src/events/EventTileFactory";
import { getMockClientWithEventEmitter, makeBeaconEvent, mockClientMethodsUser } from "./test-utils";
@@ -29,9 +25,9 @@ jest.mock("../src/events/EventTileFactory", () => ({
haveRendererForEvent: jest.fn(),
}));
describe('eventTriggersUnreadCount()', () => {
const aliceId = '@alice:server.org';
const bobId = '@bob:server.org';
describe("eventTriggersUnreadCount()", () => {
const aliceId = "@alice:server.org";
const bobId = "@bob:server.org";
// mock user credentials
getMockClientWithEventEmitter({
@@ -44,7 +40,7 @@ describe('eventTriggersUnreadCount()', () => {
sender: aliceId,
content: {
msgtype: MsgType.Text,
body: 'Hello from Alice',
body: "Hello from Alice",
},
});
@@ -53,7 +49,7 @@ describe('eventTriggersUnreadCount()', () => {
sender: bobId,
content: {
msgtype: MsgType.Text,
body: 'Hello from Bob',
body: "Hello from Bob",
},
});
@@ -68,31 +64,31 @@ describe('eventTriggersUnreadCount()', () => {
mocked(haveRendererForEvent).mockClear().mockReturnValue(false);
});
it('returns false when the event was sent by the current user', () => {
it("returns false when the event was sent by the current user", () => {
expect(eventTriggersUnreadCount(bobsMessage)).toBe(false);
// returned early before checking renderer
expect(haveRendererForEvent).not.toHaveBeenCalled();
});
it('returns false for a redacted event', () => {
it("returns false for a redacted event", () => {
expect(eventTriggersUnreadCount(redactedEvent)).toBe(false);
// returned early before checking renderer
expect(haveRendererForEvent).not.toHaveBeenCalled();
});
it('returns false for an event without a renderer', () => {
it("returns false for an event without a renderer", () => {
mocked(haveRendererForEvent).mockReturnValue(false);
expect(eventTriggersUnreadCount(alicesMessage)).toBe(false);
expect(haveRendererForEvent).toHaveBeenCalledWith(alicesMessage, false);
});
it('returns true for an event with a renderer', () => {
it("returns true for an event with a renderer", () => {
mocked(haveRendererForEvent).mockReturnValue(true);
expect(eventTriggersUnreadCount(alicesMessage)).toBe(true);
expect(haveRendererForEvent).toHaveBeenCalledWith(alicesMessage, false);
});
it('returns false for beacon locations', () => {
it("returns false for beacon locations", () => {
const beaconLocationEvent = makeBeaconEvent(aliceId);
expect(eventTriggersUnreadCount(beaconLocationEvent)).toBe(false);
expect(haveRendererForEvent).not.toHaveBeenCalled();
@@ -107,7 +103,7 @@ describe('eventTriggersUnreadCount()', () => {
EventType.RoomServerAcl,
];
it.each(noUnreadEventTypes)('returns false without checking for renderer for events with type %s', (eventType) => {
it.each(noUnreadEventTypes)("returns false without checking for renderer for events with type %s", (eventType) => {
const event = new MatrixEvent({
type: eventType,
sender: aliceId,

View File

@@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import FakeTimers from '@sinonjs/fake-timers';
import EventEmitter from 'events';
import FakeTimers from "@sinonjs/fake-timers";
import EventEmitter from "events";
import UserActivity from '../src/UserActivity';
import UserActivity from "../src/UserActivity";
class FakeDomEventEmitter extends EventEmitter {
addEventListener(what, l) {
@@ -29,13 +29,13 @@ class FakeDomEventEmitter extends EventEmitter {
}
}
describe('UserActivity', function() {
describe("UserActivity", function () {
let fakeWindow;
let fakeDocument;
let userActivity;
let clock;
beforeEach(function() {
beforeEach(function () {
fakeWindow = new FakeDomEventEmitter();
fakeDocument = new FakeDomEventEmitter();
userActivity = new UserActivity(fakeWindow, fakeDocument);
@@ -43,26 +43,26 @@ describe('UserActivity', function() {
clock = FakeTimers.install();
});
afterEach(function() {
afterEach(function () {
userActivity.stop();
userActivity = null;
clock.uninstall();
clock = null;
});
it('should return the same shared instance', function() {
it("should return the same shared instance", function () {
expect(UserActivity.sharedInstance()).toBe(UserActivity.sharedInstance());
});
it('should consider user inactive if no activity', function() {
it("should consider user inactive if no activity", function () {
expect(userActivity.userActiveNow()).toBe(false);
});
it('should consider user not active recently if no activity', function() {
it("should consider user not active recently if no activity", function () {
expect(userActivity.userActiveRecently()).toBe(false);
});
it('should not consider user active after activity if no window focus', function() {
it("should not consider user active after activity if no window focus", function () {
fakeDocument.hasFocus = jest.fn().mockReturnValue(false);
userActivity.onUserActivity({});
@@ -70,7 +70,7 @@ describe('UserActivity', function() {
expect(userActivity.userActiveRecently()).toBe(false);
});
it('should consider user active shortly after activity', function() {
it("should consider user active shortly after activity", function () {
fakeDocument.hasFocus = jest.fn().mockReturnValue(true);
userActivity.onUserActivity({});
@@ -81,7 +81,7 @@ describe('UserActivity', function() {
expect(userActivity.userActiveRecently()).toBe(true);
});
it('should consider user not active after 10s of no activity', function() {
it("should consider user not active after 10s of no activity", function () {
fakeDocument.hasFocus = jest.fn().mockReturnValue(true);
userActivity.onUserActivity({});
@@ -89,7 +89,7 @@ describe('UserActivity', function() {
expect(userActivity.userActiveNow()).toBe(false);
});
it('should consider user passive after 10s of no activity', function() {
it("should consider user passive after 10s of no activity", function () {
fakeDocument.hasFocus = jest.fn().mockReturnValue(true);
userActivity.onUserActivity({});
@@ -97,19 +97,19 @@ describe('UserActivity', function() {
expect(userActivity.userActiveRecently()).toBe(true);
});
it('should not consider user passive after 10s if window un-focused', function() {
it("should not consider user passive after 10s if window un-focused", function () {
fakeDocument.hasFocus = jest.fn().mockReturnValue(true);
userActivity.onUserActivity({});
clock.tick(10000);
fakeDocument.hasFocus = jest.fn().mockReturnValue(false);
fakeWindow.emit('blur', {});
fakeWindow.emit("blur", {});
expect(userActivity.userActiveRecently()).toBe(false);
});
it('should not consider user passive after 3 mins', function() {
it("should not consider user passive after 3 mins", function () {
fakeDocument.hasFocus = jest.fn().mockReturnValue(true);
userActivity.onUserActivity({});
@@ -118,7 +118,7 @@ describe('UserActivity', function() {
expect(userActivity.userActiveRecently()).toBe(false);
});
it('should extend timer on activity', function() {
it("should extend timer on activity", function () {
fakeDocument.hasFocus = jest.fn().mockReturnValue(true);
userActivity.onUserActivity({});

View File

@@ -41,8 +41,8 @@ describe("KeyboardShortcutUtils", () => {
it("doesn't change KEYBOARD_SHORTCUTS when getting shortcuts", async () => {
mockKeyboardShortcuts({
KEYBOARD_SHORTCUTS: {
"Keybind1": {},
"Keybind2": {},
Keybind1: {},
Keybind2: {},
},
MAC_ONLY_SHORTCUTS: ["Keybind1"],
DESKTOP_SHORTCUTS: ["Keybind2"],
@@ -62,29 +62,29 @@ describe("KeyboardShortcutUtils", () => {
it("when on web and not on macOS ", async () => {
mockKeyboardShortcuts({
KEYBOARD_SHORTCUTS: {
"Keybind1": {},
"Keybind2": {},
"Keybind3": { "controller": { settingDisabled: true } },
"Keybind4": {},
Keybind1: {},
Keybind2: {},
Keybind3: { controller: { settingDisabled: true } },
Keybind4: {},
},
MAC_ONLY_SHORTCUTS: ["Keybind1"],
DESKTOP_SHORTCUTS: ["Keybind2"],
});
mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) });
expect((await getUtils()).getKeyboardShortcuts()).toEqual({ "Keybind4": {} });
expect((await getUtils()).getKeyboardShortcuts()).toEqual({ Keybind4: {} });
});
it("when on desktop", async () => {
mockKeyboardShortcuts({
KEYBOARD_SHORTCUTS: {
"Keybind1": {},
"Keybind2": {},
Keybind1: {},
Keybind2: {},
},
MAC_ONLY_SHORTCUTS: [],
DESKTOP_SHORTCUTS: ["Keybind2"],
});
mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(true) });
expect((await getUtils()).getKeyboardShortcuts()).toEqual({ "Keybind1": {}, "Keybind2": {} });
expect((await getUtils()).getKeyboardShortcuts()).toEqual({ Keybind1: {}, Keybind2: {} });
});
});
});

View File

@@ -46,26 +46,38 @@ const button4 = <Button key={4}>d</Button>;
// mock offsetParent
Object.defineProperty(HTMLElement.prototype, "offsetParent", {
get() { return this.parentNode; },
get() {
return this.parentNode;
},
});
describe("RovingTabIndex", () => {
it("RovingTabIndexProvider renders children as expected", () => {
const { container } = render(<RovingTabIndexProvider>
{ () => <div><span>Test</span></div> }
</RovingTabIndexProvider>);
const { container } = render(
<RovingTabIndexProvider>
{() => (
<div>
<span>Test</span>
</div>
)}
</RovingTabIndexProvider>,
);
expect(container.textContent).toBe("Test");
expect(container.innerHTML).toBe('<div><span>Test</span></div>');
expect(container.innerHTML).toBe("<div><span>Test</span></div>");
});
it("RovingTabIndexProvider works as expected with useRovingTabIndex", () => {
const { container, rerender } = render(<RovingTabIndexProvider>
{ () => <React.Fragment>
{ button1 }
{ button2 }
{ button3 }
</React.Fragment> }
</RovingTabIndexProvider>);
const { container, rerender } = render(
<RovingTabIndexProvider>
{() => (
<React.Fragment>
{button1}
{button2}
{button3}
</React.Fragment>
)}
</RovingTabIndexProvider>,
);
// should begin with 0th being active
checkTabIndexes(container.querySelectorAll("button"), [0, -1, -1]);
@@ -83,42 +95,57 @@ describe("RovingTabIndex", () => {
checkTabIndexes(container.querySelectorAll("button"), [-1, 0, -1]);
// update the children, it should remain on the same button
rerender(<RovingTabIndexProvider>
{ () => <React.Fragment>
{ button1 }
{ button4 }
{ button2 }
{ button3 }
</React.Fragment> }
</RovingTabIndexProvider>);
rerender(
<RovingTabIndexProvider>
{() => (
<React.Fragment>
{button1}
{button4}
{button2}
{button3}
</React.Fragment>
)}
</RovingTabIndexProvider>,
);
checkTabIndexes(container.querySelectorAll("button"), [-1, -1, 0, -1]);
// update the children, remove the active button, it should move to the next one
rerender(<RovingTabIndexProvider>
{ () => <React.Fragment>
{ button1 }
{ button4 }
{ button3 }
</React.Fragment> }
</RovingTabIndexProvider>);
rerender(
<RovingTabIndexProvider>
{() => (
<React.Fragment>
{button1}
{button4}
{button3}
</React.Fragment>
)}
</RovingTabIndexProvider>,
);
checkTabIndexes(container.querySelectorAll("button"), [-1, -1, 0]);
});
it("RovingTabIndexProvider works as expected with RovingTabIndexWrapper", () => {
const { container } = render(<RovingTabIndexProvider>
{ () => <React.Fragment>
{ button1 }
{ button2 }
<RovingTabIndexWrapper>
{ ({ onFocus, isActive, ref }) =>
<button
onFocus={onFocus}
tabIndex={isActive ? 0 : -1}
ref={ref as React.RefObject<HTMLButtonElement>}>.</button>
}
</RovingTabIndexWrapper>
</React.Fragment> }
</RovingTabIndexProvider>);
const { container } = render(
<RovingTabIndexProvider>
{() => (
<React.Fragment>
{button1}
{button2}
<RovingTabIndexWrapper>
{({ onFocus, isActive, ref }) => (
<button
onFocus={onFocus}
tabIndex={isActive ? 0 : -1}
ref={ref as React.RefObject<HTMLButtonElement>}
>
.
</button>
)}
</RovingTabIndexWrapper>
</React.Fragment>
)}
</RovingTabIndexProvider>,
);
// should begin with 0th being active
checkTabIndexes(container.querySelectorAll("button"), [0, -1, -1]);
@@ -132,15 +159,20 @@ describe("RovingTabIndex", () => {
it("SetFocus works as expected", () => {
const ref1 = React.createRef<HTMLElement>();
const ref2 = React.createRef<HTMLElement>();
expect(reducer({
activeRef: ref1,
refs: [ref1, ref2],
}, {
type: Type.SetFocus,
payload: {
ref: ref2,
},
})).toStrictEqual({
expect(
reducer(
{
activeRef: ref1,
refs: [ref1, ref2],
},
{
type: Type.SetFocus,
payload: {
ref: ref2,
},
},
),
).toStrictEqual({
activeRef: ref2,
refs: [ref1, ref2],
});
@@ -208,12 +240,14 @@ describe("RovingTabIndex", () => {
const ref3 = React.createRef<HTMLElement>();
const ref4 = React.createRef<HTMLElement>();
render(<React.Fragment>
<span ref={ref1} />
<span ref={ref2} />
<span ref={ref3} />
<span ref={ref4} />
</React.Fragment>);
render(
<React.Fragment>
<span ref={ref1} />
<span ref={ref2} />
<span ref={ref3} />
<span ref={ref4} />
</React.Fragment>,
);
let state: IState = {
activeRef: null,
@@ -337,4 +371,3 @@ describe("RovingTabIndex", () => {
});
});
});

View File

@@ -19,14 +19,14 @@ import { UserTab } from "../../../src/components/views/dialogs/UserTab";
import { Action } from "../../../src/dispatcher/actions";
import defaultDispatcher from "../../../src/dispatcher/dispatcher";
describe('viewUserDeviceSettings()', () => {
const dispatchSpy = jest.spyOn(defaultDispatcher, 'dispatch');
describe("viewUserDeviceSettings()", () => {
const dispatchSpy = jest.spyOn(defaultDispatcher, "dispatch");
beforeEach(() => {
dispatchSpy.mockClear();
});
it('dispatches action to view new session manager when enabled', () => {
it("dispatches action to view new session manager when enabled", () => {
const isNewDeviceManagerEnabled = true;
viewUserDeviceSettings(isNewDeviceManagerEnabled);
@@ -36,7 +36,7 @@ describe('viewUserDeviceSettings()', () => {
});
});
it('dispatches action to view old session manager when disabled', () => {
it("dispatches action to view old session manager when disabled", () => {
const isNewDeviceManagerEnabled = false;
viewUserDeviceSettings(isNewDeviceManagerEnabled);

View File

@@ -14,18 +14,18 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { mocked } from 'jest-mock';
import { logger } from 'matrix-js-sdk/src/logger';
import { mocked } from "jest-mock";
import { logger } from "matrix-js-sdk/src/logger";
import { createAudioContext, decodeOgg } from '../../src/audio/compat';
import { createAudioContext, decodeOgg } from "../../src/audio/compat";
import { Playback, PlaybackState } from "../../src/audio/Playback";
jest.mock('../../src/audio/compat', () => ({
jest.mock("../../src/audio/compat", () => ({
createAudioContext: jest.fn(),
decodeOgg: jest.fn(),
}));
describe('Playback', () => {
describe("Playback", () => {
const mockAudioBufferSourceNode = {
addEventListener: jest.fn(),
connect: jest.fn(),
@@ -47,18 +47,16 @@ describe('Playback', () => {
const mockChannelData = new Float32Array();
beforeEach(() => {
jest.spyOn(logger, 'error').mockRestore();
jest.spyOn(logger, "error").mockRestore();
mockAudioBuffer.getChannelData.mockClear().mockReturnValue(mockChannelData);
mockAudioContext.decodeAudioData.mockReset().mockImplementation(
(_b, callback) => callback(mockAudioBuffer),
);
mockAudioContext.decodeAudioData.mockReset().mockImplementation((_b, callback) => callback(mockAudioBuffer));
mockAudioContext.resume.mockClear().mockResolvedValue(undefined);
mockAudioContext.suspend.mockClear().mockResolvedValue(undefined);
mocked(decodeOgg).mockClear().mockResolvedValue(new ArrayBuffer(1));
mocked(createAudioContext).mockReturnValue(mockAudioContext as unknown as AudioContext);
});
it('initialises correctly', () => {
it("initialises correctly", () => {
const buffer = new ArrayBuffer(8);
const playback = new Playback(buffer);
@@ -71,7 +69,7 @@ describe('Playback', () => {
expect(playback.currentState).toEqual(PlaybackState.Decoding);
});
it('toggles playback on from stopped state', async () => {
it("toggles playback on from stopped state", async () => {
const buffer = new ArrayBuffer(8);
const playback = new Playback(buffer);
await playback.prepare();
@@ -83,7 +81,7 @@ describe('Playback', () => {
expect(playback.currentState).toEqual(PlaybackState.Playing);
});
it('toggles playback to paused from playing state', async () => {
it("toggles playback to paused from playing state", async () => {
const buffer = new ArrayBuffer(8);
const playback = new Playback(buffer);
await playback.prepare();
@@ -96,7 +94,7 @@ describe('Playback', () => {
expect(playback.currentState).toEqual(PlaybackState.Paused);
});
it('stop playbacks', async () => {
it("stop playbacks", async () => {
const buffer = new ArrayBuffer(8);
const playback = new Playback(buffer);
await playback.prepare();
@@ -109,8 +107,8 @@ describe('Playback', () => {
expect(playback.currentState).toEqual(PlaybackState.Stopped);
});
describe('prepare()', () => {
it('decodes audio data when not greater than 5mb', async () => {
describe("prepare()", () => {
it("decodes audio data when not greater than 5mb", async () => {
const buffer = new ArrayBuffer(8);
const playback = new Playback(buffer);
@@ -127,18 +125,16 @@ describe('Playback', () => {
expect(playback.currentState).toEqual(PlaybackState.Stopped);
});
it('tries to decode ogg when decodeAudioData fails', async () => {
it("tries to decode ogg when decodeAudioData fails", async () => {
// stub logger to keep console clean from expected error
jest.spyOn(logger, 'error').mockReturnValue(undefined);
jest.spyOn(logger, 'warn').mockReturnValue(undefined);
jest.spyOn(logger, "error").mockReturnValue(undefined);
jest.spyOn(logger, "warn").mockReturnValue(undefined);
const buffer = new ArrayBuffer(8);
const decodingError = new Error('test');
mockAudioContext.decodeAudioData.mockImplementationOnce(
(_b, _callback, error) => error(decodingError),
).mockImplementationOnce(
(_b, callback) => callback(mockAudioBuffer),
);
const decodingError = new Error("test");
mockAudioContext.decodeAudioData
.mockImplementationOnce((_b, _callback, error) => error(decodingError))
.mockImplementationOnce((_b, callback) => callback(mockAudioBuffer));
const playback = new Playback(buffer);
@@ -154,7 +150,7 @@ describe('Playback', () => {
expect(playback.currentState).toEqual(PlaybackState.Stopped);
});
it('does not try to re-decode audio', async () => {
it("does not try to re-decode audio", async () => {
const buffer = new ArrayBuffer(8);
const playback = new Playback(buffer);
await playback.prepare();

View File

@@ -57,10 +57,7 @@ describe("VoiceMessageRecording", () => {
liveData: jest.fn(),
amplitudes: testAmplitudes,
} as unknown as VoiceRecording;
voiceMessageRecording = new VoiceMessageRecording(
client,
voiceRecording,
);
voiceMessageRecording = new VoiceMessageRecording(client, voiceRecording);
});
it("hasRecording should return false", () => {
@@ -120,9 +117,7 @@ describe("VoiceMessageRecording", () => {
});
it("upload should raise an error", async () => {
await expect(voiceMessageRecording.upload(roomId))
.rejects
.toThrow("No recording available to upload");
await expect(voiceMessageRecording.upload(roomId)).rejects.toThrow("No recording available to upload");
});
describe("when the first data has been received", () => {
@@ -157,21 +152,23 @@ describe("VoiceMessageRecording", () => {
uploadFileRoomId = null;
uploadBlob = null;
mocked(uploadFile).mockImplementation((
matrixClient: MatrixClient,
roomId: string,
file: File | Blob,
_progressHandler?: UploadOpts["progressHandler"],
): Promise<{ url?: string, file?: IEncryptedFile }> => {
uploadFileClient = matrixClient;
uploadFileRoomId = roomId;
uploadBlob = file;
// @ts-ignore
return Promise.resolve({
url: uploadUrl,
file: encryptedFile,
});
});
mocked(uploadFile).mockImplementation(
(
matrixClient: MatrixClient,
roomId: string,
file: File | Blob,
_progressHandler?: UploadOpts["progressHandler"],
): Promise<{ url?: string; file?: IEncryptedFile }> => {
uploadFileClient = matrixClient;
uploadFileRoomId = roomId;
uploadBlob = file;
// @ts-ignore
return Promise.resolve({
url: uploadUrl,
file: encryptedFile,
});
},
);
});
it("should upload the file and trigger the upload events", async () => {

View File

@@ -14,18 +14,18 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { mocked } from 'jest-mock';
import { mocked } from "jest-mock";
// @ts-ignore
import Recorder from 'opus-recorder/dist/recorder.min.js';
import Recorder from "opus-recorder/dist/recorder.min.js";
import { VoiceRecording, voiceRecorderOptions, highQualityRecorderOptions } from "../../src/audio/VoiceRecording";
import { createAudioContext } from '../..//src/audio/compat';
import { createAudioContext } from "../..//src/audio/compat";
import MediaDeviceHandler from "../../src/MediaDeviceHandler";
jest.mock('opus-recorder/dist/recorder.min.js');
jest.mock("opus-recorder/dist/recorder.min.js");
const RecorderMock = mocked(Recorder);
jest.mock('../../src/audio/compat', () => ({
jest.mock("../../src/audio/compat", () => ({
createAudioContext: jest.fn(),
}));
const createAudioContextMock = mocked(createAudioContext);
@@ -97,26 +97,34 @@ describe("VoiceRecording", () => {
MediaDeviceHandlerMock.getAudioNoiseSuppression.mockReturnValue(false);
await recording.start();
expect(navigator.mediaDevices.getUserMedia).toHaveBeenCalledWith(expect.objectContaining({
audio: expect.objectContaining({ noiseSuppression: { ideal: false } }),
}));
expect(RecorderMock).toHaveBeenCalledWith(expect.objectContaining({
encoderBitRate: highQualityRecorderOptions.bitrate,
encoderApplication: highQualityRecorderOptions.encoderApplication,
}));
expect(navigator.mediaDevices.getUserMedia).toHaveBeenCalledWith(
expect.objectContaining({
audio: expect.objectContaining({ noiseSuppression: { ideal: false } }),
}),
);
expect(RecorderMock).toHaveBeenCalledWith(
expect.objectContaining({
encoderBitRate: highQualityRecorderOptions.bitrate,
encoderApplication: highQualityRecorderOptions.encoderApplication,
}),
);
});
it("should record normal-quality voice if voice processing is enabled", async () => {
MediaDeviceHandlerMock.getAudioNoiseSuppression.mockReturnValue(true);
await recording.start();
expect(navigator.mediaDevices.getUserMedia).toHaveBeenCalledWith(expect.objectContaining({
audio: expect.objectContaining({ noiseSuppression: { ideal: true } }),
}));
expect(RecorderMock).toHaveBeenCalledWith(expect.objectContaining({
encoderBitRate: voiceRecorderOptions.bitrate,
encoderApplication: voiceRecorderOptions.encoderApplication,
}));
expect(navigator.mediaDevices.getUserMedia).toHaveBeenCalledWith(
expect.objectContaining({
audio: expect.objectContaining({ noiseSuppression: { ideal: true } }),
}),
);
expect(RecorderMock).toHaveBeenCalledWith(
expect.objectContaining({
encoderBitRate: voiceRecorderOptions.bitrate,
encoderApplication: voiceRecorderOptions.encoderApplication,
}),
);
});
});

View File

@@ -14,11 +14,11 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import EmojiProvider from '../../src/autocomplete/EmojiProvider';
import { mkStubRoom } from '../test-utils/test-utils';
import EmojiProvider from "../../src/autocomplete/EmojiProvider";
import { mkStubRoom } from "../test-utils/test-utils";
import { add } from "../../src/emojipicker/recent";
import { stubClient } from "../test-utils";
import { MatrixClientPeg } from '../../src/MatrixClientPeg';
import { MatrixClientPeg } from "../../src/MatrixClientPeg";
const EMOJI_SHORTCODES = [
":+1",
@@ -39,20 +39,18 @@ const EMOJI_SHORTCODES = [
// Some emoji shortcodes are too short and do not actually trigger autocompletion until the ending `:`.
// This means that we cannot compare their autocompletion before and after the ending `:` and have
// to simply assert that the final completion with the colon is the exact emoji.
const TOO_SHORT_EMOJI_SHORTCODE = [
{ emojiShortcode: ":o", expectedEmoji: "⭕️" },
];
const TOO_SHORT_EMOJI_SHORTCODE = [{ emojiShortcode: ":o", expectedEmoji: "⭕️" }];
describe('EmojiProvider', function() {
describe("EmojiProvider", function () {
const testRoom = mkStubRoom(undefined, undefined, undefined);
stubClient();
MatrixClientPeg.get();
it.each(EMOJI_SHORTCODES)('Returns consistent results after final colon %s', async function(emojiShortcode) {
it.each(EMOJI_SHORTCODES)("Returns consistent results after final colon %s", async function (emojiShortcode) {
const ep = new EmojiProvider(testRoom);
const range = { "beginning": true, "start": 0, "end": 3 };
const range = { beginning: true, start: 0, end: 3 };
const completionsBeforeColon = await ep.getCompletions(emojiShortcode, range);
const completionsAfterColon = await ep.getCompletions(emojiShortcode + ':', range);
const completionsAfterColon = await ep.getCompletions(emojiShortcode + ":", range);
const firstCompletionWithoutColon = completionsBeforeColon[0].completion;
const firstCompletionWithColon = completionsAfterColon[0].completion;
@@ -60,17 +58,18 @@ describe('EmojiProvider', function() {
expect(firstCompletionWithoutColon).toEqual(firstCompletionWithColon);
});
it.each(
TOO_SHORT_EMOJI_SHORTCODE,
)('Returns correct results after final colon $emojiShortcode', async ({ emojiShortcode, expectedEmoji }) => {
const ep = new EmojiProvider(testRoom);
const range = { "beginning": true, "start": 0, "end": 3 };
const completions = await ep.getCompletions(emojiShortcode + ':', range);
it.each(TOO_SHORT_EMOJI_SHORTCODE)(
"Returns correct results after final colon $emojiShortcode",
async ({ emojiShortcode, expectedEmoji }) => {
const ep = new EmojiProvider(testRoom);
const range = { beginning: true, start: 0, end: 3 };
const completions = await ep.getCompletions(emojiShortcode + ":", range);
expect(completions[0].completion).toEqual(expectedEmoji);
});
expect(completions[0].completion).toEqual(expectedEmoji);
},
);
it('Returns correct autocompletion based on recently used emoji', async function() {
it("Returns correct autocompletion based on recently used emoji", async function () {
add("😘"); //kissing_heart
add("😘");
add("😚"); //kissing_closed_eyes

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import QueryMatcher from '../../src/autocomplete/QueryMatcher';
import QueryMatcher from "../../src/autocomplete/QueryMatcher";
const OBJECTS = [
{ name: "Mel B", nick: "Scary" },
@@ -24,160 +24,151 @@ const OBJECTS = [
{ name: "Victoria", nick: "Posh" },
];
const NONWORDOBJECTS = [
{ name: "B.O.B" },
{ name: "bob" },
];
const NONWORDOBJECTS = [{ name: "B.O.B" }, { name: "bob" }];
describe('QueryMatcher', function() {
it('Returns results by key', function() {
describe("QueryMatcher", function () {
it("Returns results by key", function () {
const qm = new QueryMatcher(OBJECTS, { keys: ["name"] });
const results = qm.match('Geri');
const results = qm.match("Geri");
expect(results.length).toBe(1);
expect(results[0].name).toBe('Geri');
expect(results[0].name).toBe("Geri");
});
it('Returns results by prefix', function() {
it("Returns results by prefix", function () {
const qm = new QueryMatcher(OBJECTS, { keys: ["name"] });
const results = qm.match('Ge');
const results = qm.match("Ge");
expect(results.length).toBe(1);
expect(results[0].name).toBe('Geri');
expect(results[0].name).toBe("Geri");
});
it('Matches case-insensitive', function() {
it("Matches case-insensitive", function () {
const qm = new QueryMatcher(OBJECTS, { keys: ["name"] });
const results = qm.match('geri');
const results = qm.match("geri");
expect(results.length).toBe(1);
expect(results[0].name).toBe('Geri');
expect(results[0].name).toBe("Geri");
});
it('Matches ignoring accents', function() {
it("Matches ignoring accents", function () {
const qm = new QueryMatcher([{ name: "Gëri", foo: 46 }], { keys: ["name"] });
const results = qm.match('geri');
const results = qm.match("geri");
expect(results.length).toBe(1);
expect(results[0].foo).toBe(46);
});
it('Returns multiple results in order of search string appearance', function() {
it("Returns multiple results in order of search string appearance", function () {
const qm = new QueryMatcher(OBJECTS, { keys: ["name", "nick"] });
const results = qm.match('or');
const results = qm.match("or");
expect(results.length).toBe(2);
expect(results[0].name).toBe('Mel C');
expect(results[1].name).toBe('Victoria');
expect(results[0].name).toBe("Mel C");
expect(results[1].name).toBe("Victoria");
qm.setObjects(OBJECTS.slice().reverse());
const reverseResults = qm.match('or');
const reverseResults = qm.match("or");
// should still be in the same order: search string position
// takes precedence over input order
expect(reverseResults.length).toBe(2);
expect(reverseResults[0].name).toBe('Mel C');
expect(reverseResults[1].name).toBe('Victoria');
expect(reverseResults[0].name).toBe("Mel C");
expect(reverseResults[1].name).toBe("Victoria");
});
it('Returns results with search string in same place according to key index', function() {
it("Returns results with search string in same place according to key index", function () {
const objects = [
{ name: "a", first: "hit", second: "miss", third: "miss" },
{ name: "b", first: "miss", second: "hit", third: "miss" },
{ name: "c", first: "miss", second: "miss", third: "hit" },
];
const qm = new QueryMatcher(objects, { keys: ["second", "first", "third"] });
const results = qm.match('hit');
const results = qm.match("hit");
expect(results.length).toBe(3);
expect(results[0].name).toBe('b');
expect(results[1].name).toBe('a');
expect(results[2].name).toBe('c');
expect(results[0].name).toBe("b");
expect(results[1].name).toBe("a");
expect(results[2].name).toBe("c");
qm.setObjects(objects.slice().reverse());
const reverseResults = qm.match('hit');
const reverseResults = qm.match("hit");
// should still be in the same order: key index
// takes precedence over input order
expect(reverseResults.length).toBe(3);
expect(reverseResults[0].name).toBe('b');
expect(reverseResults[1].name).toBe('a');
expect(reverseResults[2].name).toBe('c');
expect(reverseResults[0].name).toBe("b");
expect(reverseResults[1].name).toBe("a");
expect(reverseResults[2].name).toBe("c");
});
it('Returns results with search string in same place and key in same place in insertion order', function() {
it("Returns results with search string in same place and key in same place in insertion order", function () {
const qm = new QueryMatcher(OBJECTS, { keys: ["name"] });
const results = qm.match('Mel');
const results = qm.match("Mel");
expect(results.length).toBe(2);
expect(results[0].name).toBe('Mel B');
expect(results[1].name).toBe('Mel C');
expect(results[0].name).toBe("Mel B");
expect(results[1].name).toBe("Mel C");
qm.setObjects(OBJECTS.slice().reverse());
const reverseResults = qm.match('Mel');
const reverseResults = qm.match("Mel");
expect(reverseResults.length).toBe(2);
expect(reverseResults[0].name).toBe('Mel C');
expect(reverseResults[1].name).toBe('Mel B');
expect(reverseResults[0].name).toBe("Mel C");
expect(reverseResults[1].name).toBe("Mel B");
});
it('Returns numeric results in correct order (input pos)', function() {
it("Returns numeric results in correct order (input pos)", function () {
// regression test for depending on object iteration order
const qm = new QueryMatcher([
{ name: "123456badger" },
{ name: "123456" },
], { keys: ["name"] });
const results = qm.match('123456');
const qm = new QueryMatcher([{ name: "123456badger" }, { name: "123456" }], { keys: ["name"] });
const results = qm.match("123456");
expect(results.length).toBe(2);
expect(results[0].name).toBe('123456badger');
expect(results[1].name).toBe('123456');
expect(results[0].name).toBe("123456badger");
expect(results[1].name).toBe("123456");
});
it('Returns numeric results in correct order (query pos)', function() {
const qm = new QueryMatcher([
{ name: "999999123456" },
{ name: "123456badger" },
], { keys: ["name"] });
const results = qm.match('123456');
it("Returns numeric results in correct order (query pos)", function () {
const qm = new QueryMatcher([{ name: "999999123456" }, { name: "123456badger" }], { keys: ["name"] });
const results = qm.match("123456");
expect(results.length).toBe(2);
expect(results[0].name).toBe('123456badger');
expect(results[1].name).toBe('999999123456');
expect(results[0].name).toBe("123456badger");
expect(results[1].name).toBe("999999123456");
});
it('Returns results by function', function() {
it("Returns results by function", function () {
const qm = new QueryMatcher(OBJECTS, {
keys: ["name"],
funcs: [x => x.name.replace('Mel', 'Emma')],
funcs: [(x) => x.name.replace("Mel", "Emma")],
});
const results = qm.match('Emma');
const results = qm.match("Emma");
expect(results.length).toBe(3);
expect(results[0].name).toBe('Emma');
expect(results[1].name).toBe('Mel B');
expect(results[2].name).toBe('Mel C');
expect(results[0].name).toBe("Emma");
expect(results[1].name).toBe("Mel B");
expect(results[2].name).toBe("Mel C");
});
it('Matches words only by default', function() {
it("Matches words only by default", function () {
const qm = new QueryMatcher(NONWORDOBJECTS, { keys: ["name"] });
const results = qm.match('bob');
const results = qm.match("bob");
expect(results.length).toBe(2);
expect(results[0].name).toBe('B.O.B');
expect(results[1].name).toBe('bob');
expect(results[0].name).toBe("B.O.B");
expect(results[1].name).toBe("bob");
});
it('Matches all chars with words-only off', function() {
it("Matches all chars with words-only off", function () {
const qm = new QueryMatcher(NONWORDOBJECTS, {
keys: ["name"],
shouldMatchWordsOnly: false,
});
const results = qm.match('bob');
const results = qm.match("bob");
expect(results.length).toBe(1);
expect(results[0].name).toBe('bob');
expect(results[0].name).toBe("bob");
});
});

View File

@@ -14,43 +14,44 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { screen, render, fireEvent, waitFor, within, act } from '@testing-library/react';
import React from "react";
import { screen, render, fireEvent, waitFor, within, act } from "@testing-library/react";
import * as TestUtils from '../../test-utils';
import AutocompleteProvider from '../../../src/autocomplete/AutocompleteProvider';
import { ICompletion } from '../../../src/autocomplete/Autocompleter';
import * as TestUtils from "../../test-utils";
import AutocompleteProvider from "../../../src/autocomplete/AutocompleteProvider";
import { ICompletion } from "../../../src/autocomplete/Autocompleter";
import { AutocompleteInput } from "../../../src/components/structures/AutocompleteInput";
describe('AutocompleteInput', () => {
describe("AutocompleteInput", () => {
const mockCompletion: ICompletion[] = [
{ type: 'user', completion: 'user_1', completionId: '@user_1:host.local', range: { start: 1, end: 1 } },
{ type: 'user', completion: 'user_2', completionId: '@user_2:host.local', range: { start: 1, end: 1 } },
{ type: "user", completion: "user_1", completionId: "@user_1:host.local", range: { start: 1, end: 1 } },
{ type: "user", completion: "user_2", completionId: "@user_2:host.local", range: { start: 1, end: 1 } },
];
const constructMockProvider = (data: ICompletion[]) => ({
getCompletions: jest.fn().mockImplementation(async () => data),
}) as unknown as AutocompleteProvider;
const constructMockProvider = (data: ICompletion[]) =>
({
getCompletions: jest.fn().mockImplementation(async () => data),
} as unknown as AutocompleteProvider);
beforeEach(() => {
TestUtils.stubClient();
});
const getEditorInput = () => {
const input = screen.getByTestId('autocomplete-input');
const input = screen.getByTestId("autocomplete-input");
expect(input).toBeDefined();
return input;
};
it('should render suggestions when a query is set', async () => {
it("should render suggestions when a query is set", async () => {
const mockProvider = constructMockProvider(mockCompletion);
const onSelectionChangeMock = jest.fn();
render(
<AutocompleteInput
provider={mockProvider}
placeholder='Search ...'
placeholder="Search ..."
selection={[]}
onSelectionChange={onSelectionChangeMock}
/>,
@@ -60,45 +61,45 @@ describe('AutocompleteInput', () => {
act(() => {
fireEvent.focus(input);
fireEvent.change(input, { target: { value: 'user' } });
fireEvent.change(input, { target: { value: "user" } });
});
await waitFor(() => expect(mockProvider.getCompletions).toHaveBeenCalledTimes(1));
expect(screen.getByTestId('autocomplete-matches').childNodes).toHaveLength(mockCompletion.length);
expect(screen.getByTestId("autocomplete-matches").childNodes).toHaveLength(mockCompletion.length);
});
it('should render selected items passed in via props', () => {
it("should render selected items passed in via props", () => {
const mockProvider = constructMockProvider(mockCompletion);
const onSelectionChangeMock = jest.fn();
render(
<AutocompleteInput
provider={mockProvider}
placeholder='Search ...'
placeholder="Search ..."
selection={mockCompletion}
onSelectionChange={onSelectionChangeMock}
/>,
);
const editor = screen.getByTestId('autocomplete-editor');
const editor = screen.getByTestId("autocomplete-editor");
const selection = within(editor).getAllByTestId("autocomplete-selection-item", { exact: false });
expect(selection).toHaveLength(mockCompletion.length);
});
it('should call onSelectionChange() when an item is removed from selection', () => {
it("should call onSelectionChange() when an item is removed from selection", () => {
const mockProvider = constructMockProvider(mockCompletion);
const onSelectionChangeMock = jest.fn();
render(
<AutocompleteInput
provider={mockProvider}
placeholder='Search ...'
placeholder="Search ..."
selection={mockCompletion}
onSelectionChange={onSelectionChangeMock}
/>,
);
const editor = screen.getByTestId('autocomplete-editor');
const editor = screen.getByTestId("autocomplete-editor");
const removeButtons = within(editor).getAllByTestId("autocomplete-selection-remove-button", { exact: false });
expect(removeButtons).toHaveLength(mockCompletion.length);
@@ -110,39 +111,35 @@ describe('AutocompleteInput', () => {
expect(onSelectionChangeMock).toHaveBeenCalledWith([mockCompletion[1]]);
});
it('should render custom selection element when renderSelection() is defined', () => {
it("should render custom selection element when renderSelection() is defined", () => {
const mockProvider = constructMockProvider(mockCompletion);
const onSelectionChangeMock = jest.fn();
const renderSelection = () => (
<span data-testid='custom-selection-element'>custom selection element</span>
);
const renderSelection = () => <span data-testid="custom-selection-element">custom selection element</span>;
render(
<AutocompleteInput
provider={mockProvider}
placeholder='Search ...'
placeholder="Search ..."
selection={mockCompletion}
onSelectionChange={onSelectionChangeMock}
renderSelection={renderSelection}
/>,
);
expect(screen.getAllByTestId('custom-selection-element')).toHaveLength(mockCompletion.length);
expect(screen.getAllByTestId("custom-selection-element")).toHaveLength(mockCompletion.length);
});
it('should render custom suggestion element when renderSuggestion() is defined', async () => {
it("should render custom suggestion element when renderSuggestion() is defined", async () => {
const mockProvider = constructMockProvider(mockCompletion);
const onSelectionChangeMock = jest.fn();
const renderSuggestion = () => (
<span data-testid='custom-suggestion-element'>custom suggestion element</span>
);
const renderSuggestion = () => <span data-testid="custom-suggestion-element">custom suggestion element</span>;
render(
<AutocompleteInput
provider={mockProvider}
placeholder='Search ...'
placeholder="Search ..."
selection={mockCompletion}
onSelectionChange={onSelectionChangeMock}
renderSuggestion={renderSuggestion}
@@ -153,21 +150,21 @@ describe('AutocompleteInput', () => {
act(() => {
fireEvent.focus(input);
fireEvent.change(input, { target: { value: 'user' } });
fireEvent.change(input, { target: { value: "user" } });
});
await waitFor(() => expect(mockProvider.getCompletions).toHaveBeenCalledTimes(1));
expect(screen.getAllByTestId('custom-suggestion-element')).toHaveLength(mockCompletion.length);
expect(screen.getAllByTestId("custom-suggestion-element")).toHaveLength(mockCompletion.length);
});
it('should mark selected suggestions as selected', async () => {
it("should mark selected suggestions as selected", async () => {
const mockProvider = constructMockProvider(mockCompletion);
const onSelectionChangeMock = jest.fn();
const { container } = render(
<AutocompleteInput
provider={mockProvider}
placeholder='Search ...'
placeholder="Search ..."
selection={mockCompletion}
onSelectionChange={onSelectionChangeMock}
/>,
@@ -177,23 +174,23 @@ describe('AutocompleteInput', () => {
act(() => {
fireEvent.focus(input);
fireEvent.change(input, { target: { value: 'user' } });
fireEvent.change(input, { target: { value: "user" } });
});
await waitFor(() => expect(mockProvider.getCompletions).toHaveBeenCalledTimes(1));
const suggestions = await within(container).findAllByTestId('autocomplete-suggestion-item', { exact: false });
const suggestions = await within(container).findAllByTestId("autocomplete-suggestion-item", { exact: false });
expect(suggestions).toHaveLength(mockCompletion.length);
suggestions.map(suggestion => expect(suggestion).toHaveClass('mx_AutocompleteInput_suggestion--selected'));
suggestions.map((suggestion) => expect(suggestion).toHaveClass("mx_AutocompleteInput_suggestion--selected"));
});
it('should remove the last added selection when backspace is pressed in empty input', () => {
it("should remove the last added selection when backspace is pressed in empty input", () => {
const mockProvider = constructMockProvider(mockCompletion);
const onSelectionChangeMock = jest.fn();
render(
<AutocompleteInput
provider={mockProvider}
placeholder='Search ...'
placeholder="Search ..."
selection={mockCompletion}
onSelectionChange={onSelectionChangeMock}
/>,
@@ -202,20 +199,20 @@ describe('AutocompleteInput', () => {
const input = getEditorInput();
act(() => {
fireEvent.keyDown(input, { key: 'Backspace' });
fireEvent.keyDown(input, { key: "Backspace" });
});
expect(onSelectionChangeMock).toHaveBeenCalledWith([mockCompletion[0]]);
});
it('should toggle a selected item when a suggestion is clicked', async () => {
it("should toggle a selected item when a suggestion is clicked", async () => {
const mockProvider = constructMockProvider(mockCompletion);
const onSelectionChangeMock = jest.fn();
const { container } = render(
<AutocompleteInput
provider={mockProvider}
placeholder='Search ...'
placeholder="Search ..."
selection={[]}
onSelectionChange={onSelectionChangeMock}
/>,
@@ -225,10 +222,10 @@ describe('AutocompleteInput', () => {
act(() => {
fireEvent.focus(input);
fireEvent.change(input, { target: { value: 'user' } });
fireEvent.change(input, { target: { value: "user" } });
});
const suggestions = await within(container).findAllByTestId('autocomplete-suggestion-item', { exact: false });
const suggestions = await within(container).findAllByTestId("autocomplete-suggestion-item", { exact: false });
act(() => {
fireEvent.mouseDown(suggestions[0]);

View File

@@ -85,4 +85,3 @@ describe("ContextMenu", () => {
});
});
});

View File

@@ -14,12 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { MatrixClient, MatrixEvent } from 'matrix-js-sdk/src/matrix';
import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
import { EventType } from "matrix-js-sdk/src/@types/event";
import { CallState } from "matrix-js-sdk/src/webrtc/call";
import { stubClient } from '../../test-utils';
import { MatrixClientPeg } from '../../../src/MatrixClientPeg';
import { stubClient } from "../../test-utils";
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
import LegacyCallEventGrouper, { CustomCallState } from "../../../src/components/structures/LegacyCallEventGrouper";
const MY_USER_ID = "@me:here";
@@ -27,7 +27,7 @@ const THEIR_USER_ID = "@they:here";
let client: MatrixClient;
describe('LegacyCallEventGrouper', () => {
describe("LegacyCallEventGrouper", () => {
beforeEach(() => {
stubClient();
client = MatrixClientPeg.get();

View File

@@ -15,13 +15,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import ReactDOM from "react-dom";
import { EventEmitter } from "events";
import { Room, RoomMember } from 'matrix-js-sdk/src/matrix';
import FakeTimers from '@sinonjs/fake-timers';
import { render } from '@testing-library/react';
import { Thread } from 'matrix-js-sdk/src/models/thread';
import { Room, RoomMember } from "matrix-js-sdk/src/matrix";
import FakeTimers from "@sinonjs/fake-timers";
import { render } from "@testing-library/react";
import { Thread } from "matrix-js-sdk/src/models/thread";
import MessagePanel, { shouldFormContinuation } from "../../../src/components/structures/MessagePanel";
import SettingsStore from "../../../src/settings/SettingsStore";
@@ -34,21 +34,21 @@ import {
makeBeaconInfoEvent,
mockClientMethodsEvents,
mockClientMethodsUser,
} from '../../test-utils';
import ResizeNotifier from '../../../src/utils/ResizeNotifier';
import { IRoomState } from '../../../src/components/structures/RoomView';
} from "../../test-utils";
import ResizeNotifier from "../../../src/utils/ResizeNotifier";
import { IRoomState } from "../../../src/components/structures/RoomView";
jest.mock('../../../src/utils/beacon', () => ({
jest.mock("../../../src/utils/beacon", () => ({
useBeacon: jest.fn(),
}));
const roomId = "!roomId:server_name";
describe('MessagePanel', function() {
describe("MessagePanel", function () {
let clock = null;
const realSetTimeout = window.setTimeout;
const events = mkEvents();
const userId = '@me:here';
const userId = "@me:here";
const client = getMockClientWithEventEmitter({
...mockClientMethodsUser(userId),
...mockClientMethodsEvents(),
@@ -61,22 +61,22 @@ describe('MessagePanel', function() {
const room = new Room(roomId, client, userId);
const bobMember = new RoomMember(roomId, '@bob:id');
bobMember.name = 'Bob';
jest.spyOn(bobMember, 'getAvatarUrl').mockReturnValue('avatar.jpeg');
jest.spyOn(bobMember, 'getMxcAvatarUrl').mockReturnValue('mxc://avatar.url/image.png');
const bobMember = new RoomMember(roomId, "@bob:id");
bobMember.name = "Bob";
jest.spyOn(bobMember, "getAvatarUrl").mockReturnValue("avatar.jpeg");
jest.spyOn(bobMember, "getMxcAvatarUrl").mockReturnValue("mxc://avatar.url/image.png");
const alice = "@alice:example.org";
const aliceMember = new RoomMember(roomId, alice);
aliceMember.name = 'Alice';
jest.spyOn(aliceMember, 'getAvatarUrl').mockReturnValue('avatar.jpeg');
jest.spyOn(aliceMember, 'getMxcAvatarUrl').mockReturnValue('mxc://avatar.url/image.png');
aliceMember.name = "Alice";
jest.spyOn(aliceMember, "getAvatarUrl").mockReturnValue("avatar.jpeg");
jest.spyOn(aliceMember, "getMxcAvatarUrl").mockReturnValue("mxc://avatar.url/image.png");
const defaultProps = {
resizeNotifier: new EventEmitter as unknown as ResizeNotifier,
resizeNotifier: new EventEmitter() as unknown as ResizeNotifier,
callEventGroupers: new Map(),
room,
className: 'cls',
className: "cls",
events: [],
};
@@ -95,24 +95,26 @@ describe('MessagePanel', function() {
showHiddenEvents: false,
} as unknown as IRoomState;
const getComponent = (props = {}, roomContext: Partial<IRoomState> = {}) =>
const getComponent = (props = {}, roomContext: Partial<IRoomState> = {}) => (
<MatrixClientContext.Provider value={client}>
<RoomContext.Provider value={{ ...defaultRoomContext, ...roomContext }}>
<MessagePanel {...defaultProps} {...props} />
</RoomContext.Provider>);
</MatrixClientContext.Provider>;
</RoomContext.Provider>
);
</MatrixClientContext.Provider>
);
beforeEach(function() {
beforeEach(function () {
jest.clearAllMocks();
// HACK: We assume all settings want to be disabled
jest.spyOn(SettingsStore, 'getValue').mockImplementation((arg) => {
jest.spyOn(SettingsStore, "getValue").mockImplementation((arg) => {
return arg === "showDisplaynameChanges";
});
DMRoomMap.makeShared();
});
afterEach(function() {
afterEach(function () {
if (clock) {
clock.uninstall();
clock = null;
@@ -123,11 +125,14 @@ describe('MessagePanel', function() {
const events = [];
const ts0 = Date.now();
for (let i = 0; i < 10; i++) {
events.push(TestUtilsMatrix.mkMessage(
{
event: true, room: "!room:id", user: "@user:id",
events.push(
TestUtilsMatrix.mkMessage({
event: true,
room: "!room:id",
user: "@user:id",
ts: ts0 + i * 1000,
}));
}),
);
}
return events;
}
@@ -135,13 +140,16 @@ describe('MessagePanel', function() {
// Just to avoid breaking Dateseparator tests that might run at 00hrs
function mkOneDayEvents() {
const events = [];
const ts0 = Date.parse('09 May 2004 00:12:00 GMT');
const ts0 = Date.parse("09 May 2004 00:12:00 GMT");
for (let i = 0; i < 10; i++) {
events.push(TestUtilsMatrix.mkMessage(
{
event: true, room: "!room:id", user: "@user:id",
events.push(
TestUtilsMatrix.mkMessage({
event: true,
room: "!room:id",
user: "@user:id",
ts: ts0 + i * 1000,
}));
}),
);
}
return events;
}
@@ -152,26 +160,38 @@ describe('MessagePanel', function() {
const ts0 = Date.now();
let i = 0;
events.push(TestUtilsMatrix.mkMessage({
event: true, room: "!room:id", user: "@user:id",
ts: ts0 + ++i * 1000,
}));
events.push(
TestUtilsMatrix.mkMessage({
event: true,
room: "!room:id",
user: "@user:id",
ts: ts0 + ++i * 1000,
}),
);
for (i = 0; i < 10; i++) {
events.push(TestUtilsMatrix.mkMembership({
event: true, room: "!room:id", user: "@user:id",
target: bobMember,
ts: ts0 + i*1000,
mship: 'join',
prevMship: 'join',
name: 'A user',
}));
events.push(
TestUtilsMatrix.mkMembership({
event: true,
room: "!room:id",
user: "@user:id",
target: bobMember,
ts: ts0 + i * 1000,
mship: "join",
prevMship: "join",
name: "A user",
}),
);
}
events.push(TestUtilsMatrix.mkMessage({
event: true, room: "!room:id", user: "@user:id",
ts: ts0 + ++i*1000,
}));
events.push(
TestUtilsMatrix.mkMessage({
event: true,
room: "!room:id",
user: "@user:id",
ts: ts0 + ++i * 1000,
}),
);
return events;
}
@@ -184,14 +204,18 @@ describe('MessagePanel', function() {
let i = 0;
for (i = 0; i < 10; i++) {
events.push(TestUtilsMatrix.mkMembership({
event: true, room: "!room:id", user: "@user:id",
target: bobMember,
ts: ts0 + i * 1000,
mship: 'join',
prevMship: 'join',
name: 'A user',
}));
events.push(
TestUtilsMatrix.mkMembership({
event: true,
room: "!room:id",
user: "@user:id",
target: bobMember,
ts: ts0 + i * 1000,
mship: "join",
prevMship: "join",
name: "A user",
}),
);
}
return events;
@@ -227,8 +251,8 @@ describe('MessagePanel', function() {
user: alice,
target: aliceMember,
ts: ts0 + 1,
mship: 'join',
name: 'Alice',
mship: "join",
name: "Alice",
}),
mkEvent({
event: true,
@@ -236,7 +260,7 @@ describe('MessagePanel', function() {
room: roomId,
user: alice,
content: {
"join_rule": "invite",
join_rule: "invite",
},
ts: ts0 + 2,
}),
@@ -246,7 +270,7 @@ describe('MessagePanel', function() {
room: roomId,
user: alice,
content: {
"history_visibility": "invited",
history_visibility: "invited",
},
ts: ts0 + 3,
}),
@@ -256,7 +280,7 @@ describe('MessagePanel', function() {
room: roomId,
user: alice,
content: {
"algorithm": "m.megolm.v1.aes-sha2",
algorithm: "m.megolm.v1.aes-sha2",
},
ts: ts0 + 4,
}),
@@ -267,8 +291,8 @@ describe('MessagePanel', function() {
skey: "@bob:example.org",
target: bobMember,
ts: ts0 + 5,
mship: 'invite',
name: 'Bob',
mship: "invite",
name: "Bob",
}),
];
}
@@ -300,52 +324,58 @@ describe('MessagePanel', function() {
return rmContainer && rmContainer.children.length > 0;
}
it('should show the events', function() {
it("should show the events", function () {
const { container } = render(getComponent({ events }));
// just check we have the right number of tiles for now
const tiles = container.getElementsByClassName('mx_EventTile');
const tiles = container.getElementsByClassName("mx_EventTile");
expect(tiles.length).toEqual(10);
});
it('should collapse adjacent member events', function() {
it("should collapse adjacent member events", function () {
const { container } = render(getComponent({ events: mkMelsEvents() }));
// just check we have the right number of tiles for now
const tiles = container.getElementsByClassName('mx_EventTile');
const tiles = container.getElementsByClassName("mx_EventTile");
expect(tiles.length).toEqual(2);
const summaryTiles = container.getElementsByClassName('mx_GenericEventListSummary');
const summaryTiles = container.getElementsByClassName("mx_GenericEventListSummary");
expect(summaryTiles.length).toEqual(1);
});
it('should insert the read-marker in the right place', function() {
const { container } = render(getComponent({
events, readMarkerEventId: events[4].getId(), readMarkerVisible: true,
}));
it("should insert the read-marker in the right place", function () {
const { container } = render(
getComponent({
events,
readMarkerEventId: events[4].getId(),
readMarkerVisible: true,
}),
);
const tiles = container.getElementsByClassName('mx_EventTile');
const tiles = container.getElementsByClassName("mx_EventTile");
// find the <li> which wraps the read marker
const [rm] = container.getElementsByClassName('mx_RoomView_myReadMarker_container');
const [rm] = container.getElementsByClassName("mx_RoomView_myReadMarker_container");
// it should follow the <li> which wraps the event tile for event 4
const eventContainer = ReactDOM.findDOMNode(tiles[4]);
expect(rm.previousSibling).toEqual(eventContainer);
});
it('should show the read-marker that fall in summarised events after the summary', function() {
it("should show the read-marker that fall in summarised events after the summary", function () {
const melsEvents = mkMelsEvents();
const { container } = render(getComponent({
events: melsEvents,
readMarkerEventId: melsEvents[4].getId(),
readMarkerVisible: true,
}));
const { container } = render(
getComponent({
events: melsEvents,
readMarkerEventId: melsEvents[4].getId(),
readMarkerVisible: true,
}),
);
const [summary] = container.getElementsByClassName('mx_GenericEventListSummary');
const [summary] = container.getElementsByClassName("mx_GenericEventListSummary");
// find the <li> which wraps the read marker
const [rm] = container.getElementsByClassName('mx_RoomView_myReadMarker_container');
const [rm] = container.getElementsByClassName("mx_RoomView_myReadMarker_container");
expect(rm.previousSibling).toEqual(summary);
@@ -353,19 +383,21 @@ describe('MessagePanel', function() {
expect(isReadMarkerVisible(rm)).toBeTruthy();
});
it('should hide the read-marker at the end of summarised events', function() {
it("should hide the read-marker at the end of summarised events", function () {
const melsEvents = mkMelsEventsOnly();
const { container } = render(getComponent({
events: melsEvents,
readMarkerEventId: melsEvents[9].getId(),
readMarkerVisible: true,
}));
const { container } = render(
getComponent({
events: melsEvents,
readMarkerEventId: melsEvents[9].getId(),
readMarkerVisible: true,
}),
);
const [summary] = container.getElementsByClassName('mx_GenericEventListSummary');
const [summary] = container.getElementsByClassName("mx_GenericEventListSummary");
// find the <li> which wraps the read marker
const [rm] = container.getElementsByClassName('mx_RoomView_myReadMarker_container');
const [rm] = container.getElementsByClassName("mx_RoomView_myReadMarker_container");
expect(rm.previousSibling).toEqual(summary);
@@ -373,34 +405,38 @@ describe('MessagePanel', function() {
expect(isReadMarkerVisible(rm)).toBeFalsy();
});
it('shows a ghost read-marker when the read-marker moves', function(done) {
it("shows a ghost read-marker when the read-marker moves", function (done) {
// fake the clock so that we can test the velocity animation.
clock = FakeTimers.install();
const { container, rerender } = render(<div>
{ getComponent({
events,
readMarkerEventId: events[4].getId(),
readMarkerVisible: true,
}) }
</div>);
const { container, rerender } = render(
<div>
{getComponent({
events,
readMarkerEventId: events[4].getId(),
readMarkerVisible: true,
})}
</div>,
);
const tiles = container.getElementsByClassName('mx_EventTile');
const tiles = container.getElementsByClassName("mx_EventTile");
// find the <li> which wraps the read marker
const [rm] = container.getElementsByClassName('mx_RoomView_myReadMarker_container');
const [rm] = container.getElementsByClassName("mx_RoomView_myReadMarker_container");
expect(rm.previousSibling).toEqual(tiles[4]);
rerender(<div>
{ getComponent({
events,
readMarkerEventId: events[6].getId(),
readMarkerVisible: true,
}) }
</div>);
rerender(
<div>
{getComponent({
events,
readMarkerEventId: events[6].getId(),
readMarkerVisible: true,
})}
</div>,
);
// now there should be two RM containers
const readMarkers = container.getElementsByClassName('mx_RoomView_myReadMarker_container');
const readMarkers = container.getElementsByClassName("mx_RoomView_myReadMarker_container");
expect(readMarkers.length).toEqual(2);
@@ -420,72 +456,72 @@ describe('MessagePanel', function() {
clock.tick(1000);
realSetTimeout(() => {
// the ghost should now have finished
expect(hr.style.opacity).toEqual('0');
expect(hr.style.opacity).toEqual("0");
done();
}, 100);
}, 100);
});
it('should collapse creation events', function() {
it("should collapse creation events", function () {
const events = mkCreationEvents();
TestUtilsMatrix.upsertRoomStateEvents(room, events);
const { container } = render(getComponent({ events }));
const createEvent = events.find(event => event.getType() === 'm.room.create');
const encryptionEvent = events.find(event => event.getType() === 'm.room.encryption');
const createEvent = events.find((event) => event.getType() === "m.room.create");
const encryptionEvent = events.find((event) => event.getType() === "m.room.encryption");
// we expect that
// - the room creation event, the room encryption event, and Alice inviting Bob,
// should be outside of the room creation summary
// - all other events should be inside the room creation summary
const tiles = container.getElementsByClassName('mx_EventTile');
const tiles = container.getElementsByClassName("mx_EventTile");
expect(tiles[0].getAttribute('data-event-id')).toEqual(createEvent.getId());
expect(tiles[1].getAttribute('data-event-id')).toEqual(encryptionEvent.getId());
expect(tiles[0].getAttribute("data-event-id")).toEqual(createEvent.getId());
expect(tiles[1].getAttribute("data-event-id")).toEqual(encryptionEvent.getId());
const [summaryTile] = container.getElementsByClassName('mx_GenericEventListSummary');
const [summaryTile] = container.getElementsByClassName("mx_GenericEventListSummary");
const summaryEventTiles = summaryTile.getElementsByClassName('mx_EventTile');
const summaryEventTiles = summaryTile.getElementsByClassName("mx_EventTile");
// every event except for the room creation, room encryption, and Bob's
// invite event should be in the event summary
expect(summaryEventTiles.length).toEqual(tiles.length - 3);
});
it('should not collapse beacons as part of creation events', function() {
it("should not collapse beacons as part of creation events", function () {
const events = mkCreationEvents();
const creationEvent = events.find(event => event.getType() === 'm.room.create');
const beaconInfoEvent = makeBeaconInfoEvent(
creationEvent.getSender(),
creationEvent.getRoomId(),
{ isLive: true },
);
const creationEvent = events.find((event) => event.getType() === "m.room.create");
const beaconInfoEvent = makeBeaconInfoEvent(creationEvent.getSender(), creationEvent.getRoomId(), {
isLive: true,
});
const combinedEvents = [...events, beaconInfoEvent];
TestUtilsMatrix.upsertRoomStateEvents(room, combinedEvents);
const { container } = render(getComponent({ events: combinedEvents }));
const [summaryTile] = container.getElementsByClassName('mx_GenericEventListSummary');
const [summaryTile] = container.getElementsByClassName("mx_GenericEventListSummary");
// beacon body is not in the summary
expect(summaryTile.getElementsByClassName('mx_MBeaconBody').length).toBe(0);
expect(summaryTile.getElementsByClassName("mx_MBeaconBody").length).toBe(0);
// beacon tile is rendered
expect(container.getElementsByClassName('mx_MBeaconBody').length).toBe(1);
expect(container.getElementsByClassName("mx_MBeaconBody").length).toBe(1);
});
it('should hide read-marker at the end of creation event summary', function() {
it("should hide read-marker at the end of creation event summary", function () {
const events = mkCreationEvents();
TestUtilsMatrix.upsertRoomStateEvents(room, events);
const { container } = render(getComponent({
events,
readMarkerEventId: events[5].getId(),
readMarkerVisible: true,
}));
const { container } = render(
getComponent({
events,
readMarkerEventId: events[5].getId(),
readMarkerVisible: true,
}),
);
// find the <li> which wraps the read marker
const [rm] = container.getElementsByClassName('mx_RoomView_myReadMarker_container');
const [rm] = container.getElementsByClassName("mx_RoomView_myReadMarker_container");
const [messageList] = container.getElementsByClassName('mx_RoomView_MessageList');
const [messageList] = container.getElementsByClassName("mx_RoomView_MessageList");
const rows = messageList.children;
expect(rows.length).toEqual(7); // 6 events + the NewRoomIntro
expect(rm.previousSibling).toEqual(rows[5]);
@@ -494,22 +530,22 @@ describe('MessagePanel', function() {
expect(isReadMarkerVisible(rm)).toBeFalsy();
});
it('should render Date separators for the events', function() {
it("should render Date separators for the events", function () {
const events = mkOneDayEvents();
const { queryAllByRole } = render(getComponent({ events }));
const dates = queryAllByRole('separator');
const dates = queryAllByRole("separator");
expect(dates.length).toEqual(1);
});
it('appends events into summaries during forward pagination without changing key', () => {
it("appends events into summaries during forward pagination without changing key", () => {
const events = mkMelsEvents().slice(1, 11);
const { container, rerender } = render(getComponent({ events }));
let els = container.getElementsByClassName('mx_GenericEventListSummary');
let els = container.getElementsByClassName("mx_GenericEventListSummary");
expect(els.length).toEqual(1);
expect(els[0].getAttribute('data-testid')).toEqual("eventlistsummary-" + events[0].getId());
expect(els[0].getAttribute("data-scroll-tokens").split(',').length).toEqual(10);
expect(els[0].getAttribute("data-testid")).toEqual("eventlistsummary-" + events[0].getId());
expect(els[0].getAttribute("data-scroll-tokens").split(",").length).toEqual(10);
const updatedEvents = [
...events,
@@ -519,27 +555,27 @@ describe('MessagePanel', function() {
user: "@user:id",
target: bobMember,
ts: Date.now(),
mship: 'join',
prevMship: 'join',
name: 'A user',
mship: "join",
prevMship: "join",
name: "A user",
}),
];
rerender(getComponent({ events: updatedEvents }));
els = container.getElementsByClassName('mx_GenericEventListSummary');
els = container.getElementsByClassName("mx_GenericEventListSummary");
expect(els.length).toEqual(1);
expect(els[0].getAttribute('data-testid')).toEqual("eventlistsummary-" + events[0].getId());
expect(els[0].getAttribute("data-scroll-tokens").split(',').length).toEqual(11);
expect(els[0].getAttribute("data-testid")).toEqual("eventlistsummary-" + events[0].getId());
expect(els[0].getAttribute("data-scroll-tokens").split(",").length).toEqual(11);
});
it('prepends events into summaries during backward pagination without changing key', () => {
it("prepends events into summaries during backward pagination without changing key", () => {
const events = mkMelsEvents().slice(1, 11);
const { container, rerender } = render(getComponent({ events }));
let els = container.getElementsByClassName('mx_GenericEventListSummary');
let els = container.getElementsByClassName("mx_GenericEventListSummary");
expect(els.length).toEqual(1);
expect(els[0].getAttribute('data-testid')).toEqual("eventlistsummary-" + events[0].getId());
expect(els[0].getAttribute("data-scroll-tokens").split(',').length).toEqual(10);
expect(els[0].getAttribute("data-testid")).toEqual("eventlistsummary-" + events[0].getId());
expect(els[0].getAttribute("data-scroll-tokens").split(",").length).toEqual(10);
const updatedEvents = [
TestUtilsMatrix.mkMembership({
@@ -548,28 +584,28 @@ describe('MessagePanel', function() {
user: "@user:id",
target: bobMember,
ts: Date.now(),
mship: 'join',
prevMship: 'join',
name: 'A user',
mship: "join",
prevMship: "join",
name: "A user",
}),
...events,
];
rerender(getComponent({ events: updatedEvents }));
els = container.getElementsByClassName('mx_GenericEventListSummary');
els = container.getElementsByClassName("mx_GenericEventListSummary");
expect(els.length).toEqual(1);
expect(els[0].getAttribute('data-testid')).toEqual("eventlistsummary-" + events[0].getId());
expect(els[0].getAttribute("data-scroll-tokens").split(',').length).toEqual(11);
expect(els[0].getAttribute("data-testid")).toEqual("eventlistsummary-" + events[0].getId());
expect(els[0].getAttribute("data-scroll-tokens").split(",").length).toEqual(11);
});
it('assigns different keys to summaries that get split up', () => {
it("assigns different keys to summaries that get split up", () => {
const events = mkMelsEvents().slice(1, 11);
const { container, rerender } = render(getComponent({ events }));
let els = container.getElementsByClassName('mx_GenericEventListSummary');
let els = container.getElementsByClassName("mx_GenericEventListSummary");
expect(els.length).toEqual(1);
expect(els[0].getAttribute('data-testid')).toEqual(`eventlistsummary-${events[0].getId()}`);
expect(els[0].getAttribute("data-scroll-tokens").split(',').length).toEqual(10);
expect(els[0].getAttribute("data-testid")).toEqual(`eventlistsummary-${events[0].getId()}`);
expect(els[0].getAttribute("data-scroll-tokens").split(",").length).toEqual(10);
const updatedEvents = [
...events.slice(0, 5),
@@ -584,13 +620,13 @@ describe('MessagePanel', function() {
rerender(getComponent({ events: updatedEvents }));
// summaries split becuase room messages are not summarised
els = container.getElementsByClassName('mx_GenericEventListSummary');
els = container.getElementsByClassName("mx_GenericEventListSummary");
expect(els.length).toEqual(2);
expect(els[0].getAttribute('data-testid')).toEqual(`eventlistsummary-${events[0].getId()}`);
expect(els[0].getAttribute("data-scroll-tokens").split(',').length).toEqual(5);
expect(els[0].getAttribute("data-testid")).toEqual(`eventlistsummary-${events[0].getId()}`);
expect(els[0].getAttribute("data-scroll-tokens").split(",").length).toEqual(5);
expect(els[1].getAttribute('data-testid')).toEqual(`eventlistsummary-${events[5].getId()}`);
expect(els[1].getAttribute("data-scroll-tokens").split(',').length).toEqual(5);
expect(els[1].getAttribute("data-testid")).toEqual(`eventlistsummary-${events[5].getId()}`);
expect(els[1].getAttribute("data-scroll-tokens").split(",").length).toEqual(5);
});
// We test this because setting lookups can be *slow*, and we don't want
@@ -638,9 +674,9 @@ describe('MessagePanel', function() {
];
const { container } = render(getComponent({ events }, { showHiddenEvents: true }));
const els = container.getElementsByClassName('mx_GenericEventListSummary');
const els = container.getElementsByClassName("mx_GenericEventListSummary");
expect(els.length).toEqual(1);
expect(els[0].getAttribute("data-scroll-tokens").split(',').length).toEqual(3);
expect(els[0].getAttribute("data-scroll-tokens").split(",").length).toEqual(3);
});
});

View File

@@ -55,8 +55,8 @@ describe("RightPanel", () => {
});
afterEach(async () => {
const roomChanged = new Promise<void>(resolve => {
const ref = dis.register(payload => {
const roomChanged = new Promise<void>((resolve) => {
const ref = dis.register((payload) => {
if (payload.action === Action.ActiveRoomChanged) {
dis.unregister(ref);
resolve();
@@ -84,12 +84,11 @@ describe("RightPanel", () => {
await RightPanelStore.instance.onReady();
};
const waitForRpsUpdate = () =>
new Promise<void>(resolve => RightPanelStore.instance.once(UPDATE_EVENT, resolve));
const waitForRpsUpdate = () => new Promise<void>((resolve) => RightPanelStore.instance.once(UPDATE_EVENT, resolve));
it("navigates from room summary to member list", async () => {
const r1 = mkRoom(cli, "r1");
cli.getRoom.mockImplementation(roomId => roomId === "r1" ? r1 : null);
cli.getRoom.mockImplementation((roomId) => (roomId === "r1" ? r1 : null));
// Set up right panel state
const realGetValue = SettingsStore.getValue;
@@ -127,7 +126,7 @@ describe("RightPanel", () => {
const r1 = mkRoom(cli, "r1");
const r2 = mkRoom(cli, "r2");
cli.getRoom.mockImplementation(roomId => {
cli.getRoom.mockImplementation((roomId) => {
if (roomId === "r1") return r1;
if (roomId === "r2") return r2;
return null;
@@ -168,7 +167,7 @@ describe("RightPanel", () => {
// We want to verify that as we change to room 2, we should always have
// the correct right panel state for whichever room we are showing.
const instance = wrapper.find(_RightPanel).instance() as _RightPanel;
const rendered = new Promise<void>(resolve => {
const rendered = new Promise<void>((resolve) => {
jest.spyOn(instance, "render").mockImplementation(() => {
const { props, state } = instance;
if (props.room.roomId === "r2" && state.phase === RightPanelPhases.RoomMemberList) {

View File

@@ -63,7 +63,7 @@ describe("<RoomSearchView/>", () => {
it("should show a spinner before the promise resolves", async () => {
const deferred = defer<ISearchResults>();
render((
render(
<RoomSearchView
term="search term"
scope={SearchScope.All}
@@ -72,50 +72,57 @@ describe("<RoomSearchView/>", () => {
permalinkCreator={permalinkCreator}
className="someClass"
onUpdate={jest.fn()}
/>
));
/>,
);
await screen.findByTestId("messagePanelSearchSpinner");
});
it("should render results when the promise resolves", async () => {
render((
render(
<MatrixClientContext.Provider value={client}>
<RoomSearchView
term="search term"
scope={SearchScope.All}
promise={Promise.resolve<ISearchResults>({
results: [
SearchResult.fromJson({
rank: 1,
result: {
room_id: room.roomId,
event_id: "$2",
sender: client.getUserId(),
origin_server_ts: 1,
content: { body: "Foo Test Bar", msgtype: "m.text" },
type: EventType.RoomMessage,
},
context: {
profile_info: {},
events_before: [{
SearchResult.fromJson(
{
rank: 1,
result: {
room_id: room.roomId,
event_id: "$1",
event_id: "$2",
sender: client.getUserId(),
origin_server_ts: 1,
content: { body: "Before", msgtype: "m.text" },
content: { body: "Foo Test Bar", msgtype: "m.text" },
type: EventType.RoomMessage,
}],
events_after: [{
room_id: room.roomId,
event_id: "$3",
sender: client.getUserId(),
origin_server_ts: 1,
content: { body: "After", msgtype: "m.text" },
type: EventType.RoomMessage,
}],
},
context: {
profile_info: {},
events_before: [
{
room_id: room.roomId,
event_id: "$1",
sender: client.getUserId(),
origin_server_ts: 1,
content: { body: "Before", msgtype: "m.text" },
type: EventType.RoomMessage,
},
],
events_after: [
{
room_id: room.roomId,
event_id: "$3",
sender: client.getUserId(),
origin_server_ts: 1,
content: { body: "After", msgtype: "m.text" },
type: EventType.RoomMessage,
},
],
},
},
}, eventMapper),
eventMapper,
),
],
highlights: [],
count: 1,
@@ -125,8 +132,8 @@ describe("<RoomSearchView/>", () => {
className="someClass"
onUpdate={jest.fn()}
/>
</MatrixClientContext.Provider>
));
</MatrixClientContext.Provider>,
);
await screen.findByText("Before");
await screen.findByText("Foo Test Bar");
@@ -134,29 +141,32 @@ describe("<RoomSearchView/>", () => {
});
it("should highlight words correctly", async () => {
render((
render(
<MatrixClientContext.Provider value={client}>
<RoomSearchView
term="search term"
scope={SearchScope.Room}
promise={Promise.resolve<ISearchResults>({
results: [
SearchResult.fromJson({
rank: 1,
result: {
room_id: room.roomId,
event_id: "$2",
sender: client.getUserId(),
origin_server_ts: 1,
content: { body: "Foo Test Bar", msgtype: "m.text" },
type: EventType.RoomMessage,
SearchResult.fromJson(
{
rank: 1,
result: {
room_id: room.roomId,
event_id: "$2",
sender: client.getUserId(),
origin_server_ts: 1,
content: { body: "Foo Test Bar", msgtype: "m.text" },
type: EventType.RoomMessage,
},
context: {
profile_info: {},
events_before: [],
events_after: [],
},
},
context: {
profile_info: {},
events_before: [],
events_after: [],
},
}, eventMapper),
eventMapper,
),
],
highlights: ["test"],
count: 1,
@@ -166,8 +176,8 @@ describe("<RoomSearchView/>", () => {
className="someClass"
onUpdate={jest.fn()}
/>
</MatrixClientContext.Provider>
));
</MatrixClientContext.Provider>,
);
const text = await screen.findByText("Test");
expect(text).toHaveClass("mx_EventTile_searchHighlight");
@@ -176,22 +186,25 @@ describe("<RoomSearchView/>", () => {
it("should show spinner above results when backpaginating", async () => {
const searchResults: ISearchResults = {
results: [
SearchResult.fromJson({
rank: 1,
result: {
room_id: room.roomId,
event_id: "$2",
sender: client.getUserId(),
origin_server_ts: 1,
content: { body: "Foo Test Bar", msgtype: "m.text" },
type: EventType.RoomMessage,
SearchResult.fromJson(
{
rank: 1,
result: {
room_id: room.roomId,
event_id: "$2",
sender: client.getUserId(),
origin_server_ts: 1,
content: { body: "Foo Test Bar", msgtype: "m.text" },
type: EventType.RoomMessage,
},
context: {
profile_info: {},
events_before: [],
events_after: [],
},
},
context: {
profile_info: {},
events_before: [],
events_after: [],
},
}, eventMapper),
eventMapper,
),
],
highlights: ["test"],
next_batch: "next_batch",
@@ -202,27 +215,30 @@ describe("<RoomSearchView/>", () => {
...searchResults,
results: [
...searchResults.results,
SearchResult.fromJson({
rank: 1,
result: {
room_id: room.roomId,
event_id: "$4",
sender: client.getUserId(),
origin_server_ts: 4,
content: { body: "Potato", msgtype: "m.text" },
type: EventType.RoomMessage,
SearchResult.fromJson(
{
rank: 1,
result: {
room_id: room.roomId,
event_id: "$4",
sender: client.getUserId(),
origin_server_ts: 4,
content: { body: "Potato", msgtype: "m.text" },
type: EventType.RoomMessage,
},
context: {
profile_info: {},
events_before: [],
events_after: [],
},
},
context: {
profile_info: {},
events_before: [],
events_after: [],
},
}, eventMapper),
eventMapper,
),
],
next_batch: undefined,
});
render((
render(
<MatrixClientContext.Provider value={client}>
<RoomSearchView
term="search term"
@@ -233,8 +249,8 @@ describe("<RoomSearchView/>", () => {
className="someClass"
onUpdate={jest.fn()}
/>
</MatrixClientContext.Provider>
));
</MatrixClientContext.Provider>,
);
await screen.findByRole("progressbar");
await screen.findByText("Potato");
@@ -244,7 +260,7 @@ describe("<RoomSearchView/>", () => {
it("should handle resolutions after unmounting sanely", async () => {
const deferred = defer<ISearchResults>();
const { unmount } = render((
const { unmount } = render(
<MatrixClientContext.Provider value={client}>
<RoomSearchView
term="search term"
@@ -255,8 +271,8 @@ describe("<RoomSearchView/>", () => {
className="someClass"
onUpdate={jest.fn()}
/>
</MatrixClientContext.Provider>
));
</MatrixClientContext.Provider>,
);
unmount();
deferred.resolve({
@@ -268,7 +284,7 @@ describe("<RoomSearchView/>", () => {
it("should handle rejections after unmounting sanely", async () => {
const deferred = defer<ISearchResults>();
const { unmount } = render((
const { unmount } = render(
<MatrixClientContext.Provider value={client}>
<RoomSearchView
term="search term"
@@ -279,8 +295,8 @@ describe("<RoomSearchView/>", () => {
className="someClass"
onUpdate={jest.fn()}
/>
</MatrixClientContext.Provider>
));
</MatrixClientContext.Provider>,
);
unmount();
deferred.reject({
@@ -292,7 +308,7 @@ describe("<RoomSearchView/>", () => {
it("should show modal if error is encountered", async () => {
const deferred = defer<ISearchResults>();
render((
render(
<MatrixClientContext.Provider value={client}>
<RoomSearchView
term="search term"
@@ -303,8 +319,8 @@ describe("<RoomSearchView/>", () => {
className="someClass"
onUpdate={jest.fn()}
/>
</MatrixClientContext.Provider>
));
</MatrixClientContext.Provider>,
);
deferred.reject(new Error("Some error"));
await screen.findByText("Search failed");

View File

@@ -85,7 +85,7 @@ describe("RoomStatusBar", () => {
expect(pendingEvents[2].threadRootId).toBe(rootEvent.getId());
// Filters out the non thread events
expect(pendingEvents.every(ev => ev.getId() !== event.getId())).toBe(true);
expect(pendingEvents.every((ev) => ev.getId() !== event.getId())).toBe(true);
});
});
});

View File

@@ -24,7 +24,7 @@ describe("RoomStatusBarUnsentMessages", () => {
const title = "test title";
const description = "test description";
const buttonsText = "test buttons";
const buttons = <div>{ buttonsText }</div>;
const buttons = <div>{buttonsText}</div>;
beforeEach(() => {
render(

View File

@@ -75,7 +75,7 @@ describe("RoomView", () => {
stores.client = cli;
stores.rightPanelStore.useUnitTestClient(cli);
jest.spyOn(VoipUserMapper.sharedInstance(), 'getVirtualRoomForRoom').mockResolvedValue(null);
jest.spyOn(VoipUserMapper.sharedInstance(), "getVirtualRoomForRoom").mockResolvedValue(null);
});
afterEach(async () => {
@@ -85,7 +85,7 @@ describe("RoomView", () => {
const mountRoomView = async (): Promise<ReactWrapper> => {
if (stores.roomViewStore.getRoomId() !== room.roomId) {
const switchedRoom = new Promise<void>(resolve => {
const switchedRoom = new Promise<void>((resolve) => {
const subFn = () => {
if (stores.roomViewStore.getRoomId()) {
stores.roomViewStore.off(UPDATE_EVENT, subFn);
@@ -121,7 +121,7 @@ describe("RoomView", () => {
const renderRoomView = async (): Promise<ReturnType<typeof render>> => {
if (stores.roomViewStore.getRoomId() !== room.roomId) {
const switchedRoom = new Promise<void>(resolve => {
const switchedRoom = new Promise<void>((resolve) => {
const subFn = () => {
if (stores.roomViewStore.getRoomId()) {
stores.roomViewStore.off(UPDATE_EVENT, subFn);
@@ -180,13 +180,15 @@ describe("RoomView", () => {
cli.isRoomEncrypted.mockReturnValue(true);
// and fake an encryption event into the room to prompt it to re-check
room.addLiveEvents([new MatrixEvent({
type: "m.room.encryption",
sender: cli.getUserId()!,
content: {},
event_id: "someid",
room_id: room.roomId,
})]);
room.addLiveEvents([
new MatrixEvent({
type: "m.room.encryption",
sender: cli.getUserId()!,
content: {},
event_id: "someid",
room_id: room.roomId,
}),
]);
// URL previews should now be disabled
expect(roomViewInstance.state.showUrlPreview).toBe(false);
@@ -200,13 +202,13 @@ describe("RoomView", () => {
expect(roomViewInstance.state.liveTimeline).not.toEqual(oldTimeline);
});
describe('with virtual rooms', () => {
describe("with virtual rooms", () => {
it("checks for a virtual room on initial load", async () => {
const { container } = await renderRoomView();
expect(VoipUserMapper.sharedInstance().getVirtualRoomForRoom).toHaveBeenCalledWith(room.roomId);
// quick check that rendered without error
expect(container.querySelector('.mx_ErrorBoundary')).toBeFalsy();
expect(container.querySelector(".mx_ErrorBoundary")).toBeFalsy();
});
it("checks for a virtual room on room event", async () => {
@@ -307,7 +309,7 @@ describe("RoomView", () => {
it("clicking retry should set the room state to new dispatch a local room event", async () => {
jest.spyOn(defaultDispatcher, "dispatch");
const { getByText } = await renderRoomView();
fireEvent.click(getByText('Retry'));
fireEvent.click(getByText("Retry"));
expect(localRoom.state).toBe(LocalRoomState.NEW);
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({
action: "local_room_event",

View File

@@ -53,17 +53,17 @@ describe("SpaceHierarchy", () => {
it("shows room", () => {
showRoom(client, hierarchy, "room-id2");
expect(dispatcher.dispatch).toHaveBeenCalledWith({
"action": Action.ViewRoom,
"should_peek": true,
"room_alias": "canonical-alias",
"room_id": "room-id2",
"via_servers": [],
"oob_data": {
action: Action.ViewRoom,
should_peek: true,
room_alias: "canonical-alias",
room_id: "room-id2",
via_servers: [],
oob_data: {
avatarUrl: undefined,
name: "canonical-alias",
},
"roomType": undefined,
"metricsTrigger": "RoomDirectory",
roomType: undefined,
metricsTrigger: "RoomDirectory",
});
});
});

View File

@@ -14,31 +14,16 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { fireEvent, render } from "@testing-library/react";
import { act } from 'react-dom/test-utils';
import { act } from "react-dom/test-utils";
import TabbedView, { Tab, TabLocation } from "../../../src/components/structures/TabbedView";
describe('<TabbedView />', () => {
const generalTab = new Tab(
'GENERAL',
'General',
'general',
<div>general</div>,
);
const labsTab = new Tab(
'LABS',
'Labs',
'labs',
<div>labs</div>,
);
const securityTab = new Tab(
'SECURITY',
'Security',
'security',
<div>security</div>,
);
describe("<TabbedView />", () => {
const generalTab = new Tab("GENERAL", "General", "general", <div>general</div>);
const labsTab = new Tab("LABS", "Labs", "labs", <div>labs</div>);
const securityTab = new Tab("SECURITY", "Security", "security", <div>security</div>);
const defaultProps = {
tabLocation: TabLocation.LEFT,
tabs: [generalTab, labsTab, securityTab],
@@ -47,39 +32,39 @@ describe('<TabbedView />', () => {
const getTabTestId = (tab: Tab): string => `settings-tab-${tab.id}`;
const getActiveTab = (container: HTMLElement): Element | undefined =>
container.getElementsByClassName('mx_TabbedView_tabLabel_active')[0];
container.getElementsByClassName("mx_TabbedView_tabLabel_active")[0];
const getActiveTabBody = (container: HTMLElement): Element | undefined =>
container.getElementsByClassName('mx_TabbedView_tabPanel')[0];
container.getElementsByClassName("mx_TabbedView_tabPanel")[0];
it('renders tabs', () => {
it("renders tabs", () => {
const { container } = render(getComponent());
expect(container).toMatchSnapshot();
});
it('renders first tab as active tab when no initialTabId', () => {
it("renders first tab as active tab when no initialTabId", () => {
const { container } = render(getComponent());
expect(getActiveTab(container).textContent).toEqual(generalTab.label);
expect(getActiveTabBody(container).textContent).toEqual('general');
expect(getActiveTabBody(container).textContent).toEqual("general");
});
it('renders first tab as active tab when initialTabId is not valid', () => {
const { container } = render(getComponent({ initialTabId: 'bad-tab-id' }));
it("renders first tab as active tab when initialTabId is not valid", () => {
const { container } = render(getComponent({ initialTabId: "bad-tab-id" }));
expect(getActiveTab(container).textContent).toEqual(generalTab.label);
expect(getActiveTabBody(container).textContent).toEqual('general');
expect(getActiveTabBody(container).textContent).toEqual("general");
});
it('renders initialTabId tab as active when valid', () => {
it("renders initialTabId tab as active when valid", () => {
const { container } = render(getComponent({ initialTabId: securityTab.id }));
expect(getActiveTab(container).textContent).toEqual(securityTab.label);
expect(getActiveTabBody(container).textContent).toEqual('security');
expect(getActiveTabBody(container).textContent).toEqual("security");
});
it('renders without error when there are no tabs', () => {
it("renders without error when there are no tabs", () => {
const { container } = render(getComponent({ tabs: [] }));
expect(container).toMatchSnapshot();
});
it('sets active tab on tab click', () => {
it("sets active tab on tab click", () => {
const { container, getByTestId } = render(getComponent());
act(() => {
@@ -87,10 +72,10 @@ describe('<TabbedView />', () => {
});
expect(getActiveTab(container).textContent).toEqual(securityTab.label);
expect(getActiveTabBody(container).textContent).toEqual('security');
expect(getActiveTabBody(container).textContent).toEqual("security");
});
it('calls onchange on on tab click', () => {
it("calls onchange on on tab click", () => {
const onChange = jest.fn();
const { getByTestId } = render(getComponent({ onChange }));
@@ -101,7 +86,7 @@ describe('<TabbedView />', () => {
expect(onChange).toHaveBeenCalledWith(securityTab.id);
});
it('keeps same tab active when order of tabs changes', () => {
it("keeps same tab active when order of tabs changes", () => {
// start with middle tab active
const { container, rerender } = render(getComponent({ initialTabId: labsTab.id }));
@@ -113,7 +98,7 @@ describe('<TabbedView />', () => {
expect(getActiveTab(container).textContent).toEqual(labsTab.label);
});
it('does not reactivate inititalTabId on rerender', () => {
it("does not reactivate inititalTabId on rerender", () => {
const { container, getByTestId, rerender } = render(getComponent());
expect(getActiveTab(container).textContent).toEqual(generalTab.label);

View File

@@ -14,22 +14,22 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { render, screen, fireEvent } from "@testing-library/react";
import { mocked } from "jest-mock";
import 'focus-visible'; // to fix context menus
import "focus-visible"; // to fix context menus
import ThreadPanel, { ThreadFilterType, ThreadPanelHeader } from '../../../src/components/structures/ThreadPanel';
import { _t } from '../../../src/languageHandler';
import ResizeNotifier from '../../../src/utils/ResizeNotifier';
import { RoomPermalinkCreator } from '../../../src/utils/permalinks/Permalinks';
import { createTestClient, mkStubRoom } from '../../test-utils';
import ThreadPanel, { ThreadFilterType, ThreadPanelHeader } from "../../../src/components/structures/ThreadPanel";
import { _t } from "../../../src/languageHandler";
import ResizeNotifier from "../../../src/utils/ResizeNotifier";
import { RoomPermalinkCreator } from "../../../src/utils/permalinks/Permalinks";
import { createTestClient, mkStubRoom } from "../../test-utils";
import { shouldShowFeedback } from "../../../src/utils/Feedback";
import MatrixClientContext from "../../../src/contexts/MatrixClientContext";
jest.mock("../../../src/utils/Feedback");
describe('ThreadPanel', () => {
describe("ThreadPanel", () => {
describe("Feedback prompt", () => {
const cli = createTestClient();
const room = mkStubRoom("!room:server", "room", cli);
@@ -38,59 +38,66 @@ describe('ThreadPanel', () => {
it("should show feedback prompt if feedback is enabled", () => {
mocked(shouldShowFeedback).mockReturnValue(true);
render(<MatrixClientContext.Provider value={cli}>
<ThreadPanel
roomId="!room:server"
onClose={jest.fn()}
resizeNotifier={new ResizeNotifier()}
permalinkCreator={new RoomPermalinkCreator(room)}
/>
</MatrixClientContext.Provider>);
render(
<MatrixClientContext.Provider value={cli}>
<ThreadPanel
roomId="!room:server"
onClose={jest.fn()}
resizeNotifier={new ResizeNotifier()}
permalinkCreator={new RoomPermalinkCreator(room)}
/>
</MatrixClientContext.Provider>,
);
expect(screen.queryByText("Give feedback")).toBeTruthy();
});
it("should hide feedback prompt if feedback is disabled", () => {
mocked(shouldShowFeedback).mockReturnValue(false);
render(<MatrixClientContext.Provider value={cli}>
<ThreadPanel
roomId="!room:server"
onClose={jest.fn()}
resizeNotifier={new ResizeNotifier()}
permalinkCreator={new RoomPermalinkCreator(room)}
/>
</MatrixClientContext.Provider>);
render(
<MatrixClientContext.Provider value={cli}>
<ThreadPanel
roomId="!room:server"
onClose={jest.fn()}
resizeNotifier={new ResizeNotifier()}
permalinkCreator={new RoomPermalinkCreator(room)}
/>
</MatrixClientContext.Provider>,
);
expect(screen.queryByText("Give feedback")).toBeFalsy();
});
});
describe('Header', () => {
it('expect that All filter for ThreadPanelHeader properly renders Show: All threads', () => {
describe("Header", () => {
it("expect that All filter for ThreadPanelHeader properly renders Show: All threads", () => {
const { asFragment } = render(
<ThreadPanelHeader
empty={false}
filterOption={ThreadFilterType.All}
setFilterOption={() => undefined} />,
setFilterOption={() => undefined}
/>,
);
expect(asFragment()).toMatchSnapshot();
});
it('expect that My filter for ThreadPanelHeader properly renders Show: My threads', () => {
it("expect that My filter for ThreadPanelHeader properly renders Show: My threads", () => {
const { asFragment } = render(
<ThreadPanelHeader
empty={false}
filterOption={ThreadFilterType.My}
setFilterOption={() => undefined} />,
setFilterOption={() => undefined}
/>,
);
expect(asFragment()).toMatchSnapshot();
});
it('expect that ThreadPanelHeader properly opens a context menu when clicked on the button', () => {
it("expect that ThreadPanelHeader properly opens a context menu when clicked on the button", () => {
const { container } = render(
<ThreadPanelHeader
empty={false}
filterOption={ThreadFilterType.All}
setFilterOption={() => undefined} />,
setFilterOption={() => undefined}
/>,
);
const found = container.querySelector(".mx_ThreadPanel_dropdown");
expect(found).toBeTruthy();
@@ -99,18 +106,19 @@ describe('ThreadPanel', () => {
expect(screen.queryByRole("menu")).toBeTruthy();
});
it('expect that ThreadPanelHeader has the correct option selected in the context menu', () => {
it("expect that ThreadPanelHeader has the correct option selected in the context menu", () => {
const { container } = render(
<ThreadPanelHeader
empty={false}
filterOption={ThreadFilterType.All}
setFilterOption={() => undefined} />,
setFilterOption={() => undefined}
/>,
);
fireEvent.click(container.querySelector(".mx_ThreadPanel_dropdown"));
const found = screen.queryAllByRole("menuitemradio");
expect(found).toHaveLength(2);
const foundButton = screen.queryByRole("menuitemradio", { checked: true });
expect(foundButton.textContent).toEqual(`${_t("All threads")}${_t('Shows all threads from current room')}`);
expect(foundButton.textContent).toEqual(`${_t("All threads")}${_t("Shows all threads from current room")}`);
expect(foundButton).toMatchSnapshot();
});
});

View File

@@ -51,27 +51,25 @@ describe("ThreadView", () => {
const [event, setEvent] = useState(rootEvent);
changeEvent = setEvent;
return <MatrixClientContext.Provider value={mockClient}>
<RoomContext.Provider value={getRoomContext(room, {
canSendMessages: true,
})}>
<ThreadView
room={room}
onClose={jest.fn()}
mxEvent={event}
resizeNotifier={new ResizeNotifier()}
/>
</RoomContext.Provider>,
</MatrixClientContext.Provider>;
return (
<MatrixClientContext.Provider value={mockClient}>
<RoomContext.Provider
value={getRoomContext(room, {
canSendMessages: true,
})}
>
<ThreadView room={room} onClose={jest.fn()} mxEvent={event} resizeNotifier={new ResizeNotifier()} />
</RoomContext.Provider>
,
</MatrixClientContext.Provider>
);
}
async function getComponent(): Promise<RenderResult> {
const renderResult = render(
<TestThreadView />,
);
const renderResult = render(<TestThreadView />);
await waitFor(() => {
expect(() => getByTestId(renderResult.container, 'spinner')).toThrow();
expect(() => getByTestId(renderResult.container, "spinner")).toThrow();
});
return renderResult;
@@ -92,9 +90,12 @@ describe("ThreadView", () => {
"event_id": rootEvent.getId(),
"is_falling_back": true,
"m.in_reply_to": {
"event_id": rootEvent.getThread().lastReply((ev: MatrixEvent) => {
return ev.isRelation(THREAD_RELATION_TYPE.name);
}).getId(),
event_id: rootEvent
.getThread()
.lastReply((ev: MatrixEvent) => {
return ev.isRelation(THREAD_RELATION_TYPE.name);
})
.getId(),
},
"rel_type": RelationType.Thread,
},
@@ -133,7 +134,9 @@ describe("ThreadView", () => {
await sendMessage(container, "Hello world!");
expect(mockClient.sendMessage).toHaveBeenCalledWith(
ROOM_ID, rootEvent.getId(), expectedMessageBody(rootEvent, "Hello world!"),
ROOM_ID,
rootEvent.getId(),
expectedMessageBody(rootEvent, "Hello world!"),
);
});
@@ -154,7 +157,9 @@ describe("ThreadView", () => {
await sendMessage(container, "yolo");
expect(mockClient.sendMessage).toHaveBeenCalledWith(
ROOM_ID, rootEvent2.getId(), expectedMessageBody(rootEvent2, "yolo"),
ROOM_ID,
rootEvent2.getId(),
expectedMessageBody(rootEvent2, "yolo"),
);
});

View File

@@ -17,7 +17,7 @@ limitations under the License.
import { render, RenderResult } from "@testing-library/react";
// eslint-disable-next-line deprecate/import
import { mount, ReactWrapper } from "enzyme";
import { MessageEvent } from 'matrix-events-sdk';
import { MessageEvent } from "matrix-events-sdk";
import { ReceiptType } from "matrix-js-sdk/src/@types/read_receipts";
import {
EventTimelineSet,
@@ -28,7 +28,7 @@ import {
Room,
RoomEvent,
TimelineWindow,
} from 'matrix-js-sdk/src/matrix';
} from "matrix-js-sdk/src/matrix";
import { EventTimeline } from "matrix-js-sdk/src/models/event-timeline";
import {
FeatureSupport,
@@ -37,13 +37,13 @@ import {
ThreadEvent,
ThreadFilterType,
} from "matrix-js-sdk/src/models/thread";
import React from 'react';
import React from "react";
import TimelinePanel from '../../../src/components/structures/TimelinePanel';
import TimelinePanel from "../../../src/components/structures/TimelinePanel";
import MatrixClientContext from "../../../src/contexts/MatrixClientContext";
import { MatrixClientPeg } from '../../../src/MatrixClientPeg';
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
import SettingsStore from "../../../src/settings/SettingsStore";
import { isCallEvent } from '../../../src/components/structures/LegacyCallEventGrouper';
import { isCallEvent } from "../../../src/components/structures/LegacyCallEventGrouper";
import { flushPromises, mkRoom, stubClient } from "../../test-utils";
const newReceipt = (eventId: string, userId: string, readTs: number, fullyReadTs: number): MatrixEvent => {
@@ -81,13 +81,15 @@ const renderPanel = (room: Room, events: MatrixEvent[]): RenderResult => {
const mockEvents = (room: Room, count = 2): MatrixEvent[] => {
const events: MatrixEvent[] = [];
for (let index = 0; index < count; index++) {
events.push(new MatrixEvent({
room_id: room.roomId,
event_id: `${room.roomId}_event_${index}`,
type: EventType.RoomMessage,
user_id: "userId",
content: MessageEvent.from(`Event${index}`).serialize().content,
}));
events.push(
new MatrixEvent({
room_id: room.roomId,
event_id: `${room.roomId}_event_${index}`,
type: EventType.RoomMessage,
user_id: "userId",
content: MessageEvent.from(`Event${index}`).serialize().content,
}),
);
}
return events;
@@ -100,13 +102,13 @@ const setupTestData = (): [MatrixClient, Room, MatrixEvent[]] => {
return [client, room, events];
};
describe('TimelinePanel', () => {
describe("TimelinePanel", () => {
beforeEach(() => {
stubClient();
});
describe('read receipts and markers', () => {
it('should forget the read marker when asked to', () => {
describe("read receipts and markers", () => {
it("should forget the read marker when asked to", () => {
const cli = MatrixClientPeg.get();
const readMarkersSent: string[] = [];
@@ -131,22 +133,19 @@ describe('TimelinePanel', () => {
const roomId = "#room:example.com";
const userId = cli.credentials.userId!;
const room = new Room(
roomId,
cli,
userId,
{ pendingEventOrdering: PendingEventOrdering.Detached },
);
const room = new Room(roomId, cli, userId, { pendingEventOrdering: PendingEventOrdering.Detached });
// Create a TimelinePanel with ev0 already present
const timelineSet = new EventTimelineSet(room, {});
timelineSet.addLiveEvent(ev0);
const component: ReactWrapper<TimelinePanel> = mount(<TimelinePanel
timelineSet={timelineSet}
manageReadMarkers={true}
manageReadReceipts={true}
eventId={ev0.getId()}
/>);
const component: ReactWrapper<TimelinePanel> = mount(
<TimelinePanel
timelineSet={timelineSet}
manageReadMarkers={true}
manageReadReceipts={true}
eventId={ev0.getId()}
/>,
);
const timelinePanel = component.instance() as TimelinePanel;
// An event arrived, and we read it
@@ -208,8 +207,8 @@ describe('TimelinePanel', () => {
expect(props.onEventScrolledIntoView).toHaveBeenCalledWith(events[1].getId());
});
describe('onRoomTimeline', () => {
it('ignores events for other timelines', () => {
describe("onRoomTimeline", () => {
it("ignores events for other timelines", () => {
const [client, room, events] = setupTestData();
const otherTimelineSet = { room: room as Room } as EventTimelineSet;
@@ -220,7 +219,7 @@ describe('TimelinePanel', () => {
onEventScrolledIntoView: jest.fn(),
};
const paginateSpy = jest.spyOn(TimelineWindow.prototype, 'paginate').mockClear();
const paginateSpy = jest.spyOn(TimelineWindow.prototype, "paginate").mockClear();
render(<TimelinePanel {...props} />);
@@ -231,12 +230,12 @@ describe('TimelinePanel', () => {
expect(paginateSpy).not.toHaveBeenCalled();
});
it('ignores timeline updates without a live event', () => {
it("ignores timeline updates without a live event", () => {
const [client, room, events] = setupTestData();
const props = getProps(room, events);
const paginateSpy = jest.spyOn(TimelineWindow.prototype, 'paginate').mockClear();
const paginateSpy = jest.spyOn(TimelineWindow.prototype, "paginate").mockClear();
render(<TimelinePanel {...props} />);
@@ -247,12 +246,12 @@ describe('TimelinePanel', () => {
expect(paginateSpy).not.toHaveBeenCalled();
});
it('ignores timeline where toStartOfTimeline is true', () => {
it("ignores timeline where toStartOfTimeline is true", () => {
const [client, room, events] = setupTestData();
const props = getProps(room, events);
const paginateSpy = jest.spyOn(TimelineWindow.prototype, 'paginate').mockClear();
const paginateSpy = jest.spyOn(TimelineWindow.prototype, "paginate").mockClear();
render(<TimelinePanel {...props} />);
@@ -264,12 +263,12 @@ describe('TimelinePanel', () => {
expect(paginateSpy).not.toHaveBeenCalled();
});
it('advances the timeline window', () => {
it("advances the timeline window", () => {
const [client, room, events] = setupTestData();
const props = getProps(room, events);
const paginateSpy = jest.spyOn(TimelineWindow.prototype, 'paginate').mockClear();
const paginateSpy = jest.spyOn(TimelineWindow.prototype, "paginate").mockClear();
render(<TimelinePanel {...props} />);
@@ -280,7 +279,7 @@ describe('TimelinePanel', () => {
expect(paginateSpy).toHaveBeenCalledWith(EventTimeline.FORWARDS, 1, false);
});
it('advances the overlay timeline window', async () => {
it("advances the overlay timeline window", async () => {
const [client, room, events] = setupTestData();
const virtualRoom = mkRoom(client, "virtualRoomId");
@@ -292,7 +291,7 @@ describe('TimelinePanel', () => {
overlayTimelineSet,
};
const paginateSpy = jest.spyOn(TimelineWindow.prototype, 'paginate').mockClear();
const paginateSpy = jest.spyOn(TimelineWindow.prototype, "paginate").mockClear();
render(<TimelinePanel {...props} />);
@@ -306,25 +305,21 @@ describe('TimelinePanel', () => {
});
});
describe('with overlayTimeline', () => {
it('renders merged timeline', () => {
describe("with overlayTimeline", () => {
it("renders merged timeline", () => {
const [client, room, events] = setupTestData();
const virtualRoom = mkRoom(client, "virtualRoomId");
const virtualCallInvite = new MatrixEvent({
type: 'm.call.invite',
type: "m.call.invite",
room_id: virtualRoom.roomId,
event_id: `virtualCallEvent1`,
});
const virtualCallMetaEvent = new MatrixEvent({
type: 'org.matrix.call.sdp_stream_metadata_changed',
type: "org.matrix.call.sdp_stream_metadata_changed",
room_id: virtualRoom.roomId,
event_id: `virtualCallEvent2`,
});
const virtualEvents = [
virtualCallInvite,
...mockEvents(virtualRoom),
virtualCallMetaEvent,
];
const virtualEvents = [virtualCallInvite, ...mockEvents(virtualRoom), virtualCallMetaEvent];
const { timelineSet: overlayTimelineSet } = getProps(virtualRoom, virtualEvents);
const props = {
@@ -335,8 +330,8 @@ describe('TimelinePanel', () => {
const { container } = render(<TimelinePanel {...props} />);
const eventTiles = container.querySelectorAll('.mx_EventTile');
const eventTileIds = [...eventTiles].map(tileElement => tileElement.getAttribute('data-event-id'));
const eventTiles = container.querySelectorAll(".mx_EventTile");
const eventTileIds = [...eventTiles].map((tileElement) => tileElement.getAttribute("data-event-id"));
expect(eventTileIds).toEqual([
// main timeline events are included
events[1].getId(),
@@ -368,16 +363,22 @@ describe('TimelinePanel', () => {
});
room = new Room("roomId", client, "userId");
allThreads = new EventTimelineSet(room, {
pendingEvents: false,
}, undefined, undefined, ThreadFilterType.All);
allThreads = new EventTimelineSet(
room,
{
pendingEvents: false,
},
undefined,
undefined,
ThreadFilterType.All,
);
const timeline = new EventTimeline(allThreads);
allThreads.getLiveTimeline = () => timeline;
allThreads.getTimelineForEvent = () => timeline;
reply1 = new MatrixEvent({
room_id: room.roomId,
event_id: 'event_reply_1',
event_id: "event_reply_1",
type: EventType.RoomMessage,
user_id: "userId",
content: MessageEvent.from(`ReplyEvent1`).serialize().content,
@@ -385,7 +386,7 @@ describe('TimelinePanel', () => {
reply2 = new MatrixEvent({
room_id: room.roomId,
event_id: 'event_reply_2',
event_id: "event_reply_2",
type: EventType.RoomMessage,
user_id: "userId",
content: MessageEvent.from(`ReplyEvent2`).serialize().content,
@@ -393,7 +394,7 @@ describe('TimelinePanel', () => {
root = new MatrixEvent({
room_id: room.roomId,
event_id: 'event_root_1',
event_id: "event_root_1",
type: EventType.RoomMessage,
user_id: "userId",
content: MessageEvent.from(`RootEvent`).serialize().content,
@@ -410,13 +411,13 @@ describe('TimelinePanel', () => {
roomId === room.roomId ? eventMap[eventId]?.event : {};
});
it('updates thread previews', async () => {
it("updates thread previews", async () => {
root.setUnsigned({
"m.relations": {
[THREAD_RELATION_TYPE.name]: {
"latest_event": reply1.event,
"count": 1,
"current_user_participated": true,
latest_event: reply1.event,
count: 1,
current_user_participated: true,
},
},
});
@@ -432,11 +433,7 @@ describe('TimelinePanel', () => {
const dom = render(
<MatrixClientContext.Provider value={client}>
<TimelinePanel
timelineSet={allThreads}
manageReadReceipts
sendReadReceiptOnLoad
/>
<TimelinePanel timelineSet={allThreads} manageReadReceipts sendReadReceiptOnLoad />
</MatrixClientContext.Provider>,
);
await dom.findByText("RootEvent");
@@ -446,9 +443,9 @@ describe('TimelinePanel', () => {
root.setUnsigned({
"m.relations": {
[THREAD_RELATION_TYPE.name]: {
"latest_event": reply2.event,
"count": 2,
"current_user_participated": true,
latest_event: reply2.event,
count: 2,
current_user_participated: true,
},
},
});
@@ -460,13 +457,13 @@ describe('TimelinePanel', () => {
expect(replyToEvent).toHaveBeenCalled();
});
it('ignores thread updates for unknown threads', async () => {
it("ignores thread updates for unknown threads", async () => {
root.setUnsigned({
"m.relations": {
[THREAD_RELATION_TYPE.name]: {
"latest_event": reply1.event,
"count": 1,
"current_user_participated": true,
latest_event: reply1.event,
count: 1,
current_user_participated: true,
},
},
});
@@ -499,11 +496,7 @@ describe('TimelinePanel', () => {
const dom = render(
<MatrixClientContext.Provider value={client}>
<TimelinePanel
timelineSet={allThreads}
manageReadReceipts
sendReadReceiptOnLoad
/>
<TimelinePanel timelineSet={allThreads} manageReadReceipts sendReadReceiptOnLoad />
</MatrixClientContext.Provider>,
);
await dom.findByText("RootEvent");

View File

@@ -42,10 +42,7 @@ describe("<UserMenu>", () => {
client.getUserId() || "",
client.getDeviceId() || "",
);
voiceBroadcastRecording = new VoiceBroadcastRecording(
voiceBroadcastInfoEvent,
client,
);
voiceBroadcastRecording = new VoiceBroadcastRecording(voiceBroadcastInfoEvent, client);
});
beforeEach(() => {

View File

@@ -100,11 +100,9 @@ describe("<ForgotPassword>", () => {
describe("when starting a password reset flow", () => {
beforeEach(() => {
renderResult = render(<ForgotPassword
serverConfig={serverConfig}
onComplete={onComplete}
onLoginClick={onLoginClick}
/>);
renderResult = render(
<ForgotPassword serverConfig={serverConfig} onComplete={onComplete} onLoginClick={onLoginClick} />,
);
});
it("should show the email input and mention the homeserver", () => {
@@ -115,11 +113,9 @@ describe("<ForgotPassword>", () => {
describe("and updating the server config", () => {
beforeEach(() => {
serverConfig.hsName = "example2.com";
renderResult.rerender(<ForgotPassword
serverConfig={serverConfig}
onComplete={onComplete}
onLoginClick={onLoginClick}
/>);
renderResult.rerender(
<ForgotPassword serverConfig={serverConfig} onComplete={onComplete} onLoginClick={onLoginClick} />,
);
});
it("should show the new homeserver server name", () => {
@@ -171,10 +167,12 @@ describe("<ForgotPassword>", () => {
});
it("should show an info about that", () => {
expect(screen.getByText(
"Cannot reach homeserver: "
+ "Ensure you have a stable internet connection, or get in touch with the server admin",
)).toBeInTheDocument();
expect(
screen.getByText(
"Cannot reach homeserver: " +
"Ensure you have a stable internet connection, or get in touch with the server admin",
),
).toBeInTheDocument();
});
});

View File

@@ -14,13 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { fireEvent, render, screen, waitForElementToBeRemoved } from "@testing-library/react";
import { mocked, MockedObject } from 'jest-mock';
import { mocked, MockedObject } from "jest-mock";
import { createClient, MatrixClient } from "matrix-js-sdk/src/matrix";
import fetchMock from "fetch-mock-jest";
import SdkConfig from '../../../../src/SdkConfig';
import SdkConfig from "../../../../src/SdkConfig";
import { mkServerConfig, mockPlatformPeg, unmockPlatformPeg } from "../../../test-utils";
import Login from "../../../../src/components/structures/auth/Login";
import BasePlatform from "../../../../src/BasePlatform";
@@ -29,7 +29,7 @@ jest.mock("matrix-js-sdk/src/matrix");
jest.useRealTimers();
describe('Login', function() {
describe("Login", function () {
let platform: MockedObject<BasePlatform>;
const mockClient = mocked({
@@ -37,14 +37,14 @@ describe('Login', function() {
loginFlows: jest.fn(),
} as unknown as MatrixClient);
beforeEach(function() {
beforeEach(function () {
SdkConfig.put({
brand: "test-brand",
disable_custom_urls: true,
});
mockClient.login.mockClear().mockResolvedValue({});
mockClient.loginFlows.mockClear().mockResolvedValue({ flows: [{ type: "m.login.password" }] });
mocked(createClient).mockImplementation(opts => {
mocked(createClient).mockImplementation((opts) => {
mockClient.idBaseUrl = opts.idBaseUrl;
mockClient.baseUrl = opts.baseUrl;
return mockClient;
@@ -58,26 +58,28 @@ describe('Login', function() {
});
});
afterEach(function() {
afterEach(function () {
fetchMock.restore();
SdkConfig.unset(); // we touch the config, so clean up
unmockPlatformPeg();
});
function getRawComponent(hsUrl = "https://matrix.org", isUrl = "https://vector.im") {
return <Login
serverConfig={mkServerConfig(hsUrl, isUrl)}
onLoggedIn={() => { }}
onRegisterClick={() => { }}
onServerConfigChange={() => { }}
/>;
return (
<Login
serverConfig={mkServerConfig(hsUrl, isUrl)}
onLoggedIn={() => {}}
onRegisterClick={() => {}}
onServerConfigChange={() => {}}
/>
);
}
function getComponent(hsUrl?: string, isUrl?: string) {
return render(getRawComponent(hsUrl, isUrl));
}
it('should show form with change server link', async () => {
it("should show form with change server link", async () => {
SdkConfig.put({
brand: "test-brand",
disable_custom_urls: false,
@@ -90,7 +92,7 @@ describe('Login', function() {
expect(container.querySelector(".mx_ServerPicker_change")).toBeTruthy();
});
it('should show form without change server link when custom URLs disabled', async () => {
it("should show form without change server link when custom URLs disabled", async () => {
const { container } = getComponent();
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading..."));
@@ -122,19 +124,25 @@ describe('Login', function() {
it("should show multiple SSO buttons if multiple identity_providers are available", async () => {
mockClient.loginFlows.mockResolvedValue({
flows: [{
"type": "m.login.sso",
"identity_providers": [{
id: "a",
name: "Provider 1",
}, {
id: "b",
name: "Provider 2",
}, {
id: "c",
name: "Provider 3",
}],
}],
flows: [
{
type: "m.login.sso",
identity_providers: [
{
id: "a",
name: "Provider 1",
},
{
id: "b",
name: "Provider 2",
},
{
id: "c",
name: "Provider 3",
},
],
},
],
});
const { container } = getComponent();
@@ -146,9 +154,11 @@ describe('Login', function() {
it("should show single SSO button if identity_providers is null", async () => {
mockClient.loginFlows.mockResolvedValue({
flows: [{
"type": "m.login.sso",
}],
flows: [
{
type: "m.login.sso",
},
],
});
const { container } = getComponent();
@@ -160,9 +170,11 @@ describe('Login', function() {
it("should handle serverConfig updates correctly", async () => {
mockClient.loginFlows.mockResolvedValue({
flows: [{
"type": "m.login.sso",
}],
flows: [
{
type: "m.login.sso",
},
],
});
const { container, rerender } = render(getRawComponent());

View File

@@ -15,37 +15,42 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { fireEvent, render, screen, waitForElementToBeRemoved } from "@testing-library/react";
import { createClient, MatrixClient } from 'matrix-js-sdk/src/matrix';
import { MatrixError } from 'matrix-js-sdk/src/http-api/errors';
import { mocked } from 'jest-mock';
import { createClient, MatrixClient } from "matrix-js-sdk/src/matrix";
import { MatrixError } from "matrix-js-sdk/src/http-api/errors";
import { mocked } from "jest-mock";
import fetchMock from "fetch-mock-jest";
import SdkConfig, { DEFAULTS } from '../../../../src/SdkConfig';
import SdkConfig, { DEFAULTS } from "../../../../src/SdkConfig";
import { mkServerConfig, mockPlatformPeg, unmockPlatformPeg } from "../../../test-utils";
import Registration from "../../../../src/components/structures/auth/Registration";
jest.mock('matrix-js-sdk/src/matrix');
jest.mock("matrix-js-sdk/src/matrix");
jest.useFakeTimers();
describe('Registration', function() {
describe("Registration", function () {
const registerRequest = jest.fn();
const mockClient = mocked({
registerRequest,
loginFlows: jest.fn(),
} as unknown as MatrixClient);
beforeEach(function() {
beforeEach(function () {
SdkConfig.put({
...DEFAULTS,
disable_custom_urls: true,
});
mockClient.registerRequest.mockRejectedValueOnce(new MatrixError({
flows: [{ stages: [] }],
}, 401));
mockClient.registerRequest.mockRejectedValueOnce(
new MatrixError(
{
flows: [{ stages: [] }],
},
401,
),
);
mockClient.loginFlows.mockClear().mockResolvedValue({ flows: [{ type: "m.login.password" }] });
mocked(createClient).mockImplementation(opts => {
mocked(createClient).mockImplementation((opts) => {
mockClient.idBaseUrl = opts.idBaseUrl;
mockClient.baseUrl = opts.baseUrl;
return mockClient;
@@ -59,14 +64,14 @@ describe('Registration', function() {
});
});
afterEach(function() {
afterEach(function () {
fetchMock.restore();
SdkConfig.unset(); // we touch the config, so clean up
unmockPlatformPeg();
});
const defaultProps = {
defaultDeviceDisplayName: 'test-device-display-name',
defaultDeviceDisplayName: "test-device-display-name",
makeRegistrationUrl: jest.fn(),
onLoggedIn: jest.fn(),
onLoginClick: jest.fn(),
@@ -74,22 +79,19 @@ describe('Registration', function() {
};
function getRawComponent(hsUrl = "https://matrix.org", isUrl = "https://vector.im") {
return <Registration
{...defaultProps}
serverConfig={mkServerConfig(hsUrl, isUrl)}
/>;
return <Registration {...defaultProps} serverConfig={mkServerConfig(hsUrl, isUrl)} />;
}
function getComponent(hsUrl?: string, isUrl?: string) {
return render(getRawComponent(hsUrl, isUrl));
}
it('should show server picker', async function() {
it("should show server picker", async function () {
const { container } = getComponent();
expect(container.querySelector(".mx_ServerPicker")).toBeTruthy();
});
it('should show form when custom URLs disabled', async function() {
it("should show form when custom URLs disabled", async function () {
const { container } = getComponent();
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading..."));
expect(container.querySelector("form")).toBeTruthy();
@@ -106,9 +108,11 @@ describe('Registration', function() {
it("should handle serverConfig updates correctly", async () => {
mockClient.loginFlows.mockResolvedValue({
flows: [{
"type": "m.login.sso",
}],
flows: [
{
type: "m.login.sso",
},
],
});
const { container, rerender } = render(getRawComponent());

View File

@@ -21,11 +21,15 @@ describe("Validation", () => {
const handler = withValidation({
rules: [],
});
return expect(handler({
value: "value",
focused: true,
})).resolves.toEqual(expect.objectContaining({
valid: true,
}));
return expect(
handler({
value: "value",
focused: true,
}),
).resolves.toEqual(
expect.objectContaining({
valid: true,
}),
);
});
});

View File

@@ -14,28 +14,28 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
// eslint-disable-next-line deprecate/import
import { mount } from 'enzyme';
import { mocked } from 'jest-mock';
import { logger } from 'matrix-js-sdk/src/logger';
import { act } from 'react-dom/test-utils';
import { mount } from "enzyme";
import { mocked } from "jest-mock";
import { logger } from "matrix-js-sdk/src/logger";
import { act } from "react-dom/test-utils";
import RecordingPlayback, { PlaybackLayout } from '../../../../src/components/views/audio_messages/RecordingPlayback';
import { Playback } from '../../../../src/audio/Playback';
import RoomContext, { TimelineRenderingType } from '../../../../src/contexts/RoomContext';
import { createAudioContext } from '../../../../src/audio/compat';
import { findByTestId, flushPromises } from '../../../test-utils';
import PlaybackWaveform from '../../../../src/components/views/audio_messages/PlaybackWaveform';
import RecordingPlayback, { PlaybackLayout } from "../../../../src/components/views/audio_messages/RecordingPlayback";
import { Playback } from "../../../../src/audio/Playback";
import RoomContext, { TimelineRenderingType } from "../../../../src/contexts/RoomContext";
import { createAudioContext } from "../../../../src/audio/compat";
import { findByTestId, flushPromises } from "../../../test-utils";
import PlaybackWaveform from "../../../../src/components/views/audio_messages/PlaybackWaveform";
import SeekBar from "../../../../src/components/views/audio_messages/SeekBar";
import PlaybackClock from "../../../../src/components/views/audio_messages/PlaybackClock";
jest.mock('../../../../src/audio/compat', () => ({
jest.mock("../../../../src/audio/compat", () => ({
createAudioContext: jest.fn(),
decodeOgg: jest.fn().mockResolvedValue({}),
}));
describe('<RecordingPlayback />', () => {
describe("<RecordingPlayback />", () => {
const mockAudioBufferSourceNode = {
addEventListener: jest.fn(),
connect: jest.fn(),
@@ -57,7 +57,7 @@ describe('<RecordingPlayback />', () => {
const mockChannelData = new Float32Array();
const defaultRoom = { roomId: '!room:server.org', timelineRenderingType: TimelineRenderingType.File };
const defaultRoom = { roomId: "!room:server.org", timelineRenderingType: TimelineRenderingType.File };
const getComponent = (props: React.ComponentProps<typeof RecordingPlayback>, room = defaultRoom) =>
mount(<RecordingPlayback {...props} />, {
wrappingComponent: RoomContext.Provider,
@@ -65,29 +65,27 @@ describe('<RecordingPlayback />', () => {
});
beforeEach(() => {
jest.spyOn(logger, 'error').mockRestore();
jest.spyOn(logger, "error").mockRestore();
mockAudioBuffer.getChannelData.mockClear().mockReturnValue(mockChannelData);
mockAudioContext.decodeAudioData.mockReset().mockImplementation(
(_b, callback) => callback(mockAudioBuffer),
);
mockAudioContext.decodeAudioData.mockReset().mockImplementation((_b, callback) => callback(mockAudioBuffer));
mocked(createAudioContext).mockReturnValue(mockAudioContext as unknown as AudioContext);
});
const getPlayButton = component => findByTestId(component, 'play-pause-button').at(0);
const getPlayButton = (component) => findByTestId(component, "play-pause-button").at(0);
it('renders recording playback', () => {
it("renders recording playback", () => {
const playback = new Playback(new ArrayBuffer(8));
const component = getComponent({ playback });
expect(component).toBeTruthy();
});
it('disables play button while playback is decoding', async () => {
it("disables play button while playback is decoding", async () => {
const playback = new Playback(new ArrayBuffer(8));
const component = getComponent({ playback });
expect(getPlayButton(component).props().disabled).toBeTruthy();
});
it('enables play button when playback is finished decoding', async () => {
it("enables play button when playback is finished decoding", async () => {
const playback = new Playback(new ArrayBuffer(8));
const component = getComponent({ playback });
await flushPromises();
@@ -95,43 +93,41 @@ describe('<RecordingPlayback />', () => {
expect(getPlayButton(component).props().disabled).toBeFalsy();
});
it('displays error when playback decoding fails', async () => {
it("displays error when playback decoding fails", async () => {
// stub logger to keep console clean from expected error
jest.spyOn(logger, 'error').mockReturnValue(undefined);
jest.spyOn(logger, 'warn').mockReturnValue(undefined);
mockAudioContext.decodeAudioData.mockImplementation(
(_b, _cb, error) => error(new Error('oh no')),
);
jest.spyOn(logger, "error").mockReturnValue(undefined);
jest.spyOn(logger, "warn").mockReturnValue(undefined);
mockAudioContext.decodeAudioData.mockImplementation((_b, _cb, error) => error(new Error("oh no")));
const playback = new Playback(new ArrayBuffer(8));
const component = getComponent({ playback });
await flushPromises();
expect(component.find('.text-warning').length).toBeFalsy();
expect(component.find(".text-warning").length).toBeFalsy();
});
it('displays pre-prepared playback with correct playback phase', async () => {
it("displays pre-prepared playback with correct playback phase", async () => {
const playback = new Playback(new ArrayBuffer(8));
await playback.prepare();
const component = getComponent({ playback });
// playback already decoded, button is not disabled
expect(getPlayButton(component).props().disabled).toBeFalsy();
expect(component.find('.text-warning').length).toBeFalsy();
expect(component.find(".text-warning").length).toBeFalsy();
});
it('toggles playback on play pause button click', async () => {
it("toggles playback on play pause button click", async () => {
const playback = new Playback(new ArrayBuffer(8));
jest.spyOn(playback, 'toggle').mockResolvedValue(undefined);
jest.spyOn(playback, "toggle").mockResolvedValue(undefined);
await playback.prepare();
const component = getComponent({ playback });
act(() => {
getPlayButton(component).simulate('click');
getPlayButton(component).simulate("click");
});
expect(playback.toggle).toHaveBeenCalled();
});
describe('Composer Layout', () => {
it('should have a waveform, no seek bar, and clock', () => {
describe("Composer Layout", () => {
it("should have a waveform, no seek bar, and clock", () => {
const playback = new Playback(new ArrayBuffer(8));
const component = getComponent({ playback, layout: PlaybackLayout.Composer });
@@ -141,8 +137,8 @@ describe('<RecordingPlayback />', () => {
});
});
describe('Timeline Layout', () => {
it('should have a waveform, a seek bar, and clock', () => {
describe("Timeline Layout", () => {
it("should have a waveform, a seek bar, and clock", () => {
const playback = new Playback(new ArrayBuffer(8));
const component = getComponent({ playback, layout: PlaybackLayout.Timeline });
@@ -151,7 +147,7 @@ describe('<RecordingPlayback />', () => {
expect(component.find(SeekBar).length).toBeTruthy();
});
it('should be the default', () => {
it("should be the default", () => {
const playback = new Playback(new ArrayBuffer(8));
const component = getComponent({ playback }); // no layout set for test

View File

@@ -30,9 +30,10 @@ describe("SeekBar", () => {
beforeEach(() => {
seekBarRef = createRef();
jest.spyOn(window, "requestAnimationFrame").mockImplementation(
(callback: FrameRequestCallback) => { frameRequestCallback = callback; return 0; },
);
jest.spyOn(window, "requestAnimationFrame").mockImplementation((callback: FrameRequestCallback) => {
frameRequestCallback = callback;
return 0;
});
playback = createTestPlayback();
});

View File

@@ -36,14 +36,11 @@ describe("MemberAvatar", () => {
let member: RoomMember;
function getComponent(props) {
return <RoomContext.Provider value={getRoomContext(room, {})}>
<MemberAvatar
member={null}
width={35}
height={35}
{...props}
/>
</RoomContext.Provider>;
return (
<RoomContext.Provider value={getRoomContext(room, {})}>
<MemberAvatar member={null} width={35} height={35} {...props} />
</RoomContext.Provider>
);
}
beforeEach(() => {

View File

@@ -14,32 +14,28 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { fireEvent, render } from "@testing-library/react";
import {
Beacon,
RoomMember,
MatrixEvent,
} from 'matrix-js-sdk/src/matrix';
import { LocationAssetType } from 'matrix-js-sdk/src/@types/location';
import { act } from 'react-dom/test-utils';
import { Beacon, RoomMember, MatrixEvent } from "matrix-js-sdk/src/matrix";
import { LocationAssetType } from "matrix-js-sdk/src/@types/location";
import { act } from "react-dom/test-utils";
import BeaconListItem from '../../../../src/components/views/beacon/BeaconListItem';
import MatrixClientContext from '../../../../src/contexts/MatrixClientContext';
import BeaconListItem from "../../../../src/components/views/beacon/BeaconListItem";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
import {
getMockClientWithEventEmitter,
makeBeaconEvent,
makeBeaconInfoEvent,
makeRoomWithBeacons,
} from '../../../test-utils';
} from "../../../test-utils";
describe('<BeaconListItem />', () => {
describe("<BeaconListItem />", () => {
// 14.03.2022 16:15
const now = 1647270879403;
// go back in time to create beacons and locations in the past
jest.spyOn(global.Date, 'now').mockReturnValue(now - 600000);
const roomId = '!room:server';
const aliceId = '@alice:server';
jest.spyOn(global.Date, "now").mockReturnValue(now - 600000);
const roomId = "!room:server";
const aliceId = "@alice:server";
const mockClient = getMockClientWithEventEmitter({
getUserId: jest.fn().mockReturnValue(aliceId),
@@ -47,36 +43,41 @@ describe('<BeaconListItem />', () => {
isGuest: jest.fn().mockReturnValue(false),
});
const aliceBeaconEvent = makeBeaconInfoEvent(aliceId,
roomId,
{ isLive: true },
'$alice-room1-1',
);
const alicePinBeaconEvent = makeBeaconInfoEvent(aliceId,
const aliceBeaconEvent = makeBeaconInfoEvent(aliceId, roomId, { isLive: true }, "$alice-room1-1");
const alicePinBeaconEvent = makeBeaconInfoEvent(
aliceId,
roomId,
{ isLive: true, assetType: LocationAssetType.Pin, description: "Alice's car" },
'$alice-room1-1',
"$alice-room1-1",
);
const pinBeaconWithoutDescription = makeBeaconInfoEvent(aliceId,
const pinBeaconWithoutDescription = makeBeaconInfoEvent(
aliceId,
roomId,
{ isLive: true, assetType: LocationAssetType.Pin },
'$alice-room1-1',
"$alice-room1-1",
);
const aliceLocation1 = makeBeaconEvent(
aliceId, { beaconInfoId: aliceBeaconEvent.getId(), geoUri: 'geo:51,41', timestamp: now - 1 },
);
const aliceLocation2 = makeBeaconEvent(
aliceId, { beaconInfoId: aliceBeaconEvent.getId(), geoUri: 'geo:52,42', timestamp: now - 500000 },
);
const aliceLocation1 = makeBeaconEvent(aliceId, {
beaconInfoId: aliceBeaconEvent.getId(),
geoUri: "geo:51,41",
timestamp: now - 1,
});
const aliceLocation2 = makeBeaconEvent(aliceId, {
beaconInfoId: aliceBeaconEvent.getId(),
geoUri: "geo:52,42",
timestamp: now - 500000,
});
const defaultProps = {
beacon: new Beacon(aliceBeaconEvent),
};
const getComponent = (props = {}) => render(<MatrixClientContext.Provider value={mockClient}>
<BeaconListItem {...defaultProps} {...props} />
</MatrixClientContext.Provider>);
const getComponent = (props = {}) =>
render(
<MatrixClientContext.Provider value={mockClient}>
<BeaconListItem {...defaultProps} {...props} />
</MatrixClientContext.Provider>,
);
const setupRoomWithBeacons = (beaconInfoEvents: MatrixEvent[], locationEvents?: MatrixEvent[]): Beacon[] => {
const beacons = makeRoomWithBeacons(roomId, mockClient, beaconInfoEvents, locationEvents);
@@ -84,102 +85,101 @@ describe('<BeaconListItem />', () => {
const member = new RoomMember(roomId, aliceId);
member.name = `Alice`;
const room = mockClient.getRoom(roomId);
jest.spyOn(room, 'getMember').mockReturnValue(member);
jest.spyOn(room, "getMember").mockReturnValue(member);
return beacons;
};
beforeEach(() => {
jest.clearAllMocks();
jest.spyOn(Date, 'now').mockReturnValue(now);
jest.spyOn(Date, "now").mockReturnValue(now);
});
it('renders null when beacon is not live', () => {
const notLiveBeacon = makeBeaconInfoEvent(aliceId,
roomId,
{ isLive: false },
);
it("renders null when beacon is not live", () => {
const notLiveBeacon = makeBeaconInfoEvent(aliceId, roomId, { isLive: false });
const [beacon] = setupRoomWithBeacons([notLiveBeacon]);
const { container } = getComponent({ beacon });
expect(container.innerHTML).toBeFalsy();
});
it('renders null when beacon has no location', () => {
it("renders null when beacon has no location", () => {
const [beacon] = setupRoomWithBeacons([aliceBeaconEvent]);
const { container } = getComponent({ beacon });
expect(container.innerHTML).toBeFalsy();
});
describe('when a beacon is live and has locations', () => {
it('renders beacon info', () => {
describe("when a beacon is live and has locations", () => {
it("renders beacon info", () => {
const [beacon] = setupRoomWithBeacons([alicePinBeaconEvent], [aliceLocation1]);
const { asFragment } = getComponent({ beacon });
expect(asFragment()).toMatchSnapshot();
});
describe('non-self beacons', () => {
it('uses beacon description as beacon name', () => {
describe("non-self beacons", () => {
it("uses beacon description as beacon name", () => {
const [beacon] = setupRoomWithBeacons([alicePinBeaconEvent], [aliceLocation1]);
const { container } = getComponent({ beacon });
expect(container.querySelector('.mx_BeaconStatus_label')).toHaveTextContent("Alice's car");
expect(container.querySelector(".mx_BeaconStatus_label")).toHaveTextContent("Alice's car");
});
it('uses beacon owner mxid as beacon name for a beacon without description', () => {
it("uses beacon owner mxid as beacon name for a beacon without description", () => {
const [beacon] = setupRoomWithBeacons([pinBeaconWithoutDescription], [aliceLocation1]);
const { container } = getComponent({ beacon });
expect(container.querySelector('.mx_BeaconStatus_label')).toHaveTextContent(aliceId);
expect(container.querySelector(".mx_BeaconStatus_label")).toHaveTextContent(aliceId);
});
it('renders location icon', () => {
it("renders location icon", () => {
const [beacon] = setupRoomWithBeacons([alicePinBeaconEvent], [aliceLocation1]);
const { container } = getComponent({ beacon });
expect(container.querySelector('.mx_StyledLiveBeaconIcon')).toBeTruthy();
expect(container.querySelector(".mx_StyledLiveBeaconIcon")).toBeTruthy();
});
});
describe('self locations', () => {
it('renders beacon owner avatar', () => {
describe("self locations", () => {
it("renders beacon owner avatar", () => {
const [beacon] = setupRoomWithBeacons([aliceBeaconEvent], [aliceLocation1]);
const { container } = getComponent({ beacon });
expect(container.querySelector('.mx_BaseAvatar')).toBeTruthy();
expect(container.querySelector(".mx_BaseAvatar")).toBeTruthy();
});
it('uses beacon owner name as beacon name', () => {
it("uses beacon owner name as beacon name", () => {
const [beacon] = setupRoomWithBeacons([aliceBeaconEvent], [aliceLocation1]);
const { container } = getComponent({ beacon });
expect(container.querySelector('.mx_BeaconStatus_label')).toHaveTextContent("Alice");
expect(container.querySelector(".mx_BeaconStatus_label")).toHaveTextContent("Alice");
});
});
describe('on location updates', () => {
it('updates last updated time on location updated', () => {
describe("on location updates", () => {
it("updates last updated time on location updated", () => {
const [beacon] = setupRoomWithBeacons([aliceBeaconEvent], [aliceLocation2]);
const { container } = getComponent({ beacon });
expect(container.querySelector('.mx_BeaconListItem_lastUpdated'))
.toHaveTextContent('Updated 9 minutes ago');
expect(container.querySelector(".mx_BeaconListItem_lastUpdated")).toHaveTextContent(
"Updated 9 minutes ago",
);
// update to a newer location
act(() => {
beacon.addLocations([aliceLocation1]);
});
expect(container.querySelector('.mx_BeaconListItem_lastUpdated'))
.toHaveTextContent('Updated a few seconds ago');
expect(container.querySelector(".mx_BeaconListItem_lastUpdated")).toHaveTextContent(
"Updated a few seconds ago",
);
});
});
describe('interactions', () => {
it('does not call onClick handler when clicking share button', () => {
describe("interactions", () => {
it("does not call onClick handler when clicking share button", () => {
const [beacon] = setupRoomWithBeacons([alicePinBeaconEvent], [aliceLocation1]);
const onClick = jest.fn();
const { getByTestId } = getComponent({ beacon, onClick });
fireEvent.click(getByTestId('open-location-in-osm'));
fireEvent.click(getByTestId("open-location-in-osm"));
expect(onClick).not.toHaveBeenCalled();
});
it('calls onClick handler when clicking outside of share buttons', () => {
it("calls onClick handler when clicking outside of share buttons", () => {
const [beacon] = setupRoomWithBeacons([alicePinBeaconEvent], [aliceLocation1]);
const onClick = jest.fn();
const { container } = getComponent({ beacon, onClick });

View File

@@ -14,36 +14,30 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
// eslint-disable-next-line deprecate/import
import { mount } from 'enzyme';
import maplibregl from 'maplibre-gl';
import { act } from 'react-dom/test-utils';
import {
Beacon,
Room,
RoomMember,
MatrixEvent,
getBeaconInfoIdentifier,
} from 'matrix-js-sdk/src/matrix';
import { mount } from "enzyme";
import maplibregl from "maplibre-gl";
import { act } from "react-dom/test-utils";
import { Beacon, Room, RoomMember, MatrixEvent, getBeaconInfoIdentifier } from "matrix-js-sdk/src/matrix";
import BeaconMarker from '../../../../src/components/views/beacon/BeaconMarker';
import MatrixClientContext from '../../../../src/contexts/MatrixClientContext';
import BeaconMarker from "../../../../src/components/views/beacon/BeaconMarker";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
import {
getMockClientWithEventEmitter,
makeBeaconEvent,
makeBeaconInfoEvent,
makeRoomWithStateEvents,
} from '../../../test-utils';
import { TILE_SERVER_WK_KEY } from '../../../../src/utils/WellKnownUtils';
} from "../../../test-utils";
import { TILE_SERVER_WK_KEY } from "../../../../src/utils/WellKnownUtils";
describe('<BeaconMarker />', () => {
describe("<BeaconMarker />", () => {
// 14.03.2022 16:15
const now = 1647270879403;
// stable date for snapshots
jest.spyOn(global.Date, 'now').mockReturnValue(now);
const roomId = '!room:server';
const aliceId = '@alice:server';
jest.spyOn(global.Date, "now").mockReturnValue(now);
const roomId = "!room:server";
const aliceId = "@alice:server";
const aliceMember = new RoomMember(roomId, aliceId);
@@ -51,7 +45,7 @@ describe('<BeaconMarker />', () => {
const mockClient = getMockClientWithEventEmitter({
getClientWellKnown: jest.fn().mockReturnValue({
[TILE_SERVER_WK_KEY.name]: { map_style_url: 'maps.com' },
[TILE_SERVER_WK_KEY.name]: { map_style_url: "maps.com" },
}),
getUserId: jest.fn().mockReturnValue(aliceId),
getRoom: jest.fn(),
@@ -62,27 +56,23 @@ describe('<BeaconMarker />', () => {
// as we update room state
const setupRoom = (stateEvents: MatrixEvent[] = []): Room => {
const room1 = makeRoomWithStateEvents(stateEvents, { roomId, mockClient });
jest.spyOn(room1, 'getMember').mockReturnValue(aliceMember);
jest.spyOn(room1, "getMember").mockReturnValue(aliceMember);
return room1;
};
const defaultEvent = makeBeaconInfoEvent(aliceId,
roomId,
{ isLive: true },
'$alice-room1-1',
);
const notLiveEvent = makeBeaconInfoEvent(aliceId,
roomId,
{ isLive: false },
'$alice-room1-2',
);
const defaultEvent = makeBeaconInfoEvent(aliceId, roomId, { isLive: true }, "$alice-room1-1");
const notLiveEvent = makeBeaconInfoEvent(aliceId, roomId, { isLive: false }, "$alice-room1-2");
const location1 = makeBeaconEvent(
aliceId, { beaconInfoId: defaultEvent.getId(), geoUri: 'geo:51,41', timestamp: now + 1 },
);
const location2 = makeBeaconEvent(
aliceId, { beaconInfoId: defaultEvent.getId(), geoUri: 'geo:52,42', timestamp: now + 10000 },
);
const location1 = makeBeaconEvent(aliceId, {
beaconInfoId: defaultEvent.getId(),
geoUri: "geo:51,41",
timestamp: now + 1,
});
const location2 = makeBeaconEvent(aliceId, {
beaconInfoId: defaultEvent.getId(),
geoUri: "geo:52,42",
timestamp: now + 10000,
});
const defaultProps = {
map: mockMap,
@@ -99,21 +89,21 @@ describe('<BeaconMarker />', () => {
jest.clearAllMocks();
});
it('renders nothing when beacon is not live', () => {
it("renders nothing when beacon is not live", () => {
const room = setupRoom([notLiveEvent]);
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(notLiveEvent));
const component = getComponent({ beacon });
expect(component.html()).toBe(null);
});
it('renders nothing when beacon has no location', () => {
it("renders nothing when beacon has no location", () => {
const room = setupRoom([defaultEvent]);
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent));
const component = getComponent({ beacon });
expect(component.html()).toBe(null);
});
it('renders marker when beacon has location', () => {
it("renders marker when beacon has location", () => {
const room = setupRoom([defaultEvent]);
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent));
beacon.addLocations([location1]);
@@ -121,12 +111,12 @@ describe('<BeaconMarker />', () => {
expect(component).toMatchSnapshot();
});
it('updates with new locations', () => {
it("updates with new locations", () => {
const room = setupRoom([defaultEvent]);
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent));
beacon.addLocations([location1]);
const component = getComponent({ beacon });
expect(component.find('SmartMarker').props()['geoUri']).toEqual('geo:51,41');
expect(component.find("SmartMarker").props()["geoUri"]).toEqual("geo:51,41");
act(() => {
beacon.addLocations([location2]);
@@ -134,6 +124,6 @@ describe('<BeaconMarker />', () => {
component.setProps({});
// updated to latest location
expect(component.find('SmartMarker').props()['geoUri']).toEqual('geo:52,42');
expect(component.find("SmartMarker").props()["geoUri"]).toEqual("geo:52,42");
});
});

View File

@@ -14,75 +14,75 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
// eslint-disable-next-line deprecate/import
import { mount } from 'enzyme';
import { Beacon } from 'matrix-js-sdk/src/matrix';
import { mount } from "enzyme";
import { Beacon } from "matrix-js-sdk/src/matrix";
import BeaconStatus from '../../../../src/components/views/beacon/BeaconStatus';
import { BeaconDisplayStatus } from '../../../../src/components/views/beacon/displayStatus';
import { findByTestId, makeBeaconInfoEvent } from '../../../test-utils';
import BeaconStatus from "../../../../src/components/views/beacon/BeaconStatus";
import { BeaconDisplayStatus } from "../../../../src/components/views/beacon/displayStatus";
import { findByTestId, makeBeaconInfoEvent } from "../../../test-utils";
describe('<BeaconStatus />', () => {
describe("<BeaconStatus />", () => {
const defaultProps = {
displayStatus: BeaconDisplayStatus.Loading,
label: 'test label',
label: "test label",
withIcon: true,
};
const getComponent = (props = {}) =>
mount(<BeaconStatus {...defaultProps} {...props} />);
const getComponent = (props = {}) => mount(<BeaconStatus {...defaultProps} {...props} />);
it('renders loading state', () => {
it("renders loading state", () => {
const component = getComponent({ displayStatus: BeaconDisplayStatus.Loading });
expect(component).toMatchSnapshot();
});
it('renders stopped state', () => {
it("renders stopped state", () => {
const component = getComponent({ displayStatus: BeaconDisplayStatus.Stopped });
expect(component).toMatchSnapshot();
});
it('renders without icon', () => {
it("renders without icon", () => {
const component = getComponent({ withIcon: false, displayStatus: BeaconDisplayStatus.Stopped });
expect(component.find('StyledLiveBeaconIcon').length).toBeFalsy();
expect(component.find("StyledLiveBeaconIcon").length).toBeFalsy();
});
describe('active state', () => {
it('renders without children', () => {
describe("active state", () => {
it("renders without children", () => {
// mock for stable snapshot
jest.spyOn(Date, 'now').mockReturnValue(123456789);
const beacon = new Beacon(makeBeaconInfoEvent('@user:server', '!room:server', { isLive: false }, '$1'));
jest.spyOn(Date, "now").mockReturnValue(123456789);
const beacon = new Beacon(makeBeaconInfoEvent("@user:server", "!room:server", { isLive: false }, "$1"));
const component = getComponent({ beacon, displayStatus: BeaconDisplayStatus.Active });
expect(component).toMatchSnapshot();
});
it('renders with children', () => {
const beacon = new Beacon(makeBeaconInfoEvent('@user:server', '!room:sever', { isLive: false }));
it("renders with children", () => {
const beacon = new Beacon(makeBeaconInfoEvent("@user:server", "!room:sever", { isLive: false }));
const component = getComponent({
beacon,
children: <span data-test-id='test'>test</span>,
children: <span data-test-id="test">test</span>,
displayStatus: BeaconDisplayStatus.Active,
});
expect(findByTestId(component, 'test-child')).toMatchSnapshot();
expect(findByTestId(component, "test-child")).toMatchSnapshot();
});
it('renders static remaining time when displayLiveTimeRemaining is falsy', () => {
it("renders static remaining time when displayLiveTimeRemaining is falsy", () => {
// mock for stable snapshot
jest.spyOn(Date, 'now').mockReturnValue(123456789);
const beacon = new Beacon(makeBeaconInfoEvent('@user:server', '!room:server', { isLive: false }, '$1'));
jest.spyOn(Date, "now").mockReturnValue(123456789);
const beacon = new Beacon(makeBeaconInfoEvent("@user:server", "!room:server", { isLive: false }, "$1"));
const component = getComponent({ beacon, displayStatus: BeaconDisplayStatus.Active });
expect(component.text().includes('Live until 11:17')).toBeTruthy();
expect(component.text().includes("Live until 11:17")).toBeTruthy();
});
it('renders live time remaining when displayLiveTimeRemaining is truthy', () => {
it("renders live time remaining when displayLiveTimeRemaining is truthy", () => {
// mock for stable snapshot
jest.spyOn(Date, 'now').mockReturnValue(123456789);
const beacon = new Beacon(makeBeaconInfoEvent('@user:server', '!room:server', { isLive: false }, '$1'));
jest.spyOn(Date, "now").mockReturnValue(123456789);
const beacon = new Beacon(makeBeaconInfoEvent("@user:server", "!room:server", { isLive: false }, "$1"));
const component = getComponent({
beacon, displayStatus: BeaconDisplayStatus.Active,
beacon,
displayStatus: BeaconDisplayStatus.Active,
displayLiveTimeRemaining: true,
});
expect(component.text().includes('1h left')).toBeTruthy();
expect(component.text().includes("1h left")).toBeTruthy();
});
});
});

View File

@@ -14,21 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
// eslint-disable-next-line deprecate/import
import { mount, ReactWrapper } from 'enzyme';
import { act } from 'react-dom/test-utils';
import {
MatrixClient,
MatrixEvent,
Room,
RoomMember,
getBeaconInfoIdentifier,
} from 'matrix-js-sdk/src/matrix';
import maplibregl from 'maplibre-gl';
import { mocked } from 'jest-mock';
import { mount, ReactWrapper } from "enzyme";
import { act } from "react-dom/test-utils";
import { MatrixClient, MatrixEvent, Room, RoomMember, getBeaconInfoIdentifier } from "matrix-js-sdk/src/matrix";
import maplibregl from "maplibre-gl";
import { mocked } from "jest-mock";
import BeaconViewDialog from '../../../../src/components/views/beacon/BeaconViewDialog';
import BeaconViewDialog from "../../../../src/components/views/beacon/BeaconViewDialog";
import {
findByAttr,
findByTestId,
@@ -37,26 +31,26 @@ import {
makeBeaconInfoEvent,
makeRoomWithBeacons,
makeRoomWithStateEvents,
} from '../../../test-utils';
import { TILE_SERVER_WK_KEY } from '../../../../src/utils/WellKnownUtils';
import { OwnBeaconStore } from '../../../../src/stores/OwnBeaconStore';
import { BeaconDisplayStatus } from '../../../../src/components/views/beacon/displayStatus';
import BeaconListItem from '../../../../src/components/views/beacon/BeaconListItem';
} from "../../../test-utils";
import { TILE_SERVER_WK_KEY } from "../../../../src/utils/WellKnownUtils";
import { OwnBeaconStore } from "../../../../src/stores/OwnBeaconStore";
import { BeaconDisplayStatus } from "../../../../src/components/views/beacon/displayStatus";
import BeaconListItem from "../../../../src/components/views/beacon/BeaconListItem";
describe('<BeaconViewDialog />', () => {
describe("<BeaconViewDialog />", () => {
// 14.03.2022 16:15
const now = 1647270879403;
// stable date for snapshots
jest.spyOn(global.Date, 'now').mockReturnValue(now);
const roomId = '!room:server';
const aliceId = '@alice:server';
const bobId = '@bob:server';
jest.spyOn(global.Date, "now").mockReturnValue(now);
const roomId = "!room:server";
const aliceId = "@alice:server";
const bobId = "@bob:server";
const aliceMember = new RoomMember(roomId, aliceId);
const mockClient = getMockClientWithEventEmitter({
getClientWellKnown: jest.fn().mockReturnValue({
[TILE_SERVER_WK_KEY.name]: { map_style_url: 'maps.com' },
[TILE_SERVER_WK_KEY.name]: { map_style_url: "maps.com" },
}),
getUserId: jest.fn().mockReturnValue(bobId),
getRoom: jest.fn(),
@@ -70,20 +64,18 @@ describe('<BeaconViewDialog />', () => {
// as we update room state
const setupRoom = (stateEvents: MatrixEvent[] = []): Room => {
const room1 = makeRoomWithStateEvents(stateEvents, { roomId, mockClient });
jest.spyOn(room1, 'getMember').mockReturnValue(aliceMember);
jest.spyOn(room1, "getMember").mockReturnValue(aliceMember);
return room1;
};
const defaultEvent = makeBeaconInfoEvent(aliceId,
roomId,
{ isLive: true },
'$alice-room1-1',
);
const defaultEvent = makeBeaconInfoEvent(aliceId, roomId, { isLive: true }, "$alice-room1-1");
const location1 = makeBeaconEvent(
aliceId, { beaconInfoId: defaultEvent.getId(), geoUri: 'geo:51,41', timestamp: now + 1 },
);
const location1 = makeBeaconEvent(aliceId, {
beaconInfoId: defaultEvent.getId(),
geoUri: "geo:51,41",
timestamp: now + 1,
});
const defaultProps = {
onFinished: jest.fn(),
@@ -91,74 +83,73 @@ describe('<BeaconViewDialog />', () => {
matrixClient: mockClient as MatrixClient,
};
const getComponent = (props = {}) =>
mount(<BeaconViewDialog {...defaultProps} {...props} />);
const getComponent = (props = {}) => mount(<BeaconViewDialog {...defaultProps} {...props} />);
const openSidebar = (component: ReactWrapper) => act(() => {
findByTestId(component, 'beacon-view-dialog-open-sidebar').at(0).simulate('click');
component.setProps({});
});
const openSidebar = (component: ReactWrapper) =>
act(() => {
findByTestId(component, "beacon-view-dialog-open-sidebar").at(0).simulate("click");
component.setProps({});
});
beforeAll(() => {
maplibregl.AttributionControl = jest.fn();
});
beforeEach(() => {
jest.spyOn(OwnBeaconStore.instance, 'getLiveBeaconIds').mockRestore();
jest.spyOn(OwnBeaconStore.instance, 'getBeaconById').mockRestore();
jest.spyOn(global.Date, 'now').mockReturnValue(now);
jest.spyOn(OwnBeaconStore.instance, "getLiveBeaconIds").mockRestore();
jest.spyOn(OwnBeaconStore.instance, "getBeaconById").mockRestore();
jest.spyOn(global.Date, "now").mockReturnValue(now);
jest.clearAllMocks();
});
it('renders a map with markers', () => {
it("renders a map with markers", () => {
const room = setupRoom([defaultEvent]);
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent));
beacon.addLocations([location1]);
const component = getComponent();
expect(component.find('Map').props()).toEqual(expect.objectContaining({
centerGeoUri: 'geo:51,41',
interactive: true,
}));
expect(component.find('SmartMarker').length).toEqual(1);
expect(component.find("Map").props()).toEqual(
expect.objectContaining({
centerGeoUri: "geo:51,41",
interactive: true,
}),
);
expect(component.find("SmartMarker").length).toEqual(1);
});
it('does not render any own beacon status when user is not live sharing', () => {
it("does not render any own beacon status when user is not live sharing", () => {
// default event belongs to alice, we are bob
const room = setupRoom([defaultEvent]);
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent));
beacon.addLocations([location1]);
const component = getComponent();
expect(component.find('DialogOwnBeaconStatus').html()).toBeNull();
expect(component.find("DialogOwnBeaconStatus").html()).toBeNull();
});
it('renders own beacon status when user is live sharing', () => {
it("renders own beacon status when user is live sharing", () => {
// default event belongs to alice
const room = setupRoom([defaultEvent]);
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent));
beacon.addLocations([location1]);
// mock own beacon store to show default event as alice's live beacon
jest.spyOn(OwnBeaconStore.instance, 'getLiveBeaconIds').mockReturnValue([beacon.identifier]);
jest.spyOn(OwnBeaconStore.instance, 'getBeaconById').mockReturnValue(beacon);
jest.spyOn(OwnBeaconStore.instance, "getLiveBeaconIds").mockReturnValue([beacon.identifier]);
jest.spyOn(OwnBeaconStore.instance, "getBeaconById").mockReturnValue(beacon);
const component = getComponent();
expect(component.find('MemberAvatar').length).toBeTruthy();
expect(component.find('OwnBeaconStatus').props()).toEqual({
beacon, displayStatus: BeaconDisplayStatus.Active,
className: 'mx_DialogOwnBeaconStatus_status',
expect(component.find("MemberAvatar").length).toBeTruthy();
expect(component.find("OwnBeaconStatus").props()).toEqual({
beacon,
displayStatus: BeaconDisplayStatus.Active,
className: "mx_DialogOwnBeaconStatus_status",
});
});
it('updates markers on changes to beacons', () => {
it("updates markers on changes to beacons", () => {
const room = setupRoom([defaultEvent]);
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent));
beacon.addLocations([location1]);
const component = getComponent();
expect(component.find('BeaconMarker').length).toEqual(1);
expect(component.find("BeaconMarker").length).toEqual(1);
const anotherBeaconEvent = makeBeaconInfoEvent(bobId,
roomId,
{ isLive: true },
'$bob-room1-1',
);
const anotherBeaconEvent = makeBeaconInfoEvent(bobId, roomId, { isLive: true }, "$bob-room1-1");
act(() => {
// emits RoomStateEvent.BeaconLiveness
@@ -168,21 +159,17 @@ describe('<BeaconViewDialog />', () => {
component.setProps({});
// two markers now!
expect(component.find('BeaconMarker').length).toEqual(2);
expect(component.find("BeaconMarker").length).toEqual(2);
});
it('does not update bounds or center on changing beacons', () => {
it("does not update bounds or center on changing beacons", () => {
const room = setupRoom([defaultEvent]);
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent));
beacon.addLocations([location1]);
const component = getComponent();
expect(component.find('BeaconMarker').length).toEqual(1);
expect(component.find("BeaconMarker").length).toEqual(1);
const anotherBeaconEvent = makeBeaconInfoEvent(bobId,
roomId,
{ isLive: true },
'$bob-room1-1',
);
const anotherBeaconEvent = makeBeaconInfoEvent(bobId, roomId, { isLive: true }, "$bob-room1-1");
act(() => {
// emits RoomStateEvent.BeaconLiveness
@@ -196,7 +183,7 @@ describe('<BeaconViewDialog />', () => {
expect(mockMap.fitBounds).toHaveBeenCalledTimes(1);
});
it('renders a fallback when there are no locations', () => {
it("renders a fallback when there are no locations", () => {
// this is a cornercase, should not be a reachable state in UI anymore
const onFinished = jest.fn();
const room = setupRoom([defaultEvent]);
@@ -204,30 +191,26 @@ describe('<BeaconViewDialog />', () => {
const component = getComponent({ onFinished });
// map placeholder
expect(findByTestId(component, 'beacon-view-dialog-map-fallback')).toMatchSnapshot();
expect(findByTestId(component, "beacon-view-dialog-map-fallback")).toMatchSnapshot();
act(() => {
findByTestId(component, 'beacon-view-dialog-fallback-close').at(0).simulate('click');
findByTestId(component, "beacon-view-dialog-fallback-close").at(0).simulate("click");
});
expect(onFinished).toHaveBeenCalled();
});
it('renders map without markers when no live beacons remain', () => {
it("renders map without markers when no live beacons remain", () => {
const onFinished = jest.fn();
const room = setupRoom([defaultEvent]);
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent));
beacon.addLocations([location1]);
const component = getComponent({ onFinished });
expect(component.find('BeaconMarker').length).toEqual(1);
expect(component.find("BeaconMarker").length).toEqual(1);
// this will replace the defaultEvent
// leading to no more live beacons
const anotherBeaconEvent = makeBeaconInfoEvent(aliceId,
roomId,
{ isLive: false },
'$alice-room1-2',
);
const anotherBeaconEvent = makeBeaconInfoEvent(aliceId, roomId, { isLive: false }, "$alice-room1-2");
expect(mockMap.setCenter).toHaveBeenCalledWith({ lat: 51, lon: 41 });
// reset call counts
@@ -242,16 +225,16 @@ describe('<BeaconViewDialog />', () => {
component.setProps({});
// no more avatars
expect(component.find('MemberAvatar').length).toBeFalsy();
expect(component.find("MemberAvatar").length).toBeFalsy();
// map still rendered
expect(component.find('Map').length).toBeTruthy();
expect(component.find("Map").length).toBeTruthy();
// map location unchanged
expect(mockMap.setCenter).not.toHaveBeenCalled();
expect(mockMap.fitBounds).not.toHaveBeenCalled();
});
describe('sidebar', () => {
it('opens sidebar on view list button click', () => {
describe("sidebar", () => {
it("opens sidebar on view list button click", () => {
const room = setupRoom([defaultEvent]);
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent));
beacon.addLocations([location1]);
@@ -259,10 +242,10 @@ describe('<BeaconViewDialog />', () => {
openSidebar(component);
expect(component.find('DialogSidebar').length).toBeTruthy();
expect(component.find("DialogSidebar").length).toBeTruthy();
});
it('closes sidebar on close button click', () => {
it("closes sidebar on close button click", () => {
const room = setupRoom([defaultEvent]);
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent));
beacon.addLocations([location1]);
@@ -271,34 +254,35 @@ describe('<BeaconViewDialog />', () => {
// open the sidebar
openSidebar(component);
expect(component.find('DialogSidebar').length).toBeTruthy();
expect(component.find("DialogSidebar").length).toBeTruthy();
// now close it
act(() => {
findByAttr('data-testid')(component, 'dialog-sidebar-close').at(0).simulate('click');
findByAttr("data-testid")(component, "dialog-sidebar-close").at(0).simulate("click");
component.setProps({});
});
expect(component.find('DialogSidebar').length).toBeFalsy();
expect(component.find("DialogSidebar").length).toBeFalsy();
});
});
describe('focused beacons', () => {
const beacon2Event = makeBeaconInfoEvent(bobId,
roomId,
{ isLive: true },
'$bob-room1-2',
);
describe("focused beacons", () => {
const beacon2Event = makeBeaconInfoEvent(bobId, roomId, { isLive: true }, "$bob-room1-2");
const location2 = makeBeaconEvent(
bobId, { beaconInfoId: beacon2Event.getId(), geoUri: 'geo:33,22', timestamp: now + 1 },
);
const location2 = makeBeaconEvent(bobId, {
beaconInfoId: beacon2Event.getId(),
geoUri: "geo:33,22",
timestamp: now + 1,
});
const fitBoundsOptions = { maxZoom: 15, padding: 100 };
it('opens map with both beacons in view on first load without initialFocusedBeacon', () => {
it("opens map with both beacons in view on first load without initialFocusedBeacon", () => {
const [beacon1, beacon2] = makeRoomWithBeacons(
roomId, mockClient, [defaultEvent, beacon2Event], [location1, location2],
roomId,
mockClient,
[defaultEvent, beacon2Event],
[location1, location2],
);
getComponent({ beacons: [beacon1, beacon2] });
@@ -308,15 +292,19 @@ describe('<BeaconViewDialog />', () => {
// only called once
expect(mockMap.setCenter).toHaveBeenCalledTimes(1);
// bounds fit both beacons, only called once
expect(mockMap.fitBounds).toHaveBeenCalledWith(new maplibregl.LngLatBounds(
[22, 33], [41, 51],
), fitBoundsOptions);
expect(mockMap.fitBounds).toHaveBeenCalledWith(
new maplibregl.LngLatBounds([22, 33], [41, 51]),
fitBoundsOptions,
);
expect(mockMap.fitBounds).toHaveBeenCalledTimes(1);
});
it('opens map with both beacons in view on first load with an initially focused beacon', () => {
it("opens map with both beacons in view on first load with an initially focused beacon", () => {
const [beacon1, beacon2] = makeRoomWithBeacons(
roomId, mockClient, [defaultEvent, beacon2Event], [location1, location2],
roomId,
mockClient,
[defaultEvent, beacon2Event],
[location1, location2],
);
getComponent({ beacons: [beacon1, beacon2], initialFocusedBeacon: beacon1 });
@@ -326,15 +314,19 @@ describe('<BeaconViewDialog />', () => {
// only called once
expect(mockMap.setCenter).toHaveBeenCalledTimes(1);
// bounds fit both beacons, only called once
expect(mockMap.fitBounds).toHaveBeenCalledWith(new maplibregl.LngLatBounds(
[22, 33], [41, 51],
), fitBoundsOptions);
expect(mockMap.fitBounds).toHaveBeenCalledWith(
new maplibregl.LngLatBounds([22, 33], [41, 51]),
fitBoundsOptions,
);
expect(mockMap.fitBounds).toHaveBeenCalledTimes(1);
});
it('focuses on beacon location on sidebar list item click', () => {
it("focuses on beacon location on sidebar list item click", () => {
const [beacon1, beacon2] = makeRoomWithBeacons(
roomId, mockClient, [defaultEvent, beacon2Event], [location1, location2],
roomId,
mockClient,
[defaultEvent, beacon2Event],
[location1, location2],
);
const component = getComponent({ beacons: [beacon1, beacon2] });
@@ -346,7 +338,7 @@ describe('<BeaconViewDialog />', () => {
act(() => {
// click on the first beacon in the list
component.find(BeaconListItem).at(0).simulate('click');
component.find(BeaconListItem).at(0).simulate("click");
});
// centered on clicked beacon
@@ -354,16 +346,20 @@ describe('<BeaconViewDialog />', () => {
// only called once
expect(mockMap.setCenter).toHaveBeenCalledTimes(1);
// bounds fitted just to clicked beacon
expect(mockMap.fitBounds).toHaveBeenCalledWith(new maplibregl.LngLatBounds(
[41, 51], [41, 51],
), fitBoundsOptions);
expect(mockMap.fitBounds).toHaveBeenCalledWith(
new maplibregl.LngLatBounds([41, 51], [41, 51]),
fitBoundsOptions,
);
expect(mockMap.fitBounds).toHaveBeenCalledTimes(1);
});
it('refocuses on same beacon when clicking list item again', () => {
it("refocuses on same beacon when clicking list item again", () => {
// test the map responds to refocusing the same beacon
const [beacon1, beacon2] = makeRoomWithBeacons(
roomId, mockClient, [defaultEvent, beacon2Event], [location1, location2],
roomId,
mockClient,
[defaultEvent, beacon2Event],
[location1, location2],
);
const component = getComponent({ beacons: [beacon1, beacon2] });
@@ -375,19 +371,17 @@ describe('<BeaconViewDialog />', () => {
act(() => {
// click on the second beacon in the list
component.find(BeaconListItem).at(1).simulate('click');
component.find(BeaconListItem).at(1).simulate("click");
});
const expectedBounds = new maplibregl.LngLatBounds(
[22, 33], [22, 33],
);
const expectedBounds = new maplibregl.LngLatBounds([22, 33], [22, 33]);
// date is mocked but this relies on timestamp, manually mock a tick
jest.spyOn(global.Date, 'now').mockReturnValue(now + 1);
jest.spyOn(global.Date, "now").mockReturnValue(now + 1);
act(() => {
// click on the second beacon in the list
component.find(BeaconListItem).at(1).simulate('click');
component.find(BeaconListItem).at(1).simulate("click");
});
// centered on clicked beacon

View File

@@ -14,21 +14,21 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { fireEvent, render } from '@testing-library/react';
import { act } from 'react-dom/test-utils';
import React from "react";
import { fireEvent, render } from "@testing-library/react";
import { act } from "react-dom/test-utils";
import DialogSidebar from '../../../../src/components/views/beacon/DialogSidebar';
import MatrixClientContext from '../../../../src/contexts/MatrixClientContext';
import DialogSidebar from "../../../../src/components/views/beacon/DialogSidebar";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
import {
getMockClientWithEventEmitter,
makeBeaconEvent,
makeBeaconInfoEvent,
makeRoomWithBeacons,
mockClientMethodsUser,
} from '../../../test-utils';
} from "../../../test-utils";
describe('<DialogSidebar />', () => {
describe("<DialogSidebar />", () => {
const defaultProps = {
beacons: [],
requestClose: jest.fn(),
@@ -37,66 +37,66 @@ describe('<DialogSidebar />', () => {
const now = 1647270879403;
const roomId = '!room:server.org';
const aliceId = '@alice:server.org';
const roomId = "!room:server.org";
const aliceId = "@alice:server.org";
const client = getMockClientWithEventEmitter({
...mockClientMethodsUser(aliceId),
getRoom: jest.fn(),
});
const beaconEvent = makeBeaconInfoEvent(aliceId,
roomId,
{ isLive: true, timestamp: now },
'$alice-room1-1',
);
const location1 = makeBeaconEvent(
aliceId, { beaconInfoId: beaconEvent.getId(), geoUri: 'geo:51,41', timestamp: now },
);
const beaconEvent = makeBeaconInfoEvent(aliceId, roomId, { isLive: true, timestamp: now }, "$alice-room1-1");
const location1 = makeBeaconEvent(aliceId, {
beaconInfoId: beaconEvent.getId(),
geoUri: "geo:51,41",
timestamp: now,
});
const getComponent = (props = {}) => (
<MatrixClientContext.Provider value={client}>
<DialogSidebar {...defaultProps} {...props} />);
</MatrixClientContext.Provider>);
<DialogSidebar {...defaultProps} {...props} />
);
</MatrixClientContext.Provider>
);
beforeEach(() => {
// mock now so time based text in snapshots is stable
jest.spyOn(Date, 'now').mockReturnValue(now);
jest.spyOn(Date, "now").mockReturnValue(now);
});
afterAll(() => {
jest.spyOn(Date, 'now').mockRestore();
jest.spyOn(Date, "now").mockRestore();
});
it('renders sidebar correctly without beacons', () => {
it("renders sidebar correctly without beacons", () => {
const { container } = render(getComponent());
expect(container).toMatchSnapshot();
});
it('renders sidebar correctly with beacons', () => {
it("renders sidebar correctly with beacons", () => {
const [beacon] = makeRoomWithBeacons(roomId, client, [beaconEvent], [location1]);
const { container } = render(getComponent({ beacons: [beacon] }));
expect(container).toMatchSnapshot();
});
it('calls on beacon click', () => {
it("calls on beacon click", () => {
const onBeaconClick = jest.fn();
const [beacon] = makeRoomWithBeacons(roomId, client, [beaconEvent], [location1]);
const { container } = render(getComponent({ beacons: [beacon], onBeaconClick }));
act(() => {
const [listItem] = container.getElementsByClassName('mx_BeaconListItem');
const [listItem] = container.getElementsByClassName("mx_BeaconListItem");
fireEvent.click(listItem);
});
expect(onBeaconClick).toHaveBeenCalled();
});
it('closes on close button click', () => {
it("closes on close button click", () => {
const requestClose = jest.fn();
const { getByTestId } = render(getComponent({ requestClose }));
act(() => {
fireEvent.click(getByTestId('dialog-sidebar-close'));
fireEvent.click(getByTestId("dialog-sidebar-close"));
});
expect(requestClose).toHaveBeenCalled();
});

View File

@@ -14,19 +14,19 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { mocked } from 'jest-mock';
import React from "react";
import { mocked } from "jest-mock";
import { fireEvent, render } from "@testing-library/react";
import { act } from 'react-dom/test-utils';
import { Beacon, BeaconIdentifier } from 'matrix-js-sdk/src/matrix';
import { act } from "react-dom/test-utils";
import { Beacon, BeaconIdentifier } from "matrix-js-sdk/src/matrix";
import LeftPanelLiveShareWarning from '../../../../src/components/views/beacon/LeftPanelLiveShareWarning';
import { OwnBeaconStore, OwnBeaconStoreEvent } from '../../../../src/stores/OwnBeaconStore';
import { flushPromises, makeBeaconInfoEvent } from '../../../test-utils';
import dispatcher from '../../../../src/dispatcher/dispatcher';
import { Action } from '../../../../src/dispatcher/actions';
import LeftPanelLiveShareWarning from "../../../../src/components/views/beacon/LeftPanelLiveShareWarning";
import { OwnBeaconStore, OwnBeaconStoreEvent } from "../../../../src/stores/OwnBeaconStore";
import { flushPromises, makeBeaconInfoEvent } from "../../../test-utils";
import dispatcher from "../../../../src/dispatcher/dispatcher";
import { Action } from "../../../../src/dispatcher/actions";
jest.mock('../../../../src/stores/OwnBeaconStore', () => {
jest.mock("../../../../src/stores/OwnBeaconStore", () => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const EventEmitter = require("events");
class MockOwnBeaconStore extends EventEmitter {
@@ -38,57 +38,52 @@ jest.mock('../../../../src/stores/OwnBeaconStore', () => {
}
return {
// @ts-ignore
...jest.requireActual('../../../../src/stores/OwnBeaconStore'),
...jest.requireActual("../../../../src/stores/OwnBeaconStore"),
OwnBeaconStore: {
instance: new MockOwnBeaconStore() as unknown as OwnBeaconStore,
},
};
},
);
});
describe('<LeftPanelLiveShareWarning />', () => {
describe("<LeftPanelLiveShareWarning />", () => {
const getComponent = (props = {}) => render(<LeftPanelLiveShareWarning {...props} />);
const roomId1 = '!room1:server';
const roomId2 = '!room2:server';
const aliceId = '@alive:server';
const roomId1 = "!room1:server";
const roomId2 = "!room2:server";
const aliceId = "@alive:server";
const now = 1647270879403;
const HOUR_MS = 3600000;
beforeEach(() => {
jest.spyOn(global.Date, 'now').mockReturnValue(now);
jest.spyOn(dispatcher, 'dispatch').mockClear().mockImplementation(() => { });
jest.spyOn(global.Date, "now").mockReturnValue(now);
jest.spyOn(dispatcher, "dispatch")
.mockClear()
.mockImplementation(() => {});
OwnBeaconStore.instance.beaconUpdateErrors.clear();
});
afterAll(() => {
jest.spyOn(global.Date, 'now').mockRestore();
jest.spyOn(global.Date, "now").mockRestore();
jest.restoreAllMocks();
});
// 12h old, 12h left
const beacon1 = new Beacon(makeBeaconInfoEvent(aliceId,
roomId1,
{ timeout: HOUR_MS * 24, timestamp: now - 12 * HOUR_MS },
'$1',
));
const beacon1 = new Beacon(
makeBeaconInfoEvent(aliceId, roomId1, { timeout: HOUR_MS * 24, timestamp: now - 12 * HOUR_MS }, "$1"),
);
// 10h left
const beacon2 = new Beacon(makeBeaconInfoEvent(aliceId,
roomId2,
{ timeout: HOUR_MS * 10, timestamp: now },
'$2',
));
const beacon2 = new Beacon(makeBeaconInfoEvent(aliceId, roomId2, { timeout: HOUR_MS * 10, timestamp: now }, "$2"));
it('renders nothing when user has no live beacons', () => {
it("renders nothing when user has no live beacons", () => {
const { container } = getComponent();
expect(container.innerHTML).toBeFalsy();
});
describe('when user has live location monitor', () => {
describe("when user has live location monitor", () => {
beforeAll(() => {
mocked(OwnBeaconStore.instance).getBeaconById.mockImplementation(beaconId => {
mocked(OwnBeaconStore.instance).getBeaconById.mockImplementation((beaconId) => {
if (beaconId === beacon1.identifier) {
return beacon1;
}
@@ -104,17 +99,17 @@ describe('<LeftPanelLiveShareWarning />', () => {
});
afterAll(() => {
jest.spyOn(document, 'addEventListener').mockRestore();
jest.spyOn(document, "addEventListener").mockRestore();
});
it('renders correctly when not minimized', () => {
it("renders correctly when not minimized", () => {
const { asFragment } = getComponent();
expect(asFragment()).toMatchSnapshot();
});
it('goes to room of latest beacon when clicked', () => {
it("goes to room of latest beacon when clicked", () => {
const { container } = getComponent();
const dispatchSpy = jest.spyOn(dispatcher, 'dispatch');
const dispatchSpy = jest.spyOn(dispatcher, "dispatch");
fireEvent.click(container.querySelector("[role=button]"));
@@ -129,25 +124,25 @@ describe('<LeftPanelLiveShareWarning />', () => {
});
});
it('renders correctly when minimized', () => {
it("renders correctly when minimized", () => {
const { asFragment } = getComponent({ isMinimized: true });
expect(asFragment()).toMatchSnapshot();
});
it('renders location publish error', () => {
mocked(OwnBeaconStore.instance).getLiveBeaconIdsWithLocationPublishError.mockReturnValue(
[beacon1.identifier],
);
it("renders location publish error", () => {
mocked(OwnBeaconStore.instance).getLiveBeaconIdsWithLocationPublishError.mockReturnValue([
beacon1.identifier,
]);
const { asFragment } = getComponent();
expect(asFragment()).toMatchSnapshot();
});
it('goes to room of latest beacon with location publish error when clicked', () => {
mocked(OwnBeaconStore.instance).getLiveBeaconIdsWithLocationPublishError.mockReturnValue(
[beacon1.identifier],
);
it("goes to room of latest beacon with location publish error when clicked", () => {
mocked(OwnBeaconStore.instance).getLiveBeaconIdsWithLocationPublishError.mockReturnValue([
beacon1.identifier,
]);
const { container } = getComponent();
const dispatchSpy = jest.spyOn(dispatcher, 'dispatch');
const dispatchSpy = jest.spyOn(dispatcher, "dispatch");
fireEvent.click(container.querySelector("[role=button]"));
@@ -162,30 +157,30 @@ describe('<LeftPanelLiveShareWarning />', () => {
});
});
it('goes back to default style when wire errors are cleared', () => {
mocked(OwnBeaconStore.instance).getLiveBeaconIdsWithLocationPublishError.mockReturnValue(
[beacon1.identifier],
);
it("goes back to default style when wire errors are cleared", () => {
mocked(OwnBeaconStore.instance).getLiveBeaconIdsWithLocationPublishError.mockReturnValue([
beacon1.identifier,
]);
const { container, rerender } = getComponent();
// error mode
expect(container.querySelector('.mx_LeftPanelLiveShareWarning').textContent).toEqual(
'An error occurred whilst sharing your live location',
expect(container.querySelector(".mx_LeftPanelLiveShareWarning").textContent).toEqual(
"An error occurred whilst sharing your live location",
);
act(() => {
mocked(OwnBeaconStore.instance).getLiveBeaconIdsWithLocationPublishError.mockReturnValue([]);
OwnBeaconStore.instance.emit(OwnBeaconStoreEvent.LocationPublishError, 'abc');
OwnBeaconStore.instance.emit(OwnBeaconStoreEvent.LocationPublishError, "abc");
});
rerender(<LeftPanelLiveShareWarning />);
// default mode
expect(container.querySelector('.mx_LeftPanelLiveShareWarning').textContent).toEqual(
'You are sharing your live location',
expect(container.querySelector(".mx_LeftPanelLiveShareWarning").textContent).toEqual(
"You are sharing your live location",
);
});
it('removes itself when user stops having live beacons', async () => {
it("removes itself when user stops having live beacons", async () => {
const { container, rerender } = getComponent({ isMinimized: true });
// started out rendered
expect(container.innerHTML).toBeTruthy();
@@ -202,14 +197,14 @@ describe('<LeftPanelLiveShareWarning />', () => {
expect(container.innerHTML).toBeFalsy();
});
it('refreshes beacon liveness monitors when pagevisibilty changes to visible', () => {
it("refreshes beacon liveness monitors when pagevisibilty changes to visible", () => {
OwnBeaconStore.instance.beacons.set(beacon1.identifier, beacon1);
OwnBeaconStore.instance.beacons.set(beacon2.identifier, beacon2);
const beacon1MonitorSpy = jest.spyOn(beacon1, 'monitorLiveness');
const beacon2MonitorSpy = jest.spyOn(beacon1, 'monitorLiveness');
const beacon1MonitorSpy = jest.spyOn(beacon1, "monitorLiveness");
const beacon2MonitorSpy = jest.spyOn(beacon1, "monitorLiveness");
jest.spyOn(document, 'addEventListener').mockImplementation(
(_e, listener) => (listener as EventListener)(new Event('')),
jest.spyOn(document, "addEventListener").mockImplementation((_e, listener) =>
(listener as EventListener)(new Event("")),
);
expect(beacon1MonitorSpy).not.toHaveBeenCalled();
@@ -220,42 +215,42 @@ describe('<LeftPanelLiveShareWarning />', () => {
expect(beacon2MonitorSpy).toHaveBeenCalled();
});
describe('stopping errors', () => {
it('renders stopping error', () => {
OwnBeaconStore.instance.beaconUpdateErrors.set(beacon2.identifier, new Error('error'));
describe("stopping errors", () => {
it("renders stopping error", () => {
OwnBeaconStore.instance.beaconUpdateErrors.set(beacon2.identifier, new Error("error"));
const { container } = getComponent();
expect(container.textContent).toEqual('An error occurred while stopping your live location');
expect(container.textContent).toEqual("An error occurred while stopping your live location");
});
it('starts rendering stopping error on beaconUpdateError emit', () => {
it("starts rendering stopping error on beaconUpdateError emit", () => {
const { container } = getComponent();
// no error
expect(container.textContent).toEqual('You are sharing your live location');
expect(container.textContent).toEqual("You are sharing your live location");
act(() => {
OwnBeaconStore.instance.beaconUpdateErrors.set(beacon2.identifier, new Error('error'));
OwnBeaconStore.instance.beaconUpdateErrors.set(beacon2.identifier, new Error("error"));
OwnBeaconStore.instance.emit(OwnBeaconStoreEvent.BeaconUpdateError, beacon2.identifier, true);
});
expect(container.textContent).toEqual('An error occurred while stopping your live location');
expect(container.textContent).toEqual("An error occurred while stopping your live location");
});
it('renders stopping error when beacons have stopping and location errors', () => {
mocked(OwnBeaconStore.instance).getLiveBeaconIdsWithLocationPublishError.mockReturnValue(
[beacon1.identifier],
);
OwnBeaconStore.instance.beaconUpdateErrors.set(beacon2.identifier, new Error('error'));
it("renders stopping error when beacons have stopping and location errors", () => {
mocked(OwnBeaconStore.instance).getLiveBeaconIdsWithLocationPublishError.mockReturnValue([
beacon1.identifier,
]);
OwnBeaconStore.instance.beaconUpdateErrors.set(beacon2.identifier, new Error("error"));
const { container } = getComponent();
expect(container.textContent).toEqual('An error occurred while stopping your live location');
expect(container.textContent).toEqual("An error occurred while stopping your live location");
});
it('goes to room of latest beacon with stopping error when clicked', () => {
mocked(OwnBeaconStore.instance).getLiveBeaconIdsWithLocationPublishError.mockReturnValue(
[beacon1.identifier],
);
OwnBeaconStore.instance.beaconUpdateErrors.set(beacon2.identifier, new Error('error'));
it("goes to room of latest beacon with stopping error when clicked", () => {
mocked(OwnBeaconStore.instance).getLiveBeaconIdsWithLocationPublishError.mockReturnValue([
beacon1.identifier,
]);
OwnBeaconStore.instance.beaconUpdateErrors.set(beacon2.identifier, new Error("error"));
const { container } = getComponent();
const dispatchSpy = jest.spyOn(dispatcher, 'dispatch');
const dispatchSpy = jest.spyOn(dispatcher, "dispatch");
fireEvent.click(container.querySelector("[role=button]"));

View File

@@ -14,62 +14,61 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
// eslint-disable-next-line deprecate/import
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import { mocked } from 'jest-mock';
import { Beacon } from 'matrix-js-sdk/src/matrix';
import { mount } from "enzyme";
import { act } from "react-dom/test-utils";
import { mocked } from "jest-mock";
import { Beacon } from "matrix-js-sdk/src/matrix";
import OwnBeaconStatus from '../../../../src/components/views/beacon/OwnBeaconStatus';
import { BeaconDisplayStatus } from '../../../../src/components/views/beacon/displayStatus';
import { useOwnLiveBeacons } from '../../../../src/utils/beacon';
import { findByTestId, makeBeaconInfoEvent } from '../../../test-utils';
import OwnBeaconStatus from "../../../../src/components/views/beacon/OwnBeaconStatus";
import { BeaconDisplayStatus } from "../../../../src/components/views/beacon/displayStatus";
import { useOwnLiveBeacons } from "../../../../src/utils/beacon";
import { findByTestId, makeBeaconInfoEvent } from "../../../test-utils";
jest.mock('../../../../src/utils/beacon/useOwnLiveBeacons', () => ({
jest.mock("../../../../src/utils/beacon/useOwnLiveBeacons", () => ({
useOwnLiveBeacons: jest.fn(),
}));
describe('<OwnBeaconStatus />', () => {
describe("<OwnBeaconStatus />", () => {
const defaultProps = {
displayStatus: BeaconDisplayStatus.Loading,
};
const userId = '@user:server';
const roomId = '!room:server';
const userId = "@user:server";
const roomId = "!room:server";
let defaultBeacon;
const getComponent = (props = {}) =>
mount(<OwnBeaconStatus {...defaultProps} {...props} />);
const getComponent = (props = {}) => mount(<OwnBeaconStatus {...defaultProps} {...props} />);
beforeEach(() => {
jest.spyOn(global.Date, 'now').mockReturnValue(123456789);
jest.spyOn(global.Date, "now").mockReturnValue(123456789);
mocked(useOwnLiveBeacons).mockClear().mockReturnValue({});
defaultBeacon = new Beacon(makeBeaconInfoEvent(userId, roomId));
});
it('renders without a beacon instance', () => {
it("renders without a beacon instance", () => {
const component = getComponent();
expect(component).toMatchSnapshot();
});
it('renders loading state correctly', () => {
it("renders loading state correctly", () => {
const component = getComponent();
expect(component.find('BeaconStatus').props()).toBeTruthy();
expect(component.find("BeaconStatus").props()).toBeTruthy();
});
describe('Active state', () => {
it('renders stop button', () => {
describe("Active state", () => {
it("renders stop button", () => {
const displayStatus = BeaconDisplayStatus.Active;
mocked(useOwnLiveBeacons).mockReturnValue({
onStopSharing: jest.fn(),
});
const component = getComponent({ displayStatus, beacon: defaultBeacon });
expect(component.text()).toContain('Live location enabled');
expect(component.text()).toContain("Live location enabled");
expect(findByTestId(component, 'beacon-status-stop-beacon').length).toBeTruthy();
expect(findByTestId(component, "beacon-status-stop-beacon").length).toBeTruthy();
});
it('stops sharing on stop button click', () => {
it("stops sharing on stop button click", () => {
const displayStatus = BeaconDisplayStatus.Active;
const onStopSharing = jest.fn();
mocked(useOwnLiveBeacons).mockReturnValue({
@@ -78,37 +77,37 @@ describe('<OwnBeaconStatus />', () => {
const component = getComponent({ displayStatus, beacon: defaultBeacon });
act(() => {
findByTestId(component, 'beacon-status-stop-beacon').at(0).simulate('click');
findByTestId(component, "beacon-status-stop-beacon").at(0).simulate("click");
});
expect(onStopSharing).toHaveBeenCalled();
});
});
describe('errors', () => {
it('renders in error mode when displayStatus is error', () => {
describe("errors", () => {
it("renders in error mode when displayStatus is error", () => {
const displayStatus = BeaconDisplayStatus.Error;
const component = getComponent({ displayStatus });
expect(component.text()).toEqual('Live location error');
expect(component.text()).toEqual("Live location error");
// no actions for plain error
expect(component.find('AccessibleButton').length).toBeFalsy();
expect(component.find("AccessibleButton").length).toBeFalsy();
});
describe('with location publish error', () => {
it('renders in error mode', () => {
describe("with location publish error", () => {
it("renders in error mode", () => {
const displayStatus = BeaconDisplayStatus.Active;
mocked(useOwnLiveBeacons).mockReturnValue({
hasLocationPublishError: true,
onResetLocationPublishError: jest.fn(),
});
const component = getComponent({ displayStatus, beacon: defaultBeacon });
expect(component.text()).toContain('Live location error');
expect(component.text()).toContain("Live location error");
// retry button
expect(findByTestId(component, 'beacon-status-reset-wire-error').length).toBeTruthy();
expect(findByTestId(component, "beacon-status-reset-wire-error").length).toBeTruthy();
});
it('retry button resets location publish error', () => {
it("retry button resets location publish error", () => {
const displayStatus = BeaconDisplayStatus.Active;
const onResetLocationPublishError = jest.fn();
mocked(useOwnLiveBeacons).mockReturnValue({
@@ -117,15 +116,15 @@ describe('<OwnBeaconStatus />', () => {
});
const component = getComponent({ displayStatus, beacon: defaultBeacon });
act(() => {
findByTestId(component, 'beacon-status-reset-wire-error').at(0).simulate('click');
findByTestId(component, "beacon-status-reset-wire-error").at(0).simulate("click");
});
expect(onResetLocationPublishError).toHaveBeenCalled();
});
});
describe('with stopping error', () => {
it('renders in error mode', () => {
describe("with stopping error", () => {
it("renders in error mode", () => {
const displayStatus = BeaconDisplayStatus.Active;
mocked(useOwnLiveBeacons).mockReturnValue({
hasLocationPublishError: false,
@@ -133,12 +132,12 @@ describe('<OwnBeaconStatus />', () => {
onStopSharing: jest.fn(),
});
const component = getComponent({ displayStatus, beacon: defaultBeacon });
expect(component.text()).toContain('Live location error');
expect(component.text()).toContain("Live location error");
// retry button
expect(findByTestId(component, 'beacon-status-stop-beacon-retry').length).toBeTruthy();
expect(findByTestId(component, "beacon-status-stop-beacon-retry").length).toBeTruthy();
});
it('retry button retries stop sharing', () => {
it("retry button retries stop sharing", () => {
const displayStatus = BeaconDisplayStatus.Active;
const onStopSharing = jest.fn();
mocked(useOwnLiveBeacons).mockReturnValue({
@@ -147,7 +146,7 @@ describe('<OwnBeaconStatus />', () => {
});
const component = getComponent({ displayStatus, beacon: defaultBeacon });
act(() => {
findByTestId(component, 'beacon-status-stop-beacon-retry').at(0).simulate('click');
findByTestId(component, "beacon-status-stop-beacon-retry").at(0).simulate("click");
});
expect(onStopSharing).toHaveBeenCalled();
@@ -155,7 +154,7 @@ describe('<OwnBeaconStatus />', () => {
});
});
it('renders loading state correctly', () => {
it("renders loading state correctly", () => {
const component = getComponent();
expect(component).toBeTruthy();
});

View File

@@ -16,28 +16,12 @@ limitations under the License.
import React from "react";
import { act } from "react-dom/test-utils";
import {
Room,
PendingEventOrdering,
MatrixClient,
RoomMember,
RoomStateEvent,
} from "matrix-js-sdk/src/matrix";
import { Room, PendingEventOrdering, MatrixClient, RoomMember, RoomStateEvent } from "matrix-js-sdk/src/matrix";
import { ClientWidgetApi, Widget } from "matrix-widget-api";
import {
cleanup,
render,
screen,
} from "@testing-library/react";
import { cleanup, render, screen } from "@testing-library/react";
import { mocked, Mocked } from "jest-mock";
import {
mkRoomMember,
MockedCall,
setupAsyncStoreWithClient,
stubClient,
useMockedCalls,
} from "../../../test-utils";
import { mkRoomMember, MockedCall, setupAsyncStoreWithClient, stubClient, useMockedCalls } from "../../../test-utils";
import RoomCallBanner from "../../../../src/components/views/beacon/RoomCallBanner";
import { CallStore } from "../../../../src/stores/CallStore";
import { WidgetMessagingStore } from "../../../../src/stores/widgets/WidgetMessagingStore";
@@ -64,13 +48,9 @@ describe("<RoomCallBanner />", () => {
pendingEventOrdering: PendingEventOrdering.Detached,
});
alice = mkRoomMember(room.roomId, "@alice:example.org");
jest.spyOn(room, "getMember").mockImplementation((userId) =>
userId === alice.userId ? alice : null,
);
jest.spyOn(room, "getMember").mockImplementation((userId) => (userId === alice.userId ? alice : null));
client.getRoom.mockImplementation((roomId) =>
roomId === room.roomId ? room : null,
);
client.getRoom.mockImplementation((roomId) => (roomId === room.roomId ? room : null));
client.getRooms.mockReturnValue([room]);
client.reEmitter.reEmit(room, [RoomStateEvent.Events]);
@@ -100,7 +80,9 @@ describe("<RoomCallBanner />", () => {
beforeEach(() => {
MockedCall.create(room, "1");
const maybeCall = CallStore.instance.getCall(room.roomId);
if (!(maybeCall instanceof MockedCall)) {throw new Error("Failed to create call");}
if (!(maybeCall instanceof MockedCall)) {
throw new Error("Failed to create call");
}
call = maybeCall;
widget = new Widget(call.widget);

View File

@@ -14,15 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { act } from 'react-dom/test-utils';
import React from "react";
import { act } from "react-dom/test-utils";
// eslint-disable-next-line deprecate/import
import { mount } from 'enzyme';
import { Room, Beacon, BeaconEvent, getBeaconInfoIdentifier } from 'matrix-js-sdk/src/matrix';
import { logger } from 'matrix-js-sdk/src/logger';
import { mount } from "enzyme";
import { Room, Beacon, BeaconEvent, getBeaconInfoIdentifier } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import RoomLiveShareWarning from '../../../../src/components/views/beacon/RoomLiveShareWarning';
import { OwnBeaconStore, OwnBeaconStoreEvent } from '../../../../src/stores/OwnBeaconStore';
import RoomLiveShareWarning from "../../../../src/components/views/beacon/RoomLiveShareWarning";
import { OwnBeaconStore, OwnBeaconStoreEvent } from "../../../../src/stores/OwnBeaconStore";
import {
advanceDateAndTime,
findByTestId,
@@ -32,20 +32,20 @@ import {
mockGeolocation,
resetAsyncStoreWithClient,
setupAsyncStoreWithClient,
} from '../../../test-utils';
import defaultDispatcher from '../../../../src/dispatcher/dispatcher';
import { Action } from '../../../../src/dispatcher/actions';
} from "../../../test-utils";
import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
import { Action } from "../../../../src/dispatcher/actions";
jest.useFakeTimers();
describe('<RoomLiveShareWarning />', () => {
const aliceId = '@alice:server.org';
const room1Id = '$room1:server.org';
const room2Id = '$room2:server.org';
const room3Id = '$room3:server.org';
describe("<RoomLiveShareWarning />", () => {
const aliceId = "@alice:server.org";
const room1Id = "$room1:server.org";
const room2Id = "$room2:server.org";
const room3Id = "$room3:server.org";
const mockClient = getMockClientWithEventEmitter({
getVisibleRooms: jest.fn().mockReturnValue([]),
getUserId: jest.fn().mockReturnValue(aliceId),
unstable_setLiveBeacon: jest.fn().mockResolvedValue({ event_id: '1' }),
unstable_setLiveBeacon: jest.fn().mockResolvedValue({ event_id: "1" }),
sendEvent: jest.fn(),
});
@@ -54,14 +54,19 @@ describe('<RoomLiveShareWarning />', () => {
const MINUTE_MS = 60000;
const HOUR_MS = 3600000;
// mock the date so events are stable for snapshots etc
jest.spyOn(global.Date, 'now').mockReturnValue(now);
const room1Beacon1 = makeBeaconInfoEvent(aliceId, room1Id, {
isLive: true,
timeout: HOUR_MS,
}, '$0');
const room2Beacon1 = makeBeaconInfoEvent(aliceId, room2Id, { isLive: true, timeout: HOUR_MS }, '$1');
const room2Beacon2 = makeBeaconInfoEvent(aliceId, room2Id, { isLive: true, timeout: HOUR_MS * 12 }, '$2');
const room3Beacon1 = makeBeaconInfoEvent(aliceId, room3Id, { isLive: true, timeout: HOUR_MS }, '$3');
jest.spyOn(global.Date, "now").mockReturnValue(now);
const room1Beacon1 = makeBeaconInfoEvent(
aliceId,
room1Id,
{
isLive: true,
timeout: HOUR_MS,
},
"$0",
);
const room2Beacon1 = makeBeaconInfoEvent(aliceId, room2Id, { isLive: true, timeout: HOUR_MS }, "$1");
const room2Beacon2 = makeBeaconInfoEvent(aliceId, room2Id, { isLive: true, timeout: HOUR_MS * 12 }, "$2");
const room3Beacon1 = makeBeaconInfoEvent(aliceId, room3Id, { isLive: true, timeout: HOUR_MS }, "$3");
// make fresh rooms every time
// as we update room state
@@ -96,42 +101,39 @@ describe('<RoomLiveShareWarning />', () => {
return component;
};
const localStorageSpy = jest.spyOn(localStorage.__proto__, 'getItem').mockReturnValue(undefined);
const localStorageSpy = jest.spyOn(localStorage.__proto__, "getItem").mockReturnValue(undefined);
beforeEach(() => {
mockGeolocation();
jest.spyOn(global.Date, 'now').mockReturnValue(now);
mockClient.unstable_setLiveBeacon.mockReset().mockResolvedValue({ event_id: '1' });
jest.spyOn(global.Date, "now").mockReturnValue(now);
mockClient.unstable_setLiveBeacon.mockReset().mockResolvedValue({ event_id: "1" });
// assume all beacons were created on this device
localStorageSpy.mockReturnValue(JSON.stringify([
room1Beacon1.getId(),
room2Beacon1.getId(),
room2Beacon2.getId(),
room3Beacon1.getId(),
]));
localStorageSpy.mockReturnValue(
JSON.stringify([room1Beacon1.getId(), room2Beacon1.getId(), room2Beacon2.getId(), room3Beacon1.getId()]),
);
});
afterEach(async () => {
jest.spyOn(OwnBeaconStore.instance, 'beaconHasLocationPublishError').mockRestore();
jest.spyOn(OwnBeaconStore.instance, "beaconHasLocationPublishError").mockRestore();
await resetAsyncStoreWithClient(OwnBeaconStore.instance);
});
afterAll(() => {
jest.spyOn(global.Date, 'now').mockRestore();
jest.spyOn(global.Date, "now").mockRestore();
localStorageSpy.mockRestore();
jest.spyOn(defaultDispatcher, 'dispatch').mockRestore();
jest.spyOn(defaultDispatcher, "dispatch").mockRestore();
});
const getExpiryText = wrapper => findByTestId(wrapper, 'room-live-share-expiry').text();
const getExpiryText = (wrapper) => findByTestId(wrapper, "room-live-share-expiry").text();
it('renders nothing when user has no live beacons at all', async () => {
it("renders nothing when user has no live beacons at all", async () => {
await makeOwnBeaconStore();
const component = getComponent();
expect(component.html()).toBe(null);
});
it('renders nothing when user has no live beacons in room', async () => {
it("renders nothing when user has no live beacons in room", async () => {
await act(async () => {
await makeRoomsWithStateEvents([room2Beacon1]);
await makeOwnBeaconStore();
@@ -140,8 +142,8 @@ describe('<RoomLiveShareWarning />', () => {
expect(component.html()).toBe(null);
});
it('does not render when geolocation is not working', async () => {
jest.spyOn(logger, 'error').mockImplementation(() => { });
it("does not render when geolocation is not working", async () => {
jest.spyOn(logger, "error").mockImplementation(() => {});
// @ts-ignore
navigator.geolocation = undefined;
await act(async () => {
@@ -155,7 +157,7 @@ describe('<RoomLiveShareWarning />', () => {
expect(component.html()).toBeNull();
});
describe('when user has live beacons and geolocation is available', () => {
describe("when user has live beacons and geolocation is available", () => {
beforeEach(async () => {
await act(async () => {
await makeRoomsWithStateEvents([room1Beacon1, room2Beacon1, room2Beacon2]);
@@ -163,23 +165,23 @@ describe('<RoomLiveShareWarning />', () => {
});
});
it('renders correctly with one live beacon in room', () => {
it("renders correctly with one live beacon in room", () => {
const component = getComponent({ roomId: room1Id });
// beacons have generated ids that break snapshots
// assert on html
expect(component.html()).toMatchSnapshot();
});
it('renders correctly with two live beacons in room', () => {
it("renders correctly with two live beacons in room", () => {
const component = getComponent({ roomId: room2Id });
// beacons have generated ids that break snapshots
// assert on html
expect(component.html()).toMatchSnapshot();
// later expiry displayed
expect(getExpiryText(component)).toEqual('12h left');
expect(getExpiryText(component)).toEqual("12h left");
});
it('removes itself when user stops having live beacons', async () => {
it("removes itself when user stops having live beacons", async () => {
const component = getComponent({ roomId: room1Id });
// started out rendered
expect(component.html()).toBeTruthy();
@@ -196,7 +198,7 @@ describe('<RoomLiveShareWarning />', () => {
expect(component.html()).toBe(null);
});
it('removes itself when user stops monitoring live position', async () => {
it("removes itself when user stops monitoring live position", async () => {
const component = getComponent({ roomId: room1Id });
// started out rendered
expect(component.html()).toBeTruthy();
@@ -212,7 +214,7 @@ describe('<RoomLiveShareWarning />', () => {
expect(component.html()).toBe(null);
});
it('renders when user adds a live beacon', async () => {
it("renders when user adds a live beacon", async () => {
const component = getComponent({ roomId: room3Id });
// started out not rendered
expect(component.html()).toBeFalsy();
@@ -225,40 +227,45 @@ describe('<RoomLiveShareWarning />', () => {
expect(component.html()).toBeTruthy();
});
it('updates beacon time left periodically', () => {
it("updates beacon time left periodically", () => {
const component = getComponent({ roomId: room1Id });
expect(getExpiryText(component)).toEqual('1h left');
expect(getExpiryText(component)).toEqual("1h left");
act(() => {
advanceDateAndTime(MINUTE_MS * 25);
});
expect(getExpiryText(component)).toEqual('35m left');
expect(getExpiryText(component)).toEqual("35m left");
});
it('updates beacon time left when beacon updates', () => {
it("updates beacon time left when beacon updates", () => {
const component = getComponent({ roomId: room1Id });
expect(getExpiryText(component)).toEqual('1h left');
expect(getExpiryText(component)).toEqual("1h left");
expect(getExpiryText(component)).toEqual('1h left');
expect(getExpiryText(component)).toEqual("1h left");
act(() => {
const beacon = OwnBeaconStore.instance.getBeaconById(getBeaconInfoIdentifier(room1Beacon1));
const room1Beacon1Update = makeBeaconInfoEvent(aliceId, room1Id, {
isLive: true,
timeout: 3 * HOUR_MS,
}, '$0');
const room1Beacon1Update = makeBeaconInfoEvent(
aliceId,
room1Id,
{
isLive: true,
timeout: 3 * HOUR_MS,
},
"$0",
);
beacon.update(room1Beacon1Update);
});
// update to expiry of new beacon
expect(getExpiryText(component)).toEqual('3h left');
expect(getExpiryText(component)).toEqual("3h left");
});
it('clears expiry time interval on unmount', () => {
const clearIntervalSpy = jest.spyOn(global, 'clearInterval');
it("clears expiry time interval on unmount", () => {
const clearIntervalSpy = jest.spyOn(global, "clearInterval");
const component = getComponent({ roomId: room1Id });
expect(getExpiryText(component)).toEqual('1h left');
expect(getExpiryText(component)).toEqual("1h left");
act(() => {
component.unmount();
@@ -267,12 +274,12 @@ describe('<RoomLiveShareWarning />', () => {
expect(clearIntervalSpy).toHaveBeenCalled();
});
it('navigates to beacon tile on click', () => {
const dispatcherSpy = jest.spyOn(defaultDispatcher, 'dispatch');
it("navigates to beacon tile on click", () => {
const dispatcherSpy = jest.spyOn(defaultDispatcher, "dispatch");
const component = getComponent({ roomId: room1Id });
act(() => {
component.simulate('click');
component.simulate("click");
});
expect(dispatcherSpy).toHaveBeenCalledWith({
@@ -285,30 +292,30 @@ describe('<RoomLiveShareWarning />', () => {
});
});
describe('stopping beacons', () => {
it('stops beacon on stop sharing click', () => {
describe("stopping beacons", () => {
it("stops beacon on stop sharing click", () => {
const component = getComponent({ roomId: room2Id });
act(() => {
findByTestId(component, 'room-live-share-primary-button').at(0).simulate('click');
findByTestId(component, "room-live-share-primary-button").at(0).simulate("click");
component.setProps({});
});
expect(mockClient.unstable_setLiveBeacon).toHaveBeenCalled();
expect(component.find('Spinner').length).toBeTruthy();
expect(findByTestId(component, 'room-live-share-primary-button').at(0).props().disabled).toBeTruthy();
expect(component.find("Spinner").length).toBeTruthy();
expect(findByTestId(component, "room-live-share-primary-button").at(0).props().disabled).toBeTruthy();
});
it('displays error when stop sharing fails', async () => {
it("displays error when stop sharing fails", async () => {
const component = getComponent({ roomId: room1Id });
// fail first time
mockClient.unstable_setLiveBeacon
.mockRejectedValueOnce(new Error('oups'))
.mockResolvedValue(({ event_id: '1' }));
.mockRejectedValueOnce(new Error("oups"))
.mockResolvedValue({ event_id: "1" });
await act(async () => {
findByTestId(component, 'room-live-share-primary-button').at(0).simulate('click');
findByTestId(component, "room-live-share-primary-button").at(0).simulate("click");
await flushPromisesWithFakeTimers();
});
component.setProps({});
@@ -316,20 +323,20 @@ describe('<RoomLiveShareWarning />', () => {
expect(component.html()).toMatchSnapshot();
act(() => {
findByTestId(component, 'room-live-share-primary-button').at(0).simulate('click');
findByTestId(component, "room-live-share-primary-button").at(0).simulate("click");
component.setProps({});
});
expect(mockClient.unstable_setLiveBeacon).toHaveBeenCalledTimes(2);
});
it('displays again with correct state after stopping a beacon', () => {
it("displays again with correct state after stopping a beacon", () => {
// make sure the loading state is reset correctly after removing a beacon
const component = getComponent({ roomId: room1Id });
// stop the beacon
act(() => {
findByTestId(component, 'room-live-share-primary-button').at(0).simulate('click');
findByTestId(component, "room-live-share-primary-button").at(0).simulate("click");
});
// time travel until room1Beacon1 is expired
act(() => {
@@ -345,28 +352,30 @@ describe('<RoomLiveShareWarning />', () => {
});
// button not disabled and expiry time shown
expect(findByTestId(component, 'room-live-share-primary-button').at(0).props().disabled).toBeFalsy();
expect(findByTestId(component, 'room-live-share-expiry').text()).toEqual('1h left');
expect(findByTestId(component, "room-live-share-primary-button").at(0).props().disabled).toBeFalsy();
expect(findByTestId(component, "room-live-share-expiry").text()).toEqual("1h left");
});
});
describe('with location publish errors', () => {
it('displays location publish error when mounted with location publish errors', async () => {
const locationPublishErrorSpy = jest.spyOn(OwnBeaconStore.instance, 'beaconHasLocationPublishError')
describe("with location publish errors", () => {
it("displays location publish error when mounted with location publish errors", async () => {
const locationPublishErrorSpy = jest
.spyOn(OwnBeaconStore.instance, "beaconHasLocationPublishError")
.mockReturnValue(true);
const component = getComponent({ roomId: room2Id });
expect(component).toMatchSnapshot();
expect(locationPublishErrorSpy).toHaveBeenCalledWith(
getBeaconInfoIdentifier(room2Beacon1), 0, [getBeaconInfoIdentifier(room2Beacon1)],
);
expect(locationPublishErrorSpy).toHaveBeenCalledWith(getBeaconInfoIdentifier(room2Beacon1), 0, [
getBeaconInfoIdentifier(room2Beacon1),
]);
});
it(
'displays location publish error when locationPublishError event is emitted' +
' and beacons have errors',
"displays location publish error when locationPublishError event is emitted" +
" and beacons have errors",
async () => {
const locationPublishErrorSpy = jest.spyOn(OwnBeaconStore.instance, 'beaconHasLocationPublishError')
const locationPublishErrorSpy = jest
.spyOn(OwnBeaconStore.instance, "beaconHasLocationPublishError")
.mockReturnValue(false);
const component = getComponent({ roomId: room2Id });
@@ -374,20 +383,23 @@ describe('<RoomLiveShareWarning />', () => {
act(() => {
locationPublishErrorSpy.mockReturnValue(true);
OwnBeaconStore.instance.emit(
OwnBeaconStoreEvent.LocationPublishError, getBeaconInfoIdentifier(room2Beacon1),
OwnBeaconStoreEvent.LocationPublishError,
getBeaconInfoIdentifier(room2Beacon1),
);
});
component.setProps({});
// renders wire error ui
expect(component.find('.mx_RoomLiveShareWarning_label').text()).toEqual(
'An error occurred whilst sharing your live location, please try again',
expect(component.find(".mx_RoomLiveShareWarning_label").text()).toEqual(
"An error occurred whilst sharing your live location, please try again",
);
expect(findByTestId(component, 'room-live-share-wire-error-close-button').length).toBeTruthy();
});
expect(findByTestId(component, "room-live-share-wire-error-close-button").length).toBeTruthy();
},
);
it('stops displaying wire error when errors are cleared', async () => {
const locationPublishErrorSpy = jest.spyOn(OwnBeaconStore.instance, 'beaconHasLocationPublishError')
it("stops displaying wire error when errors are cleared", async () => {
const locationPublishErrorSpy = jest
.spyOn(OwnBeaconStore.instance, "beaconHasLocationPublishError")
.mockReturnValue(true);
const component = getComponent({ roomId: room2Id });
@@ -395,39 +407,40 @@ describe('<RoomLiveShareWarning />', () => {
act(() => {
locationPublishErrorSpy.mockReturnValue(false);
OwnBeaconStore.instance.emit(
OwnBeaconStoreEvent.LocationPublishError, getBeaconInfoIdentifier(room2Beacon1),
OwnBeaconStoreEvent.LocationPublishError,
getBeaconInfoIdentifier(room2Beacon1),
);
});
component.setProps({});
// renders error-free ui
expect(component.find('.mx_RoomLiveShareWarning_label').text()).toEqual(
'You are sharing your live location',
expect(component.find(".mx_RoomLiveShareWarning_label").text()).toEqual(
"You are sharing your live location",
);
expect(findByTestId(component, 'room-live-share-wire-error-close-button').length).toBeFalsy();
expect(findByTestId(component, "room-live-share-wire-error-close-button").length).toBeFalsy();
});
it('clicking retry button resets location publish errors', async () => {
jest.spyOn(OwnBeaconStore.instance, 'beaconHasLocationPublishError').mockReturnValue(true);
const resetErrorSpy = jest.spyOn(OwnBeaconStore.instance, 'resetLocationPublishError');
it("clicking retry button resets location publish errors", async () => {
jest.spyOn(OwnBeaconStore.instance, "beaconHasLocationPublishError").mockReturnValue(true);
const resetErrorSpy = jest.spyOn(OwnBeaconStore.instance, "resetLocationPublishError");
const component = getComponent({ roomId: room2Id });
act(() => {
findByTestId(component, 'room-live-share-primary-button').at(0).simulate('click');
findByTestId(component, "room-live-share-primary-button").at(0).simulate("click");
});
expect(resetErrorSpy).toHaveBeenCalledWith(getBeaconInfoIdentifier(room2Beacon1));
});
it('clicking close button stops beacons', async () => {
jest.spyOn(OwnBeaconStore.instance, 'beaconHasLocationPublishError').mockReturnValue(true);
const stopBeaconSpy = jest.spyOn(OwnBeaconStore.instance, 'stopBeacon');
it("clicking close button stops beacons", async () => {
jest.spyOn(OwnBeaconStore.instance, "beaconHasLocationPublishError").mockReturnValue(true);
const stopBeaconSpy = jest.spyOn(OwnBeaconStore.instance, "stopBeacon");
const component = getComponent({ roomId: room2Id });
act(() => {
findByTestId(component, 'room-live-share-wire-error-close-button').at(0).simulate('click');
findByTestId(component, "room-live-share-wire-error-close-button").at(0).simulate("click");
});
expect(stopBeaconSpy).toHaveBeenCalledWith(getBeaconInfoIdentifier(room2Beacon1));

View File

@@ -14,21 +14,21 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { fireEvent, render } from "@testing-library/react";
import ShareLatestLocation from '../../../../src/components/views/beacon/ShareLatestLocation';
import { copyPlaintext } from '../../../../src/utils/strings';
import { flushPromises } from '../../../test-utils';
import ShareLatestLocation from "../../../../src/components/views/beacon/ShareLatestLocation";
import { copyPlaintext } from "../../../../src/utils/strings";
import { flushPromises } from "../../../test-utils";
jest.mock('../../../../src/utils/strings', () => ({
jest.mock("../../../../src/utils/strings", () => ({
copyPlaintext: jest.fn().mockResolvedValue(undefined),
}));
describe('<ShareLatestLocation />', () => {
describe("<ShareLatestLocation />", () => {
const defaultProps = {
latestLocationState: {
uri: 'geo:51,42;u=35',
uri: "geo:51,42;u=35",
timestamp: 123,
},
};
@@ -38,18 +38,18 @@ describe('<ShareLatestLocation />', () => {
jest.clearAllMocks();
});
it('renders null when no location', () => {
it("renders null when no location", () => {
const { container } = getComponent({ latestLocationState: undefined });
expect(container.innerHTML).toBeFalsy();
});
it('renders share buttons when there is a location', async () => {
it("renders share buttons when there is a location", async () => {
const { container, asFragment } = getComponent();
expect(asFragment()).toMatchSnapshot();
fireEvent.click(container.querySelector('.mx_CopyableText_copyButton'));
fireEvent.click(container.querySelector(".mx_CopyableText_copyButton"));
await flushPromises();
expect(copyPlaintext).toHaveBeenCalledWith('51,42');
expect(copyPlaintext).toHaveBeenCalledWith("51,42");
});
});

View File

@@ -14,16 +14,16 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { render } from "@testing-library/react";
import StyledLiveBeaconIcon from '../../../../src/components/views/beacon/StyledLiveBeaconIcon';
import StyledLiveBeaconIcon from "../../../../src/components/views/beacon/StyledLiveBeaconIcon";
describe('<StyledLiveBeaconIcon />', () => {
describe("<StyledLiveBeaconIcon />", () => {
const defaultProps = {};
const getComponent = (props = {}) => render(<StyledLiveBeaconIcon {...defaultProps} {...props} />);
it('renders', () => {
it("renders", () => {
const { asFragment } = getComponent();
expect(asFragment()).toMatchSnapshot();
});

View File

@@ -25,7 +25,7 @@ import SettingsStore from "../../../../src/settings/SettingsStore";
jest.mock("../../../../src/utils/Feedback");
jest.mock("../../../../src/settings/SettingsStore");
describe('<BetaCard />', () => {
describe("<BetaCard />", () => {
describe("Feedback prompt", () => {
const featureId = "featureId";

View File

@@ -27,10 +27,13 @@ describe("<ContextMenu />", () => {
// Hardcode window and menu dimensions
const windowSize = 300;
const menuSize = 200;
jest.spyOn(UIStore, "instance", "get").mockImplementation(() => ({
windowWidth: windowSize,
windowHeight: windowSize,
}) as unknown as UIStore);
jest.spyOn(UIStore, "instance", "get").mockImplementation(
() =>
({
windowWidth: windowSize,
windowHeight: windowSize,
} as unknown as UIStore),
);
window.Element.prototype.getBoundingClientRect = jest.fn().mockReturnValue({
width: menuSize,
height: menuSize,

View File

@@ -14,33 +14,33 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
// eslint-disable-next-line deprecate/import
import { mount, ReactWrapper } from 'enzyme';
import { EventStatus, MatrixEvent } from 'matrix-js-sdk/src/models/event';
import { Room } from 'matrix-js-sdk/src/models/room';
import { mount, ReactWrapper } from "enzyme";
import { EventStatus, MatrixEvent } from "matrix-js-sdk/src/models/event";
import { Room } from "matrix-js-sdk/src/models/room";
import {
PendingEventOrdering,
BeaconIdentifier,
Beacon,
getBeaconInfoIdentifier,
EventType,
} from 'matrix-js-sdk/src/matrix';
import { ExtensibleEvent, MessageEvent, M_POLL_KIND_DISCLOSED, PollStartEvent } from 'matrix-events-sdk';
} from "matrix-js-sdk/src/matrix";
import { ExtensibleEvent, MessageEvent, M_POLL_KIND_DISCLOSED, PollStartEvent } from "matrix-events-sdk";
import { FeatureSupport, Thread } from "matrix-js-sdk/src/models/thread";
import { mocked } from "jest-mock";
import { act } from '@testing-library/react';
import { act } from "@testing-library/react";
import { MatrixClientPeg } from '../../../../src/MatrixClientPeg';
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import RoomContext, { TimelineRenderingType } from "../../../../src/contexts/RoomContext";
import { IRoomState } from "../../../../src/components/structures/RoomView";
import { canEditContent } from "../../../../src/utils/EventUtils";
import { copyPlaintext, getSelectedText } from "../../../../src/utils/strings";
import MessageContextMenu from "../../../../src/components/views/context_menus/MessageContextMenu";
import { makeBeaconEvent, makeBeaconInfoEvent, makeLocationEvent, stubClient } from '../../../test-utils';
import dispatcher from '../../../../src/dispatcher/dispatcher';
import SettingsStore from '../../../../src/settings/SettingsStore';
import { ReadPinsEventId } from '../../../../src/components/views/right_panel/types';
import { makeBeaconEvent, makeBeaconInfoEvent, makeLocationEvent, stubClient } from "../../../test-utils";
import dispatcher from "../../../../src/dispatcher/dispatcher";
import SettingsStore from "../../../../src/settings/SettingsStore";
import { ReadPinsEventId } from "../../../../src/components/views/right_panel/types";
import { Action } from "../../../../src/dispatcher/actions";
jest.mock("../../../../src/utils/strings", () => ({
@@ -52,17 +52,17 @@ jest.mock("../../../../src/utils/EventUtils", () => ({
...jest.requireActual("../../../../src/utils/EventUtils"),
canEditContent: jest.fn(),
}));
jest.mock('../../../../src/dispatcher/dispatcher');
jest.mock("../../../../src/dispatcher/dispatcher");
const roomId = 'roomid';
const roomId = "roomid";
describe('MessageContextMenu', () => {
describe("MessageContextMenu", () => {
beforeEach(() => {
jest.resetAllMocks();
stubClient();
});
it('does show copy link button when supplied a link', () => {
it("does show copy link button when supplied a link", () => {
const eventContent = MessageEvent.from("hello");
const props = {
link: "https://google.com/",
@@ -73,119 +73,123 @@ describe('MessageContextMenu', () => {
expect(copyLinkButton.props().href).toBe(props.link);
});
it('does not show copy link button when not supplied a link', () => {
it("does not show copy link button when not supplied a link", () => {
const eventContent = MessageEvent.from("hello");
const menu = createMenuWithContent(eventContent);
const copyLinkButton = menu.find('a[aria-label="Copy link"]');
expect(copyLinkButton).toHaveLength(0);
});
describe('message pinning', () => {
describe("message pinning", () => {
beforeEach(() => {
jest.spyOn(SettingsStore, 'getValue').mockReturnValue(true);
jest.spyOn(SettingsStore, "getValue").mockReturnValue(true);
});
afterAll(() => {
jest.spyOn(SettingsStore, 'getValue').mockRestore();
jest.spyOn(SettingsStore, "getValue").mockRestore();
});
it('does not show pin option when user does not have rights to pin', () => {
it("does not show pin option when user does not have rights to pin", () => {
const eventContent = MessageEvent.from("hello");
const event = new MatrixEvent(eventContent.serialize());
const room = makeDefaultRoom();
// mock permission to disallow adding pinned messages to room
jest.spyOn(room.currentState, 'mayClientSendStateEvent').mockReturnValue(false);
jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(false);
const menu = createMenu(event, {}, {}, undefined, room);
expect(menu.find('div[aria-label="Pin"]')).toHaveLength(0);
});
it('does not show pin option for beacon_info event', () => {
const deadBeaconEvent = makeBeaconInfoEvent('@alice:server.org', roomId, { isLive: false });
it("does not show pin option for beacon_info event", () => {
const deadBeaconEvent = makeBeaconInfoEvent("@alice:server.org", roomId, { isLive: false });
const room = makeDefaultRoom();
// mock permission to allow adding pinned messages to room
jest.spyOn(room.currentState, 'mayClientSendStateEvent').mockReturnValue(true);
jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(true);
const menu = createMenu(deadBeaconEvent, {}, {}, undefined, room);
expect(menu.find('div[aria-label="Pin"]')).toHaveLength(0);
});
it('does not show pin option when pinning feature is disabled', () => {
it("does not show pin option when pinning feature is disabled", () => {
const eventContent = MessageEvent.from("hello");
const pinnableEvent = new MatrixEvent({ ...eventContent.serialize(), room_id: roomId });
const room = makeDefaultRoom();
// mock permission to allow adding pinned messages to room
jest.spyOn(room.currentState, 'mayClientSendStateEvent').mockReturnValue(true);
jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(true);
// disable pinning feature
jest.spyOn(SettingsStore, 'getValue').mockReturnValue(false);
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
const menu = createMenu(pinnableEvent, {}, {}, undefined, room);
expect(menu.find('div[aria-label="Pin"]')).toHaveLength(0);
});
it('shows pin option when pinning feature is enabled', () => {
it("shows pin option when pinning feature is enabled", () => {
const eventContent = MessageEvent.from("hello");
const pinnableEvent = new MatrixEvent({ ...eventContent.serialize(), room_id: roomId });
const room = makeDefaultRoom();
// mock permission to allow adding pinned messages to room
jest.spyOn(room.currentState, 'mayClientSendStateEvent').mockReturnValue(true);
jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(true);
const menu = createMenu(pinnableEvent, {}, {}, undefined, room);
expect(menu.find('div[aria-label="Pin"]')).toHaveLength(1);
});
it('pins event on pin option click', () => {
it("pins event on pin option click", () => {
const onFinished = jest.fn();
const eventContent = MessageEvent.from("hello");
const pinnableEvent = new MatrixEvent({ ...eventContent.serialize(), room_id: roomId });
pinnableEvent.event.event_id = '!3';
pinnableEvent.event.event_id = "!3";
const client = MatrixClientPeg.get();
const room = makeDefaultRoom();
// mock permission to allow adding pinned messages to room
jest.spyOn(room.currentState, 'mayClientSendStateEvent').mockReturnValue(true);
jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(true);
// mock read pins account data
const pinsAccountData = new MatrixEvent({ content: { event_ids: ['!1', '!2'] } });
jest.spyOn(room, 'getAccountData').mockReturnValue(pinsAccountData);
const pinsAccountData = new MatrixEvent({ content: { event_ids: ["!1", "!2"] } });
jest.spyOn(room, "getAccountData").mockReturnValue(pinsAccountData);
const menu = createMenu(pinnableEvent, { onFinished }, {}, undefined, room);
act(() => {
menu.find('div[aria-label="Pin"]').simulate('click');
menu.find('div[aria-label="Pin"]').simulate("click");
});
// added to account data
expect(client.setRoomAccountData).toHaveBeenCalledWith(
roomId,
ReadPinsEventId,
{ event_ids: [
expect(client.setRoomAccountData).toHaveBeenCalledWith(roomId, ReadPinsEventId, {
event_ids: [
// from account data
'!1', '!2',
"!1",
"!2",
pinnableEvent.getId(),
],
},
);
});
// add to room's pins
expect(client.sendStateEvent).toHaveBeenCalledWith(roomId, EventType.RoomPinnedEvents, {
pinned: [pinnableEvent.getId()] }, "");
expect(client.sendStateEvent).toHaveBeenCalledWith(
roomId,
EventType.RoomPinnedEvents,
{
pinned: [pinnableEvent.getId()],
},
"",
);
expect(onFinished).toHaveBeenCalled();
});
it('unpins event on pin option click when event is pinned', () => {
it("unpins event on pin option click when event is pinned", () => {
const eventContent = MessageEvent.from("hello");
const pinnableEvent = new MatrixEvent({ ...eventContent.serialize(), room_id: roomId });
pinnableEvent.event.event_id = '!3';
pinnableEvent.event.event_id = "!3";
const client = MatrixClientPeg.get();
const room = makeDefaultRoom();
@@ -194,52 +198,53 @@ describe('MessageContextMenu', () => {
type: EventType.RoomPinnedEvents,
room_id: roomId,
state_key: "",
content: { pinned: [pinnableEvent.getId(), '!another-event'] },
content: { pinned: [pinnableEvent.getId(), "!another-event"] },
});
room.currentState.setStateEvents([pinEvent]);
// mock permission to allow adding pinned messages to room
jest.spyOn(room.currentState, 'mayClientSendStateEvent').mockReturnValue(true);
jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(true);
// mock read pins account data
const pinsAccountData = new MatrixEvent({ content: { event_ids: ['!1', '!2'] } });
jest.spyOn(room, 'getAccountData').mockReturnValue(pinsAccountData);
const pinsAccountData = new MatrixEvent({ content: { event_ids: ["!1", "!2"] } });
jest.spyOn(room, "getAccountData").mockReturnValue(pinsAccountData);
const menu = createMenu(pinnableEvent, {}, {}, undefined, room);
act(() => {
menu.find('div[aria-label="Unpin"]').simulate('click');
menu.find('div[aria-label="Unpin"]').simulate("click");
});
expect(client.setRoomAccountData).not.toHaveBeenCalled();
// add to room's pins
expect(client.sendStateEvent).toHaveBeenCalledWith(
roomId, EventType.RoomPinnedEvents,
roomId,
EventType.RoomPinnedEvents,
// pinnableEvent's id removed, other pins intact
{ pinned: ['!another-event'] },
{ pinned: ["!another-event"] },
"",
);
});
});
describe('message forwarding', () => {
it('allows forwarding a room message', () => {
describe("message forwarding", () => {
it("allows forwarding a room message", () => {
const eventContent = MessageEvent.from("hello");
const menu = createMenuWithContent(eventContent);
expect(menu.find('div[aria-label="Forward"]')).toHaveLength(1);
});
it('does not allow forwarding a poll', () => {
it("does not allow forwarding a poll", () => {
const eventContent = PollStartEvent.from("why?", ["42"], M_POLL_KIND_DISCLOSED);
const menu = createMenuWithContent(eventContent);
expect(menu.find('div[aria-label="Forward"]')).toHaveLength(0);
});
describe('forwarding beacons', () => {
describe("forwarding beacons", () => {
const aliceId = "@alice:server.org";
it('does not allow forwarding a beacon that is not live', () => {
it("does not allow forwarding a beacon that is not live", () => {
const deadBeaconEvent = makeBeaconInfoEvent(aliceId, roomId, { isLive: false });
const beacon = new Beacon(deadBeaconEvent);
const beacons = new Map<BeaconIdentifier, Beacon>();
@@ -248,11 +253,12 @@ describe('MessageContextMenu', () => {
expect(menu.find('div[aria-label="Forward"]')).toHaveLength(0);
});
it('does not allow forwarding a beacon that is not live but has a latestLocation', () => {
it("does not allow forwarding a beacon that is not live but has a latestLocation", () => {
const deadBeaconEvent = makeBeaconInfoEvent(aliceId, roomId, { isLive: false });
const beaconLocation = makeBeaconEvent(
aliceId, { beaconInfoId: deadBeaconEvent.getId(), geoUri: 'geo:51,41' },
);
const beaconLocation = makeBeaconEvent(aliceId, {
beaconInfoId: deadBeaconEvent.getId(),
geoUri: "geo:51,41",
});
const beacon = new Beacon(deadBeaconEvent);
// @ts-ignore illegally set private prop
beacon._latestLocationEvent = beaconLocation;
@@ -262,7 +268,7 @@ describe('MessageContextMenu', () => {
expect(menu.find('div[aria-label="Forward"]')).toHaveLength(0);
});
it('does not allow forwarding a live beacon that does not have a latestLocation', () => {
it("does not allow forwarding a live beacon that does not have a latestLocation", () => {
const beaconEvent = makeBeaconInfoEvent(aliceId, roomId, { isLive: true });
const beacon = new Beacon(beaconEvent);
@@ -272,11 +278,12 @@ describe('MessageContextMenu', () => {
expect(menu.find('div[aria-label="Forward"]')).toHaveLength(0);
});
it('allows forwarding a live beacon that has a location', () => {
it("allows forwarding a live beacon that has a location", () => {
const liveBeaconEvent = makeBeaconInfoEvent(aliceId, roomId, { isLive: true });
const beaconLocation = makeBeaconEvent(
aliceId, { beaconInfoId: liveBeaconEvent.getId(), geoUri: 'geo:51,41' },
);
const beaconLocation = makeBeaconEvent(aliceId, {
beaconInfoId: liveBeaconEvent.getId(),
geoUri: "geo:51,41",
});
const beacon = new Beacon(liveBeaconEvent);
// @ts-ignore illegally set private prop
beacon._latestLocationEvent = beaconLocation;
@@ -286,12 +293,13 @@ describe('MessageContextMenu', () => {
expect(menu.find('div[aria-label="Forward"]')).toHaveLength(1);
});
it('opens forward dialog with correct event', () => {
const dispatchSpy = jest.spyOn(dispatcher, 'dispatch');
it("opens forward dialog with correct event", () => {
const dispatchSpy = jest.spyOn(dispatcher, "dispatch");
const liveBeaconEvent = makeBeaconInfoEvent(aliceId, roomId, { isLive: true });
const beaconLocation = makeBeaconEvent(
aliceId, { beaconInfoId: liveBeaconEvent.getId(), geoUri: 'geo:51,41' },
);
const beaconLocation = makeBeaconEvent(aliceId, {
beaconInfoId: liveBeaconEvent.getId(),
geoUri: "geo:51,41",
});
const beacon = new Beacon(liveBeaconEvent);
// @ts-ignore illegally set private prop
beacon._latestLocationEvent = beaconLocation;
@@ -300,26 +308,28 @@ describe('MessageContextMenu', () => {
const menu = createMenu(liveBeaconEvent, {}, {}, beacons);
act(() => {
menu.find('div[aria-label="Forward"]').simulate('click');
menu.find('div[aria-label="Forward"]').simulate("click");
});
// called with forwardableEvent, not beaconInfo event
expect(dispatchSpy).toHaveBeenCalledWith(expect.objectContaining({
event: beaconLocation,
}));
expect(dispatchSpy).toHaveBeenCalledWith(
expect.objectContaining({
event: beaconLocation,
}),
);
});
});
});
describe('open as map link', () => {
it('does not allow opening a plain message in open street maps', () => {
describe("open as map link", () => {
it("does not allow opening a plain message in open street maps", () => {
const eventContent = MessageEvent.from("hello");
const menu = createMenuWithContent(eventContent);
expect(menu.find('a[aria-label="Open in OpenStreetMap"]')).toHaveLength(0);
});
it('does not allow opening a beacon that does not have a shareable location event', () => {
const deadBeaconEvent = makeBeaconInfoEvent('@alice', roomId, { isLive: false });
it("does not allow opening a beacon that does not have a shareable location event", () => {
const deadBeaconEvent = makeBeaconInfoEvent("@alice", roomId, { isLive: false });
const beacon = new Beacon(deadBeaconEvent);
const beacons = new Map<BeaconIdentifier, Beacon>();
beacons.set(getBeaconInfoIdentifier(deadBeaconEvent), beacon);
@@ -327,20 +337,21 @@ describe('MessageContextMenu', () => {
expect(menu.find('a[aria-label="Open in OpenStreetMap"]')).toHaveLength(0);
});
it('allows opening a location event in open street map', () => {
const locationEvent = makeLocationEvent('geo:50,50');
it("allows opening a location event in open street map", () => {
const locationEvent = makeLocationEvent("geo:50,50");
const menu = createMenu(locationEvent);
// exists with a href with the lat/lon from the location event
expect(
menu.find('a[aria-label="Open in OpenStreetMap"]').at(0).props().href,
).toEqual('https://www.openstreetmap.org/?mlat=50&mlon=50#map=16/50/50');
expect(menu.find('a[aria-label="Open in OpenStreetMap"]').at(0).props().href).toEqual(
"https://www.openstreetmap.org/?mlat=50&mlon=50#map=16/50/50",
);
});
it('allows opening a beacon that has a shareable location event', () => {
const liveBeaconEvent = makeBeaconInfoEvent('@alice', roomId, { isLive: true });
const beaconLocation = makeBeaconEvent(
'@alice', { beaconInfoId: liveBeaconEvent.getId(), geoUri: 'geo:51,41' },
);
it("allows opening a beacon that has a shareable location event", () => {
const liveBeaconEvent = makeBeaconInfoEvent("@alice", roomId, { isLive: true });
const beaconLocation = makeBeaconEvent("@alice", {
beaconInfoId: liveBeaconEvent.getId(),
geoUri: "geo:51,41",
});
const beacon = new Beacon(liveBeaconEvent);
// @ts-ignore illegally set private prop
beacon._latestLocationEvent = beaconLocation;
@@ -348,14 +359,14 @@ describe('MessageContextMenu', () => {
beacons.set(getBeaconInfoIdentifier(liveBeaconEvent), beacon);
const menu = createMenu(liveBeaconEvent, {}, {}, beacons);
// exists with a href with the lat/lon from the location event
expect(
menu.find('a[aria-label="Open in OpenStreetMap"]').at(0).props().href,
).toEqual('https://www.openstreetmap.org/?mlat=51&mlon=41#map=16/51/41');
expect(menu.find('a[aria-label="Open in OpenStreetMap"]').at(0).props().href).toEqual(
"https://www.openstreetmap.org/?mlat=51&mlon=41#map=16/51/41",
);
});
});
describe("right click", () => {
it('copy button does work as expected', () => {
it("copy button does work as expected", () => {
const text = "hello";
const eventContent = MessageEvent.from(text);
mocked(getSelectedText).mockReturnValue(text);
@@ -366,7 +377,7 @@ describe('MessageContextMenu', () => {
expect(copyPlaintext).toHaveBeenCalledWith(text);
});
it('copy button is not shown when there is nothing to copy', () => {
it("copy button is not shown when there is nothing to copy", () => {
const text = "hello";
const eventContent = MessageEvent.from(text);
mocked(getSelectedText).mockReturnValue("");
@@ -376,7 +387,7 @@ describe('MessageContextMenu', () => {
expect(copyButton).toHaveLength(0);
});
it('shows edit button when we can edit', () => {
it("shows edit button when we can edit", () => {
const eventContent = MessageEvent.from("hello");
mocked(canEditContent).mockReturnValue(true);
@@ -385,7 +396,7 @@ describe('MessageContextMenu', () => {
expect(editButton).toHaveLength(1);
});
it('does not show edit button when we cannot edit', () => {
it("does not show edit button when we cannot edit", () => {
const eventContent = MessageEvent.from("hello");
mocked(canEditContent).mockReturnValue(false);
@@ -394,7 +405,7 @@ describe('MessageContextMenu', () => {
expect(editButton).toHaveLength(0);
});
it('shows reply button when we can reply', () => {
it("shows reply button when we can reply", () => {
const eventContent = MessageEvent.from("hello");
const context = {
canSendMessages: true,
@@ -405,7 +416,7 @@ describe('MessageContextMenu', () => {
expect(replyButton).toHaveLength(1);
});
it('does not show reply button when we cannot reply', () => {
it("does not show reply button when we cannot reply", () => {
const eventContent = MessageEvent.from("hello");
const context = {
canSendMessages: true,
@@ -419,7 +430,7 @@ describe('MessageContextMenu', () => {
expect(replyButton).toHaveLength(0);
});
it('shows react button when we can react', () => {
it("shows react button when we can react", () => {
const eventContent = MessageEvent.from("hello");
const context = {
canReact: true,
@@ -430,7 +441,7 @@ describe('MessageContextMenu', () => {
expect(reactButton).toHaveLength(1);
});
it('does not show react button when we cannot react', () => {
it("does not show react button when we cannot react", () => {
const eventContent = MessageEvent.from("hello");
const context = {
canReact: false,
@@ -441,10 +452,10 @@ describe('MessageContextMenu', () => {
expect(reactButton).toHaveLength(0);
});
it('shows view in room button when the event is a thread root', () => {
it("shows view in room button when the event is a thread root", () => {
const eventContent = MessageEvent.from("hello");
const mxEvent = new MatrixEvent(eventContent.serialize());
mxEvent.getThread = () => ({ rootEvent: mxEvent }) as Thread;
mxEvent.getThread = () => ({ rootEvent: mxEvent } as Thread);
const props = {
rightClick: true,
};
@@ -457,7 +468,7 @@ describe('MessageContextMenu', () => {
expect(reactButton).toHaveLength(1);
});
it('does not show view in room button when the event is not a thread root', () => {
it("does not show view in room button when the event is not a thread root", () => {
const eventContent = MessageEvent.from("hello");
const menu = createRightClickMenuWithContent(eventContent);
@@ -465,7 +476,7 @@ describe('MessageContextMenu', () => {
expect(reactButton).toHaveLength(0);
});
it('creates a new thread on reply in thread click', () => {
it("creates a new thread on reply in thread click", () => {
const eventContent = MessageEvent.from("hello");
const mxEvent = new MatrixEvent(eventContent.serialize());
@@ -473,7 +484,7 @@ describe('MessageContextMenu', () => {
const context = {
canSendMessages: true,
};
jest.spyOn(SettingsStore, 'getValue').mockReturnValue(true);
jest.spyOn(SettingsStore, "getValue").mockReturnValue(true);
const menu = createRightClickMenu(mxEvent, context);
@@ -490,10 +501,7 @@ describe('MessageContextMenu', () => {
});
});
function createRightClickMenuWithContent(
eventContent: ExtensibleEvent,
context?: Partial<IRoomState>,
): ReactWrapper {
function createRightClickMenuWithContent(eventContent: ExtensibleEvent, context?: Partial<IRoomState>): ReactWrapper {
return createMenuWithContent(eventContent, { rightClick: true }, context);
}
@@ -511,14 +519,9 @@ function createMenuWithContent(
}
function makeDefaultRoom(): Room {
return new Room(
roomId,
MatrixClientPeg.get(),
"@user:example.com",
{
pendingEventOrdering: PendingEventOrdering.Detached,
},
);
return new Room(roomId, MatrixClientPeg.get(), "@user:example.com", {
pendingEventOrdering: PendingEventOrdering.Detached,
});
}
function createMenu(
@@ -540,12 +543,7 @@ function createMenu(
return mount(
<RoomContext.Provider value={context as IRoomState}>
<MessageContextMenu
chevronFace={null}
mxEvent={mxEvent}
onFinished={jest.fn()}
{...props}
/>
<MessageContextMenu chevronFace={null} mxEvent={mxEvent} onFinished={jest.fn()} {...props} />
</RoomContext.Provider>,
);
}

View File

@@ -14,33 +14,33 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
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 'focus-visible'; // to fix context menus
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 "focus-visible"; // to fix context menus
import SpaceContextMenu from '../../../../src/components/views/context_menus/SpaceContextMenu';
import MatrixClientContext from '../../../../src/contexts/MatrixClientContext';
import { findByTestId } from '../../../test-utils';
import SpaceContextMenu from "../../../../src/components/views/context_menus/SpaceContextMenu";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
import { findByTestId } from "../../../test-utils";
import {
shouldShowSpaceSettings,
showCreateNewRoom,
showCreateNewSubspace,
showSpaceInvite,
showSpaceSettings,
} from '../../../../src/utils/space';
} from "../../../../src/utils/space";
import { leaveSpace } from "../../../../src/utils/leave-behaviour";
import { shouldShowComponent } from '../../../../src/customisations/helpers/UIComponents';
import { UIComponent } from '../../../../src/settings/UIFeature';
import { shouldShowComponent } from "../../../../src/customisations/helpers/UIComponents";
import { UIComponent } from "../../../../src/settings/UIFeature";
jest.mock('../../../../src/customisations/helpers/UIComponents', () => ({
jest.mock("../../../../src/customisations/helpers/UIComponents", () => ({
shouldShowComponent: jest.fn(),
}));
jest.mock('../../../../src/utils/space', () => ({
jest.mock("../../../../src/utils/space", () => ({
shouldShowSpaceSettings: jest.fn(),
showCreateNewRoom: jest.fn(),
showCreateNewSubspace: jest.fn(),
@@ -49,172 +49,172 @@ jest.mock('../../../../src/utils/space', () => ({
showSpaceSettings: jest.fn(),
}));
jest.mock('../../../../src/utils/leave-behaviour', () => ({
jest.mock("../../../../src/utils/leave-behaviour", () => ({
leaveSpace: jest.fn(),
}));
describe('<SpaceContextMenu />', () => {
const userId = '@test:server';
describe("<SpaceContextMenu />", () => {
const userId = "@test:server";
const mockClient = {
getUserId: jest.fn().mockReturnValue(userId),
};
const makeMockSpace = (props = {}) => ({
name: 'test space',
getJoinRule: jest.fn(),
canInvite: jest.fn(),
currentState: {
maySendStateEvent: jest.fn(),
},
client: mockClient,
getMyMembership: jest.fn(),
...props,
}) as unknown as Room;
const makeMockSpace = (props = {}) =>
({
name: "test space",
getJoinRule: jest.fn(),
canInvite: jest.fn(),
currentState: {
maySendStateEvent: jest.fn(),
},
client: mockClient,
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,
},
});
mount(<SpaceContextMenu {...defaultProps} {...props} />, {
wrappingComponent: MatrixClientContext.Provider,
wrappingComponentProps: {
value: mockClient,
},
});
beforeEach(() => {
jest.resetAllMocks();
mockClient.getUserId.mockReturnValue(userId);
});
it('renders menu correctly', () => {
it("renders menu correctly", () => {
const component = getComponent();
expect(component).toMatchSnapshot();
});
it('renders invite option when space is public', () => {
it("renders invite option when space is public", () => {
const space = makeMockSpace({
getJoinRule: jest.fn().mockReturnValue('public'),
getJoinRule: jest.fn().mockReturnValue("public"),
});
const component = getComponent({ space });
expect(findByTestId(component, 'invite-option').length).toBeTruthy();
expect(findByTestId(component, "invite-option").length).toBeTruthy();
});
it('renders invite option when user is has invite rights for space', () => {
it("renders invite option when user is has invite rights for space", () => {
const space = makeMockSpace({
canInvite: jest.fn().mockReturnValue(true),
});
const component = getComponent({ space });
expect(space.canInvite).toHaveBeenCalledWith(userId);
expect(findByTestId(component, 'invite-option').length).toBeTruthy();
expect(findByTestId(component, "invite-option").length).toBeTruthy();
});
it('opens invite dialog when invite option is clicked', () => {
it("opens invite dialog when invite option is clicked", () => {
const space = makeMockSpace({
getJoinRule: jest.fn().mockReturnValue('public'),
getJoinRule: jest.fn().mockReturnValue("public"),
});
const onFinished = jest.fn();
const component = getComponent({ space, onFinished });
act(() => {
findByTestId(component, 'invite-option').at(0).simulate('click');
findByTestId(component, "invite-option").at(0).simulate("click");
});
expect(showSpaceInvite).toHaveBeenCalledWith(space);
expect(onFinished).toHaveBeenCalled();
});
it('renders space settings option when user has rights', () => {
it("renders space settings option when user has rights", () => {
mocked(shouldShowSpaceSettings).mockReturnValue(true);
const component = getComponent();
expect(shouldShowSpaceSettings).toHaveBeenCalledWith(defaultProps.space);
expect(findByTestId(component, 'settings-option').length).toBeTruthy();
expect(findByTestId(component, "settings-option").length).toBeTruthy();
});
it('opens space settings when space settings option is clicked', () => {
it("opens space settings when space settings option is clicked", () => {
mocked(shouldShowSpaceSettings).mockReturnValue(true);
const onFinished = jest.fn();
const component = getComponent({ onFinished });
act(() => {
findByTestId(component, 'settings-option').at(0).simulate('click');
findByTestId(component, "settings-option").at(0).simulate("click");
});
expect(showSpaceSettings).toHaveBeenCalledWith(defaultProps.space);
expect(onFinished).toHaveBeenCalled();
});
it('renders leave option when user does not have rights to see space settings', () => {
it("renders leave option when user does not have rights to see space settings", () => {
const component = getComponent();
expect(findByTestId(component, 'leave-option').length).toBeTruthy();
expect(findByTestId(component, "leave-option").length).toBeTruthy();
});
it('leaves space when leave option is clicked', () => {
it("leaves space when leave option is clicked", () => {
const onFinished = jest.fn();
const component = getComponent({ onFinished });
act(() => {
findByTestId(component, 'leave-option').at(0).simulate('click');
findByTestId(component, "leave-option").at(0).simulate("click");
});
expect(leaveSpace).toHaveBeenCalledWith(defaultProps.space);
expect(onFinished).toHaveBeenCalled();
});
describe('add children section', () => {
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', () => {
it("does not render section when user does not have permission to add children", () => {
mocked(space.currentState.maySendStateEvent).mockReturnValue(false);
const component = getComponent({ 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(findByTestId(component, "add-to-space-header").length).toBeFalsy();
expect(findByTestId(component, "new-room-option").length).toBeFalsy();
expect(findByTestId(component, "new-subspace-option").length).toBeFalsy();
});
it('does not render section when UIComponent customisations disable room and space creation', () => {
it("does not render section when UIComponent customisations disable room and space creation", () => {
mocked(shouldShowComponent).mockReturnValue(false);
const component = getComponent({ 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(findByTestId(component, "add-to-space-header").length).toBeFalsy();
expect(findByTestId(component, "new-room-option").length).toBeFalsy();
expect(findByTestId(component, "new-subspace-option").length).toBeFalsy();
});
it('renders section with add room button when UIComponent customisation allows CreateRoom', () => {
it("renders section with add room button when UIComponent customisation allows CreateRoom", () => {
// only allow CreateRoom
mocked(shouldShowComponent).mockImplementation(feature => feature === UIComponent.CreateRooms);
mocked(shouldShowComponent).mockImplementation((feature) => feature === UIComponent.CreateRooms);
const component = getComponent({ 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(findByTestId(component, "add-to-space-header").length).toBeTruthy();
expect(findByTestId(component, "new-room-option").length).toBeTruthy();
expect(findByTestId(component, "new-subspace-option").length).toBeFalsy();
});
it('renders section with add space button when UIComponent customisation allows CreateSpace', () => {
it("renders section with add space button when UIComponent customisation allows CreateSpace", () => {
// only allow CreateSpaces
mocked(shouldShowComponent).mockImplementation(feature => feature === UIComponent.CreateSpaces);
mocked(shouldShowComponent).mockImplementation((feature) => feature === UIComponent.CreateSpaces);
const component = getComponent({ 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(findByTestId(component, "add-to-space-header").length).toBeTruthy();
expect(findByTestId(component, "new-room-option").length).toBeFalsy();
expect(findByTestId(component, "new-subspace-option").length).toBeTruthy();
});
it('opens create room dialog on add room button click', () => {
it("opens create room dialog on add room button click", () => {
const onFinished = jest.fn();
const component = getComponent({ space, onFinished });
act(() => {
findByTestId(component, 'new-room-option').at(0).simulate('click');
findByTestId(component, "new-room-option").at(0).simulate("click");
});
expect(showCreateNewRoom).toHaveBeenCalledWith(space);
expect(onFinished).toHaveBeenCalled();
});
it('opens create space dialog on add space button click', () => {
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');
findByTestId(component, "new-subspace-option").at(0).simulate("click");
});
expect(showCreateNewSubspace).toHaveBeenCalledWith(space);
expect(onFinished).toHaveBeenCalled();

View File

@@ -38,10 +38,7 @@ describe("ThreadListContextMenu", () => {
let event: MatrixEvent;
function getComponent(props: Partial<ThreadListContextMenuProps>) {
return render(<ThreadListContextMenu
mxEvent={event}
{...props}
/>);
return render(<ThreadListContextMenu mxEvent={event} {...props} />);
}
beforeEach(() => {

View File

@@ -14,14 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
// eslint-disable-next-line deprecate/import
import { mount, ReactWrapper } from 'enzyme';
import { act } from 'react-dom/test-utils';
import { IPassphraseInfo } from 'matrix-js-sdk/src/crypto/api';
import { mount, ReactWrapper } from "enzyme";
import { act } from "react-dom/test-utils";
import { IPassphraseInfo } from "matrix-js-sdk/src/crypto/api";
import { findByAttr, getMockClientWithEventEmitter, unmockClientPeg } from '../../../test-utils';
import { findById, flushPromises } from '../../../test-utils';
import { findByAttr, getMockClientWithEventEmitter, unmockClientPeg } from "../../../test-utils";
import { findById, flushPromises } from "../../../test-utils";
import AccessSecretStorageDialog from "../../../../src/components/views/dialogs/security/AccessSecretStorageDialog";
describe("AccessSecretStorageDialog", () => {
@@ -35,12 +35,12 @@ describe("AccessSecretStorageDialog", () => {
checkPrivateKey: jest.fn(),
keyInfo: undefined,
};
const getComponent = (props ={}): ReactWrapper =>
const getComponent = (props = {}): ReactWrapper =>
mount(<AccessSecretStorageDialog {...defaultProps} {...props} />);
beforeEach(() => {
jest.clearAllMocks();
mockClient.keyBackupKeyFromRecoveryKey.mockReturnValue('a raw key' as unknown as Uint8Array);
mockClient.keyBackupKeyFromRecoveryKey.mockReturnValue("a raw key" as unknown as Uint8Array);
mockClient.isValidRecoveryKey.mockReturnValue(false);
});
@@ -63,7 +63,7 @@ describe("AccessSecretStorageDialog", () => {
const e = { preventDefault: () => {} };
act(() => {
wrapper.find('form').simulate('submit', e);
wrapper.find("form").simulate("submit", e);
});
await flushPromises();
@@ -75,13 +75,13 @@ describe("AccessSecretStorageDialog", () => {
it("Considers a valid key to be valid", async () => {
const checkPrivateKey = jest.fn().mockResolvedValue(true);
const wrapper = getComponent({ checkPrivateKey });
mockClient.keyBackupKeyFromRecoveryKey.mockReturnValue('a raw key' as unknown as Uint8Array);
mockClient.keyBackupKeyFromRecoveryKey.mockReturnValue("a raw key" as unknown as Uint8Array);
mockClient.checkSecretStorageKey.mockResolvedValue(true);
const v = "asdf";
const e = { target: { value: v } };
act(() => {
findById(wrapper, 'mx_securityKey').find('input').simulate('change', e);
findById(wrapper, "mx_securityKey").find("input").simulate("change", e);
wrapper.setProps({});
});
await act(async () => {
@@ -91,10 +91,10 @@ describe("AccessSecretStorageDialog", () => {
wrapper.setProps({});
});
const submitButton = findByAttr('data-testid')(wrapper, 'dialog-primary-button').at(0);
const submitButton = findByAttr("data-testid")(wrapper, "dialog-primary-button").at(0);
// submit button is enabled when key is valid
expect(submitButton.props().disabled).toBeFalsy();
expect(wrapper.find('.mx_AccessSecretStorageDialog_recoveryKeyFeedback').text()).toEqual('Looks good!');
expect(wrapper.find(".mx_AccessSecretStorageDialog_recoveryKeyFeedback").text()).toEqual("Looks good!");
});
it("Notifies the user if they input an invalid Security Key", async () => {
@@ -106,36 +106,36 @@ describe("AccessSecretStorageDialog", () => {
});
act(() => {
findById(wrapper, 'mx_securityKey').find('input').simulate('change', e);
findById(wrapper, "mx_securityKey").find("input").simulate("change", e);
});
// force a validation now because it debounces
// @ts-ignore private
await wrapper.instance().validateRecoveryKey();
const submitButton = findByAttr('data-testid')(wrapper, 'dialog-primary-button').at(0);
const submitButton = findByAttr("data-testid")(wrapper, "dialog-primary-button").at(0);
// submit button is disabled when recovery key is invalid
expect(submitButton.props().disabled).toBeTruthy();
expect(
wrapper.find('.mx_AccessSecretStorageDialog_recoveryKeyFeedback').text(),
).toEqual('Invalid Security Key');
expect(wrapper.find(".mx_AccessSecretStorageDialog_recoveryKeyFeedback").text()).toEqual(
"Invalid Security Key",
);
wrapper.setProps({});
const notification = wrapper.find(".mx_AccessSecretStorageDialog_recoveryKeyFeedback");
expect(notification.props().children).toEqual("Invalid Security Key");
});
it("Notifies the user if they input an invalid passphrase", async function() {
it("Notifies the user if they input an invalid passphrase", async function () {
const keyInfo = {
name: 'test',
algorithm: 'test',
iv: 'test',
mac: '1:2:3:4',
name: "test",
algorithm: "test",
iv: "test",
mac: "1:2:3:4",
passphrase: {
// this type is weird in js-sdk
// cast 'm.pbkdf2' to itself
algorithm: 'm.pbkdf2' as IPassphraseInfo['algorithm'],
algorithm: "m.pbkdf2" as IPassphraseInfo["algorithm"],
iterations: 2,
salt: 'nonempty',
salt: "nonempty",
},
};
const checkPrivateKey = jest.fn().mockResolvedValue(false);
@@ -145,23 +145,24 @@ describe("AccessSecretStorageDialog", () => {
// update passphrase
act(() => {
const e = { target: { value: "a" } };
findById(wrapper, 'mx_passPhraseInput').at(1).simulate('change', e);
findById(wrapper, "mx_passPhraseInput").at(1).simulate("change", e);
});
wrapper.setProps({});
// input updated
expect(findById(wrapper, 'mx_passPhraseInput').at(0).props().value).toEqual('a');
expect(findById(wrapper, "mx_passPhraseInput").at(0).props().value).toEqual("a");
// submit the form
act(() => {
wrapper.find('form').at(0).simulate('submit');
wrapper.find("form").at(0).simulate("submit");
});
await flushPromises();
wrapper.setProps({});
const notification = wrapper.find(".mx_AccessSecretStorageDialog_keyStatus");
expect(notification.props().children).toEqual(
["\uD83D\uDC4E ", "Unable to access secret storage. Please verify that you " +
"entered the correct Security Phrase."]);
expect(notification.props().children).toEqual([
"\uD83D\uDC4E ",
"Unable to access secret storage. Please verify that you " + "entered the correct Security Phrase.",
]);
});
});

View File

@@ -35,11 +35,13 @@ describe("<ChangelogDialog />", () => {
ahead_by: 24,
behind_by: 0,
total_commits: 24,
commits: [{
sha: "commit-sha",
html_url: "https://api.github.com/repos/vector-im/element-web/commit/commit-sha",
commit: { message: "This is the first commit message" },
}],
commits: [
{
sha: "commit-sha",
html_url: "https://api.github.com/repos/vector-im/element-web/commit/commit-sha",
commit: { message: "This is the first commit message" },
},
],
files: [],
});
const reactUrl = "https://riot.im/github/repos/matrix-org/matrix-react-sdk/compare/oldsha2...newsha2";
@@ -55,11 +57,13 @@ describe("<ChangelogDialog />", () => {
ahead_by: 83,
behind_by: 0,
total_commits: 83,
commits: [{
sha: "commit-sha0",
html_url: "https://api.github.com/repos/matrix-org/matrix-react-sdk/commit/commit-sha",
commit: { message: "This is a commit message" },
}],
commits: [
{
sha: "commit-sha0",
html_url: "https://api.github.com/repos/matrix-org/matrix-react-sdk/commit/commit-sha",
commit: { message: "This is a commit message" },
},
],
files: [],
});
const jsUrl = "https://riot.im/github/repos/matrix-org/matrix-js-sdk/compare/oldsha3...newsha3";
@@ -75,23 +79,26 @@ describe("<ChangelogDialog />", () => {
ahead_by: 48,
behind_by: 0,
total_commits: 48,
commits: [{
sha: "commit-sha1",
html_url: "https://api.github.com/repos/matrix-org/matrix-js-sdk/commit/commit-sha1",
commit: { message: "This is a commit message" },
}, {
sha: "commit-sha2",
html_url: "https://api.github.com/repos/matrix-org/matrix-js-sdk/commit/commit-sha2",
commit: { message: "This is another commit message" },
}],
commits: [
{
sha: "commit-sha1",
html_url: "https://api.github.com/repos/matrix-org/matrix-js-sdk/commit/commit-sha1",
commit: { message: "This is a commit message" },
},
{
sha: "commit-sha2",
html_url: "https://api.github.com/repos/matrix-org/matrix-js-sdk/commit/commit-sha2",
commit: { message: "This is another commit message" },
},
],
files: [],
});
const newVersion = "newsha1-react-newsha2-js-newsha3";
const oldVersion = "oldsha1-react-oldsha2-js-oldsha3";
const { asFragment } = render((
<ChangelogDialog newVersion={newVersion} version={oldVersion} onFinished={jest.fn()} />
));
const { asFragment } = render(
<ChangelogDialog newVersion={newVersion} version={oldVersion} onFinished={jest.fn()} />,
);
// Wait for spinners to go away
await waitForElementToBeRemoved(screen.getAllByRole("progressbar"));

View File

@@ -14,33 +14,33 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
// eslint-disable-next-line deprecate/import
import { mount } from 'enzyme';
import { mocked } from 'jest-mock';
import { mount } from "enzyme";
import { mocked } from "jest-mock";
import { act } from "react-dom/test-utils";
import { Room } from 'matrix-js-sdk/src/matrix';
import { Room } from "matrix-js-sdk/src/matrix";
import ExportDialog from '../../../../src/components/views/dialogs/ExportDialog';
import { ExportType, ExportFormat } from '../../../../src/utils/exportUtils/exportUtils';
import { createTestClient, mkStubRoom } from '../../../test-utils';
import { MatrixClientPeg } from '../../../../src/MatrixClientPeg';
import ExportDialog from "../../../../src/components/views/dialogs/ExportDialog";
import { ExportType, ExportFormat } from "../../../../src/utils/exportUtils/exportUtils";
import { createTestClient, mkStubRoom } from "../../../test-utils";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import HTMLExporter from "../../../../src/utils/exportUtils/HtmlExport";
import ChatExport from '../../../../src/customisations/ChatExport';
import PlainTextExporter from '../../../../src/utils/exportUtils/PlainTextExport';
import ChatExport from "../../../../src/customisations/ChatExport";
import PlainTextExporter from "../../../../src/utils/exportUtils/PlainTextExport";
jest.useFakeTimers();
const htmlExporterInstance = ({
const htmlExporterInstance = {
export: jest.fn().mockResolvedValue({}),
});
const plainTextExporterInstance = ({
};
const plainTextExporterInstance = {
export: jest.fn().mockResolvedValue({}),
});
};
jest.mock("../../../../src/utils/exportUtils/HtmlExport", () => jest.fn());
jest.mock("../../../../src/utils/exportUtils/PlainTextExport", () => jest.fn());
jest.mock('../../../../src/customisations/ChatExport', () => ({
jest.mock("../../../../src/customisations/ChatExport", () => ({
getForceChatExportParameters: jest.fn().mockReturnValue({}),
}));
@@ -48,13 +48,13 @@ const ChatExportMock = mocked(ChatExport);
const HTMLExporterMock = mocked(HTMLExporter);
const PlainTextExporterMock = mocked(PlainTextExporter);
describe('<ExportDialog />', () => {
describe("<ExportDialog />", () => {
const mockClient = createTestClient();
jest.spyOn(MatrixClientPeg, 'get').mockReturnValue(mockClient);
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient);
const roomId = 'test:test.org';
const roomId = "test:test.org";
const defaultProps = {
room: mkStubRoom(roomId, 'test', mockClient) as unknown as Room,
room: mkStubRoom(roomId, "test", mockClient) as unknown as Room,
onFinished: jest.fn(),
};
@@ -68,32 +68,38 @@ describe('<ExportDialog />', () => {
const getPrimaryButton = (component) => component.find('[data-testid="dialog-primary-button"]');
const getSecondaryButton = (component) => component.find('[data-testid="dialog-cancel-button"]');
const submitForm = async (component) => act(async () => {
getPrimaryButton(component).simulate('click');
component.setProps({});
});
const selectExportFormat = async (component, format: ExportFormat) => act(async () => {
getExportFormatInput(component, format).simulate('change');
component.setProps({});
});
const selectExportType = async (component, type: ExportType) => act(async () => {
getExportTypeInput(component).simulate('change', { target: { value: type } });
component.setProps({});
});
const setMessageCount = async (component, count: number) => act(async () => {
getMessageCountInput(component).simulate('change', { target: { value: count } });
component.setProps({});
});
const submitForm = async (component) =>
act(async () => {
getPrimaryButton(component).simulate("click");
component.setProps({});
});
const selectExportFormat = async (component, format: ExportFormat) =>
act(async () => {
getExportFormatInput(component, format).simulate("change");
component.setProps({});
});
const selectExportType = async (component, type: ExportType) =>
act(async () => {
getExportTypeInput(component).simulate("change", { target: { value: type } });
component.setProps({});
});
const setMessageCount = async (component, count: number) =>
act(async () => {
getMessageCountInput(component).simulate("change", { target: { value: count } });
component.setProps({});
});
const setSizeLimit = async (component, limit: number) => act(async () => {
getSizeInput(component).simulate('change', { target: { value: limit } });
component.setProps({});
});
const setSizeLimit = async (component, limit: number) =>
act(async () => {
getSizeInput(component).simulate("change", { target: { value: limit } });
component.setProps({});
});
const setIncludeAttachments = async (component, checked) => act(async () => {
getAttachmentsCheckbox(component).simulate('change', { target: { checked } });
component.setProps({});
});
const setIncludeAttachments = async (component, checked) =>
act(async () => {
getAttachmentsCheckbox(component).simulate("change", { target: { checked } });
component.setProps({});
});
beforeEach(() => {
HTMLExporterMock.mockClear().mockImplementation(jest.fn().mockReturnValue(htmlExporterInstance));
@@ -105,21 +111,21 @@ describe('<ExportDialog />', () => {
ChatExportMock.getForceChatExportParameters.mockClear().mockReturnValue({});
});
it('renders export dialog', () => {
it("renders export dialog", () => {
const component = getComponent();
expect(component.find('.mx_ExportDialog')).toMatchSnapshot();
expect(component.find(".mx_ExportDialog")).toMatchSnapshot();
});
it('calls onFinished when cancel button is clicked', () => {
it("calls onFinished when cancel button is clicked", () => {
const onFinished = jest.fn();
const component = getComponent({ onFinished });
act(() => {
getSecondaryButton(component).simulate('click');
getSecondaryButton(component).simulate("click");
});
expect(onFinished).toHaveBeenCalledWith(false);
});
it('exports room on submit', async () => {
it("exports room on submit", async () => {
const component = getComponent();
await submitForm(component);
@@ -137,7 +143,7 @@ describe('<ExportDialog />', () => {
expect(htmlExporterInstance.export).toHaveBeenCalled();
});
it('exports room using values set from ForceRoomExportParameters', async () => {
it("exports room using values set from ForceRoomExportParameters", async () => {
ChatExportMock.getForceChatExportParameters.mockReturnValue({
format: ExportFormat.PlainText,
range: ExportType.Beginning,
@@ -162,35 +168,35 @@ describe('<ExportDialog />', () => {
expect(plainTextExporterInstance.export).toHaveBeenCalled();
});
it('renders success screen when export is finished', async () => {
it("renders success screen when export is finished", async () => {
const component = getComponent();
await submitForm(component);
component.setProps({});
jest.runAllTimers();
expect(component.find('.mx_InfoDialog .mx_Dialog_content')).toMatchSnapshot();
expect(component.find(".mx_InfoDialog .mx_Dialog_content")).toMatchSnapshot();
});
describe('export format', () => {
it('renders export format with html selected by default', () => {
describe("export format", () => {
it("renders export format with html selected by default", () => {
const component = getComponent();
expect(getExportFormatInput(component, ExportFormat.Html).props().checked).toBeTruthy();
});
it('sets export format on radio button click', async () => {
it("sets export format on radio button click", async () => {
const component = getComponent();
await selectExportFormat(component, ExportFormat.PlainText);
expect(getExportFormatInput(component, ExportFormat.PlainText).props().checked).toBeTruthy();
expect(getExportFormatInput(component, ExportFormat.Html).props().checked).toBeFalsy();
});
it('hides export format input when format is valid in ForceRoomExportParameters', () => {
it("hides export format input when format is valid in ForceRoomExportParameters", () => {
const component = getComponent();
expect(getExportFormatInput(component, ExportFormat.Html).props().checked).toBeTruthy();
});
it('does not render export format when set in ForceRoomExportParameters', () => {
it("does not render export format when set in ForceRoomExportParameters", () => {
ChatExportMock.getForceChatExportParameters.mockReturnValue({
format: ExportFormat.PlainText,
});
@@ -199,19 +205,19 @@ describe('<ExportDialog />', () => {
});
});
describe('export type', () => {
it('renders export type with timeline selected by default', () => {
describe("export type", () => {
it("renders export type with timeline selected by default", () => {
const component = getComponent();
expect(getExportTypeInput(component).props().value).toEqual(ExportType.Timeline);
});
it('sets export type on change', async () => {
it("sets export type on change", async () => {
const component = getComponent();
await selectExportType(component, ExportType.Beginning);
expect(getExportTypeInput(component).props().value).toEqual(ExportType.Beginning);
});
it('does not render export type when set in ForceRoomExportParameters', () => {
it("does not render export type when set in ForceRoomExportParameters", () => {
ChatExportMock.getForceChatExportParameters.mockReturnValue({
range: ExportType.Beginning,
});
@@ -219,25 +225,25 @@ describe('<ExportDialog />', () => {
expect(getExportTypeInput(component).length).toBeFalsy();
});
it('does not render message count input', async () => {
it("does not render message count input", async () => {
const component = getComponent();
expect(getMessageCountInput(component).length).toBeFalsy();
});
it('renders message count input with default value 100 when export type is lastNMessages', async () => {
it("renders message count input with default value 100 when export type is lastNMessages", async () => {
const component = getComponent();
await selectExportType(component, ExportType.LastNMessages);
expect(getMessageCountInput(component).props().value).toEqual("100");
});
it('sets message count on change', async () => {
it("sets message count on change", async () => {
const component = getComponent();
await selectExportType(component, ExportType.LastNMessages);
await setMessageCount(component, 10);
expect(getMessageCountInput(component).props().value).toEqual("10");
});
it('does not export when export type is lastNMessages and message count is falsy', async () => {
it("does not export when export type is lastNMessages and message count is falsy", async () => {
const component = getComponent();
await selectExportType(component, ExportType.LastNMessages);
await setMessageCount(component, 0);
@@ -246,7 +252,7 @@ describe('<ExportDialog />', () => {
expect(htmlExporterInstance.export).not.toHaveBeenCalled();
});
it('does not export when export type is lastNMessages and message count is more than max', async () => {
it("does not export when export type is lastNMessages and message count is more than max", async () => {
const component = getComponent();
await selectExportType(component, ExportType.LastNMessages);
await setMessageCount(component, 99999999999);
@@ -255,7 +261,7 @@ describe('<ExportDialog />', () => {
expect(htmlExporterInstance.export).not.toHaveBeenCalled();
});
it('exports when export type is NOT lastNMessages and message count is falsy', async () => {
it("exports when export type is NOT lastNMessages and message count is falsy", async () => {
const component = getComponent();
await selectExportType(component, ExportType.LastNMessages);
await setMessageCount(component, 0);
@@ -266,19 +272,19 @@ describe('<ExportDialog />', () => {
});
});
describe('size limit', () => {
it('renders size limit input with default value', () => {
describe("size limit", () => {
it("renders size limit input with default value", () => {
const component = getComponent();
expect(getSizeInput(component).props().value).toEqual("8");
});
it('updates size limit on change', async () => {
it("updates size limit on change", async () => {
const component = getComponent();
await setSizeLimit(component, 20);
expect(getSizeInput(component).props().value).toEqual("20");
});
it('does not export when size limit is falsy', async () => {
it("does not export when size limit is falsy", async () => {
const component = getComponent();
await setSizeLimit(component, 0);
await submitForm(component);
@@ -286,7 +292,7 @@ describe('<ExportDialog />', () => {
expect(htmlExporterInstance.export).not.toHaveBeenCalled();
});
it('does not export when size limit is larger than max', async () => {
it("does not export when size limit is larger than max", async () => {
const component = getComponent();
await setSizeLimit(component, 2001);
await submitForm(component);
@@ -294,7 +300,7 @@ describe('<ExportDialog />', () => {
expect(htmlExporterInstance.export).not.toHaveBeenCalled();
});
it('exports when size limit is max', async () => {
it("exports when size limit is max", async () => {
const component = getComponent();
await setSizeLimit(component, 2000);
await submitForm(component);
@@ -302,7 +308,7 @@ describe('<ExportDialog />', () => {
expect(htmlExporterInstance.export).toHaveBeenCalled();
});
it('does not render size limit input when set in ForceRoomExportParameters', () => {
it("does not render size limit input when set in ForceRoomExportParameters", () => {
ChatExportMock.getForceChatExportParameters.mockReturnValue({
sizeMb: 10000,
});
@@ -313,7 +319,7 @@ describe('<ExportDialog />', () => {
/**
* 2000mb size limit does not apply when higher limit is configured in config
*/
it('exports when size limit set in ForceRoomExportParameters is larger than 2000', async () => {
it("exports when size limit set in ForceRoomExportParameters is larger than 2000", async () => {
ChatExportMock.getForceChatExportParameters.mockReturnValue({
sizeMb: 10000,
});
@@ -324,19 +330,19 @@ describe('<ExportDialog />', () => {
});
});
describe('include attachments', () => {
it('renders input with default value of false', () => {
describe("include attachments", () => {
it("renders input with default value of false", () => {
const component = getComponent();
expect(getAttachmentsCheckbox(component).props().checked).toEqual(false);
});
it('updates include attachments on change', async () => {
it("updates include attachments on change", async () => {
const component = getComponent();
await setIncludeAttachments(component, true);
expect(getAttachmentsCheckbox(component).props().checked).toEqual(true);
});
it('does not render input when set in ForceRoomExportParameters', () => {
it("does not render input when set in ForceRoomExportParameters", () => {
ChatExportMock.getForceChatExportParameters.mockReturnValue({
includeAttachments: false,
});
@@ -345,4 +351,3 @@ describe('<ExportDialog />', () => {
});
});
});

View File

@@ -59,22 +59,22 @@ describe("ForwardDialog", () => {
getRoom: jest.fn(),
getAccountData: jest.fn().mockReturnValue(accountDataEvent),
getPushActionsForEvent: jest.fn(),
mxcUrlToHttp: jest.fn().mockReturnValue(''),
mxcUrlToHttp: jest.fn().mockReturnValue(""),
isRoomEncrypted: jest.fn().mockReturnValue(false),
getProfileInfo: jest.fn().mockResolvedValue({
displayname: 'Alice',
displayname: "Alice",
}),
decryptEventIfNeeded: jest.fn(),
sendEvent: jest.fn(),
getClientWellKnown: jest.fn().mockReturnValue({
[TILE_SERVER_WK_KEY.name]: { map_style_url: 'maps.com' },
[TILE_SERVER_WK_KEY.name]: { map_style_url: "maps.com" },
}),
});
const defaultRooms = ["a", "A", "b"].map(name => mkStubRoom(name, name, mockClient));
const defaultRooms = ["a", "A", "b"].map((name) => mkStubRoom(name, name, mockClient));
const mountForwardDialog = (message = defaultMessage, rooms = defaultRooms) => {
mockClient.getVisibleRooms.mockReturnValue(rooms);
mockClient.getRoom.mockImplementation(roomId => rooms.find(room => room.roomId === roomId));
mockClient.getRoom.mockImplementation((roomId) => rooms.find((room) => room.roomId === roomId));
const wrapper: RenderResult = render(
<ForwardDialog
@@ -96,7 +96,7 @@ describe("ForwardDialog", () => {
});
afterAll(() => {
jest.spyOn(MatrixClientPeg, 'get').mockRestore();
jest.spyOn(MatrixClientPeg, "get").mockRestore();
});
it("shows a preview with us as the sender", async () => {
@@ -127,10 +127,13 @@ describe("ForwardDialog", () => {
// Make sendEvent require manual resolution so we can see the sending state
let finishSend;
let cancelSend;
mockClient.sendEvent.mockImplementation(<T extends {}>() => new Promise<T>((resolve, reject) => {
finishSend = resolve;
cancelSend = reject;
}));
mockClient.sendEvent.mockImplementation(
<T extends {}>() =>
new Promise<T>((resolve, reject) => {
finishSend = resolve;
cancelSend = reject;
}),
);
let firstButton;
let secondButton;
@@ -141,28 +144,32 @@ describe("ForwardDialog", () => {
expect(firstButton.className).toContain("mx_ForwardList_canSend");
act(() => { fireEvent.click(firstButton); });
act(() => {
fireEvent.click(firstButton);
});
update();
expect(firstButton.className).toContain("mx_ForwardList_sending");
await act(async () => {
cancelSend();
// Wait one tick for the button to realize the send failed
await new Promise(resolve => setImmediate(resolve));
await new Promise((resolve) => setImmediate(resolve));
});
update();
expect(firstButton.className).toContain("mx_ForwardList_sendFailed");
expect(secondButton.className).toContain("mx_ForwardList_canSend");
act(() => { fireEvent.click(secondButton); });
act(() => {
fireEvent.click(secondButton);
});
update();
expect(secondButton.className).toContain("mx_ForwardList_sending");
await act(async () => {
finishSend();
// Wait one tick for the button to realize the send succeeded
await new Promise(resolve => setImmediate(resolve));
await new Promise((resolve) => setImmediate(resolve));
});
update();
expect(secondButton.className).toContain("mx_ForwardList_sent");
@@ -203,7 +210,7 @@ describe("ForwardDialog", () => {
expect(secondButton.getAttribute("aria-disabled")).toBeFalsy();
});
describe('Location events', () => {
describe("Location events", () => {
// 14.03.2022 16:15
const now = 1647270879403;
const roomId = "a";
@@ -215,11 +222,11 @@ describe("ForwardDialog", () => {
beforeEach(() => {
// legacy events will default timestamp to Date.now()
// mock a stable now for easy assertion
jest.spyOn(Date, 'now').mockReturnValue(now);
jest.spyOn(Date, "now").mockReturnValue(now);
});
afterAll(() => {
jest.spyOn(Date, 'now').mockRestore();
jest.spyOn(Date, "now").mockRestore();
});
const sendToFirstRoom = (container: HTMLElement): void =>
@@ -228,7 +235,7 @@ describe("ForwardDialog", () => {
fireEvent.click(sendToFirstRoomButton!);
});
it('converts legacy location events to pin drop shares', async () => {
it("converts legacy location events to pin drop shares", async () => {
const { container } = mountForwardDialog(legacyLocationEvent);
expect(container.querySelector(".mx_MLocationBody")).toBeTruthy();
@@ -250,11 +257,13 @@ describe("ForwardDialog", () => {
},
};
expect(mockClient.sendEvent).toHaveBeenCalledWith(
roomId, legacyLocationEvent.getType(), expectedStrippedContent,
roomId,
legacyLocationEvent.getType(),
expectedStrippedContent,
);
});
it('removes personal information from static self location shares', async () => {
it("removes personal information from static self location shares", async () => {
const { container } = mountForwardDialog(modernLocationEvent);
expect(container.querySelector(".mx_MLocationBody")).toBeTruthy();
@@ -275,13 +284,15 @@ describe("ForwardDialog", () => {
},
};
expect(mockClient.sendEvent).toHaveBeenCalledWith(
roomId, modernLocationEvent.getType(), expectedStrippedContent,
roomId,
modernLocationEvent.getType(),
expectedStrippedContent,
);
});
it('forwards beacon location as a pin drop event', async () => {
it("forwards beacon location as a pin drop event", async () => {
const timestamp = 123456;
const beaconEvent = makeBeaconEvent('@alice:server.org', { geoUri, timestamp });
const beaconEvent = makeBeaconEvent("@alice:server.org", { geoUri, timestamp });
const text = `Location ${geoUri} at ${new Date(timestamp).toISOString()}`;
const expectedContent = {
msgtype: "m.location",
@@ -301,12 +312,10 @@ describe("ForwardDialog", () => {
sendToFirstRoom(container);
expect(mockClient.sendEvent).toHaveBeenCalledWith(
roomId, EventType.RoomMessage, expectedContent,
);
expect(mockClient.sendEvent).toHaveBeenCalledWith(roomId, EventType.RoomMessage, expectedContent);
});
it('forwards pin drop event', async () => {
it("forwards pin drop event", async () => {
const { container } = mountForwardDialog(pinDropLocationEvent);
expect(container.querySelector(".mx_MLocationBody")).toBeTruthy();
@@ -314,7 +323,9 @@ describe("ForwardDialog", () => {
sendToFirstRoom(container);
expect(mockClient.sendEvent).toHaveBeenCalledWith(
roomId, pinDropLocationEvent.getType(), pinDropLocationEvent.getContent(),
roomId,
pinDropLocationEvent.getType(),
pinDropLocationEvent.getContent(),
);
});
});

View File

@@ -15,17 +15,17 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { act } from 'react-dom/test-utils';
import React from "react";
import { act } from "react-dom/test-utils";
// eslint-disable-next-line deprecate/import
import { mount, ReactWrapper } from 'enzyme';
import { mount, ReactWrapper } from "enzyme";
import InteractiveAuthDialog from "../../../../src/components/views/dialogs/InteractiveAuthDialog";
import { flushPromises, getMockClientWithEventEmitter, unmockClientPeg } from '../../../test-utils';
import { flushPromises, getMockClientWithEventEmitter, unmockClientPeg } from "../../../test-utils";
describe('InteractiveAuthDialog', function() {
describe("InteractiveAuthDialog", function () {
const mockClient = getMockClientWithEventEmitter({
generateClientSecret: jest.fn().mockReturnValue('t35tcl1Ent5ECr3T'),
generateClientSecret: jest.fn().mockReturnValue("t35tcl1Ent5ECr3T"),
});
const defaultProps = {
@@ -33,12 +33,9 @@ describe('InteractiveAuthDialog', function() {
makeRequest: jest.fn().mockResolvedValue(undefined),
onFinished: jest.fn(),
};
const getComponent = (props = {}) => mount(<InteractiveAuthDialog
{...defaultProps}
{...props}
/>);
const getComponent = (props = {}) => mount(<InteractiveAuthDialog {...defaultProps} {...props} />);
beforeEach(function() {
beforeEach(function () {
jest.clearAllMocks();
mockClient.credentials = null;
});
@@ -49,16 +46,14 @@ describe('InteractiveAuthDialog', function() {
const getSubmitButton = (wrapper: ReactWrapper) => wrapper.find('[type="submit"]').at(0);
it('Should successfully complete a password flow', async () => {
it("Should successfully complete a password flow", async () => {
const onFinished = jest.fn();
const makeRequest = jest.fn().mockResolvedValue({ a: 1 });
mockClient.credentials = { userId: "@user:id" };
const authData = {
session: "sess",
flows: [
{ "stages": ["m.login.password"] },
],
flows: [{ stages: ["m.login.password"] }],
};
const wrapper = getComponent({ makeRequest, onFinished, authData });
@@ -66,7 +61,7 @@ describe('InteractiveAuthDialog', function() {
const passwordNode = wrapper.find('input[type="password"]').at(0);
const submitNode = getSubmitButton(wrapper);
const formNode = wrapper.find('form').at(0);
const formNode = wrapper.find("form").at(0);
expect(passwordNode).toBeTruthy();
expect(submitNode).toBeTruthy();
@@ -75,7 +70,7 @@ describe('InteractiveAuthDialog', function() {
// put something in the password box
act(() => {
passwordNode.simulate('change', { target: { value: "s3kr3t" } });
passwordNode.simulate("change", { target: { value: "s3kr3t" } });
wrapper.setProps({});
});
@@ -84,22 +79,24 @@ describe('InteractiveAuthDialog', function() {
// hit enter; that should trigger a request
act(() => {
formNode.simulate('submit');
formNode.simulate("submit");
});
// wait for auth request to resolve
await flushPromises();
expect(makeRequest).toHaveBeenCalledTimes(1);
expect(makeRequest).toBeCalledWith(expect.objectContaining({
session: "sess",
type: "m.login.password",
password: "s3kr3t",
identifier: {
type: "m.id.user",
user: "@user:id",
},
}));
expect(makeRequest).toBeCalledWith(
expect.objectContaining({
session: "sess",
type: "m.login.password",
password: "s3kr3t",
identifier: {
type: "m.id.user",
user: "@user:id",
},
}),
);
expect(onFinished).toBeCalledTimes(1);
expect(onFinished).toBeCalledWith(true, { a: 1 });

View File

@@ -28,9 +28,11 @@ import { ValidatedServerConfig } from "../../../../src/utils/ValidatedServerConf
import { IConfigOptions } from "../../../../src/IConfigOptions";
const mockGetAccessToken = jest.fn().mockResolvedValue("getAccessToken");
jest.mock("../../../../src/IdentityAuthClient", () => jest.fn().mockImplementation(() => ({
getAccessToken: mockGetAccessToken,
})));
jest.mock("../../../../src/IdentityAuthClient", () =>
jest.fn().mockImplementation(() => ({
getAccessToken: mockGetAccessToken,
})),
);
describe("InviteDialog", () => {
const roomId = "!111111111111111111:example.org";
@@ -43,7 +45,7 @@ describe("InviteDialog", () => {
getRooms: jest.fn(),
getAccountData: jest.fn(),
getPushActionsForEvent: jest.fn(),
mxcUrlToHttp: jest.fn().mockReturnValue(''),
mxcUrlToHttp: jest.fn().mockReturnValue(""),
isRoomEncrypted: jest.fn().mockReturnValue(false),
getProfileInfo: jest.fn().mockRejectedValue({ errcode: "" }),
getIdentityServerUrl: jest.fn(),
@@ -70,58 +72,46 @@ describe("InviteDialog", () => {
});
afterAll(() => {
jest.spyOn(MatrixClientPeg, 'get').mockRestore();
jest.spyOn(MatrixClientPeg, "get").mockRestore();
});
it("should label with space name", () => {
mockClient.getRoom(roomId).isSpaceRoom = jest.fn().mockReturnValue(true);
mockClient.getRoom(roomId).getType = jest.fn().mockReturnValue(RoomType.Space);
mockClient.getRoom(roomId).name = "Space";
render((
<InviteDialog
kind={KIND_INVITE}
roomId={roomId}
onFinished={jest.fn()}
/>
));
render(<InviteDialog kind={KIND_INVITE} roomId={roomId} onFinished={jest.fn()} />);
expect(screen.queryByText("Invite to Space")).toBeTruthy();
});
it("should label with room name", () => {
render((
<InviteDialog
kind={KIND_INVITE}
roomId={roomId}
onFinished={jest.fn()}
/>
));
render(<InviteDialog kind={KIND_INVITE} roomId={roomId} onFinished={jest.fn()} />);
expect(screen.queryByText("Invite to Room")).toBeTruthy();
});
it("should suggest valid MXIDs even if unknown", async () => {
render((
render(
<InviteDialog
kind={KIND_INVITE}
roomId={roomId}
onFinished={jest.fn()}
initialText="@localpart:server.tld"
/>
));
/>,
);
await screen.findAllByText("@localpart:server.tld"); // Using findAllByText as the MXID is used for name too
});
it("should not suggest invalid MXIDs", () => {
render((
render(
<InviteDialog
kind={KIND_INVITE}
roomId={roomId}
onFinished={jest.fn()}
initialText="@localpart:server:tld"
/>
));
/>,
);
expect(screen.queryByText("@localpart:server:tld")).toBeFalsy();
});
@@ -138,14 +128,9 @@ describe("InviteDialog", () => {
avatar_url: "mxc://foo/bar",
});
render((
<InviteDialog
kind={KIND_INVITE}
roomId={roomId}
onFinished={jest.fn()}
initialText="foobar@email.com"
/>
));
render(
<InviteDialog kind={KIND_INVITE} roomId={roomId} onFinished={jest.fn()} initialText="foobar@email.com" />,
);
await screen.findByText("Mr. Foo");
await screen.findByText("@foobar:server");
@@ -157,14 +142,9 @@ describe("InviteDialog", () => {
mockClient.getIdentityServerUrl.mockReturnValue("https://identity-server");
mockClient.lookupThreePid.mockResolvedValue({});
render((
<InviteDialog
kind={KIND_INVITE}
roomId={roomId}
onFinished={jest.fn()}
initialText="foobar@email.com"
/>
));
render(
<InviteDialog kind={KIND_INVITE} roomId={roomId} onFinished={jest.fn()} initialText="foobar@email.com" />,
);
await screen.findByText("foobar@email.com");
await screen.findByText("Invite by email");

View File

@@ -54,16 +54,14 @@ interface MockClientOptions {
users?: IUserChunkMember[];
}
function mockClient(
{
userId = "testuser",
homeserver = "example.tld",
thirdPartyProtocols = {},
rooms = [],
members = [],
users = [],
}: MockClientOptions = {},
): MatrixClient {
function mockClient({
userId = "testuser",
homeserver = "example.tld",
thirdPartyProtocols = {},
rooms = [],
members = [],
users = [],
}: MockClientOptions = {}): MatrixClient {
stubClient();
const cli = MatrixClientPeg.get();
MatrixClientPeg.getHomeserverName = jest.fn(() => homeserver);
@@ -72,13 +70,15 @@ function mockClient(
cli.getThirdpartyProtocols = jest.fn(() => Promise.resolve(thirdPartyProtocols));
cli.publicRooms = jest.fn((options) => {
const searchTerm = options?.filter?.generic_search_term?.toLowerCase();
const chunk = rooms.filter(it =>
!searchTerm ||
it.room_id.toLowerCase().includes(searchTerm) ||
it.name?.toLowerCase().includes(searchTerm) ||
sanitizeHtml(it?.topic, { allowedTags: [] }).toLowerCase().includes(searchTerm) ||
it.canonical_alias?.toLowerCase().includes(searchTerm) ||
it.aliases?.find(alias => alias.toLowerCase().includes(searchTerm)));
const chunk = rooms.filter(
(it) =>
!searchTerm ||
it.room_id.toLowerCase().includes(searchTerm) ||
it.name?.toLowerCase().includes(searchTerm) ||
sanitizeHtml(it?.topic, { allowedTags: [] }).toLowerCase().includes(searchTerm) ||
it.canonical_alias?.toLowerCase().includes(searchTerm) ||
it.aliases?.find((alias) => alias.toLowerCase().includes(searchTerm)),
);
return Promise.resolve({
chunk,
total_room_count_estimate: chunk.length,
@@ -86,16 +86,19 @@ function mockClient(
});
cli.searchUserDirectory = jest.fn(({ term, limit }) => {
const searchTerm = term?.toLowerCase();
const results = users.filter(it => !searchTerm ||
it.user_id.toLowerCase().includes(searchTerm) ||
it.display_name.toLowerCase().includes(searchTerm));
const results = users.filter(
(it) =>
!searchTerm ||
it.user_id.toLowerCase().includes(searchTerm) ||
it.display_name.toLowerCase().includes(searchTerm),
);
return Promise.resolve({
results: results.slice(0, limit ?? +Infinity),
limited: limit && limit < results.length,
});
});
cli.getProfileInfo = jest.fn(async (userId) => {
const member = members.find(it => it.userId === userId);
const member = members.find((it) => it.userId === userId);
if (member) {
return Promise.resolve({
displayname: member.rawDisplayName,
@@ -144,11 +147,7 @@ describe("Spotlight Dialog", () => {
describe("should apply filters supplied via props", () => {
it("without filter", async () => {
const wrapper = mount(
<SpotlightDialog
initialFilter={null}
onFinished={() => null} />,
);
const wrapper = mount(<SpotlightDialog initialFilter={null} onFinished={() => null} />);
await act(async () => {
await sleep(200);
});
@@ -160,11 +159,7 @@ describe("Spotlight Dialog", () => {
wrapper.unmount();
});
it("with public room filter", async () => {
const wrapper = mount(
<SpotlightDialog
initialFilter={Filter.PublicRooms}
onFinished={() => null} />,
);
const wrapper = mount(<SpotlightDialog initialFilter={Filter.PublicRooms} onFinished={() => null} />);
await act(async () => {
await sleep(200);
});
@@ -186,7 +181,8 @@ describe("Spotlight Dialog", () => {
<SpotlightDialog
initialFilter={Filter.People}
initialText={testPerson.display_name}
onFinished={() => null} />,
onFinished={() => null}
/>,
);
await act(async () => {
await sleep(200);
@@ -208,10 +204,7 @@ describe("Spotlight Dialog", () => {
describe("should apply manually selected filter", () => {
it("with public rooms", async () => {
const wrapper = mount(
<SpotlightDialog
onFinished={() => null} />,
);
const wrapper = mount(<SpotlightDialog onFinished={() => null} />);
await act(async () => {
await sleep(1);
});
@@ -234,11 +227,7 @@ describe("Spotlight Dialog", () => {
wrapper.unmount();
});
it("with people", async () => {
const wrapper = mount(
<SpotlightDialog
initialText={testPerson.display_name}
onFinished={() => null} />,
);
const wrapper = mount(<SpotlightDialog initialText={testPerson.display_name} onFinished={() => null} />);
await act(async () => {
await sleep(1);
});
@@ -264,11 +253,7 @@ describe("Spotlight Dialog", () => {
describe("should allow clearing filter manually", () => {
it("with public room filter", async () => {
const wrapper = mount(
<SpotlightDialog
initialFilter={Filter.PublicRooms}
onFinished={() => null} />,
);
const wrapper = mount(<SpotlightDialog initialFilter={Filter.PublicRooms} onFinished={() => null} />);
await act(async () => {
await sleep(200);
});
@@ -294,7 +279,8 @@ describe("Spotlight Dialog", () => {
<SpotlightDialog
initialFilter={Filter.People}
initialText={testPerson.display_name}
onFinished={() => null} />,
onFinished={() => null}
/>,
);
await act(async () => {
await sleep(200);
@@ -323,11 +309,7 @@ describe("Spotlight Dialog", () => {
let options: ReactWrapper;
beforeAll(async () => {
wrapper = mount(
<SpotlightDialog
initialText="test23"
onFinished={() => null} />,
);
wrapper = mount(<SpotlightDialog initialText="test23" onFinished={() => null} />);
await act(async () => {
await sleep(200);
});
@@ -357,7 +339,8 @@ describe("Spotlight Dialog", () => {
<SpotlightDialog
initialFilter={Filter.People}
initialText={testPerson.display_name}
onFinished={() => null} />,
onFinished={() => null}
/>,
);
await act(async () => {

View File

@@ -14,30 +14,30 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { ReactElement } from 'react';
import { render } from '@testing-library/react';
import { mocked } from 'jest-mock';
import React, { ReactElement } from "react";
import { render } from "@testing-library/react";
import { mocked } from "jest-mock";
import SettingsStore, { CallbackFn } from '../../../../src/settings/SettingsStore';
import SdkConfig from '../../../../src/SdkConfig';
import { UserTab } from '../../../../src/components/views/dialogs/UserTab';
import UserSettingsDialog from '../../../../src/components/views/dialogs/UserSettingsDialog';
import { IDialogProps } from '../../../../src/components/views/dialogs/IDialogProps';
import SettingsStore, { CallbackFn } from "../../../../src/settings/SettingsStore";
import SdkConfig from "../../../../src/SdkConfig";
import { UserTab } from "../../../../src/components/views/dialogs/UserTab";
import UserSettingsDialog from "../../../../src/components/views/dialogs/UserSettingsDialog";
import { IDialogProps } from "../../../../src/components/views/dialogs/IDialogProps";
import {
getMockClientWithEventEmitter,
mockClientMethodsUser,
mockClientMethodsServer,
mockPlatformPeg,
} from '../../../test-utils';
import { UIFeature } from '../../../../src/settings/UIFeature';
import { SettingLevel } from '../../../../src/settings/SettingLevel';
} from "../../../test-utils";
import { UIFeature } from "../../../../src/settings/UIFeature";
import { SettingLevel } from "../../../../src/settings/SettingLevel";
mockPlatformPeg({
supportsSpellCheckSettings: jest.fn().mockReturnValue(false),
getAppVersion: jest.fn().mockResolvedValue('1'),
getAppVersion: jest.fn().mockResolvedValue("1"),
});
jest.mock('../../../../src/settings/SettingsStore', () => ({
jest.mock("../../../../src/settings/SettingsStore", () => ({
getValue: jest.fn(),
getValueAt: jest.fn(),
canSetValue: jest.fn(),
@@ -48,12 +48,12 @@ jest.mock('../../../../src/settings/SettingsStore', () => ({
getBetaInfo: jest.fn(),
}));
jest.mock('../../../../src/SdkConfig', () => ({
jest.mock("../../../../src/SdkConfig", () => ({
get: jest.fn(),
}));
describe('<UserSettingsDialog />', () => {
const userId = '@alice:server.org';
describe("<UserSettingsDialog />", () => {
const userId = "@alice:server.org";
const mockSettingsStore = mocked(SettingsStore);
const mockSdkConfig = mocked(SdkConfig);
getMockClientWithEventEmitter({
@@ -62,7 +62,7 @@ describe('<UserSettingsDialog />', () => {
});
const defaultProps = { onFinished: jest.fn() };
const getComponent = (props: Partial<IDialogProps & {initialTabId?: UserTab}> = {}): ReactElement => (
const getComponent = (props: Partial<IDialogProps & { initialTabId?: UserTab }> = {}): ReactElement => (
<UserSettingsDialog {...defaultProps} {...props} />
);
@@ -70,52 +70,52 @@ describe('<UserSettingsDialog />', () => {
jest.clearAllMocks();
mockSettingsStore.getValue.mockReturnValue(false);
mockSettingsStore.getFeatureSettingNames.mockReturnValue([]);
mockSdkConfig.get.mockReturnValue({ brand: 'Test' });
mockSdkConfig.get.mockReturnValue({ brand: "Test" });
});
const getActiveTabLabel = (container) => container.querySelector('.mx_TabbedView_tabLabel_active').textContent;
const getActiveTabHeading = (container) => container.querySelector('.mx_SettingsTab_heading').textContent;
const getActiveTabLabel = (container) => container.querySelector(".mx_TabbedView_tabLabel_active").textContent;
const getActiveTabHeading = (container) => container.querySelector(".mx_SettingsTab_heading").textContent;
it('should render general settings tab when no initialTabId', () => {
it("should render general settings tab when no initialTabId", () => {
const { container } = render(getComponent());
expect(getActiveTabLabel(container)).toEqual('General');
expect(getActiveTabHeading(container)).toEqual('General');
expect(getActiveTabLabel(container)).toEqual("General");
expect(getActiveTabHeading(container)).toEqual("General");
});
it('should render initial tab when initialTabId is set', () => {
it("should render initial tab when initialTabId is set", () => {
const { container } = render(getComponent({ initialTabId: UserTab.Help }));
expect(getActiveTabLabel(container)).toEqual('Help & About');
expect(getActiveTabHeading(container)).toEqual('Help & About');
expect(getActiveTabLabel(container)).toEqual("Help & About");
expect(getActiveTabHeading(container)).toEqual("Help & About");
});
it('should render general tab if initialTabId tab cannot be rendered', () => {
it("should render general tab if initialTabId tab cannot be rendered", () => {
// mjolnir tab is only rendered in some configs
const { container } = render(getComponent({ initialTabId: UserTab.Mjolnir }));
expect(getActiveTabLabel(container)).toEqual('General');
expect(getActiveTabHeading(container)).toEqual('General');
expect(getActiveTabLabel(container)).toEqual("General");
expect(getActiveTabHeading(container)).toEqual("General");
});
it('renders tabs correctly', () => {
it("renders tabs correctly", () => {
const { container } = render(getComponent());
expect(container.querySelectorAll('.mx_TabbedView_tabLabel')).toMatchSnapshot();
expect(container.querySelectorAll(".mx_TabbedView_tabLabel")).toMatchSnapshot();
});
it('renders ignored users tab when feature_mjolnir is enabled', () => {
it("renders ignored users tab when feature_mjolnir is enabled", () => {
mockSettingsStore.getValue.mockImplementation((settingName): any => settingName === "feature_mjolnir");
const { getByTestId } = render(getComponent());
expect(getByTestId(`settings-tab-${UserTab.Mjolnir}`)).toBeTruthy();
});
it('renders voip tab when voip is enabled', () => {
it("renders voip tab when voip is enabled", () => {
mockSettingsStore.getValue.mockImplementation((settingName): any => settingName === UIFeature.Voip);
const { getByTestId } = render(getComponent());
expect(getByTestId(`settings-tab-${UserTab.Voice}`)).toBeTruthy();
});
it('renders session manager tab when enabled', () => {
it("renders session manager tab when enabled", () => {
mockSettingsStore.getValue.mockImplementation((settingName): any => {
return settingName === "feature_new_device_manager";
});
@@ -123,23 +123,23 @@ describe('<UserSettingsDialog />', () => {
expect(getByTestId(`settings-tab-${UserTab.SessionManager}`)).toBeTruthy();
});
it('renders labs tab when show_labs_settings is enabled in config', () => {
it("renders labs tab when show_labs_settings is enabled in config", () => {
// @ts-ignore simplified test stub
mockSdkConfig.get.mockImplementation((configName) => configName === "show_labs_settings");
const { getByTestId } = render(getComponent());
expect(getByTestId(`settings-tab-${UserTab.Labs}`)).toBeTruthy();
});
it('renders labs tab when some feature is in beta', () => {
mockSettingsStore.getFeatureSettingNames.mockReturnValue(['feature_beta_setting', 'feature_just_normal_labs']);
mockSettingsStore.getBetaInfo.mockImplementation(
(settingName) => settingName === 'feature_beta_setting' ? {} as any : undefined,
it("renders labs tab when some feature is in beta", () => {
mockSettingsStore.getFeatureSettingNames.mockReturnValue(["feature_beta_setting", "feature_just_normal_labs"]);
mockSettingsStore.getBetaInfo.mockImplementation((settingName) =>
settingName === "feature_beta_setting" ? ({} as any) : undefined,
);
const { getByTestId } = render(getComponent());
expect(getByTestId(`settings-tab-${UserTab.Labs}`)).toBeTruthy();
});
it('watches settings', () => {
it("watches settings", () => {
const watchSettingCallbacks: Record<string, CallbackFn> = {};
mockSettingsStore.watchSetting.mockImplementation((settingName, roomId, callback) => {
@@ -150,17 +150,21 @@ describe('<UserSettingsDialog />', () => {
const { queryByTestId, unmount } = render(getComponent());
expect(queryByTestId(`settings-tab-${UserTab.Mjolnir}`)).toBeFalsy();
expect(mockSettingsStore.watchSetting.mock.calls[0][0]).toEqual('feature_mjolnir');
expect(mockSettingsStore.watchSetting.mock.calls[1][0]).toEqual('feature_new_device_manager');
expect(mockSettingsStore.watchSetting.mock.calls[0][0]).toEqual("feature_mjolnir");
expect(mockSettingsStore.watchSetting.mock.calls[1][0]).toEqual("feature_new_device_manager");
// call the watch setting callback
watchSettingCallbacks["feature_mjolnir"]("feature_mjolnir", '', SettingLevel.ACCOUNT, true, true);
watchSettingCallbacks["feature_mjolnir"]("feature_mjolnir", "", SettingLevel.ACCOUNT, true, true);
// tab is rendered now
expect(queryByTestId(`settings-tab-${UserTab.Mjolnir}`)).toBeTruthy();
// call the watch setting callback
watchSettingCallbacks["feature_new_device_manager"](
"feature_new_device_manager", '', SettingLevel.ACCOUNT, true, true,
"feature_new_device_manager",
"",
SettingLevel.ACCOUNT,
true,
true,
);
// tab is rendered now
expect(queryByTestId(`settings-tab-${UserTab.SessionManager}`)).toBeTruthy();
@@ -168,7 +172,7 @@ describe('<UserSettingsDialog />', () => {
unmount();
// unwatches settings on unmount
expect(mockSettingsStore.unwatchSetting).toHaveBeenCalledWith('mock-watcher-id-feature_mjolnir');
expect(mockSettingsStore.unwatchSetting).toHaveBeenCalledWith('mock-watcher-id-feature_new_device_manager');
expect(mockSettingsStore.unwatchSetting).toHaveBeenCalledWith("mock-watcher-id-feature_mjolnir");
expect(mockSettingsStore.unwatchSetting).toHaveBeenCalledWith("mock-watcher-id-feature_new_device_manager");
});
});

View File

@@ -22,19 +22,21 @@ import { PublicRoomResultDetails } from "../../../../../src/components/views/dia
describe("PublicRoomResultDetails", () => {
it("renders", () => {
const { asFragment } = render(<PublicRoomResultDetails
room={{
room_id: "room-id",
name: "hello?",
canonical_alias: "canonical-alias",
world_readable: true,
guest_can_join: false,
num_joined_members: 666,
}}
labelId="label-id"
descriptionId="description-id"
detailsId="details-id"
/>);
const { asFragment } = render(
<PublicRoomResultDetails
room={{
room_id: "room-id",
name: "hello?",
canonical_alias: "canonical-alias",
world_readable: true,
guest_can_join: false,
num_joined_members: 666,
}}
labelId="label-id"
descriptionId="description-id"
detailsId="details-id"
/>,
);
expect(asFragment()).toMatchSnapshot();
});
@@ -57,12 +59,14 @@ describe("PublicRoomResultDetails", () => {
...partialPublicRoomChunk,
};
const { asFragment } = render(<PublicRoomResultDetails
room={roomChunk}
labelId="label-id"
descriptionId="description-id"
detailsId="details-id"
/>);
const { asFragment } = render(
<PublicRoomResultDetails
room={roomChunk}
labelId="label-id"
descriptionId="description-id"
detailsId="details-id"
/>,
);
expect(asFragment()).toMatchSnapshot();
});

View File

@@ -14,22 +14,21 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
// eslint-disable-next-line deprecate/import
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import { mount } from "enzyme";
import { act } from "react-dom/test-utils";
import AccessibleButton from '../../../../src/components/views/elements/AccessibleButton';
import { Key } from '../../../../src/Keyboard';
import { mockPlatformPeg, unmockPlatformPeg } from '../../../test-utils';
import AccessibleButton from "../../../../src/components/views/elements/AccessibleButton";
import { Key } from "../../../../src/Keyboard";
import { mockPlatformPeg, unmockPlatformPeg } from "../../../test-utils";
describe('<AccessibleButton />', () => {
describe("<AccessibleButton />", () => {
const defaultProps = {
onClick: jest.fn(),
children: 'i am a button',
children: "i am a button",
};
const getComponent = (props = {}) =>
mount(<AccessibleButton {...defaultProps} {...props} />);
const getComponent = (props = {}) => mount(<AccessibleButton {...defaultProps} {...props} />);
beforeEach(() => {
mockPlatformPeg();
@@ -39,66 +38,67 @@ describe('<AccessibleButton />', () => {
unmockPlatformPeg();
});
const makeKeyboardEvent = (key: string) => ({
key,
stopPropagation: jest.fn(),
preventDefault: jest.fn(),
}) as unknown as KeyboardEvent;
const makeKeyboardEvent = (key: string) =>
({
key,
stopPropagation: jest.fn(),
preventDefault: jest.fn(),
} as unknown as KeyboardEvent);
it('renders div with role button by default', () => {
it("renders div with role button by default", () => {
const component = getComponent();
expect(component).toMatchSnapshot();
});
it('renders a button element', () => {
const component = getComponent({ element: 'button' });
it("renders a button element", () => {
const component = getComponent({ element: "button" });
expect(component).toMatchSnapshot();
});
it('renders with correct classes when button has kind', () => {
it("renders with correct classes when button has kind", () => {
const component = getComponent({
kind: 'primary',
kind: "primary",
});
expect(component).toMatchSnapshot();
});
it('disables button correctly', () => {
it("disables button correctly", () => {
const onClick = jest.fn();
const component = getComponent({
onClick,
disabled: true,
});
expect(component.find('.mx_AccessibleButton').props().disabled).toBeTruthy();
expect(component.find('.mx_AccessibleButton').props()['aria-disabled']).toBeTruthy();
expect(component.find(".mx_AccessibleButton").props().disabled).toBeTruthy();
expect(component.find(".mx_AccessibleButton").props()["aria-disabled"]).toBeTruthy();
act(() => {
component.simulate('click');
component.simulate("click");
});
expect(onClick).not.toHaveBeenCalled();
act(() => {
const keydownEvent = makeKeyboardEvent(Key.ENTER);
component.simulate('keydown', keydownEvent);
component.simulate("keydown", keydownEvent);
});
expect(onClick).not.toHaveBeenCalled();
});
it('calls onClick handler on button click', () => {
it("calls onClick handler on button click", () => {
const onClick = jest.fn();
const component = getComponent({
onClick,
});
act(() => {
component.simulate('click');
component.simulate("click");
});
expect(onClick).toHaveBeenCalled();
});
it('calls onClick handler on button mousedown when triggerOnMousedown is passed', () => {
it("calls onClick handler on button mousedown when triggerOnMousedown is passed", () => {
const onClick = jest.fn();
const component = getComponent({
onClick,
@@ -106,14 +106,14 @@ describe('<AccessibleButton />', () => {
});
act(() => {
component.simulate('mousedown');
component.simulate("mousedown");
});
expect(onClick).toHaveBeenCalled();
});
describe('handling keyboard events', () => {
it('calls onClick handler on enter keydown', () => {
describe("handling keyboard events", () => {
it("calls onClick handler on enter keydown", () => {
const onClick = jest.fn();
const component = getComponent({
onClick,
@@ -121,13 +121,13 @@ describe('<AccessibleButton />', () => {
const keyboardEvent = makeKeyboardEvent(Key.ENTER);
act(() => {
component.simulate('keydown', keyboardEvent);
component.simulate("keydown", keyboardEvent);
});
expect(onClick).toHaveBeenCalled();
act(() => {
component.simulate('keyup', keyboardEvent);
component.simulate("keyup", keyboardEvent);
});
// handler only called once on keydown
@@ -137,7 +137,7 @@ describe('<AccessibleButton />', () => {
expect(keyboardEvent.preventDefault).toHaveBeenCalledTimes(2);
});
it('calls onClick handler on space keyup', () => {
it("calls onClick handler on space keyup", () => {
const onClick = jest.fn();
const component = getComponent({
onClick,
@@ -145,13 +145,13 @@ describe('<AccessibleButton />', () => {
const keyboardEvent = makeKeyboardEvent(Key.SPACE);
act(() => {
component.simulate('keydown', keyboardEvent);
component.simulate("keydown", keyboardEvent);
});
expect(onClick).not.toHaveBeenCalled();
act(() => {
component.simulate('keyup', keyboardEvent);
component.simulate("keyup", keyboardEvent);
});
// handler only called once on keyup
@@ -161,7 +161,7 @@ describe('<AccessibleButton />', () => {
expect(keyboardEvent.preventDefault).toHaveBeenCalledTimes(2);
});
it('calls onKeydown/onKeyUp handlers for keys other than space and enter', () => {
it("calls onKeydown/onKeyUp handlers for keys other than space and enter", () => {
const onClick = jest.fn();
const onKeyDown = jest.fn();
const onKeyUp = jest.fn();
@@ -173,8 +173,8 @@ describe('<AccessibleButton />', () => {
const keyboardEvent = makeKeyboardEvent(Key.K);
act(() => {
component.simulate('keydown', keyboardEvent);
component.simulate('keyup', keyboardEvent);
component.simulate("keydown", keyboardEvent);
component.simulate("keyup", keyboardEvent);
});
expect(onClick).not.toHaveBeenCalled();
@@ -184,7 +184,7 @@ describe('<AccessibleButton />', () => {
expect(keyboardEvent.preventDefault).not.toHaveBeenCalled();
});
it('does nothing on non space/enter key presses when no onKeydown/onKeyUp handlers provided', () => {
it("does nothing on non space/enter key presses when no onKeydown/onKeyUp handlers provided", () => {
const onClick = jest.fn();
const component = getComponent({
onClick,
@@ -192,8 +192,8 @@ describe('<AccessibleButton />', () => {
const keyboardEvent = makeKeyboardEvent(Key.K);
act(() => {
component.simulate('keydown', keyboardEvent);
component.simulate('keyup', keyboardEvent);
component.simulate("keydown", keyboardEvent);
component.simulate("keyup", keyboardEvent);
});
// no onClick call, no problems

View File

@@ -52,17 +52,15 @@ describe("AppTile", () => {
let app1: IApp;
let app2: IApp;
const waitForRps = (roomId: string) => new Promise<void>(resolve => {
const update = () => {
if (
RightPanelStore.instance.currentCardForRoom(roomId).phase !==
RightPanelPhases.Widget
) return;
RightPanelStore.instance.off(UPDATE_EVENT, update);
resolve();
};
RightPanelStore.instance.on(UPDATE_EVENT, update);
});
const waitForRps = (roomId: string) =>
new Promise<void>((resolve) => {
const update = () => {
if (RightPanelStore.instance.currentCardForRoom(roomId).phase !== RightPanelPhases.Widget) return;
RightPanelStore.instance.off(UPDATE_EVENT, update);
resolve();
};
RightPanelStore.instance.on(UPDATE_EVENT, update);
});
beforeAll(async () => {
stubClient();
@@ -75,7 +73,7 @@ describe("AppTile", () => {
r1 = new Room("r1", cli, "@name:example.com");
r2 = new Room("r2", cli, "@name:example.com");
jest.spyOn(cli, "getRoom").mockImplementation(roomId => {
jest.spyOn(cli, "getRoom").mockImplementation((roomId) => {
if (roomId === "r1") return r1;
if (roomId === "r2") return r2;
return null;
@@ -105,7 +103,7 @@ describe("AppTile", () => {
creatorUserId: cli.getUserId(),
avatar_url: undefined,
};
jest.spyOn(WidgetStore.instance, "getApps").mockImplementation(roomId => {
jest.spyOn(WidgetStore.instance, "getApps").mockImplementation((roomId) => {
if (roomId === "r1") return [app1];
if (roomId === "r2") return [app2];
});
@@ -130,12 +128,14 @@ describe("AppTile", () => {
if (name !== "RightPanel.phases") return realGetValue(name, roomId);
if (roomId === "r1") {
return {
history: [{
phase: RightPanelPhases.Widget,
state: {
widgetId: "1",
history: [
{
phase: RightPanelPhases.Widget,
state: {
widgetId: "1",
},
},
}],
],
isOpen: true,
};
}
@@ -143,12 +143,11 @@ describe("AppTile", () => {
});
// Run initial render with room 1, and also running lifecycle methods
const renderer = TestRenderer.create(<MatrixClientContext.Provider value={cli}>
<RightPanel
room={r1}
resizeNotifier={resizeNotifier}
/>
</MatrixClientContext.Provider>);
const renderer = TestRenderer.create(
<MatrixClientContext.Provider value={cli}>
<RightPanel room={r1} resizeNotifier={resizeNotifier} />
</MatrixClientContext.Provider>,
);
// Wait for RPS room 1 updates to fire
const rpsUpdated = waitForRps("r1");
dis.dispatch({
@@ -169,12 +168,11 @@ describe("AppTile", () => {
action: Action.ViewRoom,
room_id: "r2",
});
renderer.update(<MatrixClientContext.Provider value={cli}>
<RightPanel
room={r2}
resizeNotifier={resizeNotifier}
/>
</MatrixClientContext.Provider>);
renderer.update(
<MatrixClientContext.Provider value={cli}>
<RightPanel room={r2} resizeNotifier={resizeNotifier} />
</MatrixClientContext.Provider>,
);
expect(endWidgetActions.mock.calls.length).toBe(1);
expect(ActiveWidgetStore.instance.isLive("1", "r1")).toBe(false);
@@ -185,16 +183,18 @@ describe("AppTile", () => {
it("distinguishes widgets with the same ID in different rooms", async () => {
// Set up right panel state
const realGetValue = SettingsStore.getValue;
jest.spyOn(SettingsStore, 'getValue').mockImplementation((name, roomId) => {
jest.spyOn(SettingsStore, "getValue").mockImplementation((name, roomId) => {
if (name === "RightPanel.phases") {
if (roomId === "r1") {
return {
history: [{
phase: RightPanelPhases.Widget,
state: {
widgetId: "1",
history: [
{
phase: RightPanelPhases.Widget,
state: {
widgetId: "1",
},
},
}],
],
isOpen: true,
};
}
@@ -204,12 +204,11 @@ describe("AppTile", () => {
});
// Run initial render with room 1, and also running lifecycle methods
const renderer = TestRenderer.create(<MatrixClientContext.Provider value={cli}>
<RightPanel
room={r1}
resizeNotifier={resizeNotifier}
/>
</MatrixClientContext.Provider>);
const renderer = TestRenderer.create(
<MatrixClientContext.Provider value={cli}>
<RightPanel room={r1} resizeNotifier={resizeNotifier} />
</MatrixClientContext.Provider>,
);
// Wait for RPS room 1 updates to fire
const rpsUpdated1 = waitForRps("r1");
dis.dispatch({
@@ -225,12 +224,14 @@ describe("AppTile", () => {
if (name === "RightPanel.phases") {
if (roomId === "r2") {
return {
history: [{
phase: RightPanelPhases.Widget,
state: {
widgetId: "1",
history: [
{
phase: RightPanelPhases.Widget,
state: {
widgetId: "1",
},
},
}],
],
isOpen: true,
};
}
@@ -245,12 +246,11 @@ describe("AppTile", () => {
action: Action.ViewRoom,
room_id: "r2",
});
renderer.update(<MatrixClientContext.Provider value={cli}>
<RightPanel
room={r2}
resizeNotifier={resizeNotifier}
/>
</MatrixClientContext.Provider>);
renderer.update(
<MatrixClientContext.Provider value={cli}>
<RightPanel room={r2} resizeNotifier={resizeNotifier} />
</MatrixClientContext.Provider>,
);
await rpsUpdated2;
expect(ActiveWidgetStore.instance.isLive("1", "r1")).toBe(false);
@@ -279,13 +279,11 @@ describe("AppTile", () => {
});
// Run initial render with room 1, and also running lifecycle methods
const renderer = TestRenderer.create(<MatrixClientContext.Provider value={cli}>
<AppsDrawer
userId={cli.getUserId()}
room={r1}
resizeNotifier={resizeNotifier}
/>
</MatrixClientContext.Provider>);
const renderer = TestRenderer.create(
<MatrixClientContext.Provider value={cli}>
<AppsDrawer userId={cli.getUserId()} room={r1} resizeNotifier={resizeNotifier} />
</MatrixClientContext.Provider>,
);
expect(ActiveWidgetStore.instance.isLive("1", "r1")).toBe(true);
@@ -319,38 +317,34 @@ describe("AppTile", () => {
let moveToContainerSpy;
beforeEach(() => {
wrapper = mount((
wrapper = mount(
<MatrixClientContext.Provider value={cli}>
<AppTile
key={app1.id}
app={app1}
room={r1}
/>
</MatrixClientContext.Provider>
));
<AppTile key={app1.id} app={app1} room={r1} />
</MatrixClientContext.Provider>,
);
moveToContainerSpy = jest.spyOn(WidgetLayoutStore.instance, 'moveToContainer');
moveToContainerSpy = jest.spyOn(WidgetLayoutStore.instance, "moveToContainer");
});
it("requiresClient should be true", () => {
expect(wrapper.state('requiresClient')).toBe(true);
expect(wrapper.state("requiresClient")).toBe(true);
});
it("clicking 'minimise' should send the widget to the right", () => {
const minimiseButton = wrapper.find('.mx_AppTileMenuBar_iconButton_minimise');
minimiseButton.first().simulate('click');
const minimiseButton = wrapper.find(".mx_AppTileMenuBar_iconButton_minimise");
minimiseButton.first().simulate("click");
expect(moveToContainerSpy).toHaveBeenCalledWith(r1, app1, Container.Right);
});
it("clicking 'maximise' should send the widget to the center", () => {
const minimiseButton = wrapper.find('.mx_AppTileMenuBar_iconButton_maximise');
minimiseButton.first().simulate('click');
const minimiseButton = wrapper.find(".mx_AppTileMenuBar_iconButton_maximise");
minimiseButton.first().simulate("click");
expect(moveToContainerSpy).toHaveBeenCalledWith(r1, app1, Container.Center);
});
describe("for a maximised (centered) widget", () => {
beforeEach(() => {
jest.spyOn(WidgetLayoutStore.instance, 'isInContainer').mockImplementation(
jest.spyOn(WidgetLayoutStore.instance, "isInContainer").mockImplementation(
(room: Optional<Room>, widget: IApp, container: Container) => {
return room === r1 && widget === app1 && container === Container.Center;
},
@@ -358,8 +352,8 @@ describe("AppTile", () => {
});
it("clicking 'un-maximise' should send the widget to the top", () => {
const unMaximiseButton = wrapper.find('.mx_AppTileMenuBar_iconButton_collapse');
unMaximiseButton.first().simulate('click');
const unMaximiseButton = wrapper.find(".mx_AppTileMenuBar_iconButton_collapse");
unMaximiseButton.first().simulate("click");
expect(moveToContainerSpy).toHaveBeenCalledWith(r1, app1, Container.Top);
});
});
@@ -378,19 +372,15 @@ describe("AppTile", () => {
const mockWidget = new ElementWidget(app1);
WidgetMessagingStore.instance.storeMessaging(mockWidget, r1.roomId, api);
wrapper = mount((
wrapper = mount(
<MatrixClientContext.Provider value={cli}>
<AppTile
key={app1.id}
app={app1}
room={r1}
/>
</MatrixClientContext.Provider>
));
<AppTile key={app1.id} app={app1} room={r1} />
</MatrixClientContext.Provider>,
);
});
it("requiresClient should be false", () => {
expect(wrapper.state('requiresClient')).toBe(false);
expect(wrapper.state("requiresClient")).toBe(false);
});
});
});

View File

@@ -14,23 +14,23 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
// eslint-disable-next-line deprecate/import
import { mount, ReactWrapper } from 'enzyme';
import { MatrixEvent, RoomMember } from 'matrix-js-sdk/src/matrix';
import { mount, ReactWrapper } from "enzyme";
import { MatrixEvent, RoomMember } from "matrix-js-sdk/src/matrix";
import {
getMockClientWithEventEmitter,
mkMembership,
mockClientMethodsUser,
unmockClientPeg,
} from '../../../test-utils';
} from "../../../test-utils";
import EventListSummary from "../../../../src/components/views/elements/EventListSummary";
import { Layout } from '../../../../src/settings/enums/Layout';
import MatrixClientContext from '../../../../src/contexts/MatrixClientContext';
import { Layout } from "../../../../src/settings/enums/Layout";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
describe('EventListSummary', function() {
const roomId = '!room:server.org';
describe("EventListSummary", function () {
const roomId = "!room:server.org";
// Generate dummy event tiles for use in simulating an expanded MELS
const generateTiles = (events: MatrixEvent[]) => {
return events.map((e) => {
@@ -64,13 +64,14 @@ describe('EventListSummary', function() {
prevMembership?: string;
}
const generateMembershipEvent = (
eventId: string, { senderId, userId, membership, prevMembership }: MembershipEventParams,
eventId: string,
{ senderId, userId, membership, prevMembership }: MembershipEventParams,
): MatrixEvent => {
const member = new RoomMember(roomId, userId);
// Use localpart as display name;
member.name = userId.match(/@([^:]*):/)[1];
jest.spyOn(member, 'getAvatarUrl').mockReturnValue('avatar.jpeg');
jest.spyOn(member, 'getMxcAvatarUrl').mockReturnValue('mxc://avatar.url/image.png');
jest.spyOn(member, "getAvatarUrl").mockReturnValue("avatar.jpeg");
jest.spyOn(member, "getMxcAvatarUrl").mockReturnValue("mxc://avatar.url/image.png");
const e = mkMembership({
event: true,
room: roomId,
@@ -102,7 +103,7 @@ describe('EventListSummary', function() {
let eventsForUsers = [];
let userId = "";
for (let i = 0; i < n; i++) {
userId = userIdTemplate.replace('$', i);
userId = userIdTemplate.replace("$", i);
events.forEach((e) => {
e.userId = userId;
});
@@ -121,13 +122,14 @@ describe('EventListSummary', function() {
children: [],
};
const renderComponent = (props = {}): ReactWrapper => {
return mount(<MatrixClientContext.Provider value={mockClient}>
<EventListSummary {...defaultProps} {...props} />
</MatrixClientContext.Provider>,
return mount(
<MatrixClientContext.Provider value={mockClient}>
<EventListSummary {...defaultProps} {...props} />
</MatrixClientContext.Provider>,
);
};
beforeEach(function() {
beforeEach(function () {
jest.clearAllMocks();
});
@@ -135,10 +137,8 @@ describe('EventListSummary', function() {
unmockClientPeg();
});
it('renders expanded events if there are less than props.threshold', function() {
const events = generateEvents([
{ userId: "@user_1:some.domain", prevMembership: "leave", membership: "join" },
]);
it("renders expanded events if there are less than props.threshold", function () {
const events = generateEvents([{ userId: "@user_1:some.domain", prevMembership: "leave", membership: "join" }]);
const props = {
events: events,
children: generateTiles(events),
@@ -149,12 +149,14 @@ describe('EventListSummary', function() {
const wrapper = renderComponent(props); // matrix cli context wrapper
expect(wrapper.find('GenericEventListSummary').props().children).toEqual([
<div className="event_tile" key="event0">Expanded membership</div>,
expect(wrapper.find("GenericEventListSummary").props().children).toEqual([
<div className="event_tile" key="event0">
Expanded membership
</div>,
]);
});
it('renders expanded events if there are less than props.threshold', function() {
it("renders expanded events if there are less than props.threshold", function () {
const events = generateEvents([
{ userId: "@user_1:some.domain", prevMembership: "leave", membership: "join" },
{ userId: "@user_1:some.domain", prevMembership: "join", membership: "leave" },
@@ -169,13 +171,17 @@ describe('EventListSummary', function() {
const wrapper = renderComponent(props); // matrix cli context wrapper
expect(wrapper.find('GenericEventListSummary').props().children).toEqual([
<div className="event_tile" key="event0">Expanded membership</div>,
<div className="event_tile" key="event1">Expanded membership</div>,
expect(wrapper.find("GenericEventListSummary").props().children).toEqual([
<div className="event_tile" key="event0">
Expanded membership
</div>,
<div className="event_tile" key="event1">
Expanded membership
</div>,
]);
});
it('renders collapsed events if events.length = props.threshold', function() {
it("renders collapsed events if events.length = props.threshold", function () {
const events = generateEvents([
{ userId: "@user_1:some.domain", prevMembership: "leave", membership: "join" },
{ userId: "@user_1:some.domain", prevMembership: "join", membership: "leave" },
@@ -196,7 +202,7 @@ describe('EventListSummary', function() {
expect(summaryText).toBe("user_1 joined and left and joined");
});
it('truncates long join,leave repetitions', function() {
it("truncates long join,leave repetitions", function () {
const events = generateEvents([
{ userId: "@user_1:some.domain", prevMembership: "leave", membership: "join" },
{ userId: "@user_1:some.domain", prevMembership: "join", membership: "leave" },
@@ -228,7 +234,7 @@ describe('EventListSummary', function() {
expect(summaryText).toBe("user_1 joined and left 7 times");
});
it('truncates long join,leave repetitions between other events', function() {
it("truncates long join,leave repetitions between other events", function () {
const events = generateEvents([
{
userId: "@user_1:some.domain",
@@ -269,12 +275,10 @@ describe('EventListSummary', function() {
const summary = wrapper.find(".mx_GenericEventListSummary_summary");
const summaryText = summary.text();
expect(summaryText).toBe(
"user_1 was unbanned, joined and left 7 times and was invited",
);
expect(summaryText).toBe("user_1 was unbanned, joined and left 7 times and was invited");
});
it('truncates multiple sequences of repetitions with other events between', function() {
it("truncates multiple sequences of repetitions with other events between", function () {
const events = generateEvents([
{
userId: "@user_1:some.domain",
@@ -318,12 +322,11 @@ describe('EventListSummary', function() {
const summaryText = summary.text();
expect(summaryText).toBe(
"user_1 was unbanned, joined and left 2 times, was banned, " +
"joined and left 3 times and was invited",
"user_1 was unbanned, joined and left 2 times, was banned, " + "joined and left 3 times and was invited",
);
});
it('handles multiple users following the same sequence of memberships', function() {
it("handles multiple users following the same sequence of memberships", function () {
const events = generateEvents([
// user_1
{
@@ -372,12 +375,10 @@ describe('EventListSummary', function() {
const summary = wrapper.find(".mx_GenericEventListSummary_summary");
const summaryText = summary.text();
expect(summaryText).toBe(
"user_1 and one other were unbanned, joined and left 2 times and were banned",
);
expect(summaryText).toBe("user_1 and one other were unbanned, joined and left 2 times and were banned");
});
it('handles many users following the same sequence of memberships', function() {
it("handles many users following the same sequence of memberships", function () {
const events = generateEventsForUsers("@user_$:some.domain", 20, [
{
prevMembership: "ban",
@@ -406,12 +407,10 @@ describe('EventListSummary', function() {
const summary = wrapper.find(".mx_GenericEventListSummary_summary");
const summaryText = summary.text();
expect(summaryText).toBe(
"user_0 and 19 others were unbanned, joined and left 2 times and were banned",
);
expect(summaryText).toBe("user_0 and 19 others were unbanned, joined and left 2 times and were banned");
});
it('correctly orders sequences of transitions by the order of their first event', function() {
it("correctly orders sequences of transitions by the order of their first event", function () {
const events = generateEvents([
{
userId: "@user_2:some.domain",
@@ -454,11 +453,11 @@ describe('EventListSummary', function() {
expect(summaryText).toBe(
"user_2 was unbanned and joined and left 2 times, user_1 was unbanned, " +
"joined and left 2 times and was banned",
"joined and left 2 times and was banned",
);
});
it('correctly identifies transitions', function() {
it("correctly identifies transitions", function () {
const events = generateEvents([
// invited
{ userId: "@user_1:some.domain", membership: "invite" },
@@ -524,11 +523,11 @@ describe('EventListSummary', function() {
expect(summaryText).toBe(
"user_1 was invited, was banned, joined, rejected their invitation, left, " +
"had their invitation withdrawn, was unbanned, was removed, left and was removed",
"had their invitation withdrawn, was unbanned, was removed, left and was removed",
);
});
it('handles invitation plurals correctly when there are multiple users', function() {
it("handles invitation plurals correctly when there are multiple users", function () {
const events = generateEvents([
{
userId: "@user_1:some.domain",
@@ -566,12 +565,11 @@ describe('EventListSummary', function() {
const summaryText = summary.text();
expect(summaryText).toBe(
"user_1 and one other rejected their invitations and " +
"had their invitations withdrawn",
"user_1 and one other rejected their invitations and " + "had their invitations withdrawn",
);
});
it('handles invitation plurals correctly when there are multiple invites', function() {
it("handles invitation plurals correctly when there are multiple invites", function () {
const events = generateEvents([
{
userId: "@user_1:some.domain",
@@ -596,12 +594,10 @@ describe('EventListSummary', function() {
const summary = wrapper.find(".mx_GenericEventListSummary_summary");
const summaryText = summary.text();
expect(summaryText).toBe(
"user_1 rejected their invitation 2 times",
);
expect(summaryText).toBe("user_1 rejected their invitation 2 times");
});
it('handles a summary length = 2, with no "others"', function() {
it('handles a summary length = 2, with no "others"', function () {
const events = generateEvents([
{ userId: "@user_1:some.domain", membership: "join" },
{ userId: "@user_1:some.domain", membership: "join" },
@@ -620,12 +616,10 @@ describe('EventListSummary', function() {
const summary = wrapper.find(".mx_GenericEventListSummary_summary");
const summaryText = summary.text();
expect(summaryText).toBe(
"user_1 and user_2 joined 2 times",
);
expect(summaryText).toBe("user_1 and user_2 joined 2 times");
});
it('handles a summary length = 2, with 1 "other"', function() {
it('handles a summary length = 2, with 1 "other"', function () {
const events = generateEvents([
{ userId: "@user_1:some.domain", membership: "join" },
{ userId: "@user_2:some.domain", membership: "join" },
@@ -643,15 +637,11 @@ describe('EventListSummary', function() {
const summary = wrapper.find(".mx_GenericEventListSummary_summary");
const summaryText = summary.text();
expect(summaryText).toBe(
"user_1, user_2 and one other joined",
);
expect(summaryText).toBe("user_1, user_2 and one other joined");
});
it('handles a summary length = 2, with many "others"', function() {
const events = generateEventsForUsers("@user_$:some.domain", 20, [
{ membership: "join" },
]);
it('handles a summary length = 2, with many "others"', function () {
const events = generateEventsForUsers("@user_$:some.domain", 20, [{ membership: "join" }]);
const props = {
events: events,
children: generateTiles(events),
@@ -664,8 +654,6 @@ describe('EventListSummary', function() {
const summary = wrapper.find(".mx_GenericEventListSummary_summary");
const summaryText = summary.text();
expect(summaryText).toBe(
"user_0, user_1 and 18 others joined",
);
expect(summaryText).toBe("user_0, user_1 and 18 others joined");
});
});

View File

@@ -12,39 +12,45 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { renderIntoDocument } from 'react-dom/test-utils';
import React from "react";
import { renderIntoDocument } from "react-dom/test-utils";
import ExternalLink from '../../../../src/components/views/elements/ExternalLink';
import ExternalLink from "../../../../src/components/views/elements/ExternalLink";
describe('<ExternalLink />', () => {
describe("<ExternalLink />", () => {
const defaultProps = {
"href": 'test.com',
"href": "test.com",
"onClick": jest.fn(),
"className": 'myCustomClass',
'data-test-id': 'test',
"className": "myCustomClass",
"data-test-id": "test",
};
const getComponent = (props = {}) => {
const wrapper = renderIntoDocument<HTMLDivElement>(
<div><ExternalLink {...defaultProps} {...props} /></div>,
<div>
<ExternalLink {...defaultProps} {...props} />
</div>,
) as HTMLDivElement;
return wrapper.children[0];
};
it('renders link correctly', () => {
const children = <span>react element <b>children</b></span>;
expect(getComponent({ children, target: '_self', rel: 'noopener' })).toMatchSnapshot();
it("renders link correctly", () => {
const children = (
<span>
react element <b>children</b>
</span>
);
expect(getComponent({ children, target: "_self", rel: "noopener" })).toMatchSnapshot();
});
it('defaults target and rel', () => {
const children = 'test';
it("defaults target and rel", () => {
const children = "test";
const component = getComponent({ children });
expect(component.getAttribute('rel')).toEqual('noreferrer noopener');
expect(component.getAttribute('target')).toEqual('_blank');
expect(component.getAttribute("rel")).toEqual("noreferrer noopener");
expect(component.getAttribute("target")).toEqual("_blank");
});
it('renders plain text link correctly', () => {
const children = 'test';
it("renders plain text link correctly", () => {
const children = "test";
expect(getComponent({ children })).toMatchSnapshot();
});
});

View File

@@ -14,55 +14,55 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { act, fireEvent, render } from '@testing-library/react';
import React from 'react';
import { act, fireEvent, render } from "@testing-library/react";
import React from "react";
import { FilterDropdown } from '../../../../src/components/views/elements/FilterDropdown';
import { flushPromises, mockPlatformPeg } from '../../../test-utils';
import { FilterDropdown } from "../../../../src/components/views/elements/FilterDropdown";
import { flushPromises, mockPlatformPeg } from "../../../test-utils";
mockPlatformPeg();
describe('<FilterDropdown />', () => {
describe("<FilterDropdown />", () => {
const options = [
{ id: 'one', label: 'Option one' },
{ id: 'two', label: 'Option two', description: 'with description' },
{ id: "one", label: "Option one" },
{ id: "two", label: "Option two", description: "with description" },
];
const defaultProps = {
className: 'test',
value: 'one',
className: "test",
value: "one",
options,
id: 'test',
label: 'test label',
id: "test",
label: "test label",
onOptionChange: jest.fn(),
};
const getComponent = (props = {}): JSX.Element =>
(<FilterDropdown {...defaultProps} {...props} />);
const getComponent = (props = {}): JSX.Element => <FilterDropdown {...defaultProps} {...props} />;
const openDropdown = async (container: HTMLElement): Promise<void> => await act(async () => {
const button = container.querySelector('[role="button"]');
expect(button).toBeTruthy();
fireEvent.click(button as Element);
await flushPromises();
});
const openDropdown = async (container: HTMLElement): Promise<void> =>
await act(async () => {
const button = container.querySelector('[role="button"]');
expect(button).toBeTruthy();
fireEvent.click(button as Element);
await flushPromises();
});
it('renders selected option', () => {
it("renders selected option", () => {
const { container } = render(getComponent());
expect(container).toMatchSnapshot();
});
it('renders when selected option is not in options', () => {
const { container } = render(getComponent({ value: 'oops' }));
it("renders when selected option is not in options", () => {
const { container } = render(getComponent({ value: "oops" }));
expect(container).toMatchSnapshot();
});
it('renders selected option with selectedLabel', () => {
const { container } = render(getComponent({ selectedLabel: 'Show' }));
it("renders selected option with selectedLabel", () => {
const { container } = render(getComponent({ selectedLabel: "Show" }));
expect(container).toMatchSnapshot();
});
it('renders dropdown options in menu', async () => {
it("renders dropdown options in menu", async () => {
const { container } = render(getComponent());
await openDropdown(container);
expect(container.querySelector('.mx_Dropdown_menu')).toMatchSnapshot();
expect(container.querySelector(".mx_Dropdown_menu")).toMatchSnapshot();
});
});

View File

@@ -14,24 +14,21 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
// eslint-disable-next-line deprecate/import
import { mount } from 'enzyme';
import { mount } from "enzyme";
import { act } from "react-dom/test-utils";
import LabelledCheckbox from "../../../../src/components/views/elements/LabelledCheckbox";
// Fake random strings to give a predictable snapshot for checkbox IDs
jest.mock(
'matrix-js-sdk/src/randomstring',
() => {
return {
randomString: () => "abdefghi",
};
},
);
jest.mock("matrix-js-sdk/src/randomstring", () => {
return {
randomString: () => "abdefghi",
};
});
describe('<LabelledCheckbox />', () => {
describe("<LabelledCheckbox />", () => {
type CompProps = React.ComponentProps<typeof LabelledCheckbox>;
const getComponent = (props: CompProps) => mount(<LabelledCheckbox {...props} />);
type CompClass = ReturnType<typeof getComponent>;
@@ -42,29 +39,26 @@ describe('<LabelledCheckbox />', () => {
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;
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) => {
const props: CompProps = {
label: "Hello world",
value: true,
byline: byline,
onChange: jest.fn(),
};
const component = getComponent(props);
const checkbox = getCheckbox(component);
test.each([null, "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);
},
);
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);
});
it('should support unchecked by default', () => {
it("should support unchecked by default", () => {
const props: CompProps = {
label: "Hello world",
value: false,
@@ -75,7 +69,7 @@ describe('<LabelledCheckbox />', () => {
expect(isChecked(getCheckbox(component))).toBe(false);
});
it('should be possible to disable the checkbox', () => {
it("should be possible to disable the checkbox", () => {
const props: CompProps = {
label: "Hello world",
value: false,
@@ -87,7 +81,7 @@ describe('<LabelledCheckbox />', () => {
expect(isDisabled(getCheckbox(component))).toBe(true);
});
it('should emit onChange calls', () => {
it("should emit onChange calls", () => {
const props: CompProps = {
label: "Hello world",
value: false,
@@ -98,13 +92,13 @@ describe('<LabelledCheckbox />', () => {
expect(props.onChange).not.toHaveBeenCalled();
act(() => {
getCheckbox(component).simulate('change');
getCheckbox(component).simulate("change");
});
expect(props.onChange).toHaveBeenCalledTimes(1);
});
it('should react to value and disabled prop changes', () => {
it("should react to value and disabled prop changes", () => {
const props: CompProps = {
label: "Hello world",
value: false,

View File

@@ -14,44 +14,41 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { fireEvent, render } from '@testing-library/react';
import React from "react";
import { fireEvent, render } from "@testing-library/react";
import LearnMore from '../../../../src/components/views/elements/LearnMore';
import Modal from '../../../../src/Modal';
import InfoDialog from '../../../../src/components/views/dialogs/InfoDialog';
import LearnMore from "../../../../src/components/views/elements/LearnMore";
import Modal from "../../../../src/Modal";
import InfoDialog from "../../../../src/components/views/dialogs/InfoDialog";
describe('<LearnMore />', () => {
describe("<LearnMore />", () => {
const defaultProps = {
title: 'Test',
description: 'test test test',
['data-testid']: 'testid',
title: "Test",
description: "test test test",
["data-testid"]: "testid",
};
const getComponent = (props = {}) =>
(<LearnMore {...defaultProps} {...props} />);
const getComponent = (props = {}) => <LearnMore {...defaultProps} {...props} />;
const modalSpy = jest.spyOn(Modal, 'createDialog').mockReturnValue(undefined);
const modalSpy = jest.spyOn(Modal, "createDialog").mockReturnValue(undefined);
beforeEach(() => {
jest.clearAllMocks();
});
it('renders button', () => {
it("renders button", () => {
const { container } = render(getComponent());
expect(container).toMatchSnapshot();
});
it('opens modal on click', async () => {
it("opens modal on click", async () => {
const { getByTestId } = render(getComponent());
fireEvent.click(getByTestId('testid'));
fireEvent.click(getByTestId("testid"));
expect(modalSpy).toHaveBeenCalledWith(
InfoDialog,
{
button: 'Got it',
description: defaultProps.description,
hasCloseButton: true,
title: defaultProps.title,
});
expect(modalSpy).toHaveBeenCalledWith(InfoDialog, {
button: "Got it",
description: defaultProps.description,
hasCloseButton: true,
title: defaultProps.title,
});
});
});

View File

@@ -19,34 +19,28 @@ import { Linkify } from "../../../../src/components/views/elements/Linkify";
describe("Linkify", () => {
it("linkifies the context", () => {
const { container } = render(<Linkify>
https://perdu.com
</Linkify>);
const { container } = render(<Linkify>https://perdu.com</Linkify>);
expect(container.innerHTML).toBe(
"<div><a href=\"https://perdu.com\" class=\"linkified\" target=\"_blank\" rel=\"noreferrer noopener\">"+
"https://perdu.com" +
"</a></div>",
'<div><a href="https://perdu.com" class="linkified" target="_blank" rel="noreferrer noopener">' +
"https://perdu.com" +
"</a></div>",
);
});
it("correctly linkifies a room alias", () => {
const { container } = render(<Linkify>
#element-web:matrix.org
</Linkify>);
const { container } = render(<Linkify>#element-web:matrix.org</Linkify>);
expect(container.innerHTML).toBe(
"<div>" +
"<a href=\"https://matrix.to/#/#element-web:matrix.org\" class=\"linkified\" rel=\"noreferrer noopener\">" +
"#element-web:matrix.org" +
"</a></div>",
'<a href="https://matrix.to/#/#element-web:matrix.org" class="linkified" rel="noreferrer noopener">' +
"#element-web:matrix.org" +
"</a></div>",
);
});
it("changes the root tag name", () => {
const TAG_NAME = "p";
const { container } = render(<Linkify as={TAG_NAME}>
Hello world!
</Linkify>);
const { container } = render(<Linkify as={TAG_NAME}>Hello world!</Linkify>);
expect(container.querySelectorAll("p")).toHaveLength(1);
});
@@ -60,31 +54,29 @@ describe("Linkify", () => {
// upon clicking the element, change the content, and expect
// linkify to update
return <div onClick={onClick}>
<Linkify>
{ n % 2 === 0
? "https://perdu.com"
: "https://matrix.org" }
</Linkify>
</div>;
return (
<div onClick={onClick}>
<Linkify>{n % 2 === 0 ? "https://perdu.com" : "https://matrix.org"}</Linkify>
</div>
);
}
const { container } = render(<DummyTest />);
expect(container.innerHTML).toBe(
"<div><div>" +
"<a href=\"https://perdu.com\" class=\"linkified\" target=\"_blank\" rel=\"noreferrer noopener\">" +
"https://perdu.com" +
"</a></div></div>",
'<a href="https://perdu.com" class="linkified" target="_blank" rel="noreferrer noopener">' +
"https://perdu.com" +
"</a></div></div>",
);
fireEvent.click(container.querySelector("div"));
expect(container.innerHTML).toBe(
"<div><div>" +
"<a href=\"https://matrix.org\" class=\"linkified\" target=\"_blank\" rel=\"noreferrer noopener\">" +
"https://matrix.org" +
"</a></div></div>",
'<a href="https://matrix.org" class="linkified" target="_blank" rel="noreferrer noopener">' +
"https://matrix.org" +
"</a></div></div>",
);
});
});

View File

@@ -24,16 +24,13 @@ import {
M_POLL_START,
M_TEXT,
PollStartEvent,
} from 'matrix-events-sdk';
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
} from "matrix-events-sdk";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import {
findById,
getMockClientWithEventEmitter,
} from '../../../test-utils';
import { findById, getMockClientWithEventEmitter } from "../../../test-utils";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import PollCreateDialog from "../../../../src/components/views/elements/PollCreateDialog";
import MatrixClientContext from '../../../../src/contexts/MatrixClientContext';
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
// Fake date to give a predictable snapshot
const realDateNow = Date.now;
@@ -50,7 +47,7 @@ afterAll(() => {
describe("PollCreateDialog", () => {
const mockClient = getMockClientWithEventEmitter({
sendEvent: jest.fn().mockResolvedValue({ event_id: '1' }),
sendEvent: jest.fn().mockResolvedValue({ event_id: "1" }),
});
beforeEach(() => {
@@ -58,48 +55,35 @@ describe("PollCreateDialog", () => {
});
it("renders a blank poll", () => {
const dialog = mount(
<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />,
{
wrappingComponent: MatrixClientContext.Provider,
wrappingComponentProps: { value: mockClient },
},
);
const dialog = mount(<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />, {
wrappingComponent: MatrixClientContext.Provider,
wrappingComponentProps: { value: mockClient },
});
expect(dialog.html()).toMatchSnapshot();
});
it("autofocuses the poll topic on mount", () => {
const dialog = mount(
<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />,
);
expect(findById(dialog, 'poll-topic-input').at(0).props().autoFocus).toEqual(true);
const dialog = mount(<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />);
expect(findById(dialog, "poll-topic-input").at(0).props().autoFocus).toEqual(true);
});
it("autofocuses the new poll option field after clicking add option button", () => {
const dialog = mount(
<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />,
);
expect(findById(dialog, 'poll-topic-input').at(0).props().autoFocus).toEqual(true);
const dialog = mount(<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />);
expect(findById(dialog, "poll-topic-input").at(0).props().autoFocus).toEqual(true);
dialog.find("div.mx_PollCreateDialog_addOption").simulate("click");
expect(findById(dialog, 'poll-topic-input').at(0).props().autoFocus).toEqual(false);
expect(findById(dialog, 'pollcreate_option_1').at(0).props().autoFocus).toEqual(false);
expect(findById(dialog, 'pollcreate_option_2').at(0).props().autoFocus).toEqual(true);
expect(findById(dialog, "poll-topic-input").at(0).props().autoFocus).toEqual(false);
expect(findById(dialog, "pollcreate_option_1").at(0).props().autoFocus).toEqual(false);
expect(findById(dialog, "pollcreate_option_2").at(0).props().autoFocus).toEqual(true);
});
it("renders a question and some options", () => {
const dialog = mount(
<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />,
);
const dialog = mount(<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />);
expect(submitIsDisabled(dialog)).toBe(true);
// When I set some values in the boxes
changeValue(
dialog,
"Question or topic",
"How many turnips is the optimal number?",
);
changeValue(dialog, "Question or topic", "How many turnips is the optimal number?");
changeValue(dialog, "Option 1", "As many as my neighbour");
changeValue(dialog, "Option 2", "The question is meaningless");
dialog.find("div.mx_PollCreateDialog_addOption").simulate("click");
@@ -109,19 +93,11 @@ describe("PollCreateDialog", () => {
it("renders info from a previous event", () => {
const previousEvent: MatrixEvent = new MatrixEvent(
PollStartEvent.from(
"Poll Q",
["Answer 1", "Answer 2"],
M_POLL_KIND_DISCLOSED,
).serialize(),
PollStartEvent.from("Poll Q", ["Answer 1", "Answer 2"], M_POLL_KIND_DISCLOSED).serialize(),
);
const dialog = mount(
<PollCreateDialog
room={createRoom()}
onFinished={jest.fn()}
editingMxEvent={previousEvent}
/>,
<PollCreateDialog room={createRoom()} onFinished={jest.fn()} editingMxEvent={previousEvent} />,
);
expect(submitIsDisabled(dialog)).toBe(false);
@@ -129,17 +105,13 @@ describe("PollCreateDialog", () => {
});
it("doesn't allow submitting until there are options", () => {
const dialog = mount(
<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />,
);
const dialog = mount(<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />);
expect(submitIsDisabled(dialog)).toBe(true);
});
it("does allow submitting when there are options and a question", () => {
// Given a dialog with no info in (which I am unable to submit)
const dialog = mount(
<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />,
);
const dialog = mount(<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />);
expect(submitIsDisabled(dialog)).toBe(true);
// When I set some values in the boxes
@@ -152,74 +124,42 @@ describe("PollCreateDialog", () => {
});
it("shows the open poll description at first", () => {
const dialog = mount(
<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />,
);
expect(
dialog.find('select').prop("value"),
).toEqual(M_POLL_KIND_DISCLOSED.name);
expect(
dialog.find('p').text(),
).toEqual("Voters see results as soon as they have voted");
const dialog = mount(<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />);
expect(dialog.find("select").prop("value")).toEqual(M_POLL_KIND_DISCLOSED.name);
expect(dialog.find("p").text()).toEqual("Voters see results as soon as they have voted");
});
it("shows the closed poll description if we choose it", () => {
const dialog = mount(
<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />,
);
const dialog = mount(<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />);
changeKind(dialog, M_POLL_KIND_UNDISCLOSED.name);
expect(
dialog.find('select').prop("value"),
).toEqual(M_POLL_KIND_UNDISCLOSED.name);
expect(
dialog.find('p').text(),
).toEqual("Results are only revealed when you end the poll");
expect(dialog.find("select").prop("value")).toEqual(M_POLL_KIND_UNDISCLOSED.name);
expect(dialog.find("p").text()).toEqual("Results are only revealed when you end the poll");
});
it("shows the open poll description if we choose it", () => {
const dialog = mount(
<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />,
);
const dialog = mount(<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />);
changeKind(dialog, M_POLL_KIND_UNDISCLOSED.name);
changeKind(dialog, M_POLL_KIND_DISCLOSED.name);
expect(
dialog.find('select').prop("value"),
).toEqual(M_POLL_KIND_DISCLOSED.name);
expect(
dialog.find('p').text(),
).toEqual("Voters see results as soon as they have voted");
expect(dialog.find("select").prop("value")).toEqual(M_POLL_KIND_DISCLOSED.name);
expect(dialog.find("p").text()).toEqual("Voters see results as soon as they have voted");
});
it("shows the closed poll description when editing a closed poll", () => {
const previousEvent: MatrixEvent = new MatrixEvent(
PollStartEvent.from(
"Poll Q",
["Answer 1", "Answer 2"],
M_POLL_KIND_UNDISCLOSED,
).serialize(),
PollStartEvent.from("Poll Q", ["Answer 1", "Answer 2"], M_POLL_KIND_UNDISCLOSED).serialize(),
);
previousEvent.event.event_id = "$prevEventId";
const dialog = mount(
<PollCreateDialog
room={createRoom()}
onFinished={jest.fn()}
editingMxEvent={previousEvent}
/>,
<PollCreateDialog room={createRoom()} onFinished={jest.fn()} editingMxEvent={previousEvent} />,
);
expect(
dialog.find('select').prop("value"),
).toEqual(M_POLL_KIND_UNDISCLOSED.name);
expect(
dialog.find('p').text(),
).toEqual("Results are only revealed when you end the poll");
expect(dialog.find("select").prop("value")).toEqual(M_POLL_KIND_UNDISCLOSED.name);
expect(dialog.find("p").text()).toEqual("Results are only revealed when you end the poll");
});
it("displays a spinner after submitting", () => {
const dialog = mount(
<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />,
);
const dialog = mount(<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />);
changeValue(dialog, "Question or topic", "Q");
changeValue(dialog, "Option 1", "A1");
changeValue(dialog, "Option 2", "A2");
@@ -230,9 +170,7 @@ describe("PollCreateDialog", () => {
});
it("sends a poll create event when submitted", () => {
const dialog = mount(
<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />,
);
const dialog = mount(<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />);
changeValue(dialog, "Question or topic", "Q");
changeValue(dialog, "Option 1", "A1");
changeValue(dialog, "Option 2", "A2");
@@ -240,50 +178,40 @@ describe("PollCreateDialog", () => {
dialog.find("button").simulate("click");
const [, , eventType, sentEventContent] = mockClient.sendEvent.mock.calls[0];
expect(M_POLL_START.matches(eventType)).toBeTruthy();
expect(sentEventContent).toEqual(
{
[M_TEXT.name]: "Q\n1. A1\n2. A2",
[M_POLL_START.name]: {
"answers": [
{
"id": expect.any(String),
[M_TEXT.name]: "A1",
},
{
"id": expect.any(String),
[M_TEXT.name]: "A2",
},
],
"kind": M_POLL_KIND_DISCLOSED.name,
"max_selections": 1,
"question": {
"body": "Q",
"format": undefined,
"formatted_body": undefined,
"msgtype": "m.text",
[M_TEXT.name]: "Q",
expect(sentEventContent).toEqual({
[M_TEXT.name]: "Q\n1. A1\n2. A2",
[M_POLL_START.name]: {
answers: [
{
id: expect.any(String),
[M_TEXT.name]: "A1",
},
{
id: expect.any(String),
[M_TEXT.name]: "A2",
},
],
kind: M_POLL_KIND_DISCLOSED.name,
max_selections: 1,
question: {
body: "Q",
format: undefined,
formatted_body: undefined,
msgtype: "m.text",
[M_TEXT.name]: "Q",
},
},
);
});
});
it("sends a poll edit event when editing", () => {
const previousEvent: MatrixEvent = new MatrixEvent(
PollStartEvent.from(
"Poll Q",
["Answer 1", "Answer 2"],
M_POLL_KIND_DISCLOSED,
).serialize(),
PollStartEvent.from("Poll Q", ["Answer 1", "Answer 2"], M_POLL_KIND_DISCLOSED).serialize(),
);
previousEvent.event.event_id = "$prevEventId";
const dialog = mount(
<PollCreateDialog
room={createRoom()}
onFinished={jest.fn()}
editingMxEvent={previousEvent}
/>,
<PollCreateDialog room={createRoom()} onFinished={jest.fn()} editingMxEvent={previousEvent} />,
);
changeValue(dialog, "Question or topic", "Poll Q updated");
@@ -293,65 +221,51 @@ describe("PollCreateDialog", () => {
const [, , eventType, sentEventContent] = mockClient.sendEvent.mock.calls[0];
expect(M_POLL_START.matches(eventType)).toBeTruthy();
expect(sentEventContent).toEqual(
{
"m.new_content": {
[M_TEXT.name]: "Poll Q updated\n1. Answer 1\n2. Answer 2 updated",
[M_POLL_START.name]: {
"answers": [
{
"id": expect.any(String),
[M_TEXT.name]: "Answer 1",
},
{
"id": expect.any(String),
[M_TEXT.name]: "Answer 2 updated",
},
],
"kind": M_POLL_KIND_UNDISCLOSED.name,
"max_selections": 1,
"question": {
"body": "Poll Q updated",
"format": undefined,
"formatted_body": undefined,
"msgtype": "m.text",
[M_TEXT.name]: "Poll Q updated",
expect(sentEventContent).toEqual({
"m.new_content": {
[M_TEXT.name]: "Poll Q updated\n1. Answer 1\n2. Answer 2 updated",
[M_POLL_START.name]: {
answers: [
{
id: expect.any(String),
[M_TEXT.name]: "Answer 1",
},
{
id: expect.any(String),
[M_TEXT.name]: "Answer 2 updated",
},
],
kind: M_POLL_KIND_UNDISCLOSED.name,
max_selections: 1,
question: {
body: "Poll Q updated",
format: undefined,
formatted_body: undefined,
msgtype: "m.text",
[M_TEXT.name]: "Poll Q updated",
},
},
"m.relates_to": {
"event_id": previousEvent.getId(),
"rel_type": "m.replace",
},
},
);
"m.relates_to": {
event_id: previousEvent.getId(),
rel_type: "m.replace",
},
});
});
});
function createRoom(): Room {
return new Room(
"roomid",
MatrixClientPeg.get(),
"@name:example.com",
{},
);
return new Room("roomid", MatrixClientPeg.get(), "@name:example.com", {});
}
function changeValue(wrapper: ReactWrapper, labelText: string, value: string) {
wrapper.find(`input[label="${labelText}"]`).simulate(
"change",
{ target: { value: value } },
);
wrapper.find(`input[label="${labelText}"]`).simulate("change", { target: { value: value } });
}
function changeKind(wrapper: ReactWrapper, value: string) {
wrapper.find("select").simulate(
"change",
{ target: { value: value } },
);
wrapper.find("select").simulate("change", { target: { value: value } });
}
function submitIsDisabled(wrapper: ReactWrapper) {
return wrapper.find('button[type="submit"]').prop("aria-disabled") === true;
}

View File

@@ -14,12 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { fireEvent, render, screen } from "@testing-library/react";
import PowerSelector from "../../../../src/components/views/elements/PowerSelector";
describe('<PowerSelector />', () => {
describe("<PowerSelector />", () => {
it("should reset back to custom value when custom input is blurred blank", async () => {
const fn = jest.fn();
render(<PowerSelector value={25} maxValue={100} usersDefault={0} onChange={fn} />);

View File

@@ -28,7 +28,9 @@ describe("<ProgressBar/>", () => {
expect(progress.value).toBe(0);
// Await the animation to conclude to our initial value of 50
act(() => { jest.runAllTimers(); });
act(() => {
jest.runAllTimers();
});
expect(progress.position).toBe(0.5);
// Move the needle to 80%
@@ -36,7 +38,9 @@ describe("<ProgressBar/>", () => {
expect(progress.position).toBe(0.5);
// Let the animaiton run a tiny bit, assert it has moved from where it was to where it needs to go
act(() => { jest.advanceTimersByTime(150); });
act(() => {
jest.advanceTimersByTime(150);
});
expect(progress.position).toBeGreaterThan(0.5);
expect(progress.position).toBeLessThan(0.8);
});

View File

@@ -24,13 +24,13 @@ describe("<QRCode />", () => {
it("renders a QR with defaults", async () => {
const { container, getAllByAltText } = render(<QRCode data="asd" />);
await waitFor(() => getAllByAltText('QR Code').length === 1);
await waitFor(() => getAllByAltText("QR Code").length === 1);
expect(container).toMatchSnapshot();
});
it("renders a QR with high error correction level", async () => {
const { container, getAllByAltText } = render(<QRCode data="asd" errorCorrectionLevel="high" />);
await waitFor(() => getAllByAltText('QR Code').length === 1);
await waitFor(() => getAllByAltText("QR Code").length === 1);
expect(container).toMatchSnapshot();
});
});

View File

@@ -14,12 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import * as testUtils from '../../../test-utils';
import * as testUtils from "../../../test-utils";
import { getParentEventId } from "../../../../src/utils/Reply";
describe("ReplyChain", () => {
describe('getParentEventId', () => {
it('retrieves relation reply from unedited event', () => {
describe("getParentEventId", () => {
it("retrieves relation reply from unedited event", () => {
const originalEventWithRelation = testUtils.mkEvent({
event: true,
type: "m.room.message",
@@ -28,7 +28,7 @@ describe("ReplyChain", () => {
"body": "> Reply to this message\n\n foo",
"m.relates_to": {
"m.in_reply_to": {
"event_id": "$qkjmFBTEc0VvfVyzq1CJuh1QZi_xDIgNEFjZ4Pq34og",
event_id: "$qkjmFBTEc0VvfVyzq1CJuh1QZi_xDIgNEFjZ4Pq34og",
},
},
},
@@ -36,11 +36,12 @@ describe("ReplyChain", () => {
room: "room_id",
});
expect(getParentEventId(originalEventWithRelation))
.toStrictEqual('$qkjmFBTEc0VvfVyzq1CJuh1QZi_xDIgNEFjZ4Pq34og');
expect(getParentEventId(originalEventWithRelation)).toStrictEqual(
"$qkjmFBTEc0VvfVyzq1CJuh1QZi_xDIgNEFjZ4Pq34og",
);
});
it('retrieves relation reply from original event when edited', () => {
it("retrieves relation reply from original event when edited", () => {
const originalEventWithRelation = testUtils.mkEvent({
event: true,
type: "m.room.message",
@@ -49,7 +50,7 @@ describe("ReplyChain", () => {
"body": "> Reply to this message\n\n foo",
"m.relates_to": {
"m.in_reply_to": {
"event_id": "$qkjmFBTEc0VvfVyzq1CJuh1QZi_xDIgNEFjZ4Pq34og",
event_id: "$qkjmFBTEc0VvfVyzq1CJuh1QZi_xDIgNEFjZ4Pq34og",
},
},
},
@@ -64,12 +65,12 @@ describe("ReplyChain", () => {
"msgtype": "m.text",
"body": "> Reply to this message\n\n * foo bar",
"m.new_content": {
"msgtype": "m.text",
"body": "foo bar",
msgtype: "m.text",
body: "foo bar",
},
"m.relates_to": {
"rel_type": "m.replace",
"event_id": originalEventWithRelation.getId(),
rel_type: "m.replace",
event_id: originalEventWithRelation.getId(),
},
},
user: "some_other_user",
@@ -80,11 +81,12 @@ describe("ReplyChain", () => {
originalEventWithRelation.makeReplaced(editEvent);
// The relation should be pulled from the original event
expect(getParentEventId(originalEventWithRelation))
.toStrictEqual('$qkjmFBTEc0VvfVyzq1CJuh1QZi_xDIgNEFjZ4Pq34og');
expect(getParentEventId(originalEventWithRelation)).toStrictEqual(
"$qkjmFBTEc0VvfVyzq1CJuh1QZi_xDIgNEFjZ4Pq34og",
);
});
it('retrieves relation reply from edit event when provided', () => {
it("retrieves relation reply from edit event when provided", () => {
const originalEvent = testUtils.mkEvent({
event: true,
type: "m.room.message",
@@ -107,13 +109,13 @@ describe("ReplyChain", () => {
"body": "foo bar",
"m.relates_to": {
"m.in_reply_to": {
"event_id": "$qkjmFBTEc0VvfVyzq1CJuh1QZi_xDIgNEFjZ4Pq34og",
event_id: "$qkjmFBTEc0VvfVyzq1CJuh1QZi_xDIgNEFjZ4Pq34og",
},
},
},
"m.relates_to": {
"rel_type": "m.replace",
"event_id": originalEvent.getId(),
rel_type: "m.replace",
event_id: originalEvent.getId(),
},
},
user: "some_other_user",
@@ -124,11 +126,10 @@ describe("ReplyChain", () => {
originalEvent.makeReplaced(editEvent);
// The relation should be pulled from the edit event
expect(getParentEventId(originalEvent))
.toStrictEqual('$qkjmFBTEc0VvfVyzq1CJuh1QZi_xDIgNEFjZ4Pq34og');
expect(getParentEventId(originalEvent)).toStrictEqual("$qkjmFBTEc0VvfVyzq1CJuh1QZi_xDIgNEFjZ4Pq34og");
});
it('prefers relation reply from edit event over original event', () => {
it("prefers relation reply from edit event over original event", () => {
const originalEventWithRelation = testUtils.mkEvent({
event: true,
type: "m.room.message",
@@ -137,7 +138,7 @@ describe("ReplyChain", () => {
"body": "> Reply to this message\n\n foo",
"m.relates_to": {
"m.in_reply_to": {
"event_id": "$111",
event_id: "$111",
},
},
},
@@ -156,13 +157,13 @@ describe("ReplyChain", () => {
"body": "foo bar",
"m.relates_to": {
"m.in_reply_to": {
"event_id": "$999",
event_id: "$999",
},
},
},
"m.relates_to": {
"rel_type": "m.replace",
"event_id": originalEventWithRelation.getId(),
rel_type: "m.replace",
event_id: originalEventWithRelation.getId(),
},
},
user: "some_other_user",
@@ -173,10 +174,10 @@ describe("ReplyChain", () => {
originalEventWithRelation.makeReplaced(editEvent);
// The relation should be pulled from the edit event
expect(getParentEventId(originalEventWithRelation)).toStrictEqual('$999');
expect(getParentEventId(originalEventWithRelation)).toStrictEqual("$999");
});
it('able to clear relation reply from original event by providing empty relation field', () => {
it("able to clear relation reply from original event by providing empty relation field", () => {
const originalEventWithRelation = testUtils.mkEvent({
event: true,
type: "m.room.message",
@@ -185,7 +186,7 @@ describe("ReplyChain", () => {
"body": "> Reply to this message\n\n foo",
"m.relates_to": {
"m.in_reply_to": {
"event_id": "$111",
event_id: "$111",
},
},
},
@@ -206,8 +207,8 @@ describe("ReplyChain", () => {
"m.relates_to": {},
},
"m.relates_to": {
"rel_type": "m.replace",
"event_id": originalEventWithRelation.getId(),
rel_type: "m.replace",
event_id: originalEventWithRelation.getId(),
},
},
user: "some_other_user",

View File

@@ -14,47 +14,47 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { fireEvent, render } from "@testing-library/react";
import StyledRadioGroup from "../../../../src/components/views/elements/StyledRadioGroup";
describe('<StyledRadioGroup />', () => {
describe("<StyledRadioGroup />", () => {
const optionA = {
value: 'Anteater',
value: "Anteater",
label: <span>Anteater label</span>,
description: 'anteater description',
className: 'a-class',
description: "anteater description",
className: "a-class",
};
const optionB = {
value: 'Badger',
value: "Badger",
label: <span>Badger label</span>,
};
const optionC = {
value: 'Canary',
value: "Canary",
label: <span>Canary label</span>,
description: <span>Canary description</span>,
};
const defaultDefinitions = [optionA, optionB, optionC];
const defaultProps = {
name: 'test',
className: 'test-class',
name: "test",
className: "test-class",
definitions: defaultDefinitions,
onChange: jest.fn(),
};
const getComponent = (props = {}) => render(<StyledRadioGroup {...defaultProps} {...props} />);
const getInputByValue = (component, value) => component.container.querySelector(`input[value="${value}"]`);
const getCheckedInput = component => component.container.querySelector('input[checked]');
const getCheckedInput = (component) => component.container.querySelector("input[checked]");
it('renders radios correctly when no value is provided', () => {
it("renders radios correctly when no value is provided", () => {
const component = getComponent();
expect(component.asFragment()).toMatchSnapshot();
expect(getCheckedInput(component)).toBeFalsy();
});
it('selects correct button when value is provided', () => {
it("selects correct button when value is provided", () => {
const component = getComponent({
value: optionC.value,
});
@@ -62,14 +62,11 @@ describe('<StyledRadioGroup />', () => {
expect(getCheckedInput(component).value).toEqual(optionC.value);
});
it('selects correct buttons when definitions have checked prop', () => {
const definitions = [
{ ...optionA, checked: true },
optionB,
{ ...optionC, checked: false },
];
it("selects correct buttons when definitions have checked prop", () => {
const definitions = [{ ...optionA, checked: true }, optionB, { ...optionC, checked: false }];
const component = getComponent({
value: optionC.value, definitions,
value: optionC.value,
definitions,
});
expect(getInputByValue(component, optionA.value)).toBeChecked();
@@ -78,26 +75,22 @@ describe('<StyledRadioGroup />', () => {
expect(getInputByValue(component, optionC.value)).not.toBeChecked();
});
it('disables individual buttons based on definition.disabled', () => {
const definitions = [
optionA,
{ ...optionB, disabled: true },
{ ...optionC, disabled: true },
];
it("disables individual buttons based on definition.disabled", () => {
const definitions = [optionA, { ...optionB, disabled: true }, { ...optionC, disabled: true }];
const component = getComponent({ definitions });
expect(getInputByValue(component, optionA.value)).not.toBeDisabled();
expect(getInputByValue(component, optionB.value)).toBeDisabled();
expect(getInputByValue(component, optionC.value)).toBeDisabled();
});
it('disables all buttons with disabled prop', () => {
it("disables all buttons with disabled prop", () => {
const component = getComponent({ disabled: true });
expect(getInputByValue(component, optionA.value)).toBeDisabled();
expect(getInputByValue(component, optionB.value)).toBeDisabled();
expect(getInputByValue(component, optionC.value)).toBeDisabled();
});
it('calls onChange on click', () => {
it("calls onChange on click", () => {
const onChange = jest.fn();
const component = getComponent({
value: optionC.value,

View File

@@ -15,29 +15,26 @@ limitations under the License.
*/
import React from "react";
import {
renderIntoDocument,
Simulate,
} from 'react-dom/test-utils';
import { renderIntoDocument, Simulate } from "react-dom/test-utils";
import { act } from "react-dom/test-utils";
import { Alignment } from '../../../../src/components/views/elements/Tooltip';
import { Alignment } from "../../../../src/components/views/elements/Tooltip";
import TooltipTarget from "../../../../src/components/views/elements/TooltipTarget";
describe('<TooltipTarget />', () => {
describe("<TooltipTarget />", () => {
const defaultProps = {
"tooltipTargetClassName": 'test tooltipTargetClassName',
"className": 'test className',
"tooltipClassName": 'test tooltipClassName',
"label": 'test label',
"tooltipTargetClassName": "test tooltipTargetClassName",
"className": "test className",
"tooltipClassName": "test tooltipClassName",
"label": "test label",
"alignment": Alignment.Left,
"id": 'test id',
'data-test-id': 'test',
"id": "test id",
"data-test-id": "test",
};
afterEach(() => {
// clean up renderer tooltips
const wrapper = document.querySelector('.mx_Tooltip_wrapper');
const wrapper = document.querySelector(".mx_Tooltip_wrapper");
while (wrapper?.firstChild) {
wrapper.removeChild(wrapper.lastChild);
}
@@ -45,19 +42,19 @@ describe('<TooltipTarget />', () => {
const getComponent = (props = {}) => {
const wrapper = renderIntoDocument<HTMLSpanElement>(
// wrap in element so renderIntoDocument can render functional component
// wrap in element so renderIntoDocument can render functional component
<span>
<TooltipTarget {...defaultProps} {...props}>
<span>child</span>
</TooltipTarget>
</span>,
) as HTMLSpanElement;
return wrapper.querySelector('[data-test-id=test]');
return wrapper.querySelector("[data-test-id=test]");
};
const getVisibleTooltip = () => document.querySelector('.mx_Tooltip.mx_Tooltip_visible');
const getVisibleTooltip = () => document.querySelector(".mx_Tooltip.mx_Tooltip_visible");
it('renders container', () => {
it("renders container", () => {
const component = getComponent();
expect(component).toMatchSnapshot();
expect(getVisibleTooltip()).toBeFalsy();
@@ -72,7 +69,7 @@ describe('<TooltipTarget />', () => {
expect(getVisibleTooltip()).toMatchSnapshot();
});
it('hides tooltip on mouseleave', () => {
it("hides tooltip on mouseleave", () => {
const wrapper = getComponent();
act(() => {
Simulate.mouseOver(wrapper);
@@ -84,7 +81,7 @@ describe('<TooltipTarget />', () => {
expect(getVisibleTooltip()).toBeFalsy();
});
it('displays tooltip on focus', () => {
it("displays tooltip on focus", () => {
const wrapper = getComponent();
act(() => {
Simulate.focus(wrapper);
@@ -92,7 +89,7 @@ describe('<TooltipTarget />', () => {
expect(getVisibleTooltip()).toBeTruthy();
});
it('hides tooltip on blur', async () => {
it("hides tooltip on blur", async () => {
const wrapper = getComponent();
act(() => {
Simulate.focus(wrapper);

View File

@@ -14,51 +14,52 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
// eslint-disable-next-line deprecate/import
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import { mount } from "enzyme";
import { act } from "react-dom/test-utils";
import LiveDurationDropdown, { DEFAULT_DURATION_MS }
from '../../../../src/components/views/location/LiveDurationDropdown';
import { findById, mockPlatformPeg } from '../../../test-utils';
import LiveDurationDropdown, {
DEFAULT_DURATION_MS,
} from "../../../../src/components/views/location/LiveDurationDropdown";
import { findById, mockPlatformPeg } from "../../../test-utils";
mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) });
describe('<LiveDurationDropdown />', () => {
describe("<LiveDurationDropdown />", () => {
const defaultProps = {
timeout: DEFAULT_DURATION_MS,
onChange: jest.fn(),
};
const getComponent = (props = {}) =>
mount(<LiveDurationDropdown {...defaultProps} {...props} />);
const getComponent = (props = {}) => mount(<LiveDurationDropdown {...defaultProps} {...props} />);
const getOption = (wrapper, timeout) => findById(wrapper, `live-duration__${timeout}`).at(0);
const getSelectedOption = (wrapper) => findById(wrapper, 'live-duration_value');
const openDropdown = (wrapper) => act(() => {
wrapper.find('[role="button"]').at(0).simulate('click');
wrapper.setProps({});
});
const getSelectedOption = (wrapper) => findById(wrapper, "live-duration_value");
const openDropdown = (wrapper) =>
act(() => {
wrapper.find('[role="button"]').at(0).simulate("click");
wrapper.setProps({});
});
it('renders timeout as selected option', () => {
it("renders timeout as selected option", () => {
const wrapper = getComponent();
expect(getSelectedOption(wrapper).text()).toEqual('Share for 15m');
expect(getSelectedOption(wrapper).text()).toEqual("Share for 15m");
});
it('renders non-default timeout as selected option', () => {
it("renders non-default timeout as selected option", () => {
const timeout = 1234567;
const wrapper = getComponent({ timeout });
expect(getSelectedOption(wrapper).text()).toEqual(`Share for 21m`);
});
it('renders a dropdown option for a non-default timeout value', () => {
it("renders a dropdown option for a non-default timeout value", () => {
const timeout = 1234567;
const wrapper = getComponent({ timeout });
openDropdown(wrapper);
expect(getOption(wrapper, timeout).text()).toEqual(`Share for 21m`);
});
it('updates value on option selection', () => {
it("updates value on option selection", () => {
const onChange = jest.fn();
const wrapper = getComponent({ onChange });
@@ -67,7 +68,7 @@ describe('<LiveDurationDropdown />', () => {
openDropdown(wrapper);
act(() => {
getOption(wrapper, ONE_HOUR).simulate('click');
getOption(wrapper, ONE_HOUR).simulate("click");
});
expect(onChange).toHaveBeenCalledWith(ONE_HOUR);

View File

@@ -14,34 +14,34 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import maplibregl from "maplibre-gl";
// eslint-disable-next-line deprecate/import
import { mount } from "enzyme";
import { act } from 'react-dom/test-utils';
import { act } from "react-dom/test-utils";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { MatrixClient } from 'matrix-js-sdk/src/client';
import { mocked } from 'jest-mock';
import { logger } from 'matrix-js-sdk/src/logger';
import { MatrixClient } from "matrix-js-sdk/src/client";
import { mocked } from "jest-mock";
import { logger } from "matrix-js-sdk/src/logger";
import LocationPicker from "../../../../src/components/views/location/LocationPicker";
import { LocationShareType } from "../../../../src/components/views/location/shareLocation";
import MatrixClientContext from '../../../../src/contexts/MatrixClientContext';
import { MatrixClientPeg } from '../../../../src/MatrixClientPeg';
import { findById, findByTestId, mockPlatformPeg } from '../../../test-utils';
import { findMapStyleUrl, LocationShareError } from '../../../../src/utils/location';
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import { findById, findByTestId, mockPlatformPeg } from "../../../test-utils";
import { findMapStyleUrl, LocationShareError } from "../../../../src/utils/location";
jest.mock('../../../../src/utils/location/findMapStyleUrl', () => ({
findMapStyleUrl: jest.fn().mockReturnValue('tileserver.com'),
jest.mock("../../../../src/utils/location/findMapStyleUrl", () => ({
findMapStyleUrl: jest.fn().mockReturnValue("tileserver.com"),
}));
// dropdown uses this
mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) });
describe("LocationPicker", () => {
describe('<LocationPicker />', () => {
const roomId = '!room:server.org';
const userId = '@user:server.org';
describe("<LocationPicker />", () => {
const roomId = "!room:server.org";
const userId = "@user:server.org";
const sender = new RoomMember(roomId, userId);
const defaultProps = {
sender,
@@ -56,10 +56,11 @@ describe("LocationPicker", () => {
isGuest: jest.fn(),
getClientWellKnown: jest.fn(),
};
const getComponent = (props = {}) => mount(<LocationPicker {...defaultProps} {...props} />, {
wrappingComponent: MatrixClientContext.Provider,
wrappingComponentProps: { value: mockClient },
});
const getComponent = (props = {}) =>
mount(<LocationPicker {...defaultProps} {...props} />, {
wrappingComponent: MatrixClientContext.Provider,
wrappingComponentProps: { value: mockClient },
});
const mockMap = new maplibregl.Map();
const mockGeolocate = new maplibregl.GeolocateControl();
@@ -82,33 +83,33 @@ describe("LocationPicker", () => {
};
beforeEach(() => {
jest.spyOn(logger, 'error').mockRestore();
jest.spyOn(MatrixClientPeg, 'get').mockReturnValue(mockClient as unknown as MatrixClient);
jest.spyOn(logger, "error").mockRestore();
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient as unknown as MatrixClient);
jest.clearAllMocks();
mocked(mockMap).addControl.mockReset();
mocked(findMapStyleUrl).mockReturnValue('tileserver.com');
mocked(findMapStyleUrl).mockReturnValue("tileserver.com");
});
it('displays error when map emits an error', () => {
it("displays error when map emits an error", () => {
// suppress expected error log
jest.spyOn(logger, 'error').mockImplementation(() => { });
jest.spyOn(logger, "error").mockImplementation(() => {});
const wrapper = getComponent();
act(() => {
// @ts-ignore
mocked(mockMap).emit('error', { error: 'Something went wrong' });
mocked(mockMap).emit("error", { error: "Something went wrong" });
wrapper.setProps({});
});
expect(findByTestId(wrapper, 'map-rendering-error').find('p').text()).toEqual(
"This homeserver is not configured correctly to display maps, "
+ "or the configured map server may be unreachable.",
expect(findByTestId(wrapper, "map-rendering-error").find("p").text()).toEqual(
"This homeserver is not configured correctly to display maps, " +
"or the configured map server may be unreachable.",
);
});
it('displays error when map display is not configured properly', () => {
it("displays error when map display is not configured properly", () => {
// suppress expected error log
jest.spyOn(logger, 'error').mockImplementation(() => { });
jest.spyOn(logger, "error").mockImplementation(() => {});
mocked(findMapStyleUrl).mockImplementation(() => {
throw new Error(LocationShareError.MapStyleUrlNotConfigured);
});
@@ -116,111 +117,111 @@ describe("LocationPicker", () => {
const wrapper = getComponent();
wrapper.setProps({});
expect(findByTestId(wrapper, 'map-rendering-error').find('p').text()).toEqual(
expect(findByTestId(wrapper, "map-rendering-error").find("p").text()).toEqual(
"This homeserver is not configured to display maps.",
);
});
it('displays error when map setup throws', () => {
it("displays error when map setup throws", () => {
// suppress expected error log
jest.spyOn(logger, 'error').mockImplementation(() => { });
jest.spyOn(logger, "error").mockImplementation(() => {});
// throw an error
mocked(mockMap).addControl.mockImplementation(() => { throw new Error('oups'); });
mocked(mockMap).addControl.mockImplementation(() => {
throw new Error("oups");
});
const wrapper = getComponent();
wrapper.setProps({});
expect(findByTestId(wrapper, 'map-rendering-error').find('p').text()).toEqual(
"This homeserver is not configured correctly to display maps, "
+ "or the configured map server may be unreachable.",
expect(findByTestId(wrapper, "map-rendering-error").find("p").text()).toEqual(
"This homeserver is not configured correctly to display maps, " +
"or the configured map server may be unreachable.",
);
});
it('initiates map with geolocation', () => {
it("initiates map with geolocation", () => {
getComponent();
expect(mockMap.addControl).toHaveBeenCalledWith(mockGeolocate);
act(() => {
// @ts-ignore
mocked(mockMap).emit('load');
mocked(mockMap).emit("load");
});
expect(mockGeolocate.trigger).toHaveBeenCalled();
});
const testUserLocationShareTypes = (shareType: LocationShareType.Own | LocationShareType.Live) => {
describe('user location behaviours', () => {
it('closes and displays error when geolocation errors', () => {
describe("user location behaviours", () => {
it("closes and displays error when geolocation errors", () => {
// suppress expected error log
jest.spyOn(logger, 'error').mockImplementation(() => { });
jest.spyOn(logger, "error").mockImplementation(() => {});
const onFinished = jest.fn();
getComponent({ onFinished, shareType });
expect(mockMap.addControl).toHaveBeenCalledWith(mockGeolocate);
act(() => {
// @ts-ignore
mockMap.emit('load');
mockMap.emit("load");
// @ts-ignore
mockGeolocate.emit('error', {});
mockGeolocate.emit("error", {});
});
// dialog is closed on error
expect(onFinished).toHaveBeenCalled();
});
it('sets position on geolocate event', () => {
it("sets position on geolocate event", () => {
const wrapper = getComponent({ shareType });
act(() => {
// @ts-ignore
mocked(mockGeolocate).emit('geolocate', mockGeolocationPosition);
mocked(mockGeolocate).emit("geolocate", mockGeolocationPosition);
wrapper.setProps({});
});
// marker added
expect(maplibregl.Marker).toHaveBeenCalled();
expect(mockMarker.setLngLat).toHaveBeenCalledWith(new maplibregl.LngLat(
12.4, 43.2,
));
expect(mockMarker.setLngLat).toHaveBeenCalledWith(new maplibregl.LngLat(12.4, 43.2));
// submit button is enabled when position is truthy
expect(findByTestId(wrapper, 'location-picker-submit-button').at(0).props().disabled).toBeFalsy();
expect(wrapper.find('MemberAvatar').length).toBeTruthy();
expect(findByTestId(wrapper, "location-picker-submit-button").at(0).props().disabled).toBeFalsy();
expect(wrapper.find("MemberAvatar").length).toBeTruthy();
});
it('disables submit button until geolocation completes', () => {
it("disables submit button until geolocation completes", () => {
const onChoose = jest.fn();
const wrapper = getComponent({ shareType, onChoose });
// submit button is enabled when position is truthy
expect(findByTestId(wrapper, 'location-picker-submit-button').at(0).props().disabled).toBeTruthy();
expect(findByTestId(wrapper, "location-picker-submit-button").at(0).props().disabled).toBeTruthy();
act(() => {
findByTestId(wrapper, 'location-picker-submit-button').at(0).simulate('click');
findByTestId(wrapper, "location-picker-submit-button").at(0).simulate("click");
});
// nothing happens on button click
expect(onChoose).not.toHaveBeenCalled();
act(() => {
// @ts-ignore
mocked(mockGeolocate).emit('geolocate', mockGeolocationPosition);
mocked(mockGeolocate).emit("geolocate", mockGeolocationPosition);
wrapper.setProps({});
});
// submit button is enabled when position is truthy
expect(findByTestId(wrapper, 'location-picker-submit-button').at(0).props().disabled).toBeFalsy();
expect(findByTestId(wrapper, "location-picker-submit-button").at(0).props().disabled).toBeFalsy();
});
it('submits location', () => {
it("submits location", () => {
const onChoose = jest.fn();
const wrapper = getComponent({ onChoose, shareType });
act(() => {
// @ts-ignore
mocked(mockGeolocate).emit('geolocate', mockGeolocationPosition);
mocked(mockGeolocate).emit("geolocate", mockGeolocationPosition);
// make sure button is enabled
wrapper.setProps({});
});
act(() => {
findByTestId(wrapper, 'location-picker-submit-button').at(0).simulate('click');
findByTestId(wrapper, "location-picker-submit-button").at(0).simulate("click");
});
// content of this call is tested in LocationShareMenu-test
@@ -229,67 +230,68 @@ describe("LocationPicker", () => {
});
};
describe('for Own location share type', () => {
describe("for Own location share type", () => {
testUserLocationShareTypes(LocationShareType.Own);
});
describe('for Live location share type', () => {
describe("for Live location share type", () => {
const shareType = LocationShareType.Live;
testUserLocationShareTypes(shareType);
const getOption = (wrapper, timeout) => findById(wrapper, `live-duration__${timeout}`).at(0);
const getDropdown = wrapper => findByTestId(wrapper, 'live-duration-dropdown');
const getSelectedOption = (wrapper) => findById(wrapper, 'live-duration_value');
const getDropdown = (wrapper) => findByTestId(wrapper, "live-duration-dropdown");
const getSelectedOption = (wrapper) => findById(wrapper, "live-duration_value");
const openDropdown = (wrapper) => act(() => {
const dropdown = getDropdown(wrapper);
dropdown.find('[role="button"]').at(0).simulate('click');
wrapper.setProps({});
});
const openDropdown = (wrapper) =>
act(() => {
const dropdown = getDropdown(wrapper);
dropdown.find('[role="button"]').at(0).simulate("click");
wrapper.setProps({});
});
it('renders live duration dropdown with default option', () => {
it("renders live duration dropdown with default option", () => {
const wrapper = getComponent({ shareType });
expect(getSelectedOption(getDropdown(wrapper)).text()).toEqual('Share for 15m');
expect(getSelectedOption(getDropdown(wrapper)).text()).toEqual("Share for 15m");
});
it('updates selected duration', () => {
it("updates selected duration", () => {
const wrapper = getComponent({ shareType });
openDropdown(wrapper);
const dropdown = getDropdown(wrapper);
act(() => {
getOption(dropdown, 3600000).simulate('click');
getOption(dropdown, 3600000).simulate("click");
});
// value updated
expect(getSelectedOption(getDropdown(wrapper)).text()).toEqual('Share for 1h');
expect(getSelectedOption(getDropdown(wrapper)).text()).toEqual("Share for 1h");
});
});
describe('for Pin drop location share type', () => {
describe("for Pin drop location share type", () => {
const shareType = LocationShareType.Pin;
it('initiates map with geolocation', () => {
it("initiates map with geolocation", () => {
getComponent({ shareType });
expect(mockMap.addControl).toHaveBeenCalledWith(mockGeolocate);
act(() => {
// @ts-ignore
mocked(mockMap).emit('load');
mocked(mockMap).emit("load");
});
expect(mockGeolocate.trigger).toHaveBeenCalled();
});
it('removes geolocation control on geolocation error', () => {
it("removes geolocation control on geolocation error", () => {
// suppress expected error log
jest.spyOn(logger, 'error').mockImplementation(() => { });
jest.spyOn(logger, "error").mockImplementation(() => {});
const onFinished = jest.fn();
getComponent({ onFinished, shareType });
act(() => {
// @ts-ignore
mockMap.emit('load');
mockMap.emit("load");
// @ts-ignore
mockGeolocate.emit('error', {});
mockGeolocate.emit("error", {});
});
expect(mockMap.removeControl).toHaveBeenCalledWith(mockGeolocate);
@@ -297,47 +299,45 @@ describe("LocationPicker", () => {
expect(onFinished).not.toHaveBeenCalled();
});
it('does not set position on geolocate event', () => {
it("does not set position on geolocate event", () => {
mocked(maplibregl.Marker).mockClear();
const wrapper = getComponent({ shareType });
act(() => {
// @ts-ignore
mocked(mockGeolocate).emit('geolocate', mockGeolocationPosition);
mocked(mockGeolocate).emit("geolocate", mockGeolocationPosition);
});
// marker not added
expect(wrapper.find('Marker').length).toBeFalsy();
expect(wrapper.find("Marker").length).toBeFalsy();
});
it('sets position on click event', () => {
it("sets position on click event", () => {
const wrapper = getComponent({ shareType });
act(() => {
// @ts-ignore
mocked(mockMap).emit('click', mockClickEvent);
mocked(mockMap).emit("click", mockClickEvent);
wrapper.setProps({});
});
// marker added
expect(maplibregl.Marker).toHaveBeenCalled();
expect(mockMarker.setLngLat).toHaveBeenCalledWith(new maplibregl.LngLat(
12.4, 43.2,
));
expect(mockMarker.setLngLat).toHaveBeenCalledWith(new maplibregl.LngLat(12.4, 43.2));
// marker is set, icon not avatar
expect(wrapper.find('.mx_Marker_icon').length).toBeTruthy();
expect(wrapper.find(".mx_Marker_icon").length).toBeTruthy();
});
it('submits location', () => {
it("submits location", () => {
const onChoose = jest.fn();
const wrapper = getComponent({ onChoose, shareType });
act(() => {
// @ts-ignore
mocked(mockMap).emit('click', mockClickEvent);
mocked(mockMap).emit("click", mockClickEvent);
wrapper.setProps({});
});
act(() => {
findByTestId(wrapper, 'location-picker-submit-button').at(0).simulate('click');
findByTestId(wrapper, "location-picker-submit-button").at(0).simulate("click");
});
// content of this call is tested in LocationShareMenu-test

View File

@@ -14,43 +14,43 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
// eslint-disable-next-line deprecate/import
import { mount, ReactWrapper } from 'enzyme';
import { mocked } from 'jest-mock';
import { RoomMember } from 'matrix-js-sdk/src/models/room-member';
import { MatrixClient } from 'matrix-js-sdk/src/client';
import { RelationType } from 'matrix-js-sdk/src/matrix';
import { logger } from 'matrix-js-sdk/src/logger';
import { M_ASSET, LocationAssetType } from 'matrix-js-sdk/src/@types/location';
import { act } from 'react-dom/test-utils';
import { mount, ReactWrapper } from "enzyme";
import { mocked } from "jest-mock";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { RelationType } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import { M_ASSET, LocationAssetType } from "matrix-js-sdk/src/@types/location";
import { act } from "react-dom/test-utils";
import LocationShareMenu from '../../../../src/components/views/location/LocationShareMenu';
import MatrixClientContext from '../../../../src/contexts/MatrixClientContext';
import { ChevronFace } from '../../../../src/components/structures/ContextMenu';
import SettingsStore from '../../../../src/settings/SettingsStore';
import { MatrixClientPeg } from '../../../../src/MatrixClientPeg';
import { LocationShareType } from '../../../../src/components/views/location/shareLocation';
import LocationShareMenu from "../../../../src/components/views/location/LocationShareMenu";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
import { ChevronFace } from "../../../../src/components/structures/ContextMenu";
import SettingsStore from "../../../../src/settings/SettingsStore";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import { LocationShareType } from "../../../../src/components/views/location/shareLocation";
import {
findByTagAndTestId,
findByTestId,
flushPromisesWithFakeTimers,
getMockClientWithEventEmitter,
setupAsyncStoreWithClient,
} from '../../../test-utils';
import Modal from '../../../../src/Modal';
import { DEFAULT_DURATION_MS } from '../../../../src/components/views/location/LiveDurationDropdown';
import { OwnBeaconStore } from '../../../../src/stores/OwnBeaconStore';
import { SettingLevel } from '../../../../src/settings/SettingLevel';
import QuestionDialog from '../../../../src/components/views/dialogs/QuestionDialog';
} from "../../../test-utils";
import Modal from "../../../../src/Modal";
import { DEFAULT_DURATION_MS } from "../../../../src/components/views/location/LiveDurationDropdown";
import { OwnBeaconStore } from "../../../../src/stores/OwnBeaconStore";
import { SettingLevel } from "../../../../src/settings/SettingLevel";
import QuestionDialog from "../../../../src/components/views/dialogs/QuestionDialog";
jest.useFakeTimers();
jest.mock('../../../../src/utils/location/findMapStyleUrl', () => ({
findMapStyleUrl: jest.fn().mockReturnValue('test'),
jest.mock("../../../../src/utils/location/findMapStyleUrl", () => ({
findMapStyleUrl: jest.fn().mockReturnValue("test"),
}));
jest.mock('../../../../src/settings/SettingsStore', () => ({
jest.mock("../../../../src/settings/SettingsStore", () => ({
getValue: jest.fn(),
setValue: jest.fn(),
monitorSetting: jest.fn(),
@@ -58,44 +58,45 @@ jest.mock('../../../../src/settings/SettingsStore', () => ({
unwatchSetting: jest.fn(),
}));
jest.mock('../../../../src/stores/OwnProfileStore', () => ({
jest.mock("../../../../src/stores/OwnProfileStore", () => ({
OwnProfileStore: {
instance: {
displayName: 'Ernie',
getHttpAvatarUrl: jest.fn().mockReturnValue('image.com/img'),
displayName: "Ernie",
getHttpAvatarUrl: jest.fn().mockReturnValue("image.com/img"),
},
},
}));
jest.mock('../../../../src/Modal', () => ({
jest.mock("../../../../src/Modal", () => ({
createDialog: jest.fn(),
on: jest.fn(),
off: jest.fn(),
ModalManagerEvent: { Opened: "opened" },
}));
describe('<LocationShareMenu />', () => {
const userId = '@ernie:server.org';
describe("<LocationShareMenu />", () => {
const userId = "@ernie:server.org";
const mockClient = getMockClientWithEventEmitter({
getUserId: jest.fn().mockReturnValue(userId),
getClientWellKnown: jest.fn().mockResolvedValue({
map_style_url: 'maps.com',
map_style_url: "maps.com",
}),
sendMessage: jest.fn(),
unstable_createLiveBeacon: jest.fn().mockResolvedValue({ event_id: '1' }),
unstable_setLiveBeacon: jest.fn().mockResolvedValue({ event_id: '1' }),
unstable_createLiveBeacon: jest.fn().mockResolvedValue({ event_id: "1" }),
unstable_setLiveBeacon: jest.fn().mockResolvedValue({ event_id: "1" }),
getVisibleRooms: jest.fn().mockReturnValue([]),
});
const defaultProps = {
menuPosition: {
top: 1, left: 1,
top: 1,
left: 1,
chevronFace: ChevronFace.Bottom,
},
onFinished: jest.fn(),
openMenu: jest.fn(),
roomId: '!room:server.org',
sender: new RoomMember('!room:server.org', userId),
roomId: "!room:server.org",
sender: new RoomMember("!room:server.org", userId),
};
const position = {
@@ -105,7 +106,7 @@ describe('<LocationShareMenu />', () => {
accuracy: 10,
},
timestamp: 1646305006802,
type: 'geolocate',
type: "geolocate",
};
const makeOwnBeaconStore = async () => {
@@ -122,11 +123,11 @@ describe('<LocationShareMenu />', () => {
});
beforeEach(async () => {
jest.spyOn(logger, 'error').mockRestore();
jest.spyOn(logger, "error").mockRestore();
mocked(SettingsStore).getValue.mockReturnValue(false);
mockClient.sendMessage.mockClear();
mockClient.unstable_createLiveBeacon.mockClear().mockResolvedValue({ event_id: '1' });
jest.spyOn(MatrixClientPeg, 'get').mockReturnValue(mockClient as unknown as MatrixClient);
mockClient.unstable_createLiveBeacon.mockClear().mockResolvedValue({ event_id: "1" });
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient as unknown as MatrixClient);
mocked(Modal).createDialog.mockClear();
jest.clearAllMocks();
@@ -135,20 +136,20 @@ describe('<LocationShareMenu />', () => {
});
const getShareTypeOption = (component: ReactWrapper, shareType: LocationShareType) =>
findByTagAndTestId(component, `share-location-option-${shareType}`, 'button');
findByTagAndTestId(component, `share-location-option-${shareType}`, "button");
const getBackButton = (component: ReactWrapper) =>
findByTagAndTestId(component, 'share-dialog-buttons-back', 'button');
findByTagAndTestId(component, "share-dialog-buttons-back", "button");
const getCancelButton = (component: ReactWrapper) =>
findByTagAndTestId(component, 'share-dialog-buttons-cancel', 'button');
findByTagAndTestId(component, "share-dialog-buttons-cancel", "button");
const getSubmitButton = (component: ReactWrapper) =>
findByTagAndTestId(component, 'location-picker-submit-button', 'button');
findByTagAndTestId(component, "location-picker-submit-button", "button");
const setLocation = (component: ReactWrapper) => {
// set the location
const locationPickerInstance = component.find('LocationPicker').instance();
const locationPickerInstance = component.find("LocationPicker").instance();
act(() => {
// @ts-ignore
locationPickerInstance.onGeolocate(position);
@@ -159,38 +160,38 @@ describe('<LocationShareMenu />', () => {
const setShareType = (component: ReactWrapper, shareType: LocationShareType) =>
act(() => {
getShareTypeOption(component, shareType).at(0).simulate('click');
getShareTypeOption(component, shareType).at(0).simulate("click");
component.setProps({});
});
describe('when only Own share type is enabled', () => {
describe("when only Own share type is enabled", () => {
beforeEach(() => enableSettings([]));
it('renders own and live location options', () => {
it("renders own and live location options", () => {
const component = getComponent();
expect(getShareTypeOption(component, LocationShareType.Own).length).toBe(1);
expect(getShareTypeOption(component, LocationShareType.Live).length).toBe(1);
});
it('renders back button from location picker screen', () => {
it("renders back button from location picker screen", () => {
const component = getComponent();
setShareType(component, LocationShareType.Own);
expect(getBackButton(component).length).toBe(1);
});
it('clicking cancel button from location picker closes dialog', () => {
it("clicking cancel button from location picker closes dialog", () => {
const onFinished = jest.fn();
const component = getComponent({ onFinished });
act(() => {
getCancelButton(component).at(0).simulate('click');
getCancelButton(component).at(0).simulate("click");
});
expect(onFinished).toHaveBeenCalled();
});
it('creates static own location share event on submission', () => {
it("creates static own location share event on submission", () => {
const onFinished = jest.fn();
const component = getComponent({ onFinished });
@@ -199,7 +200,7 @@ describe('<LocationShareMenu />', () => {
setLocation(component);
act(() => {
getSubmitButton(component).at(0).simulate('click');
getSubmitButton(component).at(0).simulate("click");
component.setProps({});
});
@@ -207,66 +208,68 @@ describe('<LocationShareMenu />', () => {
const [messageRoomId, relation, messageBody] = mockClient.sendMessage.mock.calls[0];
expect(messageRoomId).toEqual(defaultProps.roomId);
expect(relation).toEqual(null);
expect(messageBody).toEqual(expect.objectContaining({
[M_ASSET.name]: {
type: LocationAssetType.Self,
},
}));
expect(messageBody).toEqual(
expect.objectContaining({
[M_ASSET.name]: {
type: LocationAssetType.Self,
},
}),
);
});
});
describe('with pin drop share type enabled', () => {
it('renders share type switch with own and pin drop options', () => {
describe("with pin drop share type enabled", () => {
it("renders share type switch with own and pin drop options", () => {
const component = getComponent();
expect(component.find('LocationPicker').length).toBe(0);
expect(component.find("LocationPicker").length).toBe(0);
expect(getShareTypeOption(component, LocationShareType.Own).length).toBe(1);
expect(getShareTypeOption(component, LocationShareType.Pin).length).toBe(1);
});
it('does not render back button on share type screen', () => {
it("does not render back button on share type screen", () => {
const component = getComponent();
expect(getBackButton(component).length).toBe(0);
});
it('clicking cancel button from share type screen closes dialog', () => {
it("clicking cancel button from share type screen closes dialog", () => {
const onFinished = jest.fn();
const component = getComponent({ onFinished });
act(() => {
getCancelButton(component).at(0).simulate('click');
getCancelButton(component).at(0).simulate("click");
});
expect(onFinished).toHaveBeenCalled();
});
it('selecting own location share type advances to location picker', () => {
it("selecting own location share type advances to location picker", () => {
const component = getComponent();
setShareType(component, LocationShareType.Own);
expect(component.find('LocationPicker').length).toBe(1);
expect(component.find("LocationPicker").length).toBe(1);
});
it('clicking back button from location picker screen goes back to share screen', () => {
it("clicking back button from location picker screen goes back to share screen", () => {
const onFinished = jest.fn();
const component = getComponent({ onFinished });
// advance to location picker
setShareType(component, LocationShareType.Own);
expect(component.find('LocationPicker').length).toBe(1);
expect(component.find("LocationPicker").length).toBe(1);
act(() => {
getBackButton(component).at(0).simulate('click');
getBackButton(component).at(0).simulate("click");
component.setProps({});
});
// back to share type
expect(component.find('ShareType').length).toBe(1);
expect(component.find("ShareType").length).toBe(1);
});
it('creates pin drop location share event on submission', () => {
it("creates pin drop location share event on submission", () => {
const onFinished = jest.fn();
const component = getComponent({ onFinished });
@@ -276,7 +279,7 @@ describe('<LocationShareMenu />', () => {
setLocation(component);
act(() => {
getSubmitButton(component).at(0).simulate('click');
getSubmitButton(component).at(0).simulate("click");
component.setProps({});
});
@@ -284,76 +287,81 @@ describe('<LocationShareMenu />', () => {
const [messageRoomId, relation, messageBody] = mockClient.sendMessage.mock.calls[0];
expect(messageRoomId).toEqual(defaultProps.roomId);
expect(relation).toEqual(null);
expect(messageBody).toEqual(expect.objectContaining({
[M_ASSET.name]: {
type: LocationAssetType.Pin,
},
}));
expect(messageBody).toEqual(
expect.objectContaining({
[M_ASSET.name]: {
type: LocationAssetType.Pin,
},
}),
);
});
});
describe('with live location disabled', () => {
describe("with live location disabled", () => {
beforeEach(() => enableSettings([]));
const getToggle = (component: ReactWrapper) =>
findByTestId(component, 'enable-live-share-toggle').find('[role="switch"]').at(0);
findByTestId(component, "enable-live-share-toggle").find('[role="switch"]').at(0);
const getSubmitEnableButton = (component: ReactWrapper) =>
findByTestId(component, 'enable-live-share-submit').at(0);
findByTestId(component, "enable-live-share-submit").at(0);
it('goes to labs flag screen after live options is clicked', () => {
it("goes to labs flag screen after live options is clicked", () => {
const onFinished = jest.fn();
const component = getComponent({ onFinished });
setShareType(component, LocationShareType.Live);
expect(findByTestId(component, 'location-picker-enable-live-share')).toMatchSnapshot();
expect(findByTestId(component, "location-picker-enable-live-share")).toMatchSnapshot();
});
it('disables OK button when labs flag is not enabled', () => {
it("disables OK button when labs flag is not enabled", () => {
const component = getComponent();
setShareType(component, LocationShareType.Live);
expect(getSubmitEnableButton(component).props()['disabled']).toBeTruthy();
expect(getSubmitEnableButton(component).props()["disabled"]).toBeTruthy();
});
it('enables OK button when labs flag is toggled to enabled', () => {
it("enables OK button when labs flag is toggled to enabled", () => {
const component = getComponent();
setShareType(component, LocationShareType.Live);
act(() => {
getToggle(component).simulate('click');
getToggle(component).simulate("click");
component.setProps({});
});
expect(getSubmitEnableButton(component).props()['disabled']).toBeFalsy();
expect(getSubmitEnableButton(component).props()["disabled"]).toBeFalsy();
});
it('enables live share setting on ok button submit', () => {
it("enables live share setting on ok button submit", () => {
const component = getComponent();
setShareType(component, LocationShareType.Live);
act(() => {
getToggle(component).simulate('click');
getToggle(component).simulate("click");
component.setProps({});
});
act(() => {
getSubmitEnableButton(component).simulate('click');
getSubmitEnableButton(component).simulate("click");
});
expect(SettingsStore.setValue).toHaveBeenCalledWith(
'feature_location_share_live', undefined, SettingLevel.DEVICE, true,
"feature_location_share_live",
undefined,
SettingLevel.DEVICE,
true,
);
});
it('navigates to location picker when live share is enabled in settings store', () => {
it("navigates to location picker when live share is enabled in settings store", () => {
// @ts-ignore
mocked(SettingsStore.watchSetting).mockImplementation((featureName, roomId, callback) => {
callback(featureName, roomId, SettingLevel.DEVICE, '', '');
callback(featureName, roomId, SettingLevel.DEVICE, "", "");
window.setTimeout(() => {
callback(featureName, roomId, SettingLevel.DEVICE, '', '');
callback(featureName, roomId, SettingLevel.DEVICE, "", "");
}, 1000);
});
mocked(SettingsStore.getValue).mockReturnValue(false);
@@ -362,7 +370,7 @@ describe('<LocationShareMenu />', () => {
setShareType(component, LocationShareType.Live);
// we're on enable live share screen
expect(findByTestId(component, 'location-picker-enable-live-share').length).toBeTruthy();
expect(findByTestId(component, "location-picker-enable-live-share").length).toBeTruthy();
act(() => {
mocked(SettingsStore.getValue).mockReturnValue(true);
@@ -373,24 +381,24 @@ describe('<LocationShareMenu />', () => {
component.setProps({});
// advanced to location picker
expect(component.find('LocationPicker').length).toBeTruthy();
expect(component.find("LocationPicker").length).toBeTruthy();
});
});
describe('Live location share', () => {
describe("Live location share", () => {
beforeEach(() => enableSettings(["feature_location_share_live"]));
it('does not display live location share option when composer has a relation', () => {
it("does not display live location share option when composer has a relation", () => {
const relation = {
rel_type: RelationType.Thread,
event_id: '12345',
event_id: "12345",
};
const component = getComponent({ relation });
expect(getShareTypeOption(component, LocationShareType.Live).length).toBeFalsy();
});
it('creates beacon info event on submission', async () => {
it("creates beacon info event on submission", async () => {
const onFinished = jest.fn();
const component = getComponent({ onFinished });
@@ -399,7 +407,7 @@ describe('<LocationShareMenu />', () => {
setLocation(component);
act(() => {
getSubmitButton(component).at(0).simulate('click');
getSubmitButton(component).at(0).simulate("click");
component.setProps({});
});
@@ -409,21 +417,23 @@ describe('<LocationShareMenu />', () => {
expect(onFinished).toHaveBeenCalled();
const [eventRoomId, eventContent] = mockClient.unstable_createLiveBeacon.mock.calls[0];
expect(eventRoomId).toEqual(defaultProps.roomId);
expect(eventContent).toEqual(expect.objectContaining({
// default timeout
timeout: DEFAULT_DURATION_MS,
description: `Ernie's live location`,
live: true,
[M_ASSET.name]: {
type: LocationAssetType.Self,
},
}));
expect(eventContent).toEqual(
expect.objectContaining({
// default timeout
timeout: DEFAULT_DURATION_MS,
description: `Ernie's live location`,
live: true,
[M_ASSET.name]: {
type: LocationAssetType.Self,
},
}),
);
});
it('opens error dialog when beacon creation fails', async () => {
it("opens error dialog when beacon creation fails", async () => {
// stub logger to keep console clean from expected error
const logSpy = jest.spyOn(logger, 'error').mockReturnValue(undefined);
const error = new Error('oh no');
const logSpy = jest.spyOn(logger, "error").mockReturnValue(undefined);
const error = new Error("oh no");
mockClient.unstable_createLiveBeacon.mockRejectedValue(error);
const component = getComponent();
@@ -432,7 +442,7 @@ describe('<LocationShareMenu />', () => {
setLocation(component);
act(() => {
getSubmitButton(component).at(0).simulate('click');
getSubmitButton(component).at(0).simulate("click");
component.setProps({});
});
@@ -441,18 +451,21 @@ describe('<LocationShareMenu />', () => {
await flushPromisesWithFakeTimers();
expect(logSpy).toHaveBeenCalledWith("We couldn't start sharing your live location", error);
expect(mocked(Modal).createDialog).toHaveBeenCalledWith(QuestionDialog, expect.objectContaining({
button: 'Try again',
description: 'Element could not send your location. Please try again later.',
title: `We couldn't send your location`,
cancelButton: 'Cancel',
}));
expect(mocked(Modal).createDialog).toHaveBeenCalledWith(
QuestionDialog,
expect.objectContaining({
button: "Try again",
description: "Element could not send your location. Please try again later.",
title: `We couldn't send your location`,
cancelButton: "Cancel",
}),
);
});
it('opens error dialog when beacon creation fails with permission error', async () => {
it("opens error dialog when beacon creation fails with permission error", async () => {
// stub logger to keep console clean from expected error
const logSpy = jest.spyOn(logger, 'error').mockReturnValue(undefined);
const error = { errcode: 'M_FORBIDDEN' } as unknown as Error;
const logSpy = jest.spyOn(logger, "error").mockReturnValue(undefined);
const error = { errcode: "M_FORBIDDEN" } as unknown as Error;
mockClient.unstable_createLiveBeacon.mockRejectedValue(error);
const component = getComponent();
@@ -461,7 +474,7 @@ describe('<LocationShareMenu />', () => {
setLocation(component);
act(() => {
getSubmitButton(component).at(0).simulate('click');
getSubmitButton(component).at(0).simulate("click");
component.setProps({});
});
@@ -470,12 +483,15 @@ describe('<LocationShareMenu />', () => {
await flushPromisesWithFakeTimers();
expect(logSpy).toHaveBeenCalledWith("Insufficient permissions to start sharing your live location", error);
expect(mocked(Modal).createDialog).toHaveBeenCalledWith(QuestionDialog, expect.objectContaining({
button: 'OK',
description: 'You need to have the right permissions in order to share locations in this room.',
title: `You don't have permission to share locations`,
hasCancelButton: false,
}));
expect(mocked(Modal).createDialog).toHaveBeenCalledWith(
QuestionDialog,
expect.objectContaining({
button: "OK",
description: "You need to have the right permissions in order to share locations in this room.",
title: `You don't have permission to share locations`,
hasCancelButton: false,
}),
);
});
});
});

View File

@@ -14,23 +14,23 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
// eslint-disable-next-line deprecate/import
import { mount } from 'enzyme';
import { RoomMember } from 'matrix-js-sdk/src/matrix';
import { LocationAssetType } from 'matrix-js-sdk/src/@types/location';
import maplibregl from 'maplibre-gl';
import { mount } from "enzyme";
import { RoomMember } from "matrix-js-sdk/src/matrix";
import { LocationAssetType } from "matrix-js-sdk/src/@types/location";
import maplibregl from "maplibre-gl";
import LocationViewDialog from '../../../../src/components/views/location/LocationViewDialog';
import { TILE_SERVER_WK_KEY } from '../../../../src/utils/WellKnownUtils';
import { getMockClientWithEventEmitter, makeLocationEvent } from '../../../test-utils';
import LocationViewDialog from "../../../../src/components/views/location/LocationViewDialog";
import { TILE_SERVER_WK_KEY } from "../../../../src/utils/WellKnownUtils";
import { getMockClientWithEventEmitter, makeLocationEvent } from "../../../test-utils";
describe('<LocationViewDialog />', () => {
const roomId = '!room:server';
const userId = '@user:server';
describe("<LocationViewDialog />", () => {
const roomId = "!room:server";
const userId = "@user:server";
const mockClient = getMockClientWithEventEmitter({
getClientWellKnown: jest.fn().mockReturnValue({
[TILE_SERVER_WK_KEY.name]: { map_style_url: 'maps.com' },
[TILE_SERVER_WK_KEY.name]: { map_style_url: "maps.com" },
}),
isGuest: jest.fn().mockReturnValue(false),
});
@@ -40,24 +40,23 @@ describe('<LocationViewDialog />', () => {
mxEvent: defaultEvent,
onFinished: jest.fn(),
};
const getComponent = (props = {}) =>
mount(<LocationViewDialog {...defaultProps} {...props} />);
const getComponent = (props = {}) => mount(<LocationViewDialog {...defaultProps} {...props} />);
beforeAll(() => {
maplibregl.AttributionControl = jest.fn();
});
it('renders map correctly', () => {
it("renders map correctly", () => {
const component = getComponent();
expect(component.find('Map')).toMatchSnapshot();
expect(component.find("Map")).toMatchSnapshot();
});
it('renders marker correctly for self share', () => {
it("renders marker correctly for self share", () => {
const selfShareEvent = makeLocationEvent("geo:51.5076,-0.1276", LocationAssetType.Self);
const member = new RoomMember(roomId, userId);
// @ts-ignore cheat assignment to property
selfShareEvent.sender = member;
const component = getComponent({ mxEvent: selfShareEvent });
expect(component.find('SmartMarker').props()['roomMember']).toEqual(member);
expect(component.find("SmartMarker").props()["roomMember"]).toEqual(member);
});
});

View File

@@ -14,29 +14,29 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
// eslint-disable-next-line deprecate/import
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import maplibregl from 'maplibre-gl';
import { ClientEvent } from 'matrix-js-sdk/src/matrix';
import { logger } from 'matrix-js-sdk/src/logger';
import { mount } from "enzyme";
import { act } from "react-dom/test-utils";
import maplibregl from "maplibre-gl";
import { ClientEvent } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import Map from '../../../../src/components/views/location/Map';
import { findByTestId, getMockClientWithEventEmitter } from '../../../test-utils';
import MatrixClientContext from '../../../../src/contexts/MatrixClientContext';
import { TILE_SERVER_WK_KEY } from '../../../../src/utils/WellKnownUtils';
import Map from "../../../../src/components/views/location/Map";
import { findByTestId, getMockClientWithEventEmitter } from "../../../test-utils";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
import { TILE_SERVER_WK_KEY } from "../../../../src/utils/WellKnownUtils";
describe('<Map />', () => {
describe("<Map />", () => {
const defaultProps = {
centerGeoUri: 'geo:52,41',
id: 'test-123',
centerGeoUri: "geo:52,41",
id: "test-123",
onError: jest.fn(),
onClick: jest.fn(),
};
const matrixClient = getMockClientWithEventEmitter({
getClientWellKnown: jest.fn().mockReturnValue({
[TILE_SERVER_WK_KEY.name]: { map_style_url: 'maps.com' },
[TILE_SERVER_WK_KEY.name]: { map_style_url: "maps.com" },
}),
});
const getComponent = (props = {}) =>
@@ -52,33 +52,33 @@ describe('<Map />', () => {
beforeEach(() => {
jest.clearAllMocks();
matrixClient.getClientWellKnown.mockReturnValue({
[TILE_SERVER_WK_KEY.name]: { map_style_url: 'maps.com' },
[TILE_SERVER_WK_KEY.name]: { map_style_url: "maps.com" },
});
jest.spyOn(logger, 'error').mockRestore();
jest.spyOn(logger, "error").mockRestore();
});
const mockMap = new maplibregl.Map();
it('renders', () => {
it("renders", () => {
const component = getComponent();
expect(component).toBeTruthy();
});
describe('onClientWellKnown emits', () => {
it('updates map style when style url is truthy', () => {
describe("onClientWellKnown emits", () => {
it("updates map style when style url is truthy", () => {
getComponent();
act(() => {
matrixClient.emit(ClientEvent.ClientWellKnown, {
[TILE_SERVER_WK_KEY.name]: { map_style_url: 'new.maps.com' },
[TILE_SERVER_WK_KEY.name]: { map_style_url: "new.maps.com" },
});
});
expect(mockMap.setStyle).toHaveBeenCalledWith('new.maps.com');
expect(mockMap.setStyle).toHaveBeenCalledWith("new.maps.com");
});
it('does not update map style when style url is truthy', () => {
it("does not update map style when style url is truthy", () => {
getComponent();
act(() => {
@@ -91,58 +91,60 @@ describe('<Map />', () => {
});
});
describe('map centering', () => {
it('does not try to center when no center uri provided', () => {
describe("map centering", () => {
it("does not try to center when no center uri provided", () => {
getComponent({ centerGeoUri: null });
expect(mockMap.setCenter).not.toHaveBeenCalled();
});
it('sets map center to centerGeoUri', () => {
getComponent({ centerGeoUri: 'geo:51,42' });
it("sets map center to centerGeoUri", () => {
getComponent({ centerGeoUri: "geo:51,42" });
expect(mockMap.setCenter).toHaveBeenCalledWith({ lat: 51, lon: 42 });
});
it('handles invalid centerGeoUri', () => {
const logSpy = jest.spyOn(logger, 'error').mockImplementation();
getComponent({ centerGeoUri: '123 Sesame Street' });
it("handles invalid centerGeoUri", () => {
const logSpy = jest.spyOn(logger, "error").mockImplementation();
getComponent({ centerGeoUri: "123 Sesame Street" });
expect(mockMap.setCenter).not.toHaveBeenCalled();
expect(logSpy).toHaveBeenCalledWith('Could not set map center');
expect(logSpy).toHaveBeenCalledWith("Could not set map center");
});
it('updates map center when centerGeoUri prop changes', () => {
const component = getComponent({ centerGeoUri: 'geo:51,42' });
it("updates map center when centerGeoUri prop changes", () => {
const component = getComponent({ centerGeoUri: "geo:51,42" });
component.setProps({ centerGeoUri: 'geo:53,45' });
component.setProps({ centerGeoUri: 'geo:56,47' });
component.setProps({ centerGeoUri: "geo:53,45" });
component.setProps({ centerGeoUri: "geo:56,47" });
expect(mockMap.setCenter).toHaveBeenCalledWith({ lat: 51, lon: 42 });
expect(mockMap.setCenter).toHaveBeenCalledWith({ lat: 53, lon: 45 });
expect(mockMap.setCenter).toHaveBeenCalledWith({ lat: 56, lon: 47 });
});
});
describe('map bounds', () => {
it('does not try to fit map bounds when no bounds provided', () => {
describe("map bounds", () => {
it("does not try to fit map bounds when no bounds provided", () => {
getComponent({ bounds: null });
expect(mockMap.fitBounds).not.toHaveBeenCalled();
});
it('fits map to bounds', () => {
it("fits map to bounds", () => {
const bounds = { north: 51, south: 50, east: 42, west: 41 };
getComponent({ bounds });
expect(mockMap.fitBounds).toHaveBeenCalledWith(new maplibregl.LngLatBounds([bounds.west, bounds.south],
[bounds.east, bounds.north]), { padding: 100, maxZoom: 15 });
expect(mockMap.fitBounds).toHaveBeenCalledWith(
new maplibregl.LngLatBounds([bounds.west, bounds.south], [bounds.east, bounds.north]),
{ padding: 100, maxZoom: 15 },
);
});
it('handles invalid bounds', () => {
const logSpy = jest.spyOn(logger, 'error').mockImplementation();
const bounds = { north: 'a', south: 'b', east: 42, west: 41 };
it("handles invalid bounds", () => {
const logSpy = jest.spyOn(logger, "error").mockImplementation();
const bounds = { north: "a", south: "b", east: 42, west: 41 };
getComponent({ bounds });
expect(mockMap.fitBounds).not.toHaveBeenCalled();
expect(logSpy).toHaveBeenCalledWith('Invalid map bounds');
expect(logSpy).toHaveBeenCalledWith("Invalid map bounds");
});
it('updates map bounds when bounds prop changes', () => {
const component = getComponent({ centerGeoUri: 'geo:51,42' });
it("updates map bounds when bounds prop changes", () => {
const component = getComponent({ centerGeoUri: "geo:51,42" });
const bounds = { north: 51, south: 50, east: 42, west: 41 };
const bounds2 = { north: 53, south: 51, east: 45, west: 44 };
@@ -152,8 +154,8 @@ describe('<Map />', () => {
});
});
describe('children', () => {
it('renders without children', () => {
describe("children", () => {
it("renders without children", () => {
const component = getComponent({ children: null });
component.setProps({});
@@ -162,18 +164,22 @@ describe('<Map />', () => {
expect(component).toBeTruthy();
});
it('renders children with map renderProp', () => {
const children = ({ map }) => <div data-test-id='test-child' data-map={map}>Hello, world</div>;
it("renders children with map renderProp", () => {
const children = ({ map }) => (
<div data-test-id="test-child" data-map={map}>
Hello, world
</div>
);
const component = getComponent({ children });
// renders child with map instance
expect(findByTestId(component, 'test-child').props()['data-map']).toEqual(mockMap);
expect(findByTestId(component, "test-child").props()["data-map"]).toEqual(mockMap);
});
});
describe('onClick', () => {
it('eats clicks to maplibre attribution button', () => {
describe("onClick", () => {
it("eats clicks to maplibre attribution button", () => {
const onClick = jest.fn();
const component = getComponent({ onClick });
@@ -181,20 +187,20 @@ describe('<Map />', () => {
// this is added to the dom by maplibregl
// which is mocked
// just fake the target
const fakeEl = document.createElement('div');
fakeEl.className = 'maplibregl-ctrl-attrib-button';
component.simulate('click', { target: fakeEl });
const fakeEl = document.createElement("div");
fakeEl.className = "maplibregl-ctrl-attrib-button";
component.simulate("click", { target: fakeEl });
});
expect(onClick).not.toHaveBeenCalled();
});
it('calls onClick', () => {
it("calls onClick", () => {
const onClick = jest.fn();
const component = getComponent({ onClick });
act(() => {
component.simulate('click');
component.simulate("click");
});
expect(onClick).toHaveBeenCalled();

View File

@@ -14,43 +14,43 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { render, RenderResult } from '@testing-library/react';
import React from "react";
import { render, RenderResult } from "@testing-library/react";
import { MapError, MapErrorProps } from '../../../../src/components/views/location/MapError';
import { LocationShareError } from '../../../../src/utils/location';
import { MapError, MapErrorProps } from "../../../../src/components/views/location/MapError";
import { LocationShareError } from "../../../../src/utils/location";
describe('<MapError />', () => {
describe("<MapError />", () => {
const defaultProps = {
onFinished: jest.fn(),
error: LocationShareError.MapStyleUrlNotConfigured,
className: 'test',
className: "test",
};
const getComponent = (props: Partial<MapErrorProps> = {}): RenderResult =>
render(<MapError {...defaultProps} {...props} />);
it('renders correctly for MapStyleUrlNotConfigured', () => {
it("renders correctly for MapStyleUrlNotConfigured", () => {
const { container } = getComponent();
expect(container).toMatchSnapshot();
});
it('renders correctly for MapStyleUrlNotReachable', () => {
it("renders correctly for MapStyleUrlNotReachable", () => {
const { container } = getComponent({
error: LocationShareError.MapStyleUrlNotReachable,
});
expect(container).toMatchSnapshot();
});
it('does not render button when onFinished falsy', () => {
it("does not render button when onFinished falsy", () => {
const { queryByText } = getComponent({
error: LocationShareError.MapStyleUrlNotReachable,
onFinished: undefined,
});
// no button
expect(queryByText('OK')).toBeFalsy();
expect(queryByText("OK")).toBeFalsy();
});
it('applies class when isMinimised is truthy', () => {
it("applies class when isMinimised is truthy", () => {
const { container } = getComponent({
isMinimised: true,
});

View File

@@ -14,39 +14,38 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
// eslint-disable-next-line deprecate/import
import { mount } from 'enzyme';
import { RoomMember } from 'matrix-js-sdk/src/matrix';
import { mount } from "enzyme";
import { RoomMember } from "matrix-js-sdk/src/matrix";
import Marker from '../../../../src/components/views/location/Marker';
import Marker from "../../../../src/components/views/location/Marker";
describe('<Marker />', () => {
describe("<Marker />", () => {
const defaultProps = {
id: 'abc123',
id: "abc123",
};
const getComponent = (props = {}) =>
mount(<Marker {...defaultProps} {...props} />);
const getComponent = (props = {}) => mount(<Marker {...defaultProps} {...props} />);
it('renders with location icon when no room member', () => {
it("renders with location icon when no room member", () => {
const component = getComponent();
expect(component).toMatchSnapshot();
});
it('does not try to use member color without room member', () => {
it("does not try to use member color without room member", () => {
const component = getComponent({ useMemberColor: true });
expect(component.find('div').at(0).props().className).toEqual('mx_Marker mx_Marker_defaultColor');
expect(component.find("div").at(0).props().className).toEqual("mx_Marker mx_Marker_defaultColor");
});
it('uses member color class', () => {
const member = new RoomMember('!room:server', '@user:server');
it("uses member color class", () => {
const member = new RoomMember("!room:server", "@user:server");
const component = getComponent({ useMemberColor: true, roomMember: member });
expect(component.find('div').at(0).props().className).toEqual('mx_Marker mx_Username_color3');
expect(component.find("div").at(0).props().className).toEqual("mx_Marker mx_Username_color3");
});
it('renders member avatar when roomMember is truthy', () => {
const member = new RoomMember('!room:server', '@user:server');
it("renders member avatar when roomMember is truthy", () => {
const member = new RoomMember("!room:server", "@user:server");
const component = getComponent({ roomMember: member });
expect(component.find('MemberAvatar').length).toBeTruthy();
expect(component.find("MemberAvatar").length).toBeTruthy();
});
});

View File

@@ -14,34 +14,33 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
// eslint-disable-next-line deprecate/import
import { mount } from 'enzyme';
import { mocked } from 'jest-mock';
import maplibregl from 'maplibre-gl';
import { mount } from "enzyme";
import { mocked } from "jest-mock";
import maplibregl from "maplibre-gl";
import SmartMarker from '../../../../src/components/views/location/SmartMarker';
import SmartMarker from "../../../../src/components/views/location/SmartMarker";
jest.mock('../../../../src/utils/location/findMapStyleUrl', () => ({
findMapStyleUrl: jest.fn().mockReturnValue('tileserver.com'),
jest.mock("../../../../src/utils/location/findMapStyleUrl", () => ({
findMapStyleUrl: jest.fn().mockReturnValue("tileserver.com"),
}));
describe('<SmartMarker />', () => {
describe("<SmartMarker />", () => {
const mockMap = new maplibregl.Map();
const mockMarker = new maplibregl.Marker();
const defaultProps = {
map: mockMap,
geoUri: 'geo:43.2,54.6',
geoUri: "geo:43.2,54.6",
};
const getComponent = (props = {}) =>
mount(<SmartMarker {...defaultProps} {...props} />);
const getComponent = (props = {}) => mount(<SmartMarker {...defaultProps} {...props} />);
beforeEach(() => {
jest.clearAllMocks();
});
it('creates a marker on mount', () => {
it("creates a marker on mount", () => {
const component = getComponent();
expect(component).toMatchSnapshot();
@@ -55,11 +54,11 @@ describe('<SmartMarker />', () => {
expect(mockMarker.addTo).toHaveBeenCalledWith(mockMap);
});
it('updates marker position on change', () => {
const component = getComponent({ geoUri: 'geo:40,50' });
it("updates marker position on change", () => {
const component = getComponent({ geoUri: "geo:40,50" });
component.setProps({ geoUri: 'geo:41,51' });
component.setProps({ geoUri: 'geo:42,52' });
component.setProps({ geoUri: "geo:41,51" });
component.setProps({ geoUri: "geo:42,52" });
// marker added only once
expect(maplibregl.Marker).toHaveBeenCalledTimes(1);
@@ -69,7 +68,7 @@ describe('<SmartMarker />', () => {
expect(mocked(mockMarker.setLngLat)).toHaveBeenCalledWith({ lat: 42, lon: 52 });
});
it('removes marker on unmount', () => {
it("removes marker on unmount", () => {
const component = getComponent();
expect(component).toMatchSnapshot();

View File

@@ -14,48 +14,47 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
// eslint-disable-next-line deprecate/import
import { mount } from 'enzyme';
import maplibregl from 'maplibre-gl';
import { act } from 'react-dom/test-utils';
import { mount } from "enzyme";
import maplibregl from "maplibre-gl";
import { act } from "react-dom/test-utils";
import ZoomButtons from '../../../../src/components/views/location/ZoomButtons';
import { findByTestId } from '../../../test-utils';
import ZoomButtons from "../../../../src/components/views/location/ZoomButtons";
import { findByTestId } from "../../../test-utils";
describe('<ZoomButtons />', () => {
describe("<ZoomButtons />", () => {
const mockMap = new maplibregl.Map();
const defaultProps = {
map: mockMap,
};
const getComponent = (props = {}) =>
mount(<ZoomButtons {...defaultProps} {...props} />);
const getComponent = (props = {}) => mount(<ZoomButtons {...defaultProps} {...props} />);
beforeEach(() => {
jest.clearAllMocks();
});
it('renders buttons', () => {
it("renders buttons", () => {
const component = getComponent();
expect(component).toMatchSnapshot();
});
it('calls map zoom in on zoom in click', () => {
it("calls map zoom in on zoom in click", () => {
const component = getComponent();
act(() => {
findByTestId(component, 'map-zoom-in-button').at(0).simulate('click');
findByTestId(component, "map-zoom-in-button").at(0).simulate("click");
});
expect(mockMap.zoomIn).toHaveBeenCalled();
expect(component).toBeTruthy();
});
it('calls map zoom out on zoom out click', () => {
it("calls map zoom out on zoom out click", () => {
const component = getComponent();
act(() => {
findByTestId(component, 'map-zoom-out-button').at(0).simulate('click');
findByTestId(component, "map-zoom-out-button").at(0).simulate("click");
});
expect(mockMap.zoomOut).toHaveBeenCalled();

View File

@@ -47,13 +47,11 @@ describe("shareLocation", () => {
} as unknown as MatrixClient;
mocked(makeLocationContent).mockReturnValue(content);
mocked(doMaybeLocalRoomAction).mockImplementation(<T>(
roomId: string,
fn: (actualRoomId: string) => Promise<T>,
client?: MatrixClient,
) => {
return fn(roomId);
});
mocked(doMaybeLocalRoomAction).mockImplementation(
<T>(roomId: string, fn: (actualRoomId: string) => Promise<T>, client?: MatrixClient) => {
return fn(roomId);
},
);
shareLocationFn = shareLocation(client, roomId, shareType, null, () => {});
});

View File

@@ -68,16 +68,18 @@ describe("CallEvent", () => {
alice = mkRoomMember(room.roomId, "@alice:example.org");
bob = mkRoomMember(room.roomId, "@bob:example.org");
jest.spyOn(room, "getMember").mockImplementation(
userId => [alice, bob].find(member => member.userId === userId) ?? null,
(userId) => [alice, bob].find((member) => member.userId === userId) ?? null,
);
client.getRoom.mockImplementation(roomId => roomId === room.roomId ? room : null);
client.getRoom.mockImplementation((roomId) => (roomId === room.roomId ? room : null));
client.getRooms.mockReturnValue([room]);
client.reEmitter.reEmit(room, [RoomStateEvent.Events]);
await Promise.all([CallStore.instance, WidgetMessagingStore.instance].map(
store => setupAsyncStoreWithClient(store, client),
));
await Promise.all(
[CallStore.instance, WidgetMessagingStore.instance].map((store) =>
setupAsyncStoreWithClient(store, client),
),
);
MockedCall.create(room, "1");
const maybeCall = CallStore.instance.getCall(room.roomId);
@@ -99,7 +101,9 @@ describe("CallEvent", () => {
jest.restoreAllMocks();
});
const renderEvent = () => { render(<CallEvent mxEvent={call.event} />); };
const renderEvent = () => {
render(<CallEvent mxEvent={call.event} />);
};
it("shows a message and duration if the call was ended", () => {
jest.advanceTimersByTime(90000);
@@ -121,7 +125,10 @@ describe("CallEvent", () => {
it("shows call details and connection controls if the call is loaded", async () => {
jest.advanceTimersByTime(90000);
call.participants = new Map([[alice, new Set(["a"])], [bob, new Set(["b"])]]);
call.participants = new Map([
[alice, new Set(["a"])],
[bob, new Set(["b"])],
]);
renderEvent();
screen.getByText("@alice:example.org started a video call");
@@ -132,11 +139,13 @@ describe("CallEvent", () => {
const dispatcherSpy = jest.fn();
const dispatcherRef = defaultDispatcher.register(dispatcherSpy);
fireEvent.click(screen.getByRole("button", { name: "Join" }));
await waitFor(() => expect(dispatcherSpy).toHaveBeenCalledWith({
action: Action.ViewRoom,
room_id: room.roomId,
view_call: true,
}));
await waitFor(() =>
expect(dispatcherSpy).toHaveBeenCalledWith({
action: Action.ViewRoom,
room_id: room.roomId,
view_call: true,
}),
);
defaultDispatcher.unregister(dispatcherRef);
await act(() => call.connect());

Some files were not shown because too many files have changed in this diff Show More