Skip to content

Commit 5f78655

Browse files
committed
SlidePanel tests
1 parent 0624602 commit 5f78655

File tree

3 files changed

+179
-2
lines changed

3 files changed

+179
-2
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
[![npm](https://img.shields.io/npm/v/hyperparam)](https://www.npmjs.com/package/hyperparam)
44
[![workflow status](https://github.com/hyparam/hyperparam-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/hyparam/hyperparam-cli/actions)
55
[![mit license](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
6-
![coverage](https://img.shields.io/badge/Coverage-56-darkred)
6+
![coverage](https://img.shields.io/badge/Coverage-59-darkred)
77

88
This is the hyperparam cli tool.
99

src/components/viewers/SlidePanel.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ export default function SlidePanel({
3939
// Load initial panel width from localStorage if available
4040
const [panelWidth, setPanelWidth] = useState<number>(() => {
4141
const savedWidth = typeof window !== 'undefined' ? localStorage.getItem('panelWidth') : null
42-
return savedWidth ? parseInt(savedWidth, 10) : defaultWidth
42+
const parsedWidth = savedWidth ? parseInt(savedWidth, 10) : NaN
43+
return !isNaN(parsedWidth) ? parsedWidth : defaultWidth
4344
})
4445

4546
useEffect(() => {
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
/* eslint-disable @typescript-eslint/non-nullable-type-assertion-style */
2+
import React from 'react'
3+
import { beforeEach, describe, expect, it, vi } from 'vitest'
4+
import { act, fireEvent, render } from '@testing-library/react'
5+
import SlidePanel from '../../../src/components/viewers/SlidePanel.js'
6+
7+
describe('SlidePanel', () => {
8+
// Minimal localStorage mock
9+
const localStorageMock = (() => {
10+
let store: Record<string, string> = {}
11+
return {
12+
getItem: (key: string) => store[key] ?? null,
13+
setItem: (key: string, value: string) => { store[key] = value },
14+
clear: () => { store = {} },
15+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
16+
removeItem: (key: string) => { delete store[key] },
17+
}
18+
})()
19+
20+
beforeEach(() => {
21+
vi.stubGlobal('localStorage', localStorageMock)
22+
localStorage.clear()
23+
})
24+
25+
it('renders main and panel content', () => {
26+
const { getByText } = render(
27+
<SlidePanel
28+
mainContent={<div data-testid="main-content">Main</div>}
29+
panelContent={<div data-testid="panel-content">Panel</div>}
30+
isPanelOpen
31+
/>
32+
)
33+
expect(getByText('Main')).toBeDefined()
34+
expect(getByText('Panel')).toBeDefined()
35+
})
36+
37+
it('does not render the resizer if panel is closed', () => {
38+
const { container } = render(
39+
<SlidePanel
40+
mainContent={<div>Main</div>}
41+
panelContent={<div>Panel</div>}
42+
isPanelOpen={false}
43+
/>
44+
)
45+
const resizer = container.querySelector('.resizer')
46+
expect(resizer).toBeNull()
47+
})
48+
49+
it('uses default width of 400 when localStorage is empty', () => {
50+
const { container } = render(
51+
<SlidePanel
52+
mainContent={<div>Main</div>}
53+
panelContent={<div>Panel</div>}
54+
isPanelOpen
55+
/>
56+
)
57+
const panel = container.querySelector('.slidePanel') as HTMLElement
58+
expect(panel.style.width).toBe('400px')
59+
})
60+
61+
it('loads width from localStorage if present', () => {
62+
localStorage.setItem('panelWidth', '250')
63+
const { container } = render(
64+
<SlidePanel
65+
mainContent={<div>Main</div>}
66+
panelContent={<div>Panel</div>}
67+
isPanelOpen
68+
/>
69+
)
70+
const panel = container.querySelector('.slidePanel') as HTMLElement
71+
expect(panel.style.width).toBe('250px')
72+
})
73+
74+
it('falls back to default width if localStorage width is invalid', () => {
75+
localStorage.setItem('panelWidth', 'not-a-number')
76+
const { container } = render(
77+
<SlidePanel
78+
mainContent={<div>Main</div>}
79+
panelContent={<div>Panel</div>}
80+
isPanelOpen
81+
/>
82+
)
83+
const panel = container.querySelector('.slidePanel') as HTMLElement
84+
// parseInt of 'not-a-number' yields NaN so default width of 400 is expected
85+
expect(panel.style.width).toBe('400px')
86+
})
87+
88+
it('respects minWidth from config', () => {
89+
const { container } = render(
90+
<SlidePanel
91+
mainContent={<div>Main</div>}
92+
panelContent={<div>Panel</div>}
93+
isPanelOpen
94+
config={{ slidePanel: { minWidth: 300 } }}
95+
/>
96+
)
97+
const resizer = container.querySelector('.resizer') as HTMLElement
98+
const panel = container.querySelector('.slidePanel') as HTMLElement
99+
expect(panel.style.width).toBe('400px')
100+
101+
// Simulate mousedown on resizer with clientX 800
102+
act(() => {
103+
fireEvent.mouseDown(resizer, { clientX: 800 })
104+
})
105+
106+
// Simulate mousemove on document with clientX such that new width is less than minWidth
107+
act(() => {
108+
fireEvent.mouseMove(document, { clientX: 950 })
109+
fireEvent.mouseUp(document)
110+
})
111+
112+
// resizingClientX was set to 800 + 400 = 1200 so new width = max(300, 1200 - 950) = 300
113+
expect(panel.style.width).toBe('300px')
114+
})
115+
116+
it('handles dragging to resize', () => {
117+
const { container } = render(
118+
<SlidePanel
119+
mainContent={<div>Main</div>}
120+
panelContent={<div>Panel</div>}
121+
isPanelOpen
122+
/>
123+
)
124+
const resizer = container.querySelector('.resizer') as HTMLElement
125+
const panel = container.querySelector('.slidePanel') as HTMLElement
126+
expect(panel.style.width).toBe('400px')
127+
128+
// Mock panel's offsetWidth to be 400px
129+
Object.defineProperty(panel, 'offsetWidth', { value: 400, configurable: true })
130+
131+
// Simulate mousedown
132+
act(() => {
133+
fireEvent.mouseDown(resizer, { clientX: 800 })
134+
})
135+
136+
// Simulate dragging
137+
act(() => {
138+
fireEvent.mouseMove(document, { clientX: 750 })
139+
})
140+
141+
// End dragging
142+
act(() => {
143+
fireEvent.mouseUp(document)
144+
})
145+
146+
// Expected new width = 1200 - 750 = 450
147+
expect(panel.style.width).toBe('450px')
148+
expect(localStorage.getItem('panelWidth')).toBe('450')
149+
})
150+
151+
it('uses config defaultWidth if valid', () => {
152+
const { container } = render(
153+
<SlidePanel
154+
mainContent={<div>Main</div>}
155+
panelContent={<div>Panel</div>}
156+
isPanelOpen
157+
config={{ slidePanel: { defaultWidth: 500 } }}
158+
/>
159+
)
160+
const panel = container.querySelector('.slidePanel') as HTMLElement
161+
expect(panel.style.width).toBe('500px')
162+
})
163+
164+
it('ignores negative config.defaultWidth and uses 400 instead', () => {
165+
const { container } = render(
166+
<SlidePanel
167+
mainContent={<div>Main</div>}
168+
panelContent={<div>Panel</div>}
169+
isPanelOpen
170+
config={{ slidePanel: { defaultWidth: -10 } }}
171+
/>
172+
)
173+
const panel = container.querySelector('.slidePanel') as HTMLElement
174+
expect(panel.style.width).toBe('400px')
175+
})
176+
})

0 commit comments

Comments
 (0)