Skip to content

Commit 276d03a

Browse files
committed
test: add comprehensive test coverage for v1.1 features
Parser Tests (v1.1-features.test.ts): - Strikethrough parsing and serialization (4 tests) - Unicode escape sequences \uXXXX and \xXX (6 tests) - Position tracking in error messages (4 tests) - String parsing validation (5 tests) - Complete round-trip with all v1.1 features (1 test) CLI Tests (v1.1-features.test.ts): - Extended HTML rendering (ordered lists, blockquotes, code, strikethrough) - HTML XSS prevention / escaping (3 tests) - Enhanced Markdown export (4 tests) - Error position tracking (1 test) Total: 31 new tests, all passing (88/88 total) Fixes P1-1: v1.1 features now have comprehensive test coverage
1 parent b2a136d commit 276d03a

File tree

2 files changed

+477
-0
lines changed

2 files changed

+477
-0
lines changed

cli/tests/v1.1-features.test.ts

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
// File: cli/tests/v1.1-features.test.ts
2+
// What: Test v1.1 CLI features (extended rendering, security fixes)
3+
// Why: Ensure new v1.1 CLI capabilities work correctly
4+
// RELEVANT FILES: osf.ts, cli.test.ts
5+
6+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
7+
import { execSync } from 'child_process';
8+
import { writeFileSync, unlinkSync, existsSync } from 'fs';
9+
import { join } from 'path';
10+
11+
const CLI_PATH = join(__dirname, '../dist/osf.js');
12+
const TEST_FIXTURES_DIR = join(__dirname, 'fixtures');
13+
14+
describe('v1.1 CLI Features', () => {
15+
beforeEach(() => {
16+
if (!existsSync(TEST_FIXTURES_DIR)) {
17+
execSync(`mkdir -p "${TEST_FIXTURES_DIR}"`);
18+
}
19+
});
20+
21+
afterEach(() => {
22+
// Clean up test files
23+
const testFiles = ['test_v11.osf', 'output.html', 'output.md'];
24+
testFiles.forEach(file => {
25+
const filePath = join(TEST_FIXTURES_DIR, file);
26+
if (existsSync(filePath)) {
27+
unlinkSync(filePath);
28+
}
29+
});
30+
});
31+
32+
describe('Extended HTML Rendering', () => {
33+
it('should render ordered lists in HTML', () => {
34+
const osf = `@slide {
35+
title: "Test";
36+
1. First item
37+
2. Second item
38+
3. Third item
39+
}`;
40+
const testFile = join(TEST_FIXTURES_DIR, 'test_v11.osf');
41+
writeFileSync(testFile, osf, 'utf8');
42+
43+
const result = execSync(`node "${CLI_PATH}" render "${testFile}"`, {
44+
encoding: 'utf8',
45+
});
46+
47+
expect(result).toContain('<ol>');
48+
expect(result).toContain('<li>First item</li>');
49+
expect(result).toContain('<li>Second item</li>');
50+
});
51+
52+
it('should render blockquotes in HTML', () => {
53+
const osf = `@slide {
54+
title: "Quotes";
55+
> This is a quote
56+
> Second line
57+
}`;
58+
const testFile = join(TEST_FIXTURES_DIR, 'test_v11.osf');
59+
writeFileSync(testFile, osf, 'utf8');
60+
61+
const result = execSync(`node "${CLI_PATH}" render "${testFile}"`, {
62+
encoding: 'utf8',
63+
});
64+
65+
expect(result).toContain('<blockquote>');
66+
expect(result).toContain('This is a quote');
67+
});
68+
69+
it('should render code blocks in HTML', () => {
70+
const osf = `@slide {
71+
title: "Code";
72+
\`\`\`javascript
73+
console.log("hello");
74+
\`\`\`
75+
}`;
76+
const testFile = join(TEST_FIXTURES_DIR, 'test_v11.osf');
77+
writeFileSync(testFile, osf, 'utf8');
78+
79+
const result = execSync(`node "${CLI_PATH}" render "${testFile}"`, {
80+
encoding: 'utf8',
81+
});
82+
83+
expect(result).toContain('<pre><code');
84+
expect(result).toContain('language-javascript');
85+
expect(result).toContain('console.log');
86+
});
87+
88+
it('should render strikethrough in HTML', () => {
89+
const osf = `@slide {
90+
title: "Pricing";
91+
~~$99~~ **$79** today!
92+
}`;
93+
const testFile = join(TEST_FIXTURES_DIR, 'test_v11.osf');
94+
writeFileSync(testFile, osf, 'utf8');
95+
96+
const result = execSync(`node "${CLI_PATH}" render "${testFile}"`, {
97+
encoding: 'utf8',
98+
});
99+
100+
expect(result).toContain('<s>$99</s>');
101+
expect(result).toContain('<strong>$79</strong>');
102+
});
103+
});
104+
105+
describe('HTML Security (XSS Prevention)', () => {
106+
it('should escape HTML in meta properties', () => {
107+
const osf = `@meta {
108+
title: "<script>alert('xss')</script>";
109+
author: "<img src=x onerror=alert(1)>";
110+
}
111+
112+
@doc { test }`;
113+
const testFile = join(TEST_FIXTURES_DIR, 'test_v11.osf');
114+
writeFileSync(testFile, osf, 'utf8');
115+
116+
const result = execSync(`node "${CLI_PATH}" render "${testFile}"`, {
117+
encoding: 'utf8',
118+
});
119+
120+
// Verify dangerous tags are escaped
121+
expect(result).not.toContain('<script>alert');
122+
expect(result).toContain('&lt;script&gt;');
123+
124+
// Verify event handlers are escaped (the = sign is escaped as part of attribute)
125+
expect(result).not.toContain('<img src=x onerror=alert(1)>');
126+
expect(result).toContain('&lt;img');
127+
});
128+
129+
it('should escape HTML in doc content', () => {
130+
const osf = `@doc {
131+
# Test
132+
<script>alert("xss")</script>
133+
}`;
134+
const testFile = join(TEST_FIXTURES_DIR, 'test_v11.osf');
135+
writeFileSync(testFile, osf, 'utf8');
136+
137+
const result = execSync(`node "${CLI_PATH}" render "${testFile}"`, {
138+
encoding: 'utf8',
139+
});
140+
141+
expect(result).not.toContain('<script>alert');
142+
expect(result).toContain('&lt;script&gt;');
143+
});
144+
145+
it('should escape HTML in slide content', () => {
146+
const osf = `@slide {
147+
title: "XSS Test <script>alert(1)</script>";
148+
- Item with <b>html</b>
149+
}`;
150+
const testFile = join(TEST_FIXTURES_DIR, 'test_v11.osf');
151+
writeFileSync(testFile, osf, 'utf8');
152+
153+
const result = execSync(`node "${CLI_PATH}" render "${testFile}"`, {
154+
encoding: 'utf8',
155+
});
156+
157+
expect(result).toContain('&lt;script&gt;');
158+
expect(result).toContain('&lt;b&gt;html&lt;/b&gt;');
159+
});
160+
});
161+
162+
describe('Enhanced Markdown Export', () => {
163+
it('should export ordered lists to Markdown', () => {
164+
const osf = `@slide {
165+
title: "Steps";
166+
1. First step
167+
2. Second step
168+
3. Third step
169+
}`;
170+
const testFile = join(TEST_FIXTURES_DIR, 'test_v11.osf');
171+
writeFileSync(testFile, osf, 'utf8');
172+
173+
const result = execSync(`node "${CLI_PATH}" export "${testFile}" --target md`, {
174+
encoding: 'utf8',
175+
});
176+
177+
expect(result).toContain('1. First step');
178+
expect(result).toContain('2. Second step');
179+
expect(result).toContain('3. Third step');
180+
});
181+
182+
it('should export blockquotes to Markdown', () => {
183+
const osf = `@slide {
184+
title: "Quote";
185+
> This is a quote
186+
> Multiple lines
187+
}`;
188+
const testFile = join(TEST_FIXTURES_DIR, 'test_v11.osf');
189+
writeFileSync(testFile, osf, 'utf8');
190+
191+
const result = execSync(`node "${CLI_PATH}" export "${testFile}" --target md`, {
192+
encoding: 'utf8',
193+
});
194+
195+
expect(result).toContain('> This is a quote');
196+
expect(result).toContain('> Multiple lines');
197+
});
198+
199+
it('should export code blocks to Markdown', () => {
200+
const osf = `@slide {
201+
title: "Code";
202+
\`\`\`python
203+
def hello():
204+
print("world")
205+
\`\`\`
206+
}`;
207+
const testFile = join(TEST_FIXTURES_DIR, 'test_v11.osf');
208+
writeFileSync(testFile, osf, 'utf8');
209+
210+
const result = execSync(`node "${CLI_PATH}" export "${testFile}" --target md`, {
211+
encoding: 'utf8',
212+
});
213+
214+
expect(result).toContain('```python');
215+
expect(result).toContain('def hello()');
216+
expect(result).toContain('```');
217+
});
218+
219+
it('should export strikethrough to Markdown', () => {
220+
const osf = `@slide {
221+
title: "Format";
222+
Text with **bold**, *italic*, __underline__, and ~~strike~~.
223+
}`;
224+
const testFile = join(TEST_FIXTURES_DIR, 'test_v11.osf');
225+
writeFileSync(testFile, osf, 'utf8');
226+
227+
const result = execSync(`node "${CLI_PATH}" export "${testFile}" --target md`, {
228+
encoding: 'utf8',
229+
});
230+
231+
expect(result).toContain('**bold**');
232+
expect(result).toContain('*italic*');
233+
expect(result).toContain('__underline__');
234+
expect(result).toContain('~~strike~~');
235+
});
236+
});
237+
238+
describe('Error Position Tracking', () => {
239+
it('should show line:column in parse errors', () => {
240+
const osf = `@meta {\n title: "Test";\n`;
241+
const testFile = join(TEST_FIXTURES_DIR, 'test_v11.osf');
242+
writeFileSync(testFile, osf, 'utf8');
243+
244+
try {
245+
execSync(`node "${CLI_PATH}" parse "${testFile}"`, { encoding: 'utf8' });
246+
expect.fail('Should have thrown error');
247+
} catch (err: any) {
248+
const output = (err.stderr || err.stdout) as string;
249+
expect(output).toMatch(/at \d+:\d+/);
250+
}
251+
});
252+
});
253+
});

0 commit comments

Comments
 (0)