InventoryPageLastDried.test.tsx 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. /**
  2. * The "Last dried" column (#2863): shown by default, with the temperature and
  3. * hours of the run when they are known.
  4. */
  5. import { describe, it, expect, beforeEach, vi } from 'vitest';
  6. import { screen, waitFor } from '@testing-library/react';
  7. import { render } from '../utils';
  8. import InventoryPageRouter from '../../pages/InventoryPage';
  9. import { http, HttpResponse } from 'msw';
  10. import { server } from '../mocks/server';
  11. const BASE = {
  12. material: 'PLA',
  13. subtype: 'Basic',
  14. color_name: 'Red',
  15. rgba: 'FF0000FF',
  16. label_weight: 1000,
  17. core_weight: 250,
  18. weight_used: 100,
  19. slicer_filament: null,
  20. slicer_filament_name: null,
  21. nozzle_temp_min: 220,
  22. nozzle_temp_max: 240,
  23. note: null,
  24. added_full: null,
  25. last_used: null,
  26. encode_time: null,
  27. tag_uid: null,
  28. tray_uuid: null,
  29. data_origin: null,
  30. tag_type: null,
  31. archived_at: null,
  32. created_at: '2025-01-01T00:00:00Z',
  33. updated_at: '2025-01-01T00:00:00Z',
  34. k_profiles: [],
  35. cost_per_kg: null,
  36. last_scale_weight: null,
  37. last_weighed_at: null,
  38. storage_location: null,
  39. category: null,
  40. low_stock_threshold_pct: null,
  41. weight_locked: false,
  42. };
  43. const SPOOLS = [
  44. { ...BASE, id: 1, brand: 'AlphaBrand', last_dried_at: '2026-09-01T12:00:00', last_dried_temp: 55, last_dried_hours: 7.5 },
  45. { ...BASE, id: 2, brand: 'BetaBrand', last_dried_at: '2026-09-02T12:00:00', last_dried_temp: null, last_dried_hours: null },
  46. { ...BASE, id: 3, brand: 'GammaBrand', last_dried_at: null },
  47. ];
  48. function setupHandlers() {
  49. server.use(
  50. http.get('/api/v1/settings/', () => HttpResponse.json({ currency: 'USD', low_stock_threshold: 20.0, language: 'en' })),
  51. http.get('/api/v1/settings/spoolman', () =>
  52. HttpResponse.json({ spoolman_enabled: 'false', spoolman_url: '' })
  53. ),
  54. http.get('/api/v1/inventory/spools', () => HttpResponse.json(SPOOLS)),
  55. http.get('/api/v1/inventory/assignments', () => HttpResponse.json([])),
  56. http.get('/api/v1/inventory/catalog', () => HttpResponse.json([])),
  57. http.get('/api/v1/inventory/color-catalog', () => HttpResponse.json([])),
  58. http.get('/api/v1/inventory/colors', () => HttpResponse.json([])),
  59. http.get('/api/v1/inventory/spool-catalog', () => HttpResponse.json([])),
  60. http.get('/api/v1/inventory/locations', () => HttpResponse.json([])),
  61. http.get('/api/v1/printers/', () => HttpResponse.json([])),
  62. );
  63. }
  64. function row(brand: string): HTMLElement {
  65. const found = Array.from(document.querySelectorAll('tbody tr')).find((r) => r.textContent?.includes(brand));
  66. if (!found) throw new Error(`no row for ${brand}`);
  67. return found as HTMLElement;
  68. }
  69. describe('InventoryPage last dried column', () => {
  70. beforeEach(() => {
  71. setupHandlers();
  72. vi.mocked(localStorage.getItem).mockReturnValue(null);
  73. });
  74. it('is visible by default', async () => {
  75. render(<InventoryPageRouter />);
  76. await waitFor(() => expect(screen.getByRole('columnheader', { name: /Last dried/ })).toBeInTheDocument());
  77. });
  78. it('shows the temperature and hours of the run when known', async () => {
  79. render(<InventoryPageRouter />);
  80. await waitFor(() => expect(row('AlphaBrand')).toHaveTextContent('55°C · 7.5h'));
  81. expect(row('BetaBrand')).not.toHaveTextContent('°C');
  82. });
  83. it('sorts by the date', async () => {
  84. vi.mocked(localStorage.getItem).mockImplementation((key) =>
  85. key === 'bambuddy-inventory-sort' ? '{"column":"last_dried","direction":"desc"}' : null,
  86. );
  87. render(<InventoryPageRouter />);
  88. await waitFor(() => {
  89. const brands = Array.from(document.querySelectorAll('tbody tr')).map((r) => r.textContent ?? '');
  90. expect(brands.findIndex((t) => t.includes('BetaBrand'))).toBeLessThan(
  91. brands.findIndex((t) => t.includes('AlphaBrand')),
  92. );
  93. });
  94. });
  95. });