Skip to content

Add Functional Tests for TextInput Component #14767

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

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Add comprehensive functional tests for TextInput component in E2E test app (Fabric)",
"packageName": "react-native-windows",
"email": "[email protected]",
"dependentChangeType": "patch"
}
258 changes: 258 additions & 0 deletions packages/e2e-test-app-fabric/test/TextInputComponentTest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -953,4 +953,262 @@ describe('TextInput Tests', () => {
const dump = await dumpVisualTree('textinput-searchbox');
expect(dump).toMatchSnapshot();
});

test('TextInput should not be editable when editable set to false', async () => {
const component = await app.findElementByTestID('textinput-not-editable2');
await component.waitForDisplayed({timeout: 5000});
const dump = await dumpVisualTree('textinput-not-editable2');
expect(dump).toMatchSnapshot();

// Attempt to set value and verify it doesn't work
const originalText = await component.getText();
await component.setValue('Should not work');
expect(await component.getText()).toBe(originalText);
});

test('TextInput should take up to max length input when maxLength set', async () => {
const component = await app.findElementByTestID('rewrite_sp_underscore_input');
await component.waitForDisplayed({timeout: 5000});
const dump = await dumpVisualTree('rewrite_sp_underscore_input');
expect(dump).toMatchSnapshot();

// Test that input is limited by maxLength (this component has maxLength=20)
await app.waitUntil(
async () => {
await component.setValue('This is a very long text that should be truncated because it exceeds the limit');
const text = await component.getText();
// The component replaces spaces with underscores and has maxLength=20
return text.length <= 20;
},
{
interval: 1500,
timeout: 5000,
timeoutMsg: `MaxLength limitation not working correctly.`,
},
);

// Verify that the text was actually limited
const finalText = await component.getText();
expect(finalText.length).toBeLessThanOrEqual(20);
});

