import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { EntityCard } from '../desktop/src/renderer/components/entity/entity-card';
/**
* Test entity data
*/
const mockEntity = {
id: 1,
entityId: '0.0.12345',
entityName: 'Test Topic',
entityType: 'topic',
transactionId: '0.0.12345-123456789-000000000',
sessionId: 'session-1',
createdAt: new Date('2023-01-01T10:00:00Z'),
updatedAt: new Date('2023-01-01T10:00:00Z'),
isActive: true,
metadata: null,
};
const mockTokenEntity = {
id: 2,
entityId: '0.0.67890',
entityName: 'Test Token',
entityType: 'token',
transactionId: '0.0.67890-123456789-000000001',
sessionId: 'session-1',
createdAt: new Date('2023-01-02T10:00:00Z'),
updatedAt: new Date('2023-01-02T10:00:00Z'),
isActive: true,
metadata: '{"symbol":"TTK","decimals":2,"totalSupply":"1000000"}',
};
const defaultProps = {
entity: mockEntity,
isSelected: false,
onSelect: jest.fn(),
onDelete: jest.fn(),
onRename: jest.fn(),
onCopyToClipboard: jest.fn(),
};
describe('EntityCard', () => {
beforeEach(() => {
jest.clearAllMocks();
Object.assign(navigator, {
clipboard: {
writeText: jest.fn().mockImplementation(() => Promise.resolve()),
},
});
});
it('should render entity information correctly', () => {
render();
expect(screen.getByText('Test Topic')).toBeInTheDocument();
expect(screen.getByText('0.0.12345')).toBeInTheDocument();
expect(screen.getByText('topic')).toBeInTheDocument();
expect(screen.getByText('Jan 1, 2023')).toBeInTheDocument();
});
it('should display transaction ID when available', () => {
render();
expect(screen.getByText('0.0.12345-123456789-000000000')).toBeInTheDocument();
});
it('should handle entity selection', async () => {
const user = userEvent.setup();
const onSelect = jest.fn();
render();
const checkbox = screen.getByRole('checkbox');
await user.click(checkbox);
expect(onSelect).toHaveBeenCalledWith(true);
});
it('should show selected state correctly', () => {
render();
const checkbox = screen.getByRole('checkbox');
expect(checkbox).toBeChecked();
});
it('should handle entity deletion', async () => {
const user = userEvent.setup();
const onDelete = jest.fn();
render();
const deleteButton = screen.getByLabelText('Delete entity');
await user.click(deleteButton);
const confirmButton = screen.getByText('Delete');
await user.click(confirmButton);
expect(onDelete).toHaveBeenCalled();
});
it('should handle entity renaming', async () => {
const user = userEvent.setup();
const onRename = jest.fn();
render();
const renameButton = screen.getByLabelText('Rename entity');
await user.click(renameButton);
const nameInput = screen.getByDisplayValue('Test Topic');
await user.clear(nameInput);
await user.type(nameInput, 'Renamed Topic');
const saveButton = screen.getByLabelText('Save name');
await user.click(saveButton);
expect(onRename).toHaveBeenCalledWith('Renamed Topic');
});
it('should handle copy to clipboard', async () => {
const user = userEvent.setup();
const onCopyToClipboard = jest.fn();
render();
const copyButton = screen.getByLabelText('Copy entity ID');
await user.click(copyButton);
expect(navigator.clipboard.writeText).toHaveBeenCalledWith('0.0.12345');
expect(onCopyToClipboard).toHaveBeenCalled();
});
it('should display appropriate icon for entity type', () => {
render();
expect(screen.getByLabelText('Topic icon')).toBeInTheDocument();
});
it('should display token icon for token entities', () => {
render();
expect(screen.getByLabelText('Token icon')).toBeInTheDocument();
});
it('should parse and display metadata for tokens', () => {
render();
expect(screen.getByText('TTK')).toBeInTheDocument();
expect(screen.getByText('2 decimals')).toBeInTheDocument();
expect(screen.getByText('1,000,000 total supply')).toBeInTheDocument();
});
it('should handle malformed metadata gracefully', () => {
const entityWithBadMetadata = {
...mockTokenEntity,
metadata: 'invalid-json',
};
render();
expect(screen.getByText('Test Token')).toBeInTheDocument();
expect(screen.queryByText('TTK')).not.toBeInTheDocument();
});
it('should display HashScan link', () => {
render();
const hashScanLink = screen.getByLabelText('View on HashScan');
expect(hashScanLink).toHaveAttribute('href', 'https://hashscan.io/testnet/topic/0.0.12345');
expect(hashScanLink).toHaveAttribute('target', '_blank');
});
it('should display correct HashScan URL for different entity types', () => {
render();
const hashScanLink = screen.getByLabelText('View on HashScan');
expect(hashScanLink).toHaveAttribute('href', 'https://hashscan.io/testnet/token/0.0.67890');
});
it('should show loading state during operations', async () => {
const user = userEvent.setup();
const onDelete = jest.fn().mockImplementation(() => new Promise(resolve => setTimeout(resolve, 100)));
render();
const deleteButton = screen.getByLabelText('Delete entity');
await user.click(deleteButton);
const confirmButton = screen.getByText('Delete');
await user.click(confirmButton);
expect(screen.getByLabelText('Loading')).toBeInTheDocument();
await waitFor(() => {
expect(screen.queryByLabelText('Loading')).not.toBeInTheDocument();
});
});
it('should cancel rename operation', async () => {
const user = userEvent.setup();
const onRename = jest.fn();
render();
const renameButton = screen.getByLabelText('Rename entity');
await user.click(renameButton);
const nameInput = screen.getByDisplayValue('Test Topic');
await user.clear(nameInput);
await user.type(nameInput, 'Renamed Topic');
const cancelButton = screen.getByLabelText('Cancel rename');
await user.click(cancelButton);
expect(onRename).not.toHaveBeenCalled();
expect(screen.getByText('Test Topic')).toBeInTheDocument();
});
it('should validate entity name during rename', async () => {
const user = userEvent.setup();
const onRename = jest.fn();
render();
const renameButton = screen.getByLabelText('Rename entity');
await user.click(renameButton);
const nameInput = screen.getByDisplayValue('Test Topic');
await user.clear(nameInput);
const saveButton = screen.getByLabelText('Save name');
await user.click(saveButton);
expect(screen.getByText('Name cannot be empty')).toBeInTheDocument();
expect(onRename).not.toHaveBeenCalled();
});
it('should display custom style when provided', () => {
const customStyle = { backgroundColor: 'red' };
const { container } = render();
const cardElement = container.firstChild as HTMLElement;
expect(cardElement).toHaveStyle('background-color: red');
});
it('should support keyboard navigation', async () => {
const user = userEvent.setup();
const onSelect = jest.fn();
render();
const checkbox = screen.getByRole('checkbox');
checkbox.focus();
await user.keyboard('{Space}');
expect(onSelect).toHaveBeenCalledWith(true);
});
it('should handle keyboard shortcuts for actions', async () => {
const user = userEvent.setup();
const onDelete = jest.fn();
const onRename = jest.fn();
render();
const cardElement = screen.getByRole('article');
cardElement.focus();
await user.keyboard('{Delete}');
expect(screen.getByText('Are you sure you want to delete this entity?')).toBeInTheDocument();
const confirmButton = screen.getByText('Delete');
await user.click(confirmButton);
expect(onDelete).toHaveBeenCalled();
});
it('should display session information when available', () => {
render();
expect(screen.getByText('session-1')).toBeInTheDocument();
});
it('should handle entities without session ID', () => {
const entityWithoutSession = {
...mockEntity,
sessionId: null,
};
render();
expect(screen.getByText('Test Topic')).toBeInTheDocument();
expect(screen.getByText('No session')).toBeInTheDocument();
});
it('should display creation time with relative formatting', () => {
const recentEntity = {
...mockEntity,
createdAt: new Date(Date.now() - 60000), // 1 minute ago
};
render();
expect(screen.getByText('1 minute ago')).toBeInTheDocument();
});
it('should handle entity card hover effects', async () => {
const user = userEvent.setup();
render();
const cardElement = screen.getByRole('article');
await user.hover(cardElement);
expect(cardElement).toHaveClass('hover:shadow-lg');
});
it('should display appropriate badges for different entity states', () => {
const inactiveEntity = {
...mockEntity,
isActive: false,
};
render();
expect(screen.getByText('Inactive')).toBeInTheDocument();
});
it('should truncate long entity names appropriately', () => {
const longNameEntity = {
...mockEntity,
entityName: 'This is a very long entity name that should be truncated for display purposes',
};
render();
const nameElement = screen.getByText('This is a very long entity name that should be truncated for display purposes');
expect(nameElement).toHaveClass('truncate');
});
});