OverviewFilesCommitsIssuesDocsReleasesPeers

codetree / test / scan-stream.test.js

135 lines · javascript ref:
import { test } from 'brittle'
import { resolve, dirname } from 'path'
import { fileURLToPath } from 'url'
import { scanStream } from '../lib/scan-stream.js'
import { TreeReducer } from '../lib/reducers/tree-reducer.js'

const HERE = dirname(fileURLToPath(import.meta.url))
const HYPER_FLEEKY = resolve(HERE, '../../')

test('scan-stream: emits scan-start before any other event', async (t) => {
  const seen = []
  const target = resolve(HYPER_FLEEKY, 'hyper-orgs')
  await new Promise((resolveP, reject) => {
    const src = scanStream(target)
    for (const ev of ['scan-start', 'module-start', 'file-discovered', 'file-parsed', 'import-edge', 'module-end', 'scan-end']) {
      src.on(ev, () => { if (!seen.includes(ev)) seen.push(ev) })
    }
    src.once('scan-end', resolveP)
    src.once('error', reject)
  })

  t.is(seen[0], 'scan-start', 'scan-start fires first')
  t.ok(seen.includes('module-start'), 'module-start fired')
  t.ok(seen.includes('file-discovered'), 'file-discovered fired')
  t.ok(seen.includes('file-parsed'), 'file-parsed fired')
  t.is(seen[seen.length - 1], 'scan-end', 'scan-end fires last')
})

test('scan-stream: caller can attach listeners before walk starts', async (t) => {
  // The walk is deferred via queueMicrotask, so listeners attached synchronously
  // after scanStream() returns must still see scan-start.
  const src = scanStream(resolve(HYPER_FLEEKY, 'hyper-orgs'))
  let gotStart = false

  await new Promise((resolve) => {
    src.on('scan-start', () => { gotStart = true })
    src.once('scan-end', resolve)
  })

  t.ok(gotStart, 'listeners attached after scanStream() still see scan-start')
})

test('scan-stream: TreeReducer rebuilds the same shape as walkGraph', async (t) => {
  const target = resolve(HYPER_FLEEKY, 'hyper-orgs')
  const graph = await new Promise((resolveP, reject) => {
    const src = scanStream(target)
    const tree = new TreeReducer().attach(src)
    src.once('scan-end', () => resolveP(tree.finalize()))
    src.once('error', reject)
  })

  t.ok(graph.modules.size === 1, '1 module')
  t.ok(graph.modules.has('hyper-orgs'), 'has hyper-orgs')
  t.ok(graph.root.endsWith('hyper-orgs'), 'root is correct')
  t.is(graph.projectType, 'js', 'project type detected')

  const mod = graph.modules.get('hyper-orgs')
  t.ok(mod.entry, 'has entry')
  t.ok(mod.files.size > 0, 'has files')
})

test('scan-stream: multiple subscribers see the same events', async (t) => {
  let aCount = 0
  let bCount = 0

  const target = resolve(HYPER_FLEEKY, 'hyper-orgs')
  await new Promise((resolveP) => {
    const src = scanStream(target)
    src.on('file-discovered', () => aCount++)
    src.on('file-discovered', () => bCount++)
    src.once('scan-end', resolveP)
  })

  t.is(aCount, bCount, 'both subscribers received same number of events')
  t.ok(aCount > 0, 'at least one file event fired')
})

test('scan-stream: shallow mode skips internal files and import resolution', async (t) => {
  // hyper-orgs has several files in lib/; shallow mode should only emit events
  // for the entry file (index.js) and emit no import edges.
  const target = resolve(HYPER_FLEEKY, 'hyper-orgs')
  const discoveredFiles = []
  const edges = []
  let shallowFlag = null

  await new Promise((resolveP) => {
    const src = scanStream(target, { shallow: true })
    src.on('scan-start', e => { shallowFlag = e.shallow })
    src.on('file-discovered', e => discoveredFiles.push(e.relInPkg))
    src.on('import-edge', e => edges.push(e))
    src.once('scan-end', resolveP)
  })

  t.is(shallowFlag, true, 'scan-start carries shallow: true')
  t.is(edges.length, 0, 'no import edges in shallow mode')
  t.ok(discoveredFiles.length <= 2, `at most 2 files discovered (got ${discoveredFiles.length}: ${discoveredFiles.join(', ')})`)
  // The entry file should be among the discovered files
  t.ok(discoveredFiles.some(f => f === 'index.js'), 'entry file (index.js) discovered')
})

test('scan-stream: shallow mode is fast on a multi-module scan', async (t) => {
  // Rough timing assertion — shallow scans of a multi-project root should
  // complete in well under 10s. This is the whole reason shallow exists.
  const start = Date.now()
  let moduleCount = 0

  await new Promise((resolveP) => {
    const src = scanStream(HYPER_FLEEKY, { shallow: true, maxDepth: 1 })
    src.on('module-start', () => moduleCount++)
    src.once('scan-end', resolveP)
  })

  const duration = Date.now() - start
  t.ok(moduleCount > 10, `found ${moduleCount} modules`)
  t.ok(duration < 10000, `shallow scan completed in ${duration}ms (< 10s)`)
})

test('scan-stream: emits import-edge events for resolved local imports', async (t) => {
  const edges = []

  const target = resolve(HYPER_FLEEKY, 'hyper-orgs')
  await new Promise((resolveP) => {
    const src = scanStream(target)
    src.on('import-edge', e => edges.push(e))
    src.once('scan-end', resolveP)
  })

  t.ok(edges.length > 0, `at least one import edge (got ${edges.length})`)
  for (const e of edges) {
    t.ok(e.fromRelInPkg, 'edge has from')
    t.ok(e.resolvedRelPath, 'edge has resolved path')
    t.ok(e.specifier, 'edge has specifier')
  }
})