Files
simstudioai--sim/apps/sim/lib/copilot/vfs/operations.test.ts
T
wehub-resource-sync d25d482dc2
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:20:55 +08:00

141 lines
5.3 KiB
TypeScript

/**
* @vitest-environment node
*/
import { describe, expect, it } from 'vitest'
import { glob, grep } from '@/lib/copilot/vfs/operations'
function vfsFromEntries(entries: [string, string][]): Map<string, string> {
return new Map(entries)
}
describe('glob', () => {
it('matches nested file metadata paths with a single-star segment', () => {
const files = vfsFromEntries([
['files/Reports/q1.csv/meta.json', '{}'],
['files/data.csv/meta.json', '{}'],
])
const hits = glob(files, 'files/Reports/*/meta.json')
expect(hits).toContain('files/Reports/q1.csv/meta.json')
expect(hits).not.toContain('files/data.csv/meta.json')
})
it('matches one path segment for single star (files listing pattern)', () => {
const files = vfsFromEntries([
['files/a/meta.json', '{}'],
['files/a/b/meta.json', '{}'],
['uploads/x.png', ''],
])
const hits = glob(files, 'files/*/meta.json')
expect(hits).toContain('files/a/meta.json')
expect(hits).not.toContain('files/a/b/meta.json')
})
it('matches nested paths with double star', () => {
const files = vfsFromEntries([
['workflows/W/state.json', ''],
['workflows/W/sub/state.json', ''],
])
const hits = glob(files, 'workflows/**/state.json')
expect(hits.sort()).toEqual(['workflows/W/state.json', 'workflows/W/sub/state.json'].sort())
})
it('includes virtual directory prefixes when pattern matches descendants', () => {
const files = vfsFromEntries([['files/a/meta.json', '{}']])
const hits = glob(files, 'files/**')
expect(hits).toContain('files')
expect(hits).toContain('files/a')
expect(hits).toContain('files/a/meta.json')
})
it('treats braces literally when nobrace is set (matches old builder)', () => {
const files = vfsFromEntries([
['weird{brace}/x', ''],
['weirdA/x', ''],
])
const hits = glob(files, 'weird{brace}/*')
expect(hits).toContain('weird{brace}/x')
expect(hits).not.toContain('weirdA/x')
})
})
describe('grep', () => {
it('returns content matches per line in default mode', () => {
const files = vfsFromEntries([['a.txt', 'hello\nworld\nhello']])
const matches = grep(files, 'hello', undefined, { outputMode: 'content' })
expect(matches).toHaveLength(2)
expect(matches[0]).toMatchObject({ path: 'a.txt', line: 1, content: 'hello' })
expect(matches[1]).toMatchObject({ path: 'a.txt', line: 3, content: 'hello' })
})
it('strips CR before end-of-line matching on CRLF content', () => {
const files = vfsFromEntries([['x.txt', 'foo\r\n']])
const matches = grep(files, 'foo$', undefined, { outputMode: 'content' })
expect(matches).toHaveLength(1)
expect(matches[0]?.content).toBe('foo')
})
it('counts matching lines', () => {
const files = vfsFromEntries([['a.txt', 'a\nb\na']])
const counts = grep(files, 'a', undefined, { outputMode: 'count' })
expect(counts).toEqual([{ path: 'a.txt', count: 2 }])
})
it('files_with_matches scans whole file (can match across newlines with dot-all style pattern)', () => {
const files = vfsFromEntries([['a.txt', 'foo\nbar']])
const multiline = grep(files, 'foo[\\s\\S]*bar', undefined, {
outputMode: 'files_with_matches',
})
expect(multiline).toContain('a.txt')
const lineOnly = grep(files, 'foo[\\s\\S]*bar', undefined, { outputMode: 'content' })
expect(lineOnly).toHaveLength(0)
})
it('treats trailing slash on directory scope like grep (files/ matches files/foo)', () => {
const files = vfsFromEntries([
['files/TEST BOY.md/meta.json', '"name": "TEST BOY.md"'],
['workflows/x', 'TEST BOY'],
])
const hits = grep(files, 'TEST BOY', 'files/', { outputMode: 'files_with_matches' })
expect(hits).toContain('files/TEST BOY.md/meta.json')
expect(hits).not.toContain('workflows/x')
})
it('scopes to directory prefix without matching unrelated prefixes', () => {
const files = vfsFromEntries([
['workflows/a/x', 'needle'],
['workflowsManual/x', 'needle'],
])
const hits = grep(files, 'needle', 'workflows', { outputMode: 'files_with_matches' })
expect(hits).toContain('workflows/a/x')
expect(hits).not.toContain('workflowsManual/x')
})
it('treats scope with literal brackets as directory prefix, not a glob character class', () => {
const files = vfsFromEntries([['weird[bracket]/x.txt', 'needle']])
const hits = grep(files, 'needle', 'weird[bracket]', { outputMode: 'files_with_matches' })
expect(hits).toContain('weird[bracket]/x.txt')
})
it('scopes with glob pattern when path contains metacharacters', () => {
const files = vfsFromEntries([
['workflows/A/state.json', '{"x":1}'],
['workflows/B/sub/state.json', '{"x":1}'],
['workflows/C/other.json', '{"x":1}'],
])
const hits = grep(files, '1', 'workflows/*/state.json', { outputMode: 'files_with_matches' })
expect(hits).toEqual(['workflows/A/state.json'])
})
it('returns empty array for invalid regex pattern', () => {
const files = vfsFromEntries([['a.txt', 'x']])
expect(grep(files, '(unclosed', undefined, { outputMode: 'content' })).toEqual([])
})
it('respects ignoreCase', () => {
const files = vfsFromEntries([['a.txt', 'Hello']])
const hits = grep(files, 'hello', undefined, { outputMode: 'content', ignoreCase: true })
expect(hits).toHaveLength(1)
})
})