فهرست منبع

✅ test(SupplyChainDashboards): add unit tests for grain oil dashboard components

- 添加GrainOilDashboard页面测试,覆盖组件渲染、导航切换和地图点击功能
- 为KeyMetrics组件编写测试,验证默认指标、自定义指标和样式渲染
- 实现SupplyChainMap组件测试,包括点点击事件和自定义数据测试
- 创建SupplyChainModel组件测试,检查合作模式和流程渲染正确性
yourname 2 ماه پیش
والد
کامیت
af73a4519d

+ 121 - 0
tests/unit/client/home/pages/SupplyChainDashboards/GrainOilDashboard.test.tsx

@@ -0,0 +1,121 @@
+import React from 'react';
+import { render, screen, fireEvent } from '@testing-library/react';
+import { describe, it, expect, vi } from 'vitest';
+import GrainOilDashboard from '@/client/home/pages/SupplyChainDashboards/GrainOilDashboard';
+
+// Mock child components
+vi.mock('@/client/home/pages/SupplyChainDashboards/components/SupplyChainMap', () => ({
+  default: ({ onPointClick }: { onPointClick: (point: any) => void }) => (
+    <div data-testid="supply-chain-map">
+      <button onClick={() => onPointClick({ id: 'test-point', type: 'base', position: { x: 100, y: 100 } })}>
+        Test Point
+      </button>
+    </div>
+  )
+}));
+
+vi.mock('@/client/home/pages/SupplyChainDashboards/components/SupplyChainModel', () => ({
+  default: () => <div data-testid="supply-chain-model">Supply Chain Model</div>
+}));
+
+vi.mock('@/client/home/pages/SupplyChainDashboards/components/KeyMetrics', () => ({
+  default: () => <div data-testid="key-metrics">Key Metrics</div>
+}));
+
+describe('GrainOilDashboard', () => {
+  it('should render the dashboard with all components', () => {
+    render(<GrainOilDashboard />);
+
+    // Check if main container is rendered
+    const container = screen.getByRole('generic');
+    expect(container).toHaveClass('h-[1080px]');
+    expect(container).toHaveClass('w-[1920px]');
+
+    // Check if all child components are rendered
+    expect(screen.getByTestId('supply-chain-map')).toBeInTheDocument();
+    expect(screen.getByTestId('supply-chain-model')).toBeInTheDocument();
+    expect(screen.getByTestId('key-metrics')).toBeInTheDocument();
+  });
+
+  it('should render navigation with default active tab', () => {
+    render(<GrainOilDashboard />);
+
+    // Check if navigation is rendered
+    const navigation = screen.getByText('粮食');
+    expect(navigation).toBeInTheDocument();
+
+    // Check if grain tab is active by default
+    const grainText = screen.getByText('粮食').closest('div');
+    expect(grainText).toHaveClass('text-white');
+
+    // Check if oil tab is inactive
+    const oilText = screen.getByText('油脂').closest('div');
+    expect(oilText).toHaveClass('text-[rgba(255,255,255,0.5)]');
+  });
+
+  it('should handle navigation tab switching', () => {
+    render(<GrainOilDashboard />);
+
+    // Find and click the oil tab
+    const oilTab = screen.getByText('油脂').closest('div[class*="cursor-pointer"]');
+    expect(oilTab).toBeInTheDocument();
+
+    if (oilTab) {
+      fireEvent.click(oilTab);
+
+      // After clicking, the oil tab should become active
+      const oilText = screen.getByText('油脂').closest('div');
+      expect(oilText).toHaveClass('text-white');
+
+      // And grain tab should become inactive
+      const grainText = screen.getByText('粮食').closest('div');
+      expect(grainText).toHaveClass('text-[rgba(255,255,255,0.5)]');
+    }
+  });
+
+  it('should handle map point clicks', () => {
+    const consoleSpy = vi.spyOn(console, 'log');
+
+    render(<GrainOilDashboard />);
+
+    // Click on a test point in the map
+    const testPoint = screen.getByText('Test Point');
+    fireEvent.click(testPoint);
+
+    // Check if the click handler was called
+    expect(consoleSpy).toHaveBeenCalledWith('点击定位点:', {
+      id: 'test-point',
+      type: 'base',
+      position: { x: 100, y: 100 }
+    });
+
+    consoleSpy.mockRestore();
+  });
+
+  it('should render with correct background and styling', () => {
+    render(<GrainOilDashboard />);
+
+    const container = screen.getByRole('generic');
+
+    // Check background color
+    expect(container).toHaveClass('bg-[#0a1a3a]');
+
+    // Check dimensions
+    expect(container).toHaveClass('h-[1080px]');
+    expect(container).toHaveClass('w-[1920px]');
+
+    // Check positioning
+    expect(container).toHaveClass('relative');
+    expect(container).toHaveClass('overflow-hidden');
+  });
+
+  it('should render title and header elements', () => {
+    render(<GrainOilDashboard />);
+
+    // Check if title is rendered
+    const title = screen.getByText('粮食•油脂');
+    expect(title).toBeInTheDocument();
+    expect(title).toHaveClass('text-white');
+    expect(title).toHaveClass('text-[34px]');
+  });
+});

