/** * module-availability.test.ts — the generated `useModuleAvailability` primitive and the * package-version floor that decides which shape it takes. */ import { describe, it, expect } from 'vitest'; import { generate } from '../generate.js'; import { meetsFloor, parseVersionTriple, MODULE_AVAILABILITY_MIN_VERSION } from '../version.js'; import type { ScaffoldUiPrimitivesInput } from '../types.js'; function fileFor(version?: string): string { const spec = { projectPath: '/tmp/app', appCode: 'demo', force: false, smartstackVersion: version, } as ScaffoldUiPrimitivesInput; const file = generate(spec).find((f) => f.path === 'src/components/ui/useModuleAvailability.ts'); expect(file).toBeDefined(); return file!.content; } describe('version floor', () => { it('reads a triple out of every shape a dependency range takes', () => { expect(parseVersionTriple('^3.64.0')).toEqual([3, 64, 0]); expect(parseVersionTriple('~3.64.1')).toEqual([3, 64, 1]); expect(parseVersionTriple('3.64.0-localtest18')).toEqual([3, 64, 0]); expect(parseVersionTriple('workspace:*')).toBeNull(); expect(parseVersionTriple(undefined)).toBeNull(); }); it('accepts the floor itself and anything above', () => { expect(meetsFloor('3.65.0', '3.65.0')).toBe(true); expect(meetsFloor('^3.65.1', '3.65.0')).toBe(true); expect(meetsFloor('4.0.0', '3.65.0')).toBe(true); }); it('rejects anything below — including a higher patch of a lower minor', () => { expect(meetsFloor('3.64.99', '3.65.0')).toBe(false); expect(meetsFloor('2.99.99', '3.65.0')).toBe(false); }); it('ignores the pre-release suffix, because this project installs -localtest builds', () => { expect(meetsFloor('3.65.0-localtest1', '3.65.0')).toBe(true); }); it('treats an unreadable range as too old — never emit an import that may not resolve', () => { expect(meetsFloor(undefined, '3.65.0')).toBe(false); expect(meetsFloor('workspace:*', '3.65.0')).toBe(false); }); }); describe('generated useModuleAvailability primitive', () => { it('is emitted, always', () => { expect(generate({ projectPath: '/tmp/app', appCode: 'demo', force: false } as ScaffoldUiPrimitivesInput) .map((f) => f.path)).toContain('src/components/ui/useModuleAvailability.ts'); }); it('adapts the package hook when the floor is met', () => { const content = fileFor(MODULE_AVAILABILITY_MIN_VERSION); expect(content).toContain("export { useModuleAvailability } from '@atlashub/smartstack'"); expect(content).not.toContain('PERMISSIVE STUB'); }); it('falls back to a permissive stub below the floor — never a broken import', () => { const content = fileFor('3.64.0-localtest18'); expect(content).toContain('PERMISSIVE STUB'); expect(content).not.toContain("from '@atlashub/smartstack'"); expect(content).toContain('hasModule: () => true'); expect(content).toContain('ss upgrade'); }); it('exposes the same surface in both shapes, so pages never change', () => { for (const content of [fileFor(MODULE_AVAILABILITY_MIN_VERSION), fileFor('1.0.0')]) { expect(content).toContain('useModuleAvailability'); expect(content).toContain('ModuleAvailability'); } }); it('never reaches for useLicense — a different question entirely', () => { expect(fileFor('1.0.0')).not.toContain('useLicense'); expect(fileFor(MODULE_AVAILABILITY_MIN_VERSION)).not.toContain('useLicense'); }); });