A lot of refectoring

This commit is contained in:
Hein
2025-10-21 14:10:59 +02:00
parent 4186219c50
commit c92cabc569
24 changed files with 756 additions and 626 deletions

View File

@@ -1,19 +1,17 @@
import { MantineProvider } from '@mantine/core'
import { render, screen } from '@testing-library/react'
import { describe, expect, it } from 'vitest'
import { MantineProvider } from '@mantine/core';
import { render, screen } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import { MantineBetterMenusProvider } from './MantineBetterMenu'
import { MantineBetterMenusProvider } from './MantineBetterMenu';
// Mock the MenuRenderer component since it likely has complex portal logic
vi.mock('./MenuRenderer', () => ({
MenuRenderer: () => <div data-testid="menu-renderer">Menu Renderer</div>
}))
MenuRenderer: () => <div data-testid="menu-renderer">Menu Renderer</div>,
}));
const TestWrapper = ({ children }: { children: React.ReactNode }) => (
<MantineProvider>
{children}
</MantineProvider>
)
<MantineProvider>{children}</MantineProvider>
);
describe('MantineBetterMenusProvider', () => {
it('renders children correctly', () => {
@@ -23,10 +21,10 @@ describe('MantineBetterMenusProvider', () => {
<div data-testid="test-child">Test Child</div>
</MantineBetterMenusProvider>
</TestWrapper>
)
);
expect(screen.getByTestId('test-child')).toBeInTheDocument()
})
expect(screen.getByTestId('test-child')).toBeInTheDocument();
});
it('renders MenuRenderer component', () => {
render(
@@ -35,10 +33,10 @@ describe('MantineBetterMenusProvider', () => {
<div>Test</div>
</MantineBetterMenusProvider>
</TestWrapper>
)
);
expect(screen.getByTestId('menu-renderer')).toBeInTheDocument()
})
expect(screen.getByTestId('menu-renderer')).toBeInTheDocument();
});
it('accepts providerID prop', () => {
render(
@@ -47,9 +45,9 @@ describe('MantineBetterMenusProvider', () => {
<div>Test</div>
</MantineBetterMenusProvider>
</TestWrapper>
)
);
// Component should render without errors
expect(screen.getByTestId('menu-renderer')).toBeInTheDocument()
})
})
expect(screen.getByTestId('menu-renderer')).toBeInTheDocument();
});
});

View File

@@ -1,7 +1,9 @@
import { MenuRenderer } from './MenuRenderer';
import { MantineBetterMenusStoreProvider, type MenuStoreProps } from './Store';
import { MantineBetterMenusStoreProvider, type MantineBetterMenuStoreProps } from './Store';
export function MantineBetterMenusProvider(props: React.PropsWithChildren<MenuStoreProps>) {
export function MantineBetterMenusProvider(
props: React.PropsWithChildren<MantineBetterMenuStoreProps>
) {
return (
<MantineBetterMenusStoreProvider {...props}>
<MenuRenderer />

View File

@@ -1,100 +1,98 @@
import { act, renderHook } from '@testing-library/react'
import { beforeEach, describe, expect, it } from 'vitest'
import { act, renderHook } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { MantineBetterMenusStoreProvider, useMantineBetterMenus } from './Store'
import { MantineBetterMenusStoreProvider, useMantineBetterMenus } from './Store';
// Mock dependencies
vi.mock('@warkypublic/artemis-kit', () => ({
getUUID: () => 'test-uuid-123'
}))
getUUID: () => 'test-uuid-123',
}));
const createWrapper = () => {
return ({ children }: { children: React.ReactNode }) => (
<MantineBetterMenusStoreProvider>
{children}
</MantineBetterMenusStoreProvider>
)
}
<MantineBetterMenusStoreProvider>{children}</MantineBetterMenusStoreProvider>
);
};
describe('MantineBetterMenus Store', () => {
let wrapper: ReturnType<typeof createWrapper>
let wrapper: ReturnType<typeof createWrapper>;
beforeEach(() => {
wrapper = createWrapper()
})
wrapper = createWrapper();
});
it('initializes with empty menus array', () => {
const { result } = renderHook(() => useMantineBetterMenus(), { wrapper })
const { result } = renderHook(() => useMantineBetterMenus(), { wrapper });
expect(result.current.menus).toEqual([])
})
expect(result.current.menus).toEqual([]);
});
it('can show a menu', () => {
const { result } = renderHook(() => useMantineBetterMenus(), { wrapper })
const { result } = renderHook(() => useMantineBetterMenus(), { wrapper });
act(() => {
result.current.show('test-menu', {
items: [{ label: 'Test Item' }],
x: 100,
y: 200
})
})
y: 200,
});
});
expect(result.current.menus).toHaveLength(1)
expect(result.current.menus).toHaveLength(1);
expect(result.current.menus[0]).toMatchObject({
id: 'test-menu',
items: [{ label: 'Test Item' }],
visible: true,
x: 100,
y: 200
})
})
y: 200,
});
});
it('can hide a menu', () => {
const { result } = renderHook(() => useMantineBetterMenus(), { wrapper })
const { result } = renderHook(() => useMantineBetterMenus(), { wrapper });
// First show a menu
act(() => {
result.current.show('test-menu', { x: 100, y: 200 })
})
result.current.show('test-menu', { x: 100, y: 200 });
});
expect(result.current.menus[0].visible).toBe(true)
expect(result.current.menus[0].visible).toBe(true);
// Then hide it
act(() => {
result.current.hide('test-menu')
})
result.current.hide('test-menu');
});
expect(result.current.menus[0].visible).toBe(false)
})
expect(result.current.menus[0].visible).toBe(false);
});
it('can update instance state', () => {
const { result } = renderHook(() => useMantineBetterMenus(), { wrapper })
const { result } = renderHook(() => useMantineBetterMenus(), { wrapper });
// Show a menu
act(() => {
result.current.show('test-menu', { x: 100, y: 200 })
})
result.current.show('test-menu', { x: 100, y: 200 });
});
// Update its position
act(() => {
result.current.setInstanceState('test-menu', 'x', 300)
})
result.current.setInstanceState('test-menu', 'x', 300);
});
expect(result.current.menus[0].x).toBe(300)
expect(result.current.menus[0].y).toBe(200) // Should remain unchanged
})
expect(result.current.menus[0].x).toBe(300);
expect(result.current.menus[0].y).toBe(200); // Should remain unchanged
});
it('handles multiple menus', () => {
const { result } = renderHook(() => useMantineBetterMenus(), { wrapper })
const { result } = renderHook(() => useMantineBetterMenus(), { wrapper });
act(() => {
result.current.show('menu-1', { x: 100, y: 200 })
result.current.show('menu-2', { x: 300, y: 400 })
})
result.current.show('menu-1', { x: 100, y: 200 });
result.current.show('menu-2', { x: 300, y: 400 });
});
expect(result.current.menus).toHaveLength(2)
expect(result.current.menus.find(m => m.id === 'menu-1')).toBeDefined()
expect(result.current.menus.find(m => m.id === 'menu-2')).toBeDefined()
})
})
expect(result.current.menus).toHaveLength(2);
expect(result.current.menus.find((m) => m.id === 'menu-1')).toBeDefined();
expect(result.current.menus.find((m) => m.id === 'menu-2')).toBeDefined();
});
});

