-
-
Notifications
You must be signed in to change notification settings - Fork 496
Fix #869: Synchronize field name and value when name changes dynamically #1078
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+276
−2
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
ecc0c30
Fix #869: Synchronize field name and value when name changes dynamically
b43e92a
Fix lint: Convert test to .js format to match project conventions
37f517a
Update test comments to reflect that bug is now fixed
c7ea834
Add assertions for render spy calls per CodeRabbit feedback
fad4873
Tighten test assertions: after rerender only expect field 'b'
8babb88
Add checkbox and radio tests for dynamic name changes
b037c53
Merge remote-tracking branch 'origin/main' into fix-869-dynamic-field…
66e7730
Docs: add file-level comment clarifying test coverage scope
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,267 @@ | ||
| /** | ||
| * @jest-environment jsdom | ||
| */ | ||
| // Tests for dynamic Field name changes (issue #869). | ||
| // Covers text inputs, checkboxes, and radio buttons. | ||
| import React from 'react' | ||
| import { render, cleanup, act } from '@testing-library/react' | ||
| import '@testing-library/jest-dom' | ||
| import Form from './ReactFinalForm' | ||
| import Field from './Field' | ||
|
|
||
| describe('useField - Dynamic Name (Issue #869)', () => { | ||
| afterEach(cleanup) | ||
|
|
||
| it('should keep name and value in sync when field name changes', () => { | ||
| const renderSpy = jest.fn() | ||
|
|
||
| const TestComponent = ({ fieldName }) => { | ||
| return ( | ||
| <Form | ||
| onSubmit={() => {}} | ||
| initialValues={{ a: 'value-a', b: 'value-b' }} | ||
| > | ||
| {() => ( | ||
| <Field name={fieldName}> | ||
| {({ input }) => { | ||
| // Log every render to track name/value sync | ||
| renderSpy(input.name, input.value) | ||
| return <input {...input} data-testid="field" /> | ||
| }} | ||
| </Field> | ||
| )} | ||
| </Form> | ||
| ) | ||
| } | ||
|
|
||
| const { rerender } = render(<TestComponent fieldName="a" />) | ||
|
|
||
| // Initial render - field 'a' | ||
| expect(renderSpy).toHaveBeenCalledWith('a', 'value-a') | ||
|
|
||
| renderSpy.mockClear() | ||
|
|
||
| // Change field name from 'a' to 'b' | ||
| act(() => { | ||
| rerender(<TestComponent fieldName="b" />) | ||
| }) | ||
|
|
||
| // Verify all renders after name change have name='b' and value='value-b' | ||
| const calls = renderSpy.mock.calls | ||
|
|
||
| // Ensure Field actually rendered | ||
| expect(calls.length).toBeGreaterThan(0) | ||
|
|
||
| // After rerender with fieldName="b", ALL calls should be for field 'b' | ||
| calls.forEach(call => { | ||
| const [name, value] = call | ||
| expect(name).toBe('b') | ||
| expect(value).toBe('value-b') | ||
| }) | ||
| }) | ||
|
|
||
| it('should have correct value immediately after name change (no stale renders)', () => { | ||
| const TestComponent = ({ fieldName }) => { | ||
| return ( | ||
| <Form | ||
| onSubmit={() => {}} | ||
| initialValues={{ a: 'value-a', b: 'value-b' }} | ||
| > | ||
| {() => ( | ||
| <Field name={fieldName}> | ||
| {({ input }) => ( | ||
| <div> | ||
| <span data-testid="name">{input.name}</span> | ||
| <span data-testid="value">{input.value}</span> | ||
| </div> | ||
| )} | ||
| </Field> | ||
| )} | ||
| </Form> | ||
| ) | ||
| } | ||
|
|
||
| const { rerender, getByTestId } = render(<TestComponent fieldName="a" />) | ||
|
|
||
| expect(getByTestId('name')).toHaveTextContent('a') | ||
| expect(getByTestId('value')).toHaveTextContent('value-a') | ||
|
|
||
| // Change field name | ||
| act(() => { | ||
| rerender(<TestComponent fieldName="b" />) | ||
| }) | ||
|
|
||
| // Immediately after rerender, name and value should be in sync | ||
| expect(getByTestId('name')).toHaveTextContent('b') | ||
| expect(getByTestId('value')).toHaveTextContent('value-b') | ||
| }) | ||
|
|
||
| it('should keep name and checked in sync when checkbox field name changes', () => { | ||
| const renderSpy = jest.fn() | ||
|
|
||
| const TestComponent = ({ fieldName }) => { | ||
| return ( | ||
| <Form | ||
| onSubmit={() => {}} | ||
| initialValues={{ a: true, b: false }} | ||
| > | ||
| {() => ( | ||
| <Field name={fieldName} type="checkbox"> | ||
| {({ input }) => { | ||
| // Log every render to track name/checked sync | ||
| renderSpy(input.name, input.checked) | ||
| return <input {...input} data-testid="field" /> | ||
| }} | ||
| </Field> | ||
| )} | ||
| </Form> | ||
| ) | ||
| } | ||
|
|
||
| const { rerender } = render(<TestComponent fieldName="a" />) | ||
|
|
||
| // Initial render - field 'a' checked | ||
| expect(renderSpy).toHaveBeenCalledWith('a', true) | ||
|
|
||
| renderSpy.mockClear() | ||
|
|
||
| // Change field name from 'a' to 'b' | ||
| act(() => { | ||
| rerender(<TestComponent fieldName="b" />) | ||
| }) | ||
|
|
||
| // Verify all renders after name change have name='b' and checked=false | ||
| const calls = renderSpy.mock.calls | ||
|
|
||
| // Ensure Field actually rendered | ||
| expect(calls.length).toBeGreaterThan(0) | ||
|
|
||
| // After rerender with fieldName="b", ALL calls should be for field 'b' | ||
| calls.forEach(call => { | ||
| const [name, checked] = call | ||
| expect(name).toBe('b') | ||
| expect(checked).toBe(false) | ||
| }) | ||
| }) | ||
|
|
||
| it('should have correct checked immediately after checkbox name change', () => { | ||
| const TestComponent = ({ fieldName }) => { | ||
| return ( | ||
| <Form | ||
| onSubmit={() => {}} | ||
| initialValues={{ a: true, b: false }} | ||
| > | ||
| {() => ( | ||
| <Field name={fieldName} type="checkbox"> | ||
| {({ input }) => ( | ||
| <div> | ||
| <span data-testid="name">{input.name}</span> | ||
| <span data-testid="checked">{String(input.checked)}</span> | ||
| </div> | ||
| )} | ||
| </Field> | ||
| )} | ||
| </Form> | ||
| ) | ||
| } | ||
|
|
||
| const { rerender, getByTestId } = render(<TestComponent fieldName="a" />) | ||
|
|
||
| expect(getByTestId('name')).toHaveTextContent('a') | ||
| expect(getByTestId('checked')).toHaveTextContent('true') | ||
|
|
||
| // Change field name | ||
| act(() => { | ||
| rerender(<TestComponent fieldName="b" />) | ||
| }) | ||
|
|
||
| // Immediately after rerender, name and checked should be in sync | ||
| expect(getByTestId('name')).toHaveTextContent('b') | ||
| expect(getByTestId('checked')).toHaveTextContent('false') | ||
| }) | ||
|
|
||
| it('should keep name and checked in sync when radio field name changes', () => { | ||
| const renderSpy = jest.fn() | ||
|
|
||
| const TestComponent = ({ fieldName }) => { | ||
| return ( | ||
| <Form | ||
| onSubmit={() => {}} | ||
| initialValues={{ a: 'option1', b: 'option2' }} | ||
| > | ||
| {() => ( | ||
| <Field name={fieldName} type="radio" value="option2"> | ||
| {({ input }) => { | ||
| // Log every render to track name/checked sync | ||
| renderSpy(input.name, input.checked) | ||
| return <input {...input} data-testid="field" /> | ||
| }} | ||
| </Field> | ||
| )} | ||
| </Form> | ||
| ) | ||
| } | ||
|
|
||
| const { rerender } = render(<TestComponent fieldName="a" />) | ||
|
|
||
| // Initial render - field 'a' has value 'option1', not checked for 'option2' | ||
| expect(renderSpy).toHaveBeenCalledWith('a', false) | ||
|
|
||
| renderSpy.mockClear() | ||
|
|
||
| // Change field name from 'a' to 'b' | ||
| act(() => { | ||
| rerender(<TestComponent fieldName="b" />) | ||
| }) | ||
|
|
||
| // Verify all renders after name change have name='b' and checked=true | ||
| const calls = renderSpy.mock.calls | ||
|
|
||
| // Ensure Field actually rendered | ||
| expect(calls.length).toBeGreaterThan(0) | ||
|
|
||
| // After rerender with fieldName="b", ALL calls should be for field 'b' | ||
| // Field 'b' has value 'option2', so radio with value="option2" should be checked | ||
| calls.forEach(call => { | ||
| const [name, checked] = call | ||
| expect(name).toBe('b') | ||
| expect(checked).toBe(true) | ||
| }) | ||
| }) | ||
|
|
||
| it('should have correct checked immediately after radio name change', () => { | ||
| const TestComponent = ({ fieldName }) => { | ||
| return ( | ||
| <Form | ||
| onSubmit={() => {}} | ||
| initialValues={{ a: 'option1', b: 'option2' }} | ||
| > | ||
| {() => ( | ||
| <Field name={fieldName} type="radio" value="option2"> | ||
| {({ input }) => ( | ||
| <div> | ||
| <span data-testid="name">{input.name}</span> | ||
| <span data-testid="checked">{String(input.checked)}</span> | ||
| </div> | ||
| )} | ||
| </Field> | ||
| )} | ||
| </Form> | ||
| ) | ||
| } | ||
|
|
||
| const { rerender, getByTestId } = render(<TestComponent fieldName="a" />) | ||
|
|
||
| expect(getByTestId('name')).toHaveTextContent('a') | ||
| expect(getByTestId('checked')).toHaveTextContent('false') | ||
|
|
||
| // Change field name | ||
| act(() => { | ||
| rerender(<TestComponent fieldName="b" />) | ||
| }) | ||
|
|
||
| // Immediately after rerender, name and checked should be in sync | ||
| expect(getByTestId('name')).toHaveTextContent('b') | ||
| expect(getByTestId('checked')).toHaveTextContent('true') | ||
| }) | ||
| }) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.