From f08e228a7de8d342b0490189ec5611cad76f3793 Mon Sep 17 00:00:00 2001 From: infiniti <52129260+developerInfiniti@users.noreply.github.com> Date: Sat, 16 May 2026 18:03:05 +0300 Subject: [PATCH] fix: address Windows smoke review findings (#122) Co-authored-by: iliya --- scripts/electron-builder/smokePackagedApp.cjs | 11 ++- .../DirectoryTree/buildDirectoryTree.ts | 4 +- .../utils/recentProjectOpenHistory.test.ts | 17 ++++- test/main/build/smokePackagedApp.test.ts | 69 +++++++++++++++++++ .../buildDirectoryTree.test.ts | 6 ++ 5 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 test/main/build/smokePackagedApp.test.ts diff --git a/scripts/electron-builder/smokePackagedApp.cjs b/scripts/electron-builder/smokePackagedApp.cjs index 9892a072..2c9d5d5f 100644 --- a/scripts/electron-builder/smokePackagedApp.cjs +++ b/scripts/electron-builder/smokePackagedApp.cjs @@ -173,4 +173,13 @@ async function main() { fail(`Timed out after ${STARTUP_TIMEOUT_MS}ms waiting for packaged startup`, log); } -main().catch((error) => fail(error?.stack || String(error))); +if (require.main === module) { + main().catch((error) => fail(error?.stack || String(error))); +} + +module.exports = { + _internal: { + terminateChild, + waitForProcessClose, + }, +}; diff --git a/src/renderer/components/chat/SessionContextPanel/DirectoryTree/buildDirectoryTree.ts b/src/renderer/components/chat/SessionContextPanel/DirectoryTree/buildDirectoryTree.ts index 9bb7f723..8a0bc95e 100644 --- a/src/renderer/components/chat/SessionContextPanel/DirectoryTree/buildDirectoryTree.ts +++ b/src/renderer/components/chat/SessionContextPanel/DirectoryTree/buildDirectoryTree.ts @@ -12,13 +12,13 @@ import type { ClaudeMdContextInjection } from '@renderer/types/contextInjection' */ export function buildDirectoryTree( injections: ClaudeMdContextInjection[], - projectRoot: string + projectRoot?: string ): TreeNode { const root: TreeNode = { name: '', path: '', isFile: false, children: new Map() }; for (const injection of injections) { const relativePath = projectRoot - ? getRelativePathWithinPrefix(projectRoot, injection.path) ?? injection.path + ? (getRelativePathWithinPrefix(projectRoot, injection.path) ?? injection.path) : injection.path; const parts = relativePath.split(/[\\/]/); diff --git a/test/features/recent-projects/renderer/utils/recentProjectOpenHistory.test.ts b/test/features/recent-projects/renderer/utils/recentProjectOpenHistory.test.ts index 98ddb38f..67463c96 100644 --- a/test/features/recent-projects/renderer/utils/recentProjectOpenHistory.test.ts +++ b/test/features/recent-projects/renderer/utils/recentProjectOpenHistory.test.ts @@ -169,6 +169,21 @@ describe('recentProjectOpenHistory', () => { it('ignores blank project paths without throwing', () => { expect(() => recordRecentProjectOpenPaths([' '], 10_000)).not.toThrow(); - expect(getRecentProjectLastOpenedAt(makeProject({ primaryPath: ' ', associatedPaths: [' '] }))).toBe(0); + expect( + getRecentProjectLastOpenedAt(makeProject({ primaryPath: ' ', associatedPaths: [' '] })) + ).toBe(0); + }); + + it('ignores paths that normalize to an empty history key', () => { + recordRecentProjectOpenPaths(['///'], 10_000); + + expect( + getRecentProjectLastOpenedAt( + makeProject({ + primaryPath: '/', + associatedPaths: ['/'], + }) + ) + ).toBe(0); }); }); diff --git a/test/main/build/smokePackagedApp.test.ts b/test/main/build/smokePackagedApp.test.ts new file mode 100644 index 00000000..7bfb34a6 --- /dev/null +++ b/test/main/build/smokePackagedApp.test.ts @@ -0,0 +1,69 @@ +// @vitest-environment node +import { createRequire } from 'node:module'; + +import { describe, expect, it, vi } from 'vitest'; + +interface SmokePackagedAppInternals { + terminateChild( + child: { exitCode: number | null; signalCode: string | null; kill: () => void }, + exitPromise: Promise, + platform: string + ): Promise; + waitForProcessClose( + child: { exitCode: number | null; signalCode: string | null }, + exitPromise: Promise, + timeoutMs: number + ): Promise; +} + +interface SmokePackagedAppModule { + default?: { _internal?: SmokePackagedAppInternals }; + _internal?: SmokePackagedAppInternals; +} + +const requireFromTest: (id: string) => unknown = createRequire(import.meta.url); +const smokePackagedApp = requireFromTest( + '../../../scripts/electron-builder/smokePackagedApp.cjs' +) as SmokePackagedAppModule; +const smokePackagedAppInternals = + smokePackagedApp._internal ?? smokePackagedApp.default?._internal; +if (!smokePackagedAppInternals) { + throw new Error('smokePackagedApp internals were not exported'); +} +const { terminateChild, waitForProcessClose } = smokePackagedAppInternals; + +describe('smokePackagedApp shutdown handling', () => { + it('reports successful process closure before the timeout', async () => { + let resolveExit!: (value: unknown) => void; + const exitPromise = new Promise((resolve) => { + resolveExit = resolve; + }); + const child = { exitCode: null, signalCode: null }; + + const closed = waitForProcessClose(child, exitPromise, 1_000); + resolveExit({ code: 0, signal: null }); + + await expect(closed).resolves.toBe(true); + }); + + it('reports shutdown timeout instead of treating it as success', async () => { + vi.useFakeTimers(); + try { + const exitPromise = new Promise(() => undefined); + const child = { + exitCode: null, + signalCode: null, + kill: vi.fn(), + }; + + const termination = terminateChild(child, exitPromise, 'linux'); + const rejection = expect(termination).rejects.toThrow('Timed out after 5000ms'); + await vi.advanceTimersByTimeAsync(5_000); + + await rejection; + expect(child.kill).toHaveBeenCalledTimes(1); + } finally { + vi.useRealTimers(); + } + }); +}); diff --git a/test/renderer/components/chat/SessionContextPanel/buildDirectoryTree.test.ts b/test/renderer/components/chat/SessionContextPanel/buildDirectoryTree.test.ts index bd7e1205..3a867958 100644 --- a/test/renderer/components/chat/SessionContextPanel/buildDirectoryTree.test.ts +++ b/test/renderer/components/chat/SessionContextPanel/buildDirectoryTree.test.ts @@ -44,4 +44,10 @@ describe('buildDirectoryTree Windows paths', () => { expect(root.children.get('C:')?.children.get('Users')).toBeDefined(); }); + + it('falls back to absolute injection paths when project root is unavailable', () => { + const root = buildDirectoryTree([injection('C:\\Users\\Alice\\Repo\\CLAUDE.md')]); + + expect(root.children.get('C:')?.children.get('Users')).toBeDefined(); + }); });