From 06d4d2543bf559a45f195932b4ac048a58712acb Mon Sep 17 00:00:00 2001 From: Maria Violante Date: Thu, 23 Jul 2026 08:24:10 -0400 Subject: [PATCH 1/5] Add vitest runner and a smoke test to confirm setup --- .gitignore | 1 + .nvmrc | 1 + CONTRIBUTING.md | 64 +- README.md | 12 + package-lock.json | 2063 ++++++++++++++++++++++++++++++++++ package.json | 14 + tests_js/setup.smoke.test.js | 10 + vitest.config.js | 8 + 8 files changed, 2170 insertions(+), 3 deletions(-) create mode 100644 .nvmrc create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 tests_js/setup.smoke.test.js create mode 100644 vitest.config.js diff --git a/.gitignore b/.gitignore index 0aa79dc..6db2477 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ form-workflows/ __pycache__/ .venv/ FEATURE_GAP_ANALYSIS.md +node_modules/ diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..2bd5a0a --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 16e2278..bd8f74e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,7 +35,7 @@ Enhancement suggestions are tracked as GitHub issues. When creating an enhanceme 2. **Make your changes** following the coding standards 3. **Add tests** for new functionality 4. **Update documentation** as needed -5. **Ensure tests pass**: `pytest` +5. **Ensure tests pass**: `pytest` (and `npm test` if you touched front-end js`) 6. **Ensure code quality**: `black .`, `flake8`, `isort .` 7. **Commit with clear messages** 8. **Push to your fork** and submit a pull request @@ -84,6 +84,28 @@ flake8 mypy django_forms_workflows ``` +### 6. (Optional) Install JS Test Tooling + +Only needed if you're changing the visual Form/Workflow Builder's client-side +JS (`django_forms_workflows/static/django_forms_workflows/js/`). Requires +Node.js 20+ / npm — an `.nvmrc` is committed at the repo root, so `nvm use` +picks the right version automatically if you have nvm installed. + +```bash +npm install +npm test # run once +npm run test:watch # re-run on change, useful while iterating +``` + +Test files live in `tests_js/`, its own top-level directory paralleling +`tests/`. Within it, one subfolder per source file under +`django_forms_workflows/static/django_forms_workflows/js/`, each holding one test +file per method/behavior under test. + +There's a `helpers/` subfolder for anything shared across that +source file's tests (e.g. `tests_js/form-builder/helpers/loadFormBuilderClass.js`, +which loads the real, un-exported class for testing — see the example below). + ## Coding Standards ### Python Style @@ -99,6 +121,16 @@ mypy django_forms_workflows - Use Django's built-in features when possible - Avoid reinventing the wheel +### JavaScript Style + +- Vanilla JS, no framework or bundler dependency — native ES modules + (` `; } @@ -987,11 +978,6 @@ class FormBuilder { You can edit the JSON directly for advanced configurations - - `; } @@ -1032,14 +1018,24 @@ class FormBuilder { You can edit the JSON directly for advanced configurations - - `; } + initializePropertyFormTabs(field) { + // Wires up the interactive bits of the Conditional Logic, Validation, + // and Dependencies tabs. + const enableConditional = document.getElementById('propEnableConditional'); + const conditionalRulesContainer = document.getElementById('conditionalRulesContainer'); + if (enableConditional && conditionalRulesContainer) { + enableConditional.addEventListener('change', (e) => { + conditionalRulesContainer.style.display = e.target.checked ? 'block' : 'none'; + }); + } + this.initializeConditionsList(field.conditional_rules?.conditions || []); + this.initializeValidationRulesList(field.validation_rules || []); + this.initializeDependenciesList(field.field_dependencies || []); + } + initializeConditionsList(conditions) { const container = document.getElementById('conditionsList'); if (!container) return; diff --git a/tests_js/form-builder/initializePropertyFormTabs.test.js b/tests_js/form-builder/initializePropertyFormTabs.test.js new file mode 100644 index 0000000..d8c006d --- /dev/null +++ b/tests_js/form-builder/initializePropertyFormTabs.test.js @@ -0,0 +1,66 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { loadFormBuilderClass } from './helpers/loadFormBuilderClass.js'; + +function createInstance(FormBuilder) { + const instance = Object.create(FormBuilder.prototype); + instance.initializeConditionsList = vi.fn(); + instance.initializeValidationRulesList = vi.fn(); + instance.initializeDependenciesList = vi.fn(); + return instance; +} + +describe('FormBuilder#initializePropertyFormTabs', () => { + beforeEach(() => { + document.body.innerHTML = ` + + + `; + }); + + it('wires the conditional-logic toggle to show/hide its section', () => { + const FormBuilder = loadFormBuilderClass(); + const instance = createInstance(FormBuilder); + const field = { conditional_rules: null, validation_rules: [], field_dependencies: [] }; + + instance.initializePropertyFormTabs(field); + + const checkbox = document.getElementById('propEnableConditional'); + const container = document.getElementById('conditionalRulesContainer'); + + checkbox.checked = true; + checkbox.dispatchEvent(new Event('change')); + expect(container.style.display).toBe('block'); + + checkbox.checked = false; + checkbox.dispatchEvent(new Event('change')); + expect(container.style.display).toBe('none'); + }); + + it("initializes all three tabs' lists with the field's current data", () => { + const FormBuilder = loadFormBuilderClass(); + const instance = createInstance(FormBuilder); + const field = { + conditional_rules: { conditions: [{ field: 'x', operator: 'equals', value: '1' }] }, + validation_rules: [{ type: 'required' }], + field_dependencies: [{ source: 'a', target: 'b' }], + }; + + instance.initializePropertyFormTabs(field); + + expect(instance.initializeConditionsList).toHaveBeenCalledWith(field.conditional_rules.conditions); + expect(instance.initializeValidationRulesList).toHaveBeenCalledWith(field.validation_rules); + expect(instance.initializeDependenciesList).toHaveBeenCalledWith(field.field_dependencies); + }); + + it('defaults to empty lists when the field has no rules/dependencies yet', () => { + const FormBuilder = loadFormBuilderClass(); + const instance = createInstance(FormBuilder); + const field = {}; + + instance.initializePropertyFormTabs(field); + + expect(instance.initializeConditionsList).toHaveBeenCalledWith([]); + expect(instance.initializeValidationRulesList).toHaveBeenCalledWith([]); + expect(instance.initializeDependenciesList).toHaveBeenCalledWith([]); + }); +}); From 477b6c51687ec70f690cbb9038f2d7c4ebcde331 Mon Sep 17 00:00:00 2001 From: Maria Violante Date: Fri, 24 Jul 2026 10:40:30 -0400 Subject: [PATCH 5/5] copilot fixes --- CONTRIBUTING.md | 4 ++-- README.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bd8f74e..7f47b10 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,7 +35,7 @@ Enhancement suggestions are tracked as GitHub issues. When creating an enhanceme 2. **Make your changes** following the coding standards 3. **Add tests** for new functionality 4. **Update documentation** as needed -5. **Ensure tests pass**: `pytest` (and `npm test` if you touched front-end js`) +5. **Ensure tests pass**: `pytest` (and `npm test` if you touched front-end js) 6. **Ensure code quality**: `black .`, `flake8`, `isort .` 7. **Commit with clear messages** 8. **Push to your fork** and submit a pull request @@ -88,7 +88,7 @@ mypy django_forms_workflows Only needed if you're changing the visual Form/Workflow Builder's client-side JS (`django_forms_workflows/static/django_forms_workflows/js/`). Requires -Node.js 20+ / npm — an `.nvmrc` is committed at the repo root, so `nvm use` +Node.js ^20.19.0 or >=22.12.0 / npm — an `.nvmrc` is committed at the repo root, so `nvm use` picks the right version automatically if you have nvm installed. ```bash diff --git a/README.md b/README.md index 179855a..1ee81cc 100644 --- a/README.md +++ b/README.md @@ -351,7 +351,7 @@ graph TB - Optional: `openpyxl` for Excel spreadsheet field support (`pip install django-forms-workflows[excel]`) - Optional: `django-auth-ldap` for LDAP/AD integration (`pip install django-forms-workflows[ldap]`) - Optional: WeasyPrint for PDF export (`pip install django-forms-workflows[pdf]`) -- Optional: Node.js 20+ / npm, to run the JS test suite (`npm test`) covering the visual Form/Workflow Builder's client-side code +- Optional: Node.js ^20.19.0 or >=22.12.0 / npm, to run the JS test suite (`npm test`) covering the visual Form/Workflow Builder's client-side code (an `.nvmrc` is committed at the repo root) ## Testing