+ 123 - 0
tests/unit/client/home/pages/SupplyChainDashboards/components/KeyMetrics.test.tsx

@@ -0,0 +1,123 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import { describe, it, expect } from 'vitest';
+import KeyMetrics from '@/client/home/pages/SupplyChainDashboards/components/KeyMetrics';
+
+describe('KeyMetrics', () => {
+  it('should render with default metrics and title', () => {
+    render(<KeyMetrics />);
+
+    // Check title and subtitle
+    expect(screen.getByText('优质稻米')).toBeInTheDocument();
+    expect(screen.getByText('产业链联合体')).toBeInTheDocument();
+
+    // Check default metrics
+    expect(screen.getByText('加工能力达')).toBeInTheDocument();
+    expect(screen.getByText('自建优质水稻基地')).toBeInTheDocument();
+    expect(screen.getByText('储备仓容')).toBeInTheDocument();
+
+    // Check units
+    expect(screen.getByText('万吨/年')).toBeInTheDocument();
+    expect(screen.getByText('万亩')).toBeInTheDocument();
+    expect(screen.getByText('万吨')).toBeInTheDocument();
+
+    // Check data source
+    expect(screen.getByText('数据来源:省粮油集团统计年报')).toBeInTheDocument();
+  });
+
+  it('should render with custom title and subtitle', () => {
+    render(
+      <KeyMetrics
+        title="测试标题"
+        subtitle="测试副标题"
+      />
+    );
+
+    expect(screen.getByText('测试标题')).toBeInTheDocument();
+    expect(screen.getByText('测试副标题')).toBeInTheDocument();
+  });
+
+  it('should render with custom metrics', () => {
+    const customMetrics = [
+      {
+        title: '自定义指标1',
+        value: '123',
+        unit: '个',
+        digits: ['1', '2', '3']
+      },
+      {
+        title: '自定义指标2',
+        value: '45',
+        unit: '件',
+        digits: ['4', '5']
+      }
+    ];
+
+    render(<KeyMetrics metrics={customMetrics} />);
+
+    // Check custom metrics
+    expect(screen.getByText('自定义指标1')).toBeInTheDocument();
+    expect(screen.getByText('自定义指标2')).toBeInTheDocument();
+
+    // Check custom units
+    expect(screen.getByText('个')).toBeInTheDocument();
+    expect(screen.getByText('件')).toBeInTheDocument();
+
+    // Should not render default metrics
+    expect(screen.queryByText('加工能力达')).not.toBeInTheDocument();
+  });
+
+  it('should render metric digits correctly', () => {
+    render(<KeyMetrics />);
+
+    // Check if digits are rendered in the data cards
+    const processingDigits = screen.getByText('2');
+    const baseDigits = screen.getByText('1');
+    const storageDigits = screen.getByText('?');
+
+    expect(processingDigits).toBeInTheDocument();
+    expect(baseDigits).toBeInTheDocument();
+    expect(storageDigits).toBeInTheDocument();
+
+    // Check digit styling
+    expect(processingDigits).toHaveClass('text-[#c5ff92]');
+    expect(processingDigits).toHaveClass('text-[60px]');
+  });
+
+  it('should render with correct positioning and styling', () => {
+    render(<KeyMetrics />);
+
+    const container = screen.getByRole('generic');
+
+    // Check positioning
+    expect(container).toHaveClass('absolute');
+    expect(container).toHaveClass('left-[199.88px]');
+    expect(container).toHaveClass('top-[calc(50%+4.02px)]');
+
+    // Check dimensions
+    expect(container).toHaveClass('w-[330px]');
+  });
+
+  it('should render data cards with correct styling', () => {
+    render(<KeyMetrics />);
+
+    // Check title styling
+    const title = screen.getByText('加工能力达');
+    expect(title).toHaveClass('text-white');
+    expect(title).toHaveClass('text-[40px]');
+
+    // Check unit styling
+    const unit = screen.getByText('万吨/年');
+    expect(unit).toHaveClass('text-white');
+    expect(unit).toHaveClass('text-[24px]');
+  });
+
+  it('should handle empty metrics array', () => {
+    render(<KeyMetrics metrics={[]} />);
+
+    // Should still render with default metrics
+    expect(screen.getByText('加工能力达')).toBeInTheDocument();
+    expect(screen.getByText('自建优质水稻基地')).toBeInTheDocument();
+    expect(screen.getByText('储备仓容')).toBeInTheDocument();
+  });
+});

