diff --git a/client/django-formset/StepperCollection.ts b/client/django-formset/StepperCollection.ts index c3aa75ee..bfbc47ea 100644 --- a/client/django-formset/StepperCollection.ts +++ b/client/django-formset/StepperCollection.ts @@ -20,6 +20,7 @@ class StepperStep { this.path = listItem.getAttribute('prefix')?.split('.') ?? []; this.induceActivate = this.evalInducer(listItem, (...args: any[]) => this.activateStep(...args)); this.listItem.querySelector('.stepper-head')?.addEventListener('click', this.activateVisited); + formCollection.addEventListener('change', this.collectionChanged); } private evalInducer(element: HTMLElement, inducer: Function) : Function { @@ -59,6 +60,20 @@ class StepperStep { } }; + private collectionChanged = (event: Event) => { + let firstInvalidStep: StepperStep|null = null; + for (const step of this.collection.steps) { + if (step.visited) { + if (firstInvalidStep) { + step.visited = false; + step.listItem.classList.remove('visited'); + } else if (Object.values(step.formCollection.querySelectorAll('form')).some(form => !form.checkValidity())) { + firstInvalidStep = step; + } + } + } + }; + public activateStep(...args: any[]) { this.collection.steps.forEach(step => { step.formCollection.ariaCurrent = step.listItem.ariaCurrent = step === this ? 'step' : null; diff --git a/testapp/tests/test_e2e_stepper.py b/testapp/tests/test_e2e_stepper.py index 7918bf09..15a426dc 100644 --- a/testapp/tests/test_e2e_stepper.py +++ b/testapp/tests/test_e2e_stepper.py @@ -203,3 +203,41 @@ def test_steps_already_visited(page, viewname): expect(collections[1]).to_be_visible() expect(collections[2]).not_to_have_attribute('aria-current', r'.*') expect(collections[2]).not_to_be_visible() + + +@pytest.mark.urls(__name__) +@pytest.mark.parametrize('viewname', ['prefilled_stepper']) +def test_steps_partially_valid_then_invalid(page, viewname): + stepper_collection = page.locator('django-stepper-collection') + step_items = stepper_collection.locator('ul.stepper-horizontal > li.stepper-step').all() + collections = stepper_collection.locator('django-form-collection').all() + expect(step_items[0]).to_have_class('stepper-step visited') + expect(step_items[1]).not_to_have_attribute('aria-current', r'.*') + expect(step_items[1]).to_have_class('stepper-step visited') + expect(step_items[2]).not_to_have_attribute('aria-current', r'.*') + expect(step_items[2]).to_have_class('stepper-step') + expect(collections[0]).to_have_attribute('aria-current', 'step') + expect(collections[0]).to_be_visible() + step_items[1].locator('.stepper-head').click() + expect(collections[1]).to_be_visible() + collections[1].locator('input[name="street"]').fill("123 Main St") + collections[1].locator('input[name="postal_code"]').fill("12345") + collections[1].locator('input[name="city"]').fill("Springfield") + collections[1].locator('button[name="next"]').click() + expect(step_items[0]).to_have_class('stepper-step visited') + expect(step_items[1]).to_have_class('stepper-step visited') + expect(step_items[2]).to_have_class('stepper-step visited') + expect(collections[2]).to_be_visible() + + # return to first step and make the first step invalid + step_items[0].locator('.stepper-head').click() + expect(collections[0]).to_be_visible() + collections[0].locator('input[name="last_name"]').fill("") + + # then the second and third step should be inactive and impossible to reach + step_items[1].locator('.stepper-head').click() + expect(collections[1]).not_to_be_visible() + expect(collections[0]).to_be_visible() + expect(step_items[0]).to_have_class('stepper-step visited') + expect(step_items[1]).to_have_class('stepper-step') + expect(step_items[2]).to_have_class('stepper-step')