Some checks failed
Deploy to Test Environment / deploy-to-test (push) Failing after 2m15s
393 lines
12 KiB
TypeScript
393 lines
12 KiB
TypeScript
// src/features/store/StoreCard.test.tsx
|
|
import React from 'react';
|
|
import { screen } from '@testing-library/react';
|
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
import { StoreCard } from './StoreCard';
|
|
import { renderWithProviders } from '../../tests/utils/renderWithProviders';
|
|
|
|
describe('StoreCard', () => {
|
|
const mockStoreWithLogo = {
|
|
store_id: 1,
|
|
name: 'Test Store',
|
|
logo_url: 'https://example.com/logo.png',
|
|
locations: [
|
|
{
|
|
address_line_1: '123 Main Street',
|
|
city: 'Toronto',
|
|
province_state: 'ON',
|
|
postal_code: 'M5V 1A1',
|
|
},
|
|
],
|
|
};
|
|
|
|
const mockStoreWithoutLogo = {
|
|
store_id: 2,
|
|
name: 'Another Store',
|
|
logo_url: null,
|
|
locations: [
|
|
{
|
|
address_line_1: '456 Oak Avenue',
|
|
city: 'Vancouver',
|
|
province_state: 'BC',
|
|
postal_code: 'V6B 2M9',
|
|
},
|
|
],
|
|
};
|
|
|
|
const mockStoreWithMultipleLocations = {
|
|
store_id: 3,
|
|
name: 'Multi Location Store',
|
|
logo_url: 'https://example.com/multi-logo.png',
|
|
locations: [
|
|
{
|
|
address_line_1: '100 First Street',
|
|
city: 'Montreal',
|
|
province_state: 'QC',
|
|
postal_code: 'H2X 1Y6',
|
|
},
|
|
{
|
|
address_line_1: '200 Second Street',
|
|
city: 'Montreal',
|
|
province_state: 'QC',
|
|
postal_code: 'H3A 2T1',
|
|
},
|
|
{
|
|
address_line_1: '300 Third Street',
|
|
city: 'Montreal',
|
|
province_state: 'QC',
|
|
postal_code: 'H4B 3C2',
|
|
},
|
|
],
|
|
};
|
|
|
|
const mockStoreNoLocations = {
|
|
store_id: 4,
|
|
name: 'No Location Store',
|
|
logo_url: 'https://example.com/no-loc-logo.png',
|
|
locations: [],
|
|
};
|
|
|
|
const mockStoreUndefinedLocations = {
|
|
store_id: 5,
|
|
name: 'Undefined Locations Store',
|
|
logo_url: null,
|
|
};
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe('store name rendering', () => {
|
|
it('should render the store name', () => {
|
|
renderWithProviders(<StoreCard store={mockStoreWithLogo} />);
|
|
|
|
expect(screen.getByText('Test Store')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should render store name with truncation class', () => {
|
|
renderWithProviders(<StoreCard store={mockStoreWithLogo} />);
|
|
|
|
const heading = screen.getByRole('heading', { level: 3 });
|
|
expect(heading).toHaveClass('truncate');
|
|
});
|
|
});
|
|
|
|
describe('logo rendering', () => {
|
|
it('should render logo image when logo_url is provided', () => {
|
|
renderWithProviders(<StoreCard store={mockStoreWithLogo} />);
|
|
|
|
const logo = screen.getByAltText('Test Store logo');
|
|
expect(logo).toBeInTheDocument();
|
|
expect(logo).toHaveAttribute('src', 'https://example.com/logo.png');
|
|
});
|
|
|
|
it('should render initials fallback when logo_url is null', () => {
|
|
renderWithProviders(<StoreCard store={mockStoreWithoutLogo} />);
|
|
|
|
expect(screen.getByText('AN')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should render initials fallback when logo_url is undefined', () => {
|
|
const storeWithUndefinedLogo = {
|
|
store_id: 10,
|
|
name: 'Test Name',
|
|
logo_url: undefined,
|
|
};
|
|
|
|
renderWithProviders(<StoreCard store={storeWithUndefinedLogo} />);
|
|
|
|
expect(screen.getByText('TE')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should convert initials to uppercase', () => {
|
|
const storeWithLowercase = {
|
|
store_id: 11,
|
|
name: 'lowercase store',
|
|
logo_url: null,
|
|
};
|
|
|
|
renderWithProviders(<StoreCard store={storeWithLowercase} />);
|
|
|
|
expect(screen.getByText('LO')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should handle single character store name', () => {
|
|
const singleCharStore = {
|
|
store_id: 12,
|
|
name: 'X',
|
|
logo_url: null,
|
|
};
|
|
|
|
renderWithProviders(<StoreCard store={singleCharStore} />);
|
|
|
|
// Both the store name and initials will be 'X'
|
|
// Check that there are exactly 2 elements with 'X'
|
|
const elements = screen.getAllByText('X');
|
|
expect(elements).toHaveLength(2);
|
|
});
|
|
|
|
it('should handle empty string store name', () => {
|
|
const emptyNameStore = {
|
|
store_id: 13,
|
|
name: '',
|
|
logo_url: null,
|
|
};
|
|
|
|
// This will render empty string for initials
|
|
const { container } = renderWithProviders(<StoreCard store={emptyNameStore} />);
|
|
|
|
// The fallback div should still render
|
|
const fallbackDiv = container.querySelector('.h-12.w-12.flex');
|
|
expect(fallbackDiv).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('location display', () => {
|
|
it('should not show location when showLocations is false (default)', () => {
|
|
renderWithProviders(<StoreCard store={mockStoreWithLogo} />);
|
|
|
|
expect(screen.queryByText('123 Main Street')).not.toBeInTheDocument();
|
|
expect(screen.queryByText(/Toronto/)).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('should show primary location when showLocations is true', () => {
|
|
renderWithProviders(<StoreCard store={mockStoreWithLogo} showLocations={true} />);
|
|
|
|
expect(screen.getByText('123 Main Street')).toBeInTheDocument();
|
|
expect(screen.getByText('Toronto, ON M5V 1A1')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should show "No location data" when showLocations is true but no locations exist', () => {
|
|
renderWithProviders(<StoreCard store={mockStoreNoLocations} showLocations={true} />);
|
|
|
|
expect(screen.getByText('No location data')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should show "No location data" when locations is undefined', () => {
|
|
renderWithProviders(
|
|
<StoreCard store={mockStoreUndefinedLocations as any} showLocations={true} />,
|
|
);
|
|
|
|
expect(screen.getByText('No location data')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should not show "No location data" message when showLocations is false', () => {
|
|
renderWithProviders(<StoreCard store={mockStoreNoLocations} showLocations={false} />);
|
|
|
|
expect(screen.queryByText('No location data')).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('multiple locations', () => {
|
|
it('should show additional locations count for 2 locations', () => {
|
|
const storeWith2Locations = {
|
|
...mockStoreWithLogo,
|
|
locations: [
|
|
mockStoreWithMultipleLocations.locations[0],
|
|
mockStoreWithMultipleLocations.locations[1],
|
|
],
|
|
};
|
|
|
|
renderWithProviders(<StoreCard store={storeWith2Locations} showLocations={true} />);
|
|
|
|
expect(screen.getByText('+ 1 more location')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should show additional locations count for 3+ locations', () => {
|
|
renderWithProviders(
|
|
<StoreCard store={mockStoreWithMultipleLocations} showLocations={true} />,
|
|
);
|
|
|
|
expect(screen.getByText('+ 2 more locations')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should show primary location from multiple locations', () => {
|
|
renderWithProviders(
|
|
<StoreCard store={mockStoreWithMultipleLocations} showLocations={true} />,
|
|
);
|
|
|
|
// Should show first location
|
|
expect(screen.getByText('100 First Street')).toBeInTheDocument();
|
|
expect(screen.getByText('Montreal, QC H2X 1Y6')).toBeInTheDocument();
|
|
|
|
// Should NOT show secondary locations directly
|
|
expect(screen.queryByText('200 Second Street')).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('should not show additional locations count for single location', () => {
|
|
renderWithProviders(<StoreCard store={mockStoreWithLogo} showLocations={true} />);
|
|
|
|
expect(screen.queryByText(/more location/)).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('accessibility', () => {
|
|
it('should have proper alt text for logo', () => {
|
|
renderWithProviders(<StoreCard store={mockStoreWithLogo} />);
|
|
|
|
const logo = screen.getByAltText('Test Store logo');
|
|
expect(logo).toBeInTheDocument();
|
|
});
|
|
|
|
it('should use heading level 3 for store name', () => {
|
|
renderWithProviders(<StoreCard store={mockStoreWithLogo} />);
|
|
|
|
const heading = screen.getByRole('heading', { level: 3 });
|
|
expect(heading).toHaveTextContent('Test Store');
|
|
});
|
|
});
|
|
|
|
describe('styling', () => {
|
|
it('should apply flex layout to container', () => {
|
|
const { container } = renderWithProviders(<StoreCard store={mockStoreWithLogo} />);
|
|
|
|
const mainDiv = container.firstChild;
|
|
expect(mainDiv).toHaveClass('flex', 'items-start', 'space-x-3');
|
|
});
|
|
|
|
it('should apply proper styling to logo image', () => {
|
|
renderWithProviders(<StoreCard store={mockStoreWithLogo} />);
|
|
|
|
const logo = screen.getByAltText('Test Store logo');
|
|
expect(logo).toHaveClass(
|
|
'h-12',
|
|
'w-12',
|
|
'object-contain',
|
|
'rounded-md',
|
|
'bg-gray-100',
|
|
'dark:bg-gray-700',
|
|
'p-1',
|
|
'flex-shrink-0',
|
|
);
|
|
});
|
|
|
|
it('should apply proper styling to initials fallback', () => {
|
|
const { container } = renderWithProviders(<StoreCard store={mockStoreWithoutLogo} />);
|
|
|
|
const initialsDiv = container.querySelector('.h-12.w-12.flex.items-center.justify-center');
|
|
expect(initialsDiv).toHaveClass(
|
|
'h-12',
|
|
'w-12',
|
|
'flex',
|
|
'items-center',
|
|
'justify-center',
|
|
'bg-gray-200',
|
|
'dark:bg-gray-700',
|
|
'rounded-md',
|
|
'text-gray-400',
|
|
'text-xs',
|
|
'flex-shrink-0',
|
|
);
|
|
});
|
|
|
|
it('should apply italic style to "No location data" text', () => {
|
|
renderWithProviders(<StoreCard store={mockStoreNoLocations} showLocations={true} />);
|
|
|
|
const noLocationText = screen.getByText('No location data');
|
|
expect(noLocationText).toHaveClass('italic');
|
|
});
|
|
});
|
|
|
|
describe('edge cases', () => {
|
|
it('should handle store with special characters in name', () => {
|
|
const specialCharStore = {
|
|
store_id: 20,
|
|
name: "Store & Co's <Best>",
|
|
logo_url: null,
|
|
};
|
|
|
|
renderWithProviders(<StoreCard store={specialCharStore} />);
|
|
|
|
expect(screen.getByText("Store & Co's <Best>")).toBeInTheDocument();
|
|
expect(screen.getByText('ST')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should handle store with unicode characters', () => {
|
|
const unicodeStore = {
|
|
store_id: 21,
|
|
name: 'Cafe Le Cafe',
|
|
logo_url: null,
|
|
};
|
|
|
|
renderWithProviders(<StoreCard store={unicodeStore} />);
|
|
|
|
expect(screen.getByText('Cafe Le Cafe')).toBeInTheDocument();
|
|
expect(screen.getByText('CA')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should handle location with long address', () => {
|
|
const longAddressStore = {
|
|
store_id: 22,
|
|
name: 'Long Address Store',
|
|
logo_url: 'https://example.com/logo.png',
|
|
locations: [
|
|
{
|
|
address_line_1: '1234567890 Very Long Street Name That Exceeds Normal Length',
|
|
city: 'Vancouver',
|
|
province_state: 'BC',
|
|
postal_code: 'V6B 2M9',
|
|
},
|
|
],
|
|
};
|
|
|
|
renderWithProviders(<StoreCard store={longAddressStore} showLocations={true} />);
|
|
|
|
const addressElement = screen.getByText(
|
|
'1234567890 Very Long Street Name That Exceeds Normal Length',
|
|
);
|
|
expect(addressElement).toHaveClass('truncate');
|
|
});
|
|
});
|
|
|
|
describe('data types', () => {
|
|
it('should accept store_id as number', () => {
|
|
const store = {
|
|
store_id: 12345,
|
|
name: 'Numeric ID Store',
|
|
logo_url: null,
|
|
};
|
|
|
|
// This should compile and render without errors
|
|
renderWithProviders(<StoreCard store={store} />);
|
|
|
|
expect(screen.getByText('Numeric ID Store')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should handle empty logo_url string', () => {
|
|
const storeWithEmptyLogo = {
|
|
store_id: 30,
|
|
name: 'Empty Logo Store',
|
|
logo_url: '',
|
|
};
|
|
|
|
// Empty string is truthy check, but might cause issues with img src
|
|
// The component checks for truthy logo_url, so empty string will render initials
|
|
// Actually, empty string '' is falsy in JavaScript, so this would show initials
|
|
renderWithProviders(<StoreCard store={storeWithEmptyLogo} />);
|
|
|
|
// Empty string is falsy, so initials should show
|
|
expect(screen.getByText('EM')).toBeInTheDocument();
|
|
});
|
|
});
|
|
});
|