Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 56 additions & 2 deletions app/src/utils/tauriCommands/core.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { invoke, isTauri } from '@tauri-apps/api/core';
import { beforeEach, describe, expect, type Mock, test, vi } from 'vitest';
import { afterEach, beforeEach, describe, expect, type Mock, test, vi } from 'vitest';

vi.mock('@tauri-apps/api/core', () => ({ invoke: vi.fn(), isTauri: vi.fn() }));
vi.mock('../../services/coreRpcClient', () => ({ callCoreRpc: vi.fn() }));
Expand All @@ -19,22 +19,76 @@ describe('tauriCommands/core', () => {
});

describe('restartApp', () => {
// window.location.reload is non-configurable on jsdom's default location;
// swap in a mocked location object for the dev-mode tests and restore after.
let originalLocation: Location;
let reloadSpy: Mock;

beforeEach(() => {
originalLocation = window.location;
reloadSpy = vi.fn();
Object.defineProperty(window, 'location', {
value: { ...originalLocation, reload: reloadSpy },
configurable: true,
writable: true,
});
});

afterEach(() => {
Object.defineProperty(window, 'location', {
value: originalLocation,
configurable: true,
writable: true,
});
vi.unstubAllEnvs();
});

test('no-ops when not in Tauri', async () => {
mockIsTauri.mockReturnValue(false);
const debug = vi.spyOn(console, 'debug').mockImplementation(() => {});

await restartApp();

expect(mockInvoke).not.toHaveBeenCalled();
expect(reloadSpy).not.toHaveBeenCalled();
expect(debug).toHaveBeenCalledWith(
expect.stringContaining('restartApp: skipped — not running in Tauri')
);
debug.mockRestore();
});

test('invokes restart_app when in Tauri', async () => {
test('reloads webview in dev mode (#1068 — avoid orphaning vite)', async () => {
// setup.ts seeds DEV=true globally; the binding imported above already
// captured that value, so we just need to invoke the dev-mode branch.
const debug = vi.spyOn(console, 'debug').mockImplementation(() => {});

await restartApp();

expect(reloadSpy).toHaveBeenCalledTimes(1);
expect(mockInvoke).not.toHaveBeenCalled();
expect(debug).toHaveBeenCalledWith(
expect.stringContaining('restartApp: dev mode → window.location.reload()')
);
debug.mockRestore();
});

test('invokes restart_app in production mode (IS_DEV=false)', async () => {
// setup.ts globally mocks ../config with IS_DEV: true. Override with
// doMock + resetModules so a fresh import of ./core sees IS_DEV=false
// and runs the production branch (#1068 dev workaround should be inert).
vi.doMock('../config', () => ({
IS_DEV: false,
// Re-export anything else core.ts might end up using; today just IS_DEV.
}));
vi.resetModules();
const prodCore = await import('./core');

await prodCore.restartApp();

expect(mockInvoke).toHaveBeenCalledWith('restart_app');
expect(reloadSpy).not.toHaveBeenCalled();

vi.doUnmock('../config');
});
});

Expand Down
17 changes: 17 additions & 0 deletions app/src/utils/tauriCommands/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import { invoke } from '@tauri-apps/api/core';

import { callCoreRpc } from '../../services/coreRpcClient';
import { IS_DEV } from '../config';
import { CommandResponse, isTauri } from './common';

export interface CoreUpdateStatus {
Expand Down Expand Up @@ -61,12 +62,28 @@ export async function restartCoreProcess(): Promise<void> {

/**
* Restart the desktop shell so CEF relaunches with updated profile paths.
*
* In `pnpm dev:app` the launcher graph is:
* `pnpm tauri dev` → `cargo run` → `tauri-cef` CLI → `vite` (child).
* Tauri's `app.restart()` exits the cargo parent, which orphans/kills the
* vite child and tears down the entire dev session (#1068). Use a webview
* reload in dev mode instead — module init re-runs, so localStorage seeds
* (e.g. `OPENHUMAN_ACTIVE_USER_ID`, set by `setActiveUserId` before the
* caller invokes us) are read fresh and redux-persist re-hydrates from
* the active user's namespace, all without touching the cargo / vite
* processes. Packaged builds keep the original `app.restart()` path —
* there is no vite child to orphan there.
*/
export async function restartApp(): Promise<void> {
if (!isTauri()) {
console.debug('[app] restartApp: skipped — not running in Tauri');
return;
}
if (IS_DEV) {
console.debug('[app] restartApp: dev mode → window.location.reload()');
window.location.reload();
return;
}
console.debug('[app] restartApp: invoking restart_app');
await invoke<void>('restart_app');
}
Expand Down
Loading