8000 chore: display the stop button in a compose section (#13519) · podman-desktop/podman-desktop@ce73e5f · GitHub
[go: up one dir, main page]

Skip to content

Commit ce73e5f

Browse files
authored
chore: display the stop button in a compose section (#13519)
* chore: display the stop button in a compose section Signed-off-by: Vladyslav Zhukovskyi <vzhukovs@redhat.com> * chore: display the stop button in a compose section Signed-off-by: Vladyslav Zhukovskyi <vzhukovs@redhat.com> --------- Signed-off-by: Vladyslav Zhukovskyi <vzhukovs@redhat.com>
1 parent 541e3d4 commit ce73e5f

File tree

2 files changed

+146
-3
lines changed

2 files changed

+146
-3
lines changed

packages/renderer/src/lib/compose/ComposeActions.spec.ts

Lines changed: 126 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@
1818

1919
import '@testing-library/jest-dom/vitest';
2020

21+
import { within } from '@testing-library/dom';
2122
import { fireEvent, render, screen, waitFor } from '@testing-library/svelte';
22-
import { afterEach, beforeAll, beforeEach, expect, test, vi } from 'vitest';
23+
import { afterEach, beforeAll, beforeEach, expect, type Mock, test, vi } from 'vitest';
2324

2425
import type { ContainerInfoUI } from '../container/ContainerInfoUI';
2526
import ComposeActions from './ComposeActions.svelte';
@@ -64,6 +65,21 @@ const getContributedMenusMock = vi.fn();
6465
const updateMock = vi.fn();
6566
const showMessageBoxMock = vi.fn();
6667

68+
type Deferred<T = void> = {
69+
promise: Promise<T>;
70+
resolve: (value: T | PromiseLike<T>) => void;
71+
reject: (reason?: unknown) => void;
72+
};
73+
const createDeferred = <T = void>(): Deferred<T> => {
74+
let resolve!: (v: T | PromiseLike<T>) => void;
75+
let reject!: (e?: unknown) => void;
76+
const promise = new Promise<T>((res, rej) => {
77+
resolve = res;
78+
reject = rej;
79+
});
80+
return { promise, resolve, reject };
81+
};
82+
6783
beforeAll(() => {
6884
Object.defineProperty(window, 'showMessageBox', { value: showMessageBoxMock });
6985
Object.defineProperty(window, 'startContainersByLabel', { value: vi.fn() });
@@ -143,3 +159,112 @@ test('Expect no error and status deleting compose', async () => {
143159
expect(compose.containers[0].actionError).toEqual('');
144160
expect(updateMock).toHaveBeenCalled();
145161
});
162+
163+
test('Stop keeps Start hidden during STOPPING (all containers are running)', async () => {
164+
const composeAllRunning: ComposeInfoUI = new ComposeInfoUIImpl(
165+
'podman',
166+
'podman',
167+
'compose-all-running',
168+
'RUNNING',
169+
false,
170+
undefined,
171+
[
172+
{ state: 'RUNNING', actionInProgress: false } as ContainerInfoUI,
173+
{ state: 'RUNNING', actionInProgress: false } as ContainerInfoUI,
174+
],
175+
);
176+
177+
const { container } = render(ComposeActions, { compose: composeAllRunning, onUpdate: updateMock });
178+
const ui = within(container);
179+
180+
expect(ui.getByRole('button', { name: 'Start Compose', hidden: true })).toHaveClass('hidden');
181+
expect(ui.getByRole('button', { name: 'Stop Compose' })).not.toHaveClass('hidden');
182+
183+
const dStop = createDeferred<void>();
184+
(window.stopContainersByLabel as unknown as Mock).mockReturnValueOnce(dStop.promise);
185+
186+
await fireEvent.click(ui.getByRole('button', { name: 'Stop Compose' }));
187+
188+
await waitFor(() => {
189+
expect(composeAllRunning.actionInProgress).toBe(true);
190+
expect(composeAllRunning.status).toBe('STOPPING');
191+
});
192+
193+
dStop.resolve();
194+
195+
expect(ui.getByRole('button', { name: 'Start Compose', hidden: true })).toHaveClass('hidden');
196+
expect(ui.getByRole('button', { name: 'Stop Compose' })).not.toHaveClass('hidden');
197+
});
198+
199+
test('Start keeps Stop hidden during STARTING (all containers are stopped)', async () => {
200+
const composeAllStopped: ComposeInfoUI = new ComposeInfoUIImpl(
201+
'podman',
202+
'podman',
203+
'compose-all-stopped',
204+
'STOPPED',
205+
false,
206+
undefined,
207+
[
208+
{ state: 'STOPPED', actionInProgress: false } as ContainerInfoUI,
209+
{ state: 'STOPPED', actionInProgress: false } as ContainerInfoUI,
210+
],
211+
);
212+
213+
const { container } = render(ComposeActions, { compose: composeAllStopped, onUpdate: updateMock });
214+
const ui = within(container);
215+
216+
expect(ui.getByRole('button', { name: 'Start Compose' })).not.toHaveClass('hidden');
217+
expect(ui.getByRole('button', { name: 'Stop Compose', hidden: true })).toHaveClass('hidden');
218+
219+
const dStart = createDeferred<void>();
220+
(window.startContainersByLabel as unknown as Mock).mockReturnValueOnce(dStart.promise);
221+
222+
await fireEvent.click(ui.getByRole('button', { name: 'Start Compose' }));
223+
224+
await waitFor(() => {
225+
expect(composeAllStopped.actionInProgress).toBe(true);
226+
expect(composeAllStopped.status).toBe('STARTING');
227+
});
228+
229+
dStart.resolve();
230+
231+
expect(ui.getByRole('button', { name: 'Stop Compose', hidden: true })).toHaveClass('hidden');
232+
expect(ui.getByRole('button', { name: 'Start Compose' })).not.toHaveClass('hidden');
233+
});
234+
235+
test('Stop keeps both visible during STOPPING (some containers are running)', async () => {
236+
const composePartial: ComposeInfoUI = new ComposeInfoUIImpl(
237+
'podman',
238+
'podman',
239+
'compose-partial',
240+
'RUNNING',
241+
false,
242+
undefined,
243+
[
244+
{ state: 'RUNNING', actionInProgress: false } as ContainerInfoUI,
245+
{ state: 'STOPPED', actionInProgress: false } as ContainerInfoUI,
246+
{ state: 'RUNNING', actionInProgress: false } as ContainerInfoUI,
247+
],
248+
);
249+
250+
const { container } = render(ComposeActions, { compose: composePartial, onUpdate: updateMock });
251+
const ui = within(container);
252+
253+
expect(ui.getByRole('button', { name: 'Start Compose' })).not.toHaveClass('hidden');
254+
expect(ui.getByRole('button', { name: 'Stop Compose' })).not.toHaveClass('hidden');
255+
256+
const dStop = createDeferred<void>();
257+
(window.stopContainersByLabel as unknown as Mock).mockReturnValueOnce(dStop.promise);
258+
259+
await fireEvent.click(ui.getByRole('button', { name: 'Stop Compose' }));
260+
261+
await waitFor(() => {
262+
expect(composePartial.actionInProgress).toBe(true);
263+
expect(composePartial.status).toBe('STOPPING');
264+
});
265+
266+
dStop.resolve();
267+
268+
expect(ui.getByRole('button', { name: 'Start Compose' })).not.toHaveClass('hidden');
269+
expect(ui.getByRole('button', { name: 'Stop Compose' })).not.toHaveClass('hidden');
270+
});

packages/renderer/src/lib/compose/ComposeActions.svelte

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ onMount(async () => {
2828
contributions = await window.getContributedMenus(MenuContext.DASHBOARD_COMPOSE);
2929
});
3030
31+
let hideStartForStop = false;
32+
let hideStopForStart = false;
33+
34+
$: someNeedStart = compose.containers?.some(c => c.state !== 'RUNNING');
35+
$: someNeedStop = compose.containers?.some(c => c.state === 'RUNNING');
36+
3137
function inProgress(inProgress: boolean, state?: string): void {
3238
compose.actionInProgress = inProgress;
3339
// reset error when starting task
@@ -59,6 +65,7 @@ function handleError(errorMessage: string): void {
5965
}
6066
6167
async function startCompose(): Promise<void> {
68+
hideStopForStart = !someNeedStop;
6269
inProgress(true, 'STARTING');
6370
try {
6471
await window.startContainersByLabel(compose.engineId, composeLabel, compose.name);
@@ -69,6 +76,7 @@ async function startCompose(): Promise<void> {
6976
}
7077
}
7178
async function stopCompose(): Promise<void> {
79+
hideStartForStop = !someNeedStart;
7280
inProgress(true, 'STOPPING');
7381
try {
7482
await window.stopContainersByLabel(compose.engineId, composeLabel, compose.name);
@@ -122,7 +130,12 @@ if (dropdownMenu) {
122130
<ListItemButtonIcon
123131
title="Start Compose"
124132
onClick={startCompose}
125-
hidden={compose.status === 'RUNNING' || compose.status === 'STOPPING'}
133+
hidden={
134+
!compose.actionInProgress
135+
? !someNeedStart
136+
: (compose.status === 'STOPPING' && hideStartForStop)
137+
}
138+
enabled={!compose.actionInProgress}
126139
detailed={detailed}
127140
inProgress={compose.actionInProgress && compose.status === 'STARTING'}
128141
icon={faPlay}
@@ -131,8 +144,13 @@ if (dropdownMenu) {
131144
<ListItemButtonIcon
132145
title="Stop Compose"
133146
onClick={stopCompose}
134-
hidden={!(compose.status === 'RUNNING' || compose.status === 'STOPPING')}
147+
hidden={
148+
!compose.actionInProgress
149+
? !someNeedStop
150+
: (compose.status === 'STARTING' && hideStopForStart)
151+
}
135152
detailed={detailed}
153+
enabled={!compose.actionInProgress}
136154
inProgress={compose.actionInProgress && compose.status === 'STOPPING'}
137155
icon={faStop} />
138156

0 commit comments

Comments
 (0)
0