This is an automated email from the ASF dual-hosted git repository.
dengliming pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu-dashboard.git
The following commit(s) were added to refs/heads/master by this push:
new 81c7ec42 fix: preserve API debug mock state (#687)
81c7ec42 is described below
commit 81c7ec42d7e20b5cd7f91115f7393054562710a7
Author: Carlos Acosta <[email protected]>
AuthorDate: Sun Sep 27 22:32:55 2026 -0500
fix: preserve API debug mock state (#687)
* fix: preserve API debug mock state
Co-authored-by: Aeontra <aeontra@localhost>
* fix: invalidate stale API mock identity
Co-authored-by: Aeontra <aeontra@localhost>
---------
Co-authored-by: Aeontra <aeontra@localhost>
---
src/routes/Document/components/ApiDebug.js | 9 +-
.../Document/components/ApiDebug.state.test.js | 158 +++++++++++++++++++++
2 files changed, 165 insertions(+), 2 deletions(-)
diff --git a/src/routes/Document/components/ApiDebug.js
b/src/routes/Document/components/ApiDebug.js
index ee196954..3098c512 100644
--- a/src/routes/Document/components/ApiDebug.js
+++ b/src/routes/Document/components/ApiDebug.js
@@ -145,8 +145,13 @@ const FCForm = forwardRef(({ form, onSubmit }, ref) => {
}, [apiDetail.httpMethod]);
useEffect(() => {
- setInitialValue({ url: apiDetail.apiPath });
- }, [apiDetail.apiPath]);
+ setInitialValue((prev) => ({
+ ...prev,
+ id: prev.apiId === apiDetail.id ? prev.id : null,
+ apiId: apiDetail.id,
+ url: apiDetail.apiPath,
+ }));
+ }, [apiDetail.id, apiDetail.apiPath]);
useEffect(() => {
form.setFieldsValue({
diff --git a/src/routes/Document/components/ApiDebug.state.test.js
b/src/routes/Document/components/ApiDebug.state.test.js
new file mode 100644
index 00000000..31422397
--- /dev/null
+++ b/src/routes/Document/components/ApiDebug.state.test.js
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+import React from "react";
+import { fireEvent, render, screen, waitFor } from "@testing-library/react";
+import { deleteMockRequest } from "../../../services/api";
+import ApiContext from "./ApiContext";
+import ApiDebug from "./ApiDebug";
+
+jest.mock("antd", () => {
+ const ReactModule = jest.requireActual("react");
+ const antd = jest.requireActual("antd");
+ antd.Input.Group = ReactModule.forwardRef((props, ref) =>
+ ReactModule.createElement("div", {
+ ref,
+ "data-input-group": Boolean(props),
+ }),
+ );
+ return {
+ ...antd,
+ message: {
+ error: jest.fn(),
+ success: jest.fn(),
+ warn: jest.fn(),
+ },
+ };
+});
+
+jest.mock("dva/fetch", () => jest.fn());
+jest.mock("react-html-parser", () => jest.fn(() => null));
+jest.mock("react-json-view", () => () => null);
+jest.mock("./HeadersEditor", () => {
+ const ReactModule = jest.requireActual("react");
+ return ReactModule.forwardRef((props, ref) =>
+ ReactModule.createElement("div", {
+ ref,
+ "data-headers-editor": Boolean(props),
+ }),
+ );
+});
+jest.mock(
+ "../../../utils/AuthButton",
+ () =>
+ ({ children }) =>
+ children,
+);
+jest.mock("../../../utils/IntlUtils", () => ({
+ getIntlContent: (key) => key,
+}));
+
+jest.mock("../../../services/api", () => ({
+ createOrUpdateMockRequest: jest.fn(),
+ deleteMockRequest: jest.fn(),
+ getApiMockRequest: jest.fn(),
+ sandboxProxyGateway: jest.fn(() => "/sandbox"),
+}));
+
+it("keeps the saved mock id when the API path effect runs", async () => {
+ deleteMockRequest.mockResolvedValue({ code: 200, message: "Deleted" });
+
+ render(
+ <ApiContext.Provider
+ value={{
+ apiData: { envProps: [] },
+ apiDetail: {
+ apiPath: "/users",
+ httpMethod: 0,
+ id: "api-1",
+ },
+ apiMock: {
+ body: "{}",
+ header: { "X-Test": "value" },
+ host: "api.example.com",
+ id: "mock-1",
+ pathVariable: "",
+ port: "443",
+ query: "[]",
+ url: "https://api.example.com/users",
+ },
+ }}
+ >
+ <ApiDebug />
+ </ApiContext.Provider>,
+ );
+
+ fireEvent.click(screen.getByText("SHENYU.DOCUMENT.APIDOC.DEBUG.MOCK.RESET"));
+
+ await waitFor(() => {
+ expect(deleteMockRequest).toHaveBeenCalledWith("mock-1");
+ });
+});
+
+it("does not delete the previous API mock while the next API mock is loading",
async () => {
+ deleteMockRequest.mockResolvedValue({ code: 200, message: "Deleted" });
+
+ const apiMock = {
+ body: "{}",
+ header: { "X-Test": "value" },
+ host: "api.example.com",
+ id: "mock-a",
+ pathVariable: "",
+ port: "443",
+ query: "[]",
+ url: "https://api.example.com/a",
+ };
+ const apiData = { envProps: [] };
+ const { rerender } = render(
+ <ApiContext.Provider
+ value={{
+ apiData,
+ apiDetail: {
+ apiPath: "/a",
+ httpMethod: 0,
+ id: "api-a",
+ },
+ apiMock,
+ }}
+ >
+ <ApiDebug />
+ </ApiContext.Provider>,
+ );
+
+ rerender(
+ <ApiContext.Provider
+ value={{
+ apiData,
+ apiDetail: {
+ apiPath: "/b",
+ httpMethod: 0,
+ id: "api-b",
+ },
+ apiMock,
+ }}
+ >
+ <ApiDebug />
+ </ApiContext.Provider>,
+ );
+
+ fireEvent.click(screen.getByText("SHENYU.DOCUMENT.APIDOC.DEBUG.MOCK.RESET"));
+
+ await waitFor(() => {
+ expect(deleteMockRequest).not.toHaveBeenCalled();
+ });
+});