mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
fix(modal): keep confirmation checkbox state in one place
The dialog checkboxes used both Alpine selectedActions and wire:model. After the dialog closed, the two could differ, so a click did not change the checkbox. Alpine now holds the state, and the dialog sends it to Livewire on submit. Closes #12338 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
4559d21b4c
commit
b17c83d688
@@ -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)
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<x-forms.checkbox fullWidth :label="$checkbox['label']" :id="$checkbox['id']"
|
||||
x-on:change="toggleAction('{{ $checkbox['id'] }}')" :checked="$this->{$checkbox['id']}"
|
||||
:domValue="$checkbox['id']" autocomplete="off"
|
||||
x-on:change="setAction('{{ $checkbox['id'] }}', $event.target.checked)" :checked="$this->{$checkbox['id']}"
|
||||
x-bind:checked="selectedActions.includes('{{ $checkbox['id'] }}')" />
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
use App\Actions\Application\StopApplication;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
use Lorisleiva\Actions\Decorators\JobDecorator;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
config()->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);
|
||||
});
|
||||
Reference in New Issue
Block a user