Skip to content

Commit 30c77d5

Browse files
authored
Timeline - Add testIDs (#3706)
* Add testID prop to Line, Point, and Timeline components for improved testing * small type revert * Remove testID from timelineContainer * make Line and Point testID prop internal prop * Timeline driver + test * Add Point rendering tests and enhance TimelineDriver with getPoint method * Timeline driver refactor * Refactor Timeline and Point drivers for improved test structure and style retrieval * Fix testID prop usage in Line and Point components * Add TimelineDriver export to testkit index * Refactor Timeline tests to simplify driver retrieval * Top/Bottom line moved from sainty, no icon no label test * Update Timeline sainty tests to include checks for top and bottom lines * Remove redundant TopLine test * Add isVisible method to LineDriver and update tests to verify visibility
1 parent 5399864 commit 30c77d5

File tree

9 files changed

+270
-14
lines changed

9 files changed

+270
-14
lines changed

src/components/timeline/Line.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ const ENTRY_POINT_HEIGHT = 2;
1010
type LinePropsInternal = LineProps & {
1111
top?: boolean;
1212
style?: ViewStyle;
13+
testID?: string;
1314
};
1415

1516
const Line = React.memo((props: LinePropsInternal) => {
16-
const {type, color = 'transparent', entry, top, style, width = LINE_WIDTH} = props;
17+
const {type, color = 'transparent', entry, top, style, width = LINE_WIDTH, testID} = props;
1718

1819
const solidLineStyle = useMemo(() => {
1920
return [style, styles.line, {width, backgroundColor: color}];
@@ -25,15 +26,20 @@ const Line = React.memo((props: LinePropsInternal) => {
2526

2627
const renderStartPoint = () => {
2728
if (entry) {
28-
return <View style={[styles.entryPoint, {backgroundColor: color}]}/>;
29+
return (
30+
<View
31+
style={[styles.entryPoint, {backgroundColor: color}]}
32+
testID={`${testID}.entryPoint`}
33+
/>
34+
);
2935
}
3036
};
3137

3238
const renderLine = () => {
3339
if (type === LineTypes.DASHED) {
34-
return <Dash vertical color={color} containerStyle={dashedLineStyle}/>;
40+
return <Dash vertical color={color} containerStyle={dashedLineStyle} testID={testID}/>;
3541
}
36-
return <View style={solidLineStyle}/>;
42+
return <View style={solidLineStyle} testID={testID}/>;
3743
};
3844

3945
return (

src/components/timeline/Point.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@ const ICON_SIZE = 16;
1616

1717
type PointPropsInternal = PointProps & {
1818
onLayout?: (event: LayoutChangeEvent) => void;
19+
testID?: string;
1920
};
2021

2122
const Point = (props: PointPropsInternal) => {
22-
const {icon, iconProps, removeIconBackground, label, type, color, onLayout} = props;
23+
const {icon, iconProps, removeIconBackground, label, type, color, onLayout, testID} = props;
2324

2425
const pointStyle = useMemo(() => {
2526
const hasOutline = type === PointTypes.OUTLINE;
@@ -45,18 +46,20 @@ const Point = (props: PointPropsInternal) => {
4546
const tintColor = removeIconBackground ? Colors.$iconDefault : Colors.$iconDefaultLight;
4647
const iconSize = removeIconBackground ? undefined : ICON_SIZE;
4748
if (icon) {
48-
return <Icon tintColor={tintColor} {...iconProps} size={iconSize} source={icon}/>;
49+
return (
50+
<Icon testID={`${testID}.icon`} tintColor={tintColor} {...iconProps} size={iconSize} source={icon}/>
51+
);
4952
} else if (label) {
5053
return (
51-
<Text recorderTag={'unmask'} $textDefaultLight subtextBold color={labelColor}>
54+
<Text testID={`${testID}.label`} recorderTag={'unmask'} $textDefaultLight subtextBold color={labelColor}>
5255
{label}
5356
</Text>
5457
);
5558
}
5659
};
5760

5861
return (
59-
<View center style={pointStyle} onLayout={onLayout}>
62+
<View center style={pointStyle} onLayout={onLayout} testID={testID}>
6063
{renderPointContent()}
6164
</View>
6265
);
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import React from 'react';
2+
import {render} from '@testing-library/react-native';
3+
import {TimelineDriver} from '../timeline.driver';
4+
import {TimelineProps} from '../types';
5+
import Timeline from '../index';
6+
import Text from '../../text';
7+
import Assets from '../../../assets';
8+
9+
const testID = 'test-timeline';
10+
const labelContent = 2;
11+
const defaultIcon = Assets.internal.icons.check;
12+
13+
const getDriver = (props?: TimelineProps) => {
14+
const renderTree = render(<Timeline testID={testID} {...props}>
15+
<Text>Timeline</Text>
16+
</Timeline>);
17+
return TimelineDriver({renderTree, testID});
18+
};
19+
20+
describe('Timeline', () => {
21+
describe('sanity', () => {
22+
it('should render Timeline', () => {
23+
const timelineDriver = getDriver();
24+
expect(timelineDriver.exists()).toBeTruthy();
25+
expect(timelineDriver.getPoint().exists()).toBeTruthy();
26+
expect(timelineDriver.getTopLine().exists()).toBeTruthy();
27+
expect(timelineDriver.getTopLine().isVisible()).toBeFalsy();
28+
expect(timelineDriver.getBottomLine().exists()).toBeFalsy();
29+
});
30+
});
31+
32+
describe('Point', () => {
33+
describe('with Icon', () => {
34+
it('should override icon color when passing iconProps.tintColor', () => {
35+
const props = {point: {icon: defaultIcon, iconProps: {tintColor: '#A2387E'}}};
36+
const timelineDriver = getDriver(props);
37+
const contentStyle = timelineDriver.getPoint().getContentStyle();
38+
expect(contentStyle.tintColor).toEqual('#A2387E');
39+
expect(contentStyle.color).toBeUndefined();
40+
});
41+
});
42+
43+
describe('with Label', () => {
44+
it('should override label color when passing labelColor', () => {
45+
const props = {point: {label: labelContent, labelColor: '#A2387E'}};
46+
const timelineDriver = getDriver(props);
47+
const contentStyle = timelineDriver.getPoint().getContentStyle();
48+
expect(contentStyle.color).toEqual('#A2387E');
49+
expect(contentStyle.tintColor).toBeUndefined();
50+
});
51+
});
52+
53+
it('should render Icon when icon and label passed', () => {
54+
const props = {point: {icon: defaultIcon, iconProps: {tintColor: '#A2387E'}, label: labelContent}};
55+
const timelineDriver = getDriver(props);
56+
const contentStyle = timelineDriver.getPoint().getContentStyle();
57+
expect(contentStyle.tintColor).toEqual('#A2387E');
58+
expect(contentStyle.color).toBeUndefined();
59+
});
60+
61+
it('tintColor and color should be undefined when icon and label are not passed', () => {
62+
const timelineDriver = getDriver();
63+
const contentStyle = timelineDriver.getPoint().getContentStyle();
64+
expect(contentStyle.tintColor).toBeUndefined();
65+
expect(contentStyle.color).toBeUndefined();
66+
});
67+
});
68+
69+
describe('TopLine', () => {
70+
it('should override top line color and width', () => {
71+
const props = {topLine: {color: '#00A87E', width: 3}};
72+
const timelineDriver = getDriver(props);
73+
const topLine = timelineDriver.getTopLine();
74+
expect(topLine.exists()).toBeTruthy();
75+
const topLineStyle = topLine.getStyle();
76+
expect(topLineStyle.backgroundColor).toEqual('#00A87E');
77+
expect(topLineStyle.width).toEqual(3);
78+
});
79+
80+
it('should render line with entryPoint', () => {
81+
const props = {topLine: {entry: true}};
82+
const timelineDriver = getDriver(props);
83+
const topLine = timelineDriver.getTopLine();
84+
expect(topLine.isEntryPointExists()).toBeTruthy();
85+
});
86+
});
87+
88+
describe('BottomLine', () => {
89+
it('should render BottomLine', () => {
90+
const props = {bottomLine: {}};
91+
const timelineDriver = getDriver(props);
92+
const bottomLine = timelineDriver.getBottomLine();
93+
expect(bottomLine.exists()).toBeTruthy();
94+
});
95+
96+
it('should override bottom line color and width', () => {
97+
const props = {bottomLine: {color: '#FFF4D3', width: 5}};
98+
const timelineDriver = getDriver(props);
99+
const bottomLine = timelineDriver.getBottomLine();
100+
expect(bottomLine.exists()).toBeTruthy();
101+
const bottomLineStyle = bottomLine.getStyle();
102+
expect(bottomLineStyle.backgroundColor).toEqual('#FFF4D3');
103+
expect(bottomLineStyle.width).toEqual(5);
104+
});
105+
});
106+
});

src/components/timeline/index.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const ENTRY_POINT_HEIGHT = 2;
2020

2121
const Timeline = (props: TimelineProps) => {
2222
const themeProps = useThemeProps(props, 'Timeline');
23-
const {topLine, bottomLine, point, children} = themeProps;
23+
const {topLine, bottomLine, point, children, testID} = themeProps;
2424
const [anchorMeasurements, setAnchorMeasurements] = useState<Layout | undefined>();
2525
const [contentContainerMeasurements, setContentContainerMeasurements] = useState<Layout | undefined>();
2626
const [pointMeasurements, setPointMeasurements] = useState<Layout | undefined>();
@@ -96,6 +96,7 @@ const Timeline = (props: TimelineProps) => {
9696
const renderTopLine = () => {
9797
return (
9898
<Line
99+
testID={`${testID}.topLine`}
99100
{...topLine}
100101
top
101102
style={topLineStyle}
@@ -108,6 +109,7 @@ const Timeline = (props: TimelineProps) => {
108109
if (bottomLine) {
109110
return (
110111
<Line
112+
testID={`${testID}.bottomLine`}
111113
{...bottomLine}
112114
style={styles.bottomLine}
113115
color={bottomLine?.color || getStateColor(bottomLine?.state)}
@@ -117,10 +119,15 @@ const Timeline = (props: TimelineProps) => {
117119
};
118120

119121
return (
120-
<View row style={containerStyle}>
122+
<View row style={containerStyle} testID={testID}>
121123
<View style={styles.timelineContainer}>
122124
{renderTopLine()}
123-
<Point {...point} onLayout={onPointLayout} color={point?.color || getStateColor(point?.state)}/>
125+
<Point
126+
{...point}
127+
onLayout={onPointLayout}
128+
color={point?.color || getStateColor(point?.state)}
129+
testID={`${testID}.point`}
130+
/>
124131
{renderBottomLine()}
125132
</View>
126133
<View style={styles.contentContainer} onLayout={onContentContainerLayout} ref={contentContainerRef}>
@@ -136,7 +143,6 @@ Timeline.states = StateTypes;
136143
Timeline.lineTypes = LineTypes;
137144
Timeline.pointTypes = PointTypes;
138145

139-
140146
const styles = StyleSheet.create({
141147
container: {
142148
paddingHorizontal: Spacings.s5
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import {StyleSheet} from 'react-native';
2+
import {ComponentProps} from '../../testkit/new/Component.driver';
3+
import {ViewDriver} from '../view/View.driver.new';
4+
5+
export const LineDriver = (props: ComponentProps) => {
6+
const lineDriver = ViewDriver({
7+
renderTree: props.renderTree,
8+
testID: `${props.testID}`
9+
});
10+
11+
const entryPointDriver = ViewDriver({
12+
renderTree: props.renderTree,
13+
testID: `${props.testID}.entryPoint`
14+
});
15+
16+
const getLine = () => {
17+
const exists = (): boolean => {
18+
return lineDriver.exists();
19+
};
20+
const getStyle = () => {
21+
return StyleSheet.flatten(lineDriver.getElement().props.style);
22+
};
23+
const isVisible = (): boolean => {
24+
const height = getStyle().height;
25+
return exists() && (height ?? 0) > 0;
26+
};
27+
const isEntryPointExists = (): boolean => {
28+
return entryPointDriver.exists();
29+
};
30+
const getEntryPointStyle = () => {
31+
return entryPointDriver.getStyle();
32+
};
33+
return {exists, getStyle, isVisible, isEntryPointExists, getEntryPointStyle};
34+
};
35+
36+
return {
37+
getLine
38+
};
39+
};
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import {StyleSheet} from 'react-native';
2+
import {ComponentProps} from '../../testkit/new/Component.driver';
3+
import {TextDriver} from '../text/Text.driver.new';
4+
import {ImageDriver} from '../image/Image.driver.new';
5+
import {ViewDriver} from '../view/View.driver.new';
6+
7+
export const PointDriver = (props: ComponentProps) => {
8+
const pointDriver = ViewDriver({
9+
renderTree: props.renderTree,
10+
testID: `${props.testID}`
11+
});
12+
13+
const labelDriver = TextDriver({
14+
renderTree: props.renderTree,
15+
testID: `${props.testID}.label`
16+
});
17+
18+
const iconDriver = ImageDriver({
19+
renderTree: props.renderTree,
20+
testID: `${props.testID}.icon`
21+
});
22+
23+
const getLabel = () => {
24+
const exists = (): boolean => {
25+
return labelDriver.exists();
26+
};
27+
return {...labelDriver, exists};
28+
};
29+
30+
const getIcon = () => {
31+
const exists = (): boolean => {
32+
return iconDriver.exists();
33+
};
34+
return {...iconDriver, exists};
35+
};
36+
37+
const getPoint = () => {
38+
const exists = (): boolean => {
39+
return pointDriver.exists();
40+
};
41+
const getStyle = () => {
42+
return StyleSheet.flatten(pointDriver.getElement().props.style);
43+
};
44+
const getContentStyle = () => {
45+
return getIcon().exists()
46+
? StyleSheet.flatten(getIcon().getElement().props.style)
47+
: getLabel().exists() && StyleSheet.flatten(getLabel().getElement().props.style);
48+
};
49+
return {exists, getStyle, getContentStyle};
50+
};
51+
52+
return {
53+
getPoint
54+
};
55+
};
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import {useComponentDriver, ComponentProps} from '../../testkit/new/Component.driver';
2+
import {LineDriver} from './line.driver';
3+
import {PointDriver} from './point.driver';
4+
5+
export const TimelineDriver = (props: ComponentProps) => {
6+
const driver = useComponentDriver(props);
7+
8+
const pointDriver = PointDriver({
9+
renderTree: props.renderTree,
10+
testID: `${props.testID}.point`
11+
});
12+
13+
const topLineDriver = LineDriver({
14+
renderTree: props.renderTree,
15+
testID: `${props.testID}.topLine`
16+
});
17+
const bottomLineDriver = LineDriver({
18+
renderTree: props.renderTree,
19+
testID: `${props.testID}.bottomLine`
20+
});
21+
22+
const getPoint = () => {
23+
return {...pointDriver.getPoint()};
24+
};
25+
26+
const getTopLine = () => {
27+
return {...topLineDriver.getLine()};
28+
};
29+
30+
const getBottomLine = () => {
31+
return {...bottomLineDriver.getLine()};
32+
};
33+
34+
return {
35+
...driver,
36+
getPoint,
37+
getTopLine,
38+
getBottomLine
39+
};
40+
};

src/components/timeline/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export type LineProps = {
2727
/** to mark as entry point */
2828
entry?: boolean;
2929
width?: number;
30-
}
30+
};
3131

3232
export type PointProps = {
3333
state?: StateTypes | `${StateTypes}`;
@@ -40,7 +40,7 @@ export type PointProps = {
4040
labelColor?: string;
4141
/** to align point to this view's center */
4242
anchorRef?: React.MutableRefObject<undefined>;
43-
}
43+
};
4444

4545
export type Layout = {
4646
x: number;

src/testkit/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ export {PickerDriver} from '../components/picker/Picker.driver.new';
2121
export {ExpandableOverlayDriver} from '../incubator/expandableOverlay/ExpandableOverlay.driver';
2222
export {ToastDriver} from '../incubator/toast/Toast.driver.new';
2323
export {DateTimePickerDriver} from '../components/dateTimePicker/DateTimePicker.driver';
24+
export {TimelineDriver} from '../components/timeline/timeline.driver';
2425
export {ChipDriver} from '../components/chip/chip.driver';

0 commit comments

Comments
 (0)