Skip to content

Commit d315067

Browse files
jackkavCopilot
andcommitted
fix: wait for environment saves before closing modal
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 99d0ccd commit d315067

2 files changed

Lines changed: 66 additions & 19 deletions

File tree

packages/insomnia-smoke-test/tests/smoke/environment-editor-interactions.test.ts

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
1-
import { expect } from '@playwright/test';
1+
import { expect, type Page } from '@playwright/test';
22

33
import { loadFixture } from '../../playwright/paths';
44
import { test } from '../../playwright/test';
55

6+
const closeManageEnvironmentsDialog = async (page: Page) => {
7+
const dialog = page.getByTestId('WorkspaceEnvironmentsDialog');
8+
const closeButton = page.getByRole('button', { name: 'Close', exact: true });
9+
10+
await closeButton.click();
11+
12+
if (await dialog.isVisible()) {
13+
await expect.soft(dialog).toHaveAttribute('data-save-state', 'idle');
14+
await closeButton.click();
15+
}
16+
17+
await expect.soft(page.getByRole('heading', { name: 'Manage Environments' })).toBeHidden();
18+
};
19+
620
test.describe('Environment Editor', () => {
721
test('manage environment', async ({ page, app, insomnia }) => {
822
const text = await loadFixture('environments.yaml');
@@ -18,7 +32,7 @@ test.describe('Environment Editor', () => {
1832
await page.getByTestId('CreateEnvironmentDropdown').click();
1933
await page.getByRole('menuitemradio', { name: 'Shared Environment' }).press('Enter');
2034
await page.getByRole('row', { name: 'New Environment' }).click();
21-
await page.getByRole('dialog').getByRole('button', { name: 'Close' }).click();
35+
await closeManageEnvironmentsDialog(page);
2236

2337
await page.getByRole('option', { name: 'New Environment' }).press('Enter');
2438
await page.getByRole('option', { name: 'New Environment' }).press('Escape');
@@ -43,8 +57,7 @@ test.describe('Environment Editor', () => {
4357
await page.getByRole('row', { name: 'ExampleB' }).locator('input').fill('Gandalf');
4458
await page.getByRole('row', { name: 'ExampleB' }).locator('input').press('Enter');
4559

46-
await page.getByRole('button', { name: 'Close', exact: true }).click();
47-
60+
await closeManageEnvironmentsDialog(page);
4861
await page.getByRole('option', { name: 'Gandalf' }).press('Enter');
4962
await page.getByRole('option', { name: 'Gandalf' }).press('Escape');
5063

@@ -68,10 +81,7 @@ test.describe('Environment Editor', () => {
6881
await dialog.getByTestId('CodeEditor').getByRole('textbox').press('Enter');
6982
await dialog.getByTestId('CodeEditor').getByRole('textbox').fill('"testString":"Gandalf",');
7083

71-
// Blur the editor before closing so the debounce flush is triggered by the button's mousedown
72-
await dialog.getByRole('button', { name: 'Close' }).click();
73-
// Wait for the dialog to be gone before navigating away
74-
await expect.soft(page.getByRole('heading', { name: 'Manage Environments' })).toBeHidden();
84+
await closeManageEnvironmentsDialog(page);
7585
await page.getByLabel('Manage collection environments').press('Escape');
7686
await insomnia.navigationSidebar.clickRequestOrFolder('New Request');
7787

@@ -117,9 +127,7 @@ test.describe('Environment Editor', () => {
117127
await page.getByRole('button', { name: 'Modal Submit' }).click();
118128
await expect.soft(page.getByRole('dialog', { name: 'Modal' })).toBeHidden();
119129

120-
// Close the environment editor and wait for the dialog to disappear before navigating
121-
await page.getByRole('button', { name: 'Close', exact: true }).click();
122-
await expect.soft(page.getByRole('heading', { name: 'Manage Environments' })).toBeHidden();
130+
await closeManageEnvironmentsDialog(page);
123131
await page.getByLabel('Manage collection environments').press('Escape');
124132
await insomnia.navigationSidebar.clickRequestOrFolder('New Request');
125133
await page.getByRole('button', { name: 'Send' }).click();
@@ -163,8 +171,7 @@ test.describe('Environment Editor', () => {
163171
await expect.soft(exampleStringRow).toHaveCSS('opacity', '0.4');
164172

165173
// Close the editor and wait for it to disappear
166-
await page.getByRole('button', { name: 'Close', exact: true }).click();
167-
await expect.soft(page.getByRole('heading', { name: 'Manage Environments' })).toBeHidden();
174+
await closeManageEnvironmentsDialog(page);
168175
await page.getByLabel('Manage collection environments').press('Escape');
169176

170177
// Send request — disabled sub-env variable should fall back to base environment

packages/insomnia/src/ui/components/modals/workspace-environments-edit-modal.tsx

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { IconName, IconProp } from '@fortawesome/fontawesome-svg-core';
2-
import React, { Fragment, useMemo, useRef, useState } from 'react';
2+
import React, { Fragment, useEffect, useMemo, useRef, useState } from 'react';
33
import {
44
Button,
55
Dialog,
@@ -62,9 +62,11 @@ export const WorkspaceEnvironmentsEditModal = ({ onClose }: { onClose: () => voi
6262
const updateEnvironmentFetcher = useEnvironmentUpdateActionFetcher();
6363
const duplicateEnvironmentFetcher = useEnvironmentDuplicateActionFetcher();
6464
const { toggleEnvironmentType } = useToggleEnvironmentType();
65+
const pendingUpdateRef = useRef(false);
6566

6667
const { baseEnvironment, activeEnvironment, subEnvironments, activeProject, activeWorkspaceMeta } = routeData;
6768
const [selectedEnvironmentId, setSelectedEnvironmentId] = useState<string>(activeEnvironment._id);
69+
const [hasPendingUpdate, setHasPendingUpdate] = useState(false);
6870
const isUsingInsomniaCloudSync = Boolean(
6971
models.project.isRemoteProject(activeProject) && !activeWorkspaceMeta?.gitRepositoryId,
7072
);
@@ -169,6 +171,8 @@ export const WorkspaceEnvironmentsEditModal = ({ onClose }: { onClose: () => voi
169171

170172
const handleEnvironmentChange = (value: EnvironmentInfo) => {
171173
if (environmentEditorRef.current?.isValid() && selectedEnvironment) {
174+
pendingUpdateRef.current = true;
175+
setHasPendingUpdate(true);
172176
const { object, propertyOrder } = value;
173177

174178
updateEnvironmentFetcher.submit({
@@ -186,6 +190,8 @@ export const WorkspaceEnvironmentsEditModal = ({ onClose }: { onClose: () => voi
186190

187191
const handleKVPairChange = (kvPairData: EnvironmentKvPairData[]) => {
188192
if (selectedEnvironment) {
193+
pendingUpdateRef.current = true;
194+
setHasPendingUpdate(true);
189195
const environmentData = getDataFromKVPair(kvPairData);
190196
updateEnvironmentFetcher.submit({
191197
organizationId,
@@ -200,6 +206,24 @@ export const WorkspaceEnvironmentsEditModal = ({ onClose }: { onClose: () => voi
200206
});
201207
}
202208
};
209+
useEffect(() => {
210+
if (pendingUpdateRef.current && updateEnvironmentFetcher.state === 'idle') {
211+
pendingUpdateRef.current = false;
212+
setHasPendingUpdate(false);
213+
}
214+
}, [updateEnvironmentFetcher.state]);
215+
const isEnvironmentMutationPending =
216+
hasPendingUpdate ||
217+
createEnvironmentFetcher.state !== 'idle' ||
218+
deleteEnvironmentFetcher.state !== 'idle' ||
219+
duplicateEnvironmentFetcher.state !== 'idle' ||
220+
updateEnvironmentFetcher.state !== 'idle';
221+
const requestClose = (close: () => void) => {
222+
if (pendingUpdateRef.current || isEnvironmentMutationPending) {
223+
return;
224+
}
225+
close();
226+
};
203227
const environmentsDragAndDrop = useDragAndDrop({
204228
getItems: keys => [...keys].map(key => ({ 'text/plain': key.toString() })),
205229
onReorder(e) {
@@ -249,26 +273,36 @@ export const WorkspaceEnvironmentsEditModal = ({ onClose }: { onClose: () => voi
249273
<ModalOverlay
250274
isOpen
251275
onOpenChange={isOpen => {
252-
!isOpen && onClose();
276+
if (!isOpen && !pendingUpdateRef.current && !isEnvironmentMutationPending) {
277+
onClose();
278+
}
253279
}}
254280
className="fixed top-0 left-0 z-10 flex h-(--visual-viewport-height) w-full items-center justify-center bg-black/30"
255281
>
256282
<Modal
257283
onOpenChange={isOpen => {
258-
!isOpen && onClose();
284+
if (!isOpen && !pendingUpdateRef.current && !isEnvironmentMutationPending) {
285+
onClose();
286+
}
259287
}}
260288
className="flex h-[calc(100%-var(--padding-xl))] w-[calc(100%-var(--padding-xl))] flex-col rounded-md border border-solid border-(--hl-sm) bg-(--color-bg) p-(--padding-lg) text-(--color-font)"
261289
>
262-
<Dialog className="flex h-full flex-1 flex-col overflow-hidden outline-hidden">
290+
<Dialog
291+
data-testid="WorkspaceEnvironmentsDialog"
292+
data-save-state={isEnvironmentMutationPending ? 'saving' : 'idle'}
293+
aria-busy={isEnvironmentMutationPending}
294+
className="flex h-full flex-1 flex-col overflow-hidden outline-hidden"
295+
>
263296
{({ close }) => (
264297
<div className="flex h-full flex-1 flex-col gap-4 overflow-hidden">
265298
<div className="flex items-center justify-between gap-2">
266299
<Heading slot="title" className="text-2xl">
267300
Manage Environments
268301
</Heading>
269302
<Button
303+
isDisabled={isEnvironmentMutationPending}
270304
className="flex aspect-square h-6 shrink-0 items-center justify-center rounded-xs text-sm text-(--color-font) ring-1 ring-transparent transition-all hover:bg-(--hl-xs) focus:ring-(--hl-md) focus:ring-inset aria-pressed:bg-(--hl-sm)"
271-
onPress={close}
305+
onPress={() => requestClose(close)}
272306
>
273307
<Icon icon="x" />
274308
</Button>
@@ -551,9 +585,15 @@ export const WorkspaceEnvironmentsEditModal = ({ onClose }: { onClose: () => voi
551585
* Environment data can be used for <a href={docsTemplateTags}>Nunjucks Templating</a> in your
552586
requests.
553587
</p>
588+
{isEnvironmentMutationPending && (
589+
<p data-testid="WorkspaceEnvironmentsSaveStatus" className="text-sm italic">
590+
Saving environments...
591+
</p>
592+
)}
554593
</div>
555594
<Button
556-
onPress={close}
595+
isDisabled={isEnvironmentMutationPending}
596+
onPress={() => requestClose(close)}
557597
className="rounded-xs border border-solid border-(--hl-md) px-3 py-2 text-(--color-font) transition-colors hover:no-underline"
558598
>
559599
Close

0 commit comments

Comments
 (0)