mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-05-29 09:21:16 +03:00
We don't need these anymore. Theoretically this commit could go to develop, but for safety it's going to `travis/sourcemaps` first.
369 lines
11 KiB
JavaScript
369 lines
11 KiB
JavaScript
// load olm before the sdk if possible
|
|
import './olm-loader';
|
|
|
|
import {logger} from '../src/logger';
|
|
import {MatrixEvent} from "../src/models/event";
|
|
|
|
/**
|
|
* Return a promise that is resolved when the client next emits a
|
|
* SYNCING event.
|
|
* @param {Object} client The client
|
|
* @param {Number=} count Number of syncs to wait for (default 1)
|
|
* @return {Promise} Resolves once the client has emitted a SYNCING event
|
|
*/
|
|
export function syncPromise(client, count) {
|
|
if (count === undefined) {
|
|
count = 1;
|
|
}
|
|
if (count <= 0) {
|
|
return Promise.resolve();
|
|
}
|
|
|
|
const p = new Promise((resolve, reject) => {
|
|
const cb = (state) => {
|
|
logger.log(`${Date.now()} syncPromise(${count}): ${state}`);
|
|
if (state === 'SYNCING') {
|
|
resolve();
|
|
} else {
|
|
client.once('sync', cb);
|
|
}
|
|
};
|
|
client.once('sync', cb);
|
|
});
|
|
|
|
return p.then(() => {
|
|
return syncPromise(client, count-1);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Create a spy for an object and automatically spy its methods.
|
|
* @param {*} constr The class constructor (used with 'new')
|
|
* @param {string} name The name of the class
|
|
* @return {Object} An instantiated object with spied methods/properties.
|
|
*/
|
|
export function mock(constr, name) {
|
|
// Based on
|
|
// http://eclipsesource.com/blogs/2014/03/27/mocks-in-jasmine-tests/
|
|
const HelperConstr = new Function(); // jshint ignore:line
|
|
HelperConstr.prototype = constr.prototype;
|
|
const result = new HelperConstr();
|
|
result.toString = function() {
|
|
return "mock" + (name ? " of " + name : "");
|
|
};
|
|
for (const key in constr.prototype) { // eslint-disable-line guard-for-in
|
|
try {
|
|
if (constr.prototype[key] instanceof Function) {
|
|
result[key] = jest.fn();
|
|
}
|
|
} catch (ex) {
|
|
// Direct access to some non-function fields of DOM prototypes may
|
|
// cause exceptions.
|
|
// Overwriting will not work either in that case.
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Create an Event.
|
|
* @param {Object} opts Values for the event.
|
|
* @param {string} opts.type The event.type
|
|
* @param {string} opts.room The event.room_id
|
|
* @param {string} opts.sender The event.sender
|
|
* @param {string} opts.skey Optional. The state key (auto inserts empty string)
|
|
* @param {Object} opts.content The event.content
|
|
* @param {boolean} opts.event True to make a MatrixEvent.
|
|
* @return {Object} a JSON object representing this event.
|
|
*/
|
|
export function mkEvent(opts) {
|
|
if (!opts.type || !opts.content) {
|
|
throw new Error("Missing .type or .content =>" + JSON.stringify(opts));
|
|
}
|
|
const event = {
|
|
type: opts.type,
|
|
room_id: opts.room,
|
|
sender: opts.sender || opts.user, // opts.user for backwards-compat
|
|
content: opts.content,
|
|
event_id: "$" + Math.random() + "-" + Math.random(),
|
|
};
|
|
if (opts.skey !== undefined) {
|
|
event.state_key = opts.skey;
|
|
} else if (["m.room.name", "m.room.topic", "m.room.create", "m.room.join_rules",
|
|
"m.room.power_levels", "m.room.topic",
|
|
"com.example.state"].indexOf(opts.type) !== -1) {
|
|
event.state_key = "";
|
|
}
|
|
return opts.event ? new MatrixEvent(event) : event;
|
|
}
|
|
|
|
/**
|
|
* Create an m.presence event.
|
|
* @param {Object} opts Values for the presence.
|
|
* @return {Object|MatrixEvent} The event
|
|
*/
|
|
export function mkPresence(opts) {
|
|
if (!opts.user) {
|
|
throw new Error("Missing user");
|
|
}
|
|
const event = {
|
|
event_id: "$" + Math.random() + "-" + Math.random(),
|
|
type: "m.presence",
|
|
sender: opts.sender || opts.user, // opts.user for backwards-compat
|
|
content: {
|
|
avatar_url: opts.url,
|
|
displayname: opts.name,
|
|
last_active_ago: opts.ago,
|
|
presence: opts.presence || "offline",
|
|
},
|
|
};
|
|
return opts.event ? new MatrixEvent(event) : event;
|
|
}
|
|
|
|
/**
|
|
* Create an m.room.member event.
|
|
* @param {Object} opts Values for the membership.
|
|
* @param {string} opts.room The room ID for the event.
|
|
* @param {string} opts.mship The content.membership for the event.
|
|
* @param {string} opts.sender The sender user ID for the event.
|
|
* @param {string} opts.skey The target user ID for the event if applicable
|
|
* e.g. for invites/bans.
|
|
* @param {string} opts.name The content.displayname for the event.
|
|
* @param {string} opts.url The content.avatar_url for the event.
|
|
* @param {boolean} opts.event True to make a MatrixEvent.
|
|
* @return {Object|MatrixEvent} The event
|
|
*/
|
|
export function mkMembership(opts) {
|
|
opts.type = "m.room.member";
|
|
if (!opts.skey) {
|
|
opts.skey = opts.sender || opts.user;
|
|
}
|
|
if (!opts.mship) {
|
|
throw new Error("Missing .mship => " + JSON.stringify(opts));
|
|
}
|
|
opts.content = {
|
|
membership: opts.mship,
|
|
};
|
|
if (opts.name) {
|
|
opts.content.displayname = opts.name;
|
|
}
|
|
if (opts.url) {
|
|
opts.content.avatar_url = opts.url;
|
|
}
|
|
return mkEvent(opts);
|
|
}
|
|
|
|
/**
|
|
* Create an m.room.message event.
|
|
* @param {Object} opts Values for the message
|
|
* @param {string} opts.room The room ID for the event.
|
|
* @param {string} opts.user The user ID for the event.
|
|
* @param {string} opts.msg Optional. The content.body for the event.
|
|
* @param {boolean} opts.event True to make a MatrixEvent.
|
|
* @return {Object|MatrixEvent} The event
|
|
*/
|
|
export function mkMessage(opts) {
|
|
opts.type = "m.room.message";
|
|
if (!opts.msg) {
|
|
opts.msg = "Random->" + Math.random();
|
|
}
|
|
if (!opts.room || !opts.user) {
|
|
throw new Error("Missing .room or .user from %s", opts);
|
|
}
|
|
opts.content = {
|
|
msgtype: "m.text",
|
|
body: opts.msg,
|
|
};
|
|
return mkEvent(opts);
|
|
}
|
|
|
|
|
|
/**
|
|
* A mock implementation of webstorage
|
|
*
|
|
* @constructor
|
|
*/
|
|
export function MockStorageApi() {
|
|
this.data = {};
|
|
}
|
|
MockStorageApi.prototype = {
|
|
get length() {
|
|
return Object.keys(this.data).length;
|
|
},
|
|
key: function(i) {
|
|
return Object.keys(this.data)[i];
|
|
},
|
|
setItem: function(k, v) {
|
|
this.data[k] = v;
|
|
},
|
|
getItem: function(k) {
|
|
return this.data[k] || null;
|
|
},
|
|
removeItem: function(k) {
|
|
delete this.data[k];
|
|
},
|
|
};
|
|
|
|
|
|
/**
|
|
* If an event is being decrypted, wait for it to finish being decrypted.
|
|
*
|
|
* @param {MatrixEvent} event
|
|
* @returns {Promise} promise which resolves (to `event`) when the event has been decrypted
|
|
*/
|
|
export function awaitDecryption(event) {
|
|
if (!event.isBeingDecrypted()) {
|
|
return Promise.resolve(event);
|
|
}
|
|
|
|
logger.log(`${Date.now()} event ${event.getId()} is being decrypted; waiting`);
|
|
|
|
return new Promise((resolve, reject) => {
|
|
event.once('Event.decrypted', (ev) => {
|
|
logger.log(`${Date.now()} event ${event.getId()} now decrypted`);
|
|
resolve(ev);
|
|
});
|
|
});
|
|
}
|
|
|
|
|
|
export function HttpResponse(
|
|
httpLookups, acceptKeepalives, ignoreUnhandledSync,
|
|
) {
|
|
this.httpLookups = httpLookups;
|
|
this.acceptKeepalives = acceptKeepalives === undefined ? true : acceptKeepalives;
|
|
this.ignoreUnhandledSync = ignoreUnhandledSync;
|
|
this.pendingLookup = null;
|
|
}
|
|
|
|
HttpResponse.prototype.request = function(
|
|
cb, method, path, qp, data, prefix,
|
|
) {
|
|
if (path === HttpResponse.KEEP_ALIVE_PATH && this.acceptKeepalives) {
|
|
return Promise.resolve();
|
|
}
|
|
const next = this.httpLookups.shift();
|
|
const logLine = (
|
|
"MatrixClient[UT] RECV " + method + " " + path + " " +
|
|
"EXPECT " + (next ? next.method : next) + " " + (next ? next.path : next)
|
|
);
|
|
logger.log(logLine);
|
|
|
|
if (!next) { // no more things to return
|
|
if (method === "GET" && path === "/sync" && this.ignoreUnhandledSync) {
|
|
logger.log("MatrixClient[UT] Ignoring.");
|
|
return new Promise(() => {});
|
|
}
|
|
if (this.pendingLookup) {
|
|
if (this.pendingLookup.method === method
|
|
&& this.pendingLookup.path === path) {
|
|
return this.pendingLookup.promise;
|
|
}
|
|
// >1 pending thing, and they are different, whine.
|
|
expect(false).toBe(
|
|
true, ">1 pending request. You should probably handle them. " +
|
|
"PENDING: " + JSON.stringify(this.pendingLookup) + " JUST GOT: " +
|
|
method + " " + path,
|
|
);
|
|
}
|
|
this.pendingLookup = {
|
|
promise: new Promise(() => {}),
|
|
method: method,
|
|
path: path,
|
|
};
|
|
return this.pendingLookup.promise;
|
|
}
|
|
if (next.path === path && next.method === method) {
|
|
logger.log(
|
|
"MatrixClient[UT] Matched. Returning " +
|
|
(next.error ? "BAD" : "GOOD") + " response",
|
|
);
|
|
if (next.expectBody) {
|
|
expect(next.expectBody).toEqual(data);
|
|
}
|
|
if (next.expectQueryParams) {
|
|
Object.keys(next.expectQueryParams).forEach(function(k) {
|
|
expect(qp[k]).toEqual(next.expectQueryParams[k]);
|
|
});
|
|
}
|
|
|
|
if (next.thenCall) {
|
|
process.nextTick(next.thenCall, 0); // next tick so we return first.
|
|
}
|
|
|
|
if (next.error) {
|
|
return Promise.reject({
|
|
errcode: next.error.errcode,
|
|
httpStatus: next.error.httpStatus,
|
|
name: next.error.errcode,
|
|
message: "Expected testing error",
|
|
data: next.error,
|
|
});
|
|
}
|
|
return Promise.resolve(next.data);
|
|
} else if (method === "GET" && path === "/sync" && this.ignoreUnhandledSync) {
|
|
logger.log("MatrixClient[UT] Ignoring.");
|
|
this.httpLookups.unshift(next);
|
|
return new Promise(() => {});
|
|
}
|
|
expect(true).toBe(false, "Expected different request. " + logLine);
|
|
return new Promise(() => {});
|
|
};
|
|
|
|
HttpResponse.KEEP_ALIVE_PATH = "/_matrix/client/versions";
|
|
|
|
HttpResponse.PUSH_RULES_RESPONSE = {
|
|
method: "GET",
|
|
path: "/pushrules/",
|
|
data: {},
|
|
};
|
|
|
|
HttpResponse.USER_ID = "@alice:bar";
|
|
|
|
HttpResponse.filterResponse = function(userId) {
|
|
const filterPath = "/user/" + encodeURIComponent(userId) + "/filter";
|
|
return {
|
|
method: "POST",
|
|
path: filterPath,
|
|
data: { filter_id: "f1lt3r" },
|
|
};
|
|
};
|
|
|
|
HttpResponse.SYNC_DATA = {
|
|
next_batch: "s_5_3",
|
|
presence: { events: [] },
|
|
rooms: {},
|
|
};
|
|
|
|
HttpResponse.SYNC_RESPONSE = {
|
|
method: "GET",
|
|
path: "/sync",
|
|
data: HttpResponse.SYNC_DATA,
|
|
};
|
|
|
|
HttpResponse.defaultResponses = function(userId) {
|
|
return [
|
|
HttpResponse.PUSH_RULES_RESPONSE,
|
|
HttpResponse.filterResponse(userId),
|
|
HttpResponse.SYNC_RESPONSE,
|
|
];
|
|
};
|
|
|
|
export function setHttpResponses(
|
|
client, responses, acceptKeepalives, ignoreUnhandledSyncs,
|
|
) {
|
|
const httpResponseObj = new HttpResponse(
|
|
responses, acceptKeepalives, ignoreUnhandledSyncs,
|
|
);
|
|
|
|
const httpReq = httpResponseObj.request.bind(httpResponseObj);
|
|
client._http = [
|
|
"authedRequest", "authedRequestWithPrefix", "getContentUri",
|
|
"request", "requestWithPrefix", "uploadContent",
|
|
].reduce((r, k) => {r[k] = jest.fn(); return r;}, {});
|
|
client._http.authedRequest.mockImplementation(httpReq);
|
|
client._http.authedRequestWithPrefix.mockImplementation(httpReq);
|
|
client._http.requestWithPrefix.mockImplementation(httpReq);
|
|
client._http.request.mockImplementation(httpReq);
|
|
}
|