@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,182 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
normalizeText,
|
|
3
|
-
normalizeCode,
|
|
4
|
-
verifyMarkdownContent,
|
|
5
|
-
} from '../../src/verify.js';
|
|
6
|
-
|
|
7
|
-
describe('verify module', () => {
|
|
8
|
-
describe('normalizeText', () => {
|
|
9
|
-
it('lowercases text', () => {
|
|
10
|
-
expect(normalizeText('Hello World')).toBe('hello world');
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it('normalizes unicode spaces', () => {
|
|
14
|
-
expect(normalizeText('hello\u00A0world')).toBe('hello world');
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('normalizes curly quotes', () => {
|
|
18
|
-
expect(normalizeText('\u201Chello\u201D')).toBe('"hello"');
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('removes LaTeX delimiters', () => {
|
|
22
|
-
expect(normalizeText('$x + y$')).toBe('x + y');
|
|
23
|
-
expect(normalizeText('$$x + y$$')).toBe('x + y');
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('normalizes arrows', () => {
|
|
27
|
-
expect(normalizeText('\u2192')).toBe('->');
|
|
28
|
-
expect(normalizeText('\\to')).toBe('->');
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('removes \\displaystyle', () => {
|
|
32
|
-
expect(normalizeText('\\displaystyle x')).toBe('x');
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('extracts text from \\text{}', () => {
|
|
36
|
-
expect(normalizeText('\\text{hello}')).toBe('hello');
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
describe('normalizeCode', () => {
|
|
41
|
-
it('lowercases and collapses whitespace', () => {
|
|
42
|
-
expect(normalizeCode(' Hello World ')).toBe('hello world');
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('normalizes multiplication sign', () => {
|
|
46
|
-
expect(normalizeCode('2\u00D73')).toBe('2x3');
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
describe('verifyMarkdownContent', () => {
|
|
51
|
-
it('verifies title presence', () => {
|
|
52
|
-
const webContent = {
|
|
53
|
-
title: 'My Article',
|
|
54
|
-
headings: [],
|
|
55
|
-
paragraphs: [],
|
|
56
|
-
codeBlocks: [],
|
|
57
|
-
formulas: [],
|
|
58
|
-
blockquoteFormulas: [],
|
|
59
|
-
listItems: [],
|
|
60
|
-
links: [],
|
|
61
|
-
figures: [],
|
|
62
|
-
};
|
|
63
|
-
const markdown = '# My Article\n\nSome content here.';
|
|
64
|
-
const result = verifyMarkdownContent(webContent, markdown);
|
|
65
|
-
expect(result.missing.title).toBe(false);
|
|
66
|
-
expect(result.passedChecks).toBeGreaterThan(0);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('detects missing title', () => {
|
|
70
|
-
const webContent = {
|
|
71
|
-
title: 'Missing Title',
|
|
72
|
-
headings: [],
|
|
73
|
-
paragraphs: [],
|
|
74
|
-
codeBlocks: [],
|
|
75
|
-
formulas: [],
|
|
76
|
-
blockquoteFormulas: [],
|
|
77
|
-
listItems: [],
|
|
78
|
-
links: [],
|
|
79
|
-
figures: [],
|
|
80
|
-
};
|
|
81
|
-
const markdown = '# Different Title\n\nContent.';
|
|
82
|
-
const result = verifyMarkdownContent(webContent, markdown);
|
|
83
|
-
expect(result.missing.title).toBe(true);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it('verifies headings', () => {
|
|
87
|
-
const webContent = {
|
|
88
|
-
title: '',
|
|
89
|
-
headings: [
|
|
90
|
-
{ level: 'h2', text: 'Introduction' },
|
|
91
|
-
{ level: 'h3', text: 'Background' },
|
|
92
|
-
],
|
|
93
|
-
paragraphs: [],
|
|
94
|
-
codeBlocks: [],
|
|
95
|
-
formulas: [],
|
|
96
|
-
blockquoteFormulas: [],
|
|
97
|
-
listItems: [],
|
|
98
|
-
links: [],
|
|
99
|
-
figures: [],
|
|
100
|
-
};
|
|
101
|
-
const markdown = '## Introduction\n\n### Background\n\nText.';
|
|
102
|
-
const result = verifyMarkdownContent(webContent, markdown);
|
|
103
|
-
expect(result.missing.headings).toHaveLength(0);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('detects missing headings', () => {
|
|
107
|
-
const webContent = {
|
|
108
|
-
title: '',
|
|
109
|
-
headings: [{ level: 'h2', text: 'Unique Heading XYZ' }],
|
|
110
|
-
paragraphs: [],
|
|
111
|
-
codeBlocks: [],
|
|
112
|
-
formulas: [],
|
|
113
|
-
blockquoteFormulas: [],
|
|
114
|
-
listItems: [],
|
|
115
|
-
links: [],
|
|
116
|
-
figures: [],
|
|
117
|
-
};
|
|
118
|
-
const markdown = '## Different Heading\n\nText.';
|
|
119
|
-
const result = verifyMarkdownContent(webContent, markdown);
|
|
120
|
-
expect(result.missing.headings).toContain('Unique Heading XYZ');
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it('verifies code blocks with fuzzy matching', () => {
|
|
124
|
-
const webContent = {
|
|
125
|
-
title: '',
|
|
126
|
-
headings: [],
|
|
127
|
-
paragraphs: [],
|
|
128
|
-
codeBlocks: [
|
|
129
|
-
'function hello() {\n console.log("hello");\n return true;\n}',
|
|
130
|
-
],
|
|
131
|
-
formulas: [],
|
|
132
|
-
blockquoteFormulas: [],
|
|
133
|
-
listItems: [],
|
|
134
|
-
links: [],
|
|
135
|
-
figures: [],
|
|
136
|
-
};
|
|
137
|
-
const markdown =
|
|
138
|
-
'```javascript\nfunction hello() {\n console.log("hello");\n return true;\n}\n```';
|
|
139
|
-
const result = verifyMarkdownContent(webContent, markdown);
|
|
140
|
-
expect(result.missing.codeBlocks).toHaveLength(0);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it('calculates pass rate', () => {
|
|
144
|
-
const webContent = {
|
|
145
|
-
title: 'Test',
|
|
146
|
-
headings: [{ level: 'h2', text: 'Found' }],
|
|
147
|
-
paragraphs: [],
|
|
148
|
-
codeBlocks: [],
|
|
149
|
-
formulas: [],
|
|
150
|
-
blockquoteFormulas: [],
|
|
151
|
-
listItems: [],
|
|
152
|
-
links: [],
|
|
153
|
-
figures: [],
|
|
154
|
-
};
|
|
155
|
-
const markdown = '# Test\n\n## Found\n\nContent.';
|
|
156
|
-
const result = verifyMarkdownContent(webContent, markdown);
|
|
157
|
-
expect(result.passRate).toBe(1.0);
|
|
158
|
-
expect(result.success).toBe(true);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it('checks figure images when hasLocalImages is set', () => {
|
|
162
|
-
const webContent = {
|
|
163
|
-
title: '',
|
|
164
|
-
headings: [],
|
|
165
|
-
paragraphs: [],
|
|
166
|
-
codeBlocks: [],
|
|
167
|
-
formulas: [],
|
|
168
|
-
blockquoteFormulas: [],
|
|
169
|
-
listItems: [],
|
|
170
|
-
links: [],
|
|
171
|
-
figures: [1, 2, 3],
|
|
172
|
-
};
|
|
173
|
-
const markdown =
|
|
174
|
-
'\n\n';
|
|
175
|
-
const result = verifyMarkdownContent(webContent, markdown, {
|
|
176
|
-
hasLocalImages: true,
|
|
177
|
-
expectedFigures: 3,
|
|
178
|
-
});
|
|
179
|
-
expect(result.missing.images).toBe(0);
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
});
|
|
@@ -1,375 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Integration tests to verify that markdown extraction from xpaste.pro
|
|
3
|
-
* includes ALL text visible in screenshots of the same page
|
|
4
|
-
*
|
|
5
|
-
* This ensures 100% coverage of visual content extraction as required in issue #15
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import request from 'supertest';
|
|
9
|
-
import nock from '../helpers/nock.js';
|
|
10
|
-
import fs from 'fs';
|
|
11
|
-
import path from 'path';
|
|
12
|
-
import { fileURLToPath } from 'url';
|
|
13
|
-
|
|
14
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
-
|
|
16
|
-
let app;
|
|
17
|
-
|
|
18
|
-
beforeAll(async () => {
|
|
19
|
-
app = (await import('../../src/index.js')).app;
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
describe('xpaste.pro screenshot-to-markdown content matching', () => {
|
|
23
|
-
beforeEach(() => {
|
|
24
|
-
nock.cleanAll();
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
describe('GET /markdown for t4q0Lsp0', () => {
|
|
28
|
-
const testUrl = 'https://xpaste.pro/p/t4q0Lsp0';
|
|
29
|
-
|
|
30
|
-
// Load the actual HTML page content that was saved
|
|
31
|
-
const htmlPath = path.join(
|
|
32
|
-
__dirname,
|
|
33
|
-
'../../../tests/xpaste/data/t4q0Lsp0-page.html'
|
|
34
|
-
);
|
|
35
|
-
const rawTextPath = path.join(
|
|
36
|
-
__dirname,
|
|
37
|
-
'../../../tests/xpaste/data/t4q0Lsp0-actual-content.txt'
|
|
38
|
-
);
|
|
39
|
-
let testHtml;
|
|
40
|
-
let rawText;
|
|
41
|
-
|
|
42
|
-
beforeAll(() => {
|
|
43
|
-
if (fs.existsSync(htmlPath)) {
|
|
44
|
-
testHtml = fs.readFileSync(htmlPath, 'utf-8');
|
|
45
|
-
}
|
|
46
|
-
if (fs.existsSync(rawTextPath)) {
|
|
47
|
-
rawText = fs.readFileSync(rawTextPath, 'utf-8');
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
beforeEach(() => {
|
|
52
|
-
if (rawText) {
|
|
53
|
-
nock('https://xpaste.pro')
|
|
54
|
-
.persist()
|
|
55
|
-
.get('/p/t4q0Lsp0/raw')
|
|
56
|
-
.reply(200, rawText, {
|
|
57
|
-
'content-type': 'text/plain; charset=utf-8',
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('should include all header/metadata text visible in screenshot', async () => {
|
|
63
|
-
if (!testHtml) {
|
|
64
|
-
console.warn('Skipping test - HTML file not found');
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
nock('https://xpaste.pro')
|
|
69
|
-
.get('/p/t4q0Lsp0')
|
|
70
|
-
.reply(200, testHtml, { 'content-type': 'text/html; charset=utf-8' });
|
|
71
|
-
|
|
72
|
-
const response = await request(app)
|
|
73
|
-
.get('/markdown')
|
|
74
|
-
.query({ url: testUrl });
|
|
75
|
-
|
|
76
|
-
expect(response.status).toBe(200);
|
|
77
|
-
expect(response.type).toBe('text/markdown');
|
|
78
|
-
|
|
79
|
-
// Verify page title/branding (top of screenshot)
|
|
80
|
-
expect(response.text).toContain('Xpaste');
|
|
81
|
-
expect(response.text).toContain('xPaste');
|
|
82
|
-
|
|
83
|
-
// Verify metadata section (visible in screenshot)
|
|
84
|
-
expect(response.text).toContain('Формат');
|
|
85
|
-
expect(response.text).toContain('text');
|
|
86
|
-
expect(response.text).toContain('Время создания');
|
|
87
|
-
expect(response.text).toContain('07.07.2021');
|
|
88
|
-
expect(response.text).toContain('12:48 UTC');
|
|
89
|
-
expect(response.text).toContain('Будет удалена');
|
|
90
|
-
expect(response.text).toContain('05.07.2031');
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it('should include all SQL query content visible in screenshot', async () => {
|
|
94
|
-
if (!testHtml) {
|
|
95
|
-
console.warn('Skipping test - HTML file not found');
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
nock('https://xpaste.pro')
|
|
100
|
-
.get('/p/t4q0Lsp0')
|
|
101
|
-
.reply(200, testHtml, { 'content-type': 'text/html; charset=utf-8' });
|
|
102
|
-
|
|
103
|
-
const response = await request(app)
|
|
104
|
-
.get('/markdown')
|
|
105
|
-
.query({ url: testUrl });
|
|
106
|
-
|
|
107
|
-
expect(response.status).toBe(200);
|
|
108
|
-
|
|
109
|
-
// Verify all 4 SQL queries visible in screenshot are present
|
|
110
|
-
|
|
111
|
-
// Query #1 metadata (lines 1-8 in screenshot)
|
|
112
|
-
expect(response.text).toContain('# 1');
|
|
113
|
-
expect(response.text).toContain('Time: 210707 15:39:36');
|
|
114
|
-
expect(response.text).toMatch(
|
|
115
|
-
/User@Host:\s+1703313381\[1703313381\]\s+@\s+\[136\.243\.53\.188\]/
|
|
116
|
-
);
|
|
117
|
-
expect(response.text).toContain('Id: 1138102510');
|
|
118
|
-
expect(response.text).toContain('Schema: xxxxxx');
|
|
119
|
-
expect(response.text).toContain('Query_time: 2.182754');
|
|
120
|
-
expect(response.text).toContain('Rows_examined: 324036');
|
|
121
|
-
|
|
122
|
-
// Query #2 metadata (lines 19-28 in screenshot)
|
|
123
|
-
expect(response.text).toContain('# 2');
|
|
124
|
-
expect(response.text).toContain('Time: 210707 15:38:11');
|
|
125
|
-
expect(response.text).toContain('Id: 1138100591');
|
|
126
|
-
expect(response.text).toContain('Query_time: 9.259469');
|
|
127
|
-
expect(response.text).toContain('DELETE FROM phpbb_post_revisions');
|
|
128
|
-
expect(response.text).toContain('WHERE post_id = 10472158');
|
|
129
|
-
|
|
130
|
-
// Query #3 metadata (lines 30-46 in screenshot)
|
|
131
|
-
expect(response.text).toContain('# 3');
|
|
132
|
-
expect(response.text).toContain('Time: 210707 15:32:18');
|
|
133
|
-
expect(response.text).toContain('Id: 1138094492');
|
|
134
|
-
expect(response.text).toContain('Query_time: 1.113014');
|
|
135
|
-
expect(response.text).toContain('Rows_examined: 521597');
|
|
136
|
-
|
|
137
|
-
// Query #4 metadata (lines 48-64 in screenshot)
|
|
138
|
-
expect(response.text).toContain('# 4');
|
|
139
|
-
expect(response.text).toContain('Time: 210707 15:27:56');
|
|
140
|
-
expect(response.text).toContain('Id: 1138089558');
|
|
141
|
-
expect(response.text).toContain('Query_time: 4.682828');
|
|
142
|
-
expect(response.text).toContain('Rows_examined: 2457916');
|
|
143
|
-
|
|
144
|
-
// Verify SQL statements contain key elements
|
|
145
|
-
expect(response.text).toContain('SELECT f.*, t.*, p.*, u.*');
|
|
146
|
-
expect(response.text).toContain('phpbb_posts');
|
|
147
|
-
expect(response.text).toContain('phpbb_users');
|
|
148
|
-
expect(response.text).toContain('phpbb_topics');
|
|
149
|
-
expect(response.text).toContain('phpbb_forums');
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it('should include all footer text visible in screenshot', async () => {
|
|
153
|
-
if (!testHtml) {
|
|
154
|
-
console.warn('Skipping test - HTML file not found');
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
nock('https://xpaste.pro')
|
|
159
|
-
.get('/p/t4q0Lsp0')
|
|
160
|
-
.reply(200, testHtml, { 'content-type': 'text/html; charset=utf-8' });
|
|
161
|
-
|
|
162
|
-
const response = await request(app)
|
|
163
|
-
.get('/markdown')
|
|
164
|
-
.query({ url: testUrl });
|
|
165
|
-
|
|
166
|
-
expect(response.status).toBe(200);
|
|
167
|
-
|
|
168
|
-
// Verify footer elements (bottom of screenshot)
|
|
169
|
-
expect(response.text).toContain('xPaste');
|
|
170
|
-
expect(response.text).toContain('Упакуем пароль или код в');
|
|
171
|
-
expect(response.text).toMatch(/cсылку для передачи/); // Note: 'c' can be different Unicode char
|
|
172
|
-
expect(response.text).toContain('Сделано в');
|
|
173
|
-
expect(response.text).toContain('Southbridge');
|
|
174
|
-
expect(response.text).toContain('Справка');
|
|
175
|
-
expect(response.text).toContain('Политика конфиденциальности');
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
it('should include language selector links visible in screenshot', async () => {
|
|
179
|
-
if (!testHtml) {
|
|
180
|
-
console.warn('Skipping test - HTML file not found');
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
nock('https://xpaste.pro')
|
|
185
|
-
.get('/p/t4q0Lsp0')
|
|
186
|
-
.reply(200, testHtml, { 'content-type': 'text/html; charset=utf-8' });
|
|
187
|
-
|
|
188
|
-
const response = await request(app)
|
|
189
|
-
.get('/markdown')
|
|
190
|
-
.query({ url: testUrl });
|
|
191
|
-
|
|
192
|
-
expect(response.status).toBe(200);
|
|
193
|
-
|
|
194
|
-
// Verify language links (top right of screenshot)
|
|
195
|
-
expect(response.text).toContain('Ru');
|
|
196
|
-
expect(response.text).toContain('En');
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
it('should include RAW link visible in screenshot', async () => {
|
|
200
|
-
if (!testHtml) {
|
|
201
|
-
console.warn('Skipping test - HTML file not found');
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
nock('https://xpaste.pro')
|
|
206
|
-
.get('/p/t4q0Lsp0')
|
|
207
|
-
.reply(200, testHtml, { 'content-type': 'text/html; charset=utf-8' });
|
|
208
|
-
|
|
209
|
-
const response = await request(app)
|
|
210
|
-
.get('/markdown')
|
|
211
|
-
.query({ url: testUrl });
|
|
212
|
-
|
|
213
|
-
expect(response.status).toBe(200);
|
|
214
|
-
|
|
215
|
-
// Verify RAW button (visible in screenshot)
|
|
216
|
-
expect(response.text).toContain('RAW');
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
it('should embed the raw text file as a markdown text block', async () => {
|
|
220
|
-
if (!testHtml || !rawText) {
|
|
221
|
-
console.warn('Skipping test - fixture files not found');
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
nock('https://xpaste.pro')
|
|
226
|
-
.get('/p/t4q0Lsp0')
|
|
227
|
-
.reply(200, testHtml, { 'content-type': 'text/html; charset=utf-8' });
|
|
228
|
-
|
|
229
|
-
const response = await request(app)
|
|
230
|
-
.get('/markdown')
|
|
231
|
-
.query({ url: testUrl });
|
|
232
|
-
|
|
233
|
-
expect(response.status).toBe(200);
|
|
234
|
-
expect(response.type).toBe('text/markdown');
|
|
235
|
-
expect(response.text).toContain('## xpaste-pro-t4q0Lsp0.txt');
|
|
236
|
-
expect(response.text).toContain('```text');
|
|
237
|
-
expect(response.text).toContain(
|
|
238
|
-
rawText.split('\n')[0].replace(/\r/g, '')
|
|
239
|
-
);
|
|
240
|
-
expect(response.text).toContain('DELETE FROM phpbb_post_revisions');
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
it('should verify correct element ordering - heading and languages at top', async () => {
|
|
244
|
-
if (!testHtml) {
|
|
245
|
-
console.warn('Skipping test - HTML file not found');
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
nock('https://xpaste.pro')
|
|
250
|
-
.get('/p/t4q0Lsp0')
|
|
251
|
-
.reply(200, testHtml, { 'content-type': 'text/html; charset=utf-8' });
|
|
252
|
-
|
|
253
|
-
const response = await request(app)
|
|
254
|
-
.get('/markdown')
|
|
255
|
-
.query({ url: testUrl });
|
|
256
|
-
|
|
257
|
-
expect(response.status).toBe(200);
|
|
258
|
-
|
|
259
|
-
const lines = response.text.split('\n');
|
|
260
|
-
|
|
261
|
-
// Find line numbers of key elements
|
|
262
|
-
const headingLine = lines.findIndex((l) => l.includes('Упакуем пароль'));
|
|
263
|
-
const languageLine = lines.findIndex(
|
|
264
|
-
(l) => l.includes('[Ru]') || l.includes('[En]')
|
|
265
|
-
);
|
|
266
|
-
const formatLine = lines.findIndex((l) => l.includes('Формат:'));
|
|
267
|
-
const firstQueryLine = lines.findIndex(
|
|
268
|
-
(l) => l.includes('# 1') && l.includes('#')
|
|
269
|
-
);
|
|
270
|
-
|
|
271
|
-
// Verify heading comes before metadata (as shown in screenshot)
|
|
272
|
-
expect(headingLine).toBeGreaterThan(0);
|
|
273
|
-
expect(headingLine).toBeLessThan(formatLine);
|
|
274
|
-
|
|
275
|
-
// Verify languages come before metadata (as shown in screenshot)
|
|
276
|
-
expect(languageLine).toBeGreaterThan(0);
|
|
277
|
-
expect(languageLine).toBeLessThan(formatLine);
|
|
278
|
-
|
|
279
|
-
// Verify heading comes before main content
|
|
280
|
-
expect(headingLine).toBeLessThan(firstQueryLine);
|
|
281
|
-
|
|
282
|
-
// Verify metadata comes before main content
|
|
283
|
-
expect(formatLine).toBeLessThan(firstQueryLine);
|
|
284
|
-
|
|
285
|
-
console.log(
|
|
286
|
-
`✓ Element ordering verified: heading (line ${headingLine + 1}) -> languages (line ${languageLine + 1}) -> metadata (line ${formatLine + 1}) -> content (line ${firstQueryLine + 1})`
|
|
287
|
-
);
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
it('should verify markdown extraction matches screenshot - comprehensive check', async () => {
|
|
291
|
-
if (!testHtml) {
|
|
292
|
-
console.warn('Skipping test - HTML file not found');
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
nock('https://xpaste.pro')
|
|
297
|
-
.get('/p/t4q0Lsp0')
|
|
298
|
-
.reply(200, testHtml, { 'content-type': 'text/html; charset=utf-8' });
|
|
299
|
-
|
|
300
|
-
const response = await request(app)
|
|
301
|
-
.get('/markdown')
|
|
302
|
-
.query({ url: testUrl });
|
|
303
|
-
|
|
304
|
-
expect(response.status).toBe(200);
|
|
305
|
-
|
|
306
|
-
// Define all key text elements visible in the screenshot
|
|
307
|
-
const requiredElements = [
|
|
308
|
-
// Header/branding
|
|
309
|
-
'Xpaste',
|
|
310
|
-
|
|
311
|
-
// Metadata section
|
|
312
|
-
'Формат:',
|
|
313
|
-
'text',
|
|
314
|
-
'Время создания:',
|
|
315
|
-
'07.07.2021, 12:48 UTC',
|
|
316
|
-
'Будет удалена:',
|
|
317
|
-
'05.07.2031, 12:48 UTC',
|
|
318
|
-
|
|
319
|
-
// Query #1
|
|
320
|
-
'# 1',
|
|
321
|
-
'Time: 210707 15:39:36',
|
|
322
|
-
'User@Host: 1703313381[1703313381]',
|
|
323
|
-
'[136.243.53.188]',
|
|
324
|
-
'Id: 1138102510',
|
|
325
|
-
'Query_time: 2.182754',
|
|
326
|
-
|
|
327
|
-
// Query #2
|
|
328
|
-
'# 2',
|
|
329
|
-
'Time: 210707 15:38:11',
|
|
330
|
-
'Id: 1138100591',
|
|
331
|
-
'DELETE FROM phpbb_post_revisions',
|
|
332
|
-
|
|
333
|
-
// Query #3
|
|
334
|
-
'# 3',
|
|
335
|
-
'Time: 210707 15:32:18',
|
|
336
|
-
'Id: 1138094492',
|
|
337
|
-
|
|
338
|
-
// Query #4
|
|
339
|
-
'# 4',
|
|
340
|
-
'Time: 210707 15:27:56',
|
|
341
|
-
'Id: 1138089558',
|
|
342
|
-
|
|
343
|
-
// Footer
|
|
344
|
-
'Упакуем пароль',
|
|
345
|
-
'Southbridge',
|
|
346
|
-
'Справка',
|
|
347
|
-
'Политика конфиденциальности',
|
|
348
|
-
|
|
349
|
-
// Language links
|
|
350
|
-
'Ru',
|
|
351
|
-
'En',
|
|
352
|
-
|
|
353
|
-
// RAW button
|
|
354
|
-
'RAW',
|
|
355
|
-
];
|
|
356
|
-
|
|
357
|
-
// Verify ALL elements are present
|
|
358
|
-
const missingElements = [];
|
|
359
|
-
requiredElements.forEach((element) => {
|
|
360
|
-
if (!response.text.includes(element)) {
|
|
361
|
-
missingElements.push(element);
|
|
362
|
-
}
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
expect(missingElements).toEqual([]);
|
|
366
|
-
|
|
367
|
-
// Log success with coverage stats
|
|
368
|
-
if (missingElements.length === 0) {
|
|
369
|
-
console.log(
|
|
370
|
-
`✓ All ${requiredElements.length} screenshot text elements found in markdown`
|
|
371
|
-
);
|
|
372
|
-
}
|
|
373
|
-
});
|
|
374
|
-
});
|
|
375
|
-
});
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import getPort from 'get-port';
|
|
2
|
-
import nock from '../helpers/nock.js';
|
|
3
|
-
import { app } from '../../src/index.js';
|
|
4
|
-
|
|
5
|
-
describe('xpaste.pro HTTP e2e', () => {
|
|
6
|
-
let server;
|
|
7
|
-
|
|
8
|
-
afterEach(async () => {
|
|
9
|
-
nock.cleanAll();
|
|
10
|
-
if (server) {
|
|
11
|
-
await new Promise((resolve) => server.close(resolve));
|
|
12
|
-
server = null;
|
|
13
|
-
}
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('serves raw paste text through the real HTTP server', async () => {
|
|
17
|
-
const pasteText = 'console.log("xpaste fixture");\n';
|
|
18
|
-
nock('https://xpaste.pro')
|
|
19
|
-
.get('/p/js123/raw')
|
|
20
|
-
.reply(200, pasteText, { 'content-type': 'text/plain; charset=utf-8' });
|
|
21
|
-
|
|
22
|
-
const port = await getPort();
|
|
23
|
-
server = app.listen(port);
|
|
24
|
-
|
|
25
|
-
const response = await fetch(
|
|
26
|
-
`http://127.0.0.1:${port}/txt?url=${encodeURIComponent('https://xpaste.pro/p/js123')}`
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
expect(response.status).toBe(200);
|
|
30
|
-
expect(response.headers.get('content-type')).toMatch(/text\/plain/);
|
|
31
|
-
expect(response.headers.get('content-disposition')).toContain(
|
|
32
|
-
'xpaste-pro-js123.txt'
|
|
33
|
-
);
|
|
34
|
-
expect(await response.text()).toBe(pasteText);
|
|
35
|
-
});
|
|
36
|
-
});
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { fileURLToPath } from 'url';
|
|
4
|
-
import { convertHtmlToMarkdownEnhanced } from '../../src/lib.js';
|
|
5
|
-
|
|
6
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
const DATA_DIR = path.join(__dirname, '../../../tests/xpaste/data');
|
|
8
|
-
|
|
9
|
-
describe('xpaste.pro fixture integration', () => {
|
|
10
|
-
it('converts the captured page fixture with the same visual ordering as the screenshot', () => {
|
|
11
|
-
const html = fs.readFileSync(
|
|
12
|
-
path.join(DATA_DIR, 't4q0Lsp0-page.html'),
|
|
13
|
-
'utf-8'
|
|
14
|
-
);
|
|
15
|
-
const { markdown } = convertHtmlToMarkdownEnhanced(
|
|
16
|
-
html,
|
|
17
|
-
'https://xpaste.pro/p/t4q0Lsp0',
|
|
18
|
-
{
|
|
19
|
-
extractMetadata: false,
|
|
20
|
-
postProcess: false,
|
|
21
|
-
}
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
const headingIndex = markdown.indexOf('Упакуем пароль');
|
|
25
|
-
const languageIndexes = ['[Ru]', '[En]']
|
|
26
|
-
.map((label) => markdown.indexOf(label))
|
|
27
|
-
.filter((index) => index >= 0);
|
|
28
|
-
const languageIndex = Math.min(...languageIndexes);
|
|
29
|
-
const formatIndex = markdown.indexOf('Формат:');
|
|
30
|
-
const firstQueryIndex = markdown.indexOf('# 1');
|
|
31
|
-
|
|
32
|
-
expect(headingIndex).toBeGreaterThanOrEqual(0);
|
|
33
|
-
expect(languageIndexes).not.toHaveLength(0);
|
|
34
|
-
expect(languageIndex).toBeGreaterThanOrEqual(0);
|
|
35
|
-
expect(formatIndex).toBeGreaterThanOrEqual(0);
|
|
36
|
-
expect(firstQueryIndex).toBeGreaterThanOrEqual(0);
|
|
37
|
-
expect(headingIndex).toBeLessThan(formatIndex);
|
|
38
|
-
expect(languageIndex).toBeLessThan(formatIndex);
|
|
39
|
-
expect(formatIndex).toBeLessThan(firstQueryIndex);
|
|
40
|
-
});
|
|
41
|
-
});
|