MaterialNumberStats.test.tsx 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. /**
  2. * Tests for the MaterialNumberStats widget (#2870).
  3. */
  4. import { describe, it, expect, vi, beforeEach } from 'vitest';
  5. import { render, screen } from '@testing-library/react';
  6. import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
  7. import { MaterialNumberStats } from '../../components/MaterialNumberStats';
  8. import { api } from '../../api/client';
  9. vi.mock('../../api/client', () => ({
  10. api: {
  11. getMaterialNumberStats: vi.fn(),
  12. },
  13. }));
  14. function renderWidget(props: { dateFrom?: string; dateTo?: string } = {}) {
  15. const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  16. return render(
  17. <QueryClientProvider client={client}>
  18. <MaterialNumberStats currency="EUR" {...props} />
  19. </QueryClientProvider>,
  20. );
  21. }
  22. describe('MaterialNumberStats', () => {
  23. beforeEach(() => {
  24. vi.clearAllMocks();
  25. });
  26. it('renders one row per material number with weights and cost', async () => {
  27. (api.getMaterialNumberStats as ReturnType<typeof vi.fn>).mockResolvedValue([
  28. { material_number: '16', spool_count: 1, remaining_g: 500, consumed_g: 1500, cost: 45 },
  29. { material_number: '15', spool_count: 12, remaining_g: 9500, consumed_g: 250, cost: 5 },
  30. ]);
  31. renderWidget();
  32. expect(await screen.findByText('16')).toBeInTheDocument();
  33. expect(screen.getByText('15')).toBeInTheDocument();
  34. expect(screen.getByText('12')).toBeInTheDocument();
  35. // >= 1 kg renders as kilograms, below stays in grams.
  36. expect(screen.getByText('9.50 kg')).toBeInTheDocument();
  37. expect(screen.getByText('500 g')).toBeInTheDocument();
  38. expect(screen.getByText('EUR 45.00')).toBeInTheDocument();
  39. });
  40. it('shows the empty hint when no numbers are assigned', async () => {
  41. (api.getMaterialNumberStats as ReturnType<typeof vi.fn>).mockResolvedValue([]);
  42. renderWidget();
  43. expect(await screen.findByText(/No material numbers assigned yet/)).toBeInTheDocument();
  44. });
  45. // A 403 from a missing INVENTORY_READ, a 500 or a dropped connection are
  46. // not "you have not numbered your spools" — the two states must read
  47. // differently or the user goes looking for a problem that isn't there.
  48. it('reports an API failure as a failure, not as an empty inventory', async () => {
  49. (api.getMaterialNumberStats as ReturnType<typeof vi.fn>).mockRejectedValue(new Error('403'));
  50. renderWidget();
  51. expect(await screen.findByText(/Could not load the material number statistics/)).toBeInTheDocument();
  52. expect(screen.queryByText(/No material numbers assigned yet/)).not.toBeInTheDocument();
  53. });
  54. it('passes the dashboard timeframe to the endpoint', async () => {
  55. (api.getMaterialNumberStats as ReturnType<typeof vi.fn>).mockResolvedValue([]);
  56. renderWidget({ dateFrom: '2026-08-01', dateTo: '2026-08-31' });
  57. await screen.findByText(/No material numbers assigned yet/);
  58. expect(api.getMaterialNumberStats).toHaveBeenCalledWith({
  59. dateFrom: '2026-08-01',
  60. dateTo: '2026-08-31',
  61. });
  62. });
  63. // The range is part of the query key, so the cache cannot serve a
  64. // 30-day answer when the dashboard has moved to 90.
  65. it('refetches on the same client when the timeframe changes', async () => {
  66. (api.getMaterialNumberStats as ReturnType<typeof vi.fn>).mockResolvedValue([]);
  67. const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  68. const { rerender } = render(
  69. <QueryClientProvider client={client}>
  70. <MaterialNumberStats currency="EUR" dateFrom="2026-08-01" />
  71. </QueryClientProvider>,
  72. );
  73. await screen.findByText(/No material numbers assigned yet/);
  74. expect(api.getMaterialNumberStats).toHaveBeenCalledTimes(1);
  75. rerender(
  76. <QueryClientProvider client={client}>
  77. <MaterialNumberStats currency="EUR" dateFrom="2026-09-01" />
  78. </QueryClientProvider>,
  79. );
  80. await vi.waitFor(() => expect(api.getMaterialNumberStats).toHaveBeenCalledTimes(2));
  81. expect(api.getMaterialNumberStats).toHaveBeenLastCalledWith({
  82. dateFrom: '2026-09-01',
  83. dateTo: undefined,
  84. });
  85. });
  86. });