[test] Add comprehensive shim files export tests

Added unit tests to verify that treeshaking doesn't break the public API exposed through shim files in the dist folder. Tests check:
- All core shim files exist (api.js, app.js, changeTracker.js, etc.)
- Each shim file contains proper exports from window.comfyAPI
- UI subdirectory shim files are present and valid
- Metadata subdirectory shim files are present and valid
- Treeshake-enabled and treeshake-disabled builds have identical shim files

This ensures backward compatibility when treeshaking is enabled and prevents breaking changes to the extension API.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
snomiao
2025-10-16 15:34:11 +00:00
parent fdee6a51f6
commit 5216c0da9f
2 changed files with 279 additions and 0 deletions

1
.gitignore vendored
View File

@@ -19,6 +19,7 @@ yarn.lock
node_modules
dist
/dist*/
dist-ssr
*.local
# Claude configuration

View File

@@ -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)
}
})
}
})
})