mirror of
https://github.com/bringout/oca-ocb-web.git
synced 2026-04-19 11:32:04 +02:00
replace stale web_editor with html_editor and html_builder for 19.0
web_editor was removed in Odoo 19.0 and replaced by html_editor
and html_builder. The old web_editor was incorrectly included in
the 19.0 vanilla import.
🤖 assisted by claude
This commit is contained in:
parent
4b94f0abc5
commit
f866779561
1513 changed files with 396049 additions and 358525 deletions
|
|
@ -0,0 +1,5 @@
|
|||
from . import test_controller
|
||||
from . import test_converter
|
||||
from . import test_tools
|
||||
from . import test_views
|
||||
from . import test_diff_utils
|
||||
|
|
@ -0,0 +1,284 @@
|
|||
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import binascii
|
||||
import json
|
||||
|
||||
import odoo.tests
|
||||
from odoo.tests.common import HttpCase, new_test_user
|
||||
from odoo.tools.json import scriptsafe as json_safe
|
||||
from unittest.mock import patch
|
||||
from odoo.addons.mail.tools import link_preview
|
||||
|
||||
|
||||
@odoo.tests.tagged('-at_install', 'post_install')
|
||||
class TestController(HttpCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
portal_user = new_test_user(cls.env, login='portal_user', groups='base.group_portal')
|
||||
cls.portal_user = portal_user
|
||||
cls.portal = portal_user.login
|
||||
admin_user = new_test_user(cls.env, login='admin_user', groups='base.group_user,base.group_system')
|
||||
cls.admin = admin_user.login
|
||||
cls.headers = {"Content-Type": "application/json"}
|
||||
cls.pixel = 'R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs='
|
||||
|
||||
def test_01_upload_document(self):
|
||||
self.authenticate('admin', 'admin')
|
||||
# Upload document.
|
||||
response = self.url_open(
|
||||
'/html_editor/attachment/add_data',
|
||||
headers={'Content-Type': 'application/json'},
|
||||
data=json_safe.dumps({'params': {
|
||||
'name': 'test.txt',
|
||||
'data': 'SGVsbG8gd29ybGQ=', # base64 Hello world
|
||||
'is_image': False,
|
||||
}})
|
||||
).json()
|
||||
self.assertFalse('error' in response, 'Upload failed: %s' % response.get('error', {}).get('message'))
|
||||
attachment_id = response['result']['id']
|
||||
checksum = response['result']['checksum']
|
||||
# Download document and check content.
|
||||
response = self.url_open(
|
||||
'/web/content/%s?unique=%s&download=true' % (attachment_id, checksum)
|
||||
)
|
||||
self.assertEqual(200, response.status_code, 'Expect response')
|
||||
self.assertEqual(b'Hello world', response.content, 'Expect raw content')
|
||||
|
||||
def test_02_illustration_shape(self):
|
||||
self.authenticate('admin', 'admin')
|
||||
# SVG with all replaceable colors.
|
||||
svg = b"""
|
||||
<svg viewBox="0 0 400 400">
|
||||
<rect width="300" height="300" style="fill:#3AADAA;" />
|
||||
<rect x="20" y="20" width="300" height="300" style="fill:#7C6576;" />
|
||||
<rect x="40" y="40" width="300" height="300" style="fill:#F6F6F6;" />
|
||||
<rect x="60" y="60" width="300" height="300" style="fill:#FFFFFF;" />
|
||||
<rect x="80" y="80" width="300" height="300" style="fill:#383E45;" />
|
||||
</svg>
|
||||
"""
|
||||
attachment = self.env['ir.attachment'].create({
|
||||
'name': 'test.svg',
|
||||
'mimetype': 'image/svg+xml',
|
||||
'datas': binascii.b2a_base64(svg, newline=False),
|
||||
'public': True,
|
||||
'res_model': 'ir.ui.view',
|
||||
'res_id': 0,
|
||||
})
|
||||
# Shape illustration with slug.
|
||||
slug = self.env['ir.http']._slug
|
||||
url = '/html_editor/shape/illustration/%s' % slug(attachment)
|
||||
palette = 'c1=%233AADAA&c2=%237C6576&&c3=%23F6F6F6&&c4=%23FFFFFF&&c5=%23383E45'
|
||||
attachment['url'] = '%s?%s' % (url, palette)
|
||||
|
||||
response = self.url_open(url)
|
||||
self.assertEqual(200, response.status_code, 'Expect response')
|
||||
self.assertEqual(svg, response.content, 'Expect unchanged SVG')
|
||||
|
||||
response = self.url_open(url + '?c1=%23ABCDEF')
|
||||
self.assertEqual(200, response.status_code, 'Expect response')
|
||||
self.assertEqual(len(svg), len(response.content), 'Expect same length as original')
|
||||
self.assertTrue('ABCDEF' in str(response.content), 'Expect patched c1')
|
||||
self.assertTrue('3AADAA' not in str(response.content), 'Old c1 should not be there anymore')
|
||||
|
||||
# Shape illustration without slug.
|
||||
url = '/html_editor/shape/illustration/noslug'
|
||||
attachment['url'] = url
|
||||
|
||||
response = self.url_open(url)
|
||||
self.assertEqual(200, response.status_code, 'Expect response')
|
||||
self.assertEqual(svg, response.content, 'Expect unchanged SVG')
|
||||
|
||||
response = self.url_open(url + '?c1=%23ABCDEF')
|
||||
self.assertEqual(200, response.status_code, 'Expect response')
|
||||
self.assertEqual(len(svg), len(response.content), 'Expect same length as original')
|
||||
self.assertTrue('ABCDEF' in str(response.content), 'Expect patched c1')
|
||||
self.assertTrue('3AADAA' not in str(response.content), 'Old c1 should not be there anymore')
|
||||
|
||||
def test_03_get_image_info(self):
|
||||
gif_base64 = "R0lGODdhAQABAIAAAP///////ywAAAAAAQABAAACAkQBADs="
|
||||
self.authenticate('admin', 'admin')
|
||||
# Upload document.
|
||||
response = self.url_open(
|
||||
'/html_editor/attachment/add_data',
|
||||
headers={'Content-Type': 'application/json'},
|
||||
data=json_safe.dumps({'params': {
|
||||
'name': 'test.gif',
|
||||
'data': gif_base64,
|
||||
'is_image': True,
|
||||
}})
|
||||
)
|
||||
response = response.json()
|
||||
self.assertFalse('error' in response, 'Upload failed: %s' % response.get('error', {}).get('message'))
|
||||
attachment_id = response['result']['id']
|
||||
image_src = response['result']['image_src']
|
||||
mimetype = response['result']['mimetype']
|
||||
self.assertEqual('image/gif', mimetype, "Wrong mimetype")
|
||||
# Ensure image info can be retrieved.
|
||||
response = self.url_open('/html_editor/get_image_info',
|
||||
headers={'Content-Type': 'application/json'},
|
||||
data=json_safe.dumps({
|
||||
"params": {
|
||||
"src": image_src,
|
||||
}
|
||||
}),
|
||||
)
|
||||
response = response.json()
|
||||
self.assertEqual(attachment_id, response['result']['original']['id'], "Wrong id")
|
||||
self.assertEqual(image_src, response['result']['original']['image_src'], "Wrong image_src")
|
||||
self.assertEqual(mimetype, response['result']['original']['mimetype'], "Wrong mimetype")
|
||||
|
||||
def test_04_admin_attachment(self):
|
||||
self.authenticate(self.admin, self.admin)
|
||||
payload = self.build_rpc_payload({"name": "pixel", "data": self.pixel, "is_image": True})
|
||||
response = self.url_open('/html_editor/attachment/add_data', data=json.dumps(payload), headers=self.headers)
|
||||
self.assertEqual(200, response.status_code)
|
||||
attachment = self.env['ir.attachment'].search([('name', '=', 'pixel')])
|
||||
self.assertTrue(attachment)
|
||||
|
||||
domain = [('name', '=', 'pixel')]
|
||||
result = attachment.search(domain)
|
||||
self.assertTrue(len(result), "No attachment fetched")
|
||||
self.assertEqual(result, attachment)
|
||||
|
||||
def test_05_internal_link_preview(self):
|
||||
self.authenticate(self.admin, self.admin)
|
||||
|
||||
def _get_full_url(pathname):
|
||||
return f"{self.base_url()}{pathname}"
|
||||
|
||||
def _patched_get_link_preview_from_url(url):
|
||||
if url == _get_full_url("/page-with-description"):
|
||||
return {
|
||||
'og_description': 'Mocked page description',
|
||||
}
|
||||
elif url == _get_full_url("/page-without-description") or url == _get_full_url("/shop/category/1"):
|
||||
return {
|
||||
'og_description': None,
|
||||
}
|
||||
else:
|
||||
return False
|
||||
|
||||
# retrieve metadata of an record without customerized link_preview_name but with display_name
|
||||
response_without_preview_name = self.url_open(
|
||||
'/html_editor/link_preview_internal',
|
||||
data=json_safe.dumps({
|
||||
"params": {
|
||||
"preview_url": _get_full_url(f"/odoo/users/{self.portal_user.id}"),
|
||||
}
|
||||
}),
|
||||
headers=self.headers
|
||||
)
|
||||
self.assertEqual(200, response_without_preview_name.status_code)
|
||||
self.assertTrue('display_name' in response_without_preview_name.text)
|
||||
|
||||
# retrieve metadata of a url with wrong action name
|
||||
response_wrong_action = self.url_open(
|
||||
'/html_editor/link_preview_internal',
|
||||
data=json_safe.dumps({
|
||||
"params": {
|
||||
"preview_url": _get_full_url("/odoo/actionInvalid/1"),
|
||||
}
|
||||
}),
|
||||
headers=self.headers
|
||||
)
|
||||
self.assertEqual(200, response_wrong_action.status_code)
|
||||
self.assertTrue('error_msg' in response_wrong_action.text)
|
||||
|
||||
# retrieve metadata of a url with wrong record id
|
||||
response_wrong_record = self.url_open(
|
||||
'/html_editor/link_preview_internal',
|
||||
data=json_safe.dumps({
|
||||
"params": {
|
||||
"preview_url": _get_full_url("/odoo/users/9999"),
|
||||
}
|
||||
}),
|
||||
headers=self.headers
|
||||
)
|
||||
self.assertEqual(200, response_wrong_record.status_code)
|
||||
self.assertTrue('error_msg' in response_wrong_record.text)
|
||||
|
||||
# retrieve metadata of a url not directing to a record
|
||||
with patch.object(link_preview, 'get_link_preview_from_url', side_effect=_patched_get_link_preview_from_url):
|
||||
# Check metadata for a URL that points to a valid frontend page with
|
||||
# a page description set
|
||||
response_page_with_desc = self.url_open(
|
||||
'/html_editor/link_preview_internal',
|
||||
data=json_safe.dumps({
|
||||
"params": {
|
||||
"preview_url": _get_full_url("/page-with-description"),
|
||||
}
|
||||
}),
|
||||
headers=self.headers
|
||||
)
|
||||
self.assertEqual(200, response_page_with_desc.status_code)
|
||||
self.assertTrue('"description": "Mocked page description"' in response_page_with_desc.text)
|
||||
|
||||
# Check metadata for a URL that points to a valid frontend page with
|
||||
# no page description set
|
||||
response_page_without_desc = self.url_open(
|
||||
'/html_editor/link_preview_internal',
|
||||
data=json_safe.dumps({
|
||||
"params": {
|
||||
"preview_url": _get_full_url("/page-without-description"),
|
||||
}
|
||||
}),
|
||||
headers=self.headers
|
||||
)
|
||||
self.assertEqual(200, response_page_without_desc.status_code)
|
||||
self.assertTrue('"result": {}' in response_page_without_desc.text)
|
||||
|
||||
response_page_without_desc = self.url_open(
|
||||
'/html_editor/link_preview_internal',
|
||||
data=json_safe.dumps({
|
||||
"params": {
|
||||
"preview_url": _get_full_url("/shop/category/1"),
|
||||
}
|
||||
}),
|
||||
headers=self.headers
|
||||
)
|
||||
self.assertEqual(200, response_page_without_desc.status_code)
|
||||
self.assertTrue('"result": {}' in response_page_without_desc.text)
|
||||
self.assertFalse('error_msg' in response_page_without_desc.text)
|
||||
|
||||
# Check metadata for a URL that points to an invalid/unknown page
|
||||
invalid_page = self.url_open(
|
||||
'/html_editor/link_preview_internal',
|
||||
data=json_safe.dumps({
|
||||
"params": {
|
||||
"preview_url": _get_full_url("/invalid-page"),
|
||||
}
|
||||
}),
|
||||
headers=self.headers
|
||||
)
|
||||
self.assertEqual(200, invalid_page.status_code)
|
||||
self.assertTrue('"result": {}' in invalid_page.text)
|
||||
|
||||
# Attempt to retrieve metadata for path format `odoo/<model>/<record_id>`
|
||||
response_model_record = self.url_open(
|
||||
'/html_editor/link_preview_internal',
|
||||
data=json_safe.dumps({
|
||||
"params": {
|
||||
"preview_url": _get_full_url(f"/odoo/res.users/{self.portal_user.id}"),
|
||||
}
|
||||
}),
|
||||
headers=self.headers
|
||||
)
|
||||
self.assertEqual(200, response_model_record.status_code)
|
||||
self.assertTrue('display_name' in response_model_record.text)
|
||||
self.assertIn(self.portal_user.display_name, response_model_record.text)
|
||||
|
||||
# Attempt to retrieve metadata for an abstract model
|
||||
response_abstract_model = self.url_open(
|
||||
'/html_editor/link_preview_internal',
|
||||
data=json_safe.dumps({
|
||||
"params": {
|
||||
"preview_url": _get_full_url("/odoo/mail.thread/1"),
|
||||
}
|
||||
}),
|
||||
headers=self.headers
|
||||
)
|
||||
self.assertEqual(200, response_abstract_model.status_code)
|
||||
self.assertTrue('error_msg' in response_abstract_model.text)
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import textwrap
|
||||
|
||||
from lxml import etree, html
|
||||
from lxml.builder import E
|
||||
|
||||
from odoo.tests import common
|
||||
from odoo.tests.common import BaseCase
|
||||
from odoo.addons.html_editor.models.ir_qweb_fields import html_to_text
|
||||
|
||||
|
||||
class TestHTMLToText(BaseCase):
|
||||
def test_rawstring(self):
|
||||
self.assertEqual(
|
||||
"foobar",
|
||||
html_to_text(E.div("foobar")))
|
||||
|
||||
def test_br(self):
|
||||
self.assertEqual(
|
||||
"foo\nbar",
|
||||
html_to_text(E.div("foo", E.br(), "bar")))
|
||||
|
||||
self.assertEqual(
|
||||
"foo\n\nbar\nbaz",
|
||||
html_to_text(E.div(
|
||||
"foo", E.br(), E.br(),
|
||||
"bar", E.br(),
|
||||
"baz")))
|
||||
|
||||
def test_p(self):
|
||||
self.assertEqual(
|
||||
"foo\n\nbar\n\nbaz",
|
||||
html_to_text(E.div(
|
||||
"foo",
|
||||
E.p("bar"),
|
||||
"baz")))
|
||||
|
||||
self.assertEqual(
|
||||
"foo",
|
||||
html_to_text(E.div(E.p("foo"))))
|
||||
|
||||
self.assertEqual(
|
||||
"foo\n\nbar",
|
||||
html_to_text(E.div("foo", E.p("bar"))))
|
||||
self.assertEqual(
|
||||
"foo\n\nbar",
|
||||
html_to_text(E.div(E.p("foo"), "bar")))
|
||||
|
||||
self.assertEqual(
|
||||
"foo\n\nbar\n\nbaz",
|
||||
html_to_text(E.div(
|
||||
E.p("foo"),
|
||||
E.p("bar"),
|
||||
E.p("baz"),
|
||||
)))
|
||||
|
||||
def test_div(self):
|
||||
self.assertEqual(
|
||||
"foo\nbar\nbaz",
|
||||
html_to_text(E.div(
|
||||
"foo",
|
||||
E.div("bar"),
|
||||
"baz"
|
||||
)))
|
||||
|
||||
self.assertEqual(
|
||||
"foo",
|
||||
html_to_text(E.div(E.div("foo"))))
|
||||
|
||||
self.assertEqual(
|
||||
"foo\nbar",
|
||||
html_to_text(E.div("foo", E.div("bar"))))
|
||||
self.assertEqual(
|
||||
"foo\nbar",
|
||||
html_to_text(E.div(E.div("foo"), "bar")))
|
||||
|
||||
self.assertEqual(
|
||||
"foo\nbar\nbaz",
|
||||
html_to_text(E.div(
|
||||
"foo",
|
||||
E.div("bar"),
|
||||
E.div("baz")
|
||||
)))
|
||||
|
||||
def test_other_block(self):
|
||||
self.assertEqual(
|
||||
"foo\nbar\nbaz",
|
||||
html_to_text(E.div(
|
||||
"foo",
|
||||
E.section("bar"),
|
||||
"baz"
|
||||
)))
|
||||
|
||||
def test_inline(self):
|
||||
self.assertEqual(
|
||||
"foobarbaz",
|
||||
html_to_text(E.div("foo", E.span("bar"), "baz")))
|
||||
|
||||
def test_whitespace(self):
|
||||
self.assertEqual(
|
||||
"foo bar\nbaz",
|
||||
html_to_text(E.div(
|
||||
"foo\nbar",
|
||||
E.br(),
|
||||
"baz")
|
||||
))
|
||||
|
||||
self.assertEqual(
|
||||
"foo bar\nbaz",
|
||||
html_to_text(E.div(
|
||||
E.div(E.span("foo"), " bar"),
|
||||
"baz")))
|
||||
|
||||
|
||||
class TestConvertBack(common.TransactionCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.env = self.env(context={'inherit_branding': True})
|
||||
|
||||
def field_rountrip_result(self, field, value, expected):
|
||||
model = 'html_editor.converter.test'
|
||||
record = self.env[model].create({field: value})
|
||||
|
||||
t = etree.Element('t')
|
||||
e = etree.Element('span')
|
||||
t.append(e)
|
||||
field_value = 'record.%s' % field
|
||||
e.set('t-field', field_value)
|
||||
|
||||
rendered = self.env['ir.qweb']._render(t, {'record': record})
|
||||
|
||||
element = html.fromstring(rendered, parser=html.HTMLParser(encoding='utf-8'))
|
||||
model = 'ir.qweb.field.' + element.get('data-oe-type', '')
|
||||
converter = self.env[model] if model in self.env else self.env['ir.qweb.field']
|
||||
value_back = converter.from_html(model, record._fields[field], element)
|
||||
|
||||
if isinstance(expected, bytes):
|
||||
expected = expected.decode('utf-8')
|
||||
self.assertEqual(value_back, expected)
|
||||
|
||||
def field_roundtrip(self, field, value):
|
||||
self.field_rountrip_result(field, value, value)
|
||||
|
||||
def test_integer(self):
|
||||
self.field_roundtrip('integer', 42)
|
||||
self.field_roundtrip('integer', 42000)
|
||||
|
||||
def test_float(self):
|
||||
self.field_roundtrip('float', 42.567890)
|
||||
self.field_roundtrip('float', 324542.567890)
|
||||
|
||||
def test_numeric(self):
|
||||
self.field_roundtrip('numeric', 42.77)
|
||||
|
||||
def test_char(self):
|
||||
self.field_roundtrip('char', "foo bar")
|
||||
self.field_roundtrip('char', "ⒸⓄⓇⒼⒺ")
|
||||
|
||||
def test_selection_str(self):
|
||||
self.field_roundtrip('selection_str', 'B')
|
||||
|
||||
def test_text(self):
|
||||
self.field_roundtrip('text', textwrap.dedent("""\
|
||||
You must obey the dance commander
|
||||
Givin' out the order for fun
|
||||
You must obey the dance commander
|
||||
You know that he's the only one
|
||||
Who gives the orders here,
|
||||
Alright
|
||||
Who gives the orders here,
|
||||
Alright
|
||||
|
||||
It would be awesome
|
||||
If we could dance-a
|
||||
It would be awesome, yeah
|
||||
Let's take the chance-a
|
||||
It would be awesome, yeah
|
||||
Let's start the show
|
||||
Because you never know
|
||||
You never know
|
||||
You never know until you go"""))
|
||||
|
||||
def test_m2o(self):
|
||||
""" the M2O field conversion (from html) is markedly different from
|
||||
others as it directly writes into the m2o and returns nothing at all.
|
||||
"""
|
||||
field = 'many2one'
|
||||
|
||||
subrec1 = self.env['html_editor.converter.test.sub'].create({'name': "Foo"})
|
||||
subrec2 = self.env['html_editor.converter.test.sub'].create({'name': "Bar"})
|
||||
record = self.env['html_editor.converter.test'].create({field: subrec1.id})
|
||||
|
||||
t = etree.Element('t')
|
||||
e = etree.Element('span')
|
||||
t.append(e)
|
||||
field_value = 'record.%s' % field
|
||||
e.set('t-field', field_value)
|
||||
|
||||
rendered = self.env['ir.qweb']._render(t, {'record': record})
|
||||
element = html.fromstring(rendered, parser=html.HTMLParser(encoding='utf-8'))
|
||||
|
||||
# emulate edition
|
||||
element.set('data-oe-many2one-id', str(subrec2.id))
|
||||
element.text = "New content"
|
||||
|
||||
model = 'ir.qweb.field.' + element.get('data-oe-type')
|
||||
converter = self.env[model] if model in self.env else self.env['ir.qweb.field']
|
||||
value_back = converter.from_html('html_editor.converter.test', record._fields[field], element)
|
||||
|
||||
self.assertIsNone(
|
||||
value_back, "the m2o converter should return None to avoid spurious"
|
||||
" or useless writes on the parent record")
|
||||
self.assertEqual(
|
||||
subrec1.name,
|
||||
"Foo",
|
||||
"element edition can't change directly the m2o record"
|
||||
)
|
||||
self.assertEqual(
|
||||
record.many2one.name,
|
||||
"Bar",
|
||||
"element edition should have been change the m2o id"
|
||||
)
|
||||
|
|
@ -0,0 +1,355 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import odoo.tests
|
||||
|
||||
from odoo.tests.common import BaseCase
|
||||
from odoo.addons.html_editor.models.diff_utils import (
|
||||
generate_patch,
|
||||
generate_comparison,
|
||||
apply_patch,
|
||||
)
|
||||
|
||||
|
||||
@odoo.tests.tagged("post_install", "-at_install", "html_history")
|
||||
class TestPatchUtils(BaseCase):
|
||||
def test_new_content_add_line(self):
|
||||
initial_content = "<p>foo</p><p>baz</p>"
|
||||
new_content = "<p>foo</p><p>bar</p><p>baz</p>"
|
||||
|
||||
patch = generate_patch(new_content, initial_content)
|
||||
# Even if we added content in the new_content, we expect a remove
|
||||
# operation, because the patch would be used to restore the initial
|
||||
# content from the new content.
|
||||
self.assertEqual(patch, "-@3,4")
|
||||
|
||||
restored_initial_content = apply_patch(new_content, patch)
|
||||
self.assertEqual(restored_initial_content, initial_content)
|
||||
|
||||
comparison = generate_comparison(new_content, initial_content)
|
||||
self.assertEqual(
|
||||
comparison, "<p>foo</p><p><removed>bar</removed></p><p>baz</p>"
|
||||
)
|
||||
|
||||
def test_new_content_remove_line(self):
|
||||
initial_content = "<p>foo</p><p>bar</p><p>baz</p>"
|
||||
new_content = "<p>foo</p><p>baz</p>"
|
||||
|
||||
patch = generate_patch(new_content, initial_content)
|
||||
self.assertEqual(patch, "+@2:<p>bar</p>")
|
||||
|
||||
restored_initial_content = apply_patch(new_content, patch)
|
||||
self.assertEqual(restored_initial_content, initial_content)
|
||||
|
||||
comparison = generate_comparison(new_content, initial_content)
|
||||
self.assertEqual(
|
||||
comparison, "<p>foo</p><p><added>bar</added></p><p>baz</p>"
|
||||
)
|
||||
|
||||
def test_new_content_replace_line(self):
|
||||
initial_content = "<p>foo</p><p>bar</p><p>bor</p><p>bir</p><p>baz</p>"
|
||||
new_content = "<p>foo</p><p>buz</p><p>baz</p>"
|
||||
|
||||
patch = generate_patch(new_content, initial_content)
|
||||
self.assertEqual(patch, "R@3:<p>bar</p><p>bor</p><p>bir")
|
||||
|
||||
restored_initial_content = apply_patch(new_content, patch)
|
||||
self.assertEqual(restored_initial_content, initial_content)
|
||||
|
||||
comparison = generate_comparison(new_content, initial_content)
|
||||
self.assertEqual(
|
||||
comparison,
|
||||
"<p>foo</p>"
|
||||
"<p><added>bar</added></p>"
|
||||
"<p><added>bor</added></p>"
|
||||
"<p><added>bir</added><removed>buz</removed></p>"
|
||||
"<p>baz</p>",
|
||||
)
|
||||
|
||||
def test_new_content_is_falsy(self):
|
||||
initial_content = "<p>foo</p><p>bar</p>"
|
||||
new_content = ""
|
||||
|
||||
patch = generate_patch(new_content, initial_content)
|
||||
self.assertEqual(patch, "+@0:<p>foo</p><p>bar</p>")
|
||||
|
||||
restored_initial_content = apply_patch(new_content, patch)
|
||||
self.assertEqual(restored_initial_content, initial_content)
|
||||
|
||||
comparison = generate_comparison(new_content, initial_content)
|
||||
self.assertEqual(
|
||||
comparison, "<p><added>foo</added></p><p><added>bar</added></p>"
|
||||
)
|
||||
|
||||
def test_new_content_is_equal(self):
|
||||
initial_content = "<p>foo</p><p>bar</p>"
|
||||
new_content = "<p>foo</p><p>bar</p>"
|
||||
|
||||
patch = generate_patch(new_content, initial_content)
|
||||
self.assertEqual(patch, "")
|
||||
restored_initial_content = apply_patch(new_content, patch)
|
||||
self.assertEqual(restored_initial_content, initial_content)
|
||||
|
||||
initial_content = ""
|
||||
new_content = ""
|
||||
|
||||
patch = generate_patch(new_content, initial_content)
|
||||
self.assertEqual(patch, "")
|
||||
restored_initial_content = apply_patch(new_content, patch)
|
||||
self.assertEqual(restored_initial_content, initial_content)
|
||||
|
||||
def test_new_content_multiple_operation(self):
|
||||
initial_content = "<p>foo</p><p>bar</p><p>baz</p><p>buz</p><p>boz</p>"
|
||||
new_content = (
|
||||
"<p>foo</p><div>new1<b>new2</b>new3</div>"
|
||||
"<p>bar</p><p>baz</p><p>boz</p><p>end</p>"
|
||||
)
|
||||
|
||||
patch = generate_patch(new_content, initial_content)
|
||||
self.assertEqual(
|
||||
patch,
|
||||
"""-@3,6
|
||||
+@10:<p>buz</p>
|
||||
-@13,14""",
|
||||
)
|
||||
|
||||
restored_initial_content = apply_patch(new_content, patch)
|
||||
self.assertEqual(restored_initial_content, initial_content)
|
||||
|
||||
comparison = generate_comparison(new_content, initial_content)
|
||||
self.assertEqual(
|
||||
comparison,
|
||||
"<p>foo</p>"
|
||||
"<div><removed>new1</removed>"
|
||||
"<b><removed>new2</removed></b>"
|
||||
"<removed>new3</removed></div>"
|
||||
"<p>bar</p><p>baz</p><p><added>buz</added></p>"
|
||||
"<p>boz</p><p><removed>end</removed></p>",
|
||||
)
|
||||
|
||||
def test_multiple_revision(self):
|
||||
contents = [
|
||||
"<p>foo</p><p>bar</p>",
|
||||
"<p>foo</p>",
|
||||
"<p>f<b>u</b>i</p><p>baz</p>",
|
||||
"<p>fi</p><p>boz</p>",
|
||||
"<div><h1>something</h1><p>completely different</p></div>",
|
||||
"<p>foo</p><p>boz</p><p>buz</p>",
|
||||
"<p>buz</p>",
|
||||
]
|
||||
patches = []
|
||||
for i in range(len(contents) - 1):
|
||||
patches.append(generate_patch(contents[i + 1], contents[i]))
|
||||
|
||||
patches.reverse()
|
||||
reconstruct_content = contents[-1]
|
||||
for patch in patches:
|
||||
reconstruct_content = apply_patch(reconstruct_content, patch)
|
||||
|
||||
self.assertEqual(reconstruct_content, contents[0])
|
||||
|
||||
def test_replace_tag(self):
|
||||
initial_content = "<blockquote>foo</blockquote>"
|
||||
new_content = "<code>foo</code>"
|
||||
|
||||
comparison = generate_comparison(new_content, initial_content)
|
||||
self.assertEqual(
|
||||
comparison,
|
||||
"<blockquote><added>foo</added></blockquote>"
|
||||
"<code><removed>foo</removed></code>",
|
||||
)
|
||||
|
||||
def test_replace_complex(self):
|
||||
initial_content = (
|
||||
"<blockquote>foo</blockquote>"
|
||||
"<blockquote>bar</blockquote>"
|
||||
"<blockquote>baz</blockquote>"
|
||||
"<p>---<span>***</span>---</p>"
|
||||
"<blockquote>only content change</blockquote>"
|
||||
"<p>+++<span>~~~</span>+++</p>"
|
||||
"<blockquote>content and tag change</blockquote>"
|
||||
"<p>???<span>===</span>???</p>"
|
||||
"<blockquote>111</blockquote>"
|
||||
"<blockquote>222</blockquote>"
|
||||
"<blockquote>333</blockquote>"
|
||||
)
|
||||
new_content = (
|
||||
"<code>foo</code>"
|
||||
"<code>bar</code>"
|
||||
"<code>baz</code>"
|
||||
"<p>---<span>***</span>---</p>"
|
||||
"<blockquote>lorem ipsum</blockquote>"
|
||||
"<p>+++<span>~~~</span>+++</p>"
|
||||
"<code>dolor sit amet</code>"
|
||||
"<p>???<span>===</span>???</p>"
|
||||
"<blockquote>aaa</blockquote>"
|
||||
"<blockquote>bbb</blockquote>"
|
||||
"<blockquote>ccc</blockquote>"
|
||||
)
|
||||
|
||||
comparison = generate_comparison(new_content, initial_content)
|
||||
self.assertEqual(
|
||||
comparison,
|
||||
"<blockquote><added>foo</added></blockquote>"
|
||||
"<blockquote><added>bar</added></blockquote>"
|
||||
"<blockquote><added>baz</added></blockquote>"
|
||||
"<code><removed>foo</removed></code>"
|
||||
"<code><removed>bar</removed></code>"
|
||||
"<code><removed>baz</removed></code>"
|
||||
"<p>---<span>***</span>---</p>"
|
||||
"<blockquote><added>only content change</added>"
|
||||
"<removed>lorem ipsum</removed></blockquote>"
|
||||
"<p>+++<span>~~~</span>+++</p>"
|
||||
"<blockquote><added>content and tag change</added></blockquote>"
|
||||
"<code><removed>dolor sit amet</removed></code>"
|
||||
"<p>???<span>===</span>???</p>"
|
||||
"<blockquote><added>111</added><removed>aaa</removed></blockquote>"
|
||||
"<blockquote><added>222</added><removed>bbb</removed></blockquote>"
|
||||
"<blockquote><added>333</added><removed>ccc</removed></blockquote>",
|
||||
)
|
||||
|
||||
def test_replace_tag_multiline(self):
|
||||
initial_content = (
|
||||
"<blockquote>foo</blockquote>"
|
||||
"<code>bar lorem ipsum dolor</code>"
|
||||
"<blockquote>baz</blockquote>"
|
||||
)
|
||||
new_content = (
|
||||
"<code>foo</code>"
|
||||
"<blockquote>bar lorem ipsum dolor</blockquote>"
|
||||
"<code>baz</code>"
|
||||
)
|
||||
|
||||
comparison = generate_comparison(new_content, initial_content)
|
||||
self.assertEqual(
|
||||
comparison,
|
||||
"<blockquote><added>foo</added></blockquote>"
|
||||
"<code><added>bar lorem ipsum dolor</added>"
|
||||
"<removed>foo</removed></code>"
|
||||
"<blockquote><added>baz</added>"
|
||||
"<removed>bar lorem ipsum dolor</removed></blockquote>"
|
||||
"<code><removed>baz</removed></code>",
|
||||
)
|
||||
|
||||
def test_replace_nested_divs(self):
|
||||
initial_content = "<div class='A1'><div class='A2'><b>A</b></div></div>"
|
||||
new_content = "<div class='B1'><div class='B2'><i>B</i></div></div>"
|
||||
|
||||
comparison = generate_comparison(new_content, initial_content)
|
||||
# This is a trade-off because of the limitation of the current
|
||||
# comparison system :
|
||||
# We can't easily generate comparison when only the tag parameters
|
||||
# changes, because the diff system will not contain the closing tags
|
||||
# in this case.
|
||||
#
|
||||
# This is why we choose to have the comparison below instead of :
|
||||
# <div class='A1'><div class='A2'>
|
||||
# <b><removed>A</removed></b>
|
||||
# </div></div>
|
||||
# <div class='B1'><div class='B2'>
|
||||
# <i><added>B</added></i>
|
||||
# </div></div>
|
||||
#
|
||||
# If we need to improve this in the future, we would probably have to
|
||||
# change drastically the comparison system to add a way to parse HTML.
|
||||
self.assertEqual(
|
||||
comparison,
|
||||
"<div class='A1'><div class='A2'>"
|
||||
"<b><added>A</added></b>"
|
||||
"<i><removed>B</removed></i>"
|
||||
"</div></div>",
|
||||
)
|
||||
|
||||
def test_same_tag_replace_fixer(self):
|
||||
initial_content = "<div><p><b>A</b><b>B</b></p></div>"
|
||||
new_content = "<div>X<p><b>B</b></p></div>"
|
||||
|
||||
comparison = generate_comparison(new_content, initial_content)
|
||||
# This is a trade-off, see explanation in test_replace_nested_divs.
|
||||
self.assertEqual(
|
||||
comparison,
|
||||
"<div><removed>X</removed>"
|
||||
"<p><b><added>A</added></b>"
|
||||
"<b>B</b></p></div>",
|
||||
)
|
||||
|
||||
def test_simple_removal(self):
|
||||
initial_content = "<div><p>A</p></div>"
|
||||
new_content = "<div>X<p>A</p></div>"
|
||||
|
||||
comparison = generate_comparison(new_content, initial_content)
|
||||
# This is a trade-off, see explanation in test_replace_nested_divs.
|
||||
self.assertEqual(
|
||||
comparison,
|
||||
"<div><removed>X</removed><p>A</p></div>",
|
||||
)
|
||||
|
||||
def test_simple_addition(self):
|
||||
initial_content = "<div>X<p>A</p></div>"
|
||||
new_content = "<div><p>A</p></div>"
|
||||
|
||||
comparison = generate_comparison(new_content, initial_content)
|
||||
# This is a trade-off, see explanation in test_replace_nested_divs.
|
||||
self.assertEqual(
|
||||
comparison,
|
||||
"<div><added>X</added><p>A</p></div>",
|
||||
)
|
||||
|
||||
def test_replace_just_class(self):
|
||||
initial_content = "<div class='A1'>A</div>"
|
||||
new_content = "<div class='B1'>A</div>"
|
||||
|
||||
comparison = generate_comparison(new_content, initial_content)
|
||||
# This is a trade-off, see explanation in test_replace_nested_divs.
|
||||
self.assertEqual(
|
||||
comparison,
|
||||
"<div class='A1'>A</div>",
|
||||
)
|
||||
|
||||
def test_replace_twice_just_class(self):
|
||||
initial_content = (
|
||||
"<div class='A1'>A</div><p>abc</p><div class='D1'>D</div>"
|
||||
)
|
||||
new_content = "<div class='B1'>A</div><p>abc</p><div class='E1'>D</div>"
|
||||
|
||||
comparison = generate_comparison(new_content, initial_content)
|
||||
# This is a trade-off, see explanation in test_replace_nested_divs.
|
||||
self.assertEqual(
|
||||
comparison,
|
||||
"<div class='A1'>A</div><p>abc</p><div class='D1'>D</div>",
|
||||
)
|
||||
|
||||
def test_replace_with_just_class(self):
|
||||
initial_content = "<p>abc</p><div class='A1'>A</div>"
|
||||
new_content = "<p>def</p><div class='B1'>A</div>"
|
||||
|
||||
comparison = generate_comparison(new_content, initial_content)
|
||||
# This is a trade-off, see explanation in test_replace_nested_divs.
|
||||
self.assertEqual(
|
||||
comparison,
|
||||
"<p><added>abc</added><removed>def</removed></p>"
|
||||
"<div class='A1'>A</div>",
|
||||
)
|
||||
|
||||
def test_replace_class_and_content(self):
|
||||
initial_content = "<div class='A1'>A</div>"
|
||||
new_content = "<div class='B1'>B</div>"
|
||||
|
||||
comparison = generate_comparison(new_content, initial_content)
|
||||
# This is a trade-off, see explanation in test_replace_nested_divs.
|
||||
self.assertEqual(
|
||||
comparison,
|
||||
"<div class='A1'><added>A</added><removed>B</removed></div>",
|
||||
)
|
||||
|
||||
def test_replace_class_and_deep_content(self):
|
||||
initial_content = "<div class='A1'><p><i>A</i></p></div>"
|
||||
new_content = "<div class='B1'><p><i>B</i></p></div>"
|
||||
|
||||
comparison = generate_comparison(new_content, initial_content)
|
||||
# This is a trade-off, see explanation in test_replace_nested_divs.
|
||||
self.assertEqual(
|
||||
comparison,
|
||||
"<div class='A1'><p><i>"
|
||||
"<added>A</added><removed>B</removed>"
|
||||
"</i></p></div>",
|
||||
)
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import re
|
||||
|
||||
from odoo.tests import common, tagged
|
||||
from odoo.addons.html_editor import tools
|
||||
|
||||
|
||||
@tagged("post_install", "-at_install")
|
||||
class TestVideoUtils(common.BaseCase):
|
||||
urls = {
|
||||
'youtube': 'https://www.youtube.com/watch?v=xCvFZrrQq7k',
|
||||
'youtube_shorts_video': 'https://www.youtube.com/shorts/qAgW3oG7Zmc',
|
||||
'youtube_live_stream': 'https://www.youtube.com/live/fmVNEoxr7iU?feature=shared',
|
||||
'youtube_mobile': 'https://m.youtube.com/watch?v=xCvFZrrQq7k',
|
||||
'vimeo': 'https://vimeo.com/395399735',
|
||||
'vimeo_unlisted_video': 'https://vimeo.com/795669787/0763fdb816',
|
||||
'vimeo_player': 'https://player.vimeo.com/video/395399735',
|
||||
'vimeo_player_unlisted_video': 'https://player.vimeo.com/video/795669787?h=0763fdb816',
|
||||
'dailymotion': 'https://www.dailymotion.com/video/x7svr6t',
|
||||
'youku': 'https://v.youku.com/v_show/id_XMzY1MjY4.html?spm=a2hzp.8244740.0.0',
|
||||
'instagram': 'https://www.instagram.com/p/B6dXGTxggTG/',
|
||||
'dailymotion_hub_no_video': 'http://www.dailymotion.com/hub/x9q_Galatasaray',
|
||||
'dailymotion_hub_#video': 'http://www.dailymotion.com/hub/x9q_Galatasaray#video=x2jvvep',
|
||||
'dai.ly': 'https://dai.ly/x578has',
|
||||
'dailymotion_embed': 'https://www.dailymotion.com/embed/video/x578has?autoplay=1',
|
||||
'dailymotion_video_extra': 'https://www.dailymotion.com/video/x2jvvep_hakan-yukur-klip_sport',
|
||||
'player_youku': 'https://player.youku.com/player.php/sid/XMTI5Mjg5NjE4MA==/v.swf',
|
||||
'youku_embed': 'https://player.youku.com/embed/XNTIwMzE1MzUzNg',
|
||||
"facebook_video": "http://www.facebook.com/watch?v=2206239373151307",
|
||||
"facebook_reel": "https://www.facebook.com/reel/568986686120283",
|
||||
}
|
||||
|
||||
def test_player_regexes(self):
|
||||
# youtube
|
||||
self.assertIsNotNone(re.search(tools.player_regexes['youtube'], TestVideoUtils.urls['youtube']))
|
||||
self.assertIsNotNone(re.search(tools.player_regexes['youtube'], TestVideoUtils.urls['youtube_shorts_video']))
|
||||
self.assertIsNotNone(re.search(tools.player_regexes['youtube'], TestVideoUtils.urls['youtube_live_stream']))
|
||||
# vimeo
|
||||
self.assertIsNotNone(re.search(tools.player_regexes['vimeo'], TestVideoUtils.urls['vimeo']))
|
||||
self.assertIsNotNone(re.search(tools.player_regexes['vimeo'], TestVideoUtils.urls['vimeo_unlisted_video']))
|
||||
self.assertIsNotNone(re.search(tools.player_regexes['vimeo'], TestVideoUtils.urls['vimeo_player']))
|
||||
self.assertIsNotNone(re.search(tools.player_regexes['vimeo'], TestVideoUtils.urls['vimeo_player_unlisted_video']))
|
||||
# dailymotion
|
||||
self.assertIsNotNone(re.search(tools.player_regexes['dailymotion'], TestVideoUtils.urls['dailymotion']))
|
||||
# youku
|
||||
self.assertIsNotNone(re.search(tools.player_regexes['youku'], TestVideoUtils.urls['youku']))
|
||||
# instagram
|
||||
self.assertIsNotNone(re.search(tools.player_regexes['instagram'], TestVideoUtils.urls['instagram']))
|
||||
# facebook
|
||||
self.assertIsNotNone(re.search(tools.player_regexes["facebook"], TestVideoUtils.urls["facebook_video"]))
|
||||
self.assertIsNotNone(re.search(tools.player_regexes["facebook"], TestVideoUtils.urls["facebook_reel"]))
|
||||
|
||||
def test_get_video_source_data(self):
|
||||
self.assertEqual(3, len(tools.get_video_source_data(TestVideoUtils.urls['youtube'])))
|
||||
# youtube
|
||||
self.assertEqual('youtube', tools.get_video_source_data(TestVideoUtils.urls['youtube'])[0])
|
||||
self.assertEqual('xCvFZrrQq7k', tools.get_video_source_data(TestVideoUtils.urls['youtube'])[1])
|
||||
self.assertEqual('youtube', tools.get_video_source_data(TestVideoUtils.urls['youtube_shorts_video'])[0])
|
||||
self.assertEqual('qAgW3oG7Zmc', tools.get_video_source_data(TestVideoUtils.urls['youtube_shorts_video'])[1])
|
||||
self.assertEqual('youtube', tools.get_video_source_data(TestVideoUtils.urls['youtube_live_stream'])[0])
|
||||
self.assertEqual('fmVNEoxr7iU', tools.get_video_source_data(TestVideoUtils.urls['youtube_live_stream'])[1])
|
||||
self.assertEqual('youtube', tools.get_video_source_data(TestVideoUtils.urls['youtube_mobile'])[0])
|
||||
self.assertEqual('xCvFZrrQq7k', tools.get_video_source_data(TestVideoUtils.urls['youtube_mobile'])[1])
|
||||
# vimeo
|
||||
self.assertEqual('vimeo', tools.get_video_source_data(TestVideoUtils.urls['vimeo'])[0])
|
||||
self.assertEqual('395399735', tools.get_video_source_data(TestVideoUtils.urls['vimeo'])[1])
|
||||
self.assertEqual('vimeo', tools.get_video_source_data(TestVideoUtils.urls['vimeo_unlisted_video'])[0])
|
||||
self.assertEqual('795669787', tools.get_video_source_data(TestVideoUtils.urls['vimeo_unlisted_video'])[1])
|
||||
self.assertEqual('vimeo', tools.get_video_source_data(TestVideoUtils.urls['vimeo_player'])[0])
|
||||
self.assertEqual('395399735', tools.get_video_source_data(TestVideoUtils.urls['vimeo_player'])[1])
|
||||
self.assertEqual('vimeo', tools.get_video_source_data(TestVideoUtils.urls['vimeo_player_unlisted_video'])[0])
|
||||
self.assertEqual('795669787', tools.get_video_source_data(TestVideoUtils.urls['vimeo_player_unlisted_video'])[1])
|
||||
# dailymotion
|
||||
self.assertEqual('dailymotion', tools.get_video_source_data(TestVideoUtils.urls['dailymotion'])[0])
|
||||
self.assertEqual('x7svr6t', tools.get_video_source_data(TestVideoUtils.urls['dailymotion'])[1])
|
||||
self.assertEqual(None, tools.get_video_source_data(TestVideoUtils.urls['dailymotion_hub_no_video']))
|
||||
self.assertEqual('dailymotion', tools.get_video_source_data(TestVideoUtils.urls['dailymotion_hub_#video'])[0])
|
||||
self.assertEqual('x2jvvep', tools.get_video_source_data(TestVideoUtils.urls['dailymotion_hub_#video'])[1])
|
||||
self.assertEqual('dailymotion', tools.get_video_source_data(TestVideoUtils.urls['dai.ly'])[0])
|
||||
self.assertEqual('x578has', tools.get_video_source_data(TestVideoUtils.urls['dai.ly'])[1])
|
||||
self.assertEqual('dailymotion', tools.get_video_source_data(TestVideoUtils.urls['dailymotion_embed'])[0])
|
||||
self.assertEqual('x578has', tools.get_video_source_data(TestVideoUtils.urls['dailymotion_embed'])[1])
|
||||
self.assertEqual('dailymotion', tools.get_video_source_data(TestVideoUtils.urls['dailymotion_video_extra'])[0])
|
||||
self.assertEqual('x2jvvep', tools.get_video_source_data(TestVideoUtils.urls['dailymotion_video_extra'])[1])
|
||||
# youku
|
||||
self.assertEqual('youku', tools.get_video_source_data(TestVideoUtils.urls['youku'])[0])
|
||||
self.assertEqual('XMzY1MjY4', tools.get_video_source_data(TestVideoUtils.urls['youku'])[1])
|
||||
self.assertEqual('youku', tools.get_video_source_data(TestVideoUtils.urls['player_youku'])[0])
|
||||
self.assertEqual('XMTI5Mjg5NjE4MA', tools.get_video_source_data(TestVideoUtils.urls['player_youku'])[1])
|
||||
self.assertEqual('youku', tools.get_video_source_data(TestVideoUtils.urls['youku_embed'])[0])
|
||||
self.assertEqual('XNTIwMzE1MzUzNg', tools.get_video_source_data(TestVideoUtils.urls['youku_embed'])[1])
|
||||
# instagram
|
||||
self.assertEqual('instagram', tools.get_video_source_data(TestVideoUtils.urls['instagram'])[0])
|
||||
self.assertEqual('B6dXGTxggTG', tools.get_video_source_data(TestVideoUtils.urls['instagram'])[1])
|
||||
# facebook
|
||||
self.assertEqual("facebook", tools.get_video_source_data(TestVideoUtils.urls["facebook_video"])[0])
|
||||
self.assertEqual("2206239373151307", tools.get_video_source_data(TestVideoUtils.urls["facebook_video"])[1])
|
||||
self.assertEqual("facebook", tools.get_video_source_data(TestVideoUtils.urls["facebook_reel"])[0])
|
||||
self.assertEqual("568986686120283", tools.get_video_source_data(TestVideoUtils.urls["facebook_reel"])[1])
|
||||
|
||||
def test_get_video_url_data(self):
|
||||
self.assertEqual(4, len(tools.get_video_url_data(TestVideoUtils.urls['youtube'])))
|
||||
# youtube
|
||||
for key in ['youtube', 'youtube_shorts_video', 'youtube_live_stream']:
|
||||
self.assertEqual('youtube', tools.get_video_url_data(TestVideoUtils.urls[key])['platform'])
|
||||
# vimeo
|
||||
for key in ['vimeo', 'vimeo_player']:
|
||||
self.assertEqual(tools.get_video_url_data(TestVideoUtils.urls[key]), {
|
||||
'platform': 'vimeo',
|
||||
'embed_url': '//player.vimeo.com/video/395399735?autoplay=0&dnt=1',
|
||||
'video_id': '395399735',
|
||||
'params': {
|
||||
'autoplay': 0,
|
||||
'dnt': 1,
|
||||
}
|
||||
})
|
||||
for key in ['vimeo_unlisted_video', 'vimeo_player_unlisted_video']:
|
||||
self.assertEqual(tools.get_video_url_data(TestVideoUtils.urls[key]), {
|
||||
'platform': 'vimeo',
|
||||
'embed_url': '//player.vimeo.com/video/795669787?autoplay=0&dnt=1&h=0763fdb816',
|
||||
'video_id': '795669787',
|
||||
'params': {
|
||||
'autoplay': 0,
|
||||
'dnt': 1,
|
||||
'h': '0763fdb816',
|
||||
}
|
||||
})
|
||||
# dailymotion
|
||||
self.assertEqual('dailymotion', tools.get_video_url_data(TestVideoUtils.urls['dailymotion'])['platform'])
|
||||
# youku
|
||||
self.assertEqual('youku', tools.get_video_url_data(TestVideoUtils.urls['youku'])['platform'])
|
||||
# instagram
|
||||
self.assertEqual('instagram', tools.get_video_url_data(TestVideoUtils.urls['instagram'])['platform'])
|
||||
|
||||
def test_valid_video_url(self):
|
||||
self.assertIsNotNone(re.search(tools.valid_url_regex, TestVideoUtils.urls['youtube']))
|
||||
|
||||
|
||||
@tagged('-standard', 'external')
|
||||
class TestVideoUtilsExternal(common.BaseCase):
|
||||
def test_get_video_thumbnail(self):
|
||||
# youtube
|
||||
for key in ['youtube', 'youtube_shorts_video', 'youtube_live_stream']:
|
||||
self.assertIsInstance(tools.get_video_thumbnail(TestVideoUtils.urls[key]), bytes)
|
||||
# vimeo
|
||||
for key in ['vimeo', 'vimeo_unlisted_video', 'vimeo_player', 'vimeo_player_unlisted_video']:
|
||||
self.assertIsInstance(tools.get_video_thumbnail(TestVideoUtils.urls[key]), bytes)
|
||||
# dailymotion
|
||||
self.assertIsInstance(tools.get_video_thumbnail(TestVideoUtils.urls['dailymotion']), bytes)
|
||||
# instagram
|
||||
self.assertIsInstance(tools.get_video_thumbnail(TestVideoUtils.urls['instagram']), bytes)
|
||||
# default
|
||||
self.assertIsInstance(tools.get_video_thumbnail(TestVideoUtils.urls['youku']), bytes)
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests import TransactionCase
|
||||
|
||||
|
||||
class TestViews(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
View = self.env['ir.ui.view']
|
||||
self.first_view = View.create({
|
||||
'name': 'Test View 1',
|
||||
'type': 'qweb',
|
||||
'arch': '<div>Hello World</div>',
|
||||
'key': 'html_editor.test_first_view',
|
||||
})
|
||||
self.second_view = View.create({
|
||||
'name': 'Test View 2',
|
||||
'type': 'qweb',
|
||||
'arch': '<div><t t-call="html_editor.test_first_view"/></div>',
|
||||
'key': 'html_editor.test_second_view',
|
||||
})
|
||||
|
||||
def test_infinite_inherit_loop(self):
|
||||
# Creates an infinite loop: A t-call B and A inherit from B
|
||||
View = self.env['ir.ui.view']
|
||||
|
||||
self.second_view.write({
|
||||
'inherit_id': self.first_view.id,
|
||||
})
|
||||
# Test for RecursionError: maximum recursion depth exceeded in this function
|
||||
View._views_get(self.first_view)
|
||||
|
||||
def test_oe_structure_as_inherited_view(self):
|
||||
View = self.env['ir.ui.view']
|
||||
|
||||
base = View.create({
|
||||
'name': 'Test View oe_structure',
|
||||
'type': 'qweb',
|
||||
'arch': """<xpath expr='//t[@t-call="html_editor.test_first_view"]' position='after'>
|
||||
<div class="oe_structure" id='oe_structure_test_view_oe_structure'/>
|
||||
</xpath>""",
|
||||
'key': 'html_editor.oe_structure_view',
|
||||
'inherit_id': self.second_view.id
|
||||
})
|
||||
|
||||
# check view mode
|
||||
self.assertEqual(base.mode, 'extension')
|
||||
|
||||
# update content of the oe_structure
|
||||
value = '''<div class="oe_structure" id="oe_structure_test_view_oe_structure" data-oe-id="%s"
|
||||
data-oe-xpath="/div" data-oe-model="ir.ui.view" data-oe-field="arch">
|
||||
<p>Hello World!</p>
|
||||
</div>''' % base.id
|
||||
|
||||
base.save(value=value, xpath='/xpath/div')
|
||||
|
||||
self.assertEqual(len(base.inherit_children_ids), 1)
|
||||
self.assertEqual(base.inherit_children_ids.mode, 'extension')
|
||||
self.assertIn(
|
||||
'<p>Hello World!</p>',
|
||||
base.inherit_children_ids.get_combined_arch(),
|
||||
)
|
||||
|
||||
def test_find_available_name(self):
|
||||
View = self.env['ir.ui.view']
|
||||
used_names = ['Unrelated name']
|
||||
initial_name = "Test name"
|
||||
name = View._find_available_name(initial_name, used_names)
|
||||
self.assertEqual(initial_name, name)
|
||||
used_names.append(name)
|
||||
name = View._find_available_name(initial_name, used_names)
|
||||
self.assertEqual('Test name (2)', name)
|
||||
used_names.append(name)
|
||||
name = View._find_available_name(initial_name, used_names)
|
||||
self.assertEqual('Test name (3)', name)
|
||||
Loading…
Add table
Add a link
Reference in a new issue