Skip to content

Commit 6a5371d

Browse files
authored
chore: add tests for form rendering (#33)
* chore: add tests for form rendering * fix tests for cms 3 * fix tests for cms 3 * add ajax tests * chore: add action tests * add action registry tests * add helper tests * fix tests for cms 3 * fix tests for cms 3 * add select plugin test * add more model tests * fix: delete plugin for v3 * add more action maps
1 parent 517d8ca commit 6a5371d

File tree

14 files changed

+2714
-8
lines changed

14 files changed

+2714
-8
lines changed

.github/workflows/codecov.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,6 @@ jobs:
4242
- name: Generate Report
4343
run: |
4444
coverage run ./run_tests.py
45-
- name: Generate XML report
46-
run: |
4745
coverage xml
4846
- name: Upload Coverage to Codecov
4947
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7

.github/workflows/publish-to-live-pypi.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ jobs:
1111
runs-on: ubuntu-latest
1212
steps:
1313
- uses: actions/checkout@v4
14-
- name: Set up Python 3.10
14+
- name: Set up Python 3.13
1515
uses: actions/setup-python@v5
1616
with:
17-
python-version: '3.10'
17+
python-version: '3.13'
1818

1919
- name: Install pypa/build
2020
run: >-

.pre-commit-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ repos:
1616
rev: aad66557af3b56ba6d4d69cd1b6cba87cef50cbb # frozen: v0.14.3
1717
hooks:
1818
- id: ruff
19+
args: [ --fix ]
1920
- id: ruff-format

djangocms_form_builder/helpers.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,10 @@ def add_plugin(placeholder, plugin):
125125

126126
def delete_plugin(plugin):
127127
"""CMS version save function to delete a plugin (and its descendants) from a placeholder"""
128-
return plugin.placeholder.delete_plugin(plugin)
128+
if hasattr(plugin.placeholder, "delete_plugin"): # CMS v4?
129+
return plugin.placeholder.delete_plugin(plugin)
130+
else: # CMS < v4
131+
return plugin.delete()
129132

130133

131134
def coerce_decimal(value):

run_tests.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def run():
1212
django.setup()
1313
TestRunner = get_runner(settings)
1414
test_runner = TestRunner()
15-
failures = test_runner.run_tests(["tests"])
15+
failures = test_runner.run_tests(sys.argv[1:] or ["tests"])
1616
sys.exit(bool(failures))
1717

1818

tests/requirements/base.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
django-filer
22
easy-thumbnails
33
djangocms-text
4+
djangocms-link
45
coverage
56
django-app-helper
67
django-treebeard<4.5

tests/test_action_registry.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
from django.core.exceptions import ImproperlyConfigured
2+
from django.test import SimpleTestCase
3+
4+
from djangocms_form_builder import actions as actions_module
5+
from djangocms_form_builder.actions import FormAction
6+
7+
8+
class DummyAction(FormAction):
9+
verbose_name = "Dummy action"
10+
11+
def execute(self, form, request): # pragma: no cover - trivial
12+
return None
13+
14+
15+
class NoVerboseAction(FormAction):
16+
verbose_name = None
17+
18+
def execute(self, form, request): # pragma: no cover - trivial
19+
return None
20+
21+
22+
class NotAnAction:
23+
pass
24+
25+
26+
class ActionRegistryTests(SimpleTestCase):
27+
def setUp(self):
28+
super().setUp()
29+
# Backup the original registry so we can safely modify it
30+
self._orig_registry = actions_module._action_registry.copy()
31+
32+
def tearDown(self):
33+
# Restore the registry to its original state
34+
actions_module._action_registry.clear()
35+
actions_module._action_registry.update(self._orig_registry)
36+
super().tearDown()
37+
38+
def test_register_adds_action_and_getters_work(self):
39+
action_hash = actions_module.get_hash(DummyAction)
40+
self.assertIsNone(actions_module.get_action_class(action_hash))
41+
42+
actions_module.register(DummyAction)
43+
44+
# get_action_class returns the class
45+
self.assertIs(actions_module.get_action_class(action_hash), DummyAction)
46+
# get_registered_actions contains our (hash, verbose_name) pair
47+
choices = dict(actions_module.get_registered_actions())
48+
self.assertIn(action_hash, choices)
49+
self.assertEqual(choices[action_hash], DummyAction.verbose_name)
50+
51+
def test_unregister_removes_action(self):
52+
action_hash = actions_module.get_hash(DummyAction)
53+
actions_module.register(DummyAction)
54+
self.assertIsNotNone(actions_module.get_action_class(action_hash))
55+
56+
actions_module.unregister(DummyAction)
57+
self.assertIsNone(actions_module.get_action_class(action_hash))
58+
59+
choices = dict(actions_module.get_registered_actions())
60+
self.assertNotIn(action_hash, choices)
61+
62+
def test_register_rejects_non_subclass(self):
63+
with self.assertRaises(ImproperlyConfigured):
64+
actions_module.register(NotAnAction) # type: ignore[arg-type]
65+
66+
def test_register_rejects_missing_verbose_name(self):
67+
with self.assertRaises(ImproperlyConfigured):
68+
actions_module.register(NoVerboseAction)
69+
70+
def test_register_is_idempotent(self):
71+
# capture current size
72+
size_before = len(actions_module._action_registry)
73+
actions_module.register(DummyAction)
74+
size_after_first = len(actions_module._action_registry)
75+
actions_module.register(DummyAction)
76+
size_after_second = len(actions_module._action_registry)
77+
78+
self.assertEqual(size_after_first, size_before + 1)
79+
self.assertEqual(size_after_second, size_after_first)
80+
81+
actions_module.unregister(DummyAction)
82+
self.assertEqual(len(actions_module._action_registry), size_before)
83+
84+
def test_unregister_is_safe_when_absent(self):
85+
size_before = len(actions_module._action_registry)
86+
actions_module.unregister(DummyAction) # should not raise
87+
self.assertEqual(len(actions_module._action_registry), size_before)
88+
89+
def test_get_registered_actions_empty_fallback(self):
90+
# Clear registry to simulate no actions registered
91+
actions_module._action_registry.clear()
92+
choices = actions_module.get_registered_actions()
93+
94+
# Expect a single-choice tuple with an empty options tuple
95+
self.assertIsInstance(choices, tuple)
96+
self.assertEqual(len(choices), 1)
97+
self.assertIsInstance(choices[0], tuple)
98+
self.assertEqual(len(choices[0]), 2)
99+
self.assertIsInstance(choices[0][1], tuple)
100+
self.assertEqual(choices[0][1], ())

0 commit comments

Comments
 (0)