/**
* InventoryPage, SpoolLocationFooter, and SettingsPage's sensor overview all
* read one location's live readings under the same query key now
* (['locationHaSensorReadings', locationId], no 'all'/'cardOnly' suffix).
* SettingsPage was missed when the other two were unified and kept its own
* 'all'-suffixed key — this pins that navigating between the two pages in
* the same session reuses the cache instead of firing a second request for
* a location that was just fetched.
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { render, screen, waitFor, cleanup } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { BrowserRouter } from 'react-router-dom';
import { http, HttpResponse } from 'msw';
import { server } from '../mocks/server';
import { AuthProvider } from '../../contexts/AuthContext';
import { ThemeProvider } from '../../contexts/ThemeContext';
import { ToastProvider } from '../../contexts/ToastContext';
import InventoryPageRouter from '../../pages/InventoryPage';
import { SettingsPage } from '../../pages/SettingsPage';
const mockSpool = {
id: 1,
material: 'PLA',
subtype: null,
brand: 'Polymaker',
color_name: 'Red',
rgba: 'FF0000FF',
label_weight: 1000,
core_weight: 250,
weight_used: 100,
slicer_filament: null,
slicer_filament_name: null,
nozzle_temp_min: null,
nozzle_temp_max: null,
note: null,
added_full: null,
last_used: null,
encode_time: null,
tag_uid: null,
tray_uuid: null,
data_origin: null,
tag_type: null,
archived_at: null,
created_at: '2025-01-01T00:00:00Z',
updated_at: '2025-01-01T00:00:00Z',
k_profiles: [],
cost_per_kg: null,
last_scale_weight: null,
last_weighed_at: null,
location_id: 7,
storage_location: 'Drybox 1',
};
const mockSensor = {
id: 1,
location_id: 7,
name: 'Drybox 1 Temperature',
entity_id: 'sensor.drybox_1_temperature',
kind: 'numeric',
device_class: 'temperature',
unit: '°C',
alert_state: null,
alert_above: null,
alert_below: null,
notify_on_alert: false,
show_on_card: true,
sort_order: 0,
last_state: '24.5',
last_changed: '2025-01-01T00:00:00Z',
created_at: '2025-01-01T00:00:00Z',
updated_at: '2025-01-01T00:00:00Z',
};
const mockReading = {
id: 1,
name: 'Drybox 1 Temperature',
entity_id: 'sensor.drybox_1_temperature',
kind: 'numeric',
device_class: 'temperature',
unit: '°C',
state: '24.5',
value: 24.5,
alerting: false,
reachable: true,
alert_state: null,
alert_above: null,
alert_below: null,
last_changed: '2025-01-01T00:00:00Z',
show_on_card: true,
};
// A shared client, mimicking the app's real one: unlike the default test
// client's gcTime: 0, unmounting a page must not evict the cache the next
// page is meant to reuse.
function SharedProviders({ children, client }: { children: React.ReactNode; client: QueryClient }) {
return (
{children}
);
}
describe('location sensor readings — shared cache across pages', () => {
let readingsRequestCount: number;
let client: QueryClient;
beforeEach(() => {
readingsRequestCount = 0;
// staleTime matches App.tsx's real QueryClient — the default of 0 would
// make every remount refetch regardless of whether the keys line up,
// which is not what production does and would make this test pass for
// the wrong reason.
client = new QueryClient({
defaultOptions: { queries: { retry: false, staleTime: 1000 * 60 }, mutations: { retry: false } },
});
server.use(
http.get('/api/v1/inventory/spools', () => HttpResponse.json([mockSpool])),
http.get('/api/v1/location-ha-sensors/', () => HttpResponse.json([mockSensor])),
http.get('/api/v1/inventory/locations', () =>
HttpResponse.json([{ id: 7, name: 'Drybox 1', identifier: null, spool_count: 1, created_at: '', updated_at: '' }])
),
http.get('/api/v1/location-ha-sensors/by-location/7/readings', () => {
readingsRequestCount += 1;
return HttpResponse.json([mockReading]);
}),
);
});
it('does not refetch a location Inventory already loaded when Settings opens next', async () => {
const user = userEvent.setup();
render(
);
await user.click(await screen.findByText('Cards'));
await waitFor(() => expect(screen.getByText('24.50 °C')).toBeInTheDocument());
expect(readingsRequestCount).toBe(1);
cleanup();
render(
);
await user.click(await screen.findByText('Sensors'));
await screen.findByText('sensor.drybox_1_temperature');
// Give a stray refetch a moment to land before asserting its absence —
// if the keys still mismatched, this is where the second request fires.
await new Promise((r) => setTimeout(r, 50));
expect(readingsRequestCount).toBe(1);
});
});