diff --git a/packages/renderer/src/lib/ContainerList.spec.ts b/packages/renderer/src/lib/ContainerList.spec.ts index a77a0a1504c47..ff877dee6ae71 100644 --- a/packages/renderer/src/lib/ContainerList.spec.ts +++ b/packages/renderer/src/lib/ContainerList.spec.ts @@ -334,3 +334,50 @@ test('Try to delete a pod without deleting container', async () => { // and the standalone container has not been deleted expect(deleteContainerMock).not.toHaveBeenCalled(); }); + +test('Expect filter empty screen', async () => { + getProviderInfosMock.mockResolvedValue([ + { + name: 'podman', + status: 'started', + internalId: 'podman-internal-id', + containerConnections: [ + { + name: 'podman-machine-default', + status: 'started', + }, + ], + }, + ]); + + const singleContainer = { + Id: 'sha256:1234567890123', + Image: 'sha256:123', + Names: ['foo'], + Status: 'Running', + engineId: 'podman', + engineName: 'podman', + }; + + // one single container + const mockedContainers = [singleContainer]; + + listContainersMock.mockResolvedValue(mockedContainers); + + window.dispatchEvent(new CustomEvent('extensions-already-started')); + window.dispatchEvent(new CustomEvent('provider-lifecycle-change')); + window.dispatchEvent(new CustomEvent('tray:update-provider')); + + // wait store are populated + while (get(containersInfos).length === 0) { + await new Promise(resolve => setTimeout(resolve, 500)); + } + + while (get(providerInfos).length === 0) { + await new Promise(resolve => setTimeout(resolve, 500)); + } + await waitRender({ searchTerm: 'No match' }); + + const filterButton = screen.getByRole('button', { name: 'Clear filter' }); + expect(filterButton).toBeInTheDocument(); +}); diff --git a/packages/renderer/src/lib/ContainerList.svelte b/packages/renderer/src/lib/ContainerList.svelte index c57c609999079..6d8068b352825 100644 --- a/packages/renderer/src/lib/ContainerList.svelte +++ b/packages/renderer/src/lib/ContainerList.svelte @@ -11,7 +11,9 @@ import { router } from 'tinro'; import { ContainerGroupInfoTypeUI, type ContainerGroupInfoUI, type ContainerInfoUI } from './container/ContainerInfoUI'; import ContainerActions from './container/ContainerActions.svelte'; import PodActions from './pod/PodActions.svelte'; +import ContainerIcon from './images/ContainerIcon.svelte'; import ContainerEmptyScreen from './container/ContainerEmptyScreen.svelte'; +import FilteredEmptyScreen from './ui/FilteredEmptyScreen.svelte'; import Modal from './dialogs/Modal.svelte'; import { ContainerUtils } from './container/container-utils'; import { providerInfos } from '../stores/providers'; @@ -641,7 +643,11 @@ function errorCallback(container: ContainerInfoUI, errorMessage: string): void { {#if providerConnections.length === 0} {:else if $filtered.length === 0} - + {#if searchTerm} + + {:else} + + {/if} {/if} diff --git a/packages/renderer/src/lib/ImagesList.svelte b/packages/renderer/src/lib/ImagesList.svelte index e51a7ac15234f..7f36c2638cddd 100644 --- a/packages/renderer/src/lib/ImagesList.svelte +++ b/packages/renderer/src/lib/ImagesList.svelte @@ -2,6 +2,7 @@ import { filtered, searchPattern, imagesInfos } from '../stores/images'; import { onDestroy, onMount } from 'svelte'; import ImageEmptyScreen from './image/ImageEmptyScreen.svelte'; +import FilteredEmptyScreen from './ui/FilteredEmptyScreen.svelte'; import { router } from 'tinro'; import type { ImageInfoUI } from './image/ImageInfoUI'; @@ -27,7 +28,7 @@ import Checkbox from './ui/Checkbox.svelte'; import Button from './ui/Button.svelte'; import { faArrowCircleDown, faCube, faTrash } from '@fortawesome/free-solid-svg-icons'; -let searchTerm = ''; +export let searchTerm = ''; $: searchPattern.set(searchTerm); let images: ImageInfoUI[] = []; @@ -328,7 +329,11 @@ function computeInterval(): number { {#if providerConnections.length === 0} {:else if $filtered.length === 0} - + {#if searchTerm} + + {:else} + + {/if} {/if} {#if pushImageModal && pushImageModalImageInfo} diff --git a/packages/renderer/src/lib/ImagestList.spec.ts b/packages/renderer/src/lib/ImagestList.spec.ts index ef7bda99400d3..e3bcc41486655 100644 --- a/packages/renderer/src/lib/ImagestList.spec.ts +++ b/packages/renderer/src/lib/ImagestList.spec.ts @@ -135,3 +135,48 @@ test('Expect images being ordered by newest first', async () => { expect(fedoraRecent.compareDocumentPosition(veryOld)).toBe(4); expect(fedoraOld.compareDocumentPosition(veryOld)).toBe(4); }); + +test('Expect filter empty screen', async () => { + getProviderInfosMock.mockResolvedValue([ + { + name: 'podman', + status: 'started', + internalId: 'podman-internal-id', + containerConnections: [ + { + name: 'podman-machine-default', + status: 'started', + }, + ], + }, + ]); + + listImagesMock.mockResolvedValue([ + { + Id: 'sha256:1234567890123', + RepoTags: ['fedora:old'], + Created: 1644009612, + Size: 123, + Status: 'Running', + engineId: 'podman', + engineName: 'podman', + }, + ]); + + window.dispatchEvent(new CustomEvent('extensions-already-started')); + window.dispatchEvent(new CustomEvent('provider-lifecycle-change')); + window.dispatchEvent(new CustomEvent('image-build')); + + // wait store are populated + while (get(imagesInfos).length === 0) { + await new Promise(resolve => setTimeout(resolve, 500)); + } + while (get(providerInfos).length === 0) { + await new Promise(resolve => setTimeout(resolve, 500)); + } + + await waitRender({ searchTerm: 'No match' }); + + const filterButton = screen.getByRole('button', { name: 'Clear filter' }); + expect(filterButton).toBeInTheDocument(); +}); diff --git a/packages/renderer/src/lib/pod/PodsList.spec.ts b/packages/renderer/src/lib/pod/PodsList.spec.ts index 2688960ce5420..948370e1adaf1 100644 --- a/packages/renderer/src/lib/pod/PodsList.spec.ts +++ b/packages/renderer/src/lib/pod/PodsList.spec.ts @@ -231,3 +231,23 @@ test('Expect 2 kubernetes pods being displayed', async () => { const pod2Details = screen.getByRole('cell', { name: 'kubepod2 e8129c57 0 container k8s context2 tooltip' }); expect(pod2Details).toBeInTheDocument(); }); + +test('Expect filter empty screen', async () => { + getProvidersInfoMock.mockResolvedValue([provider]); + listPodsMock.mockResolvedValue([pod1]); + kubernetesListPodsMock.mockResolvedValue([]); + window.dispatchEvent(new CustomEvent('provider-lifecycle-change')); + window.dispatchEvent(new CustomEvent('extensions-already-started')); + + while (get(providerInfos).length !== 1) { + await new Promise(resolve => setTimeout(resolve, 500)); + } + + while (get(podsInfos).length !== 1) { + await new Promise(resolve => setTimeout(resolve, 500)); + } + + render(PodsList, { searchTerm: 'No match' }); + const filterButton = screen.getByRole('button', { name: 'Clear filter' }); + expect(filterButton).toBeInTheDocument(); +}); diff --git a/packages/renderer/src/lib/pod/PodsList.svelte b/packages/renderer/src/lib/pod/PodsList.svelte index 893a17679a726..22e25784e3bcf 100644 --- a/packages/renderer/src/lib/pod/PodsList.svelte +++ b/packages/renderer/src/lib/pod/PodsList.svelte @@ -11,6 +11,7 @@ import { PodUtils } from './pod-utils'; import type { PodInfo } from '../../../../main/src/plugin/api/pod-info'; import NoContainerEngineEmptyScreen from '../image/NoContainerEngineEmptyScreen.svelte'; import PodEmptyScreen from './PodEmptyScreen.svelte'; +import FilteredEmptyScreen from '../ui/FilteredEmptyScreen.svelte'; import StatusIcon from '../images/StatusIcon.svelte'; import PodIcon from '../images/PodIcon.svelte'; import PodActions from './PodActions.svelte'; @@ -24,7 +25,7 @@ import Checkbox from '../ui/Checkbox.svelte'; import Button from '../ui/Button.svelte'; import { faTrash } from '@fortawesome/free-solid-svg-icons'; -let searchTerm = ''; +export let searchTerm = ''; $: searchPattern.set(searchTerm); let pods: PodInfoUI[] = []; @@ -306,7 +307,11 @@ function errorCallback(pod: PodInfoUI, errorMessage: string): void { {#if $filtered.length === 0 && providerConnections.length === 0} {:else if $filtered.length === 0} - + {#if searchTerm} + + {:else} + + {/if} {/if} diff --git a/packages/renderer/src/lib/ui/EmptyScreen.svelte b/packages/renderer/src/lib/ui/EmptyScreen.svelte index e32b3ae27b8e6..935793ef768da 100644 --- a/packages/renderer/src/lib/ui/EmptyScreen.svelte +++ b/packages/renderer/src/lib/ui/EmptyScreen.svelte @@ -6,6 +6,7 @@ import { onMount } from 'svelte'; export let icon: any; export let title = 'No title'; export let message = 'Message'; +export let detail = ''; export let commandline = ''; export let hidden = false; @@ -46,7 +47,10 @@ let copyTextDivElement: HTMLDivElement;

{title}

{message} - {#if commandline.length > 0} + {#if detail} + {detail} + {/if} + {#if commandline}
{commandline} diff --git a/packages/renderer/src/lib/ui/FilteredEmptyScreen.svelte b/packages/renderer/src/lib/ui/FilteredEmptyScreen.svelte new file mode 100644 index 0000000000000..a8d1356977f5d --- /dev/null +++ b/packages/renderer/src/lib/ui/FilteredEmptyScreen.svelte @@ -0,0 +1,23 @@ + + + + + diff --git a/packages/renderer/src/lib/volume/VolumesList.spec.ts b/packages/renderer/src/lib/volume/VolumesList.spec.ts index 10802a1e4d87c..cd83761802206 100644 --- a/packages/renderer/src/lib/volume/VolumesList.spec.ts +++ b/packages/renderer/src/lib/volume/VolumesList.spec.ts @@ -257,3 +257,62 @@ describe('Create volume', () => { expect(window.location.pathname).toBe('/volumes/create'); }); }); + +test('Expect filter empty screen', async () => { + getProviderInfosMock.mockResolvedValue([ + { + name: 'podman', + status: 'started', + internalId: 'podman-internal-id', + containerConnections: [ + { + name: 'podman-machine-default', + status: 'started', + }, + ], + }, + ]); + + listVolumesMock.mockResolvedValue([ + { + Volumes: [ + { + Driver: 'local', + Labels: {}, + Mountpoint: '/var/lib/containers/storage/volumes/fedora/_data', + Name: '0052074a2ade930338c00aea982a90e4243e6cf58ba920eb411c388630b8c967', + Options: {}, + Scope: 'local', + engineName: 'Podman', + engineId: 'podman.Podman Machine', + UsageData: { RefCount: 1, Size: -1 }, + containersUsage: [], + }, + ], + }, + ]); + + window.dispatchEvent(new CustomEvent('extensions-already-started')); + window.dispatchEvent(new CustomEvent('provider-lifecycle-change')); + + // ask to fetch the volumes + const volumesEventStoreInfo = volumesEventStore.setup(); + + await volumesEventStoreInfo.fetch(); + + // first call is with listing without details + expect(listVolumesMock).toHaveBeenNthCalledWith(1, false); + + // wait store are populated + while (get(volumeListInfos).length === 0) { + await new Promise(resolve => setTimeout(resolve, 500)); + } + while (get(providerInfos).length === 0) { + await new Promise(resolve => setTimeout(resolve, 500)); + } + + await waitRender({ searchTerm: 'No match' }); + + const filterButton = screen.getByRole('button', { name: 'Clear filter' }); + expect(filterButton).toBeInTheDocument(); +}); diff --git a/packages/renderer/src/lib/volume/VolumesList.svelte b/packages/renderer/src/lib/volume/VolumesList.svelte index 3992b76bb8f87..8d6d2b2ea9eb8 100644 --- a/packages/renderer/src/lib/volume/VolumesList.svelte +++ b/packages/renderer/src/lib/volume/VolumesList.svelte @@ -10,6 +10,7 @@ import NavPage from '../ui/NavPage.svelte'; import { VolumeUtils } from './volume-utils'; import NoContainerEngineEmptyScreen from '../image/NoContainerEngineEmptyScreen.svelte'; import VolumeEmptyScreen from './VolumeEmptyScreen.svelte'; +import FilteredEmptyScreen from '../ui/FilteredEmptyScreen.svelte'; import VolumeActions from './VolumeActions.svelte'; import VolumeIcon from '../images/VolumeIcon.svelte'; import StatusIcon from '../images/StatusIcon.svelte'; @@ -20,7 +21,7 @@ import Checkbox from '../ui/Checkbox.svelte'; import Button from '../ui/Button.svelte'; import { faPieChart, faPlusCircle, faTrash } from '@fortawesome/free-solid-svg-icons'; -let searchTerm = ''; +export let searchTerm = ''; $: searchPattern.set(searchTerm); let volumes: VolumeInfoUI[] = []; @@ -288,7 +289,11 @@ function gotoCreateVolume(): void { {#if providerConnections.length === 0} {:else if $filtered.map(volumeInfo => volumeInfo.Volumes).flat().length === 0} - + {#if searchTerm} + + {:else} + + {/if} {/if}