Format all source code using Prettier

This commit is contained in:
Daniel Gasienica
2018-04-27 17:25:04 -04:00
parent b4dee3f30b
commit 1dd87ad197
149 changed files with 17847 additions and 15439 deletions

View File

@@ -7,7 +7,7 @@ module.exports = {
},
globals: {
assert: true
assert: true,
},
parserOptions: {
@@ -16,11 +16,14 @@ module.exports = {
rules: {
// We still get the value of this rule, it just allows for dev deps
'import/no-extraneous-dependencies': ['error', {
devDependencies: true
}],
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: true,
},
],
// We want to keep each test structured the same, even if its contents are tiny
'arrow-body-style': 'off',
}
},
};

View File

@@ -27,7 +27,7 @@ window.PROTO_ROOT = '../protos';
result: false,
message: err.message,
stack: err.stack,
titles: flattenTitles(test)
titles: flattenTitles(test),
});
});
@@ -37,10 +37,10 @@ window.PROTO_ROOT = '../protos';
SauceReporter.prototype = OriginalReporter.prototype;
mocha.reporter(SauceReporter);
}());
})();
// Override the database id.
window.Whisper = window.Whisper || {};
window.Whisper = window.Whisper || {};
window.Whisper.Database = window.Whisper.Database || {};
Whisper.Database.id = 'test';
@@ -49,21 +49,23 @@ Whisper.Database.id = 'test';
*/
function assertEqualArrayBuffers(ab1, ab2) {
assert.deepEqual(new Uint8Array(ab1), new Uint8Array(ab2));
};
}
function hexToArrayBuffer(str) {
var ret = new ArrayBuffer(str.length / 2);
var array = new Uint8Array(ret);
for (var i = 0; i < str.length/2; i++) {
array[i] = parseInt(str.substr(i*2, 2), 16);
for (var i = 0; i < str.length / 2; i++) {
array[i] = parseInt(str.substr(i * 2, 2), 16);
}
return ret;
};
}
/* Delete the database before running any tests */
before(function(done) {
var idbReq = indexedDB.deleteDatabase('test');
idbReq.onsuccess = function() { done(); };
idbReq.onsuccess = function() {
done();
};
});
async function clearDatabase(done) {
@@ -80,5 +82,5 @@ async function clearDatabase(done) {
await messages.destroyAll();
if (done) {
done();
};
}
}

View File

