diff --git a/.claude/commands/setup_repo.md b/.claude/commands/setup_repo.md index d82e22ec6..71dee96a5 100644 --- a/.claude/commands/setup_repo.md +++ b/.claude/commands/setup_repo.md @@ -122,7 +122,7 @@ echo " pnpm build - Build for production" echo " pnpm test:unit - Run unit tests" echo " pnpm typecheck - Run TypeScript checks" echo " pnpm lint - Run ESLint" -echo " pnpm format - Format code with Prettier" +echo " pnpm format - Format code with oxfmt" echo "" echo "Next steps:" echo "1. Run 'pnpm dev' to start developing" diff --git a/.cursor/rules/unit-test.mdc b/.cursor/rules/unit-test.mdc deleted file mode 100644 index 2c6704f3e..000000000 --- a/.cursor/rules/unit-test.mdc +++ /dev/null @@ -1,21 +0,0 @@ ---- -description: Creating unit tests -globs: -alwaysApply: false ---- - -# Creating unit tests - -- This project uses `vitest` for unit testing -- Tests are stored in the `test/` directory -- Tests should be cross-platform compatible; able to run on Windows, macOS, and linux - - e.g. the use of `path.resolve`, or `path.join` and `path.sep` to ensure that tests work the same on all platforms -- Tests should be mocked properly - - Mocks should be cleanly written and easy to understand - - Mocks should be re-usable where possible - -## Unit test style - -- Prefer the use of `test.extend` over loose variables - - To achieve this, import `test as baseTest` from `vitest` -- Never use `it`; `test` should be used in place of this \ No newline at end of file diff --git a/.github/AGENTS.md b/.github/AGENTS.md new file mode 100644 index 000000000..0b64ac21b --- /dev/null +++ b/.github/AGENTS.md @@ -0,0 +1,14 @@ +# PR Review Context + +Context for automated PR review system. + +## Review Scope + +This automated review performs comprehensive analysis: +- Architecture and design patterns +- Security vulnerabilities +- Performance implications +- Code quality and maintainability +- Integration concerns + +For implementation details, see `.claude/commands/comprehensive-pr-review.md`. diff --git a/.github/CLAUDE.md b/.github/CLAUDE.md index 9a95d8cd0..3c928db39 100644 --- a/.github/CLAUDE.md +++ b/.github/CLAUDE.md @@ -1,36 +1,3 @@ -# ComfyUI Frontend - Claude Review Context - -This file provides additional context for the automated PR review system. - -## Quick Reference - -### PrimeVue Component Migrations - -When reviewing, flag these deprecated components: -- `Dropdown` → Use `Select` from 'primevue/select' -- `OverlayPanel` → Use `Popover` from 'primevue/popover' -- `Calendar` → Use `DatePicker` from 'primevue/datepicker' -- `InputSwitch` → Use `ToggleSwitch` from 'primevue/toggleswitch' -- `Sidebar` → Use `Drawer` from 'primevue/drawer' -- `Chips` → Use `AutoComplete` with multiple enabled and typeahead disabled -- `TabMenu` → Use `Tabs` without panels -- `Steps` → Use `Stepper` without panels -- `InlineMessage` → Use `Message` component - -### API Utilities Reference - -- `api.apiURL()` - Backend API calls (/prompt, /queue, /view, etc.) -- `api.fileURL()` - Static file access (templates, extensions) -- `$t()` / `i18n.global.t()` - Internationalization -- `DOMPurify.sanitize()` - HTML sanitization - -## Review Scope - -This automated review performs comprehensive analysis including: -- Architecture and design patterns -- Security vulnerabilities -- Performance implications -- Code quality and maintainability -- Integration concerns - -For implementation details, see `.claude/commands/comprehensive-pr-review.md`. \ No newline at end of file + +@AGENTS.md diff --git a/.github/workflows/ci-lint-format.yaml b/.github/workflows/ci-lint-format.yaml index 3ce6d6aa9..c97f6255c 100644 --- a/.github/workflows/ci-lint-format.yaml +++ b/.github/workflows/ci-lint-format.yaml @@ -42,7 +42,7 @@ jobs: - name: Run Stylelint with auto-fix run: pnpm stylelint:fix - - name: Run Prettier with auto-format + - name: Run oxfmt with auto-format run: pnpm format - name: Check for changes @@ -60,7 +60,7 @@ jobs: git config --local user.email "action@github.com" git config --local user.name "GitHub Action" git add . - git commit -m "[automated] Apply ESLint and Prettier fixes" + git commit -m "[automated] Apply ESLint and Oxfmt fixes" git push - name: Final validation @@ -80,7 +80,7 @@ jobs: issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - body: '## 🔧 Auto-fixes Applied\n\nThis PR has been automatically updated to fix linting and formatting issues.\n\n**⚠️ Important**: Your local branch is now behind. Run `git pull` before making additional changes to avoid conflicts.\n\n### Changes made:\n- ESLint auto-fixes\n- Prettier formatting' + body: '## 🔧 Auto-fixes Applied\n\nThis PR has been automatically updated to fix linting and formatting issues.\n\n**⚠️ Important**: Your local branch is now behind. Run `git pull` before making additional changes to avoid conflicts.\n\n### Changes made:\n- ESLint auto-fixes\n- Oxfmt formatting' }) - name: Comment on PR about manual fix needed diff --git a/.i18nrc.cjs b/.i18nrc.cjs index 86ce06eaa..4369f0a70 100644 --- a/.i18nrc.cjs +++ b/.i18nrc.cjs @@ -1,7 +1,7 @@ // This file is intentionally kept in CommonJS format (.cjs) // to resolve compatibility issues with dependencies that require CommonJS. // Do not convert this file to ESModule format unless all dependencies support it. -const { defineConfig } = require('@lobehub/i18n-cli'); +const { defineConfig } = require('@lobehub/i18n-cli') module.exports = defineConfig({ modelName: 'gpt-4.1', @@ -10,7 +10,19 @@ module.exports = defineConfig({ entry: 'src/locales/en', entryLocale: 'en', output: 'src/locales', - outputLocales: ['zh', 'zh-TW', 'ru', 'ja', 'ko', 'fr', 'es', 'ar', 'tr', 'pt-BR', 'fa'], + outputLocales: [ + 'zh', + 'zh-TW', + 'ru', + 'ja', + 'ko', + 'fr', + 'es', + 'ar', + 'tr', + 'pt-BR', + 'fa' + ], reference: `Special names to keep untranslated: flux, photomaker, clip, vae, cfg, stable audio, stable cascade, stable zero, controlnet, lora, HiDream, Civitai, Hugging Face. 'latent' is the short form of 'latent space'. 'mask' is in the context of image processing. @@ -26,4 +38,4 @@ module.exports = defineConfig({ - Use Arabic-Indic numerals (۰-۹) for numbers where appropriate. - Maintain consistency with terminology used in Persian software and design applications. ` -}); +}) diff --git a/.oxfmtrc.json b/.oxfmtrc.json new file mode 100644 index 000000000..5da4febe2 --- /dev/null +++ b/.oxfmtrc.json @@ -0,0 +1,20 @@ +{ + "$schema": "./node_modules/oxfmt/configuration_schema.json", + "singleQuote": true, + "tabWidth": 2, + "semi": false, + "trailingComma": "none", + "printWidth": 80, + "ignorePatterns": [ + "packages/registry-types/src/comfyRegistryTypes.ts", + "src/types/generatedManagerTypes.ts", + "**/*.md", + "**/*.json", + "**/*.css", + "**/*.yaml", + "**/*.yml", + "**/*.html", + "**/*.svg", + "**/*.xml" + ] +} diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 4403edd8e..000000000 --- a/.prettierignore +++ /dev/null @@ -1,2 +0,0 @@ -packages/registry-types/src/comfyRegistryTypes.ts -src/types/generatedManagerTypes.ts diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index aa43a43ac..000000000 --- a/.prettierrc +++ /dev/null @@ -1,11 +0,0 @@ -{ - "singleQuote": true, - "tabWidth": 2, - "semi": false, - "trailingComma": "none", - "printWidth": 80, - "importOrder": ["^@core/(.*)$", "", "^@/(.*)$", "^[./]"], - "importOrderSeparation": true, - "importOrderSortSpecifiers": true, - "plugins": ["@prettier/plugin-oxc", "@trivago/prettier-plugin-sort-imports"] -} diff --git a/.storybook/AGENTS.md b/.storybook/AGENTS.md new file mode 100644 index 000000000..5f6373f4b --- /dev/null +++ b/.storybook/AGENTS.md @@ -0,0 +1,17 @@ +# Storybook Guidelines + +See `@docs/guidance/storybook.md` for story patterns (auto-loaded for `*.stories.ts`). + +## Available Context + +Stories have access to: +- All ComfyUI stores +- PrimeVue with ComfyUI theming +- i18n system +- CSS variables and styling + +## Troubleshooting + +1. **Import Errors**: Verify `@/` alias works +2. **Missing Styles**: Check CSS imports in `preview.ts` +3. **Store Errors**: Check store initialization in setup diff --git a/.storybook/CLAUDE.md b/.storybook/CLAUDE.md index ca8248784..320b78815 100644 --- a/.storybook/CLAUDE.md +++ b/.storybook/CLAUDE.md @@ -1,197 +1,3 @@ -# Storybook Development Guidelines for Claude - -## Quick Commands - -- `pnpm storybook`: Start Storybook development server -- `pnpm build-storybook`: Build static Storybook -- `pnpm test:unit`: Run unit tests (includes Storybook components) - -## Development Workflow for Storybook - -1. **Creating New Stories**: - - Place `*.stories.ts` files alongside components - - Follow the naming pattern: `ComponentName.stories.ts` - - Use realistic mock data that matches ComfyUI schemas - -2. **Testing Stories**: - - Verify stories render correctly in Storybook UI - - Test different component states and edge cases - - Ensure proper theming and styling - -3. **Code Quality**: - - Run `pnpm typecheck` to verify TypeScript - - Run `pnpm lint` to check for linting issues - - Follow existing story patterns and conventions - -## Story Creation Guidelines - -### Basic Story Structure - -```typescript -import type { Meta, StoryObj } from '@storybook/vue3' -import ComponentName from './ComponentName.vue' - -const meta: Meta = { - title: 'Category/ComponentName', - component: ComponentName, - parameters: { - layout: 'centered' // or 'fullscreen', 'padded' - } -} - -export default meta -type Story = StoryObj - -export const Default: Story = { - args: { - // Component props - } -} -``` - -### Mock Data Patterns - -For ComfyUI components, use realistic mock data: - -```typescript -// Node definition mock -const mockNodeDef = { - input: { - required: { - prompt: ["STRING", { multiline: true }] - } - }, - output: ["CONDITIONING"], - output_is_list: [false], - category: "conditioning" -} - -// Component instance mock -const mockComponent = { - id: "1", - type: "CLIPTextEncode", - // ... other properties -} -``` - -### Common Story Variants - -Always include these story variants when applicable: - -- **Default**: Basic component with minimal props -- **WithData**: Component with realistic data -- **Loading**: Component in loading state -- **Error**: Component with error state -- **LongContent**: Component with edge case content -- **Empty**: Component with no data - -### Storybook-Specific Code Patterns - -#### Store Access -```typescript -// In stories, access stores through the setup function -export const WithStore: Story = { - render: () => ({ - setup() { - const store = useMyStore() - return { store } - }, - template: '' - }) -} -``` - -#### Event Testing -```typescript -export const WithEvents: Story = { - args: { - onUpdate: fn() // Use Storybook's fn() for action logging - } -} -``` - -## Configuration Notes - -### Vue App Setup -The Storybook preview is configured with: -- Pinia stores initialized -- PrimeVue with ComfyUI theme -- i18n internationalization -- All necessary CSS imports - -### Build Configuration -- Vite integration with proper alias resolution -- Manual chunking for better performance -- TypeScript support with strict checking -- CSS processing for Vue components - -## Troubleshooting - -### Common Issues - -1. **Import Errors**: Verify `@/` alias is working correctly -2. **Missing Styles**: Ensure CSS imports are in `preview.ts` -3. **Store Errors**: Check store initialization in setup -4. **Type Errors**: Use proper TypeScript types for story args - -### Debug Commands - -```bash -# Check TypeScript issues -pnpm typecheck - -# Lint Storybook files -pnpm lint .storybook/ - -# Build to check for production issues -pnpm build-storybook -``` - -## File Organization - -``` -.storybook/ -├── main.ts # Core configuration -├── preview.ts # Global setup and decorators -├── README.md # User documentation -└── CLAUDE.md # This file - Claude guidelines - -src/ -├── components/ -│ └── MyComponent/ -│ ├── MyComponent.vue -│ └── MyComponent.stories.ts -``` - -## Integration with ComfyUI - -### Available Context - -Stories have access to: -- All ComfyUI stores (widgetStore, colorPaletteStore, etc.) -- PrimeVue components with ComfyUI theming -- Internationalization system -- ComfyUI CSS variables and styling - -### Testing Components - -When testing ComfyUI-specific components: -1. Use realistic node definitions and data structures -2. Test with different node types (sampling, conditioning, etc.) -3. Verify proper CSS theming and dark/light modes -4. Check component behavior with various input combinations - -### Performance Considerations - -- Use manual chunking for large dependencies -- Minimize bundle size by avoiding unnecessary imports -- Leverage Storybook's lazy loading capabilities -- Profile build times and optimize as needed - -## Best Practices - -1. **Keep Stories Focused**: Each story should demonstrate one specific use case -2. **Use Descriptive Names**: Story names should clearly indicate what they show -3. **Document Complex Props**: Use JSDoc comments for complex prop types -4. **Test Edge Cases**: Create stories for unusual but valid use cases -5. **Maintain Consistency**: Follow established patterns in existing stories \ No newline at end of file + +@AGENTS.md diff --git a/.storybook/main.ts b/.storybook/main.ts index 5b7c126e9..90fbb138a 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -96,15 +96,15 @@ const config: StorybookConfig = { } ] }, - esbuild: { - // Prevent minification of identifiers to preserve _sfc_main - minifyIdentifiers: false, - keepNames: true - }, build: { - rollupOptions: { - // Disable tree-shaking for Storybook to prevent Vue SFC exports from being removed + rolldownOptions: { + experimental: { + strictExecutionOrder: true + }, treeshake: false, + output: { + keepNames: true + }, onwarn: (warning, warn) => { // Suppress specific warnings if ( diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 54f28d400..9cbac42d7 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,25 +1,22 @@ { "recommendations": [ + "antfu.vite", "austenc.tailwind-docs", "bradlc.vscode-tailwindcss", "davidanson.vscode-markdownlint", "dbaeumer.vscode-eslint", + "donjayamanne.githistory", "eamodio.gitlens", - "esbenp.prettier-vscode", - "figma.figma-vscode-extension", "github.vscode-github-actions", "github.vscode-pull-request-github", "hbenl.vscode-test-explorer", + "kisstkondoros.vscode-codemetrics", "lokalise.i18n-ally", "ms-playwright.playwright", + "oxc.oxc-vscode", + "sonarsource.sonarlint-vscode", "vitest.explorer", "vue.volar", - "sonarsource.sonarlint-vscode", - "deque-systems.vscode-axe-linter", - "kisstkondoros.vscode-codemetrics", - "donjayamanne.githistory", - "wix.vscode-import-cost", - "prograhammer.tslint-vue", - "antfu.vite" + "wix.vscode-import-cost" ] } diff --git a/AGENTS.md b/AGENTS.md index 743572be3..9938865a9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,5 +1,7 @@ # Repository Guidelines +See @docs/guidance/*.md for file-type-specific conventions (auto-loaded by glob). + ## Project Structure & Module Organization - Source: `src/` @@ -25,10 +27,10 @@ - Build output: `dist/` - Configs - `vite.config.mts` - - `vitest.config.ts` - `playwright.config.ts` - `eslint.config.ts` - - `.prettierrc` + - `.oxfmtrc.json` + - `.oxlintrc.json` - etc. ## Monorepo Architecture @@ -44,8 +46,23 @@ The project uses **Nx** for build orchestration and task management - `pnpm test:unit`: Run Vitest unit tests - `pnpm test:browser`: Run Playwright E2E tests (`browser_tests/`) - `pnpm lint` / `pnpm lint:fix`: Lint (ESLint) -- `pnpm format` / `pnpm format:check`: Prettier +- `pnpm format` / `pnpm format:check`: oxfmt - `pnpm typecheck`: Vue TSC type checking +- `pnpm storybook`: Start Storybook development server + +## Development Workflow + +1. Make code changes +2. Run relevant tests +3. Run `pnpm typecheck`, `pnpm lint`, `pnpm format` +4. Check if README updates are needed +5. Suggest docs.comfy.org updates for user-facing changes + +## Git Conventions + +- Use `prefix:` format: `feat:`, `fix:`, `test:` +- Add "Fixes #n" to PR descriptions +- Never mention Claude/AI in commits ## Coding Style & Naming Conventions @@ -55,7 +72,7 @@ The project uses **Nx** for build orchestration and task management - Composition API only - Tailwind 4 styling - Avoid ` diff --git a/src/components/common/WorkspaceProfilePic.vue b/src/components/common/WorkspaceProfilePic.vue new file mode 100644 index 000000000..642317267 --- /dev/null +++ b/src/components/common/WorkspaceProfilePic.vue @@ -0,0 +1,43 @@ + + + diff --git a/src/components/common/statusBadge.variants.ts b/src/components/common/statusBadge.variants.ts new file mode 100644 index 000000000..479a0dda8 --- /dev/null +++ b/src/components/common/statusBadge.variants.ts @@ -0,0 +1,26 @@ +import type { VariantProps } from 'cva' +import { cva } from 'cva' + +export const statusBadgeVariants = cva({ + base: 'inline-flex items-center justify-center rounded-full', + variants: { + severity: { + default: 'bg-primary-background text-base-foreground', + secondary: 'bg-secondary-background text-base-foreground', + warn: 'bg-warning-background text-base-background', + danger: 'bg-destructive-background text-white', + contrast: 'bg-base-foreground text-base-background' + }, + variant: { + label: 'h-3.5 px-1 text-xxxs font-semibold uppercase', + dot: 'size-2', + circle: 'size-3.5 text-xxxs font-semibold' + } + }, + defaultVariants: { + severity: 'default', + variant: 'label' + } +}) + +export type StatusBadgeVariants = VariantProps diff --git a/src/components/dialog/GlobalDialog.vue b/src/components/dialog/GlobalDialog.vue index 2074132b5..2a1f0ef3d 100644 --- a/src/components/dialog/GlobalDialog.vue +++ b/src/components/dialog/GlobalDialog.vue @@ -4,7 +4,12 @@ v-for="item in dialogStore.dialogStack" :key="item.key" v-model:visible="item.visible" - class="global-dialog" + :class="[ + 'global-dialog', + item.key === 'global-settings' && teamWorkspacesEnabled + ? 'settings-dialog-workspace' + : '' + ]" v-bind="item.dialogComponentProps" :pt="item.dialogComponentProps.pt" :aria-labelledby="item.key" @@ -38,7 +43,15 @@ @@ -56,6 +69,16 @@ const dialogStore = useDialogStore() @apply pt-0; } +/* Workspace mode: wider settings dialog */ +.settings-dialog-workspace { + width: 100%; + max-width: 1440px; +} + +.settings-dialog-workspace .p-dialog-content { + width: 100%; +} + .manager-dialog { height: 80vh; max-width: 1724px; diff --git a/src/components/dialog/content/setting/SettingItem.test.ts b/src/components/dialog/content/setting/SettingItem.test.ts index 17e3bab06..673fe5894 100644 --- a/src/components/dialog/content/setting/SettingItem.test.ts +++ b/src/components/dialog/content/setting/SettingItem.test.ts @@ -18,7 +18,7 @@ vi.mock('@/utils/formatUtil', () => ({ })) describe('SettingItem', () => { - const mountComponent = (props: any, options = {}): any => { + const mountComponent = (props: Record, options = {}) => { return mount(SettingItem, { global: { plugins: [PrimeVue, i18n, createPinia()], @@ -32,6 +32,7 @@ describe('SettingItem', () => { 'i-material-symbols:experiment-outline': true } }, + // @ts-expect-error - Test utility accepts flexible props for testing edge cases props, ...options }) @@ -48,8 +49,9 @@ describe('SettingItem', () => { } }) - // Get the options property of the FormItem - const options = wrapper.vm.formItem.options + // Check the FormItem component's item prop for the options + const formItem = wrapper.findComponent({ name: 'FormItem' }) + const options = formItem.props('item').options expect(options).toEqual([ { text: 'Correctly Translated', value: 'Correctly Translated' } ]) @@ -67,7 +69,8 @@ describe('SettingItem', () => { }) // Should not throw an error and tooltip should be preserved as-is - expect(wrapper.vm.formItem.tooltip).toBe( + const formItem = wrapper.findComponent({ name: 'FormItem' }) + expect(formItem.props('item').tooltip).toBe( 'This will load a larger version of @mtb/markdown-parser that bundles shiki' ) }) diff --git a/src/components/dialog/content/setting/UsageLogsTable.test.ts b/src/components/dialog/content/setting/UsageLogsTable.test.ts index b98664668..72a4fbdba 100644 --- a/src/components/dialog/content/setting/UsageLogsTable.test.ts +++ b/src/components/dialog/content/setting/UsageLogsTable.test.ts @@ -12,6 +12,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { nextTick } from 'vue' import { createI18n } from 'vue-i18n' +import type { AuditLog } from '@/services/customerEventsService' import { EventType } from '@/services/customerEventsService' import UsageLogsTable from './UsageLogsTable.vue' @@ -19,7 +20,7 @@ import UsageLogsTable from './UsageLogsTable.vue' type ComponentInstance = InstanceType & { loading: boolean error: string | null - events: any[] + events: Partial[] pagination: { page: number limit: number diff --git a/src/components/dialog/content/setting/WorkspacePanel.vue b/src/components/dialog/content/setting/WorkspacePanel.vue new file mode 100644 index 000000000..aff8f3733 --- /dev/null +++ b/src/components/dialog/content/setting/WorkspacePanel.vue @@ -0,0 +1,11 @@ + + + diff --git a/src/components/dialog/content/setting/WorkspacePanelContent.vue b/src/components/dialog/content/setting/WorkspacePanelContent.vue new file mode 100644 index 000000000..9366a573f --- /dev/null +++ b/src/components/dialog/content/setting/WorkspacePanelContent.vue @@ -0,0 +1,163 @@ + + + diff --git a/src/components/dialog/content/setting/WorkspaceSidebarItem.vue b/src/components/dialog/content/setting/WorkspaceSidebarItem.vue new file mode 100644 index 000000000..cab92c7a8 --- /dev/null +++ b/src/components/dialog/content/setting/WorkspaceSidebarItem.vue @@ -0,0 +1,19 @@ + + + diff --git a/src/components/dialog/content/signin/ApiKeyForm.test.ts b/src/components/dialog/content/signin/ApiKeyForm.test.ts index 5d6a726b8..4d073cb08 100644 --- a/src/components/dialog/content/signin/ApiKeyForm.test.ts +++ b/src/components/dialog/content/signin/ApiKeyForm.test.ts @@ -1,3 +1,5 @@ +import type { ComponentProps } from 'vue-component-type-helpers' + import { Form } from '@primevue/forms' import { mount } from '@vue/test-utils' import { createPinia } from 'pinia' @@ -63,7 +65,7 @@ describe('ApiKeyForm', () => { mockLoading.mockReset() }) - const mountComponent = (props: any = {}) => { + const mountComponent = (props: ComponentProps = {}) => { return mount(ApiKeyForm, { global: { plugins: [PrimeVue, createPinia(), i18n], diff --git a/src/components/dialog/content/signin/SignInForm.test.ts b/src/components/dialog/content/signin/SignInForm.test.ts index da898532a..c27d15929 100644 --- a/src/components/dialog/content/signin/SignInForm.test.ts +++ b/src/components/dialog/content/signin/SignInForm.test.ts @@ -112,8 +112,10 @@ describe('SignInForm', () => { // Mock getElementById to track focus const mockFocus = vi.fn() - const mockElement = { focus: mockFocus } - vi.spyOn(document, 'getElementById').mockReturnValue(mockElement as any) + const mockElement: Partial = { focus: mockFocus } + vi.spyOn(document, 'getElementById').mockReturnValue( + mockElement as HTMLElement + ) // Click forgot password link while email is empty await forgotPasswordSpan.trigger('click') @@ -138,7 +140,10 @@ describe('SignInForm', () => { it('calls handleForgotPassword with email when link is clicked', async () => { const wrapper = mountComponent() - const component = wrapper.vm as any + const component = wrapper.vm as typeof wrapper.vm & { + handleForgotPassword: (email: string, valid: boolean) => void + onSubmit: (data: { valid: boolean; values: unknown }) => void + } // Spy on handleForgotPassword const handleForgotPasswordSpy = vi.spyOn( @@ -161,7 +166,10 @@ describe('SignInForm', () => { describe('Form Submission', () => { it('emits submit event when onSubmit is called with valid data', async () => { const wrapper = mountComponent() - const component = wrapper.vm as any + const component = wrapper.vm as typeof wrapper.vm & { + handleForgotPassword: (email: string, valid: boolean) => void + onSubmit: (data: { valid: boolean; values: unknown }) => void + } // Call onSubmit directly with valid data component.onSubmit({ @@ -181,7 +189,10 @@ describe('SignInForm', () => { it('does not emit submit event when form is invalid', async () => { const wrapper = mountComponent() - const component = wrapper.vm as any + const component = wrapper.vm as typeof wrapper.vm & { + handleForgotPassword: (email: string, valid: boolean) => void + onSubmit: (data: { valid: boolean; values: unknown }) => void + } // Call onSubmit with invalid form component.onSubmit({ valid: false, values: {} }) @@ -254,12 +265,17 @@ describe('SignInForm', () => { describe('Focus Behavior', () => { it('focuses email input when handleForgotPassword is called with invalid email', async () => { const wrapper = mountComponent() - const component = wrapper.vm as any + const component = wrapper.vm as typeof wrapper.vm & { + handleForgotPassword: (email: string, valid: boolean) => void + onSubmit: (data: { valid: boolean; values: unknown }) => void + } // Mock getElementById to track focus const mockFocus = vi.fn() - const mockElement = { focus: mockFocus } - vi.spyOn(document, 'getElementById').mockReturnValue(mockElement as any) + const mockElement: Partial = { focus: mockFocus } + vi.spyOn(document, 'getElementById').mockReturnValue( + mockElement as HTMLElement + ) // Call handleForgotPassword with no email await component.handleForgotPassword('', false) @@ -273,12 +289,17 @@ describe('SignInForm', () => { it('does not focus email input when valid email is provided', async () => { const wrapper = mountComponent() - const component = wrapper.vm as any + const component = wrapper.vm as typeof wrapper.vm & { + handleForgotPassword: (email: string, valid: boolean) => void + onSubmit: (data: { valid: boolean; values: unknown }) => void + } // Mock getElementById const mockFocus = vi.fn() - const mockElement = { focus: mockFocus } - vi.spyOn(document, 'getElementById').mockReturnValue(mockElement as any) + const mockElement: Partial = { focus: mockFocus } + vi.spyOn(document, 'getElementById').mockReturnValue( + mockElement as HTMLElement + ) // Call handleForgotPassword with valid email await component.handleForgotPassword('test@example.com', true) diff --git a/src/components/dialog/content/workspace/CreateWorkspaceDialogContent.vue b/src/components/dialog/content/workspace/CreateWorkspaceDialogContent.vue new file mode 100644 index 000000000..b9444ce58 --- /dev/null +++ b/src/components/dialog/content/workspace/CreateWorkspaceDialogContent.vue @@ -0,0 +1,113 @@ + + + diff --git a/src/components/dialog/content/workspace/DeleteWorkspaceDialogContent.vue b/src/components/dialog/content/workspace/DeleteWorkspaceDialogContent.vue new file mode 100644 index 000000000..dea2da18d --- /dev/null +++ b/src/components/dialog/content/workspace/DeleteWorkspaceDialogContent.vue @@ -0,0 +1,89 @@ + + + diff --git a/src/components/dialog/content/workspace/EditWorkspaceDialogContent.vue b/src/components/dialog/content/workspace/EditWorkspaceDialogContent.vue new file mode 100644 index 000000000..62b650a4e --- /dev/null +++ b/src/components/dialog/content/workspace/EditWorkspaceDialogContent.vue @@ -0,0 +1,104 @@ + + + diff --git a/src/components/dialog/content/workspace/LeaveWorkspaceDialogContent.vue b/src/components/dialog/content/workspace/LeaveWorkspaceDialogContent.vue new file mode 100644 index 000000000..6a3d16c36 --- /dev/null +++ b/src/components/dialog/content/workspace/LeaveWorkspaceDialogContent.vue @@ -0,0 +1,78 @@ + + + diff --git a/src/components/graph/SelectionToolbox.vue b/src/components/graph/SelectionToolbox.vue index a56a82cf9..bd6f57818 100644 --- a/src/components/graph/SelectionToolbox.vue +++ b/src/components/graph/SelectionToolbox.vue @@ -10,7 +10,7 @@ class="selection-toolbox pointer-events-auto rounded-lg border border-interface-stroke bg-interface-panel-surface" :pt="{ header: 'hidden', - content: 'p-2 h-12 flex flex-row gap-1' + content: 'p-1 h-10 flex flex-row gap-1' }" @wheel="canvasInteractions.forwardEventToCanvas" > diff --git a/src/components/graph/selectionToolbox/InfoButton.test.ts b/src/components/graph/selectionToolbox/InfoButton.test.ts index da2a13831..61e292920 100644 --- a/src/components/graph/selectionToolbox/InfoButton.test.ts +++ b/src/components/graph/selectionToolbox/InfoButton.test.ts @@ -6,67 +6,19 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { createI18n } from 'vue-i18n' import InfoButton from '@/components/graph/selectionToolbox/InfoButton.vue' -// NOTE: The component import must come after mocks so they take effect. -import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' -import { useNodeDefStore } from '@/stores/nodeDefStore' +import Button from '@/components/ui/button/Button.vue' -const mockLGraphNode = { - type: 'TestNode', - title: 'Test Node' -} - -vi.mock('@/utils/litegraphUtil', () => ({ - isLGraphNode: vi.fn(() => true) +const { openPanelMock } = vi.hoisted(() => ({ + openPanelMock: vi.fn() })) -vi.mock('@/composables/sidebarTabs/useNodeLibrarySidebarTab', () => ({ - useNodeLibrarySidebarTab: () => ({ - id: 'node-library' - }) -})) - -const openHelpMock = vi.fn() -const closeHelpMock = vi.fn() -const nodeHelpState: { currentHelpNode: any } = { currentHelpNode: null } -vi.mock('@/stores/workspace/nodeHelpStore', () => ({ - useNodeHelpStore: () => ({ - openHelp: (def: any) => { - nodeHelpState.currentHelpNode = def - openHelpMock(def) - }, - closeHelp: () => { - nodeHelpState.currentHelpNode = null - closeHelpMock() - }, - get currentHelpNode() { - return nodeHelpState.currentHelpNode - }, - get isHelpOpen() { - return nodeHelpState.currentHelpNode !== null - } - }) -})) - -const toggleSidebarTabMock = vi.fn((id: string) => { - sidebarState.activeSidebarTabId = - sidebarState.activeSidebarTabId === id ? null : id -}) -const sidebarState: { activeSidebarTabId: string | null } = { - activeSidebarTabId: 'other-tab' -} -vi.mock('@/stores/workspace/sidebarTabStore', () => ({ - useSidebarTabStore: () => ({ - get activeSidebarTabId() { - return sidebarState.activeSidebarTabId - }, - toggleSidebarTab: toggleSidebarTabMock +vi.mock('@/stores/workspace/rightSidePanelStore', () => ({ + useRightSidePanelStore: () => ({ + openPanel: openPanelMock }) })) describe('InfoButton', () => { - let canvasStore: ReturnType - let nodeDefStore: ReturnType - const i18n = createI18n({ legacy: false, locale: 'en', @@ -81,9 +33,6 @@ describe('InfoButton', () => { beforeEach(() => { setActivePinia(createPinia()) - canvasStore = useCanvasStore() - nodeDefStore = useNodeDefStore() - vi.clearAllMocks() }) @@ -92,58 +41,15 @@ describe('InfoButton', () => { global: { plugins: [i18n, PrimeVue], directives: { tooltip: Tooltip }, - stubs: { - 'i-lucide:info': true, - Button: { - template: - '', - props: ['severity', 'text', 'class'], - emits: ['click'] - } - } + components: { Button } } }) } - it('should handle click without errors', async () => { - const mockNodeDef = { - nodePath: 'test/node', - display_name: 'Test Node' - } - canvasStore.selectedItems = [mockLGraphNode] as any - vi.spyOn(nodeDefStore, 'fromLGraphNode').mockReturnValue(mockNodeDef as any) + it('should open the info panel on click', async () => { const wrapper = mountComponent() - const button = wrapper.find('button') + const button = wrapper.find('[data-testid="info-button"]') await button.trigger('click') - expect(button.exists()).toBe(true) - }) - - it('should have correct CSS classes', () => { - const mockNodeDef = { - nodePath: 'test/node', - display_name: 'Test Node' - } - canvasStore.selectedItems = [mockLGraphNode] as any - vi.spyOn(nodeDefStore, 'fromLGraphNode').mockReturnValue(mockNodeDef as any) - - const wrapper = mountComponent() - const button = wrapper.find('button') - - expect(button.classes()).toContain('help-button') - expect(button.attributes('severity')).toBe('secondary') - }) - - it('should have correct tooltip', () => { - const mockNodeDef = { - nodePath: 'test/node', - display_name: 'Test Node' - } - canvasStore.selectedItems = [mockLGraphNode] as any - vi.spyOn(nodeDefStore, 'fromLGraphNode').mockReturnValue(mockNodeDef as any) - - const wrapper = mountComponent() - const button = wrapper.find('button') - - expect(button.exists()).toBe(true) + expect(openPanelMock).toHaveBeenCalledWith('info') }) }) diff --git a/src/components/imagecrop/WidgetImageCrop.vue b/src/components/imagecrop/WidgetImageCrop.vue new file mode 100644 index 000000000..4a1c39ef6 --- /dev/null +++ b/src/components/imagecrop/WidgetImageCrop.vue @@ -0,0 +1,100 @@ + + + diff --git a/src/components/node/NodeHelpContent.vue b/src/components/node/NodeHelpContent.vue index 8cf058f81..ad1f75e3d 100644 --- a/src/components/node/NodeHelpContent.vue +++ b/src/components/node/NodeHelpContent.vue @@ -70,17 +70,17 @@