+ 87 - 0
tests/unit/client/home/pages/SupplyChainDashboards/components/SupplyChainMap.test.tsx

@@ -0,0 +1,87 @@
+import React from 'react';
+import { render, screen, fireEvent } from '@testing-library/react';
+import { describe, it, expect, vi } from 'vitest';
+import SupplyChainMap from '@/client/home/pages/SupplyChainDashboards/components/SupplyChainMap';
+
+describe('SupplyChainMap', () => {
+  it('should render the map with default points and connections', () => {
+    render(<SupplyChainMap />);
+
+    // Check if map container is rendered
+    const mapContainer = screen.getByRole('generic');
+    expect(mapContainer).toBeInTheDocument();
+
+    // Check if legend is rendered
+    expect(screen.getByText('基地')).toBeInTheDocument();
+    expect(screen.getByText('产业链')).toBeInTheDocument();
+  });
+
+  it('should render custom points when provided', () => {
+    const customPoints = [
+      { id: 'custom1', type: 'base' as const, position: { x: 100, y: 100 }, name: 'Custom Base' },
+      { id: 'custom2', type: 'industryChain' as const, position: { x: 200, y: 200 }, name: 'Custom Chain' }
+    ];
+
+    render(<SupplyChainMap points={customPoints} />);
+
+    // Check if custom point names are rendered
+    expect(screen.getByText('Custom Base')).toBeInTheDocument();
+    expect(screen.getByText('Custom Chain')).toBeInTheDocument();
+  });
+
+  it('should handle point clicks', () => {
+    const mockOnPointClick = vi.fn();
+    const customPoints = [
+      { id: 'test-point', type: 'base' as const, position: { x: 100, y: 100 }, name: 'Test Point' }
+    ];
+
+    render(<SupplyChainMap points={customPoints} onPointClick={mockOnPointClick} />);
+
+    // Find and click the point
+    const point = screen.getByText('Test Point').closest('div[class*="cursor-pointer"]');
+    expect(point).toBeInTheDocument();
+
+    if (point) {
+      fireEvent.click(point);
+      expect(mockOnPointClick).toHaveBeenCalledWith(customPoints[0]);
+    }
+  });
+
+  it('should render with correct positioning', () => {
+    render(<SupplyChainMap />);
+
+    const mapContainer = screen.getByRole('generic');
+
+    // Check positioning classes
+    expect(mapContainer).toHaveClass('absolute');
+    expect(mapContainer).toHaveClass('contents');
+    expect(mapContainer).toHaveClass('left-[529.88px]');
+  });
+
+  it('should render map border and legend', () => {
+    render(<SupplyChainMap />);
+
+    // Check if legend containers are rendered
+    const baseLegend = screen.getByText('基地').closest('div[class*="rounded-[10px]"]');
+    const chainLegend = screen.getByText('产业链').closest('div[class*="rounded-[10px]"]');
+
+    expect(baseLegend).toBeInTheDocument();
+    expect(chainLegend).toBeInTheDocument();
+  });
+
+  it('should handle empty points array', () => {
+    render(<SupplyChainMap points={[]} />);
+
+    // Should still render with default points
+    expect(screen.getByText('基地')).toBeInTheDocument();
+    expect(screen.getByText('产业链')).toBeInTheDocument();
+  });
+
+  it('should handle empty connections array', () => {
+    render(<SupplyChainMap connections={[]} />);
+
+    // Should still render with default connections
+    expect(screen.getByText('基地')).toBeInTheDocument();
+    expect(screen.getByText('产业链')).toBeInTheDocument();
+  });
+});

+ 108 - 0
tests/unit/client/home/pages/SupplyChainDashboards/components/SupplyChainModel.test.tsx

@@ -0,0 +1,108 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import { describe, it, expect } from 'vitest';
+import SupplyChainModel from '@/client/home/pages/SupplyChainDashboards/components/SupplyChainModel';
+
+describe('SupplyChainModel', () => {
+  it('should render the supply chain model with all elements', () => {
+    render(<SupplyChainModel />);
+
+    // Check main title elements
+    expect(screen.getByText('省粮油集团+区域公司+新型农业经营主体')).toBeInTheDocument();
+    expect(screen.getByText('1+1+N')).toBeInTheDocument();
+    expect(screen.getByText('供应链合作模式')).toBeInTheDocument();
+  });
+
+  it('should render the three main entities', () => {
+    render(<SupplyChainModel />);
+
+    // Check entity titles
+    expect(screen.getByText('省粮油集团')).toBeInTheDocument();
+    expect(screen.getByText('区域公司')).toBeInTheDocument();
+    expect(screen.getByText('新型农业经营主体')).toBeInTheDocument();
+
+    // Check entity descriptions
+    expect(screen.getByText('核心龙头企业')).toBeInTheDocument();
+    expect(screen.getByText('资源整合平台')).toBeInTheDocument();
+    expect(screen.getByText('标准制定者')).toBeInTheDocument();
+
+    expect(screen.getByText('区域运营中心')).toBeInTheDocument();
+    expect(screen.getByText('服务支撑平台')).toBeInTheDocument();
+    expect(screen.getByText('市场对接桥梁')).toBeInTheDocument();
+
+    expect(screen.getByText('家庭农场')).toBeInTheDocument();
+    expect(screen.getByText('合作社')).toBeInTheDocument();
+    expect(screen.getByText('农业企业')).toBeInTheDocument();
+  });
+
+  it('should render the cooperation process', () => {
+    render(<SupplyChainModel />);
+
+    // Check process title
+    expect(screen.getByText('合作流程')).toBeInTheDocument();
+
+    // Check process steps
+    expect(screen.getByText('资源整合')).toBeInTheDocument();
+    expect(screen.getByText('区域运营')).toBeInTheDocument();
+    expect(screen.getByText('产业协同')).toBeInTheDocument();
+
+    // Check process step numbers
+    expect(screen.getByText('①')).toBeInTheDocument();
+    expect(screen.getByText('②')).toBeInTheDocument();
+    expect(screen.getByText('③')).toBeInTheDocument();
+  });
+
+  it('should render with correct positioning', () => {
+    render(<SupplyChainModel />);
+
+    const container = screen.getByRole('generic');
+
+    // Check positioning
+    expect(container).toHaveClass('absolute');
+    expect(container).toHaveClass('right-[80px]');
+    expect(container).toHaveClass('top-[150px]');
+  });
+
+  it('should render with correct styling for main title', () => {
+    render(<SupplyChainModel />);
+
+    // Check 1+1+N styling
+    const mainTitle = screen.getByText('1+1+N');
+    expect(mainTitle).toHaveClass('text-[80px]');
+    expect(mainTitle).toHaveClass('font-bold');
+
+    // Check "供应链合作模式" styling
+    const subTitle = screen.getByText('供应链合作模式');
+    expect(subTitle).toHaveClass('text-[50px]');
+    expect(subTitle).toHaveClass('text-[cyan]');
+  });
+
+  it('should render entity cards with correct styling', () => {
+    render(<SupplyChainModel />);
+
+    // Check entity card containers
+    const entityCards = screen.getAllByText(/省粮油集团|区域公司|新型农业经营主体/);
+    expect(entityCards).toHaveLength(3);
+
+    // Check entity number indicators
+    const numbers = screen.getAllByText(/1|N/);
+    expect(numbers).toHaveLength(3);
+  });
+
+  it('should render process flow with correct styling', () => {
+    render(<SupplyChainModel />);
+
+    // Check process flow container
+    const processContainer = screen.getByText('合作流程').closest('div');
+    expect(processContainer).toHaveClass('border-t');
+    expect(processContainer).toHaveClass('border-[rgba(255,255,255,0.1)]');
+
+    // Check process step indicators
+    const stepIndicators = screen.getAllByText(/①|②|③/);
+    expect(stepIndicators).toHaveLength(3);
+
+    stepIndicators.forEach(indicator => {
+      expect(indicator).toHaveClass('text-white');
+    });
+  });
+});