diff --git a/.gitignore b/.gitignore index 8f69ce164..be608c254 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ yarn.lock node_modules dist +/dist*/ dist-ssr *.local # Claude configuration diff --git a/tests-ui/tests/shimFiles.test.ts b/tests-ui/tests/shimFiles.test.ts new file mode 100644 index 000000000..f5f0e9b0b --- /dev/null +++ b/tests-ui/tests/shimFiles.test.ts @@ -0,0 +1,278 @@ +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) + } + }) + } + }) +})