mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-09-30 03:01:21 +02:00
The dispatch progress toast is a fixed 420px wide and the toast viewport is anchored 80px from the right (to clear the bug-report bubble). On a 390px-wide phone that overflows the left edge by ~110px, so in the Home-Screen PWA the toast was clipped off the left, with text bleeding past the edge. Cap every toast to a viewport-relative max-width (calc(100vw - 6rem - safe-area insets)) so it can't exceed the screen; desktop keeps the 420px. Make the viewport position safe-area-aware (env(safe-area-inset-*) on bottom/right) so an installed PWA clears the home indicator and a landscape notch, and add min-w-0/shrink-0 to the per-job filename row so long names truncate instead of widening the toast at the narrower phone width. Frontend-only; no backend, schema, or i18n change. Covered by a test pinning the width cap; the suppression test's viewport lookup moved to a stable data-testid.
123 lines
4.0 KiB
TypeScript
123 lines
4.0 KiB
TypeScript
/**
|
|
* Tests for SpoolBuddyLayout component:
|
|
* - Renders without crashing
|
|
*/
|
|
|
|
import { describe, it, expect, vi } from 'vitest';
|
|
import { render } from '@testing-library/react';
|
|
import React from 'react';
|
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
import { MemoryRouter, Route, Routes } from 'react-router-dom';
|
|
import { SpoolBuddyLayout } from '../../../components/spoolbuddy/SpoolBuddyLayout';
|
|
import { ToastProvider } from '../../../contexts/ToastContext';
|
|
|
|
vi.mock('react-i18next', () => ({
|
|
useTranslation: () => ({
|
|
t: (_key: string, fallback: string) => fallback,
|
|
i18n: { language: 'en', changeLanguage: vi.fn() },
|
|
}),
|
|
}));
|
|
|
|
vi.mock('../../../api/client', () => ({
|
|
api: {
|
|
getPrinters: vi.fn().mockResolvedValue([]),
|
|
getPrinterStatus: vi.fn().mockResolvedValue({ connected: false }),
|
|
getSettings: vi.fn().mockResolvedValue({ time_format: 'system', language: 'en' }),
|
|
},
|
|
spoolbuddyApi: {
|
|
getDevices: vi.fn().mockResolvedValue([]),
|
|
},
|
|
}));
|
|
|
|
vi.mock('../../../utils/date', () => ({
|
|
formatTimeOnly: () => '12:00',
|
|
}));
|
|
|
|
vi.mock('lucide-react', () => {
|
|
const Stub = (props: Record<string, unknown>) => <span {...props} />;
|
|
return {
|
|
WifiOff: (props: Record<string, unknown>) => <span data-testid="wifi-off" {...props} />,
|
|
// ToastProvider, brought in by SpoolBuddyLayout's useToast(), imports these.
|
|
AlertCircle: Stub,
|
|
CheckCircle: Stub,
|
|
ChevronDown: Stub,
|
|
ChevronUp: Stub,
|
|
Info: Stub,
|
|
Loader2: Stub,
|
|
X: Stub,
|
|
XCircle: Stub,
|
|
};
|
|
});
|
|
|
|
vi.mock('../../../components/VirtualKeyboard', () => ({
|
|
VirtualKeyboard: () => <div data-testid="virtual-keyboard" />,
|
|
}));
|
|
|
|
function renderLayout() {
|
|
const qc = new QueryClient({ defaultOptions: { queries: { retry: false, gcTime: 0 } } });
|
|
return render(
|
|
<ToastProvider>
|
|
<QueryClientProvider client={qc}>
|
|
<MemoryRouter initialEntries={['/spoolbuddy']}>
|
|
<Routes>
|
|
<Route path="spoolbuddy" element={<SpoolBuddyLayout />}>
|
|
<Route index element={<div data-testid="child-page">Child</div>} />
|
|
</Route>
|
|
</Routes>
|
|
</MemoryRouter>
|
|
</QueryClientProvider>
|
|
</ToastProvider>
|
|
);
|
|
}
|
|
|
|
describe('SpoolBuddyLayout', () => {
|
|
it('renders without crashing', () => {
|
|
const { container } = renderLayout();
|
|
expect(container.firstChild).not.toBeNull();
|
|
});
|
|
|
|
it('renders the top bar with logo', () => {
|
|
renderLayout();
|
|
const img = document.querySelector('img[alt="SpoolBuddy"]');
|
|
expect(img).not.toBeNull();
|
|
});
|
|
|
|
it('renders the bottom nav', () => {
|
|
renderLayout();
|
|
const nav = document.querySelector('nav');
|
|
expect(nav).not.toBeNull();
|
|
});
|
|
|
|
it('renders the status bar', () => {
|
|
renderLayout();
|
|
// Status bar shows "System Ready" by default (device offline triggers warning later via useEffect)
|
|
// Just check the status bar container exists
|
|
const statusBar = document.querySelector('.shrink-0.h-9');
|
|
expect(statusBar).not.toBeNull();
|
|
});
|
|
|
|
it('renders child outlet content', () => {
|
|
renderLayout();
|
|
const child = document.querySelector('[data-testid="child-page"]');
|
|
expect(child).not.toBeNull();
|
|
});
|
|
|
|
it('suppresses the global toast viewport while mounted', () => {
|
|
const { unmount } = renderLayout();
|
|
// Visible viewport gets `hidden` class while the kiosk is up. Position is
|
|
// set via safe-area calc() (#2612) so match the stable data-testid.
|
|
const viewport = document.querySelector('[data-testid="toast-viewport"]');
|
|
expect(viewport?.className).toContain('hidden');
|
|
|
|
// Cleanup restores the viewport when the kiosk unmounts (e.g. user
|
|
// navigates back to the main app).
|
|
unmount();
|
|
const viewportAfter = document.querySelector('[data-testid="toast-viewport"]');
|
|
// After unmount the toast container is gone with the provider; the
|
|
// important guarantee is the suppression flag was untoggled, which the
|
|
// ToastContext tests pin directly. Here we only assert no crash on
|
|
// unmount during cleanup.
|
|
expect(viewportAfter).toBeNull();
|
|
});
|
|
});
|