Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
3
gaia/apps/sms/views/conversation/test/unit/.jshintrc
Executable file
3
gaia/apps/sms/views/conversation/test/unit/.jshintrc
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "../../../../test/unit/.jshintrc"
|
||||
}
|
||||
511
gaia/apps/sms/views/conversation/test/unit/attachment_renderer_test.js
Executable file
511
gaia/apps/sms/views/conversation/test/unit/attachment_renderer_test.js
Executable file
|
|
@ -0,0 +1,511 @@
|
|||
/*global MocksHelper, MockL10n, loadBodyHTML, Attachment, AttachmentRenderer,
|
||||
AssetsHelper,
|
||||
ImageUtils,
|
||||
Promise
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
require('/views/conversation/js/attachment_renderer.js');
|
||||
require('/views/shared/js/utils.js');
|
||||
|
||||
require('/shared/js/image_utils.js');
|
||||
require('/shared/test/unit/mocks/mock_l20n.js');
|
||||
require('/views/shared/test/unit/mock_attachment.js');
|
||||
require('/views/shared/test/unit/mock_utils.js');
|
||||
|
||||
var MocksHelperForAttachment = new MocksHelper([
|
||||
'Attachment',
|
||||
'Utils'
|
||||
]).init();
|
||||
|
||||
suite('AttachmentRenderer >', function() {
|
||||
MocksHelperForAttachment.attachTestHelpers();
|
||||
|
||||
var testImageBlob;
|
||||
var testImageBlob_small;
|
||||
var testImageBlob_bogus;
|
||||
var testAudioBlob;
|
||||
var testVideoBlob;
|
||||
var testVcardBlob;
|
||||
|
||||
function assertThumbnailPreview(el) {
|
||||
assert.ok(el.classList.contains('preview'));
|
||||
assert.isFalse(el.classList.contains('nopreview'));
|
||||
assert.isNull(el.querySelector('div.placeholder'));
|
||||
var thumbnail = el.querySelector('div.thumbnail');
|
||||
assert.ok(thumbnail);
|
||||
assert.include(thumbnail.style.backgroundImage, 'blob:');
|
||||
}
|
||||
|
||||
function assertThumbnailPlaceholder(el, type) {
|
||||
assert.ok(el.classList.contains('nopreview'));
|
||||
assert.isFalse(el.classList.contains('preview'));
|
||||
assert.isNull(el.querySelector('div.thumbnail'));
|
||||
assert.isFalse(el.hasAttribute('data-thumbnail'));
|
||||
var placeholder = el.querySelector('div.thumbnail-placeholder');
|
||||
assert.ok(placeholder);
|
||||
assert.ok(placeholder.classList.contains(type + '-placeholder'));
|
||||
}
|
||||
|
||||
suiteSetup(function(done) {
|
||||
this.realMozL10n = document.l10n;
|
||||
document.l10n = MockL10n;
|
||||
|
||||
// create a bogus image blob (should be rendered with a `corrupted' class)
|
||||
testImageBlob_bogus = new Blob(['This is an image message'], {
|
||||
type: 'image/jpeg'
|
||||
});
|
||||
|
||||
var mediaFolder = '/views/shared/test/unit/media';
|
||||
var blobPromises = [
|
||||
AssetsHelper.generateImageBlob(300, 300, 'image/jpeg', 0.25).then(
|
||||
(blob) => testImageBlob_small = blob
|
||||
),
|
||||
AssetsHelper.generateImageBlob(1400, 1400, 'image/jpeg', 1).then(
|
||||
(blob) => testImageBlob = blob
|
||||
),
|
||||
AssetsHelper.loadFileBlob(`${mediaFolder}/audio.oga`).then(
|
||||
(blob) => testAudioBlob = blob
|
||||
),
|
||||
AssetsHelper.loadFileBlob(`${mediaFolder}/video.ogv`).then(
|
||||
(blob) => testVideoBlob = blob
|
||||
),
|
||||
AssetsHelper.loadFileBlob(`${mediaFolder}/contacts.vcf`).then(
|
||||
(blob) => testVcardBlob = blob
|
||||
)
|
||||
];
|
||||
|
||||
Promise.all(blobPromises).then(() => {
|
||||
done(() => {
|
||||
var mmsSizeLimit = 300 * 1024;
|
||||
|
||||
assert.isTrue(
|
||||
testImageBlob_small.size < mmsSizeLimit,
|
||||
'Image blob should be greater than MMS size limit'
|
||||
);
|
||||
|
||||
assert.isTrue(
|
||||
testImageBlob.size > mmsSizeLimit,
|
||||
'Image blob should be greater than MMS size limit'
|
||||
);
|
||||
});
|
||||
}, done);
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
document.l10n = this.realMozL10n;
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
loadBodyHTML('/index.html');
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
document.body.textContent = '';
|
||||
});
|
||||
|
||||
suite('render >', function() {
|
||||
test('bogus image attachment', function(done) {
|
||||
var attachment = new Attachment(testImageBlob_bogus, {
|
||||
name: 'Image attachment'
|
||||
});
|
||||
|
||||
var attachmentRenderer = AttachmentRenderer.for(attachment),
|
||||
attachmentContainer = attachmentRenderer.getAttachmentContainer();
|
||||
|
||||
attachmentRenderer.render().then(() => {
|
||||
assert.ok(
|
||||
attachmentContainer.classList.contains('attachment-container')
|
||||
);
|
||||
assert.equal(attachmentContainer.dataset.attachmentType, 'img');
|
||||
// broken image => no thumbnail and `corrupted' class
|
||||
assertThumbnailPlaceholder(attachmentContainer, 'img');
|
||||
assert.ok(attachmentContainer.querySelector('div.corrupted'));
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('small image attachment (thumbnail)', function(done) {
|
||||
var attachment = new Attachment(testImageBlob_small, {
|
||||
name: 'Image attachment'
|
||||
});
|
||||
|
||||
var attachmentRenderer = AttachmentRenderer.for(attachment),
|
||||
attachmentContainer = attachmentRenderer.getAttachmentContainer();
|
||||
|
||||
attachmentRenderer.render().then(() => {
|
||||
assert.ok(
|
||||
attachmentContainer.classList.contains('attachment-container')
|
||||
);
|
||||
assert.equal(attachmentContainer.dataset.attachmentType, 'img');
|
||||
// image < message limit => thumbnail blob
|
||||
assertThumbnailPreview(attachmentContainer);
|
||||
assert.isNull(attachmentContainer.querySelector('div.corrupted'));
|
||||
assert.isDefined(attachmentContainer.dataset.thumbnail);
|
||||
assert.include(attachmentContainer.dataset.thumbnail, 'blob:');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('HUGE (fake) image attachment', function(done) {
|
||||
var attachment = new Attachment({
|
||||
size: 10 * 1024 * 1024,
|
||||
type: 'image/jpeg'
|
||||
}, {
|
||||
name: 'Image attachment'
|
||||
});
|
||||
|
||||
var attachmentRenderer = AttachmentRenderer.for(attachment),
|
||||
attachmentContainer = attachmentRenderer.getAttachmentContainer();
|
||||
|
||||
attachmentRenderer.render().then(() => {
|
||||
assert.ok(
|
||||
attachmentContainer.classList.contains('attachment-container')
|
||||
);
|
||||
assert.equal(attachmentContainer.dataset.attachmentType, 'img');
|
||||
// image > message limit => no thumbnail preview
|
||||
assertThumbnailPlaceholder(attachmentContainer, 'img');
|
||||
assert.isNull(attachmentContainer.querySelector('div.corrupted'));
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('big image attachment', function(done) {
|
||||
var attachment = new Attachment(testImageBlob, {
|
||||
name: 'Image attachment'
|
||||
});
|
||||
|
||||
var attachmentRenderer = AttachmentRenderer.for(attachment),
|
||||
attachmentContainer = attachmentRenderer.getAttachmentContainer();
|
||||
|
||||
attachmentRenderer.render().then(() => {
|
||||
assert.ok(
|
||||
attachmentContainer.classList.contains('attachment-container')
|
||||
);
|
||||
assert.equal(attachmentContainer.dataset.attachmentType, 'img');
|
||||
// image < message limit => thumbnail blob
|
||||
assertThumbnailPreview(attachmentContainer);
|
||||
assert.isNull(attachmentContainer.querySelector('div.corrupted'));
|
||||
assert.isDefined(attachmentContainer.dataset.thumbnail);
|
||||
assert.include(attachmentContainer.dataset.thumbnail, 'blob:');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('audio attachment', function(done) {
|
||||
var attachment = new Attachment(testAudioBlob, {
|
||||
name: 'Audio attachment'
|
||||
});
|
||||
|
||||
var attachmentRenderer = AttachmentRenderer.for(attachment),
|
||||
attachmentContainer = attachmentRenderer.getAttachmentContainer();
|
||||
|
||||
attachmentRenderer.render().then(() => {
|
||||
assert.ok(
|
||||
attachmentContainer.classList.contains('attachment-container')
|
||||
);
|
||||
assert.equal(attachmentContainer.dataset.attachmentType, 'audio');
|
||||
// not an image => no thumbnail preview
|
||||
assertThumbnailPlaceholder(attachmentContainer, 'audio');
|
||||
assert.isNull(attachmentContainer.querySelector('div.corrupted'));
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('video attachment', function(done) {
|
||||
var attachment = new Attachment(testVideoBlob, {
|
||||
name: 'Video attachment'
|
||||
});
|
||||
|
||||
var attachmentRenderer = AttachmentRenderer.for(attachment),
|
||||
attachmentContainer = attachmentRenderer.getAttachmentContainer();
|
||||
|
||||
attachmentRenderer.render().then(() => {
|
||||
assert.ok(
|
||||
attachmentContainer.classList.contains('attachment-container')
|
||||
);
|
||||
assert.equal(attachmentContainer.dataset.attachmentType, 'video');
|
||||
// not an image => no thumbnail preview
|
||||
assertThumbnailPlaceholder(attachmentContainer, 'video');
|
||||
assert.isNull(attachmentContainer.querySelector('div.corrupted'));
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('vcard attachment', function(done) {
|
||||
var attachment = new Attachment(testVcardBlob, {
|
||||
name: 'vcard.vcf'
|
||||
});
|
||||
|
||||
var attachmentRenderer = AttachmentRenderer.for(attachment),
|
||||
attachmentContainer = attachmentRenderer.getAttachmentContainer();
|
||||
|
||||
attachmentRenderer.render().then(() => {
|
||||
assert.ok(
|
||||
attachmentContainer.classList.contains('attachment-container')
|
||||
);
|
||||
assert.equal(attachmentContainer.dataset.attachmentType, 'vcard');
|
||||
// not an image => no thumbnail preview
|
||||
assertThumbnailPlaceholder(attachmentContainer, 'vcard');
|
||||
assert.isNull(attachmentContainer.querySelector('div.corrupted'));
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
suite('draft attachments > ', function() {
|
||||
test('> vcard', function(done) {
|
||||
var attachment = new Attachment(testVcardBlob, {
|
||||
name: 'vcard.vcf',
|
||||
isDraft: true
|
||||
});
|
||||
var attachmentRenderer = AttachmentRenderer.for(attachment),
|
||||
attachmentContainer = attachmentRenderer.getAttachmentContainer();
|
||||
|
||||
attachmentRenderer.render().then(() => {
|
||||
assert.equal(attachmentContainer.tagName, 'IFRAME');
|
||||
assert.equal(attachmentContainer.dataset.attachmentType, 'vcard');
|
||||
var doc = attachmentContainer.contentDocument;
|
||||
var attachmentNode = doc.querySelector('.attachment');
|
||||
assert.ok(attachmentNode);
|
||||
|
||||
assert.isNull(attachmentNode.querySelector('div.corrupted'));
|
||||
assert.ok(attachmentNode.querySelector('.vcard-placeholder'));
|
||||
var fileNameNode = doc.querySelector('.file-name');
|
||||
assert.ok(fileNameNode);
|
||||
assert.isNull(fileNameNode.firstElementChild);
|
||||
assert.equal(fileNameNode.textContent, attachment.name);
|
||||
}).then(done, done);
|
||||
|
||||
document.body.appendChild(attachmentContainer);
|
||||
});
|
||||
|
||||
suite('> image', function() {
|
||||
function testDraftImage(testName, attachmentName) {
|
||||
test(testName + ' attachment', function(done) {
|
||||
var attachment = new Attachment(testImageBlob, {
|
||||
name: attachmentName,
|
||||
isDraft: true
|
||||
});
|
||||
|
||||
var attachmentRenderer = AttachmentRenderer.for(attachment),
|
||||
attachmentContainer =
|
||||
attachmentRenderer.getAttachmentContainer();
|
||||
|
||||
attachmentRenderer.render().then(() => {
|
||||
assert.equal(attachmentContainer.tagName, 'IFRAME');
|
||||
var doc = attachmentContainer.contentDocument;
|
||||
var attachmentNode = doc.querySelector('.attachment');
|
||||
assert.ok(attachmentNode);
|
||||
assert.isNull(attachmentNode.querySelector('div.corrupted'));
|
||||
assert.ok(attachmentNode.querySelector('.thumbnail'));
|
||||
assert.isDefined(attachmentContainer.dataset.thumbnail);
|
||||
assert.include(attachmentContainer.dataset.thumbnail, 'blob:');
|
||||
var fileNameNode = doc.querySelector('.file-name');
|
||||
assert.ok(fileNameNode);
|
||||
assert.isNull(fileNameNode.firstElementChild);
|
||||
assert.equal(fileNameNode.textContent, attachment.name);
|
||||
}).then(done, done);
|
||||
|
||||
document.body.appendChild(attachmentContainer);
|
||||
});
|
||||
}
|
||||
|
||||
testDraftImage('normal', 'Image attachment');
|
||||
testDraftImage(
|
||||
'malicious script',
|
||||
'%3Cscript%3Ealert(%22I%20am%20dangerous%22)%3C%2Fscript%3E'
|
||||
);
|
||||
testDraftImage(
|
||||
'malicious image',
|
||||
'%3Cimg%20src%3D%22http%3A%2F%2Fmalicious.server.ru%2Fpingback%22%3E'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('getThumbnail >', function() {
|
||||
|
||||
setup(function() {
|
||||
this.sinon.stub(ImageUtils, 'getSizeAndType');
|
||||
this.sinon.spy(ImageUtils.Downsample, 'sizeNoMoreThan');
|
||||
});
|
||||
|
||||
test('Use ImageUtils for no resize case', function(done) {
|
||||
var attachmentRenderer = AttachmentRenderer.for(new Attachment(
|
||||
testImageBlob, {
|
||||
name: 'auto_generated'
|
||||
}
|
||||
));
|
||||
|
||||
ImageUtils.getSizeAndType.returns(
|
||||
Promise.resolve({
|
||||
width: 100,
|
||||
height: 100
|
||||
})
|
||||
);
|
||||
|
||||
attachmentRenderer.getThumbnail().then(function(thumbnail) {
|
||||
assert.include(thumbnail.url, 'blob:');
|
||||
assert.equal(thumbnail.fragment.toString(), '');
|
||||
sinon.assert.calledWith(
|
||||
ImageUtils.Downsample.sizeNoMoreThan,
|
||||
sinon.match.number
|
||||
);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('Use ImageUtils for resize needed case', function(done) {
|
||||
var attachmentRenderer = AttachmentRenderer.for(new Attachment(
|
||||
testImageBlob, {
|
||||
name: 'auto_generated'
|
||||
}
|
||||
));
|
||||
|
||||
ImageUtils.getSizeAndType.returns(
|
||||
Promise.resolve({
|
||||
width: 300,
|
||||
height: 300
|
||||
})
|
||||
);
|
||||
|
||||
attachmentRenderer.getThumbnail().then(function(thumbnails) {
|
||||
assert.include(thumbnails.url, 'blob:');
|
||||
assert.include(thumbnails.fragment.toString(), '#-moz-samplesize=');
|
||||
sinon.assert.calledWith(
|
||||
ImageUtils.Downsample.sizeNoMoreThan,
|
||||
sinon.match.number
|
||||
);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('throw error if image is corrupted', function(done) {
|
||||
var attachmentRenderer = AttachmentRenderer.for(
|
||||
new Attachment(testImageBlob_bogus, {
|
||||
name: 'auto_generated'
|
||||
})
|
||||
);
|
||||
var imgError = 'image error';
|
||||
|
||||
ImageUtils.getSizeAndType.returns(
|
||||
Promise.reject(imgError)
|
||||
);
|
||||
|
||||
attachmentRenderer.getThumbnail().catch(function(error) {
|
||||
sinon.assert.calledWith(ImageUtils.getSizeAndType, testImageBlob_bogus);
|
||||
assert.equal(imgError, error);
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('getAttachmentContainer >', function() {
|
||||
test('uses only one container per attachment renderer', function() {
|
||||
var attachment = new Attachment(testImageBlob_bogus, {
|
||||
name: 'Image attachment'
|
||||
});
|
||||
|
||||
var attachmentRenderer1 = AttachmentRenderer.for(attachment),
|
||||
attachmentRenderer2 = AttachmentRenderer.for(attachment);
|
||||
|
||||
var attachmentContainer1 = attachmentRenderer1.getAttachmentContainer(),
|
||||
attachmentContainer2 = attachmentRenderer2.getAttachmentContainer();
|
||||
|
||||
assert.isNotNull(attachmentContainer1);
|
||||
assert.isNotNull(attachmentContainer2);
|
||||
|
||||
assert.notEqual(attachmentContainer1, attachmentContainer2);
|
||||
|
||||
var theSameAttachmentContainer1 =
|
||||
attachmentRenderer1.getAttachmentContainer();
|
||||
var theSameAttachmentContainer2 =
|
||||
attachmentRenderer2.getAttachmentContainer();
|
||||
|
||||
assert.equal(attachmentContainer1, theSameAttachmentContainer1);
|
||||
assert.equal(attachmentContainer2, theSameAttachmentContainer2);
|
||||
});
|
||||
|
||||
test('returns correct container for non-draft attachment', function() {
|
||||
var attachment = new Attachment(testImageBlob_bogus, {
|
||||
name: 'Image attachment'
|
||||
});
|
||||
|
||||
var attachmentRenderer = AttachmentRenderer.for(attachment);
|
||||
|
||||
var attachmentContainer = attachmentRenderer.getAttachmentContainer();
|
||||
|
||||
assert.isNotNull(attachmentContainer);
|
||||
assert.equal(attachmentContainer.nodeName.toLowerCase(), 'div');
|
||||
assert.isTrue(
|
||||
attachmentContainer.classList.contains('attachment-container')
|
||||
);
|
||||
assert.equal(attachmentContainer.dataset.attachmentType, attachment.type);
|
||||
});
|
||||
|
||||
test('returns correct container for draft attachment', function() {
|
||||
var attachment = new Attachment(testImageBlob_bogus, {
|
||||
name: 'Image attachment',
|
||||
isDraft: true
|
||||
});
|
||||
|
||||
var attachmentRenderer = AttachmentRenderer.for(attachment);
|
||||
|
||||
var attachmentContainer = attachmentRenderer.getAttachmentContainer();
|
||||
|
||||
assert.isNotNull(attachmentContainer);
|
||||
assert.equal(attachmentContainer.nodeName.toLowerCase(), 'iframe');
|
||||
assert.isTrue(
|
||||
attachmentContainer.classList.contains('attachment-container')
|
||||
);
|
||||
assert.equal(attachmentContainer.dataset.attachmentType, attachment.type);
|
||||
assert.equal(
|
||||
attachmentContainer.getAttribute('sandbox'),
|
||||
'allow-same-origin'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
suite('updateFileSize >', function() {
|
||||
test('updates size in the same container', function(done) {
|
||||
var attachment = new Attachment(testImageBlob, {
|
||||
name: 'Image attachment'
|
||||
});
|
||||
|
||||
var attachmentRenderer = AttachmentRenderer.for(attachment);
|
||||
var attachmentContainer = attachmentRenderer.getAttachmentContainer();
|
||||
|
||||
var currentFileSize, sizeInfo;
|
||||
attachmentRenderer.render().then(() => {
|
||||
sizeInfo = attachmentContainer.querySelector('.size-indicator');
|
||||
currentFileSize = sizeInfo.dataset.l10nArgs;
|
||||
|
||||
attachment.blob = testImageBlob_small;
|
||||
return attachmentRenderer.updateFileSize();
|
||||
}).then(() => {
|
||||
var newFileSize = sizeInfo.dataset.l10nArgs;
|
||||
assert.notEqual(newFileSize, currentFileSize);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('updates size in iframes too', function(done) {
|
||||
this.sinon.spy(document.l10n, 'translateFragment');
|
||||
|
||||
var attachment = new Attachment(testImageBlob, {
|
||||
name: 'Image attachment',
|
||||
isDraft: true
|
||||
});
|
||||
|
||||
var attachmentRenderer = AttachmentRenderer.for(attachment);
|
||||
var attachmentContainer = attachmentRenderer.getAttachmentContainer();
|
||||
document.body.appendChild(attachmentContainer);
|
||||
|
||||
var currentFileSize, sizeInfo, node;
|
||||
attachmentRenderer.render().then(() => {
|
||||
node = attachmentContainer.contentDocument.documentElement;
|
||||
sizeInfo = node.querySelector('.size-indicator');
|
||||
currentFileSize = sizeInfo.dataset.l10nArgs;
|
||||
|
||||
attachment.blob = testImageBlob_small;
|
||||
return attachmentRenderer.updateFileSize();
|
||||
}).then(() => {
|
||||
var newFileSize = sizeInfo.dataset.l10nArgs;
|
||||
assert.notEqual(newFileSize, currentFileSize);
|
||||
|
||||
sinon.assert.calledWith(document.l10n.translateFragment, node);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
267
gaia/apps/sms/views/conversation/test/unit/attachment_test.js
Executable file
267
gaia/apps/sms/views/conversation/test/unit/attachment_test.js
Executable file
|
|
@ -0,0 +1,267 @@
|
|||
/*global MocksHelper, MockL10n, Attachment,
|
||||
AttachmentRenderer, MimeMapper, MockMozActivity, Promise, Utils,
|
||||
AssetsHelper
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
require('/views/conversation/js/attachment.js');
|
||||
require('/views/shared/js/utils.js');
|
||||
|
||||
require('/views/shared/test/unit/mock_attachment_renderer.js');
|
||||
require('/shared/test/unit/mocks/mock_l20n.js');
|
||||
require('/views/shared/test/unit/mock_utils.js');
|
||||
require('/views/shared/test/unit/mock_moz_activity.js');
|
||||
require('/views/shared/test/unit/mock_mime_mapper.js');
|
||||
|
||||
var MocksHelperForAttachment = new MocksHelper([
|
||||
'Utils',
|
||||
'MozActivity',
|
||||
'MimeMapper',
|
||||
'AttachmentRenderer'
|
||||
]).init();
|
||||
|
||||
suite('attachment_test.js', function() {
|
||||
MocksHelperForAttachment.attachTestHelpers();
|
||||
|
||||
var testImageBlob;
|
||||
|
||||
suiteSetup(function(done) {
|
||||
// this sometimes takes longer because we fetch 4 assets via XHR
|
||||
this.realMozL10n = document.l10n;
|
||||
document.l10n = MockL10n;
|
||||
|
||||
AssetsHelper.generateImageBlob(1400, 1400, 'image/jpeg', 1).then((blob) => {
|
||||
done(() => {
|
||||
assert.isTrue(
|
||||
blob.size > 300 * 1024,
|
||||
'Image blob should be greater than MMS size limit'
|
||||
);
|
||||
testImageBlob = blob;
|
||||
});
|
||||
}, done);
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
document.l10n = this.realMozL10n;
|
||||
});
|
||||
|
||||
test('Name property defaults to a string value', function() {
|
||||
var attachment = new Attachment(new Blob());
|
||||
assert.typeOf(attachment.name, 'string');
|
||||
});
|
||||
|
||||
test('Filename is not a string', function() {
|
||||
var attachment = new Attachment(testImageBlob, {});
|
||||
|
||||
assert.equal(attachment.name, '');
|
||||
});
|
||||
|
||||
test('correct enumerable properties', function() {
|
||||
var expectedProperties = ['name', 'blob', 'isDraft'];
|
||||
var attachment = new Attachment(new Blob());
|
||||
attachment.render();
|
||||
|
||||
expectedProperties.forEach((prop) => assert.ok(prop in attachment));
|
||||
|
||||
var keys = Object.keys(attachment);
|
||||
assert.equal(keys.length, expectedProperties.length);
|
||||
});
|
||||
|
||||
suite('render attachment', function() {
|
||||
var attachmentRendererMock;
|
||||
|
||||
var attachmentContainer;
|
||||
|
||||
setup(function() {
|
||||
attachmentContainer = document.createElement('x-container');
|
||||
this.sinon.stub(AttachmentRenderer.prototype, 'getAttachmentContainer');
|
||||
AttachmentRenderer.prototype.getAttachmentContainer.returns(
|
||||
attachmentContainer
|
||||
);
|
||||
|
||||
attachmentRendererMock = new AttachmentRenderer();
|
||||
this.sinon.stub(AttachmentRenderer, 'for').returns(
|
||||
attachmentRendererMock
|
||||
);
|
||||
});
|
||||
|
||||
test('returns attachment container', function() {
|
||||
var attachment = new Attachment(new Blob(), {
|
||||
name: 'Image attachment'
|
||||
});
|
||||
|
||||
assert.equal(attachment.render(), attachmentContainer);
|
||||
});
|
||||
|
||||
test('calls ready callback in fail and success cases', function(done) {
|
||||
var attachmentRendererResult = Promise.resolve();
|
||||
|
||||
this.sinon.stub(attachmentRendererMock, 'render', function() {
|
||||
return attachmentRendererResult;
|
||||
});
|
||||
|
||||
var attachment = new Attachment(new Blob(), {
|
||||
name: 'Image attachment'
|
||||
});
|
||||
|
||||
var successfulDeferred = Utils.Promise.defer();
|
||||
attachment.render(function() {
|
||||
successfulDeferred.resolve();
|
||||
});
|
||||
|
||||
attachmentRendererResult = Promise.reject();
|
||||
|
||||
var failedDeferred = Utils.Promise.defer();
|
||||
attachment.render(function() {
|
||||
failedDeferred.resolve();
|
||||
});
|
||||
|
||||
successfulDeferred.promise.then(() => failedDeferred.promise).
|
||||
then(done, done);
|
||||
});
|
||||
|
||||
test('updateFileSize delegates to the renderer', function() {
|
||||
var firstAttachmentRenderer = new AttachmentRenderer();
|
||||
AttachmentRenderer.for.onCall(0).returns(
|
||||
firstAttachmentRenderer
|
||||
);
|
||||
|
||||
this.sinon.spy(AttachmentRenderer.prototype, 'render');
|
||||
this.sinon.spy(AttachmentRenderer.prototype, 'updateFileSize');
|
||||
|
||||
var attachment = new Attachment(new Blob());
|
||||
|
||||
attachment.render();
|
||||
attachment.updateFileSize();
|
||||
|
||||
sinon.assert.calledOnce(AttachmentRenderer.for);
|
||||
sinon.assert.alwaysCalledOn(
|
||||
firstAttachmentRenderer.render, firstAttachmentRenderer
|
||||
);
|
||||
sinon.assert.alwaysCalledOn(
|
||||
firstAttachmentRenderer.updateFileSize, firstAttachmentRenderer
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
suite('view attachment with open activity', function() {
|
||||
setup(function() {
|
||||
this.sinon.spy(MimeMapper, 'guessTypeFromFileProperties');
|
||||
this.sinon.spy(MimeMapper, 'ensureFilenameMatchesType');
|
||||
});
|
||||
|
||||
test('Open normal image attachment', function(done) {
|
||||
var fileName = 'jpeg_image.jpg';
|
||||
var attachment = new Attachment(testImageBlob, {
|
||||
name: fileName
|
||||
});
|
||||
var typeSpy = MimeMapper.guessTypeFromFileProperties;
|
||||
var matchSpy = MimeMapper.ensureFilenameMatchesType;
|
||||
attachment.view().then(() => {
|
||||
assert.ok(typeSpy.calledWith(fileName, 'image/jpeg'));
|
||||
assert.ok(matchSpy.calledWith(fileName, typeSpy.returnValues[0]));
|
||||
assert.equal(MockMozActivity.calls.length, 1);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('Filename has no extension', function(done) {
|
||||
var fileName = 'jpeg_image';
|
||||
var attachment = new Attachment(testImageBlob, {
|
||||
name: fileName
|
||||
});
|
||||
var typeSpy = MimeMapper.guessTypeFromFileProperties;
|
||||
var matchSpy = MimeMapper.ensureFilenameMatchesType;
|
||||
attachment.view().then(() => {
|
||||
assert.ok(typeSpy.calledWith(fileName, 'image/jpeg'));
|
||||
assert.ok(matchSpy.calledWith(fileName, typeSpy.returnValues[0]));
|
||||
assert.equal(MockMozActivity.calls.length, 1);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('Filename is overridden using single attachment folder',
|
||||
function(done) {
|
||||
|
||||
var attachment1 = new Attachment(testImageBlob, {
|
||||
name: '/some_path/.hidden_folder/attachment1.jpg'
|
||||
});
|
||||
|
||||
attachment1.view().then(() => {
|
||||
|
||||
assert.equal(MockMozActivity.calls.length, 1);
|
||||
assert.equal(
|
||||
MockMozActivity.calls[0].data.filename,
|
||||
'sms-attachments/attachment1.jpg'
|
||||
);
|
||||
|
||||
var attachment2 = new Attachment(testImageBlob, {
|
||||
name: 'attachment2.jpg'
|
||||
});
|
||||
|
||||
return attachment2.view();
|
||||
}).then(() => {
|
||||
assert.equal(MockMozActivity.calls.length, 2);
|
||||
assert.equal(
|
||||
MockMozActivity.calls[1].data.filename,
|
||||
'sms-attachments/attachment2.jpg'
|
||||
);
|
||||
}).then(done, done);
|
||||
|
||||
});
|
||||
|
||||
test('Filename is undefined',
|
||||
function(done) {
|
||||
|
||||
var attachment1 = new Attachment(testImageBlob, {
|
||||
name: undefined
|
||||
});
|
||||
|
||||
attachment1.view().then(() => {
|
||||
assert.equal(MockMozActivity.calls.length, 1);
|
||||
assert.equal(
|
||||
MockMozActivity.calls[0].data.filename,
|
||||
'sms-attachments/unnamed-attachment.jpg'
|
||||
);
|
||||
}).then(done, done);
|
||||
|
||||
});
|
||||
|
||||
suite('Activity errors >', function() {
|
||||
var activity;
|
||||
setup(function(done) {
|
||||
this.sinon.spy(window, 'MozActivity');
|
||||
this.sinon.stub(Utils, 'alert').returns(Promise.resolve());
|
||||
|
||||
var attachment = new Attachment(testImageBlob, {
|
||||
name: 'jpeg_image.jpg'
|
||||
});
|
||||
|
||||
attachment.view().then(() => {
|
||||
activity = window.MozActivity.firstCall.thisValue;
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('No handler for this image', function() {
|
||||
activity.onerror.call({
|
||||
error: { name: 'NO_PROVIDER' }
|
||||
});
|
||||
sinon.assert.calledWith(Utils.alert, 'attachmentOpenError');
|
||||
});
|
||||
|
||||
test('Activity is canceled', function() {
|
||||
activity.onerror.call({
|
||||
error: { name: 'ActivityCanceled' }
|
||||
});
|
||||
sinon.assert.notCalled(Utils.alert);
|
||||
});
|
||||
|
||||
test('Activity is canceled (on some other environment)', function() {
|
||||
activity.onerror.call({
|
||||
error: { name: 'USER_ABORT' }
|
||||
});
|
||||
sinon.assert.notCalled(Utils.alert);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
1955
gaia/apps/sms/views/conversation/test/unit/compose_test.js
Executable file
1955
gaia/apps/sms/views/conversation/test/unit/compose_test.js
Executable file
File diff suppressed because it is too large
Load diff
555
gaia/apps/sms/views/conversation/test/unit/conversation_integration_test.js
Executable file
555
gaia/apps/sms/views/conversation/test/unit/conversation_integration_test.js
Executable file
|
|
@ -0,0 +1,555 @@
|
|||
/*global MocksHelper, MockL10n, ConversationView,
|
||||
loadBodyHTML, Compose, MessageManager, Navigation */
|
||||
|
||||
'use strict';
|
||||
|
||||
if (typeof GestureDetector === 'undefined') {
|
||||
require('/shared/js/gesture_detector.js');
|
||||
}
|
||||
require('/shared/js/event_dispatcher.js');
|
||||
require('/shared/test/unit/mocks/mock_gesture_detector.js');
|
||||
require('/shared/test/unit/mocks/mock_l20n.js');
|
||||
|
||||
require('/views/shared/test/unit/mock_contact.js');
|
||||
require('/services/test/unit/mock_message_manager.js');
|
||||
require('/views/shared/test/unit/mock_moz_activity.js');
|
||||
require('/views/shared/test/unit/mock_information.js');
|
||||
require('/views/shared/test/unit/mock_navigation.js');
|
||||
require('/views/shared/js/utils.js');
|
||||
require('/views/shared/js/settings.js');
|
||||
require('/views/conversation/js/subject_composer.js');
|
||||
require('/views/conversation/js/compose.js');
|
||||
require('/views/shared/js/contacts.js');
|
||||
require('/views/conversation/js/recipients.js');
|
||||
require('/views/inbox/js/inbox.js');
|
||||
require('/views/conversation/js/conversation.js');
|
||||
require('/views/conversation/js/attachment.js');
|
||||
require('/views/shared/js/contact_renderer.js');
|
||||
require('/views/shared/js/navigation.js');
|
||||
require('/services/test/unit/mock_threads.js');
|
||||
require('/services/test/unit/activity/mock_activity_client.js');
|
||||
|
||||
var mHelperIntegration = new MocksHelper([
|
||||
'MessageManager',
|
||||
'MozActivity',
|
||||
'Information',
|
||||
'Navigation',
|
||||
'Threads',
|
||||
'ActivityClient'
|
||||
]).init();
|
||||
|
||||
suite('ConversationView Integration', function() {
|
||||
var realMozL10n;
|
||||
var recipients;
|
||||
var children;
|
||||
var fixture;
|
||||
var sendButton;
|
||||
var input;
|
||||
|
||||
mHelperIntegration.attachTestHelpers();
|
||||
|
||||
if (typeof loadBodyHTML === 'undefined') {
|
||||
require('/shared/test/unit/load_body_html_helper.js');
|
||||
}
|
||||
|
||||
suiteSetup(function() {
|
||||
realMozL10n = document.l10n;
|
||||
document.l10n = MockL10n;
|
||||
|
||||
loadBodyHTML('/index.html');
|
||||
Navigation.init();
|
||||
ConversationView.init();
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
document.l10n = realMozL10n;
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
|
||||
sendButton = document.getElementById('messages-send-button');
|
||||
input = document.getElementById('messages-input');
|
||||
|
||||
fixture = {
|
||||
name: 'foo',
|
||||
number: '999',
|
||||
email: 'a@b.com',
|
||||
source: 'none',
|
||||
// Mapped to node attr, not true boolean
|
||||
editable: 'true'
|
||||
};
|
||||
|
||||
|
||||
ConversationView.recipients = null;
|
||||
ConversationView.initRecipients();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
recipients = null;
|
||||
fixture = null;
|
||||
});
|
||||
|
||||
suite('Search Contact Events', function() {
|
||||
|
||||
setup(function() {
|
||||
this.sinon.spy(ConversationView, 'searchContact');
|
||||
});
|
||||
|
||||
test('toFieldInput handler, successful', function() {
|
||||
var fakeEvent = {
|
||||
target: {
|
||||
isPlaceholder: true,
|
||||
textContent: 'abc'
|
||||
}
|
||||
};
|
||||
|
||||
ConversationView.toFieldInput.call(ConversationView, fakeEvent);
|
||||
|
||||
sinon.assert.calledOnce(ConversationView.searchContact);
|
||||
sinon.assert.calledWith(ConversationView.searchContact, 'abc');
|
||||
});
|
||||
|
||||
test('toFieldInput handler, unsuccessful', function() {
|
||||
var fakeEvent = {
|
||||
target: {
|
||||
isPlaceholder: true,
|
||||
textContent: ''
|
||||
}
|
||||
};
|
||||
|
||||
ConversationView.toFieldInput.call(ConversationView, fakeEvent);
|
||||
|
||||
sinon.assert.called(ConversationView.searchContact);
|
||||
sinon.assert.calledWith(ConversationView.searchContact, '');
|
||||
|
||||
fakeEvent.target.textContent = 'abd';
|
||||
fakeEvent.target.isPlaceholder = false;
|
||||
|
||||
ConversationView.toFieldInput.call(ConversationView, fakeEvent);
|
||||
|
||||
assert.isFalse(ConversationView.searchContact.calledTwice);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Recipient List Display', function() {
|
||||
test('Always begins in singleline mode', function() {
|
||||
|
||||
// Assert initial state: #messages-recipients-list is singleline
|
||||
assert.isFalse(
|
||||
ConversationView.recipientsList.classList.contains('multiline')
|
||||
);
|
||||
assert.isTrue(
|
||||
ConversationView.recipientsList.classList.contains('singleline')
|
||||
);
|
||||
|
||||
// Modify state
|
||||
ConversationView.recipients.visible('multiline');
|
||||
|
||||
// Assert modified state: #messages-recipients-list is multiline
|
||||
assert.isTrue(
|
||||
ConversationView.recipientsList.classList.contains('multiline')
|
||||
);
|
||||
assert.isFalse(
|
||||
ConversationView.recipientsList.classList.contains('singleline')
|
||||
);
|
||||
|
||||
// Reset state
|
||||
ConversationView.initRecipients();
|
||||
|
||||
// Assert initial/reset state: #messages-recipients-list is singleline
|
||||
assert.isFalse(
|
||||
ConversationView.recipientsList.classList.contains('multiline')
|
||||
);
|
||||
assert.isTrue(
|
||||
ConversationView.recipientsList.classList.contains('singleline')
|
||||
);
|
||||
});
|
||||
|
||||
test('Typing in list will switch to singline mode', function(done) {
|
||||
var toField = ConversationView.toField;
|
||||
var recipientsList = ConversationView.recipientsList;
|
||||
|
||||
children = recipientsList.children;
|
||||
recipients = ConversationView.recipients;
|
||||
|
||||
// Fill the recipients list with enough recipients to
|
||||
// enable the "pan" event
|
||||
recipients.add({
|
||||
number: '999999999999999999999999999999'
|
||||
});
|
||||
|
||||
recipients.add({
|
||||
number: '888888888888888888888888888888'
|
||||
});
|
||||
|
||||
// Since the offsetHeight is lazily captured during any
|
||||
// first event, trigger an event to ensure that the
|
||||
// "pan" event will have a height to calculate with.
|
||||
//
|
||||
toField.click();
|
||||
|
||||
// Simulate list growth
|
||||
recipientsList.style.height = '100px';
|
||||
|
||||
// Listen for the pan event, simulated immediately following.
|
||||
toField.addEventListener('pan', function panTest() {
|
||||
done(function() {
|
||||
assert.isTrue(toField.classList.contains('multiline'));
|
||||
|
||||
toField.removeEventListener('pan', panTest, false);
|
||||
|
||||
// Once the pan event has occured, the following steps
|
||||
// will simulate the user actions necessary for testing
|
||||
// the return to single line mode
|
||||
|
||||
// 1. Enter text into the editable "placeholder"
|
||||
children[2].textContent = '0';
|
||||
|
||||
// 2. Trigger a "keyup" event
|
||||
children[2].dispatchEvent(
|
||||
new CustomEvent('keyup', {
|
||||
bubbles: true
|
||||
})
|
||||
);
|
||||
|
||||
// toField should now be "singleline" again
|
||||
assert.isTrue(toField.classList.contains('singleline'));
|
||||
});
|
||||
});
|
||||
|
||||
// Simulate pan event
|
||||
toField.dispatchEvent(
|
||||
new CustomEvent('pan', {
|
||||
detail: {
|
||||
absolute: {
|
||||
dy: 1
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('Clicking in list will switch to singline mode', function(done) {
|
||||
var toField = ConversationView.toField;
|
||||
var recipientsList = ConversationView.recipientsList;
|
||||
|
||||
children = recipientsList.children;
|
||||
recipients = ConversationView.recipients;
|
||||
|
||||
// Fill the recipients list with enough recipients to
|
||||
// enable the "multiline" view
|
||||
recipients.add({
|
||||
number: '999999999999999999999999999999'
|
||||
});
|
||||
|
||||
recipients.add({
|
||||
number: '888888888888888888888888888888'
|
||||
});
|
||||
|
||||
// Simulate list growth
|
||||
recipientsList.style.height = '100px';
|
||||
|
||||
// Listen for the pan event, simulated immediately following.
|
||||
toField.addEventListener('click', function clickTest() {
|
||||
done(function() {
|
||||
toField.removeEventListener('click', clickTest, false);
|
||||
|
||||
// toField should now be "singleline" again
|
||||
assert.isTrue(toField.classList.contains('singleline'));
|
||||
});
|
||||
});
|
||||
|
||||
// Artificially set the list to multiline view
|
||||
recipients.visible('multiline');
|
||||
|
||||
// Click "anywhere"
|
||||
toField.click();
|
||||
});
|
||||
});
|
||||
|
||||
suite('Recipient Input Behaviours', function() {
|
||||
var is = {
|
||||
corresponding: function(recipient, avatar, value) {
|
||||
return is.recipient(recipient, value) &&
|
||||
is.avatar(avatar, value);
|
||||
},
|
||||
recipient: function(candidate, value) {
|
||||
return (candidate.name === value || candidate.number === value);
|
||||
},
|
||||
avatar: function(candidate, value) {
|
||||
return !is.placeholder(candidate) &&
|
||||
!is.editable(candidate) && candidate.textContent.trim() === value;
|
||||
},
|
||||
placeholder: function(candidate, opts) {
|
||||
opts = opts || { isEmpty: true };
|
||||
return candidate.isPlaceholder &&
|
||||
((opts.isEmpty && candidate.textContent.trim() === '') || true);
|
||||
},
|
||||
editable: function(candidate) {
|
||||
return candidate.contentEditable === false;
|
||||
}
|
||||
};
|
||||
|
||||
setup(function() {
|
||||
this.sinon.stub(Navigation, 'isCurrentPanel').returns(false);
|
||||
Navigation.isCurrentPanel.withArgs('composer').returns(true);
|
||||
|
||||
this.sinon.stub(MessageManager, 'sendSMS');
|
||||
});
|
||||
|
||||
test('Assimilate stranded recipients (message input)', function() {
|
||||
|
||||
ConversationView.recipients.add({
|
||||
number: '999'
|
||||
});
|
||||
|
||||
children = ConversationView.recipientsList.children;
|
||||
recipients = ConversationView.recipients;
|
||||
|
||||
// There are one recipients...
|
||||
assert.equal(recipients.length, 1);
|
||||
// And two displayed children,
|
||||
// (the recipient "avatars" and a
|
||||
// placeholder for the next entry)
|
||||
assert.equal(children.length, 2);
|
||||
|
||||
assert.ok(is.corresponding(recipients.list[0], children[0], '999'));
|
||||
assert.ok(is.placeholder(children[1]));
|
||||
|
||||
// Set text in the placeholder, as if the user has typed
|
||||
// something before jumping to the input field
|
||||
children[1].textContent = '000';
|
||||
|
||||
// Simulate input field focus/entry
|
||||
input.dispatchEvent(new CustomEvent('focus'));
|
||||
|
||||
|
||||
// There are now two recipients...
|
||||
assert.equal(recipients.length, 2);
|
||||
// And three displayed children,
|
||||
// (the recipient "avatars" and a
|
||||
// placeholder for the next entry)
|
||||
assert.equal(children.length, 3);
|
||||
|
||||
assert.ok(is.corresponding(recipients.list[0], children[0], '999'));
|
||||
assert.ok(is.corresponding(recipients.list[1], children[1], '000'));
|
||||
assert.ok(is.placeholder(children[2]));
|
||||
});
|
||||
|
||||
test('Assimilate stranded recipients (attachButton)', function() {
|
||||
|
||||
ConversationView.recipients.add({
|
||||
number: '777'
|
||||
});
|
||||
|
||||
children = ConversationView.recipientsList.children;
|
||||
recipients = ConversationView.recipients;
|
||||
|
||||
// There are one recipients...
|
||||
assert.equal(recipients.length, 1);
|
||||
// And two displayed children,
|
||||
// (the recipient "avatars" and a
|
||||
// placeholder for the next entry)
|
||||
assert.equal(children.length, 2);
|
||||
|
||||
assert.ok(is.corresponding(recipients.list[0], children[0], '777'));
|
||||
assert.ok(is.placeholder(children[1]));
|
||||
|
||||
// Set text in the placeholder, as if the user has typed
|
||||
// something before jumping to the input field
|
||||
children[1].textContent = '555';
|
||||
|
||||
// Simulate input field focus/entry
|
||||
document.getElementById('messages-attach-button').click();
|
||||
|
||||
// There are now two recipients...
|
||||
assert.equal(recipients.length, 2);
|
||||
// And three displayed children,
|
||||
// (the recipient "avatars" and a
|
||||
// placeholder for the next entry)
|
||||
assert.equal(children.length, 3);
|
||||
|
||||
assert.ok(is.corresponding(recipients.list[0], children[0], '777'));
|
||||
assert.ok(is.corresponding(recipients.list[1], children[1], '555'));
|
||||
assert.ok(is.placeholder(children[2]));
|
||||
});
|
||||
|
||||
test('Assimilate stranded recipients (sendButton)', function() {
|
||||
// To ensure the onSendClick handler will succeed:
|
||||
|
||||
// 1. Add some content to the message
|
||||
Compose.append('foo');
|
||||
|
||||
// 2. Create a recipient
|
||||
ConversationView.recipients.add({
|
||||
number: '999'
|
||||
});
|
||||
|
||||
|
||||
children = ConversationView.recipientsList.children;
|
||||
recipients = ConversationView.recipients;
|
||||
|
||||
// There are one recipients...
|
||||
assert.equal(recipients.length, 1);
|
||||
// And two displayed children,
|
||||
// (the recipient "avatars" and a
|
||||
// placeholder for the next entry)
|
||||
assert.equal(children.length, 2);
|
||||
|
||||
assert.ok(is.corresponding(recipients.list[0], children[0], '999'));
|
||||
assert.ok(is.placeholder(children[1]));
|
||||
|
||||
// Set text in the placeholder, as if the user has typed
|
||||
// something before jumping to the input field
|
||||
children[1].textContent = '000';
|
||||
|
||||
// Simulate sendButton click
|
||||
ConversationView.onSendClick();
|
||||
ConversationView.simSelectedCallback(undefined, 0);
|
||||
|
||||
// Ensure that the "unaccepted" recipient was assimilated
|
||||
// and included in the recipients list when message was sent
|
||||
sinon.assert.calledWithMatch(MessageManager.sendSMS, {
|
||||
recipients: ['999', '000'],
|
||||
content: 'foo'
|
||||
});
|
||||
});
|
||||
|
||||
/* Bug:909641 test fails on ci
|
||||
test('Assimilate stranded recipients (contactPickButton)', function(done) {
|
||||
// To ensure the recipient wrapped before picker return:
|
||||
|
||||
// 1. Add some content to the message
|
||||
Compose.append('foo');
|
||||
|
||||
// 2. Create a recipient
|
||||
ConversationView.recipients.add({
|
||||
number: '111'
|
||||
});
|
||||
|
||||
children = ConversationView.recipientsList.children;
|
||||
recipients = ConversationView.recipients;
|
||||
|
||||
// Set text in the placeholder, as if the user has typed
|
||||
// something before jumping to the input field
|
||||
children[1].textContent = '222';
|
||||
|
||||
// Simulate contact pick
|
||||
ConversationView.requestContact();
|
||||
|
||||
// Simulate the picker activity success
|
||||
setTimeout(function onsuccess() {
|
||||
ConversationView.recipients.add({
|
||||
number: '333'
|
||||
});
|
||||
|
||||
// There are now three recipients after picker activity success
|
||||
assert.equal(recipients.length, 3);
|
||||
// And four displayed children,
|
||||
// (the recipient "avatars" and a
|
||||
// placeholder for the next entry)
|
||||
assert.equal(children.length, 4);
|
||||
assert.ok(is.corresponding(recipients.list[0], children[0], '111'));
|
||||
assert.ok(is.corresponding(recipients.list[1], children[1], '222'));
|
||||
assert.ok(is.corresponding(recipients.list[2], children[2], '333'));
|
||||
assert.ok(is.placeholder(children[3]));
|
||||
done();
|
||||
});
|
||||
});
|
||||
*/
|
||||
|
||||
test('Lone ";" are not recipients', function() {
|
||||
|
||||
|
||||
children = ConversationView.recipientsList.children;
|
||||
recipients = ConversationView.recipients;
|
||||
|
||||
// Set ";" in the placeholder, as if the user has typed
|
||||
children[0].textContent = ';';
|
||||
|
||||
// Simulate input field focus/entry
|
||||
input.click();
|
||||
|
||||
// There are no recipients...
|
||||
assert.equal(recipients.length, 0);
|
||||
// And one displayed child...
|
||||
assert.equal(children.length, 1);
|
||||
|
||||
// The the ";" has been removed from the
|
||||
// recipients list
|
||||
assert.equal(
|
||||
ConversationView.recipientsList.children[0].textContent, ''
|
||||
);
|
||||
});
|
||||
|
||||
test('Taps on in-progress recipients do nothing special', function() {
|
||||
|
||||
children = ConversationView.recipientsList.children;
|
||||
recipients = ConversationView.recipients;
|
||||
|
||||
// Set text in the placeholder, as if the user has typed
|
||||
children[0].textContent = '9999999999';
|
||||
|
||||
// Simulate a tap on the placeholder
|
||||
children[0].click();
|
||||
|
||||
// There should be no recipients created
|
||||
assert.equal(recipients.length, 0);
|
||||
|
||||
// And only the placeholder is displayed
|
||||
assert.equal(children.length, 1);
|
||||
assert.isTrue(children[0].isPlaceholder);
|
||||
});
|
||||
|
||||
/**
|
||||
* The code in this test works exactly as it should
|
||||
* when run as "standalone".
|
||||
*
|
||||
* The test runner appears to cause a loss of
|
||||
* node references that are expected to exist
|
||||
|
||||
test('<delete> removes "known" contact to the left', function() {
|
||||
function backspace(target) {
|
||||
var doc = target.ownerDocument;
|
||||
var view = doc.defaultView;
|
||||
|
||||
var event = doc.createEvent('KeyboardEvent');
|
||||
|
||||
event.initKeyEvent(
|
||||
'keypress', true, true, view,
|
||||
false, false, false, false,
|
||||
KeyEvent.DOM_VK_BACK_SPACE, 0
|
||||
);
|
||||
|
||||
target.dispatchEvent(event);
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
ConversationView.recipients = new Recipients({
|
||||
outer: 'messages-to-field',
|
||||
inner: 'messages-recipients-list',
|
||||
template: new Template('messages-recipient-tmpl')
|
||||
});
|
||||
|
||||
ConversationView.recipients.add({
|
||||
source: 'contacts',
|
||||
name: 'Rick',
|
||||
number: '99999'
|
||||
});
|
||||
|
||||
children = ConversationView.recipientsList.children;
|
||||
recipients = ConversationView.recipients;
|
||||
|
||||
// Simulate backspace on the current placeholder
|
||||
backspace(children[1]);
|
||||
|
||||
// There are no recipients...
|
||||
assert.equal(recipients.length, 0);
|
||||
// And one displayed placeholder
|
||||
assert.equal(children.length, 1);
|
||||
});
|
||||
*/
|
||||
|
||||
});
|
||||
});
|
||||
7655
gaia/apps/sms/views/conversation/test/unit/conversation_test.js
Executable file
7655
gaia/apps/sms/views/conversation/test/unit/conversation_test.js
Executable file
File diff suppressed because it is too large
Load diff
1408
gaia/apps/sms/views/conversation/test/unit/information_test.js
Executable file
1408
gaia/apps/sms/views/conversation/test/unit/information_test.js
Executable file
File diff suppressed because it is too large
Load diff
154
gaia/apps/sms/views/conversation/test/unit/link_action_handler_test.js
Executable file
154
gaia/apps/sms/views/conversation/test/unit/link_action_handler_test.js
Executable file
|
|
@ -0,0 +1,154 @@
|
|||
/*global MocksHelper, MockL10n, LinkActionHandler, ActivityPicker,
|
||||
ConversationView,
|
||||
Contacts
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
require('/shared/test/unit/mocks/mock_l20n.js');
|
||||
require('/shared/test/unit/mocks/mock_option_menu.js');
|
||||
|
||||
require('/views/conversation/js/link_action_handler.js');
|
||||
require('/views/shared/js/utils.js');
|
||||
|
||||
require('/views/shared/test/unit/mock_activity_picker.js');
|
||||
require('/views/shared/test/unit/mock_contacts.js');
|
||||
require('/views/shared/test/unit/mock_moz_activity.js');
|
||||
require('/views/shared/test/unit/mock_conversation.js');
|
||||
require('/views/shared/test/unit/mock_utils.js');
|
||||
|
||||
var mocksHelperLAH = new MocksHelper([
|
||||
'ActivityPicker',
|
||||
'Contacts',
|
||||
'MozActivity',
|
||||
'OptionMenu',
|
||||
'ConversationView',
|
||||
'Utils'
|
||||
]).init();
|
||||
|
||||
suite('LinkActionHandler', function() {
|
||||
var realMozL10n, events;
|
||||
|
||||
suiteSetup(function() {
|
||||
realMozL10n = document.l10n;
|
||||
document.l10n = MockL10n;
|
||||
mocksHelperLAH.suiteSetup();
|
||||
|
||||
events = {
|
||||
phone: {
|
||||
target: {
|
||||
dataset: {
|
||||
action: 'dial-link',
|
||||
dial: '999'
|
||||
}
|
||||
}
|
||||
},
|
||||
email: {
|
||||
target: {
|
||||
dataset: {
|
||||
action: 'email-link',
|
||||
email: 'a@b.com'
|
||||
}
|
||||
}
|
||||
},
|
||||
url: {
|
||||
target: {
|
||||
dataset: {
|
||||
action: 'url-link',
|
||||
url: 'http://mozilla.com'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
document.l10n = realMozL10n;
|
||||
mocksHelperLAH.suiteTeardown();
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
|
||||
Object.keys(events).forEach(function(type) {
|
||||
events[type].preventDefault = this.sinon.spy();
|
||||
events[type].stopPropagation = this.sinon.spy();
|
||||
}, this);
|
||||
|
||||
mocksHelperLAH.setup();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
mocksHelperLAH.teardown();
|
||||
});
|
||||
|
||||
|
||||
suite('onClick', function() {
|
||||
|
||||
setup(function() {
|
||||
mocksHelperLAH.setup();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
mocksHelperLAH.teardown();
|
||||
});
|
||||
|
||||
test('dial-link: (known) delegates to promptContact ', function() {
|
||||
this.sinon.stub(ConversationView, 'promptContact');
|
||||
this.sinon.stub(Contacts, 'findByPhoneNumber')
|
||||
.callsArgWith(1, [{
|
||||
name: ['Huey'],
|
||||
tel: {
|
||||
value: '999'
|
||||
}
|
||||
}]);
|
||||
|
||||
LinkActionHandler.onClick(events.phone);
|
||||
|
||||
assert.deepEqual(ConversationView.promptContact.args[0][0], {
|
||||
number: '999',
|
||||
inMessage: true
|
||||
});
|
||||
|
||||
assert.ok(events.phone.preventDefault.called);
|
||||
assert.ok(events.phone.stopPropagation.called);
|
||||
});
|
||||
|
||||
test('dial-link: (unknown) delegates to promptContact ', function() {
|
||||
this.sinon.stub(ConversationView, 'promptContact');
|
||||
this.sinon.stub(Contacts, 'findByPhoneNumber')
|
||||
.callsArgWith(1, []);
|
||||
|
||||
LinkActionHandler.onClick(events.phone);
|
||||
|
||||
assert.deepEqual(ConversationView.promptContact.args[0][0], {
|
||||
number: '999',
|
||||
inMessage: true
|
||||
});
|
||||
|
||||
assert.ok(events.phone.preventDefault.called);
|
||||
assert.ok(events.phone.stopPropagation.called);
|
||||
});
|
||||
|
||||
test('email-link: delegates to prompt ', function() {
|
||||
this.sinon.stub(ConversationView, 'prompt');
|
||||
|
||||
LinkActionHandler.onClick(events.email);
|
||||
|
||||
assert.ok(ConversationView.prompt.called);
|
||||
assert.deepEqual(ConversationView.prompt.args[0][0], {
|
||||
email: 'a@b.com',
|
||||
inMessage: true
|
||||
});
|
||||
});
|
||||
|
||||
test('url-link: go directly to the action ', function() {
|
||||
this.sinon.stub(ActivityPicker, 'url');
|
||||
|
||||
LinkActionHandler.onClick(events.url);
|
||||
|
||||
assert.isTrue(ActivityPicker.url.called);
|
||||
assert.ok(events.url.preventDefault.called);
|
||||
assert.ok(events.url.stopPropagation.called);
|
||||
});
|
||||
});
|
||||
});
|
||||
377
gaia/apps/sms/views/conversation/test/unit/link_helper_test.js
Executable file
377
gaia/apps/sms/views/conversation/test/unit/link_helper_test.js
Executable file
|
|
@ -0,0 +1,377 @@
|
|||
/*global LinkHelper, FixturePhones */
|
||||
|
||||
/*
|
||||
Link Helper Tests
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
require('/views/conversation/js/link_helper.js');
|
||||
|
||||
require('/views/shared/js/utils.js');
|
||||
require('/views/shared/test/unit/mock_utils.js');
|
||||
|
||||
suite('link_helper_test.js', function() {
|
||||
|
||||
suite('LinkHelper URL replacements', function() {
|
||||
|
||||
function url2msg(url, addprefix) {
|
||||
var link = url;
|
||||
|
||||
if (addprefix) {
|
||||
url = 'http://' + url;
|
||||
}
|
||||
|
||||
return '<a data-url="' + url + '"' +
|
||||
' data-action="url-link" >' + link + '</a>';
|
||||
}
|
||||
|
||||
function testURLMatch(url, match, addprefix) {
|
||||
var expected = url.replace(match, url2msg(match, addprefix));
|
||||
var result = LinkHelper.searchAndLinkClickableData(url);
|
||||
assert.equal(result, expected);
|
||||
}
|
||||
|
||||
function testURLOK(url, addprefix) {
|
||||
var expected = url2msg(url, addprefix);
|
||||
var result = LinkHelper.searchAndLinkClickableData(url);
|
||||
assert.equal(result, expected);
|
||||
}
|
||||
|
||||
function testURLNOK(url) {
|
||||
// we only test url here to make sure it doesn't match
|
||||
var result = LinkHelper.searchAndLinkUrl(url);
|
||||
assert.equal(result, url);
|
||||
}
|
||||
|
||||
suite('Matches', function() {
|
||||
test('Simple URL', function() {
|
||||
testURLOK('http://www.mozilla.org/');
|
||||
});
|
||||
test('Simple URL .co.uk', function() {
|
||||
testURLOK('http://www.mozilla.co.uk/');
|
||||
});
|
||||
test('Simple short URL', function() {
|
||||
testURLOK('mzl.la', true);
|
||||
});
|
||||
test('Simple short URL with parens', function() {
|
||||
testURLMatch('(mzl.la)', 'mzl.la', true);
|
||||
});
|
||||
test('Simple short URL with trailing dot', function() {
|
||||
testURLMatch('mzl.la.', 'mzl.la', true);
|
||||
});
|
||||
test('Simple short URL with trailing dot and port', function() {
|
||||
testURLMatch('mzl.la.:527', 'mzl.la', true);
|
||||
});
|
||||
test('Simple short URL with value', function() {
|
||||
testURLOK('mzl.la/ac', true);
|
||||
});
|
||||
test('Simple URL https', function() {
|
||||
testURLOK('https://www.mozilla.org');
|
||||
});
|
||||
test('Simple URL https without www.', function() {
|
||||
testURLOK('https://mozilla.org');
|
||||
});
|
||||
test('Simple URL https with trailing dot on domain.', function() {
|
||||
testURLOK('https://mozilla.org./blah');
|
||||
});
|
||||
test('Simple URL http with port', function() {
|
||||
testURLOK('http://www.mozilla.org:8080/');
|
||||
});
|
||||
test('Simple URL http without www. and with port', function() {
|
||||
testURLOK('http://mozilla.org:8080/');
|
||||
});
|
||||
test('Simple URL with IPv4', function() {
|
||||
testURLOK('http://8.8.8.8/');
|
||||
});
|
||||
test('Simple URL with IPv4 with zero', function() {
|
||||
testURLOK('http://8.0.8.8/');
|
||||
});
|
||||
test('Simple URL rtsp', function() {
|
||||
testURLOK('rtsp://www.mozilla.org/video.mov');
|
||||
});
|
||||
test('<br>prefix', function() {
|
||||
testURLMatch('<br>mozilla.org', 'mozilla.org', true);
|
||||
});
|
||||
test('Trailing question mark', function() {
|
||||
testURLMatch('OMG mozilla.org? it is cool!', 'mozilla.org', true);
|
||||
});
|
||||
test('Trailing colon', function() {
|
||||
testURLMatch('OMG mozilla.org: it is cool!', 'mozilla.org', true);
|
||||
});
|
||||
test('Trailing semi-colon', function() {
|
||||
testURLMatch('OMG mozilla.org; it is cool!', 'mozilla.org', true);
|
||||
});
|
||||
test('Trailing bang', function() {
|
||||
testURLMatch('OMG mozilla.org! it is cool!', 'mozilla.org', true);
|
||||
});
|
||||
test('Trailing period', function() {
|
||||
testURLMatch('Check out mozilla.org.', 'mozilla.org', true);
|
||||
});
|
||||
test('Trailing comma', function() {
|
||||
testURLMatch('Check out mozilla.org, or not', 'mozilla.org', true);
|
||||
});
|
||||
test('Trailing parens', function() {
|
||||
testURLMatch('(Check out mozilla.org)', 'mozilla.org', true);
|
||||
});
|
||||
test('Trailing question mark on path', function() {
|
||||
testURLMatch('OMG mzl.la/huh? it is cool!', 'mzl.la/huh', true);
|
||||
});
|
||||
test('Trailing colon on path', function() {
|
||||
testURLMatch('OMG mzl.la/huh: it is cool!', 'mzl.la/huh', true);
|
||||
});
|
||||
test('Trailing semi-colon on path', function() {
|
||||
testURLMatch('OMG mzl.la/huh; it is cool!', 'mzl.la/huh', true);
|
||||
});
|
||||
test('Trailing bang on path', function() {
|
||||
testURLMatch('OMG mzl.la/huh! it is cool!', 'mzl.la/huh', true);
|
||||
});
|
||||
test('Trailing parens on path', function() {
|
||||
testURLMatch('(Check out mzl.la/ac)', 'mzl.la/ac', true);
|
||||
});
|
||||
test('Trailing period on path', function() {
|
||||
testURLMatch('Check out mzl.la/ac.', 'mzl.la/ac', true);
|
||||
});
|
||||
test('Trailing comma on path', function() {
|
||||
testURLMatch('Check out mzl.la/ac, or not', 'mzl.la/ac', true);
|
||||
});
|
||||
test('wiki link that has paren', function() {
|
||||
testURLOK('http://en.wikipedia.org/wiki/Arete_(disambiguation)');
|
||||
});
|
||||
test('Crazy URL from #887146', function() {
|
||||
testURLOK('http://sani.tiz.ed.com/wap/videos/files_mm/' +
|
||||
'3GPP%20MPEG4%20-%20176×144_7fps_15kpbs_AACmono_8KHz_5kbps.3gp');
|
||||
});
|
||||
test('One letter second-level', function() {
|
||||
testURLOK('http://x.com');
|
||||
});
|
||||
test('URL with phone number in the middle', function() {
|
||||
testURLOK('http://somesite.com/q,12288296666/');
|
||||
});
|
||||
test('Full width char prefix', function() {
|
||||
testURLMatch(':mozilla.org', 'mozilla.org', true);
|
||||
});
|
||||
test('non ascii prefix', function() {
|
||||
testURLMatch('àmozilla.org', 'mozilla.org', true);
|
||||
});
|
||||
test('Bug 941763: Add dot as URL prefix', function() {
|
||||
testURLMatch('.website.com', 'website.com', true);
|
||||
testURLMatch('https://.website.com', 'website.com', true);
|
||||
testURLMatch('htt://www.mozilla.org', 'mozilla.org', true);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Failures', function() {
|
||||
test('Simple invalid URL with slashes', function() {
|
||||
testURLNOK('http://www.a/b/d.com');
|
||||
});
|
||||
test('Simple invalid TLD URL', function() {
|
||||
testURLNOK('http://www.mozilla.o');
|
||||
});
|
||||
test('Simple ellipse', function() {
|
||||
testURLNOK('aaa...ccc');
|
||||
});
|
||||
test('Simple URL with username', function() {
|
||||
testURLNOK('http://user@mozilla.org');
|
||||
});
|
||||
test('Simple URL with credentials', function() {
|
||||
testURLNOK('http://user:pass@mozilla.org');
|
||||
});
|
||||
test('Simple URL with IPv6', function() {
|
||||
testURLNOK('http://[::1]/');
|
||||
});
|
||||
test('Bug 890342 1', function() {
|
||||
testURLNOK('2.74');
|
||||
});
|
||||
test('Bug 890342 2', function() {
|
||||
testURLNOK('21.72');
|
||||
});
|
||||
|
||||
test('Bug 923739', function() {
|
||||
testURLNOK('foo.zz');
|
||||
testURLNOK('foo.foo');
|
||||
testURLNOK('foo.quux');
|
||||
|
||||
testURLOK('http://whatever.stuff');
|
||||
testURLOK('https://whatever.stuff');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('LinkHelper Email replacements', function() {
|
||||
|
||||
function email2msg(email) {
|
||||
return '<a data-email="' + email + '"' +
|
||||
' data-action="email-link">' + email + '</a>';
|
||||
}
|
||||
|
||||
function testEmailOK(email) {
|
||||
var exp = email2msg(email);
|
||||
var res = LinkHelper.searchAndLinkClickableData(email);
|
||||
assert.equal(res, exp);
|
||||
}
|
||||
|
||||
function testEmailNOK(email) {
|
||||
var res = LinkHelper.searchAndLinkEmail(email);
|
||||
assert.equal(res, email);
|
||||
}
|
||||
|
||||
test('Simple email', function() {
|
||||
testEmailOK('user@hostname.tld');
|
||||
});
|
||||
test('Simple email with alias', function() {
|
||||
testEmailOK('user+alias@hostname.tld');
|
||||
});
|
||||
test('Simple email with subdomain', function() {
|
||||
testEmailOK('user@cs.subdhostname.tld');
|
||||
});
|
||||
test('Invalid email no hostname', function() {
|
||||
testEmailNOK('user@');
|
||||
});
|
||||
test('Invalid email no user', function() {
|
||||
testEmailNOK('@hostname.tld');
|
||||
});
|
||||
});
|
||||
|
||||
suite('LinkHelper Phone replacements', function() {
|
||||
|
||||
function testPhoneMatch(text, match) {
|
||||
var expected = text.replace(match, phone2msg(match));
|
||||
var result = LinkHelper.searchAndLinkClickableData(text);
|
||||
assert.equal(result, expected);
|
||||
}
|
||||
|
||||
function phone2msg(phone) {
|
||||
return '<a dir="ltr" data-dial="' + phone + '"' +
|
||||
' data-action="dial-link">' + phone + '</a>';
|
||||
}
|
||||
|
||||
function testPhoneOK(phone) {
|
||||
var exp = phone2msg(phone);
|
||||
var res = LinkHelper.searchAndLinkClickableData(phone);
|
||||
assert.equal(res, exp);
|
||||
}
|
||||
|
||||
function testPhoneNOK(phone) {
|
||||
var res = LinkHelper.searchAndLinkPhone(phone);
|
||||
assert.equal(res, phone);
|
||||
}
|
||||
|
||||
suite('Matches', function() {
|
||||
test('Simple french mobile nat.', function() {
|
||||
testPhoneOK('0612345678');
|
||||
});
|
||||
|
||||
test('Simple french mobile intl', function() {
|
||||
testPhoneOK('+33612345678');
|
||||
});
|
||||
|
||||
test('Phone from #887737', function() {
|
||||
testPhoneOK('+5511 98907-6047');
|
||||
});
|
||||
|
||||
test('word before trailing number', function() {
|
||||
testPhoneMatch('Test1 600123123', '600123123');
|
||||
});
|
||||
test('word after preceding number', function() {
|
||||
testPhoneMatch('600123123 1Test', '600123123');
|
||||
});
|
||||
[
|
||||
// Bug 900413 - detect 5 digit short codes
|
||||
'24242',
|
||||
'10086',
|
||||
'+1234',
|
||||
// bug 941976 - 7 digit 4 digit with symbols should still work
|
||||
'123-1234 1234'
|
||||
].forEach(function(phone) {
|
||||
test(phone, testPhoneOK.bind(null, phone));
|
||||
});
|
||||
});
|
||||
|
||||
suite('Failures', function() {
|
||||
[
|
||||
'0.34',
|
||||
'1200',
|
||||
'5000',
|
||||
'0.122',
|
||||
'0921',
|
||||
// Bug 900413 - 5 digit codes with separators not allowed
|
||||
'1-10-13',
|
||||
'12-3-13',
|
||||
'1-800-A',
|
||||
'1(234)5',
|
||||
'1234..5',
|
||||
'1 2 3 45',
|
||||
'1\t23\t45',
|
||||
'12+34'
|
||||
].forEach(function(phone) {
|
||||
test(phone, testPhoneNOK.bind(null, phone));
|
||||
});
|
||||
});
|
||||
|
||||
suite('Varied cases', function() {
|
||||
FixturePhones.forEach(function(fixture) {
|
||||
if (fixture.isTestable) {
|
||||
suite(fixture.title, function() {
|
||||
fixture.values.forEach(function(value) {
|
||||
test(value, function() {
|
||||
testPhoneOK(value);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
suite('Tricky Problems', function() {
|
||||
test('Two 9 digit numbers separated by space (#892480)', function() {
|
||||
var test = '123456789 987654321';
|
||||
var expected = test.split(' ').map(phone2msg).join(' ');
|
||||
var result = LinkHelper.searchAndLinkClickableData(test);
|
||||
assert.equal(result, expected);
|
||||
});
|
||||
test('Two 6 digit numbers separated by newline (#892480)', function() {
|
||||
var test = '222333\n333222';
|
||||
var expected = test.split('\n').map(phone2msg).join('\n');
|
||||
var result = LinkHelper.searchAndLinkClickableData(test);
|
||||
assert.equal(result, expected);
|
||||
});
|
||||
test('7 digit and 9 digit (#941976)', function() {
|
||||
var test = '1234567 987654321';
|
||||
var expected = test.split(' ').map(phone2msg).join(' ');
|
||||
var result = LinkHelper.searchAndLinkClickableData(test);
|
||||
assert.equal(result, expected);
|
||||
});
|
||||
test('7 digit and 5 digit (#941976)', function() {
|
||||
var test = '1234567 12345';
|
||||
var expected = test.split(' ').map(phone2msg).join(' ');
|
||||
var result = LinkHelper.searchAndLinkClickableData(test);
|
||||
assert.equal(result, expected);
|
||||
});
|
||||
test('7 digit and 5 digit(with plus) (#941976)', function() {
|
||||
var test = '1234567 +1234';
|
||||
var expected = test.split(' ').map(phone2msg).join(' ');
|
||||
var result = LinkHelper.searchAndLinkClickableData(test);
|
||||
assert.equal(result, expected);
|
||||
});
|
||||
test('7 digit and 4 digit (#941976)', function() {
|
||||
testPhoneMatch('1234567 1234', '1234567');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
suite('Multiple in the same string', function() {
|
||||
test('Hits every case', function() {
|
||||
var test = 'Hey, check out http://stackoverflow.com/q/12882966/ and ' +
|
||||
'call me at +18155551212 or (e-mail user@hostname.tld)';
|
||||
var expected = 'Hey, check out ' +
|
||||
'<a data-url="http://stackoverflow.com/q/12882966/" ' +
|
||||
'data-action="url-link" >http://stackoverflow.com/q/12882966/</a>' +
|
||||
' and call me at ' +
|
||||
'<a dir="ltr" data-dial="+18155551212" data-action="dial-link">' +
|
||||
'+18155551212</a> or (e-mail <a data-email="user@hostname.tld"' +
|
||||
' data-action="email-link">user@hostname.tld</a>)';
|
||||
assert.equal(LinkHelper.searchAndLinkClickableData(test), expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
1208
gaia/apps/sms/views/conversation/test/unit/recipients_test.js
Executable file
1208
gaia/apps/sms/views/conversation/test/unit/recipients_test.js
Executable file
File diff suppressed because it is too large
Load diff
154
gaia/apps/sms/views/conversation/test/unit/startup_test.js
Executable file
154
gaia/apps/sms/views/conversation/test/unit/startup_test.js
Executable file
|
|
@ -0,0 +1,154 @@
|
|||
/*global App,
|
||||
ConversationView,
|
||||
Information,
|
||||
InterInstanceEventDispatcher,
|
||||
LazyLoader,
|
||||
LocalizationHelper,
|
||||
MessageManager,
|
||||
MessagingClient,
|
||||
MozMobileConnectionsClient,
|
||||
MocksHelper,
|
||||
Navigation,
|
||||
Settings,
|
||||
Startup,
|
||||
TimeHeaders,
|
||||
Utils
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
require('/views/shared/test/unit/mock_time_headers.js');
|
||||
require('/views/shared/test/unit/mock_navigation.js');
|
||||
require('/views/shared/test/unit/mock_settings.js');
|
||||
require('/views/shared/test/unit/mock_inter_instance_event_dispatcher.js');
|
||||
require('/views/shared/test/unit/mock_conversation.js');
|
||||
require('/views/shared/test/unit/mock_information.js');
|
||||
require('/views/shared/test/unit/mock_localization_helper.js');
|
||||
require('/views/shared/test/unit/mock_app.js');
|
||||
require('/shared/test/unit/mocks/mock_lazy_loader.js');
|
||||
require('/services/test/unit/messaging/mock_messaging_client.js');
|
||||
require('/services/test/unit/moz_mobile_connections/' +
|
||||
'mock_moz_mobile_connections_client.js');
|
||||
require('/services/test/unit/mock_message_manager.js');
|
||||
require('/views/shared/js/utils.js');
|
||||
require('/views/shared/test/unit/mock_utils.js');
|
||||
|
||||
require('/views/conversation/js/startup.js');
|
||||
|
||||
var MocksHelperForInboxStartup = new MocksHelper([
|
||||
'App',
|
||||
'ConversationView',
|
||||
'Information',
|
||||
'InterInstanceEventDispatcher',
|
||||
'LazyLoader',
|
||||
'LocalizationHelper',
|
||||
'MessageManager',
|
||||
'MessagingClient',
|
||||
'MozMobileConnectionsClient',
|
||||
'Navigation',
|
||||
'TimeHeaders',
|
||||
'Settings',
|
||||
'Utils'
|
||||
]).init();
|
||||
|
||||
suite('ConversationView Startup,', function() {
|
||||
MocksHelperForInboxStartup.attachTestHelpers();
|
||||
|
||||
setup(function() {
|
||||
this.sinon.spy(MessageManager, 'init');
|
||||
this.sinon.spy(Navigation, 'init');
|
||||
this.sinon.spy(ConversationView, 'init');
|
||||
this.sinon.spy(LocalizationHelper, 'init');
|
||||
this.sinon.spy(Information, 'initDefaultViews');
|
||||
this.sinon.spy(TimeHeaders, 'init');
|
||||
this.sinon.spy(Settings, 'init');
|
||||
this.sinon.spy(Navigation, 'setReady');
|
||||
this.sinon.spy(InterInstanceEventDispatcher, 'connect');
|
||||
this.sinon.stub(ConversationView, 'once');
|
||||
this.sinon.stub(LazyLoader, 'load').returns(Promise.resolve());
|
||||
this.sinon.stub(MessageManager, 'getThreads');
|
||||
this.sinon.stub(Navigation, 'isDefaultPanel');
|
||||
this.sinon.spy(MessagingClient, 'init');
|
||||
this.sinon.spy(MozMobileConnectionsClient, 'init');
|
||||
this.sinon.spy(Utils, 'initializeShimHost');
|
||||
});
|
||||
|
||||
suite('In default panel,', function() {
|
||||
setup(function() {
|
||||
Navigation.isDefaultPanel.returns(true);
|
||||
|
||||
Startup.init();
|
||||
});
|
||||
|
||||
test('correctly initializes dependencies', function(done) {
|
||||
sinon.assert.calledWith(Utils.initializeShimHost, App.instanceId);
|
||||
sinon.assert.calledOnce(MessageManager.init);
|
||||
sinon.assert.calledOnce(ConversationView.init);
|
||||
|
||||
// Workaround to deal with temporary shim code.
|
||||
MessageManager.getThreads.lastCall.args[0].done();
|
||||
Promise.resolve().then(
|
||||
() => sinon.assert.calledOnce(Navigation.init)
|
||||
).then(done, done);
|
||||
});
|
||||
|
||||
test('starts non-critical initializations only once view is visually ready',
|
||||
function() {
|
||||
// Lazy loading is not started yet and headers are not processed either.
|
||||
sinon.assert.notCalled(LazyLoader.load);
|
||||
|
||||
ConversationView.once.withArgs('visually-loaded').yield();
|
||||
|
||||
sinon.assert.calledOnce(LazyLoader.load);
|
||||
});
|
||||
|
||||
test('correctly initializes lazy dependencies', function(done) {
|
||||
ConversationView.once.withArgs('visually-loaded').yield();
|
||||
|
||||
LazyLoader.load.lastCall.returnValue.then(() => {
|
||||
sinon.assert.calledOnce(LocalizationHelper.init);
|
||||
sinon.assert.calledOnce(TimeHeaders.init);
|
||||
sinon.assert.calledOnce(Settings.init);
|
||||
sinon.assert.calledOnce(Navigation.setReady);
|
||||
sinon.assert.calledOnce(Information.initDefaultViews);
|
||||
sinon.assert.calledOnce(InterInstanceEventDispatcher.connect);
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('In a subpanel,', function() {
|
||||
setup(function() {
|
||||
Navigation.isDefaultPanel.returns(false);
|
||||
|
||||
Startup.init();
|
||||
});
|
||||
|
||||
test('correctly initializes dependencies', function(done) {
|
||||
sinon.assert.calledWith(Utils.initializeShimHost, App.instanceId);
|
||||
sinon.assert.calledOnce(MessageManager.init);
|
||||
sinon.assert.calledOnce(ConversationView.init);
|
||||
|
||||
// Workaround to deal with temporary shim code.
|
||||
MessageManager.getThreads.lastCall.args[0].done();
|
||||
Promise.resolve().then(
|
||||
() => sinon.assert.calledOnce(Navigation.init)
|
||||
).then(done, done);
|
||||
});
|
||||
|
||||
test('starts non-critical initializations right away', function() {
|
||||
sinon.assert.notCalled(ConversationView.once);
|
||||
sinon.assert.calledOnce(LazyLoader.load);
|
||||
});
|
||||
|
||||
test('correctly initializes lazy dependencies', function(done) {
|
||||
LazyLoader.load.lastCall.returnValue.then(() => {
|
||||
sinon.assert.calledOnce(LocalizationHelper.init);
|
||||
sinon.assert.calledOnce(TimeHeaders.init);
|
||||
sinon.assert.calledOnce(Settings.init);
|
||||
sinon.assert.calledOnce(Navigation.setReady);
|
||||
sinon.assert.calledOnce(Information.initDefaultViews);
|
||||
sinon.assert.calledOnce(InterInstanceEventDispatcher.connect);
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
367
gaia/apps/sms/views/conversation/test/unit/subject_composer_test.js
Executable file
367
gaia/apps/sms/views/conversation/test/unit/subject_composer_test.js
Executable file
|
|
@ -0,0 +1,367 @@
|
|||
/*global loadBodyHTML,
|
||||
FocusEvent,
|
||||
InputEvent,
|
||||
KeyEvent,
|
||||
KeyboardEvent,
|
||||
SubjectComposer
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
require('/shared/js/event_dispatcher.js');
|
||||
|
||||
require('/views/conversation/js/subject_composer.js');
|
||||
|
||||
suite('SubjectComposer >', function() {
|
||||
var node, subjectComposer, input;
|
||||
|
||||
setup(function() {
|
||||
loadBodyHTML('/index.html');
|
||||
|
||||
node = document.querySelector('.js-subject-composer');
|
||||
subjectComposer = new SubjectComposer(node);
|
||||
input = node.querySelector('.subject-composer-input');
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
subjectComposer.reset();
|
||||
});
|
||||
|
||||
test('correctly instantiate', function() {
|
||||
assert.throws(
|
||||
() => new SubjectComposer(),
|
||||
'Subject node is required'
|
||||
);
|
||||
|
||||
assert.ok(new SubjectComposer(node));
|
||||
assert.isTrue(
|
||||
node.classList.contains('hide'),
|
||||
'Should be hidden by default'
|
||||
);
|
||||
});
|
||||
|
||||
suite('visibility >', function() {
|
||||
teardown(function() {
|
||||
subjectComposer.reset();
|
||||
});
|
||||
|
||||
test('show and hide work correctly', function() {
|
||||
var visibilityChangeStub = sinon.stub();
|
||||
subjectComposer.on('visibility-change', visibilityChangeStub);
|
||||
|
||||
subjectComposer.show();
|
||||
|
||||
assert.isFalse(node.classList.contains('hide'));
|
||||
sinon.assert.called(visibilityChangeStub);
|
||||
|
||||
subjectComposer.hide();
|
||||
|
||||
assert.isTrue(node.classList.contains('hide'));
|
||||
sinon.assert.called(visibilityChangeStub);
|
||||
});
|
||||
|
||||
test('keeps value between show and hide', function() {
|
||||
subjectComposer.show();
|
||||
subjectComposer.setValue('my value');
|
||||
|
||||
assert.equal(subjectComposer.getValue(), 'my value');
|
||||
assert.equal(input.textContent, 'my value');
|
||||
|
||||
// Hide subject and show again
|
||||
subjectComposer.hide();
|
||||
subjectComposer.show();
|
||||
|
||||
assert.equal(subjectComposer.getValue(), 'my value');
|
||||
assert.equal(input.textContent, 'my value');
|
||||
});
|
||||
|
||||
test('isVisible tracks visibility correctly', function() {
|
||||
assert.isFalse(subjectComposer.isVisible());
|
||||
|
||||
subjectComposer.show();
|
||||
assert.isTrue(subjectComposer.isVisible());
|
||||
|
||||
subjectComposer.hide();
|
||||
assert.isFalse(subjectComposer.isVisible());
|
||||
});
|
||||
});
|
||||
|
||||
suite('value management >', function() {
|
||||
setup(function() {
|
||||
subjectComposer.show();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
subjectComposer.reset();
|
||||
});
|
||||
|
||||
test('correctly sets and gets value', function() {
|
||||
assert.throws(
|
||||
() => subjectComposer.setValue(), 'Value should be a valid string'
|
||||
);
|
||||
assert.equal(subjectComposer.getValue(), '');
|
||||
|
||||
subjectComposer.setValue('test');
|
||||
|
||||
assert.equal(subjectComposer.getValue(), 'test');
|
||||
assert.equal(input.textContent, 'test');
|
||||
|
||||
input.innerHTML = 'test#2<br><br>';
|
||||
input.dispatchEvent(new InputEvent('input'));
|
||||
|
||||
assert.equal(subjectComposer.getValue(), 'test#2');
|
||||
assert.equal(input.textContent, 'test#2');
|
||||
|
||||
subjectComposer.setValue('test#3');
|
||||
|
||||
assert.equal(subjectComposer.getValue(), 'test#3');
|
||||
assert.equal(input.textContent, 'test#3');
|
||||
|
||||
input.innerHTML = '<br>test#4<br><br>';
|
||||
input.dispatchEvent(new InputEvent('input'));
|
||||
|
||||
assert.equal(subjectComposer.getValue(), 'test#4');
|
||||
assert.equal(input.textContent, 'test#4');
|
||||
});
|
||||
|
||||
test('correctly pre-process value', function() {
|
||||
var content = 'Line\u00A01<br>\n Line 2<br><br><br><br>\nLine\n 3<br>';
|
||||
|
||||
subjectComposer.setValue(content);
|
||||
|
||||
assert.equal(
|
||||
subjectComposer.getValue(),
|
||||
'Line 1<br> Line 2<br><br><br><br> Line 3<br>'
|
||||
);
|
||||
|
||||
subjectComposer.setValue('');
|
||||
input.innerHTML = content;
|
||||
input.dispatchEvent(new InputEvent('input'));
|
||||
|
||||
assert.equal(subjectComposer.getValue(), 'Line 1 Line 2 Line 3');
|
||||
|
||||
subjectComposer.setValue('');
|
||||
input.textContent = content;
|
||||
input.dispatchEvent(new InputEvent('input'));
|
||||
|
||||
assert.equal(
|
||||
subjectComposer.getValue(),
|
||||
'Line 1<br> Line 2<br><br><br><br> Line 3<br>'
|
||||
);
|
||||
});
|
||||
|
||||
test('fires "change" only if value actually changes via setValue',
|
||||
function() {
|
||||
var onChange = sinon.stub();
|
||||
subjectComposer.on('change', onChange);
|
||||
|
||||
subjectComposer.setValue('test');
|
||||
sinon.assert.calledOnce(onChange);
|
||||
|
||||
subjectComposer.setValue('test');
|
||||
sinon.assert.calledOnce(onChange);
|
||||
|
||||
subjectComposer.setValue('test#2');
|
||||
sinon.assert.calledTwice(onChange);
|
||||
});
|
||||
|
||||
test('fires "change" only if value actually changes via innerHTML',
|
||||
function() {
|
||||
var onChange = sinon.stub();
|
||||
subjectComposer.on('change', onChange);
|
||||
|
||||
input.innerHTML = 'test';
|
||||
input.dispatchEvent(new InputEvent('input'));
|
||||
sinon.assert.calledOnce(onChange);
|
||||
|
||||
input.innerHTML = 'test';
|
||||
input.dispatchEvent(new InputEvent('input'));
|
||||
sinon.assert.calledOnce(onChange);
|
||||
|
||||
input.innerHTML = 'test#2';
|
||||
input.dispatchEvent(new InputEvent('input'));
|
||||
sinon.assert.calledTwice(onChange);
|
||||
});
|
||||
|
||||
test('correctly manages placeholder', function() {
|
||||
var placeholder = node.querySelector(
|
||||
'.subject-composer-placeholder'
|
||||
);
|
||||
|
||||
assert.isNotNull(placeholder, 'Placeholder should be presented');
|
||||
assert.equal(
|
||||
placeholder.getAttribute('data-l10n-id'),
|
||||
'messagesSubjectInput_placeholder'
|
||||
);
|
||||
|
||||
subjectComposer.setValue('a');
|
||||
assert.isNull(
|
||||
node.querySelector('.subject-composer-placeholder'),
|
||||
'Placeholder should be removed'
|
||||
);
|
||||
|
||||
input.textContent = '';
|
||||
input.dispatchEvent(new InputEvent('input'));
|
||||
assert.isNotNull(
|
||||
node.querySelector('.subject-composer-placeholder'),
|
||||
'Placeholder should be presented'
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
suite('Backspace key handling >', function() {
|
||||
var downEvent, upEvent, backspace;
|
||||
|
||||
setup(function() {
|
||||
downEvent = new KeyboardEvent('keydown', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
keyCode: KeyEvent.DOM_VK_BACK_SPACE
|
||||
});
|
||||
|
||||
upEvent = new KeyboardEvent('keyup', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
keyCode: KeyEvent.DOM_VK_BACK_SPACE
|
||||
});
|
||||
|
||||
backspace = function() {
|
||||
input.dispatchEvent(downEvent);
|
||||
input.dispatchEvent(upEvent);
|
||||
};
|
||||
|
||||
subjectComposer.show();
|
||||
subjectComposer.setValue('Howdy!');
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
subjectComposer.reset();
|
||||
});
|
||||
|
||||
test('<delete> in empty subject hides field', function() {
|
||||
// 1. Assert the correct state condition updates have occurred,
|
||||
// as described in step 1
|
||||
assert.isTrue(subjectComposer.isVisible());
|
||||
|
||||
// 2. To simulate the user "deleting" the subject,
|
||||
// set the value to an empty string.
|
||||
input.textContent = '';
|
||||
input.dispatchEvent(new InputEvent('input'));
|
||||
|
||||
// 3. Simulate backspace on the subject field
|
||||
backspace();
|
||||
|
||||
// 4. Confirm that the state of the compose
|
||||
// area has updated properly.
|
||||
assert.isFalse(subjectComposer.isVisible());
|
||||
});
|
||||
|
||||
test('<delete> in non-empty subject does not hide field', function() {
|
||||
// 1. Assert the correct state condition updates have occurred,
|
||||
// as described in step 1
|
||||
assert.isTrue(subjectComposer.isVisible());
|
||||
|
||||
// 2. Simulate backspace on the subject field
|
||||
backspace();
|
||||
|
||||
// 3. Confirm that the state of the compose area not changed.
|
||||
assert.isTrue(subjectComposer.isVisible());
|
||||
});
|
||||
|
||||
test('<delete> holding subject does not hide field', function() {
|
||||
// 1. Assert the correct state condition updates have occurred,
|
||||
// as described in step 1
|
||||
assert.isTrue(subjectComposer.isVisible());
|
||||
|
||||
// 2. Simulate holding backspace on the subject field
|
||||
for (var i = 0; i < 5; i++) {
|
||||
input.dispatchEvent(downEvent);
|
||||
}
|
||||
|
||||
// 3. This is the "release" from a holding state
|
||||
input.dispatchEvent(upEvent);
|
||||
|
||||
// 4. Confirm that the state of the compose area not changed.
|
||||
assert.isTrue(subjectComposer.isVisible());
|
||||
});
|
||||
|
||||
test('<delete> holding subject, release and tap hides field', function() {
|
||||
// 1. Assert the correct state condition updates have occurred,
|
||||
// as described in step 1
|
||||
assert.isTrue(subjectComposer.isVisible());
|
||||
|
||||
// 2. Simulate holding backspace on the subject field
|
||||
for (var i = 0; i < 5; i++) {
|
||||
input.dispatchEvent(downEvent);
|
||||
}
|
||||
|
||||
// 3. This is the "release" from a holding state
|
||||
input.dispatchEvent(upEvent);
|
||||
|
||||
// 4. To simulate the user "deleting" the subject,
|
||||
// set the value to an empty string.
|
||||
input.textContent = '';
|
||||
input.dispatchEvent(new InputEvent('input'));
|
||||
|
||||
// 5. Simulate backspace on the subject field.
|
||||
backspace();
|
||||
|
||||
// 6. Confirm that the state of the compose area not changed.
|
||||
assert.isFalse(subjectComposer.isVisible());
|
||||
});
|
||||
});
|
||||
|
||||
suite('Return key handling >', function() {
|
||||
test('Return key should be ignored at key down', function() {
|
||||
subjectComposer.show();
|
||||
subjectComposer.setValue('Howdy!');
|
||||
|
||||
var event = new KeyboardEvent('keydown', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
keyCode: KeyEvent.DOM_VK_RETURN
|
||||
});
|
||||
this.sinon.spy(event, 'stopPropagation');
|
||||
input.dispatchEvent(event);
|
||||
|
||||
assert.ok(event.defaultPrevented);
|
||||
sinon.assert.called(event.stopPropagation);
|
||||
});
|
||||
});
|
||||
|
||||
test('correctly resets subject', function() {
|
||||
var onChangeStub = sinon.stub();
|
||||
|
||||
subjectComposer.show();
|
||||
|
||||
assert.equal(subjectComposer.getValue(), '');
|
||||
|
||||
subjectComposer.setValue('test');
|
||||
|
||||
assert.equal(subjectComposer.getValue(), 'test');
|
||||
assert.equal(input.textContent, 'test');
|
||||
|
||||
subjectComposer.on('visibility-change', onChangeStub);
|
||||
subjectComposer.on('change', onChangeStub);
|
||||
subjectComposer.reset();
|
||||
|
||||
assert.equal(subjectComposer.getValue(), '');
|
||||
assert.equal(input.textContent, '');
|
||||
assert.isFalse(subjectComposer.isVisible());
|
||||
// Visibility change and change events should not be fired on reset
|
||||
sinon.assert.notCalled(onChangeStub);
|
||||
});
|
||||
|
||||
test('fires "focus" event', function() {
|
||||
subjectComposer.show();
|
||||
|
||||
var onFocusStub = sinon.stub();
|
||||
|
||||
subjectComposer.on('focus', onFocusStub);
|
||||
|
||||
input.dispatchEvent(new FocusEvent('focus'));
|
||||
|
||||
sinon.assert.calledOnce(onFocusStub);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue