Skip to content

Commit 9661542

Browse files
New history V2 header
Signed-off-by: Adhitya Mamallan <adhitya.mamallan@uber.com>
1 parent 1054087 commit 9661542

File tree

6 files changed

+562
-60
lines changed

6 files changed

+562
-60
lines changed
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
import React from 'react';
2+
3+
import {
4+
mockIsIntersecting,
5+
intersectionMockInstance,
6+
} from 'react-intersection-observer/test-utils';
7+
8+
import { act, render, screen, userEvent } from '@/test-utils/rtl';
9+
10+
import WorkflowHistoryHeader from '../workflow-history-header';
11+
import { type Props } from '../workflow-history-header.types';
12+
13+
jest.mock(
14+
'@/views/workflow-history/workflow-history-export-json-button/workflow-history-export-json-button',
15+
() => jest.fn(() => <button>Export JSON</button>)
16+
);
17+
18+
jest.mock(
19+
'@/components/page-filters/page-filters-toggle/page-filters-toggle',
20+
() =>
21+
jest.fn(({ onClick, isActive, activeFiltersCount }) => (
22+
<button onClick={onClick} data-testid="page-filters-toggle">
23+
{isActive ? 'Hide Filters' : 'Show Filters'} ({activeFiltersCount})
24+
</button>
25+
))
26+
);
27+
28+
describe(WorkflowHistoryHeader.name, () => {
29+
it('should render the header with title', () => {
30+
setup();
31+
expect(screen.getByText('Workflow history')).toBeInTheDocument();
32+
});
33+
34+
it('should render export JSON button', () => {
35+
setup();
36+
expect(screen.getByText('Export JSON')).toBeInTheDocument();
37+
});
38+
39+
it('should render segmented control with grouped and ungrouped segments', () => {
40+
setup();
41+
expect(screen.getByText('Grouped')).toBeInTheDocument();
42+
expect(screen.getByText('Ungrouped')).toBeInTheDocument();
43+
});
44+
45+
it('should call onClickGroupModeToggle when segmented control segment is clicked', async () => {
46+
const { user, mockOnClickGroupModeToggle } = setup();
47+
const groupedSegment = screen.getByText('Grouped');
48+
49+
await user.click(groupedSegment);
50+
51+
expect(mockOnClickGroupModeToggle).toHaveBeenCalledTimes(1);
52+
});
53+
54+
it('should render filters toggle button', () => {
55+
setup();
56+
expect(screen.getByTestId('page-filters-toggle')).toBeInTheDocument();
57+
});
58+
59+
it('should show filters toggle as inactive by default', () => {
60+
setup();
61+
const filtersToggle = screen.getByTestId('page-filters-toggle');
62+
expect(filtersToggle).toHaveTextContent('Show Filters (0)');
63+
});
64+
65+
it('should toggle filters visibility when filter toggle is clicked', async () => {
66+
const { user } = setup();
67+
const filtersToggle = screen.getByTestId('page-filters-toggle');
68+
69+
expect(filtersToggle).toHaveTextContent('Show Filters (0)');
70+
71+
await user.click(filtersToggle);
72+
73+
expect(filtersToggle).toHaveTextContent('Hide Filters (0)');
74+
75+
await user.click(filtersToggle);
76+
77+
expect(filtersToggle).toHaveTextContent('Show Filters (0)');
78+
});
79+
80+
it('should display active filters count in filters toggle', () => {
81+
setup({
82+
pageFiltersProps: {
83+
activeFiltersCount: 3,
84+
queryParams: {
85+
historyEventTypes: undefined,
86+
historyEventStatuses: undefined,
87+
historySelectedEventId: undefined,
88+
ungroupedHistoryViewEnabled: undefined,
89+
},
90+
setQueryParams: jest.fn(),
91+
resetAllFilters: jest.fn(),
92+
},
93+
});
94+
const filtersToggle = screen.getByTestId('page-filters-toggle');
95+
expect(filtersToggle).toHaveTextContent('Show Filters (3)');
96+
});
97+
98+
it('should render sentinel when sticky is enabled', () => {
99+
setup({ isStickyEnabled: true });
100+
expect(screen.getByTestId('sentinel')).toBeInTheDocument();
101+
});
102+
103+
it('should not render sentinel when sticky is disabled', () => {
104+
setup({ isStickyEnabled: false });
105+
expect(screen.queryByTestId('sentinel')).not.toBeInTheDocument();
106+
});
107+
108+
it('should set up intersection observer when sticky is enabled', () => {
109+
setup({ isStickyEnabled: true });
110+
111+
const sentinel = screen.getByTestId('sentinel');
112+
const instance = intersectionMockInstance(sentinel);
113+
expect(instance.observe).toHaveBeenCalledWith(sentinel);
114+
});
115+
116+
it('should not set up intersection observer when sticky is disabled', () => {
117+
setup({ isStickyEnabled: false });
118+
119+
const sentinel = screen.queryByTestId('sentinel');
120+
expect(sentinel).not.toBeInTheDocument();
121+
});
122+
123+
it('should set sticky state to false when sentinel is in view', () => {
124+
setup({ isStickyEnabled: true });
125+
126+
const wrapper = screen.getByTestId('workflow-history-header-wrapper');
127+
const sentinel = screen.getByTestId('sentinel');
128+
129+
expect(wrapper).toHaveAttribute('data-is-sticky', 'false');
130+
131+
act(() => {
132+
mockIsIntersecting(sentinel, 1);
133+
});
134+
135+
expect(wrapper).toHaveAttribute('data-is-sticky', 'false');
136+
});
137+
138+
it('should set sticky state to true when sentinel is out of view', () => {
139+
setup({ isStickyEnabled: true });
140+
141+
const wrapper = screen.getByTestId('workflow-history-header-wrapper');
142+
const sentinel = screen.getByTestId('sentinel');
143+
144+
expect(wrapper).toHaveAttribute('data-is-sticky', 'false');
145+
146+
act(() => {
147+
mockIsIntersecting(sentinel, 0);
148+
});
149+
150+
expect(wrapper).toHaveAttribute('data-is-sticky', 'true');
151+
});
152+
153+
it('should toggle sticky state when sentinel visibility changes', () => {
154+
setup({ isStickyEnabled: true });
155+
156+
const wrapper = screen.getByTestId('workflow-history-header-wrapper');
157+
const sentinel = screen.getByTestId('sentinel');
158+
159+
expect(wrapper).toHaveAttribute('data-is-sticky', 'false');
160+
161+
act(() => {
162+
mockIsIntersecting(sentinel, 0);
163+
});
164+
165+
expect(wrapper).toHaveAttribute('data-is-sticky', 'true');
166+
167+
act(() => {
168+
mockIsIntersecting(sentinel, 1);
169+
});
170+
171+
expect(wrapper).toHaveAttribute('data-is-sticky', 'false');
172+
});
173+
174+
it('should disable sticky when isStickyEnabled changes to false', () => {
175+
const { rerender } = setup({ isStickyEnabled: true });
176+
177+
const wrapper = screen.getByTestId('workflow-history-header-wrapper');
178+
const sentinel = screen.getByTestId('sentinel');
179+
180+
act(() => {
181+
mockIsIntersecting(sentinel, 0);
182+
});
183+
184+
expect(wrapper).toHaveAttribute('data-is-sticky', 'true');
185+
186+
rerender(
187+
<WorkflowHistoryHeader {...getDefaultProps()} isStickyEnabled={false} />
188+
);
189+
190+
expect(screen.queryByTestId('sentinel')).not.toBeInTheDocument();
191+
const newWrapper = screen.getByTestId('workflow-history-header-wrapper');
192+
expect(newWrapper).toHaveAttribute('data-is-sticky', 'false');
193+
});
194+
});
195+
196+
function setup(props: Partial<Props> = {}) {
197+
const user = userEvent.setup();
198+
const mockOnClickGroupModeToggle = jest.fn();
199+
200+
const defaultProps = getDefaultProps();
201+
const mergedProps = {
202+
...defaultProps,
203+
onClickGroupModeToggle: mockOnClickGroupModeToggle,
204+
...props,
205+
};
206+
207+
const renderResult = render(<WorkflowHistoryHeader {...mergedProps} />);
208+
209+
return {
210+
user,
211+
mockOnClickGroupModeToggle,
212+
...renderResult,
213+
};
214+
}
215+
216+
function getDefaultProps(): Props {
217+
return {
218+
isExpandAllEvents: false,
219+
toggleIsExpandAllEvents: jest.fn(),
220+
isUngroupedHistoryViewEnabled: false,
221+
onClickGroupModeToggle: jest.fn(),
222+
wfHistoryRequestArgs: {
223+
domain: 'test-domain',
224+
cluster: 'test-cluster',
225+
workflowId: 'test-workflowId',
226+
runId: 'test-runId',
227+
},
228+
pageFiltersProps: {
229+
activeFiltersCount: 0,
230+
queryParams: {
231+
historyEventTypes: undefined,
232+
historyEventStatuses: undefined,
233+
historySelectedEventId: undefined,
234+
ungroupedHistoryViewEnabled: undefined,
235+
},
236+
setQueryParams: jest.fn(),
237+
resetAllFilters: jest.fn(),
238+
},
239+
isStickyEnabled: true,
240+
};
241+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { styled as createStyled, type Theme } from 'baseui';
2+
import {
3+
type SegmentOverrides,
4+
type SegmentedControlOverrides,
5+
} from 'baseui/segmented-control';
6+
import { type StyleObject } from 'styletron-react';
7+
8+
export const styled = {
9+
Sentinel: createStyled('div', {
10+
height: '1px',
11+
visibility: 'hidden',
12+
}),
13+
Container: createStyled<
14+
'div',
15+
{ $isSticky?: boolean; $isStickyEnabled?: boolean }
16+
>(
17+
'div',
18+
({ $theme, $isSticky, $isStickyEnabled }): StyleObject => ({
19+
paddingTop: $theme.sizing.scale600,
20+
paddingBottom: $theme.sizing.scale600,
21+
marginTop: `-${$theme.sizing.scale600}`,
22+
backgroundColor: $theme.colors.backgroundPrimary,
23+
transition: 'box-shadow 0.2s ease-in-out',
24+
// Non-sticky by default or when disabled
25+
position: 'static',
26+
boxShadow: 'none',
27+
// Sticky only on medium screens and up when enabled
28+
...($isStickyEnabled && {
29+
[$theme.mediaQuery.medium]: {
30+
position: 'sticky',
31+
top: 0,
32+
boxShadow: $isSticky ? $theme.lighting.shallowBelow : 'none',
33+
zIndex: 1,
34+
},
35+
}),
36+
})
37+
),
38+
Header: createStyled(
39+
'div',
40+
({ $theme }: { $theme: Theme }): StyleObject => ({
41+
display: 'flex',
42+
flexDirection: 'column',
43+
justifyContent: 'space-between',
44+
flexWrap: 'wrap',
45+
gap: $theme.sizing.scale500,
46+
[$theme.mediaQuery.medium]: {
47+
alignItems: 'center',
48+
flexDirection: 'row',
49+
},
50+
})
51+
),
52+
Actions: createStyled(
53+
'div',
54+
({ $theme }: { $theme: Theme }): StyleObject => ({
55+
display: 'flex',
56+
flexDirection: 'column',
57+
flexWrap: 'wrap',
58+
gap: $theme.sizing.scale500,
59+
[$theme.mediaQuery.medium]: {
60+
flexDirection: 'row',
61+
},
62+
})
63+
),
64+
};
65+
66+
export const overrides = {
67+
groupToggle: {
68+
Root: {
69+
style: ({ $theme }: { $theme: Theme }): StyleObject => ({
70+
height: $theme.sizing.scale950,
71+
padding: $theme.sizing.scale0,
72+
borderRadius: $theme.borders.radius300,
73+
...$theme.typography.ParagraphSmall,
74+
width: 'auto',
75+
flexGrow: 1,
76+
[$theme.mediaQuery.medium]: {
77+
flexGrow: 0,
78+
},
79+
}),
80+
},
81+
SegmentList: {
82+
style: ({ $theme }: { $theme: Theme }): StyleObject => ({
83+
height: $theme.sizing.scale950,
84+
...$theme.typography.ParagraphSmall,
85+
}),
86+
},
87+
Active: {
88+
style: ({ $theme }: { $theme: Theme }): StyleObject => ({
89+
height: $theme.sizing.scale900,
90+
top: 0,
91+
}),
92+
},
93+
} satisfies SegmentedControlOverrides,
94+
groupToggleSegment: {
95+
Segment: {
96+
style: ({ $theme }: { $theme: Theme }): StyleObject => ({
97+
height: $theme.sizing.scale900,
98+
whiteSpace: 'nowrap',
99+
}),
100+
},
101+
} satisfies SegmentOverrides,
102+
};

0 commit comments

Comments
 (0)