Skip to content
Merged
4 changes: 3 additions & 1 deletion .claude/skills/commit.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ When using Claude Code to commit changes:
## Examples

### Initial Commit

```bash
git add src/components/NewFeature.tsx
make commit
Expand All @@ -29,6 +30,7 @@ make commit
```

### Amend Commit

```bash
git commit --amend
# Edit the commit message to add:
Expand All @@ -42,4 +44,4 @@ git commit --amend
- Keep subject line under 50 characters
- Use body to explain "why" not just "what"
- Reference related issues if applicable
- Ensure commit passes linting and tests before pushing
- Ensure commit passes linting and tests before pushing
13 changes: 10 additions & 3 deletions jest.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
import '@localization/config';
// import './__mocks__/zustand';
if (typeof localStorage !== 'undefined') {
localStorage.clear();
}

jest.useFakeTimers();
jest.mock('zustand');

// Mocking Date
class MockDate extends Date {
constructor() {
super('2020-05-14T11:01:58.135'); // add whatever date you'll expect to get
const RealDate = Date;
class MockDate extends RealDate {
constructor(date) {
if (date) return new RealDate(date);
return new RealDate('2020-05-14T11:01:58.135Z');
}
}

// @ts-ignore
global.Date = MockDate;
Date.now = () => new RealDate('2020-05-14T11:01:58.135Z').getTime();

// Mocking Draft.js
jest.mock('draft-js', () => ({
Expand Down
247 changes: 236 additions & 11 deletions src/components/Window/__tests__/Windows.test.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,64 @@
import { fireEvent, render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import Window from '../Window';
import { ProgramType } from '@processStore';
import { ProgramType, processStore, WindowSize } from '@processStore';
import React from 'react';
import { act } from '@testing-library/react-hooks';

// Shared variable to capture props
let capturedDraggableProps: any;
let capturedResizableProps: any;

// Mock DraggableProvider and ResizableBox to trigger callbacks and capture props
jest.mock('@providers', () => ({
DraggableProvider: (props: any) => {
capturedDraggableProps = props;
return (
<div
data-testid="draggable-provider"
onClick={() => props.onPositionChange({ x: 100, y: 100 })}
>
{props.children}
</div>
);
},
}));

jest.mock('react-resizable', () => ({
ResizableBox: (props: any) => {
capturedResizableProps = props;
return (
<div
data-testid="resizable-box"
onClick={() =>
props.onResize &&
props.onResize({}, { size: { width: 800, height: 600 } })
}
>
{props.children}
</div>
);
},
}));

describe('Windows', () => {
const clickHandler = jest.fn();
beforeEach(() => {
clickHandler.mockReset();
jest.clearAllMocks();
capturedDraggableProps = null;
capturedResizableProps = null;
// Reset store before each test
act(() => {
processStore.setState((state: any) => {
state.ActiveApp.apps = {};
});
// Seed FINDER app to state so it can be updated
processStore.getState().ActiveApp.addApp(ProgramType.FINDER);
});
});

it('should render for default values', () => {
const { container } = render(
<Window app={ProgramType.FINDER}>
<div>Test</div>
</Window>,
);

expect(container).toMatchSnapshot();
});

it('should render with top bar component values', () => {
const { container } = render(
<Window app={ProgramType.FINDER} topBar={<div>Top bar</div>}>
<div>Test</div>
Expand Down Expand Up @@ -65,5 +105,190 @@ describe('Windows', () => {

expect(container).toMatchSnapshot();
expect(container.querySelector('.left-side')).toBeDefined();

// Verify it changed to MAX
const appState = processStore.getState().ActiveApp.apps[ProgramType.FINDER];
expect(appState?.size).toBe(WindowSize.MAX);
});

it('should toggle maximize on double click on title bar', () => {
const { container } = render(
<Window app={ProgramType.FINDER}>
<div>Test</div>
</Window>,
);

const titleBar = container.querySelector('.handle');
expect(titleBar).toBeTruthy();

if (titleBar) {
fireEvent.doubleClick(titleBar);
}

// Should now be maximized
expect(container).toMatchSnapshot();
const appState = processStore.getState().ActiveApp.apps[ProgramType.FINDER];
expect(appState?.size).toBe(WindowSize.MAX);
});

it('should handle resize', () => {
render(
<Window app={ProgramType.FINDER}>
<div>Test</div>
</Window>,
);

const resizable = screen.getByTestId('resizable-box');
fireEvent.click(resizable);
// onResize should have been called and updated componentDimension state
});

it('should handle position change', () => {
render(
<Window app={ProgramType.FINDER}>
<div>Test</div>
</Window>,
);

const draggable = screen.getByTestId('draggable-provider');
fireEvent.click(draggable);
// onPositionChange should have been called and triggered updatePosition
});

it('should render maximized window', () => {
// We need to mock processStore to return a maximized app
act(() => {
processStore.setState((state: any) => {
state.ActiveApp.apps[ProgramType.FINDER] = {
app: ProgramType.FINDER,
size: WindowSize.MAX,
position: { x: 0, y: 0 },
};
});
});

const { container } = render(
<Window app={ProgramType.FINDER}>
<div>Test</div>
</Window>,
);

expect(container).toMatchSnapshot();
});

it('should memoize correctly', () => {
const children = <div key="1">Test</div>;
const { rerender } = render(
<Window app={ProgramType.FINDER}>{children}</Window>,
);

// Re-rendering with same children should not cause re-render of memoized component
rerender(<Window app={ProgramType.FINDER}>{children}</Window>);

// Re-rendering with different children should cause re-render
rerender(
<Window app={ProgramType.FINDER}>
<div key="2">Different</div>
</Window>,
);
});

describe('Branch Coverage Edge Cases', () => {
it('should handle undefined currentApp (defaults to not maximized)', () => {
// Clear seeded app for this test
act(() => {
processStore.setState((state: any) => {
state.ActiveApp.apps = {};
});
});

render(
<Window app={ProgramType.FINDER}>
<div>Test Undefined</div>
</Window>,
);

expect(capturedDraggableProps.maximized).toBe(false);
expect(capturedDraggableProps.position).toEqual({ x: 0, y: 0 });
expect(capturedResizableProps.resizeHandles).toEqual(['se']);
});

it('should handle DEFAULT size app', () => {
act(() => {
processStore.setState((state: any) => {
state.ActiveApp.apps[ProgramType.FINDER] = {
app: ProgramType.FINDER,
size: WindowSize.DEFAULT,
position: { x: 50, y: 50 },
};
});
});

render(
<Window app={ProgramType.FINDER}>
<div>Test Default</div>
</Window>,
);

expect(capturedDraggableProps.maximized).toBe(false);
expect(capturedDraggableProps.position).toEqual({ x: 50, y: 50 });
expect(capturedResizableProps.resizeHandles).toEqual(['se']);
});

it('should handle MAX size app branches', () => {
act(() => {
processStore.setState((state: any) => {
state.ActiveApp.apps[ProgramType.FINDER] = {
app: ProgramType.FINDER,
size: WindowSize.MAX,
position: { x: 10, y: 10 },
};
});
});

render(
<Window app={ProgramType.FINDER}>
<div>Test Max</div>
</Window>,
);

expect(capturedDraggableProps.maximized).toBe(true);
expect(capturedResizableProps.resizeHandles).toEqual([]);
expect(capturedResizableProps.style.transition).toBe('all 0.2s');

// Test toggle back to DEFAULT
fireEvent.click(screen.getByLabelText('maximize'));
const appState =
processStore.getState().ActiveApp.apps[ProgramType.FINDER];
expect(appState?.size).toBe(WindowSize.DEFAULT);
});

it('should show/hide handle class based on maximized state', () => {
const { rerender } = render(
<Window app={ProgramType.FINDER}>
<div>Test Class</div>
</Window>,
);
const handleContainer =
screen.getByText('Test Class').parentElement?.previousSibling;
expect(handleContainer).toHaveClass('handle');

act(() => {
processStore.setState((state: any) => {
state.ActiveApp.apps[ProgramType.FINDER] = {
app: ProgramType.FINDER,
size: WindowSize.MAX,
position: { x: 0, y: 0 },
};
});
});

rerender(
<Window app={ProgramType.FINDER}>
<div>Test Class</div>
</Window>,
);
expect(handleContainer).not.toHaveClass('handle');
});
});
});
Loading