Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 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": "copilot@github.com",
"dependentChangeType": "patch"
}
32 changes: 32 additions & 0 deletions packages/e2e-test-app-fabric/test/LegacyTextInputTest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,38 @@ describe('LegacyTextInputTest', () => {
]);
*/
});

// Additional functional tests for event handlers
test('TextInput should trigger action upon onBlur', async () => {
const textInput = await textInputField();
await textInput.click(); // Focus

const multiLineTextInput = await multiLineTextInputField();
await multiLineTextInput.click(); // This should trigger onBlur on the first TextInput

await assertLogContains('onBlur');
});

test('TextInput should trigger action upon onFocus', async () => {
const textInput = await textInputField();
await textInput.click(); // This should trigger onFocus

await assertLogContains('onFocus');
});

test('TextInput should trigger action upon onChange', async () => {
const textInput = await textInputField();
await textInput.setValue('test');

await assertLogContains('onChange text: test');
});

test('TextInput should trigger action upon onSelectionChange', async () => {
const textInput = await textInputField();
await textInput.setValue('test');

await assertLogContains('onSelectionChange range: 4,4');
});
});

async function textInputField() {
Expand Down
267 changes: 267 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,271 @@ describe('TextInput Tests', () => {
const dump = await dumpVisualTree('textinput-searchbox');
expect(dump).toMatchSnapshot();
});

// Additional functional tests for specific task requirements
Copy link
Contributor

Choose a reason for hiding this comment

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

@copilot Remove the comment on line 957.

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
Contributor 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.

});

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
Contributor 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();

// Simulate focus by clicking
await component.click();

// 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
Contributor 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();

// Then blur by clicking somewhere else
const search = await app.findElementByTestID('example_search');
await search.click();

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

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

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();

// Check if the component shows focused state (style change indicates focus)
const dump = await dumpVisualTree('uncontrolled-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 Remove lines 1196-1197; they are not needed for this test.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed the unnecessary dumpVisualTree and expect calls as requested. See commit 4363ed3.

expect(dump).toMatchSnapshot();

// 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();

// Check if the component shows unfocused state
const dump = await dumpVisualTree('uncontrolled-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 Remove lines 1214-1215; they are not needed for this test.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed the unnecessary dumpVisualTree and expect calls as requested. See commit 4363ed3.

expect(dump).toMatchSnapshot();

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

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