|
|
@@ -33,8 +33,21 @@ const mockPrinter = {
|
|
|
enabled: true,
|
|
|
};
|
|
|
|
|
|
+/** As a window.open popup: something opened it, so it can close itself. */
|
|
|
+function asPopup() {
|
|
|
+ Object.defineProperty(window, 'opener', { value: {}, configurable: true, writable: true });
|
|
|
+}
|
|
|
+
|
|
|
+/** As a web app started from the iOS home screen. */
|
|
|
+function asWebApp(): () => void {
|
|
|
+ Object.defineProperty(navigator, 'standalone', { value: true, configurable: true });
|
|
|
+ return () => {
|
|
|
+ delete (navigator as Navigator & { standalone?: boolean }).standalone;
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
// Custom render for CameraPage which needs specific route params
|
|
|
-function renderCameraPage(printerId: number, search = '', strict = false) {
|
|
|
+function renderCameraPage(printerId: number, search = '', strict = false, entries?: string[]) {
|
|
|
const queryClient = new QueryClient({
|
|
|
defaultOptions: {
|
|
|
queries: { retry: false, gcTime: 0 },
|
|
|
@@ -45,12 +58,14 @@ function renderCameraPage(printerId: number, search = '', strict = false) {
|
|
|
const tree = (
|
|
|
<QueryClientProvider client={queryClient}>
|
|
|
<I18nextProvider i18n={i18n}>
|
|
|
- <MemoryRouter initialEntries={[`/cameras/${printerId}${search}`]}>
|
|
|
+ <MemoryRouter initialEntries={entries ?? [`/cameras/${printerId}${search}`]}>
|
|
|
<AuthProvider>
|
|
|
<ThemeProvider>
|
|
|
<ToastProvider>
|
|
|
<Routes>
|
|
|
<Route path="/cameras/:printerId" element={<CameraPage />} />
|
|
|
+ <Route path="/" element={<p>Bambuddy home</p>} />
|
|
|
+ <Route path="/printers" element={<p>printers page</p>} />
|
|
|
</Routes>
|
|
|
</ToastProvider>
|
|
|
</ThemeProvider>
|
|
|
@@ -88,6 +103,7 @@ describe('CameraPage', () => {
|
|
|
|
|
|
afterEach(() => {
|
|
|
document.title = originalTitle;
|
|
|
+ Object.defineProperty(window, 'opener', { value: null, configurable: true, writable: true });
|
|
|
});
|
|
|
|
|
|
describe('rendering', () => {
|
|
|
@@ -456,6 +472,7 @@ describe('CameraPage', () => {
|
|
|
});
|
|
|
|
|
|
it('moves into the open tab as an overlay and closes the window', async () => {
|
|
|
+ asPopup();
|
|
|
const open = vi.fn();
|
|
|
const stop = listenForCameraPopIn('t1', open);
|
|
|
const close = vi.spyOn(window, 'close').mockImplementation(() => {});
|
|
|
@@ -515,6 +532,83 @@ describe('CameraPage', () => {
|
|
|
});
|
|
|
});
|
|
|
|
|
|
+ describe('a page no window opened (#3419)', () => {
|
|
|
+ afterEach(() => {
|
|
|
+ sessionStorage.clear();
|
|
|
+ });
|
|
|
+
|
|
|
+ it('offers a way back to Bambuddy', async () => {
|
|
|
+ renderCameraPage(1);
|
|
|
+ await screen.findByText('X1 Carbon');
|
|
|
+
|
|
|
+ fireEvent.click(screen.getByRole('button', { name: 'Back' }));
|
|
|
+
|
|
|
+ expect(await screen.findByText('Bambuddy home')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+
|
|
|
+ it('goes back instead of closing itself once the camera moved to a tab', async () => {
|
|
|
+ const open = vi.fn();
|
|
|
+ const stop = listenForCameraPopIn('t1', open);
|
|
|
+ const close = vi.spyOn(window, 'close').mockImplementation(() => {});
|
|
|
+ try {
|
|
|
+ renderCameraPage(1);
|
|
|
+ await screen.findByText('X1 Carbon');
|
|
|
+
|
|
|
+ fireEvent.click(screen.getByRole('button', { name: 'Show as an overlay in Bambuddy' }));
|
|
|
+
|
|
|
+ expect(await screen.findByText('Bambuddy home')).toBeInTheDocument();
|
|
|
+ expect(open).toHaveBeenCalledExactlyOnceWith(1, 'X1 Carbon');
|
|
|
+ expect(close).not.toHaveBeenCalled();
|
|
|
+ } finally {
|
|
|
+ stop();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ it('goes back to the Bambuddy page it came from', async () => {
|
|
|
+ renderCameraPage(1, '', false, ['/printers', '/cameras/1']);
|
|
|
+ await screen.findByText('X1 Carbon');
|
|
|
+
|
|
|
+ fireEvent.click(screen.getByRole('button', { name: 'Back' }));
|
|
|
+
|
|
|
+ expect(await screen.findByText('printers page')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+
|
|
|
+ it('a popup whose tab has gone still closes rather than navigating', async () => {
|
|
|
+ window.name = 'camera-1';
|
|
|
+ try {
|
|
|
+ renderCameraPage(1);
|
|
|
+ await screen.findByText('X1 Carbon');
|
|
|
+
|
|
|
+ expect(screen.queryByRole('button', { name: 'Back' })).toBeNull();
|
|
|
+ } finally {
|
|
|
+ window.name = '';
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ it('a popup has no back button: it closes', async () => {
|
|
|
+ asPopup();
|
|
|
+ renderCameraPage(1);
|
|
|
+ await screen.findByText('X1 Carbon');
|
|
|
+
|
|
|
+ expect(screen.queryByRole('button', { name: 'Back' })).toBeNull();
|
|
|
+ });
|
|
|
+
|
|
|
+ it('in a home-screen web app, has a way back and no tab to pop into', async () => {
|
|
|
+ // iOS may report an opener for the page it loaded into the app itself.
|
|
|
+ asPopup();
|
|
|
+ const restore = asWebApp();
|
|
|
+ try {
|
|
|
+ renderCameraPage(1);
|
|
|
+ await screen.findByText('X1 Carbon');
|
|
|
+
|
|
|
+ expect(screen.getByRole('button', { name: 'Back' })).toBeInTheDocument();
|
|
|
+ expect(screen.queryByRole('button', { name: 'Show as an overlay in Bambuddy' })).toBeNull();
|
|
|
+ } finally {
|
|
|
+ restore();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
describe('invalid printer', () => {
|
|
|
it('shows invalid printer message for ID 0', async () => {
|
|
|
renderCameraPage(0);
|