SpoolBuddyCalibrationPage.test.tsx 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. /**
  2. * Tests for SpoolBuddyCalibrationPage:
  3. * - Renders "Scale Calibration" heading
  4. * - Shows current weight display
  5. * - Shows Tare and Calibrate buttons
  6. * - Shows "No SpoolBuddy device found" when no device
  7. */
  8. import { describe, it, expect, vi, beforeEach } from 'vitest';
  9. import { screen, waitFor } from '@testing-library/react';
  10. import React from 'react';
  11. import { render } from '@testing-library/react';
  12. import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
  13. import { MemoryRouter, Route, Routes, Outlet } from 'react-router-dom';
  14. import { SpoolBuddyCalibrationPage } from '../../pages/spoolbuddy/SpoolBuddyCalibrationPage';
  15. const mockDevice = {
  16. id: 1,
  17. device_id: 'sb-test-001',
  18. hostname: 'spoolbuddy-pi',
  19. ip_address: '192.168.1.100',
  20. firmware_version: '1.2.3',
  21. has_nfc: true,
  22. has_scale: true,
  23. tare_offset: 0,
  24. calibration_factor: 1.0,
  25. nfc_reader_type: 'PN532',
  26. nfc_connection: 'I2C',
  27. display_brightness: 80,
  28. display_blank_timeout: 300,
  29. has_backlight: true,
  30. last_calibrated_at: null,
  31. last_seen: '2026-03-22T12:00:00Z',
  32. pending_command: null,
  33. nfc_ok: true,
  34. scale_ok: true,
  35. uptime_s: 3600,
  36. update_status: null,
  37. update_message: null,
  38. online: true,
  39. };
  40. let mockDevices = [mockDevice];
  41. vi.mock('../../api/client', () => ({
  42. spoolbuddyApi: {
  43. getDevices: vi.fn(() => Promise.resolve(mockDevices)),
  44. tare: vi.fn().mockResolvedValue({ status: 'ok' }),
  45. setCalibrationFactor: vi.fn().mockResolvedValue({ status: 'ok' }),
  46. },
  47. }));
  48. vi.mock('react-i18next', () => ({
  49. useTranslation: () => ({
  50. t: (_key: string, fallback: string) => fallback,
  51. i18n: { language: 'en', changeLanguage: vi.fn() },
  52. }),
  53. }));
  54. function makeOutletContext(overrides: Record<string, unknown> = {}) {
  55. return {
  56. selectedPrinterId: null,
  57. setSelectedPrinterId: vi.fn(),
  58. sbState: {
  59. weight: 250.5,
  60. weightStable: true,
  61. rawAdc: 12345,
  62. matchedSpool: null,
  63. unknownTagUid: null,
  64. deviceOnline: true,
  65. deviceId: 'sb-test-001',
  66. remainingWeight: null,
  67. netWeight: null,
  68. ...(overrides.sbState as Record<string, unknown> || {}),
  69. },
  70. setAlert: vi.fn(),
  71. displayBrightness: 100,
  72. setDisplayBrightness: vi.fn(),
  73. displayBlankTimeout: 0,
  74. setDisplayBlankTimeout: vi.fn(),
  75. };
  76. }
  77. function renderPage(contextOverrides: Record<string, unknown> = {}) {
  78. const ctx = makeOutletContext(contextOverrides);
  79. function Wrapper() {
  80. return <Outlet context={ctx} />;
  81. }
  82. const qc = new QueryClient({ defaultOptions: { queries: { retry: false, gcTime: 0 } } });
  83. return render(
  84. <QueryClientProvider client={qc}>
  85. <MemoryRouter initialEntries={['/spoolbuddy/calibration']}>
  86. <Routes>
  87. <Route element={<Wrapper />}>
  88. <Route path="spoolbuddy/calibration" element={<SpoolBuddyCalibrationPage />} />
  89. </Route>
  90. </Routes>
  91. </MemoryRouter>
  92. </QueryClientProvider>
  93. );
  94. }
  95. describe('SpoolBuddyCalibrationPage', () => {
  96. beforeEach(() => {
  97. vi.clearAllMocks();
  98. mockDevices = [mockDevice];
  99. });
  100. it('renders "Scale Calibration" heading', async () => {
  101. renderPage();
  102. await waitFor(() => {
  103. expect(screen.getByText('Scale Calibration')).toBeDefined();
  104. });
  105. });
  106. it('shows current weight display when device available', async () => {
  107. renderPage();
  108. await waitFor(() => {
  109. expect(screen.getByText('Current weight')).toBeDefined();
  110. expect(screen.getByText('250.5 g')).toBeDefined();
  111. });
  112. });
  113. it('shows Tare and Calibrate buttons when device available', async () => {
  114. renderPage();
  115. await waitFor(() => {
  116. expect(screen.getByText('Tare')).toBeDefined();
  117. expect(screen.getByText('Calibrate')).toBeDefined();
  118. });
  119. });
  120. it('shows "No SpoolBuddy device found" when no device', async () => {
  121. mockDevices = [];
  122. renderPage();
  123. await waitFor(() => {
  124. expect(screen.getByText('No SpoolBuddy device found')).toBeDefined();
  125. });
  126. });
  127. it('shows back button that navigates to settings', () => {
  128. renderPage();
  129. // Find the back button (contains a chevron SVG)
  130. const buttons = screen.getAllByRole('button');
  131. // First button is the back button
  132. expect(buttons.length).toBeGreaterThan(0);
  133. });
  134. });