Background
Discovered while root-causing a CI-only E2E failure on #76/#77 (a real crash: django-cotton's `Attrs` bag colliding with `RESERVED_ATTRS` on every render, fixed in 58e4065). The crash reproduced reliably in `tests/e2e/` but not in the pre-existing `tests/integration/test_cotton_integration.py`, which uses a real Django `test.Client()` against `tests/integration/cotton_app` — a setup that looks like it should exercise real cotton compilation just as much as the E2E tier does.
The gap
`tests/integration/cotton_app/settings.py`'s `INSTALLED_APPS` does not include `"django_cotton"` — only `"cf_ui.django.CfUiConfig"`:
```python
INSTALLED_APPS = [
"django.contrib.contenttypes",
"django.contrib.auth",
"django.contrib.staticfiles",
"cf_ui.django.CfUiConfig",
]
```
`cf_ui.django.CfUiConfig.ready()` deliberately does not touch template settings (see its own docstring — this is correct behavior for cf-ui, which must not clobber a consumer's own cotton wiring). Without `django_cotton` registered, its `AppConfig.ready()` never runs, so django-cotton's own loader/builtins never get added to `TEMPLATES[0]["OPTIONS"]`.
Compare to `tests/e2e/_e2e_django_settings.py`, which explicitly lists `"django_cotton"` in `INSTALLED_APPS` and documents why: "so that cf-ui cotton components render fully."
Impact
Without django-cotton's loader wired in, Django's template engine treats `<c-cf.form-field ...>` as inert literal text — it isn't a recognized tag, so DTL emits it unchanged, while ordinary `{{ name }}`/`{{ label }}` interpolations inside the (never-compiled) tag's attributes still resolve normally via standard Django variable substitution. `test_form_field_cotton_renders`'s assertions (`b'name="email"' in r.content`, `b"Email" in r.content`) are satisfied by this literal, uncompiled output just as well as by real compiled markup — so the test has been giving a false sense of coverage since it was written. This affects every test in `test_cotton_integration.py` (`form-field`, `modal`, `card`), not just form-field.
This is exactly the class of failure CLAUDE.md already documents for the unit tier ("Unit tests using `render_to_string` bypass the django-cotton compiler") — this issue is that this repo's integration tier has the same gap, unintentionally, via a missing `INSTALLED_APPS` entry rather than a deliberate `render_to_string` bypass.
Suggested approach
Add `"django_cotton"` to `tests/integration/cotton_app/settings.py`'s `INSTALLED_APPS`, then re-run `test_cotton_integration.py` to see what — if anything — actually breaks once real compilation is turned on. Given #76/#77's discovery, at least `test_form_field_cotton_renders` may need adjustment (or may already pass cleanly given 58e4065's fix). Treat any newly-surfaced failures as real bugs this integration tier was supposed to catch all along, not as regressions to work around.
Related: #76, #77
Background
Discovered while root-causing a CI-only E2E failure on #76/#77 (a real crash: django-cotton's `Attrs` bag colliding with `RESERVED_ATTRS` on every render, fixed in 58e4065). The crash reproduced reliably in `tests/e2e/` but not in the pre-existing `tests/integration/test_cotton_integration.py`, which uses a real Django `test.Client()` against `tests/integration/cotton_app` — a setup that looks like it should exercise real cotton compilation just as much as the E2E tier does.
The gap
`tests/integration/cotton_app/settings.py`'s `INSTALLED_APPS` does not include `"django_cotton"` — only `"cf_ui.django.CfUiConfig"`:
```python
INSTALLED_APPS = [
"django.contrib.contenttypes",
"django.contrib.auth",
"django.contrib.staticfiles",
"cf_ui.django.CfUiConfig",
]
```
`cf_ui.django.CfUiConfig.ready()` deliberately does not touch template settings (see its own docstring — this is correct behavior for cf-ui, which must not clobber a consumer's own cotton wiring). Without `django_cotton` registered, its `AppConfig.ready()` never runs, so django-cotton's own loader/builtins never get added to `TEMPLATES[0]["OPTIONS"]`.
Compare to `tests/e2e/_e2e_django_settings.py`, which explicitly lists `"django_cotton"` in `INSTALLED_APPS` and documents why: "so that cf-ui cotton components render fully."
Impact
Without django-cotton's loader wired in, Django's template engine treats `<c-cf.form-field ...>` as inert literal text — it isn't a recognized tag, so DTL emits it unchanged, while ordinary `{{ name }}`/`{{ label }}` interpolations inside the (never-compiled) tag's attributes still resolve normally via standard Django variable substitution. `test_form_field_cotton_renders`'s assertions (`b'name="email"' in r.content`, `b"Email" in r.content`) are satisfied by this literal, uncompiled output just as well as by real compiled markup — so the test has been giving a false sense of coverage since it was written. This affects every test in `test_cotton_integration.py` (`form-field`, `modal`, `card`), not just form-field.
This is exactly the class of failure CLAUDE.md already documents for the unit tier ("Unit tests using `render_to_string` bypass the django-cotton compiler") — this issue is that this repo's integration tier has the same gap, unintentionally, via a missing `INSTALLED_APPS` entry rather than a deliberate `render_to_string` bypass.
Suggested approach
Add `"django_cotton"` to `tests/integration/cotton_app/settings.py`'s `INSTALLED_APPS`, then re-run `test_cotton_integration.py` to see what — if anything — actually breaks once real compilation is turned on. Given #76/#77's discovery, at least `test_form_field_cotton_renders` may need adjustment (or may already pass cleanly given 58e4065's fix). Treat any newly-surfaced failures as real bugs this integration tier was supposed to catch all along, not as regressions to work around.
Related: #76, #77