import { contains, defineModels, mountView, onRpc, serverState } from "@web/../tests/web_test_helpers"; import { omit } from "@web/core/utils/objects"; import { describe, expect, test } from "@odoo/hoot"; import { waitFor, waitForNone } from "@odoo/hoot-dom"; import { animationFrame } from "@odoo/hoot-mock"; import { DocumentsModels, getDocumentsTestServerModelsData, makeDocumentRecordData, } from "@documents/../tests/helpers/data"; import { makeDocumentsMockEnv } from "@documents/../tests/helpers/model"; import { basicDocumentsKanbanArch, mountDocumentsKanbanView, } from "@documents/../tests/helpers/views/kanban"; import { getEnrichedSearchArch } from "@documents/../tests/helpers/views/search"; const archWithTags = basicDocumentsKanbanArch.replace( '', /* xml */ ` ` ); /** * Shortcut for details panel selector * @param selector * @return {string} `selector` prefixed with `".o_documents_details_panel "`. */ const dp = (selector) => `.o_documents_details_panel ${selector}`; const binaryTestedValues = { tag_ids: [1, 2], owner_id: serverState.userId }; const folderTestedValues = { alias_id: 1, alias_domain_id: 1, alias_name: "alias", alias_tag_ids: [1, 2], mail_alias_domain_count: 2, owner_id: serverState.userId, type: "folder", create_activity_type_id: 1, }; describe.current.tags("desktop"); defineModels(DocumentsModels); onRpc("ir.model", "display_name_for", ({ args }) => args[0].map((model) => ({ model, display_name: model })) ); onRpc("/documents/touch/", () => ({})); test("Details panel rendering for editors", async () => { const serverData = getDocumentsTestServerModelsData([ makeDocumentRecordData(2, "Testing tags", { folder_id: 1, ...binaryTestedValues }), makeDocumentRecordData(3, "Testing container", { folder_id: 1, ...folderTestedValues }), ]); await makeDocumentsMockEnv({ serverData }); await mountDocumentsKanbanView({ arch: archWithTags }); await contains(".o_kanban_record:contains('Testing tags')").click(); await contains(".o_control_panel_navigation .fa-info-circle").click(); await animationFrame(); expect(dp(".o_documents_details_panel_name input")).toHaveCount(1); expect(dp(".o_documents_details_panel_name input")).toHaveValue("Testing tags"); expect(dp(".o_field_tags input")).toHaveCount(1); await contains(dp(".o_field_tags span:contains('Colorless') a")).click(); await waitForNone(dp(".o_field_tags span:contains('Colorless')")); await contains(dp(".o_field_tags span:contains('Colorful') a")).click(); expect(dp(".o_field_tags input[placeholder='Add tags...']")).toHaveCount(1); expect(dp("input[placeholder='No owner']")).toHaveValue("Mitchell Admin"); await contains(".o_kanban_record:contains('Testing container')").click(); expect(dp(".o_documents_details_panel_name input")).toHaveCount(1); expect(dp(".o_documents_details_panel_name input")).toHaveValue("Testing container"); expect(dp("input[placeholder='No activity']")).toHaveValue("Email"); expect(dp("input[placeholder='Activity assigned to']")).toHaveCount(1); await contains(dp(".o_field_tags span:contains('Colorless') a")).click(); await waitForNone(dp(".o_field_tags span:contains('Colorless')")); await contains(dp(".o_field_tags span:contains('Colorful') a")).click(); await waitForNone(dp(".o_field_tags span:contains('Colorful')")); await waitFor(dp(".o_field_tags input[placeholder='Add an alias tag...']")); }); test("Details panel rendering for viewers - m2o/m2m values", async () => { const serverData = getDocumentsTestServerModelsData([ makeDocumentRecordData(2, "Testing tags", { folder_id: 1, ...binaryTestedValues, user_permission: "view", }), makeDocumentRecordData(3, "Testing container", { folder_id: 1, ...folderTestedValues, user_permission: "view", }), ]); await makeDocumentsMockEnv({ serverData }); await mountDocumentsKanbanView({ arch: archWithTags }); await contains(".o_kanban_record:contains('Testing tags')").click(); await contains(".o_control_panel_navigation .fa-info-circle").click(); await animationFrame(); await waitFor(dp(".o_documents_details_panel_name span:contains('Testing Tags')")); expect(dp(".o_documents_details_panel_name input")).toHaveCount(0); expect(dp(".o_field_tags span:contains('Colorless')")).toHaveCount(1); expect(dp(".o_field_tags span:contains('Colorful')")).toHaveCount(1); expect(dp("span:contains('Mitchell Admin')")).toHaveCount(1); await contains(".o_kanban_record:contains('Testing container')").click(); await waitFor(dp(".o_documents_details_panel_name span:contains('Testing Container')")); expect(dp(".o_field_tags input")).toHaveCount(0); expect(dp(".o_field_tags span:contains('Colorless')")).toHaveCount(1); expect(dp(".o_field_tags span:contains('Colorful')")).toHaveCount(1); expect(dp("div:contains('alias@odoo.com')")).toHaveCount(3); // nested wrappers expect(dp("span:contains('Email')")).toHaveCount(1); // activity type expect(dp("span:contains('No activity assignee')")).toHaveCount(1); // activity type }); test("Details panel rendering for viewers - m2o/m2m pseudo-placeholders", async () => { const serverData = getDocumentsTestServerModelsData([ makeDocumentRecordData(2, "Testing tags", { folder_id: 1, user_permission: "view" }), makeDocumentRecordData(3, "Testing container", { folder_id: 1, ...omit(folderTestedValues, "alias_tag_ids"), user_permission: "view", }), ]); await makeDocumentsMockEnv({ serverData }); await mountDocumentsKanbanView({ arch: archWithTags }); await contains(".o_kanban_record:contains('Testing tags')").click(); await contains(".o_control_panel_navigation .fa-info-circle").click(); await animationFrame(); await waitFor(dp(".o_documents_details_panel_name span:contains('Testing Tags')")); expect(dp(".o_field_tags span.o_documents_details_panel_placeholder")).toHaveText("No tags"); await waitFor(dp("span.o_documents_details_panel_placeholder:contains('No owner')")); await contains(".o_kanban_record:contains('Testing container')").click(); await waitFor(dp(".o_documents_details_panel_name span:contains('Testing Container')")); await waitFor(dp("div:contains('alias@odoo.com')")); await waitFor(dp("span:contains('No activity')")); expect(dp(".o_field_tags span.o_documents_details_panel_placeholder")).toHaveText( "No alias tags" ); }); test("Details panel required document name", async () => { const serverData = getDocumentsTestServerModelsData([ makeDocumentRecordData(2, "Testing file", { folder_id: 1 }), makeDocumentRecordData(3, "Testing folder", { folder_id: 1 }), ]); await makeDocumentsMockEnv({ serverData }); await mountDocumentsKanbanView({ arch: archWithTags }); await contains(".o_control_panel_navigation .fa-info-circle").click(); for (const documentName of [ "Folder 1", // Container "Testing folder", "Testing file", ]) { await contains(`.o_kanban_record:contains('${documentName}')`).click(); await animationFrame(); expect(dp(".o_documents_details_panel_name input")).toHaveCount(1); expect(dp(".o_documents_details_panel_name input")).toHaveValue(documentName); // Set empty name await contains(".o_documents_details_panel_name input").edit(""); await animationFrame(); expect(".o_notification").toHaveCount(1); expect(".o_notification").toHaveText("Name cannot be empty."); await contains(".o_notification .o_notification_close").click(); expect(dp(".o_documents_details_panel_name input")).toHaveValue(documentName); } }); test("Details panel root folder placeholders", async () => { const serverData = getDocumentsTestServerModelsData([ makeDocumentRecordData(2, "In COMPANY (readonly)", { user_permission: "view" }), makeDocumentRecordData(3, "In MY DRIVE", { owner_id: serverState.userId }), makeDocumentRecordData(4, "In SHARED WITH ME", { owner_id: serverState.odoobotId }), makeDocumentRecordData(5, "In COMPANY"), ]); await makeDocumentsMockEnv({ serverData }); await mountDocumentsKanbanView({ arch: archWithTags }); await contains(".o_control_panel_navigation .fa-info-circle").click(); // Edit mode for (const [documentName, rootPlaceholder] of [ ["In COMPANY", "Company"], ["In MY DRIVE", "My Drive"], ["In SHARED WITH ME", "Shared with me"], ]) { await contains(`.o_kanban_record:contains('${documentName}')`).click(); await animationFrame(); expect(dp(".o_documents_details_panel_name input")).toHaveCount(1); expect(dp(".o_documents_details_panel_name input")).toHaveValue(documentName); expect(dp(".fa-folder + .o_field_many2one input")).toHaveAttribute( "placeholder", rootPlaceholder, { message: "Document should have correct root folder placeholder (editors)." } ); } // Readonly mode await contains(".o_kanban_record:contains('In COMPANY (readonly)')").click(); await animationFrame(); expect(dp(".o_documents_details_panel_name span")).toHaveCount(1); expect(dp(".o_documents_details_panel_name span")).toHaveText("In COMPANY (readonly)"); expect(dp(".fa-folder + .o_field_many2one .o_documents_details_panel_placeholder")).toHaveText( "Company", { message: "Document should have correct root folder placeholder (viewers)." } ); }); test("Details panel should be updated when clearing a selection", async function () { const serverData = getDocumentsTestServerModelsData([ makeDocumentRecordData(2, "Test file", { folder_id: 1 }), ]); await makeDocumentsMockEnv({ serverData }); await mountDocumentsKanbanView({ arch: archWithTags }); await contains(".o_control_panel_navigation .fa-info-circle").click(); for (const documentName of ["Folder 1", "Test file"]) { await contains(`.o_kanban_record:contains('${documentName}')`).click(); await animationFrame(); expect(dp(".o_documents_details_panel_name input")).toHaveCount(1); expect(dp(".o_documents_details_panel_name input")).toHaveValue(documentName); } // Clearing a selection. await contains(".o_unselect_all").click(); expect(dp(".o_documents_details_panel_name input")).toHaveCount(1); expect(dp(".o_documents_details_panel_name input")).toHaveValue("Folder 1"); }); test("Details panel changes to folders are immediately saved and visible in the app", async function () { await makeDocumentsMockEnv({ serverData: getDocumentsTestServerModelsData() }); await mountDocumentsKanbanView({ arch: archWithTags }); await contains(".o_control_panel_navigation .fa-info-circle").click(); await contains( "li.o_search_panel_category_value:contains('COMPANY') button.o_toggle_fold" ).click(); let counter = 0; onRpc("search_panel_select_range", () => { counter++; }); const renameTwice = async (from) => { expect(dp(".o_documents_details_panel_name input")).toHaveCount(1); expect(dp(".o_documents_details_panel_name input")).toHaveValue("Folder 1"); expect("span.o_search_panel_label_title:contains('Folder 1')").toHaveCount(1); await contains(dp(".o_documents_details_panel_name input")).edit( `Folder Renamed from ${from}` ); await animationFrame(); expect(dp(".o_documents_details_panel_name input")).toHaveValue( `Folder Renamed from ${from}` ); expect( `span.o_search_panel_label_title:contains('Folder Renamed from ${from}')` ).toHaveCount(1); await contains(dp(".o_documents_details_panel_name input")).edit("Folder 1"); await animationFrame(); expect(dp(".o_documents_details_panel_name input")).toHaveValue("Folder 1"); expect("span.o_search_panel_label_title:contains('Folder 1')").toHaveCount(1); }; // From container Record await contains(`.o_kanban_record:contains('Folder 1')`).click({ ctrlKey: true }); await animationFrame(); await renameTwice("container"); expect(counter).toBe(2); // From KanbanRecord await contains(`.o_kanban_record:contains('Folder 1')`).click(); await animationFrame(); await renameTwice("kanban record"); expect(counter).toBe(4); }); test("All models should be displayed in the details panel", async function () { const serverData = getDocumentsTestServerModelsData([ makeDocumentRecordData(2, "Test Request", { folder_id: 1 }), ]); await makeDocumentsMockEnv({ serverData }); await mountDocumentsKanbanView({ arch: archWithTags }); await contains(".o_kanban_record:contains('Test Request')").click(); await contains(".o_control_panel_navigation .fa-info-circle").click(); await animationFrame(); await contains(dp("input[placeholder='No linked model']")).click(); await animationFrame(); expect(dp("div ul li")).toHaveCount(9); expect(dp("div ul li:contains('Start typing...')")).toHaveCount(0); }); test("Details panel rendering", async function () { const serverData = getDocumentsTestServerModelsData([ makeDocumentRecordData(2, "Testing container", { folder_id: 1, ...folderTestedValues, user_permission: "edit", }), ]); await makeDocumentsMockEnv({ serverData }); await mountView({ type: "list", resModel: "documents.document", arch: ` `, searchViewArch: getEnrichedSearchArch(), }); await contains(`.o_data_row td[name="id"]:contains(2)`).click(); await contains(".o_control_panel_navigation .fa-info-circle").click(); expect(dp(".o_documents_details_panel_name input")).toHaveValue("Testing container"); expect(dp(".fa-envelope + div .o_field_char input")).toHaveValue("alias"); }); test("Add from document from log a note", async () => { onRpc("ir.model", "display_name_for", ({ args }) => args[0].map((model) => ({ model, display_name: model })) ); onRpc("documents.document", "add_documents_attachment", ({ args }) => { expect(args).toEqual([[12], "mail.compose.message", 0]); expect.step("add_documents_attachment"); return [ { id: 1002, name: "File 2", type: "binary", res_id: 0, res_model: "mail.compose.message", mimetype: "text/plain", public: false, original_id: [12, "File 2"], }, ]; }); const serverData = getDocumentsTestServerModelsData([ makeDocumentRecordData(2, "Folder 2", { type: "folder" }), makeDocumentRecordData(11, "File 1", { attachment_id: 101, user_folder_id: "1", type: "binary", }), makeDocumentRecordData(12, "File 2", { attachment_id: 102, user_folder_id: "2", type: "binary", }), ]); serverData["ir.attachment"] = [ { id: 101, name: "File 1", mimetype: "text/plain" }, { id: 102, name: "File 2", mimetype: "text/plain" }, ]; await makeDocumentsMockEnv({ serverData }); await mountDocumentsKanbanView({ arch: archWithTags }); await contains(".o_kanban_record:contains('File 1')").click(); await contains(".o_control_panel_navigation .fa-info-circle").click(); await waitFor(".o_document_chatter_container button.o-mail-Chatter-logNote:not(:disabled)"); await contains(".o_document_chatter_container button.o-mail-Chatter-logNote").click(); await contains(".o_document_chatter_container button[title='Add from Documents']").click(); await contains( ".o_select_create_dialog_content .o_search_panel_label_title:contains('Company')" ).click(); await contains( ".o_select_create_dialog_content .o_search_panel_label_title:contains('Folder 2')" ).click(); // As defining a list view interferes with the view behind the modal, we select "File 2" as only child of Folder 2. await contains(".o_select_create_dialog_content .o_data_row .o-checkbox input").click(); await contains(".o_select_create_dialog_content button:contains('Add from Documents')").click(); // The attachment has been added in the composer. expect(".o-mail-Composer .o-mail-Attachment-hoverImageText:contains('File 2')").toHaveCount(1); // But the attachment is not linked to the thread. expect(".o-mail-Chatter-topbar .o-mail-Chatter-attachFiles:contains('1')").toHaveCount(0); await expect.waitForSteps(["add_documents_attachment"]); });