Skip to content

Commit 0fce5ec

Browse files
fix(dashboard): normalize spacings and background colors (#35001)
1 parent 385471c commit 0fce5ec

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1589
-623
lines changed
Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
import { fireEvent, render } from '@superset-ui/core/spec';
21+
import Tabs, { EditableTabs, LineEditableTabs } from './Tabs';
22+
23+
describe('Tabs', () => {
24+
const defaultItems = [
25+
{
26+
key: '1',
27+
label: 'Tab 1',
28+
children: <div data-testid="tab1-content">Tab 1 content</div>,
29+
},
30+
{
31+
key: '2',
32+
label: 'Tab 2',
33+
children: <div data-testid="tab2-content">Tab 2 content</div>,
34+
},
35+
{
36+
key: '3',
37+
label: 'Tab 3',
38+
children: <div data-testid="tab3-content">Tab 3 content</div>,
39+
},
40+
];
41+
42+
describe('Basic Tabs', () => {
43+
it('should render tabs with default props', () => {
44+
const { getByText, container } = render(<Tabs items={defaultItems} />);
45+
46+
expect(getByText('Tab 1')).toBeInTheDocument();
47+
expect(getByText('Tab 2')).toBeInTheDocument();
48+
expect(getByText('Tab 3')).toBeInTheDocument();
49+
50+
const activeTabContent = container.querySelector(
51+
'.ant-tabs-tabpane-active',
52+
);
53+
54+
expect(activeTabContent).toBeDefined();
55+
expect(
56+
activeTabContent?.querySelector('[data-testid="tab1-content"]'),
57+
).toBeDefined();
58+
});
59+
60+
it('should render tabs component structure', () => {
61+
const { container } = render(<Tabs items={defaultItems} />);
62+
const tabsElement = container.querySelector('.ant-tabs');
63+
const tabsNav = container.querySelector('.ant-tabs-nav');
64+
const tabsContent = container.querySelector('.ant-tabs-content-holder');
65+
66+
expect(tabsElement).toBeDefined();
67+
expect(tabsNav).toBeDefined();
68+
expect(tabsContent).toBeDefined();
69+
});
70+
71+
it('should apply default tabBarStyle with padding', () => {
72+
const { container } = render(<Tabs items={defaultItems} />);
73+
const tabsNav = container.querySelector('.ant-tabs-nav') as HTMLElement;
74+
75+
// Check that tabBarStyle is applied (default padding is added)
76+
expect(tabsNav?.style?.paddingLeft).toBeDefined();
77+
});
78+
79+
it('should merge custom tabBarStyle with defaults', () => {
80+
const customStyle = { paddingRight: '20px', backgroundColor: 'red' };
81+
const { container } = render(
82+
<Tabs items={defaultItems} tabBarStyle={customStyle} />,
83+
);
84+
const tabsNav = container.querySelector('.ant-tabs-nav') as HTMLElement;
85+
86+
expect(tabsNav?.style?.paddingLeft).toBeDefined();
87+
expect(tabsNav?.style?.paddingRight).toBe('20px');
88+
expect(tabsNav?.style?.backgroundColor).toBe('red');
89+
});
90+
91+
it('should handle allowOverflow prop', () => {
92+
const { container: allowContainer } = render(
93+
<Tabs items={defaultItems} allowOverflow />,
94+
);
95+
const { container: disallowContainer } = render(
96+
<Tabs items={defaultItems} allowOverflow={false} />,
97+
);
98+
99+
expect(allowContainer.querySelector('.ant-tabs')).toBeDefined();
100+
expect(disallowContainer.querySelector('.ant-tabs')).toBeDefined();
101+
});
102+
103+
it('should disable animation by default', () => {
104+
const { container } = render(<Tabs items={defaultItems} />);
105+
const tabsElement = container.querySelector('.ant-tabs');
106+
107+
expect(tabsElement?.className).not.toContain('ant-tabs-animated');
108+
});
109+
110+
it('should handle tab change events', () => {
111+
const onChangeMock = jest.fn();
112+
const { getByText } = render(
113+
<Tabs items={defaultItems} onChange={onChangeMock} />,
114+
);
115+
116+
fireEvent.click(getByText('Tab 2'));
117+
118+
expect(onChangeMock).toHaveBeenCalledWith('2');
119+
});
120+
121+
it('should pass through additional props to Antd Tabs', () => {
122+
const onTabClickMock = jest.fn();
123+
const { getByText } = render(
124+
<Tabs
125+
items={defaultItems}
126+
onTabClick={onTabClickMock}
127+
size="large"
128+
centered
129+
/>,
130+
);
131+
132+
fireEvent.click(getByText('Tab 2'));
133+
134+
expect(onTabClickMock).toHaveBeenCalled();
135+
});
136+
});
137+
138+
describe('EditableTabs', () => {
139+
it('should render with editable features', () => {
140+
const { container } = render(<EditableTabs items={defaultItems} />);
141+
142+
const tabsElement = container.querySelector('.ant-tabs');
143+
144+
expect(tabsElement?.className).toContain('ant-tabs-card');
145+
expect(tabsElement?.className).toContain('ant-tabs-editable-card');
146+
});
147+
148+
it('should handle onEdit callback for add/remove actions', () => {
149+
const onEditMock = jest.fn();
150+
const itemsWithRemove = defaultItems.map(item => ({
151+
...item,
152+
closable: true,
153+
}));
154+
155+
const { container } = render(
156+
<EditableTabs items={itemsWithRemove} onEdit={onEditMock} />,
157+
);
158+
159+
const removeButton = container.querySelector('.ant-tabs-tab-remove');
160+
expect(removeButton).toBeDefined();
161+
162+
fireEvent.click(removeButton!);
163+
expect(onEditMock).toHaveBeenCalledWith(expect.any(String), 'remove');
164+
});
165+
166+
it('should have default props set correctly', () => {
167+
expect(EditableTabs.defaultProps?.type).toBe('editable-card');
168+
expect(EditableTabs.defaultProps?.animated).toEqual({
169+
inkBar: true,
170+
tabPane: false,
171+
});
172+
});
173+
});
174+
175+
describe('LineEditableTabs', () => {
176+
it('should render as line-style editable tabs', () => {
177+
const { container } = render(<LineEditableTabs items={defaultItems} />);
178+
179+
const tabsElement = container.querySelector('.ant-tabs');
180+
181+
expect(tabsElement?.className).toContain('ant-tabs-card');
182+
expect(tabsElement?.className).toContain('ant-tabs-editable-card');
183+
});
184+
185+
it('should render with line-specific styling', () => {
186+
const { container } = render(<LineEditableTabs items={defaultItems} />);
187+
188+
const inkBar = container.querySelector('.ant-tabs-ink-bar');
189+
expect(inkBar).toBeDefined();
190+
});
191+
});
192+
193+
describe('TabPane Legacy Support', () => {
194+
it('should support TabPane component access', () => {
195+
expect(Tabs.TabPane).toBeDefined();
196+
expect(EditableTabs.TabPane).toBeDefined();
197+
expect(LineEditableTabs.TabPane).toBeDefined();
198+
});
199+
200+
it('should render using legacy TabPane syntax', () => {
201+
const { getByText, container } = render(
202+
<Tabs>
203+
<Tabs.TabPane tab="Legacy Tab 1" key="1">
204+
<div data-testid="legacy-content-1">Legacy content 1</div>
205+
</Tabs.TabPane>
206+
<Tabs.TabPane tab="Legacy Tab 2" key="2">
207+
<div data-testid="legacy-content-2">Legacy content 2</div>
208+
</Tabs.TabPane>
209+
</Tabs>,
210+
);
211+
212+
expect(getByText('Legacy Tab 1')).toBeInTheDocument();
213+
expect(getByText('Legacy Tab 2')).toBeInTheDocument();
214+
215+
const activeTabContent = container.querySelector(
216+
'.ant-tabs-tabpane-active [data-testid="legacy-content-1"]',
217+
);
218+
219+
expect(activeTabContent).toBeDefined();
220+
expect(activeTabContent?.textContent).toBe('Legacy content 1');
221+
});
222+
});
223+
224+
describe('Edge Cases', () => {
225+
it('should handle empty items array', () => {
226+
const { container } = render(<Tabs items={[]} />);
227+
const tabsElement = container.querySelector('.ant-tabs');
228+
229+
expect(tabsElement).toBeDefined();
230+
});
231+
232+
it('should handle undefined items', () => {
233+
const { container } = render(<Tabs />);
234+
const tabsElement = container.querySelector('.ant-tabs');
235+
236+
expect(tabsElement).toBeDefined();
237+
});
238+
239+
it('should handle tabs with no content', () => {
240+
const itemsWithoutContent = [
241+
{ key: '1', label: 'Tab 1' },
242+
{ key: '2', label: 'Tab 2' },
243+
];
244+
245+
const { getByText } = render(<Tabs items={itemsWithoutContent} />);
246+
247+
expect(getByText('Tab 1')).toBeInTheDocument();
248+
expect(getByText('Tab 2')).toBeInTheDocument();
249+
});
250+
251+
it('should handle allowOverflow default value', () => {
252+
const { container } = render(<Tabs items={defaultItems} />);
253+
expect(container.querySelector('.ant-tabs')).toBeDefined();
254+
});
255+
});
256+
257+
describe('Accessibility', () => {
258+
it('should render with proper ARIA roles', () => {
259+
const { container } = render(<Tabs items={defaultItems} />);
260+
261+
const tablist = container.querySelector('[role="tablist"]');
262+
const tabs = container.querySelectorAll('[role="tab"]');
263+
264+
expect(tablist).toBeDefined();
265+
expect(tabs.length).toBe(3);
266+
});
267+
268+
it('should support keyboard navigation', () => {
269+
const { container, getByText } = render(<Tabs items={defaultItems} />);
270+
271+
const firstTab = container.querySelector('[role="tab"]');
272+
const secondTab = getByText('Tab 2');
273+
274+
if (firstTab) {
275+
fireEvent.keyDown(firstTab, { key: 'ArrowRight', code: 'ArrowRight' });
276+
}
277+
278+
fireEvent.click(secondTab);
279+
280+
expect(secondTab).toBeInTheDocument();
281+
});
282+
});
283+
284+
describe('Styling Integration', () => {
285+
it('should accept and apply custom CSS classes', () => {
286+
const { container } = render(
287+
<Tabs items={defaultItems} className="custom-tabs-class" />,
288+
);
289+
290+
const tabsElement = container.querySelector('.ant-tabs');
291+
292+
expect(tabsElement?.className).toContain('custom-tabs-class');
293+
});
294+
295+
it('should accept and apply custom styles', () => {
296+
const customStyle = { minHeight: '200px' };
297+
const { container } = render(
298+
<Tabs items={defaultItems} style={customStyle} />,
299+
);
300+
301+
const tabsElement = container.querySelector('.ant-tabs') as HTMLElement;
302+
303+
expect(tabsElement?.style?.minHeight).toBe('200px');
304+
});
305+
});
306+
});

superset-frontend/packages/superset-ui-core/src/components/Tabs/Tabs.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,18 @@ export interface TabsProps extends AntdTabsProps {
2929
const StyledTabs = ({
3030
animated = false,
3131
allowOverflow = true,
32+
tabBarStyle,
3233
...props
3334
}: TabsProps) => {
3435
const theme = useTheme();
36+
const defaultTabBarStyle = { paddingLeft: theme.sizeUnit * 4 };
37+
const mergedStyle = { ...defaultTabBarStyle, ...tabBarStyle };
38+
3539
return (
3640
<AntdTabs
3741
animated={animated}
3842
{...props}
39-
tabBarStyle={{ paddingLeft: theme.sizeUnit * 4 }}
43+
tabBarStyle={mergedStyle}
4044
css={theme => css`
4145
overflow: ${allowOverflow ? 'visible' : 'hidden'};
4246

superset-frontend/src/dashboard/actions/dashboardState.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ import {
8080
getDynamicLabelsColors,
8181
} from '../../utils/colorScheme';
8282

83+
export const TOGGLE_NATIVE_FILTERS_BAR = 'TOGGLE_NATIVE_FILTERS_BAR';
84+
export function toggleNativeFiltersBar(isOpen) {
85+
return { type: TOGGLE_NATIVE_FILTERS_BAR, isOpen };
86+
}
87+
8388
export const SET_UNSAVED_CHANGES = 'SET_UNSAVED_CHANGES';
8489
export function setUnsavedChanges(hasUnsavedChanges) {
8590
return { type: SET_UNSAVED_CHANGES, payload: { hasUnsavedChanges } };

superset-frontend/src/dashboard/components/BuilderComponentPane/BuilderComponentPane.test.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,14 @@ jest.mock('src/dashboard/containers/SliceAdder', () => () => (
2525
));
2626

2727
test('BuilderComponentPane has correct tabs in correct order', () => {
28-
render(<BuilderComponentPane topOffset={115} />);
28+
render(<BuilderComponentPane topOffset={115} />, {
29+
useRedux: true,
30+
initialState: {
31+
dashboardState: {
32+
nativeFiltersBarOpen: false,
33+
},
34+
},
35+
});
2936
const tabs = screen.getAllByRole('tab');
3037
expect(tabs).toHaveLength(2);
3138
expect(tabs[0]).toHaveTextContent('Charts');

0 commit comments

Comments
 (0)