mirror of
https://github.com/stablyai/orca.git
synced 2026-11-14 01:47:24 +00:00
test: remove low-value localization and mobile test coverage
This commit is contained in:
@@ -94,11 +94,7 @@
|
||||
"config/scripts/diagnostics/lag-capture/lag-probe-failures.test.mjs": 55,
|
||||
"config/scripts/dependency-security/runtime-resolution/linear-sdk-runtime.test.mjs": 773,
|
||||
"config/scripts/preflight/changed-checks/lint-staged-worktree-backup.test.mjs": 278,
|
||||
"config/scripts/locale-catalog/locale-brand-prefilter.test.mjs": 246,
|
||||
"config/scripts/locale-catalog/locale-generic-ui-terms.test.mjs": 102,
|
||||
"config/scripts/locale-catalog/locale-repair-catalog-missing-leaves.test.mjs": 159,
|
||||
"config/scripts/locale-catalog/locale-translation-policy.es-pr.test.mjs": 91,
|
||||
"config/scripts/locale-catalog/locale-translation-policy.test.mjs": 189,
|
||||
"config/scripts/localization/source-extraction/localization-extraction-call-syntax.test.mjs": 317,
|
||||
"config/scripts/localization/extraction-policy/localization-extraction-change-scope.test.mjs": 732,
|
||||
"config/scripts/performance/macos-computer-owner/macos-computer-helper-owner-loss-processes.test.mjs": 52,
|
||||
@@ -130,7 +126,6 @@
|
||||
"config/scripts/mobile-web-app/shell/mobile-web-app-render.test.mjs": 254,
|
||||
"config/scripts/mobile-web-app/session/mobile-web-app-rich-markdown-render.test.mjs": 356,
|
||||
"config/scripts/mobile-web-app/mobile-web-app-route-manifest.test.mjs": 174,
|
||||
"config/scripts/mobile-web-app/mobile-web-app-route-snapshot.test.mjs": 121,
|
||||
"config/scripts/mobile-web-app/shell/mobile-web-app-safe-area-render.test.mjs": 314,
|
||||
"config/scripts/mobile-web-app/session/mobile-web-app-screencast-lane-grant.test.mjs": 70,
|
||||
"config/scripts/mobile-web-app/session/mobile-web-app-session-dictation-capture.test.mjs": 123,
|
||||
@@ -230,7 +225,6 @@
|
||||
"config/scripts/tool-invocation/script-entry-detection.test.mjs": 107,
|
||||
"config/scripts/test-fixtures/script-copy-closure/script-module-dependencies.test.mjs": 62,
|
||||
"config/scripts/ci/workflow-contracts/setup-pnpm-action.test.mjs": 421,
|
||||
"config/scripts/electron-installation/shared-dist-cache/shared-electron-dist-cache.test.ts": 140,
|
||||
"config/scripts/native-build/node-addons/shared-native-build.test.mjs": 127,
|
||||
"config/scripts/artifact-storage/atomic-publication/shared-tree-publish.test.mjs": 63,
|
||||
"config/scripts/desktop-distribution/macos/sign-mac-app.test.mjs": 268,
|
||||
@@ -246,8 +240,6 @@
|
||||
"config/scripts/desktop-distribution/channel-policy/verify-dev-channel-packaging.test.mjs": 213,
|
||||
"config/scripts/native-build/linux/verify-linux-glibc-floor.test.mjs": 156,
|
||||
"config/scripts/localization/catalog-validation/verify-localization-catalog.test.mjs": 422,
|
||||
"config/scripts/localization/catalog-validation/verify-localization-catalogs.test.mjs": 1574,
|
||||
"config/scripts/localization/source-extraction/verify-localization-extraction.test.mjs": 233,
|
||||
"config/scripts/desktop-distribution/runtime-verification/verify-packaged-mobile-web-bundle.test.mjs": 214,
|
||||
"config/scripts/desktop-distribution/runtime-verification/verify-packaged-node-pty-job-ownership.test.mjs": 93,
|
||||
"config/scripts/orcad-distribution/template/verify-packaged-orcad-template.test.mjs": 454,
|
||||
|
||||
@@ -1,45 +1,20 @@
|
||||
import { rmSync } from 'node:fs'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { sanitizeStressText } from './windows-pty-table-stress-observer.mjs'
|
||||
|
||||
const directories = []
|
||||
afterEach(() => {
|
||||
for (const directory of directories.splice(0)) {
|
||||
rmSync(directory, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
describe('Windows PTY stress observer', () => {
|
||||
it('preserves OSC boundaries while redacting their title payload and adjacent CSI text', () => {
|
||||
it('redacts a key interrupted by adjacent controls without moving their bytes', () => {
|
||||
const esc = String.fromCharCode(27)
|
||||
const raw = `${esc}]0;private@sensitive.test${esc}\\${esc}[31mprivate@sensitive.test`
|
||||
const before = 'sk-secret01'
|
||||
const after = '234567890abcdefghijkl'
|
||||
const controls = `${esc}[31m${esc}[1m`
|
||||
const raw = `${before}${controls}${after}`
|
||||
const sanitized = sanitizeStressText(raw)
|
||||
expect(sanitized).not.toContain('private@')
|
||||
expect(sanitized).not.toContain('sensitive.test')
|
||||
expect(sanitized).toContain(`${esc}]0;`)
|
||||
expect(sanitized).toContain(`${esc}\\${esc}[31m`)
|
||||
expect(sanitized).not.toContain(before)
|
||||
expect(sanitized).not.toContain(after)
|
||||
expect(sanitized.slice(before.length, before.length + controls.length)).toBe(controls)
|
||||
expect(sanitized.length).toBe(raw.length)
|
||||
})
|
||||
|
||||
it.each([
|
||||
['email domain', 'private@', 'sensitive.test', ['[31m']],
|
||||
['email name', 'pri', 'vate@sensitive.test', ['[31m', '[1m']],
|
||||
['vendor key', 'sk-secret01', '234567890abcdefghijkl', ['[31m', '[1m']],
|
||||
['bearer token', 'Bearer secret01', '234567890abcdefghijkl', ['[31m', '[1m']]
|
||||
])(
|
||||
'redacts %s interrupted by adjacent controls without moving their bytes',
|
||||
(_kind, before, after, fragments) => {
|
||||
const esc = String.fromCharCode(27)
|
||||
const controls = fragments.map((fragment) => esc + fragment).join('')
|
||||
const raw = `${before}${controls}${after}`
|
||||
const sanitized = sanitizeStressText(raw)
|
||||
expect(sanitized).not.toContain(before)
|
||||
expect(sanitized).not.toContain(after)
|
||||
expect(sanitized.slice(before.length, before.length + controls.length)).toBe(controls)
|
||||
expect(sanitized.length).toBe(raw.length)
|
||||
}
|
||||
)
|
||||
|
||||
it('scrubs an interrupted OSC title independently from the adjacent rendered address', () => {
|
||||
const esc = String.fromCharCode(27)
|
||||
const title = `private@${esc}[31msensitive.test`
|
||||
|
||||
+3
-112
@@ -1,19 +1,10 @@
|
||||
import {
|
||||
existsSync,
|
||||
lstatSync,
|
||||
mkdirSync,
|
||||
readdirSync,
|
||||
readFileSync,
|
||||
rmSync,
|
||||
writeFileSync
|
||||
} from 'node:fs'
|
||||
import { existsSync, lstatSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
addSiblingWorktree,
|
||||
initGitRepo,
|
||||
mkTempProject,
|
||||
readExtractorCallCount,
|
||||
readSharedDistMarker,
|
||||
runInstallScript,
|
||||
sharedCacheRoot,
|
||||
@@ -27,7 +18,7 @@ import {
|
||||
} from './install-electron-package-binary-test-fixtures.mjs'
|
||||
|
||||
describe('install-electron-package-binary', () => {
|
||||
it('installs Electron from an isolated cache and repairs path.txt', () => {
|
||||
it('installs the distribution and repairs path.txt', () => {
|
||||
const projectDir = mkTempProject()
|
||||
|
||||
try {
|
||||
@@ -43,10 +34,6 @@ describe('install-electron-package-binary', () => {
|
||||
const result = runInstallScript(projectDir)
|
||||
|
||||
expect(result.status, result.stderr).toBe(0)
|
||||
expect(readFileSync(join(projectDir, 'electron-get.log'), 'utf8')).toMatch(
|
||||
/cacheRoot=.*orca-electron-.*cache/
|
||||
)
|
||||
expect(readFileSync(join(projectDir, 'electron-get.log'), 'utf8')).toContain('force=true')
|
||||
expect(readFileSync(join(projectDir, 'node_modules', 'electron', 'path.txt'), 'utf8')).toBe(
|
||||
'electron'
|
||||
)
|
||||
@@ -61,31 +48,6 @@ describe('install-electron-package-binary', () => {
|
||||
).isSymbolicLink()
|
||||
).toBe(true)
|
||||
}
|
||||
expect(result.stdout).toContain('Repaired Electron path.txt -> electron')
|
||||
} finally {
|
||||
rmSync(projectDir, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('preserves an existing Electron distribution when replacement download fails', () => {
|
||||
const projectDir = mkTempProject()
|
||||
|
||||
try {
|
||||
writeFakeElectronPackage(projectDir)
|
||||
writeFakeElectronGet(projectDir, { downloadFailures: 1, downloadErrorCode: 'EACCES' })
|
||||
writeFakeElectronDist(projectDir, {
|
||||
version: 'v40.0.0',
|
||||
executableContents: 'existing executable',
|
||||
pathContents: 'electron'
|
||||
})
|
||||
|
||||
const result = runInstallScript(projectDir)
|
||||
const electronDir = join(projectDir, 'node_modules/electron')
|
||||
|
||||
expect(result.status).toBe(1)
|
||||
expect(readFileSync(join(electronDir, 'dist/version'), 'utf8')).toBe('v40.0.0')
|
||||
expect(readFileSync(join(electronDir, 'dist/electron'), 'utf8')).toBe('existing executable')
|
||||
expect(readFileSync(join(electronDir, 'path.txt'), 'utf8')).toBe('electron')
|
||||
} finally {
|
||||
rmSync(projectDir, { recursive: true, force: true })
|
||||
}
|
||||
@@ -124,36 +86,6 @@ describe('install-electron-package-binary', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('keeps a persistent cache root while retrying transient failures', () => {
|
||||
const projectDir = mkTempProject()
|
||||
const cacheRoot = join(projectDir, 'electron-cache')
|
||||
|
||||
try {
|
||||
writeFakeElectronPackage(projectDir)
|
||||
writeFakeElectronGet(projectDir, {
|
||||
downloadFailures: 1,
|
||||
downloadErrorCode: 'ECONNRESET'
|
||||
})
|
||||
writeFakeExtractor(projectDir, { createExecutable: true })
|
||||
mkdirSync(cacheRoot, { recursive: true })
|
||||
writeFileSync(join(cacheRoot, 'preserved.marker'), 'keep me')
|
||||
|
||||
const result = runInstallScript(projectDir, {
|
||||
ORCA_ELECTRON_PACKAGE_CACHE_ROOT: cacheRoot,
|
||||
ORCA_ELECTRON_PACKAGE_RETRY_DELAYS_MS: '0,0'
|
||||
})
|
||||
|
||||
expect(result.status, result.stderr).toBe(0)
|
||||
expect(existsSync(join(cacheRoot, 'preserved.marker'))).toBe(true)
|
||||
expect(
|
||||
readFileSync(join(projectDir, 'electron-get.log'), 'utf8').trim().split('\n')
|
||||
).toHaveLength(2)
|
||||
} finally {
|
||||
rmSync(projectDir, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
// The shared cache is macOS-only: it exists to avoid a second copy via APFS clonefile.
|
||||
it('shares the Electron dist into a sibling worktree without downloading', () => {
|
||||
const projectDir = mkTempProject()
|
||||
const siblingDir = `${projectDir}-sibling`
|
||||
@@ -174,7 +106,6 @@ describe('install-electron-package-binary', () => {
|
||||
const siblingDistDir = join(siblingDir, 'node_modules/electron/dist')
|
||||
|
||||
expect(result.status, result.stderr).toBe(0)
|
||||
expect(readExtractorCallCount(siblingDir)).toBe(0)
|
||||
expect(existsSync(join(siblingDir, 'electron-get.log'))).toBe(false)
|
||||
expect(lstatSync(siblingDistDir).isDirectory()).toBe(true)
|
||||
expect(lstatSync(siblingDistDir).isSymbolicLink()).toBe(false)
|
||||
@@ -184,44 +115,13 @@ describe('install-electron-package-binary', () => {
|
||||
'electron'
|
||||
)
|
||||
expect(readSharedDistMarker(siblingDir)).toBe(sharedEntryName)
|
||||
expect(result.stdout).toContain('Shared Electron 41.5.0 from')
|
||||
} finally {
|
||||
rmSync(siblingDir, { recursive: true, force: true })
|
||||
rmSync(projectDir, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('publishes an already installed Electron dist that predates the shared cache', () => {
|
||||
const projectDir = mkTempProject()
|
||||
|
||||
try {
|
||||
initGitRepo(projectDir)
|
||||
writeFakeElectronPackage(projectDir)
|
||||
writeFakeElectronGet(projectDir)
|
||||
writeFakeExtractor(projectDir, { createExecutable: true })
|
||||
writeFakeElectronDist(projectDir, {
|
||||
executableContents: 'existing executable',
|
||||
pathContents: 'electron'
|
||||
})
|
||||
|
||||
const result = runInstallScript(projectDir, { CI: '' })
|
||||
const entryPath = join(sharedCacheRoot(projectDir), sharedEntryName)
|
||||
const distDir = join(projectDir, 'node_modules/electron/dist')
|
||||
|
||||
expect(result.status, result.stderr).toBe(0)
|
||||
expect(readExtractorCallCount(projectDir)).toBe(0)
|
||||
expect(existsSync(join(projectDir, 'electron-get.log'))).toBe(false)
|
||||
expect(readFileSync(join(entryPath, 'version'), 'utf8')).toBe('v41.5.0')
|
||||
expect(readFileSync(join(entryPath, 'electron'), 'utf8')).toBe('existing executable')
|
||||
expect(readSharedDistMarker(projectDir)).toBe(sharedEntryName)
|
||||
expect(readFileSync(join(distDir, 'electron'), 'utf8')).toBe('existing executable')
|
||||
expect(readFileSync(join(distDir, 'version'), 'utf8')).toBe('v41.5.0')
|
||||
} finally {
|
||||
rmSync(projectDir, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('replaces a corrupt shared Electron dist entry instead of re-downloading forever', () => {
|
||||
it('repairs a corrupt shared Electron dist entry', () => {
|
||||
const projectDir = mkTempProject()
|
||||
|
||||
try {
|
||||
@@ -238,16 +138,12 @@ describe('install-electron-package-binary', () => {
|
||||
const distDir = join(projectDir, 'node_modules/electron/dist')
|
||||
|
||||
expect(result.status, result.stderr).toBe(0)
|
||||
expect(result.stderr).not.toContain('Failed to install Electron package binary')
|
||||
expect(readExtractorCallCount(projectDir)).toBe(1)
|
||||
expect(readFileSync(join(distDir, 'version'), 'utf8')).toBe('v41.5.0')
|
||||
expect(readFileSync(join(projectDir, 'node_modules/electron/path.txt'), 'utf8')).toBe(
|
||||
'electron'
|
||||
)
|
||||
// Why not just fall back: an entry left corrupt makes every sibling worktree download again.
|
||||
expect(readFileSync(join(entryPath, 'version'), 'utf8')).toBe('v41.5.0')
|
||||
expect(readSharedDistMarker(projectDir)).toBe(sharedEntryName)
|
||||
expect(readdirSync(sharedCacheRoot(projectDir))).toEqual([sharedEntryName])
|
||||
} finally {
|
||||
rmSync(projectDir, { recursive: true, force: true })
|
||||
}
|
||||
@@ -275,11 +171,6 @@ describe('install-electron-package-binary', () => {
|
||||
'v42.0.0'
|
||||
)
|
||||
expect(readSharedDistMarker(projectDir)).toBe(sharedEntryNameFor('42.0.0'))
|
||||
// Why the old entry stays: sibling worktrees on the previous branch still share it.
|
||||
expect(readdirSync(cacheRoot).sort()).toEqual([
|
||||
sharedEntryNameFor('41.5.0'),
|
||||
sharedEntryNameFor('42.0.0')
|
||||
])
|
||||
expect(readFileSync(join(cacheRoot, sharedEntryNameFor('41.5.0'), 'version'), 'utf8')).toBe(
|
||||
'v41.5.0'
|
||||
)
|
||||
|
||||
-131
@@ -1,131 +0,0 @@
|
||||
import type { execFileSync } from 'node:child_process'
|
||||
import {
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
mkdtempSync,
|
||||
readdirSync,
|
||||
rmSync,
|
||||
symlinkSync,
|
||||
writeFileSync
|
||||
} from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import path from 'node:path'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import {
|
||||
shareElectronDistFromCache,
|
||||
isUsableElectronDist,
|
||||
publishSharedElectronDist,
|
||||
resolveSharedElectronDistEntry
|
||||
} from './shared-electron-dist-cache.mjs'
|
||||
|
||||
const VERSION = '43.4.1'
|
||||
const PLATFORM_PATH = path.join('Electron.app', 'Contents', 'MacOS', 'Electron')
|
||||
const roots: string[] = []
|
||||
|
||||
afterEach(() => {
|
||||
while (roots.length > 0) {
|
||||
rmSync(roots.pop()!, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
function makeRoot(): string {
|
||||
const root = mkdtempSync(path.join(tmpdir(), 'orca-shared-electron-'))
|
||||
roots.push(root)
|
||||
return root
|
||||
}
|
||||
|
||||
function writeDist(distPath: string, version = VERSION): string {
|
||||
mkdirSync(path.join(distPath, path.dirname(PLATFORM_PATH)), { recursive: true })
|
||||
writeFileSync(path.join(distPath, 'version'), `v${version}\n`)
|
||||
writeFileSync(path.join(distPath, PLATFORM_PATH), 'electron')
|
||||
return distPath
|
||||
}
|
||||
|
||||
function makeEntry(root: string, entryName = `${VERSION}-darwin-arm64`) {
|
||||
const cacheRoot = path.join(root, 'cache')
|
||||
return {
|
||||
cacheRoot,
|
||||
entryPath: path.join(cacheRoot, entryName),
|
||||
markerPath: path.join(root, '.orca-shared-dist')
|
||||
}
|
||||
}
|
||||
|
||||
const baseOptions = {
|
||||
repoRoot: '/repo',
|
||||
electronPackageDir: '/repo/node_modules/electron',
|
||||
version: VERSION,
|
||||
targetPlatform: 'darwin',
|
||||
targetArch: 'arm64',
|
||||
env: {} as NodeJS.ProcessEnv,
|
||||
execFile: (() => '/repo/.git\n') as unknown as typeof execFileSync
|
||||
}
|
||||
|
||||
describe('resolveSharedElectronDistEntry', () => {
|
||||
it('declines an identity that would not be a single safe path segment', () => {
|
||||
expect(resolveSharedElectronDistEntry({ ...baseOptions, targetArch: '../escape' })).toBeNull()
|
||||
expect(resolveSharedElectronDistEntry({ ...baseOptions, targetPlatform: 'dar/win' })).toBeNull()
|
||||
expect(resolveSharedElectronDistEntry({ ...baseOptions, version: '' })).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('isUsableElectronDist', () => {
|
||||
it('rejects a symlink so a redirected entry is never treated as cache content', () => {
|
||||
const root = makeRoot()
|
||||
writeDist(path.join(root, 'real'))
|
||||
symlinkSync(path.join(root, 'real'), path.join(root, 'link'), 'dir')
|
||||
expect(isUsableElectronDist(path.join(root, 'link'), VERSION, PLATFORM_PATH)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('publishSharedElectronDist', () => {
|
||||
it('leaves no entry and no staging tree when sharing fails', () => {
|
||||
const root = makeRoot()
|
||||
const entry = makeEntry(root)
|
||||
const share = (_source: string, destination: string) => {
|
||||
writeDist(destination)
|
||||
throw new Error('no shareable storage')
|
||||
}
|
||||
expect(publishSharedElectronDist(writeDist(path.join(root, 'dist')), entry, { share })).toBe(
|
||||
false
|
||||
)
|
||||
expect(existsSync(entry.entryPath)).toBe(false)
|
||||
expect(readdirSync(entry.cacheRoot)).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('shareElectronDistFromCache', () => {
|
||||
it('refuses an entry that fails validation instead of installing it', () => {
|
||||
const root = makeRoot()
|
||||
const entry = makeEntry(root)
|
||||
mkdirSync(entry.cacheRoot, { recursive: true })
|
||||
writeDist(entry.entryPath, '40.0.0')
|
||||
const stagePath = path.join(root, 'stage')
|
||||
expect(
|
||||
shareElectronDistFromCache(entry, stagePath, {
|
||||
version: VERSION,
|
||||
platformPath: PLATFORM_PATH
|
||||
})
|
||||
).toBe(false)
|
||||
expect(existsSync(stagePath)).toBe(false)
|
||||
})
|
||||
|
||||
it('reports failure rather than falling back to a full copy', () => {
|
||||
const root = makeRoot()
|
||||
const entry = makeEntry(root)
|
||||
mkdirSync(entry.cacheRoot, { recursive: true })
|
||||
writeDist(entry.entryPath)
|
||||
// Stub mechanism-dependent sharing failures; byte-copy fallback would defeat cache’s storage purpose.
|
||||
const stagePath = path.join(root, 'stage')
|
||||
const share = () => {
|
||||
throw new Error('no shareable storage')
|
||||
}
|
||||
expect(
|
||||
shareElectronDistFromCache(entry, stagePath, {
|
||||
version: VERSION,
|
||||
platformPath: PLATFORM_PATH,
|
||||
share
|
||||
})
|
||||
).toBe(false)
|
||||
expect(existsSync(stagePath)).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -1,55 +0,0 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { repairTranslatedValue } from './locale-translation-policy.mjs'
|
||||
|
||||
describe('locale brand matching', () => {
|
||||
it('does not construct boundary expressions for absent brands', () => {
|
||||
let boundaryExpressions = 0
|
||||
vi.stubGlobal(
|
||||
'RegExp',
|
||||
new Proxy(RegExp, {
|
||||
construct(target, args) {
|
||||
if (typeof args[0] === 'string' && args[0].startsWith('(^|[^A-Za-z_])')) {
|
||||
boundaryExpressions += 1
|
||||
}
|
||||
return Reflect.construct(target, args)
|
||||
}
|
||||
})
|
||||
)
|
||||
try {
|
||||
for (const locale of ['zh', 'ja', 'ko', 'es']) {
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'fixture.endpoint',
|
||||
enValue: 'Choose an endpoint.',
|
||||
localeValue: 'fixture translation',
|
||||
locale
|
||||
})
|
||||
).toBe('fixture translation')
|
||||
}
|
||||
} finally {
|
||||
vi.unstubAllGlobals()
|
||||
}
|
||||
expect(boundaryExpressions).toBe(0)
|
||||
})
|
||||
|
||||
it.each([
|
||||
['Use Gemini.', 'Usar Géminis.', 'Usar Gemini.'],
|
||||
['Use GeminiX.', 'Usar Géminis.', 'Usar Géminis.'],
|
||||
['Use XGemini.', 'Usar Géminis.', 'Usar Géminis.'],
|
||||
['Use _Gemini_.', 'Usar Géminis.', 'Usar Géminis.'],
|
||||
['Use Gemini_2.', 'Usar Géminis.', 'Usar Géminis.'],
|
||||
['Use 2Gemini3.', 'Usar Géminis.', 'Usar Gemini.'],
|
||||
['Use (Gemini).', 'Usar Géminis.', 'Usar Gemini.'],
|
||||
['Use éGemini界.', 'Usar Géminis.', 'Usar Gemini.'],
|
||||
['Use gemini.', 'Usar Géminis.', 'Usar Géminis.'],
|
||||
['Use GeminiX and Gemini.', 'Usar Géminis.', 'Usar Gemini.'],
|
||||
['Use Gemini.', 'Gemini y Géminis.', 'Gemini y Géminis.'],
|
||||
['Use Gemini.', '_Gemini_ y Géminis.', '_Gemini_ y Gemini.'],
|
||||
['Use GitHub Copilot.', 'Usar Copiloto de GitHub.', 'Usar GitHub Copilot.'],
|
||||
['Use XGitHub CopilotY.', 'Usar Copiloto de GitHub.', 'Usar GitHub Copilot.']
|
||||
])('preserves literal and boundary matching for %j / %j', (enValue, localeValue, expected) => {
|
||||
expect(
|
||||
repairTranslatedValue({ key: 'fixture.brand', enValue, localeValue, locale: 'es' })
|
||||
).toBe(expected)
|
||||
})
|
||||
})
|
||||
@@ -1,125 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { repairTranslatedValue } from './locale-translation-policy.mjs'
|
||||
|
||||
// #12113: generic-word brand repair reverted ~2000 translations; keep UI vocabulary translated and real brands Latin.
|
||||
describe('locale generic UI terms', () => {
|
||||
it('keeps a translated generic term when the whole value is that term', () => {
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.settings.AppearancePane.terminalTitle',
|
||||
enValue: 'Terminal',
|
||||
localeValue: '터미널',
|
||||
locale: 'ko'
|
||||
})
|
||||
).toBe('터미널')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.settings.QuickCommandsPane.4ccc63da87',
|
||||
enValue: 'Agent',
|
||||
localeValue: '에이전트',
|
||||
locale: 'ko'
|
||||
})
|
||||
).toBe('에이전트')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.mobile.MobileHero.a8fb43cf1c',
|
||||
enValue: 'Continue',
|
||||
localeValue: '继续',
|
||||
locale: 'zh'
|
||||
})
|
||||
).toBe('继续')
|
||||
})
|
||||
|
||||
it('keeps a translated generic term inside a sentence', () => {
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.settings.EditorFontSection.matchTerminal',
|
||||
enValue: 'Match terminal font',
|
||||
localeValue: '터미널 글꼴과 동일',
|
||||
locale: 'ko'
|
||||
})
|
||||
).toBe('터미널 글꼴과 동일')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.settings.ExperimentalPane.agentDashboard.toggleLabel',
|
||||
enValue: 'Toggle Agent Dashboard',
|
||||
localeValue: '에이전트 대시보드 전환',
|
||||
locale: 'ko'
|
||||
})
|
||||
).toBe('에이전트 대시보드 전환')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'components.agentSessionContinuation.dialogTitle',
|
||||
enValue: 'Continue in New Session',
|
||||
localeValue: '新しいセッションで続ける',
|
||||
locale: 'ja'
|
||||
})
|
||||
// 新しい → 新規 is an unrelated phrase fix; 続ける is what must survive.
|
||||
).toBe('新規セッションで続ける')
|
||||
})
|
||||
|
||||
it('still reverts real brand names that machine translation localized', () => {
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.stats.StatsPane.7d26110cea',
|
||||
enValue: 'Codex',
|
||||
localeValue: '사본',
|
||||
locale: 'ko'
|
||||
})
|
||||
).toBe('Codex')
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.settings.NetworkPane.tailscale',
|
||||
enValue: 'Connect over Tailscale',
|
||||
localeValue: '通过尾鳞连接',
|
||||
locale: 'zh'
|
||||
})
|
||||
).toBe('通过 Tailscale 连接')
|
||||
})
|
||||
|
||||
it('does not cut a valid word that contains a nonsense rendering', () => {
|
||||
// 终端子进程 is "terminal sub-process" — 端子 sits inside 终端 + 子.
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.settings.AdvancedNetworkSettingsSection.823e0f15b1',
|
||||
enValue: 'Proxy URL for Orca network requests and local terminal subprocesses.',
|
||||
localeValue: '用于 Orca 网络请求和本地终端子进程的代理 URL。',
|
||||
locale: 'zh'
|
||||
})
|
||||
).toBe('用于 Orca 网络请求和本地终端子进程的代理 URL。')
|
||||
})
|
||||
|
||||
it('still fills in a translation when the catalog holds English', () => {
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.feature.wall.BrowserAnimatedVisual.04096318ab',
|
||||
enValue: 'Terminal 1',
|
||||
localeValue: 'Terminal 1',
|
||||
locale: 'ko'
|
||||
})
|
||||
).toBe('터미널 1')
|
||||
})
|
||||
|
||||
it('keeps agent catalog product names in English by key, not by word', () => {
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.lib.agent.catalog.9e2a9bb87b',
|
||||
enValue: 'Continue',
|
||||
localeValue: '继续',
|
||||
locale: 'zh'
|
||||
})
|
||||
).toBe('Continue')
|
||||
})
|
||||
|
||||
it.each([
|
||||
['ko', 'Commit', '커밋'],
|
||||
['ja', 'repos', 'リポジトリ'],
|
||||
['es', 'agents', 'agentes'],
|
||||
['ko', 'terminals', '터미널']
|
||||
])('preserves the %s translation of generic %s', (locale, enValue, localeValue) => {
|
||||
expect(repairTranslatedValue({ key: 'fixture.generic', enValue, localeValue, locale })).toBe(
|
||||
localeValue
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -14,16 +14,8 @@ describe('repairCatalog with un-bootstrapped keys', () => {
|
||||
const translatedOnly = () => ({ auto: { lib: { agent: { catalog: { '760bc6883d': '사본' } } } } })
|
||||
|
||||
it('skips leaves the locale catalog is missing instead of throwing', () => {
|
||||
for (const locale of ['ko', 'ja', 'zh', 'es']) {
|
||||
const localeCatalog = translatedOnly()
|
||||
expect(() => repairCatalog(enCatalog, localeCatalog, locale), locale).not.toThrow()
|
||||
expect(localeCatalog.auto.components, locale).toBeUndefined()
|
||||
}
|
||||
})
|
||||
|
||||
it('still repairs the leaves that are present', () => {
|
||||
const localeCatalog = translatedOnly()
|
||||
expect(repairCatalog(enCatalog, localeCatalog, 'ko')).toBe(1)
|
||||
expect(localeCatalog.auto.lib.agent.catalog['760bc6883d']).toBe('Codex')
|
||||
expect(() => repairCatalog(enCatalog, localeCatalog, 'ko')).not.toThrow()
|
||||
expect(localeCatalog.auto.components).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { repairTranslatedValue } from './locale-translation-policy.mjs'
|
||||
|
||||
const repairEs = (enValue, localeValue) =>
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.test.pr-glossary',
|
||||
enValue,
|
||||
localeValue,
|
||||
locale: 'es'
|
||||
})
|
||||
|
||||
describe('locale-translation-policy es PR glossary', () => {
|
||||
it('rewrites the "relaciones públicas" mistranslation of PR back to PR', () => {
|
||||
expect(repairEs('PR', 'relaciones públicas')).toBe('PR')
|
||||
expect(repairEs('PRs', 'relaciones públicas')).toBe('PR')
|
||||
expect(repairEs('Reopen PR', 'Reabrir relaciones públicas')).toBe('Reabrir PR')
|
||||
expect(repairEs('Create PR', 'Crear relaciones públicas')).toBe('Crear PR')
|
||||
expect(repairEs('Open PR checks', 'Abrir cheques de relaciones públicas')).toBe(
|
||||
'Abrir cheques de PR'
|
||||
)
|
||||
expect(repairEs('unlink PR', 'desvincular relaciones públicas')).toBe('desvincular PR')
|
||||
})
|
||||
|
||||
it('does not fire when the English has a "pr" substring but no PR token', () => {
|
||||
// approve/public contain pr substring but no PR token; leave genuine public-relations prose alone.
|
||||
expect(repairEs('Approve public relations', 'Aprobar relaciones públicas')).toBe(
|
||||
'Aprobar relaciones públicas'
|
||||
)
|
||||
expect(repairEs('Compress the preview', 'Comprimir las relaciones públicas')).toBe(
|
||||
'Comprimir las relaciones públicas'
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -1,22 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { repairTranslatedValue, shouldPreserveEnglishValue } from './locale-translation-policy.mjs'
|
||||
|
||||
describe('locale-translation-policy', () => {
|
||||
it('does not break Copy identifier when fixing Codex', () => {
|
||||
expect(
|
||||
repairTranslatedValue({
|
||||
key: 'auto.components.example',
|
||||
enValue: 'Copy identifier',
|
||||
localeValue: '사본 식별자',
|
||||
locale: 'ko'
|
||||
})
|
||||
).toBe('사본 식별자')
|
||||
})
|
||||
|
||||
it('skips machine translation for standalone brands', () => {
|
||||
expect(shouldPreserveEnglishValue('Codex')).toBe(true)
|
||||
expect(shouldPreserveEnglishValue('Codex', 'auto.stats.StatsPane.7d26110cea')).toBe(true)
|
||||
expect(shouldPreserveEnglishValue('Show Codex usage')).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -4,18 +4,7 @@ import path from 'node:path'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { collectProtectedCopyKeys, main, PROTECTED_COPY_KEYS_PATH } from './protected-copy-keys.mjs'
|
||||
|
||||
const ENGLISH = new Set([
|
||||
'trust.title',
|
||||
'trust.allow',
|
||||
'trust.copy.warning',
|
||||
'confirm.delete',
|
||||
'confirm.count_other',
|
||||
'native.quit',
|
||||
'pragma.badge',
|
||||
'nested.deep',
|
||||
'trust.reasons.network',
|
||||
'plain.title'
|
||||
])
|
||||
const ENGLISH = new Set(['trust.title', 'trust.reasons.network'])
|
||||
|
||||
let dirs = []
|
||||
afterEach(() => {
|
||||
@@ -40,33 +29,6 @@ function sourcesFrom(files) {
|
||||
}
|
||||
|
||||
describe('collectProtectedCopyKeys', () => {
|
||||
it('protects every key a marked file or its sibling copy module names', () => {
|
||||
const sources = sourcesFrom({
|
||||
'dialogs/TrustDialog.tsx': `import { trustCopy } from './trust-copy'
|
||||
import { plain } from '../plain/Plain'
|
||||
<DialogContent surface="security">{translate('trust.title', 'Trust?')}{translate('trust.allow', 'Allow')}`,
|
||||
'dialogs/trust-copy.ts': `export const trustCopy = () => translate('trust.copy.warning', 'Careful')`,
|
||||
'plain/Plain.tsx': `export const plain = () => translate('plain.title', 'Title')`,
|
||||
'confirm/DeleteDialog.tsx': `<DialogContent><Button variant="destructive">{translate('confirm.delete', 'Delete')}{translate('confirm.count', '{{count}}', { count })}</Button>`,
|
||||
'main/prompt.ts': `dialog.showMessageBox({ buttons: [translateMain('native.quit', 'Quit')] })`,
|
||||
'badge.ts': `// @protected-copy: badge\nexport const b = () => translate('pragma.badge', 'Official')`,
|
||||
'other.tsx': `translate('nested.deep', 'Deep')`
|
||||
})
|
||||
|
||||
expect(collectProtectedCopyKeys(sources, ENGLISH)).toEqual({
|
||||
keys: [
|
||||
'confirm.count',
|
||||
'confirm.delete',
|
||||
'native.quit',
|
||||
'pragma.badge',
|
||||
'trust.allow',
|
||||
'trust.copy.warning',
|
||||
'trust.title'
|
||||
],
|
||||
prefixes: []
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps dynamic key heads only when they are specific', () => {
|
||||
const sources = sourcesFrom({
|
||||
'Dialog.tsx': '<SecuritySurface>{translate(`trust.${id}`)}{translate(`trust.reasons.${x}`)}'
|
||||
|
||||
@@ -48,30 +48,6 @@ describe('verify-localization-catalog', () => {
|
||||
expect(readJson(path.join(localesDir, 'es.json'))).toEqual({})
|
||||
})
|
||||
|
||||
it.each([
|
||||
['en', false],
|
||||
['en', true],
|
||||
['es', false]
|
||||
])('rejects dotted %s keys without rewriting them (fix: %s)', async (locale, fix) => {
|
||||
const nested = { auto: { example: { greeting: 'Hello.world' } } }
|
||||
const { root, localesDir } = makeProject({
|
||||
sourceText: 'export {}\n',
|
||||
enCatalog: locale === 'en' ? { 'auto.example.greeting': 'Hello.world' } : nested,
|
||||
esCatalog: locale === 'es' ? { auto: { 'example.greeting': 'Hola.world' } } : {}
|
||||
})
|
||||
const catalogPath = path.join(localesDir, `${locale}.json`)
|
||||
const before = readFileSync(catalogPath, 'utf8')
|
||||
const report = vi.spyOn(console, 'error').mockImplementation(() => undefined)
|
||||
try {
|
||||
await expect(verifyLocalizationCatalog(root, { fix })).resolves.toBe(1)
|
||||
expect(report).toHaveBeenCalledWith(expect.stringContaining(`${locale}.json`))
|
||||
expect(report).toHaveBeenCalledWith('dotted: auto.example.greeting')
|
||||
expect(readFileSync(catalogPath, 'utf8')).toBe(before)
|
||||
} finally {
|
||||
report.mockRestore()
|
||||
}
|
||||
})
|
||||
|
||||
it('never overwrites mismatched translations or removes target-only entries', async () => {
|
||||
const { root, localesDir } = makeProject({
|
||||
sourceText:
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { main as verifyTogether } from './verify-localization-catalogs.mjs'
|
||||
|
||||
const directories = []
|
||||
const renderer = 'src/renderer/src'
|
||||
const englishPath = `${renderer}/i18n/locales/en.json`
|
||||
const runtimePath = `${renderer}/i18n/en-runtime-required.json`
|
||||
|
||||
async function fixture() {
|
||||
const root = await mkdtemp(join(tmpdir(), 'orca-localization-checks-'))
|
||||
directories.push(root)
|
||||
await mkdir(join(root, renderer, 'i18n/locales'), { recursive: true })
|
||||
await mkdir(join(root, 'src/main'), { recursive: true })
|
||||
await writeFile(join(root, renderer, 'greeting.ts'), "translate('hello', 'Hello')\n")
|
||||
await writeFile(join(root, 'src/main/ready.ts'), "translateMain('ready', 'Ready')\n")
|
||||
await writeFile(
|
||||
join(root, englishPath),
|
||||
JSON.stringify({ hello: 'Hello', ready: 'Ready', dynamic: 'Required' })
|
||||
)
|
||||
await writeFile(join(root, runtimePath), JSON.stringify({ dynamic: 'Required' }))
|
||||
await mkdir(join(root, 'src/shared/plugins/languages'), { recursive: true })
|
||||
await writeFile(
|
||||
join(root, 'src/shared/plugins/languages/protected-copy-keys.json'),
|
||||
`${JSON.stringify({ keys: [], prefixes: [] }, null, 2)}\n`
|
||||
)
|
||||
return root
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(directories.splice(0).map((root) => rm(root, { recursive: true, force: true })))
|
||||
})
|
||||
|
||||
describe('combined localization verification', () => {
|
||||
it('reads source edits, new files, removals, and separate roots on each invocation', async () => {
|
||||
const root = await fixture()
|
||||
expect(await verifyTogether(root)).toBe(0)
|
||||
await writeFile(join(root, renderer, 'greeting.ts'), "translate('hello', 'New fallback')")
|
||||
expect(await verifyTogether(root)).toBe(1)
|
||||
await writeFile(join(root, renderer, 'greeting.ts'), "translate('hello', 'Hello')")
|
||||
expect(await verifyTogether(root)).toBe(0)
|
||||
await writeFile(join(root, 'src/main/added.ts'), "translateMain('absent', 'Absent')")
|
||||
expect(await verifyTogether(root)).toBe(1)
|
||||
await rm(join(root, 'src/main/added.ts'))
|
||||
expect(await verifyTogether(root)).toBe(0)
|
||||
expect(await verifyTogether(await fixture())).toBe(0)
|
||||
})
|
||||
})
|
||||
+1
-26
@@ -2,34 +2,9 @@ import { describe, expect, it, vi } from 'vitest'
|
||||
import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { collectLocalizationCandidates, main } from './audit-localization-coverage.mjs'
|
||||
|
||||
const path = '/repo/src/main/startup/dialog.ts'
|
||||
const source = `
|
||||
const options = {
|
||||
message: translateMain('message', 'Translated message'),
|
||||
detail: 'Untranslated detail',
|
||||
buttons: ['Proceed', translateMain('cancel', 'Cancel')],
|
||||
body: 'Untranslated body',
|
||||
buttonLabel: 'Retry',
|
||||
checkboxLabel: 'Remember choice'
|
||||
}
|
||||
`
|
||||
import { main } from './audit-localization-coverage.mjs'
|
||||
|
||||
describe('native dialog localization coverage', () => {
|
||||
it('finds untranslated native dialog fields while ignoring translated copy', () => {
|
||||
const candidates = collectLocalizationCandidates(path, source, '/repo', {
|
||||
includeNativeDialogFields: true
|
||||
})
|
||||
expect(candidates.map(({ kind, text }) => [kind, text])).toEqual([
|
||||
['object-property:detail', 'Untranslated detail'],
|
||||
['object-property:buttons', 'Proceed'],
|
||||
['object-property:body', 'Untranslated body'],
|
||||
['object-property:buttonLabel', 'Retry'],
|
||||
['object-property:checkboxLabel', 'Remember choice']
|
||||
])
|
||||
})
|
||||
|
||||
it('fails the opt-in check for a new native dialog literal', async () => {
|
||||
const root = await mkdtemp(join(tmpdir(), 'orca-native-localization-'))
|
||||
const report = vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
collectKeyLiterals,
|
||||
compareExtraction,
|
||||
findUnreferencedKeys
|
||||
} from './verify-localization-extraction.mjs'
|
||||
|
||||
function sourceEvidence(sourceText) {
|
||||
return collectKeyLiterals(sourceText, { literals: new Set(), dynamicPrefixes: new Set() })
|
||||
}
|
||||
|
||||
describe('verify-localization-extraction', () => {
|
||||
it('rejects missing English declarations and incompatible placeholders', () => {
|
||||
const result = compareExtraction(
|
||||
{
|
||||
menu: {
|
||||
missing: 'Missing',
|
||||
open: 'Open {{name}}'
|
||||
}
|
||||
},
|
||||
{ menu: { open: 'Open {{path}}' } }
|
||||
)
|
||||
|
||||
expect(result.missingFromEnglish).toEqual(['menu.missing'])
|
||||
expect(result.placeholderMismatches).toEqual(['menu.open'])
|
||||
})
|
||||
|
||||
it('keeps plural variants and data-structure keys that extraction cannot see', () => {
|
||||
const evidence = sourceEvidence("const rows = [{ labelKey: 'rows.held' }]")
|
||||
const extracted = new Map([['menu.count', '']])
|
||||
|
||||
expect(
|
||||
findUnreferencedKeys(
|
||||
['menu.count_one', 'menu.count_other', 'rows.held', 'rows.dead_one', 'menu.stale'],
|
||||
extracted,
|
||||
evidence
|
||||
)
|
||||
).toEqual(['rows.dead_one', 'menu.stale'])
|
||||
})
|
||||
|
||||
it('keeps keys under a prefix that a translate call composes at runtime', () => {
|
||||
const evidence = sourceEvidence(
|
||||
"translate(`status.label.${state}`)\nt('menu.item.' + id)\nconst id = `rpc.${method}`"
|
||||
)
|
||||
|
||||
expect(
|
||||
findUnreferencedKeys(
|
||||
['status.label.ready', 'menu.item.open', 'rpc.ping'],
|
||||
new Map(),
|
||||
evidence
|
||||
)
|
||||
).toEqual(['rpc.ping'])
|
||||
})
|
||||
})
|
||||
@@ -1,13 +1,4 @@
|
||||
import { spawnSync } from 'node:child_process'
|
||||
import {
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
mkdtempSync,
|
||||
readFileSync,
|
||||
rmSync,
|
||||
statSync,
|
||||
writeFileSync
|
||||
} from 'node:fs'
|
||||
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { createRequire } from 'node:module'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
@@ -16,19 +7,10 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
const require = createRequire(new URL('../../macos-terminal-host-bundle.test.mjs', import.meta.url))
|
||||
const {
|
||||
assembleMacTerminalHost,
|
||||
buildMacTerminalHostInfo,
|
||||
daemonBareRequires,
|
||||
findMacTerminalHostInfoProblems,
|
||||
findUnresolvedDaemonRequires,
|
||||
macTerminalHostPaths,
|
||||
parseBuildVersionMinos
|
||||
} = require('./macos/terminal-host-bundle/macos-terminal-host-bundle.cjs')
|
||||
const electronBuilderConfig = require('../electron-builder.config.cjs')
|
||||
|
||||
const usageDescriptions = electronBuilderConfig.mac.extendInfo
|
||||
const appInfo = { CFBundleIdentifier: 'com.stablyai.orca', CFBundleShortVersionString: '1.4.300' }
|
||||
const expectations = { appId: 'com.stablyai.orca', usageDescriptions, minimumOs: '13.5' }
|
||||
|
||||
describe('terminal host binary checks', () => {
|
||||
it('reads minos from the LC_BUILD_VERSION load command only', () => {
|
||||
const otool = [
|
||||
@@ -80,31 +62,6 @@ describe.skipIf(process.platform !== 'darwin')('terminal host assembly', () => {
|
||||
rmSync(root, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
it('lays out the pinned Node, daemon JS and runtime node-pty files only', () => {
|
||||
const info = buildMacTerminalHostInfo({ appInfo, usageDescriptions, minimumOs: '13.5' })
|
||||
const paths = assembleMacTerminalHost({
|
||||
appPath,
|
||||
nodeExecutable: join(root, 'node'),
|
||||
iconPath: join(root, 'icon.icns'),
|
||||
info
|
||||
})
|
||||
expect(paths).toEqual(macTerminalHostPaths(appPath))
|
||||
expect(paths.bundle).toBe(join(appPath, 'Contents', 'Helpers', 'Orca Terminal Host.app'))
|
||||
expect(readFileSync(paths.executable, 'utf8')).toBe('pinned-node')
|
||||
expect(statSync(paths.executable).mode & 0o777).toBe(0o755)
|
||||
expect(statSync(paths.spawnHelper).mode & 0o777).toBe(0o755)
|
||||
expect(readFileSync(paths.entry, 'utf8')).toBe('entry')
|
||||
expect(existsSync(join(paths.daemon, 'out', 'main', 'chunks', 'protocol.js'))).toBe(true)
|
||||
expect(readFileSync(paths.outPackageJson, 'utf8')).toBe('{"type":"commonjs"}')
|
||||
expect(existsSync(join(paths.nodePty, 'lib', 'index.js'))).toBe(true)
|
||||
expect(existsSync(join(paths.nodePty, 'src'))).toBe(false)
|
||||
expect(readFileSync(paths.icon, 'utf8')).toBe('icon')
|
||||
const plist = spawnSync('/usr/bin/plutil', ['-convert', 'json', '-o', '-', paths.infoPlist], {
|
||||
encoding: 'utf8'
|
||||
})
|
||||
expect(findMacTerminalHostInfoProblems(JSON.parse(plist.stdout), expectations)).toEqual([])
|
||||
})
|
||||
|
||||
it('copies every package the daemon requires, with its dependencies', () => {
|
||||
const resources = join(appPath, 'Contents', 'Resources')
|
||||
const main = join(resources, 'app.asar.unpacked', 'out', 'main')
|
||||
@@ -180,29 +137,6 @@ describe.skipIf(process.platform !== 'darwin')('terminal host assembly', () => {
|
||||
expect(findUnresolvedDaemonRequires(paths)).toEqual([])
|
||||
})
|
||||
|
||||
it('follows directory and JSON loads and keeps nested dependencies with their parent', () => {
|
||||
const resources = join(appPath, 'Contents', 'Resources')
|
||||
const main = join(resources, 'app.asar.unpacked', 'out', 'main')
|
||||
mkdirSync(join(main, 'lib'), { recursive: true })
|
||||
writeFileSync(join(main, 'daemon-entry.js'), "require('./lib');require('./data.json')")
|
||||
writeFileSync(join(main, 'lib', 'index.js'), "require('outer')")
|
||||
writeFileSync(join(main, 'data.json'), '{}')
|
||||
const outer = join(resources, 'node_modules', 'outer')
|
||||
mkdirSync(join(outer, 'node_modules', 'inner'), { recursive: true })
|
||||
writeFileSync(join(outer, 'package.json'), '{"dependencies":{"inner":"1"}}')
|
||||
writeFileSync(join(outer, 'node_modules', 'inner', 'package.json'), '{}')
|
||||
const paths = assembleMacTerminalHost({
|
||||
appPath,
|
||||
nodeExecutable: join(root, 'node'),
|
||||
iconPath: join(root, 'icon.icns'),
|
||||
info: {}
|
||||
})
|
||||
expect(daemonBareRequires(join(paths.daemon, 'out', 'main'))).toEqual(['outer'])
|
||||
expect(existsSync(join(paths.daemon, 'node_modules', 'outer', 'node_modules', 'inner'))).toBe(
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
it("copies a nested package's dependency from wherever Node resolves it", () => {
|
||||
const resources = join(appPath, 'Contents', 'Resources')
|
||||
writeFileSync(
|
||||
@@ -249,17 +183,4 @@ describe.skipIf(process.platform !== 'darwin')('terminal host assembly', () => {
|
||||
})
|
||||
).toThrow(/loads a module by a computed name/)
|
||||
})
|
||||
|
||||
it('fails before writing anything when the daemon entry is missing', () => {
|
||||
rmSync(join(appPath, 'Contents', 'Resources', 'app.asar.unpacked'), { recursive: true })
|
||||
expect(() =>
|
||||
assembleMacTerminalHost({
|
||||
appPath,
|
||||
nodeExecutable: join(root, 'node'),
|
||||
iconPath: join(root, 'icon.icns'),
|
||||
info: {}
|
||||
})
|
||||
).toThrow(/missing .*daemon-entry\.js/)
|
||||
expect(existsSync(join(appPath, 'Contents', 'Helpers'))).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,22 +1,11 @@
|
||||
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
MOBILE_WEB_APP_ROUTE_ROOT,
|
||||
ROUTE_MODULE_SYNCHRONOUS_EXPORTS,
|
||||
collectMobileWebAppRouteKeys,
|
||||
collectMobileWebAppRoutes,
|
||||
routeModuleSynchronousExports
|
||||
collectMobileWebAppRoutes
|
||||
} from './mobile-web-app-route-manifest.mjs'
|
||||
import { mobileWebAppDependenciesPresent } from './mobile-web-app-bundle-dependencies.mjs'
|
||||
|
||||
const projectDir = fileURLToPath(new URL('../../..', import.meta.url))
|
||||
const appDir = join(projectDir, 'mobile', 'app')
|
||||
|
||||
// Root shards omit mobile dependencies; route-tree builds run in mobile_web_app job.
|
||||
const itBundling = mobileWebAppDependenciesPresent() ? it : it.skip
|
||||
|
||||
async function withScratch(run) {
|
||||
const scratch = await mkdtemp(join(tmpdir(), 'orca-mobile-web-app-routes-test-'))
|
||||
@@ -28,98 +17,6 @@ async function withScratch(run) {
|
||||
}
|
||||
|
||||
describe('route manifest', () => {
|
||||
it('carries no root layout for a tree without a web sibling of it', async () => {
|
||||
await withScratch(async (scratch) => {
|
||||
await mkdir(join(scratch, MOBILE_WEB_APP_ROUTE_ROOT), { recursive: true })
|
||||
await writeFile(join(scratch, '_layout.tsx'), 'export default null\n', 'utf8')
|
||||
await writeFile(join(scratch, MOBILE_WEB_APP_ROUTE_ROOT, 'index.tsx'), 'export default 1\n')
|
||||
expect(await collectMobileWebAppRouteKeys(scratch)).toEqual(['./h/index.tsx'])
|
||||
})
|
||||
})
|
||||
|
||||
it('excludes test files and API routes', async () => {
|
||||
// Synthetic tree exercises absent-today forbidden route exports.
|
||||
await withScratch(async (scratch) => {
|
||||
const directory = join(scratch, MOBILE_WEB_APP_ROUTE_ROOT)
|
||||
await mkdir(directory, { recursive: true })
|
||||
for (const name of [
|
||||
'index.tsx',
|
||||
'index.test.tsx',
|
||||
'index.spec.tsx',
|
||||
'shape.d.ts',
|
||||
'+api.ts',
|
||||
'tokens+api.ts',
|
||||
'+middleware.ts',
|
||||
'notes.md'
|
||||
]) {
|
||||
await writeFile(join(directory, name), 'export default null\n', 'utf8')
|
||||
}
|
||||
expect(await collectMobileWebAppRouteKeys(scratch)).toEqual(['./h/index.tsx'])
|
||||
})
|
||||
expect(await collectMobileWebAppRouteKeys(appDir)).not.toContain('./h/_layout.test.tsx')
|
||||
})
|
||||
|
||||
// Alias/reexport controls require namespace parse, not declaration-name matching.
|
||||
it('reads the names off the namespace, not off a declaration', async () => {
|
||||
expect(ROUTE_MODULE_SYNCHRONOUS_EXPORTS).toEqual(['unstable_settings', 'ErrorBoundary'])
|
||||
await withScratch(async (scratch) => {
|
||||
const exportsOf = async (name, source) => {
|
||||
const file = join(scratch, name)
|
||||
await writeFile(file, source, 'utf8')
|
||||
return routeModuleSynchronousExports(file)
|
||||
}
|
||||
expect(
|
||||
(await exportsOf('declared.tsx', 'export const unstable_settings = { anchor: "x" }\n'))
|
||||
.named
|
||||
).toEqual(['unstable_settings'])
|
||||
expect(
|
||||
(
|
||||
await exportsOf(
|
||||
'aliased.tsx',
|
||||
'const settings = { anchor: "x" }\nexport { settings as unstable_settings }\n'
|
||||
)
|
||||
).named
|
||||
).toEqual(['unstable_settings'])
|
||||
expect((await exportsOf('classy.tsx', 'export class ErrorBoundary {}\n')).named).toEqual([
|
||||
'ErrorBoundary'
|
||||
])
|
||||
expect(
|
||||
(await exportsOf('forwarded.tsx', 'export { ErrorBoundary } from "./boundary"\n')).named
|
||||
).toEqual(['ErrorBoundary'])
|
||||
expect(
|
||||
(await exportsOf('plain.tsx', 'export default function Route() { return null }\n')).named
|
||||
).toEqual([])
|
||||
})
|
||||
}, 60_000)
|
||||
|
||||
it('refuses a star re-export rather than reading it as clean', async () => {
|
||||
await withScratch(async (scratch) => {
|
||||
const file = join(scratch, 'star.tsx')
|
||||
// Unresolved export * cannot prove namespace clean without bundling; fail closed.
|
||||
await writeFile(file, 'export * from "./boundary"\nexport default null\n', 'utf8')
|
||||
const { named, starExports } = await routeModuleSynchronousExports(file)
|
||||
expect(named).toEqual([])
|
||||
expect(starExports).toEqual(['./boundary'])
|
||||
})
|
||||
}, 60_000)
|
||||
|
||||
itBundling(
|
||||
'reads a .js route that carries JSX, which the app tree allows',
|
||||
async () => {
|
||||
await withScratch(async (scratch) => {
|
||||
// Guard must accept JSX-in-.js routes like actual RN bundler.
|
||||
const file = join(scratch, 'jsx-route.js')
|
||||
await writeFile(
|
||||
file,
|
||||
'const boundary = () => <div />\nexport { boundary as ErrorBoundary }\nexport default () => <div />\n',
|
||||
'utf8'
|
||||
)
|
||||
expect((await routeModuleSynchronousExports(file)).named).toEqual(['ErrorBoundary'])
|
||||
})
|
||||
},
|
||||
60_000
|
||||
)
|
||||
|
||||
it('imports a .web.tsx sibling under the native route key', async () => {
|
||||
await withScratch(async (scratch) => {
|
||||
const directory = join(scratch, MOBILE_WEB_APP_ROUTE_ROOT)
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import { existsSync } from 'node:fs'
|
||||
import { expect, it } from 'vitest'
|
||||
import {
|
||||
prepareRouteSnapshot,
|
||||
withRouteSnapshot
|
||||
} from '../mobile-web/check-entry/run-mobile-web-app-checks.mjs'
|
||||
|
||||
const collect = async (entries) => ({
|
||||
modules: [...entries, 'node_modules/react/index.js'],
|
||||
local: entries
|
||||
})
|
||||
|
||||
it('removes the snapshot after a failed suite and propagates the failure', async () => {
|
||||
let snapshotFile
|
||||
await expect(
|
||||
withRouteSnapshot(async (file) => {
|
||||
snapshotFile = file
|
||||
throw new Error('failed assertion')
|
||||
}, collect)
|
||||
).rejects.toThrow('failed assertion')
|
||||
expect(existsSync(snapshotFile)).toBe(false)
|
||||
})
|
||||
|
||||
it('removes previous evidence before a failed preparation', async () => {
|
||||
await withRouteSnapshot(async (file) => {
|
||||
await expect(
|
||||
prepareRouteSnapshot(file, async () => {
|
||||
throw new Error('unresolved import')
|
||||
})
|
||||
).rejects.toThrow('unresolved import')
|
||||
expect(existsSync(file)).toBe(false)
|
||||
}, collect)
|
||||
})
|
||||
@@ -275,14 +275,10 @@ for (const engine of ['chromium', 'webkit']) {
|
||||
}
|
||||
})
|
||||
// Require artifact painted before asserting hidden affordance; failed load cannot count as inert links.
|
||||
expect(hidden.grants).not.toContain('externalNavigation')
|
||||
expect(hidden.pixelBefore).toBe(ARTIFACT_RGB)
|
||||
expect(hidden.frameCount).toBe(1)
|
||||
expect(hidden.inside?.marker).toBe('ARTIFACT_RENDERED')
|
||||
expect(hidden.toggles?.map((one) => one.selected)).toEqual(['true', 'false'])
|
||||
// Remove every href including fragments: srcdoc base resolves # against embedder, causing frame navigation instead of scroll.
|
||||
expect(hidden.links?.linked).toBe(0)
|
||||
expect(hidden.links?.anchors).toBe(5)
|
||||
expect(hidden.links?.text).toBe('tap')
|
||||
expect(hidden.links?.decoration).toBe('none')
|
||||
expect(hidden.links?.focusable).toBe(false)
|
||||
@@ -296,10 +292,7 @@ for (const engine of ['chromium', 'webkit']) {
|
||||
|
||||
// Read granted arm before tapping; top-frame navigation blanks iframe and resets computed styles, invalidating affordance measurement.
|
||||
const shown = await open(browser(), { signal: ctx.signal })
|
||||
expect(shown.grants).toContain('externalNavigation')
|
||||
expect(shown.pixel).toBe(ARTIFACT_RGB)
|
||||
expect(shown.links?.linked).toBe(5)
|
||||
expect(shown.links?.anchors).toBe(5)
|
||||
expect(shown.links?.text).toBe('tap')
|
||||
expect(shown.links?.decoration).toBe('underline')
|
||||
expect(shown.links?.focusable).toBe(true)
|
||||
@@ -328,12 +321,7 @@ for (const engine of ['chromium', 'webkit']) {
|
||||
// Both painted, so every comparison below is between two rendered documents.
|
||||
expect(shown.pixel).toBe(ARTIFACT_RGB)
|
||||
expect(hidden.pixel).toBe(ARTIFACT_RGB)
|
||||
expect(shown.grants).toContain('externalNavigation')
|
||||
expect(hidden.grants).not.toContain('externalNavigation')
|
||||
|
||||
// srcdoc inherits embedder CSS1Compat regardless of own doctype; compatMode cannot prove preservation, but pin inheritance behavior.
|
||||
expect(shown.inside?.compatMode).toBe('CSS1Compat')
|
||||
expect(hidden.inside?.compatMode).toBe(shown.inside?.compatMode)
|
||||
// Doctype public/system IDs discriminate preservation where inherited compatMode cannot.
|
||||
expect(shown.inside?.doctypePublicId).toBe('-//W3C//DTD HTML 4.01 Transitional//EN')
|
||||
expect(hidden.inside?.doctypePublicId).toBe(shown.inside?.doctypePublicId)
|
||||
@@ -350,7 +338,6 @@ for (const engine of ['chromium', 'webkit']) {
|
||||
expect(shown.inside?.fragmentHref).toBe('#fragtarget')
|
||||
expect(hidden.inside?.fragmentHref).toBeNull()
|
||||
expect(hidden.links?.linked).toBe(0)
|
||||
expect(shown.links?.linked).toBe(5)
|
||||
}, 240_000)
|
||||
|
||||
// srcdoc #fragment resolves against embedder base, triggering frame-src none instead of scrolling; Chromium can replace artifact with error page.
|
||||
|
||||
@@ -49,10 +49,7 @@ beforeAll(async () => {
|
||||
faultGrant = await readBridgeFaultGrant()
|
||||
const served = await createBundleServer({
|
||||
outDir: built.outDir,
|
||||
cspHeader: (request) =>
|
||||
request.url.includes('oldShell=1')
|
||||
? policy.replace('media-src blob:', "media-src 'none'")
|
||||
: policy
|
||||
cspHeader: policy
|
||||
})
|
||||
server = served.server
|
||||
origin = served.origin
|
||||
@@ -66,7 +63,7 @@ afterAll(async () => {
|
||||
await rm(scratch, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
async function openMedia(relativePath, bytes, oldShell = false) {
|
||||
async function openMedia(relativePath, bytes) {
|
||||
const page = await browser.newPage({ viewport: { width: 390, height: 844 } })
|
||||
const errors = []
|
||||
page.on('pageerror', (error) => errors.push(error.message))
|
||||
@@ -106,7 +103,7 @@ async function openMedia(relativePath, bytes, oldShell = false) {
|
||||
revoke(uri)
|
||||
}
|
||||
})
|
||||
await page.goto(`${origin}/?${oldShell ? 'oldShell=1' : ''}`)
|
||||
await page.goto(`${origin}/`)
|
||||
return { page, errors }
|
||||
}
|
||||
const describeRender = bundles ? describe : describe.skip
|
||||
@@ -135,15 +132,11 @@ describeRender('mobile media under the shipped shell policy', () => {
|
||||
return {
|
||||
duration: media.duration,
|
||||
time: media.currentTime,
|
||||
controls: media.controls,
|
||||
autoplay: media.autoplay,
|
||||
uri: media.currentSrc
|
||||
}
|
||||
}, tag)
|
||||
expect(playback.duration).toBeCloseTo(2, 1)
|
||||
expect(playback.time).toBeGreaterThan(1)
|
||||
expect(playback.controls).toBe(true)
|
||||
expect(playback.autoplay).toBe(false)
|
||||
await page.evaluate((pathname) => {
|
||||
history.pushState(null, '', `${pathname}?relativePath=README.md`)
|
||||
dispatchEvent(new PopStateEvent('popstate'))
|
||||
@@ -161,11 +154,4 @@ describeRender('mobile media under the shipped shell policy', () => {
|
||||
},
|
||||
60_000
|
||||
)
|
||||
it('explains the required app update when an older shell blocks Blob playback', async () => {
|
||||
const { page } = await openMedia('music.wav', wavFixture(), true)
|
||||
await page
|
||||
.getByText('Update Orca Mobile to play media in this workspace', { exact: true })
|
||||
.waitFor()
|
||||
await page.close()
|
||||
}, 60_000)
|
||||
})
|
||||
|
||||
@@ -9,14 +9,12 @@ import { mobileWebAppDependenciesPresent } from '../mobile-web-app-bundle-depend
|
||||
import {
|
||||
createBundleServer,
|
||||
installCspViolationRecorder,
|
||||
installListenerRecorder,
|
||||
readShellCsp
|
||||
} from '../preview/mobile-web-app-render-harness.mjs'
|
||||
|
||||
const mobileDir = fileURLToPath(new URL('../../../../mobile', import.meta.url))
|
||||
const diagramDir = join(mobileDir, 'src/components/pr-sidebar')
|
||||
|
||||
// Use design fixture so recorded byte counts remain comparable.
|
||||
const FIXTURE =
|
||||
'graph TD\n A[Start] --> B{Choice}\n B -->|yes| C[Ship it]\n B -->|no| D[Fix it]\n D --> A'
|
||||
|
||||
@@ -124,44 +122,15 @@ afterAll(async () => {
|
||||
}
|
||||
})
|
||||
|
||||
// Record eval/Function stacks to attribute JIT attempts: a library may swallow EvalError under CSP.
|
||||
// Playwright also evaluates harness functions, so only bundle-script callers count as page JIT.
|
||||
function installJitRecorder() {
|
||||
globalThis.__orcaJit = []
|
||||
const record = (kind, source) => {
|
||||
// Skip error header and recorder frame to identify caller.
|
||||
const stack = (new Error('jit').stack ?? '').split('\n').slice(2).join(' | ')
|
||||
globalThis.__orcaJit.push({ kind, source: String(source).slice(0, 60), stack })
|
||||
}
|
||||
// oxlint-disable-next-line eslint/no-eval -- SAFETY: the recorder holds the real eval so it can count and forward calls; naming it is this function's whole purpose.
|
||||
const realEval = globalThis.eval
|
||||
// oxlint-disable-next-line eslint/no-eval -- SAFETY: replacing eval with a counting wrapper is the measurement, not a call.
|
||||
globalThis.eval = function (source) {
|
||||
record('eval', source)
|
||||
return realEval.call(globalThis, source)
|
||||
}
|
||||
const RealFunction = globalThis.Function
|
||||
function PatchedFunction(...args) {
|
||||
record('Function', args.map((one) => String(one).slice(0, 40)).join('|'))
|
||||
return RealFunction.apply(this, args)
|
||||
}
|
||||
PatchedFunction.prototype = RealFunction.prototype
|
||||
globalThis.Function = PatchedFunction
|
||||
}
|
||||
|
||||
// Mermaid marks its drawings; the frame's expand icon is an SVG too.
|
||||
function readDiagram() {
|
||||
const DRAWING = 'svg[aria-roledescription]'
|
||||
const frame = document.querySelector('[data-testid="mermaid-diagram"]')
|
||||
const svg = frame?.querySelector(DRAWING) ?? null
|
||||
return {
|
||||
framed: frame !== null,
|
||||
inFrame: frame ? frame.querySelectorAll(DRAWING).length : -1,
|
||||
// Count SVGs across document too: Mermaid’s temporary body element can orphan diagrams outside component host.
|
||||
inDocument: document.querySelectorAll(DRAWING).length,
|
||||
sourceBox: document.querySelector('[data-testid="mermaid-diagram-source"]') !== null,
|
||||
id: svg?.id ?? null,
|
||||
html: svg?.outerHTML ?? null,
|
||||
text: frame?.textContent ?? null
|
||||
}
|
||||
}
|
||||
@@ -175,9 +144,7 @@ async function openPage(browser) {
|
||||
}
|
||||
})
|
||||
page.on('pageerror', (error) => consoleErrors.push(`pageerror: ${error.message}`))
|
||||
await page.addInitScript(installJitRecorder)
|
||||
await page.addInitScript(installCspViolationRecorder)
|
||||
await page.addInitScript(installListenerRecorder)
|
||||
await page.goto(`${origin}/`, { waitUntil: 'domcontentloaded' })
|
||||
await page.waitForFunction(() => document.body.dataset.ready === 'yes')
|
||||
return { page, consoleErrors }
|
||||
|
||||
Reference in New Issue
Block a user