/* @odoo-module */ import { serverState, startServer } from "@bus/../tests/helpers/mock_python_environment"; import { fileUploadService } from "@web/core/file_upload/file_upload_service"; import { DocumentsKanbanRenderer } from "@documents/views/kanban/documents_kanban_renderer"; import { createDocumentsView as originalCreateDocumentsView, createDocumentsViewWithMessaging, loadServices, } from "./documents_test_utils"; import * as dsHelpers from "@web/../tests/core/domain_selector_tests"; import { setupViewRegistries } from "@web/../tests/views/helpers"; import { patchUserWithCleanup } from "@web/../tests/helpers/mock_services"; import { toggleMenuItem, toggleSearchBarMenu, pagerNext, pagerPrevious, } from "@web/../tests/search/helpers"; import testUtils from "@web/../tests/legacy_tests/helpers/test_utils"; import { click as legacyClick, editInput, clickOpenedDropdownItem, clickOpenM2ODropdown, dragAndDrop, getFixture, makeDeferred, nextTick, patchWithCleanup, triggerEvent, } from "@web/../tests/helpers/utils"; import { browser, makeRAMLocalStorage } from "@web/core/browser/browser"; import { serializeDate } from "@web/core/l10n/dates"; import { router } from "@web/core/browser/router"; import { click, contains, dragoverFiles, dropFiles, inputFiles, insertText, } from "@web/../tests/utils"; const { DateTime } = luxon; function createDocumentsView(params) { return originalCreateDocumentsView({ serverData: { models: pyEnv.getData(), views: {} }, ...params, }); } let target; let pyEnv; QUnit.module("documents", {}, function () { QUnit.module( "documents_kanban_tests.js", { async beforeEach() { loadServices(); patchWithCleanup(browser, { navigator: { ...browser.navigator, clipboard: { writeText: () => {}, }, }, }); this.ORIGINAL_CREATE_XHR = fileUploadService.createXhr; this.patchDocumentXHR = (mockedXHRs, customSend) => { fileUploadService.createXhr = () => { const xhr = new window.EventTarget(); Object.assign(xhr, { upload: new window.EventTarget(), open() {}, send(data) { customSend && customSend(data); }, }); mockedXHRs.push(xhr); return xhr; }; }; pyEnv = await startServer(); const resPartnerIds = pyEnv["res.partner"].create([ { display_name: "Hazard" }, { display_name: "Lukaku" }, { display_name: "De Bruyne" }, { email: "raoul@grosbedon.fr", name: "Raoul Grosbedon" }, { email: "raoulette@grosbedon.fr", name: "Raoulette Grosbedon" }, ]); const resUsersIds = pyEnv["res.users"].create([ { display_name: "Hazard", partner_id: resPartnerIds[0], login: "hazard", password: "hazard", }, { display_name: "Lukaku", partner_id: resPartnerIds[1] }, { display_name: "De Bruyne", partner_id: resPartnerIds[2] }, ]); const documentsFolderIds = pyEnv["documents.document"].create([ { name: "Workspace1", type: "folder", access_internal: "edit", user_permission: "edit", is_pinned_folder: true, folder_id: false, owner_id: 1 }, { name: "Workspace2", type: "folder", access_internal: "edit", user_permission: "edit", is_pinned_folder: true, folder_id: false, owner_id: 1 }, ]); documentsFolderIds.push( pyEnv["documents.document"].create([ { name: "Workspace3", folder_id: documentsFolderIds[0], type: "folder", access_internal: "edit", user_permission: "edit", }, ]) ); const documentsTagIds = pyEnv["documents.tag"].create([ { display_name: "New", sequence: 11 }, { display_name: "Draft", sequence: 10 }, { display_name: "No stress", sequence: 10 }, ]); const documentsEmbeddedActions = []; const resFakeIds = pyEnv["res.fake"].create([{ name: "fake1" }, { name: "fake2" }]); const irAttachmentId1 = pyEnv["ir.attachment"].create({}); const [documentsDocumentId1, documentsDocumentId2] = pyEnv[ "documents.document" ].create([ { activity_state: "today", available_embedded_actions_ids: documentsEmbeddedActions, file_size: 30000, folder_id: documentsFolderIds[0], is_editable_attachment: true, name: "yop", owner_id: resUsersIds[0], partner_id: resPartnerIds[1], res_id: resFakeIds[0], res_model: "res.fake", res_model_name: "Task", res_name: "Write specs", tag_ids: [documentsTagIds[0], documentsTagIds[1]], }, { attachment_id: pyEnv["ir.attachment"].create({}), available_embedded_actions_ids: documentsEmbeddedActions, file_size: 20000, folder_id: documentsFolderIds[0], mimetype: "application/pdf", name: "blip", owner_id: resUsersIds[1], partner_id: resPartnerIds[1], res_id: resFakeIds[1], res_model: "res.fake", res_model_name: "Task", res_name: "Write tests", tag_ids: [documentsTagIds[1]], }, ]); pyEnv["documents.document"].create([ { available_embedded_actions_ids: documentsEmbeddedActions, file_size: 15000, folder_id: documentsFolderIds[0], lock_uid: resUsersIds[2], name: "gnap", owner_id: resUsersIds[1], partner_id: resPartnerIds[0], res_id: documentsDocumentId2, res_model: "documents.document", res_model_name: "Task", tag_ids: [documentsTagIds[0], documentsTagIds[1], documentsTagIds[2]], }, { available_embedded_actions_ids: documentsEmbeddedActions, file_size: 10000, folder_id: documentsFolderIds[0], mimetype: "image/png", name: "burp", owner_id: resUsersIds[0], partner_id: resPartnerIds[2], res_id: irAttachmentId1, res_model: "ir.attachment", res_model_name: "Attachment", }, { available_embedded_actions_ids: documentsEmbeddedActions, file_size: 40000, folder_id: documentsFolderIds[0], lock_uid: resUsersIds[0], name: "zip", owner_id: resUsersIds[1], partner_id: resPartnerIds[1], tag_ids: documentsTagIds, }, { available_embedded_actions_ids: [], file_size: 70000, folder_id: documentsFolderIds[1], name: "pom", partner_id: resPartnerIds[2], res_id: documentsDocumentId1, res_model: "documents.document", res_model_name: "Document", }, { active: false, file_size: 70000, available_embedded_actions_ids: [], folder_id: documentsFolderIds[0], name: "wip", owner_id: resUsersIds[2], partner_id: resPartnerIds[2], res_id: irAttachmentId1, res_model: "ir.attachment", res_model_name: "Attachment", }, { active: false, available_embedded_actions_ids: [], file_size: 20000, folder_id: documentsFolderIds[0], mimetype: "text/plain", name: "zorro", owner_id: resUsersIds[2], partner_id: resPartnerIds[2], }, ]); setupViewRegistries(); target = getFixture(); }, afterEach() { fileUploadService.createXhr = this.ORIGINAL_CREATE_XHR; pyEnv = undefined; }, }, function () { QUnit.skip("kanban basic rendering", async function (assert) { assert.expect(30); await createDocumentsView({ type: "kanban", resModel: "documents.document", arch: ` `, }); assert.strictEqual( target .querySelector( "header.active > .o_search_panel_label .o_search_panel_label_title" ) .textContent.trim(), "Workspace1", "the first selected record should be the first folder" ); assert.strictEqual( target .querySelector("header.active > .o_search_panel_counter") .textContent.trim(), "5", "the first folder should have 5 as counter" ); assert.containsOnce( target, ".o_search_panel_category_value:contains(All) header", "Should only have a single all selector" ); await legacyClick(target, ".o_search_panel_category_value:nth-of-type(1) header"); assert.ok( target.querySelector(".o_documents_kanban_upload").disabled, "the upload button should be disabled on global view" ); assert.notOk( target.querySelector(".o_documents_kanban_url").disabled, "the upload url button should be enabled on global view" ); assert.notOk( target.querySelector(".o_documents_kanban_request").disabled, "the request button should be enabled on global view" ); assert.notOk( target.querySelector(".o_documents_kanban_workspace"), "the workspace button should only be visible for documents manager on global view" ); await legacyClick(target, ".o_kanban_record:nth-of-type(1) .o_record_selector"); assert.ok( target.querySelector(".o_documents_kanban_share_domain").disabled === false, "the share button should be enabled on global view when documents are selected" ); assert.containsN( target, ".o_kanban_renderer .o_kanban_record:not(.o_kanban_ghost)", 6, "should have 6 records in the renderer" ); assert.containsNone( target, ".o_documents_selector_tags", "should not display the tag navigation because no workspace is selected by default" ); await legacyClick(target, ".o_search_panel_category_value:nth-of-type(2) header"); // check view layout assert.containsOnce(target, ".o_content > div.o_search_panel"); assert.containsOnce(target, ".o_content > div.o_kanban_renderer"); assert.containsOnce(target, ".o_content > div.o_documents_inspector"); assert.containsOnce( target.querySelector(".o_content"), "> div.o_search_panel", "should have a 'documents selector' column" ); assert.containsOnce( target, ".o_content > .o_kanban_renderer", "should have a 'classical kanban view' column" ); assert.hasClass( target.querySelector(".o_kanban_view"), "o_documents_kanban_view", "should have classname 'o_documents_kanban_view'" ); assert.containsN( target, ".o_kanban_renderer .o_kanban_record:not(.o_kanban_ghost)", 5, "should have 5 records in the renderer" ); assert.containsOnce( target, ".o_kanban_record:first .o_record_selector", "should have a 'selected' button" ); assert.containsOnce( target, ".o_content > .o_documents_inspector", "should have a 'document inspector' column" ); // check control panel buttons assert.containsOnce( target, ".o_cp_buttons .btn-primary.dropdown-toggle:visible" ); assert.strictEqual( $(".o_cp_buttons .btn-primary.dropdown-toggle:visible") .get(0) .textContent.trim(), "New", "should have a primary 'New' button" ); assert.ok( target.querySelector(".o_documents_kanban_upload").disabled === false, "the upload button should be enabled when a folder is selected" ); assert.containsOnce( target, ".o_cp_buttons button.o_documents_kanban_url", "should allow to save a URL on small / xl screen" ); assert.ok( target.querySelector(".o_documents_kanban_url").disabled === false, "the upload url button should be enabled when a folder is selected" ); assert.strictEqual( target .querySelector(".o_cp_buttons button.o_documents_kanban_request") .textContent.trim(), "Request", "should have a primary 'request' button" ); assert.ok( target.querySelector(".o_documents_kanban_request").disabled === false, "the request button should be enabled when a folder is selected" ); assert.containsOnce( target, ".o_cp_buttons .btn-secondary:visible" ); assert.strictEqual( target .querySelector( ".o_cp_buttons .o_documents_kanban_share_domain" ) .textContent.trim(), "Share", "should have a secondary 'Share' button" ); assert.ok( target.querySelector(".o_documents_kanban_share_domain").disabled === false, "the share button should be enabled when a folder is selected" ); await legacyClick(target, ".o_search_panel_category_value[title='Trash'] header"); assert.ok( target.querySelector(".o_documents_kanban_upload").disabled, "the upload button should be disabled inside TRASH folder." ); }); QUnit.skip( "can select records by clicking on the select icon", async function (assert) { assert.expect(6); await createDocumentsView({ type: "kanban", resModel: "documents.document", arch: ` `, }); const firstRecord = target.querySelector(".o_kanban_record"); assert.doesNotHaveClass( firstRecord, "o_record_selected", "first record should not be selected" ); await legacyClick(firstRecord, ".o_record_selector"); assert.hasClass( firstRecord, "o_record_selected", "first record should be selected" ); const thirdRecord = target.querySelectorAll(".o_kanban_record")[2]; assert.doesNotHaveClass( thirdRecord, "o_record_selected", "third record should not be selected" ); await legacyClick(thirdRecord, ".o_record_selector"); assert.hasClass( thirdRecord, "o_record_selected", "third record should be selected" ); await legacyClick(firstRecord, ".o_record_selector"); assert.doesNotHaveClass( firstRecord, "o_record_selected", "first record should not be selected" ); assert.hasClass( thirdRecord, "o_record_selected", "third record should be selected" ); } ); QUnit.skip("can select records by clicking on them", async function (assert) { assert.expect(5); await createDocumentsView({ type: "kanban", resModel: "documents.document", arch: ` `, }); assert.containsNone( target, ".o_kanban_record.o_record_selected", "no record should be selected" ); const firstRecord = target.querySelector(".o_kanban_record"); await legacyClick(firstRecord); assert.containsOnce( target, ".o_kanban_record.o_record_selected", "one record should be selected" ); assert.hasClass( firstRecord, "o_record_selected", "first record should be selected" ); const thirdRecord = target.querySelectorAll(".o_kanban_record")[2]; await legacyClick(thirdRecord); assert.containsOnce( target, ".o_kanban_record.o_record_selected", "one record should be selected" ); assert.hasClass( thirdRecord, "o_record_selected", "third record should be selected" ); }); QUnit.skip("can unselect a record", async function () { await createDocumentsView({ type: "kanban", resModel: "documents.document", arch: ` `, }); await contains(".o_kanban_record", { count: 11 }); await contains(".o_kanban_record.o_record_selected", { count: 0 }); await click(":nth-child(1 of .o_kanban_record)"); await contains(".o_kanban_record.o_record_selected"); await click(":nth-child(1 of .o_kanban_record)"); await contains(".o_kanban_record.o_record_selected", { count: 0 }); }); QUnit.skip("can select records with keyboard navigation", async function (assert) { assert.expect(4); const kanban = await createDocumentsView({ type: "kanban", resModel: "documents.document", arch: `
`, }); patchWithCleanup(kanban.env.services.action, { doActionButton({ onClose }) { assert.ok(false, "should not trigger an 'execute_action' event"); onClose(); }, }); const firstRecord = target.querySelector(".o_kanban_record"); assert.doesNotHaveClass( firstRecord, "o_record_selected", "first record should not be selected" ); await triggerEvent(firstRecord, null, "keydown", { key: "Enter", }); assert.hasClass( firstRecord, "o_record_selected", "first record should be selected" ); const thirdRecord = target.querySelector(".o_kanban_record:nth-of-type(3)"); await triggerEvent(thirdRecord, null, "keydown", { key: "Enter", }); assert.hasClass( thirdRecord, "o_record_selected", "third record should be selected" ); assert.doesNotHaveClass( firstRecord, "o_record_selected", "first record should no longer be selected" ); }); QUnit.skip("can multi select records with shift and ctrl", async function (assert) { assert.expect(6); await createDocumentsView({ type: "kanban", resModel: "documents.document", arch: `
`, }); const firstRecord = target.querySelector(".o_kanban_record"); assert.doesNotHaveClass( firstRecord, "o_record_selected", "first record should not be selected" ); await triggerEvent(firstRecord, null, "keydown", { key: "Enter", }); assert.hasClass( firstRecord, "o_record_selected", "first record should be selected" ); const thirdRecord = target.querySelectorAll(".o_kanban_record")[2]; await triggerEvent(thirdRecord, null, "keydown", { key: "Enter", shiftKey: true, }); assert.hasClass( thirdRecord, "o_record_selected", "third record should be selected (shift)" ); assert.hasClass( firstRecord, "o_record_selected", "first record should still be selected (shift)" ); await triggerEvent(firstRecord, null, "keydown", { key: "Enter", ctrlKey: true, }); assert.hasClass( thirdRecord, "o_record_selected", "third record should still be selected (ctrl)" ); assert.doesNotHaveClass( firstRecord, "o_record_selected", "first record should no longer be selected (ctrl)" ); }); QUnit.skip("can multi edit records", async function (assert) { assert.expect(6); await createDocumentsView({ type: "kanban", resModel: "documents.document", arch: `
`, mockRPC(route, args) { if (args.method === "write") { assert.deepEqual(args.args, [[4, 5], { is_editable_attachment: true }]); } }, }); assert.containsN(target, ".o_kanban_record:not(.o_kanban_ghost)", 5); assert.containsNone(target, ".o_record_selected"); assert.containsOnce( target, ".o_field_widget[name=is_editable_attachment] input:checked" ); await legacyClick(target.querySelectorAll(".o_record_selector")[3]); await legacyClick(target.querySelectorAll(".o_record_selector")[4]); assert.containsN(target, ".o_record_selected", 2); await legacyClick( target.querySelectorAll(".o_field_widget[name=is_editable_attachment] input")[4] ); await nextTick(); assert.containsN( target, ".o_field_widget[name=is_editable_attachment] input:checked", 3 ); }); QUnit.skip( "only visible selected records are kept after a reload", async function (assert) { assert.expect(6); const kanban = await createDocumentsView({ type: "kanban", resModel: "documents.document", arch: '' + '' + '' + '