pdfdancer-client-typescript 1.0.14 → 1.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pdfdancer-client-typescript",
3
- "version": "1.0.14",
3
+ "version": "1.0.15",
4
4
  "description": "A TypeScript client library for the PDFDancer PDF manipulation API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -10,7 +10,7 @@
10
10
  "test:unit": "jest --selectProjects unit",
11
11
  "test:e2e": "jest --selectProjects e2e",
12
12
  "test:watch": "jest --watch",
13
- "lint": "eslint src/**/*.ts",
13
+ "lint": "eslint src/",
14
14
  "prepublishOnly": "npm run build",
15
15
  "release": "node scripts/release.js",
16
16
  "release:skip-tests": "node scripts/release.js --skip-tests",
@@ -105,4 +105,62 @@ describe('AcroForm Fields E2E Tests (v2 API)', () => {
105
105
  const assertions = await PDFAssertions.create(pdf);
106
106
  await assertions.assertNumberOfFormFields(10);
107
107
  });
108
+
109
+ // Tests for singular select methods
110
+ test('selectFormField returns first field or null', async () => {
111
+ const [baseUrl, token, pdfData] = await requireEnvAndFixture('mixed-form-types.pdf');
112
+ const pdf = await PDFDancer.open(pdfData, token, baseUrl);
113
+
114
+ // Test with results
115
+ const field = await pdf.page(0).selectFormField();
116
+ expect(field).not.toBeNull();
117
+ expect(field!.internalId).toBe('FORM_FIELD_000001');
118
+
119
+ // Test with PDFDancer class
120
+ const fieldFromPdf = await pdf.selectFormField();
121
+ expect(fieldFromPdf).not.toBeNull();
122
+ expect(fieldFromPdf!.internalId).toBe('FORM_FIELD_000001');
123
+ });
124
+
125
+ test('selectFormFieldByName returns first field by name or null', async () => {
126
+ const [baseUrl, token, pdfData] = await requireEnvAndFixture('mixed-form-types.pdf');
127
+ const pdf = await PDFDancer.open(pdfData, token, baseUrl);
128
+
129
+ const field = await pdf.page(0).selectFormFieldByName('firstName');
130
+ expect(field).not.toBeNull();
131
+ expect(field!.name).toBe('firstName');
132
+ expect(field!.internalId).toBe('FORM_FIELD_000001');
133
+
134
+ // Test with no match
135
+ const noMatch = await pdf.page(0).selectFormFieldByName('nonExistent');
136
+ expect(noMatch).toBeNull();
137
+ });
138
+
139
+ test('selectFieldByName returns first field by name or null', async () => {
140
+ const [baseUrl, token, pdfData] = await requireEnvAndFixture('mixed-form-types.pdf');
141
+ const pdf = await PDFDancer.open(pdfData, token, baseUrl);
142
+
143
+ const field = await pdf.selectFieldByName('firstName');
144
+ expect(field).not.toBeNull();
145
+ expect(field!.name).toBe('firstName');
146
+ expect(field!.internalId).toBe('FORM_FIELD_000001');
147
+
148
+ // Test with no match
149
+ const noMatch = await pdf.selectFieldByName('nonExistent');
150
+ expect(noMatch).toBeNull();
151
+ });
152
+
153
+ test('selectFormFieldAt returns first field at position or null', async () => {
154
+ const [baseUrl, token, pdfData] = await requireEnvAndFixture('mixed-form-types.pdf');
155
+ const pdf = await PDFDancer.open(pdfData, token, baseUrl);
156
+
157
+ const field = await pdf.page(0).selectFormFieldAt(280, 455, 1);
158
+ expect(field).not.toBeNull();
159
+ expect(field!.type).toBe('RADIO_BUTTON');
160
+ expect(field!.internalId).toBe('FORM_FIELD_000008');
161
+
162
+ // Test with no match
163
+ const noMatch = await pdf.page(0).selectFormFieldAt(1000, 1000, 1);
164
+ expect(noMatch).toBeNull();
165
+ });
108
166
  });
@@ -55,4 +55,33 @@ describe('Form E2E Tests (v2 API)', () => {
55
55
  const assertions = await PDFAssertions.create(pdf);
56
56
  await assertions.assertNumberOfFormXObjects(0);
57
57
  });
58
+
59
+ // Tests for singular select methods
60
+ test('selectForm returns first form or null', async () => {
61
+ const [baseUrl, token, pdfData] = await requireEnvAndFixture('form-xobject-example.pdf');
62
+ const pdf = await PDFDancer.open(pdfData, token, baseUrl);
63
+
64
+ // Test with results
65
+ const form = await pdf.page(0).selectForm();
66
+ expect(form).not.toBeNull();
67
+ expect(form!.internalId).toBe('FORM_000001');
68
+
69
+ // Test with PDFDancer class
70
+ const formFromPdf = await pdf.selectForm();
71
+ expect(formFromPdf).not.toBeNull();
72
+ expect(formFromPdf!.internalId).toBe('FORM_000001');
73
+ });
74
+
75
+ test('selectFormAt returns first form at position or null', async () => {
76
+ const [baseUrl, token, pdfData] = await requireEnvAndFixture('form-xobject-example.pdf');
77
+ const pdf = await PDFDancer.open(pdfData, token, baseUrl);
78
+
79
+ const form = await pdf.page(0).selectFormAt(321, 601, 1);
80
+ expect(form).not.toBeNull();
81
+ expect(form!.internalId).toBe('FORM_000005');
82
+
83
+ // Test with no match
84
+ const noMatch = await pdf.page(0).selectFormAt(0, 0);
85
+ expect(noMatch).toBeNull();
86
+ });
58
87
  });
@@ -110,4 +110,38 @@ describe('Image E2E Tests (v2 API)', () => {
110
110
  await assertions.assertImageAt(50.1, 98.0, 6);
111
111
  await assertions.assertNumberOfImages(1, 6);
112
112
  });
113
+
114
+ // Tests for singular select methods
115
+ test('selectImage returns first image or null', async () => {
116
+ const [baseUrl, token, pdfData] = await requireEnvAndFixture('ObviouslyAwesome.pdf');
117
+ const pdf = await PDFDancer.open(pdfData, token, baseUrl);
118
+
119
+ // Test with results - page 11 has images
120
+ const image = await pdf.page(11).selectImage();
121
+ expect(image).not.toBeNull();
122
+ expect(image!.internalId).toBe('IMAGE_000003');
123
+
124
+ // Test with PDFDancer class
125
+ const imageFromPdf = await pdf.selectImage();
126
+ expect(imageFromPdf).not.toBeNull();
127
+ expect(imageFromPdf!.internalId).toBe('IMAGE_000001');
128
+
129
+ // Test page 0 also has images (2 images)
130
+ const imageOnPage0 = await pdf.page(0).selectImage();
131
+ expect(imageOnPage0).not.toBeNull();
132
+ expect(imageOnPage0!.internalId).toBe('IMAGE_000001');
133
+ });
134
+
135
+ test('selectImageAt returns first image at position or null', async () => {
136
+ const [baseUrl, token, pdfData] = await requireEnvAndFixture('ObviouslyAwesome.pdf');
137
+ const pdf = await PDFDancer.open(pdfData, token, baseUrl);
138
+
139
+ const image = await pdf.page(11).selectImageAt(54, 300, 1);
140
+ expect(image).not.toBeNull();
141
+ expect(image!.internalId).toBe('IMAGE_000003');
142
+
143
+ // Test with no match
144
+ const noMatch = await pdf.page(11).selectImageAt(0, 0);
145
+ expect(noMatch).toBeNull();
146
+ });
113
147
  });
@@ -15,7 +15,6 @@ describe('Text Line E2E Tests (Showcase)', () => {
15
15
  const status = line.objectRef().status;
16
16
  expect(status).toBeDefined();
17
17
  expect(status?.isModified()).toBe(false);
18
- expect(status?.isEncodable()).toBe(true);
19
18
  }
20
19
  }
21
20
  });
@@ -33,14 +32,12 @@ describe('Text Line E2E Tests (Showcase)', () => {
33
32
  expect(Math.abs(first.position.getX()! - 180)).toBeLessThanOrEqual(1);
34
33
  expect(Math.abs(first.position.getY()! - 750)).toBeLessThanOrEqual(1);
35
34
  expect(first.objectRef().status?.isModified()).toBe(false);
36
- expect(first.objectRef().status?.isEncodable()).toBe(true);
37
35
 
38
36
  const last = lines[lines.length - 1];
39
37
  expect(last.internalId).toBe('TEXTLINE_000036');
40
38
  expect(Math.abs(last.position.getX()! - 69.3)).toBeLessThanOrEqual(2);
41
39
  expect(Math.abs(last.position.getY()! - 45)).toBeLessThanOrEqual(2);
42
40
  expect(last.objectRef().status?.isModified()).toBe(false);
43
- expect(last.objectRef().status?.isEncodable()).toBe(true);
44
41
  });
45
42
 
46
43
  test('find lines by text', async () => {
@@ -84,7 +81,6 @@ describe('Text Line E2E Tests (Showcase)', () => {
84
81
  const moved = (await pdf.page(0).selectTextLinesAt(newX, newY, 1))[0];
85
82
  const status = moved.objectRef().status;
86
83
  expect(status).toBeDefined();
87
- expect(status?.isEncodable()).toBe(true);
88
84
  expect(status?.getFontType()).toBe(FontType.EMBEDDED);
89
85
  expect(status?.isModified()).toBe(false);
90
86
 
@@ -106,7 +102,6 @@ describe('Text Line E2E Tests (Showcase)', () => {
106
102
  expect(lines.length).toBeGreaterThan(0);
107
103
  const status = lines[0].objectRef().status;
108
104
  expect(status).toBeDefined();
109
- expect(status?.isEncodable()).toBe(true);
110
105
  expect(status?.getFontType()).toBe(FontType.EMBEDDED);
111
106
  expect(status?.isModified()).toBe(true);
112
107
 
@@ -24,7 +24,6 @@ describe('Text Line E2E Tests (v2 API)', () => {
24
24
  expectWithin(first.position.boundingRect?.y, 706, 1);
25
25
  expect(first.objectRef().status).toBeDefined();
26
26
  expect(first.objectRef().status?.isModified()).toBe(false);
27
- expect(first.objectRef().status?.isEncodable()).toBe(true);
28
27
 
29
28
  const last = lines[lines.length - 1];
30
29
  expect(last.internalId).toBe('TEXTLINE_000340');
@@ -33,7 +32,6 @@ describe('Text Line E2E Tests (v2 API)', () => {
33
32
  expectWithin(last.position.boundingRect?.y, 35, 1);
34
33
  expect(last.objectRef().status).toBeDefined();
35
34
  expect(last.objectRef().status?.isModified()).toBe(false);
36
- expect(last.objectRef().status?.isEncodable()).toBe(true);
37
35
  });
38
36
 
39
37
  test('find lines on page', async () => {
@@ -107,17 +105,15 @@ describe('Text Line E2E Tests (v2 API)', () => {
107
105
  const [line] = await pdf.page(0).selectTextLinesStartingWith('The Complete');
108
106
  const result = await line.edit().text(' replaced ').apply();
109
107
 
110
- // This should issue a warning about an embedded font modification
111
- expect(result.warning).toBeDefined();
112
- expect(result.warning).toContain('You are using an embedded font and modified the text.');
108
+ expect(result.warning).toBeDefined(); // This should issue a warning about an embedded font modification
109
+ expect(result.warning).toContain('Text is not encodable with your current font, we are using \'Poppins-Bold\' as a fallback font instead.');
113
110
 
114
111
  const stillOld = await pdf.page(0).selectParagraphsStartingWith('The Complete');
115
112
  expect(stillOld).toHaveLength(0);
116
113
 
117
- const lines = await pdf.page(0).selectTextLinesStartingWith(' replaced ');
114
+ const lines = await pdf.page(0).selectTextLinesMatching('.*replaced.*');
118
115
  expect(lines.length).toBeGreaterThan(0);
119
116
  expect(lines[0].objectRef().status).toBeDefined();
120
- expect(lines[0].objectRef().status?.isEncodable()).toBe(true);
121
117
  expect(lines[0].objectRef().status?.getFontType()).toBe(FontType.EMBEDDED);
122
118
  expect(lines[0].objectRef().status?.isModified()).toBe(true);
123
119
 
@@ -130,4 +126,64 @@ describe('Text Line E2E Tests (v2 API)', () => {
130
126
  const assertions = await PDFAssertions.create(pdf);
131
127
  await assertions.assertTextlineExists(' replaced ');
132
128
  });
129
+
130
+ // Tests for singular select methods
131
+ test('selectTextLine returns first line or null', async () => {
132
+ const [baseUrl, token, pdfData] = await requireEnvAndFixture('ObviouslyAwesome.pdf');
133
+ const pdf = await PDFDancer.open(pdfData, token, baseUrl);
134
+
135
+ // Test with results
136
+ const line = await pdf.page(1).selectTextLine();
137
+ expect(line).not.toBeNull();
138
+ expect(line!.internalId).toBe('TEXTLINE_000005');
139
+
140
+ // Test with PDFDancer class
141
+ const lineFromPdf = await pdf.selectTextLine();
142
+ expect(lineFromPdf).not.toBeNull();
143
+ expect(lineFromPdf!.internalId).toBe('TEXTLINE_000001');
144
+
145
+ // Test alias selectLine
146
+ const lineAlias = await pdf.selectLine();
147
+ expect(lineAlias).not.toBeNull();
148
+ expect(lineAlias!.internalId).toBe('TEXTLINE_000001');
149
+ });
150
+
151
+ test('selectTextLineStartingWith returns first match or null', async () => {
152
+ const [baseUrl, token, pdfData] = await requireEnvAndFixture('ObviouslyAwesome.pdf');
153
+ const pdf = await PDFDancer.open(pdfData, token, baseUrl);
154
+
155
+ const line = await pdf.page(0).selectTextLineStartingWith('the complete');
156
+ expect(line).not.toBeNull();
157
+ expect(line!.internalId).toBe('TEXTLINE_000002');
158
+
159
+ // Test with no match
160
+ const noMatch = await pdf.page(0).selectTextLineStartingWith('NoMatch');
161
+ expect(noMatch).toBeNull();
162
+ });
163
+
164
+ test('selectTextLineMatching returns first match or null', async () => {
165
+ const [baseUrl, token, pdfData] = await requireEnvAndFixture('ObviouslyAwesome.pdf');
166
+ const pdf = await PDFDancer.open(pdfData, token, baseUrl);
167
+
168
+ const line = await pdf.page(0).selectTextLineMatching('.*Complete.*');
169
+ expect(line).not.toBeNull();
170
+ expect(line!.internalId).toBe('TEXTLINE_000002');
171
+
172
+ // Test with no match
173
+ const noMatch = await pdf.page(0).selectTextLineMatching('.*NOT FOUND.*');
174
+ expect(noMatch).toBeNull();
175
+ });
176
+
177
+ test('selectTextLineAt returns first line at position or null', async () => {
178
+ const [baseUrl, token, pdfData] = await requireEnvAndFixture('ObviouslyAwesome.pdf');
179
+ const pdf = await PDFDancer.open(pdfData, token, baseUrl);
180
+
181
+ const line = await pdf.page(0).selectTextLineAt(54, 606, 10);
182
+ expect(line).not.toBeNull();
183
+ expect(line!.internalId).toBe('TEXTLINE_000002');
184
+
185
+ // Test with no match
186
+ const noMatch = await pdf.page(0).selectTextLineAt(1000, 1000, 1);
187
+ expect(noMatch).toBeNull();
188
+ });
133
189
  });
@@ -28,7 +28,6 @@ describe('Paragraph E2E Tests (Showcase)', () => {
28
28
 
29
29
  const status = last.objectRef().status;
30
30
  expect(status).toBeDefined();
31
- expect(status?.isEncodable()).toBe(true);
32
31
  expect(status?.getFontType()).toBe(FontType.EMBEDDED);
33
32
  expect(status?.isModified()).toBe(false);
34
33
  });
@@ -137,7 +136,6 @@ describe('Paragraph E2E Tests (Showcase)', () => {
137
136
  const [moved] = await pdf.page(0).selectParagraphsStartingWith(SAMPLE_PARAGRAPH);
138
137
  const status = moved.objectRef().status;
139
138
  expect(status).toBeDefined();
140
- expect(status?.isEncodable()).toBe(true);
141
139
  expect(status?.getFontType()).toBe(FontType.EMBEDDED);
142
140
  expect(status?.isModified()).toBe(false);
143
141
 
@@ -181,7 +179,6 @@ describe('Paragraph E2E Tests (Showcase)', () => {
181
179
  const moved = (await pdf.page(0).selectParagraphsAt(300.1, 500))[0];
182
180
  const status = moved.objectRef().status;
183
181
  expect(status).toBeDefined();
184
- expect(status?.isEncodable()).toBe(true);
185
182
  expect(status?.getFontType()).toBe(FontType.STANDARD);
186
183
  expect(status?.isModified()).toBe(true);
187
184
 
@@ -242,7 +239,6 @@ describe('Paragraph E2E Tests (Showcase)', () => {
242
239
  const [updated] = await pdf.page(0).selectParagraphsStartingWith(SAMPLE_PARAGRAPH);
243
240
  const status = updated.objectRef().status;
244
241
  expect(status).toBeDefined();
245
- expect(status?.isEncodable()).toBe(true);
246
242
  expect(status?.getFontType()).toBe(FontType.EMBEDDED);
247
243
  expect(status?.isModified()).toBe(false);
248
244
 
@@ -261,7 +257,6 @@ describe('Paragraph E2E Tests (Showcase)', () => {
261
257
  const [updated] = await pdf.page(0).selectParagraphsStartingWith('lorem');
262
258
  const status = updated.objectRef().status;
263
259
  expect(status).toBeDefined();
264
- expect(status?.isEncodable()).toBe(true);
265
260
  expect(status?.getFontType()).toBe(FontType.EMBEDDED);
266
261
  expect(status?.isModified()).toBe(true);
267
262
 
@@ -282,7 +277,6 @@ describe('Paragraph E2E Tests (Showcase)', () => {
282
277
  const [updated] = await pdf.page(0).selectParagraphsStartingWith(SAMPLE_PARAGRAPH);
283
278
  const status = updated.objectRef().status;
284
279
  expect(status).toBeDefined();
285
- expect(status?.isEncodable()).toBe(true);
286
280
  expect(status?.getFontType()).toBe(FontType.STANDARD);
287
281
  expect(status?.isModified()).toBe(true);
288
282
 
@@ -300,7 +294,6 @@ describe('Paragraph E2E Tests (Showcase)', () => {
300
294
  const [updated] = await pdf.page(0).selectParagraphsStartingWith(SAMPLE_PARAGRAPH);
301
295
  const status = updated.objectRef().status;
302
296
  expect(status).toBeDefined();
303
- expect(status?.isEncodable()).toBe(true);
304
297
  expect(status?.getFontType()).toBe(FontType.EMBEDDED);
305
298
  expect(status?.isModified()).toBe(false);
306
299
 
@@ -517,7 +510,6 @@ describe('Paragraph E2E Tests (Showcase)', () => {
517
510
 
518
511
  const status = moved[0].objectRef().status;
519
512
  expect(status).toBeDefined();
520
- expect(status?.isEncodable()).toBe(true);
521
513
  expect(status?.getFontType()).toBe(FontType.EMBEDDED);
522
514
  expect(status?.isModified()).toBe(false);
523
515
  });
@@ -32,7 +32,6 @@ describe('Paragraph E2E Tests (v2 API)', () => {
32
32
  expectWithin(last.position.boundingRect?.y, 496, 2);
33
33
 
34
34
  expect(last.objectRef().status).toBeDefined();
35
- expect(last.objectRef().status?.isEncodable()).toBe(true);
36
35
  expect(last.objectRef().status?.getFontType()).toBe(FontType.EMBEDDED);
37
36
  expect(last.objectRef().status?.isModified()).toBe(false);
38
37
  });
@@ -112,7 +111,6 @@ describe('Paragraph E2E Tests (v2 API)', () => {
112
111
  expect(movedParas.length).toBeGreaterThan(0);
113
112
  const moved = movedParas[0];
114
113
  expect(moved.objectRef().status).toBeDefined();
115
- expect(moved.objectRef().status?.isEncodable()).toBe(true);
116
114
  expect(moved.objectRef().status?.getFontType()).toBe(FontType.STANDARD);
117
115
  expect(moved.objectRef().status?.isModified()).toBe(true);
118
116
 
@@ -134,7 +132,7 @@ describe('Paragraph E2E Tests (v2 API)', () => {
134
132
  // This should issue a warning about an embedded font modification
135
133
  expect(typeof result).toBe('object');
136
134
  expect((result as any).warning).toBeDefined();
137
- expect((result as any).warning).toContain('You are using an embedded font and modified the text.');
135
+ expect((result as any).warning).toBe("Text is not encodable with your current font, we are using 'Poppins-Bold' as a fallback font instead.");
138
136
 
139
137
  await assertNewParagraphExists(pdf);
140
138
 
@@ -142,13 +140,12 @@ describe('Paragraph E2E Tests (v2 API)', () => {
142
140
  expect(modifiedParas.length).toBeGreaterThan(0);
143
141
  const modified = modifiedParas[0];
144
142
  expect(modified.objectRef().status).toBeDefined();
145
- expect(modified.objectRef().status?.isEncodable()).toBe(true);
146
143
  expect(modified.objectRef().status?.getFontType()).toBe(FontType.EMBEDDED);
147
144
  expect(modified.objectRef().status?.isModified()).toBe(true);
148
145
 
149
146
  const assertions = await PDFAssertions.create(pdf);
150
- await assertions.assertTextlineHasFont('Awesomely', 'IXKSWR+Poppins-Bold', 45, 0);
151
- await assertions.assertTextlineHasFont('Obvious!', 'IXKSWR+Poppins-Bold', 45, 0);
147
+ await assertions.assertTextlineHasFontMatching('Awesomely', 'Poppins-Bold', 45, 0);
148
+ await assertions.assertTextlineHasFontMatching('Obvious!', 'Poppins-Bold', 45, 0);
152
149
  await assertions.assertTextlineHasColor('Awesomely', new Color(255, 255, 255), 0);
153
150
  await assertions.assertTextlineHasColor('Obvious!', new Color(255, 255, 255), 0);
154
151
  });
@@ -543,8 +540,8 @@ describe('Paragraph E2E Tests (v2 API)', () => {
543
540
  await para.edit().replace('Awesomely\nObvious!').apply();
544
541
 
545
542
  const assertions = await PDFAssertions.create(pdf);
546
- await assertions.assertTextlineHasFont('Awesomely', 'IXKSWR+Poppins-Bold', 45, 0);
547
- await assertions.assertTextlineHasFont('Obvious!', 'IXKSWR+Poppins-Bold', 45, 0);
543
+ await assertions.assertTextlineHasFontMatching('Awesomely', 'Poppins-Bold', 45, 0);
544
+ await assertions.assertTextlineHasFontMatching('Obvious!', 'Poppins-Bold', 45, 0);
548
545
  await assertions.assertTextlineHasColor('Awesomely', new Color(255, 255, 255), 0);
549
546
  await assertions.assertTextlineHasColor('Obvious!', new Color(255, 255, 255), 0);
550
547
  });
@@ -624,4 +621,63 @@ describe('Paragraph E2E Tests (v2 API)', () => {
624
621
  expect(StandardFonts.ZAPF_DINGBATS).toBe('ZapfDingbats');
625
622
  });
626
623
 
624
+ // Tests for singular select methods
625
+ test('selectParagraph returns first paragraph or null', async () => {
626
+ const [baseUrl, token, pdfData] = await requireEnvAndFixture('ObviouslyAwesome.pdf');
627
+ const pdf = await PDFDancer.open(pdfData, token, baseUrl);
628
+
629
+ // Test with results
630
+ const para = await pdf.page(0).selectParagraph();
631
+ expect(para).not.toBeNull();
632
+ expect(para!.internalId).toBe('PARAGRAPH_000003');
633
+
634
+ // Test with PDFDancer class
635
+ const paraFromPdf = await pdf.selectParagraph();
636
+ expect(paraFromPdf).not.toBeNull();
637
+ expect(paraFromPdf!.internalId).toBe('PARAGRAPH_000003');
638
+ });
639
+
640
+ test('selectParagraphStartingWith returns first match or null', async () => {
641
+ const [baseUrl, token, pdfData] = await requireEnvAndFixture('ObviouslyAwesome.pdf');
642
+ const pdf = await PDFDancer.open(pdfData, token, baseUrl);
643
+
644
+ const para = await pdf.page(0).selectParagraphStartingWith('The Complete');
645
+ expect(para).not.toBeNull();
646
+ expect(para!.internalId).toBe('PARAGRAPH_000004');
647
+
648
+ // Test with no match
649
+ const noMatch = await pdf.page(0).selectParagraphStartingWith('NoMatch');
650
+ expect(noMatch).toBeNull();
651
+ });
652
+
653
+ test('selectParagraphMatching returns first match or null', async () => {
654
+ const [baseUrl, token, pdfData] = await requireEnvAndFixture('ObviouslyAwesome.pdf');
655
+ const pdf = await PDFDancer.open(pdfData, token, baseUrl);
656
+
657
+ const para = await pdf.page(0).selectParagraphMatching('.*Complete.*');
658
+ expect(para).not.toBeNull();
659
+ expect(para!.internalId).toBe('PARAGRAPH_000004');
660
+
661
+ // Test with PDFDancer class
662
+ const paraFromPdf = await pdf.selectParagraphMatching('.*Complete.*');
663
+ expect(paraFromPdf).not.toBeNull();
664
+
665
+ // Test with no match
666
+ const noMatch = await pdf.page(0).selectParagraphMatching('.*NOT FOUND.*');
667
+ expect(noMatch).toBeNull();
668
+ });
669
+
670
+ test('selectParagraphAt returns first paragraph at position or null', async () => {
671
+ const [baseUrl, token, pdfData] = await requireEnvAndFixture('ObviouslyAwesome.pdf');
672
+ const pdf = await PDFDancer.open(pdfData, token, baseUrl);
673
+
674
+ const para = await pdf.page(0).selectParagraphAt(54, 496, 10);
675
+ expect(para).not.toBeNull();
676
+ expect(para!.internalId).toBe('PARAGRAPH_000004');
677
+
678
+ // Test with no match
679
+ const noMatch = await pdf.page(0).selectParagraphAt(1000, 1000, 1);
680
+ expect(noMatch).toBeNull();
681
+ });
682
+
627
683
  });
@@ -76,4 +76,33 @@ describe('Path E2E Tests (New API)', () => {
76
76
  const assertions = await PDFAssertions.create(pdf);
77
77
  await assertions.assertPathIsAt('PATH_000001', 50.1, 100);
78
78
  });
79
+
80
+ // Tests for singular select methods
81
+ test('selectPath returns first path or null', async () => {
82
+ const [baseUrl, token, pdfData] = await requireEnvAndFixture('basic-paths.pdf');
83
+ const pdf = await PDFDancer.open(pdfData, token, baseUrl);
84
+
85
+ // Test with PDFDancer class (document-level)
86
+ const pathFromPdf = await pdf.selectPath();
87
+ expect(pathFromPdf).not.toBeNull();
88
+ expect(pathFromPdf!.internalId).toBe('PATH_000001');
89
+
90
+ // Test with page-level using position since paths may require coordinates
91
+ const pathOnPage = await pdf.page(0).selectPathAt(80, 720);
92
+ expect(pathOnPage).not.toBeNull();
93
+ expect(pathOnPage!.internalId).toBe('PATH_000001');
94
+ });
95
+
96
+ test('selectPathAt returns first path at position or null', async () => {
97
+ const [baseUrl, token, pdfData] = await requireEnvAndFixture('basic-paths.pdf');
98
+ const pdf = await PDFDancer.open(pdfData, token, baseUrl);
99
+
100
+ const path = await pdf.page(0).selectPathAt(80, 720);
101
+ expect(path).not.toBeNull();
102
+ expect(path!.internalId).toBe('PATH_000001');
103
+
104
+ // Test with no match
105
+ const noMatch = await pdf.page(0).selectPathAt(1000, 1000);
106
+ expect(noMatch).toBeNull();
107
+ });
79
108
  });
@@ -4,12 +4,13 @@
4
4
 
5
5
  import * as fs from 'fs';
6
6
  import * as path from 'path';
7
+ import os from "os";
7
8
 
8
9
  /**
9
10
  * Get the base URL from environment variable or default
10
11
  */
11
12
  export function getBaseUrl(): string {
12
- return process.env.PDFDANCER_BASE_URL || 'http://localhost:8080';
13
+ return (process.env.PDFDANCER_BASE_URL || 'http://localhost:8080').trim();
13
14
  }
14
15
 
15
16
  /**
@@ -102,7 +103,12 @@ export async function requireEnvAndFixture(pdfFilename: string): Promise<[string
102
103
  * Helper to open temporary file path (for Node.js environment)
103
104
  */
104
105
  export function createTempPath(filename: string): string {
105
- const tmpDir = process.env.TMPDIR || '/tmp';
106
+ const tmpDir =
107
+ process.env.TMPDIR ||
108
+ process.env.TEMP ||
109
+ process.env.TMP ||
110
+ os.tmpdir(); // reliable built-in fallback
111
+
106
112
  return path.join(tmpDir, filename);
107
113
  }
108
114
 
@@ -3,7 +3,6 @@ import {PDFDancer} from "../../pdfdancer_v1";
3
3
  import {HttpClientException, ValidationException} from "../../exceptions";
4
4
 
5
5
  describe('Env Token E2E Tests', () => {
6
- const MISSING_TOKEN_MESSAGE = "Missing PDFDancer API token. Pass a token via the `token` argument or set the PDFDANCER_TOKEN environment variable.";
7
6
  let originalToken: string | undefined;
8
7
  let originalBaseUrl: string | undefined;
9
8
  let baseUrl: string;