diff --git a/addons/mail/static/src/components/discuss_sidebar_category_item/tests/discuss_sidebar_category_item_tests.js b/addons/mail/static/src/components/discuss_sidebar_category_item/tests/discuss_sidebar_category_item_tests.js index 3bad19c38ec..350a3a15f39 100644 --- a/addons/mail/static/src/components/discuss_sidebar_category_item/tests/discuss_sidebar_category_item_tests.js +++ b/addons/mail/static/src/components/discuss_sidebar_category_item/tests/discuss_sidebar_category_item_tests.js @@ -101,9 +101,8 @@ QUnit.test('channel - avatar: should update avatar url from bus', async function }); const newCacheKey = result[0]['avatarCacheKey']; - // FIXME: current test framework does not replace `src` with `data-src` during the re-rendering. assert.strictEqual( - channelItemAvatar.getAttribute('src'), + channelItemAvatar.dataset.src, `/web/image/mail.channel/20/avatar_128?unique=${newCacheKey}`, ); }); diff --git a/addons/mail/static/tests/activity_tests.js b/addons/mail/static/tests/activity_tests.js index d57062e7a9d..ee59cd8475c 100644 --- a/addons/mail/static/tests/activity_tests.js +++ b/addons/mail/static/tests/activity_tests.js @@ -580,7 +580,7 @@ QUnit.test('Activity view: many2one_avatar_user widget in activity view', async await legacyExtraNextTick(); assert.containsN(target, '.o_m2o_avatar', 2); - assert.containsOnce(target, 'tr[data-res-id=13] .o_m2o_avatar > img[src="/web/image/res.users/1/avatar_128"]', + assert.containsOnce(target, 'tr[data-res-id=13] .o_m2o_avatar > img[data-src="/web/image/res.users/1/avatar_128"]', "should have m2o avatar image"); assert.containsNone(target, '.o_m2o_avatar > span', "should not have text on many2one_avatar_user if onlyImage node option is passed"); diff --git a/addons/mail/static/tests/document_viewer_tests.js b/addons/mail/static/tests/document_viewer_tests.js index c0fcb84970d..cde111a4b37 100644 --- a/addons/mail/static/tests/document_viewer_tests.js +++ b/addons/mail/static/tests/document_viewer_tests.js @@ -102,23 +102,16 @@ QUnit.module('document_viewer_tests.js', { }); QUnit.test('Document Viewer Youtube', async function (assert) { - assert.expect(3); + assert.expect(2); - var youtubeURL = 'https://www.youtube.com/embed/FYqW0Gdwbzk'; var viewer = await createViewer({ attachmentID: 2, attachments: this.attachments, - mockRPC: function (route) { - if (route === youtubeURL) { - assert.ok(true, "should have called youtube URL"); - } - return this._super.apply(this, arguments); - }, }); assert.strictEqual(viewer.$(".o_image_caption:contains('urlYoutube')").length, 1, "the viewer should be on the right attachment"); - assert.containsOnce(viewer, '.o_viewer_text[data-src="' + youtubeURL + '"]', + assert.containsOnce(viewer, '.o_viewer_text[data-src="https://www.youtube.com/embed/FYqW0Gdwbzk"]', "there should be a video player"); viewer.destroy(); diff --git a/addons/mrp/static/tests/mrp_tests.js b/addons/mrp/static/tests/mrp_tests.js index 951197cdc12..c7912f38fd9 100644 --- a/addons/mrp/static/tests/mrp_tests.js +++ b/addons/mrp/static/tests/mrp_tests.js @@ -92,7 +92,7 @@ QUnit.module('mrp', { await testUtils.form.clickEdit(form); assert.isNotVisible(form.$('iframe.o_embed_iframe'), "there should be an invisible iframe in edit mode"); await testUtils.fields.editAndTrigger(form.$('.o_field_char'), 'http://example.com', ['input', 'change', 'focusout']); - assert.strictEqual(form.$('iframe.o_embed_iframe').attr('src'), 'http://example.com', + assert.strictEqual(form.$('iframe.o_embed_iframe').attr('data-src'), 'http://example.com', "src should updated on the iframe"); assert.isVisible(form.$('iframe.o_embed_iframe'), "there should be a visible iframe in edit mode"); await testUtils.form.clickSave(form); diff --git a/addons/web/static/tests/legacy/fields/basic_fields_tests.js b/addons/web/static/tests/legacy/fields/basic_fields_tests.js index 83d4c80148e..35157a20735 100644 --- a/addons/web/static/tests/legacy/fields/basic_fields_tests.js +++ b/addons/web/static/tests/legacy/fields/basic_fields_tests.js @@ -2958,10 +2958,6 @@ QUnit.module('basic_fields', { if (route === '/web/dataset/call_kw/partner/read') { assert.deepEqual(args.args[1], ['document', '__last_update', 'display_name'], "The fields document, display_name and __last_update should be present when reading an image"); } - if (route === `data:image/png;base64,${MY_IMAGE}`) { - assert.ok(true, "should called the correct route"); - return 'wow'; - } return this._super.apply(this, arguments); }, }); @@ -2970,6 +2966,8 @@ QUnit.module('basic_fields', { "the widget should have the correct class"); assert.containsOnce(form, 'div[name="document"] > img', "the widget should contain an image"); + assert.strictEqual(form.$('div[name="document"] > img')[0].dataset.src, + `data:image/png;base64,${MY_IMAGE}`, "the image should have the correct src"); assert.hasClass(form.$('div[name="document"] > img'),'img-fluid', "the image should have the correct class"); assert.hasAttrValue(form.$('div[name="document"] > img'), 'width', "90", @@ -2980,7 +2978,7 @@ QUnit.module('basic_fields', { }); QUnit.test('image fields are correctly replaced when given an incorrect value', async function (assert) { - assert.expect(7); + assert.expect(6); this.data.partner.records[0].__last_update = '2017-02-08 10:00:00'; this.data.partner.records[0].document = 'incorrect_base64_value'; @@ -3006,19 +3004,14 @@ QUnit.module('basic_fields', { `, res_id: 1, - async mockRPC(route, args) { - const _super = this._super; - if (route === '/web/static/img/placeholder.png') { - assert.step('call placeholder route'); - } - return _super.apply(this, arguments); - }, }); assert.hasClass(form.$('div[name="document"]'),'o_field_image', "the widget should have the correct class"); assert.containsOnce(form, 'div[name="document"] > img', "the widget should contain an image"); + assert.strictEqual(form.$('div[name="document"] > img')[0].dataset.src, + `/web/static/img/placeholder.png`, "the image should have the correct src"); assert.hasClass(form.$('div[name="document"] > img'), 'img-fluid', "the image should have the correct class"); assert.hasAttrValue(form.$('div[name="document"] > img'), 'width', "90", @@ -3026,7 +3019,6 @@ QUnit.module('basic_fields', { assert.strictEqual(form.$('div[name="document"] > img').css('max-width'), "90px", "the image should correctly set its attributes"); - assert.verifySteps(['call placeholder route']); form.destroy(); testUtils.mock.unpatch(basicFields.FieldBinaryImage); @@ -3061,7 +3053,7 @@ QUnit.module('basic_fields', { }); QUnit.test('image fields in subviews are loaded correctly', async function (assert) { - assert.expect(6); + assert.expect(4); this.data.partner.records[0].__last_update = '2017-02-08 10:00:00'; this.data.partner.records[0].document = MY_IMAGE; @@ -3094,19 +3086,11 @@ QUnit.module('basic_fields', { '' + '', res_id: 1, - async mockRPC(route) { - if (route === `data:image/png;base64,${MY_IMAGE}`) { - assert.step("The view's image should have been fetched"); - return 'wow'; - } - if (route === `data:image/gif;base64,${PRODUCT_IMAGE}`) { - assert.step("The dialog's image should have been fetched"); - return; - } - return this._super.apply(this, arguments); - }, }); - assert.verifySteps(["The view's image should have been fetched"]); + assert.ok( + document.querySelector(`img[data-src="data:image/png;base64,${MY_IMAGE}"]`), + "The view's image is in the DOM" + ); assert.containsOnce(form, '.o_kanban_record.oe_kanban_global_click', 'There should be one record in the many2many'); @@ -3115,7 +3099,10 @@ QUnit.module('basic_fields', { await testUtils.dom.click(form.$('.oe_kanban_global_click')); assert.strictEqual($('.modal').length, 1, 'The modal should have opened'); - assert.verifySteps(["The dialog's image should have been fetched"]); + assert.ok( + document.querySelector(`img[data-src="data:image/gif;base64,${PRODUCT_IMAGE}"]`), + "The dialog's image is in the DOM" + ); form.destroy(); }); @@ -3139,17 +3126,14 @@ QUnit.module('basic_fields', { '' + '', res_id: 1, - async mockRPC(route) { - if (route === `data:image/gif;base64,${PRODUCT_IMAGE}`) { - assert.ok(true, "The list's image should have been fetched"); - return; - } - return this._super.apply(this, arguments); - }, }); assert.containsOnce(form, 'tr.o_data_row', 'There should be one record in the many2many'); + assert.ok( + document.querySelector(`img[data-src="data:image/gif;base64,${PRODUCT_IMAGE}"]`), + "The list's image is in the DOM" + ); form.destroy(); }); @@ -3203,18 +3187,14 @@ QUnit.module('basic_fields', { ' ' + '', res_id: 6, - async mockRPC(route, args) { - if (route === FR_FLAG_URL) { - assert.ok(true, "the correct route should have been called."); - } - return this._super.apply(this, arguments); - }, }); assert.hasClass(form.$('div[name="foo"]'), 'o_field_image', "the widget should have the correct class"); assert.containsOnce(form, 'div[name="foo"] > img', "the widget should contain an image"); + assert.strictEqual(form.$('div[name="foo"] > img')[0].dataset.src, + FR_FLAG_URL, "the image should have the correct src"); assert.hasClass(form.$('div[name="foo"] > img'), 'img-fluid', "the image should have the correct class"); assert.hasAttrValue(form.$('div[name="foo"] > img'), 'width', "90", @@ -3225,7 +3205,7 @@ QUnit.module('basic_fields', { }); QUnit.test('image_url widget in subviews are loaded correctly', async function (assert) { - assert.expect(6); + assert.expect(4); this.data.partner_type.fields.image = {name: 'image', type: 'char'}; this.data.partner_type.records[0].image = EN_FLAG_URL; @@ -3256,19 +3236,11 @@ QUnit.module('basic_fields', { '' + '', res_id: 6, - async mockRPC(route) { - if (route === FR_FLAG_URL) { - assert.step("The view's image should have been fetched"); - return 'wow'; - } - if (route === EN_FLAG_URL) { - assert.step("The dialog's image should have been fetched"); - return; - } - return this._super.apply(this, arguments); - }, }); - assert.verifySteps(["The view's image should have been fetched"]); + assert.ok( + document.querySelector(`img[data-src="${FR_FLAG_URL}"]`), + "The view's image is in the DOM" + ); assert.containsOnce(form, '.o_kanban_record.oe_kanban_global_click', 'There should be one record in the many2many'); @@ -3277,7 +3249,10 @@ QUnit.module('basic_fields', { await testUtils.dom.click(form.$('.oe_kanban_global_click')); assert.strictEqual($('.modal').length, 1, 'The modal should have opened'); - assert.verifySteps(["The dialog's image should have been fetched"]); + assert.ok( + document.querySelector(`img[data-src="${EN_FLAG_URL}"]`), + "The dialog's image is in the DOM" + ); form.destroy(); }); @@ -3301,17 +3276,14 @@ QUnit.module('basic_fields', { '' + '', res_id: 6, - async mockRPC(route) { - if (route === EN_FLAG_URL) { - assert.ok(true, "The list's image should have been fetched"); - return; - } - return this._super.apply(this, arguments); - }, }); assert.containsOnce(form, 'tr.o_data_row', 'There should be one record in the many2many'); + assert.ok( + document.querySelector(`img[data-src="${EN_FLAG_URL}"]`), + "The list's image is in the DOM" + ); form.destroy(); }); diff --git a/addons/web/static/tests/legacy/helpers/test_utils_mock.js b/addons/web/static/tests/legacy/helpers/test_utils_mock.js index f363fdeaced..9e7b941ffc3 100644 --- a/addons/web/static/tests/legacy/helpers/test_utils_mock.js +++ b/addons/web/static/tests/legacy/helpers/test_utils_mock.js @@ -287,44 +287,6 @@ function intercept(widget, eventName, fn, propagate) { }; } -/** - * Removes the src attribute on images and iframes to prevent not found errors, - * and optionally triggers an rpc with the src url as route on a widget. - * This method is critical and must be fastest (=> no jQuery, no underscore) - * - * @param {HTMLElement} el - * @param {[function]} rpc - */ -function removeSrcAttribute(el, rpc) { - var nodes; - if (el.nodeName === "#comment") { - return; - } - el = el.nodeType === 8 ? el.nextSibling : el; - if (el.nodeName === 'IMG' || el.nodeName === 'IFRAME') { - nodes = [el]; - } else { - nodes = Array.prototype.slice.call(el.getElementsByTagName('img')) - .concat(Array.prototype.slice.call(el.getElementsByTagName('iframe'))); - } - var node; - while (node = nodes.pop()) { - var src = node.attributes.src && node.attributes.src.value; - if (src && src !== 'about:blank') { - node.setAttribute('data-src', src); - if (node.nodeName === 'IMG') { - node.attributes.removeNamedItem('src'); - } else { - node.setAttribute('src', 'about:blank'); - } - if (rpc) { - rpc(src, []); - } - $(node).trigger('load'); - } - } -} - /** * Add a mock environment to test Owl Components. This function generates a test * env and sets it on the given Component. It also has several side effects, @@ -396,15 +358,6 @@ async function addMockEnvironmentOwl(Component, params, mockServer) { }; } - // make sure images do not trigger a GET on the server - $('body').on('DOMNodeInserted.removeSRC', function (ev) { - let rpc; - if (params.mockSRC) { - rpc = mockServer.performRpc.bind(mockServer); - } - removeSrcAttribute(ev.target, rpc); - }); - // mock global objects for legacy widgets (session, config...) const restoreMockedGlobalObjects = _mockGlobalObjects(params); diff --git a/addons/web/static/tests/legacy/views/kanban_tests.js b/addons/web/static/tests/legacy/views/kanban_tests.js index 91f6a9c1b28..c2a868f6b42 100644 --- a/addons/web/static/tests/legacy/views/kanban_tests.js +++ b/addons/web/static/tests/legacy/views/kanban_tests.js @@ -7124,13 +7124,6 @@ QUnit.module('Views', { '' + '' + '', - mockRPC: function (route, args) { - if (route === 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACAA==') { - assert.ok("The view's image should have been fetched."); - return Promise.resolve(); - } - return this._super.apply(this, arguments); - }, }); var images = kanban.el.querySelectorAll('img'); var placeholders = []; @@ -7143,6 +7136,8 @@ QUnit.module('Views', { assert.strictEqual(placeholders.length, this.data.partner.records.length - 1, "partner with no image should display the placeholder"); + assert.strictEqual(images[0].dataset.src, "data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACAA==", + "The first partners non-placeholder image should be set"); kanban.destroy(); }); @@ -7161,13 +7156,6 @@ QUnit.module('Views', { '' + '' + '', - mockRPC: function (route, args) { - if (route === 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACAA==') { - assert.ok("The view's image should have been fetched."); - return Promise.resolve(); - } - return this._super.apply(this, arguments); - }, }); // the field image is set, but we request the image for a specific id @@ -7176,6 +7164,9 @@ QUnit.module('Views', { var imageOnRecord = kanban.$('img[data-src*="/web/image"][data-src*="&id=1"]'); assert.strictEqual(imageOnRecord.length, this.data.partner.records.length - 1, "display image by url when requested for another record"); + assert.strictEqual(kanban.el.querySelector("img").dataset.src, + "data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACAA==", + "display image as value when requested for the record itself"); kanban.destroy(); }); diff --git a/addons/web/static/tests/setup.js b/addons/web/static/tests/setup.js index 6952aab6610..5a5dfb65f67 100644 --- a/addons/web/static/tests/setup.js +++ b/addons/web/static/tests/setup.js @@ -222,4 +222,33 @@ export async function setupTests() { const templates = await loadFile(templatesUrl); window.__OWL_TEMPLATES__ = processTemplates(templates); await Promise.all([whenReady(), legacyProm]); + + // make sure images do not trigger a GET on the server + new MutationObserver((mutations) => { + const nodes = mutations.flatMap(({ target }) => { + if (target.nodeName === "IMG" || target.nodeName === "IFRAME") { + return target; + } + return [ + ...target.getElementsByTagName("img"), + ...target.getElementsByTagName("iframe"), + ]; + }); + for (const node of nodes) { + const src = node.getAttribute("src"); + if (src && src !== "about:blank") { + node.dataset.src = src; + if (node.nodeName === "IMG") { + node.removeAttribute("src"); + } else { + node.setAttribute("src", "about:blank"); + } + node.dispatchEvent(new Event("load")); + } + } + }).observe(document.body, { + subtree: true, + childList: true, + attributeFilter: ["src"], + }); } diff --git a/addons/web/static/tests/webclient/user_menu_tests.js b/addons/web/static/tests/webclient/user_menu_tests.js index 953620376db..829c71625d3 100644 --- a/addons/web/static/tests/webclient/user_menu_tests.js +++ b/addons/web/static/tests/webclient/user_menu_tests.js @@ -88,7 +88,7 @@ QUnit.test("can be rendered", async (assert) => { let userMenuEl = userMenu.el; assert.containsOnce(userMenuEl, "img.o_user_avatar"); assert.strictEqual( - userMenuEl.querySelector("img.o_user_avatar").src, + userMenuEl.querySelector("img.o_user_avatar").dataset.src, "http://lordofthering/web/image?model=res.users&field=avatar_128&id=7" ); assert.containsOnce(userMenuEl, "span.oe_topbar_name");