mirror of
https://github.com/bringout/oca-ocb-test.git
synced 2026-04-23 03:42:01 +02:00
19.0 vanilla
This commit is contained in:
parent
38c6088dcc
commit
d9452d2060
243 changed files with 30797 additions and 10815 deletions
|
|
@ -21,24 +21,30 @@ models which only purpose is to run tests.""",
|
|||
'data/test_website_demo.xml',
|
||||
],
|
||||
'data': [
|
||||
'security/test_website_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'views/templates.xml',
|
||||
'views/test_model_multi_website_views.xml',
|
||||
'views/test_model_views.xml',
|
||||
'data/test_website_data.xml',
|
||||
'security/test_website_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
'installable': True,
|
||||
'assets': {
|
||||
'test_website.test_bundle': [
|
||||
'http://test.external.link/javascript1.js',
|
||||
'/web/static/src/libs/fontawesome/css/font-awesome.css',
|
||||
'http://test.external.link/style1.css',
|
||||
'/web/static/src/module_loader.js',
|
||||
'http://test.external.link/javascript2.js',
|
||||
'http://test.external.link/style2.css',
|
||||
],
|
||||
'web.assets_frontend': [
|
||||
'test_website/static/src/js/test_error.js',
|
||||
'test_website/static/src/interactions/**/*',
|
||||
],
|
||||
'web.assets_tests': [
|
||||
'test_website/static/tests/tours/*',
|
||||
],
|
||||
'web.qunit_suite_tests': [
|
||||
'test_website/static/tests/*.js',
|
||||
],
|
||||
},
|
||||
'author': 'Odoo S.A.',
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,12 @@ class WebsiteTest(Home):
|
|||
def test_view(self, **kwargs):
|
||||
return request.render('test_website.test_view')
|
||||
|
||||
@http.route('/test_view_access_error', type='http', auth='public', website=True, sitemap=False)
|
||||
def test_view_access_error(self, **kwargs):
|
||||
public = self.env.ref('base.public_user')
|
||||
record = self.env.ref('test_website.test_model_exposed_record_not_published')
|
||||
return request.render('test_website.test_view_access_error', {'record': record.with_user(public)})
|
||||
|
||||
@http.route('/ignore_args/converteronly/<string:a>', type='http', auth="public", website=True, sitemap=False)
|
||||
def test_ignore_args_converter_only(self, a):
|
||||
return request.make_response(json.dumps(dict(a=a, kw=None)))
|
||||
|
|
@ -42,7 +48,7 @@ class WebsiteTest(Home):
|
|||
|
||||
@http.route('/multi_company_website', type='http', auth="public", website=True, sitemap=False)
|
||||
def test_company_context(self):
|
||||
return request.make_response(json.dumps(request.context.get('allowed_company_ids')))
|
||||
return request.make_response(json.dumps(request.env.context.get('allowed_company_ids')))
|
||||
|
||||
@http.route('/test_lang_url/<model("res.country"):country>', type='http', auth='public', website=True, sitemap=False)
|
||||
def test_lang_url(self, **kwargs):
|
||||
|
|
@ -50,7 +56,7 @@ class WebsiteTest(Home):
|
|||
|
||||
# Test Session
|
||||
|
||||
@http.route('/test_get_dbname', type='json', auth='public', website=True, sitemap=False)
|
||||
@http.route('/test_get_dbname', type='jsonrpc', auth='public', website=True, sitemap=False)
|
||||
def test_get_dbname(self, **kwargs):
|
||||
return request.env.cr.dbname
|
||||
|
||||
|
|
@ -64,7 +70,7 @@ class WebsiteTest(Home):
|
|||
def test_user_error_http(self, **kwargs):
|
||||
raise UserError("This is a user http test")
|
||||
|
||||
@http.route('/test_user_error_json', type='json', auth='public', website=True, sitemap=False)
|
||||
@http.route('/test_user_error_json', type='jsonrpc', auth='public', website=True, sitemap=False)
|
||||
def test_user_error_json(self, **kwargs):
|
||||
raise UserError("This is a user rpc test")
|
||||
|
||||
|
|
@ -72,11 +78,11 @@ class WebsiteTest(Home):
|
|||
def test_validation_error_http(self, **kwargs):
|
||||
raise ValidationError("This is a validation http test")
|
||||
|
||||
@http.route('/test_validation_error_json', type='json', auth='public', website=True, sitemap=False)
|
||||
@http.route('/test_validation_error_json', type='jsonrpc', auth='public', website=True, sitemap=False)
|
||||
def test_validation_error_json(self, **kwargs):
|
||||
raise ValidationError("This is a validation rpc test")
|
||||
|
||||
@http.route('/test_access_error_json', type='json', auth='public', website=True, sitemap=False)
|
||||
@http.route('/test_access_error_json', type='jsonrpc', auth='public', website=True, sitemap=False)
|
||||
def test_access_error_json(self, **kwargs):
|
||||
raise AccessError("This is an access rpc test")
|
||||
|
||||
|
|
@ -84,7 +90,7 @@ class WebsiteTest(Home):
|
|||
def test_access_error_http(self, **kwargs):
|
||||
raise AccessError("This is an access http test")
|
||||
|
||||
@http.route('/test_missing_error_json', type='json', auth='public', website=True, sitemap=False)
|
||||
@http.route('/test_missing_error_json', type='jsonrpc', auth='public', website=True, sitemap=False)
|
||||
def test_missing_error_json(self, **kwargs):
|
||||
raise MissingError("This is a missing rpc test")
|
||||
|
||||
|
|
@ -92,7 +98,7 @@ class WebsiteTest(Home):
|
|||
def test_missing_error_http(self, **kwargs):
|
||||
raise MissingError("This is a missing http test")
|
||||
|
||||
@http.route('/test_internal_error_json', type='json', auth='public', website=True, sitemap=False)
|
||||
@http.route('/test_internal_error_json', type='jsonrpc', auth='public', website=True, sitemap=False)
|
||||
def test_internal_error_json(self, **kwargs):
|
||||
raise werkzeug.exceptions.InternalServerError()
|
||||
|
||||
|
|
@ -100,7 +106,7 @@ class WebsiteTest(Home):
|
|||
def test_internal_error_http(self, **kwargs):
|
||||
raise werkzeug.exceptions.InternalServerError()
|
||||
|
||||
@http.route('/test_access_denied_json', type='json', auth='public', website=True, sitemap=False)
|
||||
@http.route('/test_access_denied_json', type='jsonrpc', auth='public', website=True, sitemap=False)
|
||||
def test_denied_error_json(self, **kwargs):
|
||||
raise AccessDenied("This is an access denied rpc test")
|
||||
|
||||
|
|
@ -131,7 +137,7 @@ class WebsiteTest(Home):
|
|||
return 'Basic Controller Content'
|
||||
|
||||
# Test Redirects
|
||||
@http.route(['/test_website/country/<model("res.country"):country>'], type='http', auth="public", website=True, sitemap=False)
|
||||
@http.route(['/test_website/country/<model("res.country"):country>'], type='http', auth="public", website=True, sitemap=True)
|
||||
def test_model_converter_country(self, country, **kw):
|
||||
return request.render('test_website.test_redirect_view', {'country': country})
|
||||
|
||||
|
|
@ -141,6 +147,16 @@ class WebsiteTest(Home):
|
|||
|
||||
@http.route(['/test_website/model_item/<int:record_id>'], type='http', methods=['GET'], auth="public", website=True, sitemap=False)
|
||||
def test_model_item(self, record_id):
|
||||
record = request.env['test.model'].browse(record_id)
|
||||
values = {
|
||||
'record': record,
|
||||
'main_object': record,
|
||||
'tag': record.tag_id,
|
||||
}
|
||||
return request.render("test_website.model_item", values)
|
||||
|
||||
@http.route(['/test_website/model_item_sudo/<int:record_id>'], type='http', methods=['GET'], auth="public", website=True, sitemap=False)
|
||||
def test_model_item_sudo(self, record_id):
|
||||
values = {
|
||||
'record': request.env['test.model'].sudo().browse(record_id),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,23 @@
|
|||
</record>
|
||||
|
||||
<!-- SOME DEFAULT TEST.MODEL RECORDS WITH DIFFERENT WEBSITE_ID -->
|
||||
<record id="test_tag_generic" model="test.tag">
|
||||
<field name="name">Test Tag</field>
|
||||
</record>
|
||||
<record id="test_tag_2" model="test.tag">
|
||||
<field name="name">Test Tag #2</field>
|
||||
</record>
|
||||
<record id="test_tag_3" model="test.tag">
|
||||
<field name="name">Test Tag #3</field>
|
||||
</record>
|
||||
<record id="test_model_generic" model="test.model">
|
||||
<field name="name">Test Model</field>
|
||||
<field name="tag_id" ref="test_website.test_tag_generic"/>
|
||||
</record>
|
||||
<record id="test_submodel_generic" model="test.submodel">
|
||||
<field name="name">Test Submodel</field>
|
||||
<field name="test_model_id" ref="test_website.test_model_generic"/>
|
||||
<field name="tag_id" ref="test_website.test_tag_generic"/>
|
||||
</record>
|
||||
<record id="test_model_multi_generic" model="test.model.multi.website">
|
||||
<field name="name">Test Multi Model Generic</field>
|
||||
|
|
@ -62,9 +77,9 @@
|
|||
<div class="row">
|
||||
<ul class="list-group http_error col-6">
|
||||
<li class="list-group-item list-group-item-primary"><h2>http Errors</h2></li>
|
||||
<li class="list-group-item"><a href="/test_user_error_http">http UserError (400)</a></li>
|
||||
<li class="list-group-item"><a href="/test_validation_error_http">http ValidationError (400)</a></li>
|
||||
<li class="list-group-item"><a href="/test_missing_error_http">http MissingError (400)</a></li>
|
||||
<li class="list-group-item"><a href="/test_user_error_http">http UserError (422)</a></li>
|
||||
<li class="list-group-item"><a href="/test_validation_error_http">http ValidationError (422)</a></li>
|
||||
<li class="list-group-item"><a href="/test_missing_error_http">http MissingError (404)</a></li>
|
||||
<li class="list-group-item"><a href="/test_access_error_http">http AccessError (403)</a></li>
|
||||
<li class="list-group-item"><a href="/test_access_denied_http">http AccessDenied (403)</a></li>
|
||||
<li class="list-group-item"><a href="/test_internal_error_http">http InternalServerError (500)</a></li>
|
||||
|
|
@ -109,6 +124,9 @@
|
|||
<p>placeholder</p>
|
||||
</xpath>
|
||||
</template>
|
||||
<template id="test_view_access_error">
|
||||
<p><t t-out="record.name"/></p>
|
||||
</template>
|
||||
|
||||
<!-- RECORDS FOR MODULE OPERATION TESTS -->
|
||||
<template id="update_module_base_view">
|
||||
|
|
@ -117,8 +135,8 @@
|
|||
|
||||
<!-- RECORDS FOR REDIRECT TESTS -->
|
||||
<template id="test_redirect_view">
|
||||
<t t-esc="country.name"/>
|
||||
<t t-if="not request.env.user._is_public()" t-esc="'Logged In'"/>
|
||||
<t t-out="country.name"/>
|
||||
<t t-if="not request.env.user._is_public()" t-out="'Logged In'"/>
|
||||
<!-- `href` is send through `url_for` for non editor users -->
|
||||
<a href="/test_website/country/andorra-1">I am a link</a>
|
||||
</template>
|
||||
|
|
@ -143,5 +161,13 @@
|
|||
<record id="test_model_record" model="test.model">
|
||||
<field name="name">Test Model Record</field>
|
||||
</record>
|
||||
<record id="test_model_exposed_record_published" model="test.model">
|
||||
<field name="name">Test Model Exposed Record (published)</field>
|
||||
<field name="website_published">True</field>
|
||||
</record>
|
||||
<record id="test_model_exposed_record_not_published" model="test.model">
|
||||
<field name="name">Test Model Exposed Record (not published)</field>
|
||||
<field name="website_published">False</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,2 @@
|
|||
from . import model
|
||||
from . import res_config_settings
|
||||
from . import website
|
||||
|
|
|
|||
|
|
@ -2,6 +2,13 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.tools.translate import html_translate
|
||||
|
||||
|
||||
class Website(models.Model):
|
||||
_inherit = "website"
|
||||
|
||||
name_translated = fields.Char(translate=True)
|
||||
|
||||
|
||||
class TestModel(models.Model):
|
||||
|
|
@ -13,14 +20,24 @@ class TestModel(models.Model):
|
|||
]
|
||||
_description = 'Website Model Test'
|
||||
|
||||
name = fields.Char(required=1)
|
||||
name = fields.Char(required=True, translate=True)
|
||||
submodel_ids = fields.One2many('test.submodel', 'test_model_id', "Submodels")
|
||||
website_description = fields.Html(
|
||||
string="Description for the website",
|
||||
translate=html_translate,
|
||||
sanitize_overridable=True,
|
||||
sanitize_attributes=False,
|
||||
sanitize_form=False,
|
||||
default="""<div class="o_test_website_description"><p>A simple website description content.</p></div>""",
|
||||
)
|
||||
tag_id = fields.Many2one('test.tag')
|
||||
|
||||
@api.model
|
||||
def _search_get_detail(self, website, order, options):
|
||||
return {
|
||||
'model': 'test.model',
|
||||
'base_domain': [],
|
||||
'search_fields': ['name'],
|
||||
'search_fields': ['name', 'submodel_ids.name', 'submodel_ids.tag_id.name'],
|
||||
'fetch_fields': ['name'],
|
||||
'mapping': {
|
||||
'name': {'name': 'name', 'type': 'text', 'match': True},
|
||||
|
|
@ -35,6 +52,22 @@ class TestModel(models.Model):
|
|||
return self.env['website'].get_client_action(f'/test_model/{self.id}')
|
||||
|
||||
|
||||
class TestSubmodel(models.Model):
|
||||
_name = 'test.submodel'
|
||||
_description = 'Website Submodel Test'
|
||||
|
||||
name = fields.Char(required=True)
|
||||
test_model_id = fields.Many2one('test.model')
|
||||
tag_id = fields.Many2one('test.tag')
|
||||
|
||||
|
||||
class TestTag(models.Model):
|
||||
_name = 'test.tag'
|
||||
_description = 'Website Tag Test'
|
||||
|
||||
name = fields.Char(required=True)
|
||||
|
||||
|
||||
class TestModelMultiWebsite(models.Model):
|
||||
_name = 'test.model.multi.website'
|
||||
_inherit = [
|
||||
|
|
@ -42,9 +75,21 @@ class TestModelMultiWebsite(models.Model):
|
|||
]
|
||||
_description = 'Multi Website Model Test'
|
||||
|
||||
name = fields.Char(required=1)
|
||||
name = fields.Char(required=True)
|
||||
# `cascade` is needed as there is demo data for this model which are bound
|
||||
# to website 2 (demo website). But some tests are unlinking the website 2,
|
||||
# which would fail if the `cascade` is not set. Note that the website 2 is
|
||||
# never set on any records in all other modules.
|
||||
website_id = fields.Many2one('website', string='Website', ondelete='cascade')
|
||||
|
||||
|
||||
class TestModelExposed(models.Model):
|
||||
_name = 'test.model.exposed'
|
||||
_inherit = [
|
||||
'website.seo.metadata',
|
||||
'website.published.mixin',
|
||||
]
|
||||
_description = 'Website Model Test Exposed'
|
||||
_rec_name = "name"
|
||||
|
||||
name = fields.Char()
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models
|
||||
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = 'res.config.settings'
|
||||
|
||||
def action_website_test_setting(self):
|
||||
return self.env['website'].get_client_action('/')
|
||||
|
|
@ -1,12 +1,16 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models
|
||||
from odoo import fields, models
|
||||
from odoo.exceptions import AccessError
|
||||
|
||||
|
||||
class Website(models.Model):
|
||||
_inherit = "website"
|
||||
|
||||
some_translatable_field = fields.Char(string="A translatable field",
|
||||
translate=True, default='something')
|
||||
|
||||
def _search_get_details(self, search_type, order, options):
|
||||
result = super()._search_get_details(search_type, order, options)
|
||||
if search_type in ['test']:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,20 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_test_model,access_test_model,model_test_model,,1,0,0,0
|
||||
access_test_model_multi_website,access_test_model_multi_website,model_test_model_multi_website,,1,0,0,0
|
||||
access_test_model_public,access_test_model,model_test_model,base.group_public,1,0,0,0
|
||||
access_test_model_portal,access_test_model,model_test_model,base.group_portal,1,0,0,0
|
||||
access_test_model_employee,access_test_model,model_test_model,base.group_user,1,0,0,0
|
||||
access_test_model_admin,access_test_model,model_test_model,base.group_system,1,1,1,1
|
||||
access_test_submodel_public,access_test_submodel,model_test_submodel,base.group_public,1,0,0,0
|
||||
access_test_submodel_portal,access_test_submodel,model_test_submodel,base.group_portal,1,0,0,0
|
||||
access_test_submodel_employee,access_test_submodel,model_test_submodel,base.group_user,1,0,0,0
|
||||
access_test_tag_public,access_test_tag,model_test_tag,base.group_public,1,0,0,0
|
||||
access_test_tag_portal,access_test_tag,model_test_tag,base.group_portal,1,0,0,0
|
||||
access_test_tag_employee,access_test_tag,model_test_tag,base.group_user,1,0,0,0
|
||||
access_test_model_test_admin,access_test_model,model_test_model,test_website.group_test_website_admin,1,1,1,1
|
||||
access_test_model_exposed_employee,access_test_model_exposed,model_test_model_exposed,base.group_user,1,0,0,0
|
||||
access_test_model_exposed_test_admin,access_test_model_exposed,model_test_model_exposed,test_website.group_test_website_admin,1,1,1,1
|
||||
access_test_model_multi_website_public,access_test_model_multi_website,model_test_model_multi_website,base.group_public,1,0,0,0
|
||||
access_test_model_multi_website_portal,access_test_model_multi_website,model_test_model_multi_website,base.group_portal,1,0,0,0
|
||||
access_test_model_multi_website_employee,access_test_model_multi_website,model_test_model_multi_website,base.group_user,1,0,0,0
|
||||
access_test_model_multi_website_test_admin,access_test_model_multi_website,model_test_model_multi_website,test_website.group_test_website_admin,1,1,1,1
|
||||
access_test_model_multi_website,access_test_model_multi_website,model_test_model_multi_website,base.group_user,1,0,0,0
|
||||
access_test_model_tester,access_test_model,model_test_model,test_website.group_test_website_tester,1,1,1,1
|
||||
|
|
|
|||
|
|
|
@ -1,16 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record model="ir.module.category" id="test_website.module_category_test_website">
|
||||
<field name="name">Tests about Website with additional model</field>
|
||||
<field name="sequence">24</field>
|
||||
|
||||
<record id="test_website.group_test_website_admin" model="res.groups">
|
||||
<field name="name">Test Administrator</field>
|
||||
</record>
|
||||
|
||||
<record id="group_test_website_tester" model="res.groups">
|
||||
<field name="name">Tester</field>
|
||||
<field name="category_id" ref="test_website.module_category_test_website"/>
|
||||
<field name="comment">Tests about Website with additional model</field>
|
||||
</record>
|
||||
|
||||
<record id="base.user_admin" model="res.users">
|
||||
<field name="groups_id" eval="[(4, ref('test_website.group_test_website_tester'))]"/>
|
||||
<field name="group_ids" eval="[(4,ref('test_website.group_test_website_tester'))]"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
import { Interaction } from "@web/public/interaction";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
import { rpc } from "@web/core/network/rpc";
|
||||
|
||||
export class TestError extends Interaction {
|
||||
static selector = ".rpc_error a";
|
||||
dynamicContent = {
|
||||
_root: { "t-on-click.prevent": () => rpc(this.el.getAttribute("href")) },
|
||||
}
|
||||
}
|
||||
|
||||
registry
|
||||
.category("public.interactions")
|
||||
.add("test_website.test_error", TestError);
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
odoo.define('website_forum.test_error', function (require) {
|
||||
'use strict';
|
||||
|
||||
var publicWidget = require('web.public.widget');
|
||||
|
||||
publicWidget.registry.testError = publicWidget.Widget.extend({
|
||||
selector: '.rpc_error',
|
||||
events: {
|
||||
'click a': '_onRpcErrorClick',
|
||||
},
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Handlers
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* make a rpc call with the href of the DOM element clicked
|
||||
* @private
|
||||
* @param {Event} ev
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_onRpcErrorClick: function (ev) {
|
||||
ev.preventDefault();
|
||||
var $link = $(ev.currentTarget);
|
||||
return this._rpc({
|
||||
route: $link.attr('href'),
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -1,137 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { FileSelectorControlPanel } from '@web_editor/components/media_dialog/file_selector';
|
||||
import { getFixture, patchWithCleanup } from "@web/../tests/helpers/utils";
|
||||
import { HtmlField } from '@web_editor/js/backend/html_field';
|
||||
import {registry} from '@web/core/registry';
|
||||
import testUtils from 'web.test_utils';
|
||||
import { uploadService } from '@web_editor/components/upload_progress_toast/upload_service';
|
||||
import { unsplashService } from '@web_unsplash/services/unsplash_service';
|
||||
import { createWebClient, doAction } from "@web/../tests/webclient/helpers";
|
||||
import weTestUtils from 'web_editor.test_utils';
|
||||
import Wysiwyg from 'web_editor.wysiwyg';
|
||||
|
||||
const { useEffect } = owl;
|
||||
|
||||
QUnit.module('field html file upload', {
|
||||
beforeEach: function () {
|
||||
this.data = weTestUtils.wysiwygData({
|
||||
'mail.compose.message': {
|
||||
fields: {
|
||||
display_name: {
|
||||
string: "Displayed name",
|
||||
type: "char"
|
||||
},
|
||||
body: {
|
||||
string: "Message Body inline (to send)",
|
||||
type: "html"
|
||||
},
|
||||
attachment_ids: {
|
||||
string: "Attachments",
|
||||
type: "many2many",
|
||||
relation: "ir.attachment",
|
||||
}
|
||||
},
|
||||
records: [{
|
||||
id: 1,
|
||||
display_name: "Some Composer",
|
||||
body: "Hello",
|
||||
attachment_ids: [],
|
||||
}],
|
||||
},
|
||||
});
|
||||
},
|
||||
}, function () {
|
||||
QUnit.test('media dialog: upload', async function (assert) {
|
||||
assert.expect(4);
|
||||
const onAttachmentChangeTriggered = testUtils.makeTestPromise();
|
||||
patchWithCleanup(HtmlField.prototype, {
|
||||
'_onAttachmentChange': function (event) {
|
||||
this._super(event);
|
||||
onAttachmentChangeTriggered.resolve(true);
|
||||
}
|
||||
});
|
||||
const defFileSelector = testUtils.makeTestPromise();
|
||||
const onChangeTriggered = testUtils.makeTestPromise();
|
||||
patchWithCleanup(FileSelectorControlPanel.prototype, {
|
||||
setup() {
|
||||
this._super();
|
||||
useEffect(() => {
|
||||
defFileSelector.resolve(true);
|
||||
}, () => []);
|
||||
},
|
||||
async onChangeFileInput() {
|
||||
this._super();
|
||||
onChangeTriggered.resolve(true);
|
||||
}
|
||||
});
|
||||
// create and load form view
|
||||
const serviceRegistry = registry.category("services");
|
||||
serviceRegistry.add("upload", uploadService);
|
||||
serviceRegistry.add("unsplash", unsplashService);
|
||||
const serverData = {
|
||||
models: this.data,
|
||||
};
|
||||
serverData.actions = {
|
||||
1: {
|
||||
id: 1,
|
||||
name: "test",
|
||||
res_model: "mail.compose.message",
|
||||
type: "ir.actions.act_window",
|
||||
views: [[false, "form"]],
|
||||
},
|
||||
};
|
||||
serverData.views = {
|
||||
"mail.compose.message,false,search": "<search></search>",
|
||||
"mail.compose.message,false,form": `
|
||||
<form>
|
||||
<field name="body" type="html"/>
|
||||
<field name="attachment_ids" widget="many2many_binary"/>
|
||||
</form>`,
|
||||
};
|
||||
const mockRPC = (route, args) => {
|
||||
if (route === "/web_editor/attachment/add_data") {
|
||||
return Promise.resolve({"id": 5, "name": "test.jpg", "description": false, "mimetype": "image/jpeg", "checksum": "7951a43bbfb08fd742224ada280913d1897b89ab",
|
||||
"url": false, "type": "binary", "res_id": 1, "res_model": "note.note", "public": false, "access_token": false,
|
||||
"image_src": "/web/image/1-a0e63e61/test.jpg", "image_width": 1, "image_height": 1, "original_id": false
|
||||
});
|
||||
}
|
||||
else if (route === "/web/dataset/call_kw/ir.attachment/generate_access_token") {
|
||||
return Promise.resolve(["129a52e1-6bf2-470a-830e-8e368b022e13"]);
|
||||
}
|
||||
};
|
||||
const webClient = await createWebClient({ serverData, mockRPC });
|
||||
await doAction(webClient, 1);
|
||||
//trigger wysiwyg mediadialog
|
||||
const fixture = getFixture();
|
||||
const formField = fixture.querySelector('.o_field_html[name="body"]');
|
||||
const textInput = formField.querySelector('.note-editable p');
|
||||
textInput.innerText = "test";
|
||||
const pText = $(textInput).contents()[0];
|
||||
Wysiwyg.setRange(pText, 1, pText, 2);
|
||||
await new Promise((resolve) => setTimeout(resolve)); //ensure fully set up
|
||||
const wysiwyg = $(textInput.parentElement).data('wysiwyg');
|
||||
wysiwyg.openMediaDialog();
|
||||
assert.ok(await Promise.race([defFileSelector, new Promise((res, _) => setTimeout(() => res(false), 400))]), "File Selector did not mount");
|
||||
// upload test
|
||||
const fileInputs = document.querySelectorAll(".o_select_media_dialog input.d-none.o_file_input");
|
||||
const fileB64 = '/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAABAAEDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD3+iiigD//2Q==';
|
||||
const fileBytes = new Uint8Array(atob(fileB64).split('').map(char => char.charCodeAt(0)));
|
||||
// redefine 'files' so we can put mock data in through js
|
||||
fileInputs.forEach((input) => Object.defineProperty(input, 'files', {
|
||||
value: [new File(fileBytes, "test.jpg", { type: 'image/jpeg' })],
|
||||
}));
|
||||
fileInputs.forEach(input => {
|
||||
input.dispatchEvent(new Event('change', {}));
|
||||
});
|
||||
|
||||
assert.ok(await Promise.race([onChangeTriggered, new Promise((res, _) => setTimeout(() => res(false), 400))]),
|
||||
"File change event was not triggered");
|
||||
assert.ok(await Promise.race([onAttachmentChangeTriggered, new Promise((res, _) => setTimeout(() => res(false), 400))]),
|
||||
"_onAttachmentChange was not called with the new attachment, necessary for unsused upload cleanup on backend");
|
||||
|
||||
// wait to check that dom is properly updated
|
||||
await new Promise((res, _) => setTimeout(() => res(false), 400));
|
||||
assert.ok(fixture.querySelector('.o_attachment[title="test.jpg"]'));
|
||||
});
|
||||
});
|
||||
|
|
@ -1,7 +1,4 @@
|
|||
odoo.define('test_website.custom_snippets', function (require) {
|
||||
'use strict';
|
||||
|
||||
const wTourUtils = require('website.tour_utils');
|
||||
import { insertSnippet, registerWebsitePreviewTour } from "@website/js/tours/tour_utils";
|
||||
|
||||
/**
|
||||
* The purpose of this tour is to check the custom snippets flow:
|
||||
|
|
@ -11,8 +8,8 @@ const wTourUtils = require('website.tour_utils');
|
|||
* -> customize banner (set text)
|
||||
* -> save banner as custom snippet
|
||||
* -> confirm save
|
||||
* -> ensure custom snippet is available
|
||||
* -> drag custom snippet
|
||||
* -> ensure custom snippet is available in the "add snippet" dialog
|
||||
* -> add custom snippet into the page
|
||||
* -> ensure block appears as banner
|
||||
* -> ensure block appears as custom banner
|
||||
* -> rename custom banner
|
||||
|
|
@ -22,87 +19,89 @@ const wTourUtils = require('website.tour_utils');
|
|||
* -> ensure it was deleted
|
||||
*/
|
||||
|
||||
wTourUtils.registerWebsitePreviewTour('test_custom_snippet', {
|
||||
registerWebsitePreviewTour('test_custom_snippet', {
|
||||
url: '/',
|
||||
edition: true,
|
||||
test: true,
|
||||
}, [
|
||||
}, () => [
|
||||
...insertSnippet({
|
||||
id: 's_banner',
|
||||
name: 'Banner',
|
||||
groupName: "Intro",
|
||||
}),
|
||||
{
|
||||
content: "drop a snippet",
|
||||
trigger: ".oe_snippet[name='Banner'] .oe_snippet_thumbnail:not(.o_we_already_dragging)",
|
||||
moveTrigger: ".oe_drop_zone",
|
||||
run: "drag_and_drop iframe #wrap",
|
||||
content: "Customize snippet",
|
||||
trigger: ":iframe #wrapwrap .s_banner h1",
|
||||
run: "editor Test",
|
||||
},
|
||||
{
|
||||
content: "customize snippet",
|
||||
trigger: "iframe #wrapwrap .s_banner h1",
|
||||
run: "text",
|
||||
consumeEvent: "input",
|
||||
content: "Save custom snippet",
|
||||
trigger: "div[data-container-title='Banner'] .oe_snippet_save",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "save custom snippet",
|
||||
trigger: ".snippet-option-SnippetSave we-button",
|
||||
content: "Confirm reload",
|
||||
trigger: ".modal-dialog button:contains('Save')",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "confirm reload",
|
||||
trigger: ".modal-dialog button span:contains('Save and Reload')",
|
||||
content: "Click on the block tab",
|
||||
trigger: ".o-snippets-tabs button[data-name='blocks']",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "ensure custom snippet appeared",
|
||||
trigger: "#oe_snippets.o_loaded .oe_snippet[name='Custom Banner']",
|
||||
run: function () {
|
||||
$("#oe_snippets .oe_snippet[name='Custom Banner'] .o_rename_btn").attr("style", "display: block;");
|
||||
// hover is needed for rename button to appear
|
||||
},
|
||||
content: "Click on the Custom category block",
|
||||
trigger: ".o_block_tab:not(.o_we_ongoing_insertion) #snippet_groups .o_snippet[name='Custom'].o_draggable .o_snippet_thumbnail .o_snippet_thumbnail_area",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "rename custom snippet",
|
||||
trigger: ".oe_snippet[name='Custom Banner'] we-button.o_rename_btn",
|
||||
extra_trigger: ".oe_snippet[name='Custom Banner'] .oe_snippet_thumbnail:not(.o_we_already_dragging)",
|
||||
content: "Ensure custom snippet preview appeared in the dialog",
|
||||
trigger: ":iframe .o_snippet_preview_wrap[data-snippet-id^='s_banner_'] section[data-name='Custom Banner']",
|
||||
},
|
||||
{
|
||||
content: "set name",
|
||||
trigger: ".oe_snippet[name='Custom Banner'] input",
|
||||
run: "text Bruce Banner",
|
||||
content: "Rename custom snippet",
|
||||
trigger: ":iframe .o_custom_snippet_edit > button",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "confirm rename",
|
||||
trigger: ".oe_snippet[name='Custom Banner'] we-button.o_we_confirm_btn",
|
||||
content: "Set name",
|
||||
trigger: ".modal-dialog:not(.o_inactive_modal body) input[id='inputConfirmation']",
|
||||
run: "edit Bruce Banner",
|
||||
},
|
||||
{
|
||||
content: "drop custom snippet",
|
||||
trigger: ".oe_snippet[name='Bruce Banner'] .oe_snippet_thumbnail:not(.o_we_already_dragging)",
|
||||
extra_trigger: "iframe body.editor_enable",
|
||||
moveTrigger: ".oe_drop_zone",
|
||||
run: "drag_and_drop iframe #wrap",
|
||||
content: "Confirm rename",
|
||||
trigger: ".modal-dialog:not(.o_inactive_modal body) footer .btn-primary",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "ensure banner section exists",
|
||||
trigger: "iframe #wrap section[data-name='Banner']",
|
||||
run: function () {}, // check
|
||||
content: "Click on the 'Bruce Banner' snippet",
|
||||
trigger: ":iframe .o_snippet_preview_wrap[data-snippet-id^='s_banner_']:has(section[data-name='Bruce Banner'])",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "ensure custom banner section exists",
|
||||
trigger: "iframe #wrap section[data-name='Bruce Banner']",
|
||||
run: function () {
|
||||
$("#oe_snippets .oe_snippet[name='Bruce Banner'] .o_delete_btn").attr("style", "display: block;");
|
||||
// hover is needed for delete button to appear
|
||||
},
|
||||
content: "Ensure banner section exists",
|
||||
trigger: ":iframe #wrap section[data-name='Banner']",
|
||||
},
|
||||
{
|
||||
content: "delete custom snippet",
|
||||
trigger: ".oe_snippet[name='Bruce Banner'] we-button.o_delete_btn",
|
||||
extra_trigger: ".oe_snippet[name='Bruce Banner'] .oe_snippet_thumbnail:not(.o_we_already_dragging)",
|
||||
content: "Ensure custom banner section exists",
|
||||
trigger: ":iframe #wrap section[data-name='Bruce Banner']",
|
||||
},
|
||||
{
|
||||
content: "confirm delete",
|
||||
trigger: ".modal-dialog button:has(span:contains('Yes'))",
|
||||
content: "Click on the Custom category block",
|
||||
trigger: ".o_block_tab:not(.o_we_ongoing_insertion) #snippet_groups .o_snippet[name='Custom'].o_draggable .o_snippet_thumbnail .o_snippet_thumbnail_area",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "ensure custom snippet disappeared",
|
||||
trigger: "#oe_snippets:not(:has(.oe_snippet[name='Bruce Banner']))",
|
||||
run: function () {}, // check
|
||||
content: "Delete custom snippet",
|
||||
trigger: ":iframe .o_custom_snippet_edit > button + button",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Confirm delete",
|
||||
trigger: ".modal-dialog button:contains('Yes')",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Ensure custom snippet disappeared",
|
||||
trigger: ":iframe .o_add_snippets_preview:not(:has(section[data-name='Bruce Banner']))",
|
||||
},
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,56 +1,81 @@
|
|||
odoo.define('test_website.error_views', function (require) {
|
||||
'use strict';
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
var tour = require('web_tour.tour');
|
||||
|
||||
tour.register('test_error_website', {
|
||||
test: true,
|
||||
registry.category("web_tour.tours").add('test_error_website', {
|
||||
url: '/test_error_view',
|
||||
},
|
||||
[
|
||||
steps: () => [
|
||||
// RPC ERROR
|
||||
{
|
||||
content: "trigger rpc user error",
|
||||
trigger: 'a[href="/test_user_error_json"]',
|
||||
}, {
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: '.o_notification:contains("This is a user rpc test")',
|
||||
},
|
||||
{
|
||||
content: "rpc user error modal has message",
|
||||
extra_trigger: 'div.o_notification_content:contains("This is a user rpc test")',
|
||||
trigger: 'button.o_notification_close',
|
||||
run: "click",
|
||||
}, {
|
||||
content: "trigger rpc access error",
|
||||
trigger: 'a[href="/test_access_error_json"]',
|
||||
}, {
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: '.o_notification:contains("This is an access rpc test")',
|
||||
},
|
||||
{
|
||||
content: "rpc access error modal has message",
|
||||
extra_trigger: 'div.o_notification_content:contains("This is an access rpc test")',
|
||||
trigger: 'button.o_notification_close',
|
||||
run: "click",
|
||||
}, {
|
||||
content: "trigger validation rpc error",
|
||||
trigger: 'a[href="/test_validation_error_json"]',
|
||||
}, {
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: '.o_notification:contains("This is a validation rpc test")',
|
||||
},
|
||||
{
|
||||
content: "rpc validation error modal has message",
|
||||
extra_trigger: 'div.o_notification_content:contains("This is a validation rpc test")',
|
||||
trigger: 'button.o_notification_close',
|
||||
run: "click",
|
||||
}, {
|
||||
content: "trigger rpc missing error",
|
||||
trigger: 'a[href="/test_missing_error_json"]',
|
||||
}, {
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: '.o_notification:contains("This is a missing rpc test")',
|
||||
},
|
||||
{
|
||||
content: "rpc missing error modal has message",
|
||||
extra_trigger: 'div.o_notification_content:contains("This is a missing rpc test")',
|
||||
trigger: 'button.o_notification_close',
|
||||
run: "click",
|
||||
}, {
|
||||
content: "trigger rpc error 403",
|
||||
trigger: 'a[href="/test_access_denied_json"]',
|
||||
}, {
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: '.o_notification:contains("This is an access denied rpc test")',
|
||||
},
|
||||
{
|
||||
content: "rpc error 403 modal has message",
|
||||
extra_trigger: 'div.o_notification_content:contains("This is an access denied rpc test")',
|
||||
trigger: 'button.o_notification_close',
|
||||
run: "click",
|
||||
}, {
|
||||
content: "trigger rpc error 500",
|
||||
trigger: 'a[href="/test_internal_error_json"]',
|
||||
}, {
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: "div.o_error_dialog.modal-content",
|
||||
},
|
||||
{
|
||||
content: "rpc error 500 modal is an ErrorDialog",
|
||||
extra_trigger: 'div.o_dialog_error.modal-content div.alert.alert-warning',
|
||||
trigger: '.modal-footer button.btn.btn-primary',
|
||||
run: "click",
|
||||
},
|
||||
// HTTP ERROR
|
||||
{
|
||||
|
|
@ -59,94 +84,139 @@ tour.register('test_error_website', {
|
|||
run: function () {
|
||||
window.location.href = window.location.origin + '/test_user_error_http?debug=0';
|
||||
},
|
||||
}, {
|
||||
expectUnloadPage: true,
|
||||
},
|
||||
{
|
||||
trigger: 'h1:contains("Something went wrong.")',
|
||||
},
|
||||
{
|
||||
content: "http user error page has title and message",
|
||||
extra_trigger: 'h1:contains("Something went wrong.")',
|
||||
trigger: 'div.container pre:contains("This is a user http test")',
|
||||
run: function () {
|
||||
window.location.href = window.location.origin + '/test_user_error_http?debug=1';
|
||||
},
|
||||
}, {
|
||||
expectUnloadPage: true,
|
||||
},
|
||||
{
|
||||
trigger: 'h1:contains("Something went wrong.")',
|
||||
},
|
||||
{
|
||||
content: "http user error page debug has title and message open",
|
||||
extra_trigger: 'h1:contains("Something went wrong.")',
|
||||
trigger: 'div#error_main.collapse.show pre:contains("This is a user http test")',
|
||||
run: function () {},
|
||||
}, {
|
||||
content: "http user error page debug has traceback closed",
|
||||
trigger: 'body:has(div#error_traceback.collapse:not(.show) pre#exception_traceback)',
|
||||
run: function () {
|
||||
window.location.href = window.location.origin + '/test_validation_error_http?debug=0';
|
||||
},
|
||||
}, {
|
||||
expectUnloadPage: true,
|
||||
},
|
||||
{
|
||||
trigger: 'h1:contains("Something went wrong.")',
|
||||
},
|
||||
{
|
||||
content: "http validation error page has title and message",
|
||||
extra_trigger: 'h1:contains("Something went wrong.")',
|
||||
trigger: 'div.container pre:contains("This is a validation http test")',
|
||||
run: function () {
|
||||
window.location.href = window.location.origin + '/test_validation_error_http?debug=1';
|
||||
},
|
||||
}, {
|
||||
expectUnloadPage: true,
|
||||
},
|
||||
{
|
||||
trigger: 'h1:contains("Something went wrong.")',
|
||||
},
|
||||
{
|
||||
content: "http validation error page debug has title and message open",
|
||||
extra_trigger: 'h1:contains("Something went wrong.")',
|
||||
trigger: 'div#error_main.collapse.show pre:contains("This is a validation http test")',
|
||||
run: function () {},
|
||||
}, {
|
||||
content: "http validation error page debug has traceback closed",
|
||||
trigger: 'body:has(div#error_traceback.collapse:not(.show) pre#exception_traceback)',
|
||||
run: function () {
|
||||
window.location.href = window.location.origin + '/test_access_error_http?debug=0';
|
||||
},
|
||||
}, {
|
||||
expectUnloadPage: true,
|
||||
},
|
||||
{
|
||||
trigger: 'h1:contains("403: Forbidden")',
|
||||
},
|
||||
{
|
||||
content: "http access error page has title and message",
|
||||
extra_trigger: 'h1:contains("403: Forbidden")',
|
||||
trigger: 'div.container pre:contains("This is an access http test")',
|
||||
run: function () {
|
||||
window.location.href = window.location.origin + '/test_access_error_http?debug=1';
|
||||
},
|
||||
}, {
|
||||
expectUnloadPage: true,
|
||||
},
|
||||
{
|
||||
trigger: 'h1:contains("403: Forbidden")',
|
||||
},
|
||||
{
|
||||
content: "http access error page debug has title and message open",
|
||||
extra_trigger: 'h1:contains("403: Forbidden")',
|
||||
trigger: 'div#error_main.collapse.show pre:contains("This is an access http test")',
|
||||
run: function () {},
|
||||
}, {
|
||||
content: "http access error page debug has traceback closed",
|
||||
trigger: 'body:has(div#error_traceback.collapse:not(.show) pre#exception_traceback)',
|
||||
run: function () {
|
||||
window.location.href = window.location.origin + '/test_view_access_error?debug=0';
|
||||
},
|
||||
expectUnloadPage: true,
|
||||
},
|
||||
{
|
||||
trigger: 'h1:contains("403: Forbidden")',
|
||||
},
|
||||
{
|
||||
content: "http access error page has title and message",
|
||||
trigger: 'div.container pre:contains("Uh-oh! Looks like you have stumbled upon some top-secret records.")',
|
||||
run: function () {
|
||||
window.location.href = window.location.origin + '/test_view_access_error?debug=1';
|
||||
},
|
||||
expectUnloadPage: true,
|
||||
},
|
||||
{
|
||||
trigger: 'h1:contains("403: Forbidden")',
|
||||
},
|
||||
{
|
||||
content: "http access error page debug has title and message open",
|
||||
trigger: 'div#error_main.collapse.show pre:contains("Uh-oh! Looks like you have stumbled upon some top-secret records.")',
|
||||
}, {
|
||||
content: "http access error page debug has traceback closed",
|
||||
trigger: 'body:has(div#error_traceback.collapse:not(.show) pre#exception_traceback)',
|
||||
run: function () {
|
||||
window.location.href = window.location.origin + '/test_missing_error_http?debug=0';
|
||||
},
|
||||
}, {
|
||||
content: "http missing error page has title and message",
|
||||
extra_trigger: 'h1:contains("Something went wrong.")',
|
||||
trigger: 'div.container pre:contains("This is a missing http test")',
|
||||
expectUnloadPage: true,
|
||||
},
|
||||
{
|
||||
trigger: 'h1:contains("Error 404")',
|
||||
run: function () {
|
||||
window.location.href = window.location.origin + '/test_missing_error_http?debug=1';
|
||||
},
|
||||
}, {
|
||||
content: "http missing error page debug has title and message open",
|
||||
extra_trigger: 'h1:contains("Something went wrong.")',
|
||||
trigger: 'div#error_main.collapse.show pre:contains("This is a missing http test")',
|
||||
run: function () {},
|
||||
}, {
|
||||
content: "http missing error page debug has traceback closed",
|
||||
trigger: 'body:has(div#error_traceback.collapse:not(.show) pre#exception_traceback)',
|
||||
run: function () {
|
||||
window.location.href = window.location.origin + '/test_access_denied_http?debug=0';
|
||||
},
|
||||
}, {
|
||||
content: "http error 403 page has title but no message",
|
||||
extra_trigger: 'h1:contains("403: Forbidden")',
|
||||
trigger: 'div#wrap:not(:has(pre:contains("This is an access denied http test"))', //See ir_http.py handle_exception, the exception is replaced so there is no message !
|
||||
expectUnloadPage: true,
|
||||
},
|
||||
{
|
||||
trigger: 'h1:contains("Error 404")',
|
||||
run: function () {
|
||||
window.location.href = window.location.origin + '/test_access_denied_http?debug=1';
|
||||
},
|
||||
}, {
|
||||
content: "http 403 error page debug has title but no message",
|
||||
extra_trigger: 'h1:contains("403: Forbidden")',
|
||||
trigger: 'div#debug_infos:not(:has(#error_main))',
|
||||
run: function () {},
|
||||
}, {
|
||||
content: "http 403 error page debug has traceback open",
|
||||
trigger: 'body:has(div#error_traceback.collapse.show pre#exception_traceback)',
|
||||
run: function () {},
|
||||
expectUnloadPage: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
{
|
||||
trigger: 'h1:contains("403: Forbidden")',
|
||||
},
|
||||
{
|
||||
content: "http error 403 page has title but no message",
|
||||
// See http.py _transactionning, the exception is replaced so there is no message !
|
||||
trigger: 'div#wrap:not(:has(pre:contains("Traceback"))',
|
||||
run: function () {
|
||||
window.location.href = window.location.origin + '/test_access_denied_http?debug=1';
|
||||
},
|
||||
expectUnloadPage: true,
|
||||
},
|
||||
{
|
||||
trigger: 'h1:contains("403: Forbidden")',
|
||||
},
|
||||
{
|
||||
content: "http 403 error page debug has title but no message",
|
||||
trigger: 'div#wrap:not(:has(pre:contains("Traceback"))',
|
||||
},
|
||||
]});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
import {
|
||||
clickOnEditAndWaitEditMode,
|
||||
clickOnSave,
|
||||
registerWebsitePreviewTour,
|
||||
changeOptionInPopover,
|
||||
} from "@website/js/tours/tour_utils";
|
||||
|
||||
registerWebsitePreviewTour(
|
||||
"test_form_conditional_visibility_record_field",
|
||||
{
|
||||
url: "/test_website/model_item/1",
|
||||
edition: true,
|
||||
},
|
||||
() => [
|
||||
{
|
||||
content: "Select name field",
|
||||
trigger: ":iframe .s_website_form .s_website_form_input[name=name]",
|
||||
run: "click",
|
||||
},
|
||||
...changeOptionInPopover("Field", "Visibility", "Visible only if"),
|
||||
{
|
||||
content: "Open model selector",
|
||||
trigger: "button[id='hidden_condition_record_opt']:contains('Test Tag')",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Set model to tag #2",
|
||||
trigger: ".o_popover div.o-dropdown-item:contains('Test Tag #2')",
|
||||
run: "click",
|
||||
},
|
||||
...clickOnSave(),
|
||||
{
|
||||
content: "Name field is hidden",
|
||||
trigger: ":iframe .s_website_form:has(.s_website_form_field_hidden_if.d-none)",
|
||||
},
|
||||
...clickOnEditAndWaitEditMode(),
|
||||
|
||||
{
|
||||
content: "Select name field",
|
||||
trigger: ":iframe .s_website_form .s_website_form_input[name=name]",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Open comparator dropdown",
|
||||
trigger: "button[id='hidden_condition_record_opt']:contains('Is equal to')",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Set comparator to Is not equal",
|
||||
trigger: ".o_popover div.o-dropdown-item:contains('Is not equal to')",
|
||||
run: "click",
|
||||
},
|
||||
...clickOnSave(),
|
||||
{
|
||||
content: "Name field is shown",
|
||||
trigger: ":iframe .s_website_form:has(.s_website_form_field_hidden_if:not(.d-none))",
|
||||
},
|
||||
],
|
||||
);
|
||||
|
|
@ -1,6 +1,4 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import wTourUtils from 'website.tour_utils';
|
||||
import { insertSnippet, registerWebsitePreviewTour } from '@website/js/tours/tour_utils';
|
||||
|
||||
/**
|
||||
* The purpose of this tour is to check the link on image flow.
|
||||
|
|
@ -8,78 +6,81 @@ import wTourUtils from 'website.tour_utils';
|
|||
|
||||
const selectImageSteps = [{
|
||||
content: "select block",
|
||||
trigger: "iframe #wrapwrap .s_text_image",
|
||||
trigger: ":iframe #wrapwrap .s_text_image",
|
||||
async run(helpers) {
|
||||
await helpers.click();
|
||||
const el = this.anchor;
|
||||
const sel = el.ownerDocument.getSelection();
|
||||
sel.collapse(el, 0);
|
||||
el.focus();
|
||||
},
|
||||
}, {
|
||||
content: "check link popover disappeared",
|
||||
trigger: "iframe body:not(:has(.o_edit_menu_popover))",
|
||||
run: () => {}, // check
|
||||
trigger: ":iframe body:not(:has(.o_edit_menu_popover))",
|
||||
}, {
|
||||
content: "select image",
|
||||
trigger: "iframe #wrapwrap .s_text_image img",
|
||||
trigger: ":iframe #wrapwrap .s_text_image img",
|
||||
run: "click",
|
||||
}];
|
||||
|
||||
wTourUtils.registerWebsitePreviewTour('test_image_link', {
|
||||
test: true,
|
||||
registerWebsitePreviewTour('test_image_link', {
|
||||
url: '/',
|
||||
edition: true,
|
||||
}, [
|
||||
wTourUtils.dragNDrop({
|
||||
}, () => [
|
||||
...insertSnippet({
|
||||
id: 's_text_image',
|
||||
name: 'Text - Image',
|
||||
groupName: "Content",
|
||||
}),
|
||||
...selectImageSteps,
|
||||
{
|
||||
content: "enable link",
|
||||
trigger: "#oe_snippets we-customizeblock-options:has(we-title:contains('Image')) we-customizeblock-option:has(we-title:contains(Media)) we-button.fa-link",
|
||||
trigger: ".o_customize_tab [data-container-title='Image'] button[data-action-id='setLink']",
|
||||
run: "click",
|
||||
}, {
|
||||
content: "enter site URL",
|
||||
trigger: "#oe_snippets we-customizeblock-options:has(we-title:contains('Image')) we-input:contains(Your URL) input",
|
||||
run: "text odoo.com",
|
||||
trigger: ".o_customize_tab [data-container-title='Image'] div[data-action-id='setUrl'] input",
|
||||
run: "edit odoo.com && click body",
|
||||
},
|
||||
...selectImageSteps,
|
||||
{
|
||||
content: "check popover content has site URL",
|
||||
trigger: "iframe .o_edit_menu_popover a.o_we_url_link[href='http://odoo.com/']:contains(http://odoo.com/)",
|
||||
run: () => {}, // check
|
||||
trigger: ".o-we-linkpopover a.o_we_url_link[href='http://odoo.com']:contains(http://odoo.com)",
|
||||
}, {
|
||||
content: "remove URL",
|
||||
trigger: "#oe_snippets we-customizeblock-options:has(we-title:contains('Image')) we-input:contains(Your URL) input",
|
||||
run: "remove_text",
|
||||
trigger: ".o_customize_tab [data-container-title='Image'] div[data-action-id='setUrl'] input",
|
||||
run: "clear && click body",
|
||||
},
|
||||
...selectImageSteps,
|
||||
{
|
||||
content: "check popover content has no URL",
|
||||
trigger: "iframe .o_edit_menu_popover a.o_we_url_link:not([href]):contains(No URL specified)",
|
||||
run: () => {}, // check
|
||||
trigger: ".o-we-linkpopover .o_we_href_input_link:value()",
|
||||
}, {
|
||||
content: "enter email URL",
|
||||
trigger: "#oe_snippets we-customizeblock-options:has(we-title:contains('Image')) we-input:contains(Your URL) input",
|
||||
run: "text mailto:test@test.com",
|
||||
trigger: ".o_customize_tab [data-container-title='Image'] div[data-action-id='setUrl'] input",
|
||||
run: "edit mailto:test@test.com && click body",
|
||||
},
|
||||
...selectImageSteps,
|
||||
{
|
||||
content: "check popover content has mail URL",
|
||||
trigger: "iframe .o_edit_menu_popover:has(.fa-envelope-o) a.o_we_url_link[href='mailto:test@test.com']:contains(mailto:test@test.com)",
|
||||
run: () => {}, // check
|
||||
trigger: ".o-we-linkpopover:has(.fa-envelope-o) a.o_we_url_link[href='mailto:test@test.com']:contains(mailto:test@test.com)",
|
||||
}, {
|
||||
content: "enter phone URL",
|
||||
trigger: "#oe_snippets we-customizeblock-options:has(we-title:contains('Image')) we-input:contains(Your URL) input",
|
||||
run: "text tel:555-2368",
|
||||
trigger: ".o_customize_tab [data-container-title='Image'] div[data-action-id='setUrl'] input",
|
||||
run: "edit tel:555-2368 && click body",
|
||||
},
|
||||
...selectImageSteps,
|
||||
{
|
||||
content: "check popover content has phone URL",
|
||||
trigger: "iframe .o_edit_menu_popover:has(.fa-phone) a.o_we_url_link[href='tel:555-2368']:contains(tel:555-2368)",
|
||||
run: () => {}, // check
|
||||
trigger: ".o-we-linkpopover:has(.fa-phone) a.o_we_url_link[href='tel:555-2368']:contains(tel:555-2368)",
|
||||
}, {
|
||||
content: "remove URL",
|
||||
trigger: "#oe_snippets we-customizeblock-options:has(we-title:contains('Image')) we-input:contains(Your URL) input",
|
||||
run: "remove_text",
|
||||
trigger: ".o_customize_tab [data-container-title='Image'] div[data-action-id='setUrl'] input",
|
||||
run: "clear && click body",
|
||||
},
|
||||
...selectImageSteps,
|
||||
{
|
||||
content: "check popover content has no URL",
|
||||
trigger: "iframe .o_edit_menu_popover a.o_we_url_link:not([href]):contains(No URL specified)",
|
||||
run: () => {}, // check
|
||||
trigger: ".o-we-linkpopover .o_we_href_input_link:value()",
|
||||
},
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -1,13 +1,10 @@
|
|||
odoo.define('test_website.image_upload_progress', function (require) {
|
||||
'use strict';
|
||||
import { insertSnippet, registerWebsitePreviewTour } from "@website/js/tours/tour_utils";
|
||||
|
||||
const wTourUtils = require('website.tour_utils');
|
||||
|
||||
const { FileSelectorControlPanel } = require('@web_editor/components/media_dialog/file_selector');
|
||||
const { patch, unpatch } = require('web.utils');
|
||||
import { FileSelectorControlPanel } from "@html_editor/main/media/media_dialog/file_selector";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
|
||||
let patchWithError = false;
|
||||
const patchMediaDialog = () => patch(FileSelectorControlPanel.prototype, 'test_website.mock_image_widgets', {
|
||||
const patchMediaDialog = () => patch(FileSelectorControlPanel.prototype, {
|
||||
async onChangeFileInput() {
|
||||
const getFileFromB64 = (fileData) => {
|
||||
const binary = atob(fileData[2]);
|
||||
|
|
@ -37,46 +34,50 @@ const patchMediaDialog = () => patch(FileSelectorControlPanel.prototype, 'test_w
|
|||
}
|
||||
});
|
||||
|
||||
const unpatchMediaDialog = () => unpatch(FileSelectorControlPanel.prototype, 'test_website.mock_image_widgets');
|
||||
let unpatchMediaDialog = null;
|
||||
|
||||
const setupSteps = [{
|
||||
content: "reload to load patch",
|
||||
trigger: ".o_website_preview",
|
||||
run: () => {
|
||||
patchMediaDialog();
|
||||
},
|
||||
}, {
|
||||
content: "drop a snippet",
|
||||
trigger: "#oe_snippets .oe_snippet[name='Text - Image'] .oe_snippet_thumbnail:not(.o_we_already_dragging)",
|
||||
moveTrigger: "iframe .oe_drop_zone",
|
||||
run: "drag_and_drop iframe #wrap",
|
||||
}, {
|
||||
content: "drop a snippet",
|
||||
trigger: "#oe_snippets .oe_snippet[name='Image Gallery'] .oe_snippet_thumbnail:not(.o_we_already_dragging)",
|
||||
extra_trigger: "body.editor_has_snippets",
|
||||
moveTrigger: ".oe_drop_zone",
|
||||
run: "drag_and_drop iframe #wrap",
|
||||
}];
|
||||
const setupSteps = function () {
|
||||
return [
|
||||
{
|
||||
content: "reload to load patch",
|
||||
trigger: ".o_website_preview",
|
||||
run() {
|
||||
unpatchMediaDialog = patchMediaDialog();
|
||||
},
|
||||
},
|
||||
...insertSnippet({
|
||||
id: "s_text_image",
|
||||
name: "Text - Image",
|
||||
groupName: "Content",
|
||||
}),
|
||||
...insertSnippet({
|
||||
id: "s_image_gallery",
|
||||
name: "Image Gallery",
|
||||
groupName: "Images",
|
||||
})
|
||||
];
|
||||
};
|
||||
|
||||
const formatErrorMsg = "format is not supported. Try with: .gif, .jpe, .jpeg, .jpg, .png, .svg";
|
||||
const formatErrorMsg = "format is not supported. Try with: .gif, .jpe, .jpeg, .jpg, .png, .svg, .webp";
|
||||
|
||||
wTourUtils.registerWebsitePreviewTour('test_image_upload_progress', {
|
||||
registerWebsitePreviewTour('test_image_upload_progress', {
|
||||
url: '/test_image_progress',
|
||||
test: true,
|
||||
edition: true,
|
||||
}, [
|
||||
...setupSteps,
|
||||
}, () => [
|
||||
...setupSteps(),
|
||||
// 1. Check multi image upload
|
||||
{
|
||||
content: "click on dropped snippet",
|
||||
trigger: "iframe #wrap .s_image_gallery .img",
|
||||
trigger: ":iframe #wrap .s_image_gallery .img",
|
||||
run: "click",
|
||||
}, {
|
||||
content: "click on add images to open image dialog (in multi mode)",
|
||||
trigger: 'we-customizeblock-option [data-add-images]',
|
||||
trigger: "button[data-action-id='addImage']",
|
||||
run: "click",
|
||||
}, {
|
||||
content: "manually trigger input change",
|
||||
trigger: ".o_select_media_dialog .o_upload_media_button",
|
||||
run: () => {
|
||||
run() {
|
||||
// This will trigger upload of dummy files for test purpose, as a
|
||||
// test can't select local files to upload into the input.
|
||||
document.body.querySelector('.o_select_media_dialog .o_file_input').dispatchEvent(new Event('change'));
|
||||
|
|
@ -84,52 +85,50 @@ wTourUtils.registerWebsitePreviewTour('test_image_upload_progress', {
|
|||
}, {
|
||||
content: "check upload progress bar is correctly shown (1)",
|
||||
trigger: `.o_we_progressbar:contains('icon.ico'):contains('${formatErrorMsg}')`,
|
||||
in_modal: false,
|
||||
run: function () {}, // it's a check
|
||||
}, {
|
||||
content: "check upload progress bar is correctly shown (2)",
|
||||
trigger: `.o_we_progressbar:contains('image.webp'):contains('${formatErrorMsg}')`,
|
||||
in_modal: false,
|
||||
run: function () {}, // it's a check
|
||||
trigger: ".o_we_progressbar:contains('image.webp'):contains('File has been uploaded')",
|
||||
}, {
|
||||
content: "check upload progress bar is correctly shown (3)",
|
||||
trigger: ".o_we_progressbar:contains('image.png'):contains('File has been uploaded')",
|
||||
in_modal: false,
|
||||
run: function () {}, // it's a check
|
||||
}, {
|
||||
content: "check upload progress bar is correctly shown (4)",
|
||||
trigger: ".o_we_progressbar:contains('image.jpeg'):contains('File has been uploaded')",
|
||||
in_modal: false,
|
||||
run: function () {}, // it's a check
|
||||
}, {
|
||||
content: "there should only have one notification toaster",
|
||||
},
|
||||
{
|
||||
trigger: ".o_notification",
|
||||
in_modal: false,
|
||||
run: () => {
|
||||
const notificationCount = $('.o_notification').length;
|
||||
},
|
||||
{
|
||||
content: "there should only have one notification toaster",
|
||||
trigger: "body",
|
||||
run() {
|
||||
const notificationCount = document.querySelectorAll(".o_notification").length;
|
||||
if (notificationCount !== 1) {
|
||||
console.error("There should be one noficiation toaster opened, and only one.");
|
||||
throw new Error(`There should be one notification toaster opened, and only one, found ${notificationCount}.`);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
content: "close notification",
|
||||
trigger: '.o_notification_close',
|
||||
in_modal: false,
|
||||
run: "click",
|
||||
}, {
|
||||
content: "close media dialog",
|
||||
trigger: '.modal-footer .btn-secondary',
|
||||
run: "click",
|
||||
},
|
||||
// 2. Check success single image upload
|
||||
{
|
||||
content: "click on dropped snippet",
|
||||
trigger: "iframe #wrap .s_text_image .img",
|
||||
trigger: ":iframe #wrap .s_text_image .img",
|
||||
run: "click",
|
||||
}, {
|
||||
content: "click on replace media to open image dialog",
|
||||
trigger: 'we-customizeblock-option [data-replace-media]',
|
||||
trigger: "button[data-action-id='replaceMedia']",
|
||||
run: "click",
|
||||
}, {
|
||||
content: "manually trigger input change",
|
||||
trigger: ".o_select_media_dialog .o_upload_media_button",
|
||||
run: () => {
|
||||
run() {
|
||||
// This will trigger upload of dummy files for test purpose, as a
|
||||
// test can't select local files to upload into the input.
|
||||
document.body.querySelector('.o_select_media_dialog .o_file_input').dispatchEvent(new Event('change'));
|
||||
|
|
@ -137,43 +136,38 @@ wTourUtils.registerWebsitePreviewTour('test_image_upload_progress', {
|
|||
}, {
|
||||
content: "check upload progress bar is correctly shown",
|
||||
trigger: ".o_we_progressbar:contains('image.png')",
|
||||
in_modal: false,
|
||||
run: function () {}, // it's a check
|
||||
}, {
|
||||
content: "there should only have one notification toaster",
|
||||
trigger: ".o_notification",
|
||||
in_modal: false,
|
||||
run: () => {
|
||||
const notificationCount = $('.o_notification').length;
|
||||
run() {
|
||||
const notificationCount = document.querySelectorAll(".o_notification").length;
|
||||
if (notificationCount !== 1) {
|
||||
console.error("There should be one noficiation toaster opened, and only one.");
|
||||
throw new Error(`There should be one notification toaster opened, and only one, found ${notificationCount}.`);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
content: "media dialog has closed after the upload",
|
||||
trigger: 'body:not(:has(.o_select_media_dialog))',
|
||||
run: () => {}, // It's a check.
|
||||
}, {
|
||||
content: "the upload progress toast was updated",
|
||||
trigger: ".o_we_progressbar:contains('image.png'):contains('File has been uploaded')",
|
||||
run: () => {}, // It's a check.
|
||||
}, {
|
||||
content: "toaster should disappear after a few seconds if the uploaded image is successful",
|
||||
trigger: "body:not(:has(.o_we_progressbar))",
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
// 3. Check error single image upload
|
||||
{
|
||||
content: "click on dropped snippet",
|
||||
trigger: "iframe #wrap .s_text_image .img",
|
||||
trigger: ":iframe #wrap .s_text_image .img",
|
||||
run: "click",
|
||||
}, {
|
||||
content: "click on replace media to open image dialog",
|
||||
trigger: 'we-customizeblock-option [data-replace-media]',
|
||||
trigger: "button[data-action-id='replaceMedia']",
|
||||
run: "click",
|
||||
}, {
|
||||
content: "manually trigger input change",
|
||||
trigger: ".o_select_media_dialog .o_upload_media_button",
|
||||
in_modal: false,
|
||||
run: () => {
|
||||
run() {
|
||||
patchWithError = true;
|
||||
// This will trigger upload of dummy files for test purpose, as a
|
||||
// test can't select local files to upload into the input.
|
||||
|
|
@ -183,18 +177,16 @@ wTourUtils.registerWebsitePreviewTour('test_image_upload_progress', {
|
|||
}, {
|
||||
content: "check upload progress bar is correctly shown",
|
||||
trigger: `.o_we_progressbar:contains('icon.ico'):contains('${formatErrorMsg}')`,
|
||||
in_modal: false,
|
||||
run: function () {
|
||||
run() {
|
||||
patchWithError = false;
|
||||
},
|
||||
}, {
|
||||
content: "there should only have one notification toaster",
|
||||
trigger: ".o_notification",
|
||||
in_modal: false,
|
||||
run: () => {
|
||||
const notificationCount = $('.o_notification').length;
|
||||
run() {
|
||||
const notificationCount = document.querySelectorAll(".o_notification").length;
|
||||
if (notificationCount !== 1) {
|
||||
console.error("There should be one noficiation toaster opened, and only one.");
|
||||
throw new Error(`There should be one noficiation toaster opened, and only one, found ${notificationCount}.`);
|
||||
}
|
||||
unpatchMediaDialog();
|
||||
}
|
||||
|
|
@ -202,44 +194,45 @@ wTourUtils.registerWebsitePreviewTour('test_image_upload_progress', {
|
|||
]);
|
||||
|
||||
|
||||
wTourUtils.registerWebsitePreviewTour('test_image_upload_progress_unsplash', {
|
||||
registerWebsitePreviewTour('test_image_upload_progress_unsplash', {
|
||||
url: '/test_image_progress',
|
||||
test: true,
|
||||
edition: true,
|
||||
}, [
|
||||
...setupSteps,
|
||||
}, () => [
|
||||
...setupSteps(),
|
||||
// 1. Check multi image upload
|
||||
{
|
||||
content: "click on dropped snippet",
|
||||
trigger: "iframe #wrap .s_image_gallery .img",
|
||||
trigger: ":iframe #wrap .s_image_gallery .img",
|
||||
run: "click",
|
||||
}, {
|
||||
content: "click on replace media to open image dialog",
|
||||
trigger: 'we-customizeblock-option [data-replace-media]',
|
||||
trigger: "button[data-action-id='replaceMedia']",
|
||||
run: "click",
|
||||
}, {
|
||||
content: "search 'fox' images",
|
||||
trigger: ".o_we_search",
|
||||
run: "text fox",
|
||||
run: "edit fox",
|
||||
}, {
|
||||
content: "click on unsplash result", // note that unsplash is mocked
|
||||
trigger: "img[alt~=fox]"
|
||||
}, {
|
||||
trigger: ".o_we_media_dialog_img_wrapper:has(img[alt~=fox]) + .o_button_area",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_notification_close",
|
||||
},
|
||||
{
|
||||
content: "check that the upload progress bar is correctly shown",
|
||||
// ensure it is there so we are sure next step actually test something
|
||||
extra_trigger: '.o_notification_close',
|
||||
trigger: ".o_we_progressbar:contains('fox'):contains('File has been uploaded')",
|
||||
in_modal: false,
|
||||
run: function () {}, // it's a check
|
||||
}, {
|
||||
content: "notification should close after 3 seconds",
|
||||
trigger: 'body:not(:has(.o_notification_close))',
|
||||
in_modal: false,
|
||||
run: "click",
|
||||
}, {
|
||||
content: "unsplash image (mocked to logo) should have been used",
|
||||
trigger: "iframe #wrap .s_image_gallery .img[data-original-src^='/unsplash/HQqIOc8oYro/fox']",
|
||||
run: () => {
|
||||
trigger: ":iframe #wrap .s_image_gallery img[data-original-src^='/unsplash/HQqIOc8oYro/fox']",
|
||||
run() {
|
||||
unpatchMediaDialog();
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,16 +1,12 @@
|
|||
odoo.define('test_website.json_auth', function (require) {
|
||||
'use strict';
|
||||
import { registry } from "@web/core/registry";
|
||||
import { rpc } from "@web/core/network/rpc";
|
||||
|
||||
var tour = require('web_tour.tour');
|
||||
var session = require('web.session')
|
||||
|
||||
tour.register('test_json_auth', {
|
||||
test: true,
|
||||
}, [{
|
||||
registry.category("web_tour.tours").add('test_json_auth', {
|
||||
steps: () => [{
|
||||
trigger: 'body',
|
||||
run: async function () {
|
||||
await session.rpc('/test_get_dbname').then( function (result){
|
||||
return session.rpc("/web/session/authenticate", {
|
||||
await rpc('/test_get_dbname').then( function (result){
|
||||
return rpc("/web/session/authenticate", {
|
||||
db: result,
|
||||
login: 'admin',
|
||||
password: 'admin'
|
||||
|
|
@ -18,9 +14,8 @@ tour.register('test_json_auth', {
|
|||
});
|
||||
window.location.href = window.location.origin;
|
||||
},
|
||||
expectUnloadPage: true,
|
||||
}, {
|
||||
trigger: 'span:contains(Mitchell Admin), span:contains(Administrator)',
|
||||
run: function () {},
|
||||
}
|
||||
]);
|
||||
});
|
||||
]});
|
||||
|
|
|
|||
|
|
@ -1,77 +1,87 @@
|
|||
/** @odoo-module **/
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
import tour from 'web_tour.tour';
|
||||
|
||||
tour.register('test_website_page_manager', {
|
||||
test: true,
|
||||
url: '/web#action=test_website.action_test_model_multi_website',
|
||||
}, [
|
||||
registry.category("web_tour.tours").add('test_website_page_manager', {
|
||||
url: '/odoo/action-test_website.action_test_model_multi_website',
|
||||
steps: () => [
|
||||
// Part 1: check that the website filter is working
|
||||
{
|
||||
content: "Check that we see records from My Website",
|
||||
trigger: ".o_list_table .o_data_row .o_data_cell[name=name]:contains('Test Multi Model Website 1') " +
|
||||
"~ .o_data_cell[name=website_id]:contains('My Website')",
|
||||
run: () => null, // it's a check
|
||||
}, {
|
||||
content: "Check that there is only 2 records in the pager",
|
||||
trigger: ".o_pager .o_pager_value:contains('1-2')",
|
||||
run: () => null, // it's a check
|
||||
}, {
|
||||
content: "Click on the 'Select all records' checkbox",
|
||||
trigger: "thead .o_list_record_selector",
|
||||
run: "click",
|
||||
}, {
|
||||
content: "Check that there is only 2 records selected",
|
||||
trigger: ".o_list_selection_box:contains('2 selected')",
|
||||
run: () => null, // it's a check
|
||||
trigger: ".o_selection_box:contains(2):contains(selected)",
|
||||
}, {
|
||||
content: "Click on My Website search filter",
|
||||
trigger: "button.dropdown-toggle:contains('My Website')",
|
||||
content: "Click on the 'Select all records' checkbox again to unselect all records and see the search bar",
|
||||
trigger: "thead .o_list_record_selector",
|
||||
run: "click",
|
||||
}, {
|
||||
content: "Select My Website 2",
|
||||
trigger: ".dropdown-menu.show > .dropdown-item:contains('My Website 2')",
|
||||
content: "Click on the search options",
|
||||
trigger: ".o_searchview_dropdown_toggler",
|
||||
run: "click",
|
||||
}, {
|
||||
content: "Remove 'My Website' filter",
|
||||
trigger: ".o_filter_menu .o-dropdown-item:contains('My Website')",
|
||||
run: "click",
|
||||
}, {
|
||||
content: "Select 'My Website 2' filter",
|
||||
trigger: ".o_filter_menu .o-dropdown-item:contains('My Website 2')",
|
||||
run: "click",
|
||||
}, {
|
||||
// This step is just here to ensure there is more records than the 2
|
||||
// available on website 1, to ensure the test is actually testing something.
|
||||
content: "Check that we see records from My Website 2",
|
||||
trigger: ".o_list_table .o_data_row .o_data_cell[name=name]:contains('Test Model Multi Website 2') " +
|
||||
"~ .o_data_cell[name=website_id]:contains('My Website 2')",
|
||||
run: () => null, // it's a check
|
||||
},
|
||||
// Part 2: ensure Kanban View is working / not crashing
|
||||
{
|
||||
content: "Click on Kanban View",
|
||||
trigger: '.o_cp_switch_buttons .o_kanban',
|
||||
}, {
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_kanban_renderer",
|
||||
},
|
||||
{
|
||||
content: "Click on List View",
|
||||
extra_trigger: '.o_kanban_renderer',
|
||||
trigger: '.o_cp_switch_buttons .o_list',
|
||||
run: "click",
|
||||
}, {
|
||||
content: "Wait for List View to be loaded",
|
||||
trigger: '.o_list_renderer',
|
||||
run: () => null, // it's a check
|
||||
}]);
|
||||
}]
|
||||
});
|
||||
|
||||
tour.register('test_website_page_manager_js_class_bug', {
|
||||
test: true,
|
||||
url: '/web#action=test_website.action_test_model_multi_website_js_class_bug',
|
||||
}, [{
|
||||
registry.category("web_tour.tours").add('test_website_page_manager_js_class_bug', {
|
||||
url: '/odoo/action-test_website.action_test_model_multi_website_js_class_bug',
|
||||
steps: () => [
|
||||
{
|
||||
content: "Click on Kanban View",
|
||||
trigger: '.o_cp_switch_buttons .o_kanban',
|
||||
run: "click",
|
||||
}, {
|
||||
content: "Wait for Kanban View to be loaded",
|
||||
trigger: '.o_kanban_renderer',
|
||||
run: () => null, // it's a check
|
||||
}]);
|
||||
}]
|
||||
});
|
||||
|
||||
tour.register('test_website_page_manager_no_website_id', {
|
||||
test: true,
|
||||
url: '/web#action=test_website.action_test_model',
|
||||
}, [{
|
||||
registry.category("web_tour.tours").add('test_website_page_manager_no_website_id', {
|
||||
url: '/odoo/action-test_website.action_test_model',
|
||||
steps: () => [
|
||||
{
|
||||
content: "Click on Kanban View",
|
||||
trigger: '.o_cp_switch_buttons .o_kanban',
|
||||
run: "click",
|
||||
}, {
|
||||
content: "Wait for Kanban View to be loaded",
|
||||
trigger: '.o_kanban_renderer',
|
||||
run: () => null, // it's a check
|
||||
}]);
|
||||
|
||||
}]
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,19 +1,20 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { patch } from '@web/core/utils/patch';
|
||||
import { VideoSelector } from '@web_editor/components/media_dialog/video_selector';
|
||||
import wTourUtils from 'website.tour_utils';
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { VideoSelector } from "@html_editor/main/media/media_dialog/video_selector";
|
||||
import {
|
||||
changeOption,
|
||||
insertSnippet,
|
||||
registerWebsitePreviewTour,
|
||||
} from '@website/js/tours/tour_utils';
|
||||
|
||||
const VIDEO_URL = 'https://www.youtube.com/watch?v=Dpq87YCHmJc';
|
||||
|
||||
/**
|
||||
* The purpose of this tour is to check the media replacement flow.
|
||||
*/
|
||||
wTourUtils.registerWebsitePreviewTour('test_replace_media', {
|
||||
registerWebsitePreviewTour('test_replace_media', {
|
||||
url: '/',
|
||||
test: true,
|
||||
edition: true,
|
||||
}, [
|
||||
}, () => [
|
||||
{
|
||||
trigger: "body",
|
||||
run: function () {
|
||||
|
|
@ -23,138 +24,148 @@ wTourUtils.registerWebsitePreviewTour('test_replace_media', {
|
|||
// specific to an URL only, it is acceptable).
|
||||
// TODO if we ever give the possibility to upload its own videos,
|
||||
// this won't be necessary anymore.
|
||||
patch(VideoSelector.prototype, "Video selector patch", {
|
||||
patch(VideoSelector.prototype, {
|
||||
async _getVideoURLData(src, options) {
|
||||
if (src === VIDEO_URL || src === 'about:blank') {
|
||||
return {platform: 'youtube', embed_url: 'about:blank'};
|
||||
}
|
||||
return this._super(...arguments);
|
||||
return super._getVideoURLData(...arguments);
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
content: "drop picture snippet",
|
||||
trigger: "#oe_snippets .oe_snippet[name='Picture'] .oe_snippet_thumbnail:not(.o_we_already_dragging)",
|
||||
moveTrigger: "iframe .oe_drop_zone",
|
||||
run: "drag_and_drop iframe #wrap",
|
||||
},
|
||||
...insertSnippet({
|
||||
name: 'Title - Image',
|
||||
id: 's_picture',
|
||||
groupName: "Images",
|
||||
}),
|
||||
{
|
||||
content: "select image",
|
||||
trigger: "iframe .s_picture figure img",
|
||||
trigger: ":iframe .s_picture figure img",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "ensure image size is displayed",
|
||||
trigger: "#oe_snippets we-title:contains('Image') .o_we_image_weight:contains('kb')",
|
||||
run: function () {}, // check
|
||||
trigger: ".o_customize_tab [data-container-title='Image'] .options-container-header:contains('kb')",
|
||||
},
|
||||
wTourUtils.changeOption("ImageTools", 'we-select[data-name="shape_img_opt"] we-toggler'),
|
||||
wTourUtils.changeOption("ImageTools", "we-button[data-set-img-shape]"),
|
||||
changeOption("Image", "[data-label='Shape'] .dropdown-toggle"),
|
||||
{
|
||||
content: "replace image",
|
||||
trigger: "#oe_snippets we-button[data-replace-media]",
|
||||
content: "Click on the first image shape",
|
||||
trigger: "button[data-action-id='setImageShape']",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Open MediaDialog from an image",
|
||||
trigger: "button[data-action-id='replaceMedia']",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "select svg",
|
||||
trigger: ".o_select_media_dialog img[title='sample.svg']",
|
||||
trigger: ".o_select_media_dialog .o_button_area[aria-label='sample.svg']",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "ensure the svg doesn't have a shape",
|
||||
trigger: "iframe .s_picture figure img:not([data-shape])",
|
||||
run: function () {}, // check
|
||||
content: "ensure the svg does have a shape",
|
||||
trigger: ":iframe .s_picture figure img[data-shape]",
|
||||
},
|
||||
{
|
||||
content: "ensure image size is not displayed",
|
||||
trigger: "#oe_snippets we-title:contains('Image'):not(:has(.o_we_image_weight:visible))",
|
||||
run: function () {}, // check
|
||||
content: "ensure image size is displayed",
|
||||
trigger: ".o_customize_tab [data-container-title='Image'] span[title='Size']",
|
||||
},
|
||||
{
|
||||
content: "replace image",
|
||||
trigger: "#oe_snippets we-button[data-replace-media]",
|
||||
trigger: "button[data-action-id='replaceMedia']",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "go to pictogram tab",
|
||||
trigger: ".o_select_media_dialog .nav-link:contains('Icons')",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "select an icon",
|
||||
trigger: ".o_select_media_dialog:has(.nav-link.active:contains('Icons')) .tab-content span.fa-lemon-o",
|
||||
trigger: ".o_select_media_dialog:has(.nav-link.active:contains('Icons')) .tab-content span.fa-heart",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "ensure icon block is displayed",
|
||||
trigger: "#oe_snippets we-customizeblock-options we-title:contains('Icon')",
|
||||
run: function () {}, // check
|
||||
trigger: ".o_customize_tab [data-container-title='Icon']",
|
||||
},
|
||||
{
|
||||
content: "select footer",
|
||||
trigger: "iframe footer",
|
||||
trigger: ":iframe footer",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "select icon",
|
||||
trigger: "iframe .s_picture figure span.fa-lemon-o",
|
||||
trigger: ":iframe .s_picture figure span.fa-heart",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "ensure icon block is still displayed",
|
||||
trigger: "#oe_snippets we-customizeblock-options we-title:contains('Icon')",
|
||||
run: function () {}, // check
|
||||
trigger: ".o_customize_tab [data-container-title='Icon']",
|
||||
},
|
||||
{
|
||||
content: "replace icon",
|
||||
trigger: "#oe_snippets we-button[data-replace-media]",
|
||||
trigger: "button[data-action-id='replaceMedia']",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "go to video tab",
|
||||
trigger: ".o_select_media_dialog .nav-link:contains('Video')",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "enter a video URL",
|
||||
trigger: ".o_select_media_dialog #o_video_text",
|
||||
// Design your first web page.
|
||||
run: `text ${VIDEO_URL}`,
|
||||
run: `edit ${VIDEO_URL}`,
|
||||
},
|
||||
{
|
||||
content: "wait for preview to appear",
|
||||
// "about:blank" because the VideoWidget was patched at the start of this tour
|
||||
trigger: ".o_select_media_dialog div.media_iframe_video iframe[src='about:blank']",
|
||||
run: function () {}, // check
|
||||
trigger: ".o_select_media_dialog div.media_iframe_video [src='about:blank']:iframe body",
|
||||
},
|
||||
{
|
||||
content: "confirm selection",
|
||||
trigger: ".o_select_media_dialog .modal-footer .btn-primary",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "ensure video option block is displayed",
|
||||
trigger: "#oe_snippets we-customizeblock-options we-title:contains('Video')",
|
||||
run: function () {}, // check
|
||||
trigger: ".o_customize_tab [data-container-title='Video']",
|
||||
},
|
||||
{
|
||||
content: "replace image",
|
||||
trigger: "#oe_snippets we-button[data-replace-media]",
|
||||
trigger: ".btn-success[data-action-id='replaceMedia']",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "go to pictogram tab",
|
||||
trigger: ".o_select_media_dialog .nav-link:contains('Icons')",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "select an icon",
|
||||
trigger: ".o_select_media_dialog:has(.nav-link.active:contains('Icons')) .tab-content span.fa-lemon-o",
|
||||
trigger: ".o_select_media_dialog:has(.nav-link.active:contains('Icons')) .tab-content span.fa-heart",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "ensure icon block is displayed",
|
||||
trigger: "#oe_snippets we-customizeblock-options we-title:contains('Icon')",
|
||||
run: function () {}, // check
|
||||
trigger: ".o_customize_tab [data-container-title='Icon']",
|
||||
},
|
||||
{
|
||||
content: "select footer",
|
||||
trigger: "iframe footer",
|
||||
trigger: ":iframe footer",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "select icon",
|
||||
trigger: "iframe .s_picture figure span.fa-lemon-o",
|
||||
trigger: ":iframe .s_picture figure span.fa-heart",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "ensure icon block is still displayed",
|
||||
trigger: "#oe_snippets we-customizeblock-options we-title:contains('Icon')",
|
||||
run: function () {}, // check
|
||||
trigger: ".o_customize_tab [data-container-title='Icon']",
|
||||
},
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
/* global ace */
|
||||
odoo.define('test_website.reset_views', function (require) {
|
||||
'use strict';
|
||||
|
||||
const wTourUtils = require('website.tour_utils');
|
||||
import { clickOnSave, registerWebsitePreviewTour } from "@website/js/tours/tour_utils";
|
||||
|
||||
var BROKEN_STEP = {
|
||||
// because saving a broken template opens a recovery page with no assets
|
||||
|
|
@ -10,98 +8,110 @@ var BROKEN_STEP = {
|
|||
// to properly wait for the page to be saved & reloaded in order to fix the
|
||||
// race condition of a tour ending on a side-effect (with the possible
|
||||
// exception of somehow telling the harness / browser to do it)
|
||||
trigger: 'body',
|
||||
run: function () {}
|
||||
trigger: "body",
|
||||
};
|
||||
wTourUtils.registerWebsitePreviewTour('test_reset_page_view_complete_flow_part1', {
|
||||
test: true,
|
||||
url: '/test_page_view',
|
||||
// 1. Edit the page through Edit Mode, it will COW the view
|
||||
edition: true,
|
||||
},
|
||||
[
|
||||
registerWebsitePreviewTour(
|
||||
"test_reset_page_view_complete_flow_part1",
|
||||
{
|
||||
url: "/test_page_view",
|
||||
// 1. Edit the page through Edit Mode, it will COW the view
|
||||
edition: true,
|
||||
},
|
||||
() => [
|
||||
{
|
||||
content: "drop a snippet",
|
||||
trigger: ".oe_snippet:has(.s_cover) .oe_snippet_thumbnail",
|
||||
content: "Drag the Intro snippet group and drop it in #oe_structure_test_website_page.",
|
||||
trigger:
|
||||
".o_block_tab:not(.o_we_ongoing_insertion) #snippet_groups .o_snippet[name='Intro'] .o_snippet_thumbnail .o_snippet_thumbnail_area",
|
||||
// id starting by 'oe_structure..' will actually create an inherited view
|
||||
run: "drag_and_drop iframe #oe_structure_test_website_page",
|
||||
run: "drag_and_drop :iframe #oe_structure_test_website_page",
|
||||
},
|
||||
{
|
||||
content: "save the page",
|
||||
extra_trigger: 'iframe #oe_structure_test_website_page.o_dirty',
|
||||
trigger: "button[data-action=save]",
|
||||
content: "Click on the s_cover snippet.",
|
||||
trigger: ':iframe .o_snippet_preview_wrap[data-snippet-id="s_cover"]',
|
||||
run: "click",
|
||||
},
|
||||
...clickOnSave(),
|
||||
// 2. Edit that COW'd view in the HTML editor to break it.
|
||||
{
|
||||
content: "open site menu",
|
||||
extra_trigger: "iframe body:not(.editor_enable)",
|
||||
trigger: 'button[data-menu-xmlid="website.menu_site"]',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "open html editor",
|
||||
trigger: 'a[data-menu-xmlid="website.menu_ace_editor"]',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "add a broken t-field in page DOM",
|
||||
trigger: 'div.ace_line .ace_xml:contains("placeholder")',
|
||||
run: function () {
|
||||
ace.edit('ace-view-editor').getSession().insert({row: 4, column: 1}, '<t t-field="not.exist"/>\n');
|
||||
run() {
|
||||
ace.edit(document.querySelector("#resource-editor div"))
|
||||
.getSession()
|
||||
.insert({row: 4, column: 1}, '<t t-field="not.exist"/>\n');
|
||||
},
|
||||
},
|
||||
{
|
||||
content: "save the html editor",
|
||||
extra_trigger: '.ace_content:contains("not.exist")',
|
||||
trigger: ".o_ace_view_editor button[data-action=save]",
|
||||
trigger: '.ace_content:contains("not.exist")',
|
||||
},
|
||||
BROKEN_STEP
|
||||
{
|
||||
content: "save the html editor",
|
||||
trigger: ".o_resource_editor button:contains(Save)",
|
||||
run: "click",
|
||||
},
|
||||
BROKEN_STEP,
|
||||
]
|
||||
);
|
||||
|
||||
wTourUtils.registerWebsitePreviewTour('test_reset_page_view_complete_flow_part2', {
|
||||
test: true,
|
||||
url: '/test_page_view',
|
||||
},
|
||||
[
|
||||
registerWebsitePreviewTour(
|
||||
"test_reset_page_view_complete_flow_part2",
|
||||
{
|
||||
url: "/test_page_view",
|
||||
},
|
||||
() => [
|
||||
{
|
||||
content: "check that the view got fixed",
|
||||
trigger: 'iframe p:containsExact("Test Page View")',
|
||||
run: function () {}, // it's a check
|
||||
trigger: ":iframe p:text(Test Page View)",
|
||||
},
|
||||
{
|
||||
content: "check that the inherited COW view is still there (created during edit mode)",
|
||||
trigger: 'iframe #oe_structure_test_website_page .s_cover',
|
||||
run: function () {}, // it's a check
|
||||
trigger: ":iframe #oe_structure_test_website_page .s_cover",
|
||||
},
|
||||
//4. Now break the inherited view created when dropping a snippet
|
||||
{
|
||||
content: "open site menu",
|
||||
trigger: 'button[data-menu-xmlid="website.menu_site"]',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "open html editor",
|
||||
trigger: 'a[data-menu-xmlid="website.menu_ace_editor"]',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "select oe_structure view",
|
||||
trigger: '#s2id_ace-view-list', // use select2 version
|
||||
run: function () {
|
||||
var viewId = $('#ace-view-list option:contains("oe_structure_test_website_page")').val();
|
||||
$('#ace-view-list').val(viewId).trigger('change');
|
||||
},
|
||||
trigger: ".o_resource_editor_title .o_select_menu_toggler",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "select oe_structure view",
|
||||
trigger: ".o_select_menu_menu .o_select_menu_item:contains(Test Page View)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "add a broken t-field in page DOM",
|
||||
trigger: 'div.ace_line .ace_xml:contains("oe_structure_test_website_page")',
|
||||
run: function () {
|
||||
ace.edit('ace-view-editor').getSession().insert({row: 4, column: 1}, '<t t-field="not.exist"/>\n');
|
||||
run() {
|
||||
ace.edit(document.querySelector("#resource-editor div"))
|
||||
.getSession()
|
||||
.insert({row: 4, column: 1}, '<t t-field="not.exist"/>\n');
|
||||
},
|
||||
},
|
||||
{
|
||||
content: "save the html editor",
|
||||
trigger: ".o_ace_view_editor button[data-action=save]",
|
||||
trigger: ".o_resource_editor button:contains(Save)",
|
||||
run: "click",
|
||||
},
|
||||
BROKEN_STEP
|
||||
BROKEN_STEP,
|
||||
]
|
||||
);
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,174 @@
|
|||
import {
|
||||
clickOnSave,
|
||||
clickOnEditAndWaitEditMode,
|
||||
clickOnExtraMenuItem,
|
||||
registerWebsitePreviewTour,
|
||||
insertSnippet
|
||||
} from '@website/js/tours/tour_utils';
|
||||
import { stepUtils } from "@web_tour/tour_utils";
|
||||
|
||||
const EDIT_BUTTON_SELECTOR = "body .o_menu_systray button.o-website-btn-custo-primary:contains(edit)";
|
||||
|
||||
const checkNoTranslate = {
|
||||
content: "Check there is no translate button",
|
||||
trigger: `${EDIT_BUTTON_SELECTOR}:not(.o-dropdown-toggle-custo)`,
|
||||
};
|
||||
const translate = [{
|
||||
content: "Open Edit menu",
|
||||
trigger: `${EDIT_BUTTON_SELECTOR}.o-dropdown-toggle-custo`,
|
||||
run: "click",
|
||||
}, {
|
||||
content: "Click on translate button",
|
||||
trigger: ".o_popover .o_translate_website_dropdown_item:contains(translate)",
|
||||
run: "click",
|
||||
}];
|
||||
const closeErrorDialog = [{
|
||||
content: "Check has error dialog",
|
||||
trigger: ".modal:contains(error) .o_error_dialog.modal-content",
|
||||
}, {
|
||||
content: "Close error dialog",
|
||||
trigger: ".modal .modal-footer button.btn.btn-primary",
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: "body:not(:has(.modal))",
|
||||
}];
|
||||
const switchTo = (lang) => [
|
||||
{
|
||||
content: `Switch to ${lang}`,
|
||||
trigger: `:iframe .js_change_lang[data-url_code='${lang}']`,
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: `Wait until ${lang} is applied`,
|
||||
trigger: `:iframe html[lang*="${lang}"]`,
|
||||
},
|
||||
stepUtils.waitIframeIsReady(),
|
||||
];
|
||||
const goToMenuItem = [
|
||||
clickOnExtraMenuItem({}, true),
|
||||
{
|
||||
content: "Navigate to model item page",
|
||||
trigger: ":iframe a[href='/test_website/model_item/1']",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Wait to land on model item page",
|
||||
trigger: ':iframe a[href="/test_website/model_item/1"].nav-link.active:not(:visible)',
|
||||
},
|
||||
stepUtils.waitIframeIsReady(),
|
||||
];
|
||||
|
||||
registerWebsitePreviewTour('test_restricted_editor_only', {
|
||||
url: '/',
|
||||
}, () => [
|
||||
// Home
|
||||
checkNoTranslate,
|
||||
...clickOnEditAndWaitEditMode(),
|
||||
{
|
||||
content: "Check icons cannot be dragged",
|
||||
trigger: "#snippet_groups .o_snippet[name='Intro'].o_disabled",
|
||||
run: function () {
|
||||
if (document.querySelector("button.o_snippet_thumbnail_area")) {
|
||||
console.error(
|
||||
"The button to open the add snippet dialog should not be display for restricted editor."
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
...clickOnSave(),
|
||||
...switchTo('fr'),
|
||||
...translate,
|
||||
...closeErrorDialog,
|
||||
...switchTo('en'),
|
||||
// Model item
|
||||
{
|
||||
trigger: ":iframe body:contains(welcome to your)"
|
||||
},
|
||||
...goToMenuItem,
|
||||
checkNoTranslate,
|
||||
...clickOnEditAndWaitEditMode(),
|
||||
{
|
||||
content: "Check icons cannot be dragged",
|
||||
trigger: "#snippet_groups .o_snippet[name='Intro'].o_disabled",
|
||||
run: function () {
|
||||
if (document.querySelector("button.o_snippet_thumbnail_area")) {
|
||||
console.error(
|
||||
"The button to open the add snippet dialog should not be display for restricted editor."
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
...clickOnSave(),
|
||||
...switchTo('fr'),
|
||||
...translate,
|
||||
...closeErrorDialog,
|
||||
]);
|
||||
|
||||
registerWebsitePreviewTour('test_restricted_editor_test_admin', {
|
||||
url: '/',
|
||||
}, () => [
|
||||
// Home
|
||||
checkNoTranslate,
|
||||
...clickOnEditAndWaitEditMode(),
|
||||
{
|
||||
content: "Check icons cannot be dragged",
|
||||
trigger: "#snippet_groups .o_snippet[name='Intro'].o_disabled",
|
||||
},
|
||||
...clickOnSave(),
|
||||
...switchTo('fr'),
|
||||
...translate,
|
||||
...closeErrorDialog,
|
||||
...switchTo('en'),
|
||||
// Model item
|
||||
...goToMenuItem,
|
||||
checkNoTranslate,
|
||||
...clickOnEditAndWaitEditMode(),
|
||||
{
|
||||
content: "Check icons can be dragged",
|
||||
trigger: "#snippet_groups .o_snippet[name='Intro']:not(.o_disabled)",
|
||||
},
|
||||
...insertSnippet({ id: "s_banner", name: "Banner", groupName: "Intro" }),
|
||||
{
|
||||
content: "Change name",
|
||||
trigger: ":iframe [data-oe-expression='record.name']",
|
||||
run: "editor New value",
|
||||
},
|
||||
...clickOnSave(),
|
||||
...switchTo('fr'),
|
||||
...translate,
|
||||
{
|
||||
content: "Close the dialog",
|
||||
trigger: ".modal .modal-footer .btn-primary",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Assure the modal is well closed",
|
||||
trigger: "body:not(:has(.modal))",
|
||||
},
|
||||
{
|
||||
content: "Check that html fields are not content editable when translating",
|
||||
trigger: ":iframe [data-oe-expression='record.website_description']:not([contenteditable='true'])",
|
||||
},
|
||||
{
|
||||
content: "Translate name",
|
||||
trigger: ":iframe [data-oe-expression='record.name']",
|
||||
run: "editor Nouvelle valeur",
|
||||
},
|
||||
{
|
||||
content: "Translate some banner text",
|
||||
trigger: ":iframe [data-oe-expression='record.website_description'] strong",
|
||||
run: "editor potentiel.",
|
||||
},
|
||||
...clickOnSave(),
|
||||
]);
|
||||
|
||||
registerWebsitePreviewTour('test_restricted_editor_tester', {
|
||||
url: '/test_model/1',
|
||||
}, () => [
|
||||
...clickOnEditAndWaitEditMode(),
|
||||
{
|
||||
content: "Footer should not be be editable for restricted user",
|
||||
trigger: ":iframe :has(.o_editable) footer:not(.o_editable):not(:has(.o_editable))",
|
||||
},
|
||||
...clickOnSave(),
|
||||
]);
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { VideoSelector } from "@html_editor/main/media/media_dialog/video_selector";
|
||||
import { insertSnippet, registerWebsitePreviewTour } from "@website/js/tours/tour_utils";
|
||||
|
||||
registerWebsitePreviewTour(
|
||||
"snippet_background_video",
|
||||
{
|
||||
url: "/",
|
||||
edition: true,
|
||||
}, () => [
|
||||
{
|
||||
trigger: "body",
|
||||
run: function () {
|
||||
// Patch the VideoDialog so that it does not do external calls
|
||||
// during the test (note that we don't unpatch but as the patch
|
||||
// is only done after the execution of a test_website test, it
|
||||
// is acceptable).
|
||||
// TODO we should investigate to rather mock the external calls,
|
||||
// maybe not using a tour. Probably easier to discuss when the
|
||||
// new OWL editor will have been implemented.
|
||||
patch(VideoSelector.prototype, {
|
||||
async prepareVimeoPreviews() {
|
||||
// Ignore the super call and directly push a fake video
|
||||
this.state.vimeoPreviews.push({
|
||||
id: 1,
|
||||
// Those lead to 404 but it's fine for the test
|
||||
thumbnailSrc: "/hello/world.jpg",
|
||||
src: "/hello/world.mp4",
|
||||
});
|
||||
},
|
||||
async _getVideoURLData(src, options) {
|
||||
if (src === '/hello/world.mp4') {
|
||||
return {
|
||||
'platform': 'vimeo',
|
||||
'embed_url': 'about:blank',
|
||||
};
|
||||
}
|
||||
return super._getVideoURLData(...arguments);
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
...insertSnippet({
|
||||
id: "s_text_block",
|
||||
name: "Text",
|
||||
groupName: "Text",
|
||||
}),
|
||||
{
|
||||
content: "Click on the text block.",
|
||||
trigger: ":iframe #wrap section.s_text_block",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Click on the 'Background Video' button option.",
|
||||
trigger: "button[data-action-id='toggleBgVideo']",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Click on the first sample video in the modal.",
|
||||
trigger: "#video-suggestion .o_sample_video",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Check the video is select.",
|
||||
trigger: "textarea.is-valid",
|
||||
},
|
||||
{
|
||||
content: "Click on the 'Add' button to apply the selected video as the background.",
|
||||
trigger: ".modal-footer button.btn-primary",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Verify that the video is set as the background of the snippet.",
|
||||
trigger: ":iframe #wrap section.o_background_video",
|
||||
},
|
||||
{
|
||||
content: "Check that the video container is not editable.",
|
||||
trigger: ":iframe #wrap section.o_background_video > .o_bg_video_container[contenteditable=false]",
|
||||
},
|
||||
]
|
||||
);
|
||||
|
|
@ -1,6 +1,10 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import wTourUtils from 'website.tour_utils';
|
||||
import {
|
||||
clickOnEditAndWaitEditMode,
|
||||
clickOnSave,
|
||||
registerWebsitePreviewTour,
|
||||
} from '@website/js/tours/tour_utils';
|
||||
import { stepUtils } from "@web_tour/tour_utils";
|
||||
|
||||
/**
|
||||
* The purpose of these tours is to check the systray visibility:
|
||||
|
|
@ -12,189 +16,209 @@ import wTourUtils from 'website.tour_utils';
|
|||
* - as an unrelated user (neither "tester" nor restricted editor)
|
||||
*/
|
||||
|
||||
const canPublish = [{
|
||||
content: 'Publish',
|
||||
const canPublish = () => [{
|
||||
content: "Publish",
|
||||
trigger: '.o_menu_systray .o_menu_systray_item:contains("Unpublished")',
|
||||
run: "click",
|
||||
}, {
|
||||
content: 'Wait for Publish',
|
||||
content: "Wait for Publish",
|
||||
trigger: '.o_menu_systray .o_menu_systray_item:contains("Published"):not([data-processing])',
|
||||
run: () => {}, // This is a check.
|
||||
}, {
|
||||
content: 'Unpublish',
|
||||
content: "Unpublish",
|
||||
trigger: '.o_menu_systray .o_menu_systray_item:contains("Published")',
|
||||
run: "click",
|
||||
}, {
|
||||
content: 'Wait for Unpublish',
|
||||
content: "Wait for Unpublish",
|
||||
trigger: '.o_menu_systray .o_menu_systray_item:contains("Unpublished"):not([data-processing])',
|
||||
run: () => {}, // This is a check.
|
||||
}];
|
||||
|
||||
const cannotPublish = [{
|
||||
content: 'Check has no Publish/Unpublish',
|
||||
const cannotPublish = () => [{
|
||||
content: "Check has no Publish/Unpublish",
|
||||
trigger: '.o_menu_systray:not(:has(.o_menu_systray_item:contains("ublished")))',
|
||||
run: () => {}, // This is a check.
|
||||
}];
|
||||
|
||||
const canToggleMobilePreview = [{
|
||||
content: 'Enable mobile preview',
|
||||
trigger: '.o_menu_systray .o_menu_systray_item.o_mobile_preview:not(.o_mobile_preview_active)',
|
||||
const canToggleMobilePreview = () => [{
|
||||
content: "Enable mobile preview",
|
||||
trigger: '.o_menu_systray .o_menu_systray_item.o_mobile_preview:not(.o_mobile_preview_active) span',
|
||||
run: "click",
|
||||
}, {
|
||||
content: 'Disable mobile preview',
|
||||
trigger: '.o_menu_systray .o_menu_systray_item.o_mobile_preview.o_mobile_preview_active',
|
||||
content: "Disable mobile preview",
|
||||
trigger: '.o_menu_systray .o_menu_systray_item.o_mobile_preview.o_mobile_preview_active span',
|
||||
run: "click",
|
||||
}];
|
||||
|
||||
const cannotToggleMobilePreview = [{
|
||||
const cannotToggleMobilePreview = () => [{
|
||||
content: 'Enable mobile preview',
|
||||
trigger: '.o_menu_systray:not(:has(.o_menu_systray_item.o_mobile_preview))',
|
||||
run: () => {}, // This is a check.
|
||||
}];
|
||||
|
||||
// For non-website users, switching across website only works if the domains are
|
||||
// specified. Within the scope of test tours, this cannot be achieved.
|
||||
const canSwitchWebsiteNoCheck = [{
|
||||
const canSwitchWebsiteNoCheck = () => [{
|
||||
content: 'Open website switcher',
|
||||
trigger: '.o_menu_systray .o_menu_systray_item.o_website_switcher_container .dropdown-toggle:contains("My Website"):not(:contains("My Website 2"))',
|
||||
run: "click",
|
||||
}, {
|
||||
content: 'Switch to other website',
|
||||
trigger: '.o_menu_systray .o_menu_systray_item.o_website_switcher_container .dropdown-item:contains("Other")',
|
||||
run: () => {}, // This is a check.
|
||||
content: 'Can switch to other website',
|
||||
trigger: '.o-dropdown--menu .dropdown-item:contains("Other")',
|
||||
}];
|
||||
|
||||
|
||||
const canSwitchWebsite = [{
|
||||
content: 'Open website switcher',
|
||||
const canSwitchWebsite = () => [{
|
||||
content: "Open website switcher",
|
||||
trigger: '.o_menu_systray .o_menu_systray_item.o_website_switcher_container .dropdown-toggle:contains("My Website"):not(:contains("My Website 2"))',
|
||||
run: "click",
|
||||
}, {
|
||||
content: 'Switch to other website',
|
||||
trigger: '.o_menu_systray .o_menu_systray_item.o_website_switcher_container .dropdown-item:contains("Other")',
|
||||
content: "Switch to other website",
|
||||
trigger: '.o-dropdown--menu .dropdown-item:contains("Other")',
|
||||
run: "click",
|
||||
}, {
|
||||
content: 'Wait for other website',
|
||||
trigger: 'iframe body:contains("Test Model") div:contains("Other")',
|
||||
run: () => {}, // This is a check.
|
||||
content: "Wait for other website",
|
||||
trigger: ':iframe body:contains("Test Model") div:contains("Other")',
|
||||
}];
|
||||
|
||||
const canAddNewContent = [{
|
||||
content: 'Open +New content',
|
||||
trigger: '.o_menu_systray .o_menu_systray_item.o_new_content_container',
|
||||
const canAddNewContent = () => [{
|
||||
content: "Open +New content",
|
||||
trigger: '.o_menu_systray .o_menu_systray_item.o_new_content_container button',
|
||||
run: "click",
|
||||
}, {
|
||||
content: 'Close +New content',
|
||||
trigger: '#o_new_content_menu_choices',
|
||||
content: "Close +New content",
|
||||
trigger: '.o_new_content_menu_choices',
|
||||
run: "click",
|
||||
}];
|
||||
|
||||
const cannotAddNewContent = [{
|
||||
const cannotAddNewContent = () => [{
|
||||
content: 'No +New content',
|
||||
trigger: '.o_menu_systray:not(:has(.o_menu_systray_item.o_new_content_container))',
|
||||
run: () => {}, // This is a check.
|
||||
}];
|
||||
|
||||
const canEditInBackEnd = [{
|
||||
content: 'Edit in backend',
|
||||
const canEditInBackEnd = () => [{
|
||||
content: "Edit in backend",
|
||||
trigger: '.o_menu_systray .o_website_edit_in_backend a',
|
||||
run: "click",
|
||||
}, {
|
||||
content: 'Check that the form is editable',
|
||||
content: "Check that the form is editable",
|
||||
trigger: '.o_form_view_container .o_form_editable',
|
||||
run: () => {}, // This is a check.
|
||||
}, {
|
||||
content: 'Return to website',
|
||||
trigger: '.oe_button_box .fa-globe',
|
||||
content: "Return to website",
|
||||
trigger: '.o-form-buttonbox .fa-globe',
|
||||
run: "click",
|
||||
}];
|
||||
|
||||
const canViewInBackEnd = [{
|
||||
content: 'Go to backend',
|
||||
const canViewInBackEnd = () => [{
|
||||
content: "Go to backend",
|
||||
trigger: '.o_menu_systray .o_website_edit_in_backend a',
|
||||
run: "click",
|
||||
}, {
|
||||
content: 'Check that the form is read-only',
|
||||
content: "Check that the form is read-only",
|
||||
trigger: '.o_form_view_container .o_form_readonly',
|
||||
run: () => {}, // This is a check.
|
||||
}, {
|
||||
content: 'Return to website',
|
||||
trigger: '.oe_button_box .fa-globe',
|
||||
content: "Return to website",
|
||||
trigger: '.o-form-buttonbox .fa-globe',
|
||||
run: "click",
|
||||
}];
|
||||
|
||||
const canEdit = [
|
||||
...wTourUtils.clickOnEditAndWaitEditMode(),
|
||||
const canEdit = () => [
|
||||
...clickOnEditAndWaitEditMode(),
|
||||
{
|
||||
content: 'Click on name',
|
||||
trigger: 'iframe span[data-oe-expression="test_model.name"][contenteditable="true"]',
|
||||
content: "Click on name",
|
||||
trigger: ':iframe span[data-oe-expression="test_model.name"][contenteditable="true"]',
|
||||
run: "click",
|
||||
}, {
|
||||
content: 'Change name',
|
||||
trigger: 'iframe span[data-oe-expression="test_model.name"][contenteditable="true"]',
|
||||
run: 'text Better name',
|
||||
content: "Change name",
|
||||
trigger: ':iframe span[data-oe-expression="test_model.name"][contenteditable="true"]',
|
||||
run: "editor Better name",
|
||||
}, {
|
||||
content: 'Check that field becomes dirty',
|
||||
trigger: 'iframe span[data-oe-expression="test_model.name"].o_dirty',
|
||||
run: () => {}, // This is a check.
|
||||
content: "Check that field becomes dirty",
|
||||
trigger: ':iframe span[data-oe-expression="test_model.name"].o_dirty',
|
||||
},
|
||||
...wTourUtils.clickOnSave(),
|
||||
...clickOnSave(),
|
||||
{
|
||||
content: 'Check whether name is saved',
|
||||
trigger: 'iframe span[data-oe-expression="test_model.name"]:contains("Better name")',
|
||||
run: () => {}, // This is a check.
|
||||
content: "Check whether name is saved",
|
||||
trigger: ':iframe span[data-oe-expression="test_model.name"]:contains("Better name")',
|
||||
},
|
||||
];
|
||||
|
||||
const cannotEdit = [{
|
||||
content: 'Check Edit is not available',
|
||||
const cannotEdit = () => [stepUtils.waitIframeIsReady(), {
|
||||
content: "Check Edit is not available",
|
||||
trigger: '.o_menu_systray:not(:has(.o_edit_website_container))',
|
||||
run: () => {}, // This is a check.
|
||||
}];
|
||||
|
||||
const canEditButCannotChange = [
|
||||
...wTourUtils.clickOnEditAndWaitEditMode(),
|
||||
const canEditButCannotChange = () => [
|
||||
...clickOnEditAndWaitEditMode(),
|
||||
{
|
||||
content: 'Cannot change name',
|
||||
trigger: 'iframe main:not(:has([data-oe-expression])):contains("Test Model")',
|
||||
run: () => {}, // This is a check.
|
||||
trigger: ':iframe main:not(:has([data-oe-expression])):contains("Test Model")',
|
||||
},
|
||||
];
|
||||
|
||||
const ensureWebsiteSwitcherIsNotVisible = [
|
||||
{
|
||||
content: "Ensure website switcher is hidden when only one website exists",
|
||||
trigger: ".o_menu_systray:not(:has(.o_website_switcher_container))",
|
||||
},
|
||||
];
|
||||
|
||||
const ensureWebsiteSwitcherIsVisible = [
|
||||
{
|
||||
content: "Ensure website switcher is present when multiple website exists",
|
||||
trigger: ".o_menu_systray:has(.o_website_switcher_container)",
|
||||
},
|
||||
];
|
||||
|
||||
const register = (title, steps) => {
|
||||
wTourUtils.registerWebsitePreviewTour(title, {
|
||||
url: '/test_model/1',
|
||||
test: true,
|
||||
registerWebsitePreviewTour(title, {
|
||||
url: "/test_model/1",
|
||||
}, steps);
|
||||
};
|
||||
|
||||
register('test_systray_admin', [
|
||||
...canPublish,
|
||||
...canToggleMobilePreview,
|
||||
...canSwitchWebsite,
|
||||
...canAddNewContent,
|
||||
...canEditInBackEnd,
|
||||
...canEdit,
|
||||
register("test_systray_admin", () => [
|
||||
...canPublish(),
|
||||
...canToggleMobilePreview(),
|
||||
...canSwitchWebsite(),
|
||||
...canAddNewContent(),
|
||||
...canEditInBackEnd(),
|
||||
...canEdit(),
|
||||
]);
|
||||
|
||||
register('test_systray_reditor_tester', [
|
||||
...canPublish,
|
||||
...canToggleMobilePreview,
|
||||
...canSwitchWebsite,
|
||||
...canAddNewContent,
|
||||
...canEditInBackEnd,
|
||||
...canEdit,
|
||||
register("test_systray_reditor_tester", () => [
|
||||
...canPublish(),
|
||||
...canToggleMobilePreview(),
|
||||
...canSwitchWebsite(),
|
||||
...canAddNewContent(),
|
||||
...canEditInBackEnd(),
|
||||
...canEdit(),
|
||||
]);
|
||||
|
||||
register('test_systray_reditor_not_tester', [
|
||||
...cannotPublish,
|
||||
...canToggleMobilePreview,
|
||||
...canSwitchWebsite,
|
||||
...canAddNewContent,
|
||||
...canViewInBackEnd,
|
||||
...canEditButCannotChange,
|
||||
register("test_systray_reditor_not_tester", () => [
|
||||
...cannotPublish(),
|
||||
...canToggleMobilePreview(),
|
||||
...canSwitchWebsite(),
|
||||
...canAddNewContent(),
|
||||
...canViewInBackEnd(),
|
||||
...canEditButCannotChange(),
|
||||
]);
|
||||
|
||||
register('test_systray_not_reditor_tester', [
|
||||
...canPublish,
|
||||
...cannotToggleMobilePreview,
|
||||
...canSwitchWebsiteNoCheck,
|
||||
...cannotAddNewContent,
|
||||
...canEditInBackEnd,
|
||||
...cannotEdit,
|
||||
register("test_systray_not_reditor_tester", () => [
|
||||
...canPublish(),
|
||||
...cannotToggleMobilePreview(),
|
||||
...canSwitchWebsiteNoCheck(),
|
||||
...cannotAddNewContent(),
|
||||
...canEditInBackEnd(),
|
||||
...cannotEdit(),
|
||||
]);
|
||||
|
||||
register('test_systray_not_reditor_not_tester', [
|
||||
...cannotPublish,
|
||||
...cannotToggleMobilePreview,
|
||||
...canSwitchWebsiteNoCheck,
|
||||
...cannotAddNewContent,
|
||||
...canViewInBackEnd,
|
||||
...cannotEdit,
|
||||
register("test_systray_not_reditor_not_tester", () => [
|
||||
...cannotPublish(),
|
||||
...cannotToggleMobilePreview(),
|
||||
...canSwitchWebsiteNoCheck(),
|
||||
...cannotAddNewContent(),
|
||||
...canViewInBackEnd(),
|
||||
...cannotEdit(),
|
||||
{
|
||||
trigger: ":iframe main:contains(test model)",
|
||||
},
|
||||
]);
|
||||
|
||||
register("test_systray_single_website", () => ensureWebsiteSwitcherIsNotVisible);
|
||||
|
||||
register("test_systray_multi_website", () => ensureWebsiteSwitcherIsVisible);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,499 @@
|
|||
import {
|
||||
clickOnEditAndWaitEditMode,
|
||||
clickOnSave,
|
||||
insertSnippet,
|
||||
registerWebsitePreviewTour,
|
||||
} from "@website/js/tours/tour_utils";
|
||||
import { stepUtils } from "@web_tour/tour_utils";
|
||||
import { translationIsReady } from "@web/core/l10n/translation";
|
||||
|
||||
function createNewPage() {
|
||||
return [
|
||||
{
|
||||
content: "Open +New content",
|
||||
trigger: ".o_menu_systray .o_menu_systray_item.o_new_content_container button",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Create a New page",
|
||||
trigger: `button.o_new_content_element img[src="/website/static/description/icon.png"]`,
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Select Blank page",
|
||||
trigger: ".o_page_template:has(div.text-muted) .o_button_area:not(:visible)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Page name",
|
||||
trigger: ".modal-dialog .o_website_dialog input",
|
||||
run: "edit Test",
|
||||
},
|
||||
{
|
||||
content: "Confirm creation",
|
||||
trigger: ".modal-dialog .o_website_dialog .btn-primary",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_builder_sidebar_open",
|
||||
timeout: 20000,
|
||||
},
|
||||
...insertSnippet({
|
||||
id: "s_banner",
|
||||
name: "Banner",
|
||||
groupName: "Intro",
|
||||
}),
|
||||
{
|
||||
content: "Click on the link",
|
||||
trigger: ":iframe main section.s_banner a",
|
||||
async run(helpers) {
|
||||
await helpers.click();
|
||||
const el = this.anchor;
|
||||
const sel = el.ownerDocument.getSelection();
|
||||
sel.collapse(el, 0);
|
||||
el.focus();
|
||||
},
|
||||
},
|
||||
{
|
||||
content: "Click on Edit link",
|
||||
trigger: ".o_we_edit_link",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Replace URL",
|
||||
trigger: ".o-we-linkpopover .o_we_href_input_link",
|
||||
run: "edit /test_view",
|
||||
},
|
||||
{
|
||||
content: "Apply",
|
||||
trigger: ".o-we-linkpopover .btn-primary",
|
||||
run: "click",
|
||||
},
|
||||
...clickOnSave("bottom", 50000, false),
|
||||
];
|
||||
}
|
||||
|
||||
function openHtmlEditor() {
|
||||
return [
|
||||
{
|
||||
content: "Open Site menu",
|
||||
trigger: ".o_menu_sections [data-menu-xmlid='website.menu_site']",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Open HTML editor",
|
||||
trigger: ".o-overlay-item .dropdown-item[data-menu-xmlid='website.menu_ace_editor']",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Edit anyway",
|
||||
trigger: ".o_resource_editor_wrapper [role='alert'] button.btn-link",
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function saveHtmlEditor() {
|
||||
return [
|
||||
{
|
||||
content: "Save the html editor",
|
||||
trigger: ".o_resource_editor button.btn-primary",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Close the html editor",
|
||||
trigger: ".o_resource_editor button.btn-secondary",
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function singleLanguage() {
|
||||
return [
|
||||
{
|
||||
content: "Ensure single language site",
|
||||
trigger: ":iframe body:not(:has(.js_language_selector))",
|
||||
},
|
||||
// new page
|
||||
...createNewPage(),
|
||||
...openHtmlEditor(),
|
||||
{
|
||||
content: "Change text",
|
||||
trigger: 'div.ace_line .ace_xml:contains("oe_structure")',
|
||||
run() {
|
||||
window.ace.edit(document.querySelector("#resource-editor div"))
|
||||
.getSession()
|
||||
.insert({row: 8, column: 1}, '<p>More text</p>\n');
|
||||
},
|
||||
},
|
||||
...saveHtmlEditor(),
|
||||
{
|
||||
content: "Ensure page is updated",
|
||||
trigger: ":iframe body:contains(More text)",
|
||||
},
|
||||
// xml record
|
||||
{
|
||||
content: "Go to test_view page",
|
||||
trigger: ":iframe main section.s_banner a",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Wait until page is reached",
|
||||
trigger: ":iframe body:contains(Test View)",
|
||||
},
|
||||
...clickOnEditAndWaitEditMode(),
|
||||
{
|
||||
content: "Edit template text",
|
||||
trigger: ":iframe main p.o_editable[data-oe-field='arch'][contenteditable='true']",
|
||||
run: "editor Modified Text",
|
||||
},
|
||||
...clickOnSave("bottom", 50000, false),
|
||||
...openHtmlEditor(),
|
||||
{
|
||||
content: "Change text",
|
||||
trigger: 'div.ace_line .ace_xml:contains("test_website.test_view")',
|
||||
run() {
|
||||
window.ace.edit(document.querySelector("#resource-editor div"))
|
||||
.getSession()
|
||||
.insert({row: 2, column: 36}, 'Further ');
|
||||
},
|
||||
},
|
||||
...saveHtmlEditor(),
|
||||
{
|
||||
content: "Ensure view is updated",
|
||||
trigger: ":iframe body:contains(Further Modified Text)",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const ensureFrUser = {
|
||||
content: "Ensure FR user",
|
||||
trigger: ".o_website_systray:contains(Publié)",
|
||||
};
|
||||
|
||||
const ensureEnUser = {
|
||||
content: "Ensure EN user",
|
||||
trigger: ".o_website_systray:contains(Published)",
|
||||
};
|
||||
|
||||
const ensureFrSite = {
|
||||
content: "Ensure FR site",
|
||||
trigger: ":iframe .o_main_nav:contains(Accueil)",
|
||||
};
|
||||
|
||||
const ensureEnSite = {
|
||||
content: "Ensure EN site",
|
||||
trigger: ":iframe .o_main_nav:contains(Home)",
|
||||
};
|
||||
|
||||
registerWebsitePreviewTour(
|
||||
"translation_single_language_fr_user_fr_site",
|
||||
{
|
||||
url: "/",
|
||||
},
|
||||
() => [
|
||||
ensureFrUser,
|
||||
ensureFrSite,
|
||||
...singleLanguage(),
|
||||
]
|
||||
);
|
||||
|
||||
registerWebsitePreviewTour(
|
||||
"translation_single_language_en_user_fr_site",
|
||||
{
|
||||
url: "/",
|
||||
},
|
||||
() => [
|
||||
ensureEnUser,
|
||||
ensureFrSite,
|
||||
...singleLanguage(),
|
||||
]
|
||||
);
|
||||
|
||||
registerWebsitePreviewTour(
|
||||
"translation_single_language_fr_user_en_site",
|
||||
{
|
||||
url: "/",
|
||||
},
|
||||
() => [
|
||||
ensureFrUser,
|
||||
ensureEnSite,
|
||||
...singleLanguage(),
|
||||
]
|
||||
);
|
||||
|
||||
function switchLanguage(lang, timeout = 50000) {
|
||||
return [
|
||||
{
|
||||
content: "Ensure was in other language",
|
||||
trigger: `:iframe .o_header_language_selector:contains(${lang !== "fr" ? "Français" : "English"})`,
|
||||
timeout,
|
||||
}, {
|
||||
content: "Open language dropdown",
|
||||
trigger: ":iframe .o_header_language_selector .dropdown-toggle",
|
||||
run: "click",
|
||||
}, {
|
||||
content: "Select language",
|
||||
trigger: `:iframe .o_header_language_selector .js_change_lang[data-url_code=${lang}]`,
|
||||
run: "click",
|
||||
}, {
|
||||
content: "Wait until target page is loaded",
|
||||
trigger: `:iframe .o_header_language_selector:contains(${lang === "fr" ? "Français" : "English"})`,
|
||||
timeout,
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
// TODO Such a step should not be needed, but test randomly fails without it.
|
||||
const awaitTranslationIsReady = {
|
||||
content: "Await translationIsReady",
|
||||
trigger: "body",
|
||||
run: async () => {
|
||||
await translationIsReady;
|
||||
},
|
||||
};
|
||||
|
||||
function openTranslate(timeout = 50000) {
|
||||
return [
|
||||
stepUtils.waitIframeIsReady(),
|
||||
awaitTranslationIsReady,
|
||||
{
|
||||
content: "Open edit dropdown",
|
||||
trigger: ".o_edit_website_container button",
|
||||
run: "click",
|
||||
}, {
|
||||
content: "Enter translate mode",
|
||||
trigger: ".o_translate_website_dropdown_item",
|
||||
run: "click",
|
||||
}, {
|
||||
content: "Effect's 200ms setTimeout passed",
|
||||
trigger: ".o_builder_open .o_main_navbar.d-none:not(:visible)",
|
||||
}, {
|
||||
content: "Translatable text became highlighted",
|
||||
trigger: ":iframe [data-oe-translation-state=to_translate]",
|
||||
timeout,
|
||||
}, {
|
||||
content: "Confirm popup",
|
||||
trigger: ".o_website_dialog .btn-secondary",
|
||||
run: "click",
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
function saveTranslation(timeout = 50000) {
|
||||
return [
|
||||
{
|
||||
content: "Save translation",
|
||||
trigger: ".o-website-builder_sidebar button[data-action=save]",
|
||||
run: "click",
|
||||
}, {
|
||||
content: "Back to preview mode",
|
||||
trigger: ".o_edit_website_container button",
|
||||
timeout,
|
||||
}, {
|
||||
trigger: "body:not(.o_builder_open)",
|
||||
noPrepend: true,
|
||||
timeout,
|
||||
},
|
||||
stepUtils.waitIframeIsReady(),
|
||||
awaitTranslationIsReady,
|
||||
];
|
||||
}
|
||||
|
||||
function multiLanguage(mainLanguage, secondLanguage) {
|
||||
return [
|
||||
{
|
||||
content: "Ensure multi language site",
|
||||
trigger: ":iframe body:has(.js_language_selector)",
|
||||
},
|
||||
// new page
|
||||
...createNewPage(),
|
||||
...switchLanguage(secondLanguage),
|
||||
...openTranslate(),
|
||||
{
|
||||
content: "Translate some text",
|
||||
trigger: ":iframe h1 [data-oe-translation-state=to_translate]",
|
||||
run: "editor Some translated text",
|
||||
},
|
||||
...saveTranslation(),
|
||||
{
|
||||
content: "Check translation is displayed",
|
||||
trigger: ":iframe h1:contains(Some translated text)",
|
||||
},
|
||||
...openHtmlEditor(),
|
||||
{
|
||||
content: "Change text",
|
||||
trigger: 'div.ace_line .ace_xml:contains("oe_structure")',
|
||||
run() {
|
||||
window.ace.edit(document.querySelector("#resource-editor div"))
|
||||
.getSession()
|
||||
.insert({row: 6, column: 50}, "more text ");
|
||||
},
|
||||
},
|
||||
{
|
||||
content: "Pollute old DOM to detect reload",
|
||||
trigger: ":iframe body",
|
||||
run() {
|
||||
this.anchor.dataset.reloaded = false;
|
||||
},
|
||||
},
|
||||
...saveHtmlEditor(),
|
||||
{
|
||||
content: "Ensure page is NOT updated",
|
||||
trigger: ":iframe body:not([data-reloaded=false]) h1:not(:contains(more text))",
|
||||
},
|
||||
...switchLanguage(mainLanguage),
|
||||
{
|
||||
content: "Ensure French page IS updated",
|
||||
trigger: ":iframe h1:contains(more text)",
|
||||
},
|
||||
...clickOnEditAndWaitEditMode(),
|
||||
{
|
||||
content: "Change text again",
|
||||
trigger: ":iframe h1",
|
||||
run: "editor Yet another version of the text.",
|
||||
},
|
||||
...clickOnSave("bottom", 50000, false),
|
||||
...switchLanguage(secondLanguage),
|
||||
{
|
||||
content: "Ensure English page is NOT updated",
|
||||
trigger: ":iframe h1:not(:contains(Yet another))",
|
||||
},
|
||||
...openTranslate(),
|
||||
{
|
||||
content: "Ensure English page is updated",
|
||||
trigger: ":iframe h1:contains(Yet another)",
|
||||
},
|
||||
{
|
||||
content: "Translate again",
|
||||
trigger: ":iframe h1 [data-oe-translation-state=to_translate]",
|
||||
run: "editor Yet another translated text",
|
||||
},
|
||||
...saveTranslation(),
|
||||
{
|
||||
content: "Check translation is displayed",
|
||||
trigger: ":iframe h1:contains(Yet another translated text)",
|
||||
},
|
||||
// xml record
|
||||
{
|
||||
content: "Go to test_view page",
|
||||
trigger: ":iframe main section.s_banner a",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Wait until page is reached",
|
||||
trigger: ":iframe body:contains(Test View)",
|
||||
},
|
||||
...switchLanguage(mainLanguage),
|
||||
...clickOnEditAndWaitEditMode(),
|
||||
{
|
||||
content: "Edit template text",
|
||||
trigger: ":iframe main p.o_editable[contenteditable='true']",
|
||||
run: "editor Modified View",
|
||||
},
|
||||
...clickOnSave("bottom", 50000, false),
|
||||
...switchLanguage(secondLanguage),
|
||||
...openTranslate(),
|
||||
{
|
||||
content: "Translate test view",
|
||||
trigger: ":iframe main > p > span[data-oe-translation-state=to_translate]",
|
||||
run: "editor Some translated view",
|
||||
},
|
||||
...saveTranslation(),
|
||||
{
|
||||
content: "Check translation is displayed",
|
||||
trigger: ":iframe p:contains(Some translated view)",
|
||||
},
|
||||
...switchLanguage(mainLanguage),
|
||||
...openHtmlEditor(),
|
||||
{
|
||||
content: "Change text",
|
||||
trigger: 'div.ace_line .ace_xml:contains("test_website.test_view")',
|
||||
run() {
|
||||
window.ace.edit(document.querySelector("#resource-editor div"))
|
||||
.getSession()
|
||||
.insert({row: 2, column: 36}, 'Further ');
|
||||
},
|
||||
},
|
||||
...saveHtmlEditor(),
|
||||
{
|
||||
content: "Ensure view is updated",
|
||||
trigger: ":iframe body:contains(Further Modified View)",
|
||||
},
|
||||
...clickOnEditAndWaitEditMode(),
|
||||
{
|
||||
content: "Edit template text",
|
||||
trigger: ":iframe main p.o_editable[data-oe-field='arch'][contenteditable='true']",
|
||||
run: "editor Even more modified Text",
|
||||
},
|
||||
...clickOnSave("bottom", 50000, false),
|
||||
...switchLanguage(secondLanguage),
|
||||
{
|
||||
content: "Check old translation is displayed",
|
||||
trigger: ":iframe p:contains(Some translated view)",
|
||||
},
|
||||
...openTranslate(),
|
||||
{
|
||||
content: "Check new original is displayed",
|
||||
trigger: ":iframe p:contains(Even more modified text)",
|
||||
},
|
||||
{
|
||||
content: "Translate test view",
|
||||
trigger: ":iframe main > p > span[data-oe-translation-state=to_translate]",
|
||||
run: "editor Even more translated text",
|
||||
},
|
||||
...saveTranslation(),
|
||||
{
|
||||
content: "Check new translation is displayed",
|
||||
trigger: ":iframe p:contains(Even more translated text)",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
registerWebsitePreviewTour(
|
||||
"translation_multi_language_fr_user_fr_en_site",
|
||||
{
|
||||
url: "/fr",
|
||||
},
|
||||
() => [
|
||||
ensureFrUser,
|
||||
ensureFrSite,
|
||||
...multiLanguage("fr", "en"),
|
||||
]
|
||||
);
|
||||
|
||||
registerWebsitePreviewTour(
|
||||
"translation_multi_language_fr_user_en_fr_site",
|
||||
{
|
||||
url: "/en",
|
||||
},
|
||||
() => [
|
||||
ensureFrUser,
|
||||
ensureEnSite,
|
||||
...multiLanguage("en", "fr"),
|
||||
]
|
||||
);
|
||||
|
||||
registerWebsitePreviewTour(
|
||||
"translation_multi_language_en_user_fr_en_site",
|
||||
{
|
||||
url: "/fr",
|
||||
},
|
||||
() => [
|
||||
ensureEnUser,
|
||||
ensureFrSite,
|
||||
...multiLanguage("fr", "en"),
|
||||
]
|
||||
);
|
||||
|
||||
registerWebsitePreviewTour(
|
||||
"translation_multi_language_en_user_en_fr_site",
|
||||
{
|
||||
url: "/en",
|
||||
},
|
||||
() => [
|
||||
ensureEnUser,
|
||||
ensureEnSite,
|
||||
...multiLanguage("en", "fr"),
|
||||
]
|
||||
);
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
import {
|
||||
clickOnSave,
|
||||
registerWebsitePreviewTour,
|
||||
changeOptionInPopover,
|
||||
} from "@website/js/tours/tour_utils";
|
||||
|
||||
function assertEqual(actual, expected) {
|
||||
if (actual !== expected) {
|
||||
throw new Error(`Assert failed: expected: ${expected} ; got: ${actual}`);
|
||||
}
|
||||
}
|
||||
|
||||
registerWebsitePreviewTour('website_controller_page_listing_layout', {
|
||||
url: '/model/exposed-model',
|
||||
edition: true,
|
||||
}, () => [
|
||||
{
|
||||
content: "website is in preview mode",
|
||||
trigger: '.o_website_preview',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "records are listed in grid mode by default",
|
||||
trigger: ':iframe .o_website_grid',
|
||||
run() {
|
||||
const iframeDocument = document.querySelector(".o_website_preview iframe").contentDocument;
|
||||
// grid option is selected by default in the switch
|
||||
assertEqual(iframeDocument.querySelector(".listing_layout_switcher #o_wstudio_apply_grid").checked, true);
|
||||
assertEqual([...iframeDocument.querySelectorAll(".test_record_listing")].length, 2);
|
||||
},
|
||||
},
|
||||
{
|
||||
content: "open customize tab",
|
||||
trigger: ":iframe .listing_layout_switcher",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o-snippets-menu .o_customize_tab",
|
||||
},
|
||||
...changeOptionInPopover("Layout", "Default Layout", "list"),
|
||||
{
|
||||
content: "records are now displayed in list mode",
|
||||
trigger: ':iframe .o_website_list',
|
||||
run() {
|
||||
const iframeDocument = document.querySelector(".o_website_preview iframe").contentDocument;
|
||||
// list option is now selected in the switch
|
||||
assertEqual(iframeDocument.querySelector(".listing_layout_switcher #o_wstudio_apply_list").checked, true);
|
||||
},
|
||||
},
|
||||
...clickOnSave(),
|
||||
]);
|
||||
|
||||
registerWebsitePreviewTour('website_controller_page_default_page_check', {
|
||||
url: '/model/exposed-model',
|
||||
}, () => [
|
||||
{
|
||||
content: "records are listed in list mode by default",
|
||||
trigger: ':iframe [is-ready=true] .o_website_list',
|
||||
run() {
|
||||
const iframeDocument = document.querySelector(".o_website_preview iframe").contentDocument;
|
||||
// list option is selected by default in the switch
|
||||
assertEqual(iframeDocument.querySelector(".listing_layout_switcher #o_wstudio_apply_list").checked, true);
|
||||
assertEqual([...iframeDocument.querySelectorAll(".test_record_listing")].length, 2);
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
import { clickOnSave, registerWebsitePreviewTour } from "@website/js/tours/tour_utils";
|
||||
|
||||
// As admin, add a YouTube video iframe in a `sanitize_overridable` HTML field.
|
||||
registerWebsitePreviewTour("website_designer_iframe_video",
|
||||
{
|
||||
url: "/test_website/model_item/1",
|
||||
edition: true,
|
||||
},
|
||||
() => [
|
||||
{
|
||||
content: "As administrator, add a video block to the description field",
|
||||
trigger: `.o_block_tab:not(.o_we_ongoing_insertion) #snippet_content .o_snippet[name="Video"].o_draggable .o_snippet_thumbnail`,
|
||||
run: "drag_and_drop :iframe .o_test_website_description",
|
||||
},
|
||||
{
|
||||
content: "Add a video URL",
|
||||
trigger: ".o_select_media_dialog #o_video_text",
|
||||
run: `edit https://www.youtube.com/watch?v=G8b4UZIcTfg`,
|
||||
},
|
||||
{
|
||||
content: "Add the video",
|
||||
trigger: ".o_select_media_dialog .modal-footer .btn-primary",
|
||||
run: "click",
|
||||
},
|
||||
...clickOnSave(),
|
||||
{
|
||||
content: "Check that the video was correctly saved",
|
||||
trigger: ":iframe .media_iframe_video[data-oe-expression*='G8b4UZIcTfg']",
|
||||
run: () => {},
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
// Check that a restricted editor can edit the field content (even with
|
||||
// a video iframe).
|
||||
registerWebsitePreviewTour("website_restricted_editor_iframe_video", {
|
||||
url: "/test_website/model_item/1",
|
||||
edition: true,
|
||||
},
|
||||
() => [
|
||||
{
|
||||
content: "Check that the video iframe was correctly restored after saving the changes",
|
||||
trigger:
|
||||
":iframe [data-oe-field]:not([data-oe-sanitize-prevent-edition]) .media_iframe_video[data-oe-expression*='G8b4UZIcTfg']",
|
||||
run: () => {},
|
||||
},
|
||||
{
|
||||
content: "As a restricted editor, edit the HTML field content",
|
||||
trigger: ":iframe .o_test_website_description",
|
||||
run: "editor I can still edit the HTML field",
|
||||
},
|
||||
...clickOnSave(),
|
||||
{
|
||||
content: "Check that the HTML content (with a video iframe) was correctly updated",
|
||||
trigger:
|
||||
":iframe .o_test_website_description:contains('I can still edit the HTML field')",
|
||||
run: () => {},
|
||||
},
|
||||
]
|
||||
);
|
||||
|
|
@ -0,0 +1,382 @@
|
|||
import {
|
||||
assertPathName,
|
||||
clickOnSave,
|
||||
getClientActionUrl,
|
||||
registerWebsitePreviewTour,
|
||||
} from "@website/js/tours/tour_utils";
|
||||
import { stepUtils } from "@web_tour/tour_utils";
|
||||
|
||||
const openPagePropertiesDialog = [
|
||||
{
|
||||
content: "Open Site backend menu",
|
||||
trigger: '[data-menu-xmlid="website.menu_site"]',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Open page properties dialog",
|
||||
trigger: '[data-menu-xmlid="website.menu_page_properties"]',
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
|
||||
const clickOnSaveButtonStep = [
|
||||
{
|
||||
content: "Click on Save & Close",
|
||||
trigger: ".o_form_button_save:enabled",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Wait",
|
||||
trigger: "body:not(.modal-open)",
|
||||
}
|
||||
];
|
||||
|
||||
const openCreatePageDialog = [
|
||||
{
|
||||
content: "Open create content menu",
|
||||
trigger: ".o_new_content_container button",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Create a new page",
|
||||
trigger: 'button[aria-label="New Page"]',
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
|
||||
function assertPageCanonicalUrlIs(url) {
|
||||
return [
|
||||
{
|
||||
content: `Verify page canonical url is ${url}`,
|
||||
trigger: `:iframe head:hidden link[rel="canonical"][href$="${url}"]`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function checkIsTemplate(isTemplate, pageTitle = undefined) {
|
||||
return [
|
||||
...openCreatePageDialog,
|
||||
{
|
||||
trigger: 'button[data-id="custom"]',
|
||||
},
|
||||
{
|
||||
content: "Go to custom section",
|
||||
trigger: 'button[data-id="custom"]',
|
||||
run: "click",
|
||||
},
|
||||
...(isTemplate
|
||||
? [
|
||||
{
|
||||
content: `Verify template ${pageTitle} exists`,
|
||||
trigger: `.o_page_template .o_page_name:contains(${pageTitle}):hidden`,
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
trigger: ".o_website_page_templates_pane .alert-info",
|
||||
},
|
||||
{
|
||||
content: `Verify custom templates section is empty`,
|
||||
trigger: `.o_website_page_templates_pane:not(:has(.o_page_template))`,
|
||||
},
|
||||
]
|
||||
),
|
||||
{
|
||||
content: "Exit dialog",
|
||||
trigger: ".modal-header .btn-close",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Exit new content backdrop",
|
||||
trigger: "body",
|
||||
run: "press escape",
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
function testCommonProperties(url, canPublish, modifiedUrl = undefined) {
|
||||
if (!modifiedUrl) {
|
||||
modifiedUrl = url;
|
||||
}
|
||||
|
||||
const steps = {
|
||||
setup: [
|
||||
{
|
||||
content: "Open Edit Menu dialog",
|
||||
trigger: '.o_field_widget[name="is_in_menu"] + .btn-link',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Check that menu editor was opened",
|
||||
trigger: ".oe_menu_editor",
|
||||
},
|
||||
{
|
||||
content: "Close Edit Menu dialog",
|
||||
trigger: ".modal:has(.oe_menu_editor) .btn-close",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Add to menu",
|
||||
trigger: "#is_in_menu_0",
|
||||
run: "check",
|
||||
},
|
||||
{
|
||||
content: "Set as homepage",
|
||||
trigger: "#is_homepage_0",
|
||||
run: "check",
|
||||
},
|
||||
],
|
||||
check: [
|
||||
{
|
||||
content: "Verify is in menu",
|
||||
trigger: `:visible :iframe #top_menu a[href="${modifiedUrl}"]`,
|
||||
},
|
||||
stepUtils.goToUrl(getClientActionUrl("/")),
|
||||
...assertPageCanonicalUrlIs(modifiedUrl),
|
||||
stepUtils.goToUrl(getClientActionUrl(modifiedUrl)),
|
||||
],
|
||||
teardown: [
|
||||
{
|
||||
content: "Remove from menu",
|
||||
trigger: "#is_in_menu_0",
|
||||
run: "uncheck",
|
||||
},
|
||||
{
|
||||
content: "Unset as homepage",
|
||||
trigger: "#is_homepage_0",
|
||||
run: "uncheck",
|
||||
},
|
||||
],
|
||||
checkTorndown: [
|
||||
{
|
||||
content: "Verify is not in menu",
|
||||
trigger: `:visible :iframe #top_menu:not(:has(a[href="${url}"]))`,
|
||||
},
|
||||
stepUtils.goToUrl(getClientActionUrl("/")),
|
||||
...assertPageCanonicalUrlIs("/"),
|
||||
stepUtils.goToUrl(getClientActionUrl(url)),
|
||||
],
|
||||
finalize() {
|
||||
return [
|
||||
...openPagePropertiesDialog,
|
||||
...this.setup,
|
||||
...clickOnSaveButtonStep,
|
||||
...this.check,
|
||||
...openPagePropertiesDialog,
|
||||
...this.teardown,
|
||||
...clickOnSaveButtonStep,
|
||||
...this.checkTorndown,
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
if (canPublish) {
|
||||
steps.setup.push({
|
||||
content: "Publish",
|
||||
trigger: "#is_published_0",
|
||||
run: "check",
|
||||
});
|
||||
steps.check.push({
|
||||
content: "Verify is published",
|
||||
trigger: '[data-hotkey="p"] .form-check input:checked',
|
||||
});
|
||||
steps.teardown.push({
|
||||
content: "Unpublish",
|
||||
trigger: "#is_published_0",
|
||||
run: "uncheck",
|
||||
});
|
||||
steps.checkTorndown.push({
|
||||
content: "Verify is not published",
|
||||
trigger: '[data-hotkey="p"] .form-check input:not(:checked)',
|
||||
});
|
||||
}
|
||||
|
||||
return steps;
|
||||
}
|
||||
|
||||
function testWebsitePageProperties() {
|
||||
const steps = testCommonProperties("/new-page", true, "/cool-page");
|
||||
steps.setup.unshift(
|
||||
{
|
||||
content: "Change page title",
|
||||
trigger: "#name_0",
|
||||
run: "edit Cool Page",
|
||||
},
|
||||
{
|
||||
content: `Change url to /cool-page`,
|
||||
trigger: "#url_0",
|
||||
run: `edit cool-page && press Enter`,
|
||||
},
|
||||
{
|
||||
content: "Enable old url redirect",
|
||||
trigger: "#redirect_old_url_0",
|
||||
run: "check",
|
||||
},
|
||||
{
|
||||
content: "Open redirect type popup",
|
||||
trigger: "#redirect_type_0",
|
||||
run: "click"
|
||||
},
|
||||
{
|
||||
content: "Set redirect type to temporary",
|
||||
trigger: ".o-dropdown-item[data-choice-index='1']",
|
||||
run: "click"
|
||||
},
|
||||
{
|
||||
// TODO: this needs to be tested
|
||||
content: "Change date published",
|
||||
trigger: "#date_publish_0",
|
||||
run: "edit 02/01/2005 01:00:00 && press enter",
|
||||
},
|
||||
{
|
||||
content: "Don't index",
|
||||
trigger: "#website_indexed_0",
|
||||
run: "uncheck",
|
||||
},
|
||||
{
|
||||
content: "Open visibility popup",
|
||||
trigger: "#visibility_0",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
// TODO: this needs to be tested
|
||||
content: "Make visible with password only",
|
||||
trigger: ".o-dropdown-item[data-choice-index='3']",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Set password to 123",
|
||||
trigger: "#visibility_password_display_0",
|
||||
run: "edit 123",
|
||||
},
|
||||
{
|
||||
content: "Make it a template",
|
||||
trigger: "#is_new_page_template_0",
|
||||
run: "check",
|
||||
},
|
||||
);
|
||||
steps.check.push(
|
||||
{
|
||||
content: "Verify page title",
|
||||
trigger: ":iframe head:hidden title:contains(/Cool Page/)",
|
||||
},
|
||||
...assertPageCanonicalUrlIs("/cool-page"),
|
||||
stepUtils.goToUrl(getClientActionUrl("/new-page")),
|
||||
assertPathName("/cool-page", "body"),
|
||||
{
|
||||
content: "Verify no index",
|
||||
trigger: ':iframe head:hidden meta[name="robots"][content="noindex"]',
|
||||
},
|
||||
...checkIsTemplate(true, "Cool Page"),
|
||||
);
|
||||
steps.teardown.unshift(
|
||||
{
|
||||
content: "Reset page title",
|
||||
trigger: "#name_0",
|
||||
run: `edit New Page`,
|
||||
},
|
||||
{
|
||||
content: `Change url back to /new-page`,
|
||||
trigger: "#url_0",
|
||||
run: `edit new-page && press Enter`,
|
||||
},
|
||||
{
|
||||
content: "Open date published popup",
|
||||
trigger: "#date_publish_0",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Reset date published",
|
||||
trigger: "button[title='Clear']",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Do index",
|
||||
trigger: "#website_indexed_0",
|
||||
run: "check",
|
||||
},
|
||||
{
|
||||
content: "Open visibility popup",
|
||||
trigger: "#visibility_0",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Make visible public",
|
||||
trigger: ".o-dropdown-item[data-choice-index='0']",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Remove from templates",
|
||||
trigger: "#is_new_page_template_0",
|
||||
run: "uncheck",
|
||||
},
|
||||
);
|
||||
steps.checkTorndown.push(
|
||||
{
|
||||
content: "Verify page title",
|
||||
trigger: ":iframe head:hidden title:contains(/New Page/)",
|
||||
},
|
||||
...assertPageCanonicalUrlIs("/new-page"),
|
||||
stepUtils.goToUrl(getClientActionUrl("/new-page")),
|
||||
assertPathName("/new-page", "body"),
|
||||
{
|
||||
content: "Verify is indexed",
|
||||
trigger: ':iframe head:hidden:not(:has(meta[name="robots"][content="noindex"]))',
|
||||
},
|
||||
...checkIsTemplate(false),
|
||||
);
|
||||
return steps;
|
||||
}
|
||||
|
||||
registerWebsitePreviewTour(
|
||||
"website_page_properties_common",
|
||||
{
|
||||
url: "/test_view",
|
||||
},
|
||||
() => [...testCommonProperties("/test_view", false).finalize()],
|
||||
);
|
||||
|
||||
registerWebsitePreviewTour(
|
||||
"website_page_properties_can_publish",
|
||||
{
|
||||
url: "/test_website/model_item/1",
|
||||
},
|
||||
() => [...testCommonProperties("/test_website/model_item/1", true).finalize()],
|
||||
);
|
||||
|
||||
registerWebsitePreviewTour(
|
||||
"website_page_properties_website_page",
|
||||
{
|
||||
url: "/",
|
||||
},
|
||||
() => [
|
||||
...openCreatePageDialog,
|
||||
{
|
||||
content: "Use blank template",
|
||||
trigger: ".o_page_template .o_button_area:hidden",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Name page",
|
||||
trigger: ".modal-body input",
|
||||
run: "edit New Page",
|
||||
},
|
||||
{
|
||||
content: "Don't add to menu",
|
||||
trigger: ".modal-body .o_switch",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Click on Create button",
|
||||
trigger: ".modal-footer .btn-primary",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Wait for editor to open",
|
||||
trigger: ":iframe body.editor_enable",
|
||||
timeout: 30000,
|
||||
},
|
||||
...clickOnSave(),
|
||||
...testWebsitePageProperties().finalize(),
|
||||
],
|
||||
);
|
||||
|
|
@ -1,49 +1,39 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import tour from "web_tour.tour";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { stepUtils } from "@web_tour/tour_utils";
|
||||
|
||||
const websiteName = "Website Test Settings";
|
||||
|
||||
tour.register("website_settings_m2o_dirty", {
|
||||
test: true,
|
||||
url: "/web",
|
||||
},
|
||||
[
|
||||
tour.stepUtils.showAppsMenuItem(),
|
||||
{
|
||||
content: "open settings",
|
||||
trigger: ".o_app[data-menu-xmlid='base.menu_administration'",
|
||||
}, {
|
||||
content: "open website settings",
|
||||
trigger: ".settings_tab .tab[data-key='website']",
|
||||
}, {
|
||||
content: "check that the 'Shared Customers Accounts' setting is checked",
|
||||
trigger: "input#shared_user_account:checked",
|
||||
run: function () {}, // it's a check
|
||||
}, {
|
||||
content: "open website switcher",
|
||||
trigger: "input#website_id",
|
||||
}, {
|
||||
content: `select ${websiteName} in the website switcher`,
|
||||
trigger: `li:has(.dropdown-item:contains('${websiteName}'))`,
|
||||
}, {
|
||||
content: `check that the settings of ${websiteName} are loaded (Shared Customers Accounts)`,
|
||||
trigger: "input#shared_user_account:not(:checked)",
|
||||
run: function () {}, // it's a check
|
||||
}, {
|
||||
content: "click on the fake website setting after checking the edited website",
|
||||
trigger: "button[name='action_website_test_setting']",
|
||||
}, {
|
||||
content: "check that we are on '/'",
|
||||
trigger: "iframe body div#wrap",
|
||||
run: function () {
|
||||
if (window.location.pathname !== "/") {
|
||||
// If this fails, it's probably because the change of website
|
||||
// in the settings dirty the record and so there is a dialog
|
||||
// save/discard displayed. This test ensure that does not happen
|
||||
// because it makes actions unreachable in multi website.
|
||||
console.error("We should be on '/' the settings didn't work");
|
||||
}
|
||||
}
|
||||
},
|
||||
]);
|
||||
registry.category("web_tour.tours").add("website_settings_m2o_dirty", {
|
||||
url: "/odoo",
|
||||
steps: () => [
|
||||
stepUtils.showAppsMenuItem(),
|
||||
{
|
||||
content: "open settings",
|
||||
trigger: ".o_app[data-menu-xmlid='base.menu_administration']",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "open website settings",
|
||||
trigger: ".settings_tab .tab[data-key='website']",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "check that the 'Shared Customers Accounts' setting is checked",
|
||||
trigger: "input[id^='shared_user_account']:checked",
|
||||
},
|
||||
{
|
||||
content: "open website switcher",
|
||||
trigger: "input[id^='website_id']",
|
||||
run: `edit ${websiteName}`,
|
||||
},
|
||||
{
|
||||
content: `select ${websiteName} in the website switcher`,
|
||||
trigger: `li:has(.dropdown-item:contains('${websiteName}'))`,
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: `check that the settings of ${websiteName} are loaded (Shared Customers Accounts)`,
|
||||
trigger: "input[id^='shared_user_account']:not(:checked)",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
from . import test_controller_args
|
||||
from . import test_custom_snippet
|
||||
from . import test_error
|
||||
from . import test_form
|
||||
from . import test_fuzzy
|
||||
from . import test_image_upload_progress
|
||||
from . import test_is_multilang
|
||||
|
|
@ -13,9 +14,17 @@ from . import test_multi_company
|
|||
from . import test_page_manager
|
||||
from . import test_page
|
||||
from . import test_performance
|
||||
from . import test_qweb
|
||||
from . import test_redirect
|
||||
from . import test_reset_views
|
||||
from . import test_restricted_editor
|
||||
from . import test_session
|
||||
from . import test_settings
|
||||
from . import test_snippet_background_video
|
||||
from . import test_systray
|
||||
from . import test_theme_ir_asset
|
||||
from . import test_translation
|
||||
from . import test_views_during_module_operation
|
||||
from . import test_website_controller_page
|
||||
from . import test_website_page_properties
|
||||
from . import test_website_field_sanitize
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<asset id="theme_default.test_asset_tag_aaa" name="Test asset tag (init active => keep active => update active)">
|
||||
<bundle>test_asset_bundle</bundle>
|
||||
<path>theme_default/tests/something.scss</path>
|
||||
</asset>
|
||||
<asset id="theme_default.test_asset_tag_aii" name="Test asset tag (init active => make inactive => update inactive)">
|
||||
<bundle>test_asset_bundle</bundle>
|
||||
<path>theme_default/tests/something.scss</path>
|
||||
</asset>
|
||||
<asset id="theme_default.test_asset_tag_aia" name="Test asset tag (init active => make inactive => update active)">
|
||||
<bundle>test_asset_bundle</bundle>
|
||||
<path>theme_default/tests/something.scss</path>
|
||||
<field name="active">True</field> <!-- Take into account during update -->
|
||||
</asset>
|
||||
<asset id="theme_default.test_asset_tag_iii" name="Test asset tag (init inactive => keep inactive => update inactive)" active="False">
|
||||
<bundle>test_asset_bundle</bundle>
|
||||
<path>theme_default/tests/something.scss</path>
|
||||
</asset>
|
||||
<asset id="theme_default.test_asset_tag_iaa" name="Test asset tag (init inactive => make active => update active)" active="False">
|
||||
<bundle>test_asset_bundle</bundle>
|
||||
<path>theme_default/tests/something.scss</path>
|
||||
</asset>
|
||||
<asset id="theme_default.test_asset_tag_prepend" name="Test asset tag with directive">
|
||||
<bundle directive="prepend">test_asset_bundle</bundle>
|
||||
<path>theme_default/tests/something.scss</path>
|
||||
</asset>
|
||||
<asset id="theme_default.test_asset_tag_extra" name="Test asset tag with extra field">
|
||||
<bundle>test_asset_bundle</bundle>
|
||||
<path>theme_default/tests/something.scss</path>
|
||||
<field name="sequence" eval="17"/>
|
||||
</asset>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<template id="test_website.test_template" name="test template 2">
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<t t-call-assets="test_website.test_bundle" t-js="False"/>
|
||||
<meta/>
|
||||
<t t-call-assets="test_website.test_bundle" t-css="False"/>
|
||||
</head>
|
||||
<body>
|
||||
<img src="http://test.external.link/img.png"/>
|
||||
<img src="/test_website/static/img.png"/>
|
||||
<a href="http://test.external.link/link">x</a>
|
||||
<a href="/web/content/local_link">x</a>
|
||||
<span t-attf-style="background-image: url('/web/image/2')" t-att-empty="False">xxx</span>
|
||||
<div widget="html" t-field="user.signature"/>
|
||||
<div widget="image" t-field="user.avatar_1920" t-options="{'widget': 'image'}"/>
|
||||
</body>
|
||||
</html>
|
||||
</template>
|
||||
|
||||
<template id="test_website.test_template_tatt_qweb" name="t-att template">
|
||||
<a t-att-href='"/"'>1</a>
|
||||
<a t-att-href='False'>2</a>
|
||||
<a t-att-href='None'>3</a>
|
||||
<a t-att-href=''>4</a>
|
||||
<a t-att-href='""'>5</a>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import odoo.tests
|
||||
from odoo.tools import mute_logger
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
@odoo.tests.common.tagged('post_install', '-at_install')
|
||||
|
|
@ -32,9 +33,10 @@ class TestWebsiteControllerArgs(odoo.tests.HttpCase):
|
|||
self.assertEqual(req.status_code, 200)
|
||||
self.assertEqual(req.json(), {'a': 'valueA', 'kw': {'b': 'valueB'}})
|
||||
|
||||
req = self.url_open('/test_website/country/whatever-999999')
|
||||
self.assertEqual(req.status_code, 404,
|
||||
"Model converter record does not exist, return a 404.")
|
||||
with patch.object(self.registry['ir.http'], '_get_error_html', lambda e, code, v: (code, '')):
|
||||
req = self.url_open('/test_website/country/whatever-999999')
|
||||
self.assertEqual(req.status_code, 404,
|
||||
"Model converter record does not exist, return a 404.")
|
||||
|
||||
|
||||
@odoo.tests.common.tagged('post_install', '-at_install')
|
||||
|
|
@ -44,3 +46,8 @@ class TestWebsiteControllers(odoo.tests.TransactionCase):
|
|||
website = self.env['website'].browse(1)
|
||||
locs = website.with_user(website.user_id)._enumerate_pages(query_string='test_website_sitemap')
|
||||
self.assertEqual(len(list(locs)), 1, "The same URL should only be shown once")
|
||||
|
||||
def test_02_search_controller(self):
|
||||
website = self.env['website'].browse(1)
|
||||
res = website._enumerate_pages(query_string="/test_website/country/elgium")
|
||||
self.assertIn('/test_website/country/belgium', next(res).get('loc'))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests import tagged, HttpCase
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestForm(HttpCase):
|
||||
|
||||
def test_form_conditional_visibility_record_field(self):
|
||||
self.start_tour(
|
||||
self.env['website'].get_client_action_url('/test_website/model_item/1'),
|
||||
'test_form_conditional_visibility_record_field',
|
||||
login='admin',
|
||||
)
|
||||
|
|
@ -5,7 +5,7 @@ import logging
|
|||
import psycopg2
|
||||
|
||||
from odoo.addons.website.controllers.main import Website
|
||||
from odoo.addons.website.tools import MockRequest
|
||||
from odoo.addons.http_routing.tests.common import MockRequest
|
||||
import odoo.tests
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
|
@ -108,15 +108,15 @@ class TestAutoComplete(TransactionCase):
|
|||
test_page.name = 'testTotallyUnique'
|
||||
|
||||
# Editor and Designer see pages in result
|
||||
self._autocomplete_page('testTotallyUnique', 1, False)
|
||||
self._autocomplete_page('testTotallyUnique', 1, None)
|
||||
|
||||
test_page.visibility = 'connected'
|
||||
self._autocomplete_page('testTotallyUnique', 1, False)
|
||||
test_page.visibility = False
|
||||
|
||||
test_page.groups_id = self.env.ref('base.group_public')
|
||||
test_page.group_ids = self.env.ref('base.group_public')
|
||||
self._autocomplete_page('testTotallyUnique', 1, False)
|
||||
test_page.groups_id = False
|
||||
test_page.group_ids = False
|
||||
|
||||
# Public user don't see restricted page
|
||||
saved_env = self.env
|
||||
|
|
@ -126,12 +126,12 @@ class TestAutoComplete(TransactionCase):
|
|||
test_page.website_indexed = True
|
||||
self._autocomplete_page('testTotallyUnique', 1, False)
|
||||
|
||||
test_page.groups_id = self.env.ref('base.group_system')
|
||||
test_page.group_ids = self.env.ref('base.group_system')
|
||||
self._autocomplete_page('testTotallyUnique', 0, "Not found")
|
||||
|
||||
test_page.groups_id = self.env.ref('base.group_public')
|
||||
test_page.group_ids = self.env.ref('base.group_public')
|
||||
self._autocomplete_page('testTotallyUnique', 1, False)
|
||||
test_page.groups_id = False
|
||||
test_page.group_ids = False
|
||||
|
||||
test_page.visibility = 'password'
|
||||
self._autocomplete_page('testTotallyUnique', 0, "Not found")
|
||||
|
|
@ -141,3 +141,11 @@ class TestAutoComplete(TransactionCase):
|
|||
|
||||
# restore website env for next tests
|
||||
self.website.env = self.env = saved_env
|
||||
|
||||
def test_indirect(self):
|
||||
self._autocomplete('module', 4, 'model')
|
||||
self._autocomplete('rechord', 3, 'record')
|
||||
self._autocomplete('suborder', 1, 'submodel')
|
||||
# Sub-sub-fields are currently not supported.
|
||||
# Adapt expected result if this becomes a feature.
|
||||
self._autocomplete('tagg', 0, "Not found")
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.web_editor.controllers.main import Web_Editor
|
||||
from odoo.addons.html_editor.controllers.main import HTML_Editor
|
||||
from odoo.addons.web_unsplash.controllers.main import Web_Unsplash
|
||||
|
||||
import odoo.tests
|
||||
|
|
@ -18,15 +18,15 @@ class TestImageUploadProgress(odoo.tests.HttpCase):
|
|||
def test_02_image_upload_progress_unsplash(self):
|
||||
BASE_URL = self.base_url()
|
||||
|
||||
@http.route('/web_editor/media_library_search', type='json', auth="user", website=True)
|
||||
@http.route('/html_editor/media_library_search', type='jsonrpc', auth="user", website=True)
|
||||
def media_library_search(self, **params):
|
||||
return {"results": 0, "media": []}
|
||||
# because not preprocessed by ControllerType metaclass
|
||||
media_library_search.original_endpoint.routing_type = 'json'
|
||||
# disable undraw, no third party should be called in tests
|
||||
self.patch(Web_Editor, 'media_library_search', media_library_search)
|
||||
self.patch(HTML_Editor, 'media_library_search', media_library_search)
|
||||
|
||||
@http.route("/web_unsplash/fetch_images", type='json', auth="user")
|
||||
@http.route("/web_unsplash/fetch_images", type='jsonrpc', auth="user")
|
||||
def fetch_unsplash_images(self, **post):
|
||||
return {
|
||||
'total': 1434,
|
||||
|
|
@ -36,11 +36,11 @@ class TestImageUploadProgress(odoo.tests.HttpCase):
|
|||
'alt_description': 'brown fox sitting on green grass field during daytime',
|
||||
'urls': {
|
||||
# 'regular': 'https://images.unsplash.com/photo-1462953491269-9aff00919695?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwzMDUwOHwwfDF8c2VhcmNofDF8fGZveHxlbnwwfHx8fDE2MzEwMzIzNDE&ixlib=rb-1.2.1&q=80&w=1080',
|
||||
'regular': BASE_URL + '/website/static/src/img/phone.png',
|
||||
'regular': BASE_URL + '/website/static/src/img/user-restricted-image.png',
|
||||
},
|
||||
'links': {
|
||||
# 'download_location': 'https://api.unsplash.com/photos/HQqIOc8oYro/download?ixid=MnwzMDUwOHwwfDF8c2VhcmNofDF8fGZveHxlbnwwfHx8fDE2MzEwMzIzNDE'
|
||||
'download_location': BASE_URL + '/website/static/src/img/phone.png',
|
||||
'download_location': BASE_URL + '/website/static/src/img/user-restricted-image.png',
|
||||
},
|
||||
'user': {
|
||||
'name': 'Mitchell Admin',
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ class TestIsMultiLang(odoo.tests.HttpCase):
|
|||
it = self.env.ref('base.lang_it').sudo()
|
||||
en = self.env.ref('base.lang_en').sudo()
|
||||
be = self.env.ref('base.lang_fr_BE').sudo()
|
||||
country1 = self.env['res.country'].create({'name': "My Super Country"})
|
||||
country1 = self.env['res.country'].create({'name': "My Super Country", 'code': 'ZV'})
|
||||
|
||||
it.active = True
|
||||
be.active = True
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
from lxml import html
|
||||
|
||||
from odoo.addons.website.tools import MockRequest
|
||||
from odoo.tests import tagged, HttpCase
|
||||
|
||||
|
||||
|
|
@ -21,6 +20,9 @@ class TestWebsiteMenu(HttpCase):
|
|||
controller_url = '/test_website/model_item/'
|
||||
website = self.env['website'].browse(1)
|
||||
|
||||
# First render to fill the cache.
|
||||
self.url_open(f"{controller_url}{records[0].id}")
|
||||
|
||||
self.env['website.menu'].create([{
|
||||
'name': records[0].name,
|
||||
'url': f"{controller_url}{records[0].id}",
|
||||
|
|
@ -36,10 +38,6 @@ class TestWebsiteMenu(HttpCase):
|
|||
}])
|
||||
for record in records:
|
||||
record_url = f"{controller_url}{record.id}"
|
||||
with MockRequest(self.env, website=website, url_root='', path=record_url):
|
||||
tree = html.fromstring(self.env['ir.qweb']._render('test_website.model_item', {
|
||||
'record': record,
|
||||
'main_object': record,
|
||||
}))
|
||||
menu_link_el = tree.xpath(".//*[@id='top_menu']//a[@href='%s' and contains(@class, 'active')]" % record_url)
|
||||
self.assertEqual(len(menu_link_el), 1, "The menu link related to the current record should be active")
|
||||
tree = html.fromstring(self.url_open(record_url).content)
|
||||
menu_link_el = tree.xpath(".//*[@id='top_menu']//a[@href='%s' and hasclass('active')]" % record_url)
|
||||
self.assertEqual(len(menu_link_el), 1, "The menu link related to the current record should be active")
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests import HttpCase, tagged
|
||||
from odoo.tests.common import HOST
|
||||
from odoo.tools import config, mute_logger
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
|
|
@ -12,7 +11,7 @@ class WithContext(HttpCase):
|
|||
website = self.env['website'].browse([1])
|
||||
website.write({
|
||||
'name': 'Test Website',
|
||||
'domain': f'http://{HOST}:{config["http_port"]}',
|
||||
'domain': self.base_url(),
|
||||
'homepage_url': '/unexisting',
|
||||
})
|
||||
home_url = '/'
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import odoo.tests
|
||||
from odoo.addons.website.tests.common import HttpCaseWithWebsiteUser
|
||||
|
||||
|
||||
@odoo.tests.common.tagged('post_install', '-at_install')
|
||||
class TestWebsitePageManager(odoo.tests.HttpCase):
|
||||
class TestWebsitePageManager(HttpCaseWithWebsiteUser):
|
||||
def test_page_manager_test_model(self):
|
||||
if self.env['website'].search_count([]) == 1:
|
||||
website2 = self.env['website'].create({
|
||||
|
|
@ -21,8 +22,8 @@ class TestWebsitePageManager(odoo.tests.HttpCase):
|
|||
"There should at least be one record without website_id and one for 2 different websites",
|
||||
)
|
||||
self.assertNotIn('website_id', self.env['test.model']._fields)
|
||||
self.start_tour('/web#action=test_website.action_test_model_multi_website', 'test_website_page_manager', login="admin")
|
||||
self.start_tour('/odoo/action-test_website.action_test_model_multi_website', 'test_website_page_manager', login="website_user")
|
||||
# This second test is about ensuring that you can switch from a list
|
||||
# view which has no `website_pages_list` js_class to its kanban view
|
||||
self.start_tour('/web#action=test_website.action_test_model_multi_website_js_class_bug', 'test_website_page_manager_js_class_bug', login="admin")
|
||||
self.start_tour('/web#action=test_website.action_test_model', 'test_website_page_manager_no_website_id', login="admin")
|
||||
self.start_tour('/odoo/action-test_website.action_test_model_multi_website_js_class_bug', 'test_website_page_manager_js_class_bug', login="website_user")
|
||||
self.start_tour('/odoo/action-test_website.action_test_model', 'test_website_page_manager_no_website_id', login="website_user")
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ class TestPerformance(UtilPerf):
|
|||
def test_10_perf_sql_website_controller_minimalist(self):
|
||||
url = '/empty_controller_test'
|
||||
select_tables_perf = {
|
||||
'base_registry_signaling': 1,
|
||||
'orm_signaling_registry': 1,
|
||||
}
|
||||
self._check_url_hot_query(url, 1, select_tables_perf)
|
||||
self.assertEqual(self._get_url_hot_query(url, cache=False), 1)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,88 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from lxml import etree
|
||||
import re
|
||||
|
||||
from odoo import tools
|
||||
from odoo.addons.base.tests.common import TransactionCaseWithUserDemo
|
||||
from odoo.addons.http_routing.tests.common import MockRequest
|
||||
|
||||
|
||||
class TestQweb(TransactionCaseWithUserDemo):
|
||||
def _load(self, module, filepath):
|
||||
tools.convert_file(self.env, module, filepath, {}, 'init')
|
||||
|
||||
def test_qweb_cdn(self):
|
||||
self._load('test_website', 'tests/template_qweb_test.xml')
|
||||
|
||||
website = self.env.ref('website.default_website')
|
||||
website.write({
|
||||
"cdn_activated": True,
|
||||
"cdn_url": "http://test.cdn"
|
||||
})
|
||||
|
||||
demo = self.env['res.users'].search([('login', '=', 'demo')])[0]
|
||||
demo.write({"signature": '''<span class="toto">
|
||||
span<span class="fa"></span><img src="/web/image/1"/>
|
||||
</span>'''})
|
||||
|
||||
demo_env = self.env(user=demo)
|
||||
|
||||
html = demo_env['ir.qweb']._render('test_website.test_template', {"user": demo}, website_id=website.id)
|
||||
asset_bundle_xmlid = 'test_website.test_bundle'
|
||||
qweb = self.env['ir.qweb']
|
||||
bundle = qweb._get_asset_bundle(asset_bundle_xmlid, css=True, js=True, assets_params={'website_id': website.id})
|
||||
|
||||
asset_version_js = bundle.get_version('js')
|
||||
asset_version_css = bundle.get_version('css')
|
||||
css_url, js_url = bundle.get_links()[-2:]
|
||||
|
||||
html = html.strip()
|
||||
html = re.sub(r'\?unique=[^"]+', '', html).encode('utf8')
|
||||
|
||||
format_data = {
|
||||
"css": css_url,
|
||||
"js": js_url,
|
||||
"user_id": demo.id,
|
||||
"filename": "Marc%20Demo",
|
||||
"alt": "Marc Demo",
|
||||
"asset_xmlid": asset_bundle_xmlid,
|
||||
"asset_version_css": asset_version_css,
|
||||
"asset_version_js": asset_version_js,
|
||||
}
|
||||
self.assertHTMLEqual(html, ("""<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="http://test.external.link/style1.css"/>
|
||||
<link type="text/css" rel="stylesheet" href="http://test.external.link/style2.css"/>
|
||||
<link type="text/css" rel="stylesheet" href="http://test.cdn%(css)s"/>
|
||||
<meta/>
|
||||
<script type="text/javascript" src="http://test.external.link/javascript1.js"></script>
|
||||
<script type="text/javascript" src="http://test.external.link/javascript2.js"></script>
|
||||
<script type="text/javascript" src="http://test.cdn%(js)s" onerror="__odooAssetError=1"></script>
|
||||
</head>
|
||||
<body>
|
||||
<img src="http://test.external.link/img.png" loading="lazy"/>
|
||||
<img src="http://test.cdn/test_website/static/img.png" loading="lazy"/>
|
||||
<a href="http://test.external.link/link">x</a>
|
||||
<a href="http://test.cdn/web/content/local_link">x</a>
|
||||
<span style="background-image: url('http://test.cdn/web/image/2')">xxx</span>
|
||||
<div widget="html"><span class="toto">
|
||||
span<span class="fa"></span><img src="http://test.cdn/web/image/1" loading="lazy">
|
||||
</span></div>
|
||||
<div widget="image"><img src="http://test.cdn/web/image/res.users/%(user_id)s/avatar_1920/%(filename)s" class="img img-fluid" alt="%(alt)s" loading="lazy"/></div>
|
||||
</body>
|
||||
</html>""" % format_data).encode('utf8'))
|
||||
|
||||
with MockRequest(self.env, website=website):
|
||||
html = demo_env['ir.qweb']._render('test_website.test_template_tatt_qweb', {}, website_id=website.id)
|
||||
self.assertHTMLEqual(html, ("""
|
||||
<html>
|
||||
<body><a href="/">1</a>
|
||||
<a>2</a>
|
||||
<a>3</a>
|
||||
<a>4</a>
|
||||
<a href="">5</a></body>
|
||||
</html>
|
||||
"""))
|
||||
|
|
@ -3,7 +3,6 @@
|
|||
import odoo
|
||||
from odoo.tests import HttpCase, tagged
|
||||
from odoo.tools import mute_logger
|
||||
from odoo.addons.http_routing.models.ir_http import slug
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
|
|
@ -19,7 +18,7 @@ class TestRedirect(HttpCase):
|
|||
'login': 'portal_user',
|
||||
'password': 'portal_user',
|
||||
'email': 'portal_user@mail.com',
|
||||
'groups_id': [(6, 0, [self.env.ref('base.group_portal').id])]
|
||||
'group_ids': [(6, 0, [self.env.ref('base.group_portal').id])]
|
||||
})
|
||||
|
||||
def test_01_redirect_308_model_converter(self):
|
||||
|
|
@ -37,7 +36,7 @@ class TestRedirect(HttpCase):
|
|||
- Correct & working redirect as logged in user
|
||||
- Correct replace of url_for() URLs in DOM
|
||||
"""
|
||||
url = '/test_website/country/' + slug(country_ad)
|
||||
url = '/test_website/country/' + self.env['ir.http']._slug(country_ad)
|
||||
redirect_url = url.replace('test_website', 'redirected')
|
||||
|
||||
# [Public User] Open the original url and check redirect OK
|
||||
|
|
@ -57,12 +56,12 @@ class TestRedirect(HttpCase):
|
|||
self.assertTrue(redirect_url in r.text, "Ensure the url_for has replaced the href URL in the DOM")
|
||||
|
||||
def test_redirect_308_by_method_url_rewrite(self):
|
||||
self.env['website.rewrite'].create({
|
||||
self.env['website.rewrite'].create([{
|
||||
'name': 'Test Website Redirect',
|
||||
'redirect_type': '308',
|
||||
'url_from': url_from,
|
||||
'url_to': f'{url_from}_new',
|
||||
} for url_from in ('/get', '/post', '/get_post'))
|
||||
} for url_from in ('/get', '/post', '/get_post')])
|
||||
|
||||
self.env.ref('test_website.test_view').arch = '''
|
||||
<t>
|
||||
|
|
@ -90,7 +89,7 @@ class TestRedirect(HttpCase):
|
|||
rec_published = self.env['test.model'].create({'name': 'name', 'website_published': True})
|
||||
rec_unpublished = self.env['test.model'].create({'name': 'name', 'website_published': False})
|
||||
|
||||
WebsiteHttp = odoo.addons.website.models.ir_http.Http
|
||||
WebsiteHttp = odoo.addons.website.models.ir_http.IrHttp
|
||||
|
||||
def _get_error_html(env, code, value):
|
||||
return str(code).split('_')[-1], f"CUSTOM {code}"
|
||||
|
|
@ -201,7 +200,7 @@ class TestRedirect(HttpCase):
|
|||
'name': '301 test record',
|
||||
'is_published': True,
|
||||
})
|
||||
url_rec1 = '/test_website/200/' + slug(rec1)
|
||||
url_rec1 = '/test_website/200/' + self.env['ir.http']._slug(rec1)
|
||||
r = self.url_open(url_rec1)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
|
@ -223,7 +222,7 @@ class TestRedirect(HttpCase):
|
|||
# 4. Accessing unpublished record with redirect to another published
|
||||
# record: expecting redirect to that record
|
||||
rec2 = rec1.copy({'is_published': True})
|
||||
url_rec2 = '/test_website/200/' + slug(rec2)
|
||||
url_rec2 = '/test_website/200/' + self.env['ir.http']._slug(rec2)
|
||||
redirect.url_to = url_rec2
|
||||
r = self.url_open(url_rec1)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
|
@ -264,7 +263,7 @@ class TestRedirect(HttpCase):
|
|||
'name': '301 test record',
|
||||
'is_published': True,
|
||||
})
|
||||
url_rec1 = f"/test_countries_308/{slug(rec1)}"
|
||||
url_rec1 = f"/test_countries_308/{self.env['ir.http']._slug(rec1)}"
|
||||
|
||||
resp = self.url_open("/test_countries_308", allow_redirects=False)
|
||||
self.assertEqual(resp.status_code, 308)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import json
|
||||
import re
|
||||
|
||||
import odoo.tests
|
||||
from odoo.tools import mute_logger
|
||||
import unittest
|
||||
|
||||
|
||||
def break_view(view, fr='<p>placeholder</p>', to='<p t-field="no_record.exist"/>'):
|
||||
|
|
@ -17,8 +20,10 @@ class TestWebsiteResetViews(odoo.tests.HttpCase):
|
|||
resp = self.url_open(page)
|
||||
self.assertEqual(resp.status_code, 500, "Waiting 500")
|
||||
self.assertTrue('<button data-mode="soft" class="reset_templates_button' in resp.text)
|
||||
data = {'view_id': self.find_template(resp), 'redirect': page, 'mode': mode}
|
||||
resp = self.url_open('/website/reset_template', data)
|
||||
data = {'params': {'view_id': self.find_template(resp), 'mode': mode}}
|
||||
self.url_open('/website/reset_template', data=json.dumps(data), headers={'Content-Type': 'application/json'})
|
||||
resp = self.url_open(page)
|
||||
self.assertTrue(resp.url.endswith(page), "We should be checking the test page")
|
||||
self.assertEqual(resp.status_code, 200, "Waiting 200")
|
||||
|
||||
def find_template(self, response):
|
||||
|
|
@ -104,6 +109,7 @@ class TestWebsiteResetViews(odoo.tests.HttpCase):
|
|||
# Break it again to have a previous arch different than file arch
|
||||
break_view(self.test_page_view.with_context(website_id=1))
|
||||
self.assertEqual(total_views + 1, self.View.search_count([('type', '=', 'qweb')]), "Missing COW view")
|
||||
|
||||
with self.assertRaises(AssertionError):
|
||||
# soft reset should not be able to reset the view as previous
|
||||
# version is also broken
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import unittest
|
||||
|
||||
import odoo.tests
|
||||
from odoo.tools import mute_logger
|
||||
from odoo.addons.website.tests.common import HttpCaseWithWebsiteUser
|
||||
|
||||
|
||||
@odoo.tests.common.tagged('post_install', '-at_install')
|
||||
class TestRestrictedEditor(HttpCaseWithWebsiteUser):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
website = cls.env['website'].search([], limit=1)
|
||||
fr = cls.env.ref('base.lang_fr').sudo()
|
||||
en = cls.env.ref('base.lang_en').sudo()
|
||||
|
||||
fr.active = True
|
||||
|
||||
website.default_lang_id = en
|
||||
website.language_ids = en + fr
|
||||
|
||||
cls.env['website.menu'].create({
|
||||
'name': 'Model item',
|
||||
'url': '/test_website/model_item/1',
|
||||
'parent_id': website.menu_id.id,
|
||||
'sequence': 100,
|
||||
})
|
||||
|
||||
@mute_logger('odoo.addons.http_routing.models.ir_http', 'odoo.http')
|
||||
def test_01_restricted_editor_only(self):
|
||||
self.start_tour(self.env['website'].get_client_action_url('/'), 'test_restricted_editor_only', login="website_user")
|
||||
|
||||
@mute_logger('odoo.addons.http_routing.models.ir_http', 'odoo.http')
|
||||
def test_02_restricted_editor_test_admin(self):
|
||||
self.user_website_user.group_ids += self.env.ref("test_website.group_test_website_admin")
|
||||
self.start_tour(self.env['website'].get_client_action_url('/'), 'test_restricted_editor_test_admin', login="website_user")
|
||||
|
||||
# FIXME the logic of the commit that introduced the fix at 8c41c147a4c6a415e
|
||||
# was reverted, so this test is disabled for now. Branding *on views* as
|
||||
# a restricted editor is something we want in some custo (e.g. odoo.com).
|
||||
# See commit messages for details.
|
||||
@unittest.skip
|
||||
@mute_logger('odoo.addons.http_routing.models.ir_http', 'odoo.http')
|
||||
def test_03_restricted_editor_tester(self):
|
||||
"""
|
||||
Tests that restricted users cannot edit ir.ui.view records despite being
|
||||
on a page of a record (main_object) they can edit.
|
||||
"""
|
||||
self.user_website_user.group_ids += self.env.ref("test_website.group_test_website_tester")
|
||||
self.start_tour(self.env['website'].get_client_action_url('/test_model/1'), 'test_restricted_editor_tester', login='website_user')
|
||||
|
|
@ -1,7 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from lxml import html
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo import http
|
||||
from odoo.addons.website.models.website import Website
|
||||
import odoo.tests
|
||||
|
||||
from odoo.addons.base.tests.common import HttpCaseWithUserDemo
|
||||
|
|
@ -13,27 +16,77 @@ class TestWebsiteSession(HttpCaseWithUserDemo):
|
|||
def test_01_run_test(self):
|
||||
self.start_tour('/', 'test_json_auth')
|
||||
|
||||
def test_02_inactive_session_lang(self):
|
||||
session = self.authenticate(None, None)
|
||||
self.env.ref('base.lang_fr').active = False
|
||||
session.context['lang'] = 'fr_FR'
|
||||
odoo.http.root.session_store.save(session)
|
||||
|
||||
# ensure that _get_current_website_id will be able to match a website
|
||||
current_website_id = self.env["website"]._get_current_website_id(odoo.tests.HOST)
|
||||
self.env["website"].browse(current_website_id).domain = odoo.tests.HOST
|
||||
|
||||
res = self.url_open('/test_website_sitemap') # any auth='public' route would do
|
||||
res.raise_for_status()
|
||||
|
||||
def test_03_totp_login_with_inactive_session_lang(self):
|
||||
session = self.authenticate(None, None)
|
||||
self.env.ref('base.lang_fr').active = False
|
||||
session.context['lang'] = 'fr_FR'
|
||||
odoo.http.root.session_store.save(session)
|
||||
|
||||
# ensure that _get_current_website_id will be able to match a website
|
||||
current_website_id = self.env["website"]._get_current_website_id(odoo.tests.HOST)
|
||||
self.env["website"].browse(current_website_id).domain = odoo.tests.HOST
|
||||
|
||||
with patch.object(self.env.registry["res.users"], "_mfa_url", return_value="/web/login/totp"):
|
||||
res = self.url_open('/web/login', allow_redirects=False, data={
|
||||
'login': 'demo',
|
||||
'password': 'demo',
|
||||
'csrf_token': http.Request.csrf_token(self),
|
||||
})
|
||||
res.raise_for_status()
|
||||
self.assertEqual(res.status_code, 303)
|
||||
self.assertTrue(res.next.path_url.startswith("/web/login/totp"))
|
||||
|
||||
def test_04_ensure_website_get_cached_values_can_be_called(self):
|
||||
session = self.authenticate('admin', 'admin')
|
||||
|
||||
# Force a browser language that is not installed
|
||||
session.context['lang'] = 'fr_MC'
|
||||
http.root.session_store.save(session)
|
||||
|
||||
# Disable cache in order to make sure that values would be fetched at any time
|
||||
get_cached_values_without_cache = Website._get_cached_values.__cache__.method
|
||||
with patch.object(Website, '_get_cached_values',
|
||||
side_effect=get_cached_values_without_cache, autospec=True):
|
||||
|
||||
# ensure that permissions on logout are OK
|
||||
res = self.url_open('/web/session/logout')
|
||||
self.assertEqual(res.status_code, 200)
|
||||
|
||||
def test_branding_cache(self):
|
||||
def has_branding(html_text):
|
||||
el = html.fromstring(html_text)
|
||||
return el.xpath('//*[@data-oe-model="test.model"]')
|
||||
|
||||
self.user_demo.groups_id += self.env.ref('website.group_website_restricted_editor')
|
||||
self.user_demo.groups_id -= self.env.ref('website.group_website_designer')
|
||||
self.user_demo.group_ids += self.env.ref('website.group_website_restricted_editor')
|
||||
self.user_demo.group_ids += self.env.ref('test_website.group_test_website_admin')
|
||||
self.user_demo.group_ids -= self.env.ref('website.group_website_designer')
|
||||
|
||||
# Create session for demo user.
|
||||
public_session = self.authenticate(None, None)
|
||||
demo_session = self.authenticate('demo', 'demo')
|
||||
record = self.env['test.model'].search([], limit=1)
|
||||
result = self.url_open(f'/test_website/model_item/{record.id}')
|
||||
result = self.url_open(f'/test_website/model_item_sudo/{record.id}')
|
||||
self.assertTrue(has_branding(result.text), "Should have branding for user demo")
|
||||
|
||||
# Public user.
|
||||
self.opener.cookies['session_id'] = public_session.sid
|
||||
result = self.url_open(f'/test_website/model_item/{record.id}')
|
||||
self.opener.cookies.set("session_id", public_session.sid, domain=odoo.tests.common.HOST)
|
||||
result = self.url_open(f'/test_website/model_item_sudo/{record.id}')
|
||||
self.assertFalse(has_branding(result.text), "Should have no branding for public user")
|
||||
|
||||
# Back to demo user.
|
||||
self.opener.cookies['session_id'] = demo_session.sid
|
||||
result = self.url_open(f'/test_website/model_item/{record.id}')
|
||||
self.opener.cookies.set("session_id", demo_session.sid, domain=odoo.tests.common.HOST)
|
||||
result = self.url_open(f'/test_website/model_item_sudo/{record.id}')
|
||||
self.assertTrue(has_branding(result.text), "Should have branding for user demo")
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import odoo.tests
|
|||
|
||||
@odoo.tests.tagged('-at_install', 'post_install')
|
||||
class TestWebsiteSettings(odoo.tests.HttpCase):
|
||||
|
||||
def test_01_multi_website_settings(self):
|
||||
# If not enabled (like in demo data), landing on res.config will try
|
||||
# to disable module_sale_quotation_builder and raise an issue
|
||||
|
|
@ -13,4 +14,4 @@ class TestWebsiteSettings(odoo.tests.HttpCase):
|
|||
if group_order_template:
|
||||
self.env.ref('base.group_user').write({"implied_ids": [(4, group_order_template.id)]})
|
||||
self.env['website'].create({'name': "Website Test Settings", 'specific_user_account': True})
|
||||
self.start_tour("/web", 'website_settings_m2o_dirty', login="admin")
|
||||
self.start_tour("/odoo", 'website_settings_m2o_dirty', login="admin")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import odoo.tests
|
||||
|
||||
|
||||
@odoo.tests.common.tagged('post_install', '-at_install')
|
||||
class TestSnippetBackgroundVideo(odoo.tests.HttpCase):
|
||||
|
||||
def test_snippet_background_video(self):
|
||||
self.start_tour("/", "snippet_background_video", login="admin")
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests.common import HOST, new_test_user, tagged
|
||||
|
|
@ -31,7 +32,8 @@ class TestSystray(HttpCase):
|
|||
</xpath>
|
||||
"""
|
||||
})
|
||||
|
||||
# Remain on page when switching website
|
||||
cls.env['website'].search([]).homepage_url = '/test_model/1'
|
||||
|
||||
@mute_logger('odoo.addons.http_routing.models.ir_http', 'odoo.http')
|
||||
def test_01_admin(self):
|
||||
|
|
@ -39,27 +41,43 @@ class TestSystray(HttpCase):
|
|||
|
||||
@mute_logger('odoo.addons.http_routing.models.ir_http', 'odoo.http')
|
||||
def test_02_reditor_tester(self):
|
||||
self.user_test.groups_id |= self.group_restricted_editor
|
||||
self.user_test.groups_id |= self.group_tester
|
||||
self.user_test.group_ids |= self.group_restricted_editor
|
||||
self.user_test.group_ids |= self.group_tester
|
||||
self.start_tour(self.env['website'].get_client_action_url('/test_model/1'), 'test_systray_reditor_tester', login="testtest")
|
||||
|
||||
@mute_logger('odoo.addons.http_routing.models.ir_http', 'odoo.http')
|
||||
def test_03_reditor_not_tester(self):
|
||||
self.user_test.groups_id |= self.group_restricted_editor
|
||||
self.user_test.groups_id = self.user_test.groups_id.filtered(lambda group: group != self.group_tester)
|
||||
self.assertNotIn(self.group_tester.id, self.user_test.groups_id.ids, "User should not be a group_tester")
|
||||
self.user_test.group_ids |= self.group_restricted_editor
|
||||
self.user_test.group_ids = self.user_test.group_ids.filtered(lambda group: group != self.group_tester)
|
||||
self.assertNotIn(self.group_tester.id, self.user_test.group_ids.ids, "User should not be a group_tester")
|
||||
self.start_tour(self.env['website'].get_client_action_url('/test_model/1'), 'test_systray_reditor_not_tester', login="testtest")
|
||||
|
||||
@mute_logger('odoo.addons.http_routing.models.ir_http', 'odoo.http')
|
||||
def test_04_not_reditor_tester(self):
|
||||
self.user_test.groups_id = self.user_test.groups_id.filtered(lambda group: group != self.group_restricted_editor)
|
||||
self.user_test.groups_id |= self.group_tester
|
||||
self.assertNotIn(self.group_restricted_editor.id, self.user_test.groups_id.ids, "User should not be a group_restricted_editor")
|
||||
self.user_test.group_ids = self.user_test.group_ids.filtered(lambda group: group != self.group_restricted_editor)
|
||||
self.user_test.group_ids |= self.group_tester
|
||||
self.assertNotIn(self.group_restricted_editor.id, self.user_test.group_ids.ids, "User should not be a group_restricted_editor")
|
||||
self.start_tour(self.env['website'].get_client_action_url('/test_model/1'), 'test_systray_not_reditor_tester', login="testtest")
|
||||
|
||||
@mute_logger('odoo.addons.http_routing.models.ir_http', 'odoo.http')
|
||||
def test_05_not_reditor_not_tester(self):
|
||||
self.user_test.groups_id = self.user_test.groups_id.filtered(lambda group: group not in [self.group_restricted_editor, self.group_tester])
|
||||
self.assertNotIn(self.group_restricted_editor.id, self.user_test.groups_id.ids, "User should not be a group_restricted_editor")
|
||||
self.assertNotIn(self.group_tester.id, self.user_test.groups_id.ids, "User should not be a group_tester")
|
||||
self.user_test.group_ids = self.user_test.group_ids.filtered(lambda group: group not in [self.group_restricted_editor, self.group_tester])
|
||||
self.assertNotIn(self.group_restricted_editor.id, self.user_test.group_ids.ids, "User should not be a group_restricted_editor")
|
||||
self.assertNotIn(self.group_tester.id, self.user_test.group_ids.ids, "User should not be a group_tester")
|
||||
self.start_tour(self.env['website'].get_client_action_url('/test_model/1'), 'test_systray_not_reditor_not_tester', login="testtest")
|
||||
|
||||
@mute_logger('odoo.addons.http_routing.models.ir_http', 'odoo.http')
|
||||
def test_06_single_website(self):
|
||||
if self.env['website'].search_count([]) > 1:
|
||||
website = self.env['website'].search([], limit=1)
|
||||
websites_to_remove = self.env['website'].search([('id', '!=', website.id)])
|
||||
websites_to_remove.unlink()
|
||||
self.start_tour(self.env['website'].get_client_action_url('/test_model/1'), 'test_systray_single_website', login="admin")
|
||||
|
||||
@mute_logger('odoo.addons.http_routing.models.ir_http', 'odoo.http')
|
||||
def test_07_multi_website(self):
|
||||
if self.env['website'].search_count([]) == 1:
|
||||
self.env['website'].create({
|
||||
'name': 'My Website 2',
|
||||
})
|
||||
self.start_tour(self.env['website'].get_client_action_url('/test_model/1'), 'test_systray_multi_website', login="admin")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests.common import TransactionCase, tagged
|
||||
from odoo.tools import convert_file
|
||||
from odoo.tools.misc import file_path
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestThemeAsset(TransactionCase):
|
||||
|
||||
def test_theme_asset_tag(self):
|
||||
"""
|
||||
Verify that assets defined with the <asset> tag are properly imported.
|
||||
"""
|
||||
# Load new records
|
||||
convert_file(
|
||||
self.env, 'theme_default',
|
||||
file_path('test_website/tests/asset_tag.xml'),
|
||||
{}, 'init', False,
|
||||
)
|
||||
active_keep_asset = self.env.ref('theme_default.test_asset_tag_aaa')
|
||||
inactive_keep_asset = self.env.ref('theme_default.test_asset_tag_iii')
|
||||
active_switch_asset_reset = self.env.ref('theme_default.test_asset_tag_aia')
|
||||
active_switch_asset_ignore = self.env.ref('theme_default.test_asset_tag_aii')
|
||||
inactive_switch_asset = self.env.ref('theme_default.test_asset_tag_iaa')
|
||||
prepend_asset = self.env.ref('theme_default.test_asset_tag_prepend')
|
||||
asset_with_extra_field = self.env.ref('theme_default.test_asset_tag_extra')
|
||||
|
||||
# Verify initial load
|
||||
self.assertEqual(prepend_asset._name, 'theme.ir.asset', 'Model should be theme.ir.asset')
|
||||
self.assertEqual(prepend_asset.name, 'Test asset tag with directive', 'Name not loaded')
|
||||
self.assertEqual(prepend_asset.directive, 'prepend', 'Directive not loaded')
|
||||
self.assertEqual(prepend_asset.bundle, 'test_asset_bundle', 'Bundle not loaded')
|
||||
self.assertEqual(prepend_asset.path, 'theme_default/tests/something.scss', 'Path not loaded')
|
||||
self.assertEqual(asset_with_extra_field.sequence, 17, 'Sequence not loaded')
|
||||
self.assertTrue(active_keep_asset.active, 'Should be active')
|
||||
self.assertTrue(active_switch_asset_reset.active, 'Should be active')
|
||||
self.assertTrue(active_switch_asset_ignore.active, 'Should be active')
|
||||
self.assertFalse(inactive_keep_asset.active, 'Should be inactive')
|
||||
self.assertFalse(inactive_switch_asset.active, 'Should be inactive')
|
||||
|
||||
# Patch records
|
||||
prepend_asset.name = 'changed'
|
||||
prepend_asset.directive = 'append'
|
||||
prepend_asset.bundle = 'changed'
|
||||
prepend_asset.path = 'theme_default/tests/changed.scss'
|
||||
asset_with_extra_field.sequence = 3
|
||||
active_switch_asset_reset.active = False
|
||||
active_switch_asset_ignore.active = False
|
||||
inactive_switch_asset.active = True
|
||||
|
||||
# Update records
|
||||
convert_file(
|
||||
self.env, 'theme_default',
|
||||
file_path('test_website/tests/asset_tag.xml'),
|
||||
{
|
||||
'theme_default.test_asset_tag_aaa': active_keep_asset.id,
|
||||
'theme_default.test_asset_tag_iii': inactive_keep_asset.id,
|
||||
'theme_default.test_asset_tag_aia': active_switch_asset_reset.id,
|
||||
'theme_default.test_asset_tag_aii': active_switch_asset_ignore.id,
|
||||
'theme_default.test_asset_tag_iaa': inactive_switch_asset.id,
|
||||
'theme_default.test_asset_tag_prepend': prepend_asset.id,
|
||||
'theme_default.test_asset_tag_extra': asset_with_extra_field.id,
|
||||
}, 'update', False,
|
||||
)
|
||||
|
||||
# Verify updated load
|
||||
self.assertEqual(prepend_asset.name, 'Test asset tag with directive', 'Name not restored')
|
||||
self.assertEqual(prepend_asset.directive, 'prepend', 'Directive not restored')
|
||||
self.assertEqual(prepend_asset.bundle, 'test_asset_bundle', 'Bundle not restored')
|
||||
self.assertEqual(prepend_asset.path, 'theme_default/tests/something.scss', 'Path not restored')
|
||||
self.assertEqual(asset_with_extra_field.sequence, 17, 'Sequence not restored')
|
||||
self.assertTrue(active_keep_asset.active, 'Should be active')
|
||||
self.assertTrue(active_switch_asset_reset.active, 'Should be reset to active')
|
||||
self.assertFalse(active_switch_asset_ignore.active, 'Should be kept inactive')
|
||||
self.assertFalse(inactive_keep_asset.active, 'Should be inactive')
|
||||
self.assertTrue(inactive_switch_asset.active, 'Should be kept active')
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from odoo import Command
|
||||
from odoo.tests import HttpCase, tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install', 'is_tour')
|
||||
class TestTranslation(HttpCase):
|
||||
|
||||
def _single_language_fr_user_fr_site(self):
|
||||
self.start_tour(self.env['website'].get_client_action_url('/'), 'translation_single_language_fr_user_fr_site', login='admin')
|
||||
|
||||
def _single_language_en_user_fr_site(self):
|
||||
self.start_tour(self.env['website'].get_client_action_url('/'), 'translation_single_language_en_user_fr_site', login='admin')
|
||||
|
||||
def _single_language_fr_user_en_site(self):
|
||||
self.start_tour(self.env['website'].get_client_action_url('/'), 'translation_single_language_fr_user_en_site', login='admin')
|
||||
|
||||
def _multi_language_fr_user_fr_en_site(self):
|
||||
self.start_tour(self.env['website'].get_client_action_url('/'), 'translation_multi_language_fr_user_fr_en_site', login='admin', timeout=250)
|
||||
|
||||
def _multi_language_fr_user_en_fr_site(self):
|
||||
self.start_tour(self.env['website'].get_client_action_url('/'), 'translation_multi_language_fr_user_en_fr_site', login='admin', timeout=250)
|
||||
|
||||
def _multi_language_en_user_fr_en_site(self):
|
||||
self.start_tour(self.env['website'].get_client_action_url('/'), 'translation_multi_language_en_user_fr_en_site', login='admin', timeout=250)
|
||||
|
||||
def _multi_language_en_user_en_fr_site(self):
|
||||
self.start_tour(self.env['website'].get_client_action_url('/'), 'translation_multi_language_en_user_en_fr_site', login='admin', timeout=250)
|
||||
|
||||
def _fr_db(self):
|
||||
self._fr_en_db()
|
||||
lang_en = self.env.ref('base.lang_en')
|
||||
lang_en.active = False
|
||||
|
||||
def _fr_en_db(self):
|
||||
lang_en = self.env.ref('base.lang_en')
|
||||
lang_fr = self.env.ref('base.lang_fr')
|
||||
self.env["base.language.install"].create({
|
||||
'overwrite': True,
|
||||
'lang_ids': [(6, 0, [lang_fr.id])],
|
||||
}).lang_install()
|
||||
for website in self.env['website'].search([]):
|
||||
website.language_ids += lang_fr
|
||||
website.default_lang_id = lang_fr
|
||||
website.language_ids -= lang_en
|
||||
self.env['website'].create({
|
||||
'sequence': 1,
|
||||
'name': 'Test FR Website',
|
||||
'language_ids': [
|
||||
Command.link(lang_fr.id),
|
||||
],
|
||||
'default_lang_id': lang_fr.id,
|
||||
})
|
||||
|
||||
for user in self.env['res.users'].search([]):
|
||||
user.lang = lang_fr.code
|
||||
for partner in self.env['res.partner'].search([]):
|
||||
partner.lang = lang_fr.code
|
||||
for user in self.env['res.users'].with_context(active_test=False).search([]):
|
||||
user.lang = lang_fr.code
|
||||
|
||||
def _en_fr_db(self):
|
||||
lang_fr = self.env.ref('base.lang_fr')
|
||||
self.env["base.language.install"].create({
|
||||
'overwrite': True,
|
||||
'lang_ids': [(6, 0, [lang_fr.id])],
|
||||
}).lang_install()
|
||||
|
||||
def test_fr_db_fr_site(self):
|
||||
self._fr_db()
|
||||
self._single_language_fr_user_fr_site()
|
||||
|
||||
def test_fr_en_db_fr_site(self):
|
||||
self._fr_en_db()
|
||||
self._single_language_fr_user_fr_site()
|
||||
|
||||
def test_fr_en_db_en_site(self):
|
||||
self._fr_en_db()
|
||||
lang_en = self.env.ref('base.lang_en')
|
||||
lang_fr = self.env.ref('base.lang_fr')
|
||||
for website in self.env['website'].search([]):
|
||||
website.language_ids += lang_en
|
||||
website.default_lang_id = lang_en
|
||||
website.language_ids -= lang_fr
|
||||
self._single_language_fr_user_en_site()
|
||||
|
||||
def test_fr_en_db_fr_en_site(self):
|
||||
self._fr_en_db()
|
||||
lang_en = self.env.ref('base.lang_en')
|
||||
for website in self.env['website'].search([]):
|
||||
website.language_ids += lang_en
|
||||
self._multi_language_fr_user_fr_en_site()
|
||||
|
||||
def test_fr_en_db_en_fr_site(self):
|
||||
self._fr_en_db()
|
||||
lang_en = self.env.ref('base.lang_en')
|
||||
for website in self.env['website'].search([]):
|
||||
website.language_ids += lang_en
|
||||
website.default_lang_id = lang_en
|
||||
self._multi_language_fr_user_en_fr_site()
|
||||
|
||||
def test_en_fr_db_fr_site(self):
|
||||
self._en_fr_db()
|
||||
lang_en = self.env.ref('base.lang_en')
|
||||
lang_fr = self.env.ref('base.lang_fr')
|
||||
self.env["base.language.install"].create({
|
||||
'overwrite': True,
|
||||
'lang_ids': [(6, 0, [lang_fr.id])],
|
||||
}).lang_install()
|
||||
for website in self.env['website'].search([]):
|
||||
website.language_ids += lang_fr
|
||||
website.default_lang_id = lang_fr
|
||||
website.language_ids -= lang_en
|
||||
self._single_language_en_user_fr_site()
|
||||
|
||||
def test_en_fr_db_fr_en_site(self):
|
||||
self._en_fr_db()
|
||||
lang_fr = self.env.ref('base.lang_fr')
|
||||
for website in self.env['website'].search([]):
|
||||
website.language_ids += lang_fr
|
||||
website.default_lang_id = lang_fr
|
||||
self._multi_language_en_user_fr_en_site()
|
||||
|
||||
def test_en_fr_db_en_fr_site(self):
|
||||
self._en_fr_db()
|
||||
lang_fr = self.env.ref('base.lang_fr')
|
||||
for website in self.env['website'].search([]):
|
||||
website.language_ids += lang_fr
|
||||
self._multi_language_en_user_en_fr_site()
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.website.tools import MockRequest
|
||||
from odoo.addons.http_routing.tests.common import MockRequest
|
||||
from odoo.tests import standalone
|
||||
|
||||
|
||||
|
|
@ -70,8 +70,7 @@ def test_01_cow_views_unlink_on_module_update(env):
|
|||
# Upgrade the module
|
||||
test_website_module = env['ir.module.module'].search([('name', '=', 'test_website')])
|
||||
test_website_module.button_immediate_upgrade()
|
||||
env.reset() # clear the set of environments
|
||||
env = env() # get an environment that refers to the new registry
|
||||
env.transaction.reset() # clear the set of environments
|
||||
|
||||
# Ensure generic views got removed
|
||||
view = env.ref('test_website.update_module_view_to_be_t_called', raise_if_not_found=False)
|
||||
|
|
@ -178,10 +177,22 @@ def test_02_copy_ids_views_unlink_on_module_update(env):
|
|||
|
||||
view_website_1, view_website_2, theme_child_view = _simulate_xml_view()
|
||||
|
||||
old_registry = env.registry
|
||||
|
||||
# Upgrade the module
|
||||
theme_default.button_immediate_upgrade()
|
||||
env.reset() # clear the set of environments
|
||||
env = env() # get an environment that refers to the new registry
|
||||
env.transaction.reset() # clear the set of environments
|
||||
|
||||
# Beware: records do not belong to the correct registry anymore
|
||||
assert env.registry is not old_registry
|
||||
# Therefore we need to re-obtain them
|
||||
View = env['ir.ui.view']
|
||||
ThemeView = env['theme.ir.ui.view']
|
||||
Imd = env['ir.model.data']
|
||||
|
||||
website_1 = env['website'].browse(1)
|
||||
website_2 = env['website'].browse(2)
|
||||
theme_default = env.ref('base.module_theme_default')
|
||||
|
||||
# Ensure the theme.ir.ui.view got removed (since there is an IMD but not
|
||||
# present in XML files)
|
||||
|
|
@ -204,8 +215,7 @@ def test_02_copy_ids_views_unlink_on_module_update(env):
|
|||
# Upgrade the module
|
||||
with MockRequest(env, website=website_1):
|
||||
theme_default.button_immediate_upgrade()
|
||||
env.reset() # clear the set of environments
|
||||
env = env() # get an environment that refers to the new registry
|
||||
env.transaction.reset() # clear the set of environments
|
||||
|
||||
# Ensure the theme.ir.ui.view got removed (since there is an IMD but not
|
||||
# present in XML files)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,195 @@
|
|||
from lxml import html
|
||||
|
||||
from odoo.tools import mute_logger
|
||||
from odoo.exceptions import AccessError, ValidationError
|
||||
from odoo.tests import HttpCase, tagged
|
||||
import unittest
|
||||
|
||||
from odoo.addons.website.controllers.model_page import ModelPageController
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsiteControllerPage(HttpCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.model = cls.env["ir.model"]._get("test.model.exposed")
|
||||
|
||||
cls.model_acl = cls.env["ir.model.access"].create({
|
||||
"name": "test acl expose",
|
||||
"model_id": cls.model.id,
|
||||
"group_id": cls.env.ref("website.website_page_controller_expose").id,
|
||||
"perm_read": True,
|
||||
})
|
||||
|
||||
cls.listing_view = cls.env["ir.ui.view"].create({
|
||||
"type": "qweb",
|
||||
"model": cls.model.model,
|
||||
"arch": """<t t-call="website.layout">
|
||||
<t t-set="_activeClasses" t-translation="off">border-primary</t>
|
||||
<div t-attf-class="listing_layout_switcher btn-group ms-3" t-att-data-active-classes="_activeClasses" t-att-data-view-id="view_id">
|
||||
<input type="radio" class="btn-check" name="wstudio_layout" id="o_wstudio_apply_grid" value="grid" t-att-checked="'checked' if layout_mode != 'list' else None"/>
|
||||
<label t-attf-class="btn btn-light #{_activeClasses if layout_mode != 'list' else None} o_wstudio_apply_grid" title="Grid" for="o_wstudio_apply_grid">
|
||||
<i class="fa fa-th-large"/>
|
||||
</label>
|
||||
<input type="radio" class="btn-check" name="wstudio_layout" id="o_wstudio_apply_list" t-att-checked="'checked' if layout_mode == 'list' else None" value="list"/>
|
||||
<label t-attf-class="btn btn-light #{_activeClasses if layout_mode == 'list' else None} o_wstudio_apply_list" title="List" for="o_wstudio_apply_list">
|
||||
<i class="oi oi-view-list"/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div t-attf-class="row mx-n2 mt8 #{'o_website_grid' if layout_mode == 'grid' else 'o_website_list'}">
|
||||
<t t-foreach="records" t-as="record">
|
||||
<a class="test_record_listing" t-out="record.display_name" t-att-href="record_to_url(record)" />
|
||||
</t>
|
||||
</div>
|
||||
</t> """
|
||||
})
|
||||
|
||||
cls.single_view = cls.env["ir.ui.view"].create({
|
||||
"type": "qweb",
|
||||
"model": cls.model.model,
|
||||
"arch": """<t t-call="website.layout">
|
||||
<div class="test_record" t-out="record.display_name" />
|
||||
</t> """
|
||||
})
|
||||
|
||||
cls.listing_controller_page = cls.env["website.controller.page"].create({
|
||||
"name": "Exposed Model",
|
||||
"view_id": cls.listing_view.id,
|
||||
"record_view_id": cls.single_view.id,
|
||||
"record_domain": "[('name', '=ilike', 'test_partner_%')]",
|
||||
"website_published": True,
|
||||
})
|
||||
|
||||
partner_data = {}
|
||||
if "is_published" in cls.env[cls.model.model]._fields:
|
||||
partner_data["is_published"] = True
|
||||
|
||||
records_to_create = [dict(name=f"test_partner_{i}", **partner_data) for i in range(2)]
|
||||
cls.exposed_records = cls.env[cls.model.model].create(records_to_create)
|
||||
|
||||
def test_cannot_bypass_read_rights(self):
|
||||
self.env["ir.model.access"].search([("model_id", "=", self.model.id)]).perm_read = False
|
||||
|
||||
with self.assertRaises(AccessError) as cm:
|
||||
self.env["website.controller.page"].with_user(2).create({
|
||||
"name": "Exposed Model Read",
|
||||
"website_id": False,
|
||||
"view_id": self.single_view.id,
|
||||
"record_domain": "[('name', '=ilike', 'test_partner_%')]",
|
||||
"website_published": True,
|
||||
})
|
||||
self.assertEqual(str(cm.exception).split("\n")[0], "You are not allowed to access 'Website Model Test Exposed' (test.model.exposed) records.")
|
||||
|
||||
def test_access_rights_and_rules(self):
|
||||
self.authenticate(None, None)
|
||||
self.model_acl.active = False
|
||||
with mute_logger("odoo.http"):
|
||||
response = self.url_open(f"/model/{self.listing_controller_page.name_slugified}")
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
self.model_acl.active = True
|
||||
response = self.url_open(f"/model/{self.listing_controller_page.name_slugified}")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
tree = html.fromstring(response.content.decode())
|
||||
rec_nodes = tree.xpath("//a[@class='test_record_listing']")
|
||||
self.assertEqual(len(rec_nodes), 2)
|
||||
|
||||
self.env["ir.rule"].create({
|
||||
"name": "dummy",
|
||||
"model_id": self.model.id,
|
||||
"domain_force": "[('name', '=', 'test_partner_1')]",
|
||||
"groups": self.env.ref("base.group_public"),
|
||||
})
|
||||
response = self.url_open(f"/model/{self.listing_controller_page.name_slugified}")
|
||||
tree = html.fromstring(response.content.decode())
|
||||
rec_nodes = tree.xpath("//a[@class='test_record_listing']")
|
||||
self.assertEqual(len(rec_nodes), 1)
|
||||
|
||||
def test_expose_model(self):
|
||||
self.authenticate(None, None)
|
||||
|
||||
slug = self.env['ir.http']._slug
|
||||
response = self.url_open(f"/model/{self.listing_controller_page.name_slugified}")
|
||||
tree = html.fromstring(response.content.decode())
|
||||
rec_nodes = tree.xpath("//a[@class='test_record_listing']")
|
||||
self.assertEqual(len(rec_nodes), 2)
|
||||
for n, record in zip(rec_nodes, self.exposed_records):
|
||||
self.assertEqual(n.get("href"), f"/model/{self.listing_controller_page.name_slugified}/{slug(record)}")
|
||||
|
||||
response = self.url_open(f"/model/{self.listing_controller_page.name_slugified}/{slug(self.exposed_records[0])}")
|
||||
tree = html.fromstring(response.content.decode())
|
||||
self.assertEqual(len(tree.xpath("//div[@class='test_record']")), 1)
|
||||
|
||||
response = self.url_open(f"/model/{self.listing_controller_page.name_slugified}/fake-slug-{self.exposed_records[0].id}")
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
non_reachable_record = self.env[self.model.model].create({"name": "non_reachable"})
|
||||
response = self.url_open(f"/model/{self.listing_controller_page.name_slugified}/{slug(non_reachable_record)}")
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
response = self.url_open("/model/some-other-slug")
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
self.listing_controller_page.website_published = False
|
||||
response = self.url_open(f"/model/{self.listing_controller_page.name_slugified}")
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_search_listing(self):
|
||||
self.authenticate(None, None)
|
||||
|
||||
slug = self.env['ir.http']._slug
|
||||
response = self.url_open(f"/model/{self.listing_controller_page.name_slugified}?search=1")
|
||||
tree = html.fromstring(response.content.decode())
|
||||
rec_nodes = tree.xpath("//a[@class='test_record_listing']")
|
||||
self.assertEqual(len(rec_nodes), 1)
|
||||
self.assertEqual(rec_nodes[0].get("href"), f"/model/{self.listing_controller_page.name_slugified}/{slug(self.exposed_records[1])}")
|
||||
|
||||
self.patch(ModelPageController, "pager_step", 1)
|
||||
response = self.url_open(f"/model/{self.listing_controller_page.name_slugified}/page/2")
|
||||
tree = html.fromstring(response.content.decode())
|
||||
rec_nodes = tree.xpath("//a[@class='test_record_listing']")
|
||||
self.assertEqual(len(rec_nodes), 1)
|
||||
self.assertEqual(rec_nodes[0].get("href"), f"/model/{self.listing_controller_page.name_slugified}/{slug(self.exposed_records[1])}")
|
||||
|
||||
response = self.url_open(f"/model/{self.listing_controller_page.name_slugified}/page/2?search={self.exposed_records[0].name}")
|
||||
self.assertEqual(response.url.partition('/model/')[2], f"exposed-model?search={self.exposed_records[0].name}&order=create_date+desc")
|
||||
tree = html.fromstring(response.content.decode())
|
||||
rec_nodes = tree.xpath("//a[@class='test_record_listing']")
|
||||
self.assertEqual(len(rec_nodes), 1)
|
||||
self.assertEqual(rec_nodes[0].get("href"), f"/model/{self.listing_controller_page.name_slugified}/{slug(self.exposed_records[0])}")
|
||||
|
||||
def test_default_layout(self):
|
||||
self.assertEqual(self.listing_controller_page.default_layout, 'grid')
|
||||
self.start_tour('/model/exposed-model', 'website_controller_page_listing_layout', login='admin')
|
||||
self.assertEqual(self.listing_controller_page.default_layout, 'list')
|
||||
#check that the user that has not previously interacted with the layout switcher will prompt on the default layout
|
||||
self.start_tour('/model/exposed-model', 'website_controller_page_default_page_check', login='admin')
|
||||
|
||||
def test_model_constrains(self):
|
||||
def get_model_meta_params(model_name):
|
||||
m = self.env[model_name]
|
||||
return (m._abstract, m._auto, m._transient)
|
||||
|
||||
default_page_vals = {
|
||||
"arch": "<t><div/></t>",
|
||||
"type": "qweb",
|
||||
"name": "some_name"
|
||||
}
|
||||
# Abstract Model
|
||||
self.assertEqual(get_model_meta_params("website.published.mixin"), (True, False, False))
|
||||
with self.assertRaises(ValidationError) as cm:
|
||||
self.env["website.controller.page"].create({**default_page_vals, "model": "website.published.mixin"})
|
||||
self.assertEqual(str(cm.exception), "A page must be set to display a concrete model.")
|
||||
|
||||
# Transient Model
|
||||
self.assertEqual(get_model_meta_params("base.partner.merge.automatic.wizard"), (False, True, True))
|
||||
with self.assertRaises(ValidationError) as cm:
|
||||
self.env["website.controller.page"].create({**default_page_vals, "model": "base.partner.merge.automatic.wizard"})
|
||||
self.assertEqual(str(cm.exception), "A page must be set to display a concrete model.")
|
||||
|
||||
# _auto = False Model
|
||||
self.assertEqual(get_model_meta_params("res.device"), (False, False, False))
|
||||
with self.assertRaises(ValidationError) as cm:
|
||||
self.env["website.controller.page"].create({**default_page_vals, "model": "res.device"})
|
||||
self.assertEqual(str(cm.exception), "A page must be set to display a concrete model.")
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import odoo
|
||||
import odoo.tests
|
||||
|
||||
|
||||
@odoo.tests.tagged('-at_install', 'post_install')
|
||||
class TestWebsiteFieldSanitize(odoo.tests.HttpCase):
|
||||
def test_sanitize_video_iframe(self):
|
||||
self.env['res.users'].create({
|
||||
'name': 'Restricted Editor',
|
||||
'login': 'restricted',
|
||||
'password': 'restricted',
|
||||
'group_ids': [(6, 0, [
|
||||
self.ref('base.group_user'),
|
||||
self.ref('website.group_website_restricted_editor'),
|
||||
self.ref('test_website.group_test_website_admin'),
|
||||
])]
|
||||
})
|
||||
|
||||
# Add a video to an HTML field (admin).
|
||||
self.start_tour(
|
||||
self.env['website'].get_client_action_url('/test_website/model_item/1'),
|
||||
'website_designer_iframe_video',
|
||||
login='admin'
|
||||
)
|
||||
# Make sure a user can still edit the content (restricted editor).
|
||||
self.start_tour(
|
||||
self.env['website'].get_client_action_url('/test_website/model_item/1'),
|
||||
'website_restricted_editor_iframe_video',
|
||||
login='restricted'
|
||||
)
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests import HttpCase, tagged
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestWebsitePageProperties(HttpCase):
|
||||
|
||||
def test_website_page_properties_common(self):
|
||||
self.start_tour('/test_view', 'website_page_properties_common', login='admin')
|
||||
|
||||
def test_website_page_properties_can_publish(self):
|
||||
self.start_tour('/test_website/model_item/1', 'website_page_properties_can_publish', login='admin')
|
||||
|
||||
def test_website_page_properties_website_page(self):
|
||||
# Create a website page with a different URL to be tested for dependency
|
||||
# tracking
|
||||
self.env['website.page'].create({
|
||||
'name': 'Base',
|
||||
'type': 'qweb',
|
||||
'arch': '<div><a href="/cool-page">Cool page</a></div>',
|
||||
'key': 'test.cool_page',
|
||||
'url': '/dependency_page',
|
||||
'website_id': self.env['website'].search([], limit=1).id,
|
||||
})
|
||||
|
||||
self.start_tour('/', 'website_page_properties_website_page', login='admin')
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
<t t-name='multi_url'>
|
||||
<div>
|
||||
<a id='get' href="/get">get</a>
|
||||
<form id='post' action="/post">post</form>>
|
||||
<form id='post' action="/post">post</form>
|
||||
<a id='get_post' href="/get_post">get_post</a>
|
||||
<a id='get_post_nomultilang' href="/get_post_nomultilang">get_post_nomultilang</a>
|
||||
</div>
|
||||
|
|
@ -23,29 +23,59 @@
|
|||
<template id="model_item" name="Model item">
|
||||
<t t-call="website.layout">
|
||||
<div id="wrap">
|
||||
<section t-cache="record">
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col" t-field="record.name"/>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col" t-field="record.website_description"/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<t t-call="test_website.test_form"/>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<record id="res_config_settings_view_form" model="ir.ui.view">
|
||||
<field name="name">res.config.settings.view.form.inherit.test.website</field>
|
||||
<field name="model">res.config.settings</field>
|
||||
<field name="inherit_id" ref="website.res_config_settings_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@id='plausbile_setting']" position="after">
|
||||
<div class="col-12 col-lg-6 o_setting_box" id="website_test_setting">
|
||||
<button type="object" name="action_website_test_setting" string="Website test setting" class="btn-link" icon="fa-arrow-right"/>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
<template id="test_form" name="Test Form">
|
||||
<span class="hidden" data-for="test_form" t-att-data-values="{'tag_id': tag and tag.id or ''}" />
|
||||
<section class="s_website_form pt16 pb16 o_colored_level" data-vcss="001" data-snippet="s_website_form" data-name="Form">
|
||||
<div class="container">
|
||||
<form id="test_form" action="#fake" method="post" enctype="multipart/form-data" class="o_mark_required" data-mark="*" data-pre-fill="true" data-success-mode="redirect" data-success-page="/success" data-model_name="test.model" hide-change-model="true">
|
||||
<div class="s_website_form_rows row s_col_no_bgcolor">
|
||||
<div class="mb-0 py-2 s_website_form_field col-12 s_website_form_required" data-type="char" data-name="Field">
|
||||
<div class="row s_col_no_resize s_col_no_bgcolor">
|
||||
<label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="testform1">
|
||||
<span class="s_website_form_label_content">Name</span>
|
||||
<span class="s_website_form_mark"> *</span>
|
||||
</label>
|
||||
<div class="col-sm">
|
||||
<input type="text" class="form-control s_website_form_input" name="name" required="1" data-fill-with="name" id="testform1"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-0 py-2 s_website_form_field col-12 s_website_form_dnone" data-name="Field"
|
||||
data-type="record" data-model="test.tag">
|
||||
<div class="row s_col_no_resize s_col_no_bgcolor">
|
||||
<label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="testmodel2">
|
||||
<span class="s_website_form_label_content">Tag</span>
|
||||
</label>
|
||||
<div class="col-sm">
|
||||
<input type="hidden" class="form-control s_website_form_input" name="tag_id" id="testmodel2" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-0 py-2 col-12 s_website_form_submit" data-name="Submit Button">
|
||||
<div style="width: 200px;" class="s_website_form_label"/>
|
||||
<a href="#" role="button" class="btn btn-primary btn-lg s_website_form_send">Submit</a>
|
||||
<span id="s_website_form_result"/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<!-- Front end page for test model -->
|
||||
<template id="test_model_page_layout" name="Test Model">
|
||||
|
|
|
|||
|
|
@ -7,25 +7,17 @@
|
|||
<field name="model">test.model.multi.website</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban js_class="website_pages_kanban" class="o_kanban_mobile" action="open_website_url" type="object" sample="1">
|
||||
<field name="name"/>
|
||||
<field name="website_url"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_global_click d-flex flex-column">
|
||||
<div class="row mb-auto">
|
||||
<strong class="col-8">
|
||||
<span class="o_text_overflow" t-esc="record.name.value"/>
|
||||
<div class="text-muted" t-if="record.website_id.value" groups="website.group_multi_website">
|
||||
<i class="fa fa-globe me-1" title="Website"/>
|
||||
<field name="website_id"/>
|
||||
</div>
|
||||
</strong>
|
||||
</div>
|
||||
<div class="border-top mt-2 pt-2">
|
||||
<field name="is_published" widget="boolean_toggle"/>
|
||||
<t t-if="record.is_published.raw_value">Published</t>
|
||||
<t t-else="">Not Published</t>
|
||||
</div>
|
||||
<t t-name="card">
|
||||
<field name="name" class="text-truncate fw-bolder mb-auto"/>
|
||||
<div class="text-muted fw-bolder" t-if="record.website_id.value" groups="website.group_multi_website">
|
||||
<i class="fa fa-globe me-1" title="Website"/>
|
||||
<field name="website_id"/>
|
||||
</div>
|
||||
<div class="d-flex border-top mt-2 pt-2">
|
||||
<field name="is_published" widget="boolean_toggle"/>
|
||||
<t t-if="record.is_published.raw_value">Published</t>
|
||||
<t t-else="">Not Published</t>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
@ -33,47 +25,47 @@
|
|||
</field>
|
||||
</record>
|
||||
<record id="test_model_multi_website_view_list" model="ir.ui.view">
|
||||
<field name="name">Test Multi Model Pages Tree</field>
|
||||
<field name="name">Test Multi Model Pages List</field>
|
||||
<field name="model">test.model.multi.website</field>
|
||||
<field name="priority">99</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree js_class="website_pages_list" type="object" action="open_website_url" multi_edit="1">
|
||||
<list js_class="website_pages_list" type="object" action="open_website_url" multi_edit="1">
|
||||
<field name="name"/>
|
||||
<field name="website_url"/>
|
||||
<field name="website_id" groups="website.group_multi_website"/>
|
||||
</tree>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
<record id="action_test_model_multi_website" model="ir.actions.act_window">
|
||||
<field name="name">Test Multi Model Pages</field>
|
||||
<field name="res_model">test.model.multi.website</field>
|
||||
<field name="view_mode">tree,kanban,form</field>
|
||||
<field name="view_mode">list,kanban,form</field>
|
||||
<field name="view_ids" eval="[(5, 0, 0),
|
||||
(0, 0, {'view_mode': 'tree', 'view_id': ref('test_model_multi_website_view_list')}),
|
||||
(0, 0, {'view_mode': 'list', 'view_id': ref('test_model_multi_website_view_list')}),
|
||||
(0, 0, {'view_mode': 'kanban', 'view_id': ref('test_model_multi_website_view_kanban')}),
|
||||
]"/>
|
||||
</record>
|
||||
|
||||
<!-- js_class bug records -->
|
||||
<record id="test_model_multi_website_view_list_js_class_bug" model="ir.ui.view">
|
||||
<field name="name">Test Multi Model Pages Tree js_class bug</field>
|
||||
<field name="name">Test Multi Model Pages list js_class bug</field>
|
||||
<field name="model">test.model.multi.website</field>
|
||||
<field name="priority">99</field>
|
||||
<!-- Omitting `website_pages_list` on purpose to test it does not crash -->
|
||||
<field name="arch" type="xml">
|
||||
<tree type="object" action="open_website_url" multi_edit="1">
|
||||
<list type="object" action="open_website_url" multi_edit="1">
|
||||
<field name="name"/>
|
||||
<field name="website_url"/>
|
||||
<field name="website_id" groups="website.group_multi_website"/>
|
||||
</tree>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
<record id="action_test_model_multi_website_js_class_bug" model="ir.actions.act_window">
|
||||
<field name="name">Test Multi Model Pages js_class bug</field>
|
||||
<field name="res_model">test.model.multi.website</field>
|
||||
<field name="view_mode">tree,kanban,form</field>
|
||||
<field name="view_mode">list,kanban,form</field>
|
||||
<field name="view_ids" eval="[(5, 0, 0),
|
||||
(0, 0, {'view_mode': 'tree', 'view_id': ref('test_model_multi_website_view_list_js_class_bug')}),
|
||||
(0, 0, {'view_mode': 'list', 'view_id': ref('test_model_multi_website_view_list_js_class_bug')}),
|
||||
(0, 0, {'view_mode': 'kanban', 'view_id': ref('test_model_multi_website_view_kanban')}),
|
||||
]"/>
|
||||
</record>
|
||||
|
|
|
|||
|
|
@ -7,21 +7,13 @@
|
|||
<field name="model">test.model</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban js_class="website_pages_kanban" class="o_kanban_mobile" action="open_website_url" type="object" sample="1">
|
||||
<field name="name"/>
|
||||
<field name="website_url"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_global_click d-flex flex-column">
|
||||
<div class="row mb-auto">
|
||||
<strong class="col-8">
|
||||
<span class="o_text_overflow" t-esc="record.name.value"/>
|
||||
</strong>
|
||||
</div>
|
||||
<div class="border-top mt-2 pt-2">
|
||||
<field name="is_published" widget="boolean_toggle"/>
|
||||
<t t-if="record.is_published.raw_value">Published</t>
|
||||
<t t-else="">Not Published</t>
|
||||
</div>
|
||||
<t t-name="card">
|
||||
<field class="text-truncate mb-auto fw-bolder" name="name"/>
|
||||
<div class="d-flex border-top mt-2 pt-2">
|
||||
<field name="is_published" widget="boolean_toggle"/>
|
||||
<t t-if="record.is_published.raw_value">Published</t>
|
||||
<t t-else="">Not Published</t>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
@ -29,22 +21,22 @@
|
|||
</field>
|
||||
</record>
|
||||
<record id="test_model_view_list" model="ir.ui.view">
|
||||
<field name="name">Test Model Pages Tree</field>
|
||||
<field name="name">Test Model Pages List</field>
|
||||
<field name="model">test.model</field>
|
||||
<field name="priority">99</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree js_class="website_pages_list" type="object" action="open_website_url" multi_edit="1">
|
||||
<list js_class="website_pages_list" type="object" action="open_website_url" multi_edit="1">
|
||||
<field name="name"/>
|
||||
<field name="website_url"/>
|
||||
</tree>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
<record id="action_test_model" model="ir.actions.act_window">
|
||||
<field name="name">Test Model Pages</field>
|
||||
<field name="res_model">test.model</field>
|
||||
<field name="view_mode">tree,kanban,form</field>
|
||||
<field name="view_mode">list,kanban,form</field>
|
||||
<field name="view_ids" eval="[(5, 0, 0),
|
||||
(0, 0, {'view_mode': 'tree', 'view_id': ref('test_model_view_list')}),
|
||||
(0, 0, {'view_mode': 'list', 'view_id': ref('test_model_view_list')}),
|
||||
(0, 0, {'view_mode': 'kanban', 'view_id': ref('test_model_view_kanban')}),
|
||||
]"/>
|
||||
</record>
|
||||
|
|
@ -54,6 +46,7 @@
|
|||
<field name="name">Test Model</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">test.model</field>
|
||||
<field name="path">test-model</field>
|
||||
<field name="view_id" eval="False"/>
|
||||
</record>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue