Skip to content

Commit 9863204

Browse files
committed
SlidePanel tests
1 parent 0624602 commit 9863204

File tree

3 files changed

+198
-2
lines changed

3 files changed

+198
-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: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
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, 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+
// Mock panel's offsetWidth to be 400px
102+
Object.defineProperty(panel, 'offsetWidth', { value: 400, configurable: true })
103+
104+
// Simulate mousedown on resizer with clientX 800
105+
act(() => {
106+
resizer.dispatchEvent(
107+
new MouseEvent('mousedown', { clientX: 800, bubbles: true })
108+
)
109+
})
110+
111+
// Simulate mousemove on document with clientX such that new width is less than minWidth
112+
act(() => {
113+
document.dispatchEvent(
114+
new MouseEvent('mousemove', { clientX: 950, bubbles: true })
115+
)
116+
})
117+
118+
// Finish dragging
119+
act(() => {
120+
document.dispatchEvent(
121+
new MouseEvent('mouseup', { bubbles: true })
122+
)
123+
})
124+
125+
// resizingClientX was set to 800 + 400 = 1200 so new width = max(300, 1200 - 950) = 300
126+
expect(panel.style.width).toBe('300px')
127+
})
128+
129+
it('handles dragging to resize', () => {
130+
const { container } = render(
131+
<SlidePanel
132+
mainContent={<div>Main</div>}
133+
panelContent={<div>Panel</div>}
134+
isPanelOpen
135+
/>
136+
)
137+
const resizer = container.querySelector('.resizer') as HTMLElement
138+
const panel = container.querySelector('.slidePanel') as HTMLElement
139+
expect(panel.style.width).toBe('400px')
140+
141+
// Mock panel's offsetWidth to be 400px
142+
Object.defineProperty(panel, 'offsetWidth', { value: 400, configurable: true })
143+
144+
// Simulate mousedown on resizer with clientX 800
145+
act(() => {
146+
resizer.dispatchEvent(
147+
new MouseEvent('mousedown', { clientX: 800, bubbles: true })
148+
)
149+
})
150+
151+
// Now simulate dragging: mousemove on document with clientX 750
152+
act(() => {
153+
document.dispatchEvent(
154+
new MouseEvent('mousemove', { clientX: 750, bubbles: true })
155+
)
156+
})
157+
158+
// End dragging with mouseup on document
159+
act(() => {
160+
document.dispatchEvent(
161+
new MouseEvent('mouseup', { bubbles: true })
162+
)
163+
})
164+
165+
// Expected new width = 1200 - 750 = 450
166+
expect(panel.style.width).toBe('450px')
167+
expect(localStorage.getItem('panelWidth')).toBe('450')
168+
})
169+
170+
it('uses config defaultWidth if valid', () => {
171+
const { container } = render(
172+
<SlidePanel
173+
mainContent={<div>Main</div>}
174+
panelContent={<div>Panel</div>}
175+
isPanelOpen
176+
config={{ slidePanel: { defaultWidth: 500 } }}
177+
/>
178+
)
179+
const panel = container.querySelector('.slidePanel') as HTMLElement
180+
expect(panel.style.width).toBe('500px')
181+
})
182+
183+
it('ignores negative config.defaultWidth and uses 400 instead', () => {
184+
const { container } = render(
185+
<SlidePanel
186+
mainContent={<div>Main</div>}
187+
panelContent={<div>Panel</div>}
188+
isPanelOpen
189+
config={{ slidePanel: { defaultWidth: -10 } }}
190+
/>
191+
)
192+
const panel = container.querySelector('.slidePanel') as HTMLElement
193+
expect(panel.style.width).toBe('400px')
194+
})
195+
})

0 commit comments

Comments
 (0)