@link-assistant/web-capture 1.12.0 → 2.0.1
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/CHANGELOG.md +17 -0
- package/README.md +2 -1
- package/bin/web-capture.js +5 -7
- package/package.json +32 -18
- package/src/archive.js +1 -2
- package/src/binary-download.js +13 -0
- package/src/docx.js +14 -11
- package/src/fetch.js +1 -2
- package/src/figures.js +2 -12
- package/src/gdocs-images.js +0 -1
- package/src/gdocs.js +0 -1
- package/src/github.js +0 -1
- package/src/lib.js +1 -2
- package/src/localize-images.js +2 -12
- package/src/markdown.js +0 -1
- package/src/shared-dialog.js +0 -1
- package/src/stream.js +0 -1
- package/src/transport.js +23 -5
- package/src/txt.js +0 -1
- package/.changeset/README.md +0 -8
- package/.changeset/config.json +0 -11
- package/.dockerignore +0 -6
- package/.jscpd.json +0 -19
- package/.lenv +0 -8
- package/.prettierignore +0 -8
- package/.prettierrc +0 -10
- package/Dockerfile +0 -52
- package/babel.config.cjs +0 -13
- package/docker-compose.yml +0 -11
- package/eslint.config.js +0 -112
- package/examples/js/capture_cache.js +0 -12
- package/examples/js/engine_comparison.js +0 -39
- package/examples/js/html_download.js +0 -23
- package/examples/js/image_download.js +0 -36
- package/examples/js/markdown_download.js +0 -23
- package/examples/js/playwright_example.js +0 -23
- package/examples/python/html_download.py +0 -15
- package/examples/python/image_download.py +0 -20
- package/examples/python/markdown_download.py +0 -15
- package/examples/python/playwright_example.py +0 -22
- package/jest.config.mjs +0 -20
- package/tests/ci/dependency-freshness.test.js +0 -74
- package/tests/ci/detect-code-changes.test.js +0 -174
- package/tests/ci/docker-install.test.js +0 -31
- package/tests/ci/rust-dockerfile.test.js +0 -54
- package/tests/ci/workflow-policy.test.js +0 -79
- package/tests/e2e/docker.test.js +0 -186
- package/tests/e2e/process.test.js +0 -215
- package/tests/fixtures/bold-coalesce-and-leakage.html +0 -27
- package/tests/fixtures/deepwiki/deepwiki-full-page.png +0 -0
- package/tests/fixtures/deepwiki/index.md +0 -1500
- package/tests/fixtures/deepwiki/part-1.md +0 -776
- package/tests/fixtures/habr-articles.js +0 -45
- package/tests/fixtures/heading-numbering.html +0 -19
- package/tests/fixtures/list-item-with-br.html +0 -8
- package/tests/fixtures/ol-continuous-numbering.html +0 -12
- package/tests/fixtures/paragraph-vs-line-break.html +0 -11
- package/tests/helpers/nock.js +0 -10
- package/tests/helpers/search-fixture.cjs +0 -17
- package/tests/helpers/temporary-directory.js +0 -12
- package/tests/integration/api-endpoints.test.js +0 -418
- package/tests/integration/archive-default-layout.test.js +0 -37
- package/tests/integration/browser-engines.test.js +0 -128
- package/tests/integration/browser-packaged-path.test.js +0 -45
- package/tests/integration/deepwiki-download.test.js +0 -144
- package/tests/integration/deepwiki-rendered-page.mock.test.js +0 -121
- package/tests/integration/formalai-contract.test.js +0 -214
- package/tests/integration/gdocs-public-doc.test.js +0 -315
- package/tests/integration/github-readme.test.js +0 -137
- package/tests/integration/google-drive-image.test.js +0 -246
- package/tests/integration/habr-article.test.js +0 -337
- package/tests/integration/search-endpoint.test.js +0 -88
- package/tests/integration/stackoverflow-download.test.js +0 -173
- package/tests/integration/wikipedia-download.test.js +0 -142
- package/tests/jest.setup.mjs +0 -7
- package/tests/mock/index.test.js +0 -225
- package/tests/unit/animation.test.js +0 -33
- package/tests/unit/batch.test.js +0 -114
- package/tests/unit/bold-coalesce-and-leakage.test.js +0 -54
- package/tests/unit/browser-launch-options.test.js +0 -42
- package/tests/unit/browser.test.js +0 -172
- package/tests/unit/cache.test.js +0 -182
- package/tests/unit/cli.test.js +0 -399
- package/tests/unit/convertGoogleDriveUrl.test.js +0 -89
- package/tests/unit/convertRelativeUrls.test.js +0 -34
- package/tests/unit/extract-images.test.js +0 -259
- package/tests/unit/fetch-html.test.js +0 -54
- package/tests/unit/figures.test.js +0 -74
- package/tests/unit/gdocs-export-headers.test.js +0 -28
- package/tests/unit/gdocs-preprocess.test.js +0 -36
- package/tests/unit/gdocs.test.js +0 -1490
- package/tests/unit/github.test.js +0 -156
- package/tests/unit/heading-image.test.js +0 -49
- package/tests/unit/heading-numbering.test.js +0 -28
- package/tests/unit/html2md-br-in-list-item.test.js +0 -23
- package/tests/unit/html2md-ol-numbering.test.js +0 -20
- package/tests/unit/html2md.test.js +0 -695
- package/tests/unit/image-mode-defaults.test.js +0 -54
- package/tests/unit/kreuzberg.test.js +0 -249
- package/tests/unit/latex.test.js +0 -123
- package/tests/unit/localize-images.test.js +0 -114
- package/tests/unit/markdown.test.js +0 -36
- package/tests/unit/metadata.test.js +0 -140
- package/tests/unit/package-runtime-dependencies.test.js +0 -74
- package/tests/unit/paragraph-vs-line-break.test.js +0 -56
- package/tests/unit/postprocess.test.js +0 -110
- package/tests/unit/proxy-headers.test.js +0 -100
- package/tests/unit/publish-verification.test.js +0 -57
- package/tests/unit/retry.test.js +0 -111
- package/tests/unit/search.test.js +0 -339
- package/tests/unit/shared-dialog.test.js +0 -173
- package/tests/unit/stackoverflow.test.js +0 -32
- package/tests/unit/stream.test.js +0 -85
- package/tests/unit/verify.test.js +0 -182
- package/tests/xpaste/xpaste-screenshot-markdown.mock.test.js +0 -375
- package/tests/xpaste/xpaste.e2e.test.js +0 -36
- package/tests/xpaste/xpaste.integration.test.js +0 -41
- package/tests/xpaste/xpaste.mock.test.js +0 -261
- package/tests/xpaste/xpaste.unit.test.js +0 -119
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
import request from 'supertest';
|
|
2
|
-
import nock from '../helpers/nock.js';
|
|
3
|
-
import { jest } from '@jest/globals';
|
|
4
|
-
import unzipper from 'unzipper';
|
|
5
|
-
|
|
6
|
-
let app;
|
|
7
|
-
|
|
8
|
-
beforeAll(async () => {
|
|
9
|
-
app = (await import('../../src/index.js')).app;
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
describe('xpaste.pro integration tests', () => {
|
|
13
|
-
beforeEach(() => {
|
|
14
|
-
nock.cleanAll();
|
|
15
|
-
jest.clearAllMocks();
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
describe('GET /txt with xpaste.pro URL', () => {
|
|
19
|
-
const testUrl = 'https://xpaste.pro/p/t4q0Lsp0';
|
|
20
|
-
// Using actual data from https://xpaste.pro/p/t4q0Lsp0 (captured on 2025-11-14)
|
|
21
|
-
const testText = `# 1
|
|
22
|
-
#
|
|
23
|
-
# Time: 210707 15:39:36
|
|
24
|
-
# User@Host: 1703313381[1703313381] @ [136.243.53.188] Id: 1138102510
|
|
25
|
-
# Schema: xxxxxx Last_errno: 0 Killed: 0
|
|
26
|
-
# Query_time: 2.182754 Lock_time: 0.000120 Rows_sent: 0 Rows_examined: 324036 Rows_affected: 0
|
|
27
|
-
# Bytes_sent: 20494
|
|
28
|
-
SET timestamp=1625661576;
|
|
29
|
-
SELECT f.*, t.*, p.*, u.*, tt.mark_time AS topic_mark_time, ft.mark_time AS forum_mark_time FROM (phpbb_posts p CROSS JOIN phpbb_users u CROSS JOIN phpbb_topics t) LEFT JOIN phpbb_forums f ON (t.forum_id = f.forum_id) LEFT JOIN phpbb_topics_track tt ON (t.topic_id = tt.topic_id AND tt.user_id = 659822) LEFT JOIN phpbb_forums_track ft ON (f.forum_id = ft.forum_id AND ft.user_id = 659822) WHERE p.topic_id = t.topic_id
|
|
30
|
-
AND p.poster_id = u.user_id
|
|
31
|
-
AND p.post_time > 1625660099
|
|
32
|
-
AND p.forum_id = 326
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
AND p.post_approved = 1 ORDER BY t.topic_last_post_time DESC, p.post_time
|
|
37
|
-
LIMIT 100;`;
|
|
38
|
-
|
|
39
|
-
it('should fetch and return text content from xpaste.pro', async () => {
|
|
40
|
-
nock('https://xpaste.pro')
|
|
41
|
-
.get('/p/t4q0Lsp0/raw')
|
|
42
|
-
.reply(200, testText, { 'content-type': 'text/plain; charset=utf-8' });
|
|
43
|
-
|
|
44
|
-
const response = await request(app).get('/txt').query({ url: testUrl });
|
|
45
|
-
|
|
46
|
-
expect(response.status).toBe(200);
|
|
47
|
-
expect(response.type).toBe('text/plain');
|
|
48
|
-
expect(response.text).toBe(testText);
|
|
49
|
-
expect(response.headers['content-disposition']).toContain('attachment');
|
|
50
|
-
expect(response.headers['content-disposition']).toContain('.txt');
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('should return 400 when URL is missing', async () => {
|
|
54
|
-
const response = await request(app).get('/txt');
|
|
55
|
-
|
|
56
|
-
expect(response.status).toBe(400);
|
|
57
|
-
expect(response.text).toBe('Missing `url` parameter');
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('should return 500 when fetch fails', async () => {
|
|
61
|
-
nock('https://xpaste.pro')
|
|
62
|
-
.get('/p/t4q0Lsp0/raw')
|
|
63
|
-
.replyWithError('Network error');
|
|
64
|
-
|
|
65
|
-
const response = await request(app).get('/txt').query({ url: testUrl });
|
|
66
|
-
|
|
67
|
-
expect(response.status).toBe(500);
|
|
68
|
-
expect(response.text).toBe('Error fetching text content');
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
describe('GET /markdown with xpaste.pro URL (small content)', () => {
|
|
73
|
-
const testUrl = 'https://xpaste.pro/p/t4q0Lsp0';
|
|
74
|
-
const rawText = `# 1
|
|
75
|
-
#
|
|
76
|
-
# Time: 210707 15:39:36
|
|
77
|
-
SET timestamp=1625661576;
|
|
78
|
-
SELECT * FROM phpbb_posts WHERE topic_id = 123;`;
|
|
79
|
-
// Mock HTML that resembles actual xpaste.pro page structure
|
|
80
|
-
const testHtml = `<!DOCTYPE html>
|
|
81
|
-
<html>
|
|
82
|
-
<head><title>xPaste</title></head>
|
|
83
|
-
<body>
|
|
84
|
-
<h1>Xpaste</h1>
|
|
85
|
-
<div>
|
|
86
|
-
<strong>Формат:</strong> text<br>
|
|
87
|
-
<strong>Время создания:</strong> 07.07.2021, 12:48 UTC<br>
|
|
88
|
-
<strong>Будет удалена:</strong> 05.07.2031, 12:48 UTC
|
|
89
|
-
</div>
|
|
90
|
-
<pre># 1
|
|
91
|
-
#
|
|
92
|
-
# Time: 210707 15:39:36
|
|
93
|
-
# User@Host: 1703313381[1703313381] @ [136.243.53.188] Id: 1138102510
|
|
94
|
-
# Schema: xxxxxx Last_errno: 0 Killed: 0
|
|
95
|
-
# Query_time: 2.182754 Lock_time: 0.000120 Rows_sent: 0 Rows_examined: 324036 Rows_affected: 0
|
|
96
|
-
SET timestamp=1625661576;
|
|
97
|
-
SELECT * FROM phpbb_posts WHERE topic_id = 123;</pre>
|
|
98
|
-
<footer>
|
|
99
|
-
<a href="https://xpaste.pro/">xPaste</a>
|
|
100
|
-
<p>Упакуем пароль или код в cсылку для передачи</p>
|
|
101
|
-
<p>Сделано в <a href="https://southbridge.io/">Southbridge</a></p>
|
|
102
|
-
</footer>
|
|
103
|
-
</body>
|
|
104
|
-
</html>`;
|
|
105
|
-
|
|
106
|
-
it('should convert HTML to markdown with all page elements when content is less than 1500 lines', async () => {
|
|
107
|
-
nock('https://xpaste.pro')
|
|
108
|
-
.get('/p/t4q0Lsp0')
|
|
109
|
-
.reply(200, testHtml, { 'content-type': 'text/html; charset=utf-8' });
|
|
110
|
-
nock('https://xpaste.pro')
|
|
111
|
-
.get('/p/t4q0Lsp0/raw')
|
|
112
|
-
.reply(200, rawText, { 'content-type': 'text/plain; charset=utf-8' });
|
|
113
|
-
|
|
114
|
-
const response = await request(app)
|
|
115
|
-
.get('/markdown')
|
|
116
|
-
.query({ url: testUrl });
|
|
117
|
-
|
|
118
|
-
expect(response.status).toBe(200);
|
|
119
|
-
expect(response.type).toBe('text/markdown');
|
|
120
|
-
|
|
121
|
-
// Verify it includes page metadata (visible in screenshot)
|
|
122
|
-
expect(response.text).toContain('Xpaste');
|
|
123
|
-
expect(response.text).toContain('Формат:');
|
|
124
|
-
expect(response.text).toContain('text');
|
|
125
|
-
expect(response.text).toContain('07.07.2021');
|
|
126
|
-
|
|
127
|
-
// Verify it includes the SQL query content
|
|
128
|
-
expect(response.text).toContain('1703313381');
|
|
129
|
-
// Note: markdown escapes special characters like * and _
|
|
130
|
-
expect(response.text).toMatch(/SELECT.*FROM phpbb.*posts/);
|
|
131
|
-
|
|
132
|
-
// Verify it includes footer elements (visible in screenshot)
|
|
133
|
-
expect(response.text).toContain('Southbridge');
|
|
134
|
-
expect(response.text).toContain('Упакуем пароль или код');
|
|
135
|
-
|
|
136
|
-
// Verify the raw text file is embedded as a named markdown block.
|
|
137
|
-
expect(response.text).toContain('## xpaste-pro-t4q0Lsp0.txt');
|
|
138
|
-
expect(response.text).toContain('```text\n# 1\n#');
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it('should normalize raw xpaste URLs before markdown conversion', async () => {
|
|
142
|
-
nock('https://xpaste.pro')
|
|
143
|
-
.get('/p/t4q0Lsp0')
|
|
144
|
-
.reply(200, testHtml, { 'content-type': 'text/html; charset=utf-8' });
|
|
145
|
-
nock('https://xpaste.pro')
|
|
146
|
-
.get('/p/t4q0Lsp0/raw')
|
|
147
|
-
.reply(200, rawText, { 'content-type': 'text/plain; charset=utf-8' });
|
|
148
|
-
|
|
149
|
-
const response = await request(app)
|
|
150
|
-
.get('/markdown')
|
|
151
|
-
.query({ url: `${testUrl}/raw` });
|
|
152
|
-
|
|
153
|
-
expect(response.status).toBe(200);
|
|
154
|
-
expect(response.type).toBe('text/markdown');
|
|
155
|
-
expect(response.text).toContain('Формат:');
|
|
156
|
-
expect(response.text).toContain('## xpaste-pro-t4q0Lsp0.txt');
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
describe('GET /markdown with xpaste.pro URL (large content)', () => {
|
|
161
|
-
const testUrl = 'https://xpaste.pro/p/largefile';
|
|
162
|
-
|
|
163
|
-
it('should create a zip archive when content is 1500 lines or more', async () => {
|
|
164
|
-
// Create HTML content that will result in exactly 1500 lines of markdown
|
|
165
|
-
const largeContent = Array(1480).fill('<p>Line of text</p>').join('\n');
|
|
166
|
-
const largeHtml = `<!DOCTYPE html>
|
|
167
|
-
<html><head><title>Large File</title></head><body>${largeContent}</body></html>`;
|
|
168
|
-
|
|
169
|
-
nock('https://xpaste.pro')
|
|
170
|
-
.get('/p/largefile')
|
|
171
|
-
.reply(200, largeHtml, { 'content-type': 'text/html; charset=utf-8' });
|
|
172
|
-
nock('https://xpaste.pro')
|
|
173
|
-
.get('/p/largefile/raw')
|
|
174
|
-
.reply(200, largeContent.replace(/<\/?p>/g, ''), {
|
|
175
|
-
'content-type': 'text/plain; charset=utf-8',
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
const response = await request(app)
|
|
179
|
-
.get('/markdown')
|
|
180
|
-
.query({ url: testUrl })
|
|
181
|
-
.buffer(true)
|
|
182
|
-
.parse((res, callback) => {
|
|
183
|
-
const chunks = [];
|
|
184
|
-
res.on('data', (chunk) => chunks.push(chunk));
|
|
185
|
-
res.on('end', () => callback(null, Buffer.concat(chunks)));
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
expect(response.status).toBe(200);
|
|
189
|
-
expect(response.type).toBe('application/zip');
|
|
190
|
-
expect(response.headers['content-disposition']).toContain('attachment');
|
|
191
|
-
expect(response.headers['content-disposition']).toContain(
|
|
192
|
-
'largefile.zip'
|
|
193
|
-
);
|
|
194
|
-
|
|
195
|
-
const dir = await unzipper.Open.buffer(response.body);
|
|
196
|
-
const names = dir.files.map((file) => file.path);
|
|
197
|
-
expect(names).toEqual(
|
|
198
|
-
expect.arrayContaining([
|
|
199
|
-
'index.md',
|
|
200
|
-
'xpaste-pro-largefile.md',
|
|
201
|
-
'xpaste-pro-largefile.txt',
|
|
202
|
-
])
|
|
203
|
-
);
|
|
204
|
-
|
|
205
|
-
const index = await dir.files
|
|
206
|
-
.find((file) => file.path === 'index.md')
|
|
207
|
-
.buffer();
|
|
208
|
-
const rawTextFile = await dir.files
|
|
209
|
-
.find((file) => file.path === 'xpaste-pro-largefile.txt')
|
|
210
|
-
.buffer();
|
|
211
|
-
expect(index.toString()).toContain(
|
|
212
|
-
'[xpaste-pro-largefile.txt](xpaste-pro-largefile.txt)'
|
|
213
|
-
);
|
|
214
|
-
expect(rawTextFile.toString()).toContain('Line of text');
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
it('should create a zip archive for content with more than 1500 lines', async () => {
|
|
218
|
-
// Create HTML content that will result in more than 1500 lines of markdown
|
|
219
|
-
const largeContent = Array(1600).fill('<p>Line of text</p>').join('\n');
|
|
220
|
-
const largeHtml = `<!DOCTYPE html>
|
|
221
|
-
<html><head><title>Large File</title></head><body>${largeContent}</body></html>`;
|
|
222
|
-
|
|
223
|
-
nock('https://xpaste.pro')
|
|
224
|
-
.get('/p/largefile')
|
|
225
|
-
.reply(200, largeHtml, { 'content-type': 'text/html; charset=utf-8' });
|
|
226
|
-
nock('https://xpaste.pro')
|
|
227
|
-
.get('/p/largefile/raw')
|
|
228
|
-
.reply(200, largeContent.replace(/<\/?p>/g, ''), {
|
|
229
|
-
'content-type': 'text/plain; charset=utf-8',
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
const response = await request(app)
|
|
233
|
-
.get('/markdown')
|
|
234
|
-
.query({ url: testUrl });
|
|
235
|
-
|
|
236
|
-
expect(response.status).toBe(200);
|
|
237
|
-
expect(response.type).toBe('application/zip');
|
|
238
|
-
});
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
describe('GET /markdown with non-xpaste.pro URL', () => {
|
|
242
|
-
const testUrl = 'https://example.com';
|
|
243
|
-
const testHtml =
|
|
244
|
-
'<html><body><h1>Test Page</h1><p>Regular HTML content</p></body></html>';
|
|
245
|
-
|
|
246
|
-
it('should process regular HTML URLs normally', async () => {
|
|
247
|
-
nock(testUrl).get('/').reply(200, testHtml);
|
|
248
|
-
|
|
249
|
-
const response = await request(app)
|
|
250
|
-
.get('/markdown')
|
|
251
|
-
.query({ url: testUrl });
|
|
252
|
-
|
|
253
|
-
expect(response.status).toBe(200);
|
|
254
|
-
expect(response.type).toBe('text/markdown');
|
|
255
|
-
expect(response.text).toContain('Test Page');
|
|
256
|
-
expect(response.text).toContain('Regular HTML content');
|
|
257
|
-
// Should not be a zip file
|
|
258
|
-
expect(response.headers['content-type']).not.toBe('application/zip');
|
|
259
|
-
});
|
|
260
|
-
});
|
|
261
|
-
});
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
appendTextPasteMarkdownAttachment,
|
|
3
|
-
getTextPasteFilename,
|
|
4
|
-
isTextPasteUrl,
|
|
5
|
-
normalizeUrlForTextContent,
|
|
6
|
-
normalizeUrlForTextPage,
|
|
7
|
-
} from '../../src/lib.js';
|
|
8
|
-
|
|
9
|
-
describe('xpaste.pro URL handling utilities', () => {
|
|
10
|
-
describe('normalizeUrlForTextContent', () => {
|
|
11
|
-
it('should convert xpaste.pro URL to raw endpoint', () => {
|
|
12
|
-
const url = 'https://xpaste.pro/p/t4q0Lsp0';
|
|
13
|
-
const normalized = normalizeUrlForTextContent(url);
|
|
14
|
-
expect(normalized).toBe('https://xpaste.pro/p/t4q0Lsp0/raw');
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('should convert localized xpaste.pro URLs to localized raw endpoints', () => {
|
|
18
|
-
expect(
|
|
19
|
-
normalizeUrlForTextContent('https://xpaste.pro/ru/p/t4q0Lsp0')
|
|
20
|
-
).toBe('https://xpaste.pro/ru/p/t4q0Lsp0/raw');
|
|
21
|
-
expect(
|
|
22
|
-
normalizeUrlForTextContent('https://xpaste.pro/en/p/t4q0Lsp0')
|
|
23
|
-
).toBe('https://xpaste.pro/en/p/t4q0Lsp0/raw');
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('should not modify xpaste.pro URL that already has /raw', () => {
|
|
27
|
-
const url = 'https://xpaste.pro/p/t4q0Lsp0/raw';
|
|
28
|
-
const normalized = normalizeUrlForTextContent(url);
|
|
29
|
-
expect(normalized).toBe('https://xpaste.pro/p/t4q0Lsp0/raw');
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it('should not modify non-xpaste.pro URLs', () => {
|
|
33
|
-
const url = 'https://example.com/page';
|
|
34
|
-
const normalized = normalizeUrlForTextContent(url);
|
|
35
|
-
expect(normalized).toBe(url);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('should handle invalid URLs gracefully', () => {
|
|
39
|
-
const url = 'not-a-valid-url';
|
|
40
|
-
const normalized = normalizeUrlForTextContent(url);
|
|
41
|
-
expect(normalized).toBe(url);
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
describe('normalizeUrlForTextPage', () => {
|
|
46
|
-
it('should convert xpaste.pro raw URLs back to visual page URLs', () => {
|
|
47
|
-
expect(normalizeUrlForTextPage('https://xpaste.pro/p/t4q0Lsp0/raw')).toBe(
|
|
48
|
-
'https://xpaste.pro/p/t4q0Lsp0'
|
|
49
|
-
);
|
|
50
|
-
expect(
|
|
51
|
-
normalizeUrlForTextPage('https://xpaste.pro/ru/p/t4q0Lsp0/raw')
|
|
52
|
-
).toBe('https://xpaste.pro/ru/p/t4q0Lsp0');
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('should not modify non-xpaste.pro URLs', () => {
|
|
56
|
-
const url = 'https://example.com/page';
|
|
57
|
-
expect(normalizeUrlForTextPage(url)).toBe(url);
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
describe('isTextPasteUrl', () => {
|
|
62
|
-
it('should return true for xpaste.pro paste URLs', () => {
|
|
63
|
-
expect(isTextPasteUrl('https://xpaste.pro/p/t4q0Lsp0')).toBe(true);
|
|
64
|
-
expect(isTextPasteUrl('https://xpaste.pro/p/abc123')).toBe(true);
|
|
65
|
-
expect(isTextPasteUrl('https://xpaste.pro/ru/p/t4q0Lsp0')).toBe(true);
|
|
66
|
-
expect(isTextPasteUrl('https://xpaste.pro/en/p/t4q0Lsp0')).toBe(true);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('should return false for non-xpaste.pro URLs', () => {
|
|
70
|
-
expect(isTextPasteUrl('https://example.com')).toBe(false);
|
|
71
|
-
expect(isTextPasteUrl('https://pastebin.com/xyz')).toBe(false);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('should return false for xpaste.pro URLs without /p/ path', () => {
|
|
75
|
-
expect(isTextPasteUrl('https://xpaste.pro')).toBe(false);
|
|
76
|
-
expect(isTextPasteUrl('https://xpaste.pro/about')).toBe(false);
|
|
77
|
-
expect(isTextPasteUrl('https://xpaste.pro/foo/p/t4q0Lsp0')).toBe(false);
|
|
78
|
-
expect(isTextPasteUrl('https://xpaste.pro/p/t4q0Lsp0/raw/extra')).toBe(
|
|
79
|
-
false
|
|
80
|
-
);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('should return false for invalid URLs', () => {
|
|
84
|
-
expect(isTextPasteUrl('not-a-url')).toBe(false);
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
describe('getTextPasteFilename', () => {
|
|
89
|
-
it('should derive a canonical text filename for paste URLs', () => {
|
|
90
|
-
expect(getTextPasteFilename('https://xpaste.pro/p/t4q0Lsp0')).toBe(
|
|
91
|
-
'xpaste-pro-t4q0Lsp0.txt'
|
|
92
|
-
);
|
|
93
|
-
expect(getTextPasteFilename('https://xpaste.pro/ru/p/t4q0Lsp0/raw')).toBe(
|
|
94
|
-
'xpaste-pro-t4q0Lsp0.txt'
|
|
95
|
-
);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('should return null for non-paste URLs', () => {
|
|
99
|
-
expect(getTextPasteFilename('https://example.com/page')).toBeNull();
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
describe('appendTextPasteMarkdownAttachment', () => {
|
|
104
|
-
it('should embed raw text as a named fenced attachment block', () => {
|
|
105
|
-
const markdown = '# Page\n\nVisible content';
|
|
106
|
-
const rawText = 'first line\n```inside paste```\nlast line';
|
|
107
|
-
const result = appendTextPasteMarkdownAttachment(
|
|
108
|
-
markdown,
|
|
109
|
-
'https://xpaste.pro/p/t4q0Lsp0',
|
|
110
|
-
rawText
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
expect(result).toContain('## xpaste-pro-t4q0Lsp0.txt');
|
|
114
|
-
expect(result).toContain(
|
|
115
|
-
'````text\nfirst line\n```inside paste```\nlast line\n````'
|
|
116
|
-
);
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
});
|