mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-03 00:13:09 +00:00
feat: keep the Pro skeleton over a standard page until the Pro workspace takes it over
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { cleanup, render } from '@testing-library/react';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
// The Pro shell takes over the standard routes and hands their deep link to
|
||||
@@ -11,7 +11,9 @@ vi.mock('@/i18n/navigation', () => ({
|
||||
usePathname: () => nav.pathname,
|
||||
useRouter: () => ({ replace: nav.replace }),
|
||||
}));
|
||||
vi.mock('@/hooks/use-media-query', () => ({ useIsDesktop: () => true }));
|
||||
const device = vi.hoisted(() => ({ desktop: true }));
|
||||
vi.mock('@/hooks/use-media-query', () => ({ useIsDesktop: () => device.desktop }));
|
||||
vi.mock('next-intl', () => ({ useTranslations: () => (key: string) => key }));
|
||||
vi.mock('@/stores/settings-store', () => ({
|
||||
useSettingsStore: (selector: (s: { proInterface: boolean }) => unknown) => selector({ proInterface: true }),
|
||||
}));
|
||||
@@ -25,6 +27,7 @@ const { ProInterfaceRedirect } = await import('../pro-interface-redirect');
|
||||
describe('ProInterfaceRedirect', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
device.desktop = true;
|
||||
});
|
||||
|
||||
function visit(path: string, search: string) {
|
||||
@@ -56,4 +59,24 @@ describe('ProInterfaceRedirect', () => {
|
||||
expect(setPendingDeepLink).not.toHaveBeenCalled();
|
||||
expect(openTab).toHaveBeenLastCalledWith('files');
|
||||
});
|
||||
|
||||
it('covers the standard page with the Pro skeleton until /pro renders', () => {
|
||||
visit('/calendar/week/2026-11-04', '');
|
||||
const cover = document.querySelector('[data-pro-takeover]');
|
||||
expect(cover?.querySelector('[data-boot-skeleton="pro"]')).not.toBeNull();
|
||||
expect(cover?.className).toContain('fixed');
|
||||
});
|
||||
|
||||
it('draws nothing on /pro itself, below desktop width, or on other pages', () => {
|
||||
visit('/pro', '');
|
||||
expect(document.querySelector('[data-pro-takeover]')).toBeNull();
|
||||
cleanup();
|
||||
visit('/login', '');
|
||||
expect(document.querySelector('[data-pro-takeover]')).toBeNull();
|
||||
cleanup();
|
||||
device.desktop = false;
|
||||
visit('/mail', '');
|
||||
expect(document.querySelector('[data-pro-takeover]')).toBeNull();
|
||||
expect(nav.replace).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,6 +7,8 @@ import { useIsDesktop } from "@/hooks/use-media-query";
|
||||
import { useProTabStore, type ProAppTabKind } from "@/stores/pro-tab-store";
|
||||
import { matchSurface, type AppSurface } from "@/lib/deep-links";
|
||||
import { setPendingDeepLink } from "@/lib/deep-link-handoff";
|
||||
import { ProSkeleton } from "@/components/boot-skeleton/pro-skeleton";
|
||||
import { useSkeletonLabels } from "@/components/boot-skeleton/use-skeleton-labels";
|
||||
|
||||
const SURFACE_TO_TAB: Record<AppSurface, ProAppTabKind> = {
|
||||
mail: 'mail',
|
||||
@@ -37,12 +39,21 @@ export function useProInterfaceActive(): boolean {
|
||||
* segments are handed to the tab that opens (#733), so `/mail/message/<id>`
|
||||
* opens that message in a Pro mail tab instead of silently landing on the
|
||||
* inbox.
|
||||
*
|
||||
* Until /pro has rendered, the Pro skeleton covers the standard page, which
|
||||
* may finish loading first (a fast session restore, a slow /pro chunk): the
|
||||
* visitor sees the Pro frame from the first paint to the loaded Pro shell,
|
||||
* never the standard page in between. The server HTML and the hydration pass
|
||||
* draw nothing here (isDesktop is false there); the page's own skeleton is
|
||||
* already the Pro frame then (components/boot-skeleton/shell.tsx).
|
||||
*/
|
||||
export function ProInterfaceRedirect() {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const proInterface = useSettingsStore((s) => s.proInterface);
|
||||
const isDesktop = useIsDesktop();
|
||||
const { label } = useSkeletonLabels();
|
||||
const takingOver = proInterface && isDesktop && matchSurface(pathname) !== null;
|
||||
|
||||
useEffect(() => {
|
||||
if (!proInterface || !isDesktop) return;
|
||||
@@ -63,5 +74,10 @@ export function ProInterfaceRedirect() {
|
||||
router.replace('/pro');
|
||||
}, [proInterface, isDesktop, pathname, router]);
|
||||
|
||||
return null;
|
||||
if (!takingOver) return null;
|
||||
return (
|
||||
<div className="fixed inset-0 z-50" data-pro-takeover="">
|
||||
<ProSkeleton label={label} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user