Contributing ~~~~~~~~~~~~ djmvc is a Django MVC layer with a Bulma frontend. Most contributors work in Python; the JavaScript is thin glue over server-rendered HTML, not a separate frontend application. - :doc:`Philosophy ` - :doc:`Install ` - :doc:`Tutorial ` - :doc:`Reference ` The example project used by tests lives in :file:`src/djmvc_example/`. Development setup ================= .. code-block:: bash git clone https://github.com/jpic/djmvc.git cd djmvc pip install --pre -e ".[dev,docs]" npm ci # only needed for JavaScript unit tests **Browser tests** require Firefox and geckodriver on your ``PATH`` (CI installs both via ``browser-actions/setup-firefox`` and ``browser-actions/setup-geckodriver``). **Documentation builds** need the ``[docs]`` extra from :file:`pyproject.toml` (or :file:`docs/requirements.txt`). Running tests ============= Run the same checks as CI before opening a pull request. .. list-table:: :header-rows: 1 :widths: 20 40 40 * - Layer - Command - Notes * - Fast Python - ``pytest -m "not splinter" -n auto`` - Default ``addopts`` in :file:`pyproject.toml` enable parallel runs * - Browser (Splinter) - ``pytest -m splinter -n 0 --splinter-headless`` - Must use ``-n 0`` (serial); needs Firefox + geckodriver * - Full Python suite - ``tox`` - Runs both commands above * - JavaScript (Vitest) - ``npm test`` - happy-dom; see :file:`vitest.config.js` * - Tutorial integrity - ``pytest -m tutorial -n 0`` - Validates ``literalinclude`` paths in docs * - Doc screenshots - see :ref:`updating-screenshots` - Always ``-n 0`` Pytest configuration -------------------- Configuration lives in :file:`pyproject.toml` (``[tool.pytest.ini_options]``). Tests use the ``dmvc_example`` Django project (``DJANGO_SETTINGS_MODULE=dmvc_example.settings``). .. list-table:: Pytest markers :header-rows: 1 :widths: 25 75 * - Marker - Purpose * - ``splinter`` - Browser tests; always run with ``-n 0`` * - ``docs_screenshot`` - Captures PNGs into :file:`docs/_static/screenshots/` * - ``tutorial`` - Validates tutorial example apps referenced from docs * - ``django_db`` - Needs database access * - ``bulma`` / ``bootstrap`` - Frontend-specific (reserved) Shared fixtures and helpers --------------------------- :file:`tests/conftest.py` provides: - ``_autodiscover_routes`` (autouse) — calls ``djmvc.site.build()`` before each test - ``admin_user`` — pytest-django superuser (``admin`` / ``password``) - ``browser_login`` — Splinter login helper - ``many_users``, ``stage0_bulk_items`` — seeded data for browser tests :file:`tests/alight_helpers.py` has DAL/autocomplete helpers for Splinter tests (``wait_alight_ready``, ``type_and_select``, ``open_autocomplete``, etc.). Subset examples --------------- .. code-block:: bash pytest tests/test_stage0.py -v pytest tests/test_list_action.py -n 0 --splinter-headless npm run test:ui # optional Vitest UI Documentation ============= Docs are **Sphinx** with the Furo theme under :file:`docs/`. .. code-block:: bash make -C docs html # preview: docs/_build/html/index.html CI (``.github/workflows/docs.yml``) runs ``pytest -m tutorial -n 0`` then ``make -C docs html`` on every push and pull request. **Layout:** - :file:`docs/tutorial/` — staged tutorial RST files - :file:`docs/reference/` — API reference - :file:`docs/_static/screenshots/` — committed PNGs embedded in RST When editing tutorial code shown via ``.. literalinclude::`` in :file:`docs/**/*.rst`, keep paths valid — :file:`tests/test_tutorial_docs.py` fails if a referenced file is missing or moved. .. _updating-screenshots: Updating screenshots ==================== There are two screenshot directories. Only one is used in published docs. .. list-table:: :header-rows: 1 :widths: 35 15 50 * - Directory - Committed? - Purpose * - :file:`docs/_static/screenshots/` - Yes - PNGs embedded in Sphinx via ``.. figure:: /_static/screenshots/.png`` * - :file:`tests/screenshots/dal/`, :file:`tests/screenshots/topbar/` - Yes (debug) - Intermediate captures from DAL/topbar Splinter tests; not used in docs Regenerate all doc screenshots ------------------------------ After changing templates or navigation that appear in the docs: .. code-block:: bash pytest tests/test_docs_screenshots.py tests/test_djmvc_dal_topbar_splinter.py -n0 --splinter-headless make -C docs html Commit the updated PNGs under :file:`docs/_static/screenshots/` with your change. Helpers live in :file:`tests/doc_screenshots.py`: - ``capture(browser, name)`` — writes :file:`docs/_static/screenshots/.png` - ``prepare_browser(browser)`` — sets viewport to 1280×900 - ``DOC_SCREENSHOTS`` — registry of expected PNG names - ``assert_all_captured()`` — verifies every registered screenshot exists **Skip screenshot regeneration** for pure Python changes, doc text-only edits, or tutorial code changes with no visual impact. Adding a new doc screenshot --------------------------- #. Add ``.. figure:: /_static/screenshots/.png`` in the appropriate ``.rst`` file. #. Capture it in :file:`tests/test_docs_screenshots.py` (or an existing Splinter test) using ``from doc_screenshots import capture``. #. Add ```` to ``DOC_SCREENSHOTS`` in :file:`tests/doc_screenshots.py`. #. Run the regenerate command above and commit the PNG. JavaScript for Python developers ================================ djmvc JavaScript manages client-side state and DOM updates within server-rendered HTML. Python owns permissions, URLs, and markup structure. .. code-block:: text Django view (list_actions, unpoly_attributes) ↓ Django template (custom element tags, data-* hooks) ↓ Static ES module (custom element or up.compiler) ↓ Unpoly partial updates (up:fragment:inserted, compilers) ↓ Vitest (unit) + pytest/Splinter (integration) Where JavaScript lives ---------------------- .. list-table:: :header-rows: 1 :widths: 50 50 * - Location - Role * - :file:`src/djmvc_bulma/static/djmvc_bulma/js/` - Core UI components * - :file:`src/djmvc_dal_topbar/static/djmvc_dal_topbar/js/` - Extension components (e.g. site search) * - :file:`src/djmvc_bulma/static/djmvc_bulma/css/style.css` - Layout and component styles Scripts load from :file:`src/djmvc_bulma/templates/djmvc/base.html` as native ES modules (``type="module"``). There is **no bundler** — files ship via setuptools ``package-data`` and Django ``{% static %}``. :file:`package.json` is dev-only (Vitest + happy-dom). Two patterns ------------ Choose one when adding frontend behavior. .. list-table:: :header-rows: 1 :widths: 25 35 40 * - Pattern - When to use - Examples * - **Custom element** (``HTMLElement``) - Interactive UI tied to server-rendered children inside the tag - ````, ````, ```` * - **Unpoly compiler module** - Attach behavior when Unpoly compiles new DOM fragments - ``form-focus.js``, ``toast.js``, ``nav-config.js`` Custom element conventions -------------------------- - **No Shadow DOM** — light DOM only. Django renders children inside the tag; JS uses ``this.querySelector(...)``. - Register with a guard so the module is safe to load more than once: .. code-block:: javascript if (!customElements.get('list-action-bar')) { customElements.define('list-action-bar', ListActionBar); } - ``connectedCallback()`` wires listeners; defer init with ``queueMicrotask(() => this.init())``. - ``disconnectedCallback()`` unbinds listeners where needed. - Listen for ``up:fragment:inserted`` when the feature must survive ``[up-list]`` / ``[up-table]`` partial updates (see :file:`list-action-bar.js`). - Config via HTML attributes (``table``, ``scope``, ``target``) and ``data-*`` hooks (``data-pk``, ``data-role``, ``data-list-action``). - Export classes and pure helpers for Vitest. - Expose ``window.djmvc*`` functions when templates or Python need to call JS (e.g. ``djmvcClearListActionSelections``). Some toggles wrap light-DOM children in a ``