Some basic tests for MessagePanel
Check that it puts the read marker in the right place, and check that the animation works. ... all of which has been surprisingly painful.pull/21833/head
parent
d2e69c819f
commit
dc5c0928b2
|
@ -1,6 +1,5 @@
|
|||
// karma.conf.js - the config file for karma, which runs our tests.
|
||||
|
||||
var webpack = require('webpack');
|
||||
var path = require('path');
|
||||
|
||||
/*
|
||||
|
@ -8,6 +7,10 @@ var path = require('path');
|
|||
* to build everything; however it's the easiest way to load our dependencies
|
||||
* from node_modules.
|
||||
*
|
||||
* If you run karma in multi-run mode (with `npm run test-multi`), it will watch
|
||||
* the tests for changes, and webpack will rebuild using a cache. This is much quicker
|
||||
* than a clean rebuild.
|
||||
*
|
||||
* TODO:
|
||||
* - can we run one test at a time?
|
||||
*/
|
||||
|
@ -26,11 +29,18 @@ module.exports = function (config) {
|
|||
],
|
||||
|
||||
// list of files to exclude
|
||||
// (this doesn't work, and I don't know why - we still rerun the tests
|
||||
// when lockfiles are created)
|
||||
exclude: [
|
||||
'**/.#*'
|
||||
],
|
||||
//
|
||||
// This doesn't work. It turns out that it's webpack which does the
|
||||
// watching of the /test directory (possibly karma only watches
|
||||
// tests.js itself). Webpack watches the directory so that it can spot
|
||||
// new tests, which is fair enough; unfortunately it triggers a rebuild
|
||||
// every time a lockfile is created in that directory, and there
|
||||
// doesn't seem to be any way to tell webpack to ignore particular
|
||||
// files in a watched directory.
|
||||
//
|
||||
// exclude: [
|
||||
// '**/.#*'
|
||||
// ],
|
||||
|
||||
// preprocess matching files before serving them to the browser
|
||||
// available preprocessors:
|
||||
|
@ -83,13 +93,6 @@ module.exports = function (config) {
|
|||
module: {
|
||||
loaders: [
|
||||
{ test: /\.json$/, loader: "json" },
|
||||
{
|
||||
// disable 'require' and 'define' for sinon, per
|
||||
// https://github.com/webpack/webpack/issues/304#issuecomment-170883329
|
||||
test: /sinon\/pkg\/sinon\.js/,
|
||||
// TODO: use 'query'?
|
||||
loader: 'imports?define=>false,require=>false',
|
||||
},
|
||||
{
|
||||
test: /\.js$/, loader: "babel",
|
||||
include: [path.resolve('./src'),
|
||||
|
@ -107,6 +110,11 @@ module.exports = function (config) {
|
|||
// there is no need for webpack to parse them - they can
|
||||
// just be included as-is.
|
||||
/highlight\.js\/lib\/languages/,
|
||||
|
||||
// also disable parsing for sinon, because it
|
||||
// tries to do voodoo with 'require' which upsets
|
||||
// webpack (https://github.com/webpack/webpack/issues/304)
|
||||
/sinon\/pkg\/sinon\.js$/,
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
|
@ -114,6 +122,10 @@ module.exports = function (config) {
|
|||
'matrix-react-sdk': path.resolve('src/index.js'),
|
||||
'sinon': 'sinon/pkg/sinon.js',
|
||||
},
|
||||
root: [
|
||||
path.resolve('./src'),
|
||||
path.resolve('./test'),
|
||||
],
|
||||
},
|
||||
devtool: 'inline-source-map',
|
||||
},
|
||||
|
|
|
@ -48,7 +48,6 @@
|
|||
"babel-preset-react": "^6.5.0",
|
||||
"babel-runtime": "^6.6.1",
|
||||
"expect": "^1.16.0",
|
||||
"imports-loader": "^0.6.5",
|
||||
"json-loader": "^0.5.3",
|
||||
"karma": "^0.13.22",
|
||||
"karma-chrome-launcher": "^0.2.3",
|
||||
|
|
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
Copyright 2016 OpenMarket 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.
|
||||
*/
|
||||
|
||||
var React = require('react');
|
||||
var ReactDOM = require("react-dom");
|
||||
var TestUtils = require('react-addons-test-utils');
|
||||
var expect = require('expect');
|
||||
|
||||
var sdk = require('matrix-react-sdk');
|
||||
|
||||
var MessagePanel = sdk.getComponent('structures.MessagePanel');
|
||||
|
||||
var test_utils = require('test-utils');
|
||||
var mockclock = require('mock-clock');
|
||||
|
||||
describe('MessagePanel', function () {
|
||||
var clock = mockclock.clock();
|
||||
var realSetTimeout = window.setTimeout;
|
||||
var events = mkEvents();
|
||||
|
||||
afterEach(function () {
|
||||
clock.uninstall();
|
||||
});
|
||||
|
||||
function mkEvents() {
|
||||
var events = [];
|
||||
var ts0 = Date.now();
|
||||
for (var i = 0; i < 10; i++) {
|
||||
events.push(test_utils.mkMessage(
|
||||
{
|
||||
event: true, room: "!room:id", user: "@user:id",
|
||||
ts: ts0 + i*1000,
|
||||
}));
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
it('should show the events', function() {
|
||||
var res = TestUtils.renderIntoDocument(
|
||||
<MessagePanel events={events} />
|
||||
);
|
||||
|
||||
// just check we have the right number of tiles for now
|
||||
var tiles = TestUtils.scryRenderedComponentsWithType(
|
||||
res, sdk.getComponent('rooms.EventTile'));
|
||||
expect(tiles.length).toEqual(10);
|
||||
});
|
||||
|
||||
it('should show the read-marker in the right place', function() {
|
||||
var res = TestUtils.renderIntoDocument(
|
||||
<MessagePanel events={events} readMarkerEventId={events[4].getId()}
|
||||
readMarkerVisible={true} />
|
||||
);
|
||||
|
||||
var tiles = TestUtils.scryRenderedComponentsWithType(
|
||||
res, sdk.getComponent('rooms.EventTile'));
|
||||
|
||||
// find the <li> which wraps the read marker
|
||||
var rm = TestUtils.findRenderedDOMComponentWithClass(res, 'mx_RoomView_myReadMarker_container');
|
||||
|
||||
// it should follow the <li> which wraps the event tile for event 4
|
||||
var eventContainer = ReactDOM.findDOMNode(tiles[4]).parentNode;
|
||||
expect(rm.previousSibling).toEqual(eventContainer);
|
||||
});
|
||||
|
||||
it('shows a ghost read-marker when the read-marker moves', function(done) {
|
||||
// fake the clock so that we can test the velocity animation.
|
||||
clock.install();
|
||||
clock.mockDate();
|
||||
|
||||
var parentDiv = document.createElement('div');
|
||||
|
||||
// first render with the RM in one place
|
||||
var mp = ReactDOM.render(
|
||||
<MessagePanel events={events} readMarkerEventId={events[4].getId()}
|
||||
readMarkerVisible={true}
|
||||
/>, parentDiv);
|
||||
|
||||
var tiles = TestUtils.scryRenderedComponentsWithType(
|
||||
mp, sdk.getComponent('rooms.EventTile'));
|
||||
|
||||
// find the <li> which wraps the read marker
|
||||
var rm = TestUtils.findRenderedDOMComponentWithClass(mp, 'mx_RoomView_myReadMarker_container');
|
||||
var eventContainer = ReactDOM.findDOMNode(tiles[4]).parentNode;
|
||||
expect(rm.previousSibling).toEqual(eventContainer);
|
||||
|
||||
// now move the RM
|
||||
mp = ReactDOM.render(
|
||||
<MessagePanel events={events} readMarkerEventId={events[6].getId()}
|
||||
readMarkerVisible={true}
|
||||
/>, parentDiv);
|
||||
|
||||
// now there should be two RM containers
|
||||
var found = TestUtils.scryRenderedDOMComponentsWithClass(mp, 'mx_RoomView_myReadMarker_container');
|
||||
expect(found.length).toEqual(2);
|
||||
|
||||
// the first should be the ghost
|
||||
var ghost = found[0];
|
||||
eventContainer = ReactDOM.findDOMNode(tiles[4]).parentNode;
|
||||
expect(ghost.previousSibling).toEqual(eventContainer);
|
||||
var hr = ghost.children[0];
|
||||
console.log("Opacity:", hr.style.opacity);
|
||||
|
||||
// the first should be the ghost
|
||||
eventContainer = ReactDOM.findDOMNode(tiles[4]).parentNode;
|
||||
expect(found[0].previousSibling).toEqual(eventContainer);
|
||||
|
||||
// the second should be the real thing
|
||||
eventContainer = ReactDOM.findDOMNode(tiles[4]).parentNode;
|
||||
expect(ghost.previousSibling).toEqual(eventContainer);
|
||||
|
||||
// advance the clock, and then let the browser run an animation frame,
|
||||
// to let the animation start
|
||||
clock.tick(1500);
|
||||
|
||||
realSetTimeout(() => {
|
||||
// then advance it again to let it complete
|
||||
clock.tick(1000);
|
||||
realSetTimeout(() => {
|
||||
// the ghost should now have finished
|
||||
expect(hr.style.opacity).toEqual(0);
|
||||
done();
|
||||
}, 100);
|
||||
}, 100);
|
||||
});
|
||||
});
|
|
@ -4,8 +4,17 @@
|
|||
|
||||
var React = require('react');
|
||||
|
||||
module.exports = React.createClass({
|
||||
render: function() {
|
||||
return <div />;
|
||||
},
|
||||
});
|
||||
module.exports = function(opts) {
|
||||
opts = opts || {};
|
||||
if (!opts.displayName) {
|
||||
opts.displayName = 'StubComponent';
|
||||
}
|
||||
|
||||
if (!opts.render) {
|
||||
opts.render = function() {
|
||||
return <div>{this.displayName}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
return React.createClass(opts);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,420 @@
|
|||
/*
|
||||
Copyright (c) 2008-2015 Pivotal Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/* This is jasmine's implementation of a mock clock, lifted from the depths of
|
||||
* jasmine-core and exposed as a standalone module. The interface is just the
|
||||
* same as that of jasmine.clock. For example:
|
||||
*
|
||||
* var mock_clock = require("mock-clock").clock();
|
||||
* mock_clock.install();
|
||||
* setTimeout(function() {
|
||||
* timerCallback();
|
||||
* }, 100);
|
||||
*
|
||||
* expect(timerCallback).not.toHaveBeenCalled();
|
||||
* mock_clock.tick(101);
|
||||
* expect(timerCallback).toHaveBeenCalled();
|
||||
*
|
||||
* mock_clock.uninstall();
|
||||
*
|
||||
*
|
||||
* The reason for C&Ping jasmine's clock here is that jasmine itself is
|
||||
* difficult to webpack, and we don't really want all of it. Sinon also has a
|
||||
* mock-clock implementation, but again, it is difficult to webpack.
|
||||
*/
|
||||
|
||||
var j$ = {};
|
||||
|
||||
j$.Clock = function () {
|
||||
function Clock(global, delayedFunctionSchedulerFactory, mockDate) {
|
||||
var self = this,
|
||||
realTimingFunctions = {
|
||||
setTimeout: global.setTimeout,
|
||||
clearTimeout: global.clearTimeout,
|
||||
setInterval: global.setInterval,
|
||||
clearInterval: global.clearInterval
|
||||
},
|
||||
fakeTimingFunctions = {
|
||||
setTimeout: setTimeout,
|
||||
clearTimeout: clearTimeout,
|
||||
setInterval: setInterval,
|
||||
clearInterval: clearInterval
|
||||
},
|
||||
installed = false,
|
||||
delayedFunctionScheduler,
|
||||
timer;
|
||||
|
||||
|
||||
self.install = function() {
|
||||
if(!originalTimingFunctionsIntact()) {
|
||||
throw new Error('Jasmine Clock was unable to install over custom global timer functions. Is the clock already installed?');
|
||||
}
|
||||
replace(global, fakeTimingFunctions);
|
||||
timer = fakeTimingFunctions;
|
||||
delayedFunctionScheduler = delayedFunctionSchedulerFactory();
|
||||
installed = true;
|
||||
|
||||
return self;
|
||||
};
|
||||
|
||||
self.uninstall = function() {
|
||||
delayedFunctionScheduler = null;
|
||||
mockDate.uninstall();
|
||||
replace(global, realTimingFunctions);
|
||||
|
||||
timer = realTimingFunctions;
|
||||
installed = false;
|
||||
};
|
||||
|
||||
self.withMock = function(closure) {
|
||||
this.install();
|
||||
try {
|
||||
closure();
|
||||
} finally {
|
||||
this.uninstall();
|
||||
}
|
||||
};
|
||||
|
||||
self.mockDate = function(initialDate) {
|
||||
mockDate.install(initialDate);
|
||||
};
|
||||
|
||||
self.setTimeout = function(fn, delay, params) {
|
||||
if (legacyIE()) {
|
||||
if (arguments.length > 2) {
|
||||
throw new Error('IE < 9 cannot support extra params to setTimeout without a polyfill');
|
||||
}
|
||||
return timer.setTimeout(fn, delay);
|
||||
}
|
||||
return Function.prototype.apply.apply(timer.setTimeout, [global, arguments]);
|
||||
};
|
||||
|
||||
self.setInterval = function(fn, delay, params) {
|
||||
if (legacyIE()) {
|
||||
if (arguments.length > 2) {
|
||||
throw new Error('IE < 9 cannot support extra params to setInterval without a polyfill');
|
||||
}
|
||||
return timer.setInterval(fn, delay);
|
||||
}
|
||||
return Function.prototype.apply.apply(timer.setInterval, [global, arguments]);
|
||||
};
|
||||
|
||||
self.clearTimeout = function(id) {
|
||||
return Function.prototype.call.apply(timer.clearTimeout, [global, id]);
|
||||
};
|
||||
|
||||
self.clearInterval = function(id) {
|
||||
return Function.prototype.call.apply(timer.clearInterval, [global, id]);
|
||||
};
|
||||
|
||||
self.tick = function(millis) {
|
||||
if (installed) {
|
||||
mockDate.tick(millis);
|
||||
delayedFunctionScheduler.tick(millis);
|
||||
} else {
|
||||
throw new Error('Mock clock is not installed, use jasmine.clock().install()');
|
||||
}
|
||||
};
|
||||
|
||||
return self;
|
||||
|
||||
function originalTimingFunctionsIntact() {
|
||||
return global.setTimeout === realTimingFunctions.setTimeout &&
|
||||
global.clearTimeout === realTimingFunctions.clearTimeout &&
|
||||
global.setInterval === realTimingFunctions.setInterval &&
|
||||
global.clearInterval === realTimingFunctions.clearInterval;
|
||||
}
|
||||
|
||||
function legacyIE() {
|
||||
//if these methods are polyfilled, apply will be present
|
||||
return !(realTimingFunctions.setTimeout || realTimingFunctions.setInterval).apply;
|
||||
}
|
||||
|
||||
function replace(dest, source) {
|
||||
for (var prop in source) {
|
||||
dest[prop] = source[prop];
|
||||
}
|
||||
}
|
||||
|
||||
function setTimeout(fn, delay) {
|
||||
return delayedFunctionScheduler.scheduleFunction(fn, delay, argSlice(arguments, 2));
|
||||
}
|
||||
|
||||
function clearTimeout(id) {
|
||||
return delayedFunctionScheduler.removeFunctionWithId(id);
|
||||
}
|
||||
|
||||
function setInterval(fn, interval) {
|
||||
return delayedFunctionScheduler.scheduleFunction(fn, interval, argSlice(arguments, 2), true);
|
||||
}
|
||||
|
||||
function clearInterval(id) {
|
||||
return delayedFunctionScheduler.removeFunctionWithId(id);
|
||||
}
|
||||
|
||||
function argSlice(argsObj, n) {
|
||||
return Array.prototype.slice.call(argsObj, n);
|
||||
}
|
||||
}
|
||||
|
||||
return Clock;
|
||||
}();
|
||||
|
||||
|
||||
j$.DelayedFunctionScheduler = function() {
|
||||
function DelayedFunctionScheduler() {
|
||||
var self = this;
|
||||
var scheduledLookup = [];
|
||||
var scheduledFunctions = {};
|
||||
var currentTime = 0;
|
||||
var delayedFnCount = 0;
|
||||
|
||||
self.tick = function(millis) {
|
||||
millis = millis || 0;
|
||||
var endTime = currentTime + millis;
|
||||
|
||||
runScheduledFunctions(endTime);
|
||||
currentTime = endTime;
|
||||
};
|
||||
|
||||
self.scheduleFunction = function(funcToCall, millis, params, recurring, timeoutKey, runAtMillis) {
|
||||
var f;
|
||||
if (typeof(funcToCall) === 'string') {
|
||||
/* jshint evil: true */
|
||||
f = function() { return eval(funcToCall); };
|
||||
/* jshint evil: false */
|
||||
} else {
|
||||
f = funcToCall;
|
||||
}
|
||||
|
||||
millis = millis || 0;
|
||||
timeoutKey = timeoutKey || ++delayedFnCount;
|
||||
runAtMillis = runAtMillis || (currentTime + millis);
|
||||
|
||||
var funcToSchedule = {
|
||||
runAtMillis: runAtMillis,
|
||||
funcToCall: f,
|
||||
recurring: recurring,
|
||||
params: params,
|
||||
timeoutKey: timeoutKey,
|
||||
millis: millis
|
||||
};
|
||||
|
||||
if (runAtMillis in scheduledFunctions) {
|
||||
scheduledFunctions[runAtMillis].push(funcToSchedule);
|
||||
} else {
|
||||
scheduledFunctions[runAtMillis] = [funcToSchedule];
|
||||
scheduledLookup.push(runAtMillis);
|
||||
scheduledLookup.sort(function (a, b) {
|
||||
return a - b;
|
||||
});
|
||||
}
|
||||
|
||||
return timeoutKey;
|
||||
};
|
||||
|
||||
self.removeFunctionWithId = function(timeoutKey) {
|
||||
for (var runAtMillis in scheduledFunctions) {
|
||||
var funcs = scheduledFunctions[runAtMillis];
|
||||
var i = indexOfFirstToPass(funcs, function (func) {
|
||||
return func.timeoutKey === timeoutKey;
|
||||
});
|
||||
|
||||
if (i > -1) {
|
||||
if (funcs.length === 1) {
|
||||
delete scheduledFunctions[runAtMillis];
|
||||
deleteFromLookup(runAtMillis);
|
||||
} else {
|
||||
funcs.splice(i, 1);
|
||||
}
|
||||
|
||||
// intervals get rescheduled when executed, so there's never more
|
||||
// than a single scheduled function with a given timeoutKey
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return self;
|
||||
|
||||
function indexOfFirstToPass(array, testFn) {
|
||||
var index = -1;
|
||||
|
||||
for (var i = 0; i < array.length; ++i) {
|
||||
if (testFn(array[i])) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
function deleteFromLookup(key) {
|
||||
var value = Number(key);
|
||||
var i = indexOfFirstToPass(scheduledLookup, function (millis) {
|
||||
return millis === value;
|
||||
});
|
||||
|
||||
if (i > -1) {
|
||||
scheduledLookup.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
function reschedule(scheduledFn) {
|
||||
self.scheduleFunction(scheduledFn.funcToCall,
|
||||
scheduledFn.millis,
|
||||
scheduledFn.params,
|
||||
true,
|
||||
scheduledFn.timeoutKey,
|
||||
scheduledFn.runAtMillis + scheduledFn.millis);
|
||||
}
|
||||
|
||||
function forEachFunction(funcsToRun, callback) {
|
||||
for (var i = 0; i < funcsToRun.length; ++i) {
|
||||
callback(funcsToRun[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function runScheduledFunctions(endTime) {
|
||||
if (scheduledLookup.length === 0 || scheduledLookup[0] > endTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
do {
|
||||
currentTime = scheduledLookup.shift();
|
||||
|
||||
var funcsToRun = scheduledFunctions[currentTime];
|
||||
delete scheduledFunctions[currentTime];
|
||||
|
||||
forEachFunction(funcsToRun, function(funcToRun) {
|
||||
if (funcToRun.recurring) {
|
||||
reschedule(funcToRun);
|
||||
}
|
||||
});
|
||||
|
||||
forEachFunction(funcsToRun, function(funcToRun) {
|
||||
funcToRun.funcToCall.apply(null, funcToRun.params || []);
|
||||
});
|
||||
} while (scheduledLookup.length > 0 &&
|
||||
// checking first if we're out of time prevents setTimeout(0)
|
||||
// scheduled in a funcToRun from forcing an extra iteration
|
||||
currentTime !== endTime &&
|
||||
scheduledLookup[0] <= endTime);
|
||||
}
|
||||
}
|
||||
|
||||
return DelayedFunctionScheduler;
|
||||
}();
|
||||
|
||||
|
||||
j$.MockDate = function() {
|
||||
function MockDate(global) {
|
||||
var self = this;
|
||||
var currentTime = 0;
|
||||
|
||||
if (!global || !global.Date) {
|
||||
self.install = function() {};
|
||||
self.tick = function() {};
|
||||
self.uninstall = function() {};
|
||||
return self;
|
||||
}
|
||||
|
||||
var GlobalDate = global.Date;
|
||||
|
||||
self.install = function(mockDate) {
|
||||
if (mockDate instanceof GlobalDate) {
|
||||
currentTime = mockDate.getTime();
|
||||
} else {
|
||||
currentTime = new GlobalDate().getTime();
|
||||
}
|
||||
|
||||
global.Date = FakeDate;
|
||||
};
|
||||
|
||||
self.tick = function(millis) {
|
||||
millis = millis || 0;
|
||||
currentTime = currentTime + millis;
|
||||
};
|
||||
|
||||
self.uninstall = function() {
|
||||
currentTime = 0;
|
||||
global.Date = GlobalDate;
|
||||
};
|
||||
|
||||
createDateProperties();
|
||||
|
||||
return self;
|
||||
|
||||
function FakeDate() {
|
||||
switch(arguments.length) {
|
||||
case 0:
|
||||
return new GlobalDate(currentTime);
|
||||
case 1:
|
||||
return new GlobalDate(arguments[0]);
|
||||
case 2:
|
||||
return new GlobalDate(arguments[0], arguments[1]);
|
||||
case 3:
|
||||
return new GlobalDate(arguments[0], arguments[1], arguments[2]);
|
||||
case 4:
|
||||
return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3]);
|
||||
case 5:
|
||||
return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3],
|
||||
arguments[4]);
|
||||
case 6:
|
||||
return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3],
|
||||
arguments[4], arguments[5]);
|
||||
default:
|
||||
return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3],
|
||||
arguments[4], arguments[5], arguments[6]);
|
||||
}
|
||||
}
|
||||
|
||||
function createDateProperties() {
|
||||
FakeDate.prototype = GlobalDate.prototype;
|
||||
|
||||
FakeDate.now = function() {
|
||||
if (GlobalDate.now) {
|
||||
return currentTime;
|
||||
} else {
|
||||
throw new Error('Browser does not support Date.now()');
|
||||
}
|
||||
};
|
||||
|
||||
FakeDate.toSource = GlobalDate.toSource;
|
||||
FakeDate.toString = GlobalDate.toString;
|
||||
FakeDate.parse = GlobalDate.parse;
|
||||
FakeDate.UTC = GlobalDate.UTC;
|
||||
}
|
||||
}
|
||||
|
||||
return MockDate;
|
||||
}();
|
||||
|
||||
var clock = new j$.Clock(global, function () { return new j$.DelayedFunctionScheduler(); }, new j$.MockDate(global));
|
||||
|
||||
module.exports.clock = function() {
|
||||
return clock;
|
||||
}
|
||||
|
||||
|
|
@ -5,14 +5,17 @@
|
|||
* provide
|
||||
*/
|
||||
var components = require('../src/component-index.js').components;
|
||||
var stub = require('./components/stub-component.js');
|
||||
var stubComponent = require('./components/stub-component.js');
|
||||
|
||||
components['structures.LeftPanel'] = stub;
|
||||
components['structures.RightPanel'] = stub;
|
||||
components['structures.RoomDirectory'] = stub;
|
||||
components['views.globals.MatrixToolbar'] = stub;
|
||||
components['views.globals.GuestWarningBar'] = stub;
|
||||
components['views.globals.NewVersionBar'] = stub;
|
||||
components['views.elements.Spinner'] = stub;
|
||||
components['structures.LeftPanel'] = stubComponent();
|
||||
components['structures.RightPanel'] = stubComponent();
|
||||
components['structures.RoomDirectory'] = stubComponent();
|
||||
components['views.globals.MatrixToolbar'] = stubComponent();
|
||||
components['views.globals.GuestWarningBar'] = stubComponent();
|
||||
components['views.globals.NewVersionBar'] = stubComponent();
|
||||
components['views.elements.Spinner'] = stubComponent({displayName: 'Spinner'});
|
||||
components['views.messages.DateSeparator'] = stubComponent({displayName: 'DateSeparator'});
|
||||
components['views.messages.MessageTimestamp'] = stubComponent({displayName: 'MessageTimestamp'});
|
||||
components['views.messages.SenderProfile'] = stubComponent({displayName: 'SenderProfile'});
|
||||
|
||||
module.exports.components = components;
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
|
||||
var peg = require('../src/MatrixClientPeg.js');
|
||||
var jssdk = require('matrix-js-sdk');
|
||||
var MatrixEvent = jssdk.MatrixEvent;
|
||||
var sinon = require('sinon');
|
||||
|
||||
|
||||
/**
|
||||
* Stub out the MatrixClient, and configure the MatrixClientPeg object to
|
||||
* return it when get() is called.
|
||||
|
@ -16,6 +18,117 @@ module.exports.stubClient = function() {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.user The event.user_id
|
||||
* @param {string} opts.skey Optional. The state key (auto inserts empty string)
|
||||
* @param {Number} opts.ts Optional. Timestamp for the event
|
||||
* @param {Object} opts.content The event.content
|
||||
* @param {boolean} opts.event True to make a MatrixEvent.
|
||||
* @return {Object} a JSON object representing this event.
|
||||
*/
|
||||
module.exports.mkEvent = function(opts) {
|
||||
if (!opts.type || !opts.content) {
|
||||
throw new Error("Missing .type or .content =>" + JSON.stringify(opts));
|
||||
}
|
||||
var event = {
|
||||
type: opts.type,
|
||||
room_id: opts.room,
|
||||
sender: opts.user,
|
||||
content: opts.content,
|
||||
event_id: "$" + Math.random() + "-" + Math.random(),
|
||||
origin_server_ts: opts.ts,
|
||||
};
|
||||
if (opts.skey) {
|
||||
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
|
||||
*/
|
||||
module.exports.mkPresence = function(opts) {
|
||||
if (!opts.user) {
|
||||
throw new Error("Missing user");
|
||||
}
|
||||
var event = {
|
||||
event_id: "$" + Math.random() + "-" + Math.random(),
|
||||
type: "m.presence",
|
||||
sender: opts.user,
|
||||
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.user The user ID for the event.
|
||||
* @param {string} opts.skey The other 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
|
||||
*/
|
||||
module.exports.mkMembership = function(opts) {
|
||||
opts.type = "m.room.member";
|
||||
if (!opts.skey) {
|
||||
opts.skey = 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 module.exports.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
|
||||
*/
|
||||
module.exports.mkMessage = function(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", opts);
|
||||
}
|
||||
opts.content = {
|
||||
msgtype: "m.text",
|
||||
body: opts.msg
|
||||
};
|
||||
return module.exports.mkEvent(opts);
|
||||
};
|
||||
|
||||
/**
|
||||
* make the test fail, with the given exception
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue