mirror of
https://github.com/zoriya/react-native-web.git
synced 2026-06-04 11:04:58 +00:00
[fix] Pressable cursor and touch-action styles
Also add unit tests for Pressable. Fix #1764
This commit is contained in:
+187
@@ -0,0 +1,187 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/Pressable default 1`] = `
|
||||
<div
|
||||
class="css-view-1dbjc4n r-cursor-1loqt21 r-touchAction-1otgn73"
|
||||
data-focusable="true"
|
||||
tabindex="0"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/Pressable focus interaction 1`] = `
|
||||
<div
|
||||
class="css-view-1dbjc4n r-cursor-1loqt21 r-touchAction-1otgn73"
|
||||
data-focusable="true"
|
||||
tabindex="0"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/Pressable focus interaction 2`] = `
|
||||
<div
|
||||
class="css-view-1dbjc4n r-cursor-1loqt21 r-touchAction-1otgn73"
|
||||
data-focusable="true"
|
||||
data-focusvisible-polyfill="true"
|
||||
style="outline: focus-ring;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
data-testid="focus-content"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/Pressable focus interaction 3`] = `
|
||||
<div
|
||||
class="css-view-1dbjc4n r-cursor-1loqt21 r-touchAction-1otgn73"
|
||||
data-focusable="true"
|
||||
style=""
|
||||
tabindex="0"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/Pressable hover interaction 1`] = `
|
||||
<div
|
||||
class="css-view-1dbjc4n r-cursor-1loqt21 r-touchAction-1otgn73"
|
||||
data-focusable="true"
|
||||
tabindex="0"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/Pressable hover interaction 2`] = `
|
||||
<div
|
||||
class="css-view-1dbjc4n r-cursor-1loqt21 r-touchAction-1otgn73"
|
||||
data-focusable="true"
|
||||
style="outline: hover-ring;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
data-testid="hover-content"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/Pressable hover interaction 3`] = `
|
||||
<div
|
||||
class="css-view-1dbjc4n r-cursor-1loqt21 r-touchAction-1otgn73"
|
||||
data-focusable="true"
|
||||
style=""
|
||||
tabindex="0"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/Pressable press interaction 1`] = `
|
||||
<div
|
||||
class="css-view-1dbjc4n r-cursor-1loqt21 r-touchAction-1otgn73"
|
||||
data-focusable="true"
|
||||
tabindex="0"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/Pressable press interaction 2`] = `
|
||||
<div
|
||||
class="css-view-1dbjc4n r-cursor-1loqt21 r-touchAction-1otgn73"
|
||||
data-focusable="true"
|
||||
style="outline: press-ring;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
data-testid="press-content"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/Pressable press interaction 3`] = `
|
||||
<div
|
||||
class="css-view-1dbjc4n r-cursor-1loqt21 r-touchAction-1otgn73"
|
||||
data-focusable="true"
|
||||
style=""
|
||||
tabindex="0"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/Pressable prop "accessibilityLabel" value is set 1`] = `
|
||||
<div
|
||||
aria-label="label"
|
||||
class="css-view-1dbjc4n r-cursor-1loqt21 r-touchAction-1otgn73"
|
||||
data-focusable="true"
|
||||
tabindex="0"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/Pressable prop "accessibilityLiveRegion" value is set 1`] = `
|
||||
<div
|
||||
aria-live="polite"
|
||||
class="css-view-1dbjc4n r-cursor-1loqt21 r-touchAction-1otgn73"
|
||||
data-focusable="true"
|
||||
tabindex="0"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/Pressable prop "accessibilityRole" value alters HTML element 1`] = `
|
||||
<a
|
||||
class="css-reset-4rbku5 css-cursor-18t94o4 css-view-1dbjc4n r-cursor-1loqt21 r-touchAction-1otgn73"
|
||||
data-focusable="true"
|
||||
role="link"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/Pressable prop "accessibilityRole" value is "button" 1`] = `
|
||||
<div
|
||||
class="css-cursor-18t94o4 css-view-1dbjc4n r-cursor-1loqt21 r-touchAction-1otgn73"
|
||||
data-focusable="true"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/Pressable prop "accessibilityRole" value is set 1`] = `
|
||||
<div
|
||||
class="css-view-1dbjc4n r-cursor-1loqt21 r-touchAction-1otgn73"
|
||||
data-focusable="true"
|
||||
role="presentation"
|
||||
tabindex="0"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/Pressable prop "disabled" 1`] = `
|
||||
<div
|
||||
aria-disabled="true"
|
||||
class="css-view-1dbjc4n r-cursor-1loqt21 r-touchAction-1otgn73"
|
||||
disabled=""
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/Pressable prop "nativeID" value is set 1`] = `
|
||||
<div
|
||||
class="css-view-1dbjc4n r-cursor-1loqt21 r-touchAction-1otgn73"
|
||||
data-focusable="true"
|
||||
id="nativeID"
|
||||
tabindex="0"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/Pressable prop "pointerEvents" 1`] = `
|
||||
<div
|
||||
class="css-view-1dbjc4n r-cursor-1loqt21 r-pointerEvents-ah5dr5 r-touchAction-1otgn73"
|
||||
data-focusable="true"
|
||||
tabindex="0"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/Pressable prop "style" value is set 1`] = `
|
||||
<div
|
||||
class="css-view-1dbjc4n r-cursor-1loqt21 r-touchAction-1otgn73"
|
||||
data-focusable="true"
|
||||
style="border-top-width: 5px; border-right-width: 5px; border-bottom-width: 5px; border-left-width: 5px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/Pressable prop "testID" value is set 1`] = `
|
||||
<div
|
||||
class="css-view-1dbjc4n r-cursor-1loqt21 r-touchAction-1otgn73"
|
||||
data-focusable="true"
|
||||
data-testid="123"
|
||||
tabindex="0"
|
||||
/>
|
||||
`;
|
||||
@@ -0,0 +1,185 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import React from 'react';
|
||||
import Pressable from '../';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { createEventTarget } from 'dom-event-testing-library';
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
describe('components/Pressable', () => {
|
||||
test('default', () => {
|
||||
const { container } = render(<Pressable />);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('prop "accessibilityLabel"', () => {
|
||||
test('value is set', () => {
|
||||
const { container } = render(<Pressable accessibilityLabel="label" />);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('prop "accessibilityLiveRegion"', () => {
|
||||
test('value is set', () => {
|
||||
const { container } = render(<Pressable accessibilityLiveRegion="polite" />);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('prop "accessibilityRole"', () => {
|
||||
test('value is set', () => {
|
||||
const { container } = render(<Pressable accessibilityRole="none" />);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('value is "button"', () => {
|
||||
const { container } = render(<Pressable accessibilityRole="button" />);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('value alters HTML element', () => {
|
||||
const { container } = render(<Pressable accessibilityRole="link" />);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
test('prop "disabled"', () => {
|
||||
const { container } = render(<Pressable disabled={true} />);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('prop "nativeID"', () => {
|
||||
test('value is set', () => {
|
||||
const { container } = render(<Pressable nativeID="nativeID" />);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
test('focus interaction', () => {
|
||||
let container;
|
||||
const onBlur = jest.fn();
|
||||
const onFocus = jest.fn();
|
||||
const ref = React.createRef();
|
||||
act(() => {
|
||||
({ container } = render(
|
||||
<Pressable
|
||||
children={({ focused }) => (focused ? <div data-testid="focus-content" /> : null)}
|
||||
onBlur={onBlur}
|
||||
onFocus={onFocus}
|
||||
ref={ref}
|
||||
style={({ focused }) => [focused && { outline: 'focus-ring' }]}
|
||||
/>
|
||||
));
|
||||
});
|
||||
const target = createEventTarget(ref.current);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
act(() => {
|
||||
target.focus();
|
||||
});
|
||||
expect(onFocus).toBeCalled();
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
act(() => {
|
||||
target.blur();
|
||||
});
|
||||
expect(onBlur).toBeCalled();
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('hover interaction', () => {
|
||||
let container;
|
||||
const ref = React.createRef();
|
||||
act(() => {
|
||||
({ container } = render(
|
||||
<Pressable
|
||||
children={({ hovered }) => (hovered ? <div data-testid="hover-content" /> : null)}
|
||||
ref={ref}
|
||||
style={({ hovered }) => [hovered && { outline: 'hover-ring' }]}
|
||||
/>
|
||||
));
|
||||
});
|
||||
const target = createEventTarget(ref.current);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
act(() => {
|
||||
target.pointerover();
|
||||
});
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
act(() => {
|
||||
target.pointerout();
|
||||
});
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('press interaction', () => {
|
||||
let container;
|
||||
const onPress = jest.fn();
|
||||
const onPressIn = jest.fn();
|
||||
const onPressOut = jest.fn();
|
||||
const ref = React.createRef();
|
||||
act(() => {
|
||||
({ container } = render(
|
||||
<Pressable
|
||||
children={({ pressed }) => (pressed ? <div data-testid="press-content" /> : null)}
|
||||
onPress={onPress}
|
||||
onPressIn={onPressIn}
|
||||
onPressOut={onPressOut}
|
||||
ref={ref}
|
||||
style={({ pressed }) => [pressed && { outline: 'press-ring' }]}
|
||||
/>
|
||||
));
|
||||
});
|
||||
const target = createEventTarget(ref.current);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
act(() => {
|
||||
target.pointerdown({ button: 0 });
|
||||
jest.runAllTimers();
|
||||
});
|
||||
expect(onPressIn).toBeCalled();
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
act(() => {
|
||||
target.pointerup({ button: 0 });
|
||||
jest.runAllTimers();
|
||||
});
|
||||
expect(onPressOut).toBeCalled();
|
||||
expect(onPress).toBeCalled();
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('prop "ref"', () => {
|
||||
test('value is set', () => {
|
||||
const ref = jest.fn();
|
||||
render(<Pressable ref={ref} />);
|
||||
expect(ref).toBeCalled();
|
||||
});
|
||||
|
||||
test('node has imperative methods', () => {
|
||||
const ref = React.createRef();
|
||||
act(() => {
|
||||
render(<Pressable ref={ref} />);
|
||||
});
|
||||
const node = ref.current;
|
||||
expect(typeof node.measure === 'function');
|
||||
expect(typeof node.measureLayout === 'function');
|
||||
expect(typeof node.measureInWindow === 'function');
|
||||
expect(typeof node.setNativeProps === 'function');
|
||||
});
|
||||
});
|
||||
|
||||
test('prop "pointerEvents"', () => {
|
||||
const { container } = render(<Pressable pointerEvents="box-only" />);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('prop "style"', () => {
|
||||
test('value is set', () => {
|
||||
const { container } = render(<Pressable style={{ borderWidth: 5 }} />);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('prop "testID"', () => {
|
||||
test('value is set', () => {
|
||||
const { container } = render(<Pressable testID="123" />);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
+11
-3
@@ -18,6 +18,7 @@ import { forwardRef, memo, useMemo, useState, useRef } from 'react';
|
||||
import useMergeRefs from '../../modules/useMergeRefs';
|
||||
import useHover from '../../modules/useHover';
|
||||
import usePressEvents from '../../modules/usePressEvents';
|
||||
import StyleSheet from '../StyleSheet';
|
||||
import View from '../View';
|
||||
|
||||
export type StateCallbackType = $ReadOnly<{|
|
||||
@@ -157,7 +158,7 @@ function Pressable(props: Props, forwardedRef): React.Node {
|
||||
onBlur={createFocusHandler(onBlur, false)}
|
||||
onFocus={createFocusHandler(onFocus, true)}
|
||||
ref={setRef}
|
||||
style={typeof style === 'function' ? style(interactionState) : style}
|
||||
style={[styles.root, typeof style === 'function' ? style(interactionState) : style]}
|
||||
>
|
||||
{typeof children === 'function' ? children(interactionState) : children}
|
||||
</View>
|
||||
@@ -165,10 +166,17 @@ function Pressable(props: Props, forwardedRef): React.Node {
|
||||
}
|
||||
|
||||
function useForceableState(forced: boolean): [boolean, (boolean) => void] {
|
||||
const [pressed, setPressed] = useState(false);
|
||||
return [pressed || forced, setPressed];
|
||||
const [bool, setBool] = useState(false);
|
||||
return [bool || forced, setBool];
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
cursor: 'pointer',
|
||||
touchAction: 'manipulation'
|
||||
}
|
||||
});
|
||||
|
||||
const MemoedPressable = memo(forwardRef(Pressable));
|
||||
MemoedPressable.displayName = 'Pressable';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user