InventoryPageHeaderLayout.test.tsx 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. /**
  2. * The Spool Inventory header must not overflow a phone viewport (#2813).
  3. *
  4. * Its five buttons come to roughly 600px side by side, and a flex row whose
  5. * items cannot shrink is as wide as its contents. At 390px that pushed the
  6. * header past the viewport, and since the whole page scrolls as one region
  7. * everything below it panned with the header.
  8. *
  9. * jsdom does no layout, so this asserts the two properties that make the
  10. * overflow impossible rather than a measured width: the header stacks below
  11. * `sm`, and the button group wraps. Same approach as the VirtualPrinterCard
  12. * header test (#2808).
  13. */
  14. import { describe, it, expect, beforeEach } from 'vitest';
  15. import { screen, waitFor } from '@testing-library/react';
  16. import { http, HttpResponse } from 'msw';
  17. import { render } from '../utils';
  18. import InventoryPageRouter from '../../pages/InventoryPage';
  19. import { server } from '../mocks/server';
  20. function setupHandlers() {
  21. server.use(
  22. http.get('/api/v1/settings/spoolman', () =>
  23. HttpResponse.json({
  24. spoolman_enabled: 'false',
  25. spoolman_url: '',
  26. spoolman_sync_mode: 'auto',
  27. spoolman_disable_weight_sync: 'false',
  28. spoolman_report_partial_usage: 'true',
  29. })
  30. ),
  31. http.get('/api/v1/inventory/spools', () => HttpResponse.json([])),
  32. http.get('/api/v1/inventory/assignments', () => HttpResponse.json([])),
  33. http.get('/api/v1/inventory/catalog', () => HttpResponse.json([])),
  34. http.get('/api/v1/printers/', () => HttpResponse.json([])),
  35. );
  36. }
  37. describe('InventoryPage — header layout', () => {
  38. beforeEach(() => {
  39. setupHandlers();
  40. });
  41. it('stacks the header below sm and keeps it a row from sm up', async () => {
  42. render(<InventoryPageRouter />);
  43. const heading = await screen.findByRole('heading', { name: 'Spool Inventory' });
  44. // h1 -> title block -> header row
  45. const header = heading.parentElement?.parentElement as HTMLElement;
  46. expect(header.className).toContain('flex-col');
  47. expect(header.className).toContain('sm:flex-row');
  48. // Desktop is unchanged: the row still spreads title and actions apart.
  49. expect(header.className).toContain('sm:justify-between');
  50. });
  51. it('lets the action buttons wrap instead of widening the header', async () => {
  52. render(<InventoryPageRouter />);
  53. // Located through the heading rather than by button name: the empty-state
  54. // panel offers its own "Add Spool" further down the page.
  55. const heading = await screen.findByRole('heading', { name: 'Spool Inventory' });
  56. const header = heading.parentElement?.parentElement as HTMLElement;
  57. const group = header.lastElementChild as HTMLElement;
  58. await waitFor(() => {
  59. expect(group.querySelector('button')).toBeInTheDocument();
  60. });
  61. // Without this the group is as wide as all its buttons laid end to end,
  62. // whatever the viewport is.
  63. expect(group.className).toContain('flex-wrap');
  64. // All six actions (CSV import/export, Locations, Suppliers (#2988),
  65. // labels, add) stay in that one group -- wrapping them is the fix,
  66. // hiding any of them is not.
  67. expect(group.querySelectorAll('button')).toHaveLength(6);
  68. });
  69. });