@@ -4,8 +4,9 @@ const tmp = require('tmp');
const { assert } = require('chai');
const Attachments = require('../../app/attachments');
const { stringToArrayBuffer } = require('../../js/modules/string_to_array_buffer');
const {
stringToArrayBuffer,
} = require('../../js/modules/string_to_array_buffer');
const PREFIX_LENGTH = 2;
const NUM_SEPARATORS = 1;
@@ -30,7 +31,9 @@ describe('Attachments', () => {
'Attachments_createWriterForNew'
);
const outputPath = await Attachments.createWriterForNew(tempDirectory)(input);
const outputPath = await Attachments.createWriterForNew(tempDirectory)(
input
);
const output = await fse.readFile(path.join(tempDirectory, outputPath));
assert.lengthOf(outputPath, PATH_LENGTH);
@@ -57,13 +60,16 @@ describe('Attachments', () => {
'Attachments_createWriterForExisting'
);
const relativePath = Attachments.getRelativePath(Attachments.createName());
const relativePath = Attachments.getRelativePath(
Attachments.createName()
);
const attachment = {
path: relativePath,
data: input,
};
const outputPath =
await Attachments.createWriterForExisting(tempDirectory)(attachment);
const outputPath = await Attachments.createWriterForExisting(
tempDirectory
)(attachment);
const output = await fse.readFile(path.join(tempDirectory, outputPath));
assert.equal(outputPath, relativePath);
@@ -84,16 +90,23 @@ describe('Attachments', () => {
});
it('should read file from disk', async () => {
const tempDirectory = path.join(tempRootDirectory, 'Attachments_createReader');
const tempDirectory = path.join(
tempRootDirectory,
'Attachments_createReader'
);
const relativePath = Attachments.getRelativePath(Attachments.createName());
const relativePath = Attachments.getRelativePath(
Attachments.createName()
);
const fullPath = path.join(tempDirectory, relativePath);
const input = stringToArrayBuffer('test string');
const inputBuffer = Buffer.from(input);
await fse.ensureFile(fullPath);
await fse.writeFile(fullPath, inputBuffer);
const output = await Attachments.createReader(tempDirectory)(relativePath);
const output = await Attachments.createReader(tempDirectory)(
relativePath
);
assert.deepEqual(input, output);
});
@@ -110,9 +123,14 @@ describe('Attachments', () => {
});
it('should delete file from disk', async () => {
const tempDirectory = path.join(tempRootDirectory, 'Attachments_createDeleter');
const tempDirectory = path.join(
tempRootDirectory,
'Attachments_createDeleter'
);
const relativePath = Attachments.getRelativePath(Attachments.createName());
const relativePath = Attachments.getRelativePath(
Attachments.createName()
);
const fullPath = path.join(tempDirectory, relativePath);
const input = stringToArrayBuffer('test string');
@@ -134,7 +152,8 @@ describe('Attachments', () => {
describe('getRelativePath', () => {
it('should return correct path', () => {
const name = '608ce3bc536edbf7637a6aeb6040bdfec49349140c0dd43e97c7ce263b15ff7e';
const name =
'608ce3bc536edbf7637a6aeb6040bdfec49349140c0dd43e97c7ce263b15ff7e';
assert.lengthOf(Attachments.getRelativePath(name), PATH_LENGTH);
});
});

View File

@@ -26,7 +26,7 @@ describe('app/logging', () => {
basePath = tmpDir.name;
});
afterEach((done) => {
afterEach(done => {
// we need the unsafe option to recursively remove the directory
tmpDir.removeCallback(done);
});
@@ -149,9 +149,11 @@ describe('app/logging', () => {
].join('\n');
const target = path.join(basePath, 'log.log');
const files = [{
path: target,
}];
const files = [
{
path: target,
},
];
fs.writeFileSync(target, contents);
@@ -172,9 +174,11 @@ describe('app/logging', () => {
].join('\n');
const target = path.join(basePath, 'log.log');
const files = [{
path: target,
}];
const files = [
{
path: target,
},
];
fs.writeFileSync(target, contents);
@@ -187,11 +191,16 @@ describe('app/logging', () => {
describe('#fetchLog', () => {
it('returns error if file does not exist', () => {
const target = 'random_file';
return fetchLog(target).then(() => {
throw new Error('Expected an error!');
}, (error) => {
expect(error).to.have.property('message').that.match(/random_file/);
});
return fetchLog(target).then(
() => {
throw new Error('Expected an error!');
},
error => {
expect(error)
.to.have.property('message')
.that.match(/random_file/);
}
);
});
it('returns empty array if file has no valid JSON lines', () => {
const contents = 'line 1\nline2\n';
@@ -200,7 +209,7 @@ describe('app/logging', () => {
fs.writeFileSync(target, contents);
return fetchLog(target).then((result) => {
return fetchLog(target).then(result => {
expect(result).to.deep.equal(expected);
});
});
@@ -222,21 +231,24 @@ describe('app/logging', () => {
}),
'',
].join('\n');
const expected = [{
level: 1,
time: 2,
msg: 3,
}, {
level: 2,
time: 3,
msg: 4,
}];
const expected = [
{
level: 1,
time: 2,
msg: 3,
},
{
level: 2,
time: 3,
msg: 4,
},
];
const target = path.join(basePath, 'test.log');
fs.writeFileSync(target, contents);
return fetchLog(target).then((result) => {
return fetchLog(target).then(result => {
expect(result).to.deep.equal(expected);
});
});
@@ -244,7 +256,7 @@ describe('app/logging', () => {
describe('#fetch', () => {
it('returns single entry if no files', () => {
return fetch(basePath).then((results) => {
return fetch(basePath).then(results => {
expect(results).to.have.length(1);
expect(results[0].msg).to.match(/Loaded this list/);
});
@@ -263,7 +275,7 @@ describe('app/logging', () => {
fs.writeFileSync(path.join(basePath, 'first.log'), first);
fs.writeFileSync(path.join(basePath, 'second.log'), second);
return fetch(basePath).then((results) => {
return fetch(basePath).then(results => {
expect(results).to.have.length(4);
expect(results[0].msg).to.equal(1);
expect(results[1].msg).to.equal(2);

View File

@@ -3,7 +3,6 @@ const { assert } = require('chai');
const SignalMenu = require('../../app/menu');
const { load: loadLocale } = require('../../app/locale');
const PLATFORMS = [
{
label: 'macOS',
@@ -37,7 +36,7 @@ describe('SignalMenu', () => {
describe('createTemplate', () => {
PLATFORMS.forEach(({ label, platform, fixtures }) => {
context(label, () => {
INCLUDE_SETUP_OPTIONS.forEach((includeSetup) => {
INCLUDE_SETUP_OPTIONS.forEach(includeSetup => {
const prefix = includeSetup ? 'with' : 'without';
context(`${prefix} setup options`, () => {
it('should return correct template', () => {
@@ -65,7 +64,9 @@ describe('SignalMenu', () => {
const { messages } = loadLocale({ appLocale, logger });
const actual = SignalMenu.createTemplate(options, messages);
const fixturePath = includeSetup ? fixtures.setup : fixtures.default;
const fixturePath = includeSetup
? fixtures.setup
: fixtures.default;
// eslint-disable-next-line global-require, import/no-dynamic-require
const fixture = require(fixturePath);
assert.deepEqual(actual, fixture);

View File

@@ -9,7 +9,7 @@
describe('Backup', () => {
describe('_sanitizeFileName', () => {
it('leaves a basic string alone', () => {
const initial = 'Hello, how are you #5 (\'fine\' + great).jpg';
const initial = "Hello, how are you #5 ('fine' + great).jpg";
const expected = initial;
assert.strictEqual(Signal.Backup._sanitizeFileName(initial), expected);
});
@@ -29,7 +29,8 @@ describe('Backup', () => {
});
it('handles a file with a long extension', () => {
const initial = '0123456789012345678901234567890123456789.01234567890123456789';
const initial =
'0123456789012345678901234567890123456789.01234567890123456789';
const expected = '012345678901234567890123456789';
assert.strictEqual(Signal.Backup._trimFileName(initial), expected);
});
@@ -165,7 +166,10 @@ describe('Backup', () => {
id: 'id',
};
const expected = '123 (012345678901234567890123456789 id)';
assert.strictEqual(Signal.Backup._getConversationDirName(conversation), expected);
assert.strictEqual(
Signal.Backup._getConversationDirName(conversation),
expected
);
});
it('uses just id if name is not available', () => {
@@ -174,7 +178,10 @@ describe('Backup', () => {
id: 'id',
};
const expected = '123 (id)';
assert.strictEqual(Signal.Backup._getConversationDirName(conversation), expected);
assert.strictEqual(
Signal.Backup._getConversationDirName(conversation),
expected
);
});
it('uses inactive for missing active_at', () => {
@@ -183,7 +190,10 @@ describe('Backup', () => {
id: 'id',
};
const expected = 'inactive (name id)';
assert.strictEqual(Signal.Backup._getConversationDirName(conversation), expected);
assert.strictEqual(
Signal.Backup._getConversationDirName(conversation),
expected
);
});
});
@@ -229,23 +239,45 @@ describe('Backup', () => {
describe('end-to-end', () => {
it('exports then imports to produce the same data we started with', async () => {
const {
attachmentsPath,
fse,
glob,
path,
tmp,
} = window.test;
const { attachmentsPath, fse, glob, path, tmp } = window.test;
const {
upgradeMessageSchema,
loadAttachmentData,
} = window.Signal.Migrations;
const key = new Uint8Array([
1, 3, 4, 5, 6, 7, 8, 11,
23, 34, 1, 34, 3, 5, 45, 45,
1, 3, 4, 5, 6, 7, 8, 11,
23, 34, 1, 34, 3, 5, 45, 45,
1,
3,
4,
5,
6,
7,
8,
11,
23,
34,
1,
34,
3,
5,
45,
45,
1,
3,
4,
5,
6,
7,
8,
11,
23,
34,
1,
34,
3,
5,
45,
45,
]);
const attachmentsPattern = path.join(attachmentsPath, '**');
@@ -279,7 +311,7 @@ describe('Backup', () => {
// glob returns only /. We normalize to / separators for our manipulations.
const normalizedBase = attachmentsPath.replace(/\\/g, '/');
function removeDirs(dirs) {
return _.filter(dirs, (fullDir) => {
return _.filter(dirs, fullDir => {
const dir = fullDir.replace(normalizedBase, '');
return TWO_SLASHES.test(dir);
});
@@ -291,7 +323,7 @@ describe('Backup', () => {
return message;
}
const wrappedMapper = async (attachment) => {
const wrappedMapper = async attachment => {
if (!attachment || !attachment.thumbnail) {
return attachment;
}
@@ -301,18 +333,21 @@ describe('Backup', () => {
});
};
const quotedAttachments = (message.quote && message.quote.attachments) || [];
const quotedAttachments =
(message.quote && message.quote.attachments) || [];
return Object.assign({}, message, {
quote: Object.assign({}, message.quote, {
attachments: await Promise.all(quotedAttachments.map(wrappedMapper)),
attachments: await Promise.all(
quotedAttachments.map(wrappedMapper)
),
}),
});
};
}
async function loadAllFilesFromDisk(message) {
const loadThumbnails = _mapQuotedAttachments((thumbnail) => {
const loadThumbnails = _mapQuotedAttachments(thumbnail => {
// we want to be bulletproof to thumbnails without data
if (!thumbnail.path) {
return thumbnail;
@@ -322,15 +357,12 @@ describe('Backup', () => {
});
const promises = (message.attachments || []).map(attachment =>
wrappedLoadAttachment(attachment));
return Object.assign(
{},
await loadThumbnails(message),
{
attachments: await Promise.all(promises),
}
wrappedLoadAttachment(attachment)
);
return Object.assign({}, await loadThumbnails(message), {
attachments: await Promise.all(promises),
});
}
let backupDir;
@@ -346,16 +378,46 @@ describe('Backup', () => {
received_at: 1524185933350,
timestamp: 1524185933350,
errors: [],
attachments: [{
contentType: 'image/gif',
fileName: 'sad_cat.gif',
data: new Uint8Array([
1, 2, 3, 4, 5, 6, 7, 8,
1, 2, 3, 4, 5, 6, 7, 8,
1, 2, 3, 4, 5, 6, 7, 8,
1, 2, 3, 4, 5, 6, 7, 8,
]).buffer,
}],
attachments: [
{
contentType: 'image/gif',
fileName: 'sad_cat.gif',
data: new Uint8Array([
1,
2,
3,
4,
5,
6,
7,
8,
1,
2,
3,
4,
5,
6,
7,
8,
1,
2,
3,
4,
5,
6,
7,
8,
1,
2,
3,
4,
5,
6,
7,
8,
]).buffer,
},
],
hasAttachments: 1,
hasFileAttachments: undefined,
hasVisualMediaAttachments: 1,
@@ -363,22 +425,53 @@ describe('Backup', () => {
text: "Isn't it cute?",
author: CONTACT_ONE_NUMBER,
id: 12345678,
attachments: [{
contentType: 'audio/mp3',
fileName: 'song.mp3',
}, {
contentType: 'image/gif',
fileName: 'happy_cat.gif',
thumbnail: {
contentType: 'image/png',
data: new Uint8Array([
2, 2, 3, 4, 5, 6, 7, 8,
1, 2, 3, 4, 5, 6, 7, 8,
1, 2, 3, 4, 5, 6, 7, 8,
1, 2, 3, 4, 5, 6, 7, 8,
]).buffer,
attachments: [
{
contentType: 'audio/mp3',
fileName: 'song.mp3',
},
}],
{
contentType: 'image/gif',
fileName: 'happy_cat.gif',
thumbnail: {
contentType: 'image/png',
data: new Uint8Array([
2,
2,
3,
4,
5,
6,
7,
8,
1,
2,
3,
4,
5,
6,
7,
8,
1,
2,
3,
4,
5,
6,
7,
8,
1,
2,
3,
4,
5,
6,
7,
8,
]).buffer,
},
},
],
},
};
@@ -401,18 +494,74 @@ describe('Backup', () => {
profileAvatar: {
contentType: 'image/jpeg',
data: new Uint8Array([
3, 2, 3, 4, 5, 6, 7, 8,
1, 2, 3, 4, 5, 6, 7, 8,
1, 2, 3, 4, 5, 6, 7, 8,
1, 2, 3, 4, 5, 6, 7, 8,
3,
2,
3,
4,
5,
6,
7,
8,
1,
2,
3,
4,
5,
6,
7,
8,
1,
2,
3,
4,
5,
6,
7,
8,
1,
2,
3,
4,
5,
6,
7,
8,
]).buffer,
size: 64,
},
profileKey: new Uint8Array([
4, 2, 3, 4, 5, 6, 7, 8,
1, 2, 3, 4, 5, 6, 7, 8,
1, 2, 3, 4, 5, 6, 7, 8,
1, 2, 3, 4, 5, 6, 7, 8,
4,
2,
3,
4,
5,
6,
7,
8,
1,
2,
3,
4,
5,
6,
7,
8,
1,
2,
3,
4,
5,
6,
7,
8,
1,
2,
3,
4,
5,
6,
7,
8,
]).buffer,
profileName: 'Someone! 🤔',
profileSharing: true,
@@ -432,7 +581,9 @@ describe('Backup', () => {
const conversationModel = new Whisper.Conversation(conversation);
await window.wrapDeferred(conversationModel.save());
console.log('Backup test: Ensure that all attachments were saved to disk');
console.log(
'Backup test: Ensure that all attachments were saved to disk'
);
const attachmentFiles = removeDirs(glob.sync(attachmentsPattern));
console.log({ attachmentFiles });
assert.strictEqual(ATTACHMENT_COUNT, attachmentFiles.length);
@@ -447,7 +598,9 @@ describe('Backup', () => {
const messageZipExists = fse.existsSync(zipPath);
assert.strictEqual(true, messageZipExists);
console.log('Backup test: Ensure that all attachments made it to backup dir');
console.log(
'Backup test: Ensure that all attachments made it to backup dir'
);
const backupAttachmentPattern = path.join(backupDir, 'attachments/*');
const backupAttachments = glob.sync(backupAttachmentPattern);
console.log({ backupAttachments });
@@ -460,7 +613,9 @@ describe('Backup', () => {
await Signal.Backup.importFromDirectory(backupDir, { key });
console.log('Backup test: ensure that all attachments were imported');
const recreatedAttachmentFiles = removeDirs(glob.sync(attachmentsPattern));
const recreatedAttachmentFiles = removeDirs(
glob.sync(attachmentsPattern)
);
console.log({ recreatedAttachmentFiles });
assert.strictEqual(ATTACHMENT_COUNT, recreatedAttachmentFiles.length);
assert.deepEqual(attachmentFiles, recreatedAttachmentFiles);
@@ -472,15 +627,21 @@ describe('Backup', () => {
const messageFromDB = removeId(messageCollection.at(0).attributes);
const expectedMessage = omitUndefinedKeys(message);
console.log({ messageFromDB, expectedMessage });
assert.deepEqual(
messageFromDB,
expectedMessage
);
assert.deepEqual(messageFromDB, expectedMessage);
console.log('Backup test: Check that all attachments were successfully imported');
const messageWithAttachmentsFromDB = await loadAllFilesFromDisk(messageFromDB);
const expectedMessageWithAttachments = omitUndefinedKeys(messageWithAttachments);
console.log({ messageWithAttachmentsFromDB, expectedMessageWithAttachments });
console.log(
'Backup test: Check that all attachments were successfully imported'
);
const messageWithAttachmentsFromDB = await loadAllFilesFromDisk(
messageFromDB
);
const expectedMessageWithAttachments = omitUndefinedKeys(
messageWithAttachments
);
console.log({
messageWithAttachmentsFromDB,
expectedMessageWithAttachments,
});
assert.deepEqual(
_.omit(messageWithAttachmentsFromDB, ['schemaVersion']),
expectedMessageWithAttachments

File diff suppressed because one or more lines are too long

View File

@@ -5,25 +5,35 @@ describe('ConversationController', function() {
var collection = window.getInboxCollection();
collection.reset([]);
collection.add(new Whisper.Conversation({
name: 'No timestamp',
}));
collection.add(new Whisper.Conversation({
name: 'B',
timestamp: 20,
}));
collection.add(new Whisper.Conversation({
name: 'C',
timestamp: 20,
}));
collection.add(new Whisper.Conversation({
name: 'Á',
timestamp: 20,
}));
collection.add(new Whisper.Conversation({
name: 'First!',
timestamp: 30,
}));
collection.add(
new Whisper.Conversation({
name: 'No timestamp',
})
);
collection.add(
new Whisper.Conversation({
name: 'B',
timestamp: 20,
})
);
collection.add(
new Whisper.Conversation({
name: 'C',
timestamp: 20,
})
);
collection.add(
new Whisper.Conversation({
name: 'Á',
timestamp: 20,
})
);
collection.add(
new Whisper.Conversation({
name: 'First!',
timestamp: 30,
})
);
assert.strictEqual(collection.at('0').get('name'), 'First!');
assert.strictEqual(collection.at('1').get('name'), 'Á');

View File

@@ -3,7 +3,10 @@
describe('Crypto', function() {
it('roundtrip symmetric encryption succeeds', async function() {
var message = 'this is my message';
var plaintext = new dcodeIO.ByteBuffer.wrap(message, 'binary').toArrayBuffer();
var plaintext = new dcodeIO.ByteBuffer.wrap(
message,
'binary'
).toArrayBuffer();
var key = textsecure.crypto.getRandomBytes(32);
var encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext);
@@ -17,7 +20,10 @@ describe('Crypto', function() {
it('roundtrip fails if nonce is modified', async function() {
var message = 'this is my message';
var plaintext = new dcodeIO.ByteBuffer.wrap(message, 'binary').toArrayBuffer();
var plaintext = new dcodeIO.ByteBuffer.wrap(
message,
'binary'
).toArrayBuffer();
var key = textsecure.crypto.getRandomBytes(32);
var encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext);
@@ -25,7 +31,10 @@ describe('Crypto', function() {
uintArray[2] = 9;
try {
var decrypted = await Signal.Crypto.decryptSymmetric(key, uintArray.buffer);
var decrypted = await Signal.Crypto.decryptSymmetric(
key,
uintArray.buffer
);
} catch (error) {
assert.strictEqual(
error.message,
@@ -39,7 +48,10 @@ describe('Crypto', function() {
it('fails if mac is modified', async function() {
var message = 'this is my message';
var plaintext = new dcodeIO.ByteBuffer.wrap(message, 'binary').toArrayBuffer();
var plaintext = new dcodeIO.ByteBuffer.wrap(
message,
'binary'
).toArrayBuffer();
var key = textsecure.crypto.getRandomBytes(32);
var encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext);
@@ -47,7 +59,10 @@ describe('Crypto', function() {
uintArray[uintArray.length - 3] = 9;
try {
var decrypted = await Signal.Crypto.decryptSymmetric(key, uintArray.buffer);
var decrypted = await Signal.Crypto.decryptSymmetric(
key,
uintArray.buffer
);
} catch (error) {
assert.strictEqual(
error.message,
@@ -61,7 +76,10 @@ describe('Crypto', function() {
it('fails if encrypted contents are modified', async function() {
var message = 'this is my message';
var plaintext = new dcodeIO.ByteBuffer.wrap(message, 'binary').toArrayBuffer();
var plaintext = new dcodeIO.ByteBuffer.wrap(
message,
'binary'
).toArrayBuffer();
var key = textsecure.crypto.getRandomBytes(32);
var encrypted = await Signal.Crypto.encryptSymmetric(key, plaintext);
@@ -69,7 +87,10 @@ describe('Crypto', function() {
uintArray[35] = 9;
try {
var decrypted = await Signal.Crypto.decryptSymmetric(key, uintArray.buffer);
var decrypted = await Signal.Crypto.decryptSymmetric(
key,
uintArray.buffer
);
} catch (error) {
assert.strictEqual(
error.message,

View File

@@ -1,170 +1,179 @@
'use strict';
describe('EmojiUtil', function() {
describe('getCountOfAllMatches', function() {
it('returns zero for string with no matches', function() {
var r = /s/g;
var str = 'no match';
var actual = emoji.getCountOfAllMatches(str, r);
assert.equal(actual, 0);
});
it('returns 1 for one match', function() {
var r = /s/g;
var str = 'just one match';
var actual = emoji.getCountOfAllMatches(str, r);
assert.equal(actual, 1);
});
it('returns 2 for two matches', function() {
var r = /s/g;
var str = 's + s';
var actual = emoji.getCountOfAllMatches(str, r);
assert.equal(actual, 2);
});
it('returns zero for no match with non-global regular expression', function() {
var r = /s/g;
var str = 'no match';
var actual = emoji.getCountOfAllMatches(str, r);
assert.equal(actual, 0);
});
it('returns 1 for match with non-global regular expression', function() {
var r = /s/;
var str = 's + s';
var actual = emoji.getCountOfAllMatches(str, r);
assert.equal(actual, 1);
});
describe('getCountOfAllMatches', function() {
it('returns zero for string with no matches', function() {
var r = /s/g;
var str = 'no match';
var actual = emoji.getCountOfAllMatches(str, r);
assert.equal(actual, 0);
});
it('returns 1 for one match', function() {
var r = /s/g;
var str = 'just one match';
var actual = emoji.getCountOfAllMatches(str, r);
assert.equal(actual, 1);
});
it('returns 2 for two matches', function() {
var r = /s/g;
var str = 's + s';
var actual = emoji.getCountOfAllMatches(str, r);
assert.equal(actual, 2);
});
it('returns zero for no match with non-global regular expression', function() {
var r = /s/g;
var str = 'no match';
var actual = emoji.getCountOfAllMatches(str, r);
assert.equal(actual, 0);
});
it('returns 1 for match with non-global regular expression', function() {
var r = /s/;
var str = 's + s';
var actual = emoji.getCountOfAllMatches(str, r);
assert.equal(actual, 1);
});
});
describe('hasNormalCharacters', function() {
it('returns true for all normal text', function() {
var str = 'normal';
var actual = emoji.hasNormalCharacters(str);
assert.equal(actual, true);
});
it('returns false for all emoji text', function() {
var str = '🔥🔥🔥🔥';
var actual = emoji.hasNormalCharacters(str);
assert.equal(actual, false);
});
it('returns false for emojis mixed with spaces', function() {
var str = '🔥 🔥 🔥 🔥';
var actual = emoji.hasNormalCharacters(str);
assert.equal(actual, false);
});
it('returns true for emojis and text', function() {
var str = '🔥 normal 🔥 🔥 🔥';
var actual = emoji.hasNormalCharacters(str);
assert.equal(actual, true);
});
});
describe('getSizeClass', function() {
it('returns nothing for non-emoji text', function() {
assert.equal(emoji.getSizeClass('normal text'), '');
});
it('returns nothing for emojis mixed with text', function() {
assert.equal(emoji.getSizeClass('🔥 normal 🔥'), '');
});
it('returns nothing for more than 8 emojis', function() {
assert.equal(emoji.getSizeClass('🔥🔥 🔥🔥 🔥🔥 🔥🔥 🔥'), '');
});
it('returns "small" for 7-8 emojis', function() {
assert.equal(emoji.getSizeClass('🔥🔥 🔥🔥 🔥🔥 🔥🔥'), 'small');
assert.equal(emoji.getSizeClass('🔥🔥 🔥🔥 🔥🔥 🔥'), 'small');
});
it('returns "medium" for 5-6 emojis', function() {
assert.equal(emoji.getSizeClass('🔥🔥 🔥🔥 🔥🔥'), 'medium');
assert.equal(emoji.getSizeClass('🔥🔥 🔥🔥 🔥'), 'medium');
});
it('returns "large" for 3-4 emojis', function() {
assert.equal(emoji.getSizeClass('🔥🔥 🔥🔥'), 'large');
assert.equal(emoji.getSizeClass('🔥🔥 🔥'), 'large');
});
it('returns "jumbo" for 1-2 emojis', function() {
assert.equal(emoji.getSizeClass('🔥🔥'), 'jumbo');
assert.equal(emoji.getSizeClass('🔥'), 'jumbo');
});
});
describe('addClass', function() {
it('returns original string if no emoji images', function() {
var start = 'no images. but there is some 🔥. <img src="random.jpg" />';
var expected = start;
var actual = emoji.addClass(start, 'jumbo');
assert.equal(expected, actual);
});
describe('hasNormalCharacters', function() {
it('returns true for all normal text', function() {
var str = 'normal';
var actual = emoji.hasNormalCharacters(str);
assert.equal(actual, true);
});
it('returns false for all emoji text', function() {
var str = '🔥🔥🔥🔥';
var actual = emoji.hasNormalCharacters(str);
assert.equal(actual, false);
});
it('returns false for emojis mixed with spaces', function() {
var str = '🔥 🔥 🔥 🔥';
var actual = emoji.hasNormalCharacters(str);
assert.equal(actual, false);
});
it('returns true for emojis and text', function() {
var str = '🔥 normal 🔥 🔥 🔥';
var actual = emoji.hasNormalCharacters(str);
assert.equal(actual, true);
});
it('returns original string if no sizeClass provided', function() {
var start =
'before <img src="node_modules/emoji-datasource-apple/img/apple/64/1f3e0.png" class="emoji" title="house"/> after';
var expected = start;
var actual = emoji.addClass(start);
assert.equal(expected, actual);
});
describe('getSizeClass', function() {
it('returns nothing for non-emoji text', function() {
assert.equal(emoji.getSizeClass('normal text'), '');
});
it('returns nothing for emojis mixed with text', function() {
assert.equal(emoji.getSizeClass('🔥 normal 🔥'), '');
});
it('returns nothing for more than 8 emojis', function() {
assert.equal(emoji.getSizeClass('🔥🔥 🔥🔥 🔥🔥 🔥🔥 🔥'), '');
});
it('returns "small" for 7-8 emojis', function() {
assert.equal(emoji.getSizeClass('🔥🔥 🔥🔥 🔥🔥 🔥🔥'), 'small');
assert.equal(emoji.getSizeClass('🔥🔥 🔥🔥 🔥🔥 🔥'), 'small');
});
it('returns "medium" for 5-6 emojis', function() {
assert.equal(emoji.getSizeClass('🔥🔥 🔥🔥 🔥🔥'), 'medium');
assert.equal(emoji.getSizeClass('🔥🔥 🔥🔥 🔥'), 'medium');
});
it('returns "large" for 3-4 emojis', function() {
assert.equal(emoji.getSizeClass('🔥🔥 🔥🔥'), 'large');
assert.equal(emoji.getSizeClass('🔥🔥 🔥'), 'large');
});
it('returns "jumbo" for 1-2 emojis', function() {
assert.equal(emoji.getSizeClass('🔥🔥'), 'jumbo');
assert.equal(emoji.getSizeClass('🔥'), 'jumbo');
});
it('adds provided class to image class', function() {
var start =
'before <img src="node_modules/emoji-datasource-apple/img/apple/64/1f3e0.png" class="emoji" title="house"/> after';
var expected =
'before <img src="node_modules/emoji-datasource-apple/img/apple/64/1f3e0.png" class="emoji jumbo" title="house"/> after';
var actual = emoji.addClass(start, 'jumbo');
assert.equal(expected, actual);
});
});
describe('ensureTitlesHaveColons', function() {
it('returns original string if no emoji images', function() {
var start = 'no images. but there is some 🔥. <img src="random.jpg" />';
var expected = start;
var actual = emoji.ensureTitlesHaveColons(start);
assert.equal(expected, actual);
});
describe('addClass', function() {
it('returns original string if no emoji images', function() {
var start = 'no images. but there is some 🔥. <img src="random.jpg" />';
it('returns original string if image title already has colons', function() {
var start =
'before <img src="node_modules/emoji-datasource-apple/img/apple/64/1f3e0.png" class="emoji" title=":house:"/> after';
var expected = start;
var actual = emoji.addClass(start, 'jumbo');
var expected = start;
var actual = emoji.ensureTitlesHaveColons(start);
assert.equal(expected, actual);
});
it('returns original string if no sizeClass provided', function() {
var start = 'before <img src="node_modules/emoji-datasource-apple/img/apple/64/1f3e0.png" class="emoji" title="house"/> after';
var expected = start;
var actual = emoji.addClass(start);
assert.equal(expected, actual);
});
it('adds provided class to image class', function() {
var start = 'before <img src="node_modules/emoji-datasource-apple/img/apple/64/1f3e0.png" class="emoji" title="house"/> after';
var expected = 'before <img src="node_modules/emoji-datasource-apple/img/apple/64/1f3e0.png" class="emoji jumbo" title="house"/> after';
var actual = emoji.addClass(start, 'jumbo');
assert.equal(expected, actual);
});
assert.equal(expected, actual);
});
describe('ensureTitlesHaveColons', function() {
it('returns original string if no emoji images', function() {
var start = 'no images. but there is some 🔥. <img src="random.jpg" />';
it('does not change title for non-emoji image', function() {
var start =
'before <img src="random.png" title="my random title"/> after';
var expected = start;
var actual = emoji.ensureTitlesHaveColons(start);
var expected = start;
var actual = emoji.ensureTitlesHaveColons(start);
assert.equal(expected, actual);
});
it('returns original string if image title already has colons', function() {
var start = 'before <img src="node_modules/emoji-datasource-apple/img/apple/64/1f3e0.png" class="emoji" title=":house:"/> after';
var expected = start;
var actual = emoji.ensureTitlesHaveColons(start);
assert.equal(expected, actual);
});
it('does not change title for non-emoji image', function() {
var start = 'before <img src="random.png" title="my random title"/> after';
var expected = start;
var actual = emoji.ensureTitlesHaveColons(start);
assert.equal(expected, actual);
});
it('adds colons to emoji image title', function() {
var start = 'before <img src="node_modules/emoji-datasource-apple/img/apple/64/1f3e0.png" class="emoji" title="house"/> after';
var expected = 'before <img src="node_modules/emoji-datasource-apple/img/apple/64/1f3e0.png" class="emoji" title=":house:"/> after';
var actual = emoji.ensureTitlesHaveColons(start);
assert.equal(expected, actual);
});
assert.equal(expected, actual);
});
describe('signalReplace', function() {
it('returns images for every emoji', function() {
var actual = emoji.signalReplace('🏠 🔥');
var expected = '<img src="node_modules/emoji-datasource-apple/img/apple/64/1f3e0.png" class="emoji jumbo" data-codepoints="1f3e0" title=":house:"/>'
+ ' <img src="node_modules/emoji-datasource-apple/img/apple/64/1f525.png" class="emoji jumbo" data-codepoints="1f525" title=":fire:"/>';
it('adds colons to emoji image title', function() {
var start =
'before <img src="node_modules/emoji-datasource-apple/img/apple/64/1f3e0.png" class="emoji" title="house"/> after';
assert.equal(expected, actual);
});
it('properly hyphenates a variation', function() {
var actual = emoji.signalReplace('💪🏿'); // muscle with dark skin tone modifier
var expected = '<img src="node_modules/emoji-datasource-apple/img/apple/64/1f4aa-1f3ff.png" class="emoji jumbo" data-codepoints="1f4aa-1f3ff" title=":muscle:"/>';
var expected =
'before <img src="node_modules/emoji-datasource-apple/img/apple/64/1f3e0.png" class="emoji" title=":house:"/> after';
var actual = emoji.ensureTitlesHaveColons(start);
assert.equal(expected, actual);
});
assert.equal(expected, actual);
});
});
describe('signalReplace', function() {
it('returns images for every emoji', function() {
var actual = emoji.signalReplace('🏠 🔥');
var expected =
'<img src="node_modules/emoji-datasource-apple/img/apple/64/1f3e0.png" class="emoji jumbo" data-codepoints="1f3e0" title=":house:"/>' +
' <img src="node_modules/emoji-datasource-apple/img/apple/64/1f525.png" class="emoji jumbo" data-codepoints="1f525" title=":fire:"/>';
assert.equal(expected, actual);
});
it('properly hyphenates a variation', function() {
var actual = emoji.signalReplace('💪🏿'); // muscle with dark skin tone modifier
var expected =
'<img src="node_modules/emoji-datasource-apple/img/apple/64/1f4aa-1f3ff.png" class="emoji jumbo" data-codepoints="1f4aa-1f3ff" title=":muscle:"/>';
assert.equal(expected, actual);
});
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -1,31 +1,37 @@
'use strict';
describe("Fixtures", function() {
describe('Fixtures', function() {
before(function(done) {
// NetworkStatusView checks this method every five seconds while showing
window.getSocketStatus = function() { return WebSocket.OPEN; };
window.getSocketStatus = function() {
return WebSocket.OPEN;
};
Whisper.Fixtures().saveAll().then(function() {
done();
});
Whisper.Fixtures()
.saveAll()
.then(function() {
done();
});
});
it('renders', function(done) {
ConversationController.reset();
ConversationController.load().then(function() {
var view = new Whisper.InboxView({window: window});
view.onEmpty();
view.$el.prependTo($('#render-android'));
ConversationController.load()
.then(function() {
var view = new Whisper.InboxView({ window: window });
view.onEmpty();
view.$el.prependTo($('#render-android'));
var view = new Whisper.InboxView({window: window});
view.$el.removeClass('android').addClass('ios');
view.onEmpty();
view.$el.prependTo($('#render-ios'));
var view = new Whisper.InboxView({ window: window });
view.$el.removeClass('android').addClass('ios');
view.onEmpty();
view.$el.prependTo($('#render-ios'));
var view = new Whisper.InboxView({window: window});
view.$el.removeClass('android').addClass('android-dark');
view.onEmpty();
view.$el.prependTo($('#render-android-dark'));
}).then(done, done);
var view = new Whisper.InboxView({ window: window });
view.$el.removeClass('android').addClass('android-dark');
view.onEmpty();
view.$el.prependTo($('#render-android-dark'));
})
.then(done, done);
});
});

View File

@@ -12,7 +12,10 @@ describe('i18n', function() {
});
it('returns message with multiple substitutions', function() {
const actual = i18n('verifyContact', ['<strong>', '</strong>']);
assert.equal(actual, 'You may wish to <strong> verify </strong> your safety number with this contact.');
assert.equal(
actual,
'You may wish to <strong> verify </strong> your safety number with this contact.'
);
});
});

View File

@@ -1,6 +1,9 @@
describe('KeyChangeListener', function() {
var phoneNumberWithKeyChange = '+13016886524'; // nsa
var address = new libsignal.SignalProtocolAddress(phoneNumberWithKeyChange, 1);
var phoneNumberWithKeyChange = '+13016886524'; // nsa
var address = new libsignal.SignalProtocolAddress(
phoneNumberWithKeyChange,
1
);
var oldKey = libsignal.crypto.getRandomBytes(33);
var newKey = libsignal.crypto.getRandomBytes(33);
var store;
@@ -42,7 +45,6 @@ describe('KeyChangeListener', function() {
});
});
describe('When we have a group with this contact', function() {
let convo;
before(function() {
@@ -68,7 +70,5 @@ describe('KeyChangeListener', function() {
});
return store.saveIdentity(address.toString(), newKey);
});
});
});

View File

@@ -2,28 +2,28 @@
* vim: ts=4:sw=4:expandtab
*/
(function () {
'use strict';
describe('libphonenumber util', function() {
describe('parseNumber', function() {
it('numbers with + are valid without providing regionCode', function() {
var 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', 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');
});
});
(function() {
'use strict';
describe('libphonenumber util', function() {
describe('parseNumber', function() {
it('numbers with + are valid without providing regionCode', function() {
var 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', 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

@@ -2,248 +2,285 @@
* vim: ts=4:sw=4:expandtab
*/
(function () {
'use strict';
var attributes = { type: 'outgoing',
body: 'hi',
conversationId: 'foo',
attachments: [],
timestamp: new Date().getTime() };
var conversation_attributes= {
type: 'private',
id: '+14155555555'
};
textsecure.messaging = new textsecure.MessageSender('');
(function() {
'use strict';
var attributes = {
type: 'outgoing',
body: 'hi',
conversationId: 'foo',
attachments: [],
timestamp: new Date().getTime(),
};
var conversation_attributes = {
type: 'private',
id: '+14155555555',
};
textsecure.messaging = new textsecure.MessageSender('');
describe('ConversationCollection', function() {
before(clearDatabase);
after(clearDatabase);
describe('ConversationCollection', function() {
before(clearDatabase);
after(clearDatabase);
it('adds without saving', function(done) {
it('adds without saving', function(done) {
var convos = new Whisper.ConversationCollection();
convos.add(conversation_attributes);
assert.notEqual(convos.length, 0);
var convos = new Whisper.ConversationCollection();
convos.add(conversation_attributes);
assert.notEqual(convos.length, 0);
var convos = new Whisper.ConversationCollection();
convos.fetch().then(function() {
assert.strictEqual(convos.length, 0);
done();
});
});
var convos = new Whisper.ConversationCollection();
convos.fetch().then(function() {
assert.strictEqual(convos.length, 0);
done();
});
it('saves asynchronously', function(done) {
new Whisper.ConversationCollection()
.add(conversation_attributes)
.save()
.then(done);
});
it('fetches persistent convos', function(done) {
var convos = new Whisper.ConversationCollection();
assert.strictEqual(convos.length, 0);
convos.fetch().then(function() {
var m = convos.at(0).attributes;
_.each(conversation_attributes, function(val, key) {
assert.deepEqual(m[key], val);
});
done();
});
});
it('saves asynchronously', function(done) {
new Whisper.ConversationCollection().add(conversation_attributes).save().then(done);
});
it('fetches persistent convos', function(done) {
var convos = new Whisper.ConversationCollection();
it('destroys persistent convos', function(done) {
var convos = new Whisper.ConversationCollection();
convos.fetch().then(function() {
convos.destroyAll().then(function() {
var convos = new Whisper.ConversationCollection();
convos.fetch().then(function() {
assert.strictEqual(convos.length, 0);
convos.fetch().then(function() {
var m = convos.at(0).attributes;
_.each(conversation_attributes, function(val, key) {
assert.deepEqual(m[key], val);
});
done();
});
});
it('destroys persistent convos', function(done) {
var convos = new Whisper.ConversationCollection();
convos.fetch().then(function() {
convos.destroyAll().then(function() {
var convos = new Whisper.ConversationCollection();
convos.fetch().then(function() {
assert.strictEqual(convos.length, 0);
done();
});
});
});
});
it('should be ordered newest to oldest', function() {
var conversations = new Whisper.ConversationCollection();
// Timestamps
var today = new Date();
var tomorrow = new Date();
tomorrow.setDate(today.getDate()+1);
// Add convos
conversations.add({ timestamp: today });
conversations.add({ timestamp: tomorrow });
var models = conversations.models;
var firstTimestamp = models[0].get('timestamp').getTime();
var secondTimestamp = models[1].get('timestamp').getTime();
// Compare timestamps
assert(firstTimestamp > secondTimestamp);
done();
});
});
});
});
describe('Conversation', function() {
var attributes = { type: 'private', id: '+18085555555' };
before(function(done) {
var convo = new Whisper.ConversationCollection().add(attributes);
convo.save().then(function() {
var message = convo.messageCollection.add({
body : 'hello world',
conversationId : convo.id,
type : 'outgoing',
sent_at : Date.now(),
received_at : Date.now()
});
message.save().then(done);
});
it('should be ordered newest to oldest', function() {
var conversations = new Whisper.ConversationCollection();
// Timestamps
var today = new Date();
var tomorrow = new Date();
tomorrow.setDate(today.getDate() + 1);
// Add convos
conversations.add({ timestamp: today });
conversations.add({ timestamp: tomorrow });
var models = conversations.models;
var firstTimestamp = models[0].get('timestamp').getTime();
var secondTimestamp = models[1].get('timestamp').getTime();
// Compare timestamps
assert(firstTimestamp > secondTimestamp);
});
});
describe('Conversation', function() {
var attributes = { type: 'private', id: '+18085555555' };
before(function(done) {
var convo = new Whisper.ConversationCollection().add(attributes);
convo.save().then(function() {
var message = convo.messageCollection.add({
body: 'hello world',
conversationId: convo.id,
type: 'outgoing',
sent_at: Date.now(),
received_at: Date.now(),
});
after(clearDatabase);
message.save().then(done);
});
});
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: 'Á'
}));
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', function (done) {
var convo = new Whisper.ConversationCollection().add({id: '+18085555555'});
convo.fetchMessages().then(function() {
assert.notEqual(convo.messageCollection.length, 0);
done();
});
});
it('contains only its own messages', function (done) {
var convo = new Whisper.ConversationCollection().add({id: '+18085556666'});
convo.fetchMessages().then(function() {
assert.strictEqual(convo.messageCollection.length, 0);
done();
});
});
it('adds conversation to message collection upon leaving group', function() {
var convo = new Whisper.ConversationCollection().add({type: 'group', id: 'a random string'});
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');
});
it('revokes the avatar URL', function() {
var convo = new Whisper.ConversationCollection().add(attributes);
convo.revokeAvatarUrl();
assert.notOk(convo.avatarUrl);
});
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);
});
});
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');
});
describe('Conversation search', function() {
it('contains its own messages', function(done) {
var convo = new Whisper.ConversationCollection().add({
id: '+18085555555',
});
convo.fetchMessages().then(function() {
assert.notEqual(convo.messageCollection.length, 0);
done();
});
});
it('contains only its own messages', function(done) {
var convo = new Whisper.ConversationCollection().add({
id: '+18085556666',
});
convo.fetchMessages().then(function() {
assert.strictEqual(convo.messageCollection.length, 0);
done();
});
});
it('adds conversation to message collection upon leaving group', function() {
var convo = new Whisper.ConversationCollection().add({
type: 'group',
id: 'a random string',
});
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');
});
it('revokes the avatar URL', function() {
var convo = new Whisper.ConversationCollection().add(attributes);
convo.revokeAvatarUrl();
assert.notOk(convo.avatarUrl);
});
describe('phone number parsing', function() {
after(function() {
storage.remove('regionCode');
});
function checkAttributes(number) {
var convo = new Whisper.ConversationCollection().add({
id: '+14155555555', type: 'private', name: 'John Doe'
});
before(function(done) { convo.save().then(done); });
function testSearch(queries, done) {
return Promise.all(queries.map(function(query) {
var collection = new Whisper.ConversationCollection();
return collection.search(query).then(function() {
assert.isDefined(collection.get(convo.id), 'no result for "' + query + '"');
}).catch(done);
})).then(function() {
done();
});
}
it('matches by partial phone number', function(done) {
testSearch([
'1',
'4',
'+1',
'415',
'4155',
'4155555555',
'14155555555',
'+14155555555',
], done);
});
it('matches by name', function(done) {
testSearch([ 'John', 'Doe', 'john', 'doe', 'John Doe', 'john doe' ], done);
});
it('does not match +', function(done) {
var collection = new Whisper.ConversationCollection();
return collection.search('+').then(function() {
assert.isUndefined(collection.get(convo.id), 'got result for "+"');
done();
}).catch(done);
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() {
var convo = new Whisper.ConversationCollection().add({
id: '+14155555555',
type: 'private',
name: 'John Doe',
});
before(function(done) {
convo.save().then(done);
});
function testSearch(queries, done) {
return Promise.all(
queries.map(function(query) {
var collection = new Whisper.ConversationCollection();
return collection
.search(query)
.then(function() {
assert.isDefined(
collection.get(convo.id),
'no result for "' + query + '"'
);
})
.catch(done);
})
).then(function() {
done();
});
}
it('matches by partial phone number', function(done) {
testSearch(
[
'1',
'4',
'+1',
'415',
'4155',
'4155555555',
'14155555555',
'+14155555555',
],
done
);
});
it('matches by name', function(done) {
testSearch(['John', 'Doe', 'john', 'doe', 'John Doe', 'john doe'], done);
});
it('does not match +', function(done) {
var collection = new Whisper.ConversationCollection();
return collection
.search('+')
.then(function() {
assert.isUndefined(collection.get(convo.id), 'got result for "+"');
done();
})
.catch(done);
});
});
})();

View File

@@ -1,188 +1,219 @@
/*
* vim: ts=4:sw=4:expandtab
*/
(function () {
'use strict';
function deleteAllMessages() {
return new Promise(function(resolve, reject) {
var messages = new Whisper.MessageCollection();
return messages.fetch().then(function() {
messages.destroyAll();
resolve();
}, reject);
});
}
var attributes = { type: 'outgoing',
body: 'hi',
conversationId: 'foo',
attachments: [],
received_at: new Date().getTime() };
var attachment = { data: 'datasaurus',
contentType: 'plain/text' };
var source = '+14155555555';
describe('MessageCollection', function() {
before(function() {
return Promise.all([
deleteAllMessages(),
ConversationController.load()
]);
});
after(function() {
return deleteAllMessages();
});
it('has no image url', function() {
var messages = new Whisper.MessageCollection();
var message = messages.add(attributes);
assert.isNull(message.getImageUrl());
});
it('updates image url', function() {
var messages = new Whisper.MessageCollection();
var message = messages.add({ attachments: [attachment] });
var firstUrl = message.getImageUrl();
message.updateImageUrl();
var secondUrl = message.getImageUrl();
assert.notEqual(secondUrl, firstUrl);
});
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('saves asynchronously', function(done) {
new Whisper.MessageCollection().add(attributes).save().then(done);
});
it('fetches persistent messages', function(done) {
var messages = new Whisper.MessageCollection();
assert.strictEqual(messages.length, 0);
messages.fetch().then(function() {
assert.notEqual(messages.length, 0);
var m = messages.at(0).attributes;
_.each(attributes, function(val, key) {
assert.deepEqual(m[key], val);
});
done();
});
});
it('destroys persistent messages', function(done) {
var messages = new Whisper.MessageCollection();
messages.fetch().then(function() {
messages.destroyAll().then(function() {
var messages = new Whisper.MessageCollection();
messages.fetch().then(function() {
assert.strictEqual(messages.length, 0);
done();
});
});
});
});
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(), 'Updated the group. 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(), 'Updated the group. 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(), 'Updated the group. 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(), 'Updated the group. 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());
});
(function() {
'use strict';
function deleteAllMessages() {
return new Promise(function(resolve, reject) {
var messages = new Whisper.MessageCollection();
return messages.fetch().then(function() {
messages.destroyAll();
resolve();
}, reject);
});
}
var attributes = {
type: 'outgoing',
body: 'hi',
conversationId: 'foo',
attachments: [],
received_at: new Date().getTime(),
};
var attachment = {
data: 'datasaurus',
contentType: 'plain/text',
};
var source = '+14155555555';
describe('MessageCollection', function() {
before(function() {
return Promise.all([deleteAllMessages(), ConversationController.load()]);
});
after(function() {
return deleteAllMessages();
});
it('has no image url', function() {
var messages = new Whisper.MessageCollection();
var message = messages.add(attributes);
assert.isNull(message.getImageUrl());
});
it('updates image url', function() {
var messages = new Whisper.MessageCollection();
var message = messages.add({ attachments: [attachment] });
var firstUrl = message.getImageUrl();
message.updateImageUrl();
var secondUrl = message.getImageUrl();
assert.notEqual(secondUrl, firstUrl);
});
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('saves asynchronously', function(done) {
new Whisper.MessageCollection()
.add(attributes)
.save()
.then(done);
});
it('fetches persistent messages', function(done) {
var messages = new Whisper.MessageCollection();
assert.strictEqual(messages.length, 0);
messages.fetch().then(function() {
assert.notEqual(messages.length, 0);
var m = messages.at(0).attributes;
_.each(attributes, function(val, key) {
assert.deepEqual(m[key], val);
});
done();
});
});
it('destroys persistent messages', function(done) {
var messages = new Whisper.MessageCollection();
messages.fetch().then(function() {
messages.destroyAll().then(function() {
var messages = new Whisper.MessageCollection();
messages.fetch().then(function() {
assert.strictEqual(messages.length, 0);
done();
});
});
});
});
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(),
"Updated the group. 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(),
'Updated the group. 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(),
'Updated the group. 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(),
"Updated the group. 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());
});
});
})();

View File

@@ -6,7 +6,7 @@ module.exports = {
browser: true,
},
"globals": {
globals: {
check: true,
gen: true,
},
@@ -17,11 +17,14 @@ module.exports = {
rules: {
// We still get the value of this rule, it just allows for dev deps
'import/no-extraneous-dependencies': ['error', {
devDependencies: true
}],
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: true,
},
],
// We want to keep each test structured the same, even if its contents are tiny
'arrow-body-style': 'off',
}
},
};

View File

@@ -3,11 +3,12 @@ const got = require('got');
const debuglogs = require('../../js/modules/debuglogs');
describe('debuglogs', () => {
describe('upload', () => {
it('should upload log content', async () => {
const nonce = Math.random().toString().slice(2);
const nonce = Math.random()
.toString()
.slice(2);
const url = await debuglogs.upload(nonce);
const { body } = await got.get(url);

View File

@@ -4,17 +4,18 @@ const { assert } = require('chai');
const Privacy = require('../../js/modules/privacy');
const APP_ROOT_PATH = path.join(__dirname, '..', '..', '..');
describe('Privacy', () => {
describe('redactPhoneNumbers', () => {
it('should redact all phone numbers', () => {
const text = 'This is a log line with a phone number +12223334455\n' +
const text =
'This is a log line with a phone number +12223334455\n' +
'and another one +13334445566';
const actual = Privacy.redactPhoneNumbers(text);
const expected = 'This is a log line with a phone number +[REDACTED]455\n' +
const expected =
'This is a log line with a phone number +[REDACTED]455\n' +
'and another one +[REDACTED]566';
assert.equal(actual, expected);
});
@@ -22,11 +23,13 @@ describe('Privacy', () => {
describe('redactGroupIds', () => {
it('should redact all group IDs', () => {
const text = 'This is a log line with two group IDs: group(123456789)\n' +
const text =
'This is a log line with two group IDs: group(123456789)\n' +
'and group(abcdefghij)';
const actual = Privacy.redactGroupIds(text);
const expected = 'This is a log line with two group IDs: group([REDACTED]789)\n' +
const expected =
'This is a log line with two group IDs: group([REDACTED]789)\n' +
'and group([REDACTED]hij)';
assert.equal(actual, expected);
});
@@ -35,7 +38,8 @@ describe('Privacy', () => {
describe('redactAll', () => {
it('should redact all sensitive information', () => {
const encodedAppRootPath = APP_ROOT_PATH.replace(/ /g, '%20');
const text = 'This is a log line with sensitive information:\n' +
const text =
'This is a log line with sensitive information:\n' +
`path1 ${APP_ROOT_PATH}/main.js\n` +
'phone1 +12223334455 ipsum\n' +
'group1 group(123456789) doloret\n' +
@@ -44,7 +48,8 @@ describe('Privacy', () => {
'group2 group(abcdefghij) doloret\n';
const actual = Privacy.redactAll(text);
const expected = 'This is a log line with sensitive information:\n' +
const expected =
'This is a log line with sensitive information:\n' +
'path1 [REDACTED]/main.js\n' +
'phone1 +[REDACTED]455 ipsum\n' +
'group1 group([REDACTED]789) doloret\n' +
@@ -58,12 +63,14 @@ describe('Privacy', () => {
describe('_redactPath', () => {
it('should redact file paths', () => {
const testPath = '/Users/meow/Library/Application Support/Signal Beta';
const text = 'This is a log line with sensitive information:\n' +
const text =
'This is a log line with sensitive information:\n' +
`path1 ${testPath}/main.js\n` +
'phone1 +12223334455 ipsum\n';
const actual = Privacy._redactPath(testPath)(text);
const expected = 'This is a log line with sensitive information:\n' +
const expected =
'This is a log line with sensitive information:\n' +
'path1 [REDACTED]/main.js\n' +
'phone1 +12223334455 ipsum\n';
assert.equal(actual, expected);
@@ -72,14 +79,16 @@ describe('Privacy', () => {
it('should redact URL-encoded paths', () => {
const testPath = '/Users/meow/Library/Application Support/Signal Beta';
const encodedTestPath = encodeURI(testPath);
const text = 'This is a log line with sensitive information:\n' +
const text =
'This is a log line with sensitive information:\n' +
`path1 ${testPath}/main.js\n` +
'phone1 +12223334455 ipsum\n' +
'group1 group(123456789) doloret\n' +
`path2 file:///${encodedTestPath}/js/background.js.`;
const actual = Privacy._redactPath(testPath)(text);
const expected = 'This is a log line with sensitive information:\n' +
const expected =
'This is a log line with sensitive information:\n' +
'path1 [REDACTED]/main.js\n' +
'phone1 +12223334455 ipsum\n' +
'group1 group(123456789) doloret\n' +
@@ -88,17 +97,20 @@ describe('Privacy', () => {
});
it('should redact stack traces with both forward and backslashes', () => {
const testPath = 'C:/Users/Meow/AppData/Local/Programs/signal-desktop-beta';
const testPath =
'C:/Users/Meow/AppData/Local/Programs/signal-desktop-beta';
const modifiedTestPath =
'C:\\Users\\Meow\\AppData\\Local\\Programs\\signal-desktop-beta';
const text = 'This is a log line with sensitive information:\n' +
const text =
'This is a log line with sensitive information:\n' +
`path1 ${testPath}\\main.js\n` +
'phone1 +12223334455 ipsum\n' +
'group1 group(123456789) doloret\n' +
`path2 ${modifiedTestPath}\\js\\background.js.`;
const actual = Privacy._redactPath(testPath)(text);
const expected = 'This is a log line with sensitive information:\n' +
const expected =
'This is a log line with sensitive information:\n' +
'path1 [REDACTED]\\main.js\n' +
'phone1 +12223334455 ipsum\n' +
'group1 group(123456789) doloret\n' +
@@ -107,17 +119,20 @@ describe('Privacy', () => {
});
it('should redact stack traces with escaped backslashes', () => {
const testPath = 'C:\\Users\\Meow\\AppData\\Local\\Programs\\signal-desktop-beta';
const testPath =
'C:\\Users\\Meow\\AppData\\Local\\Programs\\signal-desktop-beta';
const modifiedTestPath =
'C:\\\\Users\\\\Meow\\\\AppData\\\\Local\\\\Programs\\\\signal-desktop-beta';
const text = 'This is a log line with sensitive information:\n' +
const text =
'This is a log line with sensitive information:\n' +
`path1 ${testPath}\\main.js\n` +
'phone1 +12223334455 ipsum\n' +
'group1 group(123456789) doloret\n' +
`path2 ${modifiedTestPath}\\js\\background.js.`;
const actual = Privacy._redactPath(testPath)(text);
const expected = 'This is a log line with sensitive information:\n' +
const expected =
'This is a log line with sensitive information:\n' +
'path1 [REDACTED]\\main.js\n' +
'phone1 +12223334455 ipsum\n' +
'group1 group(123456789) doloret\n' +

View File

@@ -3,7 +3,6 @@ const { assert } = require('chai');
const Startup = require('../../js/modules/startup');
describe('Startup', () => {
const sandbox = sinon.createSandbox();

View File

@@ -3,7 +3,9 @@ require('mocha-testcheck').install();
const { assert } = require('chai');
const Attachment = require('../../../js/modules/types/attachment');
const { stringToArrayBuffer } = require('../../../js/modules/string_to_array_buffer');
const {
stringToArrayBuffer,
} = require('../../../js/modules/string_to_array_buffer');
describe('Attachment', () => {
describe('replaceUnicodeOrderOverrides', () => {
@@ -67,7 +69,7 @@ describe('Attachment', () => {
check.it(
'should ignore non-order-override characters',
gen.string.suchThat(hasNoUnicodeOrderOverrides),
(fileName) => {
fileName => {
const input = {
contentType: 'image/jpeg',
data: null,
@@ -120,15 +122,14 @@ describe('Attachment', () => {
};
const expectedAttachmentData = stringToArrayBuffer('Above us only sky');
const writeNewAttachmentData = async (attachmentData) => {
const writeNewAttachmentData = async attachmentData => {
assert.deepEqual(attachmentData, expectedAttachmentData);
return 'abc/abcdefgh123456789';
};
const actual = await Attachment.migrateDataToFileSystem(
input,
{ writeNewAttachmentData }
);
const actual = await Attachment.migrateDataToFileSystem(input, {
writeNewAttachmentData,
});
assert.deepEqual(actual, expected);
});
@@ -145,13 +146,11 @@ describe('Attachment', () => {
size: 1111,
};
const writeNewAttachmentData = async () =>
'abc/abcdefgh123456789';
const writeNewAttachmentData = async () => 'abc/abcdefgh123456789';
const actual = await Attachment.migrateDataToFileSystem(
input,
{ writeNewAttachmentData }
);
const actual = await Attachment.migrateDataToFileSystem(input, {
writeNewAttachmentData,
});
assert.deepEqual(actual, expected);
});
@@ -163,11 +162,12 @@ describe('Attachment', () => {
size: 1111,
};
const writeNewAttachmentData = async () =>
'abc/abcdefgh123456789';
const writeNewAttachmentData = async () => 'abc/abcdefgh123456789';
try {
await Attachment.migrateDataToFileSystem(input, { writeNewAttachmentData });
await Attachment.migrateDataToFileSystem(input, {
writeNewAttachmentData,
});
} catch (error) {
assert.strictEqual(
error.message,

View File

@@ -4,7 +4,6 @@ const { assert } = require('chai');
const Errors = require('../../../js/modules/types/errors');
const APP_ROOT_PATH = Path.join(__dirname, '..', '..', '..');
describe('Errors', () => {
@@ -15,7 +14,11 @@ describe('Errors', () => {
const formattedError = Errors.toLogFormat(error);
assert.include(formattedError, 'errors_test.js');
assert.include(formattedError, APP_ROOT_PATH, 'Formatted stack has app path');
assert.include(
formattedError,
APP_ROOT_PATH,
'Formatted stack has app path'
);
});
it('should return error string representation if stack is missing', () => {
@@ -28,12 +31,7 @@ describe('Errors', () => {
assert.strictEqual(formattedError, 'Error: boom');
});
[
0,
false,
null,
undefined,
].forEach((value) => {
[0, false, null, undefined].forEach(value => {
it(`should return \`${value}\` argument`, () => {
const formattedNonError = Errors.toLogFormat(value);
assert.strictEqual(formattedNonError, value);

View File

@@ -2,8 +2,9 @@ const { assert } = require('chai');
const sinon = require('sinon');
const Message = require('../../../js/modules/types/message');
const { stringToArrayBuffer } = require('../../../js/modules/string_to_array_buffer');
const {
stringToArrayBuffer,
} = require('../../../js/modules/string_to_array_buffer');
describe('Message', () => {
describe('createAttachmentDataWriter', () => {
@@ -18,8 +19,9 @@ describe('Message', () => {
};
const writeExistingAttachmentData = () => {};
const actual =
await Message.createAttachmentDataWriter(writeExistingAttachmentData)(input);
const actual = await Message.createAttachmentDataWriter(
writeExistingAttachmentData
)(input);
assert.deepEqual(actual, expected);
});
@@ -36,8 +38,9 @@ describe('Message', () => {
};
const writeExistingAttachmentData = () => {};
const actual =
await Message.createAttachmentDataWriter(writeExistingAttachmentData)(input);
const actual = await Message.createAttachmentDataWriter(
writeExistingAttachmentData
)(input);
assert.deepEqual(actual, expected);
});
@@ -45,26 +48,34 @@ describe('Message', () => {
const input = {
body: 'Imagine there is no heaven…',
schemaVersion: 4,
attachments: [{
path: 'ab/abcdefghi',
data: stringToArrayBuffer('Its easy if you try'),
}],
attachments: [
{
path: 'ab/abcdefghi',
data: stringToArrayBuffer('Its easy if you try'),
},
],
};
const expected = {
body: 'Imagine there is no heaven…',
schemaVersion: 4,
attachments: [{
path: 'ab/abcdefghi',
}],
attachments: [
{
path: 'ab/abcdefghi',
},
],
};
const writeExistingAttachmentData = (attachment) => {
const writeExistingAttachmentData = attachment => {
assert.equal(attachment.path, 'ab/abcdefghi');
assert.deepEqual(attachment.data, stringToArrayBuffer('Its easy if you try'));
assert.deepEqual(
attachment.data,
stringToArrayBuffer('Its easy if you try')
);
};
const actual =
await Message.createAttachmentDataWriter(writeExistingAttachmentData)(input);
const actual = await Message.createAttachmentDataWriter(
writeExistingAttachmentData
)(input);
assert.deepEqual(actual, expected);
});
@@ -74,12 +85,14 @@ describe('Message', () => {
schemaVersion: 4,
attachments: [],
quote: {
attachments: [{
thumbnail: {
path: 'ab/abcdefghi',
data: stringToArrayBuffer('Its easy if you try'),
attachments: [
{
thumbnail: {
path: 'ab/abcdefghi',
data: stringToArrayBuffer('Its easy if you try'),
},
},
}],
],
},
};
const expected = {
@@ -87,21 +100,27 @@ describe('Message', () => {
schemaVersion: 4,
attachments: [],
quote: {
attachments: [{
thumbnail: {
path: 'ab/abcdefghi',
attachments: [
{
thumbnail: {
path: 'ab/abcdefghi',
},
},
}],
],
},
};
const writeExistingAttachmentData = (attachment) => {
const writeExistingAttachmentData = attachment => {
assert.equal(attachment.path, 'ab/abcdefghi');
assert.deepEqual(attachment.data, stringToArrayBuffer('Its easy if you try'));
assert.deepEqual(
attachment.data,
stringToArrayBuffer('Its easy if you try')
);
};
const actual =
await Message.createAttachmentDataWriter(writeExistingAttachmentData)(input);
const actual = await Message.createAttachmentDataWriter(
writeExistingAttachmentData
)(input);
assert.deepEqual(actual, expected);
});
});
@@ -142,18 +161,22 @@ describe('Message', () => {
it('should inherit existing attachment schema version', () => {
const input = {
body: 'Imagine there is no heaven…',
attachments: [{
contentType: 'image/jpeg',
fileName: 'lennon.jpg',
schemaVersion: 7,
}],
attachments: [
{
contentType: 'image/jpeg',
fileName: 'lennon.jpg',
schemaVersion: 7,
},
],
};
const expected = {
body: 'Imagine there is no heaven…',
attachments: [{
contentType: 'image/jpeg',
fileName: 'lennon.jpg',
}],
attachments: [
{
contentType: 'image/jpeg',
fileName: 'lennon.jpg',
},
],
schemaVersion: 7,
};
@@ -166,30 +189,36 @@ describe('Message', () => {
describe('upgradeSchema', () => {
it('should upgrade an unversioned message to the latest version', async () => {
const input = {
attachments: [{
contentType: 'application/json',
data: stringToArrayBuffer('Its easy if you try'),
fileName: 'test\u202Dfig.exe',
size: 1111,
}],
attachments: [
{
contentType: 'application/json',
data: stringToArrayBuffer('Its easy if you try'),
fileName: 'test\u202Dfig.exe',
size: 1111,
},
],
schemaVersion: 0,
};
const expected = {
attachments: [{
contentType: 'application/json',
path: 'abc/abcdefg',
fileName: 'test\uFFFDfig.exe',
size: 1111,
}],
attachments: [
{
contentType: 'application/json',
path: 'abc/abcdefg',
fileName: 'test\uFFFDfig.exe',
size: 1111,
},
],
hasAttachments: 1,
hasVisualMediaAttachments: undefined,
hasFileAttachments: 1,
schemaVersion: Message.CURRENT_SCHEMA_VERSION,
};
const expectedAttachmentData = stringToArrayBuffer('Its easy if you try');
const expectedAttachmentData = stringToArrayBuffer(
'Its easy if you try'
);
const context = {
writeNewAttachmentData: async (attachmentData) => {
writeNewAttachmentData: async attachmentData => {
assert.deepEqual(attachmentData, expectedAttachmentData);
return 'abc/abcdefg';
},
@@ -201,21 +230,25 @@ describe('Message', () => {
context('with multiple upgrade steps', () => {
it('should return last valid message when any upgrade step fails', async () => {
const input = {
attachments: [{
contentType: 'application/json',
data: null,
fileName: 'test\u202Dfig.exe',
size: 1111,
}],
attachments: [
{
contentType: 'application/json',
data: null,
fileName: 'test\u202Dfig.exe',
size: 1111,
},
],
schemaVersion: 0,
};
const expected = {
attachments: [{
contentType: 'application/json',
data: null,
fileName: 'test\u202Dfig.exe',
size: 1111,
}],
attachments: [
{
contentType: 'application/json',
data: null,
fileName: 'test\u202Dfig.exe',
size: 1111,
},
],
hasUpgradedToVersion1: true,
schemaVersion: 1,
};
@@ -241,21 +274,25 @@ describe('Message', () => {
it('should skip out-of-order upgrade steps', async () => {
const input = {
attachments: [{
contentType: 'application/json',
data: null,
fileName: 'test\u202Dfig.exe',
size: 1111,
}],
attachments: [
{
contentType: 'application/json',
data: null,
fileName: 'test\u202Dfig.exe',
size: 1111,
},
],
schemaVersion: 0,
};
const expected = {
attachments: [{
contentType: 'application/json',
data: null,
fileName: 'test\u202Dfig.exe',
size: 1111,
}],
attachments: [
{
contentType: 'application/json',
data: null,
fileName: 'test\u202Dfig.exe',
size: 1111,
},
],
schemaVersion: 2,
hasUpgradedToVersion1: true,
hasUpgradedToVersion2: true,
@@ -352,7 +389,9 @@ describe('Message', () => {
describe('_mapQuotedAttachments', () => {
it('handles message with no quote', async () => {
const upgradeAttachment = sinon.stub().throws(new Error("Shouldn't be called"));
const upgradeAttachment = sinon
.stub()
.throws(new Error("Shouldn't be called"));
const upgradeVersion = Message._mapQuotedAttachments(upgradeAttachment);
const message = {
@@ -363,7 +402,9 @@ describe('Message', () => {
});
it('handles quote with no attachments', async () => {
const upgradeAttachment = sinon.stub().throws(new Error("Shouldn't be called"));
const upgradeAttachment = sinon
.stub()
.throws(new Error("Shouldn't be called"));
const upgradeVersion = Message._mapQuotedAttachments(upgradeAttachment);
const message = {
@@ -384,7 +425,9 @@ describe('Message', () => {
});
it('handles zero attachments', async () => {
const upgradeAttachment = sinon.stub().throws(new Error("Shouldn't be called"));
const upgradeAttachment = sinon
.stub()
.throws(new Error("Shouldn't be called"));
const upgradeVersion = Message._mapQuotedAttachments(upgradeAttachment);
const message = {
@@ -399,7 +442,9 @@ describe('Message', () => {
});
it('handles attachments with no thumbnail', async () => {
const upgradeAttachment = sinon.stub().throws(new Error("Shouldn't be called"));
const upgradeAttachment = sinon
.stub()
.throws(new Error("Shouldn't be called"));
const upgradeVersion = Message._mapQuotedAttachments(upgradeAttachment);
const message = {
@@ -414,30 +459,36 @@ describe('Message', () => {
});
it('eliminates thumbnails with no data fielkd', async () => {
const upgradeAttachment = sinon.stub().throws(new Error("Shouldn't be called"));
const upgradeAttachment = sinon
.stub()
.throws(new Error("Shouldn't be called"));
const upgradeVersion = Message._mapQuotedAttachments(upgradeAttachment);
const message = {
body: 'hey there!',
quote: {
text: 'hey!',
attachments: [{
fileName: 'cat.gif',
contentType: 'image/gif',
thumbnail: {
fileName: 'failed to download!',
attachments: [
{
fileName: 'cat.gif',
contentType: 'image/gif',
thumbnail: {
fileName: 'failed to download!',
},
},
}],
],
},
};
const expected = {
body: 'hey there!',
quote: {
text: 'hey!',
attachments: [{
contentType: 'image/gif',
fileName: 'cat.gif',
}],
attachments: [
{
contentType: 'image/gif',
fileName: 'cat.gif',
},
],
},
};
const result = await upgradeVersion(message);
@@ -454,22 +505,26 @@ describe('Message', () => {
body: 'hey there!',
quote: {
text: 'hey!',
attachments: [{
thumbnail: {
data: 'data is here',
attachments: [
{
thumbnail: {
data: 'data is here',
},
},
}],
],
},
};
const expected = {
body: 'hey there!',
quote: {
text: 'hey!',
attachments: [{
thumbnail: {
path: '/new/path/on/disk',
attachments: [
{
thumbnail: {
path: '/new/path/on/disk',
},
},
}],
],
},
};
const result = await upgradeVersion(message);

View File

@@ -2,7 +2,6 @@ const { assert } = require('chai');
const MIME = require('../../../ts/types/MIME');
describe('MIME', () => {
describe('isJPEG', () => {
it('should return true for `image/jpeg`', () => {
@@ -20,11 +19,10 @@ describe('MIME', () => {
false,
null,
undefined,
]
.forEach((value) => {
it(`should return false for \`${value}\``, () => {
assert.isFalse(MIME.isJPEG(value));
});
].forEach(value => {
it(`should return false for \`${value}\``, () => {
assert.isFalse(MIME.isJPEG(value));
});
});
});
});

View File

@@ -3,21 +3,16 @@ const { assert } = require('chai');
const SchemaVersion = require('../../../js/modules/types/schema_version');
describe('SchemaVersion', () => {
describe('isValid', () => {
check.it(
'should return true for positive integers',
gen.posInt,
(input) => {
assert.isTrue(SchemaVersion.isValid(input));
}
);
check.it('should return true for positive integers', gen.posInt, input => {
assert.isTrue(SchemaVersion.isValid(input));
});
check.it(
'should return false for any other value',
gen.primitive.suchThat(value => typeof value !== 'number' || value < 0),
(input) => {
input => {
assert.isFalse(SchemaVersion.isValid(input));
}
);

View File

@@ -3,7 +3,6 @@ const { assert } = require('chai');
const Settings = require('../../../js/modules/types/settings');
describe('Settings', () => {
const sandbox = sinon.createSandbox();

View File

@@ -1,147 +1,151 @@
'use strict';
describe('ReliableTrigger', function() {
describe('trigger', function() {
var Model, model;
describe('trigger', function() {
var Model, model;
before(function() {
Model = Backbone.Model;
});
beforeEach(function() {
model = new Model();
});
it('returns successfully if this._events is falsey', function() {
model._events = null;
model.trigger('click');
});
it('handles map of events to trigger', function() {
var a = 0, b = 0;
model.on('a', function(arg) {
a = arg;
});
model.on('b', function(arg) {
b = arg;
});
model.trigger({
a: 1,
b: 2
});
assert.strictEqual(a, 1);
assert.strictEqual(b, 2);
});
it('handles space-separated list of events to trigger', function() {
var a = false, b = false;
model.on('a', function() {
a = true;
});
model.on('b', function() {
b = true;
});
model.trigger('a b');
assert.strictEqual(a, true);
assert.strictEqual(b, true);
});
it('calls all clients registered for "all" event', function() {
var count = 0;
model.on('all', function() {
count += 1;
});
model.trigger('left');
model.trigger('right');
assert.strictEqual(count, 2);
});
it('calls all clients registered for target event', function() {
var a = false, b = false;
model.on('event', function() {
a = true;
});
model.on('event', function() {
b = true;
});
model.trigger('event');
assert.strictEqual(a, true);
assert.strictEqual(b, true);
});
it('successfully returns and calls all clients even if first failed', function() {
var a = false, b = false;
model.on('event', function() {
a = true;
throw new Error('a is set, but exception is thrown');
});
model.on('event', function() {
b = true;
});
model.trigger('event');
assert.strictEqual(a, true);
assert.strictEqual(b, true);
});
it('calls clients with no args', function() {
var called = false;
model.on('event', function() {
called = true;
});
model.trigger('event');
assert.strictEqual(called, true);
});
it('calls clients with 1 arg', function() {
var args;
model.on('event', function() {
args = arguments;
});
model.trigger('event', 1);
assert.strictEqual(args[0], 1);
});
it('calls clients with 2 args', function() {
var args;
model.on('event', function() {
args = arguments;
});
model.trigger('event', 1, 2);
assert.strictEqual(args[0], 1);
assert.strictEqual(args[1], 2);
});
it('calls clients with 3 args', function() {
var args;
model.on('event', function() {
args = arguments;
});
model.trigger('event', 1, 2, 3);
assert.strictEqual(args[0], 1);
assert.strictEqual(args[1], 2);
assert.strictEqual(args[2], 3);
});
it('calls clients with 4+ args', function() {
var args;
model.on('event', function() {
args = arguments;
});
model.trigger('event', 1, 2, 3, 4);
assert.strictEqual(args[0], 1);
assert.strictEqual(args[1], 2);
assert.strictEqual(args[2], 3);
assert.strictEqual(args[3], 4);
});
before(function() {
Model = Backbone.Model;
});
beforeEach(function() {
model = new Model();
});
it('returns successfully if this._events is falsey', function() {
model._events = null;
model.trigger('click');
});
it('handles map of events to trigger', function() {
var a = 0,
b = 0;
model.on('a', function(arg) {
a = arg;
});
model.on('b', function(arg) {
b = arg;
});
model.trigger({
a: 1,
b: 2,
});
assert.strictEqual(a, 1);
assert.strictEqual(b, 2);
});
it('handles space-separated list of events to trigger', function() {
var a = false,
b = false;
model.on('a', function() {
a = true;
});
model.on('b', function() {
b = true;
});
model.trigger('a b');
assert.strictEqual(a, true);
assert.strictEqual(b, true);
});
it('calls all clients registered for "all" event', function() {
var count = 0;
model.on('all', function() {
count += 1;
});
model.trigger('left');
model.trigger('right');
assert.strictEqual(count, 2);
});
it('calls all clients registered for target event', function() {
var a = false,
b = false;
model.on('event', function() {
a = true;
});
model.on('event', function() {
b = true;
});
model.trigger('event');
assert.strictEqual(a, true);
assert.strictEqual(b, true);
});
it('successfully returns and calls all clients even if first failed', function() {
var a = false,
b = false;
model.on('event', function() {
a = true;
throw new Error('a is set, but exception is thrown');
});
model.on('event', function() {
b = true;
});
model.trigger('event');
assert.strictEqual(a, true);
assert.strictEqual(b, true);
});
it('calls clients with no args', function() {
var called = false;
model.on('event', function() {
called = true;
});
model.trigger('event');
assert.strictEqual(called, true);
});
it('calls clients with 1 arg', function() {
var args;
model.on('event', function() {
args = arguments;
});
model.trigger('event', 1);
assert.strictEqual(args[0], 1);
});
it('calls clients with 2 args', function() {
var args;
model.on('event', function() {
args = arguments;
});
model.trigger('event', 1, 2);
assert.strictEqual(args[0], 1);
assert.strictEqual(args[1], 2);
});
it('calls clients with 3 args', function() {
var args;
model.on('event', function() {
args = arguments;
});
model.trigger('event', 1, 2, 3);
assert.strictEqual(args[0], 1);
assert.strictEqual(args[1], 2);
assert.strictEqual(args[2], 3);
});
it('calls clients with 4+ args', function() {
var args;
model.on('event', function() {
args = arguments;
});
model.trigger('event', 1, 2, 3, 4);
assert.strictEqual(args[0], 1);
assert.strictEqual(args[1], 2);
assert.strictEqual(args[2], 3);
assert.strictEqual(args[3], 4);
});
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -30,22 +30,25 @@ window.Signal.Backup = {};
window.Signal.Crypto = {};
window.Signal.Logs = {};
window.Signal.Migrations = {
getPlaceholderMigrations: () => [{
migrate: (transaction, next) => {
console.log('migration version 1');
transaction.db.createObjectStore('conversations');
next();
getPlaceholderMigrations: () => [
{
migrate: (transaction, next) => {
console.log('migration version 1');
transaction.db.createObjectStore('conversations');
next();
},
version: 1,
},
version: 1,
}, {
migrate: (transaction, next) => {
console.log('migration version 2');
const messages = transaction.db.createObjectStore('messages');
messages.createIndex('expires_at', 'expireTimer', { unique: false });
next();
{
migrate: (transaction, next) => {
console.log('migration version 2');
const messages = transaction.db.createObjectStore('messages');
messages.createIndex('expires_at', 'expireTimer', { unique: false });
next();
},
version: 2,
},
version: 2,
}],
],
loadAttachmentData: attachment => Promise.resolve(attachment),
};

View File

@@ -1,105 +1,119 @@
describe('ConversationSearchView', function() {
it('should match partial numbers', function() {
var $el = $('<div><div class="new-contact contact hide"></div></div>');
var view = new Whisper.ConversationSearchView({el: $el, input: $('<input>')}).render();
var view = new Whisper.ConversationSearchView({
el: $el,
input: $('<input>'),
}).render();
var maybe_numbers = [
"+1 415",
"+1415",
"+1415",
"415",
"(415)",
" (415",
"(415) 123 4567",
"+1 (415) 123 4567",
" +1 (415) 123 4567",
"1 (415) 123 4567",
"1 415-123-4567",
"415-123-4567"
'+1 415',
'+1415',
'+1415',
'415',
'(415)',
' (415',
'(415) 123 4567',
'+1 (415) 123 4567',
' +1 (415) 123 4567',
'1 (415) 123 4567',
'1 415-123-4567',
'415-123-4567',
];
maybe_numbers.forEach(function(n) {
assert.ok(view.maybeNumber(n), n);
});
});
describe('Searching for left groups', function() {
var convo = new Whisper.ConversationCollection().add({
id: 'a-left-group',
name: 'i left this group',
members: [],
type: 'group',
left: true
});
before(function(done) {
convo.save().then(done);
});
describe('with no messages', function() {
var input = $('<input>');
var view = new Whisper.ConversationSearchView({ input: input }).render();
before(function(done) {
view.$input.val('left');
view.filterContacts();
view.typeahead_view.collection.on('reset', function() {
done();
});
});
it('should not surface left groups with no messages', function() {
assert.isUndefined(view.typeahead_view.collection.get(convo.id), 'got left group');
});
});
describe('with messages', function() {
var input = $('<input>');
var view = new Whisper.ConversationSearchView({ input: input }).render();
before(function(done) {
convo.save({lastMessage: 'asdf'}).then(function() {
view.$input.val('left');
view.filterContacts();
view.typeahead_view.collection.on('reset', function() {
done();
});
});
});
it('should surface left groups with messages', function() {
assert.isDefined(view.typeahead_view.collection.get(convo.id), 'got left group');
});
});
});
describe('Showing all contacts', function() {
var convo = new Whisper.ConversationCollection().add({
id: 'a-left-group',
name: 'i left this group',
members: [],
type: 'group',
left: true,
});
before(function(done) {
convo.save().then(done);
});
describe('with no messages', function() {
var input = $('<input>');
var view = new Whisper.ConversationSearchView({ input: input }).render();
view.showAllContacts = true;
var convo = new Whisper.ConversationCollection().add({
id: 'a-left-group',
name: 'i left this group',
members: [],
type: 'group',
left: true
});
before(function(done) {
convo.save().then(done);
view.$input.val('left');
view.filterContacts();
view.typeahead_view.collection.on('reset', function() {
done();
});
});
describe('with no messages', function() {
before(function(done) {
view.resetTypeahead();
view.typeahead_view.collection.on('reset', function() {
done();
});
});
it('should not surface left groups with no messages', function() {
assert.isUndefined(view.typeahead_view.collection.get(convo.id), 'got left group');
});
it('should not surface left groups with no messages', function() {
assert.isUndefined(
view.typeahead_view.collection.get(convo.id),
'got left group'
);
});
describe('with messages', function() {
before(function(done) {
convo.save({lastMessage: 'asdf'}).then(function() {
view.typeahead_view.collection.on('reset', function() {
done();
});
view.resetTypeahead();
});
});
it('should surface left groups with messages', function() {
assert.isDefined(view.typeahead_view.collection.get(convo.id), 'got left group');
});
describe('with messages', function() {
var input = $('<input>');
var view = new Whisper.ConversationSearchView({ input: input }).render();
before(function(done) {
convo.save({ lastMessage: 'asdf' }).then(function() {
view.$input.val('left');
view.filterContacts();
view.typeahead_view.collection.on('reset', function() {
done();
});
});
});
it('should surface left groups with messages', function() {
assert.isDefined(
view.typeahead_view.collection.get(convo.id),
'got left group'
);
});
});
});
describe('Showing all contacts', function() {
var input = $('<input>');
var view = new Whisper.ConversationSearchView({ input: input }).render();
view.showAllContacts = true;
var convo = new Whisper.ConversationCollection().add({
id: 'a-left-group',
name: 'i left this group',
members: [],
type: 'group',
left: true,
});
before(function(done) {
convo.save().then(done);
});
describe('with no messages', function() {
before(function(done) {
view.resetTypeahead();
view.typeahead_view.collection.on('reset', function() {
done();
});
});
it('should not surface left groups with no messages', function() {
assert.isUndefined(
view.typeahead_view.collection.get(convo.id),
'got left group'
);
});
});
describe('with messages', function() {
before(function(done) {
convo.save({ lastMessage: 'asdf' }).then(function() {
view.typeahead_view.collection.on('reset', function() {
done();
});
view.resetTypeahead();
});
});
it('should surface left groups with messages', function() {
assert.isDefined(
view.typeahead_view.collection.get(convo.id),
'got left group'
);
});
});
});
});

View File

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

View File

@@ -1,41 +1,41 @@
describe('InboxView', function() {
var inboxView = new Whisper.InboxView({
model: {},
window: window,
initialLoadComplete: function() {}
}).render();
var inboxView = new Whisper.InboxView({
model: {},
window: window,
initialLoadComplete: function() {},
}).render();
var conversation = new Whisper.Conversation({ id: '1234', type: 'private'});
var conversation = new Whisper.Conversation({ id: '1234', type: 'private' });
describe('the conversation stack', function() {
it('should be rendered', function() {
assert.ok(inboxView.$('.conversation-stack').length === 1);
});
describe('opening a conversation', function() {
var triggeredOpenedCount = 0;
before(function() {
conversation.on('opened', function() {
triggeredOpenedCount++;
});
inboxView.conversation_stack.open(conversation);
});
it('should trigger an opened event', function() {
assert.ok(triggeredOpenedCount === 1);
});
describe('and then opening it again immediately', function() {
before(function() {
inboxView.conversation_stack.open(conversation);
});
it('should trigger the opened event again', function() {
assert.ok(triggeredOpenedCount === 2);
});
});
});
describe('the conversation stack', function() {
it('should be rendered', function() {
assert.ok(inboxView.$('.conversation-stack').length === 1);
});
describe('opening a conversation', function() {
var triggeredOpenedCount = 0;
before(function() {
conversation.on('opened', function() {
triggeredOpenedCount++;
});
inboxView.conversation_stack.open(conversation);
});
it('should trigger an opened event', function() {
assert.ok(triggeredOpenedCount === 1);
});
describe('and then opening it again immediately', function() {
before(function() {
inboxView.conversation_stack.open(conversation);
});
it('should trigger the opened event again', function() {
assert.ok(triggeredOpenedCount === 2);
});
});
});
});
});

View File

@@ -2,33 +2,32 @@
* vim: ts=4:sw=4:expandtab
*/
describe('LastSeenIndicatorView', function() {
it('renders provided count', function() {
var view = new Whisper.LastSeenIndicatorView({count: 10});
assert.equal(view.count, 10);
it('renders provided count', function() {
var view = new Whisper.LastSeenIndicatorView({ count: 10 });
assert.equal(view.count, 10);
view.render();
assert.match(view.$el.html(), /10 Unread Messages/);
});
view.render();
assert.match(view.$el.html(), /10 Unread Messages/);
});
it('renders count of 1', function() {
var view = new Whisper.LastSeenIndicatorView({count: 1});
assert.equal(view.count, 1);
it('renders count of 1', function() {
var view = new Whisper.LastSeenIndicatorView({ count: 1 });
assert.equal(view.count, 1);
view.render();
assert.match(view.$el.html(), /1 Unread Message/);
});
view.render();
assert.match(view.$el.html(), /1 Unread Message/);
});
it('increments count', function() {
var view = new Whisper.LastSeenIndicatorView({count: 4});
it('increments count', function() {
var view = new Whisper.LastSeenIndicatorView({ count: 4 });
assert.equal(view.count, 4);
view.render();
assert.match(view.$el.html(), /4 Unread Messages/);
view.increment(3);
assert.equal(view.count, 7);
view.render();
assert.match(view.$el.html(), /7 Unread Messages/);
});
assert.equal(view.count, 4);
view.render();
assert.match(view.$el.html(), /4 Unread Messages/);
view.increment(3);
assert.equal(view.count, 7);
view.render();
assert.match(view.$el.html(), /7 Unread Messages/);
});
});

View File

@@ -6,7 +6,7 @@ describe('ListView', function() {
});
it('should add children to the list element as they are added to the collection', function() {
var view = new Whisper.ListView({collection: collection});
var view = new Whisper.ListView({ collection: collection });
collection.add('hello');
assert.equal(view.$el.children().length, 1);
collection.add('world');
@@ -14,9 +14,8 @@ describe('ListView', function() {
});
it('should add all the children to the list element on reset', function() {
var view = new Whisper.ListView({collection: collection});
var view = new Whisper.ListView({ collection: collection });
collection.reset(['goodbye', 'world']);
assert.equal(view.$el.children().length, 2);
});
});

View File

@@ -1,9 +1,9 @@
describe('MessageView', function() {
var convo, message;
before(async (done) => {
before(async done => {
await clearDatabase();
convo = new Whisper.Conversation({id: 'foo'});
convo = new Whisper.Conversation({ id: 'foo' });
message = convo.messageCollection.add({
conversationId: convo.id,
body: 'hello world',
@@ -17,39 +17,39 @@ describe('MessageView', function() {
});
it('should display the message text', function() {
var view = new Whisper.MessageView({model: message}).render();
var view = new Whisper.MessageView({ model: message }).render();
assert.match(view.$el.text(), /hello world/);
});
it('should auto-update the message text', function() {
var view = new Whisper.MessageView({model: message}).render();
var view = new Whisper.MessageView({ model: message }).render();
message.set('body', 'goodbye world');
assert.match(view.$el.html(), /goodbye world/);
});
it('should have a nice timestamp', function() {
var view = new Whisper.MessageView({model: message});
message.set({'sent_at': Date.now() - 5000});
var view = new Whisper.MessageView({ model: message });
message.set({ sent_at: Date.now() - 5000 });
view.render();
assert.match(view.$el.html(), /now/);
message.set({'sent_at': Date.now() - 60000});
message.set({ sent_at: Date.now() - 60000 });
view.render();
assert.match(view.$el.html(), /min/);
message.set({'sent_at': Date.now() - 3600000});
message.set({ sent_at: Date.now() - 3600000 });
view.render();
assert.match(view.$el.html(), /hour/);
});
it('should not imply messages are from the future', function() {
var view = new Whisper.MessageView({model: message});
message.set({'sent_at': Date.now() + 60000});
var view = new Whisper.MessageView({ model: message });
message.set({ sent_at: Date.now() + 60000 });
view.render();
assert.match(view.$el.html(), /now/);
});
it('should go away when the model is destroyed', function() {
var view = new Whisper.MessageView({model: message});
var view = new Whisper.MessageView({ model: message });
var div = $('<div>').append(view.$el);
message.destroy();
assert.strictEqual(div.find(view.$el).length, 0);
@@ -58,7 +58,7 @@ describe('MessageView', function() {
it('allows links', function() {
var url = 'http://example.com';
message.set('body', url);
var view = new Whisper.MessageView({model: message});
var view = new Whisper.MessageView({ model: message });
view.render();
var link = view.$el.find('.body a');
assert.strictEqual(link.length, 1);
@@ -69,7 +69,7 @@ describe('MessageView', function() {
it('disallows xss', function() {
var xss = '<script>alert("pwnd")</script>';
message.set('body', xss);
var view = new Whisper.MessageView({model: message});
var view = new Whisper.MessageView({ model: message });
view.render();
assert.include(view.$el.text(), xss); // should appear as escaped text
assert.strictEqual(view.$el.find('script').length, 0); // should not appear as html
@@ -77,11 +77,14 @@ describe('MessageView', function() {
it('supports emoji', function() {
message.set('body', 'I \u2764\uFE0F emoji!');
var view = new Whisper.MessageView({model: message});
var view = new Whisper.MessageView({ model: message });
view.render();
var img = view.$el.find('.content img');
assert.strictEqual(img.length, 1);
assert.strictEqual(img.attr('src'), 'node_modules/emoji-datasource-apple/img/apple/64/2764-fe0f.png');
assert.strictEqual(
img.attr('src'),
'node_modules/emoji-datasource-apple/img/apple/64/2764-fe0f.png'
);
assert.strictEqual(img.attr('title'), ':heart:');
assert.strictEqual(img.attr('class'), 'emoji');
});

View File

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

View File

@@ -2,37 +2,37 @@
* vim: ts=4:sw=4:expandtab
*/
describe('ScrollDownButtonView', function() {
it('renders with count = 0', function() {
var view = new Whisper.ScrollDownButtonView();
view.render();
assert.equal(view.count, 0);
assert.match(view.$el.html(), /Scroll to bottom/);
});
it('renders with count = 0', function() {
var view = new Whisper.ScrollDownButtonView();
view.render();
assert.equal(view.count, 0);
assert.match(view.$el.html(), /Scroll to bottom/);
});
it('renders with count = 1', function() {
var view = new Whisper.ScrollDownButtonView({count: 1});
view.render();
assert.equal(view.count, 1);
assert.match(view.$el.html(), /new-messages/);
assert.match(view.$el.html(), /New message below/);
});
it('renders with count = 1', function() {
var view = new Whisper.ScrollDownButtonView({ count: 1 });
view.render();
assert.equal(view.count, 1);
assert.match(view.$el.html(), /new-messages/);
assert.match(view.$el.html(), /New message below/);
});
it('renders with count = 2', function() {
var view = new Whisper.ScrollDownButtonView({count: 2});
view.render();
assert.equal(view.count, 2);
it('renders with count = 2', function() {
var view = new Whisper.ScrollDownButtonView({ count: 2 });
view.render();
assert.equal(view.count, 2);
assert.match(view.$el.html(), /new-messages/);
assert.match(view.$el.html(), /New messages below/);
});
assert.match(view.$el.html(), /new-messages/);
assert.match(view.$el.html(), /New messages below/);
});
it('increments count and re-renders', function() {
var view = new Whisper.ScrollDownButtonView();
view.render();
assert.equal(view.count, 0);
assert.notMatch(view.$el.html(), /new-messages/);
view.increment(1);
assert.equal(view.count, 1);
assert.match(view.$el.html(), /new-messages/);
});
it('increments count and re-renders', function() {
var view = new Whisper.ScrollDownButtonView();
view.render();
assert.equal(view.count, 0);
assert.notMatch(view.$el.html(), /new-messages/);
view.increment(1);
assert.equal(view.count, 1);
assert.match(view.$el.html(), /new-messages/);
});
});

View File

@@ -3,12 +3,11 @@
*/
describe('Threads', function() {
it('should be ordered newest to oldest', function() {
// Timestamps
var today = new Date();
var tomorrow = new Date();
tomorrow.setDate(today.getDate()+1);
tomorrow.setDate(today.getDate() + 1);
// Add threads
Whisper.Threads.add({ timestamp: today });
@@ -21,6 +20,4 @@ describe('Threads', function() {
// Compare timestamps
assert(firstTimestamp > secondTimestamp);
});
});

View File

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

View File

@@ -3,8 +3,8 @@ describe('Whisper.View', function() {
var viewClass = Whisper.View.extend({
template: '<div>{{ variable }}</div>',
render_attributes: {
variable: 'value'
}
variable: 'value',
},
});
var view = new viewClass();
@@ -13,7 +13,7 @@ describe('Whisper.View', function() {
});
it('renders a template with no render_attributes', function() {
var viewClass = Whisper.View.extend({
template: '<div>static text</div>'
template: '<div>static text</div>',
});
var view = new viewClass();
@@ -22,10 +22,12 @@ describe('Whisper.View', function() {
});
it('renders a template function with render_attributes function', function() {
var viewClass = Whisper.View.extend({
template: function() { return '<div>{{ variable }}</div>'; },
template: function() {
return '<div>{{ variable }}</div>';
},
render_attributes: function() {
return { variable: 'value' };
}
},
});
var view = new viewClass();
view.render();