Files
jikimo_sf/web_studio/static/tests/app_creator_tests.js
2023-04-14 17:42:23 +08:00

503 lines
19 KiB
JavaScript

/** @odoo-module **/
import { registerCleanup } from "@web/../tests/helpers/cleanup";
import {
click,
findChildren,
getFixture,
nextTick,
triggerEvent,
} from "@web/../tests/helpers/utils";
import { AppCreatorWrapper } from "@web_studio/client_action/app_creator/app_creator";
import { IconCreator } from "@web_studio/client_action/icon_creator/icon_creator";
import testUtils from "web.test_utils";
const { Component } = owl;
const sampleIconUrl = "/web_enterprise/Parent.src/img/default_icon_app.png";
const createAppCreator = async ({ env, rpc, state, onNewAppCreated }) => {
onNewAppCreated = onNewAppCreated || (() => {});
const cleanUp = await testUtils.mock.addMockEnvironmentOwl(Component, {
debug: QUnit.config.debug,
env,
mockRPC: rpc,
});
const target = getFixture();
const wrapper = new AppCreatorWrapper(null, { onNewAppCreated });
await wrapper.prependTo(target);
const { component } = findChildren(wrapper.appCreatorComponent);
if (state) {
Object.assign(component.state, state);
await nextTick();
}
registerCleanup(() => {
wrapper.destroy();
cleanUp();
});
return { state: component.state, target };
};
const editInput = async (el, selector, value) => {
const target = el.querySelector(selector);
target.value = value;
await triggerEvent(target, null, "input");
};
QUnit.module("Studio", (hooks) => {
hooks.beforeEach(() => {
IconCreator.enableTransitions = false;
registerCleanup(() => {
IconCreator.enableTransitions = true;
});
});
QUnit.module("AppCreator");
QUnit.test("app creator: standard flow with model creation", async (assert) => {
assert.expect(39);
const { state, target } = await createAppCreator({
env: {
services: {
ui: {
block: () => assert.step("UI blocked"),
unblock: () => assert.step("UI unblocked"),
},
async httpRequest(route) {
if (route === "/web/binary/upload_attachment") {
assert.step(route);
return `[{ "id": 666 }]`;
}
},
http: {
async post(route) {
if (route === "/web/binary/upload_attachment") {
assert.step(route);
return `[{ "id": 666 }]`;
}
},
},
},
},
onNewAppCreated: () => assert.step("new-app-created"),
async rpc(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, "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'"
);
}
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.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_app_creator_field_warning",
"Input should be in warning mode"
);
await editInput(target, 'input[name="appName"]', "Kikou");
assert.doesNotHaveClass(
appNameInput,
"o_web_studio_app_creator_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_app_creator_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);
const { target } = await createAppCreator({
env: {
services: {
ui: { block: () => {}, unblock: () => {} },
},
},
rpc: 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'"
);
}
},
});
await click(target, ".o_web_studio_app_creator_next");
await editInput(target, "input[id='appName']", "testApp");
await click(target, ".o_web_studio_app_creator_next");
await editInput(target, "input[id='menuName']", "testMenu");
await click(target, ".o_web_studio_app_creator_next");
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(16);
const { state, target } = await createAppCreator({
env: {
isDebug: () => true,
services: {
ui: { block: () => {}, unblock: () => {} },
},
},
async rpc(route, params) {
assert.step(route);
switch (route) {
case "/web/dataset/call_kw/ir.model/name_search": {
assert.strictEqual(
params.model,
"ir.model",
"request should target the right model"
);
return [[69, "The Value"]];
}
case "/web_studio/create_new_app": {
assert.strictEqual(
params.model_id,
69,
"model id should be the one provided"
);
}
}
},
state: {
menuName: "Kikou",
step: "model",
},
});
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_app_creator_model"),
"o_web_studio_app_creator_field_warning"
);
assert.doesNotHaveClass(buttonNext, "is_ready");
assert.containsOnce(
target,
".o_field_many2one",
"There should be a many2one to select a model"
);
await click(buttonNext);
assert.hasClass(
target.querySelector(".o_web_studio_app_creator_model"),
"o_web_studio_app_creator_field_warning"
);
assert.doesNotHaveClass(buttonNext, "is_ready");
await click(target, ".o_field_many2one input");
await click(document.querySelector(".ui-menu-item-wrapper"));
assert.strictEqual(target.querySelector(".o_field_many2one input").value, "The Value");
assert.doesNotHaveClass(
target.querySelector(".o_web_studio_app_creator_model"),
"o_web_studio_app_creator_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, target } = await createAppCreator({
env: {
services: {
ui: {
block: () => assert.step("UI blocked"),
unblock: () => assert.step("UI unblocked"),
},
},
},
onNewAppCreated: () => assert.step("new-app-created"),
async rpc(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");
}
},
});
// step: 'welcome'
assert.strictEqual(state.step, "welcome", "Current step should be set to 1");
// 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_app_creator_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"]);
});
});