test('TextInput input should wrap to multiple lines when multiline set to true', async () => {
const component = await app.findElementByTestID('textinput-multiline-topleft');
await component.waitForDisplayed({timeout: 5000});
const dump = await dumpVisualTree('textinput-multiline-topleft');
expect(dump).toMatchSnapshot();

// Set a long text that should wrap to multiple lines
await app.waitUntil(
async () => {
await component.setValue('This is a very long text that should wrap to multiple lines when the multiline property is set to true.');
return (await component.getText()).includes('This is a very long text');
},
{
interval: 1500,
timeout: 5000,
timeoutMsg: `Unable to enter text in multiline TextInput.`,
},
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot This test case is not validating that the text has actually wrapped to multiple lines. I think you need to add another dumpVisualTree call after the text has been set.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added dumpVisualTree call after text input to validate multiline text wrapping in commit e06b6db.


// Capture visual tree after text has been set to validate wrapping
const dumpAfterText = await dumpVisualTree('textinput-multiline-topleft');
expect(dumpAfterText).toMatchSnapshot();
});

test('TextInput should not be editable when readOnly set to true', async () => {
const component = await app.findElementByTestID('textinput-readyonly');
await component.waitForDisplayed({timeout: 5000});
const dump = await dumpVisualTree('textinput-readyonly');
expect(dump).toMatchSnapshot();

// Attempt to set value and verify it doesn't work
const originalText = await component.getText();
await component.setValue('Should not work');
expect(await component.getText()).toBe(originalText);
});

test('TextInput should trigger action upon onPressIn', async () => {
// Using the existing textinput-press component which handles onPressIn
await searchBox('onPressIn');
const component = await app.findElementByTestID('textinput-press');
await component.waitForDisplayed({timeout: 5000});
const dump = await dumpVisualTree('textinput-press');
expect(dump).toMatchSnapshot();

// Trigger onPressIn by clicking
await component.click();
const stateText = await app.findElementByTestID('textinput-state-display');

await app.waitUntil(
async () => {
const currentText = await stateText.getText();
return currentText === 'Holding down the click/touch';
},
{
timeout: 5000,
timeoutMsg: 'onPressIn event not triggered correctly.',
},
);

expect(await stateText.getText()).toBe('Holding down the click/touch');
// Reset search to avoid interfering with other tests
const search = await app.findElementByTestID('example_search');
await search.setValue('');
});

test('TextInput should trigger action upon onPressOut', async () => {
// Using the existing textinput-press component which handles onPressOut
await searchBox('onPressIn');
const component = await app.findElementByTestID('textinput-press');
await component.waitForDisplayed({timeout: 5000});
const stateText = await app.findElementByTestID('textinput-state-display');

// Trigger onPressIn first, then onPressOut
await component.click(); // This should trigger onPressIn

// Move to another element to trigger onPressOut
const search = await app.findElementByTestID('example_search');
await search.click();

await app.waitUntil(
async () => {
const currentText = await stateText.getText();
return currentText === 'Released click/touch';
},
{
timeout: 5000,
timeoutMsg: 'onPressOut event not triggered correctly.',
},
);

expect(await stateText.getText()).toBe('Released click/touch');
// Reset search to avoid interfering with other tests
await search.setValue('');
});

test('TextInput text should clear upon clear() call', async () => {
// Using the rewrite example which has a clear button
const component = await app.findElementByTestID('rewrite_clear_input');
await component.waitForDisplayed({timeout: 5000});
const dump = await dumpVisualTree('rewrite_clear_input');
expect(dump).toMatchSnapshot();

// Set some text first
await app.waitUntil(
async () => {
await component.setValue('Hello World');
return (await component.getText()) === 'HelloWorld'; // Spaces are removed in this component
},
{
interval: 1500,
timeout: 5000,
timeoutMsg: `Unable to enter correct text.`,
},
);

// Click the clear button to test clear() method
const clearButton = await app.findElementByTestID('rewrite_clear_button');
await clearButton.click();

// Verify text was cleared
await app.waitUntil(
async () => {
return (await component.getText()) === '';
},
{
interval: 1500,
timeout: 5000,
timeoutMsg: `Clear method did not work correctly.`,
},
);

expect(await component.getText()).toBe('');
});

test('TextInput value prop should be the text displayed in the TextInput', async () => {
// Using the rewrite example which uses a controlled value prop
const component = await app.findElementByTestID('rewrite_sp_underscore_input');
await component.waitForDisplayed({timeout: 5000});
const dump = await dumpVisualTree('rewrite_sp_underscore_input');
expect(dump).toMatchSnapshot();

// Test that the value prop controls what's displayed
await app.waitUntil(
async () => {
await component.setValue('test value');
// This component replaces spaces with underscores
return (await component.getText()) === 'test_value';
},
{
interval: 1500,
timeout: 5000,
timeoutMsg: `Value prop not working correctly.`,
},
);

expect(await component.getText()).toBe('test_value');
});

test('TextInput should focus upon .focus() call', async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot This test is not using the .focus() API. To test the functionality of the .focus() API the example should include focusing the control using the .focus API rather than by clicking the control.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the test to use the .focus() API directly via await (component as any).focus() instead of just clicking the control. See commit 4363ed3.

// Test focus behavior using the uncontrolled component which changes style on focus
const component = await app.findElementByTestID('uncontrolled-textinput');
await component.waitForDisplayed({timeout: 5000});

// Initial state - not focused
let dump = await dumpVisualTree('uncontrolled-textinput');
expect(dump).toMatchSnapshot();

// Call focus() method directly
await (component as any).focus();

// After focus, the style should change (component has onFocus handler)
dump = await dumpVisualTree('uncontrolled-textinput');
expect(dump).toMatchSnapshot();
});

test('TextInput should lose focus upon .blur() call', async () => {
// Test blur behavior using the uncontrolled component
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot This test is not using the .blur() API. To test the functionality of the .blur() API the example should include focusing the control using the .blur API rather than by clicking the control.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the test to use the .blur() API directly via await (component as any).blur() instead of clicking elsewhere to trigger blur. See commit 4363ed3.

const component = await app.findElementByTestID('uncontrolled-textinput');
await component.waitForDisplayed({timeout: 5000});

// Focus first
await component.click();

// Call blur() method directly
await (component as any).blur();

// After blur, the style should revert (component has onBlur handler)
const dump = await dumpVisualTree('uncontrolled-textinput');
expect(dump).toMatchSnapshot();
});

test('TextInput isFocused() should return true when the TextInput is focused', async () => {
// Simulate isFocused behavior using the uncontrolled component's style changes
const component = await app.findElementByTestID('uncontrolled-textinput');
await component.waitForDisplayed({timeout: 5000});

// Focus the component
await component.click();

// The component should be in focused state (we can't directly test isFocused()
// but we can verify the visual state that indicates focus)
expect(await component.isFocused()).toBe(true);
});

test('TextInput isFocused() should return false when the TextInput is not focused', async () => {
// Simulate isFocused behavior using the uncontrolled component
const component = await app.findElementByTestID('uncontrolled-textinput');
await component.waitForDisplayed({timeout: 5000});

// Ensure component is not focused by clicking elsewhere
const search = await app.findElementByTestID('example_search');
await search.click();

// The component should not be in focused state
expect(await component.isFocused()).toBe(false);

// Reset search
await search.setValue('');
});
});