Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Test

on:
push:
branches: [main, master]
pull_request:
branches: [main, master]

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 7

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'pnpm'

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Run tests
run: pnpm test:run

2 changes: 1 addition & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ function MyApp() {

### Disabling the GUI

Each instance of the `useControls` hook will render the panel. If you want to completely disable the GUI based on preferences, you need to explicitly set `hidden` to false.
Each instance of the `useControls` hook will render the panel. If you want to completely disable the GUI based on preferences, you need to explicitly set `hidden` to true.

```jsx
import { Leva } from 'leva'
Expand Down
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
"demo:build": "pnpm --filter demo run build",
"demo:serve": "pnpm --filter demo run serve",
"ci:test": "pnpm demo:build && start-server-and-test demo:serve http-get://localhost:5000 cypress:run",
"test": "vitest",
"test:run": "vitest run",
"prettier": "prettier --check ."
},
"size-limit": [
Expand Down Expand Up @@ -105,7 +107,10 @@
"start-server-and-test": "^1.15.2",
"storybook": "^10.0.2",
"tsd": "^0.25.0",
"typescript": "^5.7.2"
"typescript": "^5.7.2",
"vitest": "^2.1.0",
"@vitest/ui": "^2.1.0",
"jsdom": "^24.0.0"
},
"prettier": {
"bracketSameLine": true,
Expand Down
126 changes: 126 additions & 0 deletions packages/leva/src/__tests__/useControls.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { describe, expect, it } from 'vitest'
import { renderHook, waitFor } from '@testing-library/react'
import { useControls } from '../useControls'
import { levaStore } from '../store'

describe('useControls', () => {
it('should return values from schema', async () => {
const { result } = renderHook(() =>
useControls({
number: 5,
string: 'test',
boolean: true,
})
)

expect(result.current).toEqual({
number: 5,
string: 'test',
boolean: true,
})

// Verify data is in the store
await waitFor(() => {
const storeData = levaStore.getData()
expect(storeData['number']).toBeDefined()
expect(storeData['string']).toBeDefined()
expect(storeData['boolean']).toBeDefined()
})

// Verify visible paths
await waitFor(() => {
const visiblePaths = levaStore.getVisiblePaths()
expect(visiblePaths.length).toBeGreaterThan(0)
expect(visiblePaths).toContain('number')
expect(visiblePaths).toContain('string')
expect(visiblePaths).toContain('boolean')
})
})

it('should have all controls from two different components in the store', async () => {
// Mount first component
const { result: result1 } = renderHook(() =>
useControls({
name: 'John',
age: 30,
})
)

// Mount second component
const { result: result2 } = renderHook(() =>
useControls({
city: 'NYC',
active: true,
})
)

expect(result1.current).toEqual({
name: 'John',
age: 30,
})

expect(result2.current).toEqual({
city: 'NYC',
active: true,
})

// Verify all controls from both components are in the store
await waitFor(() => {
const storeData = levaStore.getData()
expect(storeData['name']).toBeDefined()
expect(storeData['age']).toBeDefined()
expect(storeData['city']).toBeDefined()
expect(storeData['active']).toBeDefined()
})

// Verify all visible paths
await waitFor(() => {
const visiblePaths = levaStore.getVisiblePaths()
expect(visiblePaths).toContain('name')
expect(visiblePaths).toContain('age')
expect(visiblePaths).toContain('city')
expect(visiblePaths).toContain('active')
expect(visiblePaths.length).toBeGreaterThanOrEqual(4)
})
})

it('should remove controls from visible paths when a hook unmounts', async () => {
// Mount first component
const { unmount: unmountFirst } = renderHook(() =>
useControls({
name: 'John',
age: 30,
})
)

// Mount second component
renderHook(() =>
useControls({
city: 'NYC',
active: true,
})
)

// Verify all controls are visible initially
await waitFor(() => {
const visiblePaths = levaStore.getVisiblePaths()
expect(visiblePaths).toContain('name')
expect(visiblePaths).toContain('age')
expect(visiblePaths).toContain('city')
expect(visiblePaths).toContain('active')
})

// Unmount first component
unmountFirst()

// Verify first component's controls are no longer visible
await waitFor(() => {
const visiblePaths = levaStore.getVisiblePaths()
expect(visiblePaths).not.toContain('name')
expect(visiblePaths).not.toContain('age')
// Second component's controls should still be visible
expect(visiblePaths).toContain('city')
expect(visiblePaths).toContain('active')
})
})
})
Loading
Loading