@jobber/components-native 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/Button/Button.js +78 -0
- package/dist/src/Button/Button.style.js +92 -0
- package/dist/src/Button/components/InternalButtonLoading/InternalButtonLoading.js +36 -0
- package/dist/src/Button/components/InternalButtonLoading/InternalButtonLoading.style.js +4 -0
- package/dist/src/Button/components/InternalButtonLoading/index.js +1 -0
- package/dist/src/Button/index.js +1 -0
- package/dist/src/Button/types.js +1 -0
- package/dist/src/Heading/Heading.js +51 -0
- package/dist/src/Heading/index.js +1 -0
- package/dist/src/index.js +2 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/src/Button/Button.d.ts +71 -0
- package/dist/types/src/Button/Button.style.d.ts +86 -0
- package/dist/types/src/Button/components/InternalButtonLoading/InternalButtonLoading.d.ts +11 -0
- package/dist/types/src/Button/components/InternalButtonLoading/InternalButtonLoading.style.d.ts +4 -0
- package/dist/types/src/Button/components/InternalButtonLoading/index.d.ts +1 -0
- package/dist/types/src/Button/index.d.ts +2 -0
- package/dist/types/src/Button/types.d.ts +3 -0
- package/dist/types/src/Heading/Heading.d.ts +37 -0
- package/dist/types/src/Heading/index.d.ts +2 -0
- package/dist/types/src/index.d.ts +2 -0
- package/package.json +3 -2
- package/src/Button/Button.style.ts +116 -0
- package/src/Button/Button.test.tsx +298 -0
- package/src/Button/Button.tsx +223 -0
- package/src/Button/components/InternalButtonLoading/InternalButtonLoading.style.ts +5 -0
- package/src/Button/components/InternalButtonLoading/InternalButtonLoading.test.tsx +39 -0
- package/src/Button/components/InternalButtonLoading/InternalButtonLoading.tsx +77 -0
- package/src/Button/components/InternalButtonLoading/index.ts +1 -0
- package/src/Button/index.ts +2 -0
- package/src/Button/types.ts +3 -0
- package/src/Heading/Heading.test.tsx +83 -0
- package/src/Heading/Heading.tsx +132 -0
- package/src/Heading/__snapshots__/Heading.test.tsx.snap +270 -0
- package/src/Heading/index.ts +2 -0
- package/src/index.ts +2 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { IconNames } from "@jobber/design";
|
|
3
|
+
import { XOR } from "ts-xor";
|
|
4
|
+
import { ButtonSize, ButtonType, ButtonVariation } from "./types";
|
|
5
|
+
interface CommonButtonProps {
|
|
6
|
+
/**
|
|
7
|
+
* Press handler
|
|
8
|
+
*/
|
|
9
|
+
readonly onPress?: () => void;
|
|
10
|
+
/**
|
|
11
|
+
* Themes the button to the type of action it performs
|
|
12
|
+
*/
|
|
13
|
+
readonly variation?: ButtonVariation;
|
|
14
|
+
/**
|
|
15
|
+
* Sets the visual hierarchy
|
|
16
|
+
*/
|
|
17
|
+
readonly type?: ButtonType;
|
|
18
|
+
/**
|
|
19
|
+
* Defines the size of the button
|
|
20
|
+
*
|
|
21
|
+
* @default "base"
|
|
22
|
+
*/
|
|
23
|
+
readonly size?: ButtonSize;
|
|
24
|
+
/**
|
|
25
|
+
* Will make the button scale to take up all the available height
|
|
26
|
+
*/
|
|
27
|
+
readonly fullHeight?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Will make the button scale to take up all of the available width
|
|
30
|
+
*/
|
|
31
|
+
readonly fullWidth?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Makes the button un-clickable
|
|
34
|
+
*/
|
|
35
|
+
readonly disabled?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Accessibility hint to help users understand what will happen when they press the button
|
|
38
|
+
*/
|
|
39
|
+
readonly accessibilityHint?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Changes the button interface to imply loading and prevents the press callback
|
|
42
|
+
*
|
|
43
|
+
* @default false
|
|
44
|
+
*/
|
|
45
|
+
readonly loading?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Adds an leading icon beside the label.
|
|
48
|
+
*/
|
|
49
|
+
readonly icon?: IconNames;
|
|
50
|
+
/**
|
|
51
|
+
* Accessibility label for the component. This is required for components that
|
|
52
|
+
* have an `icon` but not a `label`.
|
|
53
|
+
*
|
|
54
|
+
* If the string is the same as the `label` prop, you don't need to add an
|
|
55
|
+
* `accessibilityLabel`. **Don't use this for testing purposes.**
|
|
56
|
+
*/
|
|
57
|
+
readonly accessibilityLabel?: string;
|
|
58
|
+
}
|
|
59
|
+
interface LabelButton extends CommonButtonProps {
|
|
60
|
+
/**
|
|
61
|
+
* Text to be displayed on the button
|
|
62
|
+
*/
|
|
63
|
+
readonly label: string;
|
|
64
|
+
}
|
|
65
|
+
interface IconButton extends CommonButtonProps {
|
|
66
|
+
readonly icon: IconNames;
|
|
67
|
+
readonly accessibilityLabel: string;
|
|
68
|
+
}
|
|
69
|
+
export type ButtonProps = XOR<LabelButton, IconButton>;
|
|
70
|
+
export declare function Button({ label, onPress, variation, type, fullHeight, fullWidth, disabled, loading, size, accessibilityLabel, accessibilityHint, icon, }: ButtonProps): JSX.Element;
|
|
71
|
+
export {};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export declare const baseButtonHeight: number;
|
|
2
|
+
export declare const smallButtonHeight: number;
|
|
3
|
+
export declare const styles: {
|
|
4
|
+
fullHeight: {
|
|
5
|
+
flexGrow: number;
|
|
6
|
+
flexShrink: number;
|
|
7
|
+
};
|
|
8
|
+
fullWidth: {
|
|
9
|
+
alignSelf: "stretch";
|
|
10
|
+
};
|
|
11
|
+
touchable: {
|
|
12
|
+
borderRadius: number;
|
|
13
|
+
};
|
|
14
|
+
button: {
|
|
15
|
+
justifyContent: "center";
|
|
16
|
+
alignItems: "center";
|
|
17
|
+
alignSelf: "stretch";
|
|
18
|
+
flexDirection: "row";
|
|
19
|
+
overflow: "hidden";
|
|
20
|
+
margin: number;
|
|
21
|
+
borderRadius: number;
|
|
22
|
+
borderWidth: number;
|
|
23
|
+
paddingVertical: number;
|
|
24
|
+
};
|
|
25
|
+
base: {
|
|
26
|
+
minHeight: number;
|
|
27
|
+
paddingHorizontal: number;
|
|
28
|
+
};
|
|
29
|
+
small: {
|
|
30
|
+
minHeight: number;
|
|
31
|
+
paddingHorizontal: number;
|
|
32
|
+
};
|
|
33
|
+
reducedPaddingForFullWidth: {
|
|
34
|
+
paddingHorizontal: number;
|
|
35
|
+
};
|
|
36
|
+
iconPaddingOffset: {
|
|
37
|
+
paddingRight: number;
|
|
38
|
+
};
|
|
39
|
+
content: {
|
|
40
|
+
paddingLeft: number;
|
|
41
|
+
height: string;
|
|
42
|
+
};
|
|
43
|
+
contentWithLabel: {
|
|
44
|
+
paddingLeft: number;
|
|
45
|
+
};
|
|
46
|
+
iconStyle: {
|
|
47
|
+
position: "absolute";
|
|
48
|
+
top: string;
|
|
49
|
+
left: number;
|
|
50
|
+
transform: {
|
|
51
|
+
translateY: number;
|
|
52
|
+
}[];
|
|
53
|
+
};
|
|
54
|
+
labelStyle: {
|
|
55
|
+
flexGrow: number;
|
|
56
|
+
justifyContent: "center";
|
|
57
|
+
};
|
|
58
|
+
work: {
|
|
59
|
+
backgroundColor: string;
|
|
60
|
+
borderColor: string;
|
|
61
|
+
};
|
|
62
|
+
learning: {
|
|
63
|
+
backgroundColor: string;
|
|
64
|
+
borderColor: string;
|
|
65
|
+
};
|
|
66
|
+
destructive: {
|
|
67
|
+
backgroundColor: string;
|
|
68
|
+
borderColor: string;
|
|
69
|
+
};
|
|
70
|
+
cancel: {
|
|
71
|
+
backgroundColor: string;
|
|
72
|
+
borderColor: string;
|
|
73
|
+
};
|
|
74
|
+
primary: {};
|
|
75
|
+
secondary: {
|
|
76
|
+
backgroundColor: string;
|
|
77
|
+
};
|
|
78
|
+
tertiary: {
|
|
79
|
+
backgroundColor: string;
|
|
80
|
+
borderColor: string;
|
|
81
|
+
};
|
|
82
|
+
disabled: {
|
|
83
|
+
borderColor: string;
|
|
84
|
+
backgroundColor: string;
|
|
85
|
+
};
|
|
86
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { ButtonType, ButtonVariation } from "../../types";
|
|
3
|
+
interface InternalButtonLoadingProps {
|
|
4
|
+
readonly variation: ButtonVariation;
|
|
5
|
+
readonly type: ButtonType;
|
|
6
|
+
}
|
|
7
|
+
declare function InternalButtonLoadingInternal({ variation, type, }: InternalButtonLoadingProps): JSX.Element;
|
|
8
|
+
export declare const darkPattern = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgAgMAAACf9p+rAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAJcEhZcwAAITgAACE4AUWWMWAAAAAMUExURQAAAEdwTAAAAAAAAKDh18UAAAAEdFJOUxkADQwimkzpAAAAtUlEQVRIx+3NqxHDQBRDUc0YuxyXokxgSkmT7sdgP++3YoYrqAsOYDto+7gfpwtfHy4Xfj7cLvw3sYlNbOINAoI4IIgTgrggiBuCIAThQyB8CIQLkXAhEi5EwoVIWEiEhURYSISFRMyQiRkyMUMmZsjECIUYoRAjFGKEQvRQiR4q0UMleqhECwuihQXRwoJoYUEQgiAEQQiCEAQhCEIQhCAIQRCCIARBCIIQBCEIQhCEIAhB8AEuzZ5wHe17xgAAAABJRU5ErkJggg==";
|
|
9
|
+
export declare const lightPattern = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgAgMAAACf9p+rAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAJcEhZcwAAITgAACE4AUWWMWAAAAAJUExURf///0dwTP///0SistEAAAADdFJOU0AAILGCadYAAAC0SURBVEjH7c2pFcNAFENRHTMX4pKUE5hS0oT7NZjlbyNmOIJ64AK2g7aP+3G68PXhcuHnw+3CfxOb2MQm3iAgiAOCOCGIC4K4IQhCED4EwodAuBAJFyLhQiRciISFRFhIhIVEWEjEDJmYIRMzZGKGTIxQiBEKMUIhRihED5XooRI9VKKHSrSwIFpYEC0siBYWBCEIQhCEIAhBEIIgBEEIghAEIQhCEIQgCEEQgiAEQQiCEAQfva6WeBniVLgAAAAASUVORK5CYII=";
|
|
10
|
+
export declare const InternalButtonLoading: React.MemoExoticComponent<typeof InternalButtonLoadingInternal>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { InternalButtonLoading } from "./InternalButtonLoading";
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { TextAlign, TextColor, TruncateLength, TypographyProps } from "../Typography";
|
|
3
|
+
type HeadingColor = Extract<TextColor, "text" | "subdued" | "heading">;
|
|
4
|
+
export type HeadingLevel = "title" | "subtitle" | "heading" | "subHeading";
|
|
5
|
+
interface HeadingProps<T extends HeadingLevel> extends Pick<TypographyProps<"base">, "selectable"> {
|
|
6
|
+
/**
|
|
7
|
+
* Text to display.
|
|
8
|
+
*/
|
|
9
|
+
readonly children: string;
|
|
10
|
+
/**
|
|
11
|
+
* The type of heading, e.g., "Title"
|
|
12
|
+
*/
|
|
13
|
+
readonly level?: T;
|
|
14
|
+
/**
|
|
15
|
+
* The text color of heading
|
|
16
|
+
*/
|
|
17
|
+
readonly variation?: HeadingColor;
|
|
18
|
+
/**
|
|
19
|
+
* Uses the reverse variant of the text color for the heading
|
|
20
|
+
*/
|
|
21
|
+
readonly reverseTheme?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Alignment of heading
|
|
24
|
+
*/
|
|
25
|
+
readonly align?: TextAlign;
|
|
26
|
+
/**
|
|
27
|
+
* The maximum amount of lines the text can occupy before being truncated with "...".
|
|
28
|
+
* Uses predefined string values that correspond to a doubling scale for the amount of lines.
|
|
29
|
+
*/
|
|
30
|
+
readonly maxLines?: TruncateLength;
|
|
31
|
+
/**
|
|
32
|
+
* Allow text to be resized based on user's device display scale
|
|
33
|
+
*/
|
|
34
|
+
readonly allowFontScaling?: boolean;
|
|
35
|
+
}
|
|
36
|
+
export declare function Heading<T extends HeadingLevel = "heading">({ children, level, variation, reverseTheme, align, maxLines, allowFontScaling, selectable, }: HeadingProps<T>): JSX.Element;
|
|
37
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jobber/components-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"module": "dist/src/index.js",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"react-intl": "^6.4.2",
|
|
27
27
|
"react-native-gesture-handler": "^2.5.0",
|
|
28
28
|
"react-native-localize": "^2.2.6",
|
|
29
|
+
"react-native-reanimated": "^2.17.0",
|
|
29
30
|
"react-native-svg": "^13.9.0",
|
|
30
31
|
"react-native-uuid": "^1.4.9",
|
|
31
32
|
"ts-xor": "^1.1.0"
|
|
@@ -46,5 +47,5 @@
|
|
|
46
47
|
"react": "^18",
|
|
47
48
|
"react-native": ">=0.69.2"
|
|
48
49
|
},
|
|
49
|
-
"gitHead": "
|
|
50
|
+
"gitHead": "b059ff05b89ebdd41e79b4b2cf761e91706bc9c9"
|
|
50
51
|
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { StyleSheet } from "react-native";
|
|
2
|
+
import { tokens } from "../utils/design";
|
|
3
|
+
|
|
4
|
+
const iconTranslateY = tokens["space-large"] / 2;
|
|
5
|
+
const buttonRadius = tokens["radius-large"];
|
|
6
|
+
export const baseButtonHeight = tokens["space-base"] * 3.5;
|
|
7
|
+
export const smallButtonHeight = tokens["space-base"] * 2.25;
|
|
8
|
+
|
|
9
|
+
export const styles = StyleSheet.create({
|
|
10
|
+
fullHeight: {
|
|
11
|
+
flexGrow: 1,
|
|
12
|
+
flexShrink: 0,
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
fullWidth: {
|
|
16
|
+
alignSelf: "stretch",
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
touchable: {
|
|
20
|
+
borderRadius: buttonRadius,
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
button: {
|
|
24
|
+
justifyContent: "center",
|
|
25
|
+
alignItems: "center",
|
|
26
|
+
alignSelf: "stretch",
|
|
27
|
+
flexDirection: "row",
|
|
28
|
+
overflow: "hidden",
|
|
29
|
+
margin: 0,
|
|
30
|
+
borderRadius: buttonRadius,
|
|
31
|
+
borderWidth: tokens["border-base"],
|
|
32
|
+
paddingVertical: tokens["space-small"],
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
base: {
|
|
36
|
+
minHeight: baseButtonHeight,
|
|
37
|
+
paddingHorizontal: tokens["space-base"],
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
small: {
|
|
41
|
+
minHeight: smallButtonHeight,
|
|
42
|
+
paddingHorizontal: tokens["space-small"] + tokens["space-smaller"],
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
reducedPaddingForFullWidth: {
|
|
46
|
+
paddingHorizontal: tokens["space-smaller"],
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
iconPaddingOffset: {
|
|
50
|
+
paddingRight: tokens["space-smaller"],
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
content: {
|
|
54
|
+
paddingLeft: tokens["space-large"],
|
|
55
|
+
height: "100%",
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
contentWithLabel: {
|
|
59
|
+
paddingLeft: tokens["space-large"] + tokens["space-small"],
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
iconStyle: {
|
|
63
|
+
position: "absolute",
|
|
64
|
+
top: "50%",
|
|
65
|
+
left: 0,
|
|
66
|
+
transform: [{ translateY: -iconTranslateY }],
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
labelStyle: {
|
|
70
|
+
flexGrow: 1,
|
|
71
|
+
justifyContent: "center",
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
/* Variations */
|
|
75
|
+
|
|
76
|
+
work: {
|
|
77
|
+
backgroundColor: tokens["color-interactive"],
|
|
78
|
+
borderColor: tokens["color-interactive"],
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
learning: {
|
|
82
|
+
backgroundColor: tokens["color-informative"],
|
|
83
|
+
borderColor: tokens["color-informative"],
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
destructive: {
|
|
87
|
+
backgroundColor: tokens["color-destructive"],
|
|
88
|
+
borderColor: tokens["color-destructive"],
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
/* Cancel is special because, by default, it's styled as a secondary button */
|
|
92
|
+
cancel: {
|
|
93
|
+
backgroundColor: tokens["color-surface"],
|
|
94
|
+
borderColor: tokens["color-interactive--subtle"],
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
/* Types */
|
|
98
|
+
|
|
99
|
+
primary: {},
|
|
100
|
+
|
|
101
|
+
secondary: {
|
|
102
|
+
backgroundColor: tokens["color-surface"],
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
tertiary: {
|
|
106
|
+
backgroundColor: tokens["color-surface"],
|
|
107
|
+
borderColor: tokens["color-surface"],
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
/* Disabled */
|
|
111
|
+
|
|
112
|
+
disabled: {
|
|
113
|
+
borderColor: tokens["color-disabled--secondary"],
|
|
114
|
+
backgroundColor: tokens["color-disabled--secondary"],
|
|
115
|
+
},
|
|
116
|
+
});
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import React, { CSSProperties, ReactElement } from "react";
|
|
2
|
+
import { fireEvent, render } from "@testing-library/react-native";
|
|
3
|
+
import { Path } from "react-native-svg";
|
|
4
|
+
import { ReactTestInstance } from "react-test-renderer";
|
|
5
|
+
import { Button, ButtonType, ButtonVariation } from ".";
|
|
6
|
+
import { ButtonSize } from "./types";
|
|
7
|
+
import { baseButtonHeight, smallButtonHeight } from "./Button.style";
|
|
8
|
+
import { tokens } from "../utils/design";
|
|
9
|
+
|
|
10
|
+
function getIconAndTextColorFromRender({
|
|
11
|
+
type = "primary",
|
|
12
|
+
variation = "work",
|
|
13
|
+
}: {
|
|
14
|
+
type?: ButtonType;
|
|
15
|
+
variation?: ButtonVariation;
|
|
16
|
+
}) {
|
|
17
|
+
const pressHandler = jest.fn();
|
|
18
|
+
const text = "🌚 I am the text 🌚";
|
|
19
|
+
const iconName = "cog";
|
|
20
|
+
|
|
21
|
+
const { getByTestId, getByRole } = render(
|
|
22
|
+
<Button
|
|
23
|
+
onPress={pressHandler}
|
|
24
|
+
label={text}
|
|
25
|
+
icon={iconName}
|
|
26
|
+
type={type}
|
|
27
|
+
variation={variation}
|
|
28
|
+
/>,
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const iconColor = getByTestId(iconName).findByType(Path).props.fill;
|
|
32
|
+
const textColor = getByRole("text").props.style.find(
|
|
33
|
+
(style: CSSProperties) => style.color,
|
|
34
|
+
).color;
|
|
35
|
+
|
|
36
|
+
return { iconColor, textColor };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function renderButton(element: ReactElement) {
|
|
40
|
+
const instance = render(element);
|
|
41
|
+
|
|
42
|
+
const button = instance.getByLabelText(element.props.label);
|
|
43
|
+
expect(button).toBeDefined();
|
|
44
|
+
expect(instance.getByText(element.props.label)).toBeDefined();
|
|
45
|
+
|
|
46
|
+
const buttonStyleEl = button.children[0] as ReactTestInstance;
|
|
47
|
+
const buttonStyle = buttonStyleEl.props.style.reduce(
|
|
48
|
+
(mergedStyles: CSSProperties, additionalStyles: CSSProperties) => ({
|
|
49
|
+
...mergedStyles,
|
|
50
|
+
...additionalStyles,
|
|
51
|
+
}),
|
|
52
|
+
{},
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
return { ...instance, button, buttonStyle };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
describe("Button", () => {
|
|
59
|
+
it("renders the default primary button", () => {
|
|
60
|
+
const { button, buttonStyle } = renderButton(
|
|
61
|
+
<Button label={"Foo"} onPress={jest.fn()} />,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
expect(button.props.accessibilityRole).toBe("button");
|
|
65
|
+
expect(buttonStyle).toMatchObject({
|
|
66
|
+
backgroundColor: tokens["color-interactive"],
|
|
67
|
+
borderColor: tokens["color-interactive"],
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it.each<[ButtonVariation, Record<string, string>]>([
|
|
72
|
+
["work", { bgColor: tokens["color-interactive"] }],
|
|
73
|
+
[
|
|
74
|
+
"cancel",
|
|
75
|
+
{
|
|
76
|
+
bgColor: tokens["color-white"],
|
|
77
|
+
borderColor: tokens["color-interactive--subtle"],
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
["destructive", { bgColor: tokens["color-destructive"] }],
|
|
81
|
+
["learning", { bgColor: tokens["color-informative"] }],
|
|
82
|
+
])("renders a %s Button", (variation, { bgColor, borderColor }) => {
|
|
83
|
+
const { buttonStyle } = renderButton(
|
|
84
|
+
<Button label={variation} variation={variation} onPress={jest.fn()} />,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
expect(buttonStyle).toMatchObject({
|
|
88
|
+
backgroundColor: bgColor,
|
|
89
|
+
borderColor: borderColor || bgColor,
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it.each<[ButtonType, Record<string, string>]>([
|
|
94
|
+
["primary", { bgColor: tokens["color-interactive"] }],
|
|
95
|
+
[
|
|
96
|
+
"secondary",
|
|
97
|
+
{
|
|
98
|
+
bgColor: tokens["color-white"],
|
|
99
|
+
borderColor: tokens["color-interactive"],
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
["tertiary", { bgColor: tokens["color-white"] }],
|
|
103
|
+
])("renders a %s Button", (type, { bgColor, borderColor }) => {
|
|
104
|
+
const { buttonStyle } = renderButton(
|
|
105
|
+
<Button label={type} type={type} onPress={jest.fn()} />,
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
expect(buttonStyle).toMatchObject({
|
|
109
|
+
backgroundColor: bgColor,
|
|
110
|
+
borderColor: borderColor || bgColor,
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it.each<[ButtonSize, Record<string, number>]>([
|
|
115
|
+
[
|
|
116
|
+
"small",
|
|
117
|
+
{
|
|
118
|
+
minHeight: smallButtonHeight,
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
[
|
|
122
|
+
"base",
|
|
123
|
+
{
|
|
124
|
+
minHeight: baseButtonHeight,
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
])("renders a %s Button", (size, { minHeight }) => {
|
|
128
|
+
const { buttonStyle } = renderButton(
|
|
129
|
+
<Button label="Button Size" size={size} />,
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
expect(buttonStyle).toMatchObject({
|
|
133
|
+
minHeight,
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("renders a disabled Button", () => {
|
|
138
|
+
const { button, buttonStyle } = renderButton(
|
|
139
|
+
<Button label="Can't touch this" disabled={true} onPress={jest.fn()} />,
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
expect(button.props.accessibilityState).toHaveProperty("disabled", true);
|
|
143
|
+
expect(buttonStyle).toMatchObject({
|
|
144
|
+
backgroundColor: tokens["color-disabled--secondary"],
|
|
145
|
+
borderColor: tokens["color-disabled--secondary"],
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("renders a non-fullWidth Button", () => {
|
|
150
|
+
const expectedValue = { alignSelf: "stretch" };
|
|
151
|
+
const { button, rerender } = renderButton(
|
|
152
|
+
<Button label="Thicc" onPress={jest.fn()} />,
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
expect(button.props.style).toContainEqual(expectedValue);
|
|
156
|
+
|
|
157
|
+
rerender(<Button label="Thicc" fullWidth={false} onPress={jest.fn()} />);
|
|
158
|
+
|
|
159
|
+
expect(button.props.style).not.toContainEqual(expectedValue);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("should call the onPress handler", () => {
|
|
163
|
+
const pressHandler = jest.fn();
|
|
164
|
+
const text = "🌚 I am the text 🌚";
|
|
165
|
+
const a11yLabel = "A button";
|
|
166
|
+
const { getByLabelText } = render(
|
|
167
|
+
<Button
|
|
168
|
+
onPress={pressHandler}
|
|
169
|
+
label={text}
|
|
170
|
+
accessibilityLabel={a11yLabel}
|
|
171
|
+
/>,
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
fireEvent.press(getByLabelText(a11yLabel));
|
|
175
|
+
expect(pressHandler).toHaveBeenCalled();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe("accessibilityLabel", () => {
|
|
179
|
+
it("uses accessibilityLabel if specified", () => {
|
|
180
|
+
const pressHandler = jest.fn();
|
|
181
|
+
const text = "🌚 I am the text 🌚";
|
|
182
|
+
const a11yLabel = "A button";
|
|
183
|
+
const { getByLabelText } = render(
|
|
184
|
+
<Button
|
|
185
|
+
onPress={pressHandler}
|
|
186
|
+
label={text}
|
|
187
|
+
accessibilityLabel={a11yLabel}
|
|
188
|
+
/>,
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
expect(getByLabelText(a11yLabel)).toBeTruthy();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("uses label if unspecified", () => {
|
|
195
|
+
const pressHandler = jest.fn();
|
|
196
|
+
const text = "🌚 I am the text 🌚";
|
|
197
|
+
const { getByLabelText } = render(
|
|
198
|
+
<Button onPress={pressHandler} label={text} />,
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
expect(getByLabelText(text)).toBeTruthy();
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe("if an icon is passed in", () => {
|
|
206
|
+
it("renders an icon Button with same color as the Button text", () => {
|
|
207
|
+
const { iconColor, textColor } = getIconAndTextColorFromRender({});
|
|
208
|
+
|
|
209
|
+
expect(iconColor).toBe(tokens["color-white"]);
|
|
210
|
+
expect(textColor).toBe(iconColor);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("renders the learning variation and secondary type with icon and label with the same color", () => {
|
|
214
|
+
const { iconColor, textColor } = getIconAndTextColorFromRender({
|
|
215
|
+
variation: "learning",
|
|
216
|
+
type: "secondary",
|
|
217
|
+
});
|
|
218
|
+
expect(iconColor).toBe(tokens["color-informative"]);
|
|
219
|
+
expect(textColor).toBe(iconColor);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("renders the destructive variation and secondary type with icon and label with the same color", () => {
|
|
223
|
+
const { iconColor, textColor } = getIconAndTextColorFromRender({
|
|
224
|
+
variation: "destructive",
|
|
225
|
+
type: "secondary",
|
|
226
|
+
});
|
|
227
|
+
expect(iconColor).toBe(tokens["color-destructive"]);
|
|
228
|
+
expect(textColor).toBe(iconColor);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("renders the cancel variation and tertiary type with icon and label with the same color", () => {
|
|
232
|
+
const { iconColor, textColor } = getIconAndTextColorFromRender({
|
|
233
|
+
variation: "cancel",
|
|
234
|
+
type: "tertiary",
|
|
235
|
+
});
|
|
236
|
+
expect(iconColor).toBe(tokens["color-interactive--subtle"]);
|
|
237
|
+
expect(textColor).toBe(iconColor);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("renders an icon Button if only an icon is passed", () => {
|
|
241
|
+
const pressHandler = jest.fn();
|
|
242
|
+
const icon = "cog";
|
|
243
|
+
const accessibilityLabel = "cog";
|
|
244
|
+
|
|
245
|
+
const { getByLabelText } = render(
|
|
246
|
+
<Button
|
|
247
|
+
onPress={pressHandler}
|
|
248
|
+
icon={icon}
|
|
249
|
+
accessibilityLabel={accessibilityLabel}
|
|
250
|
+
/>,
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
expect(getByLabelText(icon)).toBeDefined();
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
describe("Loading", () => {
|
|
258
|
+
const label = "I am loading";
|
|
259
|
+
it("does render a loading state", () => {
|
|
260
|
+
const { getByTestId, getByRole } = render(
|
|
261
|
+
<Button label={label} onPress={jest.fn} loading={true} />,
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
expect(getByTestId("loadingImage")).toBeDefined();
|
|
265
|
+
expect(getByRole("button", { busy: true })).toBeDefined();
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("doesn't render a loading state", () => {
|
|
269
|
+
const { queryByTestId, queryByRole, rerender } = render(
|
|
270
|
+
<Button label={label} onPress={jest.fn} loading={false} />,
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
expect(queryByTestId("loadingImage")).toBeNull();
|
|
274
|
+
expect(queryByRole("button", { busy: true })).toBeNull();
|
|
275
|
+
|
|
276
|
+
rerender(<Button label="I am loading" onPress={jest.fn} />);
|
|
277
|
+
|
|
278
|
+
expect(queryByTestId("loadingImage")).toBeNull();
|
|
279
|
+
expect(queryByRole("button", { busy: true })).toBeNull();
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it("should not allow press events", () => {
|
|
283
|
+
const handlePress = jest.fn();
|
|
284
|
+
const setup = (loading?: boolean) => (
|
|
285
|
+
<Button label={label} onPress={handlePress} loading={loading} />
|
|
286
|
+
);
|
|
287
|
+
const { getByLabelText, rerender } = render(setup(true));
|
|
288
|
+
|
|
289
|
+
fireEvent.press(getByLabelText(label));
|
|
290
|
+
expect(handlePress).not.toHaveBeenCalled();
|
|
291
|
+
|
|
292
|
+
// Sanity check that it's not a false positive
|
|
293
|
+
rerender(setup());
|
|
294
|
+
fireEvent.press(getByLabelText(label));
|
|
295
|
+
expect(handlePress).toHaveBeenCalledTimes(1);
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
});
|