Needed for the lint job of the CI to go green. Mostly automatic hook fixes (black, isort, prettier, pyupgrade, autoflake, whitespace). By hand: - flake8: two unused imports, an ambiguous variable name `l`. - pylint-odoo mandatory: manifest keys equal to their default value (installable, application, data, external_dependencies). - end-of-file-fixer skips *.patch too, like trailing-whitespace: the upstream patches stay as git format-patch wrote them. No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
144 lines
6.1 KiB
Python
144 lines
6.1 KiB
Python
# Copyright 2026 - Today Criptomart
|
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
|
|
|
from odoo.tests import tagged
|
|
from odoo.tests.common import HttpCase
|
|
|
|
PER_PAGE = 3
|
|
PRODUCT_COUNT = 7
|
|
PAGE_COUNT = 3 # ceil(7 / 3)
|
|
|
|
|
|
@tagged("post_install", "-at_install")
|
|
class TestLazyLoading(HttpCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
cls.website = cls.env["website"].browse(1)
|
|
cls.website.shop_ppg = PER_PAGE
|
|
# The listing has to be predictable: only the products created here may
|
|
# show up in the shop.
|
|
cls.env["product.template"].search([("is_published", "=", True)]).write(
|
|
{"is_published": False}
|
|
)
|
|
cls.category = cls.env["product.public.category"].create({"name": "Lazy Cat"})
|
|
cls.other_category = cls.env["product.public.category"].create(
|
|
{"name": "Other Cat"}
|
|
)
|
|
cls.products = cls.env["product.template"].create(
|
|
[
|
|
{
|
|
"name": "Lazy Product %02d" % index,
|
|
"list_price": 10.0 + index,
|
|
"is_published": True,
|
|
"public_categ_ids": [(6, 0, cls.category.ids)],
|
|
}
|
|
for index in range(PRODUCT_COUNT)
|
|
]
|
|
)
|
|
cls.other_product = cls.env["product.template"].create(
|
|
{
|
|
"name": "Other Product",
|
|
"list_price": 5.0,
|
|
"is_published": True,
|
|
"public_categ_ids": [(6, 0, cls.other_category.ids)],
|
|
}
|
|
)
|
|
|
|
def _get_lazy_page(self, page, **params):
|
|
"""Call the AJAX endpoint and return its decoded payload."""
|
|
query = "&".join(f"{key}={value}" for key, value in params.items())
|
|
url = "/shop/lazy_products?page=%s" % page
|
|
if query:
|
|
url = f"{url}&{query}"
|
|
response = self.url_open(url)
|
|
self.assertEqual(response.status_code, 200)
|
|
return response.json()
|
|
|
|
# ------------------------------------------------------------------
|
|
# /shop rendering
|
|
# ------------------------------------------------------------------
|
|
|
|
def test_shop_renders_lazy_loading_block(self):
|
|
"""The listing carries the block driving the lazy loading."""
|
|
self.website.shop_lazy_loading = "scroll"
|
|
body = self.url_open("/shop").text
|
|
self.assertIn("o_wsale_lazy_loading", body)
|
|
self.assertIn('data-mode="scroll"', body)
|
|
self.assertIn('data-page-count="%s"' % PAGE_COUNT, body)
|
|
# The pager is still served: it is the fallback without JavaScript.
|
|
self.assertIn("products_pager", body)
|
|
|
|
def test_shop_without_lazy_loading(self):
|
|
"""The standard pager is left alone when the mode is off."""
|
|
self.website.shop_lazy_loading = "off"
|
|
body = self.url_open("/shop").text
|
|
self.assertNotIn("o_wsale_lazy_loading", body)
|
|
self.assertIn("products_pager", body)
|
|
|
|
def test_shop_last_page_has_no_block(self):
|
|
"""Nothing can be lazy loaded from the last page."""
|
|
self.website.shop_lazy_loading = "scroll"
|
|
body = self.url_open("/shop/page/%s" % PAGE_COUNT).text
|
|
self.assertNotIn("o_wsale_lazy_loading", body)
|
|
|
|
def test_lazy_loading_url_keeps_the_filters(self):
|
|
"""The AJAX URL carries the filters of the listing on screen."""
|
|
self.website.shop_lazy_loading = "scroll"
|
|
body = self.url_open("/shop?search=Lazy&order=name+desc").text
|
|
self.assertIn("/shop/lazy_products?", body)
|
|
self.assertIn("search=Lazy", body)
|
|
self.assertIn("order=name+desc", body)
|
|
|
|
# ------------------------------------------------------------------
|
|
# /shop/lazy_products
|
|
# ------------------------------------------------------------------
|
|
|
|
def test_lazy_page_returns_the_next_products(self):
|
|
"""A page holds the products of that page, and only those."""
|
|
result = self._get_lazy_page(2)
|
|
self.assertEqual(result["page"], 2)
|
|
self.assertEqual(result["page_count"], PAGE_COUNT)
|
|
self.assertTrue(result["has_next"])
|
|
self.assertIn("oe_product", result["html"])
|
|
for product in self.products[PER_PAGE : PER_PAGE * 2]:
|
|
self.assertIn(product.name, result["html"])
|
|
for product in self.products[:PER_PAGE]:
|
|
self.assertNotIn(product.name, result["html"])
|
|
|
|
def test_lazy_page_last_page(self):
|
|
"""The last page reports that there is nothing left to load."""
|
|
result = self._get_lazy_page(PAGE_COUNT)
|
|
self.assertFalse(result["has_next"])
|
|
self.assertIn(self.products[-1].name, result["html"])
|
|
|
|
def test_lazy_page_out_of_range(self):
|
|
"""An out of range page answers empty instead of repeating the last."""
|
|
result = self._get_lazy_page(PAGE_COUNT + 5)
|
|
self.assertEqual(result["html"], "")
|
|
self.assertFalse(result["has_next"])
|
|
|
|
def test_lazy_page_invalid_page(self):
|
|
"""A broken page number falls back to the first page."""
|
|
result = self._get_lazy_page("not-a-page")
|
|
self.assertEqual(result["page"], 1)
|
|
self.assertIn(self.products[0].name, result["html"])
|
|
|
|
def test_lazy_page_keeps_the_category(self):
|
|
"""Filtering by category is kept across the appended pages."""
|
|
result = self._get_lazy_page(2, category=self.category.id)
|
|
self.assertIn(self.products[PER_PAGE].name, result["html"])
|
|
self.assertNotIn(self.other_product.name, result["html"])
|
|
|
|
def test_lazy_page_ignores_a_broken_category(self):
|
|
"""A category that is not an id is dropped instead of crashing."""
|
|
result = self._get_lazy_page(1, category="pwned")
|
|
self.assertEqual(result["page"], 1)
|
|
self.assertIn(self.products[0].name, result["html"])
|
|
|
|
def test_lazy_page_unknown_category(self):
|
|
"""An unknown category answers a clean 404, not a traceback."""
|
|
unknown_id = self.other_category.id + 1000
|
|
response = self.url_open("/shop/lazy_products?page=1&category=%s" % unknown_id)
|
|
self.assertEqual(response.status_code, 404)
|
|
self.assertEqual(response.json()["error"], "not_found")
|