View File

@@ -28,15 +28,16 @@ export interface MantineBetterMenuInstanceItem extends Partial<MenuItemProps> {
| ReactNode;
}
export interface MenuStoreProps {
export interface MantineBetterMenuStoreProps {
menus?: Array<MantineBetterMenuInstance>;
providerID?: string;
width?: number;
}
export type MenuStoreState = MenuStoreProps & MenuStoreStateOnly;
export type MantineBetterMenuStoreState = MantineBetterMenuStoreProps &
MantineBetterMenuStoreStateOnly;
export interface MenuStoreStateOnly {
export interface MantineBetterMenuStoreStateOnly {
hide: (id: string) => void;
menus: Array<MantineBetterMenuInstance>;
setInstanceState: <K extends keyof MantineBetterMenuInstance>(
@@ -44,12 +45,15 @@ export interface MenuStoreStateOnly {
key: K,
value: MantineBetterMenuInstance[K]
) => void;
setState: <K extends keyof MenuStoreState>(key: K, value: Partial<MenuStoreState[K]>) => void;
setState: <K extends keyof MantineBetterMenuStoreState>(
key: K,
value: Partial<MantineBetterMenuStoreState[K]>
) => void;
show: (id: string, options?: Partial<MantineBetterMenuInstance>) => void;
}
const { Provider: MantineBetterMenusStoreProvider, useStore: useMantineBetterMenus } =
createSyncStore<MenuStoreState, MenuStoreProps>(
createSyncStore<MantineBetterMenuStoreState, MantineBetterMenuStoreProps>(
(set, get) => ({
hide: (id: string) => {
const s = get();
@@ -59,7 +63,7 @@ const { Provider: MantineBetterMenusStoreProvider, useStore: useMantineBetterMen
setInstanceState: (id, key, value) => {
//@ts-expect-error Type instantiation is excessively deep and possibly infinite.
set(
produce((state: MenuStoreState) => {
produce((state: MantineBetterMenuStoreState) => {
const idx = state?.menus?.findIndex((m: MantineBetterMenuInstance) => m.id === id);
if (idx >= 0) {
state.menus[idx][key] = value;
@@ -89,7 +93,7 @@ const { Provider: MantineBetterMenusStoreProvider, useStore: useMantineBetterMen
s.setState('menus', [...s.menus, menu as MantineBetterMenuInstance]);
} else {
set(
produce((state: MenuStoreState) => {
produce((state: MantineBetterMenuStoreState) => {
if (!state.menus) {
state.menus = [];
}

View File

@@ -3,5 +3,5 @@ export { useMantineBetterMenus } from './Store';
export type {
MantineBetterMenuInstance,
MantineBetterMenuInstanceItem,
MenuStoreState
MantineBetterMenuStoreState
} from './Store';