Format all source code using Prettier
This commit is contained in:
@@ -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',
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@@ -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();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
});
|
||||
});
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
@@ -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
@@ -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'), 'Á');
|
||||
|
@@ -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,
|
||||
|
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
5367
test/fixtures.js
5367
test/fixtures.js
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
});
|
||||
});
|
||||
|
@@ -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.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
@@ -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);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
@@ -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);
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
@@ -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());
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
@@ -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',
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@@ -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);
|
||||
|
@@ -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' +
|
||||
|
@@ -3,7 +3,6 @@ const { assert } = require('chai');
|
||||
|
||||
const Startup = require('../../js/modules/startup');
|
||||
|
||||
|
||||
describe('Startup', () => {
|
||||
const sandbox = sinon.createSandbox();
|
||||
|
||||
|
@@ -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,
|
||||
|
@@ -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);
|
||||
|
@@ -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('It’s easy if you try'),
|
||||
}],
|
||||
attachments: [
|
||||
{
|
||||
path: 'ab/abcdefghi',
|
||||
data: stringToArrayBuffer('It’s 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('It’s easy if you try'));
|
||||
assert.deepEqual(
|
||||
attachment.data,
|
||||
stringToArrayBuffer('It’s 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('It’s easy if you try'),
|
||||
attachments: [
|
||||
{
|
||||
thumbnail: {
|
||||
path: 'ab/abcdefghi',
|
||||
data: stringToArrayBuffer('It’s 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('It’s easy if you try'));
|
||||
assert.deepEqual(
|
||||
attachment.data,
|
||||
stringToArrayBuffer('It’s 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('It’s easy if you try'),
|
||||
fileName: 'test\u202Dfig.exe',
|
||||
size: 1111,
|
||||
}],
|
||||
attachments: [
|
||||
{
|
||||
contentType: 'application/json',
|
||||
data: stringToArrayBuffer('It’s 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('It’s easy if you try');
|
||||
const expectedAttachmentData = stringToArrayBuffer(
|
||||
'It’s 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);
|
||||
|
@@ -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));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -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));
|
||||
}
|
||||
);
|
||||
|
@@ -3,7 +3,6 @@ const { assert } = require('chai');
|
||||
|
||||
const Settings = require('../../../js/modules/types/settings');
|
||||
|
||||
|
||||
describe('Settings', () => {
|
||||
const sandbox = sinon.createSandbox();
|
||||
|
||||
|
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
1255
test/storage_test.js
1255
test/storage_test.js
File diff suppressed because it is too large
Load Diff
@@ -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),
|
||||
};
|
||||
|
||||
|
@@ -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'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
@@ -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/);
|
||||
});
|
||||
});
|
||||
|
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -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/);
|
||||
});
|
||||
});
|
||||
|
@@ -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);
|
||||
});
|
||||
|
||||
});
|
||||
|
@@ -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');
|
||||
});
|
||||
|
@@ -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() {});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -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/);
|
||||
});
|
||||
});
|
||||
|
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -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();
|
||||
|
Reference in New Issue
Block a user