fix(ui): reset stale mutation state when the import configuration changes

After a failed preview or import, editing the package URL, source mode,
target, or collision strategy cleared the preview but left the mutation
error state behind, so the persistent error panel kept describing a
superseded request (and a stale import error could resurface under a
fresh preview). Route all configuration changes through a shared reset
that also clears both mutations, and cover the reset in the component
tests.
This commit is contained in:
Devin Foley committed 2026-07-30 13:07:56 -07:00
1 parent 55d849cedc
commit 37cc66e9d6
2 files changed
+29 -7

No files matched your search

+15 -2
View File
@@ -206,14 +206,14 @@ describe("CompanyImport", () => {
await flushReact();
}
async function enterGithubUrl() {
async function enterGithubUrl(url = "https://github.com/acme/starter/tree/main/company") {
const urlInput = container.querySelector<HTMLInputElement>(
'input[placeholder="https://github.com/owner/repo/tree/main/company"]',
);
expect(urlInput).toBeTruthy();
await act(async () => {
const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")!.set!;
setter.call(urlInput!, "https://github.com/acme/starter/tree/main/company");
setter.call(urlInput!, url);
urlInput!.dispatchEvent(new Event("input", { bubbles: true }));
});
await flushReact();
@@ -355,6 +355,11 @@ describe("CompanyImport", () => {
expect(container.textContent).toContain("Preview failed: stream disconnected");
expect(container.textContent).toContain("Retry, or use the CLI folder import");
expect(mockPushToast).toHaveBeenCalledWith(expect.objectContaining({ tone: "error" }));
// Changing the package supersedes the failed request: the error panel resets.
await enterGithubUrl("https://github.com/acme/other-starter/tree/main/company");
expect(container.textContent).not.toContain("Preview failed:");
});
it("shows a progress panel while the import runs and a durable error panel when it fails", async () => {
@@ -383,5 +388,13 @@ describe("CompanyImport", () => {
expect(container.textContent).toContain("Import failed: connection reset");
expect(container.textContent).toContain("check the target company before retrying.");
expect(mockPushToast).toHaveBeenCalledWith(expect.objectContaining({ tone: "error" }));
// Changing the package supersedes the failed import: previewing a fresh
// package must not resurface the stale import error panel.
await enterGithubUrl("https://github.com/acme/other-starter/tree/main/company");
await clickButton((text) => text === "Preview import");
expect(findButton((text) => text.startsWith("Import 3 file"))).toBeTruthy();
expect(container.textContent).not.toContain("Import failed:");
});
});
+14 -5
View File
@@ -927,13 +927,22 @@ export function CompanyImport() {
},
});
// Any change to the package or import configuration supersedes the last
// preview and any settled mutation state, so the persistent progress/error
// panels never keep describing a request that no longer applies.
function resetImportFlowState() {
setImportPreview(null);
previewMutation.reset();
importMutation.reset();
}
async function handleChooseLocalPackage(e: ChangeEvent<HTMLInputElement>) {
const fileList = e.target.files;
if (!fileList || fileList.length === 0) return;
try {
const pkg = await readLocalPackageZip(fileList[0]!);
setLocalPackage(pkg);
setImportPreview(null);
resetImportFlowState();
} catch (err) {
pushToast({
tone: "error",
@@ -1311,7 +1320,7 @@ export function CompanyImport() {
)}
onClick={() => {
setSourceMode(key);
setImportPreview(null);
resetImportFlowState();
}}
>
<div className="flex items-center gap-2">
@@ -1379,7 +1388,7 @@ export function CompanyImport() {
placeholder="https://github.com/owner/repo/tree/main/company"
onChange={(e) => {
setImportUrl(e.target.value);
setImportPreview(null);
resetImportFlowState();
}}
/>
</Field>
@@ -1391,7 +1400,7 @@ export function CompanyImport() {
value={targetMode}
onChange={(e) => {
setTargetMode(e.target.value as "existing" | "new");
setImportPreview(null);
resetImportFlowState();
}}
>
<option value="new">Create new company</option>
@@ -1425,7 +1434,7 @@ export function CompanyImport() {
value={collisionStrategy}
onChange={(e) => {
setCollisionStrategy(e.target.value as CompanyPortabilityCollisionStrategy);
setImportPreview(null);
resetImportFlowState();
}}
>
<option value="rename">Rename on conflict</option>