mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-24 22:58:08 +00:00
test(extension-api-v2): extract shared world mock factories to harness/worldMocks.ts
F3: bc-05.v2, bc-05.migration, bc-11.v2, bc-11.migration each duplicated
the same vi.mock('@/world/...') block (worldInstance + widgetComponents +
entityIds + componentKey + 3 extension-api stubs). Centralise the factory
bodies in src/extension-api-v2/__tests__/harness/worldMocks.ts and have
the four files consume them.
vi.hoisted handles stay inline because the hoisted factory runs before
imports resolve; vi.mock factories wrap the imported helpers in arrows
so the import binding is read lazily.
Verified: 4/4 files pass under vitest run with the shared module.
This commit is contained in:
@@ -8,35 +8,34 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
// ── Mock world (same pattern as bc-01.migration.test.ts) ──────────────────────
|
||||
|
||||
const mockGetComponent = vi.fn()
|
||||
const mockEntitiesWith = vi.fn(() => [])
|
||||
|
||||
vi.mock('@/world/worldInstance', () => ({
|
||||
getWorld: () => ({
|
||||
getComponent: mockGetComponent,
|
||||
entitiesWith: mockEntitiesWith,
|
||||
setComponent: vi.fn(),
|
||||
removeComponent: vi.fn()
|
||||
})
|
||||
// vi.hoisted factory runs before imports — keep handle creation inline.
|
||||
const { mockGetComponent, mockEntitiesWith } = vi.hoisted(() => ({
|
||||
mockGetComponent: vi.fn(),
|
||||
mockEntitiesWith: vi.fn(() => [] as unknown[])
|
||||
}))
|
||||
|
||||
vi.mock('@/world/widgets/widgetComponents', () => ({
|
||||
WidgetComponentContainer: Symbol('WidgetComponentContainer'),
|
||||
WidgetComponentDisplay: Symbol('WidgetComponentDisplay'),
|
||||
WidgetComponentSchema: Symbol('WidgetComponentSchema'),
|
||||
WidgetComponentSerialize: Symbol('WidgetComponentSerialize'),
|
||||
WidgetComponentValue: Symbol('WidgetComponentValue')
|
||||
}))
|
||||
import {
|
||||
componentKeyMockFactory,
|
||||
emptyMockFactory,
|
||||
widgetComponentsMockFactory,
|
||||
worldInstanceMockFactory
|
||||
} from './harness/worldMocks'
|
||||
|
||||
vi.mock('@/world/entityIds', () => ({}))
|
||||
// vi.mock factories are hoisted; keep imported helpers behind arrows so
|
||||
// the import binding is read lazily at factory invocation time.
|
||||
vi.mock('@/world/worldInstance', () =>
|
||||
worldInstanceMockFactory({ mockGetComponent, mockEntitiesWith })
|
||||
)
|
||||
|
||||
vi.mock('@/world/componentKey', () => ({
|
||||
defineComponentKey: (name: string) => ({ name })
|
||||
}))
|
||||
vi.mock('@/world/widgets/widgetComponents', () => widgetComponentsMockFactory())
|
||||
|
||||
vi.mock('@/extension-api/node', () => ({}))
|
||||
vi.mock('@/extension-api/widget', () => ({}))
|
||||
vi.mock('@/extension-api/lifecycle', () => ({}))
|
||||
vi.mock('@/world/entityIds', () => emptyMockFactory())
|
||||
|
||||
vi.mock('@/world/componentKey', () => componentKeyMockFactory())
|
||||
|
||||
vi.mock('@/extension-api/node', () => emptyMockFactory())
|
||||
vi.mock('@/extension-api/widget', () => emptyMockFactory())
|
||||
vi.mock('@/extension-api/lifecycle', () => emptyMockFactory())
|
||||
|
||||
import {
|
||||
_clearExtensionsForTesting,
|
||||
@@ -111,7 +110,10 @@ function stubNodeType(id: NodeEntityId, comfyClass = 'TestNode') {
|
||||
|
||||
function makeDiv(height = 120): HTMLElement {
|
||||
const el = document.createElement('div')
|
||||
Object.defineProperty(el, 'offsetHeight', { value: height, configurable: true })
|
||||
Object.defineProperty(el, 'offsetHeight', {
|
||||
value: height,
|
||||
configurable: true
|
||||
})
|
||||
return el
|
||||
}
|
||||
|
||||
@@ -172,14 +174,20 @@ describe('BC.05 migration — custom DOM widgets and node sizing', () => {
|
||||
|
||||
// v1: getHeight callback
|
||||
const v1Node = createV1Node(2)
|
||||
v1Node.addDOMWidget('widget', 'custom', el, { getHeight: () => reportedHeight })
|
||||
v1Node.addDOMWidget('widget', 'custom', el, {
|
||||
getHeight: () => reportedHeight
|
||||
})
|
||||
const v1Height = v1Node.domWidgets[0].height
|
||||
|
||||
// v2: explicit height option
|
||||
defineNodeExtension({
|
||||
name: 'bc05.mig.height-parity',
|
||||
nodeCreated(handle) {
|
||||
handle.addDOMWidget({ name: 'widget', element: el, height: reportedHeight })
|
||||
handle.addDOMWidget({
|
||||
name: 'widget',
|
||||
element: el,
|
||||
height: reportedHeight
|
||||
})
|
||||
}
|
||||
})
|
||||
const id = makeNodeId(2)
|
||||
@@ -244,7 +252,10 @@ describe('BC.05 migration — custom DOM widgets and node sizing', () => {
|
||||
mountExtensionsForNode(id)
|
||||
|
||||
const heightCmd = dispatchedCommands.find(
|
||||
(c) => c.type === 'SetWidgetOption' && c.key === '__domHeight' && c.value === newHeight
|
||||
(c) =>
|
||||
c.type === 'SetWidgetOption' &&
|
||||
c.key === '__domHeight' &&
|
||||
c.value === newHeight
|
||||
)
|
||||
|
||||
// v1 needed a computeSize override; v2 achieves the same via SetWidgetOption dispatch
|
||||
|
||||
@@ -8,35 +8,34 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
// ── Mock world (same pattern as bc-01.v2.test.ts) ────────────────────────────
|
||||
|
||||
const mockGetComponent = vi.fn()
|
||||
const mockEntitiesWith = vi.fn(() => [])
|
||||
|
||||
vi.mock('@/world/worldInstance', () => ({
|
||||
getWorld: () => ({
|
||||
getComponent: mockGetComponent,
|
||||
entitiesWith: mockEntitiesWith,
|
||||
setComponent: vi.fn(),
|
||||
removeComponent: vi.fn()
|
||||
})
|
||||
// vi.hoisted factory runs before imports — keep handle creation inline.
|
||||
const { mockGetComponent, mockEntitiesWith } = vi.hoisted(() => ({
|
||||
mockGetComponent: vi.fn(),
|
||||
mockEntitiesWith: vi.fn(() => [] as unknown[])
|
||||
}))
|
||||
|
||||
vi.mock('@/world/widgets/widgetComponents', () => ({
|
||||
WidgetComponentContainer: Symbol('WidgetComponentContainer'),
|
||||
WidgetComponentDisplay: Symbol('WidgetComponentDisplay'),
|
||||
WidgetComponentSchema: Symbol('WidgetComponentSchema'),
|
||||
WidgetComponentSerialize: Symbol('WidgetComponentSerialize'),
|
||||
WidgetComponentValue: Symbol('WidgetComponentValue')
|
||||
}))
|
||||
import {
|
||||
componentKeyMockFactory,
|
||||
emptyMockFactory,
|
||||
widgetComponentsMockFactory,
|
||||
worldInstanceMockFactory
|
||||
} from './harness/worldMocks'
|
||||
|
||||
vi.mock('@/world/entityIds', () => ({}))
|
||||
// vi.mock factories are hoisted; keep imported helpers behind arrows so
|
||||
// the import binding is read lazily at factory invocation time.
|
||||
vi.mock('@/world/worldInstance', () =>
|
||||
worldInstanceMockFactory({ mockGetComponent, mockEntitiesWith })
|
||||
)
|
||||
|
||||
vi.mock('@/world/componentKey', () => ({
|
||||
defineComponentKey: (name: string) => ({ name })
|
||||
}))
|
||||
vi.mock('@/world/widgets/widgetComponents', () => widgetComponentsMockFactory())
|
||||
|
||||
vi.mock('@/extension-api/node', () => ({}))
|
||||
vi.mock('@/extension-api/widget', () => ({}))
|
||||
vi.mock('@/extension-api/lifecycle', () => ({}))
|
||||
vi.mock('@/world/entityIds', () => emptyMockFactory())
|
||||
|
||||
vi.mock('@/world/componentKey', () => componentKeyMockFactory())
|
||||
|
||||
vi.mock('@/extension-api/node', () => emptyMockFactory())
|
||||
vi.mock('@/extension-api/widget', () => emptyMockFactory())
|
||||
vi.mock('@/extension-api/lifecycle', () => emptyMockFactory())
|
||||
|
||||
import {
|
||||
_clearExtensionsForTesting,
|
||||
@@ -63,7 +62,10 @@ function stubNodeType(id: NodeEntityId, comfyClass = 'TestNode') {
|
||||
|
||||
function makeDiv(height = 120): HTMLElement {
|
||||
const el = document.createElement('div')
|
||||
Object.defineProperty(el, 'offsetHeight', { value: height, configurable: true })
|
||||
Object.defineProperty(el, 'offsetHeight', {
|
||||
value: height,
|
||||
configurable: true
|
||||
})
|
||||
return el
|
||||
}
|
||||
|
||||
@@ -123,7 +125,10 @@ describe('BC.05 v2 contract — custom DOM widgets and node sizing', () => {
|
||||
defineNodeExtension({
|
||||
name: 'bc05.v2.handle-name',
|
||||
nodeCreated(handle) {
|
||||
const wh = handle.addDOMWidget({ name: 'preview', element: makeDiv() })
|
||||
const wh = handle.addDOMWidget({
|
||||
name: 'preview',
|
||||
element: makeDiv()
|
||||
})
|
||||
handleName = wh.name
|
||||
}
|
||||
})
|
||||
@@ -163,7 +168,11 @@ describe('BC.05 v2 contract — custom DOM widgets and node sizing', () => {
|
||||
defineNodeExtension({
|
||||
name: 'bc05.v2.custom-height',
|
||||
nodeCreated(handle) {
|
||||
handle.addDOMWidget({ name: 'editor', element: el, height: customHeight })
|
||||
handle.addDOMWidget({
|
||||
name: 'editor',
|
||||
element: el,
|
||||
height: customHeight
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -227,7 +236,10 @@ describe('BC.05 v2 contract — custom DOM widgets and node sizing', () => {
|
||||
defineNodeExtension({
|
||||
name: 'bc05.v2.set-height',
|
||||
nodeCreated(handle) {
|
||||
const wh = handle.addDOMWidget({ name: 'resizable', element: makeDiv(100) })
|
||||
const wh = handle.addDOMWidget({
|
||||
name: 'resizable',
|
||||
element: makeDiv(100)
|
||||
})
|
||||
wh.setHeight(300)
|
||||
}
|
||||
})
|
||||
@@ -237,7 +249,10 @@ describe('BC.05 v2 contract — custom DOM widgets and node sizing', () => {
|
||||
mountExtensionsForNode(id)
|
||||
|
||||
const setCmd = dispatchedCommands.find(
|
||||
(c) => c.type === 'SetWidgetOption' && c.key === '__domHeight' && c.value === 300
|
||||
(c) =>
|
||||
c.type === 'SetWidgetOption' &&
|
||||
c.key === '__domHeight' &&
|
||||
c.value === 300
|
||||
)
|
||||
|
||||
expect(setCmd).toBeDefined()
|
||||
|
||||
@@ -8,35 +8,34 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
// ── Mock world (same pattern as bc-01.migration.test.ts) ──────────────────────
|
||||
|
||||
const mockGetComponent = vi.fn()
|
||||
const mockEntitiesWith = vi.fn(() => [])
|
||||
|
||||
vi.mock('@/world/worldInstance', () => ({
|
||||
getWorld: () => ({
|
||||
getComponent: mockGetComponent,
|
||||
entitiesWith: mockEntitiesWith,
|
||||
setComponent: vi.fn(),
|
||||
removeComponent: vi.fn()
|
||||
})
|
||||
// vi.hoisted factory runs before imports — keep handle creation inline.
|
||||
const { mockGetComponent, mockEntitiesWith } = vi.hoisted(() => ({
|
||||
mockGetComponent: vi.fn(),
|
||||
mockEntitiesWith: vi.fn(() => [] as unknown[])
|
||||
}))
|
||||
|
||||
vi.mock('@/world/widgets/widgetComponents', () => ({
|
||||
WidgetComponentContainer: Symbol('WidgetComponentContainer'),
|
||||
WidgetComponentDisplay: Symbol('WidgetComponentDisplay'),
|
||||
WidgetComponentSchema: Symbol('WidgetComponentSchema'),
|
||||
WidgetComponentSerialize: Symbol('WidgetComponentSerialize'),
|
||||
WidgetComponentValue: Symbol('WidgetComponentValue')
|
||||
}))
|
||||
import {
|
||||
componentKeyMockFactory,
|
||||
emptyMockFactory,
|
||||
widgetComponentsMockFactory,
|
||||
worldInstanceMockFactory
|
||||
} from './harness/worldMocks'
|
||||
|
||||
vi.mock('@/world/entityIds', () => ({}))
|
||||
// vi.mock factories are hoisted; keep imported helpers behind arrows so
|
||||
// the import binding is read lazily at factory invocation time.
|
||||
vi.mock('@/world/worldInstance', () =>
|
||||
worldInstanceMockFactory({ mockGetComponent, mockEntitiesWith })
|
||||
)
|
||||
|
||||
vi.mock('@/world/componentKey', () => ({
|
||||
defineComponentKey: (name: string) => ({ name })
|
||||
}))
|
||||
vi.mock('@/world/widgets/widgetComponents', () => widgetComponentsMockFactory())
|
||||
|
||||
vi.mock('@/extension-api/node', () => ({}))
|
||||
vi.mock('@/extension-api/widget', () => ({}))
|
||||
vi.mock('@/extension-api/lifecycle', () => ({}))
|
||||
vi.mock('@/world/entityIds', () => emptyMockFactory())
|
||||
|
||||
vi.mock('@/world/componentKey', () => componentKeyMockFactory())
|
||||
|
||||
vi.mock('@/extension-api/node', () => emptyMockFactory())
|
||||
vi.mock('@/extension-api/widget', () => emptyMockFactory())
|
||||
vi.mock('@/extension-api/lifecycle', () => emptyMockFactory())
|
||||
|
||||
import {
|
||||
_clearExtensionsForTesting,
|
||||
@@ -65,7 +64,11 @@ function createV1Widget(name: string, value: unknown): V1Widget {
|
||||
return { name, value, callback: undefined }
|
||||
}
|
||||
|
||||
function createV1ComboWidget(name: string, value: string, values: string[]): V1Widget {
|
||||
function createV1ComboWidget(
|
||||
name: string,
|
||||
value: string,
|
||||
values: string[]
|
||||
): V1Widget {
|
||||
return { name, value, callback: undefined, options: { values } }
|
||||
}
|
||||
|
||||
@@ -177,7 +180,10 @@ describe('BC.11 migration — widget imperative state writes', () => {
|
||||
const newValues = ['euler', 'dpm_2', 'lcm']
|
||||
|
||||
// v1: direct options mutation
|
||||
const v1Widget = createV1ComboWidget('sampler', 'euler', ['euler', 'dpm_2'])
|
||||
const v1Widget = createV1ComboWidget('sampler', 'euler', [
|
||||
'euler',
|
||||
'dpm_2'
|
||||
])
|
||||
v1Widget.options!.values = newValues
|
||||
expect(v1Widget.options!.values).toEqual(newValues)
|
||||
|
||||
@@ -185,7 +191,9 @@ describe('BC.11 migration — widget imperative state writes', () => {
|
||||
defineNodeExtension({
|
||||
name: 'bc11.mig.set-options',
|
||||
nodeCreated(handle) {
|
||||
const wh = handle.addWidget('COMBO', 'sampler', 'euler', { values: ['euler', 'dpm_2'] })
|
||||
const wh = handle.addWidget('COMBO', 'sampler', 'euler', {
|
||||
values: ['euler', 'dpm_2']
|
||||
})
|
||||
wh.setOption('values', newValues)
|
||||
}
|
||||
})
|
||||
@@ -203,8 +211,14 @@ describe('BC.11 migration — widget imperative state writes', () => {
|
||||
|
||||
it('both v1 and v2 option-set operations are independent per widget', () => {
|
||||
// v1: two widgets, each with independent options mutation
|
||||
const v1WidgetA = createV1ComboWidget('schedulerA', 'karras', ['karras', 'normal'])
|
||||
const v1WidgetB = createV1ComboWidget('schedulerB', 'karras', ['karras', 'normal'])
|
||||
const v1WidgetA = createV1ComboWidget('schedulerA', 'karras', [
|
||||
'karras',
|
||||
'normal'
|
||||
])
|
||||
const v1WidgetB = createV1ComboWidget('schedulerB', 'karras', [
|
||||
'karras',
|
||||
'normal'
|
||||
])
|
||||
v1WidgetA.options!.values = ['karras', 'exponential']
|
||||
// B is unaffected
|
||||
expect(v1WidgetB.options!.values).toEqual(['karras', 'normal'])
|
||||
@@ -214,8 +228,12 @@ describe('BC.11 migration — widget imperative state writes', () => {
|
||||
defineNodeExtension({
|
||||
name: 'bc11.mig.option-independence',
|
||||
nodeCreated(handle) {
|
||||
const whA = handle.addWidget('COMBO', 'schedulerA', 'karras', { values: ['karras', 'normal'] })
|
||||
handle.addWidget('COMBO', 'schedulerB', 'karras', { values: ['karras', 'normal'] })
|
||||
const whA = handle.addWidget('COMBO', 'schedulerA', 'karras', {
|
||||
values: ['karras', 'normal']
|
||||
})
|
||||
handle.addWidget('COMBO', 'schedulerB', 'karras', {
|
||||
values: ['karras', 'normal']
|
||||
})
|
||||
whA.setOption('values', ['karras', 'exponential'])
|
||||
}
|
||||
})
|
||||
@@ -223,7 +241,9 @@ describe('BC.11 migration — widget imperative state writes', () => {
|
||||
stubNodeType(id)
|
||||
mountExtensionsForNode(id)
|
||||
|
||||
const optCmds = dispatchedCommands.filter((c) => c.type === 'SetWidgetOption' && c.key === 'values')
|
||||
const optCmds = dispatchedCommands.filter(
|
||||
(c) => c.type === 'SetWidgetOption' && c.key === 'values'
|
||||
)
|
||||
// Only one setOption dispatch — for whA
|
||||
expect(optCmds).toHaveLength(1)
|
||||
})
|
||||
|
||||
@@ -8,35 +8,34 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
// ── Mock world (same pattern as bc-01.v2.test.ts) ────────────────────────────
|
||||
|
||||
const mockGetComponent = vi.fn()
|
||||
const mockEntitiesWith = vi.fn(() => [])
|
||||
|
||||
vi.mock('@/world/worldInstance', () => ({
|
||||
getWorld: () => ({
|
||||
getComponent: mockGetComponent,
|
||||
entitiesWith: mockEntitiesWith,
|
||||
setComponent: vi.fn(),
|
||||
removeComponent: vi.fn()
|
||||
})
|
||||
// vi.hoisted factory runs before imports — keep handle creation inline.
|
||||
const { mockGetComponent, mockEntitiesWith } = vi.hoisted(() => ({
|
||||
mockGetComponent: vi.fn(),
|
||||
mockEntitiesWith: vi.fn(() => [] as unknown[])
|
||||
}))
|
||||
|
||||
vi.mock('@/world/widgets/widgetComponents', () => ({
|
||||
WidgetComponentContainer: Symbol('WidgetComponentContainer'),
|
||||
WidgetComponentDisplay: Symbol('WidgetComponentDisplay'),
|
||||
WidgetComponentSchema: Symbol('WidgetComponentSchema'),
|
||||
WidgetComponentSerialize: Symbol('WidgetComponentSerialize'),
|
||||
WidgetComponentValue: Symbol('WidgetComponentValue')
|
||||
}))
|
||||
import {
|
||||
componentKeyMockFactory,
|
||||
emptyMockFactory,
|
||||
widgetComponentsMockFactory,
|
||||
worldInstanceMockFactory
|
||||
} from './harness/worldMocks'
|
||||
|
||||
vi.mock('@/world/entityIds', () => ({}))
|
||||
// vi.mock factories are hoisted; keep imported helpers behind arrows so
|
||||
// the import binding is read lazily at factory invocation time.
|
||||
vi.mock('@/world/worldInstance', () =>
|
||||
worldInstanceMockFactory({ mockGetComponent, mockEntitiesWith })
|
||||
)
|
||||
|
||||
vi.mock('@/world/componentKey', () => ({
|
||||
defineComponentKey: (name: string) => ({ name })
|
||||
}))
|
||||
vi.mock('@/world/widgets/widgetComponents', () => widgetComponentsMockFactory())
|
||||
|
||||
vi.mock('@/extension-api/node', () => ({}))
|
||||
vi.mock('@/extension-api/widget', () => ({}))
|
||||
vi.mock('@/extension-api/lifecycle', () => ({}))
|
||||
vi.mock('@/world/entityIds', () => emptyMockFactory())
|
||||
|
||||
vi.mock('@/world/componentKey', () => componentKeyMockFactory())
|
||||
|
||||
vi.mock('@/extension-api/node', () => emptyMockFactory())
|
||||
vi.mock('@/extension-api/widget', () => emptyMockFactory())
|
||||
vi.mock('@/extension-api/lifecycle', () => emptyMockFactory())
|
||||
|
||||
import {
|
||||
_clearExtensionsForTesting,
|
||||
@@ -127,9 +126,9 @@ describe('BC.11 v2 contract — widget imperative state writes', () => {
|
||||
stubNodeType(id)
|
||||
mountExtensionsForNode(id)
|
||||
|
||||
const setCmd = dispatchedCommands.find((c) => c.type === 'SetWidgetValue') as
|
||||
| { widgetId: string; value: unknown }
|
||||
| undefined
|
||||
const setCmd = dispatchedCommands.find(
|
||||
(c) => c.type === 'SetWidgetValue'
|
||||
) as { widgetId: string; value: unknown } | undefined
|
||||
|
||||
expect(setCmd).toBeDefined()
|
||||
expect(setCmd?.widgetId).toBe(capturedWidgetId[0])
|
||||
@@ -151,7 +150,9 @@ describe('BC.11 v2 contract — widget imperative state writes', () => {
|
||||
stubNodeType(id)
|
||||
mountExtensionsForNode(id)
|
||||
|
||||
const setCmds = dispatchedCommands.filter((c) => c.type === 'SetWidgetValue')
|
||||
const setCmds = dispatchedCommands.filter(
|
||||
(c) => c.type === 'SetWidgetValue'
|
||||
)
|
||||
expect(setCmds).toHaveLength(3)
|
||||
expect(setCmds.map((c) => c.value)).toEqual([1, 2, 3])
|
||||
})
|
||||
@@ -172,7 +173,8 @@ describe('BC.11 v2 contract — widget imperative state writes', () => {
|
||||
mountExtensionsForNode(id)
|
||||
|
||||
const cmd = dispatchedCommands.find(
|
||||
(c) => c.type === 'SetWidgetOption' && c.key === 'hidden' && c.value === true
|
||||
(c) =>
|
||||
c.type === 'SetWidgetOption' && c.key === 'hidden' && c.value === true
|
||||
)
|
||||
expect(cmd).toBeDefined()
|
||||
})
|
||||
@@ -191,7 +193,10 @@ describe('BC.11 v2 contract — widget imperative state writes', () => {
|
||||
mountExtensionsForNode(id)
|
||||
|
||||
const cmd = dispatchedCommands.find(
|
||||
(c) => c.type === 'SetWidgetOption' && c.key === 'disabled' && c.value === true
|
||||
(c) =>
|
||||
c.type === 'SetWidgetOption' &&
|
||||
c.key === 'disabled' &&
|
||||
c.value === true
|
||||
)
|
||||
expect(cmd).toBeDefined()
|
||||
})
|
||||
@@ -202,7 +207,9 @@ describe('BC.11 v2 contract — widget imperative state writes', () => {
|
||||
defineNodeExtension({
|
||||
name: 'bc11.v2.set-option',
|
||||
nodeCreated(handle) {
|
||||
const wh = handle.addWidget('COMBO', 'sampler_name', 'euler', { values: ['euler', 'dpm_2'] })
|
||||
const wh = handle.addWidget('COMBO', 'sampler_name', 'euler', {
|
||||
values: ['euler', 'dpm_2']
|
||||
})
|
||||
wh.setOption('values', ['euler', 'dpm_2', 'lcm'])
|
||||
}
|
||||
})
|
||||
@@ -233,7 +240,9 @@ describe('BC.11 v2 contract — widget imperative state writes', () => {
|
||||
stubNodeType(id)
|
||||
mountExtensionsForNode(id)
|
||||
|
||||
const optCmds = dispatchedCommands.filter((c) => c.type === 'SetWidgetOption')
|
||||
const optCmds = dispatchedCommands.filter(
|
||||
(c) => c.type === 'SetWidgetOption'
|
||||
)
|
||||
const keys = optCmds.map((c) => c.key)
|
||||
expect(keys).toContain('placeholder')
|
||||
expect(keys).toContain('maxLength')
|
||||
@@ -276,7 +285,9 @@ describe('BC.11 v2 contract — widget imperative state writes', () => {
|
||||
stubNodeType(id)
|
||||
mountExtensionsForNode(id)
|
||||
|
||||
const createCmds = dispatchedCommands.filter((c) => c.type === 'CreateWidget')
|
||||
const createCmds = dispatchedCommands.filter(
|
||||
(c) => c.type === 'CreateWidget'
|
||||
)
|
||||
const names = createCmds.map((c) => c.name)
|
||||
expect(names).toContain('steps')
|
||||
expect(names).toContain('cfg')
|
||||
|
||||
99
src/extension-api-v2/__tests__/harness/worldMocks.ts
Normal file
99
src/extension-api-v2/__tests__/harness/worldMocks.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* Shared `vi.mock(...)` payloads for tests that exercise
|
||||
* `@/services/extension-api-service` against a stubbed World.
|
||||
*
|
||||
* Why this exists: BC.05 / BC.11 (and any future ECS-touching BC tests)
|
||||
* had identical, copy-pasted blocks of:
|
||||
*
|
||||
* const mockGetComponent = vi.fn()
|
||||
* const mockEntitiesWith = vi.fn(() => [])
|
||||
* vi.mock('@/world/worldInstance', ...)
|
||||
* vi.mock('@/world/widgets/widgetComponents', ...)
|
||||
* vi.mock('@/world/entityIds', () => ({}))
|
||||
* vi.mock('@/world/componentKey', ...)
|
||||
* vi.mock('@/extension-api/node', () => ({}))
|
||||
* vi.mock('@/extension-api/widget', () => ({}))
|
||||
* vi.mock('@/extension-api/lifecycle', () => ({}))
|
||||
*
|
||||
* `vi.mock` is statically hoisted, so the *call sites* must remain in
|
||||
* the consumer file. What we centralise here is the factory *bodies*
|
||||
* plus a handle-creation helper that pairs cleanly with `vi.hoisted`.
|
||||
*
|
||||
* @example
|
||||
* import { vi } from 'vitest'
|
||||
* import {
|
||||
* createWorldMockHandles,
|
||||
* emptyMockFactory,
|
||||
* componentKeyMockFactory,
|
||||
* widgetComponentsMockFactory,
|
||||
* worldInstanceMockFactory
|
||||
* } from './harness/worldMocks'
|
||||
*
|
||||
* const { mockGetComponent, mockEntitiesWith } = vi.hoisted(
|
||||
* createWorldMockHandles
|
||||
* )
|
||||
*
|
||||
* vi.mock('@/world/worldInstance', () =>
|
||||
* worldInstanceMockFactory({ mockGetComponent, mockEntitiesWith })
|
||||
* )
|
||||
* vi.mock('@/world/widgets/widgetComponents', widgetComponentsMockFactory)
|
||||
* vi.mock('@/world/entityIds', emptyMockFactory)
|
||||
* vi.mock('@/world/componentKey', componentKeyMockFactory)
|
||||
* vi.mock('@/extension-api/node', emptyMockFactory)
|
||||
* vi.mock('@/extension-api/widget', emptyMockFactory)
|
||||
* vi.mock('@/extension-api/lifecycle', emptyMockFactory)
|
||||
*/
|
||||
import { vi } from 'vitest'
|
||||
|
||||
export interface WorldMockHandles {
|
||||
mockGetComponent: ReturnType<typeof vi.fn>
|
||||
mockEntitiesWith: ReturnType<typeof vi.fn>
|
||||
}
|
||||
|
||||
/**
|
||||
* Hoist-safe factory for the per-test mock function handles.
|
||||
* Wrap with `vi.hoisted(createWorldMockHandles)` at the top of the
|
||||
* test file so the resulting handles are available inside the
|
||||
* `vi.mock(...)` factory closures.
|
||||
*/
|
||||
export function createWorldMockHandles(): WorldMockHandles {
|
||||
return {
|
||||
mockGetComponent: vi.fn(),
|
||||
mockEntitiesWith: vi.fn(() => [])
|
||||
}
|
||||
}
|
||||
|
||||
/** Factory body for `@/world/worldInstance`. */
|
||||
export function worldInstanceMockFactory(handles: WorldMockHandles) {
|
||||
return {
|
||||
getWorld: () => ({
|
||||
getComponent: handles.mockGetComponent,
|
||||
entitiesWith: handles.mockEntitiesWith,
|
||||
setComponent: vi.fn(),
|
||||
removeComponent: vi.fn()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/** Factory body for `@/world/widgets/widgetComponents`. */
|
||||
export function widgetComponentsMockFactory() {
|
||||
return {
|
||||
WidgetComponentContainer: Symbol('WidgetComponentContainer'),
|
||||
WidgetComponentDisplay: Symbol('WidgetComponentDisplay'),
|
||||
WidgetComponentSchema: Symbol('WidgetComponentSchema'),
|
||||
WidgetComponentSerialize: Symbol('WidgetComponentSerialize'),
|
||||
WidgetComponentValue: Symbol('WidgetComponentValue')
|
||||
}
|
||||
}
|
||||
|
||||
/** Factory body for `@/world/componentKey`. */
|
||||
export function componentKeyMockFactory() {
|
||||
return {
|
||||
defineComponentKey: (name: string) => ({ name })
|
||||
}
|
||||
}
|
||||
|
||||
/** Factory body for modules that need to be mocked but contribute nothing. */
|
||||
export function emptyMockFactory() {
|
||||
return {}
|
||||
}
|
||||
Reference in New Issue
Block a user