From 51ba068b0294f443171e722002b6c0a893ed442b Mon Sep 17 00:00:00 2001 From: Joon Park Date: Wed, 5 Nov 2025 11:32:58 -0500 Subject: [PATCH] feat: Block button click as default behavior --- src/Form/hooks/useCheckButtonAction.ts | 7 +++--- src/Form/index.tsx | 2 +- src/Form/tests/index.spec.tsx | 34 ++++++++++++-------------- src/Form/tests/testMocks.tsx | 8 +++--- 4 files changed, 23 insertions(+), 28 deletions(-) diff --git a/src/Form/hooks/useCheckButtonAction.ts b/src/Form/hooks/useCheckButtonAction.ts index cf567a346..ce7bcf340 100644 --- a/src/Form/hooks/useCheckButtonAction.ts +++ b/src/Form/hooks/useCheckButtonAction.ts @@ -18,11 +18,10 @@ export function useCheckButtonAction( buttonActionStateRef.current?.isUserLogicRunning; const updateButtonActionState = (elementType: string, element: any) => { - // Set the state only when the element is a button and block_button_clicks is true - const isRunning = - elementType === 'button' && !!element?.properties?.block_button_clicks; + // Always block button clicks while actions run (default behavior) + const isButton = elementType === 'button'; - if (isRunning) { + if (isButton) { buttonActionStateRef.current = { button: element, isElementActionRunning: true diff --git a/src/Form/index.tsx b/src/Form/index.tsx index 15cd8940e..b73006886 100644 --- a/src/Form/index.tsx +++ b/src/Form/index.tsx @@ -1661,7 +1661,7 @@ function Form({ setLoaders((loaders: Record) => ({ ...loaders, [button.id]: { - showOn: bp.show_loading_icon, + showOn: bp.show_loading_icon ?? 'on_button', loader, type: bp.loading_icon ? bp.loading_file_type : 'default', repeat: button.repeat diff --git a/src/Form/tests/index.spec.tsx b/src/Form/tests/index.spec.tsx index 73cc7d94e..efd47fe3d 100644 --- a/src/Form/tests/index.spec.tsx +++ b/src/Form/tests/index.spec.tsx @@ -1,5 +1,5 @@ import { CheckButtonActionMod } from './testMocks'; -import { render, screen, fireEvent, cleanup } from '@testing-library/react'; +import { render, screen, fireEvent, cleanup, waitFor } from '@testing-library/react'; import { JSForm } from '..'; import FeatheryClient from '../../utils/featheryClient'; import internalState from '../../utils/internalState'; @@ -97,7 +97,7 @@ describe('useCheckButtonAction behavior', () => { CheckButtonActionMod._spies.clearLoadersRef.current = jest.fn(); }); - it('calls setButtonLoader when _setButtonLoading(true) with a tracked block_button_clicks', async () => { + it('calls setButtonLoader when _setButtonLoading(true) with a tracked button', async () => { // Arrange: inject a custom setButtonLoader const setButtonLoader = jest.fn(async () => {}); const api = CheckButtonActionMod.useCheckButtonAction(setButtonLoader); @@ -106,11 +106,10 @@ describe('useCheckButtonAction behavior', () => { await api._setButtonLoading(true); expect(setButtonLoader).not.toHaveBeenCalled(); - // Create tracked state via updateButtonActionState + // Create tracked state via updateButtonActionState (all buttons are now tracked) const el = { id: 'b-load', properties: { - block_button_clicks: true, actions: [] } }; @@ -132,11 +131,10 @@ describe('useCheckButtonAction behavior', () => { clearLoaders ); - // Create tracked button state, then end element action + // Create tracked button state (all buttons are tracked), then end element action const el = { id: 'b-clear', properties: { - block_button_clicks: true, actions: [] } }; @@ -153,20 +151,22 @@ describe('useCheckButtonAction behavior', () => { expect(clearLoaders).toHaveBeenCalledTimes(1); }); - it('JSForm flow: keeps state cleared when block_button_clicks is not set', async () => { + it('JSForm flow: sets button action state for all buttons (always blocks clicks)', async () => { render(); const btn = await screen.findByTestId('btn'); fireEvent.click(btn); - // GridMock uses actions: [] and block_button_clicks is not set, so state must remain null - expect(CheckButtonActionMod._spies.buttonActionStateRef.current).toBeNull(); - - // Running state should be false - expect(CheckButtonActionMod._spies.isButtonActionRunning()).toBe(false); + // All buttons now block clicks by default - verify updateButtonActionState was called + await waitFor(() => { + expect(CheckButtonActionMod._spies.updateButtonActionState).toHaveBeenCalledWith( + 'button', + expect.objectContaining({ id: 'b1' }) + ); + }); }); - it('sets running when block_button_clicks=true and triggers setButtonLoader while user logic running', async () => { + it('sets running for all buttons and triggers setButtonLoader while user logic running', async () => { // Initialize mocked hook with an injectable setButtonLoader const setButtonLoader = jest.fn(async () => {}); const api = CheckButtonActionMod.useCheckButtonAction(setButtonLoader); @@ -174,12 +174,11 @@ describe('useCheckButtonAction behavior', () => { const element = { id: 'b-button', properties: { - block_button_clicks: true, actions: [] } }; - // block_button_clicks=true should set the internal state + // All buttons now block clicks by default - should set the internal state api.updateButtonActionState('button', element); expect(CheckButtonActionMod._spies.buttonActionStateRef.current).toEqual({ @@ -214,7 +213,6 @@ describe('useCheckButtonAction behavior', () => { const el = { id: 'b-x', properties: { - block_button_clicks: true, actions: [] } }; @@ -226,10 +224,10 @@ describe('useCheckButtonAction behavior', () => { it('ignores non-button element types in updateButtonActionState', () => { const api = CheckButtonActionMod.useCheckButtonAction(jest.fn()); - // container element should not be tracked + // container element should not be tracked (only buttons are tracked) api.updateButtonActionState('container', { id: 'c1', - properties: { block_button_clicks: true } + properties: {} }); expect(CheckButtonActionMod._spies.buttonActionStateRef.current).toBeNull(); diff --git a/src/Form/tests/testMocks.tsx b/src/Form/tests/testMocks.tsx index 775d48705..75d7f5cd1 100644 --- a/src/Form/tests/testMocks.tsx +++ b/src/Form/tests/testMocks.tsx @@ -363,12 +363,10 @@ jest.mock('../hooks/useCheckButtonAction', () => { // Simplified implementations const updateButtonActionState = jest.fn( (elementType: string, element: any) => { - const isRunning = - (elementType === 'button' && - element?.properties?.block_button_clicks) ?? - false; + // Always block button clicks while actions run (default behavior) + const isButton = elementType === 'button'; - buttonActionStateRef.current = isRunning + buttonActionStateRef.current = isButton ? { button: element, isElementActionRunning: true } : null; }