mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-08 21:39:58 +00:00
Compare commits
7 Commits
dev/remote
...
feat/model
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
738cad40c9 | ||
|
|
e540920fbb | ||
|
|
82595f5704 | ||
|
|
2be4a2effe | ||
|
|
6dcaed56ec | ||
|
|
78d4a10691 | ||
|
|
80617d33f0 |
Binary file not shown.
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 77 KiB |
@@ -1057,6 +1057,58 @@ describe('ComboWidget', () => {
|
||||
expect(widget.canDecrement()).toBe(false)
|
||||
})
|
||||
|
||||
it('should display placeholder text when values list is empty and placeholder is provided', () => {
|
||||
widget = new ComboWidget(
|
||||
createMockWidgetConfig({
|
||||
name: 'ckpt_name',
|
||||
value: '',
|
||||
options: {
|
||||
values: [],
|
||||
placeholder:
|
||||
'No models found in ComfyUI/models/checkpoints folder...'
|
||||
}
|
||||
}),
|
||||
node
|
||||
)
|
||||
|
||||
expect(widget._displayValue).toBe(
|
||||
'No models found in ComfyUI/models/checkpoints folder...'
|
||||
)
|
||||
})
|
||||
|
||||
it('should display normal value when values list is not empty even with placeholder', () => {
|
||||
widget = new ComboWidget(
|
||||
createMockWidgetConfig({
|
||||
name: 'ckpt_name',
|
||||
value: 'model.safetensors',
|
||||
options: {
|
||||
values: ['model.safetensors', 'other.safetensors'],
|
||||
placeholder:
|
||||
'No models found in ComfyUI/models/checkpoints folder...'
|
||||
}
|
||||
}),
|
||||
node
|
||||
)
|
||||
|
||||
expect(widget._displayValue).toBe('model.safetensors')
|
||||
})
|
||||
|
||||
it('should not display placeholder when placeholder is undefined', () => {
|
||||
widget = new ComboWidget(
|
||||
createMockWidgetConfig({
|
||||
name: 'ckpt_name',
|
||||
value: '',
|
||||
options: {
|
||||
values: [],
|
||||
placeholder: undefined
|
||||
}
|
||||
}),
|
||||
node
|
||||
)
|
||||
|
||||
expect(widget._displayValue).toBe('')
|
||||
})
|
||||
|
||||
it('should throw error when values is null in getValues', () => {
|
||||
widget = new ComboWidget(
|
||||
createMockWidgetConfig({
|
||||
|
||||
@@ -35,6 +35,17 @@ export class ComboWidget
|
||||
override get _displayValue() {
|
||||
if (this.computedDisabled) return ''
|
||||
|
||||
const { values: rawValues, placeholder } = this.options
|
||||
const resolvedValues =
|
||||
typeof rawValues === 'function' ? rawValues(this, this.node) : rawValues
|
||||
|
||||
if (resolvedValues) {
|
||||
const valuesArray = toArray(resolvedValues)
|
||||
if (valuesArray.length === 0 && placeholder) {
|
||||
return placeholder
|
||||
}
|
||||
}
|
||||
|
||||
const getOptionLabel = this.options.getOptionLabel
|
||||
if (getOptionLabel) {
|
||||
try {
|
||||
@@ -45,13 +56,8 @@ export class ComboWidget
|
||||
}
|
||||
}
|
||||
|
||||
const { values: rawValues } = this.options
|
||||
if (rawValues) {
|
||||
const values = typeof rawValues === 'function' ? rawValues() : rawValues
|
||||
|
||||
if (values && !Array.isArray(values)) {
|
||||
return values[this.value]
|
||||
}
|
||||
if (resolvedValues && !Array.isArray(resolvedValues)) {
|
||||
return resolvedValues[this.value]
|
||||
}
|
||||
return typeof this.value === 'number' ? String(this.value) : this.value
|
||||
}
|
||||
|
||||
@@ -348,4 +348,38 @@ describe('WidgetSelect Value Binding', () => {
|
||||
expect(wrapper.findComponent(WidgetSelectDropdown).exists()).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Empty options with placeholder', () => {
|
||||
it('shows placeholder when options are empty', () => {
|
||||
const widget = createMockWidget<string | undefined>({
|
||||
value: '',
|
||||
options: {
|
||||
values: [],
|
||||
placeholder: 'No models found in ComfyUI/models/checkpoints folder...'
|
||||
}
|
||||
})
|
||||
const wrapper = mountComponent(widget, '')
|
||||
const selectDefault = wrapper.findComponent(WidgetSelectDefault)
|
||||
const select = selectDefault.findComponent(SelectPlus)
|
||||
|
||||
expect(select.props('placeholder')).toBe(
|
||||
'No models found in ComfyUI/models/checkpoints folder...'
|
||||
)
|
||||
})
|
||||
|
||||
it('does not show placeholder when options exist', () => {
|
||||
const widget = createMockWidget<string | undefined>({
|
||||
value: 'option1',
|
||||
options: {
|
||||
values: ['option1', 'option2'],
|
||||
placeholder: 'No models found'
|
||||
}
|
||||
})
|
||||
const wrapper = mountComponent(widget, 'option1')
|
||||
const selectDefault = wrapper.findComponent(WidgetSelectDefault)
|
||||
const select = selectDefault.findComponent(SelectPlus)
|
||||
|
||||
expect(select.props('placeholder')).toBeFalsy()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -51,7 +51,7 @@ interface Props {
|
||||
widget: SimplifiedWidget<string | undefined>
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const { widget } = defineProps<Props>()
|
||||
|
||||
function resolveValues(values: unknown): string[] {
|
||||
if (typeof values === 'function') return values()
|
||||
@@ -76,15 +76,30 @@ function refreshOptions() {
|
||||
}
|
||||
const selectOptions = computed(() => {
|
||||
void refreshTrigger.value
|
||||
return resolveValues(props.widget.options?.values)
|
||||
return resolveValues(widget.options?.values)
|
||||
})
|
||||
const invalid = computed(
|
||||
() => !!modelValue.value && !selectOptions.value.includes(modelValue.value)
|
||||
)
|
||||
|
||||
const combinedProps = computed(() => ({
|
||||
...filterWidgetProps(props.widget.options, PANEL_EXCLUDED_PROPS),
|
||||
...transformCompatProps.value,
|
||||
...(invalid.value ? { placeholder: `${modelValue.value}` } : {})
|
||||
}))
|
||||
const combinedProps = computed(() => {
|
||||
const { placeholder: _, ...filteredOptions } = filterWidgetProps(
|
||||
widget.options,
|
||||
PANEL_EXCLUDED_PROPS
|
||||
)
|
||||
const baseProps = {
|
||||
...filteredOptions,
|
||||
...transformCompatProps.value
|
||||
}
|
||||
|
||||
if (selectOptions.value.length === 0 && widget.options?.placeholder) {
|
||||
return { ...baseProps, placeholder: widget.options.placeholder }
|
||||
}
|
||||
|
||||
if (invalid.value) {
|
||||
return { ...baseProps, placeholder: `${modelValue.value}` }
|
||||
}
|
||||
|
||||
return baseProps
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -163,10 +163,11 @@ describe('useComboWidget', () => {
|
||||
expect(mockNode.addWidget).toHaveBeenCalledWith(
|
||||
'combo',
|
||||
'inputName',
|
||||
undefined,
|
||||
'',
|
||||
expect.any(Function),
|
||||
expect.objectContaining({
|
||||
values: []
|
||||
values: [],
|
||||
placeholder: undefined
|
||||
})
|
||||
)
|
||||
expect(widget).toBe(mockWidget)
|
||||
|
||||
@@ -207,10 +207,11 @@ const addComboWidget = (
|
||||
const widget = node.addWidget(
|
||||
'combo',
|
||||
inputSpec.name,
|
||||
defaultValue,
|
||||
defaultValue ?? '',
|
||||
() => {},
|
||||
{
|
||||
values: inputSpec.options ?? []
|
||||
values: inputSpec.options ?? [],
|
||||
placeholder: inputSpec.placeholder
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -99,7 +99,9 @@ export const zComboInputOptions = zBaseInputOptions.extend({
|
||||
options: z.array(zComboOption).optional(),
|
||||
remote: zRemoteWidgetConfig.optional(),
|
||||
/** Whether the widget is a multi-select widget. */
|
||||
multi_select: zMultiSelectOption.optional()
|
||||
multi_select: zMultiSelectOption.optional(),
|
||||
/** Placeholder text to display when the options list is empty. */
|
||||
placeholder: z.string().optional()
|
||||
})
|
||||
|
||||
const zIntInputSpec = z.tuple([z.literal('INT'), zIntInputOptions.optional()])
|
||||
|
||||
@@ -85,10 +85,19 @@ vi.mock('firebase/auth', async (importOriginal) => {
|
||||
addScope = vi.fn()
|
||||
setCustomParameters = vi.fn()
|
||||
},
|
||||
getAdditionalUserInfo: vi.fn(),
|
||||
setPersistence: vi.fn().mockResolvedValue(undefined)
|
||||
}
|
||||
})
|
||||
|
||||
// Mock telemetry
|
||||
const mockTrackAuth = vi.fn()
|
||||
vi.mock('@/platform/telemetry', () => ({
|
||||
useTelemetry: () => ({
|
||||
trackAuth: mockTrackAuth
|
||||
})
|
||||
}))
|
||||
|
||||
// Mock useToastStore
|
||||
vi.mock('@/stores/toastStore', () => ({
|
||||
useToastStore: () => ({
|
||||
@@ -572,6 +581,82 @@ describe('useFirebaseAuthStore', () => {
|
||||
|
||||
expect(store.loading).toBe(false)
|
||||
})
|
||||
|
||||
describe('sign-up telemetry OR logic', () => {
|
||||
const mockUserCredential = {
|
||||
user: mockUser
|
||||
} as Partial<UserCredential> as UserCredential
|
||||
|
||||
beforeEach(() => {
|
||||
vi.mocked(firebaseAuth.signInWithPopup).mockResolvedValue(
|
||||
mockUserCredential
|
||||
)
|
||||
})
|
||||
|
||||
it.each(['loginWithGoogle', 'loginWithGithub'] as const)(
|
||||
'%s should track is_new_user=true when Firebase says new user',
|
||||
async (method) => {
|
||||
vi.mocked(firebaseAuth.getAdditionalUserInfo).mockReturnValue({
|
||||
isNewUser: true,
|
||||
providerId: 'google.com',
|
||||
profile: null
|
||||
})
|
||||
|
||||
await store[method]()
|
||||
|
||||
expect(mockTrackAuth).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ is_new_user: true })
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
it.each(['loginWithGoogle', 'loginWithGithub'] as const)(
|
||||
'%s should track is_new_user=true when UI options say new user',
|
||||
async (method) => {
|
||||
vi.mocked(firebaseAuth.getAdditionalUserInfo).mockReturnValue({
|
||||
isNewUser: false,
|
||||
providerId: 'google.com',
|
||||
profile: null
|
||||
})
|
||||
|
||||
await store[method]({ isNewUser: true })
|
||||
|
||||
expect(mockTrackAuth).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ is_new_user: true })
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
it.each(['loginWithGoogle', 'loginWithGithub'] as const)(
|
||||
'%s should track is_new_user=false when neither source says new user',
|
||||
async (method) => {
|
||||
vi.mocked(firebaseAuth.getAdditionalUserInfo).mockReturnValue({
|
||||
isNewUser: false,
|
||||
providerId: 'google.com',
|
||||
profile: null
|
||||
})
|
||||
|
||||
await store[method]()
|
||||
|
||||
expect(mockTrackAuth).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ is_new_user: false })
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
it.each(['loginWithGoogle', 'loginWithGithub'] as const)(
|
||||
'%s should track is_new_user=false when getAdditionalUserInfo returns null',
|
||||
async (method) => {
|
||||
vi.mocked(firebaseAuth.getAdditionalUserInfo).mockReturnValue(null)
|
||||
|
||||
await store[method]()
|
||||
|
||||
expect(mockTrackAuth).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ is_new_user: false })
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('accessBillingPortal', () => {
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
GoogleAuthProvider,
|
||||
browserLocalPersistence,
|
||||
createUserWithEmailAndPassword,
|
||||
getAdditionalUserInfo,
|
||||
onAuthStateChanged,
|
||||
onIdTokenChanged,
|
||||
sendPasswordResetEmail,
|
||||
@@ -386,9 +387,11 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
|
||||
)
|
||||
|
||||
if (isCloud) {
|
||||
const additionalUserInfo = getAdditionalUserInfo(result)
|
||||
useTelemetry()?.trackAuth({
|
||||
method: 'google',
|
||||
is_new_user: options?.isNewUser ?? false,
|
||||
is_new_user:
|
||||
options?.isNewUser || additionalUserInfo?.isNewUser || false,
|
||||
user_id: result.user.uid
|
||||
})
|
||||
}
|
||||
@@ -405,9 +408,11 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
|
||||
)
|
||||
|
||||
if (isCloud) {
|
||||
const additionalUserInfo = getAdditionalUserInfo(result)
|
||||
useTelemetry()?.trackAuth({
|
||||
method: 'github',
|
||||
is_new_user: options?.isNewUser ?? false,
|
||||
is_new_user:
|
||||
options?.isNewUser || additionalUserInfo?.isNewUser || false,
|
||||
user_id: result.user.uid
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user