eslintify all test files

This commit is contained in:
Scott Nonnenberg 2018-11-02 11:02:53 -07:00
parent 884bc9333d
commit dbf0be2db5
44 changed files with 1469 additions and 1484 deletions

View file

@ -3,12 +3,6 @@ components/**
coverage/** coverage/**
dist/** dist/**
# these aren't ready yet, pulling files in one-by-one
libtextsecure/test/*.js
test/*.js
test/models/*.js
test/views/*.js
# Generated files # Generated files
js/components.js js/components.js
js/libtextsecure.js js/libtextsecure.js

View file

@ -0,0 +1,25 @@
{
"env": {
"browser": true,
"node": false,
"mocha": true,
},
"parserOptions": {
"sourceType": "script"
},
"rules": {
"strict": "off",
"more/no-then": "off",
},
"globals": {
"assert": true,
"assertEqualArrayBuffers": true,
"dcodeIO": true,
"getString": true,
"hexToArrayBuffer": true,
"MockServer": true,
"MockSocket": true,
"PROTO_ROOT": true,
"stringToArrayBuffer": true,
}
}

View file

@ -1,57 +1,59 @@
/* global mocha, chai, assert */
mocha.setup('bdd'); mocha.setup('bdd');
window.assert = chai.assert; window.assert = chai.assert;
window.PROTO_ROOT = '../../protos'; window.PROTO_ROOT = '../../protos';
(function() { const OriginalReporter = mocha._reporter;
const OriginalReporter = mocha._reporter;
const SauceReporter = function(runner) { const SauceReporter = runner => {
const failedTests = []; const failedTests = [];
runner.on('end', () => { runner.on('end', () => {
window.mochaResults = runner.stats; window.mochaResults = runner.stats;
window.mochaResults.reports = failedTests; window.mochaResults.reports = failedTests;
});
runner.on('fail', (test, err) => {
const flattenTitles = item => {
const titles = [];
while (item.parent.title) {
titles.push(item.parent.title);
// eslint-disable-next-line no-param-reassign
item = item.parent;
}
return titles.reverse();
};
failedTests.push({
name: test.title,
result: false,
message: err.message,
stack: err.stack,
titles: flattenTitles(test),
}); });
});
runner.on('fail', (test, err) => { // eslint-disable-next-line no-new
const flattenTitles = function(test) { new OriginalReporter(runner);
const titles = []; };
while (test.parent.title) {
titles.push(test.parent.title);
test = test.parent;
}
return titles.reverse();
};
failedTests.push({
name: test.title,
result: false,
message: err.message,
stack: err.stack,
titles: flattenTitles(test),
});
});
new OriginalReporter(runner); SauceReporter.prototype = OriginalReporter.prototype;
};
SauceReporter.prototype = OriginalReporter.prototype; mocha.reporter(SauceReporter);
mocha.reporter(SauceReporter);
})();
/* /*
* global helpers for tests * global helpers for tests
*/ */
function assertEqualArrayBuffers(ab1, ab2) { window.assertEqualArrayBuffers = (ab1, ab2) => {
assert.deepEqual(new Uint8Array(ab1), new Uint8Array(ab2)); assert.deepEqual(new Uint8Array(ab1), new Uint8Array(ab2));
} };
function hexToArrayBuffer(str) { window.hexToArrayBuffer = str => {
const ret = new ArrayBuffer(str.length / 2); const ret = new ArrayBuffer(str.length / 2);
const array = new Uint8Array(ret); const array = new Uint8Array(ret);
for (let i = 0; i < str.length / 2; i++) for (let i = 0; i < str.length / 2; i += 1)
array[i] = parseInt(str.substr(i * 2, 2), 16); array[i] = parseInt(str.substr(i * 2, 2), 16);
return ret; return ret;
} };
window.MockSocket.prototype.addEventListener = function() {}; window.MockSocket.prototype.addEventListener = () => null;

View file

