mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-20 15:33:54 +00:00
test: port workspace-level JS tests to a new js-tests workspace package
This commit is contained in:
parent
2f3007ff51
commit
09f8a8268c
8 changed files with 90 additions and 11 deletions
12
package-lock.json
generated
12
package-lock.json
generated
|
|
@ -13,7 +13,8 @@
|
|||
"apps/*",
|
||||
"ui-tui",
|
||||
"ui-tui/packages/*",
|
||||
"web"
|
||||
"web",
|
||||
"tests-js"
|
||||
],
|
||||
"dependencies": {
|
||||
"@streamdown/math": "^1.0.2",
|
||||
|
|
@ -19678,6 +19679,15 @@
|
|||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"tests-js": {
|
||||
"name": "@hermes/root-tests",
|
||||
"version": "0.0.0",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@hermes/root-tests": {
|
||||
"resolved": "tests-js",
|
||||
"link": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
"apps/*",
|
||||
"ui-tui",
|
||||
"ui-tui/packages/*",
|
||||
"web"
|
||||
"web",
|
||||
"tests-js"
|
||||
],
|
||||
"scripts": {
|
||||
"postinstall": "echo '✅ Browser tools ready. Run: python run_agent.py --help'",
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ import path from 'node:path'
|
|||
|
||||
import { test } from 'vitest'
|
||||
|
||||
const REPO_ROOT = path.resolve(__dirname, '..', '..', '..')
|
||||
const REPO_ROOT = path.resolve(__dirname, '..')
|
||||
const LOCK_PATH = path.join(REPO_ROOT, 'package-lock.json')
|
||||
const TAP = '@assistant-ui/tap'
|
||||
|
||||
|
|
@ -51,18 +51,25 @@ function caretSatisfies(version: string, spec: string): boolean {
|
|||
function parse(v: string): [number, number, number] {
|
||||
const core = v.replace(/^[^0-9]+/, '').split('-')[0].split('+')[0]
|
||||
const parts = core.split('.').slice(0, 3)
|
||||
while (parts.length < 3) parts.push('0')
|
||||
|
||||
while (parts.length < 3) {parts.push('0')}
|
||||
|
||||
return [parseInt(parts[0], 10), parseInt(parts[1], 10), parseInt(parts[2], 10)]
|
||||
}
|
||||
|
||||
const ver = parse(version)
|
||||
|
||||
for (const clause of spec.split('||')) {
|
||||
const trimmed = clause.trim()
|
||||
if (!trimmed) continue
|
||||
|
||||
if (!trimmed) {continue}
|
||||
|
||||
if (trimmed.startsWith('^')) {
|
||||
const lo = parse(trimmed)
|
||||
if (ver[0] < lo[0] || (ver[0] === lo[0] && ver[1] < lo[1]) || (ver[0] === lo[0] && ver[1] === lo[1] && ver[2] < lo[2])) continue
|
||||
|
||||
if (ver[0] < lo[0] || (ver[0] === lo[0] && ver[1] < lo[1]) || (ver[0] === lo[0] && ver[1] === lo[1] && ver[2] < lo[2])) {continue}
|
||||
let hi: [number, number, number]
|
||||
|
||||
if (lo[0] > 0) {
|
||||
hi = [lo[0] + 1, 0, 0]
|
||||
} else if (lo[1] > 0) {
|
||||
|
|
@ -70,6 +77,7 @@ function caretSatisfies(version: string, spec: string): boolean {
|
|||
} else {
|
||||
hi = [0, 0, lo[2] + 1]
|
||||
}
|
||||
|
||||
if (
|
||||
(ver[0] < hi[0]) ||
|
||||
(ver[0] === hi[0] && ver[1] < hi[1]) ||
|
||||
|
|
@ -83,6 +91,7 @@ function caretSatisfies(version: string, spec: string): boolean {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
@ -94,39 +103,52 @@ interface LockPackage {
|
|||
}
|
||||
|
||||
function lockPackages(): Record<string, LockPackage> {
|
||||
if (!fs.existsSync(LOCK_PATH)) {return {}}
|
||||
const lock = JSON.parse(fs.readFileSync(LOCK_PATH, 'utf-8'))
|
||||
|
||||
return (lock.packages ?? {}) as Record<string, LockPackage>
|
||||
}
|
||||
|
||||
function sharedTapVersion(packages: Record<string, LockPackage>): string {
|
||||
/** The one tap version every install site resolves to. */
|
||||
const versions = new Set<string>()
|
||||
|
||||
for (const [key, meta] of Object.entries(packages)) {
|
||||
const idx = key.lastIndexOf('node_modules/')
|
||||
const name = idx >= 0 ? key.slice(idx + 'node_modules/'.length) : key
|
||||
|
||||
if (name === TAP) {
|
||||
if (meta.version) versions.add(meta.version)
|
||||
if (meta.version) {versions.add(meta.version)}
|
||||
}
|
||||
}
|
||||
|
||||
assert.ok(versions.size > 0, 'package-lock.json has no @assistant-ui/tap entry — the @assistant-ui cluster should resolve a single shared tap version.')
|
||||
assert.ok(versions.size === 1, `@assistant-ui/tap resolves to multiple versions ${[...versions].sort()} — the cluster must share one tap line (see this test's docstring).`)
|
||||
|
||||
return [...versions][0]!
|
||||
}
|
||||
|
||||
test('every @assistant-ui/* package\'s tap requirement is satisfiable', () => {
|
||||
const packages = lockPackages()
|
||||
|
||||
if (Object.keys(packages).length === 0) {return} // lockfile not materialized
|
||||
|
||||
const tapVersion = sharedTapVersion(packages)
|
||||
|
||||
const offenders: string[] = []
|
||||
|
||||
for (const [key, meta] of Object.entries(packages)) {
|
||||
const idx = key.lastIndexOf('node_modules/')
|
||||
const name = idx >= 0 ? key.slice(idx + 'node_modules/'.length) : key
|
||||
if (!name.startsWith('@assistant-ui/') || name === TAP) continue
|
||||
|
||||
if (!name.startsWith('@assistant-ui/') || name === TAP) {continue}
|
||||
const peerMeta = (meta.peerDependenciesMeta ?? {})[TAP]
|
||||
if (peerMeta?.optional) continue
|
||||
|
||||
if (peerMeta?.optional) {continue}
|
||||
const spec = (meta.dependencies ?? {})[TAP] || (meta.peerDependencies ?? {})[TAP]
|
||||
if (!spec) continue
|
||||
|
||||
if (!spec) {continue}
|
||||
|
||||
if (!caretSatisfies(tapVersion, spec)) {
|
||||
offenders.push(`${name} requires ${TAP}"${spec}"`)
|
||||
}
|
||||
5
tests-js/eslint.config.mjs
Normal file
5
tests-js/eslint.config.mjs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import shared from '../eslint.config.shared.mjs'
|
||||
|
||||
export default [
|
||||
...shared
|
||||
]
|
||||
|
|
@ -35,7 +35,7 @@ import path from 'node:path'
|
|||
|
||||
import { test } from 'vitest'
|
||||
|
||||
const REPO_ROOT = path.resolve(__dirname, '..', '..', '..')
|
||||
const REPO_ROOT = path.resolve(__dirname, '..')
|
||||
const ROOT_PKG = path.join(REPO_ROOT, 'package.json')
|
||||
const ROOT_LOCK = path.join(REPO_ROOT, 'package-lock.json')
|
||||
|
||||
|
|
@ -72,6 +72,7 @@ test('root lockfile has no camofox entries', () => {
|
|||
// Some CI matrix shards skip lockfile materialization.
|
||||
return
|
||||
}
|
||||
|
||||
const text = fs.readFileSync(ROOT_LOCK, 'utf-8')
|
||||
assert.ok(
|
||||
!text.includes('@askjo/camofox-browser'),
|
||||
18
tests-js/package.json
Normal file
18
tests-js/package.json
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "@hermes/root-tests",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit -p tsconfig.json",
|
||||
"test": "vitest run",
|
||||
"check": "npm run typecheck && npm run test",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"fix": "npm run lint:fix"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^9.39.4",
|
||||
"typescript": "^6.0.3",
|
||||
"vitest": "^4.1.9"
|
||||
}
|
||||
}
|
||||
14
tests-js/tsconfig.json
Normal file
14
tests-js/tsconfig.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
8
tests-js/vitest.config.ts
Normal file
8
tests-js/vitest.config.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { defineConfig } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
environment: 'node',
|
||||
include: ['**/*.test.ts'],
|
||||
},
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue