Skip to content

Commit 70ed4bc

Browse files
HariniMalothu17Harini Malothu
andauthored
Add button property mouse events (#15819)
* Workflow for stale branches * Workflow for stale branches * Implement button property * Change files * Forked BaseViewProps,Added examples * Updated example changes * fix lint * added e2e test cases * update snapshots * update snapshot * updated test cases --------- Co-authored-by: Harini Malothu <hmalothu@microsoft.com>
1 parent 8ca53cf commit 70ed4bc

11 files changed

Lines changed: 1331 additions & 27 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "none",
3+
"comment": "Implement button property",
4+
"packageName": "react-native-windows",
5+
"email": "hmalothu@microsoft.com",
6+
"dependentChangeType": "none"
7+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation.
3+
* Licensed under the MIT License.
4+
*
5+
* @flow strict-local
6+
* @format
7+
*/
8+
9+
'use strict';
10+
11+
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
12+
13+
const React = require('react');
14+
const {StyleSheet, Text, View} = require('react-native');
15+
16+
function buttonLabel(button: number): string {
17+
switch (button) {
18+
case 0:
19+
return 'Left';
20+
case 1:
21+
return 'Middle';
22+
case 2:
23+
return 'Right';
24+
case 3:
25+
return 'X1';
26+
case 4:
27+
return 'X2';
28+
default:
29+
return `Unknown(${button})`;
30+
}
31+
}
32+
33+
function PointerDownButtonExample(): React.Node {
34+
const [text, setText] = React.useState(
35+
'Click the box to test pointer events',
36+
);
37+
return (
38+
<View>
39+
<Text accessible={true} testID="pointer-button-state">{text}</Text>
40+
<View
41+
accessible={true}
42+
testID="pointer-button-target"
43+
style={styles.targetBox}
44+
onPointerDown={(e: any) => {
45+
const {button, buttons} = e.nativeEvent;
46+
setText(
47+
`PointerDown: ${buttonLabel(button)} (button=${button}, buttons=${buttons})`,
48+
);
49+
}}
50+
/>
51+
</View>
52+
);
53+
}
54+
55+
function PointerUpButtonExample(): React.Node {
56+
const [text, setText] = React.useState(
57+
'Click the box to test pointer up events',
58+
);
59+
return (
60+
<View>
61+
<Text accessible={true} testID="pointer-up-button-state">{text}</Text>
62+
<View
63+
accessible={true}
64+
testID="pointer-up-button-target"
65+
style={styles.targetBox}
66+
onPointerUp={(e: any) => {
67+
const {button, buttons} = e.nativeEvent;
68+
setText(
69+
`PointerUp: ${buttonLabel(button)} (button=${button}, buttons=${buttons})`,
70+
);
71+
}}
72+
/>
73+
</View>
74+
);
75+
}
76+
77+
exports.displayName = 'PointerButtonExample';
78+
exports.framework = 'React';
79+
exports.category = 'Basic';
80+
exports.title = 'Pointer Button';
81+
exports.documentationURL =
82+
'https://developer.mozilla.org/docs/Web/API/PointerEvent/button';
83+
exports.description =
84+
'Tests that PointerEvent.button and PointerEvent.buttons are correctly populated.';
85+
86+
exports.examples = [
87+
{
88+
title: 'onPointerDown button property',
89+
description:
90+
'Click the box to verify the button property on onPointerDown events.',
91+
render: function (): React.Node {
92+
return <PointerDownButtonExample />;
93+
},
94+
},
95+
{
96+
title: 'onPointerUp button property',
97+
description:
98+
'Click the box to verify the button property on onPointerUp events.',
99+
render: function (): React.Node {
100+
return <PointerUpButtonExample />;
101+
},
102+
},
103+
] as Array<RNTesterModuleExample>;
104+
105+
const styles = StyleSheet.create({
106+
targetBox: {
107+
backgroundColor: 'magenta',
108+
width: 200,
109+
height: 200,
110+
margin: 10,
111+
borderRadius: 10,
112+
justifyContent: 'center',
113+
alignItems: 'center',
114+
},
115+
});

packages/@react-native-windows/tester/src/js/utils/RNTesterList.windows.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,11 @@ const APIs: Array<RNTesterModuleInfo> = ([
389389
category: 'Basic',
390390
module: require('../examples/PointerEvents/PointerEventsExample'),
391391
},
392+
{
393+
key: 'PointerButtonExample',
394+
category: 'Basic',
395+
module: require('../examples-win/Pointer/PointerButtonExample'),
396+
},
392397
{
393398
key: 'RTLExample',
394399
category: 'Basic',
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation.
3+
* Licensed under the MIT License.
4+
*
5+
* @format
6+
*/
7+
8+
import {dumpVisualTree} from '@react-native-windows/automation-commands';
9+
import {goToApiExample} from './RNTesterNavigation';
10+
import {verifyNoErrorLogs} from './Helpers';
11+
import {app} from '@react-native-windows/automation';
12+
13+
beforeAll(async () => {
14+
// If window is partially offscreen, tests will fail to click on certain elements
15+
await app.setWindowPosition(0, 0);
16+
await app.setWindowSize(1000, 1250);
17+
await goToApiExample('Pointer Button');
18+
});
19+
20+
afterEach(async () => {
21+
await verifyNoErrorLogs();
22+
});
23+
24+
const searchBox = async (input: string) => {
25+
const searchBox = await app.findElementByTestID('example_search');
26+
await app.waitUntil(
27+
async () => {
28+
await searchBox.setValue(input);
29+
if (input === '') {
30+
return (await searchBox.getText()) === 'Search...';
31+
} else {
32+
return (await searchBox.getText()) === input;
33+
}
34+
},
35+
{
36+
interval: 1500,
37+
timeout: 5000,
38+
timeoutMsg: `Unable to enter correct search text into test searchbox.`,
39+
},
40+
);
41+
};
42+
43+
describe('Pointer Button Tests', () => {
44+
test('onPointerDown reports correct button property on left click', async () => {
45+
await searchBox('onPointerDown');
46+
const component = await app.findElementByTestID('pointer-button-target');
47+
await component.waitForDisplayed({timeout: 5000});
48+
const dump = await dumpVisualTree('pointer-button-target');
49+
expect(dump).toMatchSnapshot();
50+
51+
// Left click triggers onPointerDown with button=0
52+
await component.click();
53+
const stateText = await app.findElementByTestID('pointer-button-state');
54+
55+
await app.waitUntil(
56+
async () => {
57+
const currentText = await stateText.getText();
58+
return currentText.includes('button=0');
59+
},
60+
{
61+
timeout: 5000,
62+
timeoutMsg:
63+
'State text not updated after onPointerDown with button property.',
64+
},
65+
);
66+
67+
const text = await stateText.getText();
68+
expect(text).toContain('PointerDown');
69+
expect(text).toContain('button=0');
70+
expect(text).toContain('buttons=1');
71+
});
72+
test('onPointerDown reports correct button property on middle click', async () => {
73+
await searchBox('onPointerDown');
74+
const component = await app.findElementByTestID('pointer-button-target');
75+
await component.waitForDisplayed({timeout: 5000});
76+
77+
// Middle click triggers onPointerDown with button=1
78+
await component.click({button: 'middle'});
79+
const stateText = await app.findElementByTestID('pointer-button-state');
80+
81+
await app.waitUntil(
82+
async () => {
83+
const currentText = await stateText.getText();
84+
return currentText.includes('button=1');
85+
},
86+
{
87+
timeout: 5000,
88+
timeoutMsg:
89+
'State text not updated after onPointerDown with middle button property.',
90+
},
91+
);
92+
93+
const text = await stateText.getText();
94+
expect(text).toContain('PointerDown');
95+
expect(text).toContain('button=1');
96+
expect(text).toContain('buttons=4');
97+
});
98+
test('onPointerDown reports correct button property on right click', async () => {
99+
await searchBox('onPointerDown');
100+
const component = await app.findElementByTestID('pointer-button-target');
101+
await component.waitForDisplayed({timeout: 5000});
102+
103+
// Right click triggers onPointerDown with button=2
104+
await component.click({button: 'right'});
105+
const stateText = await app.findElementByTestID('pointer-button-state');
106+
107+
await app.waitUntil(
108+
async () => {
109+
const currentText = await stateText.getText();
110+
return currentText.includes('button=2');
111+
},
112+
{
113+
timeout: 5000,
114+
timeoutMsg:
115+
'State text not updated after onPointerDown with right button property.',
116+
},
117+
);
118+
119+
const text = await stateText.getText();
120+
expect(text).toContain('PointerDown');
121+
expect(text).toContain('button=2');
122+
expect(text).toContain('buttons=2');
123+
});
124+
test('onPointerUp reports correct button property on left click', async () => {
125+
await searchBox('onPointerUp');
126+
const component = await app.findElementByTestID(
127+
'pointer-up-button-target',
128+
);
129+
await component.waitForDisplayed({timeout: 5000});
130+
const dump = await dumpVisualTree('pointer-up-button-target');
131+
expect(dump).toMatchSnapshot();
132+
133+
// Left click release triggers onPointerUp with button=0
134+
await component.click();
135+
const stateText = await app.findElementByTestID(
136+
'pointer-up-button-state',
137+
);
138+
139+
await app.waitUntil(
140+
async () => {
141+
const currentText = await stateText.getText();
142+
return currentText.includes('button=0');
143+
},
144+
{
145+
timeout: 5000,
146+
timeoutMsg:
147+
'State text not updated after onPointerUp with button property.',
148+
},
149+
);
150+
151+
const text = await stateText.getText();
152+
expect(text).toContain('PointerUp');
153+
expect(text).toContain('button=0');
154+
expect(text).toContain('buttons=0');
155+
});
156+
test('onPointerUp reports correct button property on middle click', async () => {
157+
await searchBox('onPointerUp');
158+
const component = await app.findElementByTestID(
159+
'pointer-up-button-target',
160+
);
161+
await component.waitForDisplayed({timeout: 5000});
162+
163+
// Middle click release triggers onPointerUp with button=1
164+
await component.click({button: 'middle'});
165+
const stateText = await app.findElementByTestID(
166+
'pointer-up-button-state',
167+
);
168+
169+
await app.waitUntil(
170+
async () => {
171+
const currentText = await stateText.getText();
172+
return currentText.includes('button=1');
173+
},
174+
{
175+
timeout: 5000,
176+
timeoutMsg:
177+
'State text not updated after onPointerUp with middle button property.',
178+
},
179+
);
180+
181+
const text = await stateText.getText();
182+
expect(text).toContain('PointerUp');
183+
expect(text).toContain('button=1');
184+
expect(text).toContain('buttons=0');
185+
});
186+
test('onPointerUp reports correct button property on right click', async () => {
187+
await searchBox('onPointerUp');
188+
const component = await app.findElementByTestID(
189+
'pointer-up-button-target',
190+
);
191+
await component.waitForDisplayed({timeout: 5000});
192+
193+
// Right click release triggers onPointerUp with button=2
194+
await component.click({button: 'right'});
195+
const stateText = await app.findElementByTestID(
196+
'pointer-up-button-state',
197+
);
198+
199+
await app.waitUntil(
200+
async () => {
201+
const currentText = await stateText.getText();
202+
return currentText.includes('button=2');
203+
},
204+
{
205+
timeout: 5000,
206+
timeoutMsg:
207+
'State text not updated after onPointerUp with right button property.',
208+
},
209+
);
210+
211+
const text = await stateText.getText();
212+
expect(text).toContain('PointerUp');
213+
expect(text).toContain('button=2');
214+
expect(text).toContain('buttons=0');
215+
});
216+
});

0 commit comments

Comments
 (0)