@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.
Files changed (119) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +2 -1
  3. package/bin/web-capture.js +5 -7
  4. package/package.json +32 -18
  5. package/src/archive.js +1 -2
  6. package/src/binary-download.js +13 -0
  7. package/src/docx.js +14 -11
  8. package/src/fetch.js +1 -2
  9. package/src/figures.js +2 -12
  10. package/src/gdocs-images.js +0 -1
  11. package/src/gdocs.js +0 -1
  12. package/src/github.js +0 -1
  13. package/src/lib.js +1 -2
  14. package/src/localize-images.js +2 -12
  15. package/src/markdown.js +0 -1
  16. package/src/shared-dialog.js +0 -1
  17. package/src/stream.js +0 -1
  18. package/src/transport.js +23 -5
  19. package/src/txt.js +0 -1
  20. package/.changeset/README.md +0 -8
  21. package/.changeset/config.json +0 -11
  22. package/.dockerignore +0 -6
  23. package/.jscpd.json +0 -19
  24. package/.lenv +0 -8
  25. package/.prettierignore +0 -8
  26. package/.prettierrc +0 -10
  27. package/Dockerfile +0 -52
  28. package/babel.config.cjs +0 -13
  29. package/docker-compose.yml +0 -11
  30. package/eslint.config.js +0 -112
  31. package/examples/js/capture_cache.js +0 -12
  32. package/examples/js/engine_comparison.js +0 -39
  33. package/examples/js/html_download.js +0 -23
  34. package/examples/js/image_download.js +0 -36
  35. package/examples/js/markdown_download.js +0 -23
  36. package/examples/js/playwright_example.js +0 -23
  37. package/examples/python/html_download.py +0 -15
  38. package/examples/python/image_download.py +0 -20
  39. package/examples/python/markdown_download.py +0 -15
  40. package/examples/python/playwright_example.py +0 -22
  41. package/jest.config.mjs +0 -20
  42. package/tests/ci/dependency-freshness.test.js +0 -74
  43. package/tests/ci/detect-code-changes.test.js +0 -174
  44. package/tests/ci/docker-install.test.js +0 -31
  45. package/tests/ci/rust-dockerfile.test.js +0 -54
  46. package/tests/ci/workflow-policy.test.js +0 -79
  47. package/tests/e2e/docker.test.js +0 -186
  48. package/tests/e2e/process.test.js +0 -215
  49. package/tests/fixtures/bold-coalesce-and-leakage.html +0 -27
  50. package/tests/fixtures/deepwiki/deepwiki-full-page.png +0 -0
  51. package/tests/fixtures/deepwiki/index.md +0 -1500
  52. package/tests/fixtures/deepwiki/part-1.md +0 -776
  53. package/tests/fixtures/habr-articles.js +0 -45
  54. package/tests/fixtures/heading-numbering.html +0 -19
  55. package/tests/fixtures/list-item-with-br.html +0 -8
  56. package/tests/fixtures/ol-continuous-numbering.html +0 -12
  57. package/tests/fixtures/paragraph-vs-line-break.html +0 -11
  58. package/tests/helpers/nock.js +0 -10
  59. package/tests/helpers/search-fixture.cjs +0 -17
  60. package/tests/helpers/temporary-directory.js +0 -12
  61. package/tests/integration/api-endpoints.test.js +0 -418
  62. package/tests/integration/archive-default-layout.test.js +0 -37
  63. package/tests/integration/browser-engines.test.js +0 -128
  64. package/tests/integration/browser-packaged-path.test.js +0 -45
  65. package/tests/integration/deepwiki-download.test.js +0 -144
  66. package/tests/integration/deepwiki-rendered-page.mock.test.js +0 -121
  67. package/tests/integration/formalai-contract.test.js +0 -214
  68. package/tests/integration/gdocs-public-doc.test.js +0 -315
  69. package/tests/integration/github-readme.test.js +0 -137
  70. package/tests/integration/google-drive-image.test.js +0 -246
  71. package/tests/integration/habr-article.test.js +0 -337
  72. package/tests/integration/search-endpoint.test.js +0 -88
  73. package/tests/integration/stackoverflow-download.test.js +0 -173
  74. package/tests/integration/wikipedia-download.test.js +0 -142
  75. package/tests/jest.setup.mjs +0 -7
  76. package/tests/mock/index.test.js +0 -225
  77. package/tests/unit/animation.test.js +0 -33
  78. package/tests/unit/batch.test.js +0 -114
  79. package/tests/unit/bold-coalesce-and-leakage.test.js +0 -54
  80. package/tests/unit/browser-launch-options.test.js +0 -42
  81. package/tests/unit/browser.test.js +0 -172
  82. package/tests/unit/cache.test.js +0 -182
  83. package/tests/unit/cli.test.js +0 -399
  84. package/tests/unit/convertGoogleDriveUrl.test.js +0 -89
  85. package/tests/unit/convertRelativeUrls.test.js +0 -34
  86. package/tests/unit/extract-images.test.js +0 -259
  87. package/tests/unit/fetch-html.test.js +0 -54
  88. package/tests/unit/figures.test.js +0 -74
  89. package/tests/unit/gdocs-export-headers.test.js +0 -28
  90. package/tests/unit/gdocs-preprocess.test.js +0 -36
  91. package/tests/unit/gdocs.test.js +0 -1490
  92. package/tests/unit/github.test.js +0 -156
  93. package/tests/unit/heading-image.test.js +0 -49
  94. package/tests/unit/heading-numbering.test.js +0 -28
  95. package/tests/unit/html2md-br-in-list-item.test.js +0 -23
  96. package/tests/unit/html2md-ol-numbering.test.js +0 -20
  97. package/tests/unit/html2md.test.js +0 -695
  98. package/tests/unit/image-mode-defaults.test.js +0 -54
  99. package/tests/unit/kreuzberg.test.js +0 -249
  100. package/tests/unit/latex.test.js +0 -123
  101. package/tests/unit/localize-images.test.js +0 -114
  102. package/tests/unit/markdown.test.js +0 -36
  103. package/tests/unit/metadata.test.js +0 -140
  104. package/tests/unit/package-runtime-dependencies.test.js +0 -74
  105. package/tests/unit/paragraph-vs-line-break.test.js +0 -56
  106. package/tests/unit/postprocess.test.js +0 -110
  107. package/tests/unit/proxy-headers.test.js +0 -100
  108. package/tests/unit/publish-verification.test.js +0 -57
  109. package/tests/unit/retry.test.js +0 -111
  110. package/tests/unit/search.test.js +0 -339
  111. package/tests/unit/shared-dialog.test.js +0 -173
  112. package/tests/unit/stackoverflow.test.js +0 -32
  113. package/tests/unit/stream.test.js +0 -85
  114. package/tests/unit/verify.test.js +0 -182
  115. package/tests/xpaste/xpaste-screenshot-markdown.mock.test.js +0 -375
  116. package/tests/xpaste/xpaste.e2e.test.js +0 -36
  117. package/tests/xpaste/xpaste.integration.test.js +0 -41
  118. package/tests/xpaste/xpaste.mock.test.js +0 -261
  119. package/tests/xpaste/xpaste.unit.test.js +0 -119
