mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 00:12:39 +00:00
fix: show themes the admin installed in the theme picker without opening mail first
This commit is contained in:
@@ -3,6 +3,7 @@ import { act, render } from '@testing-library/react';
|
||||
import { appLifecycleHooks, clearAllHooks, routerHooks } from '@/lib/plugin-hooks';
|
||||
import { usePluginStore } from '@/stores/plugin-store';
|
||||
import { useAuthStore } from '@/stores/auth-store';
|
||||
import { useThemeStore } from '@/stores/theme-store';
|
||||
|
||||
let pathname = '/';
|
||||
vi.mock('next/navigation', () => ({ usePathname: () => pathname }));
|
||||
@@ -19,6 +20,7 @@ beforeEach(() => {
|
||||
clearAllHooks();
|
||||
pathname = '/';
|
||||
usePluginStore.setState({ initialized: false, initializePlugins: vi.fn().mockResolvedValue(undefined) });
|
||||
useThemeStore.setState({ syncServerThemes: vi.fn().mockResolvedValue(undefined) });
|
||||
useAuthStore.setState({ isAuthenticated: false });
|
||||
});
|
||||
afterEach(() => {
|
||||
@@ -83,6 +85,18 @@ describe('app events for plugins', () => {
|
||||
expect(usePluginStore.getState().initializePlugins).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
// A theme the admin installed used to arrive only once the mail view had
|
||||
// been opened, so Settings opened directly never listed it.
|
||||
it('syncs the server themes once the user is signed in, on any page', () => {
|
||||
pathname = '/settings';
|
||||
render(<PluginAppEvents />);
|
||||
expect(useThemeStore.getState().syncServerThemes).not.toHaveBeenCalled();
|
||||
|
||||
act(() => { useAuthStore.setState({ isAuthenticated: true }); });
|
||||
|
||||
expect(useThemeStore.getState().syncServerThemes).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('reports navigation between pages', () => {
|
||||
const navigate = listen(routerHooks.onNavigate);
|
||||
const { rerender } = render(<PluginAppEvents />);
|
||||
|
||||
@@ -130,7 +130,6 @@ import { useDocumentTitle } from "@/hooks/use-document-title";
|
||||
import { mailTitleContext } from "@/lib/tab-title";
|
||||
import { usePluginStore } from "@/stores/plugin-store";
|
||||
import { AppTopBannerSlot } from "@/components/plugins/app-top-banner-slot";
|
||||
import { useThemeStore } from "@/stores/theme-store";
|
||||
import { consumePendingMailto, subscribeToPendingMailto } from "@/lib/protocol-handlers/session";
|
||||
import { INTERNAL_MAILTO_EVENT, type ParsedMailto } from "@/lib/protocol-handlers/mailto";
|
||||
import { PLUGIN_REPLY_EVENT, type PluginReplyRequest } from "@/lib/plugin-sandbox/host-api-compose";
|
||||
@@ -1339,10 +1338,10 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
|
||||
}, [checkAuth]);
|
||||
|
||||
// Initialize plugins on mount (re-activates enabled plugins after refresh)
|
||||
// Also syncs server-managed plugins and themes to the client
|
||||
// Also syncs server-managed plugins to the client; themes sync from
|
||||
// PluginAppEvents on every page.
|
||||
useEffect(() => {
|
||||
usePluginStore.getState().initializePlugins();
|
||||
useThemeStore.getState().syncServerThemes();
|
||||
}, []);
|
||||
|
||||
// Hydrate persisted column widths from localStorage
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useEffect, useRef } from 'react';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { appLifecycleHooks, routerHooks, toastHooks, uiHooks } from '@/lib/plugin-hooks';
|
||||
import { usePluginStore } from '@/stores/plugin-store';
|
||||
import { useThemeStore } from '@/stores/theme-store';
|
||||
import { useAuthStore } from '@/stores/auth-store';
|
||||
import { useAccountStore } from '@/stores/account-store';
|
||||
|
||||
@@ -126,8 +127,12 @@ export function PluginAppEvents() {
|
||||
// Start the plugins on whichever page the user lands on. They used to start
|
||||
// only from the mail view, so a plugin's sidebar entry, page or reminders
|
||||
// were missing on Calendar, Contacts, Files or Settings opened directly.
|
||||
// Server themes had the same problem: one installed by the admin never
|
||||
// reached the theme picker until the mail view had been opened.
|
||||
useEffect(() => {
|
||||
if (signedIn) void usePluginStore.getState().initializePlugins();
|
||||
if (!signedIn) return;
|
||||
void usePluginStore.getState().initializePlugins();
|
||||
void useThemeStore.getState().syncServerThemes();
|
||||
}, [signedIn]);
|
||||
|
||||
const readyFired = useRef(false);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { act, fireEvent, render, screen } from '@testing-library/react';
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { ThemesSettings } from '../themes-settings';
|
||||
import { useThemeStore } from '@/stores/theme-store';
|
||||
|
||||
@@ -16,7 +16,16 @@ function cardNames(): string[] {
|
||||
|
||||
describe('ThemesSettings collapse', () => {
|
||||
beforeEach(() => {
|
||||
act(() => { useThemeStore.setState({ activeThemeId: null }); });
|
||||
act(() => {
|
||||
useThemeStore.setState({ activeThemeId: null, syncServerThemes: vi.fn().mockResolvedValue(undefined) });
|
||||
});
|
||||
});
|
||||
|
||||
// Without this, a theme the admin installed in the same tab stayed out of
|
||||
// the gallery until a reload.
|
||||
it('fetches the server themes when it opens', () => {
|
||||
render(<ThemesSettings embedded />);
|
||||
expect(useThemeStore.getState().syncServerThemes).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('shows three cards and a Show all button', () => {
|
||||
|
||||
@@ -25,6 +25,12 @@ export function ThemesSettings({ embedded }: { embedded?: boolean } = {}) {
|
||||
const themePolicy = getThemePolicy();
|
||||
const forcedThemeId = getForcedThemeId(installedThemes.map((theme) => theme.id));
|
||||
|
||||
// Fetch again on open, so a theme the admin just installed in this tab
|
||||
// shows up without a reload.
|
||||
useEffect(() => {
|
||||
void useThemeStore.getState().syncServerThemes();
|
||||
}, []);
|
||||
|
||||
// Render each preview in the variant matching the app's current mode, and
|
||||
// keep it in sync when the user toggles light/dark elsewhere.
|
||||
const [isDark, setIsDark] = useState(false);
|
||||
|
||||
Reference in New Issue
Block a user