import fs from 'fs' import path from 'path' import { describe, expect, it } from 'vitest' /** * Test suite to verify that shim files exist in the dist folder and all exports are accessible. * This ensures that treeshaking doesn't break the public API exposed through shim files. * * These are static file tests that don't require the browser or ComfyUI backend to be running. */ const DIST_DIR = path.join(__dirname, '../../dist-treeshake-enabled') const SCRIPTS_DIR = path.join(DIST_DIR, 'scripts') // Skip these tests if dist folder doesn't exist (e.g., during development before build) const distExists = fs.existsSync(DIST_DIR) describe.skipIf(!distExists)('Shim Files Exports', () => { describe('Core shim files should exist', () => { const coreShimFiles = [ 'api.js', 'app.js', 'changeTracker.js', 'defaultGraph.js', 'domWidget.js', 'pnginfo.js', 'ui.js', 'utils.js', 'widgets.js' ] coreShimFiles.forEach((file) => { it(`${file} should exist`, () => { const filePath = path.join(SCRIPTS_DIR, file) expect(fs.existsSync(filePath)).toBe(true) }) }) }) describe('API shim file exports', () => { it('should contain all required exports', () => { const filePath = path.join(SCRIPTS_DIR, 'api.js') const content = fs.readFileSync(filePath, 'utf-8') expect(content).toContain('// Shim for scripts/api.ts') expect(content).toContain('export const UnauthorizedError') expect(content).toContain('export const PromptExecutionError') expect(content).toContain('export const ComfyApi') expect(content).toContain('export const api') expect(content).toContain('window.comfyAPI.api') }) }) describe('App shim file exports', () => { it('should contain all required exports', () => { const filePath = path.join(SCRIPTS_DIR, 'app.js') const content = fs.readFileSync(filePath, 'utf-8') expect(content).toContain('// Shim for scripts/app.ts') expect(content).toContain('export const ANIM_PREVIEW_WIDGET') expect(content).toContain('export const ComfyApp') expect(content).toContain('export const app') expect(content).toContain('window.comfyAPI.app') }) }) describe('ChangeTracker shim file exports', () => { it('should contain all required exports', () => { const filePath = path.join(SCRIPTS_DIR, 'changeTracker.js') const content = fs.readFileSync(filePath, 'utf-8') expect(content).toContain('// Shim for scripts/changeTracker.ts') expect(content).toContain('export const ChangeTracker') expect(content).toContain('window.comfyAPI.changeTracker') }) }) describe('DefaultGraph shim file exports', () => { it('should contain all required exports', () => { const filePath = path.join(SCRIPTS_DIR, 'defaultGraph.js') const content = fs.readFileSync(filePath, 'utf-8') expect(content).toContain('// Shim for scripts/defaultGraph.ts') expect(content).toContain('export const defaultGraph') expect(content).toContain('export const defaultGraphJSON') expect(content).toContain('export const blankGraph') expect(content).toContain('window.comfyAPI.defaultGraph') }) }) describe('DomWidget shim file exports', () => { it('should contain all required exports', () => { const filePath = path.join(SCRIPTS_DIR, 'domWidget.js') const content = fs.readFileSync(filePath, 'utf-8') expect(content).toContain('// Shim for scripts/domWidget.ts') expect(content).toContain('export const isDOMWidget') expect(content).toContain('export const isComponentWidget') expect(content).toContain('export const DOMWidgetImpl') expect(content).toContain('export const ComponentWidgetImpl') expect(content).toContain('export const addWidget') expect(content).toContain('window.comfyAPI.domWidget') }) }) describe('Pnginfo shim file exports', () => { it('should contain all required exports', () => { const filePath = path.join(SCRIPTS_DIR, 'pnginfo.js') const content = fs.readFileSync(filePath, 'utf-8') expect(content).toContain('// Shim for scripts/pnginfo.ts') expect(content).toContain('export const getPngMetadata') expect(content).toContain('export const getFlacMetadata') expect(content).toContain('export const getAvifMetadata') expect(content).toContain('export const getWebpMetadata') expect(content).toContain('export const getLatentMetadata') expect(content).toContain('export const importA1111') expect(content).toContain('window.comfyAPI.pnginfo') }) }) describe('UI shim file exports', () => { it('should contain all required exports', () => { const filePath = path.join(SCRIPTS_DIR, 'ui.js') const content = fs.readFileSync(filePath, 'utf-8') expect(content).toContain('// Shim for scripts/ui.ts') expect(content).toContain('export const ComfyDialog') expect(content).toContain('export const $el') expect(content).toContain('export const ComfyUI') expect(content).toContain('window.comfyAPI.ui') }) }) describe('Utils shim file exports', () => { it('should contain all required exports', () => { const filePath = path.join(SCRIPTS_DIR, 'utils.js') const content = fs.readFileSync(filePath, 'utf-8') expect(content).toContain('// Shim for scripts/utils.ts') expect(content).toContain('export const clone') expect(content).toContain('export const applyTextReplacements') expect(content).toContain('export const addStylesheet') expect(content).toContain('export const downloadBlob') expect(content).toContain('export const uploadFile') expect(content).toContain('export const prop') expect(content).toContain('export const getStorageValue') expect(content).toContain('export const setStorageValue') expect(content).toContain('window.comfyAPI.utils') }) }) describe('Widgets shim file exports', () => { it('should contain all required exports', () => { const filePath = path.join(SCRIPTS_DIR, 'widgets.js') const content = fs.readFileSync(filePath, 'utf-8') expect(content).toContain('// Shim for scripts/widgets.ts') expect(content).toContain('export const updateControlWidgetLabel') expect(content).toContain('export const IS_CONTROL_WIDGET') expect(content).toContain('export const addValueControlWidget') expect(content).toContain('export const addValueControlWidgets') expect(content).toContain('export const ComfyWidgets') expect(content).toContain('window.comfyAPI.widgets') }) }) describe('UI subdirectory shim files', () => { const uiShimFiles = [ 'ui/components/asyncDialog.js', 'ui/components/button.js', 'ui/components/buttonGroup.js', 'ui/components/popup.js', 'ui/components/splitButton.js', 'ui/dialog.js', 'ui/draggableList.js', 'ui/imagePreview.js', 'ui/menu/index.js', 'ui/settings.js', 'ui/toggleSwitch.js', 'ui/utils.js' ] uiShimFiles.forEach((file) => { it(`${file} should exist`, () => { const filePath = path.join(SCRIPTS_DIR, file) expect(fs.existsSync(filePath)).toBe(true) }) it(`${file} should be a valid shim file`, () => { const filePath = path.join(SCRIPTS_DIR, file) const content = fs.readFileSync(filePath, 'utf-8') expect(content).toContain('window.comfyAPI') expect(content).toContain('export const') }) }) }) describe('Metadata subdirectory shim files', () => { const metadataShimFiles = [ 'metadata/avif.js', 'metadata/ebml.js', 'metadata/flac.js', 'metadata/gltf.js', 'metadata/isobmff.js', 'metadata/mp3.js', 'metadata/ogg.js', 'metadata/png.js', 'metadata/svg.js' ] metadataShimFiles.forEach((file) => { it(`${file} should exist`, () => { const filePath = path.join(SCRIPTS_DIR, file) expect(fs.existsSync(filePath)).toBe(true) }) it(`${file} should be a valid shim file`, () => { const filePath = path.join(SCRIPTS_DIR, file) const content = fs.readFileSync(filePath, 'utf-8') expect(content).toContain('window.comfyAPI') expect(content).toContain('export const') }) }) }) describe('Compare with treeshake-disabled build', () => { const treeshakeDisabledDir = path.join( __dirname, '../../dist-treeshake-disabled' ) const treeshakeDisabledScriptsDir = path.join( treeshakeDisabledDir, 'scripts' ) if (fs.existsSync(treeshakeDisabledDir)) { it('should have identical shim files between treeshake-enabled and treeshake-disabled builds', () => { const getShimFiles = (dir: string): string[] => { const files: string[] = [] const walk = (currentDir: string, relativePath = '') => { const entries = fs.readdirSync(currentDir, { withFileTypes: true }) for (const entry of entries) { const fullPath = path.join(currentDir, entry.name) const relPath = path.join(relativePath, entry.name) if (entry.isDirectory()) { walk(fullPath, relPath) } else if (entry.name.endsWith('.js')) { files.push(relPath) } } } walk(dir) return files.sort() } const enabledShimFiles = getShimFiles(SCRIPTS_DIR) const disabledShimFiles = getShimFiles(treeshakeDisabledScriptsDir) // Check that both builds have the same shim files expect(enabledShimFiles).toEqual(disabledShimFiles) // Check that the content of each shim file is identical for (const file of enabledShimFiles) { const enabledContent = fs.readFileSync( path.join(SCRIPTS_DIR, file), 'utf-8' ) const disabledContent = fs.readFileSync( path.join(treeshakeDisabledScriptsDir, file), 'utf-8' ) expect(enabledContent).toBe(disabledContent) } }) } }) })