1
0
forked from Mapan/odoo17e
odoo17e-kedaikipas58/addons/web_studio/static/tests/app_creator_tests.js
2024-12-10 09:04:09 +07:00

577 lines
21 KiB
JavaScript

/** @odoo-module **/
import { registry } from "@web/core/registry";
import { browser } from "@web/core/browser/browser";
import { registerCleanup } from "@web/../tests/helpers/cleanup";
import { makeTestEnv } from "@web/../tests/helpers/mock_env";
import { setupViewRegistries } from "@web/../tests/views/helpers";
import { AppCreator } from "@web_studio/client_action/app_creator/app_creator";
import { IconCreator } from "@web_studio/client_action/icon_creator/icon_creator";
import { makeFakeHTTPService } from "@web/../tests/helpers/mock_services";
import {
click,
getFixture,
nextTick,
triggerEvent,
editInput,
mount,
patchWithCleanup,
} from "@web/../tests/helpers/utils";
import { AutoComplete } from "@web/core/autocomplete/autocomplete";
const serviceRegistry = registry.category("services");
const sampleIconUrl = "/web_enterprise/Parent.src/img/default_icon_app.png";
function makeFakeUIService({ block = () => {}, unblock = () => {} } = {}) {
return {
start(env) {
const ui = {
block,
unblock,
};
Object.defineProperty(env, "isSmall", {
get() {
return false;
},
});
return ui;
},
};
}
async function startAtStep(target, startStep) {
if (["app", "model", "model_configuration"].includes(startStep)) {
// From welcome to app
await click(target, ".o_web_studio_app_creator_next");
}
if (["model", "model_configuration"].includes(startStep)) {
// From app to model
await editInput(target, "input[name='appName']", "testApp");
await click(target, ".o_web_studio_app_creator_next");
}
if (["model_configuration"].includes(startStep)) {
// From model to model_configuration
await editInput(target, "input[name='menuName']", "testMenu");
await click(target, ".o_web_studio_app_creator_next");
}
}
async function createAppCreator(params = {}) {
const onNewAppCreated = params.onNewAppCreated || (() => {});
for (const serviceKey in params.services) {
serviceRegistry.add(serviceKey, params.services[serviceKey], { force: true });
}
const { mockRPC, serverData, startStep } = params;
const target = getFixture();
const component = await mount(AppCreator, target, {
props: { onNewAppCreated },
env: await makeTestEnv({
serverData,
mockRPC,
}),
});
if (startStep) {
await startAtStep(target, startStep);
}
return { state: component.state };
}
QUnit.module("AppCreator", (hooks) => {
let serverData;
let target;
hooks.beforeEach(() => {
target = getFixture();
IconCreator.enableTransitions = false;
registerCleanup(() => {
IconCreator.enableTransitions = true;
});
patchWithCleanup(browser, {
setTimeout: (fn) => fn(),
clearTimeout: () => {},
});
patchWithCleanup(AutoComplete, {
timeout: 0,
});
setupViewRegistries();
serviceRegistry.add("http", makeFakeHTTPService(), { force: true });
serviceRegistry.add("ui", makeFakeUIService(), { force: true });
serverData = {
models: {
"ir.model": {
fields: {
display_name: { type: "char" },
transient: { type: "boolean" },
abstract: { type: "boolean" },
},
records: [{ id: 69, display_name: "The Value" }],
},
},
};
});
QUnit.test("app creator: standard flow with model creation", async (assert) => {
assert.expect(39);
const fakeHttpRequestService = {
start() {
return async (route) => {
if (route === "/web/binary/upload_attachment") {
assert.step(route);
return `[{ "id": 666 }]`;
}
};
},
};
const { state } = await createAppCreator({
serverData,
services: {
ui: makeFakeUIService({
block: () => assert.step("UI blocked"),
unblock: () => assert.step("UI unblocked"),
}),
httpRequest: fakeHttpRequestService,
http: makeFakeHTTPService(null, async (route) => {
if (route === "/web/binary/upload_attachment") {
assert.step(route);
return `[{ "id": 666 }]`;
}
}),
},
onNewAppCreated: () => assert.step("new-app-created"),
mockRPC: async (route, params) => {
if (typeof route === "object") {
assert.strictEqual(route.model, "ir.attachment");
return [{ datas: sampleIconUrl }];
}
if (route === "/web_studio/create_new_app") {
const { app_name, menu_name, model_choice, model_id, model_options } = params;
assert.strictEqual(app_name, "Kikou", "App name should be correct");
assert.strictEqual(menu_name, "Petite Perruche", "Menu name should be correct");
assert.notOk(model_id, "Should not have a model id");
assert.strictEqual(model_choice, "new", "Model choice should be 'new'");
assert.deepEqual(
model_options,
["use_partner", "use_sequence", "use_mail", "use_active"],
"Model options should include the defaults and 'use_partner'"
);
return true;
}
if (route === "/web/dataset/call_kw/ir.attachment/read") {
assert.strictEqual(params.model, "ir.attachment");
return [{ datas: sampleIconUrl }];
}
},
});
// step: 'welcome'
assert.strictEqual(state.step, "welcome", "Current step should be welcome");
assert.containsNone(
target,
".o_web_studio_app_creator_previous",
"Previous button should not be rendered at step welcome"
);
assert.hasClass(
target.querySelector(".o_web_studio_app_creator_next"),
"is_ready",
"Next button should be ready at step welcome"
);
// go to step: 'app'
await click(target, ".o_web_studio_app_creator_next");
assert.strictEqual(state.step, "app", "Current step should be app");
assert.containsOnce(
target,
".o_web_studio_icon_creator .o_web_studio_selectors",
"Icon creator should be rendered in edit mode"
);
// Icon creator interactions
const icon = target.querySelector(".o_app_icon i");
// Initial state: take default values
assert.strictEqual(
target.querySelector(".o_app_icon").style.backgroundColor,
"rgb(52, 73, 94)",
"default background color: #34495e"
);
assert.strictEqual(icon.style.color, "rgb(241, 196, 15)", "default color: #f1c40f");
assert.hasClass(icon, "fa fa-diamond", "default icon class: diamond");
await click(target.getElementsByClassName("o_web_studio_selector")[0]);
assert.containsOnce(target, ".o_web_studio_palette", "the first palette should be open");
await triggerEvent(target, ".o_web_studio_palette", "mouseleave");
assert.containsNone(
target,
".o_web_studio_palette",
"leaving palette with mouse should close it"
);
await click(target.querySelectorAll(".o_web_studio_selectors > .o_web_studio_selector")[0]);
await click(target.querySelectorAll(".o_web_studio_selectors > .o_web_studio_selector")[1]);
assert.containsOnce(
target,
".o_web_studio_palette",
"opening another palette should close the first"
);
await click(target.querySelectorAll(".o_web_studio_palette div")[2]);
await click(target.querySelectorAll(".o_web_studio_selectors > .o_web_studio_selector")[2]);
await click(target.querySelectorAll(".o_web_studio_icons_library div")[43]);
await triggerEvent(target, ".o_web_studio_icons_library", "mouseleave");
assert.containsNone(
target,
".o_web_studio_palette",
"no palette should be visible anymore"
);
assert.strictEqual(
target.querySelectorAll(".o_web_studio_selector")[1].style.backgroundColor,
"rgb(0, 222, 201)", // translation of #00dec9
"color selector should have changed"
);
assert.strictEqual(
icon.style.color,
"rgb(0, 222, 201)",
"icon color should also have changed"
);
assert.hasClass(
target.querySelector(".o_web_studio_selector i"),
"fa fa-heart",
"class selector should have changed"
);
assert.hasClass(icon, "fa fa-heart", "icon class should also have changed");
// Click and upload on first link: upload a file
// mimic the event triggered by the upload (jquery)
// we do not use the triggerEvent helper as it requires the element to be visible,
// which isn't the case here (and this is valid)
target.querySelector(".o_web_studio_upload input").dispatchEvent(new Event("change"));
await nextTick();
assert.strictEqual(
state.data.iconData.uploaded_attachment_id,
666,
"attachment id should have been given by the RPC"
);
assert.strictEqual(
target.querySelector(".o_web_studio_uploaded_image").style.backgroundImage,
`url("data:image/png;base64,${sampleIconUrl}")`,
"icon should take the updated attachment data"
);
// try to go to step 'model'
await click(target, ".o_web_studio_app_creator_next");
const appNameInput = target.querySelector('input[name="appName"]').parentNode;
assert.strictEqual(
state.step,
"app",
"Current step should not be update because the input is not filled"
);
assert.hasClass(
appNameInput,
"o_web_studio_field_warning",
"Input should be in warning mode"
);
await editInput(target, 'input[name="appName"]', "Kikou");
assert.doesNotHaveClass(
appNameInput,
"o_web_studio_field_warning",
"Input shouldn't be in warning mode anymore"
);
// step: 'model'
await click(target, ".o_web_studio_app_creator_next");
assert.strictEqual(state.step, "model", "Current step should be model");
assert.containsNone(
target,
".o_web_studio_selectors",
"Icon creator should be rendered in readonly mode"
);
// try to go to next step
await click(target, ".o_web_studio_app_creator_next");
assert.hasClass(
target.querySelector('input[name="menuName"]').parentNode,
"o_web_studio_field_warning",
"Input should be in warning mode"
);
await editInput(target, 'input[name="menuName"]', "Petite Perruche");
// go to next step (model configuration)
await click(target, ".o_web_studio_app_creator_next");
assert.strictEqual(
state.step,
"model_configuration",
"Current step should be model_configuration"
);
assert.containsOnce(
target,
'input[name="use_active"]',
"Debug options should be visible without debug mode"
);
// check an option
await click(target, 'input[name="use_partner"]');
assert.containsOnce(
target,
'input[name="use_partner"]:checked',
"Option should have been checked"
);
// go back then go forward again
await click(target, ".o_web_studio_model_configurator_previous");
await click(target, ".o_web_studio_app_creator_next");
// options should have been reset
assert.containsNone(
target,
'input[name="use_partner"]:checked',
"Options should have been reset by going back then forward"
);
// check the option again, we want to test it in the RPC
await click(target, 'input[name="use_partner"]');
await click(target, ".o_web_studio_model_configurator_next");
assert.verifySteps([
"/web/binary/upload_attachment",
"UI blocked",
"new-app-created",
"UI unblocked",
]);
});
QUnit.test("app creator: has 'lines' options to auto-create a one2many", async (assert) => {
assert.expect(7);
await createAppCreator({
serverData,
startStep: "model_configuration",
mockRPC: async (route, params) => {
if (route === "/web_studio/create_new_app") {
const { app_name, menu_name, model_choice, model_id, model_options } = params;
assert.strictEqual(app_name, "testApp", "App name should be correct");
assert.strictEqual(menu_name, "testMenu", "Menu name should be correct");
assert.notOk(model_id, "Should not have a model id");
assert.strictEqual(model_choice, "new", "Model choice should be 'new'");
assert.deepEqual(
model_options,
["lines", "use_sequence", "use_mail", "use_active"],
"Model options should include the defaults and 'lines'"
);
return true;
}
},
});
assert.containsOnce(
target,
".o_web_studio_model_configurator_option input[type='checkbox'][name='lines'][id='lines']"
);
assert.strictEqual(
target.querySelector("label[for='lines']").textContent,
"LinesAdd details to your records with an embedded list view"
);
await click(
target,
".o_web_studio_model_configurator_option input[type='checkbox'][name='lines']"
);
await click(target, ".o_web_studio_model_configurator_next");
});
QUnit.test("app creator: debug flow with existing model", async (assert) => {
assert.expect(17);
patchWithCleanup(odoo, { debug: "1" });
const { state } = await createAppCreator({
serverData,
startStep: "model",
async mockRPC(route, params) {
switch (route) {
case "/web/dataset/call_kw/ir.model/name_search": {
assert.deepEqual(params.kwargs.args, [
"&",
"&",
["transient", "=", false],
["abstract", "=", false],
"!",
["id", "in", []],
]);
assert.step(route);
assert.strictEqual(
params.model,
"ir.model",
"request should target the right model"
);
break;
}
case "/web_studio/create_new_app": {
assert.step(route);
assert.strictEqual(
params.model_id,
69,
"model id should be the one provided"
);
return true;
}
}
},
});
await editInput(target, "input[name='menuName']", "testMenuName");
let buttonNext = target.querySelector("button.o_web_studio_app_creator_next");
assert.hasClass(buttonNext, "is_ready");
await editInput(target, 'input[name="menuName"]', "Petite Perruche");
// check the 'new model' radio
await click(target, 'input[name="model_choice"][value="new"]');
// go to next step (model configuration)
await click(target, ".o_web_studio_app_creator_next");
assert.strictEqual(
state.step,
"model_configuration",
"Current step should be model_configuration"
);
assert.containsOnce(
target,
'input[name="use_active"]',
"Debug options should be visible in debug mode"
);
// go back, we want the 'existing model flow'
await click(target, ".o_web_studio_model_configurator_previous");
// since we came back, we need to update our buttonNext ref - the querySelector is not live
buttonNext = target.querySelector("button.o_web_studio_app_creator_next");
// check the 'existing model' radio
await click(target, 'input[name="model_choice"][value="existing"]');
assert.doesNotHaveClass(
target.querySelector(".o_web_studio_menu_creator_model"),
"o_web_studio_field_warning"
);
assert.doesNotHaveClass(buttonNext, "is_ready");
assert.containsOnce(
target,
".o_record_selector",
"There should be a many2one to select a model"
);
await click(buttonNext);
assert.hasClass(
target.querySelector(".o_web_studio_menu_creator_model"),
"o_web_studio_field_warning"
);
assert.doesNotHaveClass(buttonNext, "is_ready");
await editInput(target, ".o_record_selector input", "The");
await click(target.querySelector(".o-autocomplete--dropdown-item"));
assert.strictEqual(
target.querySelector(".o_record_selector input").value,
"The Value",
"Correct value should be selected."
);
assert.doesNotHaveClass(
target.querySelector(".o_web_studio_menu_creator_model"),
"o_web_studio_field_warning"
);
assert.hasClass(buttonNext, "is_ready");
await click(buttonNext);
assert.verifySteps([
"/web/dataset/call_kw/ir.model/name_search",
"/web_studio/create_new_app",
]);
});
QUnit.test('app creator: navigate through steps using "ENTER"', async (assert) => {
assert.expect(12);
const { state } = await createAppCreator({
serverData,
services: {
ui: makeFakeUIService({
block: () => assert.step("UI blocked"),
unblock: () => assert.step("UI unblocked"),
}),
},
onNewAppCreated: () => assert.step("new-app-created"),
async mockRPC(route, { app_name, menu_name, model_id }) {
if (route === "/web_studio/create_new_app") {
assert.strictEqual(app_name, "Kikou", "App name should be correct");
assert.strictEqual(menu_name, "Petite Perruche", "Menu name should be correct");
assert.notOk(model_id, "Should not have a model id");
return true;
}
},
});
// step: 'welcome'
assert.strictEqual(state.step, "welcome", "Current step should be set to welcome");
// go to step 'app'
await triggerEvent(document, null, "keydown", { key: "Enter" });
assert.strictEqual(state.step, "app", "Current step should be set to app");
// try to go to step 'model'
await triggerEvent(document, null, "keydown", { key: "Enter" });
assert.strictEqual(
state.step,
"app",
"Current step should not be update because the input is not filled"
);
await editInput(target, 'input[name="appName"]', "Kikou");
// go to step 'model'
await triggerEvent(document, null, "keydown", { key: "Enter" });
assert.strictEqual(state.step, "model", "Current step should be model");
// try to create app
await triggerEvent(document, null, "keydown", { key: "Enter" });
assert.hasClass(
target.querySelector('input[name="menuName"]').parentNode,
"o_web_studio_field_warning",
"a warning should be displayed on the input"
);
await editInput(target, 'input[name="menuName"]', "Petite Perruche");
await triggerEvent(document, null, "keydown", { key: "Enter" });
await triggerEvent(document, null, "keydown", { key: "Enter" });
assert.verifySteps(["UI blocked", "new-app-created", "UI unblocked"]);
});
});