diff --git a/packages/frontend/src/lib/forms/compose/QuadletComposeForm.spec.ts b/packages/frontend/src/lib/forms/compose/QuadletComposeForm.spec.ts
index f058c310..a89ff7cb 100644
--- a/packages/frontend/src/lib/forms/compose/QuadletComposeForm.spec.ts
+++ b/packages/frontend/src/lib/forms/compose/QuadletComposeForm.spec.ts
@@ -29,6 +29,10 @@ import { readable } from 'svelte/store';
import type { ProviderApi } from '/@shared/src/apis/provide-api';
import type { PodletApi } from '/@shared/src/apis/podlet-api';
import type { QuadletApi } from '/@shared/src/apis/quadlet-api';
+import { router } from 'tinro';
+
+// mock router lib
+vi.mock(import('tinro'));
// mock clients
vi.mock(import('/@/api/client'), () => ({
@@ -67,6 +71,17 @@ beforeEach(() => {
});
describe('step select', () => {
+ test('expect container engine to be automatically selected', async () => {
+ render(QuadletComposeForm, {
+ providerId: undefined,
+ connection: undefined,
+ loading: false,
+ });
+
+ expect(router.location.query.set).toHaveBeenCalledWith('providerId', WSL_PROVIDER_DETAILED_INFO.providerId);
+ expect(router.location.query.set).toHaveBeenCalledWith('connection', WSL_PROVIDER_DETAILED_INFO.name);
+ });
+
test('file provided as parameter should be displayed', async () => {
const { getByRole } = render(QuadletComposeForm, {
filepath: FILEPATH_MOCK,
diff --git a/packages/frontend/src/lib/forms/compose/QuadletComposeForm.svelte b/packages/frontend/src/lib/forms/compose/QuadletComposeForm.svelte
index d80763f9..ca5d40c5 100644
--- a/packages/frontend/src/lib/forms/compose/QuadletComposeForm.svelte
+++ b/packages/frontend/src/lib/forms/compose/QuadletComposeForm.svelte
@@ -29,6 +29,12 @@ let selectedContainerProviderConnection: ProviderContainerConnectionDetailedInfo
$providerConnectionsInfo.find(provider => provider.providerId === providerId && provider.name === connection),
);
+$effect(() => {
+ if (!selectedContainerProviderConnection && $providerConnectionsInfo.length > 0) {
+ onContainerProviderConnectionChange($providerConnectionsInfo[0]);
+ }
+});
+
const DEFAULT_KUBE_QUADLET = `
[Unit]
Description=A kubernetes yaml based service
diff --git a/packages/frontend/src/lib/forms/quadlet/QuadletGenerateForm.spec.ts b/packages/frontend/src/lib/forms/quadlet/QuadletGenerateForm.spec.ts
index 53532398..34f7c06b 100644
--- a/packages/frontend/src/lib/forms/quadlet/QuadletGenerateForm.spec.ts
+++ b/packages/frontend/src/lib/forms/quadlet/QuadletGenerateForm.spec.ts
@@ -30,6 +30,10 @@ import type { Component, ComponentProps } from 'svelte';
import type { ContainerApi } from '/@shared/src/apis/container-api';
import type { ProviderApi } from '/@shared/src/apis/provide-api';
import type { PodletApi } from '/@shared/src/apis/podlet-api';
+import { router } from 'tinro';
+
+// mock router lib
+vi.mock(import('tinro'));
// mock clients
vi.mock(import('/@/api/client'), () => ({
@@ -67,6 +71,18 @@ beforeEach(() => {
});
describe('Step options', () => {
+ test('expect container engine to be automatically selected', async () => {
+ render(QuadletGenerateForm, {
+ providerId: undefined,
+ connection: undefined,
+ loading: false,
+ close: vi.fn(),
+ });
+
+ expect(router.location.query.set).toHaveBeenCalledWith('providerId', WSL_PROVIDER_DETAILED_INFO.providerId);
+ expect(router.location.query.set).toHaveBeenCalledWith('connection', WSL_PROVIDER_DETAILED_INFO.name);
+ });
+
test('expect cancel to call close', async () => {
const closeMock = vi.fn();
const { getByRole } = render(QuadletGenerateForm, {
diff --git a/packages/frontend/src/lib/forms/quadlet/QuadletGenerateForm.svelte b/packages/frontend/src/lib/forms/quadlet/QuadletGenerateForm.svelte
index 7f040a6f..7c5942bc 100644
--- a/packages/frontend/src/lib/forms/quadlet/QuadletGenerateForm.svelte
+++ b/packages/frontend/src/lib/forms/quadlet/QuadletGenerateForm.svelte
@@ -44,6 +44,12 @@ let selectedContainerProviderConnection: ProviderContainerConnectionDetailedInfo
$providerConnectionsInfo.find(provider => provider.providerId === providerId && provider.name === connection),
);
+$effect(() => {
+ if (!selectedContainerProviderConnection && $providerConnectionsInfo.length > 0) {
+ onContainerProviderConnectionChange($providerConnectionsInfo[0]);
+ }
+});
+
function onQuadletTypeChange(value: string): void {
router.location.query.set('quadletType', value);
router.location.query.delete(RESOURCE_ID_QUERY); // delete the key
@@ -174,6 +180,7 @@ function resetGenerate(): void {
disabled={loading}
onChange={onContainerProviderConnectionChange}
value={selectedContainerProviderConnection}
+ clearable={false}
containerProviderConnections={$providerConnectionsInfo} />
{#if selectedContainerProviderConnection && selectedContainerProviderConnection.status !== 'started'}
diff --git a/packages/frontend/src/lib/select/ContainerProviderConnectionSelect.spec.ts b/packages/frontend/src/lib/select/ContainerProviderConnectionSelect.spec.ts
index 712ff618..8885f0b8 100644
--- a/packages/frontend/src/lib/select/ContainerProviderConnectionSelect.spec.ts
+++ b/packages/frontend/src/lib/select/ContainerProviderConnectionSelect.spec.ts
@@ -17,7 +17,7 @@
***********************************************************************/
import '@testing-library/jest-dom/vitest';
-import { beforeEach, expect, test, vi } from 'vitest';
+import { beforeEach, expect, test, vi, describe } from 'vitest';
import { render, within } from '@testing-library/svelte';
import ContainerProviderConnectionSelect from '/@/lib/select/ContainerProviderConnectionSelect.svelte';
import { VMType } from '/@shared/src/utils/vm-types';
@@ -71,3 +71,28 @@ test('default value should be visible', async () => {
const select = within(container).getByText(qemuConnection.name);
expect(select).toBeDefined();
});
+
+describe('clear button', () => {
+ test('clear button should be visible by default', async () => {
+ const { container } = render(ContainerProviderConnectionSelect, {
+ value: qemuConnection,
+ containerProviderConnections: [wslConnection, qemuConnection],
+ });
+
+ // find clear HTMLElement
+ const clear = container.querySelector('button[class~="clear-select"]');
+ expect(clear).toBeDefined();
+ });
+
+ test('clearable prop should be propagated to Select component', async () => {
+ const { container } = render(ContainerProviderConnectionSelect, {
+ value: qemuConnection,
+ containerProviderConnections: [wslConnection, qemuConnection],
+ clearable: false,
+ });
+
+ // find clear HTMLElement
+ const clear = container.querySelector('button[class~="clear-select"]');
+ expect(clear).toBeNull();
+ });
+});
diff --git a/packages/frontend/src/lib/select/ContainerProviderConnectionSelect.svelte b/packages/frontend/src/lib/select/ContainerProviderConnectionSelect.svelte
index 49ea5036..dcd2f182 100644
--- a/packages/frontend/src/lib/select/ContainerProviderConnectionSelect.svelte
+++ b/packages/frontend/src/lib/select/ContainerProviderConnectionSelect.svelte
@@ -8,9 +8,10 @@ interface Props {
onChange?: (value: ProviderContainerConnectionDetailedInfo | undefined) => void;
containerProviderConnections: ProviderContainerConnectionDetailedInfo[];
disabled?: boolean;
+ clearable?: boolean;
}
-let { value = $bindable(), containerProviderConnections, onChange, disabled }: Props = $props();
+let { value = $bindable(), clearable = true, containerProviderConnections, onChange, disabled }: Props = $props();
/**
* Handy mechanism to provide the mandatory property `label` and `value` to the Select component
@@ -47,6 +48,7 @@ function getProviderStatusColor(item: ProviderContainerConnectionDetailedInfo):
disabled={disabled}
value={selected}
onchange={handleOnChange}
+ clearable={clearable}
placeholder="Select container provider to use"
items={containerProviderConnections.map(containerProviderConnection => ({
...containerProviderConnection,
diff --git a/packages/frontend/src/lib/select/Select.spec.ts b/packages/frontend/src/lib/select/Select.spec.ts
index 24925fcf..553f7621 100644
--- a/packages/frontend/src/lib/select/Select.spec.ts
+++ b/packages/frontend/src/lib/select/Select.spec.ts
@@ -1,5 +1,5 @@
/**********************************************************************
- * Copyright (C) 2024 Red Hat, Inc.
+ * Copyright (C) 2024-2025 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,7 @@
***********************************************************************/
import '@testing-library/jest-dom/vitest';
-import { beforeEach, vi, test, expect } from 'vitest';
+import { beforeEach, vi, test, expect, describe } from 'vitest';
import { render, fireEvent, within } from '@testing-library/svelte';
import Select from '/@/lib/select/Select.svelte';
@@ -122,37 +122,60 @@ test('selecting value should call onchange callback', async () => {
});
});
-test('clearing value should call onchange callback with undefined', async () => {
- const onChangeMock = vi.fn();
- const { container } = render(Select, {
- label: 'Select Item',
- items: [
- {
- label: 'Dummy Item 1',
- value: 'item-1',
- },
- {
+describe('clear button', () => {
+ test('clearing value should call onchange callback with undefined', async () => {
+ const onChangeMock = vi.fn();
+ const { container } = render(Select, {
+ label: 'Select Item',
+ items: [
+ {
+ label: 'Dummy Item 1',
+ value: 'item-1',
+ },
+ {
+ label: 'Dummy Item 2',
+ value: 'item-2',
+ },
+ ],
+ value: {
label: 'Dummy Item 2',
value: 'item-2',
},
- ],
- value: {
- label: 'Dummy Item 2',
- value: 'item-2',
- },
- onchange: onChangeMock,
- });
+ onchange: onChangeMock,
+ });
- // get clear HTMLElement
- const clear = container.querySelector('button[class~="clear-select"]');
- // ensure we have two options
- expect(clear).not.toBeNull();
- if (!clear) throw new Error('clear is null');
+ // get clear HTMLElement
+ const clear = container.querySelector('button[class~="clear-select"]');
+ // ensure we have two options
+ expect(clear).not.toBeNull();
+ if (!clear) throw new Error('clear is null');
- await fireEvent.click(clear);
+ await fireEvent.click(clear);
- await vi.waitFor(() => {
- expect(onChangeMock).toHaveBeenCalledWith(undefined);
- expect(onChangeMock).toHaveBeenCalledOnce();
+ await vi.waitFor(() => {
+ expect(onChangeMock).toHaveBeenCalledWith(undefined);
+ expect(onChangeMock).toHaveBeenCalledOnce();
+ });
+ });
+
+ test('clearable props should be respected', async () => {
+ const { container } = render(Select, {
+ label: 'Select Item',
+ items: [
+ {
+ label: 'Dummy Item 1',
+ value: 'item-1',
+ },
+ ],
+ value: {
+ label: 'Dummy Item 1',
+ value: 'item-1',
+ },
+ clearable: false,
+ });
+
+ // find clear HTMLElement
+ const clear = container.querySelector('button[class~="clear-select"]');
+ expect(clear).toBeNull();
});
});
diff --git a/packages/frontend/src/lib/select/Select.svelte b/packages/frontend/src/lib/select/Select.svelte
index d87e5b44..e314f5fd 100644
--- a/packages/frontend/src/lib/select/Select.svelte
+++ b/packages/frontend/src/lib/select/Select.svelte
@@ -10,6 +10,7 @@ export let placeholder: string | undefined = undefined;
export let label: string | undefined = undefined;
export let name: string | undefined = undefined;
export let onchange: ((value: T | undefined) => void) | undefined = undefined;
+export let clearable: boolean = true;
function handleOnChange(e: CustomEvent): void {
value = e.detail;
@@ -52,6 +53,7 @@ function handleOnClear(): void {
--height="32px"
--max-height="32px"
placeholder={placeholder}
+ clearable={clearable}
class="!bg-[var(--pd-content-bg)] !text-[var(--pd-content-card-text)]"
items={items}
showChevron={!disabled}>
diff --git a/tests/playwright/src/quadlet-extension.spec.ts b/tests/playwright/src/quadlet-extension.spec.ts
index 045bfd3f..fff64749 100644
--- a/tests/playwright/src/quadlet-extension.spec.ts
+++ b/tests/playwright/src/quadlet-extension.spec.ts
@@ -137,18 +137,6 @@ test.describe.serial(`Podman Quadlet extension installation and verification`, {
await playExpect(generateForm.cancelButton).toBeEnabled();
await playExpect(generateForm.generateButton).toBeDisabled(); // default should be disabled
- // open the select dropdown
- const podmanProviders = await generateForm.containerEngineSelect.getOptions();
- playExpect(podmanProviders.length).toBeGreaterThan(0);
-
- const sorted = podmanProviders.find(provider => provider.toLowerCase().includes('podman'));
- if (!sorted) throw new Error('cannot found podman provider');
-
- // Value can be `podman-machine-default (WSL)`
- const machine = sorted.split(' ')[0];
- console.log(`Trying to use provider ${machine}`);
- await generateForm.containerEngineSelect.set(machine);
-
// wait for loading to be finished
await playExpect
.poll(async () => await generateForm.isLoading(), {