mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 03:01:54 +00:00
chore: migrate tests from tests-ui/ to colocate with source files (#7811)
## Summary Migrates all unit tests from `tests-ui/` to colocate with their source files in `src/`, improving discoverability and maintainability. ## Changes - **What**: Relocated all unit tests to be adjacent to the code they test, following the `<source>.test.ts` naming convention - **Config**: Updated `vitest.config.ts` to remove `tests-ui` include pattern and `@tests-ui` alias - **Docs**: Moved testing documentation to `docs/testing/` with updated paths and patterns ## Review Focus - Migration patterns documented in `temp/plans/migrate-tests-ui-to-src.md` - Tests use `@/` path aliases instead of relative imports - Shared fixtures placed in `__fixtures__/` directories ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7811-chore-migrate-tests-from-tests-ui-to-colocate-with-source-files-2da6d73d36508147a4cce85365dee614) by [Unito](https://www.unito.io) --------- Co-authored-by: Amp <amp@ampcode.com> Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
@@ -12,12 +12,17 @@ Documentation for unit tests is organized into three guides:
|
|||||||
|
|
||||||
## Testing Structure
|
## Testing Structure
|
||||||
|
|
||||||
The ComfyUI Frontend project uses a mixed approach to unit test organization:
|
The ComfyUI Frontend project uses **colocated tests** - test files are placed alongside their source files:
|
||||||
|
|
||||||
- **Component Tests**: Located directly alongside their components with a `.spec.ts` extension
|
- **Component Tests**: Located directly alongside their components (e.g., `MyComponent.test.ts` next to `MyComponent.vue`)
|
||||||
- **Unit Tests**: Located in the `tests-ui/tests/` directory
|
- **Unit Tests**: Located alongside their source files (e.g., `myUtil.test.ts` next to `myUtil.ts`)
|
||||||
- **Store Tests**: Located in the `tests-ui/tests/store/` directory
|
- **Store Tests**: Located in `src/stores/` alongside their store files
|
||||||
- **Browser Tests**: These are located in the `browser_tests/` directory. There is a dedicated README in the `browser_tests/` directory, so it will not be covered here.
|
- **Browser Tests**: Located in the `browser_tests/` directory (see dedicated README there)
|
||||||
|
|
||||||
|
### Test File Naming
|
||||||
|
|
||||||
|
- Use `.test.ts` extension for test files
|
||||||
|
- Name tests after their source file: `sourceFile.test.ts`
|
||||||
|
|
||||||
## Test Frameworks and Libraries
|
## Test Frameworks and Libraries
|
||||||
|
|
||||||
@@ -35,8 +40,11 @@ To run the tests locally:
|
|||||||
# Run unit tests
|
# Run unit tests
|
||||||
pnpm test:unit
|
pnpm test:unit
|
||||||
|
|
||||||
|
# Run a specific test file
|
||||||
|
pnpm test:unit -- src/path/to/file.test.ts
|
||||||
|
|
||||||
# Run unit tests in watch mode
|
# Run unit tests in watch mode
|
||||||
pnpm test:unit -- --watch
|
pnpm test:unit -- --watch
|
||||||
```
|
```
|
||||||
|
|
||||||
Refer to the specific guides for more detailed information on each testing type.
|
Refer to the specific guides for more detailed information on each testing type.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
import { getMediaTypeFromFilename, truncateFilename } from '@/utils/formatUtil'
|
import { getMediaTypeFromFilename, truncateFilename } from './formatUtil'
|
||||||
|
|
||||||
describe('formatUtil', () => {
|
describe('formatUtil', () => {
|
||||||
describe('truncateFilename', () => {
|
describe('truncateFilename', () => {
|
||||||
@@ -22,7 +22,6 @@ const QueueJobItemStub = defineComponent({
|
|||||||
runningNodeName: { type: String, default: undefined },
|
runningNodeName: { type: String, default: undefined },
|
||||||
activeDetailsId: { type: String, default: null }
|
activeDetailsId: { type: String, default: null }
|
||||||
},
|
},
|
||||||
emits: ['cancel', 'delete', 'menu', 'view', 'details-enter', 'details-leave'],
|
|
||||||
template: '<div class="queue-job-item-stub"></div>'
|
template: '<div class="queue-job-item-stub"></div>'
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -2,12 +2,8 @@ import { createPinia, setActivePinia } from 'pinia'
|
|||||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
import { useSelectedLiteGraphItems } from '@/composables/canvas/useSelectedLiteGraphItems'
|
import { useSelectedLiteGraphItems } from '@/composables/canvas/useSelectedLiteGraphItems'
|
||||||
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
import type { LGraphNode, Positionable } from '@/lib/litegraph/src/litegraph'
|
||||||
import {
|
import { LGraphEventMode, Reroute } from '@/lib/litegraph/src/litegraph'
|
||||||
LGraphEventMode,
|
|
||||||
type Positionable,
|
|
||||||
Reroute
|
|
||||||
} from '@/lib/litegraph/src/litegraph'
|
|
||||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||||
import { app } from '@/scripts/app'
|
import { app } from '@/scripts/app'
|
||||||
|
|
||||||
@@ -2,7 +2,7 @@ import { describe, expect, vi } from 'vitest'
|
|||||||
|
|
||||||
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||||
|
|
||||||
import { subgraphTest } from '../../litegraph/subgraph/fixtures/subgraphFixtures'
|
import { subgraphTest } from '@/lib/litegraph/src/subgraph/__fixtures__/subgraphFixtures'
|
||||||
|
|
||||||
import { usePriceBadge } from '@/composables/node/usePriceBadge'
|
import { usePriceBadge } from '@/composables/node/usePriceBadge'
|
||||||
|
|
||||||
@@ -284,7 +284,7 @@ describe('useJobMenu', () => {
|
|||||||
|
|
||||||
await nextTick()
|
await nextTick()
|
||||||
const entry = findActionEntry(jobMenuEntries.value, 'report-error')
|
const entry = findActionEntry(jobMenuEntries.value, 'report-error')
|
||||||
entry?.onClick?.()
|
void entry?.onClick?.()
|
||||||
|
|
||||||
expect(dialogServiceMock.showExecutionErrorDialog).toHaveBeenCalledWith(
|
expect(dialogServiceMock.showExecutionErrorDialog).toHaveBeenCalledWith(
|
||||||
error
|
error
|
||||||
@@ -460,7 +460,7 @@ describe('useJobMenu', () => {
|
|||||||
|
|
||||||
await nextTick()
|
await nextTick()
|
||||||
const entry = findActionEntry(jobMenuEntries.value, 'download')
|
const entry = findActionEntry(jobMenuEntries.value, 'download')
|
||||||
entry?.onClick?.()
|
void entry?.onClick?.()
|
||||||
|
|
||||||
expect(downloadFileMock).toHaveBeenCalledWith('https://asset')
|
expect(downloadFileMock).toHaveBeenCalledWith('https://asset')
|
||||||
})
|
})
|
||||||
@@ -471,7 +471,7 @@ describe('useJobMenu', () => {
|
|||||||
|
|
||||||
await nextTick()
|
await nextTick()
|
||||||
const entry = findActionEntry(jobMenuEntries.value, 'download')
|
const entry = findActionEntry(jobMenuEntries.value, 'download')
|
||||||
entry?.onClick?.()
|
void entry?.onClick?.()
|
||||||
|
|
||||||
expect(downloadFileMock).not.toHaveBeenCalled()
|
expect(downloadFileMock).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
@@ -9,8 +9,8 @@ import { app } from '@/scripts/app'
|
|||||||
|
|
||||||
// Mock vue-i18n for useExternalLink
|
// Mock vue-i18n for useExternalLink
|
||||||
const mockLocale = ref('en')
|
const mockLocale = ref('en')
|
||||||
vi.mock('vue-i18n', async (importOriginal) => {
|
vi.mock('vue-i18n', async () => {
|
||||||
const actual = await importOriginal<typeof import('vue-i18n')>()
|
const actual = await vi.importActual('vue-i18n')
|
||||||
return {
|
return {
|
||||||
...actual,
|
...actual,
|
||||||
useI18n: vi.fn(() => ({
|
useI18n: vi.fn(() => ({
|
||||||
@@ -8,7 +8,7 @@ import type { LGraphCanvas, SubgraphNode } from '@/lib/litegraph/src/litegraph'
|
|||||||
import {
|
import {
|
||||||
createTestSubgraph,
|
createTestSubgraph,
|
||||||
createTestSubgraphNode
|
createTestSubgraphNode
|
||||||
} from '../litegraph/subgraph/fixtures/subgraphHelpers'
|
} from '@/lib/litegraph/src/subgraph/__fixtures__/subgraphHelpers'
|
||||||
|
|
||||||
const canvasEl: Partial<HTMLCanvasElement> = { addEventListener() {} }
|
const canvasEl: Partial<HTMLCanvasElement> = { addEventListener() {} }
|
||||||
const canvas: Partial<LGraphCanvas> = { canvas: canvasEl as HTMLCanvasElement }
|
const canvas: Partial<LGraphCanvas> = { canvas: canvasEl as HTMLCanvasElement }
|
||||||
@@ -3,7 +3,7 @@ import { describe } from 'vitest'
|
|||||||
|
|
||||||
import { LGraph } from '@/lib/litegraph/src/litegraph'
|
import { LGraph } from '@/lib/litegraph/src/litegraph'
|
||||||
|
|
||||||
import { dirtyTest } from './fixtures/testExtensions'
|
import { dirtyTest } from './__fixtures__/testExtensions'
|
||||||
|
|
||||||
describe.skip('LGraph configure()', () => {
|
describe.skip('LGraph configure()', () => {
|
||||||
dirtyTest(
|
dirtyTest(
|
||||||
@@ -3,7 +3,7 @@ import { describe } from 'vitest'
|
|||||||
|
|
||||||
import { LGraph } from '@/lib/litegraph/src/litegraph'
|
import { LGraph } from '@/lib/litegraph/src/litegraph'
|
||||||
|
|
||||||
import { dirtyTest } from './fixtures/testExtensions'
|
import { dirtyTest } from './__fixtures__/testExtensions'
|
||||||
|
|
||||||
describe.skip('LGraph (constructor only)', () => {
|
describe.skip('LGraph (constructor only)', () => {
|
||||||
dirtyTest(
|
dirtyTest(
|
||||||
@@ -3,7 +3,7 @@ import { describe } from 'vitest'
|
|||||||
import { LGraph, LGraphGroup, LGraphNode } from '@/lib/litegraph/src/litegraph'
|
import { LGraph, LGraphGroup, LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||||
import type { ISerialisedGraph } from '@/lib/litegraph/src/litegraph'
|
import type { ISerialisedGraph } from '@/lib/litegraph/src/litegraph'
|
||||||
|
|
||||||
import { test } from './fixtures/testExtensions'
|
import { test } from './__fixtures__/testExtensions'
|
||||||
|
|
||||||
describe('LGraph Serialisation', () => {
|
describe('LGraph Serialisation', () => {
|
||||||
test('can (de)serialise node / group titles', ({ expect, minimalGraph }) => {
|
test('can (de)serialise node / group titles', ({ expect, minimalGraph }) => {
|
||||||
@@ -1,10 +1,43 @@
|
|||||||
import { describe } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
import { LGraph, LGraphNode, LiteGraph } from '@/lib/litegraph/src/litegraph'
|
import { LGraph, LGraphNode, LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||||
|
|
||||||
import { test } from './fixtures/testExtensions'
|
import { test } from './__fixtures__/testExtensions'
|
||||||
|
|
||||||
|
function swapNodes(nodes: LGraphNode[]) {
|
||||||
|
const firstNode = nodes[0]
|
||||||
|
const lastNode = nodes[nodes.length - 1]
|
||||||
|
nodes[0] = lastNode
|
||||||
|
nodes[nodes.length - 1] = firstNode
|
||||||
|
return nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
function createGraph(...nodes: LGraphNode[]) {
|
||||||
|
const graph = new LGraph()
|
||||||
|
nodes.forEach((node) => graph.add(node))
|
||||||
|
return graph
|
||||||
|
}
|
||||||
|
|
||||||
|
class DummyNode extends LGraphNode {
|
||||||
|
constructor() {
|
||||||
|
super('dummy')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
describe('LGraph', () => {
|
describe('LGraph', () => {
|
||||||
|
it('should serialize deterministic node order', async () => {
|
||||||
|
LiteGraph.registerNodeType('dummy', DummyNode)
|
||||||
|
const node1 = new DummyNode()
|
||||||
|
const node2 = new DummyNode()
|
||||||
|
const graph = createGraph(node1, node2)
|
||||||
|
|
||||||
|
const result1 = graph.serialize({ sortNodes: true })
|
||||||
|
expect(result1.nodes).not.toHaveLength(0)
|
||||||
|
graph._nodes = swapNodes(graph.nodes)
|
||||||
|
const result2 = graph.serialize({ sortNodes: true })
|
||||||
|
|
||||||
|
expect(result1).toEqual(result2)
|
||||||
|
})
|
||||||
test('can be instantiated', ({ expect }) => {
|
test('can be instantiated', ({ expect }) => {
|
||||||
// @ts-expect-error Intentional - extra holds any / all consumer data that should be serialised
|
// @ts-expect-error Intentional - extra holds any / all consumer data that should be serialised
|
||||||
const graph = new LGraph({ extra: 'TestGraph' })
|
const graph = new LGraph({ extra: 'TestGraph' })
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import { describe, expect, it, vi } from 'vitest'
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
import { LGraphButton } from '@/lib/litegraph/src/litegraph'
|
import { LGraphButton, Rectangle } from '@/lib/litegraph/src/litegraph'
|
||||||
import { Rectangle } from '@/lib/litegraph/src/litegraph'
|
|
||||||
|
|
||||||
describe('LGraphButton', () => {
|
describe('LGraphButton', () => {
|
||||||
describe('Constructor', () => {
|
describe('Constructor', () => {
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
import { LGraphCanvas } from '@/lib/litegraph/src/litegraph'
|
import {
|
||||||
import { LGraphNode, LiteGraph } from '@/lib/litegraph/src/litegraph'
|
LGraphCanvas,
|
||||||
|
LGraphNode,
|
||||||
|
LiteGraph
|
||||||
|
} from '@/lib/litegraph/src/litegraph'
|
||||||
|
|
||||||
describe('LGraphCanvas Title Button Rendering', () => {
|
describe('LGraphCanvas Title Button Rendering', () => {
|
||||||
let canvas: LGraphCanvas
|
let canvas: LGraphCanvas
|
||||||
@@ -2,7 +2,7 @@ import { describe, expect } from 'vitest'
|
|||||||
|
|
||||||
import { LGraphGroup } from '@/lib/litegraph/src/litegraph'
|
import { LGraphGroup } from '@/lib/litegraph/src/litegraph'
|
||||||
|
|
||||||
import { test } from './fixtures/testExtensions'
|
import { test } from './__fixtures__/testExtensions'
|
||||||
|
|
||||||
describe('LGraphGroup', () => {
|
describe('LGraphGroup', () => {
|
||||||
test('serializes to the existing format', () => {
|
test('serializes to the existing format', () => {
|
||||||
@@ -2,7 +2,7 @@ import { beforeEach, describe, expect } from 'vitest'
|
|||||||
|
|
||||||
import { LGraphNode, LiteGraph } from '@/lib/litegraph/src/litegraph'
|
import { LGraphNode, LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||||
|
|
||||||
import { test } from './fixtures/testExtensions'
|
import { test } from './__fixtures__/testExtensions'
|
||||||
|
|
||||||
describe('LGraphNode resize functionality', () => {
|
describe('LGraphNode resize functionality', () => {
|
||||||
let node: LGraphNode
|
let node: LGraphNode
|
||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
NodeOutputSlot
|
NodeOutputSlot
|
||||||
} from '@/lib/litegraph/src/litegraph'
|
} from '@/lib/litegraph/src/litegraph'
|
||||||
|
|
||||||
import { test } from './fixtures/testExtensions'
|
import { test } from './__fixtures__/testExtensions'
|
||||||
|
|
||||||
function getMockISerialisedNode(
|
function getMockISerialisedNode(
|
||||||
data: Partial<ISerialisedNode>
|
data: Partial<ISerialisedNode>
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
import { describe, expect, it, vi } from 'vitest'
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
import { LGraphButton } from '@/lib/litegraph/src/litegraph'
|
|
||||||
import type { LGraphCanvas } from '@/lib/litegraph/src/litegraph'
|
import type { LGraphCanvas } from '@/lib/litegraph/src/litegraph'
|
||||||
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
import { LGraphButton, LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||||
|
|
||||||
describe('LGraphNode Title Buttons', () => {
|
describe('LGraphNode Title Buttons', () => {
|
||||||
describe('addTitleButton', () => {
|
describe('addTitleButton', () => {
|
||||||
@@ -2,7 +2,7 @@ import { describe, expect } from 'vitest'
|
|||||||
|
|
||||||
import { LLink } from '@/lib/litegraph/src/litegraph'
|
import { LLink } from '@/lib/litegraph/src/litegraph'
|
||||||
|
|
||||||
import { test } from './fixtures/testExtensions'
|
import { test } from './__fixtures__/testExtensions'
|
||||||
|
|
||||||
describe('LLink', () => {
|
describe('LLink', () => {
|
||||||
test('matches previous snapshot', () => {
|
test('matches previous snapshot', () => {
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// oxlint-disable no-empty-pattern
|
||||||
import { test as baseTest } from 'vitest'
|
import { test as baseTest } from 'vitest'
|
||||||
|
|
||||||
import { LGraph } from '@/lib/litegraph/src/LGraph'
|
import { LGraph } from '@/lib/litegraph/src/LGraph'
|
||||||
@@ -33,7 +34,6 @@ interface DirtyFixtures {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const test = baseTest.extend<LitegraphFixtures>({
|
export const test = baseTest.extend<LitegraphFixtures>({
|
||||||
// eslint-disable-next-line no-empty-pattern
|
|
||||||
minimalGraph: async ({}, use) => {
|
minimalGraph: async ({}, use) => {
|
||||||
// Before each test function
|
// Before each test function
|
||||||
const serialisable = structuredClone(minimalSerialisableGraph)
|
const serialisable = structuredClone(minimalSerialisableGraph)
|
||||||
@@ -48,7 +48,7 @@ export const test = baseTest.extend<LitegraphFixtures>({
|
|||||||
floatingLink as unknown as ISerialisedGraph
|
floatingLink as unknown as ISerialisedGraph
|
||||||
),
|
),
|
||||||
linkedNodesGraph: structuredClone(linkedNodes as unknown as ISerialisedGraph),
|
linkedNodesGraph: structuredClone(linkedNodes as unknown as ISerialisedGraph),
|
||||||
// eslint-disable-next-line no-empty-pattern
|
|
||||||
floatingBranchGraph: async ({}, use) => {
|
floatingBranchGraph: async ({}, use) => {
|
||||||
const cloned = structuredClone(
|
const cloned = structuredClone(
|
||||||
floatingBranch as unknown as ISerialisedGraph
|
floatingBranch as unknown as ISerialisedGraph
|
||||||
@@ -56,7 +56,7 @@ export const test = baseTest.extend<LitegraphFixtures>({
|
|||||||
const graph = new LGraph(cloned)
|
const graph = new LGraph(cloned)
|
||||||
await use(graph)
|
await use(graph)
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line no-empty-pattern
|
|
||||||
reroutesComplexGraph: async ({}, use) => {
|
reroutesComplexGraph: async ({}, use) => {
|
||||||
const cloned = structuredClone(
|
const cloned = structuredClone(
|
||||||
reroutesComplex as unknown as ISerialisedGraph
|
reroutesComplex as unknown as ISerialisedGraph
|
||||||
@@ -68,7 +68,6 @@ export const test = baseTest.extend<LitegraphFixtures>({
|
|||||||
|
|
||||||
/** Test that use {@link DirtyFixtures}. One test per file. */
|
/** Test that use {@link DirtyFixtures}. One test per file. */
|
||||||
export const dirtyTest = test.extend<DirtyFixtures>({
|
export const dirtyTest = test.extend<DirtyFixtures>({
|
||||||
// eslint-disable-next-line no-empty-pattern
|
|
||||||
basicSerialisableGraph: async ({}, use) => {
|
basicSerialisableGraph: async ({}, use) => {
|
||||||
if (!basicSerialisableGraph.nodes) throw new Error('Invalid test object')
|
if (!basicSerialisableGraph.nodes) throw new Error('Invalid test object')
|
||||||
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// oxlint-disable no-empty-pattern
|
||||||
import { test as baseTest, describe, expect, vi } from 'vitest'
|
import { test as baseTest, describe, expect, vi } from 'vitest'
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// oxlint-disable no-empty-pattern
|
||||||
// TODO: Fix these tests after migration
|
// TODO: Fix these tests after migration
|
||||||
import { afterEach, describe, expect, vi } from 'vitest'
|
import { afterEach, describe, expect, vi } from 'vitest'
|
||||||
|
|
||||||
@@ -9,7 +10,7 @@ import type {
|
|||||||
} from '@/lib/litegraph/src/litegraph'
|
} from '@/lib/litegraph/src/litegraph'
|
||||||
import { LGraphNode, LLink, LinkConnector } from '@/lib/litegraph/src/litegraph'
|
import { LGraphNode, LLink, LinkConnector } from '@/lib/litegraph/src/litegraph'
|
||||||
|
|
||||||
import { test as baseTest } from './fixtures/testExtensions'
|
import { test as baseTest } from '../__fixtures__/testExtensions'
|
||||||
import type { ConnectingLink } from '@/lib/litegraph/src/interfaces'
|
import type { ConnectingLink } from '@/lib/litegraph/src/interfaces'
|
||||||
|
|
||||||
interface TestContext {
|
interface TestContext {
|
||||||
@@ -1077,6 +1078,7 @@ describe('LinkConnector Integration', () => {
|
|||||||
const originalParentChain = LLink.getReroutes(graph, toReroute)
|
const originalParentChain = LLink.getReroutes(graph, toReroute)
|
||||||
|
|
||||||
const sortAndJoin = (numbers: Iterable<number>) =>
|
const sortAndJoin = (numbers: Iterable<number>) =>
|
||||||
|
// oxlint-disable-next-line require-array-sort-compare
|
||||||
[...numbers].sort().join(',')
|
[...numbers].sort().join(',')
|
||||||
const hasIdenticalLinks = (a: Reroute, b: Reroute) =>
|
const hasIdenticalLinks = (a: Reroute, b: Reroute) =>
|
||||||
sortAndJoin(a.linkIds) === sortAndJoin(b.linkIds) &&
|
sortAndJoin(a.linkIds) === sortAndJoin(b.linkIds) &&
|
||||||
@@ -12,7 +12,7 @@ import { ToInputFromIoNodeLink } from '@/lib/litegraph/src/canvas/ToInputFromIoN
|
|||||||
import type { NodeInputSlot } from '@/lib/litegraph/src/litegraph'
|
import type { NodeInputSlot } from '@/lib/litegraph/src/litegraph'
|
||||||
import { LinkDirection } from '@/lib/litegraph/src/types/globalEnums'
|
import { LinkDirection } from '@/lib/litegraph/src/types/globalEnums'
|
||||||
|
|
||||||
import { createTestSubgraph } from '../subgraph/fixtures/subgraphHelpers'
|
import { createTestSubgraph } from '../subgraph/__fixtures__/subgraphHelpers'
|
||||||
|
|
||||||
describe('LinkConnector SubgraphInput connection validation', () => {
|
describe('LinkConnector SubgraphInput connection validation', () => {
|
||||||
let connector: LinkConnector
|
let connector: LinkConnector
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import { describe, expect, it, vi } from 'vitest'
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
import { ToOutputRenderLink } from '@/lib/litegraph/src/litegraph'
|
import {
|
||||||
import { LinkDirection } from '@/lib/litegraph/src/litegraph'
|
LinkDirection,
|
||||||
|
ToOutputRenderLink
|
||||||
|
} from '@/lib/litegraph/src/litegraph'
|
||||||
|
|
||||||
describe('ToOutputRenderLink', () => {
|
describe('ToOutputRenderLink', () => {
|
||||||
describe('connectToOutput', () => {
|
describe('connectToOutput', () => {
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// oxlint-disable no-empty-pattern
|
||||||
import { test as baseTest, describe, expect, vi } from 'vitest'
|
import { test as baseTest, describe, expect, vi } from 'vitest'
|
||||||
|
|
||||||
import { Rectangle } from '@/lib/litegraph/src/litegraph'
|
import { Rectangle } from '@/lib/litegraph/src/litegraph'
|
||||||
@@ -6,7 +7,6 @@ import type { Point, Size } from '@/lib/litegraph/src/litegraph'
|
|||||||
// TODO: If there's a common test context, use it here
|
// TODO: If there's a common test context, use it here
|
||||||
// For now, we'll define a simple context for Rectangle tests
|
// For now, we'll define a simple context for Rectangle tests
|
||||||
const test = baseTest.extend<{ rect: Rectangle }>({
|
const test = baseTest.extend<{ rect: Rectangle }>({
|
||||||
// eslint-disable-next-line no-empty-pattern
|
|
||||||
rect: async ({}, use) => {
|
rect: async ({}, use) => {
|
||||||
await use(new Rectangle())
|
await use(new Rectangle())
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
import { clamp } from 'es-toolkit/compat'
|
import { clamp } from 'es-toolkit/compat'
|
||||||
import { beforeEach, describe, expect, vi } from 'vitest'
|
import { beforeEach, describe, expect, vi } from 'vitest'
|
||||||
|
|
||||||
import { LiteGraphGlobal } from '@/lib/litegraph/src/litegraph'
|
import {
|
||||||
import { LGraphCanvas, LiteGraph } from '@/lib/litegraph/src/litegraph'
|
LiteGraphGlobal,
|
||||||
|
LGraphCanvas,
|
||||||
|
LiteGraph
|
||||||
|
} from '@/lib/litegraph/src/litegraph'
|
||||||
|
|
||||||
import { test } from './fixtures/testExtensions'
|
import { test } from './__fixtures__/testExtensions'
|
||||||
|
|
||||||
describe('Litegraph module', () => {
|
describe('Litegraph module', () => {
|
||||||
test('contains a global export', ({ expect }) => {
|
test('contains a global export', ({ expect }) => {
|
||||||
@@ -1,14 +1,17 @@
|
|||||||
// TODO: Fix these tests after migration
|
// TODO: Fix these tests after migration
|
||||||
import { describe, expect, it, vi } from 'vitest'
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
import { LGraph, LGraphNode } from '@/lib/litegraph/src/litegraph'
|
import {
|
||||||
import { ExecutableNodeDTO } from '@/lib/litegraph/src/litegraph'
|
LGraph,
|
||||||
|
LGraphNode,
|
||||||
|
ExecutableNodeDTO
|
||||||
|
} from '@/lib/litegraph/src/litegraph'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
createNestedSubgraphs,
|
createNestedSubgraphs,
|
||||||
createTestSubgraph,
|
createTestSubgraph,
|
||||||
createTestSubgraphNode
|
createTestSubgraphNode
|
||||||
} from './fixtures/subgraphHelpers'
|
} from './__fixtures__/subgraphHelpers'
|
||||||
|
|
||||||
describe.skip('ExecutableNodeDTO Creation', () => {
|
describe.skip('ExecutableNodeDTO Creation', () => {
|
||||||
it('should create DTO from regular node', () => {
|
it('should create DTO from regular node', () => {
|
||||||
@@ -8,16 +8,19 @@
|
|||||||
*/
|
*/
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
import { RecursionError } from '@/lib/litegraph/src/litegraph'
|
import {
|
||||||
import { LGraph, Subgraph } from '@/lib/litegraph/src/litegraph'
|
createUuidv4,
|
||||||
import { createUuidv4 } from '@/lib/litegraph/src/litegraph'
|
RecursionError,
|
||||||
|
LGraph,
|
||||||
|
Subgraph
|
||||||
|
} from '@/lib/litegraph/src/litegraph'
|
||||||
|
|
||||||
import { subgraphTest } from './fixtures/subgraphFixtures'
|
import { subgraphTest } from './__fixtures__/subgraphFixtures'
|
||||||
import {
|
import {
|
||||||
assertSubgraphStructure,
|
assertSubgraphStructure,
|
||||||
createTestSubgraph,
|
createTestSubgraph,
|
||||||
createTestSubgraphData
|
createTestSubgraphData
|
||||||
} from './fixtures/subgraphHelpers'
|
} from './__fixtures__/subgraphHelpers'
|
||||||
|
|
||||||
describe.skip('Subgraph Construction', () => {
|
describe.skip('Subgraph Construction', () => {
|
||||||
it('should create a subgraph with minimal data', () => {
|
it('should create a subgraph with minimal data', () => {
|
||||||
@@ -1,18 +1,17 @@
|
|||||||
// TODO: Fix these tests after migration
|
// TODO: Fix these tests after migration
|
||||||
import { assert, describe, expect, it } from 'vitest'
|
import { assert, describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
import type { LGraph } from '@/lib/litegraph/src/litegraph'
|
|
||||||
import {
|
import {
|
||||||
type ISlotType,
|
|
||||||
LGraphGroup,
|
LGraphGroup,
|
||||||
LGraphNode,
|
LGraphNode,
|
||||||
LiteGraph
|
LiteGraph
|
||||||
} from '@/lib/litegraph/src/litegraph'
|
} from '@/lib/litegraph/src/litegraph'
|
||||||
|
import type { LGraph, ISlotType } from '@/lib/litegraph/src/litegraph'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
createTestSubgraph,
|
createTestSubgraph,
|
||||||
createTestSubgraphNode
|
createTestSubgraphNode
|
||||||
} from './fixtures/subgraphHelpers'
|
} from './__fixtures__/subgraphHelpers'
|
||||||
|
|
||||||
function createNode(
|
function createNode(
|
||||||
graph: LGraph,
|
graph: LGraph,
|
||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
createNestedSubgraphs,
|
createNestedSubgraphs,
|
||||||
createTestSubgraph,
|
createTestSubgraph,
|
||||||
createTestSubgraphNode
|
createTestSubgraphNode
|
||||||
} from './fixtures/subgraphHelpers'
|
} from './__fixtures__/subgraphHelpers'
|
||||||
|
|
||||||
describe.skip('SubgraphEdgeCases - Recursion Detection', () => {
|
describe.skip('SubgraphEdgeCases - Recursion Detection', () => {
|
||||||
it('should handle circular subgraph references without crashing', () => {
|
it('should handle circular subgraph references without crashing', () => {
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
// TODO: Fix these tests after migration
|
// TODO: Fix these tests after migration
|
||||||
import { describe, expect, vi } from 'vitest'
|
import { describe, expect, vi } from 'vitest'
|
||||||
|
|
||||||
import { subgraphTest } from './fixtures/subgraphFixtures'
|
import { subgraphTest } from './__fixtures__/subgraphFixtures'
|
||||||
import { verifyEventSequence } from './fixtures/subgraphHelpers'
|
import { verifyEventSequence } from './__fixtures__/subgraphHelpers'
|
||||||
|
|
||||||
describe.skip('SubgraphEvents - Event Payload Verification', () => {
|
describe.skip('SubgraphEvents - Event Payload Verification', () => {
|
||||||
subgraphTest(
|
subgraphTest(
|
||||||
@@ -5,11 +5,11 @@ import { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
|||||||
import { ToInputFromIoNodeLink } from '@/lib/litegraph/src/canvas/ToInputFromIoNodeLink'
|
import { ToInputFromIoNodeLink } from '@/lib/litegraph/src/canvas/ToInputFromIoNodeLink'
|
||||||
import { LinkDirection } from '@/lib/litegraph/src//types/globalEnums'
|
import { LinkDirection } from '@/lib/litegraph/src//types/globalEnums'
|
||||||
|
|
||||||
import { subgraphTest } from './fixtures/subgraphFixtures'
|
import { subgraphTest } from './__fixtures__/subgraphFixtures'
|
||||||
import {
|
import {
|
||||||
createTestSubgraph,
|
createTestSubgraph,
|
||||||
createTestSubgraphNode
|
createTestSubgraphNode
|
||||||
} from './fixtures/subgraphHelpers'
|
} from './__fixtures__/subgraphHelpers'
|
||||||
|
|
||||||
describe('SubgraphIO - Input Slot Dual-Nature Behavior', () => {
|
describe('SubgraphIO - Input Slot Dual-Nature Behavior', () => {
|
||||||
subgraphTest(
|
subgraphTest(
|
||||||
@@ -3,11 +3,11 @@ import { describe, expect, it, vi } from 'vitest'
|
|||||||
|
|
||||||
import { LGraph } from '@/lib/litegraph/src/litegraph'
|
import { LGraph } from '@/lib/litegraph/src/litegraph'
|
||||||
|
|
||||||
import { subgraphTest } from './fixtures/subgraphFixtures'
|
import { subgraphTest } from './__fixtures__/subgraphFixtures'
|
||||||
import {
|
import {
|
||||||
createTestSubgraph,
|
createTestSubgraph,
|
||||||
createTestSubgraphNode
|
createTestSubgraphNode
|
||||||
} from './fixtures/subgraphHelpers'
|
} from './__fixtures__/subgraphHelpers'
|
||||||
|
|
||||||
describe.skip('SubgraphNode Memory Management', () => {
|
describe.skip('SubgraphNode Memory Management', () => {
|
||||||
describe.skip('Event Listener Cleanup', () => {
|
describe.skip('Event Listener Cleanup', () => {
|
||||||
@@ -9,11 +9,11 @@ import { describe, expect, it, vi } from 'vitest'
|
|||||||
|
|
||||||
import { LGraph, Subgraph } from '@/lib/litegraph/src/litegraph'
|
import { LGraph, Subgraph } from '@/lib/litegraph/src/litegraph'
|
||||||
|
|
||||||
import { subgraphTest } from './fixtures/subgraphFixtures'
|
import { subgraphTest } from './__fixtures__/subgraphFixtures'
|
||||||
import {
|
import {
|
||||||
createTestSubgraph,
|
createTestSubgraph,
|
||||||
createTestSubgraphNode
|
createTestSubgraphNode
|
||||||
} from './fixtures/subgraphHelpers'
|
} from './__fixtures__/subgraphHelpers'
|
||||||
|
|
||||||
describe.skip('SubgraphNode Construction', () => {
|
describe.skip('SubgraphNode Construction', () => {
|
||||||
it('should create a SubgraphNode from a subgraph definition', () => {
|
it('should create a SubgraphNode from a subgraph definition', () => {
|
||||||
@@ -7,7 +7,7 @@ import type { LGraphCanvas } from '@/lib/litegraph/src/litegraph'
|
|||||||
import {
|
import {
|
||||||
createTestSubgraph,
|
createTestSubgraph,
|
||||||
createTestSubgraphNode
|
createTestSubgraphNode
|
||||||
} from './fixtures/subgraphHelpers'
|
} from './__fixtures__/subgraphHelpers'
|
||||||
|
|
||||||
describe.skip('SubgraphNode Title Button', () => {
|
describe.skip('SubgraphNode Title Button', () => {
|
||||||
describe.skip('Constructor', () => {
|
describe.skip('Constructor', () => {
|
||||||
@@ -12,7 +12,7 @@ import { LGraph, Subgraph } from '@/lib/litegraph/src/litegraph'
|
|||||||
import {
|
import {
|
||||||
createTestSubgraph,
|
createTestSubgraph,
|
||||||
createTestSubgraphNode
|
createTestSubgraphNode
|
||||||
} from './fixtures/subgraphHelpers'
|
} from './__fixtures__/subgraphHelpers'
|
||||||
|
|
||||||
describe.skip('SubgraphSerialization - Basic Serialization', () => {
|
describe.skip('SubgraphSerialization - Basic Serialization', () => {
|
||||||
it('should save and load simple subgraphs', () => {
|
it('should save and load simple subgraphs', () => {
|
||||||
@@ -1,21 +1,24 @@
|
|||||||
// TODO: Fix these tests after migration
|
// TODO: Fix these tests after migration
|
||||||
import { describe, expect, it, vi } from 'vitest'
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
import { LinkConnector } from '@/lib/litegraph/src/litegraph'
|
|
||||||
import { ToInputFromIoNodeLink } from '@/lib/litegraph/src/litegraph'
|
|
||||||
import { SUBGRAPH_INPUT_ID } from '@/lib/litegraph/src/litegraph'
|
|
||||||
import { LGraphNode, type LinkNetwork } from '@/lib/litegraph/src/litegraph'
|
|
||||||
import type { NodeInputSlot } from '@/lib/litegraph/src/litegraph'
|
|
||||||
import type { NodeOutputSlot } from '@/lib/litegraph/src/litegraph'
|
|
||||||
import {
|
import {
|
||||||
|
SUBGRAPH_INPUT_ID,
|
||||||
|
LinkConnector,
|
||||||
|
ToInputFromIoNodeLink,
|
||||||
|
LGraphNode,
|
||||||
isSubgraphInput,
|
isSubgraphInput,
|
||||||
isSubgraphOutput
|
isSubgraphOutput
|
||||||
} from '@/lib/litegraph/src/litegraph'
|
} from '@/lib/litegraph/src/litegraph'
|
||||||
|
import type {
|
||||||
|
LinkNetwork,
|
||||||
|
NodeInputSlot,
|
||||||
|
NodeOutputSlot
|
||||||
|
} from '@/lib/litegraph/src/litegraph'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
createTestSubgraph,
|
createTestSubgraph,
|
||||||
createTestSubgraphNode
|
createTestSubgraphNode
|
||||||
} from './fixtures/subgraphHelpers'
|
} from './__fixtures__/subgraphHelpers'
|
||||||
|
|
||||||
describe.skip('Subgraph slot connections', () => {
|
describe.skip('Subgraph slot connections', () => {
|
||||||
describe.skip('SubgraphInput connections', () => {
|
describe.skip('SubgraphInput connections', () => {
|
||||||
@@ -3,7 +3,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|||||||
|
|
||||||
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||||
|
|
||||||
import { createTestSubgraph } from './fixtures/subgraphHelpers'
|
import { createTestSubgraph } from './__fixtures__/subgraphHelpers'
|
||||||
|
|
||||||
describe.skip('SubgraphSlot visual feedback', () => {
|
describe.skip('SubgraphSlot visual feedback', () => {
|
||||||
let mockCtx: CanvasRenderingContext2D
|
let mockCtx: CanvasRenderingContext2D
|
||||||
@@ -1,16 +1,18 @@
|
|||||||
// TODO: Fix these tests after migration
|
// TODO: Fix these tests after migration
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
import type { ISlotType, Subgraph } from '@/lib/litegraph/src/litegraph'
|
import type {
|
||||||
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
ISlotType,
|
||||||
import type { TWidgetType } from '@/lib/litegraph/src/litegraph'
|
Subgraph,
|
||||||
import { BaseWidget } from '@/lib/litegraph/src/litegraph'
|
TWidgetType
|
||||||
|
} from '@/lib/litegraph/src/litegraph'
|
||||||
|
import { BaseWidget, LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
createEventCapture,
|
createEventCapture,
|
||||||
createTestSubgraph,
|
createTestSubgraph,
|
||||||
createTestSubgraphNode
|
createTestSubgraphNode
|
||||||
} from './fixtures/subgraphHelpers'
|
} from './__fixtures__/subgraphHelpers'
|
||||||
|
|
||||||
// Helper to create a node with a widget
|
// Helper to create a node with a widget
|
||||||
function createNodeWithWidget(
|
function createNodeWithWidget(
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// oxlint-disable no-empty-pattern
|
||||||
/**
|
/**
|
||||||
* Vitest Fixtures for Subgraph Testing
|
* Vitest Fixtures for Subgraph Testing
|
||||||
*
|
*
|
||||||
@@ -9,7 +10,7 @@ import type { Subgraph } from '@/lib/litegraph/src/litegraph'
|
|||||||
import { LGraph } from '@/lib/litegraph/src/litegraph'
|
import { LGraph } from '@/lib/litegraph/src/litegraph'
|
||||||
import type { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode'
|
import type { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode'
|
||||||
|
|
||||||
import { test } from '../../core/fixtures/testExtensions'
|
import { test } from '../../__fixtures__/testExtensions'
|
||||||
import {
|
import {
|
||||||
createEventCapture,
|
createEventCapture,
|
||||||
createNestedSubgraphs,
|
createNestedSubgraphs,
|
||||||
@@ -59,7 +60,7 @@ interface SubgraphFixtures {
|
|||||||
*/
|
*/
|
||||||
export const subgraphTest = test.extend<SubgraphFixtures>({
|
export const subgraphTest = test.extend<SubgraphFixtures>({
|
||||||
// @ts-expect-error TODO: Fix after merge - fixture use parameter type
|
// @ts-expect-error TODO: Fix after merge - fixture use parameter type
|
||||||
// eslint-disable-next-line no-empty-pattern
|
|
||||||
emptySubgraph: async ({}, use: (value: unknown) => Promise<void>) => {
|
emptySubgraph: async ({}, use: (value: unknown) => Promise<void>) => {
|
||||||
const subgraph = createTestSubgraph({
|
const subgraph = createTestSubgraph({
|
||||||
name: 'Empty Test Subgraph',
|
name: 'Empty Test Subgraph',
|
||||||
@@ -72,7 +73,7 @@ export const subgraphTest = test.extend<SubgraphFixtures>({
|
|||||||
},
|
},
|
||||||
|
|
||||||
// @ts-expect-error TODO: Fix after merge - fixture use parameter type
|
// @ts-expect-error TODO: Fix after merge - fixture use parameter type
|
||||||
// eslint-disable-next-line no-empty-pattern
|
|
||||||
simpleSubgraph: async ({}, use: (value: unknown) => Promise<void>) => {
|
simpleSubgraph: async ({}, use: (value: unknown) => Promise<void>) => {
|
||||||
const subgraph = createTestSubgraph({
|
const subgraph = createTestSubgraph({
|
||||||
name: 'Simple Test Subgraph',
|
name: 'Simple Test Subgraph',
|
||||||
@@ -85,7 +86,7 @@ export const subgraphTest = test.extend<SubgraphFixtures>({
|
|||||||
},
|
},
|
||||||
|
|
||||||
// @ts-expect-error TODO: Fix after merge - fixture use parameter type
|
// @ts-expect-error TODO: Fix after merge - fixture use parameter type
|
||||||
// eslint-disable-next-line no-empty-pattern
|
|
||||||
complexSubgraph: async ({}, use: (value: unknown) => Promise<void>) => {
|
complexSubgraph: async ({}, use: (value: unknown) => Promise<void>) => {
|
||||||
const subgraph = createTestSubgraph({
|
const subgraph = createTestSubgraph({
|
||||||
name: 'Complex Test Subgraph',
|
name: 'Complex Test Subgraph',
|
||||||
@@ -105,7 +106,7 @@ export const subgraphTest = test.extend<SubgraphFixtures>({
|
|||||||
},
|
},
|
||||||
|
|
||||||
// @ts-expect-error TODO: Fix after merge - fixture use parameter type
|
// @ts-expect-error TODO: Fix after merge - fixture use parameter type
|
||||||
// eslint-disable-next-line no-empty-pattern
|
|
||||||
nestedSubgraph: async ({}, use: (value: unknown) => Promise<void>) => {
|
nestedSubgraph: async ({}, use: (value: unknown) => Promise<void>) => {
|
||||||
const nested = createNestedSubgraphs({
|
const nested = createNestedSubgraphs({
|
||||||
depth: 3,
|
depth: 3,
|
||||||
@@ -118,7 +119,7 @@ export const subgraphTest = test.extend<SubgraphFixtures>({
|
|||||||
},
|
},
|
||||||
|
|
||||||
// @ts-expect-error TODO: Fix after merge - fixture use parameter type
|
// @ts-expect-error TODO: Fix after merge - fixture use parameter type
|
||||||
// eslint-disable-next-line no-empty-pattern
|
|
||||||
subgraphWithNode: async ({}, use: (value: unknown) => Promise<void>) => {
|
subgraphWithNode: async ({}, use: (value: unknown) => Promise<void>) => {
|
||||||
// Create the subgraph definition
|
// Create the subgraph definition
|
||||||
const subgraph = createTestSubgraph({
|
const subgraph = createTestSubgraph({
|
||||||
@@ -146,7 +147,7 @@ export const subgraphTest = test.extend<SubgraphFixtures>({
|
|||||||
},
|
},
|
||||||
|
|
||||||
// @ts-expect-error TODO: Fix after merge - fixture use parameter type
|
// @ts-expect-error TODO: Fix after merge - fixture use parameter type
|
||||||
// eslint-disable-next-line no-empty-pattern
|
|
||||||
eventCapture: async ({}, use: (value: unknown) => Promise<void>) => {
|
eventCapture: async ({}, use: (value: unknown) => Promise<void>) => {
|
||||||
const subgraph = createTestSubgraph({
|
const subgraph = createTestSubgraph({
|
||||||
name: 'Event Test Subgraph'
|
name: 'Event Test Subgraph'
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user