mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-24 21:42:22 +02:00
121 lines
4.6 KiB
TypeScript
121 lines
4.6 KiB
TypeScript
// Run: node --test frontend/src/shared/interactiveRanking.test.ts
|
|
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { rankAndCapInteractives, goalKeywords, type RankItem } from './interactiveRanking.ts';
|
|
|
|
const mk = (role: string, name: string, id = 0): RankItem => ({ role, name, backendNodeId: id });
|
|
|
|
test('consecutive twins with same role+name collapse', () => {
|
|
const { shown } = rankAndCapInteractives([
|
|
mk('button', 'Like', 1),
|
|
mk('button', 'Like', 2),
|
|
mk('button', 'Like', 3),
|
|
mk('button', 'Share', 4),
|
|
]);
|
|
assert.deepEqual(shown.map((x) => x.backendNodeId), [1, 4]);
|
|
});
|
|
|
|
test('same role+name in different frames (sessionId) is NOT collapsed at the seam', () => {
|
|
const root: RankItem = { role: 'button', name: 'Close', backendNodeId: 1 };
|
|
const child: RankItem = { role: 'button', name: 'Close', backendNodeId: 2, sessionId: 'frameA' };
|
|
const { shown } = rankAndCapInteractives([root, child]);
|
|
// both survive: they are genuinely different elements across a frame boundary
|
|
assert.equal(shown.length, 2);
|
|
assert.ok(shown.some((x) => x.sessionId === 'frameA'));
|
|
});
|
|
|
|
test('non-consecutive same name is preserved (real list items)', () => {
|
|
const { shown } = rankAndCapInteractives([
|
|
mk('button', 'Add to cart', 1),
|
|
mk('link', 'Widget A', 2),
|
|
mk('button', 'Add to cart', 3),
|
|
mk('link', 'Widget B', 4),
|
|
mk('button', 'Add to cart', 5),
|
|
]);
|
|
// all three "Add to cart" survive because they are not back-to-back
|
|
const carts = shown.filter((x) => x.name === 'Add to cart');
|
|
assert.equal(carts.length, 3);
|
|
});
|
|
|
|
test('ranks by role priority: input > button/link > toggle > option', () => {
|
|
const { shown } = rankAndCapInteractives([
|
|
mk('option', 'opt', 1),
|
|
mk('checkbox', 'agree', 2),
|
|
mk('button', 'Go', 3),
|
|
mk('textbox', 'email', 4),
|
|
]);
|
|
assert.deepEqual(shown.map((x) => x.role), ['textbox', 'button', 'checkbox', 'option']);
|
|
});
|
|
|
|
test('preserves document order within the same priority tier', () => {
|
|
const { shown } = rankAndCapInteractives([
|
|
mk('button', 'First', 1),
|
|
mk('link', 'Second', 2),
|
|
mk('button', 'Third', 3),
|
|
]);
|
|
// button and link share tier 1; original order First, Second, Third holds
|
|
assert.deepEqual(shown.map((x) => x.backendNodeId), [1, 2, 3]);
|
|
});
|
|
|
|
test('caps to N and reports the truncated remainder', () => {
|
|
const items = Array.from({ length: 150 }, (_, i) => mk('link', `L${i}`, i));
|
|
const { shown, truncated } = rankAndCapInteractives(items, { cap: 60 });
|
|
assert.equal(shown.length, 60);
|
|
assert.equal(truncated, 90);
|
|
});
|
|
|
|
test('cap of 0 means no cap', () => {
|
|
const items = Array.from({ length: 5 }, (_, i) => mk('link', `L${i}`, i));
|
|
const { shown, truncated } = rankAndCapInteractives(items, { cap: 0 });
|
|
assert.equal(shown.length, 5);
|
|
assert.equal(truncated, 0);
|
|
});
|
|
|
|
test('goal-matched elements float to the top, above role priority', () => {
|
|
const { shown } = rankAndCapInteractives([
|
|
mk('textbox', 'Search', 1),
|
|
mk('link', 'Account settings', 2),
|
|
mk('button', 'Save', 3),
|
|
], { goal: 'open the settings page' });
|
|
// "Account settings" matches "settings" and jumps ahead of the textbox
|
|
assert.equal(shown[0].backendNodeId, 2);
|
|
});
|
|
|
|
test('goal match survives the cap even when buried deep', () => {
|
|
const items = Array.from({ length: 100 }, (_, i) => mk('link', `Item ${i}`, i));
|
|
items.push(mk('button', 'Checkout now', 999));
|
|
const { shown } = rankAndCapInteractives(items, { cap: 30, goal: 'click checkout' });
|
|
assert.ok(shown.some((x) => x.backendNodeId === 999), 'checkout should be retained');
|
|
assert.equal(shown[0].backendNodeId, 999);
|
|
});
|
|
|
|
test('no goal leaves pure role-priority ordering', () => {
|
|
const { shown } = rankAndCapInteractives([
|
|
mk('option', 'opt', 1),
|
|
mk('button', 'Go', 2),
|
|
mk('textbox', 'email', 3),
|
|
]);
|
|
assert.deepEqual(shown.map((x) => x.role), ['textbox', 'button', 'option']);
|
|
});
|
|
|
|
test('goalKeywords strips stopwords, action verbs, and short tokens', () => {
|
|
assert.deepEqual(goalKeywords('Click the Submit button to send'), ['submit', 'send']);
|
|
assert.deepEqual(goalKeywords('type into the search box'), ['search']);
|
|
assert.deepEqual(goalKeywords(''), []);
|
|
});
|
|
|
|
test('empty input yields empty result', () => {
|
|
const { shown, truncated } = rankAndCapInteractives([]);
|
|
assert.equal(shown.length, 0);
|
|
assert.equal(truncated, 0);
|
|
});
|
|
|
|
test('unknown role falls into the middle tier, not dropped', () => {
|
|
const { shown } = rankAndCapInteractives([
|
|
mk('option', 'opt', 1),
|
|
mk('weirdrole', 'mystery', 2),
|
|
mk('textbox', 'field', 3),
|
|
]);
|
|
assert.deepEqual(shown.map((x) => x.role), ['textbox', 'weirdrole', 'option']);
|
|
});
|