test(desktop): move node tests to vitest as well

This commit is contained in:
ethernet 2026-07-10 12:04:39 -04:00
parent 74c69dc8ed
commit 955c5b73c5
37 changed files with 267 additions and 206 deletions

View file

@ -1,5 +1,6 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import { test } from 'vitest'
import { dashboardFallbackArgs, serveBackendArgs, sourceDeclaresServe } from './backend-command'

View file

@ -1,6 +1,7 @@
import assert from 'node:assert/strict'
import path from 'node:path'
import test from 'node:test'
import { test } from 'vitest'
import {
appendUniquePathEntries,

View file

@ -9,7 +9,8 @@ import assert from 'node:assert/strict'
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import test from 'node:test'
import { test } from 'vitest'
import { canImportHermesCli, hermesRuntimeImportProbe, verifyHermesCli } from './backend-probes'

View file

@ -16,7 +16,8 @@ import { EventEmitter } from 'node:events'
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import test from 'node:test'
import { test } from 'vitest'
import {
DEFAULT_PORT_ANNOUNCE_TIMEOUT_MS,

View file

@ -1,5 +1,6 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import { test } from 'vitest'
import {
bundledRuntimeImportCheck,

View file

@ -2,7 +2,8 @@ import assert from 'node:assert/strict'
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import test from 'node:test'
import { test } from 'vitest'
import {
buildPinArgs,

View file

@ -11,7 +11,8 @@
*/
import assert from 'node:assert/strict'
import test from 'node:test'
import { test } from 'vitest'
import {
AT_COOKIE_VARIANTS,

View file

@ -6,7 +6,8 @@
*/
import assert from 'node:assert/strict'
import test from 'node:test'
import { test } from 'vitest'
import {
adoptServedDashboardToken,

View file

@ -10,7 +10,8 @@
*/
import assert from 'node:assert/strict'
import test from 'node:test'
import { test } from 'vitest'
import {
buildPosixCleanupScript,

View file

@ -2,9 +2,10 @@ import assert from 'node:assert/strict'
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import test from 'node:test'
import { pathToFileURL } from 'node:url'
import { test } from 'vitest'
import { readDirForIpc } from './fs-read-dir'
function mkTmpDir() {

View file

@ -10,7 +10,8 @@
*/
import assert from 'node:assert/strict'
import test from 'node:test'
import { test } from 'vitest'
import { probeGatewayWebSocket } from './gateway-ws-probe'

View file

@ -1,5 +1,6 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import { test } from 'vitest'
import { resolveRenamePath } from './git-review-ops'

View file

@ -2,9 +2,10 @@ import assert from 'node:assert/strict'
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import test from 'node:test'
import { pathToFileURL } from 'node:url'
import { test } from 'vitest'
import { gitRootForIpc } from './git-root'
function mkTmpDir() {
@ -19,20 +20,23 @@ test('gitRootForIpc returns null for invalid and device paths', async () => {
assert.equal(await gitRootForIpc('file:///%E0%A4%A'), null)
})
test('gitRootForIpc resolves directories files missing descendants and file URLs', async t => {
test('gitRootForIpc resolves directories files missing descendants and file URLs', async () => {
const root = mkTmpDir()
t.after(() => fs.rmSync(root, { recursive: true, force: true }))
const gitDir = path.join(root, '.git')
const srcDir = path.join(root, 'src')
const filePath = path.join(srcDir, 'index.ts')
fs.mkdirSync(gitDir)
fs.mkdirSync(srcDir)
fs.writeFileSync(filePath, 'export {}\n', 'utf8')
try {
const gitDir = path.join(root, '.git')
const srcDir = path.join(root, 'src')
const filePath = path.join(srcDir, 'index.ts')
fs.mkdirSync(gitDir)
fs.mkdirSync(srcDir)
fs.writeFileSync(filePath, 'export {}\n', 'utf8')
assert.equal(await gitRootForIpc(root), root)
assert.equal(await gitRootForIpc(srcDir), root)
assert.equal(await gitRootForIpc(filePath), root)
assert.equal(await gitRootForIpc(pathToFileURL(filePath).toString()), root)
assert.equal(await gitRootForIpc(path.join(srcDir, 'missing.ts')), root)
assert.equal(await gitRootForIpc(root), root)
assert.equal(await gitRootForIpc(srcDir), root)
assert.equal(await gitRootForIpc(filePath), root)
assert.equal(await gitRootForIpc(pathToFileURL(filePath).toString()), root)
assert.equal(await gitRootForIpc(path.join(srcDir, 'missing.ts')), root)
} finally {
fs.rmSync(root, { recursive: true, force: true })
}
})

View file

@ -3,7 +3,8 @@ import { execFileSync } from 'node:child_process'
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import test from 'node:test'
import { test } from 'vitest'
import {
addWorktree,

View file

@ -2,9 +2,10 @@ import assert from 'node:assert/strict'
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import test from 'node:test'
import { pathToFileURL } from 'node:url'
import { test } from 'vitest'
import {
DEFAULT_FETCH_TIMEOUT_MS,
encryptDesktopSecret,
@ -122,171 +123,184 @@ test('resolveRequestedPathForIpc expands ~ to the home directory', () => {
)
})
test('resolveReadableFileForIpc validates existence type size and sensitivity', async t => {
test('resolveReadableFileForIpc validates existence type size and sensitivity', async () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'hermes-desktop-hardening-'))
t.after(() => fs.rmSync(tempDir, { recursive: true, force: true }))
const textPath = path.join(tempDir, 'notes.txt')
fs.writeFileSync(textPath, 'hello world', 'utf8')
try {
const textPath = path.join(tempDir, 'notes.txt')
fs.writeFileSync(textPath, 'hello world', 'utf8')
const fromRelative = await resolveReadableFileForIpc('notes.txt', {
baseDir: tempDir,
maxBytes: 256,
purpose: 'File preview'
})
assert.equal(fromRelative.resolvedPath, textPath)
assert.equal(fromRelative.stat.size, 11)
const fromFileUrl = await resolveReadableFileForIpc(pathToFileURL(textPath).toString(), {
purpose: 'File preview'
})
assert.equal(fromFileUrl.resolvedPath, textPath)
const spacedPath = path.join(tempDir, 'notes with spaces.txt')
fs.writeFileSync(spacedPath, 'space ok', 'utf8')
const fromSpacedFileUrl = await resolveReadableFileForIpc(pathToFileURL(spacedPath).toString(), {
purpose: 'File preview'
})
assert.equal(fromSpacedFileUrl.resolvedPath, spacedPath)
await assert.rejects(
resolveReadableFileForIpc('missing.txt', {
const fromRelative = await resolveReadableFileForIpc('notes.txt', {
baseDir: tempDir,
purpose: 'Text preview'
}),
/file does not exist/
)
const nestedDir = path.join(tempDir, 'directory')
fs.mkdirSync(nestedDir)
await assert.rejects(
resolveReadableFileForIpc(nestedDir, {
purpose: 'Text preview'
}),
/path points to a directory/
)
const largePath = path.join(tempDir, 'large.txt')
fs.writeFileSync(largePath, 'x'.repeat(40), 'utf8')
await assert.rejects(
resolveReadableFileForIpc(largePath, {
maxBytes: 8,
maxBytes: 256,
purpose: 'File preview'
}),
/file is too large/
)
})
const envPath = path.join(tempDir, '.env')
fs.writeFileSync(envPath, 'SECRET_TOKEN=123', 'utf8')
await assert.rejects(
resolveReadableFileForIpc(envPath, {
assert.equal(fromRelative.resolvedPath, textPath)
assert.equal(fromRelative.stat.size, 11)
const fromFileUrl = await resolveReadableFileForIpc(pathToFileURL(textPath).toString(), {
purpose: 'File preview'
}),
/blocked for sensitive file/
)
})
const envTemplatePath = path.join(tempDir, '.env.example')
fs.writeFileSync(envTemplatePath, 'EXAMPLE_TOKEN=value', 'utf8')
assert.equal(fromFileUrl.resolvedPath, textPath)
const envTemplate = await resolveReadableFileForIpc(envTemplatePath, {
purpose: 'File preview'
})
const spacedPath = path.join(tempDir, 'notes with spaces.txt')
fs.writeFileSync(spacedPath, 'space ok', 'utf8')
assert.equal(envTemplate.resolvedPath, envTemplatePath)
const fromSpacedFileUrl = await resolveReadableFileForIpc(pathToFileURL(spacedPath).toString(), {
purpose: 'File preview'
})
assert.equal(fromSpacedFileUrl.resolvedPath, spacedPath)
await assert.rejects(
resolveReadableFileForIpc('missing.txt', {
baseDir: tempDir,
purpose: 'Text preview'
}),
/file does not exist/
)
const nestedDir = path.join(tempDir, 'directory')
fs.mkdirSync(nestedDir)
await assert.rejects(
resolveReadableFileForIpc(nestedDir, {
purpose: 'Text preview'
}),
/path points to a directory/
)
const largePath = path.join(tempDir, 'large.txt')
fs.writeFileSync(largePath, 'x'.repeat(40), 'utf8')
await assert.rejects(
resolveReadableFileForIpc(largePath, {
maxBytes: 8,
purpose: 'File preview'
}),
/file is too large/
)
const envPath = path.join(tempDir, '.env')
fs.writeFileSync(envPath, 'SECRET_TOKEN=123', 'utf8')
await assert.rejects(
resolveReadableFileForIpc(envPath, {
purpose: 'File preview'
}),
/blocked for sensitive file/
)
const envTemplatePath = path.join(tempDir, '.env.example')
fs.writeFileSync(envTemplatePath, 'EXAMPLE_TOKEN=value', 'utf8')
const envTemplate = await resolveReadableFileForIpc(envTemplatePath, {
purpose: 'File preview'
})
assert.equal(envTemplate.resolvedPath, envTemplatePath)
} finally {
fs.rmSync(tempDir, { recursive: true, force: true })
}
})
test('resolveReadableFileForIpc blocks common sensitive files', async t => {
test('resolveReadableFileForIpc blocks common sensitive files', async () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'hermes-desktop-sensitive-'))
t.after(() => fs.rmSync(tempDir, { recursive: true, force: true }))
const sshDir = path.join(tempDir, '.ssh')
fs.mkdirSync(sshDir)
try {
const sshDir = path.join(tempDir, '.ssh')
fs.mkdirSync(sshDir)
const blockedFiles = [
path.join(tempDir, '.env'),
path.join(tempDir, '.npmrc'),
path.join(sshDir, 'id_ed25519'),
path.join(tempDir, 'cert.pem'),
path.join(tempDir, 'cert.p12'),
path.join(tempDir, 'cert.pfx')
]
const blockedFiles = [
path.join(tempDir, '.env'),
path.join(tempDir, '.npmrc'),
path.join(sshDir, 'id_ed25519'),
path.join(tempDir, 'cert.pem'),
path.join(tempDir, 'cert.p12'),
path.join(tempDir, 'cert.pfx')
]
for (const filePath of blockedFiles) {
fs.writeFileSync(filePath, 'secret', 'utf8')
await rejectsWithCode(resolveReadableFileForIpc(filePath, { purpose: 'File preview' }), 'sensitive-file')
for (const filePath of blockedFiles) {
fs.writeFileSync(filePath, 'secret', 'utf8')
await rejectsWithCode(resolveReadableFileForIpc(filePath, { purpose: 'File preview' }), 'sensitive-file')
}
const allowed = path.join(tempDir, '.env.example')
fs.writeFileSync(allowed, 'EXAMPLE_TOKEN=value', 'utf8')
assert.equal((await resolveReadableFileForIpc(allowed, { purpose: 'File preview' })).resolvedPath, allowed)
} finally {
fs.rmSync(tempDir, { recursive: true, force: true })
}
const allowed = path.join(tempDir, '.env.example')
fs.writeFileSync(allowed, 'EXAMPLE_TOKEN=value', 'utf8')
assert.equal((await resolveReadableFileForIpc(allowed, { purpose: 'File preview' })).resolvedPath, allowed)
})
test('resolveReadableFileForIpc blocks symlinks whose realpath is sensitive', async t => {
test('resolveReadableFileForIpc blocks symlinks whose realpath is sensitive', async () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'hermes-desktop-realpath-'))
t.after(() => fs.rmSync(tempDir, { recursive: true, force: true }))
const envPath = path.join(tempDir, '.env')
const linkPath = path.join(tempDir, 'safe-name.txt')
fs.writeFileSync(envPath, 'SECRET_TOKEN=123', 'utf8')
try {
fs.symlinkSync(envPath, linkPath, 'file')
} catch (error) {
if (error?.code === 'EPERM' || error?.code === 'EACCES') {
t.skip(`symlink creation is not permitted on this platform (${error.code})`)
const envPath = path.join(tempDir, '.env')
const linkPath = path.join(tempDir, 'safe-name.txt')
fs.writeFileSync(envPath, 'SECRET_TOKEN=123', 'utf8')
return
try {
fs.symlinkSync(envPath, linkPath, 'file')
} catch (error) {
if (error?.code === 'EPERM' || error?.code === 'EACCES') {
// symlink creation is not permitted on this platform — skip
return
}
throw error
}
throw error
await rejectsWithCode(resolveReadableFileForIpc(linkPath, { purpose: 'File preview' }), 'sensitive-file')
} finally {
fs.rmSync(tempDir, { recursive: true, force: true })
}
await rejectsWithCode(resolveReadableFileForIpc(linkPath, { purpose: 'File preview' }), 'sensitive-file')
})
test('resolveDirectoryForIpc accepts directories and rejects invalid directory targets', async t => {
test('resolveDirectoryForIpc accepts directories and rejects invalid directory targets', async () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'hermes-desktop-dir-'))
t.after(() => fs.rmSync(tempDir, { recursive: true, force: true }))
const directory = path.join(tempDir, 'project')
const filePath = path.join(tempDir, 'file.txt')
fs.mkdirSync(directory)
fs.writeFileSync(filePath, 'not a directory', 'utf8')
const resolved = await resolveDirectoryForIpc(directory)
assert.equal(resolved.resolvedPath, directory)
assert.equal(resolved.stat.isDirectory(), true)
await rejectsWithCode(resolveDirectoryForIpc(filePath), 'ENOTDIR')
await rejectsWithCode(resolveDirectoryForIpc(path.join(tempDir, 'missing')), 'ENOENT')
await rejectsWithCode(resolveDirectoryForIpc('\\\\?\\C:\\secret'), 'device-path')
})
test('resolveDirectoryForIpc accepts directory symlinks or junctions', async t => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'hermes-desktop-dir-link-'))
t.after(() => fs.rmSync(tempDir, { recursive: true, force: true }))
const directory = path.join(tempDir, 'actual-project')
const linkPath = path.join(tempDir, 'linked-project')
fs.mkdirSync(directory)
try {
fs.symlinkSync(directory, linkPath, process.platform === 'win32' ? 'junction' : 'dir')
} catch (error) {
if (error?.code === 'EPERM' || error?.code === 'EACCES') {
t.skip(`directory symlink creation is not permitted on this platform (${error.code})`)
const directory = path.join(tempDir, 'project')
const filePath = path.join(tempDir, 'file.txt')
fs.mkdirSync(directory)
fs.writeFileSync(filePath, 'not a directory', 'utf8')
return
const resolved = await resolveDirectoryForIpc(directory)
assert.equal(resolved.resolvedPath, directory)
assert.equal(resolved.stat.isDirectory(), true)
await rejectsWithCode(resolveDirectoryForIpc(filePath), 'ENOTDIR')
await rejectsWithCode(resolveDirectoryForIpc(path.join(tempDir, 'missing')), 'ENOENT')
await rejectsWithCode(resolveDirectoryForIpc('\\\\?\\C:\\secret'), 'device-path')
} finally {
fs.rmSync(tempDir, { recursive: true, force: true })
}
})
test('resolveDirectoryForIpc accepts directory symlinks or junctions', async () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'hermes-desktop-dir-link-'))
try {
const directory = path.join(tempDir, 'actual-project')
const linkPath = path.join(tempDir, 'linked-project')
fs.mkdirSync(directory)
try {
fs.symlinkSync(directory, linkPath, process.platform === 'win32' ? 'junction' : 'dir')
} catch (error) {
if (error?.code === 'EPERM' || error?.code === 'EACCES') {
// directory symlink creation is not permitted on this platform — skip
return
}
throw error
}
throw error
const resolved = await resolveDirectoryForIpc(linkPath)
assert.equal(resolved.resolvedPath, linkPath)
assert.equal(resolved.stat.isDirectory(), true)
} finally {
fs.rmSync(tempDir, { recursive: true, force: true })
}
const resolved = await resolveDirectoryForIpc(linkPath)
assert.equal(resolved.resolvedPath, linkPath)
assert.equal(resolved.stat.isDirectory(), true)
})

View file

@ -1,5 +1,6 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import { test } from 'vitest'
import {
createLinkTitleWindow,

View file

@ -5,7 +5,8 @@
*/
import assert from 'node:assert/strict'
import test from 'node:test'
import { test } from 'vitest'
import { serializeJsonBody, setJsonRequestHeaders } from './oauth-net-request'

View file

@ -1,5 +1,6 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import { test } from 'vitest'
import { decideProfileDeleteAction, profileNameFromDeleteRequest, resolveRouteProfile } from './profile-delete-routing'

View file

@ -1,5 +1,6 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import { test } from 'vitest'
import { buildSessionWindowUrl, chatWindowWebPreferences, createSessionWindowRegistry } from './session-windows'

View file

@ -1,5 +1,6 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import { test } from 'vitest'
import {
MACOS_TAHOE_DARWIN_MAJOR,

View file

@ -1,5 +1,6 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import { test } from 'vitest'
import { resolveBehindCount, shouldCountCommits } from './update-count'

View file

@ -14,10 +14,11 @@
import fs from 'fs'
import assert from 'node:assert/strict'
import test from 'node:test'
import os from 'os'
import path from 'path'
import { test } from 'vitest'
import {
isPidAlive,
markerPath,

View file

@ -13,7 +13,8 @@
*/
import assert from 'node:assert/strict'
import test from 'node:test'
import { test } from 'vitest'
import { runRebuildWithRetry, shouldRetryRebuild } from './update-rebuild'

View file

@ -22,7 +22,8 @@ import { execFileSync } from 'node:child_process'
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import test from 'node:test'
import { test } from 'vitest'
import {
buildRelaunchScript,

View file

@ -16,7 +16,8 @@
*/
import assert from 'node:assert/strict'
import test from 'node:test'
import { test } from 'vitest'
import {
canonicalGitHubRemote,

View file

@ -1,5 +1,6 @@
import assert from 'node:assert'
import test from 'node:test'
import { test } from 'vitest'
import { __testing, extractThemes, readCentralDirectory } from './vscode-marketplace'

View file

@ -5,7 +5,8 @@
*/
import assert from 'node:assert/strict'
import test from 'node:test'
import { test, vi } from 'vitest'
import {
computeWindowOptions,
@ -118,8 +119,8 @@ test('computeWindowOptions does not clamp when displays are unknown', () => {
// ─── debounce ──────────────────────────────────────────────────────────────
test('debounce coalesces a burst into one trailing run', t => {
t.mock.timers.enable({ apis: ['setTimeout'] })
test('debounce coalesces a burst into one trailing run', () => {
vi.useFakeTimers()
let calls = 0
const d = debounce(() => {
@ -130,14 +131,16 @@ test('debounce coalesces a burst into one trailing run', t => {
d()
d()
assert.equal(calls, 0)
t.mock.timers.tick(249)
vi.advanceTimersByTime(249)
assert.equal(calls, 0)
t.mock.timers.tick(1)
vi.advanceTimersByTime(1)
assert.equal(calls, 1)
vi.useRealTimers()
})
test('debounce.flush runs now and cancels the pending timer', t => {
t.mock.timers.enable({ apis: ['setTimeout'] })
test('debounce.flush runs now and cancels the pending timer', () => {
vi.useFakeTimers()
let calls = 0
const d = debounce(() => {
@ -147,6 +150,8 @@ test('debounce.flush runs now and cancels the pending timer', t => {
d()
d.flush()
assert.equal(calls, 1)
t.mock.timers.tick(1000)
vi.advanceTimersByTime(1000)
assert.equal(calls, 1)
vi.useRealTimers()
})

View file

@ -1,5 +1,6 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import { test } from 'vitest'
import { stopBackendChild } from './backend-child'
import { hiddenWindowsChildOptions } from './windows-child-options'

View file

@ -1,7 +1,7 @@
import assert from 'node:assert/strict'
import fs from 'node:fs'
import path from 'node:path'
import test from 'node:test'
import { test } from 'vitest'
import { fileURLToPath } from 'node:url'
const ELECTRON_DIR = path.dirname(fileURLToPath(import.meta.url))

View file

@ -13,7 +13,7 @@
// re-selected forever instead of falling through to bootstrap.
import assert from 'node:assert/strict'
import test from 'node:test'
import { test } from 'vitest'
import { buildPathExtCandidates, chooseUpdaterArgs, resolveVenvHermesCommand } from './windows-hermes-path'

View file

@ -1,5 +1,6 @@
import assert from 'node:assert/strict'
import { test } from 'node:test'
import { test } from 'vitest'
import { expandWindowsEnvRefs, parseRegQueryValue, readWindowsUserEnvVar } from './windows-user-env'

View file

@ -6,7 +6,8 @@
import assert from 'node:assert/strict'
import path from 'node:path'
import test from 'node:test'
import { test } from 'vitest'
import { isPackagedInstallPath } from './workspace-cwd'

View file

@ -1,5 +1,6 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import { test } from 'vitest'
import {
decodeClipboardImageBase64,

View file

@ -1,5 +1,5 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import { test } from 'vitest'
import { parseDefaultDistro, resolvePickerDefaultPath, wslPosixToWindowsAccessible } from './wsl-path-bridge'

View file

@ -7,7 +7,7 @@
import assert from 'node:assert/strict'
import fs from 'node:fs'
import path from 'node:path'
import test from 'node:test'
import { test } from 'vitest'
import { fileURLToPath } from 'node:url'
import {

View file

@ -38,15 +38,16 @@
"test:desktop:nsis": "node scripts/test-desktop.mjs nsis",
"test:desktop:existing": "node scripts/test-desktop.mjs existing",
"test:desktop:fresh": "node scripts/test-desktop.mjs fresh",
"test:desktop:platforms": "node --test electron/bootstrap-platform.test.ts electron/hardening.test.ts electron/backend-env.test.ts electron/backend-probes.test.ts electron/backend-ready.test.ts electron/bootstrap-runner.test.ts electron/connection-config.test.ts electron/dashboard-token.test.ts electron/gateway-ws-probe.test.ts electron/oauth-net-request.test.ts electron/desktop-uninstall.test.ts electron/session-windows.test.ts electron/profile-delete-routing.test.ts electron/link-title-window.test.ts electron/workspace-cwd.test.ts electron/fs-read-dir.test.ts electron/git-root.test.ts electron/git-worktree-ops.test.ts electron/windows-child-options.test.ts electron/update-remote.test.ts electron/update-count.test.ts electron/update-rebuild.test.ts electron/update-marker.test.ts electron/update-relaunch.test.ts electron/windows-user-env.test.ts electron/wsl-clipboard-image.test.ts electron/titlebar-overlay-width.test.ts electron/window-state.test.ts electron/zoom.test.ts electron/windows-hermes-path.test.ts",
"typecheck": "tsc -p . --noEmit && tsc -p tsconfig.electron.json --noEmit",
"lint": "eslint src/ electron/",
"lint:fix": "eslint src/ electron/ --fix",
"fmt": "prettier --write 'src/**/*.{ts,tsx}' 'electron/**/*.ts' 'vite.config.ts'",
"fix": "npm run lint:fix && npm run fmt",
"test:ui": "vitest run",
"check": "npm run typecheck && npm run test:ui && npm run test:desktop:all && npm run build"
"test:ui": "vitest run --project ui",
"test:desktop:platforms": "vitest run --project electron",
"test": "vitest run",
"preview": "node scripts/assert-root-install.mjs && vite preview --host 127.0.0.1 --port 4174",
"check": "npm run typecheck && npm run test && npm run test:desktop:all && npm run build"
},
"dependencies": {
"@assistant-ui/react": "^0.12.28",

View file

@ -1,19 +1,28 @@
import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";
import path from "path";
import { defineConfig, TestProjectConfiguration } from 'vitest/config'
import react from '@vitejs/plugin-react'
import path from 'path'
const reactUi: TestProjectConfiguration = {
extends: './vite.config.ts',
test: {
name: 'ui',
environment: 'jsdom',
setupFiles: ['./vitest.setup.ts'],
include: ['src/**/*.test.{ts,tsx}'],
globals: true
}
}
const electronNative: TestProjectConfiguration = {
test: {
name: 'electron',
environment: 'node',
include: ['electron/**/*.test.ts']
}
}
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
test: {
environment: "jsdom",
setupFiles: ["./vitest.setup.ts"],
include: ["src/**/*.test.{ts,tsx}"],
globals: true
},
});
projects: [reactUi, electronNative]
}
})