diff --git a/karma.conf.js b/karma.conf.js index 4f3e5f111..226ea90a9 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -47,6 +47,7 @@ module.exports = function(config) { { pattern: "src/plugins/bookmark-views/tests/bookmarks.js", type: 'module' }, { pattern: "src/plugins/chatview/tests/chatbox.js", type: 'module' }, { pattern: "src/plugins/chatview/tests/me-messages.js", type: 'module' }, + { pattern: "src/plugins/chatview/tests/message-images.js", type: 'module' }, { pattern: "src/plugins/chatview/tests/messages.js", type: 'module' }, { pattern: "src/plugins/chatview/tests/receipts.js", type: 'module' }, { pattern: "src/plugins/chatview/tests/spoilers.js", type: 'module' }, diff --git a/src/headless/core.js b/src/headless/core.js index 935c25d1c..cf98d2a99 100644 --- a/src/headless/core.js +++ b/src/headless/core.js @@ -394,7 +394,7 @@ export const api = _converse.api = { * Get the value of a particular user setting. * @method _converse.api.user.settings.get * @param {String} key - The setting name - * @param {*} fallback - An optional fallback value if the user setting is undefined + * @param {*} [fallback] - An optional fallback value if the user setting is undefined * @returns {Promise} Promise which resolves with the value of the particular configuration setting. * @example _converse.api.user.settings.get("foo"); */ diff --git a/src/plugins/chatview/tests/message-images.js b/src/plugins/chatview/tests/message-images.js new file mode 100644 index 000000000..b43ab7029 --- /dev/null +++ b/src/plugins/chatview/tests/message-images.js @@ -0,0 +1,141 @@ +/*global mock, converse */ + +const { sizzle, u } = converse.env; + +describe("A Chat Message", function () { + + it("will render images from their URLs", mock.initConverse(['chatBoxesFetched'], {}, async function (done, _converse) { + await mock.waitForRoster(_converse, 'current'); + const base_url = 'https://conversejs.org'; + let message = base_url+"/logo/conversejs-filled.svg"; + const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; + await mock.openChatBoxFor(_converse, contact_jid); + const view = _converse.api.chatviews.get(contact_jid); + spyOn(view.model, 'sendMessage').and.callThrough(); + await mock.sendMessage(view, message); + await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-image').length, 1000) + expect(view.model.sendMessage).toHaveBeenCalled(); + let msg = sizzle('.chat-content .chat-msg:last .chat-msg__text').pop(); + expect(msg.innerHTML.replace(//g, '').trim()).toEqual( + ``+ + ``+ + ``); + + message += "?param1=val1¶m2=val2"; + await mock.sendMessage(view, message); + await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-image').length === 2, 1000); + expect(view.model.sendMessage).toHaveBeenCalled(); + msg = sizzle('.chat-content .chat-msg:last .chat-msg__text').pop(); + expect(msg.innerHTML.replace(//g, '').trim()).toEqual( + ``+ + ``+ + ``); + + // Test now with two images in one message + message += ' hello world '+base_url+"/logo/conversejs-filled.svg"; + await mock.sendMessage(view, message); + await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-image').length === 4, 1000); + expect(view.model.sendMessage).toHaveBeenCalled(); + msg = sizzle('.chat-content .chat-msg:last .chat-msg__text').pop(); + expect(msg.textContent.trim()).toEqual('hello world'); + expect(msg.querySelectorAll('img.chat-image').length).toEqual(2); + + // Configured image URLs are rendered + _converse.api.settings.set('image_urls_regex', /^https?:\/\/(?:www.)?(?:imgur\.com\/\w{7})\/?$/i); + message = 'https://imgur.com/oxymPax'; + await mock.sendMessage(view, message); + await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-image').length === 5, 1000); + expect(view.querySelectorAll('.chat-content .chat-image').length).toBe(5); + + // Check that the Imgur URL gets a .png attached to make it render + await u.waitUntil(() => Array.from(view.querySelectorAll('.chat-content .chat-image')).pop().src.endsWith('png'), 1000); + done(); + })); + + it("will render images from approved URLs only", + mock.initConverse( + ['chatBoxesFetched'], {'show_images_inline': ['conversejs.org']}, + async function (done, _converse) { + + await mock.waitForRoster(_converse, 'current'); + const base_url = 'https://conversejs.org'; + let message = 'https://imgur.com/oxymPax.png'; + const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; + await mock.openChatBoxFor(_converse, contact_jid); + const view = _converse.api.chatviews.get(contact_jid); + spyOn(view.model, 'sendMessage').and.callThrough(); + await mock.sendMessage(view, message); + await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-msg').length === 1); + + message = base_url+"/logo/conversejs-filled.svg"; + await mock.sendMessage(view, message); + await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-msg').length === 2, 1000); + await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-image').length === 1, 1000) + expect(view.querySelectorAll('.chat-content .chat-image').length).toBe(1); + done(); + })); + + it("will fall back to rendering images as URLs", + mock.initConverse( + ['chatBoxesFetched'], {}, + async function (done, _converse) { + + await mock.waitForRoster(_converse, 'current'); + const base_url = 'https://conversejs.org'; + const message = base_url+"/logo/non-existing.svg"; + const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; + await mock.openChatBoxFor(_converse, contact_jid); + const view = _converse.api.chatviews.get(contact_jid); + spyOn(view.model, 'sendMessage').and.callThrough(); + await mock.sendMessage(view, message); + await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-image').length, 1000) + expect(view.model.sendMessage).toHaveBeenCalled(); + const msg = sizzle('.chat-content .chat-msg:last .chat-msg__text').pop(); + await u.waitUntil(() => msg.innerHTML.replace(//g, '').trim() == + `https://conversejs.org/logo/non-existing.svg`, 1000); + done(); + })); + + it("will fall back to rendering URLs that match image_urls_regex as URLs", + mock.initConverse( + ['rosterGroupsFetched', 'chatBoxesFetched'], { + 'show_images_inline': ['twimg.com'], + 'image_urls_regex': /^https?:\/\/(www.)?(pbs\.twimg\.com\/)/i + }, + async function (done, _converse) { + + await mock.waitForRoster(_converse, 'current'); + const message = "https://pbs.twimg.com/media/string?format=jpg&name=small"; + const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; + await mock.openChatBoxFor(_converse, contact_jid); + const view = _converse.api.chatviews.get(contact_jid); + spyOn(view.model, 'sendMessage').and.callThrough(); + await mock.sendMessage(view, message); + expect(view.model.sendMessage).toHaveBeenCalled(); + await u.waitUntil(() => view.querySelector('.chat-content .chat-msg'), 1000); + const msg = view.querySelector('.chat-content .chat-msg .chat-msg__text'); + await u.waitUntil(() => msg.innerHTML.replace(//g, '').trim() == + `https://pbs.twimg.com/media/string?format=jpg&name=small`, 1000); + done(); + })); + + it("will respect a changed setting when re-rendered", + mock.initConverse( + ['chatBoxesFetched'], {'show_images_inline': true}, + async function (done, _converse) { + + const { api } = _converse; + await mock.waitForRoster(_converse, 'current'); + const message = 'https://imgur.com/oxymPax.png'; + const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; + await mock.openChatBoxFor(_converse, contact_jid); + const view = _converse.api.chatviews.get(contact_jid); + await mock.sendMessage(view, message); + await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-msg').length === 1); + api.settings.set('show_images_inline', false); + view.querySelector('converse-chat-message-body').requestUpdate(); + await u.waitUntil(() => view.querySelector('.chat-content .chat-image') === null); + expect(true).toBe(true); + done(); + })); +}); diff --git a/src/plugins/chatview/tests/messages.js b/src/plugins/chatview/tests/messages.js index 0490e5c7e..6ad594713 100644 --- a/src/plugins/chatview/tests/messages.js +++ b/src/plugins/chatview/tests/messages.js @@ -1,7 +1,6 @@ -/*global mock, converse, _ */ +/*global mock, converse */ -const { Promise, Strophe, $msg, dayjs, sizzle } = converse.env; -const u = converse.env.utils; +const { Promise, Strophe, $msg, dayjs, sizzle, u } = converse.env; describe("A Chat Message", function () { @@ -614,122 +613,6 @@ describe("A Chat Message", function () { done(); })); - it("will render images from their URLs", mock.initConverse(['chatBoxesFetched'], {}, async function (done, _converse) { - await mock.waitForRoster(_converse, 'current'); - const base_url = 'https://conversejs.org'; - let message = base_url+"/logo/conversejs-filled.svg"; - const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; - await mock.openChatBoxFor(_converse, contact_jid); - const view = _converse.api.chatviews.get(contact_jid); - spyOn(view.model, 'sendMessage').and.callThrough(); - await mock.sendMessage(view, message); - await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-image').length, 1000) - expect(view.model.sendMessage).toHaveBeenCalled(); - let msg = sizzle('.chat-content .chat-msg:last .chat-msg__text').pop(); - expect(msg.innerHTML.replace(//g, '').trim()).toEqual( - ``+ - ``+ - ``); - - message += "?param1=val1¶m2=val2"; - await mock.sendMessage(view, message); - await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-image').length === 2, 1000); - expect(view.model.sendMessage).toHaveBeenCalled(); - msg = sizzle('.chat-content .chat-msg:last .chat-msg__text').pop(); - expect(msg.innerHTML.replace(//g, '').trim()).toEqual( - ``+ - ``+ - ``); - - // Test now with two images in one message - message += ' hello world '+base_url+"/logo/conversejs-filled.svg"; - await mock.sendMessage(view, message); - await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-image').length === 4, 1000); - expect(view.model.sendMessage).toHaveBeenCalled(); - msg = sizzle('.chat-content .chat-msg:last .chat-msg__text').pop(); - expect(msg.textContent.trim()).toEqual('hello world'); - expect(msg.querySelectorAll('img.chat-image').length).toEqual(2); - - // Configured image URLs are rendered - _converse.api.settings.set('image_urls_regex', /^https?:\/\/(?:www.)?(?:imgur\.com\/\w{7})\/?$/i); - message = 'https://imgur.com/oxymPax'; - await mock.sendMessage(view, message); - await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-image').length === 5, 1000); - expect(view.querySelectorAll('.chat-content .chat-image').length).toBe(5); - - // Check that the Imgur URL gets a .png attached to make it render - await u.waitUntil(() => Array.from(view.querySelectorAll('.chat-content .chat-image')).pop().src.endsWith('png'), 1000); - done(); - })); - - it("will render images from approved URLs only", - mock.initConverse( - ['chatBoxesFetched'], {'show_images_inline': ['conversejs.org']}, - async function (done, _converse) { - - await mock.waitForRoster(_converse, 'current'); - const base_url = 'https://conversejs.org'; - let message = 'https://imgur.com/oxymPax.png'; - const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; - await mock.openChatBoxFor(_converse, contact_jid); - const view = _converse.api.chatviews.get(contact_jid); - spyOn(view.model, 'sendMessage').and.callThrough(); - await mock.sendMessage(view, message); - await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-msg').length === 1); - - message = base_url+"/logo/conversejs-filled.svg"; - await mock.sendMessage(view, message); - await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-msg').length === 2, 1000); - await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-image').length === 1, 1000) - expect(view.querySelectorAll('.chat-content .chat-image').length).toBe(1); - - done(); - })); - - it("will fall back to rendering images as URLs", - mock.initConverse( - ['chatBoxesFetched'], {}, - async function (done, _converse) { - - await mock.waitForRoster(_converse, 'current'); - const base_url = 'https://conversejs.org'; - const message = base_url+"/logo/non-existing.svg"; - const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; - await mock.openChatBoxFor(_converse, contact_jid); - const view = _converse.api.chatviews.get(contact_jid); - spyOn(view.model, 'sendMessage').and.callThrough(); - await mock.sendMessage(view, message); - await u.waitUntil(() => view.querySelectorAll('.chat-content .chat-image').length, 1000) - expect(view.model.sendMessage).toHaveBeenCalled(); - const msg = sizzle('.chat-content .chat-msg:last .chat-msg__text').pop(); - await u.waitUntil(() => msg.innerHTML.replace(//g, '').trim() == - `https://conversejs.org/logo/non-existing.svg`, 1000); - done(); - })); - - it("will fall back to rendering URLs that match image_urls_regex as URLs", - mock.initConverse( - ['rosterGroupsFetched', 'chatBoxesFetched'], { - 'show_images_inline': ['twimg.com'], - 'image_urls_regex': /^https?:\/\/(www.)?(pbs\.twimg\.com\/)/i - }, - async function (done, _converse) { - - await mock.waitForRoster(_converse, 'current'); - const message = "https://pbs.twimg.com/media/string?format=jpg&name=small"; - const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; - await mock.openChatBoxFor(_converse, contact_jid); - const view = _converse.api.chatviews.get(contact_jid); - spyOn(view.model, 'sendMessage').and.callThrough(); - await mock.sendMessage(view, message); - expect(view.model.sendMessage).toHaveBeenCalled(); - await u.waitUntil(() => view.querySelector('.chat-content .chat-msg'), 1000); - const msg = view.querySelector('.chat-content .chat-msg .chat-msg__text'); - await u.waitUntil(() => msg.innerHTML.replace(//g, '').trim() == - `https://pbs.twimg.com/media/string?format=jpg&name=small`, 1000); - done(); - })); - it("will render the message time as configured", mock.initConverse( ['chatBoxesFetched'], {}, @@ -1077,7 +960,7 @@ describe("A Chat Message", function () { await u.waitUntil(() => view.querySelector('.chat-msg__author').textContent.trim() === 'Mercutio'); let author_el = view.querySelector('.chat-msg__author'); - expect( _.includes(author_el.textContent.trim(), 'Mercutio')).toBeTruthy(); + expect(author_el.textContent.trim().includes('Mercutio')).toBeTruthy(); await u.waitUntil(() => vcard_fetched, 100); expect(_converse.api.vcard.get).toHaveBeenCalled(); await u.waitUntil(() => chatbox.vcard.get('fullname') === mock.cur_names[0])