Skip to content

Commit

Permalink
feat: add a way to choose platform when building image (podman-deskto…
Browse files Browse the repository at this point in the history
…p#5503)

* feat: add a way to choose platform when building image

fixes podman-desktop#5492
Signed-off-by: Florent Benoit <[email protected]>
  • Loading branch information
benoitf authored Jan 12, 2024
1 parent b3d7b97 commit 73958b5
Show file tree
Hide file tree
Showing 6 changed files with 476 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ beforeAll(() => {
});
(window as any).openFileDialog = vi.fn().mockResolvedValue({ canceled: false, filePaths: ['Containerfile'] });
(window as any).telemetryPage = vi.fn().mockResolvedValue(undefined);
(window as any).getOsArch = vi.fn();
});

// the build image page expects to have a valid provider connection, so let's mock one
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import TerminalWindow from '../ui/TerminalWindow.svelte';
import type { Terminal } from 'xterm';
import Button from '../ui/Button.svelte';
import { faCube } from '@fortawesome/free-solid-svg-icons';
import BuildImageFromContainerfileCards from './BuildImageFromContainerfileCards.svelte';
let buildFinished = false;
let containerImageName = 'my-custom-image';
Expand Down Expand Up @@ -216,6 +217,11 @@ async function abortBuild() {
{/if}
</div>

<div hidden="{buildImageInfo?.buildRunning}">
<label for="containerBuildPlatform" class="block mb-2 text-sm font-bold text-gray-400">Platform</label>
<BuildImageFromContainerfileCards bind:platforms="{containerBuildPlatform}" />
</div>

<div class="w-full flex flex-row space-x-4">
{#if !buildImageInfo?.buildRunning}
<Button
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/**********************************************************************
* Copyright (C) 2024 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
***********************************************************************/

/* eslint-disable @typescript-eslint/no-explicit-any */

import '@testing-library/jest-dom/vitest';
import { test, expect } from 'vitest';
import { render, screen } from '@testing-library/svelte';
import userEvent from '@testing-library/user-event';
import BuildImageFromContainerfileCard from '/@/lib/image/BuildImageFromContainerfileCard.svelte';

test('check click', async () => {
const component = render(BuildImageFromContainerfileCard, {
title: 'ARM64',
badge: 'arm64',
isDefault: false,
checked: false,
value: 'arm64',
icon: undefined,
});

const events: { mode: string; value: string }[] = [];

component.component.$on('card', (e: any) => {
events.push(e.detail);
});

// expect checkbox is unchecked
expect(screen.getByRole('checkbox')).not.toBeChecked();

expect(events).toEqual([]);

// click on the card and expect to be checked
await userEvent.click(screen.getByRole('button'));

expect(events).toEqual([{ mode: 'add', value: 'arm64' }]);
});

test('Expect checked', async () => {
render(BuildImageFromContainerfileCard, {
title: 'ARM64',
badge: 'arm64',
isDefault: false,
checked: true,
value: 'arm64',
icon: undefined,
});

// expect checkbox is unchecked
expect(screen.getByRole('checkbox')).toBeChecked();
});

test('Expect default tooltip', async () => {
render(BuildImageFromContainerfileCard, {
title: 'ARM64',
badge: 'arm64',
isDefault: true,
checked: true,
value: 'arm64',
icon: undefined,
});

// check we have a div tooltip with aria-label tooltip
const tooltip = screen.getByText('Default platform of your computer');
expect(tooltip).toBeInTheDocument();
});

test('check we can add a new card', async () => {
const component = render(BuildImageFromContainerfileCard, {
title: '',
badge: '',
isDefault: false,
checked: false,
value: '',
icon: undefined,
additionalItem: true,
});

const addCards: { value: string }[] = [];

component.component.$on('addcard', (e: any) => {
addCards.push(e.detail);
});

expect(addCards).toEqual([]);

// click on the card and expect to be checked
await userEvent.click(screen.getByRole('button'));

// add new name on the input field
await userEvent.type(screen.getByRole('textbox'), 'my/platform{enter}');

expect(addCards).toEqual([{ value: 'my/platform' }]);
});
113 changes: 113 additions & 0 deletions packages/renderer/src/lib/image/BuildImageFromContainerfileCard.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<script lang="ts">
import { faSun } from '@fortawesome/free-solid-svg-icons';
import Fa from 'svelte-fa';
import Checkbox from '../ui/Checkbox.svelte';
import Tooltip from '../ui/Tooltip.svelte';
import { onMount, tick, createEventDispatcher } from 'svelte';
export let title: string = '';
export let badge: string = '';
export let isDefault: boolean = false;
export let checked: boolean = false;
export let value: string = '';
export let icon: any;
let iconType: 'fontAwesome' | 'unknown' | undefined = undefined;
export let additionalItem: boolean = false;
let additionalValue: string = '';
let displayValueFieldInput = false;
let inputHtmlElement: HTMLInputElement | undefined;
const dispatch = createEventDispatcher();
function handleKeydownAdditionalField(event: KeyboardEvent) {
if (event.key === 'Enter') {
dispatch('addcard', { value: additionalValue });
displayValueFieldInput = false;
}
}
function handleClick() {
if (additionalItem) {
// display the new field input
displayValueFieldInput = true;
additionalValue = '';
// make focus on the input field
tick().then(() => {
if (inputHtmlElement) {
inputHtmlElement.focus();
}
});
return;
}
checked = !checked;
if (checked) {
dispatch('card', { mode: 'add', value: value });
} else {
dispatch('card', { mode: 'remove', value: value });
}
}
onMount(() => {
if (icon?.prefix?.startsWith('fa')) {
iconType = 'fontAwesome';
} else {
iconType = 'unknown';
}
if (isDefault) {
dispatch('card', { mode: 'add', value: value });
}
});
</script>

<button
class="rounded-md p-2 min-w-32 w-32 cursor-pointer hover:bg-charcoal-700 {checked
? 'border-dustypurple-700'
: 'border-gray-700'} border-2 flex flex-row"
aria-label="{value}"
on:click|preventDefault="{() => handleClick()}">
<div>
{#if !additionalItem}
<Checkbox bind:checked="{checked}" on:click="{() => handleClick()}" />
{:else}
<div>&nbsp;</div>
{/if}
</div>
<div class="mr-2 text-gray-700">
<div class="flex flex-row">
<div class="ml-1">
{#if iconType === 'fontAwesome'}
<Fa class="text-gray-700 cursor-pointer" icon="{icon}" size="24" />
{:else if iconType === 'unknown'}
<svelte:component this="{icon}" class="text-gray-700 cursor-pointer" size="24" />
{/if}
</div>

{#if isDefault}
<Tooltip tip="Default platform of your computer">
<Fa size="14" class="text-dustypurple-700 cursor-pointer" icon="{faSun}" />
</Tooltip>
{/if}
</div>
<div class="text-sm text-left truncate w-24">{title}</div>
<div class="flex">
{#if badge}
<div class="bg-dustypurple-700 text-white text-xs font-medium me-2 px-2.5 py-0.5 rounded">{badge}</div>
{/if}
{#if displayValueFieldInput}
<input
type="text"
class="w-24 outline-none text-sm bg-dustypurple-900 rounded-sm text-gray-700 placeholder-gray-700"
bind:value="{additionalValue}"
bind:this="{inputHtmlElement}"
on:keydown="{handleKeydownAdditionalField}" />
{/if}
</div>
</div>
</button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**********************************************************************
* Copyright (C) 2024 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
***********************************************************************/

/* eslint-disable @typescript-eslint/no-explicit-any */

import '@testing-library/jest-dom/vitest';
import { test, expect, beforeAll, vi } from 'vitest';
import { render } from '@testing-library/svelte';
import BuildImageFromContainerfileCards from './BuildImageFromContainerfileCards.svelte';

// fake the window.events object
beforeAll(() => {
(window.events as unknown) = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
receive: (_channel: string, func: any) => {
func();
},
};
(window as any).getOsArch = vi.fn();
});

test('check default on arm64', async () => {
vi.mocked(window.getOsArch).mockResolvedValue('arm64');

const platforms = '';
const rendered = render(BuildImageFromContainerfileCards, {
platforms,
});

// wait a little with setTimeout
await new Promise(resolve => setTimeout(resolve, 100));

// check we have a platform
expect(rendered.component.$$.ctx[5]).toEqual('linux/arm64');
});

test('check default on amd64', async () => {
vi.mocked(window.getOsArch).mockResolvedValue('x64');

const platforms = '';
const rendered = render(BuildImageFromContainerfileCards, {
platforms,
});

// wait a little with setTimeout
await new Promise(resolve => setTimeout(resolve, 100));

// check we have a platform
expect(rendered.component.$$.ctx[5]).toEqual('linux/amd64');
});
Loading

0 comments on commit 73958b5

Please sign in to comment.