@ -46,7 +46,7 @@ describe('AccountManager', () => {
return accountManager.cleanSignedPreKeys(); return accountManager.cleanSignedPreKeys();
}); });
it('eliminates confirmed keys over a week old, if more than three', () => { it('eliminates confirmed keys over a week old, if more than three', async () => {
const now = Date.now(); const now = Date.now();
signedPreKeys = [ signedPreKeys = [
{ {
@ -77,20 +77,19 @@ describe('AccountManager', () => {
]; ];
let count = 0; let count = 0;
window.textsecure.storage.protocol.removeSignedPreKey = function(keyId) { window.textsecure.storage.protocol.removeSignedPreKey = keyId => {
if (keyId !== 1 && keyId !== 4) { if (keyId !== 1 && keyId !== 4) {
throw new Error(`Wrong keys were eliminated! ${keyId}`); throw new Error(`Wrong keys were eliminated! ${keyId}`);
} }
count++; count += 1;
}; };
return accountManager.cleanSignedPreKeys().then(() => { await accountManager.cleanSignedPreKeys();
assert.strictEqual(count, 2); assert.strictEqual(count, 2);
});
}); });
it('keeps at least three unconfirmed keys if no confirmed', () => { it('keeps at least three unconfirmed keys if no confirmed', async () => {
const now = Date.now(); const now = Date.now();
signedPreKeys = [ signedPreKeys = [
{ {
@ -112,20 +111,19 @@ describe('AccountManager', () => {
]; ];
let count = 0; let count = 0;
window.textsecure.storage.protocol.removeSignedPreKey = function(keyId) { window.textsecure.storage.protocol.removeSignedPreKey = keyId => {
if (keyId !== 2) { if (keyId !== 2) {
throw new Error(`Wrong keys were eliminated! ${keyId}`); throw new Error(`Wrong keys were eliminated! ${keyId}`);
} }
count++; count += 1;
}; };
return accountManager.cleanSignedPreKeys().then(() => { await accountManager.cleanSignedPreKeys();
assert.strictEqual(count, 1); assert.strictEqual(count, 1);
});
}); });
it('if some confirmed keys, keeps unconfirmed to addd up to three total', () => { it('if some confirmed keys, keeps unconfirmed to addd up to three total', async () => {
const now = Date.now(); const now = Date.now();
signedPreKeys = [ signedPreKeys = [
{ {
@ -149,17 +147,16 @@ describe('AccountManager', () => {
]; ];
let count = 0; let count = 0;
window.textsecure.storage.protocol.removeSignedPreKey = function(keyId) { window.textsecure.storage.protocol.removeSignedPreKey = keyId => {
if (keyId !== 3) { if (keyId !== 3) {
throw new Error(`Wrong keys were eliminated! ${keyId}`); throw new Error(`Wrong keys were eliminated! ${keyId}`);
} }
count++; count += 1;
}; };
return accountManager.cleanSignedPreKeys().then(() => { await accountManager.cleanSignedPreKeys();
assert.strictEqual(count, 1); assert.strictEqual(count, 1);
});
}); });
}); });
}); });

View file

@ -1,9 +1,11 @@
/* global ContactBuffer, GroupBuffer, textsecure */
describe('ContactBuffer', () => { describe('ContactBuffer', () => {
function getTestBuffer() { function getTestBuffer() {
const buffer = new dcodeIO.ByteBuffer(); const buffer = new dcodeIO.ByteBuffer();
const avatarBuffer = new dcodeIO.ByteBuffer(); const avatarBuffer = new dcodeIO.ByteBuffer();
const avatarLen = 255; const avatarLen = 255;
for (var i = 0; i < avatarLen; ++i) { for (let i = 0; i < avatarLen; i += 1) {
avatarBuffer.writeUint8(i); avatarBuffer.writeUint8(i);
} }
avatarBuffer.limit = avatarBuffer.offset; avatarBuffer.limit = avatarBuffer.offset;
@ -15,7 +17,7 @@ describe('ContactBuffer', () => {
}); });
const contactInfoBuffer = contactInfo.encode().toArrayBuffer(); const contactInfoBuffer = contactInfo.encode().toArrayBuffer();
for (var i = 0; i < 3; ++i) { for (let i = 0; i < 3; i += 1) {
buffer.writeVarint32(contactInfoBuffer.byteLength); buffer.writeVarint32(contactInfoBuffer.byteLength);
buffer.append(contactInfoBuffer); buffer.append(contactInfoBuffer);
buffer.append(avatarBuffer.clone()); buffer.append(avatarBuffer.clone());
@ -32,14 +34,14 @@ describe('ContactBuffer', () => {
let contact = contactBuffer.next(); let contact = contactBuffer.next();
let count = 0; let count = 0;
while (contact !== undefined) { while (contact !== undefined) {
count++; count += 1;
assert.strictEqual(contact.name, 'Zero Cool'); assert.strictEqual(contact.name, 'Zero Cool');
assert.strictEqual(contact.number, '+10000000000'); assert.strictEqual(contact.number, '+10000000000');
assert.strictEqual(contact.avatar.contentType, 'image/jpeg'); assert.strictEqual(contact.avatar.contentType, 'image/jpeg');
assert.strictEqual(contact.avatar.length, 255); assert.strictEqual(contact.avatar.length, 255);
assert.strictEqual(contact.avatar.data.byteLength, 255); assert.strictEqual(contact.avatar.data.byteLength, 255);
const avatarBytes = new Uint8Array(contact.avatar.data); const avatarBytes = new Uint8Array(contact.avatar.data);
for (let j = 0; j < 255; ++j) { for (let j = 0; j < 255; j += 1) {
assert.strictEqual(avatarBytes[j], j); assert.strictEqual(avatarBytes[j], j);
} }
contact = contactBuffer.next(); contact = contactBuffer.next();
@ -53,7 +55,7 @@ describe('GroupBuffer', () => {
const buffer = new dcodeIO.ByteBuffer(); const buffer = new dcodeIO.ByteBuffer();
const avatarBuffer = new dcodeIO.ByteBuffer(); const avatarBuffer = new dcodeIO.ByteBuffer();
const avatarLen = 255; const avatarLen = 255;
for (var i = 0; i < avatarLen; ++i) { for (let i = 0; i < avatarLen; i += 1) {
avatarBuffer.writeUint8(i); avatarBuffer.writeUint8(i);
} }
avatarBuffer.limit = avatarBuffer.offset; avatarBuffer.limit = avatarBuffer.offset;
@ -66,7 +68,7 @@ describe('GroupBuffer', () => {
}); });
const groupInfoBuffer = groupInfo.encode().toArrayBuffer(); const groupInfoBuffer = groupInfo.encode().toArrayBuffer();
for (var i = 0; i < 3; ++i) { for (let i = 0; i < 3; i += 1) {
buffer.writeVarint32(groupInfoBuffer.byteLength); buffer.writeVarint32(groupInfoBuffer.byteLength);
buffer.append(groupInfoBuffer); buffer.append(groupInfoBuffer);
buffer.append(avatarBuffer.clone()); buffer.append(avatarBuffer.clone());
@ -83,7 +85,7 @@ describe('GroupBuffer', () => {
let group = groupBuffer.next(); let group = groupBuffer.next();
let count = 0; let count = 0;
while (group !== undefined) { while (group !== undefined) {
count++; count += 1;
assert.strictEqual(group.name, 'Hackers'); assert.strictEqual(group.name, 'Hackers');
assertEqualArrayBuffers( assertEqualArrayBuffers(
group.id.toArrayBuffer(), group.id.toArrayBuffer(),
@ -94,7 +96,7 @@ describe('GroupBuffer', () => {
assert.strictEqual(group.avatar.length, 255); assert.strictEqual(group.avatar.length, 255);
assert.strictEqual(group.avatar.data.byteLength, 255); assert.strictEqual(group.avatar.data.byteLength, 255);
const avatarBytes = new Uint8Array(group.avatar.data); const avatarBytes = new Uint8Array(group.avatar.data);
for (let j = 0; j < 255; ++j) { for (let j = 0; j < 255; j += 1) {
assert.strictEqual(avatarBytes[j], j); assert.strictEqual(avatarBytes[j], j);
} }
group = groupBuffer.next(); group = groupBuffer.next();

View file

@ -1,3 +1,5 @@
/* global libsignal, textsecure */
describe('encrypting and decrypting profile data', () => { describe('encrypting and decrypting profile data', () => {
const NAME_PADDED_LENGTH = 26; const NAME_PADDED_LENGTH = 26;
describe('encrypting and decrypting profile names', () => { describe('encrypting and decrypting profile names', () => {
@ -61,12 +63,12 @@ describe('encrypting and decrypting profile data', () => {
'This is an avatar' 'This is an avatar'
).toArrayBuffer(); ).toArrayBuffer();
const key = libsignal.crypto.getRandomBytes(32); const key = libsignal.crypto.getRandomBytes(32);
const bad_key = libsignal.crypto.getRandomBytes(32); const badKey = libsignal.crypto.getRandomBytes(32);
return textsecure.crypto.encryptProfile(buffer, key).then(encrypted => { return textsecure.crypto.encryptProfile(buffer, key).then(encrypted => {
assert(encrypted.byteLength === buffer.byteLength + 16 + 12); assert(encrypted.byteLength === buffer.byteLength + 16 + 12);
return textsecure.crypto return textsecure.crypto
.decryptProfile(encrypted, bad_key) .decryptProfile(encrypted, badKey)
.catch(error => { .catch(error => {
assert.strictEqual(error.name, 'ProfileDecryptError'); assert.strictEqual(error.name, 'ProfileDecryptError');
}); });

View file

@ -22,7 +22,7 @@ const fakeAPI = {
// sendMessages: fakeCall, // sendMessages: fakeCall,
setSignedPreKey: fakeCall, setSignedPreKey: fakeCall,
getKeysForNumber(number, deviceId) { getKeysForNumber(number) {
const res = getKeysForNumberMap[number]; const res = getKeysForNumberMap[number];
if (res !== undefined) { if (res !== undefined) {
delete getKeysForNumberMap[number]; delete getKeysForNumberMap[number];
@ -32,14 +32,14 @@ const fakeAPI = {
}, },
sendMessages(destination, messageArray) { sendMessages(destination, messageArray) {
for (i in messageArray) { for (let i = 0, max = messageArray.length; i < max; i += 1) {
const msg = messageArray[i]; const msg = messageArray[i];
if ( if (
(msg.type != 1 && msg.type != 3) || (msg.type !== 1 && msg.type !== 3) ||
msg.destinationDeviceId === undefined || msg.destinationDeviceId === undefined ||
msg.destinationRegistrationId === undefined || msg.destinationRegistrationId === undefined ||
msg.body === undefined || msg.body === undefined ||
msg.timestamp == undefined || msg.timestamp === undefined ||
msg.relay !== undefined || msg.relay !== undefined ||
msg.destination !== undefined msg.destination !== undefined
) )

View file

@ -1,4 +1,6 @@
describe('Key generation', function() { /* global libsignal, textsecure */
describe('Key generation', function thisNeeded() {
const count = 10; const count = 10;
this.timeout(count * 2000); this.timeout(count * 2000);
@ -60,7 +62,7 @@ describe('Key generation', function() {
result = res; result = res;
}); });
}); });
for (let i = 1; i <= count; i++) { for (let i = 1; i <= count; i += 1) {
itStoresPreKey(i); itStoresPreKey(i);
} }
itStoresSignedPreKey(1); itStoresSignedPreKey(1);
@ -68,12 +70,12 @@ describe('Key generation', function() {
it(`result contains ${count} preKeys`, () => { it(`result contains ${count} preKeys`, () => {
assert.isArray(result.preKeys); assert.isArray(result.preKeys);
assert.lengthOf(result.preKeys, count); assert.lengthOf(result.preKeys, count);
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i += 1) {
assert.isObject(result.preKeys[i]); assert.isObject(result.preKeys[i]);
} }
}); });
it('result contains the correct keyIds', () => { it('result contains the correct keyIds', () => {
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i += 1) {
assert.strictEqual(result.preKeys[i].keyId, i + 1); assert.strictEqual(result.preKeys[i].keyId, i + 1);
} }
}); });
@ -93,7 +95,7 @@ describe('Key generation', function() {
result = res; result = res;
}); });
}); });
for (let i = 1; i <= 2 * count; i++) { for (let i = 1; i <= 2 * count; i += 1) {
itStoresPreKey(i); itStoresPreKey(i);
} }
itStoresSignedPreKey(1); itStoresSignedPreKey(1);
@ -101,12 +103,12 @@ describe('Key generation', function() {
it(`result contains ${count} preKeys`, () => { it(`result contains ${count} preKeys`, () => {
assert.isArray(result.preKeys); assert.isArray(result.preKeys);
assert.lengthOf(result.preKeys, count); assert.lengthOf(result.preKeys, count);
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i += 1) {
assert.isObject(result.preKeys[i]); assert.isObject(result.preKeys[i]);
} }
}); });
it('result contains the correct keyIds', () => { it('result contains the correct keyIds', () => {
for (let i = 1; i <= count; i++) { for (let i = 1; i <= count; i += 1) {
assert.strictEqual(result.preKeys[i - 1].keyId, i + count); assert.strictEqual(result.preKeys[i - 1].keyId, i + count);
} }
}); });
@ -126,7 +128,7 @@ describe('Key generation', function() {
result = res; result = res;
}); });
}); });
for (let i = 1; i <= 3 * count; i++) { for (let i = 1; i <= 3 * count; i += 1) {
itStoresPreKey(i); itStoresPreKey(i);
} }
itStoresSignedPreKey(2); itStoresSignedPreKey(2);
@ -134,12 +136,12 @@ describe('Key generation', function() {
it(`result contains ${count} preKeys`, () => { it(`result contains ${count} preKeys`, () => {
assert.isArray(result.preKeys); assert.isArray(result.preKeys);
assert.lengthOf(result.preKeys, count); assert.lengthOf(result.preKeys, count);
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i += 1) {
assert.isObject(result.preKeys[i]); assert.isObject(result.preKeys[i]);
} }
}); });
it('result contains the correct keyIds', () => { it('result contains the correct keyIds', () => {
for (let i = 1; i <= count; i++) { for (let i = 1; i <= count; i += 1) {
assert.strictEqual(result.preKeys[i - 1].keyId, i + 2 * count); assert.strictEqual(result.preKeys[i - 1].keyId, i + 2 * count);
} }
}); });

View file

@ -12,7 +12,6 @@ describe('Helpers', () => {
describe('stringToArrayBuffer', () => { describe('stringToArrayBuffer', () => {
it('returns ArrayBuffer when passed string', () => { it('returns ArrayBuffer when passed string', () => {
const StaticArrayBufferProto = new ArrayBuffer().__proto__;
const anArrayBuffer = new ArrayBuffer(1); const anArrayBuffer = new ArrayBuffer(1);
const typedArray = new Uint8Array(anArrayBuffer); const typedArray = new Uint8Array(anArrayBuffer);
typedArray[0] = 'a'.charCodeAt(0); typedArray[0] = 'a'.charCodeAt(0);

View file

@ -36,10 +36,10 @@ SignalProtocolStore.prototype = {
isTrustedIdentity(identifier, identityKey) { isTrustedIdentity(identifier, identityKey) {
if (identifier === null || identifier === undefined) { if (identifier === null || identifier === undefined) {
throw new error('tried to check identity key for undefined/null key'); throw new Error('tried to check identity key for undefined/null key');
} }
if (!(identityKey instanceof ArrayBuffer)) { if (!(identityKey instanceof ArrayBuffer)) {
throw new error('Expected identityKey to be an ArrayBuffer'); throw new Error('Expected identityKey to be an ArrayBuffer');
} }
const trusted = this.get(`identityKey${identifier}`); const trusted = this.get(`identityKey${identifier}`);
if (trusted === undefined) { if (trusted === undefined) {
@ -96,9 +96,11 @@ SignalProtocolStore.prototype = {
loadSignedPreKeys() { loadSignedPreKeys() {
return new Promise(resolve => { return new Promise(resolve => {
const res = []; const res = [];
for (const i in this.store) { const keys = Object.keys(this.store);
if (i.startsWith('25519KeysignedKey')) { for (let i = 0, max = keys.length; i < max; i += 1) {
res.push(this.store[i]); const key = keys[i];
if (key.startsWith('25519KeysignedKey')) {
res.push(this.store[key]);
} }
} }
resolve(res); resolve(res);
@ -127,7 +129,9 @@ SignalProtocolStore.prototype = {
}, },
removeAllSessions(identifier) { removeAllSessions(identifier) {
return new Promise(resolve => { return new Promise(resolve => {
for (key in this.store) { const keys = Object.keys(this.store);
for (let i = 0, max = keys.length; i < max; i += 1) {
const key = keys[i];
if (key.match(RegExp(`^session${identifier.replace('+', '\\+')}.+`))) { if (key.match(RegExp(`^session${identifier.replace('+', '\\+')}.+`))) {
delete this.store[key]; delete this.store[key];
} }
@ -138,9 +142,11 @@ SignalProtocolStore.prototype = {
getDeviceIds(identifier) { getDeviceIds(identifier) {
return new Promise(resolve => { return new Promise(resolve => {
const deviceIds = []; const deviceIds = [];
for (key in this.store) { const keys = Object.keys(this.store);
for (let i = 0, max = keys.length; i < max; i += 1) {
const key = keys[i];
if (key.match(RegExp(`^session${identifier.replace('+', '\\+')}.+`))) { if (key.match(RegExp(`^session${identifier.replace('+', '\\+')}.+`))) {
deviceIds.push(parseInt(key.split('.')[1])); deviceIds.push(parseInt(key.split('.')[1], 10));
} }
} }
resolve(deviceIds); resolve(deviceIds);

View file

@ -1,9 +1,12 @@
/* global libsignal, textsecure, SignalProtocolStore */
describe('MessageReceiver', () => { describe('MessageReceiver', () => {
textsecure.storage.impl = new SignalProtocolStore(); textsecure.storage.impl = new SignalProtocolStore();
const WebSocket = window.WebSocket; const { WebSocket } = window;
const number = '+19999999999'; const number = '+19999999999';
const deviceId = 1; const deviceId = 1;
const signalingKey = libsignal.crypto.getRandomBytes(32 + 20); const signalingKey = libsignal.crypto.getRandomBytes(32 + 20);
before(() => { before(() => {
window.WebSocket = MockSocket; window.WebSocket = MockSocket;
textsecure.storage.user.setNumberAndDeviceId(number, deviceId, 'name'); textsecure.storage.user.setNumberAndDeviceId(number, deviceId, 'name');
@ -15,7 +18,6 @@ describe('MessageReceiver', () => {
}); });
describe('connecting', () => { describe('connecting', () => {
const blob = null;
const attrs = { const attrs = {
type: textsecure.protobuf.Envelope.Type.CIPHERTEXT, type: textsecure.protobuf.Envelope.Type.CIPHERTEXT,
source: number, source: number,
@ -29,14 +31,12 @@ describe('MessageReceiver', () => {
before(done => { before(done => {
const signal = new textsecure.protobuf.Envelope(attrs).toArrayBuffer(); const signal = new textsecure.protobuf.Envelope(attrs).toArrayBuffer();
const data = new textsecure.protobuf.DataMessage({ body: 'hello' });
const signaling_key = signalingKey; const aesKey = signalingKey.slice(0, 32);
const aes_key = signaling_key.slice(0, 32); const macKey = signalingKey.slice(32, 32 + 20);
const mac_key = signaling_key.slice(32, 32 + 20);
window.crypto.subtle window.crypto.subtle
.importKey('raw', aes_key, { name: 'AES-CBC' }, false, ['encrypt']) .importKey('raw', aesKey, { name: 'AES-CBC' }, false, ['encrypt'])
.then(key => { .then(key => {
const iv = libsignal.crypto.getRandomBytes(16); const iv = libsignal.crypto.getRandomBytes(16);
window.crypto.subtle window.crypto.subtle
@ -45,14 +45,14 @@ describe('MessageReceiver', () => {
window.crypto.subtle window.crypto.subtle
.importKey( .importKey(
'raw', 'raw',
mac_key, macKey,
{ name: 'HMAC', hash: { name: 'SHA-256' } }, { name: 'HMAC', hash: { name: 'SHA-256' } },
false, false,
['sign'] ['sign']
) )
.then(key => { .then(innerKey => {
window.crypto.subtle window.crypto.subtle
.sign({ name: 'HMAC', hash: 'SHA-256' }, key, signal) .sign({ name: 'HMAC', hash: 'SHA-256' }, innerKey, signal)
.then(mac => { .then(mac => {
const version = new Uint8Array([1]); const version = new Uint8Array([1]);
const message = dcodeIO.ByteBuffer.concat([ const message = dcodeIO.ByteBuffer.concat([
@ -82,14 +82,19 @@ describe('MessageReceiver', () => {
window.addEventListener('textsecure:message', ev => { window.addEventListener('textsecure:message', ev => {
const signal = ev.proto; const signal = ev.proto;
for (const key in attrs) { const keys = Object.keys(attrs);
for (let i = 0, max = keys.length; i < max; i += 1) {
const key = keys[i];
assert.strictEqual(attrs[key], signal[key]); assert.strictEqual(attrs[key], signal[key]);
} }
assert.strictEqual(signal.message.body, 'hello'); assert.strictEqual(signal.message.body, 'hello');
server.close(); mockServer.close();
done(); done();
}); });
const messageReceiver = new textsecure.MessageReceiver(
window.messageReceiver = new textsecure.MessageReceiver(
'username', 'username',
'password', 'password',
'signalingKey' 'signalingKey'

View file

@ -1,32 +1,34 @@
/* global textsecure */
describe('Protocol', () => { describe('Protocol', () => {
describe('Unencrypted PushMessageProto "decrypt"', () => { describe('Unencrypted PushMessageProto "decrypt"', () => {
// exclusive // exclusive
it('works', done => { it('works', done => {
localStorage.clear(); localStorage.clear();
const text_message = new textsecure.protobuf.DataMessage(); const textMessage = new textsecure.protobuf.DataMessage();
text_message.body = 'Hi Mom'; textMessage.body = 'Hi Mom';
const server_message = { const serverMessage = {
type: 4, // unencrypted type: 4, // unencrypted
source: '+19999999999', source: '+19999999999',
timestamp: 42, timestamp: 42,
message: text_message.encode(), message: textMessage.encode(),
}; };
return textsecure.protocol_wrapper return textsecure.protocol_wrapper
.handleEncryptedMessage( .handleEncryptedMessage(
server_message.source, serverMessage.source,
server_message.source_device, serverMessage.source_device,
server_message.type, serverMessage.type,
server_message.message serverMessage.message
) )
.then(message => { .then(message => {
assert.equal(message.body, text_message.body); assert.equal(message.body, textMessage.body);
assert.equal( assert.equal(
message.attachments.length, message.attachments.length,
text_message.attachments.length textMessage.attachments.length
); );
assert.equal(text_message.attachments.length, 0); assert.equal(textMessage.attachments.length, 0);
}) })
.then(done) .then(done)
.catch(done); .catch(done);

View file

@ -1,19 +1,20 @@
describe('Protocol Wrapper', function() { /* global libsignal, textsecure */
describe('Protocol Wrapper', function thisNeeded() {
const store = textsecure.storage.protocol; const store = textsecure.storage.protocol;
const identifier = '+5558675309'; const identifier = '+5558675309';
const another_identifier = '+5555590210';
let prekeys, identityKey, testKey;
this.timeout(5000); this.timeout(5000);
before(done => { before(done => {
localStorage.clear(); localStorage.clear();
libsignal.KeyHelper.generateIdentityKeyPair() libsignal.KeyHelper.generateIdentityKeyPair()
.then(identityKey => .then(key => textsecure.storage.protocol.saveIdentity(identifier, key))
textsecure.storage.protocol.saveIdentity(identifier, identityKey)
)
.then(() => { .then(() => {
done(); done();
}); });
}); });
describe('processPreKey', () => { describe('processPreKey', () => {
it('rejects if the identity key changes', () => { it('rejects if the identity key changes', () => {
const address = new libsignal.SignalProtocolAddress(identifier, 1); const address = new libsignal.SignalProtocolAddress(identifier, 1);

View file

@ -1,10 +1,11 @@
/* global libsignal, textsecure */
describe('SignalProtocolStore', () => { describe('SignalProtocolStore', () => {
before(() => { before(() => {
localStorage.clear(); localStorage.clear();
}); });
const store = textsecure.storage.protocol; const store = textsecure.storage.protocol;
const identifier = '+5558675309'; const identifier = '+5558675309';
const another_identifier = '+5555590210';
const identityKey = { const identityKey = {
pubKey: libsignal.crypto.getRandomBytes(33), pubKey: libsignal.crypto.getRandomBytes(33),
privKey: libsignal.crypto.getRandomBytes(32), privKey: libsignal.crypto.getRandomBytes(32),
@ -13,176 +14,121 @@ describe('SignalProtocolStore', () => {
pubKey: libsignal.crypto.getRandomBytes(33), pubKey: libsignal.crypto.getRandomBytes(33),
privKey: libsignal.crypto.getRandomBytes(32), privKey: libsignal.crypto.getRandomBytes(32),
}; };
it('retrieves my registration id', done => { it('retrieves my registration id', async () => {
store.put('registrationId', 1337); store.put('registrationId', 1337);
store
.getLocalRegistrationId() const reg = await store.getLocalRegistrationId();
.then(reg => { assert.strictEqual(reg, 1337);
assert.strictEqual(reg, 1337);
})
.then(done, done);
}); });
it('retrieves my identity key', done => { it('retrieves my identity key', async () => {
store.put('identityKey', identityKey); store.put('identityKey', identityKey);
store const key = await store.getIdentityKeyPair();
.getIdentityKeyPair() assertEqualArrayBuffers(key.pubKey, identityKey.pubKey);
.then(key => { assertEqualArrayBuffers(key.privKey, identityKey.privKey);
assertEqualArrayBuffers(key.pubKey, identityKey.pubKey); });
assertEqualArrayBuffers(key.privKey, identityKey.privKey); it('stores identity keys', async () => {
await store.saveIdentity(identifier, testKey.pubKey);
const key = await store.loadIdentityKey(identifier);
assertEqualArrayBuffers(key, testKey.pubKey);
});
it('returns whether a key is trusted', async () => {
const newIdentity = libsignal.crypto.getRandomBytes(33);
await store.saveIdentity(identifier, testKey.pubKey);
const trusted = await store.isTrustedIdentity(identifier, newIdentity);
if (trusted) {
throw new Error('Allowed to overwrite identity key');
}
});
it('returns whether a key is untrusted', async () => {
await store.saveIdentity(identifier, testKey.pubKey);
const trusted = await store.isTrustedIdentity(identifier, testKey.pubKey);
if (!trusted) {
throw new Error('Allowed to overwrite identity key');
}
});
it('stores prekeys', async () => {
await store.storePreKey(1, testKey);
const key = await store.loadPreKey(1);
assertEqualArrayBuffers(key.pubKey, testKey.pubKey);
assertEqualArrayBuffers(key.privKey, testKey.privKey);
});
it('deletes prekeys', async () => {
await store.storePreKey(2, testKey);
await store.removePreKey(2, testKey);
const key = await store.loadPreKey(2);
assert.isUndefined(key);
});
it('stores signed prekeys', async () => {
await store.storeSignedPreKey(3, testKey);
const key = await store.loadSignedPreKey(3);
assertEqualArrayBuffers(key.pubKey, testKey.pubKey);
assertEqualArrayBuffers(key.privKey, testKey.privKey);
});
it('deletes signed prekeys', async () => {
await store.storeSignedPreKey(4, testKey);
await store.removeSignedPreKey(4, testKey);
const key = await store.loadSignedPreKey(4);
assert.isUndefined(key);
});
it('stores sessions', async () => {
const testRecord = 'an opaque string';
const devices = [1, 2, 3].map(deviceId => [identifier, deviceId].join('.'));
await Promise.all(
devices.map(async encodedNumber => {
await store.storeSession(encodedNumber, testRecord + encodedNumber);
}) })
.then(done, done); );
const records = await Promise.all(
devices.map(store.loadSession.bind(store))
);
for (let i = 0, max = records.length; i < max; i += 1) {
assert.strictEqual(records[i], testRecord + devices[i]);
}
}); });
it('stores identity keys', done => { it('removes all sessions for a number', async () => {
store
.saveIdentity(identifier, testKey.pubKey)
.then(() =>
store.loadIdentityKey(identifier).then(key => {
assertEqualArrayBuffers(key, testKey.pubKey);
})
)
.then(done, done);
});
it('returns whether a key is trusted', done => {
const newIdentity = libsignal.crypto.getRandomBytes(33);
store.saveIdentity(identifier, testKey.pubKey).then(() => {
store
.isTrustedIdentity(identifier, newIdentity)
.then(trusted => {
if (trusted) {
done(new Error('Allowed to overwrite identity key'));
} else {
done();
}
})
.catch(done);
});
});
it('returns whether a key is untrusted', done => {
const newIdentity = libsignal.crypto.getRandomBytes(33);
store.saveIdentity(identifier, testKey.pubKey).then(() => {
store
.isTrustedIdentity(identifier, testKey.pubKey)
.then(trusted => {
if (trusted) {
done();
} else {
done(new Error('Allowed to overwrite identity key'));
}
})
.catch(done);
});
});
it('stores prekeys', done => {
store
.storePreKey(1, testKey)
.then(() =>
store.loadPreKey(1).then(key => {
assertEqualArrayBuffers(key.pubKey, testKey.pubKey);
assertEqualArrayBuffers(key.privKey, testKey.privKey);
})
)
.then(done, done);
});
it('deletes prekeys', done => {
before(done => {
store.storePreKey(2, testKey).then(done);
});
store
.removePreKey(2, testKey)
.then(() =>
store.loadPreKey(2).then(key => {
assert.isUndefined(key);
})
)
.then(done, done);
});
it('stores signed prekeys', done => {
store
.storeSignedPreKey(3, testKey)
.then(() =>
store.loadSignedPreKey(3).then(key => {
assertEqualArrayBuffers(key.pubKey, testKey.pubKey);
assertEqualArrayBuffers(key.privKey, testKey.privKey);
})
)
.then(done, done);
});
it('deletes signed prekeys', done => {
before(done => {
store.storeSignedPreKey(4, testKey).then(done);
});
store
.removeSignedPreKey(4, testKey)
.then(() =>
store.loadSignedPreKey(4).then(key => {
assert.isUndefined(key);
})
)
.then(done, done);
});
it('stores sessions', done => {
const testRecord = 'an opaque string'; const testRecord = 'an opaque string';
const devices = [1, 2, 3].map(deviceId => [identifier, deviceId].join('.')); const devices = [1, 2, 3].map(deviceId => [identifier, deviceId].join('.'));
let promise = Promise.resolve();
devices.forEach(encodedNumber => { await Promise.all(
promise = promise.then(() => devices.map(async encodedNumber => {
store.storeSession(encodedNumber, testRecord + encodedNumber) await store.storeSession(encodedNumber, testRecord + encodedNumber);
); })
}); );
promise
.then(() => await store.removeAllSessions(identifier);
Promise.all(devices.map(store.loadSession.bind(store))).then(
records => { const records = await Promise.all(
for (const i in records) { devices.map(store.loadSession.bind(store))
assert.strictEqual(records[i], testRecord + devices[i]); );
}
} for (let i = 0, max = records.length; i < max; i += 1) {
) assert.isUndefined(records[i]);
) }
.then(done, done);
}); });
it('removes all sessions for a number', done => { it('returns deviceIds for a number', async () => {
const testRecord = 'an opaque string'; const testRecord = 'an opaque string';
const devices = [1, 2, 3].map(deviceId => [identifier, deviceId].join('.')); const devices = [1, 2, 3].map(deviceId => [identifier, deviceId].join('.'));
let promise = Promise.resolve();
devices.forEach(encodedNumber => { await Promise.all(
promise = promise.then(() => devices.map(async encodedNumber => {
store.storeSession(encodedNumber, testRecord + encodedNumber) await store.storeSession(encodedNumber, testRecord + encodedNumber);
); })
}); );
promise
.then(() => const deviceIds = await store.getDeviceIds(identifier);
store.removeAllSessions(identifier).then(record => assert.sameMembers(deviceIds, [1, 2, 3]);
Promise.all(devices.map(store.loadSession.bind(store))).then(
records => {
for (const i in records) {
assert.isUndefined(records[i]);
}
}
)
)
)
.then(done, done);
}); });
it('returns deviceIds for a number', done => { it('returns empty array for a number with no device ids', async () => {
const testRecord = 'an opaque string'; const deviceIds = await store.getDeviceIds('foo');
const devices = [1, 2, 3].map(deviceId => [identifier, deviceId].join('.')); assert.sameMembers(deviceIds, []);
let promise = Promise.resolve();
devices.forEach(encodedNumber => {
promise = promise.then(() =>
store.storeSession(encodedNumber, testRecord + encodedNumber)
);
});
promise
.then(() =>
store.getDeviceIds(identifier).then(deviceIds => {
assert.sameMembers(deviceIds, [1, 2, 3]);
})
)
.then(done, done);
}); });
it('returns empty array for a number with no device ids', () =>
store.getDeviceIds('foo').then(deviceIds => {
assert.sameMembers(deviceIds, []);
}));
}); });

View file

@ -1,8 +1,8 @@
/* global textsecure */
describe('createTaskWithTimeout', () => { describe('createTaskWithTimeout', () => {
it('resolves when promise resolves', () => { it('resolves when promise resolves', () => {
const task = function() { const task = () => Promise.resolve('hi!');
return Promise.resolve('hi!');
};
const taskWithTimeout = textsecure.createTaskWithTimeout(task); const taskWithTimeout = textsecure.createTaskWithTimeout(task);
return taskWithTimeout().then(result => { return taskWithTimeout().then(result => {
@ -11,26 +11,22 @@ describe('createTaskWithTimeout', () => {
}); });
it('flows error from promise back', () => { it('flows error from promise back', () => {
const error = new Error('original'); const error = new Error('original');
const task = function() { const task = () => Promise.reject(error);
return Promise.reject(error);
};
const taskWithTimeout = textsecure.createTaskWithTimeout(task); const taskWithTimeout = textsecure.createTaskWithTimeout(task);
return taskWithTimeout().catch(flowedError => { return taskWithTimeout().catch(flowedError => {
assert.strictEqual(error, flowedError); assert.strictEqual(error, flowedError);
}); });
}); });
it('rejects if promise takes too long (this one logs error to console)', function() { it('rejects if promise takes too long (this one logs error to console)', () => {
const error = new Error('original');
let complete = false; let complete = false;
const task = function() { const task = () =>
return new Promise(resolve => { new Promise(resolve => {
setTimeout(() => { setTimeout(() => {
complete = true; complete = true;
resolve(); resolve();
}, 3000); }, 3000);
}); });
};
const taskWithTimeout = textsecure.createTaskWithTimeout(task, this.name, { const taskWithTimeout = textsecure.createTaskWithTimeout(task, this.name, {
timeout: 10, timeout: 10,
}); });
@ -45,29 +41,27 @@ describe('createTaskWithTimeout', () => {
); );
}); });
it('resolves if task returns something falsey', () => { it('resolves if task returns something falsey', () => {
const task = function() {}; const task = () => {};
const taskWithTimeout = textsecure.createTaskWithTimeout(task); const taskWithTimeout = textsecure.createTaskWithTimeout(task);
return taskWithTimeout(); return taskWithTimeout();
}); });
it('resolves if task returns a non-promise', () => { it('resolves if task returns a non-promise', () => {
const task = function() { const task = () => 'hi!';
return 'hi!';
};
const taskWithTimeout = textsecure.createTaskWithTimeout(task); const taskWithTimeout = textsecure.createTaskWithTimeout(task);
return taskWithTimeout().then(result => { return taskWithTimeout().then(result => {
assert.strictEqual(result, 'hi!'); assert.strictEqual(result, 'hi!');
}); });
}); });
it('rejects if task throws (and does not log about taking too long)', function() { it('rejects if task throws (and does not log about taking too long)', () => {
const error = new Error('Task is throwing!'); const error = new Error('Task is throwing!');
const task = function() { const task = () => {
throw error; throw error;
}; };
const taskWithTimeout = textsecure.createTaskWithTimeout(task, this.name, { const taskWithTimeout = textsecure.createTaskWithTimeout(task, this.name, {
timeout: 10, timeout: 10,
}); });
return taskWithTimeout().then( return taskWithTimeout().then(
result => { () => {
throw new Error('Overall task should reject!'); throw new Error('Overall task should reject!');
}, },
flowedError => { flowedError => {

View file

@ -1,208 +1,214 @@
(function() { /* global textsecure, WebSocketResource */
describe('WebSocket-Resource', () => {
describe('requests and responses', () => {
it('receives requests and sends responses', done => {
// mock socket
const request_id = '1';
const socket = {
send(data) {
const message = textsecure.protobuf.WebSocketMessage.decode(data);
assert.strictEqual(
message.type,
textsecure.protobuf.WebSocketMessage.Type.RESPONSE
);
assert.strictEqual(message.response.message, 'OK');
assert.strictEqual(message.response.status, 200);
assert.strictEqual(message.response.id.toString(), request_id);
done();
},
addEventListener() {},
};
// actual test describe('WebSocket-Resource', () => {
const resource = new WebSocketResource(socket, { describe('requests and responses', () => {
handleRequest(request) { it('receives requests and sends responses', done => {
assert.strictEqual(request.verb, 'PUT'); // mock socket
assert.strictEqual(request.path, '/some/path'); const requestId = '1';
assertEqualArrayBuffers( const socket = {
request.body.toArrayBuffer(), send(data) {
new Uint8Array([1, 2, 3]).buffer const message = textsecure.protobuf.WebSocketMessage.decode(data);
); assert.strictEqual(
request.respond(200, 'OK'); message.type,
}, textsecure.protobuf.WebSocketMessage.Type.RESPONSE
}); );
assert.strictEqual(message.response.message, 'OK');
assert.strictEqual(message.response.status, 200);
assert.strictEqual(message.response.id.toString(), requestId);
done();
},
addEventListener() {},
};
// mock socket request // actual test
socket.onmessage({ this.resource = new WebSocketResource(socket, {
data: new Blob([ handleRequest(request) {
new textsecure.protobuf.WebSocketMessage({ assert.strictEqual(request.verb, 'PUT');
type: textsecure.protobuf.WebSocketMessage.Type.REQUEST, assert.strictEqual(request.path, '/some/path');
request: { assertEqualArrayBuffers(
id: request_id, request.body.toArrayBuffer(),
verb: 'PUT', new Uint8Array([1, 2, 3]).buffer
path: '/some/path', );
body: new Uint8Array([1, 2, 3]).buffer, request.respond(200, 'OK');
}, },
})
.encode()
.toArrayBuffer(),
]),
});
}); });
it('sends requests and receives responses', done => { // mock socket request
// mock socket and request handler socket.onmessage({
let request_id; data: new Blob([
const socket = { new textsecure.protobuf.WebSocketMessage({
send(data) { type: textsecure.protobuf.WebSocketMessage.Type.REQUEST,
const message = textsecure.protobuf.WebSocketMessage.decode(data); request: {
assert.strictEqual( id: requestId,
message.type, verb: 'PUT',
textsecure.protobuf.WebSocketMessage.Type.REQUEST path: '/some/path',
); body: new Uint8Array([1, 2, 3]).buffer,
assert.strictEqual(message.request.verb, 'PUT'); },
assert.strictEqual(message.request.path, '/some/path'); })
assertEqualArrayBuffers( .encode()
message.request.body.toArrayBuffer(), .toArrayBuffer(),
new Uint8Array([1, 2, 3]).buffer ]),
);
request_id = message.request.id;
},
addEventListener() {},
};
// actual test
const resource = new WebSocketResource(socket);
resource.sendRequest({
verb: 'PUT',
path: '/some/path',
body: new Uint8Array([1, 2, 3]).buffer,
error: done,
success(message, status, request) {
assert.strictEqual(message, 'OK');
assert.strictEqual(status, 200);
done();
},
});
// mock socket response
socket.onmessage({
data: new Blob([
new textsecure.protobuf.WebSocketMessage({
type: textsecure.protobuf.WebSocketMessage.Type.RESPONSE,
response: { id: request_id, message: 'OK', status: 200 },
})
.encode()
.toArrayBuffer(),
]),
});
}); });
}); });
describe('close', () => { it('sends requests and receives responses', done => {
before(() => { // mock socket and request handler
window.WebSocket = MockSocket; let requestId;
}); const socket = {
after(() => { send(data) {
window.WebSocket = WebSocket; const message = textsecure.protobuf.WebSocketMessage.decode(data);
}); assert.strictEqual(
it('closes the connection', done => { message.type,
const mockServer = new MockServer('ws://localhost:8081'); textsecure.protobuf.WebSocketMessage.Type.REQUEST
mockServer.on('connection', server => { );
server.on('close', done); assert.strictEqual(message.request.verb, 'PUT');
}); assert.strictEqual(message.request.path, '/some/path');
const resource = new WebSocketResource( assertEqualArrayBuffers(
new WebSocket('ws://localhost:8081') message.request.body.toArrayBuffer(),
); new Uint8Array([1, 2, 3]).buffer
resource.close(); );
}); requestId = message.request.id;
}); },
addEventListener() {},
};
describe.skip('with a keepalive config', function() { // actual test
before(() => { const resource = new WebSocketResource(socket);
window.WebSocket = MockSocket; resource.sendRequest({
}); verb: 'PUT',
after(() => { path: '/some/path',
window.WebSocket = WebSocket; body: new Uint8Array([1, 2, 3]).buffer,
}); error: done,
this.timeout(60000); success(message, status) {
it('sends keepalives once a minute', done => { assert.strictEqual(message, 'OK');
const mockServer = new MockServer('ws://localhost:8081'); assert.strictEqual(status, 200);
mockServer.on('connection', server => { done();
server.on('message', data => { },
const message = textsecure.protobuf.WebSocketMessage.decode(data);
assert.strictEqual(
message.type,
textsecure.protobuf.WebSocketMessage.Type.REQUEST
);
assert.strictEqual(message.request.verb, 'GET');
assert.strictEqual(message.request.path, '/v1/keepalive');
server.close();
done();
});
});
new WebSocketResource(new WebSocket('ws://localhost:8081'), {
keepalive: { path: '/v1/keepalive' },
});
}); });
it('uses / as a default path', done => { // mock socket response
const mockServer = new MockServer('ws://localhost:8081'); socket.onmessage({
mockServer.on('connection', server => { data: new Blob([
server.on('message', data => { new textsecure.protobuf.WebSocketMessage({
const message = textsecure.protobuf.WebSocketMessage.decode(data); type: textsecure.protobuf.WebSocketMessage.Type.RESPONSE,
assert.strictEqual( response: { id: requestId, message: 'OK', status: 200 },
message.type, })
textsecure.protobuf.WebSocketMessage.Type.REQUEST .encode()
); .toArrayBuffer(),
assert.strictEqual(message.request.verb, 'GET'); ]),
assert.strictEqual(message.request.path, '/');
server.close();
done();
});
});
new WebSocketResource(new WebSocket('ws://localhost:8081'), {
keepalive: true,
});
});
it('optionally disconnects if no response', function(done) {
this.timeout(65000);
const mockServer = new MockServer('ws://localhost:8081');
const socket = new WebSocket('ws://localhost:8081');
mockServer.on('connection', server => {
server.on('close', done);
});
new WebSocketResource(socket, { keepalive: true });
});
it('allows resetting the keepalive timer', function(done) {
this.timeout(65000);
const mockServer = new MockServer('ws://localhost:8081');
const socket = new WebSocket('ws://localhost:8081');
const startTime = Date.now();
mockServer.on('connection', server => {
server.on('message', data => {
const message = textsecure.protobuf.WebSocketMessage.decode(data);
assert.strictEqual(
message.type,
textsecure.protobuf.WebSocketMessage.Type.REQUEST
);
assert.strictEqual(message.request.verb, 'GET');
assert.strictEqual(message.request.path, '/');
assert(
Date.now() > startTime + 60000,
'keepalive time should be longer than a minute'
);
server.close();
done();
});
});
const resource = new WebSocketResource(socket, { keepalive: true });
setTimeout(() => {
resource.resetKeepAliveTimer();
}, 5000);
}); });
}); });
}); });
})();
describe('close', () => {
before(() => {
window.WebSocket = MockSocket;
});
after(() => {
window.WebSocket = WebSocket;
});
it('closes the connection', done => {
const mockServer = new MockServer('ws://localhost:8081');
mockServer.on('connection', server => {
server.on('close', done);
});
const resource = new WebSocketResource(
new WebSocket('ws://localhost:8081')
);
resource.close();
});
});
describe.skip('with a keepalive config', function thisNeeded() {
before(() => {
window.WebSocket = MockSocket;
});
after(() => {
window.WebSocket = WebSocket;
});
this.timeout(60000);
it('sends keepalives once a minute', done => {
const mockServer = new MockServer('ws://localhost:8081');
mockServer.on('connection', server => {
server.on('message', data => {
const message = textsecure.protobuf.WebSocketMessage.decode(data);
assert.strictEqual(
message.type,
textsecure.protobuf.WebSocketMessage.Type.REQUEST
);
assert.strictEqual(message.request.verb, 'GET');
assert.strictEqual(message.request.path, '/v1/keepalive');
server.close();
done();
});
});
this.resource = new WebSocketResource(
new WebSocket('ws://loc1alhost:8081'),
{
keepalive: { path: '/v1/keepalive' },
}
);
});
it('uses / as a default path', done => {
const mockServer = new MockServer('ws://localhost:8081');
mockServer.on('connection', server => {
server.on('message', data => {
const message = textsecure.protobuf.WebSocketMessage.decode(data);
assert.strictEqual(
message.type,
textsecure.protobuf.WebSocketMessage.Type.REQUEST
);
assert.strictEqual(message.request.verb, 'GET');
assert.strictEqual(message.request.path, '/');
server.close();
done();
});
});
this.resource = new WebSocketResource(
new WebSocket('ws://localhost:8081'),
{
keepalive: true,
}
);
});
it('optionally disconnects if no response', function thisNeeded1(done) {
this.timeout(65000);
const mockServer = new MockServer('ws://localhost:8081');
const socket = new WebSocket('ws://localhost:8081');
mockServer.on('connection', server => {
server.on('close', done);
});
this.resource = new WebSocketResource(socket, { keepalive: true });
});
it('allows resetting the keepalive timer', function thisNeeded2(done) {
this.timeout(65000);
const mockServer = new MockServer('ws://localhost:8081');
const socket = new WebSocket('ws://localhost:8081');
const startTime = Date.now();
mockServer.on('connection', server => {
server.on('message', data => {
const message = textsecure.protobuf.WebSocketMessage.decode(data);
assert.strictEqual(
message.type,
textsecure.protobuf.WebSocketMessage.Type.REQUEST
);
assert.strictEqual(message.request.verb, 'GET');
assert.strictEqual(message.request.path, '/');
assert(
Date.now() > startTime + 60000,
'keepalive time should be longer than a minute'
);
server.close();
done();
});
});
const resource = new WebSocketResource(socket, { keepalive: true });
setTimeout(() => {
resource.resetKeepAliveTimer();
}, 5000);
});
});
});

View file

@ -1,3 +1,5 @@
/* global TextSecureWebSocket */
describe('TextSecureWebSocket', () => { describe('TextSecureWebSocket', () => {
const RealWebSocket = window.WebSocket; const RealWebSocket = window.WebSocket;
before(() => { before(() => {
@ -13,19 +15,19 @@ describe('TextSecureWebSocket', () => {
server.close(); server.close();
done(); done();
}); });
var socket = new TextSecureWebSocket('ws://localhost:8080'); const socket = new TextSecureWebSocket('ws://localhost:8080');
}); });
it('sends and receives', done => { it('sends and receives', done => {
const mockServer = new MockServer('ws://localhost:8080'); const mockServer = new MockServer('ws://localhost:8080');
mockServer.on('connection', server => { mockServer.on('connection', server => {
server.on('message', data => { server.on('message', () => {
server.send('ack'); server.send('ack');
server.close(); server.close();
}); });
}); });
const socket = new TextSecureWebSocket('ws://localhost:8080'); const socket = new TextSecureWebSocket('ws://localhost:8080');
socket.onmessage = function(response) { socket.onmessage = response => {
assert.strictEqual(response.data, 'ack'); assert.strictEqual(response.data, 'ack');
socket.close(); socket.close();
done(); done();
@ -40,20 +42,20 @@ describe('TextSecureWebSocket', () => {
server.close(); server.close();
socket.close(); socket.close();
}); });
var socket = new TextSecureWebSocket('ws://localhost:8082'); const socket = new TextSecureWebSocket('ws://localhost:8082');
socket.onclose = function() { socket.onclose = () => {
assert.strictEqual(socket.getStatus(), WebSocket.CLOSING); assert.strictEqual(socket.getStatus(), WebSocket.CLOSING);
done(); done();
}; };
}); });
it('reconnects', function(done) { it('reconnects', function thisNeeded(done) {
this.timeout(60000); this.timeout(60000);
const mockServer = new MockServer('ws://localhost:8082'); const mockServer = new MockServer('ws://localhost:8082');
const socket = new TextSecureWebSocket('ws://localhost:8082'); const socket = new TextSecureWebSocket('ws://localhost:8082');
socket.onclose = function() { socket.onclose = () => {
const mockServer = new MockServer('ws://localhost:8082'); const secondServer = new MockServer('ws://localhost:8082');
mockServer.on('connection', server => { secondServer.on('connection', server => {
socket.close(); socket.close();
server.close(); server.close();
done(); done();

View file

@ -8,6 +8,15 @@ module.exports = {
globals: { globals: {
assert: true, assert: true,
assertEqualArrayBuffers: true,
clearDatabase: true,
dcodeIO: true,
getString: true,
hexToArrayBuffer: true,
MockServer: true,
MockSocket: true,
PROTO_ROOT: true,
stringToArrayBuffer: true,
}, },
parserOptions: { parserOptions: {
@ -25,5 +34,8 @@ module.exports = {
// We want to keep each test structured the same, even if its contents are tiny // We want to keep each test structured the same, even if its contents are tiny
'arrow-body-style': 'off', 'arrow-body-style': 'off',
strict: 'off',
'more/no-then': 'off',
}, },
}; };

View file

@ -1,43 +1,45 @@
/* global chai, Whisper */
mocha.setup('bdd'); mocha.setup('bdd');
window.assert = chai.assert; window.assert = chai.assert;
window.PROTO_ROOT = '../protos'; window.PROTO_ROOT = '../protos';
(function() { const OriginalReporter = mocha._reporter;
var OriginalReporter = mocha._reporter;
var SauceReporter = function(runner) { const SauceReporter = runner => {
var failedTests = []; const failedTests = [];
runner.on('end', function() { runner.on('end', () => {
window.mochaResults = runner.stats; window.mochaResults = runner.stats;
window.mochaResults.reports = failedTests; window.mochaResults.reports = failedTests;
});
runner.on('fail', (test, err) => {
const flattenTitles = item => {
const titles = [];
while (item.parent.title) {
titles.push(item.parent.title);
// eslint-disable-next-line no-param-reassign
item = item.parent;
}
return titles.reverse();
};
failedTests.push({
name: test.title,
result: false,
message: err.message,
stack: err.stack,
titles: flattenTitles(test),
}); });
});
runner.on('fail', function(test, err) { // eslint-disable-next-line no-new
var flattenTitles = function(test) { new OriginalReporter(runner);
var titles = []; };
while (test.parent.title) {
titles.push(test.parent.title);
test = test.parent;
}
return titles.reverse();
};
failedTests.push({
name: test.title,
result: false,
message: err.message,
stack: err.stack,
titles: flattenTitles(test),
});
});
new OriginalReporter(runner); SauceReporter.prototype = OriginalReporter.prototype;
};
SauceReporter.prototype = OriginalReporter.prototype; mocha.reporter(SauceReporter);
mocha.reporter(SauceReporter);
})();
// Override the database id. // Override the database id.
window.Whisper = window.Whisper || {}; window.Whisper = window.Whisper || {};
@ -47,22 +49,22 @@ Whisper.Database.id = 'test';
/* /*
* global helpers for tests * global helpers for tests
*/ */
function assertEqualArrayBuffers(ab1, ab2) { window.assertEqualArrayBuffers = (ab1, ab2) => {
assert.deepEqual(new Uint8Array(ab1), new Uint8Array(ab2)); assert.deepEqual(new Uint8Array(ab1), new Uint8Array(ab2));
} };
function hexToArrayBuffer(str) { window.hexToArrayBuffer = str => {
var ret = new ArrayBuffer(str.length / 2); const ret = new ArrayBuffer(str.length / 2);
var array = new Uint8Array(ret); const array = new Uint8Array(ret);
for (var i = 0; i < str.length / 2; i++) { for (let i = 0; i < str.length / 2; i += 1) {
array[i] = parseInt(str.substr(i * 2, 2), 16); array[i] = parseInt(str.substr(i * 2, 2), 16);
} }
return ret; return ret;
} };
function deleteDatabase() { function deleteIndexedDB() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
var idbReq = indexedDB.deleteDatabase('test'); const idbReq = indexedDB.deleteIndexedDB('test');
idbReq.onsuccess = resolve; idbReq.onsuccess = resolve;
idbReq.error = reject; idbReq.error = reject;
}); });
@ -70,10 +72,10 @@ function deleteDatabase() {
/* Delete the database before running any tests */ /* Delete the database before running any tests */
before(async () => { before(async () => {
await deleteDatabase(); await deleteIndexedDB();
await window.Signal.Data.removeAll(); await window.Signal.Data.removeAll();
}); });
async function clearDatabase() { window.clearDatabase = async () => {
await window.Signal.Data.removeAll(); await window.Signal.Data.removeAll();
} };

View file

@ -4,7 +4,7 @@
/* global textsecure: false */ /* global textsecure: false */
/* global _: false */ /* global _: false */
/* eslint-disable no-unreachable */ /* eslint-disable no-unreachable, no-console */
'use strict'; 'use strict';

View file

@ -1,8 +1,10 @@
/* global Whisper */
'use strict'; 'use strict';
describe('ConversationController', function() { describe('ConversationController', () => {
it('sorts conversations based on timestamp then by intl-friendly title', function() { it('sorts conversations based on timestamp then by intl-friendly title', () => {
var collection = window.getInboxCollection(); const collection = window.getInboxCollection();
collection.reset([]); collection.reset([]);
collection.add( collection.add(

View file

@ -1,3 +1,5 @@
/* global Signal, textsecure */
'use strict'; 'use strict';
describe('Crypto', () => { describe('Crypto', () => {
@ -16,39 +18,36 @@ describe('Crypto', () => {
describe('symmetric encryption', () => { describe('symmetric encryption', () => {
it('roundtrips', async () => { it('roundtrips', async () => {
var message = 'this is my message'; const message = 'this is my message';
var plaintext = new dcodeIO.ByteBuffer.wrap( const plaintext = dcodeIO.ByteBuffer.wrap(
message, message,
'binary' 'binary'
).toArrayBuffer(); ).toArrayBuffer();
var key = textsecure.crypto.getRandomBytes(32); const key = textsecure.crypto.getRandomBytes(32);
var encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext); const encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext);
var decrypted = await Signal.Crypto.decryptSymmetric(key, encrypted); const decrypted = await Signal.Crypto.decryptSymmetric(key, encrypted);
var equal = Signal.Crypto.constantTimeEqual(plaintext, decrypted); const equal = Signal.Crypto.constantTimeEqual(plaintext, decrypted);
if (!equal) { if (!equal) {
throw new Error('The output and input did not match!'); throw new Error('The output and input did not match!');
} }
}); });
it('roundtrip fails if nonce is modified', async () => { it('roundtrip fails if nonce is modified', async () => {
var message = 'this is my message'; const message = 'this is my message';
var plaintext = new dcodeIO.ByteBuffer.wrap( const plaintext = dcodeIO.ByteBuffer.wrap(
message, message,
'binary' 'binary'
).toArrayBuffer(); ).toArrayBuffer();
var key = textsecure.crypto.getRandomBytes(32); const key = textsecure.crypto.getRandomBytes(32);
var encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext); const encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext);
var uintArray = new Uint8Array(encrypted); const uintArray = new Uint8Array(encrypted);
uintArray[2] = 9; uintArray[2] = 9;
try { try {
var decrypted = await Signal.Crypto.decryptSymmetric( await Signal.Crypto.decryptSymmetric(key, uintArray.buffer);
key,
uintArray.buffer
);
} catch (error) { } catch (error) {
assert.strictEqual( assert.strictEqual(
error.message, error.message,
@ -61,22 +60,19 @@ describe('Crypto', () => {
}); });
it('roundtrip fails if mac is modified', async () => { it('roundtrip fails if mac is modified', async () => {
var message = 'this is my message'; const message = 'this is my message';
var plaintext = new dcodeIO.ByteBuffer.wrap( const plaintext = dcodeIO.ByteBuffer.wrap(
message, message,
'binary' 'binary'
).toArrayBuffer(); ).toArrayBuffer();
var key = textsecure.crypto.getRandomBytes(32); const key = textsecure.crypto.getRandomBytes(32);
var encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext); const encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext);
var uintArray = new Uint8Array(encrypted); const uintArray = new Uint8Array(encrypted);
uintArray[uintArray.length - 3] = 9; uintArray[uintArray.length - 3] = 9;
try { try {
var decrypted = await Signal.Crypto.decryptSymmetric( await Signal.Crypto.decryptSymmetric(key, uintArray.buffer);
key,
uintArray.buffer
);
} catch (error) { } catch (error) {
assert.strictEqual( assert.strictEqual(
error.message, error.message,
@ -89,22 +85,19 @@ describe('Crypto', () => {
}); });
it('roundtrip fails if encrypted contents are modified', async () => { it('roundtrip fails if encrypted contents are modified', async () => {
var message = 'this is my message'; const message = 'this is my message';
var plaintext = new dcodeIO.ByteBuffer.wrap( const plaintext = dcodeIO.ByteBuffer.wrap(
message, message,
'binary' 'binary'
).toArrayBuffer(); ).toArrayBuffer();
var key = textsecure.crypto.getRandomBytes(32); const key = textsecure.crypto.getRandomBytes(32);
var encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext); const encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext);
var uintArray = new Uint8Array(encrypted); const uintArray = new Uint8Array(encrypted);
uintArray[35] = 9; uintArray[35] = 9;
try { try {
var decrypted = await Signal.Crypto.decryptSymmetric( await Signal.Crypto.decryptSymmetric(key, uintArray.buffer);
key,
uintArray.buffer
);
} catch (error) { } catch (error) {
assert.strictEqual( assert.strictEqual(
error.message, error.message,

View file

@ -1,30 +1,32 @@
/* global Whisper */
'use strict'; 'use strict';
describe('Database', function() { describe('Database', () => {
describe('handleDOMException', function() { describe('handleDOMException', () => {
it('handles null, still calls reject', function() { it('handles null, still calls reject', () => {
var called = 0; let called = 0;
var reject = function() { const reject = () => {
called += 1; called += 1;
}; };
var error = null; const error = null;
var prefix = 'something'; const prefix = 'something';
Whisper.Database.handleDOMException(prefix, error, reject); Whisper.Database.handleDOMException(prefix, error, reject);
assert.strictEqual(called, 1); assert.strictEqual(called, 1);
}); });
it('handles object code and message', function() { it('handles object code and message', () => {
var called = 0; let called = 0;
var reject = function() { const reject = () => {
called += 1; called += 1;
}; };
var error = { const error = {
code: 4, code: 4,
message: 'some cryptic error', message: 'some cryptic error',
}; };
var prefix = 'something'; const prefix = 'something';
Whisper.Database.handleDOMException(prefix, error, reject); Whisper.Database.handleDOMException(prefix, error, reject);

View file

@ -1,12 +1,14 @@
Whisper.Fixtures = function() { /* global Whisper */
var VERA_ID = '+13016886524'; // nsa
var NESTOR_ID = '+17034820623'; // cia
var MASHA_ID = '+441242221491'; // gchq
var FRED_ID = '+14155537400'; // fbi sf
var MICHEL_ID = '+12024561111'; // twh
var now = Date.now(); Whisper.Fixtures = () => {
var conversationCollection = new Whisper.ConversationCollection([ const VERA_ID = '+13016886524'; // nsa
const NESTOR_ID = '+17034820623'; // cia
const MASHA_ID = '+441242221491'; // gchq
const FRED_ID = '+14155537400'; // fbi sf
const MICHEL_ID = '+12024561111'; // twh
const now = Date.now();
const conversationCollection = new Whisper.ConversationCollection([
{ {
name: 'Vera Zasulich', name: 'Vera Zasulich',
id: VERA_ID, id: VERA_ID,
@ -51,7 +53,7 @@ Whisper.Fixtures = function() {
}, },
]); ]);
var Vera = conversationCollection.get(VERA_ID); const Vera = conversationCollection.get(VERA_ID);
Vera.messageCollection.add([ Vera.messageCollection.add([
{ {
conversationId: VERA_ID, conversationId: VERA_ID,
@ -62,7 +64,7 @@ Whisper.Fixtures = function() {
}, },
]); ]);
var Nestor = conversationCollection.get(NESTOR_ID); const Nestor = conversationCollection.get(NESTOR_ID);
Nestor.messageCollection.add([ Nestor.messageCollection.add([
{ {
conversationId: NESTOR_ID, conversationId: NESTOR_ID,
@ -73,7 +75,7 @@ Whisper.Fixtures = function() {
}, },
]); ]);
var Fred = conversationCollection.get(FRED_ID); const Fred = conversationCollection.get(FRED_ID);
Fred.messageCollection.add([ Fred.messageCollection.add([
{ {
conversationId: FRED_ID, conversationId: FRED_ID,
@ -85,7 +87,7 @@ Whisper.Fixtures = function() {
}, },
]); ]);
var Michel = conversationCollection.get(MICHEL_ID); const Michel = conversationCollection.get(MICHEL_ID);
Michel.messageCollection.add([ Michel.messageCollection.add([
{ {
conversationId: MICHEL_ID, conversationId: MICHEL_ID,
@ -118,7 +120,7 @@ Whisper.Fixtures = function() {
}, },
]); ]);
var Masha = conversationCollection.get(MASHA_ID); const Masha = conversationCollection.get(MASHA_ID);
Masha.messageCollection.add( Masha.messageCollection.add(
[ [
{ {
@ -150,7 +152,7 @@ Whisper.Fixtures = function() {
body: "I can't wait to try it!", body: "I can't wait to try it!",
unread: 1, unread: 1,
}, },
].map(function(m) { ].map((m) => {
return { return {
conversationId: MASHA_ID, conversationId: MASHA_ID,
type: m.type, type: m.type,
@ -165,7 +167,7 @@ Whisper.Fixtures = function() {
}) })
); );
var group = conversationCollection.add({ const group = conversationCollection.add({
name: '📖 Book Club', name: '📖 Book Club',
type: 'group', type: 'group',
active_at: now - 100000, active_at: now - 100000,
@ -210,7 +212,7 @@ Whisper.Fixtures = function() {
delivered_to: [MICHEL_ID, FRED_ID], delivered_to: [MICHEL_ID, FRED_ID],
sent_to: [NESTOR_ID], sent_to: [NESTOR_ID],
}, },
].map(function(m) { ].map((m) => {
return Object.assign({}, m, { return Object.assign({}, m, {
conversationId: group.id, conversationId: group.id,
sent_at: m.date, sent_at: m.date,
@ -221,16 +223,16 @@ Whisper.Fixtures = function() {
); );
function dataURItoBlob(dataURI) { function dataURItoBlob(dataURI) {
var binary = atob(dataURI.split(',')[1]); const binary = atob(dataURI.split(',')[1]);
var array = []; const array = [];
for (var i = 0; i < binary.length; i++) { for (let i = 0; i < binary.length; i += 1) {
array.push(binary.charCodeAt(i)); array.push(binary.charCodeAt(i));
} }
return new Uint8Array(array).buffer; return new Uint8Array(array).buffer;
} }
conversationCollection.saveAll = function() { conversationCollection.saveAll = function thisNeeded() {
return Promise.all( Promise.all(
this.map(async (convo) => { this.map(async (convo) => {
await window.Signal.Data.saveConversation(convo.attributes, { await window.Signal.Data.saveConversation(convo.attributes, {
Conversation: Whisper.Conversation, Conversation: Whisper.Conversation,
@ -239,7 +241,7 @@ Whisper.Fixtures = function() {
await Promise.all( await Promise.all(
convo.messageCollection.map(async (message) => { convo.messageCollection.map(async (message) => {
const id = await window.Signal.Data.saveMessage(message.attributes, { const id = await window.Signal.Data.saveMessage(message.attributes, {
Message: Whisper.Message Message: Whisper.Message,
}); });
message.set({ id }); message.set({ id });
}) })

View file

@ -1,11 +1,11 @@
/* global $, ConversationController, textsecure, Whisper */
'use strict'; 'use strict';
describe('Fixtures', function() { describe('Fixtures', () => {
before(async function() { before(async () => {
// NetworkStatusView checks this method every five seconds while showing // NetworkStatusView checks this method every five seconds while showing
window.getSocketStatus = function() { window.getSocketStatus = () => WebSocket.OPEN;
return WebSocket.OPEN;
};
await clearDatabase(); await clearDatabase();
await textsecure.storage.user.setNumberAndDeviceId( await textsecure.storage.user.setNumberAndDeviceId(
@ -26,11 +26,11 @@ describe('Fixtures', function() {
ConversationController.reset(); ConversationController.reset();
await ConversationController.load(); await ConversationController.load();
var view = new Whisper.InboxView({ window: window }); let view = new Whisper.InboxView({ window });
view.onEmpty(); view.onEmpty();
view.$el.prependTo($('#render-light-theme')); view.$el.prependTo($('#render-light-theme'));
var view = new Whisper.InboxView({ window: window }); view = new Whisper.InboxView({ window });
view.$el.removeClass('light-theme').addClass('dark-theme'); view.$el.removeClass('light-theme').addClass('dark-theme');
view.onEmpty(); view.onEmpty();
view.$el.prependTo($('#render-dark-theme')); view.$el.prependTo($('#render-dark-theme'));

View file

@ -1,16 +1,18 @@
describe('i18n', function() { /* global i18n */
describe('i18n', function() {
it('returns empty string for unknown string', function() { describe('i18n', () => {
describe('i18n', () => {
it('returns empty string for unknown string', () => {
assert.strictEqual(i18n('random'), ''); assert.strictEqual(i18n('random'), '');
}); });
it('returns message for given string', function() { it('returns message for given string', () => {
assert.equal(i18n('reportIssue'), 'Report an issue'); assert.equal(i18n('reportIssue'), 'Report an issue');
}); });
it('returns message with single substitution', function() { it('returns message with single substitution', () => {
const actual = i18n('attemptingReconnection', 5); const actual = i18n('attemptingReconnection', 5);
assert.equal(actual, 'Attempting reconnect in 5 seconds'); assert.equal(actual, 'Attempting reconnect in 5 seconds');
}); });
it('returns message with multiple substitutions', function() { it('returns message with multiple substitutions', () => {
const actual = i18n('theyChangedTheTimer', ['Someone', '5 minutes']); const actual = i18n('theyChangedTheTimer', ['Someone', '5 minutes']);
assert.equal( assert.equal(
actual, actual,
@ -19,8 +21,8 @@ describe('i18n', function() {
}); });
}); });
describe('getLocale', function() { describe('getLocale', () => {
it('returns a string with length two or greater', function() { it('returns a string with length two or greater', () => {
const locale = i18n.getLocale(); const locale = i18n.getLocale();
assert.isAtLeast(locale.trim().length, 2); assert.isAtLeast(locale.trim().length, 2);
}); });

View file

@ -1,26 +1,28 @@
describe('KeyChangeListener', function() { /* global ConversationController, libsignal, SignalProtocolStore, Whisper */
var phoneNumberWithKeyChange = '+13016886524'; // nsa
var address = new libsignal.SignalProtocolAddress( describe('KeyChangeListener', () => {
const phoneNumberWithKeyChange = '+13016886524'; // nsa
const address = new libsignal.SignalProtocolAddress(
phoneNumberWithKeyChange, phoneNumberWithKeyChange,
1 1
); );
var oldKey = libsignal.crypto.getRandomBytes(33); const oldKey = libsignal.crypto.getRandomBytes(33);
var newKey = libsignal.crypto.getRandomBytes(33); const newKey = libsignal.crypto.getRandomBytes(33);
var store; let store;
beforeEach(function() { beforeEach(() => {
store = new SignalProtocolStore(); store = new SignalProtocolStore();
Whisper.KeyChangeListener.init(store); Whisper.KeyChangeListener.init(store);
return store.saveIdentity(address.toString(), oldKey); return store.saveIdentity(address.toString(), oldKey);
}); });
afterEach(function() { afterEach(() => {
return store.removeIdentityKey(phoneNumberWithKeyChange); return store.removeIdentityKey(phoneNumberWithKeyChange);
}); });
describe('When we have a conversation with this contact', function() { describe('When we have a conversation with this contact', () => {
let convo; let convo;
before(async function() { before(async () => {
convo = ConversationController.dangerouslyCreateAndAdd({ convo = ConversationController.dangerouslyCreateAndAdd({
id: phoneNumberWithKeyChange, id: phoneNumberWithKeyChange,
type: 'private', type: 'private',
@ -30,12 +32,12 @@ describe('KeyChangeListener', function() {
}); });
}); });
after(async function() { after(async () => {
await convo.destroyMessages(); await convo.destroyMessages();
await window.Signal.Data.saveConversation(convo.id); await window.Signal.Data.saveConversation(convo.id);
}); });
it('generates a key change notice in the private conversation with this contact', function(done) { it('generates a key change notice in the private conversation with this contact', done => {
convo.once('newmessage', async () => { convo.once('newmessage', async () => {
await convo.fetchMessages(); await convo.fetchMessages();
const message = convo.messageCollection.at(0); const message = convo.messageCollection.at(0);
@ -46,10 +48,9 @@ describe('KeyChangeListener', function() {
}); });
}); });
describe('When we have a group with this contact', function() { describe('When we have a group with this contact', () => {
let convo; let convo;
before(async function() { before(async () => {
console.log('Creating group with contact', phoneNumberWithKeyChange);
convo = ConversationController.dangerouslyCreateAndAdd({ convo = ConversationController.dangerouslyCreateAndAdd({
id: 'groupId', id: 'groupId',
type: 'group', type: 'group',
@ -59,12 +60,12 @@ describe('KeyChangeListener', function() {
Conversation: Whisper.Conversation, Conversation: Whisper.Conversation,
}); });
}); });
after(async function() { after(async () => {
await convo.destroyMessages(); await convo.destroyMessages();
await window.Signal.Data.saveConversation(convo.id); await window.Signal.Data.saveConversation(convo.id);
}); });
it('generates a key change notice in the group conversation with this contact', function(done) { it('generates a key change notice in the group conversation with this contact', done => {
convo.once('newmessage', async () => { convo.once('newmessage', async () => {
await convo.fetchMessages(); await convo.fetchMessages();
const message = convo.messageCollection.at(0); const message = convo.messageCollection.at(0);

View file

@ -1,25 +1,26 @@
(function() { /* global libphonenumber */
'use strict';
describe('libphonenumber util', function() { 'use strict';
describe('parseNumber', function() {
it('numbers with + are valid without providing regionCode', function() { describe('libphonenumber util', () => {
var result = libphonenumber.util.parseNumber('+14155555555'); describe('parseNumber', () => {
it('numbers with + are valid without providing regionCode', () => {
const result = libphonenumber.util.parseNumber('+14155555555');
assert.isTrue(result.isValidNumber);
assert.strictEqual(result.nationalNumber, '4155555555');
assert.strictEqual(result.e164, '+14155555555');
assert.strictEqual(result.regionCode, 'US');
assert.strictEqual(result.countryCode, '1');
});
it('variant numbers with the right regionCode are valid', () => {
['4155555555', '14155555555', '+14155555555'].forEach(number => {
const result = libphonenumber.util.parseNumber(number, 'US');
assert.isTrue(result.isValidNumber); assert.isTrue(result.isValidNumber);
assert.strictEqual(result.nationalNumber, '4155555555'); assert.strictEqual(result.nationalNumber, '4155555555');
assert.strictEqual(result.e164, '+14155555555'); assert.strictEqual(result.e164, '+14155555555');
assert.strictEqual(result.regionCode, 'US'); assert.strictEqual(result.regionCode, 'US');
assert.strictEqual(result.countryCode, '1'); assert.strictEqual(result.countryCode, '1');
}); });
it('variant numbers with the right regionCode are valid', function() {
['4155555555', '14155555555', '+14155555555'].forEach(function(number) {
var result = libphonenumber.util.parseNumber(number, 'US');
assert.isTrue(result.isValidNumber);
assert.strictEqual(result.nationalNumber, '4155555555');
assert.strictEqual(result.e164, '+14155555555');
assert.strictEqual(result.regionCode, 'US');
assert.strictEqual(result.countryCode, '1');
});
});
}); });
}); });
})(); });

View file

@ -1,226 +1,213 @@
(function() { /* global storage, textsecure, Whisper */
'use strict';
var attributes = { 'use strict';
type: 'outgoing',
body: 'hi', describe('ConversationCollection', () => {
conversationId: 'foo',
attachments: [],
timestamp: new Date().getTime(),
};
var conversation_attributes = {
type: 'private',
id: '+14155555555',
};
textsecure.messaging = new textsecure.MessageSender(''); textsecure.messaging = new textsecure.MessageSender('');
describe('ConversationCollection', function() { before(clearDatabase);
before(clearDatabase); after(clearDatabase);
after(clearDatabase);
it('should be ordered newest to oldest', function() { it('should be ordered newest to oldest', () => {
var conversations = new Whisper.ConversationCollection(); const conversations = new Whisper.ConversationCollection();
// Timestamps // Timestamps
var today = new Date(); const today = new Date();
var tomorrow = new Date(); const tomorrow = new Date();
tomorrow.setDate(today.getDate() + 1); tomorrow.setDate(today.getDate() + 1);
// Add convos // Add convos
conversations.add({ timestamp: today }); conversations.add({ timestamp: today });
conversations.add({ timestamp: tomorrow }); conversations.add({ timestamp: tomorrow });
var models = conversations.models; const { models } = conversations;
var firstTimestamp = models[0].get('timestamp').getTime(); const firstTimestamp = models[0].get('timestamp').getTime();
var secondTimestamp = models[1].get('timestamp').getTime(); const secondTimestamp = models[1].get('timestamp').getTime();
// Compare timestamps // Compare timestamps
assert(firstTimestamp > secondTimestamp); assert(firstTimestamp > secondTimestamp);
});
});
describe('Conversation', () => {
const attributes = { type: 'private', id: '+18085555555' };
before(async () => {
const convo = new Whisper.ConversationCollection().add(attributes);
await window.Signal.Data.saveConversation(convo.attributes, {
Conversation: Whisper.Conversation,
});
const message = convo.messageCollection.add({
body: 'hello world',
conversationId: convo.id,
type: 'outgoing',
sent_at: Date.now(),
received_at: Date.now(),
});
await window.Signal.Data.saveMessage(message.attributes, {
Message: Whisper.Message,
}); });
}); });
after(clearDatabase);
describe('Conversation', function() { it('sorts its contacts in an intl-friendly way', () => {
var attributes = { type: 'private', id: '+18085555555' }; const convo = new Whisper.Conversation({ id: '+18085555555' });
before(async () => { convo.contactCollection.add(
var convo = new Whisper.ConversationCollection().add(attributes); new Whisper.Conversation({
await window.Signal.Data.saveConversation(convo.attributes, { name: 'C',
Conversation: Whisper.Conversation, })
}); );
convo.contactCollection.add(
new Whisper.Conversation({
name: 'B',
})
);
convo.contactCollection.add(
new Whisper.Conversation({
name: 'Á',
})
);
var message = convo.messageCollection.add({ assert.strictEqual(convo.contactCollection.at('0').get('name'), 'Á');
body: 'hello world', assert.strictEqual(convo.contactCollection.at('1').get('name'), 'B');
conversationId: convo.id, assert.strictEqual(convo.contactCollection.at('2').get('name'), 'C');
type: 'outgoing',
sent_at: Date.now(),
received_at: Date.now(),
});
await window.Signal.Data.saveMessage(message.attributes, {
Message: Whisper.Message,
});
});
after(clearDatabase);
it('sorts its contacts in an intl-friendly way', function() {
var convo = new Whisper.Conversation({ id: '+18085555555' });
convo.contactCollection.add(
new Whisper.Conversation({
name: 'C',
})
);
convo.contactCollection.add(
new Whisper.Conversation({
name: 'B',
})
);
convo.contactCollection.add(
new Whisper.Conversation({
name: 'Á',
})
);
assert.strictEqual(convo.contactCollection.at('0').get('name'), 'Á');
assert.strictEqual(convo.contactCollection.at('1').get('name'), 'B');
assert.strictEqual(convo.contactCollection.at('2').get('name'), 'C');
});
it('contains its own messages', async function() {
var convo = new Whisper.ConversationCollection().add({
id: '+18085555555',
});
await convo.fetchMessages();
assert.notEqual(convo.messageCollection.length, 0);
});
it('contains only its own messages', async function() {
var convo = new Whisper.ConversationCollection().add({
id: '+18085556666',
});
await convo.fetchMessages();
assert.strictEqual(convo.messageCollection.length, 0);
});
it('adds conversation to message collection upon leaving group', async function() {
var convo = new Whisper.ConversationCollection().add({
type: 'group',
id: 'a random string',
});
await convo.leaveGroup();
assert.notEqual(convo.messageCollection.length, 0);
});
it('has a title', function() {
var convos = new Whisper.ConversationCollection();
var convo = convos.add(attributes);
assert.equal(convo.getTitle(), '+1 808-555-5555');
convo = convos.add({ type: '' });
assert.equal(convo.getTitle(), 'Unknown group');
convo = convos.add({ name: 'name' });
assert.equal(convo.getTitle(), 'name');
});
it('returns the number', function() {
var convos = new Whisper.ConversationCollection();
var convo = convos.add(attributes);
assert.equal(convo.getNumber(), '+1 808-555-5555');
convo = convos.add({ type: '' });
assert.equal(convo.getNumber(), '');
});
it('has an avatar', function() {
var convo = new Whisper.ConversationCollection().add(attributes);
var avatar = convo.getAvatar();
assert.property(avatar, 'content');
assert.property(avatar, 'color');
});
describe('phone number parsing', function() {
after(function() {
storage.remove('regionCode');
});
function checkAttributes(number) {
var convo = new Whisper.ConversationCollection().add({
type: 'private',
});
convo.set('id', number);
convo.validate(convo.attributes);
assert.strictEqual(convo.get('id'), '+14155555555', number);
}
it('processes the phone number when validating', function() {
['+14155555555'].forEach(checkAttributes);
});
it('defaults to the local regionCode', function() {
storage.put('regionCode', 'US');
['14155555555', '4155555555'].forEach(checkAttributes);
});
it('works with common phone number formats', function() {
storage.put('regionCode', 'US');
[
'415 555 5555',
'415-555-5555',
'(415) 555 5555',
'(415) 555-5555',
'1 415 555 5555',
'1 415-555-5555',
'1 (415) 555 5555',
'1 (415) 555-5555',
'+1 415 555 5555',
'+1 415-555-5555',
'+1 (415) 555 5555',
'+1 (415) 555-5555',
].forEach(checkAttributes);
});
});
}); });
describe('Conversation search', function() { it('contains its own messages', async () => {
let convo; const convo = new Whisper.ConversationCollection().add({
id: '+18085555555',
});
await convo.fetchMessages();
assert.notEqual(convo.messageCollection.length, 0);
});
beforeEach(async function() { it('contains only its own messages', async () => {
convo = new Whisper.ConversationCollection().add({ const convo = new Whisper.ConversationCollection().add({
id: '+14155555555', id: '+18085556666',
});
await convo.fetchMessages();
assert.strictEqual(convo.messageCollection.length, 0);
});
it('adds conversation to message collection upon leaving group', async () => {
const convo = new Whisper.ConversationCollection().add({
type: 'group',
id: 'a random string',
});
await convo.leaveGroup();
assert.notEqual(convo.messageCollection.length, 0);
});
it('has a title', () => {
const convos = new Whisper.ConversationCollection();
let convo = convos.add(attributes);
assert.equal(convo.getTitle(), '+1 808-555-5555');
convo = convos.add({ type: '' });
assert.equal(convo.getTitle(), 'Unknown group');
convo = convos.add({ name: 'name' });
assert.equal(convo.getTitle(), 'name');
});
it('returns the number', () => {
const convos = new Whisper.ConversationCollection();
let convo = convos.add(attributes);
assert.equal(convo.getNumber(), '+1 808-555-5555');
convo = convos.add({ type: '' });
assert.equal(convo.getNumber(), '');
});
it('has an avatar', () => {
const convo = new Whisper.ConversationCollection().add(attributes);
const avatar = convo.getAvatar();
assert.property(avatar, 'content');
assert.property(avatar, 'color');
});
describe('phone number parsing', () => {
after(() => {
storage.remove('regionCode');
});
function checkAttributes(number) {
const convo = new Whisper.ConversationCollection().add({
type: 'private', type: 'private',
name: 'John Doe',
}); });
await window.Signal.Data.saveConversation(convo.attributes, { convo.set('id', number);
Conversation: Whisper.Conversation, convo.validate(convo.attributes);
}); assert.strictEqual(convo.get('id'), '+14155555555', number);
});
afterEach(clearDatabase);
async function testSearch(queries) {
await Promise.all(
queries.map(async function(query) {
var collection = new Whisper.ConversationCollection();
await collection.search(query);
assert.isDefined(
collection.get(convo.id),
'no result for "' + query + '"'
);
})
);
} }
it('matches by partial phone number', function() { it('processes the phone number when validating', () => {
return testSearch([ ['+14155555555'].forEach(checkAttributes);
'1',
'4',
'+1',
'415',
'4155',
'4155555555',
'14155555555',
'+14155555555',
]);
}); });
it('matches by name', function() { it('defaults to the local regionCode', () => {
return testSearch(['John', 'Doe', 'john', 'doe', 'John Doe', 'john doe']); storage.put('regionCode', 'US');
['14155555555', '4155555555'].forEach(checkAttributes);
}); });
it('does not match +', async function() { it('works with common phone number formats', () => {
var collection = new Whisper.ConversationCollection(); storage.put('regionCode', 'US');
await collection.search('+'); [
assert.isUndefined(collection.get(convo.id), 'got result for "+"'); '415 555 5555',
'415-555-5555',
'(415) 555 5555',
'(415) 555-5555',
'1 415 555 5555',
'1 415-555-5555',
'1 (415) 555 5555',
'1 (415) 555-5555',
'+1 415 555 5555',
'+1 415-555-5555',
'+1 (415) 555 5555',
'+1 (415) 555-5555',
].forEach(checkAttributes);
}); });
}); });
})(); });
describe('Conversation search', () => {
let convo;
beforeEach(async () => {
convo = new Whisper.ConversationCollection().add({
id: '+14155555555',
type: 'private',
name: 'John Doe',
});
await window.Signal.Data.saveConversation(convo.attributes, {
Conversation: Whisper.Conversation,
});
});
afterEach(clearDatabase);
async function testSearch(queries) {
await Promise.all(
queries.map(async query => {
const collection = new Whisper.ConversationCollection();
await collection.search(query);
assert.isDefined(collection.get(convo.id), `no result for "${query}"`);
})
);
}
it('matches by partial phone number', () => {
return testSearch([
'1',
'4',
'+1',
'415',
'4155',
'4155555555',
'14155555555',
'+14155555555',
]);
});
it('matches by name', () => {
return testSearch(['John', 'Doe', 'john', 'doe', 'John Doe', 'john doe']);
});
it('does not match +', async () => {
const collection = new Whisper.ConversationCollection();
await collection.search('+');
assert.isUndefined(collection.get(convo.id), 'got result for "+"');
});
});

View file

@ -1,159 +1,145 @@
(function() { /* global ConversationController, i18n, Whisper */
'use strict';
var attributes = { 'use strict';
type: 'outgoing',
body: 'hi',
conversationId: 'foo',
attachments: [],
received_at: new Date().getTime(),
};
var attachment = { const attributes = {
data: 'datasaurus', type: 'outgoing',
contentType: 'plain/text', body: 'hi',
}; conversationId: 'foo',
attachments: [],
received_at: new Date().getTime(),
};
var source = '+14155555555'; const source = '+14155555555';
describe('MessageCollection', function() { describe('MessageCollection', () => {
before(async function() { before(async () => {
await clearDatabase(); await clearDatabase();
ConversationController.reset(); ConversationController.reset();
await ConversationController.load(); await ConversationController.load();
});
after(function() {
return clearDatabase();
});
it('gets outgoing contact', function() {
var messages = new Whisper.MessageCollection();
var message = messages.add(attributes);
message.getContact();
});
it('gets incoming contact', function() {
var messages = new Whisper.MessageCollection();
var message = messages.add({
type: 'incoming',
source: source,
});
message.getContact();
});
it('adds without saving', function() {
var messages = new Whisper.MessageCollection();
var message = messages.add(attributes);
assert.notEqual(messages.length, 0);
var messages = new Whisper.MessageCollection();
assert.strictEqual(messages.length, 0);
});
it('should be ordered oldest to newest', function() {
var messages = new Whisper.MessageCollection();
// Timestamps
var today = new Date();
var tomorrow = new Date();
tomorrow.setDate(today.getDate() + 1);
// Add threads
messages.add({ received_at: today });
messages.add({ received_at: tomorrow });
var models = messages.models;
var firstTimestamp = models[0].get('received_at').getTime();
var secondTimestamp = models[1].get('received_at').getTime();
// Compare timestamps
assert(firstTimestamp < secondTimestamp);
});
it('checks if is incoming message', function() {
var messages = new Whisper.MessageCollection();
var message = messages.add(attributes);
assert.notOk(message.isIncoming());
message = messages.add({ type: 'incoming' });
assert.ok(message.isIncoming());
});
it('checks if is outgoing message', function() {
var messages = new Whisper.MessageCollection();
var message = messages.add(attributes);
assert.ok(message.isOutgoing());
message = messages.add({ type: 'incoming' });
assert.notOk(message.isOutgoing());
});
it('checks if is group update', function() {
var messages = new Whisper.MessageCollection();
var message = messages.add(attributes);
assert.notOk(message.isGroupUpdate());
message = messages.add({ group_update: true });
assert.ok(message.isGroupUpdate());
});
it('returns an accurate description', function() {
var messages = new Whisper.MessageCollection();
var message = messages.add(attributes);
assert.equal(
message.getDescription(),
'hi',
'If no group updates or end session flags, return message body.'
);
message = messages.add({ group_update: { left: 'Alice' } });
assert.equal(
message.getDescription(),
'Alice left the group',
'Notes one person leaving the group.'
);
message = messages.add({ group_update: { name: 'blerg' } });
assert.equal(
message.getDescription(),
"Title is now 'blerg'",
'Returns a single notice if only group_updates.name changes.'
);
message = messages.add({ group_update: { joined: ['Bob'] } });
assert.equal(
message.getDescription(),
'Bob joined the group',
'Returns a single notice if only group_updates.joined changes.'
);
message = messages.add({
group_update: { joined: ['Bob', 'Alice', 'Eve'] },
});
assert.equal(
message.getDescription(),
'Bob, Alice, Eve joined the group',
'Notes when >1 person joins the group.'
);
message = messages.add({
group_update: { joined: ['Bob'], name: 'blerg' },
});
assert.equal(
message.getDescription(),
"Title is now 'blerg', Bob joined the group",
'Notes when there are multiple changes to group_updates properties.'
);
message = messages.add({ flags: true });
assert.equal(message.getDescription(), i18n('sessionEnded'));
});
it('checks if it is end of the session', function() {
var messages = new Whisper.MessageCollection();
var message = messages.add(attributes);
assert.notOk(message.isEndSession());
message = messages.add({ flags: true });
assert.ok(message.isEndSession());
});
}); });
})(); after(() => {
return clearDatabase();
});
it('gets outgoing contact', () => {
const messages = new Whisper.MessageCollection();
const message = messages.add(attributes);
message.getContact();
});
it('gets incoming contact', () => {
const messages = new Whisper.MessageCollection();
const message = messages.add({
type: 'incoming',
source,
});
message.getContact();
});
it('should be ordered oldest to newest', () => {
const messages = new Whisper.MessageCollection();
// Timestamps
const today = new Date();
const tomorrow = new Date();
tomorrow.setDate(today.getDate() + 1);
// Add threads
messages.add({ received_at: today });
messages.add({ received_at: tomorrow });
const { models } = messages;
const firstTimestamp = models[0].get('received_at').getTime();
const secondTimestamp = models[1].get('received_at').getTime();
// Compare timestamps
assert(firstTimestamp < secondTimestamp);
});
it('checks if is incoming message', () => {
const messages = new Whisper.MessageCollection();
let message = messages.add(attributes);
assert.notOk(message.isIncoming());
message = messages.add({ type: 'incoming' });
assert.ok(message.isIncoming());
});
it('checks if is outgoing message', () => {
const messages = new Whisper.MessageCollection();
let message = messages.add(attributes);
assert.ok(message.isOutgoing());
message = messages.add({ type: 'incoming' });
assert.notOk(message.isOutgoing());
});
it('checks if is group update', () => {
const messages = new Whisper.MessageCollection();
let message = messages.add(attributes);
assert.notOk(message.isGroupUpdate());
message = messages.add({ group_update: true });
assert.ok(message.isGroupUpdate());
});
it('returns an accurate description', () => {
const messages = new Whisper.MessageCollection();
let message = messages.add(attributes);
assert.equal(
message.getDescription(),
'hi',
'If no group updates or end session flags, return message body.'
);
message = messages.add({ group_update: { left: 'Alice' } });
assert.equal(
message.getDescription(),
'Alice left the group',
'Notes one person leaving the group.'
);
message = messages.add({ group_update: { name: 'blerg' } });
assert.equal(
message.getDescription(),
"Title is now 'blerg'",
'Returns a single notice if only group_updates.name changes.'
);
message = messages.add({ group_update: { joined: ['Bob'] } });
assert.equal(
message.getDescription(),
'Bob joined the group',
'Returns a single notice if only group_updates.joined changes.'
);
message = messages.add({
group_update: { joined: ['Bob', 'Alice', 'Eve'] },
});
assert.equal(
message.getDescription(),
'Bob, Alice, Eve joined the group',
'Notes when >1 person joins the group.'
);
message = messages.add({
group_update: { joined: ['Bob'], name: 'blerg' },
});
assert.equal(
message.getDescription(),
"Title is now 'blerg', Bob joined the group",
'Notes when there are multiple changes to group_updates properties.'
);
message = messages.add({ flags: true });
assert.equal(message.getDescription(), i18n('sessionEnded'));
});
it('checks if it is end of the session', () => {
const messages = new Whisper.MessageCollection();
let message = messages.add(attributes);
assert.notOk(message.isEndSession());
message = messages.add({ flags: true });
assert.ok(message.isEndSession());
});
});

View file

@ -1,28 +1,32 @@
/* global Backbone */
'use strict'; 'use strict';
describe('ReliableTrigger', function() { describe('ReliableTrigger', () => {
describe('trigger', function() { describe('trigger', () => {
var Model, model; let Model;
let model;
before(function() { before(() => {
Model = Backbone.Model; ({ Model } = Backbone);
}); });
beforeEach(function() { beforeEach(() => {
model = new Model(); model = new Model();
}); });
it('returns successfully if this._events is falsey', function() { it('returns successfully if this._events is falsey', () => {
model._events = null; model._events = null;
model.trigger('click'); model.trigger('click');
}); });
it('handles map of events to trigger', function() { it('handles map of events to trigger', () => {
var a = 0, let a = 0;
b = 0; let b = 0;
model.on('a', function(arg) {
model.on('a', arg => {
a = arg; a = arg;
}); });
model.on('b', function(arg) { model.on('b', arg => {
b = arg; b = arg;
}); });
@ -34,13 +38,14 @@ describe('ReliableTrigger', function() {
assert.strictEqual(a, 1); assert.strictEqual(a, 1);
assert.strictEqual(b, 2); assert.strictEqual(b, 2);
}); });
it('handles space-separated list of events to trigger', function() { it('handles space-separated list of events to trigger', () => {
var a = false, let a = false;
b = false; let b = false;
model.on('a', function() {
model.on('a', () => {
a = true; a = true;
}); });
model.on('b', function() { model.on('b', () => {
b = true; b = true;
}); });
@ -49,9 +54,9 @@ describe('ReliableTrigger', function() {
assert.strictEqual(a, true); assert.strictEqual(a, true);
assert.strictEqual(b, true); assert.strictEqual(b, true);
}); });
it('calls all clients registered for "all" event', function() { it('calls all clients registered for "all" event', () => {
var count = 0; let count = 0;
model.on('all', function() { model.on('all', () => {
count += 1; count += 1;
}); });
@ -60,13 +65,14 @@ describe('ReliableTrigger', function() {
assert.strictEqual(count, 2); assert.strictEqual(count, 2);
}); });
it('calls all clients registered for target event', function() { it('calls all clients registered for target event', () => {
var a = false, let a = false;
b = false; let b = false;
model.on('event', function() {
model.on('event', () => {
a = true; a = true;
}); });
model.on('event', function() { model.on('event', () => {
b = true; b = true;
}); });
@ -75,14 +81,15 @@ describe('ReliableTrigger', function() {
assert.strictEqual(a, true); assert.strictEqual(a, true);
assert.strictEqual(b, true); assert.strictEqual(b, true);
}); });
it('successfully returns and calls all clients even if first failed', function() { it('successfully returns and calls all clients even if first failed', () => {
var a = false, let a = false;
b = false; let b = false;
model.on('event', function() {
model.on('event', () => {
a = true; a = true;
throw new Error('a is set, but exception is thrown'); throw new Error('a is set, but exception is thrown');
}); });
model.on('event', function() { model.on('event', () => {
b = true; b = true;
}); });
@ -91,9 +98,9 @@ describe('ReliableTrigger', function() {
assert.strictEqual(a, true); assert.strictEqual(a, true);
assert.strictEqual(b, true); assert.strictEqual(b, true);
}); });
it('calls clients with no args', function() { it('calls clients with no args', () => {
var called = false; let called = false;
model.on('event', function() { model.on('event', () => {
called = true; called = true;
}); });
@ -101,20 +108,20 @@ describe('ReliableTrigger', function() {
assert.strictEqual(called, true); assert.strictEqual(called, true);
}); });
it('calls clients with 1 arg', function() { it('calls clients with 1 arg', () => {
var args; let args;
model.on('event', function() { model.on('event', (...eventArgs) => {
args = arguments; args = eventArgs;
}); });
model.trigger('event', 1); model.trigger('event', 1);
assert.strictEqual(args[0], 1); assert.strictEqual(args[0], 1);
}); });
it('calls clients with 2 args', function() { it('calls clients with 2 args', () => {
var args; let args;
model.on('event', function() { model.on('event', (...eventArgs) => {
args = arguments; args = eventArgs;
}); });
model.trigger('event', 1, 2); model.trigger('event', 1, 2);
@ -122,10 +129,10 @@ describe('ReliableTrigger', function() {
assert.strictEqual(args[0], 1); assert.strictEqual(args[0], 1);
assert.strictEqual(args[1], 2); assert.strictEqual(args[1], 2);
}); });
it('calls clients with 3 args', function() { it('calls clients with 3 args', () => {
var args; let args;
model.on('event', function() { model.on('event', (...eventArgs) => {
args = arguments; args = eventArgs;
}); });
model.trigger('event', 1, 2, 3); model.trigger('event', 1, 2, 3);
@ -134,10 +141,10 @@ describe('ReliableTrigger', function() {
assert.strictEqual(args[1], 2); assert.strictEqual(args[1], 2);
assert.strictEqual(args[2], 3); assert.strictEqual(args[2], 3);
}); });
it('calls clients with 4+ args', function() { it('calls clients with 4+ args', () => {
var args; let args;
model.on('event', function() { model.on('event', (...eventArgs) => {
args = arguments; args = eventArgs;
}); });
model.trigger('event', 1, 2, 3, 4); model.trigger('event', 1, 2, 3, 4);

View file

@ -1,5 +1,5 @@
describe('spellChecker', function() { describe('spellChecker', () => {
it('should work', function() { it('should work', () => {
assert(window.spellChecker.spellCheck('correct')); assert(window.spellChecker.spellCheck('correct'));
assert(!window.spellChecker.spellCheck('fhqwgads')); assert(!window.spellChecker.spellCheck('fhqwgads'));
}); });

View file

@ -1,18 +1,14 @@
/* global _, textsecure, libsignal, storage */
'use strict'; 'use strict';
describe('SignalProtocolStore', function() { describe('SignalProtocolStore', () => {
var number = '+5558675309'; const number = '+5558675309';
var store; let store;
var identityKey; let identityKey;
var testKey; let testKey;
function wrapDeferred(deferred) { before(done => {
return new Promise(function(resolve, reject) {
return deferred.then(resolve, reject);
});
}
before(function(done) {
store = textsecure.storage.protocol; store = textsecure.storage.protocol;
identityKey = { identityKey = {
pubKey: libsignal.crypto.getRandomBytes(33), pubKey: libsignal.crypto.getRandomBytes(33),
@ -28,59 +24,59 @@ describe('SignalProtocolStore', function() {
storage.fetch().then(done, done); storage.fetch().then(done, done);
}); });
describe('getLocalRegistrationId', function() { describe('getLocalRegistrationId', () => {
it('retrieves my registration id', async function() { it('retrieves my registration id', async () => {
const id = await store.getLocalRegistrationId(); const id = await store.getLocalRegistrationId();
assert.strictEqual(id, 1337); assert.strictEqual(id, 1337);
}); });
}); });
describe('getIdentityKeyPair', function() { describe('getIdentityKeyPair', () => {
it('retrieves my identity key', async function() { it('retrieves my identity key', async () => {
const key = await store.getIdentityKeyPair(); const key = await store.getIdentityKeyPair();
assertEqualArrayBuffers(key.pubKey, identityKey.pubKey); assertEqualArrayBuffers(key.pubKey, identityKey.pubKey);
assertEqualArrayBuffers(key.privKey, identityKey.privKey); assertEqualArrayBuffers(key.privKey, identityKey.privKey);
}); });
}); });
describe('saveIdentity', function() { describe('saveIdentity', () => {
var address = new libsignal.SignalProtocolAddress(number, 1); const address = new libsignal.SignalProtocolAddress(number, 1);
var identifier = address.toString(); const identifier = address.toString();
it('stores identity keys', async function() { it('stores identity keys', async () => {
await store.saveIdentity(identifier, testKey.pubKey); await store.saveIdentity(identifier, testKey.pubKey);
const key = await store.loadIdentityKey(number); const key = await store.loadIdentityKey(number);
assertEqualArrayBuffers(key, testKey.pubKey); assertEqualArrayBuffers(key, testKey.pubKey);
}); });
it('allows key changes', async function() { it('allows key changes', async () => {
var newIdentity = libsignal.crypto.getRandomBytes(33); const newIdentity = libsignal.crypto.getRandomBytes(33);
await store.saveIdentity(identifier, testKey.pubKey); await store.saveIdentity(identifier, testKey.pubKey);
await store.saveIdentity(identifier, newIdentity); await store.saveIdentity(identifier, newIdentity);
}); });
describe('When there is no existing key (first use)', function() { describe('When there is no existing key (first use)', () => {
before(async function() { before(async () => {
await store.removeIdentityKey(number); await store.removeIdentityKey(number);
await store.saveIdentity(identifier, testKey.pubKey); await store.saveIdentity(identifier, testKey.pubKey);
}); });
it('marks the key firstUse', async function() { it('marks the key firstUse', async () => {
const identity = await window.Signal.Data.getIdentityKeyById(number); const identity = await window.Signal.Data.getIdentityKeyById(number);
assert(identity.firstUse); assert(identity.firstUse);
}); });
it('sets the timestamp', async function() { it('sets the timestamp', async () => {
const identity = await window.Signal.Data.getIdentityKeyById(number); const identity = await window.Signal.Data.getIdentityKeyById(number);
assert(identity.timestamp); assert(identity.timestamp);
}); });
it('sets the verified status to DEFAULT', async function() { it('sets the verified status to DEFAULT', async () => {
const identity = await window.Signal.Data.getIdentityKeyById(number); const identity = await window.Signal.Data.getIdentityKeyById(number);
assert.strictEqual(identity.verified, store.VerifiedStatus.DEFAULT); assert.strictEqual(identity.verified, store.VerifiedStatus.DEFAULT);
}); });
}); });
describe('When there is a different existing key (non first use)', function() { describe('When there is a different existing key (non first use)', () => {
const newIdentity = libsignal.crypto.getRandomBytes(33); const newIdentity = libsignal.crypto.getRandomBytes(33);
const oldTimestamp = Date.now(); const oldTimestamp = Date.now();
before(async function() { before(async () => {
await window.Signal.Data.createOrUpdateIdentityKey({ await window.Signal.Data.createOrUpdateIdentityKey({
id: identifier, id: identifier,
publicKey: testKey.pubKey, publicKey: testKey.pubKey,
@ -92,17 +88,17 @@ describe('SignalProtocolStore', function() {
await store.saveIdentity(identifier, newIdentity); await store.saveIdentity(identifier, newIdentity);
}); });
it('marks the key not firstUse', async function() { it('marks the key not firstUse', async () => {
const identity = await window.Signal.Data.getIdentityKeyById(number); const identity = await window.Signal.Data.getIdentityKeyById(number);
assert(!identity.firstUse); assert(!identity.firstUse);
}); });
it('updates the timestamp', async function() { it('updates the timestamp', async () => {
const identity = await window.Signal.Data.getIdentityKeyById(number); const identity = await window.Signal.Data.getIdentityKeyById(number);
assert.notEqual(identity.timestamp, oldTimestamp); assert.notEqual(identity.timestamp, oldTimestamp);
}); });
describe('The previous verified status was DEFAULT', function() { describe('The previous verified status was DEFAULT', () => {
before(async function() { before(async () => {
await window.Signal.Data.createOrUpdateIdentityKey({ await window.Signal.Data.createOrUpdateIdentityKey({
id: number, id: number,
publicKey: testKey.pubKey, publicKey: testKey.pubKey,
@ -114,13 +110,13 @@ describe('SignalProtocolStore', function() {
await store.saveIdentity(identifier, newIdentity); await store.saveIdentity(identifier, newIdentity);
}); });
it('sets the new key to default', async function() { it('sets the new key to default', async () => {
const identity = await window.Signal.Data.getIdentityKeyById(number); const identity = await window.Signal.Data.getIdentityKeyById(number);
assert.strictEqual(identity.verified, store.VerifiedStatus.DEFAULT); assert.strictEqual(identity.verified, store.VerifiedStatus.DEFAULT);
}); });
}); });
describe('The previous verified status was VERIFIED', function() { describe('The previous verified status was VERIFIED', () => {
before(async function() { before(async () => {
await window.Signal.Data.createOrUpdateIdentityKey({ await window.Signal.Data.createOrUpdateIdentityKey({
id: number, id: number,
publicKey: testKey.pubKey, publicKey: testKey.pubKey,
@ -131,7 +127,7 @@ describe('SignalProtocolStore', function() {
}); });
await store.saveIdentity(identifier, newIdentity); await store.saveIdentity(identifier, newIdentity);
}); });
it('sets the new key to unverified', async function() { it('sets the new key to unverified', async () => {
const identity = await window.Signal.Data.getIdentityKeyById(number); const identity = await window.Signal.Data.getIdentityKeyById(number);
assert.strictEqual( assert.strictEqual(
@ -140,8 +136,8 @@ describe('SignalProtocolStore', function() {
); );
}); });
}); });
describe('The previous verified status was UNVERIFIED', function() { describe('The previous verified status was UNVERIFIED', () => {
before(async function() { before(async () => {
await window.Signal.Data.createOrUpdateIdentityKey({ await window.Signal.Data.createOrUpdateIdentityKey({
id: number, id: number,
publicKey: testKey.pubKey, publicKey: testKey.pubKey,
@ -153,7 +149,7 @@ describe('SignalProtocolStore', function() {
await store.saveIdentity(identifier, newIdentity); await store.saveIdentity(identifier, newIdentity);
}); });
it('sets the new key to unverified', async function() { it('sets the new key to unverified', async () => {
const identity = await window.Signal.Data.getIdentityKeyById(number); const identity = await window.Signal.Data.getIdentityKeyById(number);
assert.strictEqual( assert.strictEqual(
identity.verified, identity.verified,
@ -162,9 +158,9 @@ describe('SignalProtocolStore', function() {
}); });
}); });
}); });
describe('When the key has not changed', function() { describe('When the key has not changed', () => {
var oldTimestamp = Date.now(); const oldTimestamp = Date.now();
before(async function() { before(async () => {
await window.Signal.Data.createOrUpdateIdentityKey({ await window.Signal.Data.createOrUpdateIdentityKey({
id: number, id: number,
publicKey: testKey.pubKey, publicKey: testKey.pubKey,
@ -173,13 +169,13 @@ describe('SignalProtocolStore', function() {
verified: store.VerifiedStatus.DEFAULT, verified: store.VerifiedStatus.DEFAULT,
}); });
}); });
describe('If it is marked firstUse', function() { describe('If it is marked firstUse', () => {
before(async function() { before(async () => {
const identity = await window.Signal.Data.getIdentityKeyById(number); const identity = await window.Signal.Data.getIdentityKeyById(number);
identity.firstUse = true; identity.firstUse = true;
await window.Signal.Data.createOrUpdateIdentityKey(identity); await window.Signal.Data.createOrUpdateIdentityKey(identity);
}); });
it('nothing changes', async function() { it('nothing changes', async () => {
await store.saveIdentity(identifier, testKey.pubKey, true); await store.saveIdentity(identifier, testKey.pubKey, true);
const identity = await window.Signal.Data.getIdentityKeyById(number); const identity = await window.Signal.Data.getIdentityKeyById(number);
@ -187,15 +183,15 @@ describe('SignalProtocolStore', function() {
assert.strictEqual(identity.timestamp, oldTimestamp); assert.strictEqual(identity.timestamp, oldTimestamp);
}); });
}); });
describe('If it is not marked firstUse', function() { describe('If it is not marked firstUse', () => {
before(async function() { before(async () => {
const identity = await window.Signal.Data.getIdentityKeyById(number); const identity = await window.Signal.Data.getIdentityKeyById(number);
identity.firstUse = false; identity.firstUse = false;
await window.Signal.Data.createOrUpdateIdentityKey(identity); await window.Signal.Data.createOrUpdateIdentityKey(identity);
}); });
describe('If nonblocking approval is required', function() { describe('If nonblocking approval is required', () => {
let now; let now;
before(async function() { before(async () => {
now = Date.now(); now = Date.now();
const identity = await window.Signal.Data.getIdentityKeyById( const identity = await window.Signal.Data.getIdentityKeyById(
number number
@ -203,7 +199,7 @@ describe('SignalProtocolStore', function() {
identity.timestamp = now; identity.timestamp = now;
await window.Signal.Data.createOrUpdateIdentityKey(identity); await window.Signal.Data.createOrUpdateIdentityKey(identity);
}); });
it('sets non-blocking approval', async function() { it('sets non-blocking approval', async () => {
await store.saveIdentity(identifier, testKey.pubKey, true); await store.saveIdentity(identifier, testKey.pubKey, true);
const identity = await window.Signal.Data.getIdentityKeyById( const identity = await window.Signal.Data.getIdentityKeyById(
@ -218,11 +214,11 @@ describe('SignalProtocolStore', function() {
}); });
}); });
}); });
describe('saveIdentityWithAttributes', function() { describe('saveIdentityWithAttributes', () => {
var now; let now;
var validAttributes; let validAttributes;
before(async function() { before(async () => {
now = Date.now(); now = Date.now();
validAttributes = { validAttributes = {
publicKey: testKey.pubKey, publicKey: testKey.pubKey,
@ -234,35 +230,35 @@ describe('SignalProtocolStore', function() {
await store.removeIdentityKey(number); await store.removeIdentityKey(number);
}); });
describe('with valid attributes', function() { describe('with valid attributes', () => {
before(async function() { before(async () => {
await store.saveIdentityWithAttributes(number, validAttributes); await store.saveIdentityWithAttributes(number, validAttributes);
}); });
it('publicKey is saved', async function() { it('publicKey is saved', async () => {
const identity = await window.Signal.Data.getIdentityKeyById(number); const identity = await window.Signal.Data.getIdentityKeyById(number);
assertEqualArrayBuffers(identity.publicKey, testKey.pubKey); assertEqualArrayBuffers(identity.publicKey, testKey.pubKey);
}); });
it('firstUse is saved', async function() { it('firstUse is saved', async () => {
const identity = await window.Signal.Data.getIdentityKeyById(number); const identity = await window.Signal.Data.getIdentityKeyById(number);
assert.strictEqual(identity.firstUse, true); assert.strictEqual(identity.firstUse, true);
}); });
it('timestamp is saved', async function() { it('timestamp is saved', async () => {
const identity = await window.Signal.Data.getIdentityKeyById(number); const identity = await window.Signal.Data.getIdentityKeyById(number);
assert.strictEqual(identity.timestamp, now); assert.strictEqual(identity.timestamp, now);
}); });
it('verified is saved', async function() { it('verified is saved', async () => {
const identity = await window.Signal.Data.getIdentityKeyById(number); const identity = await window.Signal.Data.getIdentityKeyById(number);
assert.strictEqual(identity.verified, store.VerifiedStatus.VERIFIED); assert.strictEqual(identity.verified, store.VerifiedStatus.VERIFIED);
}); });
it('nonblockingApproval is saved', async function() { it('nonblockingApproval is saved', async () => {
const identity = await window.Signal.Data.getIdentityKeyById(number); const identity = await window.Signal.Data.getIdentityKeyById(number);
assert.strictEqual(identity.nonblockingApproval, false); assert.strictEqual(identity.nonblockingApproval, false);
}); });
}); });
describe('with invalid attributes', function() { describe('with invalid attributes', () => {
var attributes; let attributes;
beforeEach(function() { beforeEach(() => {
attributes = _.clone(validAttributes); attributes = _.clone(validAttributes);
}); });
@ -275,38 +271,37 @@ describe('SignalProtocolStore', function() {
} }
} }
it('rejects an invalid publicKey', async function() { it('rejects an invalid publicKey', async () => {
attributes.publicKey = 'a string'; attributes.publicKey = 'a string';
await testInvalidAttributes(); await testInvalidAttributes();
}); });
it('rejects invalid firstUse', async function() { it('rejects invalid firstUse', async () => {
attributes.firstUse = 0; attributes.firstUse = 0;
await testInvalidAttributes(); await testInvalidAttributes();
}); });
it('rejects invalid timestamp', async function() { it('rejects invalid timestamp', async () => {
attributes.timestamp = NaN; attributes.timestamp = NaN;
await testInvalidAttributes(); await testInvalidAttributes();
}); });
it('rejects invalid verified', async function() { it('rejects invalid verified', async () => {
attributes.verified = null; attributes.verified = null;
await testInvalidAttributes(); await testInvalidAttributes();
}); });
it('rejects invalid nonblockingApproval', async function() { it('rejects invalid nonblockingApproval', async () => {
attributes.nonblockingApproval = 0; attributes.nonblockingApproval = 0;
await testInvalidAttributes(); await testInvalidAttributes();
}); });
}); });
}); });
describe('setApproval', function() { describe('setApproval', () => {
it('sets nonblockingApproval', async function() { it('sets nonblockingApproval', async () => {
await store.setApproval(number, true); await store.setApproval(number, true);
const identity = await window.Signal.Data.getIdentityKeyById(number); const identity = await window.Signal.Data.getIdentityKeyById(number);
assert.strictEqual(identity.nonblockingApproval, true); assert.strictEqual(identity.nonblockingApproval, true);
}); });
}); });
describe('setVerified', function() { describe('setVerified', () => {
var record;
async function saveRecordDefault() { async function saveRecordDefault() {
await window.Signal.Data.createOrUpdateIdentityKey({ await window.Signal.Data.createOrUpdateIdentityKey({
id: number, id: number,
@ -317,9 +312,9 @@ describe('SignalProtocolStore', function() {
nonblockingApproval: false, nonblockingApproval: false,
}); });
} }
describe('with no public key argument', function() { describe('with no public key argument', () => {
before(saveRecordDefault); before(saveRecordDefault);
it('updates the verified status', async function() { it('updates the verified status', async () => {
await store.setVerified(number, store.VerifiedStatus.VERIFIED); await store.setVerified(number, store.VerifiedStatus.VERIFIED);
const identity = await window.Signal.Data.getIdentityKeyById(number); const identity = await window.Signal.Data.getIdentityKeyById(number);
@ -327,9 +322,9 @@ describe('SignalProtocolStore', function() {
assertEqualArrayBuffers(identity.publicKey, testKey.pubKey); assertEqualArrayBuffers(identity.publicKey, testKey.pubKey);
}); });
}); });
describe('with the current public key', function() { describe('with the current public key', () => {
before(saveRecordDefault); before(saveRecordDefault);
it('updates the verified status', async function() { it('updates the verified status', async () => {
await store.setVerified( await store.setVerified(
number, number,
store.VerifiedStatus.VERIFIED, store.VerifiedStatus.VERIFIED,
@ -341,10 +336,10 @@ describe('SignalProtocolStore', function() {
assertEqualArrayBuffers(identity.publicKey, testKey.pubKey); assertEqualArrayBuffers(identity.publicKey, testKey.pubKey);
}); });
}); });
describe('with a mismatching public key', function() { describe('with a mismatching public key', () => {
var newIdentity = libsignal.crypto.getRandomBytes(33); const newIdentity = libsignal.crypto.getRandomBytes(33);
before(saveRecordDefault); before(saveRecordDefault);
it('does not change the record.', async function() { it('does not change the record.', async () => {
await store.setVerified( await store.setVerified(
number, number,
store.VerifiedStatus.VERIFIED, store.VerifiedStatus.VERIFIED,
@ -357,28 +352,27 @@ describe('SignalProtocolStore', function() {
}); });
}); });
}); });
describe('processContactSyncVerificationState', function() { describe('processContactSyncVerificationState', () => {
var record; const newIdentity = libsignal.crypto.getRandomBytes(33);
var newIdentity = libsignal.crypto.getRandomBytes(33); let keychangeTriggered;
var keychangeTriggered;
beforeEach(function() { beforeEach(() => {
keychangeTriggered = 0; keychangeTriggered = 0;
store.bind('keychange', function() { store.bind('keychange', () => {
keychangeTriggered++; keychangeTriggered += 1;
}); });
}); });
afterEach(function() { afterEach(() => {
store.unbind('keychange'); store.unbind('keychange');
}); });
describe('when the new verified status is DEFAULT', function() { describe('when the new verified status is DEFAULT', () => {
describe('when there is no existing record', function() { describe('when there is no existing record', () => {
before(async function() { before(async () => {
await window.Signal.Data.removeIdentityKeyById(number); await window.Signal.Data.removeIdentityKeyById(number);
}); });
it('does nothing', async function() { it('does nothing', async () => {
await store.processContactSyncVerificationState( await store.processContactSyncVerificationState(
number, number,
store.VerifiedStatus.DEFAULT, store.VerifiedStatus.DEFAULT,
@ -398,9 +392,9 @@ describe('SignalProtocolStore', function() {
assert.strictEqual(keychangeTriggered, 0); assert.strictEqual(keychangeTriggered, 0);
}); });
}); });
describe('when the record exists', function() { describe('when the record exists', () => {
describe('when the existing key is different', function() { describe('when the existing key is different', () => {
before(async function() { before(async () => {
await window.Signal.Data.createOrUpdateIdentityKey({ await window.Signal.Data.createOrUpdateIdentityKey({
id: number, id: number,
publicKey: testKey.pubKey, publicKey: testKey.pubKey,
@ -411,7 +405,7 @@ describe('SignalProtocolStore', function() {
}); });
}); });
it('does not save the new identity (because this is a less secure state)', async function() { it('does not save the new identity (because this is a less secure state)', async () => {
await store.processContactSyncVerificationState( await store.processContactSyncVerificationState(
number, number,
store.VerifiedStatus.DEFAULT, store.VerifiedStatus.DEFAULT,
@ -430,8 +424,8 @@ describe('SignalProtocolStore', function() {
assert.strictEqual(keychangeTriggered, 0); assert.strictEqual(keychangeTriggered, 0);
}); });
}); });
describe('when the existing key is the same but VERIFIED', function() { describe('when the existing key is the same but VERIFIED', () => {
before(async function() { before(async () => {
await window.Signal.Data.createOrUpdateIdentityKey({ await window.Signal.Data.createOrUpdateIdentityKey({
id: number, id: number,
publicKey: testKey.pubKey, publicKey: testKey.pubKey,
@ -442,7 +436,7 @@ describe('SignalProtocolStore', function() {
}); });
}); });
it('updates the verified status', async function() { it('updates the verified status', async () => {
await store.processContactSyncVerificationState( await store.processContactSyncVerificationState(
number, number,
store.VerifiedStatus.DEFAULT, store.VerifiedStatus.DEFAULT,
@ -458,8 +452,8 @@ describe('SignalProtocolStore', function() {
assert.strictEqual(keychangeTriggered, 0); assert.strictEqual(keychangeTriggered, 0);
}); });
}); });
describe('when the existing key is the same and already DEFAULT', function() { describe('when the existing key is the same and already DEFAULT', () => {
before(async function() { before(async () => {
await window.Signal.Data.createOrUpdateIdentityKey({ await window.Signal.Data.createOrUpdateIdentityKey({
id: number, id: number,
publicKey: testKey.pubKey, publicKey: testKey.pubKey,
@ -470,7 +464,7 @@ describe('SignalProtocolStore', function() {
}); });
}); });
it('does not hang', async function() { it('does not hang', async () => {
await store.processContactSyncVerificationState( await store.processContactSyncVerificationState(
number, number,
store.VerifiedStatus.DEFAULT, store.VerifiedStatus.DEFAULT,
@ -482,13 +476,13 @@ describe('SignalProtocolStore', function() {
}); });
}); });
}); });
describe('when the new verified status is UNVERIFIED', function() { describe('when the new verified status is UNVERIFIED', () => {
describe('when there is no existing record', function() { describe('when there is no existing record', () => {
before(async function() { before(async () => {
await window.Signal.Data.removeIdentityKeyById(number); await window.Signal.Data.removeIdentityKeyById(number);
}); });
it('saves the new identity and marks it verified', async function() { it('saves the new identity and marks it verified', async () => {
await store.processContactSyncVerificationState( await store.processContactSyncVerificationState(
number, number,
store.VerifiedStatus.UNVERIFIED, store.VerifiedStatus.UNVERIFIED,
@ -505,9 +499,9 @@ describe('SignalProtocolStore', function() {
assert.strictEqual(keychangeTriggered, 0); assert.strictEqual(keychangeTriggered, 0);
}); });
}); });
describe('when the record exists', function() { describe('when the record exists', () => {
describe('when the existing key is different', function() { describe('when the existing key is different', () => {
before(async function() { before(async () => {
await window.Signal.Data.createOrUpdateIdentityKey({ await window.Signal.Data.createOrUpdateIdentityKey({
id: number, id: number,
publicKey: testKey.pubKey, publicKey: testKey.pubKey,
@ -518,7 +512,7 @@ describe('SignalProtocolStore', function() {
}); });
}); });
it('saves the new identity and marks it UNVERIFIED', async function() { it('saves the new identity and marks it UNVERIFIED', async () => {
await store.processContactSyncVerificationState( await store.processContactSyncVerificationState(
number, number,
store.VerifiedStatus.UNVERIFIED, store.VerifiedStatus.UNVERIFIED,
@ -537,8 +531,8 @@ describe('SignalProtocolStore', function() {
assert.strictEqual(keychangeTriggered, 1); assert.strictEqual(keychangeTriggered, 1);
}); });
}); });
describe('when the key exists and is DEFAULT', function() { describe('when the key exists and is DEFAULT', () => {
before(async function() { before(async () => {
await window.Signal.Data.createOrUpdateIdentityKey({ await window.Signal.Data.createOrUpdateIdentityKey({
id: number, id: number,
publicKey: testKey.pubKey, publicKey: testKey.pubKey,
@ -549,7 +543,7 @@ describe('SignalProtocolStore', function() {
}); });
}); });
it('updates the verified status', async function() { it('updates the verified status', async () => {
await store.processContactSyncVerificationState( await store.processContactSyncVerificationState(
number, number,
store.VerifiedStatus.UNVERIFIED, store.VerifiedStatus.UNVERIFIED,
@ -567,8 +561,8 @@ describe('SignalProtocolStore', function() {
assert.strictEqual(keychangeTriggered, 0); assert.strictEqual(keychangeTriggered, 0);
}); });
}); });
describe('when the key exists and is already UNVERIFIED', function() { describe('when the key exists and is already UNVERIFIED', () => {
before(async function() { before(async () => {
await window.Signal.Data.createOrUpdateIdentityKey({ await window.Signal.Data.createOrUpdateIdentityKey({
id: number, id: number,
publicKey: testKey.pubKey, publicKey: testKey.pubKey,
@ -579,7 +573,7 @@ describe('SignalProtocolStore', function() {
}); });
}); });
it('does not hang', async function() { it('does not hang', async () => {
await store.processContactSyncVerificationState( await store.processContactSyncVerificationState(
number, number,
store.VerifiedStatus.UNVERIFIED, store.VerifiedStatus.UNVERIFIED,
@ -591,13 +585,13 @@ describe('SignalProtocolStore', function() {
}); });
}); });
}); });
describe('when the new verified status is VERIFIED', function() { describe('when the new verified status is VERIFIED', () => {
describe('when there is no existing record', function() { describe('when there is no existing record', () => {
before(async function() { before(async () => {
await window.Signal.Data.removeIdentityKeyById(number); await window.Signal.Data.removeIdentityKeyById(number);
}); });
it('saves the new identity and marks it verified', async function() { it('saves the new identity and marks it verified', async () => {
await store.processContactSyncVerificationState( await store.processContactSyncVerificationState(
number, number,
store.VerifiedStatus.VERIFIED, store.VerifiedStatus.VERIFIED,
@ -610,9 +604,9 @@ describe('SignalProtocolStore', function() {
assert.strictEqual(keychangeTriggered, 0); assert.strictEqual(keychangeTriggered, 0);
}); });
}); });
describe('when the record exists', function() { describe('when the record exists', () => {
describe('when the existing key is different', function() { describe('when the existing key is different', () => {
before(async function() { before(async () => {
await window.Signal.Data.createOrUpdateIdentityKey({ await window.Signal.Data.createOrUpdateIdentityKey({
id: number, id: number,
publicKey: testKey.pubKey, publicKey: testKey.pubKey,
@ -623,7 +617,7 @@ describe('SignalProtocolStore', function() {
}); });
}); });
it('saves the new identity and marks it VERIFIED', async function() { it('saves the new identity and marks it VERIFIED', async () => {
await store.processContactSyncVerificationState( await store.processContactSyncVerificationState(
number, number,
store.VerifiedStatus.VERIFIED, store.VerifiedStatus.VERIFIED,
@ -642,8 +636,8 @@ describe('SignalProtocolStore', function() {
assert.strictEqual(keychangeTriggered, 1); assert.strictEqual(keychangeTriggered, 1);
}); });
}); });
describe('when the existing key is the same but UNVERIFIED', function() { describe('when the existing key is the same but UNVERIFIED', () => {
before(async function() { before(async () => {
await window.Signal.Data.createOrUpdateIdentityKey({ await window.Signal.Data.createOrUpdateIdentityKey({
id: number, id: number,
publicKey: testKey.pubKey, publicKey: testKey.pubKey,
@ -654,7 +648,7 @@ describe('SignalProtocolStore', function() {
}); });
}); });
it('saves the identity and marks it verified', async function() { it('saves the identity and marks it verified', async () => {
await store.processContactSyncVerificationState( await store.processContactSyncVerificationState(
number, number,
store.VerifiedStatus.VERIFIED, store.VerifiedStatus.VERIFIED,
@ -672,8 +666,8 @@ describe('SignalProtocolStore', function() {
assert.strictEqual(keychangeTriggered, 0); assert.strictEqual(keychangeTriggered, 0);
}); });
}); });
describe('when the existing key is the same and already VERIFIED', function() { describe('when the existing key is the same and already VERIFIED', () => {
before(async function() { before(async () => {
await window.Signal.Data.createOrUpdateIdentityKey({ await window.Signal.Data.createOrUpdateIdentityKey({
id: number, id: number,
publicKey: testKey.pubKey, publicKey: testKey.pubKey,
@ -684,7 +678,7 @@ describe('SignalProtocolStore', function() {
}); });
}); });
it('does not hang', async function() { it('does not hang', async () => {
await store.processContactSyncVerificationState( await store.processContactSyncVerificationState(
number, number,
store.VerifiedStatus.VERIFIED, store.VerifiedStatus.VERIFIED,
@ -698,8 +692,8 @@ describe('SignalProtocolStore', function() {
}); });
}); });
describe('isUntrusted', function() { describe('isUntrusted', () => {
it('returns false if identity key old enough', async function() { it('returns false if identity key old enough', async () => {
await window.Signal.Data.createOrUpdateIdentityKey({ await window.Signal.Data.createOrUpdateIdentityKey({
id: number, id: number,
publicKey: testKey.pubKey, publicKey: testKey.pubKey,
@ -713,7 +707,7 @@ describe('SignalProtocolStore', function() {
assert.strictEqual(untrusted, false); assert.strictEqual(untrusted, false);
}); });
it('returns false if new but nonblockingApproval is true', async function() { it('returns false if new but nonblockingApproval is true', async () => {
await window.Signal.Data.createOrUpdateIdentityKey({ await window.Signal.Data.createOrUpdateIdentityKey({
id: number, id: number,
publicKey: testKey.pubKey, publicKey: testKey.pubKey,
@ -727,7 +721,7 @@ describe('SignalProtocolStore', function() {
assert.strictEqual(untrusted, false); assert.strictEqual(untrusted, false);
}); });
it('returns false if new but firstUse is true', async function() { it('returns false if new but firstUse is true', async () => {
await window.Signal.Data.createOrUpdateIdentityKey({ await window.Signal.Data.createOrUpdateIdentityKey({
id: number, id: number,
publicKey: testKey.pubKey, publicKey: testKey.pubKey,
@ -741,7 +735,7 @@ describe('SignalProtocolStore', function() {
assert.strictEqual(untrusted, false); assert.strictEqual(untrusted, false);
}); });
it('returns true if new, and no flags are set', async function() { it('returns true if new, and no flags are set', async () => {
await window.Signal.Data.createOrUpdateIdentityKey({ await window.Signal.Data.createOrUpdateIdentityKey({
id: number, id: number,
publicKey: testKey.pubKey, publicKey: testKey.pubKey,
@ -755,21 +749,21 @@ describe('SignalProtocolStore', function() {
}); });
}); });
describe('getVerified', function() { describe('getVerified', () => {
before(async function() { before(async () => {
await store.setVerified(number, store.VerifiedStatus.VERIFIED); await store.setVerified(number, store.VerifiedStatus.VERIFIED);
}); });
it('resolves to the verified status', async function() { it('resolves to the verified status', async () => {
const result = await store.getVerified(number); const result = await store.getVerified(number);
assert.strictEqual(result, store.VerifiedStatus.VERIFIED); assert.strictEqual(result, store.VerifiedStatus.VERIFIED);
}); });
}); });
describe('isTrustedIdentity', function() { describe('isTrustedIdentity', () => {
const address = new libsignal.SignalProtocolAddress(number, 1); const address = new libsignal.SignalProtocolAddress(number, 1);
const identifier = address.toString(); const identifier = address.toString();
describe('When invalid direction is given', function() { describe('When invalid direction is given', () => {
it('should fail', async function() { it('should fail', async () => {
try { try {
await store.isTrustedIdentity(number, testKey.pubKey); await store.isTrustedIdentity(number, testKey.pubKey);
throw new Error('isTrustedIdentity should have failed'); throw new Error('isTrustedIdentity should have failed');
@ -778,9 +772,9 @@ describe('SignalProtocolStore', function() {
} }
}); });
}); });
describe('When direction is RECEIVING', function() { describe('When direction is RECEIVING', () => {
it('always returns true', async function() { it('always returns true', async () => {
var newIdentity = libsignal.crypto.getRandomBytes(33); const newIdentity = libsignal.crypto.getRandomBytes(33);
await store.saveIdentity(identifier, testKey.pubKey); await store.saveIdentity(identifier, testKey.pubKey);
const trusted = await store.isTrustedIdentity( const trusted = await store.isTrustedIdentity(
@ -794,12 +788,12 @@ describe('SignalProtocolStore', function() {
} }
}); });
}); });
describe('When direction is SENDING', function() { describe('When direction is SENDING', () => {
describe('When there is no existing key (first use)', function() { describe('When there is no existing key (first use)', () => {
before(async function() { before(async () => {
await store.removeIdentityKey(number); await store.removeIdentityKey(number);
}); });
it('returns true', async function() { it('returns true', async () => {
const newIdentity = libsignal.crypto.getRandomBytes(33); const newIdentity = libsignal.crypto.getRandomBytes(33);
const trusted = await store.isTrustedIdentity( const trusted = await store.isTrustedIdentity(
identifier, identifier,
@ -811,12 +805,12 @@ describe('SignalProtocolStore', function() {
} }
}); });
}); });
describe('When there is an existing key', function() { describe('When there is an existing key', () => {
before(async function() { before(async () => {
await store.saveIdentity(identifier, testKey.pubKey); await store.saveIdentity(identifier, testKey.pubKey);
}); });
describe('When the existing key is different', function() { describe('When the existing key is different', () => {
it('returns false', async function() { it('returns false', async () => {
const newIdentity = libsignal.crypto.getRandomBytes(33); const newIdentity = libsignal.crypto.getRandomBytes(33);
const trusted = await store.isTrustedIdentity( const trusted = await store.isTrustedIdentity(
identifier, identifier,
@ -828,12 +822,12 @@ describe('SignalProtocolStore', function() {
} }
}); });
}); });
describe('When the existing key matches the new key', function() { describe('When the existing key matches the new key', () => {
const newIdentity = libsignal.crypto.getRandomBytes(33); const newIdentity = libsignal.crypto.getRandomBytes(33);
before(async function() { before(async () => {
await store.saveIdentity(identifier, newIdentity); await store.saveIdentity(identifier, newIdentity);
}); });
it('returns false if keys match but we just received this new identiy', async function() { it('returns false if keys match but we just received this new identiy', async () => {
const trusted = await store.isTrustedIdentity( const trusted = await store.isTrustedIdentity(
identifier, identifier,
newIdentity, newIdentity,
@ -844,7 +838,7 @@ describe('SignalProtocolStore', function() {
throw new Error('isTrusted returned true on untrusted key'); throw new Error('isTrusted returned true on untrusted key');
} }
}); });
it('returns true if we have already approved identity', async function() { it('returns true if we have already approved identity', async () => {
await store.saveIdentity(identifier, newIdentity, true); await store.saveIdentity(identifier, newIdentity, true);
const trusted = await store.isTrustedIdentity( const trusted = await store.isTrustedIdentity(
@ -860,27 +854,27 @@ describe('SignalProtocolStore', function() {
}); });
}); });
}); });
describe('storePreKey', function() { describe('storePreKey', () => {
it('stores prekeys', async function() { it('stores prekeys', async () => {
await store.storePreKey(1, testKey); await store.storePreKey(1, testKey);
const key = await store.loadPreKey(1); const key = await store.loadPreKey(1);
assertEqualArrayBuffers(key.pubKey, testKey.pubKey); assertEqualArrayBuffers(key.pubKey, testKey.pubKey);
assertEqualArrayBuffers(key.privKey, testKey.privKey); assertEqualArrayBuffers(key.privKey, testKey.privKey);
}); });
}); });
describe('removePreKey', function() { describe('removePreKey', () => {
before(async function() { before(async () => {
await store.storePreKey(2, testKey); await store.storePreKey(2, testKey);
}); });
it('deletes prekeys', async function() { it('deletes prekeys', async () => {
await store.removePreKey(2, testKey); await store.removePreKey(2, testKey);
const key = await store.loadPreKey(2); const key = await store.loadPreKey(2);
assert.isUndefined(key); assert.isUndefined(key);
}); });
}); });
describe('storeSignedPreKey', function() { describe('storeSignedPreKey', () => {
it('stores signed prekeys', async function() { it('stores signed prekeys', async () => {
await store.storeSignedPreKey(3, testKey); await store.storeSignedPreKey(3, testKey);
const key = await store.loadSignedPreKey(3); const key = await store.loadSignedPreKey(3);
@ -888,36 +882,36 @@ describe('SignalProtocolStore', function() {
assertEqualArrayBuffers(key.privKey, testKey.privKey); assertEqualArrayBuffers(key.privKey, testKey.privKey);
}); });
}); });
describe('removeSignedPreKey', function() { describe('removeSignedPreKey', () => {
before(async function() { before(async () => {
await store.storeSignedPreKey(4, testKey); await store.storeSignedPreKey(4, testKey);
}); });
it('deletes signed prekeys', async function() { it('deletes signed prekeys', async () => {
await store.removeSignedPreKey(4, testKey); await store.removeSignedPreKey(4, testKey);
const key = await store.loadSignedPreKey(4); const key = await store.loadSignedPreKey(4);
assert.isUndefined(key); assert.isUndefined(key);
}); });
}); });
describe('storeSession', function() { describe('storeSession', () => {
it('stores sessions', async function() { it('stores sessions', async () => {
const testRecord = 'an opaque string'; const testRecord = 'an opaque string';
await store.storeSession(number + '.1', testRecord); await store.storeSession(`${number}.1`, testRecord);
const record = await store.loadSession(number + '.1'); const record = await store.loadSession(`${number}.1`);
assert.deepEqual(record, testRecord); assert.deepEqual(record, testRecord);
}); });
}); });
describe('removeAllSessions', function() { describe('removeAllSessions', () => {
it('removes all sessions for a number', async function() { it('removes all sessions for a number', async () => {
const testRecord = 'an opaque string'; const testRecord = 'an opaque string';
const devices = [1, 2, 3].map(function(deviceId) { const devices = [1, 2, 3].map(deviceId => {
return [number, deviceId].join('.'); return [number, deviceId].join('.');
}); });
await Promise.all( await Promise.all(
devices.map(async function(encodedNumber) { devices.map(async encodedNumber => {
await store.storeSession(encodedNumber, testRecord + encodedNumber); await store.storeSession(encodedNumber, testRecord + encodedNumber);
}) })
); );
@ -927,30 +921,31 @@ describe('SignalProtocolStore', function() {
const records = await Promise.all( const records = await Promise.all(
devices.map(store.loadSession.bind(store)) devices.map(store.loadSession.bind(store))
); );
for (var i in records) {
for (let i = 0, max = records.length; i < max; i += 1) {
assert.isUndefined(records[i]); assert.isUndefined(records[i]);
} }
}); });
}); });
describe('clearSessionStore', function() { describe('clearSessionStore', () => {
it('clears the session store', async function() { it('clears the session store', async () => {
const testRecord = 'an opaque string'; const testRecord = 'an opaque string';
await store.storeSession(number + '.1', testRecord); await store.storeSession(`${number}.1`, testRecord);
await store.clearSessionStore(); await store.clearSessionStore();
const record = await store.loadSession(number + '.1'); const record = await store.loadSession(`${number}.1`);
assert.isUndefined(record); assert.isUndefined(record);
}); });
}); });
describe('getDeviceIds', function() { describe('getDeviceIds', () => {
it('returns deviceIds for a number', async function() { it('returns deviceIds for a number', async () => {
const testRecord = 'an opaque string'; const testRecord = 'an opaque string';
const devices = [1, 2, 3].map(function(deviceId) { const devices = [1, 2, 3].map(deviceId => {
return [number, deviceId].join('.'); return [number, deviceId].join('.');
}); });
await Promise.all( await Promise.all(
devices.map(async function(encodedNumber) { devices.map(async encodedNumber => {
await store.storeSession(encodedNumber, testRecord + encodedNumber); await store.storeSession(encodedNumber, testRecord + encodedNumber);
}) })
); );
@ -958,20 +953,20 @@ describe('SignalProtocolStore', function() {
const deviceIds = await store.getDeviceIds(number); const deviceIds = await store.getDeviceIds(number);
assert.sameMembers(deviceIds, [1, 2, 3]); assert.sameMembers(deviceIds, [1, 2, 3]);
}); });
it('returns empty array for a number with no device ids', async function() { it('returns empty array for a number with no device ids', async () => {
const deviceIds = await store.getDeviceIds('foo'); const deviceIds = await store.getDeviceIds('foo');
assert.sameMembers(deviceIds, []); assert.sameMembers(deviceIds, []);
}); });
}); });
describe('Not yet processed messages', function() { describe('Not yet processed messages', () => {
beforeEach(async function() { beforeEach(async () => {
await store.removeAllUnprocessed(); await store.removeAllUnprocessed();
const items = await store.getAllUnprocessed(); const items = await store.getAllUnprocessed();
assert.strictEqual(items.length, 0); assert.strictEqual(items.length, 0);
}); });
it('adds two and gets them back', async function() { it('adds two and gets them back', async () => {
await Promise.all([ await Promise.all([
store.addUnprocessed({ id: 2, name: 'second', timestamp: 2 }), store.addUnprocessed({ id: 2, name: 'second', timestamp: 2 }),
store.addUnprocessed({ id: 3, name: 'third', timestamp: 3 }), store.addUnprocessed({ id: 3, name: 'third', timestamp: 3 }),
@ -987,9 +982,9 @@ describe('SignalProtocolStore', function() {
assert.strictEqual(items[2].name, 'third'); assert.strictEqual(items[2].name, 'third');
}); });
it('saveUnprocessed successfully updates item', async function() { it('saveUnprocessed successfully updates item', async () => {
const id = 1; const id = 1;
await store.addUnprocessed({ id: id, name: 'first', timestamp: 1 }); await store.addUnprocessed({ id, name: 'first', timestamp: 1 });
await store.saveUnprocessed({ id, name: 'updated', timestamp: 1 }); await store.saveUnprocessed({ id, name: 'updated', timestamp: 1 });
const items = await store.getAllUnprocessed(); const items = await store.getAllUnprocessed();
@ -998,9 +993,9 @@ describe('SignalProtocolStore', function() {
assert.strictEqual(items[0].timestamp, 1); assert.strictEqual(items[0].timestamp, 1);
}); });
it('removeUnprocessed successfully deletes item', async function() { it('removeUnprocessed successfully deletes item', async () => {
const id = 1; const id = 1;
await store.addUnprocessed({ id: id, name: 'first', timestamp: 1 }); await store.addUnprocessed({ id, name: 'first', timestamp: 1 });
await store.removeUnprocessed(id); await store.removeUnprocessed(id);
const items = await store.getAllUnprocessed(); const items = await store.getAllUnprocessed();

View file

@ -1,17 +1,15 @@
/* global assert: false */ /* global assert, storage, Whisper */
/* global Whisper: false */
'use strict'; 'use strict';
describe('AttachmentView', () => { describe('AttachmentView', () => {
var convo, message; let convo;
before(async () => { before(async () => {
await clearDatabase(); await clearDatabase();
convo = new Whisper.Conversation({ id: 'foo' }); convo = new Whisper.Conversation({ id: 'foo' });
message = convo.messageCollection.add({ convo.messageCollection.add({
conversationId: convo.id, conversationId: convo.id,
body: 'hello world', body: 'hello world',
type: 'outgoing', type: 'outgoing',

View file

@ -1,11 +1,13 @@
describe('ConversationSearchView', function() { /* global $, Whisper */
it('should match partial numbers', function() {
var $el = $('<div><div class="new-contact contact hide"></div></div>'); describe('ConversationSearchView', () => {
var view = new Whisper.ConversationSearchView({ it('should match partial numbers', () => {
const $el = $('<div><div class="new-contact contact hide"></div></div>');
const view = new Whisper.ConversationSearchView({
el: $el, el: $el,
input: $('<input>'), input: $('<input>'),
}).render(); }).render();
var maybe_numbers = [ const maybeNumbers = [
'+1 415', '+1 415',
'+1415', '+1415',
'+1415', '+1415',
@ -19,11 +21,11 @@ describe('ConversationSearchView', function() {
'1 415-123-4567', '1 415-123-4567',
'415-123-4567', '415-123-4567',
]; ];
maybe_numbers.forEach(function(n) { maybeNumbers.forEach(n => {
assert.ok(view.maybeNumber(n), n); assert.ok(view.maybeNumber(n), n);
}); });
}); });
describe('Searching for left groups', function() { describe('Searching for left groups', () => {
let convo; let convo;
before(() => { before(() => {
@ -39,32 +41,32 @@ describe('ConversationSearchView', function() {
Conversation: Whisper.Conversation, Conversation: Whisper.Conversation,
}); });
}); });
describe('with no messages', function() { describe('with no messages', () => {
var input; let input;
var view; let view;
before(function(done) { before(done => {
input = $('<input>'); input = $('<input>');
view = new Whisper.ConversationSearchView({ input: input }).render(); view = new Whisper.ConversationSearchView({ input }).render();
view.$input.val('left'); view.$input.val('left');
view.filterContacts(); view.filterContacts();
view.typeahead_view.collection.on('reset', function() { view.typeahead_view.collection.on('reset', () => {
done(); done();
}); });
}); });
it('should not surface left groups with no messages', function() { it('should not surface left groups with no messages', () => {
assert.isUndefined( assert.isUndefined(
view.typeahead_view.collection.get(convo.id), view.typeahead_view.collection.get(convo.id),
'got left group' 'got left group'
); );
}); });
}); });
describe('with messages', function() { describe('with messages', () => {
var input; let input;
var view; let view;
before(async function() { before(async () => {
input = $('<input>'); input = $('<input>');
view = new Whisper.ConversationSearchView({ input: input }).render(); view = new Whisper.ConversationSearchView({ input }).render();
convo.set({ id: '2-search-view', left: false }); convo.set({ id: '2-search-view', left: false });
await window.Signal.Data.saveConversation(convo.attributes, { await window.Signal.Data.saveConversation(convo.attributes, {
@ -78,7 +80,7 @@ describe('ConversationSearchView', function() {
view.typeahead_view.collection.on('reset', resolve); view.typeahead_view.collection.on('reset', resolve);
}); });
}); });
it('should surface left groups with messages', function() { it('should surface left groups with messages', () => {
assert.isDefined( assert.isDefined(
view.typeahead_view.collection.get(convo.id), view.typeahead_view.collection.get(convo.id),
'got left group' 'got left group'

View file

@ -1,20 +1,22 @@
describe('GroupUpdateView', function() { /* global Whisper */
it('should show new group members', function() {
var view = new Whisper.GroupUpdateView({ describe('GroupUpdateView', () => {
it('should show new group members', () => {
const view = new Whisper.GroupUpdateView({
model: { joined: ['Alice', 'Bob'] }, model: { joined: ['Alice', 'Bob'] },
}).render(); }).render();
assert.match(view.$el.text(), /Alice.*Bob.*joined the group/); assert.match(view.$el.text(), /Alice.*Bob.*joined the group/);
}); });
it('should note updates to the title', function() { it('should note updates to the title', () => {
var view = new Whisper.GroupUpdateView({ const view = new Whisper.GroupUpdateView({
model: { name: 'New name' }, model: { name: 'New name' },
}).render(); }).render();
assert.match(view.$el.text(), /Title is now 'New name'/); assert.match(view.$el.text(), /Title is now 'New name'/);
}); });
it('should say "Updated the group"', function() { it('should say "Updated the group"', () => {
var view = new Whisper.GroupUpdateView({ const view = new Whisper.GroupUpdateView({
model: { avatar: 'New avatar' }, model: { avatar: 'New avatar' },
}).render(); }).render();
assert.match(view.$el.text(), /Updated the group/); assert.match(view.$el.text(), /Updated the group/);

View file

@ -1,24 +1,20 @@
describe('InboxView', function() { /* global ConversationController, textsecure, Whisper */
describe('InboxView', () => {
let inboxView; let inboxView;
let conversation; let conversation;
before(async () => { before(async () => {
try { ConversationController.reset();
await ConversationController.load(); await ConversationController.load();
} catch (error) {
console.log(
'InboxView before:',
error && error.stack ? error.stack : error
);
}
await ConversationController.getOrCreateAndWait( await ConversationController.getOrCreateAndWait(
textsecure.storage.user.getNumber(), textsecure.storage.user.getNumber(),
'private' 'private'
); );
inboxView = new Whisper.InboxView({ inboxView = new Whisper.InboxView({
model: {}, model: {},
window: window, window,
initialLoadComplete: function() {}, initialLoadComplete() {},
}).render(); }).render();
conversation = new Whisper.Conversation({ conversation = new Whisper.Conversation({
@ -27,32 +23,32 @@ describe('InboxView', function() {
}); });
}); });
describe('the conversation stack', function() { describe('the conversation stack', () => {
it('should be rendered', function() { it('should be rendered', () => {
assert.ok(inboxView.$('.conversation-stack').length === 1); assert.ok(inboxView.$('.conversation-stack').length === 1);
}); });
describe('opening a conversation', function() { describe('opening a conversation', () => {
var triggeredOpenedCount = 0; let triggeredOpenedCount = 0;
before(function() { before(() => {
conversation.on('opened', function() { conversation.on('opened', () => {
triggeredOpenedCount++; triggeredOpenedCount += 1;
}); });
inboxView.conversation_stack.open(conversation); inboxView.conversation_stack.open(conversation);
}); });
it('should trigger an opened event', function() { it('should trigger an opened event', () => {
assert.ok(triggeredOpenedCount === 1); assert.ok(triggeredOpenedCount === 1);
}); });
describe('and then opening it again immediately', function() { describe('and then opening it again immediately', () => {
before(function() { before(() => {
inboxView.conversation_stack.open(conversation); inboxView.conversation_stack.open(conversation);
}); });
it('should trigger the opened event again', function() { it('should trigger the opened event again', () => {
assert.ok(triggeredOpenedCount === 2); assert.ok(triggeredOpenedCount === 2);
}); });
}); });

View file

@ -1,22 +1,24 @@
describe('LastSeenIndicatorView', function() { /* global Whisper */
it('renders provided count', function() {
var view = new Whisper.LastSeenIndicatorView({ count: 10 }); describe('LastSeenIndicatorView', () => {
it('renders provided count', () => {
const view = new Whisper.LastSeenIndicatorView({ count: 10 });
assert.equal(view.count, 10); assert.equal(view.count, 10);
view.render(); view.render();
assert.match(view.$el.html(), /10 Unread Messages/); assert.match(view.$el.html(), /10 Unread Messages/);
}); });
it('renders count of 1', function() { it('renders count of 1', () => {
var view = new Whisper.LastSeenIndicatorView({ count: 1 }); const view = new Whisper.LastSeenIndicatorView({ count: 1 });
assert.equal(view.count, 1); assert.equal(view.count, 1);
view.render(); view.render();
assert.match(view.$el.html(), /1 Unread Message/); assert.match(view.$el.html(), /1 Unread Message/);
}); });
it('increments count', function() { it('increments count', () => {
var view = new Whisper.LastSeenIndicatorView({ count: 4 }); const view = new Whisper.LastSeenIndicatorView({ count: 4 });
assert.equal(view.count, 4); assert.equal(view.count, 4);
view.render(); view.render();

View file

@ -1,20 +1,22 @@
describe('ListView', function() { /* global Backbone, Whisper */
var collection;
beforeEach(function() { describe('ListView', () => {
let collection;
beforeEach(() => {
collection = new Backbone.Collection(); collection = new Backbone.Collection();
}); });
it('should add children to the list element as they are added to the collection', function() { it('should add children to the list element as they are added to the collection', () => {
var view = new Whisper.ListView({ collection: collection }); const view = new Whisper.ListView({ collection });
collection.add('hello'); collection.add('hello');
assert.equal(view.$el.children().length, 1); assert.equal(view.$el.children().length, 1);
collection.add('world'); collection.add('world');
assert.equal(view.$el.children().length, 2); assert.equal(view.$el.children().length, 2);
}); });
it('should add all the children to the list element on reset', function() { it('should add all the children to the list element on reset', () => {
var view = new Whisper.ListView({ collection: collection }); const view = new Whisper.ListView({ collection });
collection.reset(['goodbye', 'world']); collection.reset(['goodbye', 'world']);
assert.equal(view.$el.children().length, 2); assert.equal(view.$el.children().length, 2);
}); });

View file

@ -1,149 +1,143 @@
describe('NetworkStatusView', function() { /* global _, $, Whisper */
describe('getNetworkStatus', function() {
var networkStatusView;
var socketStatus = WebSocket.OPEN;
var oldGetSocketStatus; describe('NetworkStatusView', () => {
describe('getNetworkStatus', () => {
let networkStatusView;
let socketStatus = WebSocket.OPEN;
let oldGetSocketStatus;
/* BEGIN stubbing globals */ /* BEGIN stubbing globals */
before(function() { before(() => {
oldGetSocketStatus = window.getSocketStatus; oldGetSocketStatus = window.getSocketStatus;
window.getSocketStatus = function() { window.getSocketStatus = () => socketStatus;
return socketStatus;
};
}); });
after(function() { after(() => {
window.getSocketStatus = oldGetSocketStatus; window.getSocketStatus = oldGetSocketStatus;
// It turns out that continued calls to window.getSocketStatus happen // It turns out that continued calls to window.getSocketStatus happen
// because we host NetworkStatusView in three mock interfaces, and the view // because we host NetworkStatusView in three mock interfaces, and the view
// checks every N seconds. That results in infinite errors unless there is // checks every N seconds. That results in infinite errors unless there is
// something to call. // something to call.
window.getSocketStatus = function() { window.getSocketStatus = () => WebSocket.OPEN;
return WebSocket.OPEN;
};
}); });
/* END stubbing globals */ /* END stubbing globals */
beforeEach(function() { beforeEach(() => {
networkStatusView = new Whisper.NetworkStatusView(); networkStatusView = new Whisper.NetworkStatusView();
$('.network-status-container').append(networkStatusView.el); $('.network-status-container').append(networkStatusView.el);
}); });
afterEach(function() { afterEach(() => {
// prevents huge number of errors on console after running tests // prevents huge number of errors on console after running tests
clearInterval(networkStatusView.renderIntervalHandle); clearInterval(networkStatusView.renderIntervalHandle);
networkStatusView = null; networkStatusView = null;
}); });
describe('initialization', function() { describe('initialization', () => {
it('should have an empty interval', function() { it('should have an empty interval', () => {
assert.equal( assert.equal(
networkStatusView.socketReconnectWaitDuration.asSeconds(), networkStatusView.socketReconnectWaitDuration.asSeconds(),
0 0
); );
}); });
}); });
describe('network status with no connection', function() { describe('network status with no connection', () => {
beforeEach(function() { beforeEach(() => {
networkStatusView.navigatorOnLine = function() { networkStatusView.navigatorOnLine = () => false;
return false;
};
}); });
it('should be interrupted', function() { it('should be interrupted', () => {
networkStatusView.update(); networkStatusView.update();
var status = networkStatusView.getNetworkStatus(); const status = networkStatusView.getNetworkStatus();
assert(status.hasInterruption); assert(status.hasInterruption);
assert.equal(status.instructions, 'Check your network connection.'); assert.equal(status.instructions, 'Check your network connection.');
}); });
it('should display an offline message', function() { it('should display an offline message', () => {
networkStatusView.update(); networkStatusView.update();
assert.match(networkStatusView.$el.text(), /Offline/); assert.match(networkStatusView.$el.text(), /Offline/);
}); });
it('should override socket status', function() { it('should override socket status', () => {
_([ _([
WebSocket.CONNECTING, WebSocket.CONNECTING,
WebSocket.OPEN, WebSocket.OPEN,
WebSocket.CLOSING, WebSocket.CLOSING,
WebSocket.CLOSED, WebSocket.CLOSED,
]).map(function(socketStatusVal) { ]).forEach(socketStatusVal => {
socketStatus = socketStatusVal; socketStatus = socketStatusVal;
networkStatusView.update(); networkStatusView.update();
assert.match(networkStatusView.$el.text(), /Offline/); assert.match(networkStatusView.$el.text(), /Offline/);
}); });
}); });
it('should override registration status', function() { it('should override registration status', () => {
Whisper.Registration.remove(); Whisper.Registration.remove();
networkStatusView.update(); networkStatusView.update();
assert.match(networkStatusView.$el.text(), /Offline/); assert.match(networkStatusView.$el.text(), /Offline/);
}); });
}); });
describe('network status when registration is not done', function() { describe('network status when registration is not done', () => {
beforeEach(function() { beforeEach(() => {
Whisper.Registration.remove(); Whisper.Registration.remove();
}); });
it('should display an unlinked message', function() { it('should display an unlinked message', () => {
networkStatusView.update(); networkStatusView.update();
assert.match(networkStatusView.$el.text(), /Relink/); assert.match(networkStatusView.$el.text(), /Relink/);
}); });
it('should override socket status', function() { it('should override socket status', () => {
_([ _([
WebSocket.CONNECTING, WebSocket.CONNECTING,
WebSocket.OPEN, WebSocket.OPEN,
WebSocket.CLOSING, WebSocket.CLOSING,
WebSocket.CLOSED, WebSocket.CLOSED,
]).map(function(socketStatusVal) { ]).forEach(socketStatusVal => {
socketStatus = socketStatusVal; socketStatus = socketStatusVal;
networkStatusView.update(); networkStatusView.update();
assert.match(networkStatusView.$el.text(), /Relink/); assert.match(networkStatusView.$el.text(), /Relink/);
}); });
}); });
}); });
describe('network status when registration is done', function() { describe('network status when registration is done', () => {
beforeEach(function() { beforeEach(() => {
networkStatusView.navigatorOnLine = function() { networkStatusView.navigatorOnLine = () => true;
return true;
};
Whisper.Registration.markDone(); Whisper.Registration.markDone();
networkStatusView.update(); networkStatusView.update();
}); });
it('should not display an unlinked message', function() { it('should not display an unlinked message', () => {
networkStatusView.update(); networkStatusView.update();
assert.notMatch(networkStatusView.$el.text(), /Relink/); assert.notMatch(networkStatusView.$el.text(), /Relink/);
}); });
}); });
describe('network status when socket is connecting', function() { describe('network status when socket is connecting', () => {
beforeEach(function() { beforeEach(() => {
Whisper.Registration.markDone(); Whisper.Registration.markDone();
socketStatus = WebSocket.CONNECTING; socketStatus = WebSocket.CONNECTING;
networkStatusView.update(); networkStatusView.update();
}); });
it('it should display a connecting string if connecting and not in the connecting grace period', function() { it('it should display a connecting string if connecting and not in the connecting grace period', () => {
networkStatusView.withinConnectingGracePeriod = false; networkStatusView.withinConnectingGracePeriod = false;
var status = networkStatusView.getNetworkStatus(); networkStatusView.getNetworkStatus();
assert.match(networkStatusView.$el.text(), /Connecting/); assert.match(networkStatusView.$el.text(), /Connecting/);
}); });
it('it should not be interrupted if in connecting grace period', function() { it('it should not be interrupted if in connecting grace period', () => {
assert(networkStatusView.withinConnectingGracePeriod); assert(networkStatusView.withinConnectingGracePeriod);
var status = networkStatusView.getNetworkStatus(); const status = networkStatusView.getNetworkStatus();
assert.match(networkStatusView.$el.text(), /Connecting/); assert.match(networkStatusView.$el.text(), /Connecting/);
assert(!status.hasInterruption); assert(!status.hasInterruption);
}); });
it('it should be interrupted if connecting grace period is over', function() { it('it should be interrupted if connecting grace period is over', () => {
networkStatusView.withinConnectingGracePeriod = false; networkStatusView.withinConnectingGracePeriod = false;
var status = networkStatusView.getNetworkStatus(); const status = networkStatusView.getNetworkStatus();
assert(status.hasInterruption); assert(status.hasInterruption);
}); });
}); });
describe('network status when socket is open', function() { describe('network status when socket is open', () => {
before(function() { before(() => {
socketStatus = WebSocket.OPEN; socketStatus = WebSocket.OPEN;
}); });
it('should not be interrupted', function() { it('should not be interrupted', () => {
var status = networkStatusView.getNetworkStatus(); const status = networkStatusView.getNetworkStatus();
assert(!status.hasInterruption); assert(!status.hasInterruption);
assert.match( assert.match(
networkStatusView.$el networkStatusView.$el
@ -154,23 +148,23 @@ describe('NetworkStatusView', function() {
); );
}); });
}); });
describe('network status when socket is closed or closing', function() { describe('network status when socket is closed or closing', () => {
_([WebSocket.CLOSED, WebSocket.CLOSING]).map(function(socketStatusVal) { _([WebSocket.CLOSED, WebSocket.CLOSING]).forEach(socketStatusVal => {
it('should be interrupted', function() { it('should be interrupted', () => {
socketStatus = socketStatusVal; socketStatus = socketStatusVal;
networkStatusView.update(); networkStatusView.update();
var status = networkStatusView.getNetworkStatus(); const status = networkStatusView.getNetworkStatus();
assert(status.hasInterruption); assert(status.hasInterruption);
}); });
}); });
}); });
describe('the socket reconnect interval', function() { describe('the socket reconnect interval', () => {
beforeEach(function() { beforeEach(() => {
socketStatus = WebSocket.CLOSED; socketStatus = WebSocket.CLOSED;
networkStatusView.setSocketReconnectInterval(61000); networkStatusView.setSocketReconnectInterval(61000);
networkStatusView.update(); networkStatusView.update();
}); });
it('should format the message based on the socketReconnectWaitDuration property', function() { it('should format the message based on the socketReconnectWaitDuration property', () => {
assert.equal( assert.equal(
networkStatusView.socketReconnectWaitDuration.asSeconds(), networkStatusView.socketReconnectWaitDuration.asSeconds(),
61 61
@ -180,7 +174,7 @@ describe('NetworkStatusView', function() {
/Attempting reconnect/ /Attempting reconnect/
); );
}); });
it('should be reset by changing the socketStatus to CONNECTING', function() {}); it('should be reset by changing the socketStatus to CONNECTING', () => {});
}); });
}); });
}); });

View file

@ -1,28 +1,30 @@
describe('ScrollDownButtonView', function() { /* global Whisper */
it('renders with count = 0', function() {
var view = new Whisper.ScrollDownButtonView(); describe('ScrollDownButtonView', () => {
it('renders with count = 0', () => {
const view = new Whisper.ScrollDownButtonView();
view.render(); view.render();
assert.equal(view.count, 0); assert.equal(view.count, 0);
assert.match(view.$el.html(), /Scroll to bottom/); assert.match(view.$el.html(), /Scroll to bottom/);
}); });
it('renders with count = 1', function() { it('renders with count = 1', () => {
var view = new Whisper.ScrollDownButtonView({ count: 1 }); const view = new Whisper.ScrollDownButtonView({ count: 1 });
view.render(); view.render();
assert.equal(view.count, 1); assert.equal(view.count, 1);
assert.match(view.$el.html(), /New message below/); assert.match(view.$el.html(), /New message below/);
}); });
it('renders with count = 2', function() { it('renders with count = 2', () => {
var view = new Whisper.ScrollDownButtonView({ count: 2 }); const view = new Whisper.ScrollDownButtonView({ count: 2 });
view.render(); view.render();
assert.equal(view.count, 2); assert.equal(view.count, 2);
assert.match(view.$el.html(), /New messages below/); assert.match(view.$el.html(), /New messages below/);
}); });
it('increments count and re-renders', function() { it('increments count and re-renders', () => {
var view = new Whisper.ScrollDownButtonView(); const view = new Whisper.ScrollDownButtonView();
view.render(); view.render();
assert.equal(view.count, 0); assert.equal(view.count, 0);
assert.notMatch(view.$el.html(), /New message below/); assert.notMatch(view.$el.html(), /New message below/);

View file

@ -1,17 +1,19 @@
describe('Threads', function() { /* global Whisper */
it('should be ordered newest to oldest', function() {
describe('Threads', () => {
it('should be ordered newest to oldest', () => {
// Timestamps // Timestamps
var today = new Date(); const today = new Date();
var tomorrow = new Date(); const tomorrow = new Date();
tomorrow.setDate(today.getDate() + 1); tomorrow.setDate(today.getDate() + 1);
// Add threads // Add threads
Whisper.Threads.add({ timestamp: today }); Whisper.Threads.add({ timestamp: today });
Whisper.Threads.add({ timestamp: tomorrow }); Whisper.Threads.add({ timestamp: tomorrow });
var models = Whisper.Threads.models; const { models } = Whisper.Threads;
var firstTimestamp = models[0].get('timestamp').getTime(); const firstTimestamp = models[0].get('timestamp').getTime();
var secondTimestamp = models[1].get('timestamp').getTime(); const secondTimestamp = models[1].get('timestamp').getTime();
// Compare timestamps // Compare timestamps
assert(firstTimestamp > secondTimestamp); assert(firstTimestamp > secondTimestamp);

View file

@ -1,38 +1,41 @@
/* global moment, Whisper */
'use strict'; 'use strict';
describe('TimestampView', function() { describe('TimestampView', () => {
it('formats long-ago timestamps correctly', function() { it('formats long-ago timestamps correctly', () => {
var timestamp = Date.now(); const timestamp = Date.now();
var brief_view = new Whisper.TimestampView({ brief: true }).render(), const briefView = new Whisper.TimestampView({ brief: true }).render();
ext_view = new Whisper.ExtendedTimestampView().render(); const extendedView = new Whisper.ExtendedTimestampView().render();
// Helper functions to check absolute and relative timestamps // Helper functions to check absolute and relative timestamps
// Helper to check an absolute TS for an exact match // Helper to check an absolute TS for an exact match
var check = function(view, ts, expected) { const check = (view, ts, expected) => {
var result = view.getRelativeTimeSpanString(ts); const result = view.getRelativeTimeSpanString(ts);
assert.strictEqual(result, expected); assert.strictEqual(result, expected);
}; };
// Helper to check relative times for an exact match against both views // Helper to check relative times for an exact match against both views
var checkDiff = function(sec_ago, expected_brief, expected_ext) { const checkDiff = (secAgo, expectedBrief, expectedExtended) => {
check(brief_view, timestamp - sec_ago * 1000, expected_brief); check(briefView, timestamp - secAgo * 1000, expectedBrief);
check(ext_view, timestamp - sec_ago * 1000, expected_ext); check(extendedView, timestamp - secAgo * 1000, expectedExtended);
}; };
// Helper to check an absolute TS for an exact match against both views // Helper to check an absolute TS for an exact match against both views
var checkAbs = function(ts, expected_brief, expected_ext) { const checkAbs = (ts, expectedBrief, expectedExtended) => {
if (!expected_ext) { if (!expectedExtended) {
expected_ext = expected_brief; // eslint-disable-next-line no-param-reassign
expectedExtended = expectedBrief;
} }
check(brief_view, ts, expected_brief); check(briefView, ts, expectedBrief);
check(ext_view, ts, expected_ext); check(extendedView, ts, expectedExtended);
}; };
// Helper to check an absolute TS for a match at the beginning against // Helper to check an absolute TS for a match at the beginning against
var checkStartsWith = function(view, ts, expected) { const checkStartsWith = (view, ts, expected) => {
var result = view.getRelativeTimeSpanString(ts); const result = view.getRelativeTimeSpanString(ts);
var regexp = new RegExp('^' + expected); const regexp = new RegExp(`^${expected}`);
assert.match(result, regexp); assert.match(result, regexp);
}; };
@ -48,82 +51,85 @@ describe('TimestampView', function() {
checkDiff(125 * 60, '2 hours', '2 hours ago'); checkDiff(125 * 60, '2 hours', '2 hours ago');
// set to third of month to avoid problems on the 29th/30th/31st // set to third of month to avoid problems on the 29th/30th/31st
var last_month = moment() const lastMonth = moment()
.subtract(1, 'month') .subtract(1, 'month')
.date(3), .date(3);
months = [ const months = [
'Jan', 'Jan',
'Feb', 'Feb',
'Mar', 'Mar',
'Apr', 'Apr',
'May', 'May',
'Jun', 'Jun',
'Jul', 'Jul',
'Aug', 'Aug',
'Sep', 'Sep',
'Oct', 'Oct',
'Nov', 'Nov',
'Dec', 'Dec',
], ];
day_of_month = new Date().getDate(); check(briefView, lastMonth, `${months[lastMonth.month()]} 3`);
check(brief_view, last_month, months[last_month.month()] + ' 3'); checkStartsWith(extendedView, lastMonth, `${months[lastMonth.month()]} 3`);
checkStartsWith(ext_view, last_month, months[last_month.month()] + ' 3');
// subtract 26 hours to be safe in case of DST stuff // subtract 26 hours to be safe in case of DST stuff
var yesterday = new Date(timestamp - 26 * 60 * 60 * 1000), const yesterday = new Date(timestamp - 26 * 60 * 60 * 1000);
days_of_week = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; const daysOfWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
check(brief_view, yesterday, days_of_week[yesterday.getDay()]); check(briefView, yesterday, daysOfWeek[yesterday.getDay()]);
checkStartsWith(ext_view, yesterday, days_of_week[yesterday.getDay()]); checkStartsWith(extendedView, yesterday, daysOfWeek[yesterday.getDay()]);
// Check something long ago // Check something long ago
// months are zero-indexed in JS for some reason // months are zero-indexed in JS for some reason
check(brief_view, new Date(2012, 4, 5, 17, 30, 0), 'May 5, 2012'); check(briefView, new Date(2012, 4, 5, 17, 30, 0), 'May 5, 2012');
checkStartsWith(ext_view, new Date(2012, 4, 5, 17, 30, 0), 'May 5, 2012'); checkStartsWith(
extendedView,
new Date(2012, 4, 5, 17, 30, 0),
'May 5, 2012'
);
}); });
describe('updates within a minute reasonable intervals', function() { describe('updates within a minute reasonable intervals', () => {
var view; let view;
beforeEach(function() { beforeEach(() => {
view = new Whisper.TimestampView(); view = new Whisper.TimestampView();
}); });
afterEach(function() { afterEach(() => {
clearTimeout(view.timeout); clearTimeout(view.timeout);
}); });
it('updates timestamps this minute within a minute', function() { it('updates timestamps this minute within a minute', () => {
var now = Date.now(); const now = Date.now();
view.$el.attr('data-timestamp', now - 1000); view.$el.attr('data-timestamp', now - 1000);
view.update(); view.update();
assert.isAbove(view.delay, 0); // non zero assert.isAbove(view.delay, 0); // non zero
assert.isBelow(view.delay, 60 * 1000); // < minute assert.isBelow(view.delay, 60 * 1000); // < minute
}); });
it('updates timestamps from this hour within a minute', function() { it('updates timestamps from this hour within a minute', () => {
var now = Date.now(); const now = Date.now();
view.$el.attr('data-timestamp', now - 1000 - 1000 * 60 * 5); // 5 minutes and 1 sec ago view.$el.attr('data-timestamp', now - 1000 - 1000 * 60 * 5); // 5 minutes and 1 sec ago
view.update(); view.update();
assert.isAbove(view.delay, 0); // non zero assert.isAbove(view.delay, 0); // non zero
assert.isBelow(view.delay, 60 * 1000); // minute assert.isBelow(view.delay, 60 * 1000); // minute
}); });
it('updates timestamps from today within an hour', function() { it('updates timestamps from today within an hour', () => {
var now = Date.now(); const now = Date.now();
view.$el.attr('data-timestamp', now - 1000 - 1000 * 60 * 60 * 5); // 5 hours and 1 sec ago view.$el.attr('data-timestamp', now - 1000 - 1000 * 60 * 60 * 5); // 5 hours and 1 sec ago
view.update(); view.update();
assert.isAbove(view.delay, 60 * 1000); // minute assert.isAbove(view.delay, 60 * 1000); // minute
assert.isBelow(view.delay, 60 * 60 * 1000); // hour assert.isBelow(view.delay, 60 * 60 * 1000); // hour
}); });
it('updates timestamps from this week within a day', function() { it('updates timestamps from this week within a day', () => {
var now = Date.now(); const now = Date.now();
view.$el.attr('data-timestamp', now - 1000 - 6 * 24 * 60 * 60 * 1000); // 6 days and 1 sec ago view.$el.attr('data-timestamp', now - 1000 - 6 * 24 * 60 * 60 * 1000); // 6 days and 1 sec ago
view.update(); view.update();
assert.isAbove(view.delay, 60 * 60 * 1000); // hour assert.isAbove(view.delay, 60 * 60 * 1000); // hour
assert.isBelow(view.delay, 36 * 60 * 60 * 1000); // day and a half assert.isBelow(view.delay, 36 * 60 * 60 * 1000); // day and a half
}); });
it('does not updates very old timestamps', function() { it('does not updates very old timestamps', () => {
var now = Date.now(); const now = Date.now();
// return falsey value for long ago dates that don't update // return falsey value for long ago dates that don't update
view.$el.attr('data-timestamp', now - 8 * 24 * 60 * 60 * 1000); view.$el.attr('data-timestamp', now - 8 * 24 * 60 * 60 * 1000);
view.update(); view.update();

View file

@ -1,35 +1,37 @@
describe('Whisper.View', function() { /* global Whisper */
it('renders a template with render_attributes', function() {
var viewClass = Whisper.View.extend({ describe('Whisper.View', () => {
it('renders a template with render_attributes', () => {
const ViewClass = Whisper.View.extend({
template: '<div>{{ variable }}</div>', template: '<div>{{ variable }}</div>',
render_attributes: { render_attributes: {
variable: 'value', variable: 'value',
}, },
}); });
var view = new viewClass(); const view = new ViewClass();
view.render(); view.render();
assert.strictEqual(view.$el.html(), '<div>value</div>'); assert.strictEqual(view.$el.html(), '<div>value</div>');
}); });
it('renders a template with no render_attributes', function() { it('renders a template with no render_attributes', () => {
var viewClass = Whisper.View.extend({ const ViewClass = Whisper.View.extend({
template: '<div>static text</div>', template: '<div>static text</div>',
}); });
var view = new viewClass(); const view = new ViewClass();
view.render(); view.render();
assert.strictEqual(view.$el.html(), '<div>static text</div>'); assert.strictEqual(view.$el.html(), '<div>static text</div>');
}); });
it('renders a template function with render_attributes function', function() { it('renders a template function with render_attributes function', () => {
var viewClass = Whisper.View.extend({ const ViewClass = Whisper.View.extend({
template: function() { template() {
return '<div>{{ variable }}</div>'; return '<div>{{ variable }}</div>';
}, },
render_attributes: function() { render_attributes() {
return { variable: 'value' }; return { variable: 'value' };
}, },
}); });
var view = new viewClass(); const view = new ViewClass();
view.render(); view.render();
assert.strictEqual(view.$el.html(), '<div>value</div>'); assert.strictEqual(view.$el.html(), '<div>value</div>');
}); });