@@ -1,54 +0,0 @@
1
- import { readFileSync } from 'fs';
2
- import { dirname, resolve } from 'path';
3
- import { fileURLToPath } from 'url';
4
-
5
- const __filename = fileURLToPath(import.meta.url);
6
- const __dirname = dirname(__filename);
7
- const repoRoot = resolve(__dirname, '../../..');
8
-
9
- function parseVersion(version) {
10
- return version.split('.').map((part) => Number(part));
11
- }
12
-
13
- function compareVersions(left, right) {
14
- const leftParts = parseVersion(left);
15
- const rightParts = parseVersion(right);
16
- const maxLength = Math.max(leftParts.length, rightParts.length);
17
-
18
- for (let index = 0; index < maxLength; index += 1) {
19
- const leftPart = leftParts[index] || 0;
20
- const rightPart = rightParts[index] || 0;
21
-
22
- if (leftPart !== rightPart) {
23
- return leftPart - rightPart;
24
- }
25
- }
26
-
27
- return 0;
28
- }
29
-
30
- describe('Rust Dockerfile', () => {
31
- test('uses a Rust builder image that satisfies Cargo.toml rust-version', () => {
32
- const cargoToml = readFileSync(
33
- resolve(repoRoot, 'rust/Cargo.toml'),
34
- 'utf8'
35
- );
36
- const dockerfile = readFileSync(
37
- resolve(repoRoot, 'rust/Dockerfile'),
38
- 'utf8'
39
- );
40
-
41
- const requiredRustVersion = cargoToml.match(
42
- /^rust-version = "([^"]+)"/m
43
- )?.[1];
44
- const dockerRustVersion = dockerfile.match(
45
- /^FROM rust:(\d+(?:\.\d+){1,2})[-\s]/m
46
- )?.[1];
47
-
48
- expect(requiredRustVersion).toBeDefined();
49
- expect(dockerRustVersion).toBeDefined();
50
- expect(
51
- compareVersions(dockerRustVersion, requiredRustVersion)
52
- ).toBeGreaterThanOrEqual(0);
53
- });
54
- });
@@ -1,79 +0,0 @@
1
- import { readFileSync } from 'fs';
2
- import { dirname, resolve } from 'path';
3
- import { fileURLToPath } from 'url';
4
-
5
- const __filename = fileURLToPath(import.meta.url);
6
- const __dirname = dirname(__filename);
7
- const repoRoot = resolve(__dirname, '../../..');
8
-
9
- const workflowFiles = [
10
- '.github/workflows/js.yml',
11
- '.github/workflows/rust.yml',
12
- '.github/workflows/parity.yml',
13
- ];
14
-
15
- function readRootFile(relativePath) {
16
- return readFileSync(resolve(repoRoot, relativePath), 'utf8');
17
- }
18
-
19
- function readWorkflow(relativePath) {
20
- return readRootFile(relativePath);
21
- }
22
-
23
- describe('workflow policy', () => {
24
- test.each(workflowFiles)(
25
- '%s uses release-safe concurrency cancellation',
26
- (workflowFile) => {
27
- const workflow = readWorkflow(workflowFile);
28
-
29
- expect(workflow).toContain(
30
- "cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}"
31
- );
32
- expect(workflow).not.toContain(
33
- "cancel-in-progress: ${{ github.ref == 'refs/heads/main' }}"
34
- );
35
- }
36
- );
37
-
38
- test.each(workflowFiles)(
39
- '%s uses the current checkout action',
40
- (workflowFile) => {
41
- const workflow = readWorkflow(workflowFile);
42
-
43
- expect(workflow).toContain('uses: actions/checkout@v6');
44
- expect(workflow).not.toContain('uses: actions/checkout@v4');
45
- }
46
- );
47
-
48
- test('Rust workflow uses the current cache action', () => {
49
- const workflow = readWorkflow('.github/workflows/rust.yml');
50
-
51
- expect(workflow).toContain('uses: actions/cache@v5');
52
- expect(workflow).not.toContain('uses: actions/cache@v4');
53
- });
54
-
55
- test('parity check ignores repository-level CI tests', () => {
56
- const parityScript = readRootFile('scripts/check-js-rust-parity.mjs');
57
-
58
- expect(parityScript).toContain('"js/tests/ci/"');
59
- expect(parityScript).toContain(
60
- 'matchingFiles(files, JS_PARITY_PATHS, JS_PARITY_IGNORE_PATHS)'
61
- );
62
- });
63
- });
64
-
65
- test('release workflows require dependency health gates', () => {
66
- const js = readWorkflow('.github/workflows/js.yml');
67
- expect(js).toContain('npm audit --package-lock-only --audit-level=moderate');
68
- expect(js).toContain('check-dependency-freshness.mjs npm');
69
- expect(js).toContain('needs: [lint, test, dependency-health]');
70
- const rust = readWorkflow('.github/workflows/rust.yml');
71
- expect(rust).toContain('check-dependency-freshness.mjs cargo');
72
- expect(rust).toContain('cargo update --dry-run');
73
- expect(rust).toContain('needs: [lint, test, build, dependency-health]');
74
- for (const workflow of [js, rust]) {
75
- expect(
76
- workflow.match(/needs\.dependency-health\.result == 'success'/g)
77
- ).toHaveLength(2);
78
- }
79
- });
@@ -1,186 +0,0 @@
1
- import fetch from 'node-fetch';
2
- import http from 'http';
3
- import getPort from 'get-port';
4
- import { exec } from 'child_process';
5
- import { promisify } from 'util';
6
-
7
- const execAsync = promisify(exec);
8
- const WAIT_FOR_READY = 10000; // ms
9
- const PORT = 3000; // Use the same port as in docker-compose.yml
10
- const baseUrl = `http://localhost:${PORT}`;
11
-
12
- const timings = {};
13
- let mockServer;
14
- let mockUrl;
15
-
16
- const MOCK_HTML = `<!DOCTYPE html>
17
- <html lang="en">
18
- <head><meta charset="utf-8"><title>Example Domain</title></head>
19
- <body><h1>Example Domain</h1><p>This domain is for use in illustrative examples.</p></body>
20
- </html>`;
21
-
22
- async function isServiceRunning() {
23
- try {
24
- const res = await fetch(`${baseUrl}/health`);
25
- return res.status === 200;
26
- } catch {
27
- return false;
28
- }
29
- }
30
-
31
- beforeAll(async () => {
32
- timings.start = Date.now();
33
-
34
- // Start a local mock server to avoid depending on external network
35
- const mockPort = await getPort();
36
- mockServer = http.createServer((req, res) => {
37
- res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
38
- res.end(MOCK_HTML);
39
- });
40
- await new Promise((resolve) =>
41
- mockServer.listen(mockPort, '0.0.0.0', resolve)
42
- );
43
- // docker-compose.yml maps this hostname to the host gateway on Linux CI.
44
- mockUrl = `http://host.docker.internal:${mockPort}`;
45
-
46
- console.log('Checking if Docker service is already running...');
47
- const alreadyRunning = await isServiceRunning();
48
- if (!alreadyRunning) {
49
- console.log('Service not running, starting Docker container...');
50
- try {
51
- const dockerStart = Date.now();
52
- // Start the Docker container using docker compose (v2 command)
53
- const { stdout, stderr } = await execAsync('docker compose up -d');
54
- timings.dockerStartup = Date.now() - dockerStart;
55
- console.log('Docker compose output:', stdout);
56
- if (stderr) {
57
- console.error('Docker compose errors:', stderr);
58
- }
59
-
60
- // Wait for the service to be ready
61
- const readyStart = Date.now();
62
- await new Promise((resolve, reject) => {
63
- const timeout = setTimeout(() => {
64
- reject(new Error('Docker service did not start in time'));
65
- }, WAIT_FOR_READY);
66
-
67
- const checkReady = async () => {
68
- try {
69
- console.log(`Checking if service is ready at ${baseUrl}...`);
70
- const res = await fetch(`${baseUrl}/health`);
71
- if (res.status === 200) {
72
- console.log('Service is ready!');
73
- clearTimeout(timeout);
74
- resolve();
75
- } else {
76
- console.log(`Service not ready yet, status: ${res.status}`);
77
- setTimeout(checkReady, 500);
78
- }
79
- } catch (err) {
80
- console.log('Service not ready yet, error:', err.message);
81
- setTimeout(checkReady, 500);
82
- }
83
- };
84
- checkReady();
85
- });
86
- timings.serviceReady = Date.now() - readyStart;
87
- } catch (error) {
88
- console.error('Failed to start Docker container:', error);
89
- throw error;
90
- }
91
- timings.beforeAll = Date.now() - timings.start;
92
- console.log('Timing: Docker startup:', `${timings.dockerStartup}ms`);
93
- console.log('Timing: Service readiness:', `${timings.serviceReady}ms`);
94
- console.log('Timing: beforeAll total:', `${timings.beforeAll}ms`);
95
- } else {
96
- console.log('Docker service is already running. Skipping startup.');
97
- timings.beforeAll = Date.now() - timings.start;
98
- console.log(
99
- 'Timing: beforeAll (already running):',
100
- `${timings.beforeAll}ms`
101
- );
102
- }
103
- }, 30000); // Reduced timeout for beforeAll
104
-
105
- afterAll(async () => {
106
- if (mockServer) {
107
- mockServer.close();
108
- }
109
- // Stop Docker containers
110
- try {
111
- await execAsync('docker compose down');
112
- } catch {
113
- // Ignore errors during cleanup
114
- }
115
- });
116
-
117
- describe('E2E (Docker): Web Capture Microservice', () => {
118
- it('should return HTML from /html endpoint', async () => {
119
- const htmlStart = Date.now();
120
- const res = await fetch(
121
- `${baseUrl}/html?url=${encodeURIComponent(mockUrl)}`
122
- );
123
- expect(res.status).toBe(200);
124
- const text = await res.text();
125
- expect(text).toMatch(/<html/i);
126
- timings.html = Date.now() - htmlStart;
127
- console.log('Timing: /html endpoint:', `${timings.html}ms`);
128
- }, 20000);
129
-
130
- it('should return Markdown from /markdown endpoint', async () => {
131
- const mdStart = Date.now();
132
- const res = await fetch(
133
- `${baseUrl}/markdown?url=${encodeURIComponent(mockUrl)}`
134
- );
135
- expect(res.status).toBe(200);
136
- const text = await res.text();
137
- expect(text).toMatch(/example/i);
138
- timings.markdown = Date.now() - mdStart;
139
- console.log('Timing: /markdown endpoint:', `${timings.markdown}ms`);
140
- }, 20000);
141
-
142
- it('should return PNG from /image endpoint', async () => {
143
- const pngStart = Date.now();
144
- const res = await fetch(
145
- `${baseUrl}/image?url=${encodeURIComponent(mockUrl)}`
146
- );
147
- expect(res.status).toBe(200);
148
- expect(res.headers.get('content-type')).toMatch(/^image\/png/);
149
- const buf = Buffer.from(await res.arrayBuffer());
150
- // PNG signature check
151
- const pngSignature = Buffer.from([
152
- 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
153
- ]);
154
- expect(buf.slice(0, 8)).toEqual(pngSignature);
155
- expect(buf.length).toBeGreaterThan(100); // Should be a non-trivial PNG
156
- timings.png = Date.now() - pngStart;
157
- console.log('Timing: /image endpoint:', `${timings.png}ms`);
158
- }, 60000);
159
-
160
- it('should stream content from /stream endpoint', async () => {
161
- const startTime = Date.now();
162
- const res = await fetch(
163
- `${baseUrl}/stream?url=${encodeURIComponent(mockUrl)}`
164
- );
165
- expect(res.status).toBe(200);
166
- // Get the response as text
167
- const text = await res.text();
168
- expect(text).toMatch(/<html/i);
169
- expect(text).toMatch(/Example Domain/i);
170
- const endTime = Date.now();
171
- console.log(`Timing: /stream endpoint: ${endTime - startTime}ms`);
172
- }, 20000);
173
-
174
- it('should return content from /fetch endpoint', async () => {
175
- const startTime = Date.now();
176
- const res = await fetch(
177
- `${baseUrl}/fetch?url=${encodeURIComponent(mockUrl)}`
178
- );
179
- expect(res.status).toBe(200);
180
- const text = await res.text();
181
- expect(text).toMatch(/<html/i);
182
- expect(text).toMatch(/Example Domain/i);
183
- const endTime = Date.now();
184
- console.log(`Timing: /fetch endpoint: ${endTime - startTime}ms`);
185
- }, 20000);
186
- });
@@ -1,215 +0,0 @@
1
- import { spawn } from 'child_process';
2
- import fetch from 'node-fetch';
3
- import getPort from 'get-port';
4
- import path from 'path';
5
-
6
- const WAIT_FOR_READY = 5000; // ms
7
- let serverProcess;
8
- let serverOutput = '';
9
- let baseUrl;
10
- let mockProcess;
11
- let mockUrl;
12
-
13
- const MOCK_HTML = `<!DOCTYPE html>
14
- <html lang="en">
15
- <head><meta charset="utf-8"><title>Example Domain</title></head>
16
- <body><h1>Example Domain</h1><p>This domain is for use in illustrative examples.</p></body>
17
- </html>`;
18
-
19
- beforeAll(async () => {
20
- const mockPort = await getPort();
21
- mockProcess = startMockServer(mockPort);
22
- await waitForProcessOutput(mockProcess, 'mock fixture server listening');
23
- mockUrl = `http://127.0.0.1:${mockPort}`;
24
-
25
- const port = await getPort();
26
- baseUrl = `http://127.0.0.1:${port}`;
27
-
28
- serverProcess = spawn(
29
- 'node',
30
- [path.resolve('bin/web-capture.js'), '--serve', '--port', port.toString()],
31
- {
32
- env: process.env,
33
- stdio: ['ignore', 'pipe', 'pipe'],
34
- }
35
- );
36
- collectProcessOutput(serverProcess, (chunk) => {
37
- serverOutput += chunk;
38
- });
39
-
40
- // Wait for the server to be ready (simple delay or poll)
41
- await new Promise((resolve, reject) => {
42
- const timeout = setTimeout(() => {
43
- reject(new Error('Server did not start in time'));
44
- }, WAIT_FOR_READY);
45
-
46
- let serverStarted = false;
47
- serverProcess.stdout.on('data', (data) => {
48
- if (
49
- data.toString().includes('listening') ||
50
- data.toString().includes('Server running')
51
- ) {
52
- serverStarted = true;
53
- clearTimeout(timeout);
54
- resolve();
55
- }
56
- });
57
-
58
- serverProcess.on('error', (error) => {
59
- clearTimeout(timeout);
60
- reject(error);
61
- });
62
-
63
- serverProcess.on('exit', (code, signal) => {
64
- if (!serverStarted) {
65
- clearTimeout(timeout);
66
- reject(
67
- new Error(`Server process exited with code ${code}, signal ${signal}`)
68
- );
69
- }
70
- });
71
-
72
- // Fallback: resolve after WAIT_FOR_READY
73
- setTimeout(resolve, WAIT_FOR_READY);
74
- });
75
- });
76
-
77
- afterAll(() => {
78
- if (serverProcess) {
79
- serverProcess.kill();
80
- }
81
- if (mockProcess) {
82
- mockProcess.kill();
83
- }
84
- });
85
-
86
- describe('E2E: Web Capture Microservice', () => {
87
- it('should return HTML from /html endpoint', async () => {
88
- const res = await fetch(
89
- `${baseUrl}/html?url=${encodeURIComponent(mockUrl)}`
90
- );
91
- const text = await expectTextResponse(res);
92
- expect(text).toMatch(/<html/i);
93
- });
94
-
95
- it('should return Markdown from /markdown endpoint', async () => {
96
- const res = await fetch(
97
- `${baseUrl}/markdown?url=${encodeURIComponent(mockUrl)}`
98
- );
99
- const text = await expectTextResponse(res);
100
- expect(text).toMatch(/example/i);
101
- });
102
-
103
- it('should return PNG from /image endpoint', async () => {
104
- const res = await fetch(
105
- `${baseUrl}/image?url=${encodeURIComponent(mockUrl)}`
106
- );
107
- await expectStatusOk(res);
108
- expect(res.headers.get('content-type')).toMatch(/^image\/png/);
109
- const buf = Buffer.from(await res.arrayBuffer());
110
- // PNG signature check
111
- const pngSignature = Buffer.from([
112
- 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
113
- ]);
114
- expect(buf.slice(0, 8)).toEqual(pngSignature);
115
- expect(buf.length).toBeGreaterThan(100); // Should be a non-trivial PNG
116
- }, 60000);
117
-
118
- it('should stream content from /stream endpoint', async () => {
119
- const res = await fetch(
120
- `${baseUrl}/stream?url=${encodeURIComponent(mockUrl)}`
121
- );
122
- const text = await expectTextResponse(res);
123
- expect(text).toMatch(/<html/i);
124
- expect(text).toMatch(/Example Domain/i);
125
- }, 20000);
126
-
127
- it('should return content from /fetch endpoint', async () => {
128
- const res = await fetch(
129
- `${baseUrl}/fetch?url=${encodeURIComponent(mockUrl)}`
130
- );
131
- const text = await expectTextResponse(res);
132
- expect(text).toMatch(/<html/i);
133
- expect(text).toMatch(/Example Domain/i);
134
- }, 20000);
135
- });
136
-
137
- function startMockServer(port) {
138
- const script = `
139
- const http = require('http');
140
- const html = ${JSON.stringify(MOCK_HTML)};
141
- const server = http.createServer((req, res) => {
142
- res.writeHead(200, {
143
- 'Content-Type': 'text/html; charset=utf-8',
144
- 'Content-Length': Buffer.byteLength(html),
145
- });
146
- res.end(html);
147
- });
148
- server.listen(${port}, '127.0.0.1', () => {
149
- console.log('mock fixture server listening');
150
- });
151
- process.on('SIGTERM', () => server.close(() => process.exit(0)));
152
- process.on('SIGINT', () => server.close(() => process.exit(0)));
153
- `;
154
- return spawn('node', ['-e', script], {
155
- stdio: ['ignore', 'pipe', 'pipe'],
156
- });
157
- }
158
-
159
- function collectProcessOutput(child, onChunk) {
160
- child.stdout.on('data', (data) => onChunk(data.toString()));
161
- child.stderr.on('data', (data) => onChunk(data.toString()));
162
- }
163
-
164
- async function waitForProcessOutput(child, expected) {
165
- let output = '';
166
- collectProcessOutput(child, (chunk) => {
167
- output += chunk;
168
- });
169
- await new Promise((resolve, reject) => {
170
- const timeout = setTimeout(() => {
171
- reject(
172
- new Error(`Timed out waiting for "${expected}". Output:\n${output}`)
173
- );
174
- }, WAIT_FOR_READY);
175
- child.stdout.on('data', (data) => {
176
- if (data.toString().includes(expected)) {
177
- clearTimeout(timeout);
178
- resolve();
179
- }
180
- });
181
- child.on('exit', (code, signal) => {
182
- clearTimeout(timeout);
183
- reject(new Error(`Process exited with code ${code}, signal ${signal}`));
184
- });
185
- });
186
- }
187
-
188
- async function expectTextResponse(res) {
189
- let text;
190
- try {
191
- text = await res.text();
192
- } catch (error) {
193
- throw new Error(
194
- `${error.message}\nServer output:\n${serverOutput || '(empty)'}`,
195
- { cause: error }
196
- );
197
- }
198
- expectResponseStatus(res.status, text);
199
- return text;
200
- }
201
-
202
- async function expectStatusOk(res) {
203
- if (res.status === 200) {
204
- return;
205
- }
206
- expectResponseStatus(res.status, await res.text());
207
- }
208
-
209
- function expectResponseStatus(status, body) {
210
- if (status !== 200) {
211
- throw new Error(
212
- `Expected HTTP 200, received ${status}\nBody:\n${body}\nServer output:\n${serverOutput}`
213
- );
214
- }
215
- }
@@ -1,27 +0,0 @@
1
- <!doctype html>
2
- <html>
3
- <body>
4
- <!-- Symptom A: two adjacent bold spans -->
5
- <p>
6
- <span style="font-weight: 700">13.1 </span
7
- ><span style="font-weight: 700">First subsection</span>
8
- </p>
9
-
10
- <!-- Symptom B: outer bold ends, inner bold begins, no separator -->
11
- <ol>
12
- <li><span style="font-weight: 700">Item header.</span></li>
13
- </ol>
14
- <p><span style="font-weight: 700">Note:</span> body text.</p>
15
-
16
- <!-- Symptom C: bold range spans across <br> and <img> -->
17
- <p>
18
- <span style="font-weight: 700"
19
- >Caption A:<img
20
- alt=""
21
- src="data:image/png;base64,iVBORw0KGgo=" /><br />Caption B:<img
22
- alt=""
23
- src="data:image/png;base64,iVBORw0KGgo="
24
- /></span>
25
- </p>
26
- </body>
27
- </html>