File size: 6,032 Bytes
a0ebf39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
import { describe, expect, test } from 'vitest';
import { buildWhiteboardConflicts } from '@/lib/orchestration/summarizers/whiteboard-conflicts';

// Minimal PPTElement stand-ins β€” the summarizer only reads geometry fields.
const text = (id: string, left: number, top: number, width: number, height: number) => ({
  type: 'text',
  id,
  left,
  top,
  width,
  height,
  content: '<p>sample</p>',
});

const table = (id: string, left: number, top: number, width: number, height: number) => ({
  type: 'table',
  id,
  left,
  top,
  width,
  height,
  data: [[{ text: 'a' }]],
});

const line = (
  id: string,
  left: number,
  top: number,
  start: [number, number],
  end: [number, number],
) => ({ type: 'line', id, left, top, start, end });

describe('buildWhiteboardConflicts β€” no conflicts', () => {
  test('empty element list returns empty string', () => {
    expect(buildWhiteboardConflicts([])).toBe('');
  });

  test('two well-separated elements return empty string', () => {
    const out = buildWhiteboardConflicts([
      text('t1', 20, 20, 200, 60),
      text('t2', 400, 200, 200, 60),
    ]);
    expect(out).toBe('');
  });

  test('just-touching bboxes (intersection area = 0) are not reported', () => {
    const out = buildWhiteboardConflicts([
      text('t1', 0, 0, 100, 100),
      text('t2', 100, 0, 100, 100), // shares only the x=100 edge
    ]);
    expect(out).toBe('');
  });

  test('line routed clear of all elements produces no conflict', () => {
    const out = buildWhiteboardConflicts([
      text('t1', 100, 100, 200, 60),
      line('l1', 0, 0, [50, 50], [50, 400]),
    ]);
    expect(out).toBe('');
  });
});

describe('buildWhiteboardConflicts β€” bbox overlap', () => {
  test('one element fully inside another reports ~100% overlap', () => {
    const out = buildWhiteboardConflicts([
      table('big', 0, 0, 500, 400),
      text('small', 50, 50, 100, 80), // entirely inside the table
    ]);
    expect(out).toContain('OVERLAP:');
    expect(out).toContain('100%');
  });

  test('50% overlap is reported; 10% is not (30% threshold)', () => {
    // Each bbox 100Γ—100; smaller area = 10000. Overlap area = 50Γ—100 = 5000 β†’ 50%.
    const overlapping = buildWhiteboardConflicts([
      text('a', 0, 0, 100, 100),
      text('b', 50, 0, 100, 100),
    ]);
    expect(overlapping).toContain('OVERLAP:');
    expect(overlapping).toContain('50%');

    // Overlap area = 10Γ—100 = 1000 β†’ 10% β€” below threshold.
    const tiny = buildWhiteboardConflicts([text('a', 0, 0, 100, 100), text('b', 90, 0, 100, 100)]);
    expect(tiny).toBe('');
  });

  test('non-line elements without width/height are skipped, not crashed', () => {
    const out = buildWhiteboardConflicts([
      text('t1', 0, 0, 100, 100),
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      { type: 'text', id: 'broken', left: 10, top: 10 } as any, // missing width/height
    ]);
    // Only one valid element remaining β†’ no overlap to report.
    expect(out).toBe('');
  });
});

describe('buildWhiteboardConflicts β€” line crossing elements', () => {
  test('line passing through the middle of a text box is reported', () => {
    const out = buildWhiteboardConflicts([
      text('t1', 100, 100, 200, 60), // covers x∈[100,300], y∈[100,160]
      line('l1', 0, 0, [0, 130], [400, 130]), // horizontal line through y=130, cuts the box
    ]);
    expect(out).toContain('LINE CROSSES:');
    expect(out).toContain('t1');
  });

  test('line whose endpoint is inside a bbox is reported', () => {
    const out = buildWhiteboardConflicts([
      text('t1', 100, 100, 200, 60),
      line('l1', 0, 0, [50, 50], [200, 130]), // endpoint (200,130) is inside t1
    ]);
    expect(out).toContain('LINE CROSSES:');
  });

  test('line with endpoints on opposite sides of a box but path above the box is clean', () => {
    const out = buildWhiteboardConflicts([
      text('t1', 100, 100, 200, 60),
      line('l1', 0, 0, [50, 50], [400, 50]), // y=50, above the box (y∈[100,160])
    ]);
    expect(out).toBe('');
  });
});

describe('buildWhiteboardConflicts β€” canvas edge clipping', () => {
  test('element extending past right edge is reported', () => {
    const out = buildWhiteboardConflicts([text('wide', 900, 100, 200, 60)]);
    expect(out).toContain('OUT OF CANVAS:');
    expect(out).toContain('right edge by 100px');
  });

  test('element extending past bottom edge is reported (canvas height = 563)', () => {
    const out = buildWhiteboardConflicts([text('tall', 100, 500, 100, 80)]);
    expect(out).toContain('OUT OF CANVAS:');
    expect(out).toContain('bottom edge by 17px'); // 500+80-563 = 17
  });

  test('element with negative left is reported', () => {
    const out = buildWhiteboardConflicts([text('negx', -10, 100, 50, 50)]);
    expect(out).toContain('OUT OF CANVAS:');
    expect(out).toContain('left edge by 10px');
  });

  test('element exactly at right edge (x+w == 1000) is NOT reported', () => {
    const out = buildWhiteboardConflicts([text('edge', 900, 100, 100, 60)]);
    expect(out).toBe('');
  });

  test('element exactly at bottom edge (y+h == 563) is NOT reported', () => {
    const out = buildWhiteboardConflicts([text('edge', 100, 500, 100, 63)]);
    expect(out).toBe('');
  });
});

describe('buildWhiteboardConflicts β€” output format', () => {
  test('renders a single markdown block with a header and bullet list', () => {
    const out = buildWhiteboardConflicts([text('a', 0, 0, 100, 100), text('b', 50, 0, 100, 100)]);
    expect(out).toMatch(/## ⚠ Layout Conflicts Detected/);
    expect(out).toMatch(/\n {2}- OVERLAP:/);
  });

  test('lists multiple conflicts in one block', () => {
    const out = buildWhiteboardConflicts([
      text('a', 0, 0, 100, 100),
      text('b', 50, 0, 100, 100), // overlap with a
      text('outside', 950, 100, 200, 60), // out of canvas
    ]);
    const bullets = out.split('\n').filter((l) => l.trim().startsWith('- '));
    expect(bullets.length).toBeGreaterThanOrEqual(2);
  });
});