chore: e2e tests for compound fluent assertions + clean barrel exports
- Add 2 e2e FOL solver tests for fluent .and/.or through checkAll() using a mock page with distinct geometry positions (P0-1 regression) - Remove 4 individual adapter factories from barrel index: createReactAdapter, createVueAdapter, createStorybookAdapter, createCustomAdapter (use react()/vue()/storybook()/custom() instead) - Reduce barrel from 101 to 89 lines
This commit is contained in:
@@ -78,18 +78,6 @@ export type {
|
|||||||
CustomRendererOptions,
|
CustomRendererOptions,
|
||||||
} from './renderers.js'
|
} from './renderers.js'
|
||||||
|
|
||||||
export { createReactAdapter } from './react-adapter.js'
|
|
||||||
export type { ReactAdapterOptions } from './react-adapter.js'
|
|
||||||
|
|
||||||
export { createVueAdapter } from './vue-adapter.js'
|
|
||||||
export type { VueAdapterOptions } from './vue-adapter.js'
|
|
||||||
|
|
||||||
export { createStorybookAdapter } from './storybook-adapter.js'
|
|
||||||
export type { StorybookAdapterOptions } from './storybook-adapter.js'
|
|
||||||
|
|
||||||
export { createCustomAdapter } from './custom-renderer-adapter.js'
|
|
||||||
export type { CustomAdapterOptions } from './custom-renderer-adapter.js'
|
|
||||||
|
|
||||||
// Reusable assertion presets.
|
// Reusable assertion presets.
|
||||||
export {
|
export {
|
||||||
touchTarget,
|
touchTarget,
|
||||||
|
|||||||
@@ -484,6 +484,108 @@ describe('FOL engine integration', () => {
|
|||||||
assert.ok(result.diagnostics.some((d) => d.code === 'IMH_EXTRACT_PROTOCOL_ERROR'))
|
assert.ok(result.diagnostics.some((d) => d.code === 'IMH_EXTRACT_PROTOCOL_ERROR'))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('compound .and chaining compiles through FOL solver and evaluates', async () => {
|
||||||
|
// Elements at distinct positions:
|
||||||
|
// .a: (10,10) — top-left
|
||||||
|
// .b: (110,10) — to the right of .a (gap: 50 >= 8)
|
||||||
|
// .c: (10,110) — below .a (gap: 50 >= 8)
|
||||||
|
const page: any = {
|
||||||
|
viewportSize: () => ({ width: 1280, height: 720 }),
|
||||||
|
setViewportSize: async () => {},
|
||||||
|
emulateMedia: async () => {},
|
||||||
|
addInitScript: async () => {},
|
||||||
|
keyboard: { press: async () => {} },
|
||||||
|
mouse: { move: async () => {} },
|
||||||
|
context: () => ({}),
|
||||||
|
locator: (_selector: string) => ({ hover: async () => {}, focus: async () => {} }),
|
||||||
|
evaluate: async (_fn: any, ...args: any[]) => {
|
||||||
|
const payload = args[0]
|
||||||
|
if (!payload || !Array.isArray(payload.plans)) return undefined
|
||||||
|
const elements: Array<any> = []
|
||||||
|
const selectorToIds: Array<[string, number[]]> = []
|
||||||
|
const positions: Record<string, { x: number; y: number }> = {
|
||||||
|
'.a': { x: 10, y: 10 },
|
||||||
|
'.b': { x: 110, y: 10 },
|
||||||
|
'.c': { x: 10, y: 110 },
|
||||||
|
}
|
||||||
|
for (const plan of payload.plans as Array<{ key: string; queries: string[] }>) {
|
||||||
|
const ids: number[] = []
|
||||||
|
const pos = positions[plan.key] || { x: 0, y: 0, width: 0, height: 0 }
|
||||||
|
for (const _query of plan.queries) {
|
||||||
|
const subjectId = elements.length + 1
|
||||||
|
elements.push({
|
||||||
|
tagName: 'div',
|
||||||
|
rect: { x: pos.x, y: pos.y, width: 50, height: 50 },
|
||||||
|
transform: { matrix: [1, 0, 0, 1, 0, 0], originX: 0, originY: 0 },
|
||||||
|
})
|
||||||
|
ids.push(subjectId)
|
||||||
|
}
|
||||||
|
selectorToIds.push([plan.key, ids])
|
||||||
|
}
|
||||||
|
return { elements, selectorToIds }
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const ui = await imhotep(page)
|
||||||
|
|
||||||
|
ui.expect('.a').to.be.leftOf('.b', { minGap: 8 }).and.above('.c', { minGap: 8 })
|
||||||
|
|
||||||
|
const result = await ui.checkAll()
|
||||||
|
assert.strictEqual(result.passed, true, `Expected passed:true, got passed:${result.passed}. Diagnostics: ${JSON.stringify(result.diagnostics.map(d => d.code))}`)
|
||||||
|
assert.strictEqual(result.clauseResults.length, 1)
|
||||||
|
assert.strictEqual(result.clauseResults[0].status, 'pass')
|
||||||
|
assert.ok(['true', 'determinate'].includes(result.clauseResults[0].truth), `expected true|determinate truth, got ${result.clauseResults[0].truth}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('compound .or chaining compiles through FOL solver and evaluates', async () => {
|
||||||
|
// .a is leftOf .b (true) but NOT leftOf .c (false — same x column).
|
||||||
|
// With .or, true clause satisfies the disjunction.
|
||||||
|
const page: any = {
|
||||||
|
viewportSize: () => ({ width: 1280, height: 720 }),
|
||||||
|
setViewportSize: async () => {},
|
||||||
|
emulateMedia: async () => {},
|
||||||
|
addInitScript: async () => {},
|
||||||
|
keyboard: { press: async () => {} },
|
||||||
|
mouse: { move: async () => {} },
|
||||||
|
context: () => ({}),
|
||||||
|
locator: (_selector: string) => ({ hover: async () => {}, focus: async () => {} }),
|
||||||
|
evaluate: async (_fn: any, ...args: any[]) => {
|
||||||
|
const payload = args[0]
|
||||||
|
if (!payload || !Array.isArray(payload.plans)) return undefined
|
||||||
|
const elements: Array<any> = []
|
||||||
|
const selectorToIds: Array<[string, number[]]> = []
|
||||||
|
const positions: Record<string, { x: number; y: number }> = {
|
||||||
|
'.a': { x: 10, y: 10 },
|
||||||
|
'.b': { x: 110, y: 10 },
|
||||||
|
'.c': { x: 10, y: 110 },
|
||||||
|
}
|
||||||
|
for (const plan of payload.plans as Array<{ key: string; queries: string[] }>) {
|
||||||
|
const ids: number[] = []
|
||||||
|
const pos = positions[plan.key] || { x: 0, y: 0, width: 0, height: 0 }
|
||||||
|
for (const _query of plan.queries) {
|
||||||
|
const subjectId = elements.length + 1
|
||||||
|
elements.push({
|
||||||
|
tagName: 'div',
|
||||||
|
rect: { x: pos.x, y: pos.y, width: 50, height: 50 },
|
||||||
|
transform: { matrix: [1, 0, 0, 1, 0, 0], originX: 0, originY: 0 },
|
||||||
|
})
|
||||||
|
ids.push(subjectId)
|
||||||
|
}
|
||||||
|
selectorToIds.push([plan.key, ids])
|
||||||
|
}
|
||||||
|
return { elements, selectorToIds }
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const ui = await imhotep(page)
|
||||||
|
|
||||||
|
ui.expect('.a').to.be.leftOf('.b', { minGap: 8 }).or.leftOf('.c', { minGap: 8 })
|
||||||
|
|
||||||
|
const result = await ui.checkAll()
|
||||||
|
assert.strictEqual(result.passed, true, `Expected passed:true. Diagnostics: ${JSON.stringify(result.diagnostics.map(d => d.code))}`)
|
||||||
|
assert.strictEqual(result.clauseResults.length, 1)
|
||||||
|
assert.strictEqual(result.clauseResults[0].status, 'pass')
|
||||||
|
assert.ok(['true', 'determinate'].includes(result.clauseResults[0].truth), `expected true|determinate truth, got ${result.clauseResults[0].truth}`)
|
||||||
|
})
|
||||||
|
|
||||||
it('forall over elements evaluates all matches', async () => {
|
it('forall over elements evaluates all matches', async () => {
|
||||||
const page = createMockPage()
|
const page = createMockPage()
|
||||||
const ui = await imhotep(page)
|
const ui = await imhotep(page)
|
||||||
|
|||||||
Reference in New Issue
Block a user