mirror of
https://github.com/stablyai/orca.git
synced 2026-09-23 00:02:29 +00:00
255 lines
8.7 KiB
TypeScript
255 lines
8.7 KiB
TypeScript
import { readdirSync, readFileSync, statSync } from 'node:fs'
|
|
import { join, relative } from 'node:path'
|
|
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
FEATURE_INTERACTIONS,
|
|
FEATURE_INTERACTION_CATEGORIES,
|
|
FEATURE_INTERACTION_CATEGORY_BY_ID,
|
|
FEATURE_INTERACTION_USAGE_BUCKETS,
|
|
getFeatureInteractionUsageBucket,
|
|
hasFeatureInteraction,
|
|
normalizeFeatureInteractionTelemetryBuckets,
|
|
normalizeFeatureInteractions,
|
|
type FeatureInteractionId
|
|
} from './feature-interactions'
|
|
import { escapeRegex } from './string-utils'
|
|
|
|
type DefinedFeatureInteractionId = (typeof FEATURE_INTERACTIONS)[number]['id']
|
|
type MissingFeatureInteractionId = Exclude<FeatureInteractionId, DefinedFeatureInteractionId>
|
|
type ExtraFeatureInteractionId = Exclude<DefinedFeatureInteractionId, FeatureInteractionId>
|
|
|
|
const REPO_ROOT = join(__dirname, '..', '..')
|
|
const SOURCE_ROOTS = ['src/main', 'src/renderer/src', 'src/preload']
|
|
const PRODUCTION_FILE_PATTERN = /\.(ts|tsx)$/
|
|
const TEST_FILE_PATTERN = /(?:^|\.)(test|spec)\.(ts|tsx)$/
|
|
|
|
describe('feature interactions', () => {
|
|
it('defines local interaction semantics for product education features', () => {
|
|
const catalogMatchesPublicUnion: [
|
|
MissingFeatureInteractionId,
|
|
ExtraFeatureInteractionId
|
|
] extends [never, never]
|
|
? true
|
|
: never = true
|
|
const expectedIds: FeatureInteractionId[] = [
|
|
'workspace-board',
|
|
'workspace-agent-sessions',
|
|
'workspace-board-actions',
|
|
'cmd-j',
|
|
'cmd-j-workspace-open',
|
|
'cmd-j-browser-page-open',
|
|
'cmd-j-settings-open',
|
|
'cmd-j-quick-action',
|
|
'cmd-j-create-workspace',
|
|
'browser',
|
|
'client-hosted-browser',
|
|
'browser-tab-created',
|
|
'tasks',
|
|
'github-tasks',
|
|
'gitlab-tasks',
|
|
'linear-tasks',
|
|
'jira-tasks',
|
|
'automations',
|
|
'automation-created',
|
|
'automation-run',
|
|
'browser-annotations',
|
|
'browser-annotations-sent-to-agent',
|
|
'browser-grab',
|
|
'markdown-file-created',
|
|
'workspace-creation',
|
|
'agent-browser-setup',
|
|
'agent-browser-use',
|
|
'ephemeral-vm-setup',
|
|
'agent-orchestration-setup',
|
|
'agent-orchestration',
|
|
'mobile-emulator-agent-setup',
|
|
'ai-commit-generation',
|
|
'ai-pr-generation',
|
|
'claude-account-switching',
|
|
'computer-use-setup',
|
|
'computer-use',
|
|
'codex-account-switching',
|
|
'cookie-import',
|
|
'floating-workspace',
|
|
'floating-workspace-hidden',
|
|
'mobile-pairing',
|
|
'notifications',
|
|
'ports',
|
|
'quick-commands',
|
|
'resource-manager',
|
|
'review-notes',
|
|
'ssh',
|
|
'terminal-pane-split',
|
|
'terminal-panes',
|
|
'terminal-tabs',
|
|
'tab-splits',
|
|
'usage-tracking',
|
|
'voice-dictation',
|
|
'workspace-cleanup'
|
|
]
|
|
|
|
expect(catalogMatchesPublicUnion).toBe(true)
|
|
expect(FEATURE_INTERACTIONS.map((feature) => feature.id)).toEqual(expectedIds)
|
|
for (const feature of FEATURE_INTERACTIONS) {
|
|
expect(feature.interaction.length).toBeGreaterThan(0)
|
|
}
|
|
})
|
|
|
|
it('normalizes persisted records by removing unknown ids and malformed values', () => {
|
|
expect(
|
|
normalizeFeatureInteractions({
|
|
tasks: { firstInteractedAt: 100 },
|
|
browser: { firstInteractedAt: Number.NaN },
|
|
automations: { firstInteractedAt: 200, interactionCount: 3 },
|
|
'browser-grab': { firstInteractedAt: 250, interactionCount: 0 },
|
|
unknown: { firstInteractedAt: 200 },
|
|
'voice-dictation': { firstInteractedAt: 300 }
|
|
})
|
|
).toEqual({
|
|
tasks: { firstInteractedAt: 100, interactionCount: 1 },
|
|
automations: { firstInteractedAt: 200, interactionCount: 3 },
|
|
'browser-grab': { firstInteractedAt: 250, interactionCount: 1 },
|
|
'voice-dictation': { firstInteractedAt: 300, interactionCount: 1 }
|
|
})
|
|
})
|
|
|
|
it('treats only valid known records as interacted', () => {
|
|
expect(
|
|
hasFeatureInteraction({ tasks: { firstInteractedAt: 100, interactionCount: 1 } }, 'tasks')
|
|
).toBe(true)
|
|
expect(
|
|
hasFeatureInteraction({ tasks: { firstInteractedAt: 100, interactionCount: 1 } }, 'browser')
|
|
).toBe(false)
|
|
expect(
|
|
hasFeatureInteraction(
|
|
{ tasks: { firstInteractedAt: Number.POSITIVE_INFINITY, interactionCount: 1 } },
|
|
'tasks'
|
|
)
|
|
).toBe(false)
|
|
})
|
|
|
|
it('maps interaction counts to the exact top-coded telemetry buckets', () => {
|
|
expect(FEATURE_INTERACTION_USAGE_BUCKETS).toEqual([
|
|
'count_1',
|
|
'count_2',
|
|
'count_3_4',
|
|
'count_5_9',
|
|
'count_10_19',
|
|
'count_20_49',
|
|
'count_50_99',
|
|
'count_100_199',
|
|
'count_200_499',
|
|
'count_500_999',
|
|
'count_1000_plus'
|
|
])
|
|
expect(getFeatureInteractionUsageBucket(0)).toBeNull()
|
|
expect(getFeatureInteractionUsageBucket(1)).toBe('count_1')
|
|
expect(getFeatureInteractionUsageBucket(2)).toBe('count_2')
|
|
expect(getFeatureInteractionUsageBucket(3)).toBe('count_3_4')
|
|
expect(getFeatureInteractionUsageBucket(4)).toBe('count_3_4')
|
|
expect(getFeatureInteractionUsageBucket(5)).toBe('count_5_9')
|
|
expect(getFeatureInteractionUsageBucket(999)).toBe('count_500_999')
|
|
expect(getFeatureInteractionUsageBucket(1000)).toBe('count_1000_plus')
|
|
expect(getFeatureInteractionUsageBucket(1001)).toBe('count_1000_plus')
|
|
})
|
|
|
|
it('covers every feature id with a telemetry category', () => {
|
|
expect(FEATURE_INTERACTION_CATEGORIES).toEqual([
|
|
'workspace',
|
|
'agent',
|
|
'browser',
|
|
'launcher',
|
|
'task_management',
|
|
'notes',
|
|
'review',
|
|
'setup',
|
|
'settings',
|
|
'automation',
|
|
'terminal',
|
|
'collaboration',
|
|
'resource_management',
|
|
'voice',
|
|
'source_control'
|
|
])
|
|
expect(Object.keys(FEATURE_INTERACTION_CATEGORY_BY_ID).sort()).toEqual(
|
|
FEATURE_INTERACTIONS.map((feature) => feature.id).sort()
|
|
)
|
|
expect(FEATURE_INTERACTION_CATEGORY_BY_ID.tasks).toBe('task_management')
|
|
expect(FEATURE_INTERACTION_CATEGORY_BY_ID['github-tasks']).toBe('task_management')
|
|
expect(FEATURE_INTERACTION_CATEGORY_BY_ID['jira-tasks']).toBe('task_management')
|
|
expect(FEATURE_INTERACTION_CATEGORY_BY_ID['markdown-file-created']).toBe('notes')
|
|
expect(FEATURE_INTERACTION_CATEGORY_BY_ID['agent-browser-setup']).toBe('setup')
|
|
expect(FEATURE_INTERACTION_CATEGORY_BY_ID['terminal-tabs']).toBe('terminal')
|
|
expect(FEATURE_INTERACTION_CATEGORY_BY_ID['voice-dictation']).toBe('voice')
|
|
expect(FEATURE_INTERACTION_CATEGORY_BY_ID['ai-commit-generation']).toBe('source_control')
|
|
expect(FEATURE_INTERACTION_CATEGORY_BY_ID['resource-manager']).toBe('resource_management')
|
|
})
|
|
|
|
it('normalizes persisted telemetry bucket markers by removing unknown ids and buckets', () => {
|
|
expect(
|
|
normalizeFeatureInteractionTelemetryBuckets({
|
|
tasks: 'count_1',
|
|
browser: 'count_1000_plus',
|
|
automations: 'count_4',
|
|
unknown: 'count_1',
|
|
'voice-dictation': null
|
|
})
|
|
).toEqual({
|
|
tasks: 'count_1',
|
|
browser: 'count_1000_plus'
|
|
})
|
|
})
|
|
|
|
it('keeps every catalog id wired to a production writer', () => {
|
|
const productionText = collectProductionSourceText()
|
|
const missingWriters = FEATURE_INTERACTIONS.map((feature) => feature.id).filter((id) => {
|
|
const escaped = escapeRegex(id)
|
|
const directRecord = new RegExp(
|
|
`recordFeatureInteraction(?:\\?\\.)?\\(\\s*['"]${escaped}['"]`
|
|
)
|
|
const contextualTourRecord = new RegExp(`useContextualTour\\(\\s*['"]${escaped}['"]`)
|
|
const runtimeMappingReturn = new RegExp(`return[^\\n]*['"]${escaped}['"]`)
|
|
return (
|
|
!directRecord.test(productionText) &&
|
|
!contextualTourRecord.test(productionText) &&
|
|
!runtimeMappingReturn.test(productionText)
|
|
)
|
|
})
|
|
|
|
expect(missingWriters).toEqual([])
|
|
}, 15_000)
|
|
})
|
|
|
|
function collectProductionSourceText(): string {
|
|
const files = SOURCE_ROOTS.flatMap((root) => collectSourceFiles(join(REPO_ROOT, root)))
|
|
return files
|
|
.sort()
|
|
.map((file) => readFileSync(file, 'utf8'))
|
|
.join('\n')
|
|
}
|
|
|
|
function collectSourceFiles(directory: string): string[] {
|
|
const files: string[] = []
|
|
for (const entry of readdirSync(directory)) {
|
|
const path = join(directory, entry)
|
|
const stats = statSync(path)
|
|
if (stats.isDirectory()) {
|
|
if (entry === 'node_modules' || entry === 'dist' || entry === 'out') {
|
|
continue
|
|
}
|
|
files.push(...collectSourceFiles(path))
|
|
continue
|
|
}
|
|
const repoRelativePath = relative(REPO_ROOT, path)
|
|
if (!PRODUCTION_FILE_PATTERN.test(entry) || TEST_FILE_PATTERN.test(entry)) {
|
|
continue
|
|
}
|
|
// Why: the catalog itself proves the id exists, not that runtime code writes it.
|
|
if (repoRelativePath === 'src/shared/feature-interactions.ts') {
|
|
continue
|
|
}
|
|
files.push(path)
|
|
}
|
|
return files
|
|
}
|