Skip to content

Commit b2cd963

Browse files
authored
fixed unresponsiveness upon closing Developer logs (#249)
* fixed unresponsiveness upon closing Developer logs * test: remove `onOpenChange` prop from `DevLogs` component renders in tests. * refactor: Update DevLogs tests by removing Dialog mocks, enhancing Select mocks, and adjusting snapshots for DevLogs as a content component. * fixed movile UI for the Developer logs
1 parent 6818fcd commit b2cd963

5 files changed

Lines changed: 543 additions & 469 deletions

File tree

Lines changed: 86 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,4 @@
11
import React, { useState, useEffect } from 'react';
2-
import {
3-
Dialog,
4-
DialogContent,
5-
DialogDescription,
6-
DialogHeader,
7-
DialogTitle,
8-
} from '../../ui/dialog';
92
import { Button } from '../../ui/button';
103
import {
114
Select,
@@ -28,10 +21,9 @@ interface LogEntry {
2821

2922
interface DevLogsProps {
3023
isOpen: boolean;
31-
onOpenChange: (open: boolean) => void;
3224
}
3325

34-
export const DevLogs: React.FC<DevLogsProps> = ({ isOpen, onOpenChange }) => {
26+
export const DevLogs: React.FC<DevLogsProps> = ({ isOpen }) => {
3527
const [logs, setLogs] = useState<LogEntry[]>([]);
3628
const [filteredLogs, setFilteredLogs] = useState<LogEntry[]>([]);
3729
const [selectedLevel, setSelectedLevel] = useState<string>('all');
@@ -116,111 +108,98 @@ export const DevLogs: React.FC<DevLogsProps> = ({ isOpen, onOpenChange }) => {
116108
};
117109

118110
return (
119-
<Dialog open={isOpen} onOpenChange={onOpenChange}>
120-
<DialogContent className="max-w-4xl max-h-[80vh] overflow-hidden flex flex-col">
121-
<DialogHeader>
122-
<DialogTitle>Developer Logs</DialogTitle>
123-
<DialogDescription>
124-
View sync operation logs with timestamps and status information.
125-
</DialogDescription>
126-
</DialogHeader>
127-
128-
<div className="flex justify-between items-center mb-4 gap-4">
129-
<Select value={selectedLevel} onValueChange={setSelectedLevel}>
130-
<SelectTrigger className="w-[180px]">
131-
<SelectValue placeholder="Filter by level" />
132-
</SelectTrigger>
133-
<SelectContent>
134-
<SelectItem value="all">All Levels</SelectItem>
135-
<SelectItem value="INFO">INFO</SelectItem>
136-
<SelectItem value="WARN">WARN</SelectItem>
137-
<SelectItem value="ERROR">ERROR</SelectItem>
138-
</SelectContent>
139-
</Select>
111+
<>
112+
<div className="flex justify-between items-center mb-4 gap-4">
113+
<Select value={selectedLevel} onValueChange={setSelectedLevel}>
114+
<SelectTrigger className="w-[180px]">
115+
<SelectValue placeholder="Filter by level" />
116+
</SelectTrigger>
117+
<SelectContent>
118+
<SelectItem value="all">All Levels</SelectItem>
119+
<SelectItem value="INFO">INFO</SelectItem>
120+
<SelectItem value="WARN">WARN</SelectItem>
121+
<SelectItem value="ERROR">ERROR</SelectItem>
122+
</SelectContent>
123+
</Select>
140124

141-
<div className="flex gap-2">
142-
<Button
143-
variant="outline"
144-
size="sm"
145-
onClick={fetchLogs}
146-
disabled={isLoading}
147-
>
148-
{isLoading ? 'Refreshing...' : 'Refresh'}
149-
</Button>
150-
<Button
151-
variant="outline"
152-
size="sm"
153-
onClick={copyAllLogs}
154-
disabled={filteredLogs.length === 0}
155-
>
156-
Copy All
157-
</Button>
158-
</div>
125+
<div className="flex gap-2">
126+
<Button
127+
variant="outline"
128+
size="sm"
129+
onClick={fetchLogs}
130+
disabled={isLoading}
131+
>
132+
{isLoading ? 'Refreshing...' : 'Refresh'}
133+
</Button>
134+
<Button
135+
variant="outline"
136+
size="sm"
137+
onClick={copyAllLogs}
138+
disabled={filteredLogs.length === 0}
139+
>
140+
Copy All
141+
</Button>
159142
</div>
143+
</div>
160144

161-
<div className="flex-1 overflow-y-auto border rounded-md p-4 bg-gray-50 dark:bg-gray-900">
162-
{isLoading ? (
163-
<div className="text-center py-8 text-gray-500">
164-
Loading logs...
165-
</div>
166-
) : filteredLogs.length === 0 ? (
167-
<div className="text-center py-8 text-gray-500">
168-
No logs available
169-
</div>
170-
) : (
171-
<div className="space-y-2">
172-
{filteredLogs.map((log, index) => (
173-
<div
174-
key={index}
175-
className="p-3 bg-white dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700 hover:shadow-sm transition-shadow group"
176-
>
177-
<div className="flex justify-between items-start gap-2">
178-
<div className="flex-1 font-mono text-sm">
179-
<div className="flex items-center gap-2 mb-1">
180-
<span className="text-gray-500 dark:text-gray-400">
181-
{formatTimestamp(log.timestamp)}
182-
</span>
183-
<span
184-
className={`font-semibold ${getLevelColor(
185-
log.level
186-
)}`}
187-
>
188-
[{log.level}]
145+
<div className="flex-1 overflow-y-auto border rounded-md p-4 bg-gray-50 dark:bg-gray-900">
146+
{isLoading ? (
147+
<div className="text-center py-8 text-gray-500">Loading logs...</div>
148+
) : filteredLogs.length === 0 ? (
149+
<div className="text-center py-8 text-gray-500">
150+
No logs available
151+
</div>
152+
) : (
153+
<div className="space-y-2">
154+
{filteredLogs.map((log, index) => (
155+
<div
156+
key={index}
157+
className="p-3 bg-white dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700 hover:shadow-sm transition-shadow group"
158+
>
159+
<div className="flex justify-between items-start gap-2">
160+
<div className="flex-1 font-mono text-sm">
161+
<div className="flex items-center gap-2 mb-1">
162+
<span className="text-gray-500 dark:text-gray-400">
163+
{formatTimestamp(log.timestamp)}
164+
</span>
165+
<span
166+
className={`font-semibold ${getLevelColor(log.level)}`}
167+
>
168+
[{log.level}]
169+
</span>
170+
{log.operation && (
171+
<span className="text-purple-600 dark:text-purple-400 text-xs">
172+
{log.operation}
189173
</span>
190-
{log.operation && (
191-
<span className="text-purple-600 dark:text-purple-400 text-xs">
192-
{log.operation}
193-
</span>
194-
)}
195-
</div>
196-
<div className="text-gray-800 dark:text-gray-200 break-words">
197-
{log.message}
198-
</div>
199-
{log.syncId && (
200-
<div className="text-xs text-gray-500 dark:text-gray-400 mt-1">
201-
Sync ID: {log.syncId}
202-
</div>
203174
)}
204175
</div>
205-
<Button
206-
variant="ghost"
207-
size="sm"
208-
onClick={() => copyLog(log, index)}
209-
className="opacity-0 group-hover:opacity-100 transition-opacity"
210-
>
211-
{copiedIndex === index ? (
212-
<CheckIcon className="h-4 w-4 text-green-600" />
213-
) : (
214-
<CopyIcon className="h-4 w-4" />
215-
)}
216-
</Button>
176+
<div className="text-gray-800 dark:text-gray-200 break-words">
177+
{log.message}
178+
</div>
179+
{log.syncId && (
180+
<div className="text-xs text-gray-500 dark:text-gray-400 mt-1">
181+
Sync ID: {log.syncId}
182+
</div>
183+
)}
217184
</div>
185+
<Button
186+
variant="ghost"
187+
size="sm"
188+
onClick={() => copyLog(log, index)}
189+
className="opacity-0 group-hover:opacity-100 transition-opacity"
190+
>
191+
{copiedIndex === index ? (
192+
<CheckIcon className="h-4 w-4 text-green-600" />
193+
) : (
194+
<CopyIcon className="h-4 w-4" />
195+
)}
196+
</Button>
218197
</div>
219-
))}
220-
</div>
221-
)}
222-
</div>
223-
</DialogContent>
224-
</Dialog>
198+
</div>
199+
))}
200+
</div>
201+
)}
202+
</div>
203+
</>
225204
);
226205
};

frontend/src/components/HomeComponents/DevLogs/__tests__/DevLogs.test.tsx

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,23 @@
11
import { render, waitFor, screen } from '@testing-library/react';
22
import { DevLogs } from '../DevLogs';
33

4-
// Mock UI components
5-
jest.mock('../../../ui/dialog', () => ({
6-
Dialog: ({ children, open }: any) => (open ? <div>{children}</div> : null),
7-
DialogContent: ({ children }: any) => <div>{children}</div>,
8-
DialogDescription: ({ children }: any) => <div>{children}</div>,
9-
DialogHeader: ({ children }: any) => <div>{children}</div>,
10-
DialogTitle: ({ children }: any) => <div>{children}</div>,
11-
}));
12-
4+
// Mock UI components - DevLogs uses Button and Select components
135
jest.mock('../../../ui/button', () => ({
146
Button: ({ children, ...props }: any) => (
157
<button {...props}>{children}</button>
168
),
179
}));
1810

1911
jest.mock('../../../ui/select', () => ({
20-
Select: ({ children }: any) => <div>{children}</div>,
12+
Select: ({ children, value }: any) => (
13+
<div data-testid="select" data-value={value}>
14+
{children}
15+
</div>
16+
),
2117
SelectContent: ({ children }: any) => <div>{children}</div>,
22-
SelectItem: ({ children }: any) => <div>{children}</div>,
18+
SelectItem: ({ children, value }: any) => (
19+
<div data-value={value}>{children}</div>
20+
),
2321
SelectTrigger: ({ children }: any) => <div>{children}</div>,
2422
SelectValue: ({ placeholder }: any) => <div>{placeholder}</div>,
2523
}));
@@ -82,41 +80,43 @@ global.fetch = jest.fn(() =>
8280
})
8381
) as jest.Mock;
8482

85-
describe('DevLogs Component using Snapshot', () => {
86-
const mockOnOpenChange = jest.fn();
87-
83+
describe('DevLogs Content Component', () => {
8884
beforeEach(() => {
8985
jest.clearAllMocks();
9086
});
9187

92-
it('renders closed dialog correctly', () => {
93-
const { asFragment } = render(
94-
<DevLogs isOpen={false} onOpenChange={mockOnOpenChange} />
95-
);
96-
expect(asFragment()).toMatchSnapshot('devlogs-closed');
88+
it('renders initial state without fetching logs when isOpen is false', () => {
89+
const { asFragment } = render(<DevLogs isOpen={false} />);
90+
91+
// Should render the UI but not fetch logs
92+
expect(screen.getByText('No logs available')).toBeInTheDocument();
93+
expect(fetch).not.toHaveBeenCalled();
94+
expect(asFragment()).toMatchSnapshot('devlogs-initial-state');
9795
});
9896

99-
it('renders open dialog with logs correctly', async () => {
100-
const { asFragment } = render(
101-
<DevLogs isOpen={true} onOpenChange={mockOnOpenChange} />
102-
);
97+
it('renders with logs when isOpen is true', async () => {
98+
const { asFragment } = render(<DevLogs isOpen={true} />);
10399

104100
await waitFor(() => {
105101
expect(screen.queryByText('Loading logs...')).not.toBeInTheDocument();
106102
});
107103

104+
// Verify logs are displayed
105+
expect(screen.getByText('Sync operation started')).toBeInTheDocument();
106+
expect(screen.getByText('Warning message')).toBeInTheDocument();
107+
expect(screen.getByText('Error occurred')).toBeInTheDocument();
108+
108109
expect(asFragment()).toMatchSnapshot('devlogs-with-logs');
109110
});
110111

111-
it('renders loading state correctly', () => {
112+
it('renders loading state when fetching logs', () => {
112113
(fetch as jest.Mock).mockImplementationOnce(
113114
() => new Promise(() => {}) // Never resolves to keep loading state
114115
);
115116

116-
const { asFragment } = render(
117-
<DevLogs isOpen={true} onOpenChange={mockOnOpenChange} />
118-
);
117+
const { asFragment } = render(<DevLogs isOpen={true} />);
119118

119+
expect(screen.getByText('Loading logs...')).toBeInTheDocument();
120120
expect(asFragment()).toMatchSnapshot('devlogs-loading');
121121
});
122122

@@ -128,9 +128,7 @@ describe('DevLogs Component using Snapshot', () => {
128128
})
129129
);
130130

131-
const { asFragment } = render(
132-
<DevLogs isOpen={true} onOpenChange={mockOnOpenChange} />
133-
);
131+
const { asFragment } = render(<DevLogs isOpen={true} />);
134132

135133
await waitFor(() => {
136134
expect(screen.getByText('No logs available')).toBeInTheDocument();

0 commit comments

Comments
 (0)