OverviewFilesCommitsIssuesDocsReleasesPeers

codetree / test / sections.test.js

141 lines · javascript ref:
import { test } from 'brittle'
import { resolve, dirname } from 'path'
import { fileURLToPath } from 'url'
import { scan } from '../lib/scanner.js'
import { buildSections } from '../lib/sections.js'

const HERE = dirname(fileURLToPath(import.meta.url))
const FIXTURE = resolve(HERE, 'fixtures/connectors')

// The fixture's ground truth, by construction:
//
//   index.js    -alpha(lib/a.js)  -beta(lib/b.js)   +alpha(re-export) +entry
//   lib/a.js    -shared(util.js)                    +alpha +neverUsed
//   lib/b.js    -shared(util.js)  -missingThing(./nope.js MISSING)  +beta
//   lib/util.js                                     +shared +alsoDead
//
// So: neverUsed and alsoDead are DEAD (+ that nothing takes).
//     missingThing is UNRESOLVED (- that nothing supplies).
//     alpha and entry are the module's public boundary, NOT dead.

test('sections: every file becomes a germ with connectors', async (t) => {
  const tree = await scan(FIXTURE)
  const index = buildSections(tree)

  const files = index.sections.filter(s => s.scale === 'file')
  t.is(files.length, 6, 'six file germs')

  const names = files.map(s => s.germ.name).sort()
  t.alike(names, ['examples/demo.js', 'index.js', 'lib/a.js', 'lib/b.js', 'lib/dyn.js', 'lib/util.js'], 'germ names')
})

test('sections: imports are minus connectors, exports are plus', async (t) => {
  const tree = await scan(FIXTURE)
  const index = buildSections(tree)

  const util = index.sections.find(s => s.germ.name === 'lib/util.js')
  const plus = util.conns.filter(c => c.pol === '+').map(c => c.type).sort()
  t.alike(plus, ['alsoDead', 'shared'], 'util supplies shared + alsoDead')
  t.is(util.conns.filter(c => c.pol === '-').length, 0, 'util demands nothing')

  const b = index.sections.find(s => s.germ.name === 'lib/b.js')
  const minus = b.conns.filter(c => c.pol === '-').map(c => c.type).sort()
  t.alike(minus, ['missingThing', 'shared'], 'b demands shared + missingThing')
})

test('sections: mating pairs a minus with the plus that supplies it', async (t) => {
  const tree = await scan(FIXTURE)
  const index = buildSections(tree)

  const a = index.sections.find(s => s.germ.name === 'lib/a.js')
  const sharedDemand = a.conns.find(c => c.pol === '-' && c.type === 'shared')
  t.ok(sharedDemand.mate, 'a.js -shared is mated')

  const mate = index.connectorById(sharedDemand.mate)
  t.is(mate.pol, '+', 'mated to a supply')
  t.is(mate.type, 'shared', 'same type')
  t.is(mate.germId, 'file:connectors-fixture:lib/util.js', 'supplied by util.js')
})

test('sections: unmated plus on a non-entry file is a DEAD export', async (t) => {
  const tree = await scan(FIXTURE)
  const index = buildSections(tree)

  const dead = index.misfits().dead.map(m => m.type).sort()
  t.alike(dead, ['alsoDead', 'demo', 'neverUsed'], 'exactly the dead exports')
})

test('sections: entry-file exports are public boundary, never dead', async (t) => {
  const tree = await scan(FIXTURE)
  const index = buildSections(tree)

  const dead = index.misfits().dead.map(m => m.type)
  t.absent(dead.includes('entry'), 'entry is public API, not dead')
  t.absent(dead.includes('alpha'), 're-exported alpha is public API, not dead')
})

test('sections: unmated minus is an UNRESOLVED demand', async (t) => {
  const tree = await scan(FIXTURE)
  const index = buildSections(tree)

  const unresolved = index.misfits().unresolved.map(m => m.type)
  t.alike(unresolved, ['missingThing'], 'the one import that resolves to nothing')
})

test('sections: a dynamically imported export is mated, not dead', async (t) => {
  const tree = await scan(FIXTURE)
  const index = buildSections(tree)

  // index.js reaches lib/dyn.js only via `await import('./lib/dyn.js')`.
  // A parser that sees static imports only would call lazyThing a dead export.
  const dead = index.misfits().dead.map(m => m.type)
  t.absent(dead.includes('lazyThing'), 'lazyThing is imported dynamically, not dead')

  const dyn = index.sections.find(s => s.germ.name === 'lib/dyn.js')
  const supply = dyn.conns.find(c => c.pol === '+' && c.type === 'lazyThing')
  t.is(supply.mates.length, 1, 'mated to exactly one demand')
})

test('sections: a devDependency used in examples/tests is not a leak', async (t) => {
  const tree = await scan(FIXTURE)
  const index = buildSections(tree)
  const m = index.misfits()

  // examples/demo.js imports brittle (a devDependency) and b4a (a dependency).
  // Neither is missing — reading only `dependencies` would report brittle leaked.
  const leaked = m.leak.map(l => l.specifier)
  t.absent(leaked.includes('brittle'), 'brittle is declared in devDependencies')
  t.absent(leaked.includes('b4a'), 'b4a is declared in dependencies')

  const unused = m.unused.map(u => u.specifier)
  t.absent(unused.includes('brittle'), 'brittle is imported, so not unused')
  t.absent(unused.includes('b4a'), 'b4a is imported, so not unused')
})

test('sections: a re-export is a demand AND a supply', async (t) => {
  const tree = await scan(FIXTURE)
  const index = buildSections(tree)

  // index.js does `export { shared } from './lib/util.js'` and imports nothing.
  // That is sugar for import-then-export, so it must create a demand too —
  // otherwise nothing ever reaches util.js and it reads as an orphan.
  const entry = index.sections.find(s => s.germ.name === 'index.js')
  const demand = entry.conns.find(c => c.pol === '-' && c.type === 'shared')
  t.ok(demand, 'the re-export creates a demand')
  t.ok(demand && demand.mate, 'and that demand is mated to util.js')

  const supply = entry.conns.find(c => c.pol === '+' && c.type === 'shared')
  t.ok(supply && supply.reExport, 'it is still a supply, marked as a pass-through')
})

test('sections: a disjunct string round-trips the germ', async (t) => {
  const tree = await scan(FIXTURE)
  const index = buildSections(tree)

  const b = index.sections.find(s => s.germ.name === 'lib/b.js')
  const d = index.disjunct(b)
  t.ok(d.includes('-shared'), 'shows the shared demand')
  t.ok(d.includes('-missingThing'), 'shows the missing demand')
  t.ok(d.includes('+beta'), 'shows the beta supply')
})