diff --git a/resources/views/components/modal-confirmation.blade.php b/resources/views/components/modal-confirmation.blade.php index b59d4c39f1..d0d65d2d37 100644 --- a/resources/views/components/modal-confirmation.blade.php +++ b/resources/views/components/modal-confirmation.blade.php @@ -68,6 +68,7 @@ dispatchAction: @js($dispatchAction), submitting: false, passwordError: '', + checkboxIds: @js(collect($checkboxes)->pluck('id')->values()->all()), selectedActions: @js(collect($checkboxes)->pluck('id')->filter(fn($id) => $this->$id)->values()->all()), dispatchEvent: @js($dispatchEvent), dispatchEventType: @js($dispatchEventType), @@ -103,6 +104,9 @@ return Promise.resolve(this.passwordError); } } + // The checkboxes are not bound with wire:model, so send their state with the call. + // Methods that read the properties (for example stop() with $docker_cleanup) get what the user saw. + this.checkboxIds.forEach(id => $wire.$set(id, this.selectedActions.includes(id), false)); if (this.dispatchAction) { $wire.dispatch(this.submitAction); return Promise.resolve(true); @@ -127,11 +131,9 @@ } }); }, - toggleAction(id) { - const index = this.selectedActions.indexOf(id); - if (index > -1) { - this.selectedActions.splice(index, 1); - } else { + setAction(id, checked) { + this.selectedActions = this.selectedActions.filter(selectedId => selectedId !== id); + if (checked) { this.selectedActions.push(id); } } @@ -245,7 +247,8 @@ @foreach ($checkboxes as $index => $checkbox)
@endforeach diff --git a/tests/v4/Browser/ModalConfirmationCheckboxTest.php b/tests/v4/Browser/ModalConfirmationCheckboxTest.php new file mode 100644 index 0000000000..fb988a5c64 --- /dev/null +++ b/tests/v4/Browser/ModalConfirmationCheckboxTest.php @@ -0,0 +1,80 @@ +set('app.maintenance.store', 'array'); + + $this->stack = seedBrowserResourceStack(); + $this->application = createBrowserApplication($this->stack, [ + 'uuid' => 'app-browser-stop-modal', + 'name' => 'Stop Modal App', + 'status' => 'running:healthy', + ]); + $this->url = applicationConfigurationUrl($this->stack['project'], $this->stack['environment'], $this->application); +}); + +const OPEN_STOP_DIALOG = <<<'JS' + () => { + document.getElementById('application-mobile-stop-trigger').click(); + + return true; + } + JS; + +const CLOSE_DIALOG = <<<'JS' + () => { + window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); + + return true; + } + JS; + +const CLEANUP_CHECKBOX = 'input[id^="docker_cleanup-"]'; + +it('toggles the checkbox even when its DOM state differs from the selected actions', function () { + loginAndSkipBoarding(); + + visit($this->url) + ->assertSee('Stop Modal App') + ->assertScript(OPEN_STOP_DIALOG) + ->wait(0.5) + ->assertScript('() => { const el = document.querySelector(\''.CLEANUP_CHECKBOX.'\'); el.checked = false; return Alpine.$data(el).selectedActions.includes("docker_cleanup"); }') + ->click(CLEANUP_CHECKBOX) + ->wait(0.3) + ->assertScript('() => document.querySelector(\''.CLEANUP_CHECKBOX.'\').checked', true) + ->click(CLEANUP_CHECKBOX) + ->wait(0.3) + ->assertScript('() => document.querySelector(\''.CLEANUP_CHECKBOX.'\').checked', false) + ->screenshot(filename: 'modal-confirmation-checkbox-toggle'); +}); + +it('stops with the cleanup option that the dialog shows after it was closed and opened again', function () { + Queue::fake(); + loginAndSkipBoarding(); + + visit($this->url) + ->assertSee('Stop Modal App') + ->assertScript(OPEN_STOP_DIALOG) + ->wait(0.5) + ->click(CLEANUP_CHECKBOX) + ->wait(0.3) + ->assertScript(CLOSE_DIALOG) + ->wait(1) + ->assertScript(OPEN_STOP_DIALOG) + ->wait(0.5) + ->assertScript('() => document.querySelector(\''.CLEANUP_CHECKBOX.'\').checked', true) + ->click('Continue') + ->wait(0.3) + ->click('Confirm') + ->wait(1.5) + ->screenshot(filename: 'modal-confirmation-checkbox-stop'); + + Queue::assertPushed(JobDecorator::class, fn (JobDecorator $job) => $job->getAction() instanceof StopApplication + && $job->getParameters()[2] === true); +});