fix(ui): keep provider wizard footer callbacks fresh

- Preserve latest footer action closures without redundant renders
- Cover callback freshness and semantic accessibility assertions
This commit is contained in:
Alan Buscaglia
2026-07-20 11:34:34 +02:00
parent 1c7c5ca5e9
commit 79e595f36e
3 changed files with 49 additions and 15 deletions
@@ -274,6 +274,12 @@ describe("useProviderWizardController", () => {
it("does not rerender when setting a semantically unchanged footer config", () => {
// Given
const onOpenChange = vi.fn();
const firstOnBack = vi.fn();
const firstOnSecondaryAction = vi.fn();
const firstOnAction = vi.fn();
const latestOnBack = vi.fn();
const latestOnSecondaryAction = vi.fn();
const latestOnAction = vi.fn();
let renderCount = 0;
const { result } = renderHook(() => {
renderCount += 1;
@@ -287,34 +293,58 @@ describe("useProviderWizardController", () => {
const firstFooterConfig = {
showBack: true,
backLabel: "Back",
onBack: vi.fn(),
onBack: firstOnBack,
showSecondaryAction: true,
secondaryActionLabel: "Cancel",
secondaryActionVariant: "outline" as const,
secondaryActionType: WIZARD_FOOTER_ACTION_TYPE.BUTTON,
onSecondaryAction: firstOnSecondaryAction,
showAction: true,
actionLabel: "Next",
actionDisabled: false,
actionType: WIZARD_FOOTER_ACTION_TYPE.BUTTON,
onAction: vi.fn(),
onAction: firstOnAction,
};
act(() => {
result.current.setFooterConfig(firstFooterConfig);
});
const renderCountAfterFirstUpdate = renderCount;
const footerConfigAfterFirstUpdate = result.current.resolvedFooterConfig;
// When
act(() => {
result.current.setFooterConfig({
...firstFooterConfig,
onBack: vi.fn(),
onAction: vi.fn(),
onBack: latestOnBack,
onSecondaryAction: latestOnSecondaryAction,
onAction: latestOnAction,
});
result.current.resolvedFooterConfig.onBack?.();
result.current.resolvedFooterConfig.onSecondaryAction?.();
result.current.resolvedFooterConfig.onAction?.();
});
// Then
expect(renderCount).toBe(renderCountAfterFirstUpdate);
expect(result.current.resolvedFooterConfig).toBe(
footerConfigAfterFirstUpdate,
);
expect(result.current.resolvedFooterConfig).toMatchObject({
showBack: true,
backLabel: "Back",
showSecondaryAction: true,
secondaryActionLabel: "Cancel",
secondaryActionVariant: "outline",
secondaryActionType: WIZARD_FOOTER_ACTION_TYPE.BUTTON,
showAction: true,
actionLabel: "Next",
actionDisabled: false,
actionType: WIZARD_FOOTER_ACTION_TYPE.BUTTON,
});
expect(firstOnBack).not.toHaveBeenCalled();
expect(firstOnSecondaryAction).not.toHaveBeenCalled();
expect(firstOnAction).not.toHaveBeenCalled();
expect(latestOnBack).toHaveBeenCalledTimes(1);
expect(latestOnSecondaryAction).toHaveBeenCalledTimes(1);
expect(latestOnAction).toHaveBeenCalledTimes(1);
});
it("does not override launch footer config in the controller", () => {
@@ -117,6 +117,11 @@ export function useProviderWizardController({
const [footerConfig, setFooterConfigState] =
useState<WizardFooterConfig>(EMPTY_FOOTER_CONFIG);
const footerConfigRef = useRef<WizardFooterConfig>(EMPTY_FOOTER_CONFIG);
const footerActionCallbacksRef = useRef({
onBack: () => footerConfigRef.current.onBack?.(),
onSecondaryAction: () => footerConfigRef.current.onSecondaryAction?.(),
onAction: () => footerConfigRef.current.onAction?.(),
});
const [providerTypeHint, setProviderTypeHint] = useState<ProviderType | null>(
null,
);
@@ -268,13 +273,16 @@ export function useProviderWizardController({
typeof nextFooterConfig === "function"
? nextFooterConfig(currentFooterConfig)
: nextFooterConfig;
footerConfigRef.current = resolvedNextFooterConfig;
if (isSameFooterConfig(currentFooterConfig, resolvedNextFooterConfig)) {
return;
}
footerConfigRef.current = resolvedNextFooterConfig;
setFooterConfigState(resolvedNextFooterConfig);
setFooterConfigState({
...resolvedNextFooterConfig,
...footerActionCallbacksRef.current,
});
};
const openOrganizationsFlow = () => {
@@ -81,12 +81,8 @@ describe("ProviderWizardModal", () => {
// Then
const dialog = screen.getByRole("dialog", { name: /adding a provider/i });
const descriptionId = dialog.getAttribute("aria-describedby");
expect(descriptionId).toBeTruthy();
expect(document.getElementById(descriptionId ?? "")).toHaveTextContent(
/connect or update a provider/i,
);
expect(dialog).toHaveAccessibleDescription(/connect or update a provider/i);
});
it("shows the launch progress step when update mode reaches launch", async () => {