Files
gui/pkgs/ui/shell/barrel.test.mjs
zooqueenandHanzo Dev 15d5989fa3 The plan is named only by its catalog row, or its slug
GET /v1/billing/tier's displayName names the tier class ("Pro" for every Max
rung), so it never names the plan. The removal guard also reads the emitted
declarations, where an aliased type re-export would hide.

Co-authored-by: Hanzo Dev <[email protected]>
2026-10-15 23:18:13 -07:00

242 lines
8.3 KiB
JavaScript

/**
* The barrel is this package's whole contract, and it is hand-written, so a
* rename lands in three places or it does not land: the file, the file's own
* export, and `index.ts`. Miss the third and `tsc` is happy — the module still
* compiles — while every consumer gets a name that no longer exists.
*
* So this reads `src/index.ts` and holds it against the sources it names:
* every module resolves, every named export exists there, and the retired names
* stay retired.
*
* node --test barrel.test.mjs
*/
import { test } from 'node:test'
import assert from 'node:assert/strict'
import { readFileSync, readdirSync, existsSync } from 'node:fs'
import { basename, join, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
const SRC = join(dirname(fileURLToPath(import.meta.url)), 'src')
const barrel = readFileSync(join(SRC, 'index.ts'), 'utf8')
/** Every `export { … } from './x'` / `export type { … } from './x'` clause. */
function clauses(source) {
const out = []
const re = /export\s+(type\s+)?\{([^}]*)\}\s+from\s+'\.\/([\w.-]+)'/g
for (const [, , names, module] of source.matchAll(re)) {
const specifiers = names
.split(',')
.map((n) => n.trim())
.filter(Boolean)
// `CommandItem as OrgCommandItem` — the LOCAL name is what must exist.
.map((n) => n.split(/\s+as\s+/)[0].replace(/^type\s+/, ''))
out.push({ module, specifiers })
}
return out
}
/** Names `file` declares as its own exports. */
function exported(file) {
const source = readFileSync(file, 'utf8')
const names = new Set()
const re =
/export\s+(?:declare\s+)?(?:async\s+)?(?:function|const|let|var|class|type|interface|enum)\s+([A-Za-z_$][\w$]*)/g
for (const [, name] of source.matchAll(re)) names.add(name)
return names
}
function resolve(module) {
// A specifier names its file, so the name is tried as written before the
// extensions a bare name would take.
for (const ext of ['', '.ts', '.tsx']) {
const file = join(SRC, module + ext)
if (existsSync(file)) return file
}
return null
}
const surface = clauses(barrel)
test('the barrel names at least the whole signed-in chrome', () => {
const all = surface.flatMap((c) => c.specifiers)
for (const name of [
'OrgHeader',
'OrgCommandPalette',
'UserOrgDropdown',
'HanzoAppLauncher',
'HanzoHeader',
'HanzoMark',
'ORG_DOMAINS',
]) {
assert.ok(all.includes(name), `${name} is missing from the barrel`)
}
})
test('every module the barrel re-exports from exists', () => {
for (const { module } of surface) {
assert.ok(resolve(module), `index.ts re-exports from missing ./${module}`)
}
})
test('every name the barrel re-exports is declared by that module', () => {
for (const { module, specifiers } of surface) {
const file = resolve(module)
if (!file) continue
const declared = exported(file)
for (const name of specifiers) {
assert.ok(
declared.has(name),
`index.ts exports ${name} from ./${module}, which does not declare it`
)
}
}
})
test('org, never tenant — the vocabulary law holds across the package', () => {
const offenders = readdirSync(SRC, { recursive: true })
.filter((f) => /\.tsx?$/.test(f))
.filter((f) => /tenant/i.test(readFileSync(join(SRC, f), 'utf8')))
assert.deepEqual(offenders, [], 'these files still say "tenant"')
})
test('the retired switcher and its stale app list stay retired', () => {
const all = surface.flatMap((c) => c.specifiers)
// AppSwitcher was a second, denser cross-app popover over a 26-app list that
// had drifted from the product. HanzoAppLauncher is the one switcher, over
// the one list (HANZO_APPS).
for (const name of [
'AppSwitcher',
'DEFAULT_TENANT_APPS',
'getAppsForOrg',
'useTenantAuth',
]) {
assert.ok(!all.includes(name), `${name} is exported again`)
assert.equal(resolve(name), null, `${name}.tsx is back on disk`)
}
})
test('the retired signed-in bars stay retired', () => {
const all = surface.flatMap((c) => c.specifiers)
// Three components drew the same 56px of signed-in chrome behind three sets
// of props. HanzoAppBar had no consumer; HanzoAppHeader's one consumer needed
// `search` and `account`, which OrgHeader now carries. OrgHeader is the bar.
for (const name of ['HanzoAppBar', 'HanzoAppHeader']) {
assert.ok(!all.includes(name), `${name} is exported again`)
assert.equal(resolve(name), null, `${name}.tsx is back on disk`)
}
for (const type of [
'HanzoAppBarProps',
'HanzoAppBarAction',
'HanzoAppHeaderProps',
'HanzoAppHeaderAction',
'HanzoContextNode',
]) {
assert.ok(!all.includes(type), `${type} is exported again`)
}
})
// Every name the static plan ladder exported. Plans are the catalog's
// (GET /v1/billing/plans) and the held one is billing's (GET /v1/billing/tier).
const LADDER = [
'HANZO_PLANS',
'HANZO_PLAN_TIERS',
'DEFAULT_PLAN_TIER',
'UNLIMITED',
'getPlanTier',
'APP_ENTITLEMENTS',
'HanzoPlanKind',
'HanzoPlanTier',
'HanzoPlanLimits',
'HanzoPlanTierDef',
'entitlementFor',
'isEntitled',
'requiredTier',
'rankOf',
'normalizeTier',
'useEntitlement',
'FREE_TIER',
'Entitlement',
'EntitlementState',
'UseEntitlementOptions',
'HanzoAccessGate',
'HanzoAccessGateProps',
'HanzoPlans',
'HanzoPlansProps',
]
test('the shell keeps no plan list of its own', async () => {
assert.equal(LADDER.length, 24)
const barrelNames = surface.flatMap((c) => c.specifiers)
const registryNames = exported(join(SRC, 'hanzo-registry.ts'))
// The built entries too: `.` and `./registry` are what a consumer imports.
const built = [
...Object.keys(await import('./dist/esm/index.js')),
...Object.keys(await import('./dist/esm/hanzo-registry.js')),
]
// The emitted declarations carry the type-only names the runtime keys cannot,
// under either side of an `as`.
const declared = new Set()
for (const file of ['index.d.ts', 'hanzo-registry.d.ts']) {
const dts = readFileSync(join(SRC, '..', 'types', file), 'utf8')
for (const [, names] of dts.matchAll(/export\s+(?:type\s+)?\{([^}]*)\}/g)) {
for (const n of names.split(/[\s,]+/))
if (n && n !== 'as' && n !== 'type') declared.add(n)
}
for (const [, n] of dts.matchAll(
/export\s+(?:declare\s+)?(?:const|let|function|class|type|interface|enum)\s+([A-Za-z_$][\w$]*)/g
)) {
declared.add(n)
}
}
assert.ok(
declared.has('OrgCommandItem') && declared.has('HanzoLink'),
'the declarations were not read'
)
for (const name of LADDER) {
assert.ok(!declared.has(name), `${name} is in the emitted declarations again`)
assert.ok(!barrelNames.includes(name), `${name} is exported from the barrel again`)
assert.ok(!registryNames.has(name), `${name} is exported from ./registry again`)
assert.ok(!built.includes(name), `${name} is in the built output again`)
}
const gone =
/^(HanzoPlans|HanzoAccessGate|StudioAccessGate|entitlements|useEntitlement)\.[cm]?[jt]sx?$/
const back = readdirSync(SRC, { recursive: true }).filter((f) =>
gone.test(basename(String(f)))
)
assert.deepEqual(back, [], 'a deleted plan module is back under src/')
})
test('the one signed-in bar carries the folded-in props', () => {
const source = readFileSync(join(SRC, 'OrgHeader.tsx'), 'utf8')
for (const prop of ['search', 'account', 'headerRight', 'currentApp']) {
assert.match(
source,
new RegExp(`^\\s+${prop}\\??:`, 'm'),
`OrgHeaderProps dropped ${prop}`
)
}
})
test('no source imports a module that no longer exists', () => {
// Naming a retired bar in prose is knowledge — this package keeps a record of
// WHY a thing went. Importing one is a build break, so that is what is held.
const offenders = readdirSync(SRC, { recursive: true })
.filter((f) => /\.tsx?$/.test(f))
.filter((f) => {
const source = readFileSync(join(SRC, f), 'utf8')
return [...source.matchAll(/from\s+'\.\/([\w.-]+)'/g)].some(
([, module]) => !resolve(module)
)
})
assert.deepEqual(offenders, [], 'these files import a missing module')
})
test('HANZO_APPS is the only app registry left in the package', () => {
const lists = readdirSync(SRC, { recursive: true })
.filter((f) => /\.tsx?$/.test(f))
.filter((f) =>
/^export const [A-Z_]*APPS\b/m.test(readFileSync(join(SRC, f), 'utf8'))
)
assert.deepEqual(lists, ['hanzo-apps.tsx'])
})