PrinterHASensorRow.test.tsx 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. /**
  2. * The Home Assistant sensor row on the printer card (#1148, #448).
  3. *
  4. * The reporter's whole ask is a glance-and-know row: "is the enclosure shut?"
  5. * So the assertions are about what the pill actually says. "on" is not an
  6. * answer to that question — "Open" is, and only Home Assistant's device_class
  7. * tells us which word to use.
  8. */
  9. import { screen, waitFor } from '@testing-library/react';
  10. import { beforeEach, describe, expect, it, vi } from 'vitest';
  11. import { PrinterHASensorRow } from '../../components/PrinterHASensorRow';
  12. import { api } from '../../api/client';
  13. import { render } from '../utils';
  14. vi.mock('../../api/client', async () => {
  15. const actual = await vi.importActual<typeof import('../../api/client')>('../../api/client');
  16. return { ...actual, api: { ...actual.api, getHASensorReadings: vi.fn() } };
  17. });
  18. const getReadings = vi.mocked(api.getHASensorReadings);
  19. function reading(overrides = {}) {
  20. return {
  21. id: 1,
  22. name: 'Enclosure Door',
  23. entity_id: 'binary_sensor.enclosure_door',
  24. kind: 'binary' as const,
  25. device_class: 'door',
  26. unit: null,
  27. state: 'off',
  28. value: null,
  29. alerting: false,
  30. block_print: false,
  31. reachable: true,
  32. last_changed: null,
  33. ...overrides,
  34. };
  35. }
  36. describe('PrinterHASensorRow', () => {
  37. beforeEach(() => {
  38. getReadings.mockReset();
  39. });
  40. it('renders nothing when the printer has no sensors', async () => {
  41. getReadings.mockResolvedValue([]);
  42. render(<PrinterHASensorRow printerId={4} />);
  43. await waitFor(() => expect(getReadings).toHaveBeenCalledWith(4));
  44. // No label, no divider, no empty-state placeholder — the row must not
  45. // reserve space on every card that has no sensors configured.
  46. expect(screen.queryByText('Sensors')).not.toBeInTheDocument();
  47. });
  48. it('names a door state by its device class, not by on/off', async () => {
  49. getReadings.mockResolvedValue([reading({ state: 'off' })]);
  50. render(<PrinterHASensorRow printerId={4} />);
  51. expect(await screen.findByText('Closed')).toBeInTheDocument();
  52. expect(screen.getByText('Enclosure Door')).toBeInTheDocument();
  53. });
  54. it('says Open for the same sensor in the other state', async () => {
  55. getReadings.mockResolvedValue([reading({ state: 'on', alerting: true })]);
  56. render(<PrinterHASensorRow printerId={4} />);
  57. expect(await screen.findByText('Open')).toBeInTheDocument();
  58. });
  59. it('falls back to on/off for a class it has no wording for', async () => {
  60. getReadings.mockResolvedValue([reading({ device_class: null, state: 'on' })]);
  61. render(<PrinterHASensorRow printerId={4} />);
  62. expect(await screen.findByText('On')).toBeInTheDocument();
  63. });
  64. it('shows a numeric reading with its unit', async () => {
  65. getReadings.mockResolvedValue([
  66. reading({
  67. kind: 'numeric',
  68. device_class: 'temperature',
  69. entity_id: 'sensor.enclosure_temp',
  70. name: 'Enclosure Temp',
  71. unit: '°C',
  72. state: '41.2',
  73. value: 41.2,
  74. }),
  75. ]);
  76. render(<PrinterHASensorRow printerId={4} />);
  77. expect(await screen.findByText('41.2 °C')).toBeInTheDocument();
  78. });
  79. it('shows the Battery icon for a battery-class reading, not the generic Gauge', async () => {
  80. // The printer icon map never had its own "battery" entry — merging it
  81. // with the location-sensor map added one, deliberately, for both
  82. // consumers. This pins that the printer row picks it up rather than
  83. // silently keeping the old Gauge fallback.
  84. getReadings.mockResolvedValue([
  85. reading({
  86. kind: 'numeric',
  87. device_class: 'battery',
  88. entity_id: 'sensor.enclosure_sensor_battery',
  89. name: 'Enclosure Sensor Battery',
  90. unit: '%',
  91. state: '87',
  92. value: 87,
  93. }),
  94. ]);
  95. render(<PrinterHASensorRow printerId={4} />);
  96. const value = await screen.findByText('87 %');
  97. const row = value.closest('div.flex')!;
  98. expect(row.querySelector('.lucide-battery')).toBeInTheDocument();
  99. expect(row.querySelector('.lucide-gauge')).not.toBeInTheDocument();
  100. });
  101. it('reports an unreachable sensor as unavailable rather than as a state', async () => {
  102. // The pill must not read "Closed" for a door contact that dropped off the
  103. // network — that is the one wrong answer with real consequences.
  104. getReadings.mockResolvedValue([reading({ state: null, reachable: false })]);
  105. render(<PrinterHASensorRow printerId={4} />);
  106. expect(await screen.findByText('Unavailable')).toBeInTheDocument();
  107. expect(screen.queryByText('Closed')).not.toBeInTheDocument();
  108. });
  109. it('marks an alerting sensor for the eye', async () => {
  110. getReadings.mockResolvedValue([reading({ state: 'on', alerting: true })]);
  111. render(<PrinterHASensorRow printerId={4} />);
  112. const pill = (await screen.findByText('Open')).closest('span[title]');
  113. expect(pill?.className).toContain('red');
  114. });
  115. it('does not colour a quiet sensor as an alert', async () => {
  116. getReadings.mockResolvedValue([reading({ state: 'on', alerting: false })]);
  117. render(<PrinterHASensorRow printerId={4} />);
  118. const pill = (await screen.findByText('Open')).closest('span[title]');
  119. expect(pill?.className).not.toContain('red');
  120. });
  121. it('says so in the tooltip when a sensor holds prints', async () => {
  122. getReadings.mockResolvedValue([reading({ block_print: true })]);
  123. render(<PrinterHASensorRow printerId={4} />);
  124. await screen.findByText('Closed');
  125. expect(
  126. screen.getByTitle('binary_sensor.enclosure_door — holds prints while alerting')
  127. ).toBeInTheDocument();
  128. });
  129. it('lists every sensor bound to the printer', async () => {
  130. getReadings.mockResolvedValue([
  131. reading(),
  132. reading({
  133. id: 2,
  134. kind: 'numeric',
  135. name: 'Enclosure Temp',
  136. entity_id: 'sensor.enclosure_temp',
  137. device_class: 'temperature',
  138. unit: '°C',
  139. state: '22',
  140. value: 22,
  141. }),
  142. ]);
  143. render(<PrinterHASensorRow printerId={4} />);
  144. expect(await screen.findByText('Enclosure Door')).toBeInTheDocument();
  145. expect(screen.getByText('Enclosure Temp')).toBeInTheDocument();
  146. });
  147. });