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:
John Dvorak
2026-05-21 13:07:16 -07:00
parent 771ddaea4e
commit a222a9fa8a
2 changed files with 102 additions and 12 deletions
-12
View File
@@ -78,18 +78,6 @@ export type {
CustomRendererOptions,
} 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.
export {
touchTarget,
@@ -484,6 +484,108 @@ describe('FOL engine integration', () => {
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 () => {
const page = createMockPage()
const ui = await imhotep(page)