mirror of
https://github.com/django-components/django-components.git
synced 2025-08-31 03:07:19 +00:00
feat: add decorator for writing component tests (#1008)
* feat: add decorator for writing component tests * refactor: udpate changelog + update deps pins * refactor: fix deps * refactor: make cached_ref into generic and fix linter errors * refactor: fix coverage testing * refactor: use global var instead of env var for is_testing state
This commit is contained in:
parent
81ac59f7fb
commit
7dfcb447c4
62 changed files with 4428 additions and 3661 deletions
|
@ -1,12 +1,13 @@
|
|||
import re
|
||||
from typing import List
|
||||
|
||||
from django.test import override_settings
|
||||
from playwright.async_api import Error, Page
|
||||
import pytest
|
||||
from playwright.async_api import Browser, Error, Page
|
||||
|
||||
from django_components import types
|
||||
from tests.django_test_setup import setup_test_config
|
||||
from django_components.testing import djc_test
|
||||
from tests.testutils import setup_test_config
|
||||
from tests.e2e.utils import TEST_SERVER_URL, with_playwright
|
||||
from tests.testutils import BaseTestCase
|
||||
|
||||
setup_test_config(
|
||||
components={"autodiscover": False},
|
||||
|
@ -18,69 +19,73 @@ setup_test_config(
|
|||
urlpatterns: List = []
|
||||
|
||||
|
||||
class _BaseDepManagerTestCase(BaseTestCase):
|
||||
async def _create_page_with_dep_manager(self) -> Page:
|
||||
page = await self.browser.new_page()
|
||||
async def _create_page_with_dep_manager(browser: Browser) -> Page:
|
||||
page = await browser.new_page()
|
||||
|
||||
# Load the JS library by opening a page with the script, and then running the script code
|
||||
# E.g. `http://localhost:54017/static/django_components/django_components.min.js`
|
||||
script_url = TEST_SERVER_URL + "/static/django_components/django_components.min.js"
|
||||
await page.goto(script_url)
|
||||
# Load the JS library by opening a page with the script, and then running the script code
|
||||
# E.g. `http://localhost:54017/static/django_components/django_components.min.js`
|
||||
script_url = TEST_SERVER_URL + "/static/django_components/django_components.min.js"
|
||||
await page.goto(script_url)
|
||||
|
||||
# The page's body is the script code. We load it by executing the code
|
||||
await page.evaluate(
|
||||
"""
|
||||
() => {
|
||||
eval(document.body.textContent);
|
||||
}
|
||||
"""
|
||||
)
|
||||
# The page's body is the script code. We load it by executing the code
|
||||
await page.evaluate(
|
||||
"""
|
||||
() => {
|
||||
eval(document.body.textContent);
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
# Ensure the body is clear
|
||||
await page.evaluate(
|
||||
"""
|
||||
() => {
|
||||
document.body.innerHTML = '';
|
||||
document.head.innerHTML = '';
|
||||
}
|
||||
"""
|
||||
)
|
||||
# Ensure the body is clear
|
||||
await page.evaluate(
|
||||
"""
|
||||
() => {
|
||||
document.body.innerHTML = '';
|
||||
document.head.innerHTML = '';
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
return page
|
||||
return page
|
||||
|
||||
|
||||
@override_settings(STATIC_URL="static/")
|
||||
class DependencyManagerTests(_BaseDepManagerTestCase):
|
||||
@djc_test(
|
||||
django_settings={
|
||||
"STATIC_URL": "static/",
|
||||
}
|
||||
)
|
||||
class TestDependencyManager:
|
||||
@with_playwright
|
||||
async def test_script_loads(self):
|
||||
page = await self._create_page_with_dep_manager()
|
||||
page = await _create_page_with_dep_manager(self.browser) # type: ignore[attr-defined]
|
||||
|
||||
# Check the exposed API
|
||||
keys = sorted(await page.evaluate("Object.keys(Components)"))
|
||||
self.assertEqual(keys, ["createComponentsManager", "manager", "unescapeJs"])
|
||||
assert keys == ["createComponentsManager", "manager", "unescapeJs"]
|
||||
|
||||
keys = await page.evaluate("Object.keys(Components.manager)")
|
||||
self.assertEqual(
|
||||
keys,
|
||||
[
|
||||
"callComponent",
|
||||
"registerComponent",
|
||||
"registerComponentData",
|
||||
"loadJs",
|
||||
"loadCss",
|
||||
"markScriptLoaded",
|
||||
],
|
||||
)
|
||||
assert keys == [
|
||||
"callComponent",
|
||||
"registerComponent",
|
||||
"registerComponentData",
|
||||
"loadJs",
|
||||
"loadCss",
|
||||
"markScriptLoaded",
|
||||
]
|
||||
|
||||
await page.close()
|
||||
|
||||
|
||||
# Tests for `manager.loadJs()` / `manager.loadCss()` / `manager.markAsLoaded()`
|
||||
@override_settings(STATIC_URL="static/")
|
||||
class LoadScriptTests(_BaseDepManagerTestCase):
|
||||
@djc_test(
|
||||
django_settings={
|
||||
"STATIC_URL": "static/",
|
||||
}
|
||||
)
|
||||
class TestLoadScript:
|
||||
@with_playwright
|
||||
async def test_load_js_scripts(self):
|
||||
page = await self._create_page_with_dep_manager()
|
||||
page = await _create_page_with_dep_manager(self.browser) # type: ignore[attr-defined]
|
||||
|
||||
# JS code that loads a few dependencies, capturing the HTML after each action
|
||||
test_js: types.js = """() => {
|
||||
|
@ -113,20 +118,18 @@ class LoadScriptTests(_BaseDepManagerTestCase):
|
|||
|
||||
data = await page.evaluate(test_js)
|
||||
|
||||
self.assertEqual(data["bodyAfterFirstLoad"], '<script src="/one/two"></script>')
|
||||
self.assertEqual(data["bodyAfterSecondLoad"], '<script src="/one/two"></script>')
|
||||
self.assertEqual(
|
||||
data["bodyAfterThirdLoad"], '<script src="/one/two"></script><script src="/four/three"></script>'
|
||||
)
|
||||
assert data["bodyAfterFirstLoad"] == '<script src="/one/two"></script>'
|
||||
assert data["bodyAfterSecondLoad"] == '<script src="/one/two"></script>'
|
||||
assert data["bodyAfterThirdLoad"] == '<script src="/one/two"></script><script src="/four/three"></script>'
|
||||
|
||||
self.assertEqual(data["headBeforeFirstLoad"], data["headAfterThirdLoad"])
|
||||
self.assertEqual(data["headBeforeFirstLoad"], "")
|
||||
assert data["headBeforeFirstLoad"] == data["headAfterThirdLoad"]
|
||||
assert data["headBeforeFirstLoad"] == ""
|
||||
|
||||
await page.close()
|
||||
|
||||
@with_playwright
|
||||
async def test_load_css_scripts(self):
|
||||
page = await self._create_page_with_dep_manager()
|
||||
page = await _create_page_with_dep_manager(self.browser) # type: ignore[attr-defined]
|
||||
|
||||
# JS code that loads a few dependencies, capturing the HTML after each action
|
||||
test_js: types.js = """() => {
|
||||
|
@ -159,18 +162,18 @@ class LoadScriptTests(_BaseDepManagerTestCase):
|
|||
|
||||
data = await page.evaluate(test_js)
|
||||
|
||||
self.assertEqual(data["headAfterFirstLoad"], '<link href="/one/two">')
|
||||
self.assertEqual(data["headAfterSecondLoad"], '<link href="/one/two">')
|
||||
self.assertEqual(data["headAfterThirdLoad"], '<link href="/one/two"><link href="/four/three">')
|
||||
assert data["headAfterFirstLoad"] == '<link href="/one/two">'
|
||||
assert data["headAfterSecondLoad"] == '<link href="/one/two">'
|
||||
assert data["headAfterThirdLoad"] == '<link href="/one/two"><link href="/four/three">'
|
||||
|
||||
self.assertEqual(data["bodyBeforeFirstLoad"], data["bodyAfterThirdLoad"])
|
||||
self.assertEqual(data["bodyBeforeFirstLoad"], "")
|
||||
assert data["bodyBeforeFirstLoad"] == data["bodyAfterThirdLoad"]
|
||||
assert data["bodyBeforeFirstLoad"] == ""
|
||||
|
||||
await page.close()
|
||||
|
||||
@with_playwright
|
||||
async def test_does_not_load_script_if_marked_as_loaded(self):
|
||||
page = await self._create_page_with_dep_manager()
|
||||
page = await _create_page_with_dep_manager(self.browser) # type: ignore[attr-defined]
|
||||
|
||||
# JS code that loads a few dependencies, capturing the HTML after each action
|
||||
test_js: types.js = """() => {
|
||||
|
@ -194,18 +197,22 @@ class LoadScriptTests(_BaseDepManagerTestCase):
|
|||
|
||||
data = await page.evaluate(test_js)
|
||||
|
||||
self.assertEqual(data["headAfterFirstLoad"], "")
|
||||
self.assertEqual(data["bodyAfterSecondLoad"], "")
|
||||
assert data["headAfterFirstLoad"] == ""
|
||||
assert data["bodyAfterSecondLoad"] == ""
|
||||
|
||||
await page.close()
|
||||
|
||||
|
||||
# Tests for `manager.registerComponent()` / `registerComponentData()` / `callComponent()`
|
||||
@override_settings(STATIC_URL="static/")
|
||||
class CallComponentTests(_BaseDepManagerTestCase):
|
||||
@djc_test(
|
||||
django_settings={
|
||||
"STATIC_URL": "static/",
|
||||
}
|
||||
)
|
||||
class TestCallComponent:
|
||||
@with_playwright
|
||||
async def test_calls_component_successfully(self):
|
||||
page = await self._create_page_with_dep_manager()
|
||||
page = await _create_page_with_dep_manager(self.browser) # type: ignore[attr-defined]
|
||||
|
||||
test_js: types.js = """() => {
|
||||
const manager = Components.createComponentsManager();
|
||||
|
@ -240,26 +247,23 @@ class CallComponentTests(_BaseDepManagerTestCase):
|
|||
|
||||
data = await page.evaluate(test_js)
|
||||
|
||||
self.assertEqual(data["result"], 123)
|
||||
self.assertEqual(
|
||||
data["captured"],
|
||||
{
|
||||
"data": {
|
||||
"hello": "world",
|
||||
},
|
||||
"ctx": {
|
||||
"els": ['<div data-djc-id-12345=""> abc </div>'],
|
||||
"id": "12345",
|
||||
"name": "my_comp",
|
||||
},
|
||||
assert data["result"] == 123
|
||||
assert data["captured"] == {
|
||||
"data": {
|
||||
"hello": "world",
|
||||
},
|
||||
)
|
||||
"ctx": {
|
||||
"els": ['<div data-djc-id-12345=""> abc </div>'],
|
||||
"id": "12345",
|
||||
"name": "my_comp",
|
||||
},
|
||||
}
|
||||
|
||||
await page.close()
|
||||
|
||||
@with_playwright
|
||||
async def test_calls_component_successfully_async(self):
|
||||
page = await self._create_page_with_dep_manager()
|
||||
page = await _create_page_with_dep_manager(self.browser) # type: ignore[attr-defined]
|
||||
|
||||
test_js: types.js = """() => {
|
||||
const manager = Components.createComponentsManager();
|
||||
|
@ -292,14 +296,14 @@ class CallComponentTests(_BaseDepManagerTestCase):
|
|||
|
||||
data = await page.evaluate(test_js)
|
||||
|
||||
self.assertEqual(data["result"], 123)
|
||||
self.assertEqual(data["isPromise"], True)
|
||||
assert data["result"] == 123
|
||||
assert data["isPromise"] is True
|
||||
|
||||
await page.close()
|
||||
|
||||
@with_playwright
|
||||
async def test_error_in_component_call_do_not_propagate_sync(self):
|
||||
page = await self._create_page_with_dep_manager()
|
||||
page = await _create_page_with_dep_manager(self.browser) # type: ignore[attr-defined]
|
||||
|
||||
test_js: types.js = """() => {
|
||||
const manager = Components.createComponentsManager();
|
||||
|
@ -327,13 +331,13 @@ class CallComponentTests(_BaseDepManagerTestCase):
|
|||
|
||||
data = await page.evaluate(test_js)
|
||||
|
||||
self.assertEqual(data, None)
|
||||
assert data is None
|
||||
|
||||
await page.close()
|
||||
|
||||
@with_playwright
|
||||
async def test_error_in_component_call_do_not_propagate_async(self):
|
||||
page = await self._create_page_with_dep_manager()
|
||||
page = await _create_page_with_dep_manager(self.browser) # type: ignore[attr-defined]
|
||||
|
||||
test_js: types.js = """() => {
|
||||
const manager = Components.createComponentsManager();
|
||||
|
@ -360,16 +364,16 @@ class CallComponentTests(_BaseDepManagerTestCase):
|
|||
|
||||
data = await page.evaluate(test_js)
|
||||
|
||||
self.assertEqual(len(data), 1)
|
||||
self.assertEqual(data[0]["status"], "rejected")
|
||||
self.assertIsInstance(data[0]["reason"], Error)
|
||||
self.assertEqual(data[0]["reason"].message, "Oops!")
|
||||
assert len(data) == 1
|
||||
assert data[0]["status"] == "rejected"
|
||||
assert isinstance(data[0]["reason"], Error)
|
||||
assert data[0]["reason"].message == "Oops!"
|
||||
|
||||
await page.close()
|
||||
|
||||
@with_playwright
|
||||
async def test_raises_if_component_element_not_in_dom(self):
|
||||
page = await self._create_page_with_dep_manager()
|
||||
page = await _create_page_with_dep_manager(self.browser) # type: ignore[attr-defined]
|
||||
|
||||
test_js: types.js = """() => {
|
||||
const manager = Components.createComponentsManager();
|
||||
|
@ -390,8 +394,9 @@ class CallComponentTests(_BaseDepManagerTestCase):
|
|||
manager.callComponent(compName, compId, inputHash);
|
||||
}"""
|
||||
|
||||
with self.assertRaisesMessage(
|
||||
Error, "Error: [Components] 'my_comp': No elements with component ID '12345' found"
|
||||
with pytest.raises(
|
||||
Error,
|
||||
match=re.escape("Error: [Components] 'my_comp': No elements with component ID '12345' found"),
|
||||
):
|
||||
await page.evaluate(test_js)
|
||||
|
||||
|
@ -399,7 +404,7 @@ class CallComponentTests(_BaseDepManagerTestCase):
|
|||
|
||||
@with_playwright
|
||||
async def test_raises_if_input_hash_not_registered(self):
|
||||
page = await self._create_page_with_dep_manager()
|
||||
page = await _create_page_with_dep_manager(self.browser) # type: ignore[attr-defined]
|
||||
|
||||
test_js: types.js = """() => {
|
||||
const manager = Components.createComponentsManager();
|
||||
|
@ -418,14 +423,17 @@ class CallComponentTests(_BaseDepManagerTestCase):
|
|||
manager.callComponent(compName, compId, inputHash);
|
||||
}"""
|
||||
|
||||
with self.assertRaisesMessage(Error, "Error: [Components] 'my_comp': Cannot find input for hash 'input-abc'"):
|
||||
with pytest.raises(
|
||||
Error,
|
||||
match=re.escape("Error: [Components] 'my_comp': Cannot find input for hash 'input-abc'"),
|
||||
):
|
||||
await page.evaluate(test_js)
|
||||
|
||||
await page.close()
|
||||
|
||||
@with_playwright
|
||||
async def test_raises_if_component_not_registered(self):
|
||||
page = await self._create_page_with_dep_manager()
|
||||
page = await _create_page_with_dep_manager(self.browser) # type: ignore[attr-defined]
|
||||
|
||||
test_js: types.js = """() => {
|
||||
const manager = Components.createComponentsManager();
|
||||
|
@ -444,7 +452,10 @@ class CallComponentTests(_BaseDepManagerTestCase):
|
|||
manager.callComponent(compName, compId, inputHash);
|
||||
}"""
|
||||
|
||||
with self.assertRaisesMessage(Error, "Error: [Components] 'my_comp': No component registered for that name"):
|
||||
with pytest.raises(
|
||||
Error,
|
||||
match=re.escape("Error: [Components] 'my_comp': No component registered for that name"),
|
||||
):
|
||||
await page.evaluate(test_js)
|
||||
|
||||
await page.close()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue