Skip to content

Commit 9c51c39

Browse files
committed
test: cover pdf editor model helpers
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
1 parent e44d364 commit 9c51c39

1 file changed

Lines changed: 200 additions & 0 deletions

File tree

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2026 LibreSign contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import { describe, expect, it } from 'vitest'
7+
8+
import {
9+
buildPdfEditorSignerPayload,
10+
calculatePdfPlacement,
11+
createPdfEditorObject,
12+
createPdfObjectId,
13+
findPdfObjectLocation,
14+
getPdfEditorSignerId,
15+
getPdfEditorSignerLabel,
16+
resolvePdfEditorSignerChange,
17+
} from '../../../components/PdfEditor/pdfEditorModel'
18+
19+
describe('pdfEditorModel', () => {
20+
describe('RULE: signer identity is derived from a single stable key', () => {
21+
it('prefers signRequestId over email', () => {
22+
expect(getPdfEditorSignerId({ signRequestId: 15, email: 'fallback@example.com' })).toBe('15')
23+
})
24+
25+
it('falls back to email when signRequestId is absent', () => {
26+
expect(getPdfEditorSignerId({ email: 'fallback@example.com' })).toBe('fallback@example.com')
27+
})
28+
29+
it('returns empty string when signer has no identity', () => {
30+
expect(getPdfEditorSignerId({})).toBe('')
31+
})
32+
})
33+
34+
describe('RULE: signer labels come from a single presentation rule', () => {
35+
it('prefers displayName', () => {
36+
expect(getPdfEditorSignerLabel({ displayName: 'Ada', email: 'ada@example.com', signRequestId: 9 })).toBe('Ada')
37+
})
38+
39+
it('falls back to identity fields', () => {
40+
expect(getPdfEditorSignerLabel({ email: 'ada@example.com' })).toBe('ada@example.com')
41+
expect(getPdfEditorSignerLabel({ signRequestId: 9 })).toBe('9')
42+
})
43+
})
44+
45+
describe('RULE: signer payload cloning never mutates caller input', () => {
46+
it('ensures a detached element object exists', () => {
47+
const source = { email: 'ada@example.com' }
48+
49+
const payload = buildPdfEditorSignerPayload(source)
50+
51+
expect(payload).toEqual({ email: 'ada@example.com', element: {} })
52+
expect(payload).not.toBe(source)
53+
})
54+
55+
it('preserves placement data by value', () => {
56+
const source = {
57+
email: 'ada@example.com',
58+
element: {
59+
elementId: 9,
60+
coordinates: { page: 1, left: 10, top: 20 },
61+
},
62+
}
63+
64+
const payload = buildPdfEditorSignerPayload(source)
65+
66+
expect(payload.element).toEqual(source.element)
67+
expect(payload.element).not.toBe(source.element)
68+
})
69+
})
70+
71+
describe('RULE: object lookup is a pure document traversal', () => {
72+
it('finds the object document and page indexes', () => {
73+
const location = findPdfObjectLocation([
74+
{ allObjects: [[{ id: 'obj-1' }], [{ id: 'obj-2' }]] },
75+
{ allObjects: [[{ id: 'obj-3' }]] },
76+
], 'obj-2')
77+
78+
expect(location).toEqual({ docIndex: 0, pageIndex: 1 })
79+
})
80+
81+
it('returns null when the object does not exist', () => {
82+
expect(findPdfObjectLocation([{ allObjects: [[{ id: 'obj-1' }]] }], 'missing')).toBeNull()
83+
})
84+
})
85+
86+
describe('RULE: signer replacement is resolved without component state mutation', () => {
87+
it('returns the next signer and preserves current placement', () => {
88+
const result = resolvePdfEditorSignerChange({
89+
availableSigners: [
90+
{ signRequestId: 1, displayName: 'One' },
91+
{ signRequestId: 2, email: 'two@example.com' },
92+
],
93+
selectedSigner: { signRequestId: 2 },
94+
object: {
95+
id: 'obj-1',
96+
signer: {
97+
signRequestId: 1,
98+
element: { elementId: 99, coordinates: { page: 1, left: 10, top: 20 } },
99+
},
100+
},
101+
documents: [{ allObjects: [[{ id: 'obj-1' }]] }],
102+
})
103+
104+
expect(result).toEqual({
105+
docIndex: 0,
106+
signer: {
107+
signRequestId: 2,
108+
email: 'two@example.com',
109+
displayName: '2',
110+
element: { elementId: 99, coordinates: { page: 1, left: 10, top: 20 } },
111+
},
112+
})
113+
})
114+
115+
it('returns null when no target signer can be resolved', () => {
116+
const result = resolvePdfEditorSignerChange({
117+
availableSigners: [{ signRequestId: 1, displayName: 'One' }],
118+
selectedSigner: { signRequestId: 3 },
119+
object: { id: 'obj-1' },
120+
documents: [],
121+
})
122+
123+
expect(result).toBeNull()
124+
})
125+
})
126+
127+
describe('RULE: placement calculation is isolated from component orchestration', () => {
128+
it('uses left/top coordinates directly', () => {
129+
const placement = calculatePdfPlacement({
130+
signer: {
131+
element: {
132+
documentIndex: 1,
133+
coordinates: { page: 2, left: 10, top: 20, width: 30, height: 40 },
134+
},
135+
},
136+
defaultDocIndex: 0,
137+
pageHeight: 800,
138+
})
139+
140+
expect(placement).toEqual({
141+
docIndex: 1,
142+
pageIndex: 1,
143+
x: 10,
144+
y: 20,
145+
width: 30,
146+
height: 40,
147+
})
148+
})
149+
150+
it('converts PDF coordinate boxes into canvas placement', () => {
151+
const placement = calculatePdfPlacement({
152+
signer: {
153+
element: {
154+
coordinates: { page: 1, llx: 50, lly: 100, urx: 250, ury: 700 },
155+
},
156+
},
157+
defaultDocIndex: 0,
158+
pageHeight: 841.89,
159+
})
160+
161+
expect(placement).toEqual({
162+
docIndex: 0,
163+
pageIndex: 0,
164+
x: 50,
165+
y: 141.89,
166+
width: 200,
167+
height: 600,
168+
})
169+
})
170+
171+
it('returns null when page number is invalid', () => {
172+
expect(calculatePdfPlacement({ signer: { element: { coordinates: { left: 10, top: 10 } } }, defaultDocIndex: 0, pageHeight: 500 })).toBeNull()
173+
})
174+
})
175+
176+
describe('RULE: object creation is deterministic except for id generation', () => {
177+
it('builds a signature object from signer and placement', () => {
178+
const signer = { email: 'ada@example.com' }
179+
const object = createPdfEditorObject({
180+
signer,
181+
placement: { docIndex: 0, pageIndex: 0, x: 10, y: 20, width: 30, height: 40 },
182+
objectId: 'obj-fixed',
183+
})
184+
185+
expect(object).toEqual({
186+
id: 'obj-fixed',
187+
type: 'signature',
188+
signer,
189+
x: 10,
190+
y: 20,
191+
width: 30,
192+
height: 40,
193+
})
194+
})
195+
196+
it('creates ids with the expected prefix and random suffix', () => {
197+
expect(createPdfObjectId()).toMatch(/^obj-\d+-[a-z0-9]{6}$/)
198+
})
199+
})
200+
})

0 commit comments

Comments
 (0)