diff --git a/packages/core/src/Actions/Channels/DeleteChannel.php b/packages/core/src/Actions/Channels/DeleteChannel.php index 0089f30593..2c6e3505ab 100644 --- a/packages/core/src/Actions/Channels/DeleteChannel.php +++ b/packages/core/src/Actions/Channels/DeleteChannel.php @@ -9,11 +9,16 @@ /** * Delete a channel. Channels with order history are kept — mark them * Inactive in the admin instead — so historical orders keep their context. + * The default channel is also kept: make another channel the default first. */ class DeleteChannel implements DeletesChannel { public function execute(Channel $channel): void { + if ($channel->default) { + throw new ChannelActionException('Cannot delete the default channel. Make another channel the default first.'); + } + if ($channel->hasOrderHistory()) { throw new ChannelActionException('Cannot delete a channel with order history.'); } diff --git a/packages/core/src/Actions/Channels/UpdateChannel.php b/packages/core/src/Actions/Channels/UpdateChannel.php index fa944ac91b..2118aa70ef 100644 --- a/packages/core/src/Actions/Channels/UpdateChannel.php +++ b/packages/core/src/Actions/Channels/UpdateChannel.php @@ -3,10 +3,13 @@ namespace Lunar\Core\Actions\Channels; use Lunar\Core\Contracts\Actions\Channels\UpdatesChannel; +use Lunar\Core\Exceptions\ChannelActionException; use Lunar\Core\Models\Channel; /** * Update a channel, ensuring at most one channel is ever marked default. + * The default flag moves by promoting another channel, never by unsetting — + * so a store with channels always has a default. */ class UpdateChannel implements UpdatesChannel { @@ -15,6 +18,10 @@ class UpdateChannel implements UpdatesChannel */ public function execute(Channel $channel, array $attributes): Channel { + if ($channel->default && array_key_exists('default', $attributes) && ! $attributes['default']) { + throw new ChannelActionException('Cannot unset the default channel. Make another channel the default instead.'); + } + $attributes['handle'] ??= $attributes['name'] ?? $channel->handle; if ($attributes['default'] ?? false) { diff --git a/packages/panel/resources/js/components/Toggle.test.ts b/packages/panel/resources/js/components/Toggle.test.ts new file mode 100644 index 0000000000..6f2bc967af --- /dev/null +++ b/packages/panel/resources/js/components/Toggle.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, it } from 'vitest'; +import { mount } from '@vue/test-utils'; +import Toggle from './Toggle.vue'; + +describe('Toggle', () => { + it('emits toggle when the switch is clicked', async () => { + const wrapper = mount(Toggle, { props: { on: false } }); + + await wrapper.find('button').trigger('click'); + + expect(wrapper.emitted('toggle')).toHaveLength(1); + }); + + it('emits toggle when clicked while on', async () => { + const wrapper = mount(Toggle, { props: { on: true } }); + + await wrapper.find('button').trigger('click'); + + expect(wrapper.emitted('toggle')).toHaveLength(1); + }); + + it('does not emit toggle when disabled', async () => { + const wrapper = mount(Toggle, { props: { on: true, disabled: true } }); + + await wrapper.find('button').trigger('click'); + + expect(wrapper.emitted('toggle')).toBeUndefined(); + }); + + it('reflects the on prop in the switch state', () => { + expect(mount(Toggle, { props: { on: true } }).find('button').attributes('data-state')).toBe('checked'); + expect(mount(Toggle, { props: { on: false } }).find('button').attributes('data-state')).toBe('unchecked'); + }); +}); diff --git a/packages/panel/resources/js/components/Toggle.vue b/packages/panel/resources/js/components/Toggle.vue index c82c229f09..63d89af542 100644 --- a/packages/panel/resources/js/components/Toggle.vue +++ b/packages/panel/resources/js/components/Toggle.vue @@ -1,18 +1,20 @@