Merge branch 'develop' into sort-imports
Signed-off-by: Aaron Raimist <aaron@raim.ist>
This commit is contained in:
commit
7b94e13a84
642 changed files with 30052 additions and 8035 deletions
|
@ -16,17 +16,16 @@ limitations under the License.
|
|||
|
||||
import './skinned-sdk';
|
||||
|
||||
import { CallEvent, CallState } from 'matrix-js-sdk/src/webrtc/call';
|
||||
import EventEmitter from 'events';
|
||||
|
||||
import CallHandler, { PlaceCallType, CallHandlerEvent } from '../src/CallHandler';
|
||||
import CallHandler, { CallHandlerEvent } from '../src/CallHandler';
|
||||
import { stubClient, mkStubRoom } from './test-utils';
|
||||
import { MatrixClientPeg } from '../src/MatrixClientPeg';
|
||||
import dis from '../src/dispatcher/dispatcher';
|
||||
import { CallEvent, CallState, CallType } from 'matrix-js-sdk/src/webrtc/call';
|
||||
import DMRoomMap from '../src/utils/DMRoomMap';
|
||||
import EventEmitter from 'events';
|
||||
import SdkConfig from '../src/SdkConfig';
|
||||
import { ActionPayload } from '../src/dispatcher/payloads';
|
||||
import { Action } from '../src/dispatcher/actions';
|
||||
import { Action } from "../src/dispatcher/actions";
|
||||
|
||||
const REAL_ROOM_ID = '$room1:example.org';
|
||||
const MAPPED_ROOM_ID = '$room2:example.org';
|
||||
|
@ -89,6 +88,14 @@ function untilDispatch(waitForAction: string): Promise<ActionPayload> {
|
|||
});
|
||||
}
|
||||
|
||||
function untilCallHandlerEvent(callHandler: CallHandler, event: CallHandlerEvent): Promise<void> {
|
||||
return new Promise<void>((resolve) => {
|
||||
callHandler.addListener(event, () => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe('CallHandler', () => {
|
||||
let dmRoomMap;
|
||||
let callHandler;
|
||||
|
@ -174,12 +181,9 @@ describe('CallHandler', () => {
|
|||
},
|
||||
}]);
|
||||
|
||||
dis.dispatch({
|
||||
action: Action.DialNumber,
|
||||
number: '01818118181',
|
||||
}, true);
|
||||
await callHandler.dialNumber('01818118181');
|
||||
|
||||
const viewRoomPayload = await untilDispatch('view_room');
|
||||
const viewRoomPayload = await untilDispatch(Action.ViewRoom);
|
||||
expect(viewRoomPayload.room_id).toEqual(MAPPED_ROOM_ID);
|
||||
|
||||
// Check that a call was started
|
||||
|
@ -187,14 +191,9 @@ describe('CallHandler', () => {
|
|||
});
|
||||
|
||||
it('should move calls between rooms when remote asserted identity changes', async () => {
|
||||
dis.dispatch({
|
||||
action: 'place_call',
|
||||
type: PlaceCallType.Voice,
|
||||
room_id: REAL_ROOM_ID,
|
||||
}, true);
|
||||
callHandler.placeCall(REAL_ROOM_ID, CallType.Voice);
|
||||
|
||||
// wait for the call to be set up
|
||||
await untilDispatch('call_state');
|
||||
await untilCallHandlerEvent(callHandler, CallHandlerEvent.CallState);
|
||||
|
||||
// should start off in the actual room ID it's in at the protocol level
|
||||
expect(callHandler.getCallForRoom(REAL_ROOM_ID)).toBe(fakeCall);
|
||||
|
|
|
@ -39,7 +39,7 @@ describe('DecryptionFailureTracker', function() {
|
|||
const failedDecryptionEvent = createFailedDecryptionEvent();
|
||||
|
||||
let count = 0;
|
||||
const tracker = new DecryptionFailureTracker((total) => count += total);
|
||||
const tracker = new DecryptionFailureTracker((total) => count += total, () => "UnknownError");
|
||||
|
||||
const err = new MockDecryptionError();
|
||||
tracker.eventDecrypted(failedDecryptionEvent, err);
|
||||
|
@ -59,7 +59,7 @@ describe('DecryptionFailureTracker', function() {
|
|||
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);
|
||||
|
@ -81,7 +81,7 @@ describe('DecryptionFailureTracker', function() {
|
|||
const decryptedEvent2 = createFailedDecryptionEvent();
|
||||
|
||||
let count = 0;
|
||||
const tracker = new DecryptionFailureTracker((total) => count += total);
|
||||
const tracker = new DecryptionFailureTracker((total) => count += total, () => "UnknownError");
|
||||
|
||||
// Arbitrary number of failed decryptions for both events
|
||||
const err = new MockDecryptionError();
|
||||
|
@ -112,7 +112,7 @@ describe('DecryptionFailureTracker', function() {
|
|||
const decryptedEvent = createFailedDecryptionEvent();
|
||||
|
||||
let count = 0;
|
||||
const tracker = new DecryptionFailureTracker((total) => count += total);
|
||||
const tracker = new DecryptionFailureTracker((total) => count += total, () => "UnknownError");
|
||||
|
||||
// Indicate decryption
|
||||
const err = new MockDecryptionError();
|
||||
|
@ -140,7 +140,7 @@ describe('DecryptionFailureTracker', function() {
|
|||
const decryptedEvent = createFailedDecryptionEvent();
|
||||
|
||||
let count = 0;
|
||||
const tracker = new DecryptionFailureTracker((total) => count += total);
|
||||
const tracker = new DecryptionFailureTracker((total) => count += total, () => "UnknownError");
|
||||
|
||||
// Indicate decryption
|
||||
const err = new MockDecryptionError();
|
||||
|
@ -153,7 +153,7 @@ describe('DecryptionFailureTracker', function() {
|
|||
tracker.trackFailures();
|
||||
|
||||
// Simulate the browser refreshing by destroying tracker and creating a new tracker
|
||||
const secondTracker = new DecryptionFailureTracker((total) => count += total);
|
||||
const secondTracker = new DecryptionFailureTracker((total) => count += total, () => "UnknownError");
|
||||
|
||||
//secondTracker.loadTrackedEventHashMap();
|
||||
|
||||
|
@ -170,28 +170,29 @@ describe('DecryptionFailureTracker', function() {
|
|||
const counts = {};
|
||||
const tracker = new DecryptionFailureTracker(
|
||||
(total, errorCode) => counts[errorCode] = (counts[errorCode] || 0) + total,
|
||||
(error) => error === "UnknownError" ? "UnknownError" : "OlmKeysNotSentError",
|
||||
);
|
||||
|
||||
// One failure of ERROR_CODE_1, and effectively two for ERROR_CODE_2
|
||||
tracker.addDecryptionFailure(new DecryptionFailure('$event_id1', 'ERROR_CODE_1'));
|
||||
tracker.addDecryptionFailure(new DecryptionFailure('$event_id2', 'ERROR_CODE_2'));
|
||||
tracker.addDecryptionFailure(new DecryptionFailure('$event_id2', 'ERROR_CODE_2'));
|
||||
tracker.addDecryptionFailure(new DecryptionFailure('$event_id3', 'ERROR_CODE_2'));
|
||||
tracker.addDecryptionFailure(new DecryptionFailure('$event_id1', 'UnknownError'));
|
||||
tracker.addDecryptionFailure(new DecryptionFailure('$event_id2', 'OlmKeysNotSentError'));
|
||||
tracker.addDecryptionFailure(new DecryptionFailure('$event_id2', 'OlmKeysNotSentError'));
|
||||
tracker.addDecryptionFailure(new DecryptionFailure('$event_id3', 'OlmKeysNotSentError'));
|
||||
|
||||
// Pretend "now" is Infinity
|
||||
tracker.checkFailures(Infinity);
|
||||
|
||||
tracker.trackFailures();
|
||||
|
||||
expect(counts['ERROR_CODE_1']).toBe(1, 'should track one ERROR_CODE_1');
|
||||
expect(counts['ERROR_CODE_2']).toBe(2, 'should track two ERROR_CODE_2');
|
||||
expect(counts['UnknownError']).toBe(1, 'should track one UnknownError');
|
||||
expect(counts['OlmKeysNotSentError']).toBe(2, 'should track two OlmKeysNotSentError');
|
||||
});
|
||||
|
||||
it('should map error codes correctly', () => {
|
||||
const counts = {};
|
||||
const tracker = new DecryptionFailureTracker(
|
||||
(total, errorCode) => counts[errorCode] = (counts[errorCode] || 0) + total,
|
||||
(errorCode) => 'MY_NEW_ERROR_CODE',
|
||||
(errorCode) => 'OlmUnspecifiedError',
|
||||
);
|
||||
|
||||
// One failure of ERROR_CODE_1, and effectively two for ERROR_CODE_2
|
||||
|
@ -204,7 +205,7 @@ describe('DecryptionFailureTracker', function() {
|
|||
|
||||
tracker.trackFailures();
|
||||
|
||||
expect(counts['MY_NEW_ERROR_CODE'])
|
||||
.toBe(3, 'should track three MY_NEW_ERROR_CODE, got ' + counts['MY_NEW_ERROR_CODE']);
|
||||
expect(counts['OlmUnspecifiedError'])
|
||||
.toBe(3, 'should track three OlmUnspecifiedError, got ' + counts['OlmUnspecifiedError']);
|
||||
});
|
||||
});
|
||||
|
|
161
test/Markdown-test.ts
Normal file
161
test/Markdown-test.ts
Normal file
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import Markdown from "../src/Markdown";
|
||||
|
||||
describe("Markdown parser test", () => {
|
||||
describe("fixing HTML links", () => {
|
||||
const testString = [
|
||||
"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",
|
||||
].join("\n");
|
||||
|
||||
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>",
|
||||
"<p>Test1A:<br />#_foonetic_xkcd:matrix.org<br />http://google.com/_thing_<br />https://matrix.org/_matrix/client/foo/123_<br />#_foonetic_xkcd:matrix.org</p>",
|
||||
"<p>Test2:<br />http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg<br />http://domain.xyz/foo/bar-_stuff-like-this_-in-it.jpg</p>",
|
||||
"<p>Test3:<br />https://riot.im/app/#/room/#_foonetic_xkcd:matrix.org<br />https://riot.im/app/#/room/#_foonetic_xkcd:matrix.org</p>",
|
||||
"",
|
||||
].join("\n");
|
||||
/* eslint-enable max-len */
|
||||
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', () => {
|
||||
const test = [
|
||||
"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>",
|
||||
].join("\n");
|
||||
/* eslint-disable max-len */
|
||||
/**
|
||||
* NOTE: I'm not entirely sure if those "<"" and ">" should be visible in here for #_foonetic_xkcd:matrix.org
|
||||
* but it seems to be actually working properly
|
||||
*/
|
||||
const expectedResult = [
|
||||
"<p>Test1:<br /><#_foonetic_xkcd:matrix.org><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 /><#_foonetic_xkcd:matrix.org></p>",
|
||||
"<p>Test1A:<br /><#_foonetic_xkcd:matrix.org><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 /><#_foonetic_xkcd:matrix.org></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 */
|
||||
const md = new Markdown(test);
|
||||
expect(md.toHTML()).toEqual(expectedResult);
|
||||
});
|
||||
|
||||
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');
|
||||
const md = new Markdown("```" + testString + "```");
|
||||
expect(md.toHTML()).toEqual(expectedResult);
|
||||
});
|
||||
|
||||
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',
|
||||
].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",
|
||||
].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>', () => {
|
||||
/* 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';
|
||||
/* 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', () => {
|
||||
/* 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');
|
||||
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 />');
|
||||
/* eslint-enable max-len */
|
||||
const md = new Markdown(testString);
|
||||
expect(md.toHTML()).toEqual(expectedResult);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -17,9 +17,7 @@ limitations under the License.
|
|||
import {
|
||||
Anonymity,
|
||||
getRedactedCurrentLocation,
|
||||
IAnonymousEvent,
|
||||
IPseudonymousEvent,
|
||||
IRoomEvent,
|
||||
IEvent,
|
||||
PosthogAnalytics,
|
||||
} from '../src/PosthogAnalytics';
|
||||
import SdkConfig from '../src/SdkConfig';
|
||||
|
@ -40,25 +38,9 @@ class FakePosthog {
|
|||
}
|
||||
}
|
||||
|
||||
export interface ITestEvent extends IAnonymousEvent {
|
||||
key: "jest_test_event";
|
||||
properties: {
|
||||
foo: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ITestPseudonymousEvent extends IPseudonymousEvent {
|
||||
key: "jest_test_pseudo_event";
|
||||
properties: {
|
||||
foo: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ITestRoomEvent extends IRoomEvent {
|
||||
key: "jest_test_room_event";
|
||||
properties: {
|
||||
foo: string;
|
||||
};
|
||||
export interface ITestEvent extends IEvent {
|
||||
eventName: "JestTestEvents";
|
||||
foo: string;
|
||||
}
|
||||
|
||||
describe("PosthogAnalytics", () => {
|
||||
|
@ -126,27 +108,20 @@ describe("PosthogAnalytics", () => {
|
|||
analytics = new PosthogAnalytics(fakePosthog);
|
||||
});
|
||||
|
||||
it("Should pass trackAnonymousEvent() to posthog", async () => {
|
||||
it("Should pass event to posthog", () => {
|
||||
analytics.setAnonymity(Anonymity.Pseudonymous);
|
||||
await analytics.trackAnonymousEvent<ITestEvent>("jest_test_event", {
|
||||
analytics.trackEvent<ITestEvent>({
|
||||
eventName: "JestTestEvents",
|
||||
foo: "bar",
|
||||
});
|
||||
expect(fakePosthog.capture.mock.calls[0][0]).toBe("jest_test_event");
|
||||
expect(fakePosthog.capture.mock.calls[0][0]).toBe("JestTestEvents");
|
||||
expect(fakePosthog.capture.mock.calls[0][1]["foo"]).toEqual("bar");
|
||||
});
|
||||
|
||||
it("Should pass trackPseudonymousEvent() to posthog", async () => {
|
||||
analytics.setAnonymity(Anonymity.Pseudonymous);
|
||||
await analytics.trackPseudonymousEvent<ITestEvent>("jest_test_pseudo_event", {
|
||||
foo: "bar",
|
||||
});
|
||||
expect(fakePosthog.capture.mock.calls[0][0]).toBe("jest_test_pseudo_event");
|
||||
expect(fakePosthog.capture.mock.calls[0][1]["foo"]).toEqual("bar");
|
||||
});
|
||||
|
||||
it("Should not track pseudonymous messages if anonymous", async () => {
|
||||
it("Should not track events if anonymous", async () => {
|
||||
analytics.setAnonymity(Anonymity.Anonymous);
|
||||
await analytics.trackPseudonymousEvent<ITestEvent>("jest_test_event", {
|
||||
await analytics.trackEvent<ITestEvent>({
|
||||
eventName: "JestTestEvents",
|
||||
foo: "bar",
|
||||
});
|
||||
expect(fakePosthog.capture.mock.calls.length).toBe(0);
|
||||
|
@ -154,43 +129,25 @@ describe("PosthogAnalytics", () => {
|
|||
|
||||
it("Should not track any events if disabled", async () => {
|
||||
analytics.setAnonymity(Anonymity.Disabled);
|
||||
await analytics.trackPseudonymousEvent<ITestEvent>("jest_test_event", {
|
||||
analytics.trackEvent<ITestEvent>({
|
||||
eventName: "JestTestEvents",
|
||||
foo: "bar",
|
||||
});
|
||||
await analytics.trackAnonymousEvent<ITestEvent>("jest_test_event", {
|
||||
foo: "bar",
|
||||
});
|
||||
await analytics.trackPageView(200);
|
||||
expect(fakePosthog.capture.mock.calls.length).toBe(0);
|
||||
});
|
||||
|
||||
it("Should pseudonymise a location of a known screen", async () => {
|
||||
const location = await getRedactedCurrentLocation(
|
||||
"https://foo.bar", "#/register/some/pii", "/", Anonymity.Pseudonymous);
|
||||
it("Should anonymise location of a known screen", async () => {
|
||||
const location = getRedactedCurrentLocation("https://foo.bar", "#/register/some/pii", "/");
|
||||
expect(location).toBe("https://foo.bar/#/register/<redacted>");
|
||||
});
|
||||
|
||||
it("Should anonymise a location of a known screen", async () => {
|
||||
const location = await getRedactedCurrentLocation(
|
||||
"https://foo.bar", "#/register/some/pii", "/", Anonymity.Anonymous);
|
||||
expect(location).toBe("https://foo.bar/#/register/<redacted>");
|
||||
});
|
||||
|
||||
it("Should pseudonymise a location of an unknown screen", async () => {
|
||||
const location = await getRedactedCurrentLocation(
|
||||
"https://foo.bar", "#/not_a_screen_name/some/pii", "/", Anonymity.Pseudonymous);
|
||||
expect(location).toBe("https://foo.bar/#/<redacted_screen_name>/<redacted>");
|
||||
});
|
||||
|
||||
it("Should anonymise a location of an unknown screen", async () => {
|
||||
const location = await getRedactedCurrentLocation(
|
||||
"https://foo.bar", "#/not_a_screen_name/some/pii", "/", Anonymity.Anonymous);
|
||||
it("Should anonymise location of an unknown screen", async () => {
|
||||
const location = getRedactedCurrentLocation("https://foo.bar", "#/not_a_screen_name/some/pii", "/");
|
||||
expect(location).toBe("https://foo.bar/#/<redacted_screen_name>/<redacted>");
|
||||
});
|
||||
|
||||
it("Should handle an empty hash", async () => {
|
||||
const location = await getRedactedCurrentLocation(
|
||||
"https://foo.bar", "", "/", Anonymity.Anonymous);
|
||||
const location = getRedactedCurrentLocation("https://foo.bar", "", "/");
|
||||
expect(location).toBe("https://foo.bar/");
|
||||
});
|
||||
|
||||
|
|
|
@ -45,6 +45,11 @@ const button2 = <Button key={2}>b</Button>;
|
|||
const button3 = <Button key={3}>c</Button>;
|
||||
const button4 = <Button key={4}>d</Button>;
|
||||
|
||||
// mock offsetParent
|
||||
Object.defineProperty(HTMLElement.prototype, "offsetParent", {
|
||||
get() { return this.parentNode; },
|
||||
});
|
||||
|
||||
describe("RovingTabIndex", () => {
|
||||
it("RovingTabIndexProvider renders children as expected", () => {
|
||||
const wrapper = mount(<RovingTabIndexProvider>
|
||||
|
|
|
@ -70,7 +70,7 @@ describe('ThreadPanel', () => {
|
|||
wrapper.find(ContextMenuButton).simulate('click');
|
||||
const found = wrapper.find(ThreadPanelHeaderFilterOptionItem);
|
||||
expect(found.length).toEqual(2);
|
||||
const foundButton = found.find('[aria-selected=true]').first();
|
||||
const foundButton = found.find('[aria-checked=true]').first();
|
||||
expect(foundButton.text()).toEqual(`${_t("All threads")}${_t('Shows all threads from current room')}`);
|
||||
expect(foundButton).toMatchSnapshot();
|
||||
});
|
||||
|
|
|
@ -8,6 +8,7 @@ exports[`ThreadPanel Header expect that All filter for ThreadPanelHeader properl
|
|||
Threads
|
||||
</span>
|
||||
<ContextMenuButton
|
||||
className="mx_ThreadPanel_dropdown"
|
||||
inputRef={
|
||||
Object {
|
||||
"current": null,
|
||||
|
@ -29,6 +30,7 @@ exports[`ThreadPanel Header expect that My filter for ThreadPanelHeader properly
|
|||
Threads
|
||||
</span>
|
||||
<ContextMenuButton
|
||||
className="mx_ThreadPanel_dropdown"
|
||||
inputRef={
|
||||
Object {
|
||||
"current": null,
|
||||
|
@ -44,21 +46,21 @@ exports[`ThreadPanel Header expect that My filter for ThreadPanelHeader properly
|
|||
|
||||
exports[`ThreadPanel Header expect that ThreadPanelHeader has the correct option selected in the context menu 1`] = `
|
||||
<AccessibleButton
|
||||
aria-selected={true}
|
||||
aria-checked={true}
|
||||
className="mx_ThreadPanel_Header_FilterOptionItem"
|
||||
element="div"
|
||||
onClick={[Function]}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
role="menuitemradio"
|
||||
tabIndex={-1}
|
||||
>
|
||||
<div
|
||||
aria-selected={true}
|
||||
aria-checked={true}
|
||||
className="mx_AccessibleButton mx_ThreadPanel_Header_FilterOptionItem"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
role="menuitemradio"
|
||||
tabIndex={-1}
|
||||
>
|
||||
<span>
|
||||
All threads
|
||||
|
|
|
@ -14,13 +14,18 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import sdk from '../../../skinned-sdk';
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import ReactTestUtils from 'react-dom/test-utils';
|
||||
import { createClient } from 'matrix-js-sdk/src/matrix';
|
||||
|
||||
import sdk from '../../../skinned-sdk';
|
||||
import SdkConfig from '../../../../src/SdkConfig';
|
||||
import { mkServerConfig } from "../../../test-utils";
|
||||
import { createTestClient, mkServerConfig } from "../../../test-utils";
|
||||
|
||||
jest.mock('matrix-js-sdk/src/matrix');
|
||||
jest.useFakeTimers();
|
||||
|
||||
const Registration = sdk.getComponent(
|
||||
'structures.auth.Registration',
|
||||
|
@ -32,6 +37,7 @@ describe('Registration', function() {
|
|||
beforeEach(function() {
|
||||
parentDiv = document.createElement('div');
|
||||
document.body.appendChild(parentDiv);
|
||||
createClient.mockImplementation(() => createTestClient());
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
|
@ -49,13 +55,13 @@ describe('Registration', function() {
|
|||
/>, parentDiv);
|
||||
}
|
||||
|
||||
it('should show server picker', function() {
|
||||
it('should show server picker', async function() {
|
||||
const root = render();
|
||||
const selector = ReactTestUtils.findRenderedDOMComponentWithClass(root, "mx_ServerPicker");
|
||||
expect(selector).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should show form when custom URLs disabled', function() {
|
||||
it('should show form when custom URLs disabled', async function() {
|
||||
jest.spyOn(SdkConfig, "get").mockReturnValue({
|
||||
disable_custom_urls: true,
|
||||
});
|
||||
|
@ -78,7 +84,7 @@ describe('Registration', function() {
|
|||
expect(form).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should show SSO options if those are available", () => {
|
||||
it("should show SSO options if those are available", async () => {
|
||||
jest.spyOn(SdkConfig, "get").mockReturnValue({
|
||||
disable_custom_urls: true,
|
||||
});
|
||||
|
|
162
test/components/views/elements/InteractiveTooltip-test.ts
Normal file
162
test/components/views/elements/InteractiveTooltip-test.ts
Normal file
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import '../../../skinned-sdk';
|
||||
import { Direction, mouseWithinRegion } from "../../../../src/components/views/elements/InteractiveTooltip";
|
||||
|
||||
describe("InteractiveTooltip", () => {
|
||||
describe("mouseWithinRegion", () => {
|
||||
it("direction=left", () => {
|
||||
const targetRect = {
|
||||
width: 20,
|
||||
height: 20,
|
||||
top: 300,
|
||||
right: 370,
|
||||
bottom: 320,
|
||||
left: 350,
|
||||
} as DOMRect;
|
||||
|
||||
const contentRect = {
|
||||
width: 100,
|
||||
height: 400,
|
||||
top: 100,
|
||||
right: 200,
|
||||
bottom: 500,
|
||||
left: 100,
|
||||
} as DOMRect;
|
||||
|
||||
// just within top left corner of contentRect
|
||||
expect(mouseWithinRegion(101, 101, Direction.Left, targetRect, contentRect)).toBe(true);
|
||||
// just outside top left corner of contentRect, within buffer
|
||||
expect(mouseWithinRegion(101, 90, Direction.Left, targetRect, contentRect)).toBe(true);
|
||||
// just within top right corner of targetRect
|
||||
expect(mouseWithinRegion(369, 301, Direction.Left, targetRect, contentRect)).toBe(true);
|
||||
// within the top triangular portion of the trapezoid
|
||||
expect(mouseWithinRegion(300, 200, Direction.Left, targetRect, contentRect)).toBe(true);
|
||||
// within the bottom triangular portion of the trapezoid
|
||||
expect(mouseWithinRegion(300, 350, Direction.Left, targetRect, contentRect)).toBe(true);
|
||||
// outside the top triangular portion of the trapezoid
|
||||
expect(mouseWithinRegion(300, 140, Direction.Left, targetRect, contentRect)).toBe(false);
|
||||
// outside the bottom triangular portion of the trapezoid
|
||||
expect(mouseWithinRegion(300, 460, Direction.Left, targetRect, contentRect)).toBe(false);
|
||||
});
|
||||
|
||||
it("direction=right", () => {
|
||||
const targetRect = {
|
||||
width: 20,
|
||||
height: 20,
|
||||
top: 300,
|
||||
right: 370,
|
||||
bottom: 320,
|
||||
left: 350,
|
||||
} as DOMRect;
|
||||
|
||||
const contentRect = {
|
||||
width: 100,
|
||||
height: 400,
|
||||
top: 100,
|
||||
right: 620,
|
||||
bottom: 500,
|
||||
left: 520,
|
||||
} as DOMRect;
|
||||
|
||||
// just within top right corner of contentRect
|
||||
expect(mouseWithinRegion(619, 101, Direction.Right, targetRect, contentRect)).toBe(true);
|
||||
// just outside top right corner of contentRect, within buffer
|
||||
expect(mouseWithinRegion(619, 90, Direction.Right, targetRect, contentRect)).toBe(true);
|
||||
// just within top left corner of targetRect
|
||||
expect(mouseWithinRegion(351, 301, Direction.Right, targetRect, contentRect)).toBe(true);
|
||||
// within the top triangular portion of the trapezoid
|
||||
expect(mouseWithinRegion(420, 200, Direction.Right, targetRect, contentRect)).toBe(true);
|
||||
// within the bottom triangular portion of the trapezoid
|
||||
expect(mouseWithinRegion(420, 350, Direction.Right, targetRect, contentRect)).toBe(true);
|
||||
// outside the top triangular portion of the trapezoid
|
||||
expect(mouseWithinRegion(420, 140, Direction.Right, targetRect, contentRect)).toBe(false);
|
||||
// outside the bottom triangular portion of the trapezoid
|
||||
expect(mouseWithinRegion(420, 460, Direction.Right, targetRect, contentRect)).toBe(false);
|
||||
});
|
||||
|
||||
it("direction=top", () => {
|
||||
const targetRect = {
|
||||
width: 20,
|
||||
height: 20,
|
||||
top: 300,
|
||||
right: 370,
|
||||
bottom: 320,
|
||||
left: 350,
|
||||
} as DOMRect;
|
||||
|
||||
const contentRect = {
|
||||
width: 400,
|
||||
height: 100,
|
||||
top: 100,
|
||||
right: 550,
|
||||
bottom: 200,
|
||||
left: 150,
|
||||
} as DOMRect;
|
||||
|
||||
// just within top right corner of contentRect
|
||||
expect(mouseWithinRegion(549, 101, Direction.Top, targetRect, contentRect)).toBe(true);
|
||||
// just outside top right corner of contentRect, within buffer
|
||||
expect(mouseWithinRegion(549, 99, Direction.Top, targetRect, contentRect)).toBe(true);
|
||||
// just within bottom left corner of targetRect
|
||||
expect(mouseWithinRegion(351, 319, Direction.Top, targetRect, contentRect)).toBe(true);
|
||||
// within the left triangular portion of the trapezoid
|
||||
expect(mouseWithinRegion(240, 260, Direction.Top, targetRect, contentRect)).toBe(true);
|
||||
// within the right triangular portion of the trapezoid
|
||||
expect(mouseWithinRegion(480, 260, Direction.Top, targetRect, contentRect)).toBe(true);
|
||||
// outside the left triangular portion of the trapezoid
|
||||
expect(mouseWithinRegion(220, 260, Direction.Top, targetRect, contentRect)).toBe(false);
|
||||
// outside the right triangular portion of the trapezoid
|
||||
expect(mouseWithinRegion(500, 260, Direction.Top, targetRect, contentRect)).toBe(false);
|
||||
});
|
||||
|
||||
it("direction=bottom", () => {
|
||||
const targetRect = {
|
||||
width: 20,
|
||||
height: 20,
|
||||
top: 300,
|
||||
right: 370,
|
||||
bottom: 320,
|
||||
left: 350,
|
||||
} as DOMRect;
|
||||
|
||||
const contentRect = {
|
||||
width: 400,
|
||||
height: 100,
|
||||
top: 420,
|
||||
right: 550,
|
||||
bottom: 520,
|
||||
left: 150,
|
||||
} as DOMRect;
|
||||
|
||||
// just within bottom left corner of contentRect
|
||||
expect(mouseWithinRegion(101, 519, Direction.Bottom, targetRect, contentRect)).toBe(true);
|
||||
// just outside bottom left corner of contentRect, within buffer
|
||||
expect(mouseWithinRegion(101, 521, Direction.Bottom, targetRect, contentRect)).toBe(true);
|
||||
// just within top left corner of targetRect
|
||||
expect(mouseWithinRegion(351, 301, Direction.Bottom, targetRect, contentRect)).toBe(true);
|
||||
// within the left triangular portion of the trapezoid
|
||||
expect(mouseWithinRegion(240, 360, Direction.Bottom, targetRect, contentRect)).toBe(true);
|
||||
// within the right triangular portion of the trapezoid
|
||||
expect(mouseWithinRegion(480, 360, Direction.Bottom, targetRect, contentRect)).toBe(true);
|
||||
// outside the left triangular portion of the trapezoid
|
||||
expect(mouseWithinRegion(220, 360, Direction.Bottom, targetRect, contentRect)).toBe(false);
|
||||
// outside the right triangular portion of the trapezoid
|
||||
expect(mouseWithinRegion(500, 360, Direction.Bottom, targetRect, contentRect)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
125
test/components/views/elements/PollCreateDialog-test.tsx
Normal file
125
test/components/views/elements/PollCreateDialog-test.tsx
Normal file
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// skinned-sdk should be the first import in most tests
|
||||
import '../../../skinned-sdk';
|
||||
import React from "react";
|
||||
import { mount, ReactWrapper } from "enzyme";
|
||||
import * as TestUtils from "../../../test-utils";
|
||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import _PollCreateDialog from "../../../../src/components/views/elements/PollCreateDialog";
|
||||
const PollCreateDialog = TestUtils.wrapInMatrixClientContext(_PollCreateDialog);
|
||||
|
||||
// Fake date to give a predictable snapshot
|
||||
const realDateNow = Date.now;
|
||||
const realDateToISOString = Date.prototype.toISOString;
|
||||
Date.now = jest.fn(() => 2345678901234);
|
||||
// eslint-disable-next-line no-extend-native
|
||||
Date.prototype.toISOString = jest.fn(() => "2021-11-23T14:35:14.240Z");
|
||||
afterAll(() => {
|
||||
Date.now = realDateNow;
|
||||
// eslint-disable-next-line no-extend-native
|
||||
Date.prototype.toISOString = realDateToISOString;
|
||||
});
|
||||
|
||||
describe("PollCreateDialog", () => {
|
||||
it("renders a blank poll", () => {
|
||||
const dialog = mount(
|
||||
<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />,
|
||||
);
|
||||
expect(dialog).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders a question and some options", () => {
|
||||
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, "Option 1", "As many as my neighbour");
|
||||
changeValue(dialog, "Option 2", "The question is meaningless");
|
||||
dialog.find("div.mx_PollCreateDialog_addOption").simulate("click");
|
||||
changeValue(dialog, "Option 3", "Mu");
|
||||
expect(dialog).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("doesn't allow submitting until there are options", () => {
|
||||
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()} />,
|
||||
);
|
||||
expect(submitIsDisabled(dialog)).toBe(true);
|
||||
|
||||
// When I set some values in the boxes
|
||||
changeValue(dialog, "Question or topic", "Q");
|
||||
changeValue(dialog, "Option 1", "A1");
|
||||
changeValue(dialog, "Option 2", "A2");
|
||||
|
||||
// Then I am able to submit
|
||||
expect(submitIsDisabled(dialog)).toBe(false);
|
||||
});
|
||||
|
||||
it("displays a spinner after submitting", () => {
|
||||
TestUtils.stubClient();
|
||||
MatrixClientPeg.get().sendEvent = jest.fn(() => Promise.resolve());
|
||||
|
||||
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");
|
||||
expect(dialog.find("Spinner").length).toBe(0);
|
||||
|
||||
dialog.find("button").simulate("click");
|
||||
expect(dialog.find("Spinner").length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
function createRoom(): Room {
|
||||
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 } },
|
||||
);
|
||||
}
|
||||
|
||||
function submitIsDisabled(wrapper: ReactWrapper) {
|
||||
return wrapper.find('button[type="submit"]').prop("aria-disabled") === true;
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load diff
1171
test/components/views/messages/MPollBody-test.tsx
Normal file
1171
test/components/views/messages/MPollBody-test.tsx
Normal file
File diff suppressed because it is too large
Load diff
|
@ -282,6 +282,30 @@ describe("<TextualBody />", () => {
|
|||
'!ZxbRYPQXDXKGmDnJNg:example.com</a></span> with vias</span>',
|
||||
);
|
||||
});
|
||||
|
||||
it('renders formatted body without html corretly', () => {
|
||||
const ev = mkEvent({
|
||||
type: "m.room.message",
|
||||
room: "room_id",
|
||||
user: "sender",
|
||||
content: {
|
||||
body: "escaped \\*markdown\\*",
|
||||
msgtype: "m.text",
|
||||
format: "org.matrix.custom.html",
|
||||
formatted_body: "escaped *markdown*",
|
||||
},
|
||||
event: true,
|
||||
});
|
||||
|
||||
const wrapper = mount(<TextualBody mxEvent={ev} />);
|
||||
|
||||
const content = wrapper.find(".mx_EventTile_body");
|
||||
expect(content.html()).toBe(
|
||||
'<span class="mx_EventTile_body" dir="auto">' +
|
||||
'escaped *markdown*' +
|
||||
'</span>',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("renders url previews correctly", () => {
|
||||
|
|
2468
test/components/views/messages/__snapshots__/MPollBody-test.tsx.snap
Normal file
2468
test/components/views/messages/__snapshots__/MPollBody-test.tsx.snap
Normal file
File diff suppressed because it is too large
Load diff
|
@ -155,7 +155,6 @@ function render(room: Room): HTMLDivElement {
|
|||
<RoomHeader
|
||||
room={room}
|
||||
inRoom={true}
|
||||
onSettingsClick={() => {}}
|
||||
onSearchClick={() => {}}
|
||||
onForgetClick={() => {}}
|
||||
onCallPlaced={(_type: PlaceCallType) => {}}
|
||||
|
|
|
@ -35,7 +35,7 @@ import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
|||
import SpecPermalinkConstructor from "../../../../src/utils/permalinks/SpecPermalinkConstructor";
|
||||
import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
|
||||
import DocumentOffset from '../../../../src/editor/offset';
|
||||
import { Layout } from '../../../../src/settings/Layout';
|
||||
import { Layout } from '../../../../src/settings/enums/Layout';
|
||||
|
||||
jest.mock("../../../../src/stores/RoomViewStore");
|
||||
|
||||
|
|
|
@ -243,7 +243,7 @@ exports[`FontScalingPanel renders the font scaling UI 1`] = `
|
|||
onChange={[Function]}
|
||||
>
|
||||
<span
|
||||
className="mx_Checkbox "
|
||||
className="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
|
@ -258,8 +258,8 @@ exports[`FontScalingPanel renders the font scaling UI 1`] = `
|
|||
<div
|
||||
className="mx_Checkbox_background"
|
||||
>
|
||||
<img
|
||||
src="image-file-stub"
|
||||
<div
|
||||
className="mx_Checkbox_checkmark"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
|
|
|
@ -46,7 +46,7 @@ exports[`ThemeChoicePanel renders the theme choice UI 1`] = `
|
|||
value="light"
|
||||
>
|
||||
<label
|
||||
className="mx_RadioButton mx_ThemeSelector_light mx_RadioButton_disabled mx_RadioButton_outlined"
|
||||
className="mx_StyledRadioButton mx_ThemeSelector_light mx_StyledRadioButton_disabled mx_StyledRadioButton_outlined"
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
|
@ -60,12 +60,12 @@ exports[`ThemeChoicePanel renders the theme choice UI 1`] = `
|
|||
<div />
|
||||
</div>
|
||||
<div
|
||||
className="mx_RadioButton_content"
|
||||
className="mx_StyledRadioButton_content"
|
||||
>
|
||||
Light
|
||||
</div>
|
||||
<div
|
||||
className="mx_RadioButton_spacer"
|
||||
className="mx_StyledRadioButton_spacer"
|
||||
/>
|
||||
</label>
|
||||
</StyledRadioButton>
|
||||
|
@ -80,7 +80,7 @@ exports[`ThemeChoicePanel renders the theme choice UI 1`] = `
|
|||
value="dark"
|
||||
>
|
||||
<label
|
||||
className="mx_RadioButton mx_ThemeSelector_dark mx_RadioButton_disabled mx_RadioButton_outlined"
|
||||
className="mx_StyledRadioButton mx_ThemeSelector_dark mx_StyledRadioButton_disabled mx_StyledRadioButton_outlined"
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
|
@ -94,12 +94,12 @@ exports[`ThemeChoicePanel renders the theme choice UI 1`] = `
|
|||
<div />
|
||||
</div>
|
||||
<div
|
||||
className="mx_RadioButton_content"
|
||||
className="mx_StyledRadioButton_content"
|
||||
>
|
||||
Dark
|
||||
</div>
|
||||
<div
|
||||
className="mx_RadioButton_spacer"
|
||||
className="mx_StyledRadioButton_spacer"
|
||||
/>
|
||||
</label>
|
||||
</StyledRadioButton>
|
||||
|
|
|
@ -18,6 +18,8 @@ import '../skinned-sdk'; // Must be first for skinning to work
|
|||
import { parseEvent } from "../../src/editor/deserialize";
|
||||
import { createPartCreator } from "./mock";
|
||||
|
||||
const FOUR_SPACES = " ".repeat(4);
|
||||
|
||||
function htmlMessage(formattedBody, msgtype = "m.text") {
|
||||
return {
|
||||
getContent() {
|
||||
|
@ -197,7 +199,6 @@ describe('editor/deserialize', function() {
|
|||
it('code block with no trailing text', function() {
|
||||
const html = "<pre><code>0xDEADBEEF\n</code></pre>\n";
|
||||
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||||
console.log(parts);
|
||||
expect(parts.length).toBe(5);
|
||||
expect(parts[0]).toStrictEqual({ type: "plain", text: "```" });
|
||||
expect(parts[1]).toStrictEqual({ type: "newline", text: "\n" });
|
||||
|
@ -236,6 +237,26 @@ describe('editor/deserialize', function() {
|
|||
expect(parts[3]).toStrictEqual({ type: "newline", text: "\n" });
|
||||
expect(parts[4]).toStrictEqual({ type: "plain", text: "3. Finish" });
|
||||
});
|
||||
it('nested unordered lists', () => {
|
||||
const html = "<ul><li>Oak<ul><li>Spruce<ul><li>Birch</li></ul></li></ul></li></ul>";
|
||||
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||||
expect(parts.length).toBe(5);
|
||||
expect(parts[0]).toStrictEqual({ type: "plain", text: "- Oak" });
|
||||
expect(parts[1]).toStrictEqual({ type: "newline", text: "\n" });
|
||||
expect(parts[2]).toStrictEqual({ type: "plain", text: `${FOUR_SPACES}- Spruce` });
|
||||
expect(parts[3]).toStrictEqual({ type: "newline", text: "\n" });
|
||||
expect(parts[4]).toStrictEqual({ type: "plain", text: `${FOUR_SPACES.repeat(2)}- Birch` });
|
||||
});
|
||||
it('nested ordered lists', () => {
|
||||
const html = "<ol><li>Oak<ol><li>Spruce<ol><li>Birch</li></ol></li></ol></li></ol>";
|
||||
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||||
expect(parts.length).toBe(5);
|
||||
expect(parts[0]).toStrictEqual({ type: "plain", text: "1. Oak" });
|
||||
expect(parts[1]).toStrictEqual({ type: "newline", text: "\n" });
|
||||
expect(parts[2]).toStrictEqual({ type: "plain", text: `${FOUR_SPACES}1. Spruce` });
|
||||
expect(parts[3]).toStrictEqual({ type: "newline", text: "\n" });
|
||||
expect(parts[4]).toStrictEqual({ type: "plain", text: `${FOUR_SPACES.repeat(2)}1. Birch` });
|
||||
});
|
||||
it('mx-reply is stripped', function() {
|
||||
const html = "<mx-reply>foo</mx-reply>bar";
|
||||
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
|
||||
|
|
2
test/end-to-end-tests/.gitignore
vendored
2
test/end-to-end-tests/.gitignore
vendored
|
@ -2,3 +2,5 @@ node_modules
|
|||
*.png
|
||||
element/env
|
||||
performance-entries.json
|
||||
lib
|
||||
logs
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build": "tsc -p ./tsconfig.json"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
|
@ -15,5 +16,8 @@
|
|||
"request": "^2.88.0",
|
||||
"request-promise-native": "^1.0.7",
|
||||
"uuid": "^3.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/puppeteer": "^5.4.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,5 +35,6 @@ trap 'handle_error' ERR
|
|||
if [ $has_custom_app -ne "1" ]; then
|
||||
./element/start.sh
|
||||
fi
|
||||
node start.js $@
|
||||
yarn build
|
||||
node lib/start.js $@
|
||||
stop_servers
|
||||
|
|
24
test/end-to-end-tests/src/@types/global.d.ts
vendored
Normal file
24
test/end-to-end-tests/src/@types/global.d.ts
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import "matrix-react-sdk/src/@types/global"; // load matrix-react-sdk's type extensions first
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
mxPerformanceMonitor: any;
|
||||
mxPerformanceEntryNames: any;
|
||||
}
|
||||
}
|
|
@ -15,16 +15,20 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
module.exports = class LogBuffer {
|
||||
constructor(page, eventName, eventMapper, reduceAsync=false, initialValue = "") {
|
||||
import { Page, PageEventObject } from "puppeteer";
|
||||
|
||||
export class LogBuffer<EventMapperArg extends Parameters<Parameters<Page['on']>[1]>[0]> {
|
||||
buffer: string;
|
||||
|
||||
constructor(
|
||||
page: Page,
|
||||
eventName: keyof PageEventObject,
|
||||
eventMapper: (arg: EventMapperArg) => Promise<string>,
|
||||
initialValue = "",
|
||||
) {
|
||||
this.buffer = initialValue;
|
||||
page.on(eventName, (arg) => {
|
||||
const result = eventMapper(arg);
|
||||
if (reduceAsync) {
|
||||
result.then((r) => this.buffer += r);
|
||||
} else {
|
||||
this.buffer += result;
|
||||
}
|
||||
page.on(eventName, (arg: EventMapperArg) => {
|
||||
eventMapper(arg).then((r) => this.buffer += r);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
|
@ -15,14 +15,13 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
module.exports = class Logger {
|
||||
constructor(username) {
|
||||
this.indent = 0;
|
||||
this.username = username;
|
||||
this.muted = false;
|
||||
}
|
||||
export class Logger {
|
||||
private indent = 0;
|
||||
private muted = false;
|
||||
|
||||
startGroup(description) {
|
||||
constructor(readonly username: string) {}
|
||||
|
||||
public startGroup(description: string): Logger {
|
||||
if (!this.muted) {
|
||||
const indent = " ".repeat(this.indent * 2);
|
||||
console.log(`${indent} * ${this.username} ${description}:`);
|
||||
|
@ -31,12 +30,12 @@ module.exports = class Logger {
|
|||
return this;
|
||||
}
|
||||
|
||||
endGroup() {
|
||||
public endGroup(): Logger {
|
||||
this.indent -= 1;
|
||||
return this;
|
||||
}
|
||||
|
||||
step(description) {
|
||||
public step(description: string): Logger {
|
||||
if (!this.muted) {
|
||||
const indent = " ".repeat(this.indent * 2);
|
||||
process.stdout.write(`${indent} * ${this.username} ${description} ... `);
|
||||
|
@ -44,20 +43,20 @@ module.exports = class Logger {
|
|||
return this;
|
||||
}
|
||||
|
||||
done(status = "done") {
|
||||
public done(status = "done"): Logger {
|
||||
if (!this.muted) {
|
||||
process.stdout.write(status + "\n");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
mute() {
|
||||
public mute(): Logger {
|
||||
this.muted = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
unmute() {
|
||||
public unmute(): Logger {
|
||||
this.muted = false;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
}
|
|
@ -15,11 +15,11 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
const request = require('request-promise-native');
|
||||
const cheerio = require('cheerio');
|
||||
const url = require("url");
|
||||
import request = require('request-promise-native');
|
||||
import * as cheerio from 'cheerio';
|
||||
import * as url from "url";
|
||||
|
||||
module.exports.approveConsent = async function(consentUrl) {
|
||||
export const approveConsent = async function(consentUrl: string): Promise<void> {
|
||||
const body = await request.get(consentUrl);
|
||||
const doc = cheerio.load(body);
|
||||
const v = doc("input[name=v]").val();
|
|
@ -15,13 +15,17 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
const { exec } = require('child_process');
|
||||
const request = require('request-promise-native');
|
||||
import { exec } from 'child_process';
|
||||
import request = require('request-promise-native');
|
||||
import { RestSession } from './session';
|
||||
import { RestMultiSession } from './multi';
|
||||
|
||||
const RestSession = require('./session');
|
||||
const RestMultiSession = require('./multi');
|
||||
interface ExecResult {
|
||||
stdout: string;
|
||||
stderr: string;
|
||||
}
|
||||
|
||||
function execAsync(command, options) {
|
||||
function execAsync(command: string, options: Parameters<typeof exec>[1]): Promise<ExecResult> {
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(command, options, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
|
@ -33,27 +37,32 @@ function execAsync(command, options) {
|
|||
});
|
||||
}
|
||||
|
||||
module.exports = class RestSessionCreator {
|
||||
constructor(synapseSubdir, hsUrl, cwd) {
|
||||
this.synapseSubdir = synapseSubdir;
|
||||
this.hsUrl = hsUrl;
|
||||
this.cwd = cwd;
|
||||
}
|
||||
export interface Credentials {
|
||||
accessToken: string;
|
||||
homeServer: string;
|
||||
userId: string;
|
||||
deviceId: string;
|
||||
hsUrl: string;
|
||||
}
|
||||
|
||||
async createSessionRange(usernames, password, groupName) {
|
||||
export class RestSessionCreator {
|
||||
constructor(private readonly synapseSubdir: string, private readonly hsUrl: string, private readonly cwd: string) {}
|
||||
|
||||
public async createSessionRange(usernames: string[], password: string,
|
||||
groupName: string): Promise<RestMultiSession> {
|
||||
const sessionPromises = usernames.map((username) => this.createSession(username, password));
|
||||
const sessions = await Promise.all(sessionPromises);
|
||||
return new RestMultiSession(sessions, groupName);
|
||||
}
|
||||
|
||||
async createSession(username, password) {
|
||||
await this._register(username, password);
|
||||
public async createSession(username: string, password: string): Promise<RestSession> {
|
||||
await this.register(username, password);
|
||||
console.log(` * created REST user ${username} ... done`);
|
||||
const authResult = await this._authenticate(username, password);
|
||||
const authResult = await this.authenticate(username, password);
|
||||
return new RestSession(authResult);
|
||||
}
|
||||
|
||||
async _register(username, password) {
|
||||
private async register(username: string, password: string): Promise<void> {
|
||||
const registerArgs = [
|
||||
'-c homeserver.yaml',
|
||||
`-u ${username}`,
|
||||
|
@ -71,7 +80,7 @@ module.exports = class RestSessionCreator {
|
|||
await execAsync(allCmds, { cwd: this.cwd, encoding: 'utf-8' });
|
||||
}
|
||||
|
||||
async _authenticate(username, password) {
|
||||
private async authenticate(username: string, password: string): Promise<Credentials> {
|
||||
const requestBody = {
|
||||
"type": "m.login.password",
|
||||
"identifier": {
|
||||
|
@ -90,4 +99,4 @@ module.exports = class RestSessionCreator {
|
|||
hsUrl: this.hsUrl,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
|
@ -15,19 +15,22 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
const Logger = require('../logger');
|
||||
import { Logger } from '../logger';
|
||||
import { RestSession } from "./session";
|
||||
import { RestRoom } from "./room";
|
||||
|
||||
module.exports = class RestMultiSession {
|
||||
constructor(sessions, groupName) {
|
||||
export class RestMultiSession {
|
||||
readonly log: Logger;
|
||||
|
||||
constructor(public readonly sessions: RestSession[], groupName: string) {
|
||||
this.log = new Logger(groupName);
|
||||
this.sessions = sessions;
|
||||
}
|
||||
|
||||
slice(groupName, start, end) {
|
||||
public slice(groupName: string, start: number, end?: number): RestMultiSession {
|
||||
return new RestMultiSession(this.sessions.slice(start, end), groupName);
|
||||
}
|
||||
|
||||
pop(userName) {
|
||||
public pop(userName: string): RestSession {
|
||||
const idx = this.sessions.findIndex((s) => s.userName() === userName);
|
||||
if (idx === -1) {
|
||||
throw new Error(`user ${userName} not found`);
|
||||
|
@ -36,9 +39,9 @@ module.exports = class RestMultiSession {
|
|||
return session;
|
||||
}
|
||||
|
||||
async setDisplayName(fn) {
|
||||
public async setDisplayName(fn: (s: RestSession) => string): Promise<void> {
|
||||
this.log.step("set their display name");
|
||||
await Promise.all(this.sessions.map(async (s) => {
|
||||
await Promise.all(this.sessions.map(async (s: RestSession) => {
|
||||
s.log.mute();
|
||||
await s.setDisplayName(fn(s));
|
||||
s.log.unmute();
|
||||
|
@ -46,7 +49,7 @@ module.exports = class RestMultiSession {
|
|||
this.log.done();
|
||||
}
|
||||
|
||||
async join(roomIdOrAlias) {
|
||||
public async join(roomIdOrAlias: string): Promise<RestMultiRoom> {
|
||||
this.log.step(`join ${roomIdOrAlias}`);
|
||||
const rooms = await Promise.all(this.sessions.map(async (s) => {
|
||||
s.log.mute();
|
||||
|
@ -58,22 +61,19 @@ module.exports = class RestMultiSession {
|
|||
return new RestMultiRoom(rooms, roomIdOrAlias, this.log);
|
||||
}
|
||||
|
||||
room(roomIdOrAlias) {
|
||||
public room(roomIdOrAlias: string): RestMultiRoom {
|
||||
const rooms = this.sessions.map(s => s.room(roomIdOrAlias));
|
||||
return new RestMultiRoom(rooms, roomIdOrAlias, this.log);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class RestMultiRoom {
|
||||
constructor(rooms, roomIdOrAlias, log) {
|
||||
this.rooms = rooms;
|
||||
this.roomIdOrAlias = roomIdOrAlias;
|
||||
this.log = log;
|
||||
}
|
||||
constructor(public readonly rooms: RestRoom[], private readonly roomIdOrAlias: string,
|
||||
private readonly log: Logger) {}
|
||||
|
||||
async talk(message) {
|
||||
public async talk(message: string): Promise<void> {
|
||||
this.log.step(`say "${message}" in ${this.roomIdOrAlias}`);
|
||||
await Promise.all(this.rooms.map(async (r) => {
|
||||
await Promise.all(this.rooms.map(async (r: RestRoom) => {
|
||||
r.log.mute();
|
||||
await r.talk(message);
|
||||
r.log.unmute();
|
||||
|
@ -81,7 +81,7 @@ class RestMultiRoom {
|
|||
this.log.done();
|
||||
}
|
||||
|
||||
async leave() {
|
||||
public async leave() {
|
||||
this.log.step(`leave ${this.roomIdOrAlias}`);
|
||||
await Promise.all(this.rooms.map(async (r) => {
|
||||
r.log.mute();
|
|
@ -15,20 +15,18 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
const uuidv4 = require('uuid/v4');
|
||||
import uuidv4 = require('uuid/v4');
|
||||
import { RestSession } from "./session";
|
||||
import { Logger } from "../logger";
|
||||
|
||||
/* no pun intented */
|
||||
module.exports = class RestRoom {
|
||||
constructor(session, roomId, log) {
|
||||
this.session = session;
|
||||
this._roomId = roomId;
|
||||
this.log = log;
|
||||
}
|
||||
export class RestRoom {
|
||||
constructor(readonly session: RestSession, readonly roomId: string, readonly log: Logger) {}
|
||||
|
||||
async talk(message) {
|
||||
this.log.step(`says "${message}" in ${this._roomId}`);
|
||||
async talk(message: string): Promise<void> {
|
||||
this.log.step(`says "${message}" in ${this.roomId}`);
|
||||
const txId = uuidv4();
|
||||
await this.session._put(`/rooms/${this._roomId}/send/m.room.message/${txId}`, {
|
||||
await this.session.put(`/rooms/${this.roomId}/send/m.room.message/${txId}`, {
|
||||
"msgtype": "m.text",
|
||||
"body": message,
|
||||
});
|
||||
|
@ -36,13 +34,9 @@ module.exports = class RestRoom {
|
|||
return txId;
|
||||
}
|
||||
|
||||
async leave() {
|
||||
this.log.step(`leaves ${this._roomId}`);
|
||||
await this.session._post(`/rooms/${this._roomId}/leave`);
|
||||
async leave(): Promise<void> {
|
||||
this.log.step(`leaves ${this.roomId}`);
|
||||
await this.session.post(`/rooms/${this.roomId}/leave`);
|
||||
this.log.done();
|
||||
}
|
||||
|
||||
roomId() {
|
||||
return this._roomId;
|
||||
}
|
||||
};
|
||||
}
|
137
test/end-to-end-tests/src/rest/session.ts
Normal file
137
test/end-to-end-tests/src/rest/session.ts
Normal file
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
Copyright 2018 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import request = require('request-promise-native');
|
||||
import { Logger } from '../logger';
|
||||
import { RestRoom } from './room';
|
||||
import { approveConsent } from './consent';
|
||||
import { Credentials } from "./creator";
|
||||
|
||||
interface RoomOptions {
|
||||
invite: string;
|
||||
public: boolean;
|
||||
topic: string;
|
||||
dm: boolean;
|
||||
}
|
||||
|
||||
export class RestSession {
|
||||
private _displayName: string = null;
|
||||
private readonly rooms: Record<string, RestRoom> = {};
|
||||
readonly log: Logger;
|
||||
|
||||
constructor(private readonly credentials: Credentials) {
|
||||
this.log = new Logger(credentials.userId);
|
||||
}
|
||||
|
||||
userId(): string {
|
||||
return this.credentials.userId;
|
||||
}
|
||||
|
||||
userName(): string {
|
||||
return this.credentials.userId.split(":")[0].substr(1);
|
||||
}
|
||||
|
||||
displayName(): string {
|
||||
return this._displayName;
|
||||
}
|
||||
|
||||
async setDisplayName(displayName: string): Promise<void> {
|
||||
this.log.step(`sets their display name to ${displayName}`);
|
||||
this._displayName = displayName;
|
||||
await this.put(`/profile/${this.credentials.userId}/displayname`, {
|
||||
displayname: displayName,
|
||||
});
|
||||
this.log.done();
|
||||
}
|
||||
|
||||
async join(roomIdOrAlias: string): Promise<RestRoom> {
|
||||
this.log.step(`joins ${roomIdOrAlias}`);
|
||||
const roomId = (await this.post(`/join/${encodeURIComponent(roomIdOrAlias)}`)).room_id;
|
||||
this.log.done();
|
||||
const room = new RestRoom(this, roomId, this.log);
|
||||
this.rooms[roomId] = room;
|
||||
this.rooms[roomIdOrAlias] = room;
|
||||
return room;
|
||||
}
|
||||
|
||||
room(roomIdOrAlias: string): RestRoom {
|
||||
if (this.rooms.hasOwnProperty(roomIdOrAlias)) {
|
||||
return this.rooms[roomIdOrAlias];
|
||||
} else {
|
||||
throw new Error(`${this.credentials.userId} is not in ${roomIdOrAlias}`);
|
||||
}
|
||||
}
|
||||
|
||||
async createRoom(name: string, options: RoomOptions): Promise<RestRoom> {
|
||||
this.log.step(`creates room ${name}`);
|
||||
const body = {
|
||||
name,
|
||||
};
|
||||
if (options.invite) {
|
||||
body['invite'] = options.invite;
|
||||
}
|
||||
if (options.public) {
|
||||
body['visibility'] = "public";
|
||||
} else {
|
||||
body['visibility'] = "private";
|
||||
}
|
||||
if (options.dm) {
|
||||
body['is_direct'] = true;
|
||||
}
|
||||
if (options.topic) {
|
||||
body['topic'] = options.topic;
|
||||
}
|
||||
|
||||
const roomId = (await this.post(`/createRoom`, body)).room_id;
|
||||
this.log.done();
|
||||
return new RestRoom(this, roomId, this.log);
|
||||
}
|
||||
|
||||
post(csApiPath: string, body?: any): Promise<any> {
|
||||
return this.request("POST", csApiPath, body);
|
||||
}
|
||||
|
||||
put(csApiPath: string, body?: any): Promise<any> {
|
||||
return this.request("PUT", csApiPath, body);
|
||||
}
|
||||
|
||||
async request(method: string, csApiPath: string, body?: any): Promise<any> {
|
||||
try {
|
||||
return await request({
|
||||
url: `${this.credentials.hsUrl}/_matrix/client/r0${csApiPath}`,
|
||||
method,
|
||||
headers: {
|
||||
"Authorization": `Bearer ${this.credentials.accessToken}`,
|
||||
},
|
||||
json: true,
|
||||
body,
|
||||
});
|
||||
} catch (err) {
|
||||
if (!err.response) {
|
||||
throw err;
|
||||
}
|
||||
const responseBody = err.response.body;
|
||||
if (responseBody.errcode === 'M_CONSENT_NOT_GIVEN') {
|
||||
await approveConsent(responseBody.consent_uri);
|
||||
return this.request(method, csApiPath, body);
|
||||
} else if (responseBody && responseBody.error) {
|
||||
throw new Error(`${method} ${csApiPath}: ${responseBody.error}`);
|
||||
} else {
|
||||
throw new Error(`${method} ${csApiPath}: ${err.response.statusCode}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,14 +14,20 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
const { range } = require('./util');
|
||||
const signup = require('./usecases/signup');
|
||||
const toastScenarios = require('./scenarios/toast');
|
||||
const roomDirectoryScenarios = require('./scenarios/directory');
|
||||
const lazyLoadingScenarios = require('./scenarios/lazy-loading');
|
||||
const e2eEncryptionScenarios = require('./scenarios/e2e-encryption');
|
||||
import { range } from './util';
|
||||
import { signup } from './usecases/signup';
|
||||
import { toastScenarios } from './scenarios/toast';
|
||||
import { roomDirectoryScenarios } from './scenarios/directory';
|
||||
import { lazyLoadingScenarios } from './scenarios/lazy-loading';
|
||||
import { e2eEncryptionScenarios } from './scenarios/e2e-encryption';
|
||||
import { ElementSession } from "./session";
|
||||
import { RestSessionCreator } from "./rest/creator";
|
||||
import { RestMultiSession } from "./rest/multi";
|
||||
import { spacesScenarios } from './scenarios/spaces';
|
||||
import { RestSession } from "./rest/session";
|
||||
|
||||
module.exports = async function scenario(createSession, restCreator) {
|
||||
export async function scenario(createSession: (s: string) => Promise<ElementSession>,
|
||||
restCreator: RestSessionCreator): Promise<void> {
|
||||
let firstUser = true;
|
||||
async function createUser(username) {
|
||||
const session = await createSession(username);
|
||||
|
@ -44,13 +50,12 @@ module.exports = async function scenario(createSession, restCreator) {
|
|||
const charlies = await createRestUsers(restCreator);
|
||||
await lazyLoadingScenarios(alice, bob, charlies);
|
||||
// do spaces scenarios last as the rest of the tests may get confused by spaces
|
||||
// XXX: disabled for now as fails in CI but succeeds locally
|
||||
// await spacesScenarios(alice, bob);
|
||||
};
|
||||
await spacesScenarios(alice, bob);
|
||||
}
|
||||
|
||||
async function createRestUsers(restCreator) {
|
||||
async function createRestUsers(restCreator: RestSessionCreator): Promise<RestMultiSession> {
|
||||
const usernames = range(1, 10).map((i) => `charly-${i}`);
|
||||
const charlies = await restCreator.createSessionRange(usernames, "testtest", "charly-1..10");
|
||||
await charlies.setDisplayName((s) => `Charly #${s.userName().split('-')[1]}`);
|
||||
await charlies.setDisplayName((s: RestSession) => `Charly #${s.userName().split('-')[1]}`);
|
||||
return charlies;
|
||||
}
|
|
@ -15,13 +15,14 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
const join = require('../usecases/join');
|
||||
const sendMessage = require('../usecases/send-message');
|
||||
const { receiveMessage } = require('../usecases/timeline');
|
||||
const { createRoom } = require('../usecases/create-room');
|
||||
const { changeRoomSettings } = require('../usecases/room-settings');
|
||||
import { join } from '../usecases/join';
|
||||
import { sendMessage } from '../usecases/send-message';
|
||||
import { receiveMessage } from '../usecases/timeline';
|
||||
import { createRoom } from '../usecases/create-room';
|
||||
import { changeRoomSettings } from '../usecases/room-settings';
|
||||
import { ElementSession } from "../session";
|
||||
|
||||
module.exports = async function roomDirectoryScenarios(alice, bob) {
|
||||
export async function roomDirectoryScenarios(alice: ElementSession, bob: ElementSession) {
|
||||
console.log(" creating a public room and join through directory:");
|
||||
const room = 'test';
|
||||
await createRoom(alice, room);
|
||||
|
@ -33,4 +34,4 @@ module.exports = async function roomDirectoryScenarios(alice, bob) {
|
|||
const aliceMessage = "hi Bob, welcome!";
|
||||
await sendMessage(alice, aliceMessage);
|
||||
await receiveMessage(bob, { sender: "alice", body: aliceMessage });
|
||||
};
|
||||
}
|
|
@ -15,23 +15,24 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
const assert = require('assert');
|
||||
import { ElementSession } from "../session";
|
||||
|
||||
const sendMessage = require('../usecases/send-message');
|
||||
const acceptInvite = require('../usecases/accept-invite');
|
||||
const { receiveMessage } = require('../usecases/timeline');
|
||||
const { createDm } = require('../usecases/create-room');
|
||||
const { checkRoomSettings } = require('../usecases/room-settings');
|
||||
const { startSasVerification, acceptSasVerification } = require('../usecases/verify');
|
||||
const { setupSecureBackup } = require('../usecases/security');
|
||||
const { measureStart, measureStop } = require('../util');
|
||||
import { sendMessage } from '../usecases/send-message';
|
||||
import { acceptInvite } from '../usecases/accept-invite';
|
||||
import { receiveMessage } from '../usecases/timeline';
|
||||
import { createDm } from '../usecases/create-room';
|
||||
import { checkRoomSettings } from '../usecases/room-settings';
|
||||
import { startSasVerification, acceptSasVerification } from '../usecases/verify';
|
||||
import { setupSecureBackup } from '../usecases/security';
|
||||
import { strict as assert } from 'assert';
|
||||
import { measureStart, measureStop } from '../util';
|
||||
|
||||
module.exports = async function e2eEncryptionScenarios(alice, bob) {
|
||||
export async function e2eEncryptionScenarios(alice: ElementSession, bob: ElementSession) {
|
||||
console.log(" creating an e2e encrypted DM and join through invite:");
|
||||
await createDm(bob, ['@alice:localhost']);
|
||||
await checkRoomSettings(bob, { encryption: true }); // for sanity, should be e2e-by-default
|
||||
await acceptInvite(alice, 'bob');
|
||||
// do sas verifcation
|
||||
// do sas verification
|
||||
bob.log.step(`starts SAS verification with ${alice.username}`);
|
||||
await measureStart(bob, "mx_VerifyE2EEUser");
|
||||
const bobSasPromise = startSasVerification(bob, alice.username);
|
||||
|
@ -49,4 +50,4 @@ module.exports = async function e2eEncryptionScenarios(alice, bob) {
|
|||
await sendMessage(bob, bobMessage);
|
||||
await receiveMessage(alice, { sender: "bob", body: bobMessage, encrypted: true });
|
||||
await setupSecureBackup(alice);
|
||||
};
|
||||
}
|
|
@ -15,25 +15,27 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
const assert = require('assert');
|
||||
|
||||
const { delay } = require('../util');
|
||||
const join = require('../usecases/join');
|
||||
const sendMessage = require('../usecases/send-message');
|
||||
const {
|
||||
import { delay } from '../util';
|
||||
import { join } from '../usecases/join';
|
||||
import { sendMessage } from '../usecases/send-message';
|
||||
import {
|
||||
checkTimelineContains,
|
||||
scrollToTimelineTop,
|
||||
} = require('../usecases/timeline');
|
||||
const { createRoom } = require('../usecases/create-room');
|
||||
const { getMembersInMemberlist } = require('../usecases/memberlist');
|
||||
const { changeRoomSettings } = require('../usecases/room-settings');
|
||||
} from '../usecases/timeline';
|
||||
import { createRoom } from '../usecases/create-room';
|
||||
import { getMembersInMemberlist } from '../usecases/memberlist';
|
||||
import { changeRoomSettings } from '../usecases/room-settings';
|
||||
import { strict as assert } from 'assert';
|
||||
import { RestMultiSession } from "../rest/multi";
|
||||
import { ElementSession } from "../session";
|
||||
|
||||
module.exports = async function lazyLoadingScenarios(alice, bob, charlies) {
|
||||
export async function lazyLoadingScenarios(alice: ElementSession,
|
||||
bob: ElementSession, charlies: RestMultiSession): Promise<void> {
|
||||
console.log(" creating a room for lazy loading member scenarios:");
|
||||
const charly1to5 = charlies.slice("charly-1..5", 0, 5);
|
||||
const charly6to10 = charlies.slice("charly-6..10", 5);
|
||||
assert(charly1to5.sessions.length, 5);
|
||||
assert(charly6to10.sessions.length, 5);
|
||||
assert(charly1to5.sessions.length == 5);
|
||||
assert(charly6to10.sessions.length == 5);
|
||||
await setupRoomWithBobAliceAndCharlies(alice, bob, charly1to5);
|
||||
await checkPaginatedDisplayNames(alice, charly1to5);
|
||||
await checkMemberList(alice, charly1to5);
|
||||
|
@ -43,14 +45,15 @@ module.exports = async function lazyLoadingScenarios(alice, bob, charlies) {
|
|||
await delay(1000);
|
||||
await checkMemberListLacksCharlies(alice, charlies);
|
||||
await checkMemberListLacksCharlies(bob, charlies);
|
||||
};
|
||||
}
|
||||
|
||||
const room = "Lazy Loading Test";
|
||||
const alias = "#lltest:localhost";
|
||||
const charlyMsg1 = "hi bob!";
|
||||
const charlyMsg2 = "how's it going??";
|
||||
|
||||
async function setupRoomWithBobAliceAndCharlies(alice, bob, charlies) {
|
||||
async function setupRoomWithBobAliceAndCharlies(alice: ElementSession, bob: ElementSession,
|
||||
charlies: RestMultiSession): Promise<void> {
|
||||
await createRoom(bob, room);
|
||||
await changeRoomSettings(bob, { directory: true, visibility: "public", alias });
|
||||
// wait for alias to be set by server after clicking "save"
|
||||
|
@ -67,7 +70,7 @@ async function setupRoomWithBobAliceAndCharlies(alice, bob, charlies) {
|
|||
await join(alice, alias);
|
||||
}
|
||||
|
||||
async function checkPaginatedDisplayNames(alice, charlies) {
|
||||
async function checkPaginatedDisplayNames(alice: ElementSession, charlies: RestMultiSession): Promise<void> {
|
||||
await scrollToTimelineTop(alice);
|
||||
//alice should see 2 messages from every charly with
|
||||
//the correct display name
|
||||
|
@ -82,7 +85,7 @@ async function checkPaginatedDisplayNames(alice, charlies) {
|
|||
await checkTimelineContains(alice, expectedMessages, charlies.log.username);
|
||||
}
|
||||
|
||||
async function checkMemberList(alice, charlies) {
|
||||
async function checkMemberList(alice: ElementSession, charlies: RestMultiSession): Promise<void> {
|
||||
alice.log.step(`checks the memberlist contains herself, bob and ${charlies.log.username}`);
|
||||
const displayNames = (await getMembersInMemberlist(alice)).map((m) => m.displayName);
|
||||
assert(displayNames.includes("alice"));
|
||||
|
@ -95,7 +98,7 @@ async function checkMemberList(alice, charlies) {
|
|||
alice.log.done();
|
||||
}
|
||||
|
||||
async function checkMemberListLacksCharlies(session, charlies) {
|
||||
async function checkMemberListLacksCharlies(session: ElementSession, charlies: RestMultiSession): Promise<void> {
|
||||
session.log.step(`checks the memberlist doesn't contain ${charlies.log.username}`);
|
||||
const displayNames = (await getMembersInMemberlist(session)).map((m) => m.displayName);
|
||||
charlies.sessions.forEach((charly) => {
|
||||
|
@ -106,7 +109,7 @@ async function checkMemberListLacksCharlies(session, charlies) {
|
|||
session.log.done();
|
||||
}
|
||||
|
||||
async function joinCharliesWhileAliceIsOffline(alice, charly6to10) {
|
||||
async function joinCharliesWhileAliceIsOffline(alice: ElementSession, charly6to10: RestMultiSession) {
|
||||
await alice.setOffline(true);
|
||||
await delay(1000);
|
||||
const members6to10 = await charly6to10.join(alias);
|
|
@ -1,3 +1,5 @@
|
|||
import { ElementSession } from "../session";
|
||||
|
||||
/*
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
|
@ -14,18 +16,18 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
const { createSpace, inviteSpace } = require("../usecases/create-space");
|
||||
import { createSpace, inviteSpace } from "../usecases/create-space";
|
||||
|
||||
module.exports = async function spacesScenarios(alice, bob) {
|
||||
export async function spacesScenarios(alice: ElementSession, bob: ElementSession): Promise<void> {
|
||||
console.log(" creating a space for spaces scenarios:");
|
||||
|
||||
await alice.delay(1000); // wait for dialogs to close
|
||||
await setupSpaceUsingAliceAndInviteBob(alice, bob);
|
||||
};
|
||||
}
|
||||
|
||||
const space = "Test Space";
|
||||
|
||||
async function setupSpaceUsingAliceAndInviteBob(alice, bob) {
|
||||
async function setupSpaceUsingAliceAndInviteBob(alice: ElementSession, bob: ElementSession): Promise<void> {
|
||||
await createSpace(alice, space);
|
||||
await inviteSpace(alice, space, "@bob:localhost");
|
||||
await bob.query(`.mx_SpaceButton[aria-label="${space}"]`); // assert invite received
|
|
@ -14,9 +14,10 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
const { assertNoToasts, acceptToast, rejectToast } = require("../usecases/toasts");
|
||||
import { assertNoToasts, acceptToast, rejectToast } from "../usecases/toasts";
|
||||
import { ElementSession } from "../session";
|
||||
|
||||
module.exports = async function toastScenarios(alice, bob) {
|
||||
export async function toastScenarios(alice: ElementSession, bob: ElementSession): Promise<void> {
|
||||
console.log(" checking and clearing toasts:");
|
||||
|
||||
alice.log.startGroup(`clears toasts`);
|
||||
|
@ -25,7 +26,8 @@ module.exports = async function toastScenarios(alice, bob) {
|
|||
alice.log.done();
|
||||
|
||||
alice.log.step(`accepts analytics toast`);
|
||||
await acceptToast(alice, "Help us improve Element");
|
||||
await acceptToast(alice, "Help improve Element");
|
||||
await rejectToast(alice, "Testing small changes");
|
||||
alice.log.done();
|
||||
|
||||
alice.log.step(`checks no remaining toasts`);
|
||||
|
@ -39,11 +41,12 @@ module.exports = async function toastScenarios(alice, bob) {
|
|||
bob.log.done();
|
||||
|
||||
bob.log.step(`reject analytics toast`);
|
||||
await rejectToast(bob, "Help us improve Element");
|
||||
await rejectToast(bob, "Help improve Element");
|
||||
await rejectToast(bob, "Testing small changes");
|
||||
bob.log.done();
|
||||
|
||||
bob.log.step(`checks no remaining toasts`);
|
||||
await assertNoToasts(bob);
|
||||
bob.log.done();
|
||||
bob.log.endGroup();
|
||||
};
|
||||
}
|
|
@ -15,31 +15,37 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
const puppeteer = require('puppeteer');
|
||||
|
||||
const Logger = require('./logger');
|
||||
const LogBuffer = require('./logbuffer');
|
||||
const { delay } = require('./util');
|
||||
import * as puppeteer from 'puppeteer';
|
||||
import { Logger } from './logger';
|
||||
import { LogBuffer } from './logbuffer';
|
||||
import { delay } from './util';
|
||||
|
||||
const DEFAULT_TIMEOUT = 20000;
|
||||
|
||||
module.exports = class ElementSession {
|
||||
constructor(browser, page, username, elementServer, hsUrl) {
|
||||
this.browser = browser;
|
||||
this.page = page;
|
||||
this.hsUrl = hsUrl;
|
||||
this.elementServer = elementServer;
|
||||
this.username = username;
|
||||
this.consoleLog = new LogBuffer(page, "console", (msg) => `${msg.text()}\n`);
|
||||
this.networkLog = new LogBuffer(page, "requestfinished", async (req) => {
|
||||
const type = req.resourceType();
|
||||
const response = await req.response();
|
||||
return `${type} ${response.status()} ${req.method()} ${req.url()} \n`;
|
||||
}, true);
|
||||
interface XHRLogger {
|
||||
logs: () => string;
|
||||
}
|
||||
|
||||
export class ElementSession {
|
||||
readonly consoleLog: LogBuffer<puppeteer.ConsoleMessage>;
|
||||
readonly networkLog: LogBuffer<puppeteer.HTTPRequest>;
|
||||
readonly log: Logger;
|
||||
|
||||
constructor(readonly browser: puppeteer.Browser, readonly page: puppeteer.Page, readonly username: string,
|
||||
readonly elementServer: string, readonly hsUrl: string) {
|
||||
this.consoleLog = new LogBuffer(page, "console",
|
||||
async (msg: puppeteer.ConsoleMessage) => Promise.resolve(`${msg.text()}\n`));
|
||||
this.networkLog = new LogBuffer(page,
|
||||
"requestfinished", async (req: puppeteer.HTTPRequest) => {
|
||||
const type = req.resourceType();
|
||||
const response = await req.response();
|
||||
return `${type} ${response.status()} ${req.method()} ${req.url()} \n`;
|
||||
});
|
||||
this.log = new Logger(this.username);
|
||||
}
|
||||
|
||||
static async create(username, puppeteerOptions, elementServer, hsUrl, throttleCpuFactor = 1) {
|
||||
public static async create(username: string, puppeteerOptions: Parameters<typeof puppeteer.launch>[0],
|
||||
elementServer: string, hsUrl: string, throttleCpuFactor = 1): Promise<ElementSession> {
|
||||
const browser = await puppeteer.launch(puppeteerOptions);
|
||||
const page = await browser.newPage();
|
||||
await page.setViewport({
|
||||
|
@ -54,7 +60,7 @@ module.exports = class ElementSession {
|
|||
return new ElementSession(browser, page, username, elementServer, hsUrl);
|
||||
}
|
||||
|
||||
async tryGetInnertext(selector) {
|
||||
public async tryGetInnertext(selector: string): Promise<string> {
|
||||
const field = await this.page.$(selector);
|
||||
if (field != null) {
|
||||
const textHandle = await field.getProperty('innerText');
|
||||
|
@ -63,32 +69,32 @@ module.exports = class ElementSession {
|
|||
return null;
|
||||
}
|
||||
|
||||
async getElementProperty(handle, property) {
|
||||
public async getElementProperty(handle: puppeteer.ElementHandle, property: string): Promise<string> {
|
||||
const propHandle = await handle.getProperty(property);
|
||||
return await propHandle.jsonValue();
|
||||
}
|
||||
|
||||
innerText(field) {
|
||||
public innerText(field: puppeteer.ElementHandle): Promise<string> {
|
||||
return this.getElementProperty(field, 'innerText');
|
||||
}
|
||||
|
||||
getOuterHTML(field) {
|
||||
public getOuterHTML(field: puppeteer.ElementHandle): Promise<string> {
|
||||
return this.getElementProperty(field, 'outerHTML');
|
||||
}
|
||||
|
||||
isChecked(field) {
|
||||
public isChecked(field: puppeteer.ElementHandle): Promise<string> {
|
||||
return this.getElementProperty(field, 'checked');
|
||||
}
|
||||
|
||||
consoleLogs() {
|
||||
public consoleLogs(): string {
|
||||
return this.consoleLog.buffer;
|
||||
}
|
||||
|
||||
networkLogs() {
|
||||
public networkLogs(): string {
|
||||
return this.networkLog.buffer;
|
||||
}
|
||||
|
||||
logXHRRequests() {
|
||||
public logXHRRequests(): XHRLogger {
|
||||
let buffer = "";
|
||||
this.page.on('requestfinished', async (req) => {
|
||||
const type = req.resourceType();
|
||||
|
@ -107,11 +113,11 @@ module.exports = class ElementSession {
|
|||
};
|
||||
}
|
||||
|
||||
async printElements(label, elements) {
|
||||
public async printElements(label: string, elements: puppeteer.ElementHandle[] ): Promise<void> {
|
||||
console.log(label, await Promise.all(elements.map(this.getOuterHTML)));
|
||||
}
|
||||
|
||||
async replaceInputText(input, text) {
|
||||
public async replaceInputText(input: puppeteer.ElementHandle, text: string): Promise<void> {
|
||||
// click 3 times to select all text
|
||||
await input.click({ clickCount: 3 });
|
||||
// waiting here solves not having selected all the text by the 3x click above,
|
||||
|
@ -123,21 +129,22 @@ module.exports = class ElementSession {
|
|||
await input.type(text);
|
||||
}
|
||||
|
||||
query(selector, timeout = DEFAULT_TIMEOUT, hidden = false) {
|
||||
public query(selector: string, timeout: number = DEFAULT_TIMEOUT,
|
||||
hidden = false): Promise<puppeteer.ElementHandle> {
|
||||
return this.page.waitForSelector(selector, { visible: true, timeout, hidden });
|
||||
}
|
||||
|
||||
async queryAll(selector) {
|
||||
public async queryAll(selector: string): Promise<puppeteer.ElementHandle[]> {
|
||||
const timeout = DEFAULT_TIMEOUT;
|
||||
await this.query(selector, timeout);
|
||||
return await this.page.$$(selector);
|
||||
}
|
||||
|
||||
waitForReload() {
|
||||
public waitForReload(): Promise<void> {
|
||||
const timeout = DEFAULT_TIMEOUT;
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeoutHandle = setTimeout(() => {
|
||||
this.browser.removeEventListener('domcontentloaded', callback);
|
||||
this.page.off('domcontentloaded', callback);
|
||||
reject(new Error(`timeout of ${timeout}ms for waitForReload elapsed`));
|
||||
}, timeout);
|
||||
|
||||
|
@ -150,11 +157,11 @@ module.exports = class ElementSession {
|
|||
});
|
||||
}
|
||||
|
||||
waitForNewPage() {
|
||||
public waitForNewPage(): Promise<void> {
|
||||
const timeout = DEFAULT_TIMEOUT;
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeoutHandle = setTimeout(() => {
|
||||
this.browser.removeListener('targetcreated', callback);
|
||||
this.browser.off('targetcreated', callback);
|
||||
reject(new Error(`timeout of ${timeout}ms for waitForNewPage elapsed`));
|
||||
}, timeout);
|
||||
|
||||
|
@ -162,7 +169,7 @@ module.exports = class ElementSession {
|
|||
if (target.type() !== 'page') {
|
||||
return;
|
||||
}
|
||||
this.browser.removeListener('targetcreated', callback);
|
||||
this.browser.off('targetcreated', callback);
|
||||
clearTimeout(timeoutHandle);
|
||||
const page = await target.page();
|
||||
resolve(page);
|
||||
|
@ -173,7 +180,7 @@ module.exports = class ElementSession {
|
|||
}
|
||||
|
||||
/** wait for a /sync request started after this call that gets a 200 response */
|
||||
async waitForNextSuccessfulSync() {
|
||||
public async waitForNextSuccessfulSync(): Promise<void> {
|
||||
const syncUrls = [];
|
||||
function onRequest(request) {
|
||||
if (request.url().indexOf("/sync") !== -1) {
|
||||
|
@ -187,33 +194,33 @@ module.exports = class ElementSession {
|
|||
return syncUrls.includes(response.request().url()) && response.status() === 200;
|
||||
});
|
||||
|
||||
this.page.removeListener('request', onRequest);
|
||||
this.page.off('request', onRequest);
|
||||
}
|
||||
|
||||
goto(url) {
|
||||
public goto(url: string): Promise<puppeteer.HTTPResponse> {
|
||||
return this.page.goto(url);
|
||||
}
|
||||
|
||||
url(path) {
|
||||
public url(path: string): string {
|
||||
return this.elementServer + path;
|
||||
}
|
||||
|
||||
delay(ms) {
|
||||
public delay(ms: number) {
|
||||
return delay(ms);
|
||||
}
|
||||
|
||||
async setOffline(enabled) {
|
||||
public async setOffline(enabled: boolean): Promise<void> {
|
||||
const description = enabled ? "offline" : "back online";
|
||||
this.log.step(`goes ${description}`);
|
||||
await this.page.setOfflineMode(enabled);
|
||||
this.log.done();
|
||||
}
|
||||
|
||||
async close() {
|
||||
public async close(): Promise<void> {
|
||||
return this.browser.close();
|
||||
}
|
||||
|
||||
async poll(callback, interval = 100) {
|
||||
public async poll(callback: () => Promise<boolean>, interval = 100): Promise<boolean> {
|
||||
const timeout = DEFAULT_TIMEOUT;
|
||||
let waited = 0;
|
||||
while (waited < timeout) {
|
||||
|
@ -225,4 +232,4 @@ module.exports = class ElementSession {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
|
@ -15,9 +15,10 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
const { findSublist } = require("./create-room");
|
||||
import { findSublist } from "./create-room";
|
||||
import { ElementSession } from "../session";
|
||||
|
||||
module.exports = async function acceptInvite(session, name) {
|
||||
export async function acceptInvite(session: ElementSession, name: string): Promise<void> {
|
||||
session.log.step(`accepts "${name}" invite`);
|
||||
const inviteSublist = await findSublist(session, "invites");
|
||||
const invitesHandles = await inviteSublist.$$(".mx_RoomTile_name");
|
||||
|
@ -35,4 +36,4 @@ module.exports = async function acceptInvite(session, name) {
|
|||
await acceptInvitationLink.click();
|
||||
|
||||
session.log.done();
|
||||
};
|
||||
}
|
|
@ -15,18 +15,20 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
const { measureStart, measureStop } = require('../util');
|
||||
import { measureStart, measureStop } from '../util';
|
||||
import { ElementSession } from "../session";
|
||||
import * as puppeteer from "puppeteer";
|
||||
|
||||
async function openRoomDirectory(session) {
|
||||
export async function openRoomDirectory(session: ElementSession): Promise<void> {
|
||||
const roomDirectoryButton = await session.query('.mx_LeftPanel_exploreButton');
|
||||
await roomDirectoryButton.click();
|
||||
}
|
||||
|
||||
async function findSublist(session, name) {
|
||||
export async function findSublist(session: ElementSession, name: string): Promise<puppeteer.ElementHandle> {
|
||||
return await session.query(`.mx_RoomSublist[aria-label="${name}" i]`);
|
||||
}
|
||||
|
||||
async function createRoom(session, roomName, encrypted=false) {
|
||||
export async function createRoom(session: ElementSession, roomName: string, encrypted = false): Promise<void> {
|
||||
session.log.step(`creates room "${roomName}"`);
|
||||
|
||||
const roomsSublist = await findSublist(session, "rooms");
|
||||
|
@ -51,7 +53,7 @@ async function createRoom(session, roomName, encrypted=false) {
|
|||
session.log.done();
|
||||
}
|
||||
|
||||
async function createDm(session, invitees) {
|
||||
export async function createDm(session: ElementSession, invitees: string[]): Promise<void> {
|
||||
session.log.step(`creates DM with ${JSON.stringify(invitees)}`);
|
||||
|
||||
await measureStart(session, "mx_CreateDM");
|
||||
|
@ -83,5 +85,3 @@ async function createDm(session, invitees) {
|
|||
|
||||
await measureStop(session, "mx_CreateDM");
|
||||
}
|
||||
|
||||
module.exports = { openRoomDirectory, findSublist, createRoom, createDm };
|
|
@ -14,12 +14,14 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
async function openSpaceCreateMenu(session) {
|
||||
import { ElementSession } from "../session";
|
||||
|
||||
export async function openSpaceCreateMenu(session: ElementSession): Promise<void> {
|
||||
const spaceCreateButton = await session.query('.mx_SpaceButton_new');
|
||||
await spaceCreateButton.click();
|
||||
}
|
||||
|
||||
async function createSpace(session, name, isPublic = false) {
|
||||
export async function createSpace(session: ElementSession, name: string, isPublic = false): Promise<void> {
|
||||
session.log.step(`creates space "${name}"`);
|
||||
|
||||
await openSpaceCreateMenu(session);
|
||||
|
@ -50,7 +52,7 @@ async function createSpace(session, name, isPublic = false) {
|
|||
session.log.done();
|
||||
}
|
||||
|
||||
async function inviteSpace(session, spaceName, userId) {
|
||||
export async function inviteSpace(session: ElementSession, spaceName: string, userId: string): Promise<void> {
|
||||
session.log.step(`invites "${userId}" to space "${spaceName}"`);
|
||||
|
||||
const spaceButton = await session.query(`.mx_SpaceButton[aria-label="${spaceName}"]`);
|
||||
|
@ -58,7 +60,7 @@ async function inviteSpace(session, spaceName, userId) {
|
|||
button: 'right',
|
||||
});
|
||||
|
||||
const inviteButton = await session.query('[aria-label="Invite people"]');
|
||||
const inviteButton = await session.query('.mx_SpacePanel_contextMenu_inviteButton[aria-label="Invite"]');
|
||||
await inviteButton.click();
|
||||
|
||||
try {
|
||||
|
@ -76,5 +78,3 @@ async function inviteSpace(session, spaceName, userId) {
|
|||
await confirmButton.click();
|
||||
session.log.done();
|
||||
}
|
||||
|
||||
module.exports = { openSpaceCreateMenu, createSpace, inviteSpace };
|
|
@ -15,22 +15,23 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
const assert = require('assert');
|
||||
import { strict as assert } from 'assert';
|
||||
import { ElementSession } from "../session";
|
||||
|
||||
async function assertDialog(session, expectedTitle) {
|
||||
export async function assertDialog(session: ElementSession, expectedTitle: string): Promise<void> {
|
||||
const titleElement = await session.query(".mx_Dialog .mx_Dialog_title");
|
||||
const dialogHeader = await session.innerText(titleElement);
|
||||
assert.equal(dialogHeader, expectedTitle);
|
||||
}
|
||||
|
||||
async function acceptDialog(session, expectedTitle) {
|
||||
export async function acceptDialog(session: ElementSession, expectedTitle: string): Promise<void> {
|
||||
const foundDialog = await acceptDialogMaybe(session, expectedTitle);
|
||||
if (!foundDialog) {
|
||||
throw new Error("could not find a dialog");
|
||||
}
|
||||
}
|
||||
|
||||
async function acceptDialogMaybe(session, expectedTitle) {
|
||||
export async function acceptDialogMaybe(session: ElementSession, expectedTitle: string): Promise<boolean> {
|
||||
let primaryButton = null;
|
||||
try {
|
||||
primaryButton = await session.query(".mx_Dialog .mx_Dialog_primary");
|
||||
|
@ -43,9 +44,3 @@ async function acceptDialogMaybe(session, expectedTitle) {
|
|||
await primaryButton.click();
|
||||
return true;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
assertDialog,
|
||||
acceptDialog,
|
||||
acceptDialogMaybe,
|
||||
};
|
|
@ -15,7 +15,9 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
module.exports = async function invite(session, userId) {
|
||||
import { ElementSession } from "../session";
|
||||
|
||||
export async function invite(session: ElementSession, userId: string): Promise<void> {
|
||||
session.log.step(`invites "${userId}" to room`);
|
||||
await session.delay(1000);
|
||||
const memberPanelButton = await session.query(".mx_RightPanel_membersButton");
|
||||
|
@ -38,4 +40,4 @@ module.exports = async function invite(session, userId) {
|
|||
const confirmButton = await session.query(".mx_InviteDialog_goButton");
|
||||
await confirmButton.click();
|
||||
session.log.done();
|
||||
};
|
||||
}
|
|
@ -15,10 +15,11 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
const { openRoomDirectory } = require('./create-room');
|
||||
const { measureStart, measureStop } = require('../util');
|
||||
import { openRoomDirectory } from './create-room';
|
||||
import { measureStart, measureStop } from '../util';
|
||||
import { ElementSession } from "../session";
|
||||
|
||||
module.exports = async function join(session, roomName) {
|
||||
export async function join(session: ElementSession, roomName: string): Promise<void> {
|
||||
session.log.step(`joins room "${roomName}"`);
|
||||
await measureStart(session, "mx_JoinRoom");
|
||||
await openRoomDirectory(session);
|
||||
|
@ -30,4 +31,4 @@ module.exports = async function join(session, roomName) {
|
|||
await session.query('.mx_MessageComposer');
|
||||
await measureStop(session, "mx_JoinRoom");
|
||||
session.log.done();
|
||||
};
|
||||
}
|
|
@ -15,11 +15,12 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
const assert = require('assert');
|
||||
import { strict as assert } from 'assert';
|
||||
import { openRoomSummaryCard } from "./rightpanel";
|
||||
import { ElementSession } from "../session";
|
||||
import { ElementHandle } from "puppeteer";
|
||||
|
||||
const { openRoomSummaryCard } = require("./rightpanel");
|
||||
|
||||
async function openMemberInfo(session, name) {
|
||||
export async function openMemberInfo(session: ElementSession, name: String): Promise<void> {
|
||||
const membersAndNames = await getMembersInMemberlist(session);
|
||||
const matchingLabel = membersAndNames.filter((m) => {
|
||||
return m.displayName === name;
|
||||
|
@ -27,9 +28,13 @@ async function openMemberInfo(session, name) {
|
|||
await matchingLabel.click();
|
||||
}
|
||||
|
||||
module.exports.openMemberInfo = openMemberInfo;
|
||||
interface Device {
|
||||
id: string;
|
||||
key: string;
|
||||
}
|
||||
|
||||
module.exports.verifyDeviceForUser = async function(session, name, expectedDevice) {
|
||||
export async function verifyDeviceForUser(session: ElementSession, name: string,
|
||||
expectedDevice: Device): Promise<void> {
|
||||
session.log.step(`verifies e2e device for ${name}`);
|
||||
const membersAndNames = await getMembersInMemberlist(session);
|
||||
const matchingLabel = membersAndNames.filter((m) => {
|
||||
|
@ -51,19 +56,24 @@ module.exports.verifyDeviceForUser = async function(session, name, expectedDevic
|
|||
console.log("my sas labels", sasLabels);
|
||||
|
||||
const dialogCodeFields = await session.queryAll(".mx_QuestionDialog code");
|
||||
assert.equal(dialogCodeFields.length, 2);
|
||||
assert.strictEqual(dialogCodeFields.length, 2);
|
||||
const deviceId = await session.innerText(dialogCodeFields[0]);
|
||||
const deviceKey = await session.innerText(dialogCodeFields[1]);
|
||||
assert.equal(expectedDevice.id, deviceId);
|
||||
assert.equal(expectedDevice.key, deviceKey);
|
||||
assert.strictEqual(expectedDevice.id, deviceId);
|
||||
assert.strictEqual(expectedDevice.key, deviceKey);
|
||||
const confirmButton = await session.query(".mx_Dialog_primary");
|
||||
await confirmButton.click();
|
||||
const closeMemberInfo = await session.query(".mx_MemberInfo_cancel");
|
||||
await closeMemberInfo.click();
|
||||
session.log.done();
|
||||
};
|
||||
}
|
||||
|
||||
async function getMembersInMemberlist(session) {
|
||||
interface MemberName {
|
||||
label: ElementHandle;
|
||||
displayName: string;
|
||||
}
|
||||
|
||||
export async function getMembersInMemberlist(session: ElementSession): Promise<MemberName[]> {
|
||||
await openRoomSummaryCard(session);
|
||||
const memberPanelButton = await session.query(".mx_RoomSummaryCard_icon_people");
|
||||
// We are back at the room summary card
|
||||
|
@ -74,5 +84,3 @@ async function getMembersInMemberlist(session) {
|
|||
return { label: el, displayName: await session.innerText(el) };
|
||||
}));
|
||||
}
|
||||
|
||||
module.exports.getMembersInMemberlist = getMembersInMemberlist;
|
|
@ -14,7 +14,9 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
module.exports.openRoomRightPanel = async function(session) {
|
||||
import { ElementSession } from "../session";
|
||||
|
||||
export async function openRoomRightPanel(session: ElementSession): Promise<void> {
|
||||
try {
|
||||
await session.query('.mx_RoomHeader .mx_RightPanel_headerButton_highlight[aria-label="Room Info"]');
|
||||
} catch (e) {
|
||||
|
@ -22,9 +24,9 @@ module.exports.openRoomRightPanel = async function(session) {
|
|||
const roomSummaryButton = await session.query('.mx_RoomHeader .mx_AccessibleButton[aria-label="Room Info"]');
|
||||
await roomSummaryButton.click();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports.goBackToRoomSummaryCard = async function(session) {
|
||||
export async function goBackToRoomSummaryCard(session: ElementSession): Promise<void> {
|
||||
for (let i = 0; i < 5; i++) {
|
||||
try {
|
||||
const backButton = await session.query(".mx_BaseCard_back", 500);
|
||||
|
@ -39,9 +41,9 @@ module.exports.goBackToRoomSummaryCard = async function(session) {
|
|||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports.openRoomSummaryCard = async function(session) {
|
||||
await module.exports.openRoomRightPanel(session);
|
||||
await module.exports.goBackToRoomSummaryCard(session);
|
||||
};
|
||||
export async function openRoomSummaryCard(session: ElementSession) {
|
||||
await openRoomRightPanel(session);
|
||||
await goBackToRoomSummaryCard(session);
|
||||
}
|
|
@ -15,12 +15,13 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
const assert = require('assert');
|
||||
import { strict as assert } from 'assert';
|
||||
import { openRoomSummaryCard } from "./rightpanel";
|
||||
import { acceptDialog } from './dialog';
|
||||
import { ElementSession } from "../session";
|
||||
import { ElementHandle } from "puppeteer";
|
||||
|
||||
const { openRoomSummaryCard } = require("./rightpanel");
|
||||
const { acceptDialog } = require('./dialog');
|
||||
|
||||
async function setSettingsToggle(session, toggle, enabled) {
|
||||
export async function setSettingsToggle(session: ElementSession, toggle: ElementHandle, enabled): Promise<boolean> {
|
||||
const className = await session.getElementProperty(toggle, "className");
|
||||
const checked = className.includes("mx_ToggleSwitch_on");
|
||||
if (checked !== enabled) {
|
||||
|
@ -32,7 +33,8 @@ async function setSettingsToggle(session, toggle, enabled) {
|
|||
}
|
||||
}
|
||||
|
||||
async function checkSettingsToggle(session, toggle, shouldBeEnabled) {
|
||||
export async function checkSettingsToggle(session: ElementSession,
|
||||
toggle: ElementHandle, shouldBeEnabled: boolean): Promise<void> {
|
||||
const className = await session.getElementProperty(toggle, "className");
|
||||
const checked = className.includes("mx_ToggleSwitch_on");
|
||||
if (checked === shouldBeEnabled) {
|
||||
|
@ -43,7 +45,11 @@ async function checkSettingsToggle(session, toggle, shouldBeEnabled) {
|
|||
}
|
||||
}
|
||||
|
||||
async function findTabs(session) {
|
||||
interface Tabs {
|
||||
securityTabButton: ElementHandle;
|
||||
}
|
||||
|
||||
async function findTabs(session: ElementSession): Promise<Tabs> {
|
||||
/// XXX delay is needed here, possibly because the header is being rerendered
|
||||
/// click doesn't do anything otherwise
|
||||
await session.delay(1000);
|
||||
|
@ -61,7 +67,14 @@ async function findTabs(session) {
|
|||
return { securityTabButton };
|
||||
}
|
||||
|
||||
async function checkRoomSettings(session, expectedSettings) {
|
||||
interface Settings {
|
||||
encryption: boolean;
|
||||
directory?: boolean;
|
||||
alias?: string;
|
||||
visibility?: string;
|
||||
}
|
||||
|
||||
export async function checkRoomSettings(session: ElementSession, expectedSettings: Settings): Promise<void> {
|
||||
session.log.startGroup(`checks the room settings`);
|
||||
|
||||
const { securityTabButton } = await findTabs(session);
|
||||
|
@ -77,7 +90,7 @@ async function checkRoomSettings(session, expectedSettings) {
|
|||
session.log.step(`checks for local alias of ${expectedSettings.alias}`);
|
||||
const summary = await session.query(".mx_RoomSettingsDialog .mx_AliasSettings summary");
|
||||
await summary.click();
|
||||
const localAliases = await session.query('.mx_RoomSettingsDialog .mx_AliasSettings .mx_EditableItem_item');
|
||||
const localAliases = await session.queryAll('.mx_RoomSettingsDialog .mx_AliasSettings .mx_EditableItem_item');
|
||||
const localAliasTexts = await Promise.all(localAliases.map(a => session.innerText(a)));
|
||||
if (localAliasTexts.find(a => a.includes(expectedSettings.alias))) {
|
||||
session.log.done("present");
|
||||
|
@ -86,7 +99,7 @@ async function checkRoomSettings(session, expectedSettings) {
|
|||
}
|
||||
}
|
||||
|
||||
securityTabButton.click();
|
||||
await securityTabButton.click();
|
||||
await session.delay(500);
|
||||
const securitySwitches = await session.queryAll(".mx_RoomSettingsDialog .mx_ToggleSwitch");
|
||||
const e2eEncryptionToggle = securitySwitches[0];
|
||||
|
@ -123,7 +136,7 @@ async function checkRoomSettings(session, expectedSettings) {
|
|||
session.log.endGroup();
|
||||
}
|
||||
|
||||
async function changeRoomSettings(session, settings) {
|
||||
export async function changeRoomSettings(session, settings) {
|
||||
session.log.startGroup(`changes the room settings`);
|
||||
|
||||
const { securityTabButton } = await findTabs(session);
|
||||
|
@ -180,5 +193,3 @@ async function changeRoomSettings(session, settings) {
|
|||
|
||||
session.log.endGroup();
|
||||
}
|
||||
|
||||
module.exports = { checkRoomSettings, changeRoomSettings };
|
|
@ -14,9 +14,10 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
const { acceptToast } = require("./toasts");
|
||||
import { acceptToast } from "./toasts";
|
||||
import { ElementSession } from "../session";
|
||||
|
||||
async function setupSecureBackup(session) {
|
||||
export async function setupSecureBackup(session: ElementSession): Promise<void> {
|
||||
session.log.step("sets up Secure Backup");
|
||||
|
||||
await acceptToast(session, "Set up Secure Backup");
|
|
@ -14,9 +14,10 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
const assert = require('assert');
|
||||
import { strict as assert } from 'assert';
|
||||
import { ElementSession } from "../session";
|
||||
|
||||
module.exports = async function sendMessage(session, message) {
|
||||
export async function sendMessage(session: ElementSession, message: string): Promise<void> {
|
||||
session.log.step(`writes "${message}" in room`);
|
||||
// this selector needs to be the element that has contenteditable=true,
|
||||
// not any if its parents, otherwise it behaves flaky at best.
|
||||
|
@ -31,4 +32,4 @@ module.exports = async function sendMessage(session, message) {
|
|||
// wait for the message to appear sent
|
||||
await session.query(".mx_EventTile_last:not(.mx_EventTile_sending)");
|
||||
session.log.done();
|
||||
};
|
||||
}
|
|
@ -15,9 +15,10 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
const assert = require('assert');
|
||||
import { strict as assert } from 'assert';
|
||||
import { ElementSession } from "../session";
|
||||
|
||||
async function openSettings(session, section) {
|
||||
export async function openSettings(session: ElementSession, section: string): Promise<void> {
|
||||
const menuButton = await session.query(".mx_UserMenu");
|
||||
await menuButton.click();
|
||||
const settingsItem = await session.query(".mx_UserMenu_iconSettings");
|
||||
|
@ -29,7 +30,7 @@ async function openSettings(session, section) {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports.enableLazyLoading = async function(session) {
|
||||
export async function enableLazyLoading(session: ElementSession): Promise<void> {
|
||||
session.log.step(`enables lazy loading of members in the lab settings`);
|
||||
const settingsButton = await session.query('.mx_BottomLeftMenu_settings');
|
||||
await settingsButton.click();
|
||||
|
@ -39,17 +40,22 @@ module.exports.enableLazyLoading = async function(session) {
|
|||
const closeButton = await session.query(".mx_RoomHeader_cancelButton");
|
||||
await closeButton.click();
|
||||
session.log.done();
|
||||
};
|
||||
}
|
||||
|
||||
module.exports.getE2EDeviceFromSettings = async function(session) {
|
||||
interface E2EDevice {
|
||||
id: string;
|
||||
key: string;
|
||||
}
|
||||
|
||||
export async function getE2EDeviceFromSettings(session: ElementSession): Promise<E2EDevice> {
|
||||
session.log.step(`gets e2e device/key from settings`);
|
||||
await openSettings(session, "security");
|
||||
const deviceAndKey = await session.queryAll(".mx_SettingsTab_section .mx_CryptographyPanel code");
|
||||
assert.equal(deviceAndKey.length, 2);
|
||||
const id = await (await deviceAndKey[0].getProperty("innerText")).jsonValue();
|
||||
const key = await (await deviceAndKey[1].getProperty("innerText")).jsonValue();
|
||||
const id: string = await (await deviceAndKey[0].getProperty("innerText")).jsonValue();
|
||||
const key: string = await (await deviceAndKey[1].getProperty("innerText")).jsonValue();
|
||||
const closeButton = await session.query(".mx_UserSettingsDialog .mx_Dialog_cancelButton");
|
||||
await closeButton.click();
|
||||
session.log.done();
|
||||
return { id, key };
|
||||
};
|
||||
}
|
|
@ -15,9 +15,11 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
const assert = require('assert');
|
||||
import { strict as assert } from 'assert';
|
||||
import { ElementSession } from "../session";
|
||||
|
||||
module.exports = async function signup(session, username, password, homeserver) {
|
||||
export async function signup(session: ElementSession, username: string, password: string,
|
||||
homeserver: string): Promise<void> {
|
||||
session.log.step("signs up");
|
||||
await session.goto(session.url('/#/register'));
|
||||
// change the homeserver by clicking the advanced section
|
||||
|
@ -79,4 +81,4 @@ module.exports = async function signup(session, username, password, homeserver)
|
|||
});
|
||||
assert(foundHomeUrl);
|
||||
session.log.done();
|
||||
};
|
||||
}
|
|
@ -15,9 +15,11 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
const assert = require('assert');
|
||||
import { strict as assert } from 'assert';
|
||||
import { ElementSession } from "../session";
|
||||
import { ElementHandle } from "puppeteer";
|
||||
|
||||
module.exports.scrollToTimelineTop = async function(session) {
|
||||
export async function scrollToTimelineTop(session: ElementSession): Promise<void> {
|
||||
session.log.step(`scrolls to the top of the timeline`);
|
||||
await session.page.evaluate(() => {
|
||||
return Promise.resolve().then(async () => {
|
||||
|
@ -41,14 +43,21 @@ module.exports.scrollToTimelineTop = async function(session) {
|
|||
});
|
||||
});
|
||||
session.log.done();
|
||||
};
|
||||
}
|
||||
|
||||
module.exports.receiveMessage = async function(session, expectedMessage) {
|
||||
interface Message {
|
||||
sender: string;
|
||||
encrypted?: boolean;
|
||||
body: string;
|
||||
continuation?: boolean;
|
||||
}
|
||||
|
||||
export async function receiveMessage(session: ElementSession, expectedMessage: Message): Promise<void> {
|
||||
session.log.step(`receives message "${expectedMessage.body}" from ${expectedMessage.sender}`);
|
||||
// wait for a response to come in that contains the message
|
||||
// crude, but effective
|
||||
|
||||
async function getLastMessage() {
|
||||
async function getLastMessage(): Promise<Message> {
|
||||
const lastTile = await getLastEventTile(session);
|
||||
return getMessageFromEventTile(lastTile);
|
||||
}
|
||||
|
@ -67,25 +76,26 @@ module.exports.receiveMessage = async function(session, expectedMessage) {
|
|||
});
|
||||
assertMessage(lastMessage, expectedMessage);
|
||||
session.log.done();
|
||||
};
|
||||
}
|
||||
|
||||
module.exports.checkTimelineContains = async function(session, expectedMessages, sendersDescription) {
|
||||
export async function checkTimelineContains(session: ElementSession, expectedMessages: Message[],
|
||||
sendersDescription: string): Promise<void> {
|
||||
session.log.step(`checks timeline contains ${expectedMessages.length} ` +
|
||||
`given messages${sendersDescription ? ` from ${sendersDescription}`:""}`);
|
||||
const eventTiles = await getAllEventTiles(session);
|
||||
let timelineMessages = await Promise.all(eventTiles.map((eventTile) => {
|
||||
let timelineMessages: Message[] = await Promise.all(eventTiles.map((eventTile) => {
|
||||
return getMessageFromEventTile(eventTile);
|
||||
}));
|
||||
//filter out tiles that were not messages
|
||||
timelineMessages = timelineMessages.filter((m) => !!m);
|
||||
timelineMessages.reduce((prevSender, m) => {
|
||||
timelineMessages.reduce((prevSender: string, m) => {
|
||||
if (m.continuation) {
|
||||
m.sender = prevSender;
|
||||
return prevSender;
|
||||
} else {
|
||||
return m.sender;
|
||||
}
|
||||
});
|
||||
}, "");
|
||||
|
||||
expectedMessages.forEach((expectedMessage) => {
|
||||
const foundMessage = timelineMessages.find((message) => {
|
||||
|
@ -101,9 +111,9 @@ module.exports.checkTimelineContains = async function(session, expectedMessages,
|
|||
});
|
||||
|
||||
session.log.done();
|
||||
};
|
||||
}
|
||||
|
||||
function assertMessage(foundMessage, expectedMessage) {
|
||||
function assertMessage(foundMessage: Message, expectedMessage: Message): void {
|
||||
assert(foundMessage, `message ${JSON.stringify(expectedMessage)} not found in timeline`);
|
||||
assert.equal(foundMessage.body, expectedMessage.body);
|
||||
assert.equal(foundMessage.sender, expectedMessage.sender);
|
||||
|
@ -112,17 +122,17 @@ function assertMessage(foundMessage, expectedMessage) {
|
|||
}
|
||||
}
|
||||
|
||||
function getLastEventTile(session) {
|
||||
function getLastEventTile(session: ElementSession): Promise<ElementHandle> {
|
||||
return session.query(".mx_EventTile_last");
|
||||
}
|
||||
|
||||
function getAllEventTiles(session) {
|
||||
function getAllEventTiles(session: ElementSession): Promise<ElementHandle[]> {
|
||||
return session.queryAll(".mx_RoomView_MessageList .mx_EventTile");
|
||||
}
|
||||
|
||||
async function getMessageFromEventTile(eventTile) {
|
||||
async function getMessageFromEventTile(eventTile: ElementHandle): Promise<Message> {
|
||||
const senderElement = await eventTile.$(".mx_SenderProfile_displayName");
|
||||
const className = await (await eventTile.getProperty("className")).jsonValue();
|
||||
const className: string = await (await eventTile.getProperty("className")).jsonValue();
|
||||
const classNames = className.split(" ");
|
||||
const bodyElement = await eventTile.$(".mx_EventTile_body");
|
||||
let sender = null;
|
||||
|
@ -132,7 +142,7 @@ async function getMessageFromEventTile(eventTile) {
|
|||
if (!bodyElement) {
|
||||
return null;
|
||||
}
|
||||
const body = await(await bodyElement.getProperty("innerText")).jsonValue();
|
||||
const body: string = await(await bodyElement.getProperty("innerText")).jsonValue();
|
||||
|
||||
return {
|
||||
sender,
|
|
@ -14,9 +14,10 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
const assert = require('assert');
|
||||
import { strict as assert } from 'assert';
|
||||
import { ElementSession } from "../session";
|
||||
|
||||
async function assertNoToasts(session) {
|
||||
export async function assertNoToasts(session: ElementSession): Promise<void> {
|
||||
try {
|
||||
await session.query('.mx_Toast_toast', 1000, true);
|
||||
} catch (e) {
|
||||
|
@ -26,22 +27,20 @@ async function assertNoToasts(session) {
|
|||
}
|
||||
}
|
||||
|
||||
async function assertToast(session, expectedTitle) {
|
||||
export async function assertToast(session: ElementSession, expectedTitle: string): Promise<void> {
|
||||
const h2Element = await session.query('.mx_Toast_title h2');
|
||||
const toastTitle = await session.innerText(h2Element);
|
||||
assert.equal(toastTitle, expectedTitle);
|
||||
}
|
||||
|
||||
async function acceptToast(session, expectedTitle) {
|
||||
export async function acceptToast(session: ElementSession, expectedTitle: string): Promise<void> {
|
||||
await assertToast(session, expectedTitle);
|
||||
const btn = await session.query('.mx_Toast_buttons .mx_AccessibleButton_kind_primary');
|
||||
await btn.click();
|
||||
}
|
||||
|
||||
async function rejectToast(session, expectedTitle) {
|
||||
export async function rejectToast(session: ElementSession, expectedTitle: string): Promise<void> {
|
||||
await assertToast(session, expectedTitle);
|
||||
const btn = await session.query('.mx_Toast_buttons .mx_AccessibleButton_kind_danger_outline');
|
||||
await btn.click();
|
||||
}
|
||||
|
||||
module.exports = { assertNoToasts, assertToast, acceptToast, rejectToast };
|
|
@ -15,11 +15,11 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
const assert = require('assert');
|
||||
import { strict as assert } from 'assert';
|
||||
import { openMemberInfo } from "./memberlist";
|
||||
import { ElementSession } from "../session";
|
||||
|
||||
const { openMemberInfo } = require("./memberlist");
|
||||
|
||||
async function startVerification(session, name) {
|
||||
export async function startVerification(session: ElementSession, name: string): Promise<void> {
|
||||
session.log.step("opens their opponent's profile and starts verification");
|
||||
await openMemberInfo(session, name);
|
||||
// click verify in member info
|
||||
|
@ -30,22 +30,22 @@ async function startVerification(session, name) {
|
|||
await session.delay(1000);
|
||||
|
||||
// click 'start verification'
|
||||
const startVerifyButton = await session.query('.mx_UserInfo_container .mx_AccessibleButton_kind_primary');
|
||||
const startVerifyButton = await session.query('.mx_UserInfo_container .mx_UserInfo_startVerification');
|
||||
await startVerifyButton.click();
|
||||
session.log.done();
|
||||
}
|
||||
|
||||
async function getSasCodes(session) {
|
||||
async function getSasCodes(session: ElementSession): Promise<string[]> {
|
||||
const sasLabelElements = await session.queryAll(
|
||||
".mx_VerificationShowSas .mx_VerificationShowSas_emojiSas .mx_VerificationShowSas_emojiSas_label");
|
||||
const sasLabels = await Promise.all(sasLabelElements.map(e => session.innerText(e)));
|
||||
return sasLabels;
|
||||
}
|
||||
|
||||
async function doSasVerification(session) {
|
||||
async function doSasVerification(session: ElementSession): Promise<string[]> {
|
||||
session.log.step("hunts for the emoji to yell at their opponent");
|
||||
const sasCodes = await getSasCodes(session);
|
||||
session.log.done(sasCodes);
|
||||
session.log.done(sasCodes.join("\n"));
|
||||
|
||||
// Assume they match
|
||||
session.log.step("assumes the emoji match");
|
||||
|
@ -75,7 +75,7 @@ async function doSasVerification(session) {
|
|||
return sasCodes;
|
||||
}
|
||||
|
||||
module.exports.startSasVerification = async function(session, name) {
|
||||
export async function startSasVerification(session: ElementSession, name: string): Promise<string[]> {
|
||||
session.log.startGroup("starts verification");
|
||||
await startVerification(session, name);
|
||||
|
||||
|
@ -85,9 +85,9 @@ module.exports.startSasVerification = async function(session, name) {
|
|||
const sasCodes = await doSasVerification(session);
|
||||
session.log.endGroup();
|
||||
return sasCodes;
|
||||
};
|
||||
}
|
||||
|
||||
module.exports.acceptSasVerification = async function(session, name) {
|
||||
export async function acceptSasVerification(session: ElementSession, name: string): Promise<string[]> {
|
||||
session.log.startGroup("accepts verification");
|
||||
const requestToast = await session.query('.mx_Toast_icon_verification');
|
||||
|
||||
|
@ -111,4 +111,4 @@ module.exports.acceptSasVerification = async function(session, name) {
|
|||
const sasCodes = await doSasVerification(session);
|
||||
session.log.endGroup();
|
||||
return sasCodes;
|
||||
};
|
||||
}
|
|
@ -15,7 +15,9 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
module.exports.range = function(start, amount, step = 1) {
|
||||
import { ElementSession } from "./session";
|
||||
|
||||
export const range = function(start: number, amount: number, step = 1): Array<number> {
|
||||
const r = [];
|
||||
for (let i = 0; i < amount; ++i) {
|
||||
r.push(start + (i * step));
|
||||
|
@ -23,17 +25,17 @@ module.exports.range = function(start, amount, step = 1) {
|
|||
return r;
|
||||
};
|
||||
|
||||
module.exports.delay = function(ms) {
|
||||
export const delay = function(ms): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
};
|
||||
|
||||
module.exports.measureStart = function(session, name) {
|
||||
export const measureStart = function(session: ElementSession, name: string): Promise<void> {
|
||||
return session.page.evaluate(_name => {
|
||||
window.mxPerformanceMonitor.start(_name);
|
||||
}, name);
|
||||
};
|
||||
|
||||
module.exports.measureStop = function(session, name) {
|
||||
export const measureStop = function(session: ElementSession, name: string): Promise<void> {
|
||||
return session.page.evaluate(_name => {
|
||||
window.mxPerformanceMonitor.stop(_name);
|
||||
}, name);
|
|
@ -14,13 +14,12 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
const fs = require("fs");
|
||||
const program = require('commander');
|
||||
|
||||
const ElementSession = require('./src/session');
|
||||
const scenario = require('./src/scenario');
|
||||
const RestSessionCreator = require('./src/rest/creator');
|
||||
import { ElementSession } from './src/session';
|
||||
import { scenario } from './src/scenario';
|
||||
import { RestSessionCreator } from './src/rest/creator';
|
||||
import * as fs from "fs";
|
||||
|
||||
import program = require('commander');
|
||||
program
|
||||
.option('--no-logs', "don't output logs, document html on error", false)
|
||||
.option('--app-url [url]', "url to test", "http://localhost:5000")
|
||||
|
@ -48,11 +47,11 @@ async function runTests() {
|
|||
if (process.env.CHROME_PATH) {
|
||||
const path = process.env.CHROME_PATH;
|
||||
console.log(`(using external chrome/chromium at ${path}, make sure it's compatible with puppeteer)`);
|
||||
options.executablePath = path;
|
||||
options['executablePath'] = path;
|
||||
}
|
||||
|
||||
const restCreator = new RestSessionCreator(
|
||||
'synapse/installations/consent/env/bin',
|
||||
'../synapse/installations/consent/env/bin',
|
||||
hsUrl,
|
||||
__dirname,
|
||||
);
|
||||
|
@ -85,7 +84,7 @@ async function runTests() {
|
|||
await Promise.all(sessions.map(async (session) => {
|
||||
// Collecting all performance monitoring data before closing the session
|
||||
const measurements = await session.page.evaluate(() => {
|
||||
let measurements = [];
|
||||
let measurements;
|
||||
window.mxPerformanceMonitor.addPerformanceDataCallback({
|
||||
entryNames: [
|
||||
window.mxPerformanceEntryNames.REGISTER,
|
||||
|
@ -107,7 +106,9 @@ async function runTests() {
|
|||
performanceEntries = JSON.parse(measurements);
|
||||
return session.close();
|
||||
}));
|
||||
fs.writeFileSync(`performance-entries.json`, JSON.stringify(performanceEntries));
|
||||
if (performanceEntries) {
|
||||
fs.writeFileSync(`performance-entries.json`, JSON.stringify(performanceEntries));
|
||||
}
|
||||
if (failure) {
|
||||
process.exit(-1);
|
||||
} else {
|
23
test/end-to-end-tests/tsconfig.json
Normal file
23
test/end-to-end-tests/tsconfig.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"resolveJsonModule": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"target": "es2016",
|
||||
"noImplicitAny": false,
|
||||
"sourceMap": false,
|
||||
"outDir": "./lib",
|
||||
"declaration": true,
|
||||
"lib": [
|
||||
"es2019",
|
||||
"dom",
|
||||
"dom.iterable"
|
||||
],
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*.ts",
|
||||
"start.ts"
|
||||
]
|
||||
}
|
|
@ -7,6 +7,13 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/node/-/node-11.12.1.tgz#d90123f6c61fdf2f7cddd286ddae891586dd3488"
|
||||
integrity sha512-sKDlqv6COJrR7ar0+GqqhrXQDzQlMcqMnF2iEU6m9hLo8kxozoAGUazwPyELHlRVmjsbvlnGXjnzyptSXVmceA==
|
||||
|
||||
"@types/puppeteer@^5.4.4":
|
||||
version "5.4.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-5.4.4.tgz#e92abeccc4f46207c3e1b38934a1246be080ccd0"
|
||||
integrity sha512-3Nau+qi69CN55VwZb0ATtdUAlYlqOOQ3OfQfq0Hqgc4JMFXiQT/XInlwQ9g6LbicDslE6loIFsXFklGh5XmI6Q==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/yauzl@^2.9.1":
|
||||
version "2.9.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.1.tgz#d10f69f9f522eef3cf98e30afb684a1e1ec923af"
|
||||
|
|
3
test/globalSetup.js
Normal file
3
test/globalSetup.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
module.exports = async () => {
|
||||
process.env.TZ = 'UTC';
|
||||
};
|
230
test/linkify-matrix-test.ts
Normal file
230
test/linkify-matrix-test.ts
Normal file
|
@ -0,0 +1,230 @@
|
|||
/*
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
import { linkify } from '../src/linkify-matrix';
|
||||
|
||||
describe('linkify-matrix', () => {
|
||||
describe('roomalias', () => {
|
||||
it('properly parses #_foonetic_xkcd:matrix.org', () => {
|
||||
const test = '#_foonetic_xkcd:matrix.org';
|
||||
const found = linkify.find(test);
|
||||
expect(found).toEqual(([{
|
||||
href: "#_foonetic_xkcd:matrix.org",
|
||||
type: "roomalias",
|
||||
value: "#_foonetic_xkcd:matrix.org",
|
||||
}]));
|
||||
});
|
||||
it('properly parses #foo:localhost', () => {
|
||||
const test = "#foo:localhost";
|
||||
const found = linkify.find(test);
|
||||
expect(found).toEqual(([{
|
||||
href: "#foo:localhost",
|
||||
type: "roomalias",
|
||||
value: "#foo:localhost",
|
||||
}]));
|
||||
});
|
||||
it('accept #foo:bar.com', () => {
|
||||
const test = '#foo:bar.com';
|
||||
const found = linkify.find(test);
|
||||
expect(found).toEqual(([{
|
||||
href: "#foo:bar.com",
|
||||
type: "roomalias",
|
||||
value: "#foo:bar.com",
|
||||
}]));
|
||||
});
|
||||
it('accept #foo:com (mostly for (TLD|DOMAIN)+ mixing)', () => {
|
||||
const test = '#foo:com';
|
||||
const found = linkify.find(test);
|
||||
expect(found).toEqual(([{
|
||||
href: "#foo:com",
|
||||
type: "roomalias",
|
||||
value: "#foo:com",
|
||||
}]));
|
||||
});
|
||||
it('accept repeated TLDs (e.g .org.uk)', () => {
|
||||
const test = '#foo:bar.org.uk';
|
||||
const found = linkify.find(test);
|
||||
expect(found).toEqual(([{
|
||||
href: "#foo:bar.org.uk",
|
||||
type: "roomalias",
|
||||
value: "#foo:bar.org.uk",
|
||||
}]));
|
||||
});
|
||||
it('ignores trailing `:`', () => {
|
||||
const test = '#foo:bar.com:';
|
||||
const found = linkify.find(test);
|
||||
expect(found).toEqual(([{
|
||||
href: "#foo:bar.com",
|
||||
type: "roomalias",
|
||||
value: "#foo:bar.com",
|
||||
}]));
|
||||
});
|
||||
it('accept :NUM (port specifier)', () => {
|
||||
const test = '#foo:bar.com:2225';
|
||||
const found = linkify.find(test);
|
||||
expect(found).toEqual(([{
|
||||
href: "#foo:bar.com:2225",
|
||||
type: "roomalias",
|
||||
value: "#foo:bar.com:2225",
|
||||
}]));
|
||||
});
|
||||
it('ignores all the trailing :', () => {
|
||||
const test = '#foo:bar.com::::';
|
||||
const found = linkify.find(test);
|
||||
expect(found).toEqual(([{
|
||||
href: "#foo:bar.com",
|
||||
type: "roomalias",
|
||||
value: "#foo:bar.com",
|
||||
}]));
|
||||
});
|
||||
it('properly parses room alias with dots in name', () => {
|
||||
const test = '#foo.asdf:bar.com::::';
|
||||
const found = linkify.find(test);
|
||||
expect(found).toEqual(([{
|
||||
href: "#foo.asdf:bar.com",
|
||||
type: "roomalias",
|
||||
value: "#foo.asdf:bar.com",
|
||||
}]));
|
||||
});
|
||||
it('does not parse room alias with too many separators', () => {
|
||||
const test = '#foo:::bar.com';
|
||||
const found = linkify.find(test);
|
||||
expect(found).toEqual(([{
|
||||
href: "http://bar.com",
|
||||
type: "url",
|
||||
value: "bar.com",
|
||||
}]));
|
||||
});
|
||||
it('does not parse multiple room aliases in one string', () => {
|
||||
const test = '#foo:bar.com-baz.com';
|
||||
const found = linkify.find(test);
|
||||
expect(found).toEqual(([{
|
||||
"href": "#foo:bar.com-baz.com",
|
||||
"type": "roomalias",
|
||||
"value": "#foo:bar.com-baz.com",
|
||||
}]));
|
||||
});
|
||||
});
|
||||
|
||||
describe('groupid', () => {
|
||||
it('properly parses +foo:localhost', () => {
|
||||
const test = "+foo:localhost";
|
||||
const found = linkify.find(test);
|
||||
expect(found).toEqual(([{
|
||||
href: "+foo:localhost",
|
||||
type: "groupid",
|
||||
value: "+foo:localhost",
|
||||
}]));
|
||||
});
|
||||
it('accept +foo:bar.com', () => {
|
||||
const test = '+foo:bar.com';
|
||||
const found = linkify.find(test);
|
||||
expect(found).toEqual(([{
|
||||
href: "+foo:bar.com",
|
||||
type: "groupid",
|
||||
value: "+foo:bar.com",
|
||||
}]));
|
||||
});
|
||||
it('accept +foo:com (mostly for (TLD|DOMAIN)+ mixing)', () => {
|
||||
const test = '+foo:com';
|
||||
const found = linkify.find(test);
|
||||
expect(found).toEqual(([{
|
||||
href: "+foo:com",
|
||||
type: "groupid",
|
||||
value: "+foo:com",
|
||||
}]));
|
||||
});
|
||||
it('accept repeated TLDs (e.g .org.uk)', () => {
|
||||
const test = '+foo:bar.org.uk';
|
||||
const found = linkify.find(test);
|
||||
expect(found).toEqual(([{
|
||||
href: "+foo:bar.org.uk",
|
||||
type: "groupid",
|
||||
value: "+foo:bar.org.uk",
|
||||
}]));
|
||||
});
|
||||
it('ignore trailing `:`', () => {
|
||||
const test = '+foo:bar.com:';
|
||||
const found = linkify.find(test);
|
||||
expect(found).toEqual(([{
|
||||
"href": "+foo:bar.com",
|
||||
"type": "groupid",
|
||||
"value": "+foo:bar.com",
|
||||
}]));
|
||||
});
|
||||
it('accept :NUM (port specifier)', () => {
|
||||
const test = '+foo:bar.com:2225';
|
||||
const found = linkify.find(test);
|
||||
expect(found).toEqual(([{
|
||||
href: "+foo:bar.com:2225",
|
||||
type: "groupid",
|
||||
value: "+foo:bar.com:2225",
|
||||
}]));
|
||||
});
|
||||
});
|
||||
|
||||
describe('userid', () => {
|
||||
it('should not parse @foo without domain', () => {
|
||||
const test = "@foo";
|
||||
const found = linkify.find(test);
|
||||
expect(found).toEqual(([]));
|
||||
});
|
||||
it('accept @foo:bar.com', () => {
|
||||
const test = '@foo:bar.com';
|
||||
const found = linkify.find(test);
|
||||
expect(found).toEqual(([{
|
||||
href: "@foo:bar.com",
|
||||
type: "userid",
|
||||
value: "@foo:bar.com",
|
||||
}]));
|
||||
});
|
||||
it('accept @foo:com (mostly for (TLD|DOMAIN)+ mixing)', () => {
|
||||
const test = '@foo:com';
|
||||
const found = linkify.find(test);
|
||||
expect(found).toEqual(([{
|
||||
href: "@foo:com",
|
||||
type: "userid",
|
||||
value: "@foo:com",
|
||||
}]));
|
||||
});
|
||||
it('accept repeated TLDs (e.g .org.uk)', () => {
|
||||
const test = '@foo:bar.org.uk';
|
||||
const found = linkify.find(test);
|
||||
expect(found).toEqual(([{
|
||||
href: "@foo:bar.org.uk",
|
||||
type: "userid",
|
||||
value: "@foo:bar.org.uk",
|
||||
}]));
|
||||
});
|
||||
it('do not accept trailing `:`', () => {
|
||||
const test = '@foo:bar.com:';
|
||||
const found = linkify.find(test);
|
||||
expect(found).toEqual(([{
|
||||
href: "@foo:bar.com",
|
||||
type: "userid",
|
||||
value: "@foo:bar.com",
|
||||
}]));
|
||||
});
|
||||
it('accept :NUM (port specifier)', () => {
|
||||
const test = '@foo:bar.com:2225';
|
||||
const found = linkify.find(test);
|
||||
expect(found).toEqual(([{
|
||||
href: "@foo:bar.com:2225",
|
||||
type: "userid",
|
||||
value: "@foo:bar.com:2225",
|
||||
}]));
|
||||
});
|
||||
});
|
||||
});
|
198
test/settings/watchers/ThemeWatcher-test.tsx
Normal file
198
test/settings/watchers/ThemeWatcher-test.tsx
Normal file
|
@ -0,0 +1,198 @@
|
|||
/*
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import SettingsStore from '../../../src/settings/SettingsStore';
|
||||
import ThemeWatcher from '../../../src/settings/watchers/ThemeWatcher';
|
||||
import { SettingLevel } from '../../../src/settings/SettingLevel';
|
||||
|
||||
function makeMatchMedia(values: any) {
|
||||
class FakeMediaQueryList {
|
||||
matches: false;
|
||||
media: null;
|
||||
onchange: null;
|
||||
addListener() {}
|
||||
removeListener() {}
|
||||
addEventListener() {}
|
||||
removeEventListener() {}
|
||||
dispatchEvent() { return true; }
|
||||
|
||||
constructor(query: string) {
|
||||
this.matches = values[query];
|
||||
}
|
||||
}
|
||||
|
||||
return function matchMedia(query: string) {
|
||||
return new FakeMediaQueryList(query);
|
||||
};
|
||||
}
|
||||
|
||||
function makeGetValue(values: any) {
|
||||
return function getValue<T = any>(
|
||||
settingName: string,
|
||||
_roomId: string = null,
|
||||
_excludeDefault = false,
|
||||
): T {
|
||||
return values[settingName];
|
||||
};
|
||||
}
|
||||
|
||||
function makeGetValueAt(values: any) {
|
||||
return function getValueAt(
|
||||
_level: SettingLevel,
|
||||
settingName: string,
|
||||
_roomId: string = null,
|
||||
_explicit = false,
|
||||
_excludeDefault = false,
|
||||
): any {
|
||||
return values[settingName];
|
||||
};
|
||||
}
|
||||
|
||||
describe('ThemeWatcher', function() {
|
||||
it('should choose a light theme by default', () => {
|
||||
// Given no system settings
|
||||
global.matchMedia = makeMatchMedia({});
|
||||
|
||||
// Then getEffectiveTheme returns light
|
||||
const themeWatcher = new ThemeWatcher();
|
||||
expect(themeWatcher.getEffectiveTheme()).toBe("light");
|
||||
});
|
||||
|
||||
it('should choose default theme if system settings are inconclusive', () => {
|
||||
// Given no system settings but we asked to use them
|
||||
global.matchMedia = makeMatchMedia({});
|
||||
SettingsStore.getValue = makeGetValue({
|
||||
"use_system_theme": true,
|
||||
"theme": "light",
|
||||
});
|
||||
|
||||
// Then getEffectiveTheme returns light
|
||||
const themeWatcher = new ThemeWatcher();
|
||||
expect(themeWatcher.getEffectiveTheme()).toBe("light");
|
||||
});
|
||||
|
||||
it('should choose a dark theme if that is selected', () => {
|
||||
// Given system says light high contrast but theme is set to dark
|
||||
global.matchMedia = makeMatchMedia({
|
||||
"(prefers-contrast: more)": true,
|
||||
"(prefers-color-scheme: light)": true,
|
||||
});
|
||||
SettingsStore.getValueAt = makeGetValueAt({ "theme": "dark" });
|
||||
|
||||
// Then getEffectiveTheme returns dark
|
||||
const themeWatcher = new ThemeWatcher();
|
||||
expect(themeWatcher.getEffectiveTheme()).toBe("dark");
|
||||
});
|
||||
|
||||
it('should choose a light theme if that is selected', () => {
|
||||
// Given system settings say dark high contrast but theme set to light
|
||||
global.matchMedia = makeMatchMedia({
|
||||
"(prefers-contrast: more)": true,
|
||||
"(prefers-color-scheme: dark)": true,
|
||||
});
|
||||
SettingsStore.getValueAt = makeGetValueAt({ "theme": "light" });
|
||||
|
||||
// Then getEffectiveTheme returns light
|
||||
const themeWatcher = new ThemeWatcher();
|
||||
expect(themeWatcher.getEffectiveTheme()).toBe("light");
|
||||
});
|
||||
|
||||
it('should choose a light-high-contrast theme if that is selected', () => {
|
||||
// Given system settings say dark and theme set to light-high-contrast
|
||||
global.matchMedia = makeMatchMedia({ "(prefers-color-scheme: dark)": true });
|
||||
SettingsStore.getValueAt = makeGetValueAt({ "theme": "light-high-contrast" });
|
||||
|
||||
// Then getEffectiveTheme returns light-high-contrast
|
||||
const themeWatcher = new ThemeWatcher();
|
||||
expect(themeWatcher.getEffectiveTheme()).toBe("light-high-contrast");
|
||||
});
|
||||
|
||||
it('should choose a light theme if system prefers it (via default)', () => {
|
||||
// Given system prefers lightness, even though we did not
|
||||
// click "Use system theme" or choose a theme explicitly
|
||||
global.matchMedia = makeMatchMedia({ "(prefers-color-scheme: light)": true });
|
||||
SettingsStore.getValueAt = makeGetValueAt({});
|
||||
SettingsStore.getValue = makeGetValue({ "use_system_theme": true });
|
||||
|
||||
// Then getEffectiveTheme returns light
|
||||
const themeWatcher = new ThemeWatcher();
|
||||
expect(themeWatcher.getEffectiveTheme()).toBe("light");
|
||||
});
|
||||
|
||||
it('should choose a dark theme if system prefers it (via default)', () => {
|
||||
// Given system prefers darkness, even though we did not
|
||||
// click "Use system theme" or choose a theme explicitly
|
||||
global.matchMedia = makeMatchMedia({ "(prefers-color-scheme: dark)": true });
|
||||
SettingsStore.getValueAt = makeGetValueAt({});
|
||||
SettingsStore.getValue = makeGetValue({ "use_system_theme": true });
|
||||
|
||||
// Then getEffectiveTheme returns dark
|
||||
const themeWatcher = new ThemeWatcher();
|
||||
expect(themeWatcher.getEffectiveTheme()).toBe("dark");
|
||||
});
|
||||
|
||||
it('should choose a light theme if system prefers it (explicit)', () => {
|
||||
// Given system prefers lightness
|
||||
global.matchMedia = makeMatchMedia({ "(prefers-color-scheme: light)": true });
|
||||
SettingsStore.getValueAt = makeGetValueAt({ "use_system_theme": true });
|
||||
SettingsStore.getValue = makeGetValue({ "use_system_theme": true });
|
||||
|
||||
// Then getEffectiveTheme returns light
|
||||
const themeWatcher = new ThemeWatcher();
|
||||
expect(themeWatcher.getEffectiveTheme()).toBe("light");
|
||||
});
|
||||
|
||||
it('should choose a dark theme if system prefers it (explicit)', () => {
|
||||
// Given system prefers darkness
|
||||
global.matchMedia = makeMatchMedia({ "(prefers-color-scheme: dark)": true });
|
||||
SettingsStore.getValueAt = makeGetValueAt({ "use_system_theme": true });
|
||||
SettingsStore.getValue = makeGetValue({ "use_system_theme": true });
|
||||
|
||||
// Then getEffectiveTheme returns dark
|
||||
const themeWatcher = new ThemeWatcher();
|
||||
expect(themeWatcher.getEffectiveTheme()).toBe("dark");
|
||||
});
|
||||
|
||||
it('should choose a high-contrast theme if system prefers it', () => {
|
||||
// Given system prefers high contrast and light
|
||||
global.matchMedia = makeMatchMedia({
|
||||
"(prefers-contrast: more)": true,
|
||||
"(prefers-color-scheme: light)": true,
|
||||
});
|
||||
SettingsStore.getValueAt = makeGetValueAt({ "use_system_theme": true });
|
||||
SettingsStore.getValue = makeGetValue({ "use_system_theme": true });
|
||||
|
||||
// Then getEffectiveTheme returns light-high-contrast
|
||||
const themeWatcher = new ThemeWatcher();
|
||||
expect(themeWatcher.getEffectiveTheme()).toBe("light-high-contrast");
|
||||
});
|
||||
|
||||
it('should not choose a high-contrast theme if not available', () => {
|
||||
// Given system prefers high contrast and dark, but we don't (yet)
|
||||
// have a high-contrast dark theme
|
||||
global.matchMedia = makeMatchMedia({
|
||||
"(prefers-contrast: more)": true,
|
||||
"(prefers-color-scheme: dark)": true,
|
||||
});
|
||||
SettingsStore.getValueAt = makeGetValueAt({ "use_system_theme": true });
|
||||
SettingsStore.getValue = makeGetValue({ "use_system_theme": true });
|
||||
|
||||
// Then getEffectiveTheme returns dark
|
||||
const themeWatcher = new ThemeWatcher();
|
||||
expect(themeWatcher.getEffectiveTheme()).toBe("dark");
|
||||
});
|
||||
});
|
||||
|
|
@ -16,3 +16,5 @@ global.TextDecoder = TextDecoder;
|
|||
|
||||
configure({ adapter: new Adapter() });
|
||||
|
||||
// maplibre requires a createObjectURL mock
|
||||
global.URL.createObjectURL = jest.fn();
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import RoomViewStore from '../../src/stores/RoomViewStore';
|
||||
import { Action } from '../../src/dispatcher/actions';
|
||||
|
||||
import { MatrixClientPeg as peg } from '../../src/MatrixClientPeg';
|
||||
|
||||
import * as testUtils from '../test-utils';
|
||||
|
||||
const dispatch = testUtils.getDispatchForStore(RoomViewStore);
|
||||
|
@ -19,7 +22,7 @@ describe('RoomViewStore', function() {
|
|||
done();
|
||||
};
|
||||
|
||||
dispatch({ action: 'view_room', room_id: '!randomcharacters:aser.ver' });
|
||||
dispatch({ action: Action.ViewRoom, room_id: '!randomcharacters:aser.ver' });
|
||||
dispatch({ action: 'join_room' });
|
||||
expect(RoomViewStore.isJoining()).toBe(true);
|
||||
});
|
||||
|
@ -41,6 +44,6 @@ describe('RoomViewStore', function() {
|
|||
done();
|
||||
};
|
||||
|
||||
dispatch({ action: 'view_room', room_alias: '#somealias2:aser.ver' });
|
||||
dispatch({ action: Action.ViewRoom, room_alias: '#somealias2:aser.ver' });
|
||||
});
|
||||
});
|
||||
|
|
|
@ -18,13 +18,16 @@ import { EventEmitter } from "events";
|
|||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||
|
||||
import "./enable-metaspaces-labs";
|
||||
import "../skinned-sdk"; // Must be first for skinning to work
|
||||
import SpaceStore, {
|
||||
import SpaceStore from "../../src/stores/spaces/SpaceStore";
|
||||
import {
|
||||
MetaSpace,
|
||||
UPDATE_HOME_BEHAVIOUR,
|
||||
UPDATE_INVITED_SPACES,
|
||||
UPDATE_SELECTED_SPACE,
|
||||
UPDATE_TOP_LEVEL_SPACES,
|
||||
} from "../../src/stores/SpaceStore";
|
||||
} from "../../src/stores/spaces";
|
||||
import * as testUtils from "../utils/test-utils";
|
||||
import { mkEvent, stubClient } from "../test-utils";
|
||||
import DMRoomMap from "../../src/utils/DMRoomMap";
|
||||
|
@ -90,10 +93,18 @@ describe("SpaceStore", () => {
|
|||
await emitProm;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
jest.runAllTimers(); // run async dispatch
|
||||
client.getVisibleRooms.mockReturnValue(rooms = []);
|
||||
|
||||
await SettingsStore.setValue("Spaces.enabledMetaSpaces", null, SettingLevel.DEVICE, {
|
||||
[MetaSpace.Home]: true,
|
||||
[MetaSpace.Favourites]: true,
|
||||
[MetaSpace.People]: true,
|
||||
[MetaSpace.Orphans]: true,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await testUtils.resetAsyncStoreWithClient(store);
|
||||
});
|
||||
|
@ -377,69 +388,84 @@ describe("SpaceStore", () => {
|
|||
});
|
||||
|
||||
it("home space contains orphaned rooms", () => {
|
||||
expect(store.getSpaceFilteredRoomIds(null).has(orphan1)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(null).has(orphan2)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(orphan1)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(orphan2)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("home space contains favourites", () => {
|
||||
expect(store.getSpaceFilteredRoomIds(null).has(fav1)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(null).has(fav2)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(null).has(fav3)).toBeTruthy();
|
||||
it("home space does not contain all favourites", () => {
|
||||
expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(fav1)).toBeFalsy();
|
||||
expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(fav2)).toBeFalsy();
|
||||
expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(fav3)).toBeFalsy();
|
||||
});
|
||||
|
||||
it("home space contains dm rooms", () => {
|
||||
expect(store.getSpaceFilteredRoomIds(null).has(dm1)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(null).has(dm2)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(null).has(dm3)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(dm1)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(dm2)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(dm3)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("home space contains invites", () => {
|
||||
expect(store.getSpaceFilteredRoomIds(null).has(invite1)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(invite1)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("home space contains invites even if they are also shown in a space", () => {
|
||||
expect(store.getSpaceFilteredRoomIds(null).has(invite2)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(invite2)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("all rooms space does contain rooms/low priority even if they are also shown in a space", async () => {
|
||||
await setShowAllRooms(true);
|
||||
expect(store.getSpaceFilteredRoomIds(null).has(room1)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(room1)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("favourites space does contain favourites even if they are also shown in a space", async () => {
|
||||
expect(store.getSpaceFilteredRoomIds(MetaSpace.Favourites).has(fav1)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(MetaSpace.Favourites).has(fav2)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(MetaSpace.Favourites).has(fav3)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("people space does contain people even if they are also shown in a space", async () => {
|
||||
expect(store.getSpaceFilteredRoomIds(MetaSpace.People).has(dm1)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(MetaSpace.People).has(dm2)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(MetaSpace.People).has(dm3)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("orphans space does contain orphans even if they are also shown in all rooms", async () => {
|
||||
await setShowAllRooms(true);
|
||||
expect(store.getSpaceFilteredRoomIds(MetaSpace.Orphans).has(orphan1)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(MetaSpace.Orphans).has(orphan2)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("home space doesn't contain rooms/low priority if they are also shown in a space", async () => {
|
||||
await setShowAllRooms(false);
|
||||
expect(store.getSpaceFilteredRoomIds(null).has(room1)).toBeFalsy();
|
||||
expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(room1)).toBeFalsy();
|
||||
});
|
||||
|
||||
it("space contains child rooms", () => {
|
||||
const space = client.getRoom(space1);
|
||||
expect(store.getSpaceFilteredRoomIds(space).has(fav1)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(space).has(room1)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(space1).has(fav1)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(space1).has(room1)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("space contains child favourites", () => {
|
||||
const space = client.getRoom(space2);
|
||||
expect(store.getSpaceFilteredRoomIds(space).has(fav1)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(space).has(fav2)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(space).has(fav3)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(space).has(room1)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(space2).has(fav1)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(space2).has(fav2)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(space2).has(fav3)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(space2).has(room1)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("space contains child invites", () => {
|
||||
const space = client.getRoom(space3);
|
||||
expect(store.getSpaceFilteredRoomIds(space).has(invite2)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(space3).has(invite2)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("spaces contain dms which you have with members of that space", () => {
|
||||
expect(store.getSpaceFilteredRoomIds(client.getRoom(space1)).has(dm1)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(client.getRoom(space2)).has(dm1)).toBeFalsy();
|
||||
expect(store.getSpaceFilteredRoomIds(client.getRoom(space3)).has(dm1)).toBeFalsy();
|
||||
expect(store.getSpaceFilteredRoomIds(client.getRoom(space1)).has(dm2)).toBeFalsy();
|
||||
expect(store.getSpaceFilteredRoomIds(client.getRoom(space2)).has(dm2)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(client.getRoom(space3)).has(dm2)).toBeFalsy();
|
||||
expect(store.getSpaceFilteredRoomIds(client.getRoom(space1)).has(dm3)).toBeFalsy();
|
||||
expect(store.getSpaceFilteredRoomIds(client.getRoom(space2)).has(dm3)).toBeFalsy();
|
||||
expect(store.getSpaceFilteredRoomIds(client.getRoom(space3)).has(dm3)).toBeFalsy();
|
||||
expect(store.getSpaceFilteredRoomIds(space1).has(dm1)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(space2).has(dm1)).toBeFalsy();
|
||||
expect(store.getSpaceFilteredRoomIds(space3).has(dm1)).toBeFalsy();
|
||||
expect(store.getSpaceFilteredRoomIds(space1).has(dm2)).toBeFalsy();
|
||||
expect(store.getSpaceFilteredRoomIds(space2).has(dm2)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(space3).has(dm2)).toBeFalsy();
|
||||
expect(store.getSpaceFilteredRoomIds(space1).has(dm3)).toBeFalsy();
|
||||
expect(store.getSpaceFilteredRoomIds(space2).has(dm3)).toBeFalsy();
|
||||
expect(store.getSpaceFilteredRoomIds(space3).has(dm3)).toBeFalsy();
|
||||
});
|
||||
|
||||
it("dms are only added to Notification States for only the Home Space", () => {
|
||||
|
@ -491,11 +517,11 @@ describe("SpaceStore", () => {
|
|||
});
|
||||
|
||||
it("honours m.space.parent if sender has permission in parent space", () => {
|
||||
expect(store.getSpaceFilteredRoomIds(client.getRoom(space2)).has(room2)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(space2).has(room2)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("does not honour m.space.parent if sender does not have permission in parent space", () => {
|
||||
expect(store.getSpaceFilteredRoomIds(client.getRoom(space3)).has(room3)).toBeFalsy();
|
||||
expect(store.getSpaceFilteredRoomIds(space3).has(room3)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -586,8 +612,8 @@ describe("SpaceStore", () => {
|
|||
expect(store.invitedSpaces).toStrictEqual([]);
|
||||
expect(store.getChildSpaces(space1)).toStrictEqual([]);
|
||||
expect(store.getChildRooms(space1)).toStrictEqual([]);
|
||||
expect(store.getSpaceFilteredRoomIds(client.getRoom(space1)).has(invite1)).toBeFalsy();
|
||||
expect(store.getSpaceFilteredRoomIds(null).has(invite1)).toBeFalsy();
|
||||
expect(store.getSpaceFilteredRoomIds(space1).has(invite1)).toBeFalsy();
|
||||
expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(invite1)).toBeFalsy();
|
||||
|
||||
const invite = mkRoom(invite1);
|
||||
invite.getMyMembership.mockReturnValue("invite");
|
||||
|
@ -599,8 +625,8 @@ describe("SpaceStore", () => {
|
|||
expect(store.invitedSpaces).toStrictEqual([]);
|
||||
expect(store.getChildSpaces(space1)).toStrictEqual([]);
|
||||
expect(store.getChildRooms(space1)).toStrictEqual([invite]);
|
||||
expect(store.getSpaceFilteredRoomIds(client.getRoom(space1)).has(invite1)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(null).has(invite1)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(space1).has(invite1)).toBeTruthy();
|
||||
expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(invite1)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -614,49 +640,46 @@ describe("SpaceStore", () => {
|
|||
]);
|
||||
mkSpace(space3).getMyMembership.mockReturnValue("invite");
|
||||
await run();
|
||||
store.setActiveSpace(null);
|
||||
expect(store.activeSpace).toBe(null);
|
||||
store.setActiveSpace(MetaSpace.Home);
|
||||
expect(store.activeSpace).toBe(MetaSpace.Home);
|
||||
});
|
||||
afterEach(() => {
|
||||
fn.mockClear();
|
||||
});
|
||||
|
||||
it("switch to home space", async () => {
|
||||
store.setActiveSpace(client.getRoom(space1));
|
||||
store.setActiveSpace(space1);
|
||||
fn.mockClear();
|
||||
|
||||
store.setActiveSpace(null);
|
||||
expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, null);
|
||||
expect(store.activeSpace).toBe(null);
|
||||
store.setActiveSpace(MetaSpace.Home);
|
||||
expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, MetaSpace.Home);
|
||||
expect(store.activeSpace).toBe(MetaSpace.Home);
|
||||
});
|
||||
|
||||
it("switch to invited space", async () => {
|
||||
const space = client.getRoom(space3);
|
||||
store.setActiveSpace(space);
|
||||
expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space);
|
||||
expect(store.activeSpace).toBe(space);
|
||||
store.setActiveSpace(space3);
|
||||
expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space3);
|
||||
expect(store.activeSpace).toBe(space3);
|
||||
});
|
||||
|
||||
it("switch to top level space", async () => {
|
||||
const space = client.getRoom(space1);
|
||||
store.setActiveSpace(space);
|
||||
expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space);
|
||||
expect(store.activeSpace).toBe(space);
|
||||
store.setActiveSpace(space1);
|
||||
expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space1);
|
||||
expect(store.activeSpace).toBe(space1);
|
||||
});
|
||||
|
||||
it("switch to subspace", async () => {
|
||||
const space = client.getRoom(space2);
|
||||
store.setActiveSpace(space);
|
||||
expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space);
|
||||
expect(store.activeSpace).toBe(space);
|
||||
store.setActiveSpace(space2);
|
||||
expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space2);
|
||||
expect(store.activeSpace).toBe(space2);
|
||||
});
|
||||
|
||||
it("switch to unknown space is a nop", async () => {
|
||||
expect(store.activeSpace).toBe(null);
|
||||
expect(store.activeSpace).toBe(MetaSpace.Home);
|
||||
const space = client.getRoom(room1); // not a space
|
||||
store.setActiveSpace(space);
|
||||
expect(fn).not.toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space);
|
||||
expect(store.activeSpace).toBe(null);
|
||||
store.setActiveSpace(space.roomId);
|
||||
expect(fn).not.toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space.roomId);
|
||||
expect(store.activeSpace).toBe(MetaSpace.Home);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -678,6 +701,7 @@ describe("SpaceStore", () => {
|
|||
});
|
||||
afterEach(() => {
|
||||
localStorage.clear();
|
||||
localStorage.setItem("mx_labs_feature_feature_spaces_metaspaces", "true");
|
||||
defaultDispatcher.unregister(dispatcherRef);
|
||||
});
|
||||
|
||||
|
@ -687,59 +711,59 @@ describe("SpaceStore", () => {
|
|||
};
|
||||
|
||||
it("last viewed room in target space is the current viewed and in both spaces", async () => {
|
||||
store.setActiveSpace(client.getRoom(space1));
|
||||
store.setActiveSpace(space1);
|
||||
viewRoom(room2);
|
||||
store.setActiveSpace(client.getRoom(space2));
|
||||
store.setActiveSpace(space2);
|
||||
viewRoom(room2);
|
||||
store.setActiveSpace(client.getRoom(space1));
|
||||
store.setActiveSpace(space1);
|
||||
expect(getCurrentRoom()).toBe(room2);
|
||||
});
|
||||
|
||||
it("last viewed room in target space is in the current space", async () => {
|
||||
store.setActiveSpace(client.getRoom(space1));
|
||||
store.setActiveSpace(space1);
|
||||
viewRoom(room2);
|
||||
store.setActiveSpace(client.getRoom(space2));
|
||||
store.setActiveSpace(space2);
|
||||
expect(getCurrentRoom()).toBe(space2);
|
||||
store.setActiveSpace(client.getRoom(space1));
|
||||
store.setActiveSpace(space1);
|
||||
expect(getCurrentRoom()).toBe(room2);
|
||||
});
|
||||
|
||||
it("last viewed room in target space is not in the current space", async () => {
|
||||
store.setActiveSpace(client.getRoom(space1));
|
||||
store.setActiveSpace(space1);
|
||||
viewRoom(room1);
|
||||
store.setActiveSpace(client.getRoom(space2));
|
||||
store.setActiveSpace(space2);
|
||||
viewRoom(room2);
|
||||
store.setActiveSpace(client.getRoom(space1));
|
||||
store.setActiveSpace(space1);
|
||||
expect(getCurrentRoom()).toBe(room1);
|
||||
});
|
||||
|
||||
it("last viewed room is target space is not known", async () => {
|
||||
store.setActiveSpace(client.getRoom(space1));
|
||||
store.setActiveSpace(space1);
|
||||
viewRoom(room1);
|
||||
localStorage.setItem(`mx_space_context_${space2}`, orphan2);
|
||||
store.setActiveSpace(client.getRoom(space2));
|
||||
store.setActiveSpace(space2);
|
||||
expect(getCurrentRoom()).toBe(space2);
|
||||
});
|
||||
|
||||
it("last viewed room is target space is no longer in that space", async () => {
|
||||
store.setActiveSpace(client.getRoom(space1));
|
||||
store.setActiveSpace(space1);
|
||||
viewRoom(room1);
|
||||
localStorage.setItem(`mx_space_context_${space2}`, room1);
|
||||
store.setActiveSpace(client.getRoom(space2));
|
||||
store.setActiveSpace(space2);
|
||||
expect(getCurrentRoom()).toBe(space2); // Space home instead of room1
|
||||
});
|
||||
|
||||
it("no last viewed room in target space", async () => {
|
||||
store.setActiveSpace(client.getRoom(space1));
|
||||
store.setActiveSpace(space1);
|
||||
viewRoom(room1);
|
||||
store.setActiveSpace(client.getRoom(space2));
|
||||
store.setActiveSpace(space2);
|
||||
expect(getCurrentRoom()).toBe(space2);
|
||||
});
|
||||
|
||||
it("no last viewed room in home space", async () => {
|
||||
store.setActiveSpace(client.getRoom(space1));
|
||||
store.setActiveSpace(space1);
|
||||
viewRoom(room1);
|
||||
store.setActiveSpace(null);
|
||||
store.setActiveSpace(MetaSpace.Home);
|
||||
expect(getCurrentRoom()).toBeNull(); // Home
|
||||
});
|
||||
});
|
||||
|
@ -767,38 +791,51 @@ describe("SpaceStore", () => {
|
|||
|
||||
it("no switch required, room is in current space", async () => {
|
||||
viewRoom(room1);
|
||||
store.setActiveSpace(client.getRoom(space1), false);
|
||||
store.setActiveSpace(space1, false);
|
||||
viewRoom(room2);
|
||||
expect(store.activeSpace).toBe(client.getRoom(space1));
|
||||
expect(store.activeSpace).toBe(space1);
|
||||
});
|
||||
|
||||
it("switch to canonical parent space for room", async () => {
|
||||
viewRoom(room1);
|
||||
store.setActiveSpace(client.getRoom(space2), false);
|
||||
store.setActiveSpace(space2, false);
|
||||
viewRoom(room2);
|
||||
expect(store.activeSpace).toBe(client.getRoom(space2));
|
||||
expect(store.activeSpace).toBe(space2);
|
||||
});
|
||||
|
||||
it("switch to first containing space for room", async () => {
|
||||
viewRoom(room2);
|
||||
store.setActiveSpace(client.getRoom(space2), false);
|
||||
store.setActiveSpace(space2, false);
|
||||
viewRoom(room3);
|
||||
expect(store.activeSpace).toBe(client.getRoom(space1));
|
||||
expect(store.activeSpace).toBe(space1);
|
||||
});
|
||||
|
||||
it("switch to home for orphaned room", async () => {
|
||||
it("switch to other rooms for orphaned room", async () => {
|
||||
viewRoom(room1);
|
||||
store.setActiveSpace(client.getRoom(space1), false);
|
||||
store.setActiveSpace(space1, false);
|
||||
viewRoom(orphan1);
|
||||
expect(store.activeSpace).toBeNull();
|
||||
expect(store.activeSpace).toBe(MetaSpace.Orphans);
|
||||
});
|
||||
|
||||
it("switch to first space when selected metaspace is disabled", async () => {
|
||||
store.setActiveSpace(MetaSpace.People, false);
|
||||
expect(store.activeSpace).toBe(MetaSpace.People);
|
||||
await SettingsStore.setValue("Spaces.enabledMetaSpaces", null, SettingLevel.DEVICE, {
|
||||
[MetaSpace.Home]: false,
|
||||
[MetaSpace.Favourites]: true,
|
||||
[MetaSpace.People]: false,
|
||||
[MetaSpace.Orphans]: true,
|
||||
});
|
||||
jest.runAllTimers();
|
||||
expect(store.activeSpace).toBe(MetaSpace.Favourites);
|
||||
});
|
||||
|
||||
it("when switching rooms in the all rooms home space don't switch to related space", async () => {
|
||||
await setShowAllRooms(true);
|
||||
viewRoom(room2);
|
||||
store.setActiveSpace(null, false);
|
||||
store.setActiveSpace(MetaSpace.Home, false);
|
||||
viewRoom(room1);
|
||||
expect(store.activeSpace).toBeNull();
|
||||
expect(store.activeSpace).toBe(MetaSpace.Home);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
125
test/stores/WidgetLayoutStore-test.ts
Normal file
125
test/stores/WidgetLayoutStore-test.ts
Normal file
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import "../skinned-sdk"; // Must be first for skinning to work
|
||||
import WidgetStore, { IApp } from "../../src/stores/WidgetStore";
|
||||
import { Container, WidgetLayoutStore } from "../../src/stores/widgets/WidgetLayoutStore";
|
||||
import { Room } from "matrix-js-sdk";
|
||||
import { stubClient } from "../test-utils";
|
||||
|
||||
// setup test env values
|
||||
const roomId = "!room:server";
|
||||
const mockRoom = <Room>{
|
||||
roomId: roomId,
|
||||
currentState: {
|
||||
getStateEvents: (_l, _x) => {
|
||||
return {
|
||||
getId: ()=>"$layoutEventId",
|
||||
getContent: () => null,
|
||||
};
|
||||
},
|
||||
} };
|
||||
|
||||
const mockApps = [
|
||||
<IApp> { roomId: roomId, id: "1" },
|
||||
<IApp> { roomId: roomId, id: "2" },
|
||||
<IApp> { roomId: roomId, id: "3" },
|
||||
<IApp> { roomId: roomId, id: "4" },
|
||||
];
|
||||
|
||||
// fake the WidgetStore.instance to just return an object with `getApps`
|
||||
jest.spyOn(WidgetStore, 'instance', 'get').mockReturnValue(<WidgetStore>{ getApps: (_room) => mockApps });
|
||||
|
||||
describe("WidgetLayoutStore", () => {
|
||||
// we need to init a client so it does not error, when asking for DeviceStorage handlers (SettingsStore.setValue("Widgets.layout"))
|
||||
stubClient();
|
||||
|
||||
const store = WidgetLayoutStore.instance;
|
||||
|
||||
it("all widgets should be in the right container by default", async () => {
|
||||
store.recalculateRoom(mockRoom);
|
||||
expect(store.getContainerWidgets(mockRoom, Container.Right).length).toStrictEqual(mockApps.length);
|
||||
});
|
||||
it("add widget to top container", async () => {
|
||||
store.recalculateRoom(mockRoom);
|
||||
store.moveToContainer(mockRoom, mockApps[0], Container.Top);
|
||||
expect(store.getContainerWidgets(mockRoom, Container.Top)).toStrictEqual([mockApps[0]]);
|
||||
});
|
||||
it("add three widgets to top container", async () => {
|
||||
store.recalculateRoom(mockRoom);
|
||||
store.moveToContainer(mockRoom, mockApps[0], Container.Top);
|
||||
store.moveToContainer(mockRoom, mockApps[1], Container.Top);
|
||||
store.moveToContainer(mockRoom, mockApps[2], Container.Top);
|
||||
expect(new Set(store.getContainerWidgets(mockRoom, Container.Top)))
|
||||
.toEqual(new Set([mockApps[0], mockApps[1], mockApps[2]]));
|
||||
});
|
||||
it("cannot add more than three widgets to top container", async () => {
|
||||
store.recalculateRoom(mockRoom);
|
||||
store.moveToContainer(mockRoom, mockApps[0], Container.Top);
|
||||
store.moveToContainer(mockRoom, mockApps[1], Container.Top);
|
||||
store.moveToContainer(mockRoom, mockApps[2], Container.Top);
|
||||
expect(store.canAddToContainer(mockRoom, Container.Top))
|
||||
.toEqual(false);
|
||||
});
|
||||
it("remove pins when maximising (other widget)", async () => {
|
||||
store.recalculateRoom(mockRoom);
|
||||
store.moveToContainer(mockRoom, mockApps[0], Container.Top);
|
||||
store.moveToContainer(mockRoom, mockApps[1], Container.Top);
|
||||
store.moveToContainer(mockRoom, mockApps[2], Container.Top);
|
||||
store.moveToContainer(mockRoom, mockApps[3], Container.Center);
|
||||
expect(store.getContainerWidgets(mockRoom, Container.Top))
|
||||
.toEqual([]);
|
||||
expect(new Set(store.getContainerWidgets(mockRoom, Container.Right)))
|
||||
.toEqual(new Set([mockApps[0], mockApps[1], mockApps[2]]));
|
||||
expect(store.getContainerWidgets(mockRoom, Container.Center))
|
||||
.toEqual([mockApps[3]]);
|
||||
});
|
||||
it("remove pins when maximising (one of the pinned widgets)", async () => {
|
||||
store.recalculateRoom(mockRoom);
|
||||
store.moveToContainer(mockRoom, mockApps[0], Container.Top);
|
||||
store.moveToContainer(mockRoom, mockApps[1], Container.Top);
|
||||
store.moveToContainer(mockRoom, mockApps[2], Container.Top);
|
||||
store.moveToContainer(mockRoom, mockApps[0], Container.Center);
|
||||
expect(store.getContainerWidgets(mockRoom, Container.Top))
|
||||
.toEqual([]);
|
||||
expect(store.getContainerWidgets(mockRoom, Container.Center))
|
||||
.toEqual([mockApps[0]]);
|
||||
expect(new Set(store.getContainerWidgets(mockRoom, Container.Right)))
|
||||
.toEqual(new Set([mockApps[1], mockApps[2], mockApps[3]]));
|
||||
});
|
||||
it("remove maximised when pinning (other widget)", async () => {
|
||||
store.recalculateRoom(mockRoom);
|
||||
store.moveToContainer(mockRoom, mockApps[0], Container.Center);
|
||||
store.moveToContainer(mockRoom, mockApps[1], Container.Top);
|
||||
expect(store.getContainerWidgets(mockRoom, Container.Top))
|
||||
.toEqual([mockApps[1]]);
|
||||
expect(store.getContainerWidgets(mockRoom, Container.Center))
|
||||
.toEqual([]);
|
||||
expect(new Set(store.getContainerWidgets(mockRoom, Container.Right)))
|
||||
.toEqual(new Set([mockApps[2], mockApps[3], mockApps[0]]));
|
||||
});
|
||||
it("remove maximised when pinning (same widget)", async () => {
|
||||
store.recalculateRoom(mockRoom);
|
||||
store.moveToContainer(mockRoom, mockApps[0], Container.Center);
|
||||
store.moveToContainer(mockRoom, mockApps[0], Container.Top);
|
||||
expect(store.getContainerWidgets(mockRoom, Container.Top))
|
||||
.toEqual([mockApps[0]]);
|
||||
expect(store.getContainerWidgets(mockRoom, Container.Center))
|
||||
.toEqual([]);
|
||||
expect(new Set(store.getContainerWidgets(mockRoom, Container.Right)))
|
||||
.toEqual(new Set([mockApps[2], mockApps[3], mockApps[1]]));
|
||||
});
|
||||
});
|
17
test/stores/enable-metaspaces-labs.ts
Normal file
17
test/stores/enable-metaspaces-labs.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
localStorage.setItem("mx_labs_feature_feature_spaces_metaspaces", "true");
|
|
@ -14,17 +14,20 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import "../enable-metaspaces-labs";
|
||||
import "../../skinned-sdk"; // Must be first for skinning to work
|
||||
import { SpaceWatcher } from "../../../src/stores/room-list/SpaceWatcher";
|
||||
import type { RoomListStoreClass } from "../../../src/stores/room-list/RoomListStore";
|
||||
import SettingsStore from "../../../src/settings/SettingsStore";
|
||||
import SpaceStore, { UPDATE_HOME_BEHAVIOUR } from "../../../src/stores/SpaceStore";
|
||||
import SpaceStore from "../../../src/stores/spaces/SpaceStore";
|
||||
import { MetaSpace, UPDATE_HOME_BEHAVIOUR } from "../../../src/stores/spaces";
|
||||
import { stubClient } from "../../test-utils";
|
||||
import { SettingLevel } from "../../../src/settings/SettingLevel";
|
||||
import * as testUtils from "../../utils/test-utils";
|
||||
import { setupAsyncStoreWithClient } from "../../utils/test-utils";
|
||||
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
|
||||
import * as testUtils from "../../utils/test-utils";
|
||||
import { SpaceFilterCondition } from "../../../src/stores/room-list/filters/SpaceFilterCondition";
|
||||
import DMRoomMap from "../../../src/utils/DMRoomMap";
|
||||
|
||||
let filter: SpaceFilterCondition = null;
|
||||
|
||||
|
@ -33,8 +36,13 @@ const mockRoomListStore = {
|
|||
removeFilter: () => filter = null,
|
||||
} as unknown as RoomListStoreClass;
|
||||
|
||||
const space1Id = "!space1:server";
|
||||
const space2Id = "!space2:server";
|
||||
const getUserIdForRoomId = jest.fn();
|
||||
const getDMRoomsForUserId = jest.fn();
|
||||
// @ts-ignore
|
||||
DMRoomMap.sharedInstance = { getUserIdForRoomId, getDMRoomsForUserId };
|
||||
|
||||
const space1 = "!space1:server";
|
||||
const space2 = "!space2:server";
|
||||
|
||||
describe("SpaceWatcher", () => {
|
||||
stubClient();
|
||||
|
@ -50,17 +58,21 @@ describe("SpaceWatcher", () => {
|
|||
await testUtils.emitPromise(store, UPDATE_HOME_BEHAVIOUR);
|
||||
};
|
||||
|
||||
let space1;
|
||||
let space2;
|
||||
|
||||
beforeEach(async () => {
|
||||
filter = null;
|
||||
store.removeAllListeners();
|
||||
store.setActiveSpace(null);
|
||||
store.setActiveSpace(MetaSpace.Home);
|
||||
client.getVisibleRooms.mockReturnValue(rooms = []);
|
||||
|
||||
space1 = mkSpace(space1Id);
|
||||
space2 = mkSpace(space2Id);
|
||||
mkSpace(space1);
|
||||
mkSpace(space2);
|
||||
|
||||
await SettingsStore.setValue("Spaces.enabledMetaSpaces", null, SettingLevel.DEVICE, {
|
||||
[MetaSpace.Home]: true,
|
||||
[MetaSpace.Favourites]: true,
|
||||
[MetaSpace.People]: true,
|
||||
[MetaSpace.Orphans]: true,
|
||||
});
|
||||
|
||||
client.getRoom.mockImplementation(roomId => rooms.find(room => room.roomId === roomId));
|
||||
await setupAsyncStoreWithClient(store, client);
|
||||
|
@ -80,14 +92,14 @@ describe("SpaceWatcher", () => {
|
|||
expect(filter).toBeNull();
|
||||
});
|
||||
|
||||
it("sets space=null filter for all -> home transition", async () => {
|
||||
it("sets space=Home filter for all -> home transition", async () => {
|
||||
await setShowAllRooms(true);
|
||||
new SpaceWatcher(mockRoomListStore);
|
||||
|
||||
await setShowAllRooms(false);
|
||||
|
||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||
expect(filter["space"]).toBeNull();
|
||||
expect(filter["space"]).toBe(MetaSpace.Home);
|
||||
});
|
||||
|
||||
it("sets filter correctly for all -> space transition", async () => {
|
||||
|
@ -126,7 +138,43 @@ describe("SpaceWatcher", () => {
|
|||
SpaceStore.instance.setActiveSpace(space1);
|
||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||
expect(filter["space"]).toBe(space1);
|
||||
SpaceStore.instance.setActiveSpace(null);
|
||||
SpaceStore.instance.setActiveSpace(MetaSpace.Home);
|
||||
|
||||
expect(filter).toBeNull();
|
||||
});
|
||||
|
||||
it("removes filter for favourites -> all transition", async () => {
|
||||
await setShowAllRooms(true);
|
||||
new SpaceWatcher(mockRoomListStore);
|
||||
|
||||
SpaceStore.instance.setActiveSpace(MetaSpace.Favourites);
|
||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||
expect(filter["space"]).toBe(MetaSpace.Favourites);
|
||||
SpaceStore.instance.setActiveSpace(MetaSpace.Home);
|
||||
|
||||
expect(filter).toBeNull();
|
||||
});
|
||||
|
||||
it("removes filter for people -> all transition", async () => {
|
||||
await setShowAllRooms(true);
|
||||
new SpaceWatcher(mockRoomListStore);
|
||||
|
||||
SpaceStore.instance.setActiveSpace(MetaSpace.People);
|
||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||
expect(filter["space"]).toBe(MetaSpace.People);
|
||||
SpaceStore.instance.setActiveSpace(MetaSpace.Home);
|
||||
|
||||
expect(filter).toBeNull();
|
||||
});
|
||||
|
||||
it("removes filter for orphans -> all transition", async () => {
|
||||
await setShowAllRooms(true);
|
||||
new SpaceWatcher(mockRoomListStore);
|
||||
|
||||
SpaceStore.instance.setActiveSpace(MetaSpace.Orphans);
|
||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||
expect(filter["space"]).toBe(MetaSpace.Orphans);
|
||||
SpaceStore.instance.setActiveSpace(MetaSpace.Home);
|
||||
|
||||
expect(filter).toBeNull();
|
||||
});
|
||||
|
@ -138,10 +186,36 @@ describe("SpaceWatcher", () => {
|
|||
new SpaceWatcher(mockRoomListStore);
|
||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||
expect(filter["space"]).toBe(space1);
|
||||
SpaceStore.instance.setActiveSpace(null);
|
||||
SpaceStore.instance.setActiveSpace(MetaSpace.Home);
|
||||
|
||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||
expect(filter["space"]).toBe(null);
|
||||
expect(filter["space"]).toBe(MetaSpace.Home);
|
||||
});
|
||||
|
||||
it("updates filter correctly for space -> orphans transition", async () => {
|
||||
await setShowAllRooms(false);
|
||||
SpaceStore.instance.setActiveSpace(space1);
|
||||
|
||||
new SpaceWatcher(mockRoomListStore);
|
||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||
expect(filter["space"]).toBe(space1);
|
||||
SpaceStore.instance.setActiveSpace(MetaSpace.Orphans);
|
||||
|
||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||
expect(filter["space"]).toBe(MetaSpace.Orphans);
|
||||
});
|
||||
|
||||
it("updates filter correctly for orphans -> people transition", async () => {
|
||||
await setShowAllRooms(false);
|
||||
SpaceStore.instance.setActiveSpace(MetaSpace.Orphans);
|
||||
|
||||
new SpaceWatcher(mockRoomListStore);
|
||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||
expect(filter["space"]).toBe(MetaSpace.Orphans);
|
||||
SpaceStore.instance.setActiveSpace(MetaSpace.People);
|
||||
|
||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||
expect(filter["space"]).toBe(MetaSpace.People);
|
||||
});
|
||||
|
||||
it("updates filter correctly for space -> space transition", async () => {
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { formatSeconds } from "../../src/DateUtils";
|
||||
import { formatSeconds, formatRelativeTime } from "../../src/DateUtils";
|
||||
|
||||
describe("formatSeconds", () => {
|
||||
it("correctly formats time with hours", () => {
|
||||
|
@ -29,3 +29,44 @@ describe("formatSeconds", () => {
|
|||
expect(formatSeconds((60 * 60 * 0) + (60 * 31) + (0))).toBe("31:00");
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatRelativeTime", () => {
|
||||
let dateSpy;
|
||||
beforeAll(() => {
|
||||
dateSpy = jest
|
||||
.spyOn(global.Date, 'now')
|
||||
// Tuesday, 2 November 2021 11:18:03 UTC
|
||||
.mockImplementation(() => 1635851883000);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
dateSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("returns hour format for events created less than 24 hours ago", () => {
|
||||
const date = new Date(2021, 10, 2, 11, 1, 23, 0);
|
||||
expect(formatRelativeTime(date)).toBe("11:01");
|
||||
});
|
||||
|
||||
it("honours the hour format setting", () => {
|
||||
const date = new Date(2021, 10, 2, 11, 1, 23, 0);
|
||||
expect(formatRelativeTime(date)).toBe("11:01");
|
||||
expect(formatRelativeTime(date, false)).toBe("11:01");
|
||||
expect(formatRelativeTime(date, true)).toBe("11:01AM");
|
||||
});
|
||||
|
||||
it("returns month and day for events created in the current year", () => {
|
||||
const date = new Date(1632567741000);
|
||||
expect(formatRelativeTime(date, true)).toBe("Sep 25");
|
||||
});
|
||||
|
||||
it("does not return a leading 0 for single digit days", () => {
|
||||
const date = new Date(1635764541000);
|
||||
expect(formatRelativeTime(date, true)).toBe("Nov 1");
|
||||
});
|
||||
|
||||
it("appends the year for events created in previous years", () => {
|
||||
const date = new Date(1604142141000);
|
||||
expect(formatRelativeTime(date, true)).toBe("Oct 31, 2020");
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue