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
18 changes: 18 additions & 0 deletions apps/desktop/src/main/__tests__/about-settings-page.test.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { createElement } from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { AstryxLocaleProvider, LocaleProvider, ToastProvider } from '@maka/ui';
import { AboutSettingsPage } from '../../renderer/settings/about-settings-page.js';

test('keeps manual diagnostics available while About metadata is pending', () => {
const page = createElement(AboutSettingsPage, {});
const withToasts = createElement(ToastProvider, { children: page });
const withAstryxLocale = createElement(AstryxLocaleProvider, { children: withToasts });
const markup = renderToStaticMarkup(
createElement(LocaleProvider, { locale: 'en', children: withAstryxLocale }),
);

assert.match(markup, />Copy diagnostics</);
assert.match(markup, /role="status"[^>]*aria-busy="true"/);
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { resolveManualDiagnosticTarget } from '../../renderer/app-shell-command-actions.js';

test('targets manual diagnostics to the current task or new-task Host profile', () => {
assert.deepEqual(
resolveManualDiagnosticTarget(
{ navSection: 'sessions', sessionId: '["remote-host","session-1"]' },
'new-task-profile',
),
{ sessionId: '["remote-host","session-1"]' },
);
assert.deepEqual(
resolveManualDiagnosticTarget(
{ navSection: 'sessions', sessionId: undefined },
'new-task-profile',
),
{ profileId: 'new-task-profile' },
);
assert.equal(
resolveManualDiagnosticTarget(
{ navSection: 'extensions', sessionId: undefined },
'new-task-profile',
),
undefined,
);
assert.deepEqual(
resolveManualDiagnosticTarget(
{ navSection: 'sessions', sessionId: '["hidden-host","hidden-session"]' },
'hidden-new-task-profile',
true,
'settings-profile',
),
{ profileId: 'settings-profile' },
);
assert.equal(
resolveManualDiagnosticTarget(
{ navSection: 'sessions', sessionId: '["hidden-host","hidden-session"]' },
'hidden-new-task-profile',
true,
),
undefined,
);
});
131 changes: 112 additions & 19 deletions apps/desktop/src/main/__tests__/app-shell-project-actions.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,33 @@ import { build } from 'esbuild';
import type * as ProjectActions from '../../renderer/app-shell-project-actions.js';

const REPO_ROOT = resolve(import.meta.dirname, '../../../../..');
const NO_PROJECT_CAPABILITIES = {
chooseClientDirectory: false,
chooseHostDirectory: false,
selectNoProject: false,
setLocalDefault: false,
viewClientPath: false,
} as const;

function createTestProjectActions(
actionsModule: typeof ProjectActions,
overrides: Partial<Parameters<typeof ProjectActions.createAppShellProjectActions>[0]> = {},
) {
return actionsModule.createAppShellProjectActions({
uiLocale: 'en',
projectPickerPendingRef: { current: false },
projectPickerRequestRef: { current: 0 },
rendererMountedRef: { current: true },
setProjectPickerPending: () => {},
refreshDefaultProjectState: async () => [],
selectedProjectId: null,
projects: [],
projectCapabilities: NO_PROJECT_CAPABILITIES,
onProjectSelected: () => {},
toastApi: { success: () => {}, error: () => {} },
...overrides,
});
}

test('remote Project capabilities do not dispatch Client-local actions', async () => {
const actionsModule = await importProjectActions();
Expand All@@ -32,25 +59,7 @@ test('remote Project capabilities do not dispatch Client-local actions', async (
} as unknown as Window & typeof globalThis;

try {
const actions = actionsModule.createAppShellProjectActions({
uiLocale: 'en',
projectPickerPendingRef: { current: false },
projectPickerRequestRef: { current: 0 },
rendererMountedRef: { current: true },
setProjectPickerPending: () => {},
refreshDefaultProjectState: async () => [],
selectedProjectId: null,
projects: [],
projectCapabilities: {
chooseClientDirectory: false,
chooseHostDirectory: false,
selectNoProject: false,
setLocalDefault: false,
viewClientPath: false,
},
onProjectSelected: () => {},
toastApi: { success: () => {}, error: () => {} },
});
const actions = createTestProjectActions(actionsModule);

assert.equal(await actions.addProject(), null);
await actions.selectNoProject();
Expand All@@ -61,6 +70,90 @@ test('remote Project capabilities do not dispatch Client-local actions', async (
}
});

test('Project errors preserve the Host authority of the failed operation', async () => {
const actionsModule = await importProjectActions();
const previousWindow = globalThis.window;
const diagnosticTargets: unknown[] = [];
const toastApi = {
success: () => {},
error: (_title: string, _description?: string, _details?: string, target?: unknown) => {
diagnosticTargets.push(target);
},
};
globalThis.window = {
maka: {
runtimeHostProfiles: {
getDefaultHost: async () => ({ profileId: 'default-profile', hostId: 'default-host' }),
},
app: {
openPath: async () => {
throw new Error('unavailable');
},
},
},
} as unknown as Window & typeof globalThis;

try {
const actions = createTestProjectActions(actionsModule, {
sessionId: 'session-key',
toastApi,
});

await actions.openWorkspaceFolder();
await actions.openProjectFolder();
await createTestProjectActions(actionsModule, {
toastApi,
}).openProjectFolder();

assert.deepEqual(diagnosticTargets, [
{ profileId: 'default-profile' },
{ sessionId: 'session-key' },
{ profileId: 'default-profile' },
]);
} finally {
globalThis.window = previousWindow;
}
});

test('an old Host mutation cannot refresh the current default Host presentation', async () => {
const actionsModule = await importProjectActions();
const previousWindow = globalThis.window;
const hosts = [
{ profileId: 'profile-a', hostId: 'host-a' },
{ profileId: 'profile-b', hostId: 'host-b' },
];
let renamedOnHost: unknown;
let refreshCalls = 0;
globalThis.window = {
maka: {
runtimeHostProfiles: {
getDefaultHost: async () => hosts.shift() ?? { profileId: 'profile-b', hostId: 'host-b' },
},
projects: {
rename: async (_projectId: string, _name: string, host: unknown) => {
renamedOnHost = host;
},
},
},
} as unknown as Window & typeof globalThis;

try {
const actions = createTestProjectActions(actionsModule, {
refreshDefaultProjectState: async () => {
refreshCalls += 1;
return [];
},
});

await actions.renameProject('project-1', 'Renamed');

assert.deepEqual(renamedOnHost, { profileId: 'profile-a', hostId: 'host-a' });
assert.equal(refreshCalls, 0);
} finally {
globalThis.window = previousWindow;
}
});

async function importProjectActions(): Promise<typeof ProjectActions> {
const outdir = await mkdtemp(resolve(REPO_ROOT, 'apps/desktop/dist/main/__tests__/project-actions-'));
const outfile = resolve(outdir, 'app-shell-project-actions.mjs');
Expand Down
29 changes: 26 additions & 3 deletions apps/desktop/src/main/__tests__/app-shell-session-purge.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -46,6 +46,7 @@ function installWindow(
harness: SweepHarness,
options: {
rejectIds?: readonly string[];
rejectWithUndefinedIds?: readonly string[];
surviving?: readonly SessionSummary[];
/** Runs after each accepted removal, to model what another client did meanwhile. */
onRemove?: (sessionId: string) => void;
Expand All@@ -67,6 +68,9 @@ function installWindow(
sessions: {
remove: async (id: string, removeOptions?: { requireArchived?: boolean }) => {
harness.removeOptions.push([id, removeOptions?.requireArchived === true]);
if (options.rejectWithUndefinedIds?.includes(id)) {
return Promise.reject(undefined);
}
if (options.rejectIds?.includes(id)) throw new Error(`busy:${id}`);
const target = options.catalog?.find((session) => session.id === id);
if (removeOptions?.requireArchived && target && !target.isArchived) return 'restored';
Expand DownExpand Up@@ -151,7 +155,7 @@ describe('purgeSessions', () => {
remaining: [],
restored: [],
verified: true,
firstError: undefined,
firstFailure: undefined,
});
// Every delete in a sweep carries the archived premise the confirm named.
assert.deepEqual(h.removeOptions, [
Expand DownExpand Up@@ -226,7 +230,7 @@ describe('purgeSessions', () => {
const outcome = await actions.purgeSessions(['first', 'rescued']).finally(restore);

assert.deepEqual(outcome.restored, ['rescued']);
assert.equal(outcome.firstError, undefined);
assert.equal(outcome.firstFailure, undefined);
// A task that is still there keeps its renderer state, including being the
// open one.
assert.deepEqual(h.cleared, ['first']);
Expand DownExpand Up@@ -296,7 +300,26 @@ describe('purgeSessions', () => {
assert.equal(h.listCalls, 1);
assert.deepEqual(outcome.remaining, ['survivor']);
assert.equal(outcome.removed, 1);
assert.equal((outcome.firstError as Error).message, 'busy:committed');
assert.ok(outcome.firstFailure);
assert.equal((outcome.firstFailure.error as Error).message, 'busy:committed');
assert.equal(outcome.firstFailure.sessionId, 'committed');
});

it('retains the first failing Session even when the rejection value is undefined', async () => {
const h = harness();
const sessions = [summary('first'), summary('second')];
const restore = installWindow(h, {
rejectWithUndefinedIds: ['first'],
rejectIds: ['second'],
surviving: sessions,
});
const actions = createActions({ harness: h, sessions, activeIdRef: { current: undefined } });

const outcome = await actions.purgeSessions(['first', 'second']).finally(restore);

assert.ok(outcome.firstFailure);
assert.equal(outcome.firstFailure.sessionId, 'first');
assert.equal(outcome.firstFailure.error, undefined);
});

it('claims nothing when the catalog cannot be read back', async () => {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,6 +50,7 @@ function createHarness(options: {
const permissionCalls: string[] = [];
const thinkingCalls: string[] = [];
const errors: string[] = [];
const errorTargets: Array<{ sessionId: string } | undefined> = [];
const successes: Array<{ title: string; description?: string }> = [];
const newTaskPermissionModes: string[] = [];
const modelResult = deferred<DesktopSessionSummary>();
Expand DownExpand Up@@ -99,7 +100,10 @@ function createHarness(options: {
},
toastApi: {
success: (title, description) => successes.push({ title, description }),
error: (title) => errors.push(title),
error: (title, _description, _details, target) => {
errors.push(title);
errorTargets.push(target);
},
confirm: options.confirm ?? (async () => true),
},
});
Expand All@@ -108,6 +112,7 @@ function createHarness(options: {
actions,
activeIdRef,
errors,
errorTargets,
modelCalls,
modelResult,
newTaskPermissionModes,
Expand DownExpand Up@@ -275,6 +280,7 @@ describe('AppShell session settings actions', () => {
assert.equal(harness.pending.has('session-a'), false);
assert.equal(harness.pendingBySession['session-a'], undefined);
assert.equal(harness.errors.length, 1);
assert.deepEqual(harness.errorTargets, [{ sessionId: 'session-a' }]);

const modelChange = harness.actions.setSessionModel({
llmConnectionSlug: 'e2e',
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
import assert from 'node:assert/strict';
import { afterEach, test } from 'node:test';
import {
defaultRuntimeHostDiagnosticTarget,
runOnDefaultRuntimeHost,
} from '../../renderer/default-runtime-host-operation.js';

afterEach(() => {
delete (globalThis as { window?: unknown }).window;
});

test('binds a default Host operation and its diagnostics to one authoritative identity', async () => {
const host = { profileId: 'profile-b', hostId: 'host-b' };
(globalThis as { window?: unknown }).window = {
maka: {
runtimeHostProfiles: {
getDefaultHost: async () => host,
},
},
};
let operatedOn: unknown;
const error = await runOnDefaultRuntimeHost(async (boundHost) => {
operatedOn = boundHost;
throw new Error('Host B failed');
}).catch((caught: unknown) => caught);

assert.deepEqual(operatedOn, host);
assert.equal(error instanceof Error ? error.message : '', 'Host B failed');
assert.deepEqual(defaultRuntimeHostDiagnosticTarget(error), { profileId: 'profile-b' });
});

test('does not invent Host authority when resolving the default Host fails', async () => {
(globalThis as { window?: unknown }).window = {
maka: {
runtimeHostProfiles: {
getDefaultHost: async () => {
throw new Error('No default Host');
},
},
},
};
const error = await runOnDefaultRuntimeHost(async () => undefined).catch(
(caught: unknown) => caught,
);

assert.equal(defaultRuntimeHostDiagnosticTarget(error), undefined);
});
Loading