| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720 |
- /**
- * Tests for the StreamOverlayPage component.
- */
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
- import { screen, waitFor, render as rtlRender } from '@testing-library/react';
- import { StreamOverlayPage } from '../../pages/StreamOverlayPage';
- import { http, HttpResponse } from 'msw';
- import { server } from '../mocks/server';
- import { MemoryRouter, Route, Routes } from 'react-router-dom';
- import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
- import { ThemeProvider } from '../../contexts/ThemeContext';
- import { ToastProvider } from '../../contexts/ToastContext';
- import { AuthProvider } from '../../contexts/AuthContext';
- const mockPrinter = {
- id: 1,
- name: 'X1 Carbon',
- ip_address: '192.168.1.100',
- serial_number: '00M09A350100001',
- access_code: '12345678',
- model: 'X1C',
- enabled: true,
- };
- const mockStatusIdle = {
- id: 1,
- name: 'X1 Carbon',
- connected: true,
- state: 'IDLE',
- progress: 0,
- current_print: null,
- remaining_time: null,
- layer_num: null,
- total_layers: null,
- stg_cur_name: null,
- };
- const mockStatusPrinting = {
- id: 1,
- name: 'X1 Carbon',
- connected: true,
- state: 'RUNNING',
- progress: 45,
- current_print: 'Benchy.gcode.3mf',
- remaining_time: 82,
- layer_num: 150,
- total_layers: 300,
- stg_cur_name: null,
- };
- // Custom render for StreamOverlayPage
- function renderOverlayPage(printerId: number, queryParams = '') {
- const queryClient = new QueryClient({
- defaultOptions: {
- queries: { retry: false, gcTime: 0 },
- mutations: { retry: false },
- },
- });
- return rtlRender(
- <QueryClientProvider client={queryClient}>
- <MemoryRouter initialEntries={[`/overlay/${printerId}${queryParams}`]}>
- <AuthProvider>
- <ThemeProvider>
- <ToastProvider>
- <Routes>
- <Route path="/overlay/:printerId" element={<StreamOverlayPage />} />
- </Routes>
- </ToastProvider>
- </ThemeProvider>
- </AuthProvider>
- </MemoryRouter>
- </QueryClientProvider>
- );
- }
- describe('StreamOverlayPage', () => {
- const originalTitle = document.title;
- beforeEach(() => {
- // Mock WebSocket. vitest 4 dropped support for arrow-function constructor
- // mocks (`new (() => ...)` throws "is not a constructor"); use a plain
- // function so `new WebSocket(...)` resolves correctly.
- vi.stubGlobal(
- 'WebSocket',
- vi.fn().mockImplementation(function (this: { close: () => void; onmessage: null; onerror: null }) {
- this.close = vi.fn();
- this.onmessage = null;
- this.onerror = null;
- }),
- );
- server.use(
- http.get('/api/v1/printers/:id', () => {
- return HttpResponse.json(mockPrinter);
- }),
- http.get('/api/v1/printers/:id/status', () => {
- return HttpResponse.json(mockStatusIdle);
- })
- );
- });
- afterEach(() => {
- document.title = originalTitle;
- vi.unstubAllGlobals();
- });
- describe('updated artwork', () => {
- it('maps legacy model codes in version 2', async () => {
- server.use(http.get('/api/v1/printers/:id', () => HttpResponse.json({ ...mockPrinter, model: 'BL-P001' })));
- renderOverlayPage(1, '?artwork=2&show=model');
- expect(await screen.findByText('X1C')).toBeInTheDocument();
- expect(screen.queryByText('BL-P001')).not.toBeInTheDocument();
- });
- it.each(['', '?artwork=other', '?artwork=updated'])('retains the original renderer for %s', async (query) => {
- const { container } = renderOverlayPage(1, query);
- await screen.findByAltText('Bambuddy');
- expect(container.querySelector('.updated-overlay')).not.toBeInTheDocument();
- expect(screen.getByAltText('Camera stream')).toHaveClass('object-contain');
- });
- it('uses token data, rotation and both nozzles without inventing missing temperatures', async () => {
- server.use(http.get('/api/v1/printers/:id/overlay-status', () => HttpResponse.json({
- ...mockStatusPrinting, name: 'Token printer', model: 'O1D', camera_rotation: 90,
- time_format: '24h', temperatures: { nozzle: 210, nozzle_target: 220, nozzle_2: 215, bed: 45 },
- })));
- renderOverlayPage(1, '?artwork=2&token=bblt_test&show=model,nozzle,bed,chamber&size=large');
- expect(await screen.findByText('H2D')).toBeInTheDocument();
- expect(screen.queryByText('Token printer')).not.toBeInTheDocument();
- expect(screen.getByText('Nozzle 2')).toBeInTheDocument();
- expect(screen.queryByText('Chamber')).not.toBeInTheDocument();
- const camera = screen.getByAltText('Camera stream');
- expect(camera).toHaveAttribute('src', expect.stringContaining('token=bblt_test'));
- expect(camera).toHaveStyle({ transform: 'translate(-50%, -50%) rotate(90deg)' });
- expect(camera.closest('.updated-overlay')).toHaveAttribute('data-size', 'large');
- expect(screen.queryByRole('progressbar')).not.toBeInTheDocument();
- });
- it.each([
- [90, { width: '640px', height: '360px' }],
- [0, null],
- ])('sizes a camera turned %i degrees from its slot, not the viewport', async (rotation, expected) => {
- // A portrait source: the camera fills a 360 x 640 middle row, so the
- // viewport (the old vw/vh fallback) would be the wrong box to swap.
- const rect = vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockImplementation(function (this: HTMLElement) {
- return this.classList.contains('updated-overlay__camera')
- ? ({ width: 360, height: 640, top: 0, left: 0, right: 360, bottom: 640, x: 0, y: 0, toJSON: () => ({}) } as DOMRect)
- : ({ width: 0, height: 0, top: 0, left: 0, right: 0, bottom: 0, x: 0, y: 0, toJSON: () => ({}) } as DOMRect);
- });
- try {
- server.use(http.get('/api/v1/printers/:id', () => HttpResponse.json({ ...mockPrinter, camera_rotation: rotation })));
- renderOverlayPage(1, '?artwork=2');
- const camera = await screen.findByAltText('Camera stream');
- await waitFor(() => expect(camera).toHaveStyle({ transform: `translate(-50%, -50%) rotate(${rotation}deg)` }));
- if (expected) {
- await waitFor(() => expect(camera).toHaveStyle(expected));
- } else {
- expect(camera.style.width).toBe('');
- expect(camera.style.height).toBe('');
- }
- } finally {
- rect.mockRestore();
- }
- });
- it('renders selected fields and accessible progress', async () => {
- server.use(http.get('/api/v1/printers/:id/status', () => HttpResponse.json(mockStatusPrinting)));
- renderOverlayPage(1, '?artwork=2&show=printer,model,filename,status,progress,layers,eta');
- expect(await screen.findByRole('progressbar')).toHaveAttribute('aria-valuenow', '45');
- expect(screen.getByText('X1 Carbon')).toBeInTheDocument();
- expect(screen.getByText('X1C')).toBeInTheDocument();
- expect(screen.getByText('Benchy')).toBeInTheDocument();
- expect(screen.getByText('150 / 300')).toBeInTheDocument();
- });
- it('hides all unselected information, including idle status', async () => {
- const { container } = renderOverlayPage(1, '?artwork=2&show=&camera=false');
- await screen.findByAltText('Bambuddy');
- expect(screen.queryByText('Printer is idle')).not.toBeInTheDocument();
- expect(screen.queryByAltText('Camera stream')).not.toBeInTheDocument();
- expect(container.querySelector('.updated-overlay__panel')).not.toBeInTheDocument();
- });
- it.each(['FINISH', 'FAILED'])('shows %s without contradictory idle text', async (state) => {
- server.use(http.get('/api/v1/printers/:id/status', () => HttpResponse.json({ ...mockStatusPrinting, state })));
- renderOverlayPage(1, '?artwork=2&show=status,progress');
- expect(await screen.findByText(state === 'FINISH' ? 'Finished' : 'Failed')).toBeInTheDocument();
- expect(screen.queryByText('Printer is idle')).not.toBeInTheDocument();
- expect(screen.queryByRole('progressbar')).not.toBeInTheDocument();
- });
- it('hides stale progress when disconnected', async () => {
- server.use(http.get('/api/v1/printers/:id/status', () => HttpResponse.json({ ...mockStatusPrinting, connected: false })));
- renderOverlayPage(1, '?artwork=2&show=status,progress,layers,eta');
- expect(await screen.findByText('Printer offline')).toBeInTheDocument();
- expect(screen.queryByRole('progressbar')).not.toBeInTheDocument();
- });
- });
- describe('rendering', () => {
- it('renders overlay page for printer', async () => {
- renderOverlayPage(1);
- await waitFor(() => {
- expect(screen.getByText('Printer is idle')).toBeInTheDocument();
- });
- });
- it('shows Bambuddy logo', async () => {
- renderOverlayPage(1);
- await waitFor(() => {
- expect(screen.getByAltText('Bambuddy')).toBeInTheDocument();
- });
- });
- it('logo links to GitHub', async () => {
- renderOverlayPage(1);
- await waitFor(() => {
- const logo = screen.getByAltText('Bambuddy');
- const link = logo.closest('a');
- expect(link).toHaveAttribute('href', 'https://github.com/maziggy/bambuddy');
- });
- });
- });
- describe('printer model', () => {
- it.each(['', '?show=printer', '?show='])('hides the model unless selected (%s)', async (query) => {
- renderOverlayPage(1, query);
- await screen.findByText('Printer is idle');
- expect(screen.queryByText(/X1C/)).not.toBeInTheDocument();
- });
- it.each([
- ['?show=model', 'X1C'],
- ['?show=printer,model', 'X1 Carbon · X1C'],
- ])('shows the selected printer identity (%s)', async (query, identity) => {
- renderOverlayPage(1, query);
- expect(await screen.findByText(identity)).toBeInTheDocument();
- if (query === '?show=model') expect(screen.queryByText(/X1 Carbon/)).not.toBeInTheDocument();
- });
- it.each([
- ['BL-P001', 'X1C'],
- ['O1D', 'H2D'],
- ['H2D', 'H2D'],
- ['Future printer', 'Future printer'],
- ])('displays the saved model %s as %s', async (model, displayName) => {
- server.use(http.get('/api/v1/printers/:id', () => HttpResponse.json({ ...mockPrinter, model })));
- renderOverlayPage(1, '?show=model');
- expect(await screen.findByText(displayName)).toBeInTheDocument();
- });
- it.each([null, ''])('omits a missing model without adding a separator (%s)', async (model) => {
- server.use(http.get('/api/v1/printers/:id', () => HttpResponse.json({ ...mockPrinter, model })));
- renderOverlayPage(1, '?show=printer,model');
- expect(await screen.findByText('X1 Carbon')).toBeInTheDocument();
- expect(screen.queryByText(/·/)).not.toBeInTheDocument();
- });
- it.each([['H2D', 'H2D'], ['BL-P001', 'X1C'], ['O1D', 'H2D'], [null, ''], ['', '']] as const)('reads the model %s from the OBS token feed without requesting printer details', async (model, displayName) => {
- let printerHit = false;
- server.use(
- http.get('/api/v1/printers/:id/overlay-status', () => HttpResponse.json({
- ...mockStatusIdle, name: 'Workshop', model, camera_rotation: 0,
- gcode_file: null, temperatures: {}, time_format: 'system',
- })),
- http.get('/api/v1/printers/:id', () => {
- printerHit = true;
- return new HttpResponse(null, { status: 401 });
- }),
- );
- renderOverlayPage(1, '?token=obs-tok&show=printer,model');
- expect(await screen.findByText(displayName ? `Workshop · ${displayName}` : 'Workshop')).toBeInTheDocument();
- expect(printerHit).toBe(false);
- if (!model) expect(screen.queryByText(/·/)).not.toBeInTheDocument();
- });
- });
- describe('printing state', () => {
- beforeEach(() => {
- server.use(
- http.get('/api/v1/printers/:id/status', () => {
- return HttpResponse.json(mockStatusPrinting);
- })
- );
- });
- it('shows filename when printing', async () => {
- renderOverlayPage(1);
- await waitFor(() => {
- expect(screen.getByText('Benchy')).toBeInTheDocument();
- });
- });
- it('shows progress percentage', async () => {
- renderOverlayPage(1);
- await waitFor(() => {
- expect(screen.getByText('45%')).toBeInTheDocument();
- });
- });
- it('shows layer count', async () => {
- renderOverlayPage(1);
- await waitFor(() => {
- expect(screen.getByText('150')).toBeInTheDocument();
- expect(screen.getByText('300')).toBeInTheDocument();
- });
- });
- it('shows status text', async () => {
- renderOverlayPage(1);
- await waitFor(() => {
- expect(screen.getByText('Printing')).toBeInTheDocument();
- });
- });
- });
- describe('invalid printer', () => {
- it('shows invalid printer message for ID 0', async () => {
- renderOverlayPage(0);
- await waitFor(() => {
- expect(screen.getByText('Invalid printer ID')).toBeInTheDocument();
- });
- });
- });
- describe('query parameters', () => {
- it('respects size parameter', async () => {
- renderOverlayPage(1, '?size=large');
- await waitFor(() => {
- // Just verify it renders without error
- expect(screen.getByAltText('Bambuddy')).toBeInTheDocument();
- });
- });
- it('respects show parameter to hide elements', async () => {
- server.use(
- http.get('/api/v1/printers/:id/status', () => {
- return HttpResponse.json(mockStatusPrinting);
- })
- );
- renderOverlayPage(1, '?show=progress');
- await waitFor(() => {
- // Progress should be visible
- expect(screen.getByText('45%')).toBeInTheDocument();
- // Status text should be hidden when not in show list
- expect(screen.queryByText('Printing')).not.toBeInTheDocument();
- });
- });
- });
- describe('FPS configuration', () => {
- it('uses default FPS of 15 when not specified', async () => {
- renderOverlayPage(1);
- await waitFor(() => {
- const img = screen.getByAltText('Camera stream') as HTMLImageElement;
- expect(img.src).toContain('fps=15');
- });
- });
- it('uses custom FPS when specified in query params', async () => {
- renderOverlayPage(1, '?fps=30');
- await waitFor(() => {
- const img = screen.getByAltText('Camera stream') as HTMLImageElement;
- expect(img.src).toContain('fps=30');
- });
- });
- it('clamps FPS to maximum of 30', async () => {
- renderOverlayPage(1, '?fps=60');
- await waitFor(() => {
- const img = screen.getByAltText('Camera stream') as HTMLImageElement;
- expect(img.src).toContain('fps=30');
- });
- });
- it('clamps FPS to minimum of 1', async () => {
- renderOverlayPage(1, '?fps=0');
- await waitFor(() => {
- const img = screen.getByAltText('Camera stream') as HTMLImageElement;
- expect(img.src).toContain('fps=1');
- });
- });
- it('handles invalid FPS value gracefully', async () => {
- renderOverlayPage(1, '?fps=invalid');
- await waitFor(() => {
- const img = screen.getByAltText('Camera stream') as HTMLImageElement;
- // Should fall back to default of 15
- expect(img.src).toContain('fps=15');
- });
- });
- });
- describe('camera toggle (status-only mode)', () => {
- it('shows camera by default', async () => {
- renderOverlayPage(1);
- await waitFor(() => {
- expect(screen.getByAltText('Camera stream')).toBeInTheDocument();
- });
- });
- it('hides camera when camera=false', async () => {
- renderOverlayPage(1, '?camera=false');
- await waitFor(() => {
- // Status should still be visible
- expect(screen.getByText('Printer is idle')).toBeInTheDocument();
- });
- // Camera should not be rendered
- expect(screen.queryByAltText('Camera stream')).not.toBeInTheDocument();
- });
- it('hides camera when camera=0', async () => {
- renderOverlayPage(1, '?camera=0');
- await waitFor(() => {
- expect(screen.getByText('Printer is idle')).toBeInTheDocument();
- });
- expect(screen.queryByAltText('Camera stream')).not.toBeInTheDocument();
- });
- it('shows camera when camera=true', async () => {
- renderOverlayPage(1, '?camera=true');
- await waitFor(() => {
- expect(screen.getByAltText('Camera stream')).toBeInTheDocument();
- });
- });
- it('shows camera when camera=1', async () => {
- renderOverlayPage(1, '?camera=1');
- await waitFor(() => {
- expect(screen.getByAltText('Camera stream')).toBeInTheDocument();
- });
- });
- });
- describe('combined parameters', () => {
- it('supports fps and camera together', async () => {
- renderOverlayPage(1, '?fps=25&camera=true');
- await waitFor(() => {
- const img = screen.getByAltText('Camera stream') as HTMLImageElement;
- expect(img.src).toContain('fps=25');
- });
- });
- it('supports status-only with custom size', async () => {
- renderOverlayPage(1, '?camera=false&size=large');
- await waitFor(() => {
- expect(screen.getByText('Printer is idle')).toBeInTheDocument();
- });
- expect(screen.queryByAltText('Camera stream')).not.toBeInTheDocument();
- });
- it('supports show parameter with fps', async () => {
- server.use(
- http.get('/api/v1/printers/:id/status', () => {
- return HttpResponse.json(mockStatusPrinting);
- })
- );
- renderOverlayPage(1, '?fps=20&show=progress');
- await waitFor(() => {
- const img = screen.getByAltText('Camera stream') as HTMLImageElement;
- expect(img.src).toContain('fps=20');
- expect(screen.getByText('45%')).toBeInTheDocument();
- });
- });
- });
- describe('offline state', () => {
- beforeEach(() => {
- server.use(
- http.get('/api/v1/printers/:id/status', () => {
- return HttpResponse.json({
- ...mockStatusIdle,
- connected: false,
- });
- })
- );
- });
- it('shows offline message when printer disconnected', async () => {
- renderOverlayPage(1);
- await waitFor(() => {
- expect(screen.getByText('Printer offline')).toBeInTheDocument();
- });
- });
- });
- describe('kiosk token mode (#2613)', () => {
- const mockOverlayPrinting = {
- id: 1,
- name: 'X1 Carbon',
- camera_rotation: 0,
- connected: true,
- state: 'RUNNING',
- current_print: 'KioskBenchy.gcode.3mf',
- gcode_file: 'plate_1.gcode',
- progress: 67,
- remaining_time: 40,
- layer_num: 10,
- total_layers: 20,
- stg_cur_name: null,
- time_format: 'system',
- };
- it('reads the token-authed overlay-status feed and carries the token to the camera', async () => {
- let overlayHit = false;
- server.use(
- http.get('/api/v1/printers/:id/overlay-status', () => {
- overlayHit = true;
- return HttpResponse.json(mockOverlayPrinting);
- })
- );
- renderOverlayPage(1, '?token=obs-tok');
- await waitFor(() => {
- expect(screen.getByText('KioskBenchy')).toBeInTheDocument();
- });
- expect(overlayHit).toBe(true);
- expect(screen.getByText('67%')).toBeInTheDocument();
- // The camera <img> must carry the same kiosk token — a fresh OBS browser
- // has no session to mint a camera stream token from.
- const img = screen.getByAltText('Camera stream') as HTMLImageElement;
- expect(img.src).toContain('token=obs-tok');
- });
- it('does not touch the JWT-only status endpoint or a WebSocket in kiosk mode', async () => {
- let statusHit = false;
- server.use(
- http.get('/api/v1/printers/:id/overlay-status', () => HttpResponse.json(mockOverlayPrinting)),
- http.get('/api/v1/printers/:id/status', () => {
- statusHit = true;
- return HttpResponse.json(mockStatusIdle);
- })
- );
- renderOverlayPage(1, '?token=obs-tok');
- await waitFor(() => {
- expect(screen.getByText('KioskBenchy')).toBeInTheDocument();
- });
- // The logged-in status query is disabled when a token is present, so an
- // unauthenticated OBS browser never fires a doomed 401 (or opens a socket).
- expect(statusHit).toBe(false);
- expect(WebSocket).not.toHaveBeenCalled();
- });
- });
- describe('temperatures (#1422)', () => {
- const withTemps = {
- ...mockStatusPrinting,
- temperatures: {
- nozzle: 219.6,
- nozzle_target: 220,
- bed: 60,
- bed_target: 60,
- chamber: 38.4,
- },
- };
- beforeEach(() => {
- server.use(http.get('/api/v1/printers/:id/status', () => HttpResponse.json(withTemps)));
- });
- it('draws no temperatures unless the URL asks for them', async () => {
- renderOverlayPage(1);
- await waitFor(() => {
- expect(screen.getByText('45%')).toBeInTheDocument();
- });
- // Default ?show= is unchanged by #1422, so overlays already running in an
- // OBS scene look identical after the upgrade.
- expect(screen.queryByText('Nozzle')).not.toBeInTheDocument();
- expect(screen.queryByText('Bed')).not.toBeInTheDocument();
- });
- it('draws only the readings named in ?show=', async () => {
- renderOverlayPage(1, '?show=progress,nozzle');
- await waitFor(() => {
- expect(screen.getByText('Nozzle')).toBeInTheDocument();
- });
- expect(screen.queryByText('Bed')).not.toBeInTheDocument();
- expect(screen.queryByText('Chamber')).not.toBeInTheDocument();
- });
- it('rounds the reading and hides a target it has already reached', async () => {
- renderOverlayPage(1, '?show=nozzle,bed');
- await waitFor(() => {
- expect(screen.getByText('220°C')).toBeInTheDocument();
- });
- // Nozzle is 219.6 against a target of 220: both round to 220, so the
- // "/ 220°C" half is dropped rather than reading "220 / 220°C" all print.
- expect(screen.queryByText('/')).not.toBeInTheDocument();
- expect(screen.getByText('60°C')).toBeInTheDocument();
- });
- it('shows the target while the heater is still climbing', async () => {
- server.use(
- http.get('/api/v1/printers/:id/status', () =>
- HttpResponse.json({ ...withTemps, temperatures: { nozzle: 140, nozzle_target: 220 } }),
- ),
- );
- renderOverlayPage(1, '?show=nozzle');
- await waitFor(() => {
- expect(screen.getByText('140°C')).toBeInTheDocument();
- });
- expect(screen.getByText('220°C')).toBeInTheDocument();
- });
- it('skips a reading the printer does not report', async () => {
- server.use(
- http.get('/api/v1/printers/:id/status', () =>
- // A P1S: the backend drops chamber for models without a real sensor,
- // so asking for it in ?show= must not produce an empty row.
- HttpResponse.json({ ...withTemps, temperatures: { nozzle: 200, bed: 55 } }),
- ),
- );
- renderOverlayPage(1, '?show=nozzle,bed,chamber');
- await waitFor(() => {
- expect(screen.getByText('Nozzle')).toBeInTheDocument();
- });
- expect(screen.queryByText('Chamber')).not.toBeInTheDocument();
- });
- it('draws both nozzles on a dual-nozzle printer', async () => {
- server.use(
- http.get('/api/v1/printers/:id/status', () =>
- HttpResponse.json({
- ...withTemps,
- temperatures: { nozzle: 220, nozzle_2: 250, nozzle_2_target: 250 },
- }),
- ),
- );
- renderOverlayPage(1, '?show=nozzle');
- await waitFor(() => {
- expect(screen.getByText('Nozzle')).toBeInTheDocument();
- });
- expect(screen.getByText('Nozzle 2')).toBeInTheDocument();
- expect(screen.getByText('250°C')).toBeInTheDocument();
- });
- it('draws temperatures while the printer is idle', async () => {
- server.use(
- http.get('/api/v1/printers/:id/status', () =>
- HttpResponse.json({ ...mockStatusIdle, temperatures: { bed: 45, bed_target: 60 } }),
- ),
- );
- renderOverlayPage(1, '?show=bed');
- await waitFor(() => {
- expect(screen.getByText('Printer is idle')).toBeInTheDocument();
- });
- // A preheating printer is exactly when the readings are worth watching,
- // so they are not gated behind a running print.
- expect(screen.getByText('45°C')).toBeInTheDocument();
- expect(screen.getByText('60°C')).toBeInTheDocument();
- });
- it('reads temperatures from the token-authed feed in kiosk mode', async () => {
- server.use(
- http.get('/api/v1/printers/:id/overlay-status', () =>
- HttpResponse.json({
- id: 1,
- name: 'X1 Carbon',
- camera_rotation: 0,
- connected: true,
- state: 'RUNNING',
- current_print: 'KioskBenchy.gcode.3mf',
- gcode_file: 'plate_1.gcode',
- progress: 67,
- remaining_time: 40,
- layer_num: 10,
- total_layers: 20,
- stg_cur_name: null,
- temperatures: { chamber: 38, chamber_target: 40 },
- time_format: 'system',
- }),
- ),
- );
- renderOverlayPage(1, '?token=obs-tok&show=chamber');
- await waitFor(() => {
- expect(screen.getByText('Chamber')).toBeInTheDocument();
- });
- expect(screen.getByText('38°C')).toBeInTheDocument();
- });
- });
- });
|