mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-26 19:09:52 +00:00
refactor: improve TypeScript patterns in test files (Group 1/8) (#8253)
## Summary Improves type safety in test files by replacing unsafe type patterns with proper TypeScript idioms. ## Changes - Define typed `TestWindow` interface extending `Window` for Playwright tests with custom properties - Use `Partial<HTMLElement>` with single type assertion for DOM element mocks - Remove redundant type imports - Fix `console.log` → `console.warn` in test fixture ## Files Changed 16 test files across browser_tests, packages, and src/components ## Test Plan - ✅ `pnpm typecheck` passes - ✅ No new `any` types introduced - ✅ All pre-commit hooks pass ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8253-refactor-improve-TypeScript-patterns-in-test-files-Group-1-8-2f16d73d365081548f9ece7bcf0525ee) by [Unito](https://www.unito.io) --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
9efcbe682f
commit
941cd2b4a5
@@ -119,8 +119,7 @@ class NodeSlotReference {
|
|||||||
window['app'].canvas.ds.convertOffsetToCanvas(rawPos)
|
window['app'].canvas.ds.convertOffsetToCanvas(rawPos)
|
||||||
|
|
||||||
// Debug logging - convert Float64Arrays to regular arrays for visibility
|
// Debug logging - convert Float64Arrays to regular arrays for visibility
|
||||||
// eslint-disable-next-line no-console
|
console.warn(
|
||||||
console.log(
|
|
||||||
`NodeSlotReference debug for ${type} slot ${index} on node ${id}:`,
|
`NodeSlotReference debug for ${type} slot ${index} on node ${id}:`,
|
||||||
{
|
{
|
||||||
nodePos: [node.pos[0], node.pos[1]],
|
nodePos: [node.pos[0], node.pos[1]],
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ test.describe('Feature Flags', () => {
|
|||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(data)
|
const parsed = JSON.parse(data)
|
||||||
if (parsed.type === 'feature_flags') {
|
if (parsed.type === 'feature_flags') {
|
||||||
window.__capturedMessages.clientFeatureFlags = parsed
|
window.__capturedMessages!.clientFeatureFlags = parsed
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Not JSON, ignore
|
// Not JSON, ignore
|
||||||
@@ -41,7 +41,7 @@ test.describe('Feature Flags', () => {
|
|||||||
window['app']?.api?.serverFeatureFlags &&
|
window['app']?.api?.serverFeatureFlags &&
|
||||||
Object.keys(window['app'].api.serverFeatureFlags).length > 0
|
Object.keys(window['app'].api.serverFeatureFlags).length > 0
|
||||||
) {
|
) {
|
||||||
window.__capturedMessages.serverFeatureFlags =
|
window.__capturedMessages!.serverFeatureFlags =
|
||||||
window['app'].api.serverFeatureFlags
|
window['app'].api.serverFeatureFlags
|
||||||
clearInterval(checkInterval)
|
clearInterval(checkInterval)
|
||||||
}
|
}
|
||||||
@@ -57,8 +57,8 @@ test.describe('Feature Flags', () => {
|
|||||||
// Wait for both client and server feature flags
|
// Wait for both client and server feature flags
|
||||||
await newPage.waitForFunction(
|
await newPage.waitForFunction(
|
||||||
() =>
|
() =>
|
||||||
window.__capturedMessages.clientFeatureFlags !== null &&
|
window.__capturedMessages!.clientFeatureFlags !== null &&
|
||||||
window.__capturedMessages.serverFeatureFlags !== null,
|
window.__capturedMessages!.serverFeatureFlags !== null,
|
||||||
{ timeout: 10000 }
|
{ timeout: 10000 }
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -66,27 +66,27 @@ test.describe('Feature Flags', () => {
|
|||||||
const messages = await newPage.evaluate(() => window.__capturedMessages)
|
const messages = await newPage.evaluate(() => window.__capturedMessages)
|
||||||
|
|
||||||
// Verify client sent feature flags
|
// Verify client sent feature flags
|
||||||
expect(messages.clientFeatureFlags).toBeTruthy()
|
expect(messages!.clientFeatureFlags).toBeTruthy()
|
||||||
expect(messages.clientFeatureFlags).toHaveProperty('type', 'feature_flags')
|
expect(messages!.clientFeatureFlags).toHaveProperty('type', 'feature_flags')
|
||||||
expect(messages.clientFeatureFlags).toHaveProperty('data')
|
expect(messages!.clientFeatureFlags).toHaveProperty('data')
|
||||||
expect(messages.clientFeatureFlags.data).toHaveProperty(
|
expect(messages!.clientFeatureFlags!.data).toHaveProperty(
|
||||||
'supports_preview_metadata'
|
'supports_preview_metadata'
|
||||||
)
|
)
|
||||||
expect(
|
expect(
|
||||||
typeof messages.clientFeatureFlags.data.supports_preview_metadata
|
typeof messages!.clientFeatureFlags!.data.supports_preview_metadata
|
||||||
).toBe('boolean')
|
).toBe('boolean')
|
||||||
|
|
||||||
// Verify server sent feature flags back
|
// Verify server sent feature flags back
|
||||||
expect(messages.serverFeatureFlags).toBeTruthy()
|
expect(messages!.serverFeatureFlags).toBeTruthy()
|
||||||
expect(messages.serverFeatureFlags).toHaveProperty(
|
expect(messages!.serverFeatureFlags).toHaveProperty(
|
||||||
'supports_preview_metadata'
|
'supports_preview_metadata'
|
||||||
)
|
)
|
||||||
expect(typeof messages.serverFeatureFlags.supports_preview_metadata).toBe(
|
expect(typeof messages!.serverFeatureFlags!.supports_preview_metadata).toBe(
|
||||||
'boolean'
|
'boolean'
|
||||||
)
|
)
|
||||||
expect(messages.serverFeatureFlags).toHaveProperty('max_upload_size')
|
expect(messages!.serverFeatureFlags).toHaveProperty('max_upload_size')
|
||||||
expect(typeof messages.serverFeatureFlags.max_upload_size).toBe('number')
|
expect(typeof messages!.serverFeatureFlags!.max_upload_size).toBe('number')
|
||||||
expect(Object.keys(messages.serverFeatureFlags).length).toBeGreaterThan(0)
|
expect(Object.keys(messages!.serverFeatureFlags!).length).toBeGreaterThan(0)
|
||||||
|
|
||||||
await newPage.close()
|
await newPage.close()
|
||||||
})
|
})
|
||||||
@@ -96,7 +96,7 @@ test.describe('Feature Flags', () => {
|
|||||||
}) => {
|
}) => {
|
||||||
// Get the actual server feature flags from the backend
|
// Get the actual server feature flags from the backend
|
||||||
const serverFlags = await comfyPage.page.evaluate(() => {
|
const serverFlags = await comfyPage.page.evaluate(() => {
|
||||||
return window['app'].api.serverFeatureFlags
|
return window['app']!.api.serverFeatureFlags
|
||||||
})
|
})
|
||||||
|
|
||||||
// Verify we received real feature flags from the backend
|
// Verify we received real feature flags from the backend
|
||||||
@@ -115,7 +115,7 @@ test.describe('Feature Flags', () => {
|
|||||||
}) => {
|
}) => {
|
||||||
// Test serverSupportsFeature with real backend flags
|
// Test serverSupportsFeature with real backend flags
|
||||||
const supportsPreviewMetadata = await comfyPage.page.evaluate(() => {
|
const supportsPreviewMetadata = await comfyPage.page.evaluate(() => {
|
||||||
return window['app'].api.serverSupportsFeature(
|
return window['app']!.api.serverSupportsFeature(
|
||||||
'supports_preview_metadata'
|
'supports_preview_metadata'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -124,15 +124,17 @@ test.describe('Feature Flags', () => {
|
|||||||
|
|
||||||
// Test non-existent feature - should always return false
|
// Test non-existent feature - should always return false
|
||||||
const supportsNonExistent = await comfyPage.page.evaluate(() => {
|
const supportsNonExistent = await comfyPage.page.evaluate(() => {
|
||||||
return window['app'].api.serverSupportsFeature('non_existent_feature_xyz')
|
return window['app']!.api.serverSupportsFeature(
|
||||||
|
'non_existent_feature_xyz'
|
||||||
|
)
|
||||||
})
|
})
|
||||||
expect(supportsNonExistent).toBe(false)
|
expect(supportsNonExistent).toBe(false)
|
||||||
|
|
||||||
// Test that the method only returns true for boolean true values
|
// Test that the method only returns true for boolean true values
|
||||||
const testResults = await comfyPage.page.evaluate(() => {
|
const testResults = await comfyPage.page.evaluate(() => {
|
||||||
// Temporarily modify serverFeatureFlags to test behavior
|
// Temporarily modify serverFeatureFlags to test behavior
|
||||||
const original = window['app'].api.serverFeatureFlags
|
const original = window['app']!.api.serverFeatureFlags
|
||||||
window['app'].api.serverFeatureFlags = {
|
window['app']!.api.serverFeatureFlags = {
|
||||||
bool_true: true,
|
bool_true: true,
|
||||||
bool_false: false,
|
bool_false: false,
|
||||||
string_value: 'yes',
|
string_value: 'yes',
|
||||||
@@ -141,15 +143,15 @@ test.describe('Feature Flags', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const results = {
|
const results = {
|
||||||
bool_true: window['app'].api.serverSupportsFeature('bool_true'),
|
bool_true: window['app']!.api.serverSupportsFeature('bool_true'),
|
||||||
bool_false: window['app'].api.serverSupportsFeature('bool_false'),
|
bool_false: window['app']!.api.serverSupportsFeature('bool_false'),
|
||||||
string_value: window['app'].api.serverSupportsFeature('string_value'),
|
string_value: window['app']!.api.serverSupportsFeature('string_value'),
|
||||||
number_value: window['app'].api.serverSupportsFeature('number_value'),
|
number_value: window['app']!.api.serverSupportsFeature('number_value'),
|
||||||
null_value: window['app'].api.serverSupportsFeature('null_value')
|
null_value: window['app']!.api.serverSupportsFeature('null_value')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore original
|
// Restore original
|
||||||
window['app'].api.serverFeatureFlags = original
|
window['app']!.api.serverFeatureFlags = original
|
||||||
return results
|
return results
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -166,20 +168,20 @@ test.describe('Feature Flags', () => {
|
|||||||
}) => {
|
}) => {
|
||||||
// Test getServerFeature method
|
// Test getServerFeature method
|
||||||
const previewMetadataValue = await comfyPage.page.evaluate(() => {
|
const previewMetadataValue = await comfyPage.page.evaluate(() => {
|
||||||
return window['app'].api.getServerFeature('supports_preview_metadata')
|
return window['app']!.api.getServerFeature('supports_preview_metadata')
|
||||||
})
|
})
|
||||||
expect(typeof previewMetadataValue).toBe('boolean')
|
expect(typeof previewMetadataValue).toBe('boolean')
|
||||||
|
|
||||||
// Test getting max_upload_size
|
// Test getting max_upload_size
|
||||||
const maxUploadSize = await comfyPage.page.evaluate(() => {
|
const maxUploadSize = await comfyPage.page.evaluate(() => {
|
||||||
return window['app'].api.getServerFeature('max_upload_size')
|
return window['app']!.api.getServerFeature('max_upload_size')
|
||||||
})
|
})
|
||||||
expect(typeof maxUploadSize).toBe('number')
|
expect(typeof maxUploadSize).toBe('number')
|
||||||
expect(maxUploadSize).toBeGreaterThan(0)
|
expect(maxUploadSize).toBeGreaterThan(0)
|
||||||
|
|
||||||
// Test getServerFeature with default value for non-existent feature
|
// Test getServerFeature with default value for non-existent feature
|
||||||
const defaultValue = await comfyPage.page.evaluate(() => {
|
const defaultValue = await comfyPage.page.evaluate(() => {
|
||||||
return window['app'].api.getServerFeature(
|
return window['app']!.api.getServerFeature(
|
||||||
'non_existent_feature_xyz',
|
'non_existent_feature_xyz',
|
||||||
'default'
|
'default'
|
||||||
)
|
)
|
||||||
@@ -192,7 +194,7 @@ test.describe('Feature Flags', () => {
|
|||||||
}) => {
|
}) => {
|
||||||
// Test getServerFeatures returns all flags
|
// Test getServerFeatures returns all flags
|
||||||
const allFeatures = await comfyPage.page.evaluate(() => {
|
const allFeatures = await comfyPage.page.evaluate(() => {
|
||||||
return window['app'].api.getServerFeatures()
|
return window['app']!.api.getServerFeatures()
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(allFeatures).toBeTruthy()
|
expect(allFeatures).toBeTruthy()
|
||||||
@@ -205,14 +207,14 @@ test.describe('Feature Flags', () => {
|
|||||||
test('Client feature flags are immutable', async ({ comfyPage }) => {
|
test('Client feature flags are immutable', async ({ comfyPage }) => {
|
||||||
// Test that getClientFeatureFlags returns a copy
|
// Test that getClientFeatureFlags returns a copy
|
||||||
const immutabilityTest = await comfyPage.page.evaluate(() => {
|
const immutabilityTest = await comfyPage.page.evaluate(() => {
|
||||||
const flags1 = window['app'].api.getClientFeatureFlags()
|
const flags1 = window['app']!.api.getClientFeatureFlags()
|
||||||
const flags2 = window['app'].api.getClientFeatureFlags()
|
const flags2 = window['app']!.api.getClientFeatureFlags()
|
||||||
|
|
||||||
// Modify the first object
|
// Modify the first object
|
||||||
flags1.test_modification = true
|
flags1.test_modification = true
|
||||||
|
|
||||||
// Get flags again to check if original was modified
|
// Get flags again to check if original was modified
|
||||||
const flags3 = window['app'].api.getClientFeatureFlags()
|
const flags3 = window['app']!.api.getClientFeatureFlags()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
areEqual: flags1 === flags2,
|
areEqual: flags1 === flags2,
|
||||||
@@ -238,14 +240,14 @@ test.describe('Feature Flags', () => {
|
|||||||
}) => {
|
}) => {
|
||||||
const immutabilityTest = await comfyPage.page.evaluate(() => {
|
const immutabilityTest = await comfyPage.page.evaluate(() => {
|
||||||
// Get a copy of server features
|
// Get a copy of server features
|
||||||
const features1 = window['app'].api.getServerFeatures()
|
const features1 = window['app']!.api.getServerFeatures()
|
||||||
|
|
||||||
// Try to modify it
|
// Try to modify it
|
||||||
features1.supports_preview_metadata = false
|
features1.supports_preview_metadata = false
|
||||||
features1.new_feature = 'added'
|
features1.new_feature = 'added'
|
||||||
|
|
||||||
// Get another copy
|
// Get another copy
|
||||||
const features2 = window['app'].api.getServerFeatures()
|
const features2 = window['app']!.api.getServerFeatures()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
modifiedValue: features1.supports_preview_metadata,
|
modifiedValue: features1.supports_preview_metadata,
|
||||||
@@ -274,7 +276,8 @@ test.describe('Feature Flags', () => {
|
|||||||
// Set up monitoring before navigation
|
// Set up monitoring before navigation
|
||||||
await newPage.addInitScript(() => {
|
await newPage.addInitScript(() => {
|
||||||
// Track when various app components are ready
|
// Track when various app components are ready
|
||||||
;(window as any).__appReadiness = {
|
|
||||||
|
window.__appReadiness = {
|
||||||
featureFlagsReceived: false,
|
featureFlagsReceived: false,
|
||||||
apiInitialized: false,
|
apiInitialized: false,
|
||||||
appInitialized: false
|
appInitialized: false
|
||||||
@@ -286,7 +289,10 @@ test.describe('Feature Flags', () => {
|
|||||||
window['app']?.api?.serverFeatureFlags?.supports_preview_metadata !==
|
window['app']?.api?.serverFeatureFlags?.supports_preview_metadata !==
|
||||||
undefined
|
undefined
|
||||||
) {
|
) {
|
||||||
;(window as any).__appReadiness.featureFlagsReceived = true
|
window.__appReadiness = {
|
||||||
|
...window.__appReadiness,
|
||||||
|
featureFlagsReceived: true
|
||||||
|
}
|
||||||
clearInterval(checkFeatureFlags)
|
clearInterval(checkFeatureFlags)
|
||||||
}
|
}
|
||||||
}, 10)
|
}, 10)
|
||||||
@@ -294,7 +300,10 @@ test.describe('Feature Flags', () => {
|
|||||||
// Monitor API initialization
|
// Monitor API initialization
|
||||||
const checkApi = setInterval(() => {
|
const checkApi = setInterval(() => {
|
||||||
if (window['app']?.api) {
|
if (window['app']?.api) {
|
||||||
;(window as any).__appReadiness.apiInitialized = true
|
window.__appReadiness = {
|
||||||
|
...window.__appReadiness,
|
||||||
|
apiInitialized: true
|
||||||
|
}
|
||||||
clearInterval(checkApi)
|
clearInterval(checkApi)
|
||||||
}
|
}
|
||||||
}, 10)
|
}, 10)
|
||||||
@@ -302,7 +311,10 @@ test.describe('Feature Flags', () => {
|
|||||||
// Monitor app initialization
|
// Monitor app initialization
|
||||||
const checkApp = setInterval(() => {
|
const checkApp = setInterval(() => {
|
||||||
if (window['app']?.graph) {
|
if (window['app']?.graph) {
|
||||||
;(window as any).__appReadiness.appInitialized = true
|
window.__appReadiness = {
|
||||||
|
...window.__appReadiness,
|
||||||
|
appInitialized: true
|
||||||
|
}
|
||||||
clearInterval(checkApp)
|
clearInterval(checkApp)
|
||||||
}
|
}
|
||||||
}, 10)
|
}, 10)
|
||||||
@@ -331,8 +343,8 @@ test.describe('Feature Flags', () => {
|
|||||||
// Get readiness state
|
// Get readiness state
|
||||||
const readiness = await newPage.evaluate(() => {
|
const readiness = await newPage.evaluate(() => {
|
||||||
return {
|
return {
|
||||||
...(window as any).__appReadiness,
|
...window.__appReadiness,
|
||||||
currentFlags: window['app'].api.serverFeatureFlags
|
currentFlags: window['app']!.api.serverFeatureFlags
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -2,15 +2,17 @@ import {
|
|||||||
comfyExpect as expect,
|
comfyExpect as expect,
|
||||||
comfyPageFixture as test
|
comfyPageFixture as test
|
||||||
} from '../fixtures/ComfyPage'
|
} from '../fixtures/ComfyPage'
|
||||||
|
import type { ComfyPage } from '../fixtures/ComfyPage'
|
||||||
import { fitToViewInstant } from '../helpers/fitToView'
|
import { fitToViewInstant } from '../helpers/fitToView'
|
||||||
|
import type { NodeReference } from '../fixtures/utils/litegraphUtils'
|
||||||
|
|
||||||
// TODO: there might be a better solution for this
|
// TODO: there might be a better solution for this
|
||||||
// Helper function to pan canvas and select node
|
// Helper function to pan canvas and select node
|
||||||
async function selectNodeWithPan(comfyPage: any, nodeRef: any) {
|
async function selectNodeWithPan(comfyPage: ComfyPage, nodeRef: NodeReference) {
|
||||||
const nodePos = await nodeRef.getPosition()
|
const nodePos = await nodeRef.getPosition()
|
||||||
|
|
||||||
await comfyPage.page.evaluate((pos) => {
|
await comfyPage.page.evaluate((pos) => {
|
||||||
const app = window['app']
|
const app = window['app']!
|
||||||
const canvas = app.canvas
|
const canvas = app.canvas
|
||||||
canvas.ds.offset[0] = -pos.x + canvas.canvas.width / 2
|
canvas.ds.offset[0] = -pos.x + canvas.canvas.width / 2
|
||||||
canvas.ds.offset[1] = -pos.y + canvas.canvas.height / 2 + 100
|
canvas.ds.offset[1] = -pos.y + canvas.canvas.height / 2 + 100
|
||||||
@@ -345,7 +347,7 @@ This is documentation for a custom node.
|
|||||||
|
|
||||||
// Find and select a custom/group node
|
// Find and select a custom/group node
|
||||||
const nodeRefs = await comfyPage.page.evaluate(() => {
|
const nodeRefs = await comfyPage.page.evaluate(() => {
|
||||||
return window['app'].graph.nodes.map((n: any) => n.id)
|
return window['app']!.graph!.nodes.map((n) => n.id)
|
||||||
})
|
})
|
||||||
if (nodeRefs.length > 0) {
|
if (nodeRefs.length > 0) {
|
||||||
const firstNode = await comfyPage.getNodeRefById(nodeRefs[0])
|
const firstNode = await comfyPage.getNodeRefById(nodeRefs[0])
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { expect } from '@playwright/test'
|
import { expect } from '@playwright/test'
|
||||||
|
|
||||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||||
|
import type { ComfyPage } from '../fixtures/ComfyPage'
|
||||||
|
|
||||||
test.beforeEach(async ({ comfyPage }) => {
|
test.beforeEach(async ({ comfyPage }) => {
|
||||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||||
@@ -15,7 +16,7 @@ test.describe('Selection Toolbox - More Options Submenus', () => {
|
|||||||
await comfyPage.nextFrame()
|
await comfyPage.nextFrame()
|
||||||
})
|
})
|
||||||
|
|
||||||
const openMoreOptions = async (comfyPage: any) => {
|
const openMoreOptions = async (comfyPage: ComfyPage) => {
|
||||||
const ksamplerNodes = await comfyPage.getNodeRefsByTitle('KSampler')
|
const ksamplerNodes = await comfyPage.getNodeRefsByTitle('KSampler')
|
||||||
if (ksamplerNodes.length === 0) {
|
if (ksamplerNodes.length === 0) {
|
||||||
throw new Error('No KSampler nodes found')
|
throw new Error('No KSampler nodes found')
|
||||||
|
|||||||
@@ -419,7 +419,7 @@ test.describe('Vue Node Link Interaction', () => {
|
|||||||
// This avoids relying on an exact path hit-test position.
|
// This avoids relying on an exact path hit-test position.
|
||||||
await comfyPage.page.evaluate(
|
await comfyPage.page.evaluate(
|
||||||
([targetNodeId, targetSlot, clientPoint]) => {
|
([targetNodeId, targetSlot, clientPoint]) => {
|
||||||
const app = (window as any)['app']
|
const app = window['app']
|
||||||
const graph = app?.canvas?.graph ?? app?.graph
|
const graph = app?.canvas?.graph ?? app?.graph
|
||||||
if (!graph) throw new Error('Graph not available')
|
if (!graph) throw new Error('Graph not available')
|
||||||
const node = graph.getNodeById(targetNodeId)
|
const node = graph.getNodeById(targetNodeId)
|
||||||
@@ -505,7 +505,7 @@ test.describe('Vue Node Link Interaction', () => {
|
|||||||
// This avoids relying on an exact path hit-test position.
|
// This avoids relying on an exact path hit-test position.
|
||||||
await comfyPage.page.evaluate(
|
await comfyPage.page.evaluate(
|
||||||
([targetNodeId, targetSlot, clientPoint]) => {
|
([targetNodeId, targetSlot, clientPoint]) => {
|
||||||
const app = (window as any)['app']
|
const app = window['app']
|
||||||
const graph = app?.canvas?.graph ?? app?.graph
|
const graph = app?.canvas?.graph ?? app?.graph
|
||||||
if (!graph) throw new Error('Graph not available')
|
if (!graph) throw new Error('Graph not available')
|
||||||
const node = graph.getNodeById(targetNodeId)
|
const node = graph.getNodeById(targetNodeId)
|
||||||
|
|||||||
@@ -120,8 +120,8 @@ describe('formatUtil', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should handle null and undefined gracefully', () => {
|
it('should handle null and undefined gracefully', () => {
|
||||||
expect(getMediaTypeFromFilename(null as any)).toBe('image')
|
expect(getMediaTypeFromFilename(null)).toBe('image')
|
||||||
expect(getMediaTypeFromFilename(undefined as any)).toBe('image')
|
expect(getMediaTypeFromFilename(undefined)).toBe('image')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle special characters in filenames', () => {
|
it('should handle special characters in filenames', () => {
|
||||||
|
|||||||
@@ -537,7 +537,9 @@ export function truncateFilename(
|
|||||||
* @param filename The filename to analyze
|
* @param filename The filename to analyze
|
||||||
* @returns The media type: 'image', 'video', 'audio', or '3D'
|
* @returns The media type: 'image', 'video', 'audio', or '3D'
|
||||||
*/
|
*/
|
||||||
export function getMediaTypeFromFilename(filename: string): MediaType {
|
export function getMediaTypeFromFilename(
|
||||||
|
filename: string | null | undefined
|
||||||
|
): MediaType {
|
||||||
if (!filename) return 'image'
|
if (!filename) return 'image'
|
||||||
const ext = filename.split('.').pop()?.toLowerCase()
|
const ext = filename.split('.').pop()?.toLowerCase()
|
||||||
if (!ext) return 'image'
|
if (!ext) return 'image'
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ interface IdleDeadline {
|
|||||||
interface IDisposable {
|
interface IDisposable {
|
||||||
dispose(): void
|
dispose(): void
|
||||||
}
|
}
|
||||||
|
type GlobalWindow = typeof globalThis
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal implementation function that handles the actual scheduling logic.
|
* Internal implementation function that handles the actual scheduling logic.
|
||||||
@@ -21,7 +22,7 @@ interface IDisposable {
|
|||||||
* or fall back to setTimeout-based implementation.
|
* or fall back to setTimeout-based implementation.
|
||||||
*/
|
*/
|
||||||
let _runWhenIdle: (
|
let _runWhenIdle: (
|
||||||
targetWindow: any,
|
targetWindow: GlobalWindow,
|
||||||
callback: (idle: IdleDeadline) => void,
|
callback: (idle: IdleDeadline) => void,
|
||||||
timeout?: number
|
timeout?: number
|
||||||
) => IDisposable
|
) => IDisposable
|
||||||
@@ -37,7 +38,7 @@ export let runWhenGlobalIdle: (
|
|||||||
|
|
||||||
// Self-invoking function to set up the idle callback implementation
|
// Self-invoking function to set up the idle callback implementation
|
||||||
;(function () {
|
;(function () {
|
||||||
const safeGlobal: any = globalThis
|
const safeGlobal: GlobalWindow = globalThis as GlobalWindow
|
||||||
|
|
||||||
if (
|
if (
|
||||||
typeof safeGlobal.requestIdleCallback !== 'function' ||
|
typeof safeGlobal.requestIdleCallback !== 'function' ||
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { createTestingPinia } from '@pinia/testing'
|
import { createTestingPinia } from '@pinia/testing'
|
||||||
import type { VueWrapper } from '@vue/test-utils'
|
import type { VueWrapper } from '@vue/test-utils'
|
||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
|
import type { Mock } from 'vitest'
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
import { nextTick } from 'vue'
|
import { nextTick } from 'vue'
|
||||||
import { createI18n } from 'vue-i18n'
|
import { createI18n } from 'vue-i18n'
|
||||||
@@ -151,8 +152,8 @@ describe('BaseTerminal', () => {
|
|||||||
// Trigger the selection change callback that was registered during mount
|
// Trigger the selection change callback that was registered during mount
|
||||||
expect(mockTerminal.onSelectionChange).toHaveBeenCalled()
|
expect(mockTerminal.onSelectionChange).toHaveBeenCalled()
|
||||||
// Access the mock calls - TypeScript can't infer the mock structure dynamically
|
// Access the mock calls - TypeScript can't infer the mock structure dynamically
|
||||||
const selectionCallback = (mockTerminal.onSelectionChange as any).mock
|
const mockCalls = (mockTerminal.onSelectionChange as Mock).mock.calls
|
||||||
.calls[0][0]
|
const selectionCallback = mockCalls[0][0] as () => void
|
||||||
selectionCallback()
|
selectionCallback()
|
||||||
await nextTick()
|
await nextTick()
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { createApp } from 'vue'
|
|||||||
import type { SettingOption } from '@/platform/settings/types'
|
import type { SettingOption } from '@/platform/settings/types'
|
||||||
|
|
||||||
import FormRadioGroup from './FormRadioGroup.vue'
|
import FormRadioGroup from './FormRadioGroup.vue'
|
||||||
|
import type { ComponentProps } from 'vue-component-type-helpers'
|
||||||
|
|
||||||
describe('FormRadioGroup', () => {
|
describe('FormRadioGroup', () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
@@ -14,7 +15,8 @@ describe('FormRadioGroup', () => {
|
|||||||
app.use(PrimeVue)
|
app.use(PrimeVue)
|
||||||
})
|
})
|
||||||
|
|
||||||
const mountComponent = (props: any, options = {}) => {
|
type FormRadioGroupProps = ComponentProps<typeof FormRadioGroup>
|
||||||
|
const mountComponent = (props: FormRadioGroupProps, options = {}) => {
|
||||||
return mount(FormRadioGroup, {
|
return mount(FormRadioGroup, {
|
||||||
global: {
|
global: {
|
||||||
plugins: [PrimeVue],
|
plugins: [PrimeVue],
|
||||||
@@ -92,9 +94,9 @@ describe('FormRadioGroup', () => {
|
|||||||
|
|
||||||
it('handles custom object with optionLabel and optionValue', () => {
|
it('handles custom object with optionLabel and optionValue', () => {
|
||||||
const options = [
|
const options = [
|
||||||
{ name: 'First Option', id: 1 },
|
{ name: 'First Option', id: '1' },
|
||||||
{ name: 'Second Option', id: 2 },
|
{ name: 'Second Option', id: '2' },
|
||||||
{ name: 'Third Option', id: 3 }
|
{ name: 'Third Option', id: '3' }
|
||||||
]
|
]
|
||||||
|
|
||||||
const wrapper = mountComponent({
|
const wrapper = mountComponent({
|
||||||
@@ -108,9 +110,9 @@ describe('FormRadioGroup', () => {
|
|||||||
const radioButtons = wrapper.findAllComponents(RadioButton)
|
const radioButtons = wrapper.findAllComponents(RadioButton)
|
||||||
expect(radioButtons).toHaveLength(3)
|
expect(radioButtons).toHaveLength(3)
|
||||||
|
|
||||||
expect(radioButtons[0].props('value')).toBe(1)
|
expect(radioButtons[0].props('value')).toBe('1')
|
||||||
expect(radioButtons[1].props('value')).toBe(2)
|
expect(radioButtons[1].props('value')).toBe('2')
|
||||||
expect(radioButtons[2].props('value')).toBe(3)
|
expect(radioButtons[2].props('value')).toBe('3')
|
||||||
|
|
||||||
const labels = wrapper.findAll('label')
|
const labels = wrapper.findAll('label')
|
||||||
expect(labels[0].text()).toBe('First Option')
|
expect(labels[0].text()).toBe('First Option')
|
||||||
@@ -167,10 +169,7 @@ describe('FormRadioGroup', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('handles object with missing properties gracefully', () => {
|
it('handles object with missing properties gracefully', () => {
|
||||||
const options = [
|
const options = [{ label: 'Option 1', val: 'opt1' }]
|
||||||
{ label: 'Option 1', val: 'opt1' },
|
|
||||||
{ text: 'Option 2', value: 'opt2' }
|
|
||||||
]
|
|
||||||
|
|
||||||
const wrapper = mountComponent({
|
const wrapper = mountComponent({
|
||||||
modelValue: 'opt1',
|
modelValue: 'opt1',
|
||||||
@@ -179,11 +178,10 @@ describe('FormRadioGroup', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const radioButtons = wrapper.findAllComponents(RadioButton)
|
const radioButtons = wrapper.findAllComponents(RadioButton)
|
||||||
expect(radioButtons).toHaveLength(2)
|
expect(radioButtons).toHaveLength(1)
|
||||||
|
|
||||||
const labels = wrapper.findAll('label')
|
const labels = wrapper.findAll('label')
|
||||||
expect(labels[0].text()).toBe('Unknown')
|
expect(labels[0].text()).toBe('Unknown')
|
||||||
expect(labels[1].text()).toBe('Option 2')
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import type { SettingOption } from '@/platform/settings/types'
|
|||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: any
|
modelValue: any
|
||||||
options: (SettingOption | string)[]
|
options?: (string | SettingOption | Record<string, string>)[]
|
||||||
optionLabel?: string
|
optionLabel?: string
|
||||||
optionValue?: string
|
optionValue?: string
|
||||||
id?: string
|
id?: string
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { beforeEach, describe, expect, it } from 'vitest'
|
|||||||
import { createApp, nextTick } from 'vue'
|
import { createApp, nextTick } from 'vue'
|
||||||
|
|
||||||
import UrlInput from './UrlInput.vue'
|
import UrlInput from './UrlInput.vue'
|
||||||
|
import type { ComponentProps } from 'vue-component-type-helpers'
|
||||||
|
|
||||||
describe('UrlInput', () => {
|
describe('UrlInput', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -14,7 +15,13 @@ describe('UrlInput', () => {
|
|||||||
app.use(PrimeVue)
|
app.use(PrimeVue)
|
||||||
})
|
})
|
||||||
|
|
||||||
const mountComponent = (props: any, options = {}) => {
|
const mountComponent = (
|
||||||
|
props: ComponentProps<typeof UrlInput> & {
|
||||||
|
placeholder?: string
|
||||||
|
disabled?: boolean
|
||||||
|
},
|
||||||
|
options = {}
|
||||||
|
) => {
|
||||||
return mount(UrlInput, {
|
return mount(UrlInput, {
|
||||||
global: {
|
global: {
|
||||||
plugins: [PrimeVue],
|
plugins: [PrimeVue],
|
||||||
@@ -169,25 +176,25 @@ describe('UrlInput', () => {
|
|||||||
await input.setValue(' https://leading-space.com')
|
await input.setValue(' https://leading-space.com')
|
||||||
await input.trigger('input')
|
await input.trigger('input')
|
||||||
await nextTick()
|
await nextTick()
|
||||||
expect(wrapper.vm.internalValue).toBe('https://leading-space.com')
|
expect(input.element.value).toBe('https://leading-space.com')
|
||||||
|
|
||||||
// Test trailing whitespace
|
// Test trailing whitespace
|
||||||
await input.setValue('https://trailing-space.com ')
|
await input.setValue('https://trailing-space.com ')
|
||||||
await input.trigger('input')
|
await input.trigger('input')
|
||||||
await nextTick()
|
await nextTick()
|
||||||
expect(wrapper.vm.internalValue).toBe('https://trailing-space.com')
|
expect(input.element.value).toBe('https://trailing-space.com')
|
||||||
|
|
||||||
// Test both leading and trailing whitespace
|
// Test both leading and trailing whitespace
|
||||||
await input.setValue(' https://both-spaces.com ')
|
await input.setValue(' https://both-spaces.com ')
|
||||||
await input.trigger('input')
|
await input.trigger('input')
|
||||||
await nextTick()
|
await nextTick()
|
||||||
expect(wrapper.vm.internalValue).toBe('https://both-spaces.com')
|
expect(input.element.value).toBe('https://both-spaces.com')
|
||||||
|
|
||||||
// Test whitespace in the middle of the URL
|
// Test whitespace in the middle of the URL
|
||||||
await input.setValue('https:// middle-space.com')
|
await input.setValue('https:// middle-space.com')
|
||||||
await input.trigger('input')
|
await input.trigger('input')
|
||||||
await nextTick()
|
await nextTick()
|
||||||
expect(wrapper.vm.internalValue).toBe('https://middle-space.com')
|
expect(input.element.value).toBe('https://middle-space.com')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('trims whitespace when value set externally', async () => {
|
it('trims whitespace when value set externally', async () => {
|
||||||
@@ -196,15 +203,17 @@ describe('UrlInput', () => {
|
|||||||
placeholder: 'Enter URL'
|
placeholder: 'Enter URL'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const input = wrapper.find('input')
|
||||||
|
|
||||||
// Check initial value is trimmed
|
// Check initial value is trimmed
|
||||||
expect(wrapper.vm.internalValue).toBe('https://initial-value.com')
|
expect(input.element.value).toBe('https://initial-value.com')
|
||||||
|
|
||||||
// Update props with whitespace
|
// Update props with whitespace
|
||||||
await wrapper.setProps({ modelValue: ' https://updated-value.com ' })
|
await wrapper.setProps({ modelValue: ' https://updated-value.com ' })
|
||||||
await nextTick()
|
await nextTick()
|
||||||
|
|
||||||
// Check updated value is trimmed
|
// Check updated value is trimmed
|
||||||
expect(wrapper.vm.internalValue).toBe('https://updated-value.com')
|
expect(input.element.value).toBe('https://updated-value.com')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { ComponentProps } from 'vue-component-type-helpers'
|
||||||
|
|
||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import Avatar from 'primevue/avatar'
|
import Avatar from 'primevue/avatar'
|
||||||
import PrimeVue from 'primevue/config'
|
import PrimeVue from 'primevue/config'
|
||||||
@@ -27,7 +29,7 @@ describe('UserAvatar', () => {
|
|||||||
app.use(PrimeVue)
|
app.use(PrimeVue)
|
||||||
})
|
})
|
||||||
|
|
||||||
const mountComponent = (props: any = {}) => {
|
const mountComponent = (props: ComponentProps<typeof UserAvatar> = {}) => {
|
||||||
return mount(UserAvatar, {
|
return mount(UserAvatar, {
|
||||||
global: {
|
global: {
|
||||||
plugins: [PrimeVue, i18n],
|
plugins: [PrimeVue, i18n],
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ vi.mock('@/utils/formatUtil', () => ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
describe('SettingItem', () => {
|
describe('SettingItem', () => {
|
||||||
const mountComponent = (props: any, options = {}): any => {
|
const mountComponent = (props: Record<string, unknown>, options = {}) => {
|
||||||
return mount(SettingItem, {
|
return mount(SettingItem, {
|
||||||
global: {
|
global: {
|
||||||
plugins: [PrimeVue, i18n, createPinia()],
|
plugins: [PrimeVue, i18n, createPinia()],
|
||||||
@@ -32,6 +32,7 @@ describe('SettingItem', () => {
|
|||||||
'i-material-symbols:experiment-outline': true
|
'i-material-symbols:experiment-outline': true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// @ts-expect-error - Test utility accepts flexible props for testing edge cases
|
||||||
props,
|
props,
|
||||||
...options
|
...options
|
||||||
})
|
})
|
||||||
@@ -48,8 +49,9 @@ describe('SettingItem', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Get the options property of the FormItem
|
// Check the FormItem component's item prop for the options
|
||||||
const options = wrapper.vm.formItem.options
|
const formItem = wrapper.findComponent({ name: 'FormItem' })
|
||||||
|
const options = formItem.props('item').options
|
||||||
expect(options).toEqual([
|
expect(options).toEqual([
|
||||||
{ text: 'Correctly Translated', value: 'Correctly Translated' }
|
{ text: 'Correctly Translated', value: 'Correctly Translated' }
|
||||||
])
|
])
|
||||||
@@ -67,7 +69,8 @@ describe('SettingItem', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Should not throw an error and tooltip should be preserved as-is
|
// 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'
|
'This will load a larger version of @mtb/markdown-parser that bundles shiki'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|||||||
import { nextTick } from 'vue'
|
import { nextTick } from 'vue'
|
||||||
import { createI18n } from 'vue-i18n'
|
import { createI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
import type { AuditLog } from '@/services/customerEventsService'
|
||||||
import { EventType } from '@/services/customerEventsService'
|
import { EventType } from '@/services/customerEventsService'
|
||||||
|
|
||||||
import UsageLogsTable from './UsageLogsTable.vue'
|
import UsageLogsTable from './UsageLogsTable.vue'
|
||||||
@@ -19,7 +20,7 @@ import UsageLogsTable from './UsageLogsTable.vue'
|
|||||||
type ComponentInstance = InstanceType<typeof UsageLogsTable> & {
|
type ComponentInstance = InstanceType<typeof UsageLogsTable> & {
|
||||||
loading: boolean
|
loading: boolean
|
||||||
error: string | null
|
error: string | null
|
||||||
events: any[]
|
events: Partial<AuditLog>[]
|
||||||
pagination: {
|
pagination: {
|
||||||
page: number
|
page: number
|
||||||
limit: number
|
limit: number
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { ComponentProps } from 'vue-component-type-helpers'
|
||||||
|
|
||||||
import { Form } from '@primevue/forms'
|
import { Form } from '@primevue/forms'
|
||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
@@ -63,7 +65,7 @@ describe('ApiKeyForm', () => {
|
|||||||
mockLoading.mockReset()
|
mockLoading.mockReset()
|
||||||
})
|
})
|
||||||
|
|
||||||
const mountComponent = (props: any = {}) => {
|
const mountComponent = (props: ComponentProps<typeof ApiKeyForm> = {}) => {
|
||||||
return mount(ApiKeyForm, {
|
return mount(ApiKeyForm, {
|
||||||
global: {
|
global: {
|
||||||
plugins: [PrimeVue, createPinia(), i18n],
|
plugins: [PrimeVue, createPinia(), i18n],
|
||||||
|
|||||||
@@ -112,8 +112,10 @@ describe('SignInForm', () => {
|
|||||||
|
|
||||||
// Mock getElementById to track focus
|
// Mock getElementById to track focus
|
||||||
const mockFocus = vi.fn()
|
const mockFocus = vi.fn()
|
||||||
const mockElement = { focus: mockFocus }
|
const mockElement: Partial<HTMLElement> = { focus: mockFocus }
|
||||||
vi.spyOn(document, 'getElementById').mockReturnValue(mockElement as any)
|
vi.spyOn(document, 'getElementById').mockReturnValue(
|
||||||
|
mockElement as HTMLElement
|
||||||
|
)
|
||||||
|
|
||||||
// Click forgot password link while email is empty
|
// Click forgot password link while email is empty
|
||||||
await forgotPasswordSpan.trigger('click')
|
await forgotPasswordSpan.trigger('click')
|
||||||
@@ -138,7 +140,10 @@ describe('SignInForm', () => {
|
|||||||
|
|
||||||
it('calls handleForgotPassword with email when link is clicked', async () => {
|
it('calls handleForgotPassword with email when link is clicked', async () => {
|
||||||
const wrapper = mountComponent()
|
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
|
// Spy on handleForgotPassword
|
||||||
const handleForgotPasswordSpy = vi.spyOn(
|
const handleForgotPasswordSpy = vi.spyOn(
|
||||||
@@ -161,7 +166,10 @@ describe('SignInForm', () => {
|
|||||||
describe('Form Submission', () => {
|
describe('Form Submission', () => {
|
||||||
it('emits submit event when onSubmit is called with valid data', async () => {
|
it('emits submit event when onSubmit is called with valid data', async () => {
|
||||||
const wrapper = mountComponent()
|
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
|
// Call onSubmit directly with valid data
|
||||||
component.onSubmit({
|
component.onSubmit({
|
||||||
@@ -181,7 +189,10 @@ describe('SignInForm', () => {
|
|||||||
|
|
||||||
it('does not emit submit event when form is invalid', async () => {
|
it('does not emit submit event when form is invalid', async () => {
|
||||||
const wrapper = mountComponent()
|
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
|
// Call onSubmit with invalid form
|
||||||
component.onSubmit({ valid: false, values: {} })
|
component.onSubmit({ valid: false, values: {} })
|
||||||
@@ -254,12 +265,17 @@ describe('SignInForm', () => {
|
|||||||
describe('Focus Behavior', () => {
|
describe('Focus Behavior', () => {
|
||||||
it('focuses email input when handleForgotPassword is called with invalid email', async () => {
|
it('focuses email input when handleForgotPassword is called with invalid email', async () => {
|
||||||
const wrapper = mountComponent()
|
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
|
// Mock getElementById to track focus
|
||||||
const mockFocus = vi.fn()
|
const mockFocus = vi.fn()
|
||||||
const mockElement = { focus: mockFocus }
|
const mockElement: Partial<HTMLElement> = { focus: mockFocus }
|
||||||
vi.spyOn(document, 'getElementById').mockReturnValue(mockElement as any)
|
vi.spyOn(document, 'getElementById').mockReturnValue(
|
||||||
|
mockElement as HTMLElement
|
||||||
|
)
|
||||||
|
|
||||||
// Call handleForgotPassword with no email
|
// Call handleForgotPassword with no email
|
||||||
await component.handleForgotPassword('', false)
|
await component.handleForgotPassword('', false)
|
||||||
@@ -273,12 +289,17 @@ describe('SignInForm', () => {
|
|||||||
|
|
||||||
it('does not focus email input when valid email is provided', async () => {
|
it('does not focus email input when valid email is provided', async () => {
|
||||||
const wrapper = mountComponent()
|
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
|
// Mock getElementById
|
||||||
const mockFocus = vi.fn()
|
const mockFocus = vi.fn()
|
||||||
const mockElement = { focus: mockFocus }
|
const mockElement: Partial<HTMLElement> = { focus: mockFocus }
|
||||||
vi.spyOn(document, 'getElementById').mockReturnValue(mockElement as any)
|
vi.spyOn(document, 'getElementById').mockReturnValue(
|
||||||
|
mockElement as HTMLElement
|
||||||
|
)
|
||||||
|
|
||||||
// Call handleForgotPassword with valid email
|
// Call handleForgotPassword with valid email
|
||||||
await component.handleForgotPassword('test@example.com', true)
|
await component.handleForgotPassword('test@example.com', true)
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ export class ComfyApp {
|
|||||||
|
|
||||||
// TODO: Migrate internal usage to the
|
// TODO: Migrate internal usage to the
|
||||||
/** @deprecated Use {@link rootGraph} instead */
|
/** @deprecated Use {@link rootGraph} instead */
|
||||||
get graph(): unknown {
|
get graph(): LGraph | undefined {
|
||||||
return this.rootGraphInternal!
|
return this.rootGraphInternal!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,17 @@ export type {
|
|||||||
ToastMessageOptions
|
ToastMessageOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CapturedMessages {
|
||||||
|
clientFeatureFlags: { type: string; data: Record<string, unknown> } | null
|
||||||
|
serverFeatureFlags: Record<string, unknown> | null
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AppReadiness {
|
||||||
|
featureFlagsReceived: boolean
|
||||||
|
apiInitialized: boolean
|
||||||
|
appInitialized: boolean
|
||||||
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
/** For use by extensions and in the browser console. Where possible, import `app` from '@/scripts/app' instead. */
|
/** For use by extensions and in the browser console. Where possible, import `app` from '@/scripts/app' instead. */
|
||||||
@@ -71,5 +82,11 @@ declare global {
|
|||||||
|
|
||||||
/** For use by extensions and in the browser console. Where possible, import `app` and access via `app.graph` instead. */
|
/** For use by extensions and in the browser console. Where possible, import `app` and access via `app.graph` instead. */
|
||||||
graph?: unknown
|
graph?: unknown
|
||||||
|
|
||||||
|
/** For use in tests to capture WebSocket messages */
|
||||||
|
__capturedMessages?: CapturedMessages
|
||||||
|
|
||||||
|
/** For use in tests to track app initialization state */
|
||||||
|
__appReadiness?: AppReadiness
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user