You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React组件状态联动UI测试问题:输入有效地址后按钮未启用

问题:输入有效钱包地址后,测试无法验证按钮启用状态

我已经给expect(input.value)添加了waitFor(),消除了act错误,但测试仍失败。需要验证的场景是:当WalletAddressInput组件输入有效钱包地址后,ReqTokensButton组件变为可用状态。


组件代码

FaucetCard.js

export default function FaucetCard({ setSuccess, setError }) {
  const [walletAddress, setWalletAddress] = useState("");
  const [showTransactionCard, setShowTransactionCard] = useState(false);
  const [walletBalance, setWalletBalance] = useState(null);
  const [txHash, setTxHash] = useState(null);

  async function callContract() {
    if (!walletAddress) {
      setError("Please enter a valid wallet address!");
      return;
    }

    try {
      setSuccess(
        "Requesting tokens... It may take a few seconds to mine on the testnet."
      );
      const api = new Axios();
      const res = await api.requestTokens(walletAddress);
      setSuccess(res.message);
      setWalletBalance(res.balance);
      setTxHash(res.txHash);
      setShowTransactionCard(true);
    } catch (error) {
      setSuccess("");
      const message = error?.response?.data?.error
        ? JSON.stringify(error.response.data.error)
        : error.message;
      const msg = message ? message : "Check console for error";
      setError(`Something went wrong! Error: ${msg}`);
      console.log(error);
      setShowTransactionCard(false);
    }
  }

  return (
    <div className="card" data-testid="faucet-card">
      <div className="card-header">
        <h1>Faucet</h1>
        <img src={drip} alt="drippy" />
      </div>
      <div className="button">
        <WalletAddressInput
          type="text"
          variant="outlined"
          placeholder="Address..."
          label="Wallet Address"
          value={walletAddress}
          onChange={(e) => setWalletAddress(e.target.value)}
        />
        <ReqTokensButton
          onClick={callContract}
          isDisabled={!isAddress(walletAddress)}
        />
      </div>
      {showTransactionCard && (
        <div className="transaction-card">
          <TransactionCard
            title="Transaction success! "
            txHash={txHash}
            balance={walletBalance}
          />
        </div>
      )}
    </div>
  );
}

isAddress 是从ethers.js库导入的函数,根据输入地址是否有效返回布尔值。

WalletAddressInput.js

const WalletAddressInput = styled(TextField)({
  width: "100%",
  marginBottom: "1em",
  "& .MuiOutlinedInput-root": {
    "& fieldset": {
      borderColor: "#148FA0",
      borderRadius: ".25em",
    },
    "&:hover fieldset": {
      borderColor: "#117684",
    },
    "&.Mui-focused fieldset": {
      borderColor: "#148FA0",
    },
  },
  "& .MuiInputBase-input": {
    fontFamily: "Rubik",
    fontWeight: "400",
    fontSize: "16px",
    color: "#333",
  },
  "& .MuiFormLabel-root": {
    fontFamily: "Rubik",
    fontWeight: "400",
    fontSize: "16px",
    color: "#148FA0",
    "&.Mui-focused": {
      color: "#117684",
    },
  },
});

export default WalletAddressInput;

ReqTokensButton.js

const CustomButton = styled(Button)({
  borderRadius: ".25em",
  width: "252px",
  height: "56px",
  margin: "0 0 0 20px",
  padding: "0",
  "& .MuiTouchRipple-root": {
    color: "#fff",
  },
  boxShadow: "0px 4px 4px rgba(0, 0, 0, 0.25)",
  background: "#148FA0",
  "&:hover": {
    background: "#117684",
    cursor: "pointer",
  },
  border: "none",
  fontFamily: "Rubik",
  fontWeight: "400",
  fontSize: "16px",
  lineHeight: "19px",
  textAlign: "center",
  color: "#FFFFFF",

  // Add styles for the disabled state
  "&:disabled": {
    background: "#B0B0B0",  // Grey background when disabled
    color: "#FFFFFF",        // Keep text white or adjust as needed
    boxShadow: "none",       // Remove shadow to make it look flat
    cursor: "not-allowed",   // Change cursor to not-allowed to indicate it's disabled
  },
});

export default function ReqTokensButton({ onClick, isDisabled }) {
  return (
    <CustomButton data-testid="request-tokens-button" onClick={onClick} disabled={isDisabled}>
      Request Tokens
    </CustomButton>
  );
}

测试代码

jest.mock("ethers/lib/utils.js", () => ({
  isAddress: jest.fn(),
}));

const validWalletAddress = "0x117875b555f.......";

describe("Request Tokens", () => {
  beforeEach(() => {
    // Reset mocks before each test
    isAddress.mockReset();
  });

it.only("enables the request tokens button if the wallet address input has a valid address", async () => {
    render(<FaucetCard setSuccess={jest.fn()} setError={jest.fn()} />)

    const input = screen.getByRole("textbox");
    expect(input).toBeInTheDocument();
    const button = screen.getByTestId("request-tokens-button");
    expect(button).toBeInTheDocument();

    // Simulate user entering a valid wallet address
    userEvent.type(input, validWalletAddress);

    expect(input.value).toBe("0x117875b555f.........");
    await waitFor(() => {
      expect(input.value).toBe("0x117875b555f........");
    });

    expect(button).not.toBeDisabled();
  });
});

此前遇到的错误(已解决)

When testing, code that causes React state updates should be wrapped into act(...):

act(() => {
  /* fire events that update state */
});
/* assert on the output */

This ensures that you're testing the behavior the user would see in the browser.

目前UI实际运行正常,但测试始终失败,找不到问题根源。


内容的提问来源于stack exchange,提问作者melly18

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 11:34:51