import { after, expect, test } from "@odoo/hoot"; import { click, queryOne, queryValue, setInputFiles, waitFor } from "@odoo/hoot-dom"; import { Deferred, animationFrame } from "@odoo/hoot-mock"; import { clickSave, contains, defineModels, fields, makeServerError, models, mountView, onRpc, pagerNext, } from "@web/../tests/web_test_helpers"; import { toBase64Length } from "@web/core/utils/binary"; import { MAX_FILENAME_SIZE_BYTES } from "@web/views/fields/binary/binary_field"; const BINARY_FILE = "R0lGODlhDAAMAKIFAF5LAP/zxAAAANyuAP/gaP///wAAAAAAACH5BAEAAAUALAAAAAAMAAwAAAMlWLPcGjDKFYi9lxKBOaGcF35DhWHamZUW0K4mAbiwWtuf0uxFAgA7"; class Partner extends models.Model { _name = "res.partner"; foo = fields.Char({ default: "My little Foo Value" }); document = fields.Binary(); product_id = fields.Many2one({ relation: "product" }); _records = [{ foo: "coucou.txt", document: "coucou==\n" }]; } class Product extends models.Model { name = fields.Char(); _records = [ { id: 37, name: "xphone" }, { id: 41, name: "xpad" }, ]; } defineModels([Partner, Product]); onRpc("has_group", () => true); test("BinaryField is correctly rendered (readonly)", async () => { onRpc("/web/content", async (request) => { expect.step("/web/content"); const body = await request.text(); expect(body).toBeInstanceOf(FormData); expect(body.get("field")).toBe("document", { message: "we should download the field document", }); expect(body.get("data")).toBe("coucou==\n", { message: "we should download the correct data", }); return new Blob([body.get("data")], { type: "text/plain" }); }); await mountView({ resModel: "res.partner", resId: 1, type: "form", arch: `
`, }); expect(`.o_field_widget[name="document"] a > .fa-download`).toHaveCount(1, { message: "the binary field should be rendered as a downloadable link in readonly", }); expect(`.o_field_widget[name="document"]`).toHaveText("coucou.txt", { message: "the binary field should display the name of the file in the link", }); expect(`.o_field_char`).toHaveText("coucou.txt", { message: "the filename field should have the file name as value", }); // Testing the download button in the field // We must avoid the browser to download the file effectively const deferred = new Deferred(); const downloadOnClick = (ev) => { const target = ev.target; if (target.tagName === "A" && "download" in target.attributes) { ev.preventDefault(); document.removeEventListener("click", downloadOnClick); deferred.resolve(); } }; document.addEventListener("click", downloadOnClick); after(() => document.removeEventListener("click", downloadOnClick)); await contains(`.o_field_widget[name="document"] a`).click(); await deferred; expect.verifySteps(["/web/content"]); }); test("BinaryField is correctly rendered", async () => { onRpc("/web/content", async (request) => { expect.step("/web/content"); const body = await request.text(); expect(body).toBeInstanceOf(FormData); expect(body.get("field")).toBe("document", { message: "we should download the field document", }); expect(body.get("data")).toBe("coucou==\n", { message: "we should download the correct data", }); return new Blob([body.get("data")], { type: "text/plain" }); }); await mountView({ resModel: "res.partner", resId: 1, type: "form", arch: ` `, }); expect(`.o_field_widget[name="document"] a > .fa-download`).toHaveCount(0, { message: "the binary field should not be rendered as a downloadable link in edit", }); expect(`.o_field_widget[name="document"].o_field_binary .o_input`).toHaveValue("coucou.txt", { message: "the binary field should display the file name in the input edit mode", }); expect(`.o_field_binary .o_clear_file_button`).toHaveCount(1, { message: "there shoud be a button to clear the file", }); expect(`.o_field_char input`).toHaveValue("coucou.txt", { message: "the filename field should have the file name as value", }); // Testing the download button in the field // We must avoid the browser to download the file effectively const deferred = new Deferred(); const downloadOnClick = (ev) => { const target = ev.target; if (target.tagName === "A" && "download" in target.attributes) { ev.preventDefault(); document.removeEventListener("click", downloadOnClick); deferred.resolve(); } }; document.addEventListener("click", downloadOnClick); after(() => document.removeEventListener("click", downloadOnClick)); await click(`.fa-download`); await deferred; expect.verifySteps(["/web/content"]); await click(`.o_field_binary .o_clear_file_button`); await animationFrame(); expect(`.o_field_binary input`).not.toBeVisible({ message: "the input should be hidden" }); expect(`.o_field_binary .o_select_file_button`).toHaveCount(1, { message: "there should be a button to upload the file", }); expect(`.o_field_char input`).toHaveValue("", { message: "the filename field should be empty since we removed the file", }); await clickSave(); expect(`.o_field_widget[name="document"] a > .fa-download`).toHaveCount(0, { message: "the binary field should not render as a downloadable link since we removed the file", }); expect(`o_field_widget span`).toHaveCount(0, { message: "the binary field should not display a filename in the link since we removed the file", }); }); test("BinaryField is correctly rendered (isDirty)", async () => { await mountView({ resModel: "res.partner", resId: 1, type: "form", arch: ` `, }); // Simulate a file upload await click(`.o_select_file_button`); await animationFrame(); const file = new File(["test"], "fake_file.txt", { type: "text/plain" }); await setInputFiles([file]); await waitFor(`.o_form_button_save:visible`); expect(`.o_field_widget[name="document"] .fa-download`).toHaveCount(0, { message: "the binary field should not be rendered as a downloadable since the record is dirty", }); await clickSave(); expect(`.o_field_widget[name="document"] .fa-download`).toHaveCount(1, { message: "the binary field should render as a downloadable link since the record is not dirty", }); }); test("file name field is not defined", async () => { await mountView({ resModel: "res.partner", resId: 1, type: "form", arch: ``, }); expect(`.o_field_binary`).toHaveText("", { message: "there should be no text since the name field is not in the view", }); expect(`.o_field_binary .fa-download`).toBeDisplayed({ message: "download icon should be visible", }); }); test("icons are displayed exactly once", async () => { await mountView({ resModel: "res.partner", resId: 1, type: "form", arch: ``, }); expect(queryOne`.o_field_binary .o_select_file_button`).toBeVisible({ message: "only one select file icon should be visible", }); expect(queryOne`.o_field_binary .o_download_file_button`).toBeVisible({ message: "only one download file icon should be visible", }); expect(queryOne`.o_field_binary .o_clear_file_button`).toBeVisible({ message: "only one clear file icon should be visible", }); }); test("input value is empty when clearing after uploading", async () => { await mountView({ resModel: "res.partner", resId: 1, type: "form", arch: ` `, }); await click(`.o_select_file_button`); await animationFrame(); const file = new File(["test"], "fake_file.txt", { type: "text/plain" }); await setInputFiles([file]); await waitFor(`.o_form_button_save:visible`); expect(`.o_field_binary input[type=text]`).toHaveAttribute("readonly"); expect(`.o_field_binary input[type=text]`).toHaveValue("fake_file.txt"); expect(`.o_field_char input[type=text]`).toHaveValue("fake_file.txt"); await click(`.o_clear_file_button`); await animationFrame(); expect(`.o_field_binary .o_input_file`).toHaveValue(""); expect(`.o_field_char input`).toHaveValue(""); }); test("option accepted_file_extensions", async () => { await mountView({ resModel: "res.partner", type: "form", arch: ` `, }); expect(`input.o_input_file`).toHaveAttribute("accept", ".dat,.bin", { message: "the input should have the correct ``accept`` attribute", }); }); test.tags("desktop"); test("readonly in create mode does not download", async () => { onRpc("/web/content", () => { expect.step("We shouldn't be getting the file."); }); Partner._onChanges.product_id = (record) => { record.document = "onchange==\n"; }; Partner._fields.document.readonly = true; await mountView({ resModel: "res.partner", type: "form", arch: ` `, }); await click(`.o_field_many2one[name='product_id'] input`); await animationFrame(); await click(`.o_field_many2one[name='product_id'] .dropdown-item`); await animationFrame(); expect(`.o_field_widget[name="document"] a`).toHaveCount(0, { message: "The link to download the binary should not be present", }); expect(`.o_field_widget[name="document"] a > .fa-download`).toHaveCount(0, { message: "The download icon should not be present", }); expect.verifySteps([]); }); test("BinaryField in list view (formatter)", async () => { Partner._records[0]["document"] = BINARY_FILE; await mountView({ resModel: "res.partner", type: "list", arch: `