Compare commits

...

2 Commits

Author SHA1 Message Date
Joe Li
0b442ef9ae fix(dashboard): address review feedback and fix test type errors
- Assert the actual .empty-droptarget element exists on the Droppable
  (addresses Copilot review feedback on PR #39423)
- Add @emotion/jest type reference to fix pre-existing toHaveStyleRule
  TS errors in this test file
- Prettier formatting fix

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-30 13:35:04 -07:00
Joe Li
5bb88b2b74 fix(dashboard): restore top-level tab drop target height for dashboards with content
PR #37891 moved the DashboardHeader out of the root Droppable, leaving
it with zero height when no top-level tabs exist. This made it impossible
to drag a Tabs component onto dashboards that already have content.

Add min-height to .empty-droptarget in StyledHeader, matching the pattern
used by DashboardGrid for its drop targets.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-30 09:49:14 -07:00
2 changed files with 53 additions and 0 deletions

View File

@@ -16,6 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
/// <reference types="@emotion/jest" />
import fetchMock from 'fetch-mock';
import {
fireEvent,
@@ -487,6 +488,54 @@ test('should render ParentSize wrapper with height 100% for tabs', async () => {
expect(tabPanels.length).toBeGreaterThan(0);
});
test('should apply min-height to the top-level tab drop target so tabs can be dropped on dashboards with content', () => {
(useStoredSidebarWidth as jest.Mock).mockImplementation(() => [
100,
jest.fn(),
]);
(fetchFaveStar as jest.Mock).mockReturnValue({ type: 'mock-action' });
(setActiveTab as jest.Mock).mockReturnValue({ type: 'mock-action' });
const { container } = render(<DashboardBuilder />, {
useRedux: true,
store: storeWithState({
...mockState,
dashboardLayout: undoableDashboardLayout,
dashboardState: { ...mockState.dashboardState, editMode: true },
}),
useDnd: true,
useTheme: true,
});
const headerWrapper = container.querySelector(
'[data-test="dashboard-header-wrapper"]',
);
expect(headerWrapper).toBeInTheDocument();
// The Droppable inside the header should have the empty-droptarget class
// when there are no top-level tabs and edit mode is active. Without this
// class (and its associated min-height CSS rule), the drop target has zero
// height and users cannot drag tabs onto dashboards that already have
// content.
const droptarget = headerWrapper!.querySelector('.empty-droptarget');
expect(droptarget).toBeInTheDocument();
// Verify the StyledHeader CSS defines min-height for .empty-droptarget.
// getComputedStyle doesn't work in jsdom for styled-components injected
// styles, so we check the CSSOM directly.
const allRules = Array.from(document.styleSheets).flatMap(sheet => {
try {
return Array.from(sheet.cssRules).map(rule => rule.cssText);
} catch {
return [];
}
});
const emptyDroptargetRule = allRules.find(
rule => rule.includes('.empty-droptarget') && rule.includes('min-height'),
);
expect(emptyDroptargetRule).toBeDefined();
});
test('should maintain layout when switching between tabs', async () => {
(useStoredSidebarWidth as jest.Mock).mockImplementation(() => [
100,

View File

@@ -99,6 +99,10 @@ const StyledHeader = styled.div<{ filterBarWidth: number }>`
z-index: 99;
max-width: calc(100vw - ${filterBarWidth}px);
.empty-droptarget {
min-height: ${theme.sizeUnit * 4}px;
}
.empty-droptarget:before {
position: absolute;
content: '';